I want to look at the problem in the previous blog post about using gdb to figure out a segfault in a bit more detail.
As a refresher this is what gdb showed us as the problem:
Program received signal SIGSEGV, Segmentation fault.
check_send_msg (plx=plx@entry=111, a=a@entry=-13872, t=t@entry=0x7fffffffd1f0 "The Thief has gone west.\n") at sendsys.c:172
172 if (test_rcv(plx,b->mode,b->min,b->max,b->x1,b->x2)) return t;
(gdb)I said that the address didn’t look right, and that’s probably one of the most frustrating things about learning this kind of debugging.
I have sat down many times with experienced engineers to work through crash dumps, and at some point they inevitably say that “something looks wrong.” That comes from experience, and it’s difficult to explain to someone unfamiliar with the code why it looks wrong.
So here’s how we can dig down and see exactly what the problem was caused by.
This is also often where you find yourself looking up assembly instructions and registers. Unless you’re doing this regularly, you forget it, the goal is to have some idea of what to look for, rather than retaining detailed knowledge of x86 assembly.
Looking at the instruction that caused the segfault
On x64, we can look at the instruction pointer to see exactly what line of assembly the CPU is currently executing:
(gdb) x/i $rip
=> 0x426e72 : cmp %edi,0xc(%rsi)
(gdb)This is comparing %edi with 0xc(%rsi), specifically, the value in the register %edi with the value at the memory address stored in %rsi + 12 (0xc in hex).
This corresponds to part of the C call
test_rcv(plx,b->mode,b->min,b->max,b->x1,b->x2)A quick note on registers
On x64, when we call a function, arguments are passed in registers:
| Argument | Register |
|---|---|
| 1st | rdi |
| 2nd | rsi |
| 3rd | rdx |
| 4th | rcx |
You can dump all registers with:
(gdb) info registers
...
rsi 0xffffffffffffc9d0 -13872
rdi 0x6f 111
...Register sizes
On a 64-bit system, rdi is a 64-bit register. The instruction above uses edi, which is the lower 32 bits.
In this case, they contain the same value:
(gdb) print $rdi
$5 = 111
(gdb) print $edi
$6 = 111GDB already told us that the first argument (plx) is 111, so this lines up.
Mapping back to the C code
The second argument is a:
(gdb) list
167
168 char *check_send_msg(int plx, int a, char *t)
169 {
170 struct _send_msg_box *b = (struct _send_msg_box *)a;
171
172 if (test_rcv(plx,b->mode,b->min,b->max,b->x1,b->x2)) return t;
173 return NULL;
174 }The assembly is comparing that 111 with the contents of the address at rsi + 12.
Why +12?
Let’s look at the struct:
(gdb) ptype struct _send_msg_box
type = struct _send_msg_box {
int mode;
int min;
int max;
int x1;
int x2;
}It’s just five integers:
mode→ offset 0min→ offset 4max→ offset 8x1→ offset 12x2→ offset 16
So rsi + 12 corresponds to b->x1.
The compiler has generated assembly that accesses that field first.
The real problem
However, we don’t actually need to go that far to find the issue.
Just look at rsi:
gdb) print $rsi
$9 = -13872
(gdb) print/x $rsi
$10 = 0xffffffffffffc9d0
(gdb) x/x $rsi
0xffffffffffffc9d0: Cannot access memory at address 0xffffffffffffc9d0
We know this is supposed to be a memory address, when we try to examine it we get told we can’t access it.
At that point, we can go back through the code and see how that value ended up there.
Conclusion
Do you need to know all of this? Not really.
With practice, you do get a feel for what looks wrong. In reality, you’d probably only need to go as far as I did in the last blog post where we got the ‘Cannot access memory at address’ message. But it’s handy to know how to poke around a bit more.
0 Comments