Trap - Back Up and Running
It’s been a long time and I’m finally coming back to this project and need to get things building again. I tried compiling the project and ran into the following errors:
-- Configuring done
-- Generating done
-- Build files have been written to: /home/jkain/projects/trap/_out
Scanning dependencies of target trap
[ 4%] Building C object src/CMakeFiles/trap.dir/inferior_load.c.o
/home/jkain/projects/trap/src/inferior_load.c: In function ‘attach_to_inferior’:
/home/jkain/projects/trap/src/inferior_load.c:24:49: error: ‘SIGTRAP’ undeclared (first use in this function)
24 | if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) {
| ^~~~~~~
/home/jkain/projects/trap/src/inferior_load.c:24:49: note: each undeclared identifier is reported only once for each function it appears in
/home/jkain/projects/trap/src/inferior_load.c: In function ‘trap_inferior_continue’:
/home/jkain/projects/trap/src/inferior_load.c:71:51: error: ‘SIGTRAP’ undeclared (first use in this function)
71 | if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) {
| ^~~~~~~
make[2]: *** [src/CMakeFiles/trap.dir/build.make:63: src/CMakeFiles/trap.dir/inferior_load.c.o] Error 1
make[1]: *** [CMakeFiles/Makefile2:149: src/CMakeFiles/trap.dir/all] Error 2
make: *** [Makefile:95: all] Error 2
SIGTRAP
isn’t defined? I guess I need to include signal.h but I don’t understand why I didn’t need this before.
modified src/inferior_load.c
@@ -5,6 +5,7 @@
#include <sys/wait.h>
#include <assert.h>
#include <errno.h>
+#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
Now it builds, great! But the tests fail:
Test project /home/jkain/projects/trap/_out
Start 1: test_exec
1/7 Test #1: test_exec ............................ Passed 0.00 sec
Start 2: test_breakpoint
2/7 Test #2: test_breakpoint ......................Child aborted***Exception: 0.01 sec
PTRACE_POKETEXT: : Input/output error
Hello World!
Start 3: test_multi_breakpoint
3/7 Test #3: test_multi_breakpoint ................Child aborted***Exception: 0.01 sec
PTRACE_POKETEXT: : Input/output error
Start 4: test_multiple_breakpoints
4/7 Test #4: test_multiple_breakpoints ............Child aborted***Exception: 0.01 sec
PTRACE_POKETEXT: : Input/output error
Start 5: test_delete_breakpoint
5/7 Test #5: test_delete_breakpoint ...............Child aborted***Exception: 0.01 sec
PTRACE_POKETEXT: : Input/output error
Start 6: test_delete_breakpoint_in_callback
6/7 Test #6: test_delete_breakpoint_in_callback ...Child aborted***Exception: 0.01 sec
PTRACE_POKETEXT: : Input/output error
Start 7: test_duplicate_breakpoint
7/7 Test #7: test_duplicate_breakpoint ............Child aborted***Exception: 0.01 sec
PTRACE_POKETEXT: : Input/output error
14% tests passed, 6 tests failed out of 7
Total Test time (real) = 0.06 sec
The following tests FAILED:
2 - test_breakpoint (Child aborted)
3 - test_multi_breakpoint (Child aborted)
4 - test_multiple_breakpoints (Child aborted)
5 - test_delete_breakpoint (Child aborted)
6 - test_delete_breakpoint_in_callback (Child aborted)
7 - test_duplicate_breakpoint (Child aborted)
Errors while running CTest
Oh, this is because the address for the breakpoints has changed because it’s been 10 years and I’m using a different system, compiler, etc. now. This is a great reminder that we should implement symbol look up so that the breakpoints aren’t just hard coded addresses, but let’s get the basics working again before we take that on.
Fixing the offsets isn’t enough to fix this. There is probably address space randomization like we saw with Rust. I’ve implemented personality
calls to disable address randomization.
@@ -12,6 +14,11 @@ static inferior_t g_inferior;
static void setup_inferior(const char *path, char *const argv[])
{
+ unsigned long old = personality(0xFFFFFFFF);
+ if (personality(old | ADDR_NO_RANDOMIZE) < 0) {
+ perror("Failed to set personality:");
+ }
+
ptrace_util_traceme();
execv(path, argv);
}
But this still doesn’t work. So, what is the address of main?
From gdb main = 0x555555555169
. And it is always this value, it’s not random. I am running on WSL, maybe that’s weird. Also does this binary have a start address set?
- Yes, it does. The start address is 0x0000000000001080.
Which coresponds with the normal offset of .text
Sections:
Idx Name Size VMA LMA File off Algn
15 .text 00000195 0000000000001080 0000000000001080 00001080 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
Ok, so what’s going on? Maybe another look at gdb can help:
$ gdb inferiors/hello
(No debugging symbols found in inferiors/hello)
(gdb) b main
Breakpoint 1 at 0x1169
(gdb)
So gdb gets the basic address (note this is different than the 0x1149 we saw before because I added a printf call and string.).
Hang on, let’s run under gdb:
(gdb) r
Starting program: /home/jkain/projects/trap/_out/test/inferiors/hello
Breakpoint 1, 0x0000555555555169 in main ()
(gdb)
Ok, so object is being loaded into a different address as it runs. And at the start gdb doesn’t know where it will be loaded (just like trap).
For now, I can just hard code this address, I guess. I’ve removed the printout from the inferior and use 0x555555555149 as the breakpoint address.
Now that I think about it, I saw the same thing in Rust when I last worked on it. This makes sense in that the binary wouldn’t necessarily get loaded at address 0. If I understand correctly, there are non-relocatable types of binaries and that must have been what I was building before and the newer tool chain allows for relocation. But, that’s just speculation on my part.
Even with these new addresses the I see an assertion failure in breakpoint_resolve
. I added a printout in breakpoint_resolve
to print the instruction pointer. I see two calls:
breakpoint_resolve: ip = 0x555555555149
breakpoint_resolve: ip = 0x55555555514c
test_breakpoint: /home/jkain/projects/trap/src/breakpoint.c:102: breakpoint_resolve: Assertion `result' failed.
The first call to breakpoint_resolve
must be when the breakpoint in main is reached. And the second call is 3 bytes later. I think this is trying to resolve the stop in execution that happens when we are in state INFERIOR_SINGLE_STEPPING
to step over the breakpoint. And in this case the instruction pointer will have moved past the byte where we inserted the breakpoint – past the entire original instruction. So breakpoint_resolve
should fail as this address isn’t one of our breakpoints. That is, breakpoint_resolve
assumes we executed a one byte instruction and subtracts 1 from the instruction pointer to find the target_address
.
This is not correct and wasn’t correct when I originally wrote it. I don’t know how this worked before.
Is this something I never wrote about (and presumably didn’t test properly)?
- It looks like this was added in the multiple-breakpoints post.
I think I need to back up to the previous post and, make these breakpoint address tweaks, and then retrace my steps through multiple-breakpoints. I also should get in the habit of announcing which commit I’m starting with in each post and have a PR at the end of each post.
I’ll take this work on in a separate post. For now, I’ve pushed the fixes as a PR and we should get started on getting the Rust version working again.
But, before, I go I have one question for you. At some point in the future I plan to port trap and rusty_trap to ARM. Do you have a favorite ARM board? I have a few: an old Raspberry Pi 2, an odroid XU4, and an Orange Pi Zero 3. I’m thinking about getting an Orange Pi 5. Let me know your thoughts in the comments.