Looking at the disassembly, gdb is stopped at the first instruction of function foo
, before the function prologue (which sets up the stack and frame pointers) has been run:
(gdb) step
9 foo(a);
(gdb) step
foo (v=21845) at t.c:3
3 void foo(int v){
(gdb) disas
Dump of assembler code for function foo:
=> 0x0000555555555149 <+0>: endbr64
0x000055555555514d <+4>: push %rbp
0x000055555555514e <+5>: mov %rsp,%rbp
0x0000555555555151 <+8>: sub $0x10,%rsp
0x0000555555555155 <+12>: mov %edi,-0x4(%rbp)
0x0000555555555158 <+15>: mov -0x4(%rbp),%eax
0x000055555555515b <+18>: mov %eax,%esi
0x000055555555515d <+20>: lea 0xea0(%rip),%rdi # 0x555555556004
0x0000555555555164 <+27>: mov $0x0,%eax
0x0000555555555169 <+32>: callq 0x555555555050 <printf@plt>
0x000055555555516e <+37>: nop
0x000055555555516f <+38>: leaveq
0x0000555555555170 <+39>: retq
End of assembler dump.
Gdb's step command normally steps over a function's prologue, that is, it stops the program after the prologue has run. Here, gdb apparently doesn't recognize the instruction endbr64
as being part of any known prologue.
We can see that &v
is beyond the bounds of the current stack frame:
(gdb) p &v
$1 = (int *) 0x7fffffffe3fc
(gdb) i r rbp rsp
rbp 0x7fffffffe420
rsp 0x7fffffffe408
Since the new stack frame hasn't been set up yet, gdb will read a garbage value for v
.
Stepping a few more instructions will set up the stack frame and spill v
from %edi
to -0x4(%rbp)
:
(gdb) stepi
=> 0x000055555555514d <foo+4>: push %rbp
(gdb) stepi
=> 0x000055555555514e <foo+5>: mov %rsp,%rbp
(gdb) stepi
=> 0x0000555555555151 <foo+8>: sub $0x10,%rsp
(gdb) stepi
=> 0x0000555555555155 <foo+12>: mov %edi,-0x4(%rbp)
(gdb) stepi
4 printf(" BAR = %d
", v);
=> 0x0000555555555158 <foo+15>: mov -0x4(%rbp),%eax
Verify that &v
is now within the stack frame, and examine v
's value:
(gdb) p &v
$2 = (int *) 0x7fffffffe3fc
(gdb) i r rbp rsp
rbp 0x7fffffffe400
rsp 0x7fffffffe3f0
(gdb) p v
$3 = 8
Why did this happen
Gcc emits endbr64
when given the -fcf-protection
option, which has been the default in Ubuntu's gcc since version 19.10.
One workaround
If you compile your program with -fcf-protection=none
, gdb can recognize and run the prologue before stopping, and it will show the correct value of v
:
(gdb) step
9 foo(a);
(gdb) step
foo (v=8) at t.c:4
4 printf(" BAR = %d
", v);
(gdb) disas
Dump of assembler code for function foo:
0x0000555555555139 <+0>: push %rbp
0x000055555555513a <+1>: mov %rsp,%rbp
0x000055555555513d <+4>: sub $0x10,%rsp
0x0000555555555141 <+8>: mov %edi,-0x4(%rbp)
=> 0x0000555555555144 <+11>: mov -0x4(%rbp),%eax
0x0000555555555147 <+14>: mov %eax,%esi
0x0000555555555149 <+16>: lea 0xeb4(%rip),%rdi # 0x555555556004
0x0000555555555150 <+23>: mov $0x0,%eax
0x0000555555555155 <+28>: callq 0x555555555030 <printf@plt>
0x000055555555515a <+33>: nop
0x000055555555515b <+34>: leaveq
0x000055555555515c <+35>: retq
End of assembler dump.
Fixed in the new gdb
It looks like support for the endbr
instructions was added to gdb in March 2020, so things should be fine if you can use gdb 10.1 or later:
$ ~/gdb10.1/bin/gdb -q t
...
(gdb) step
9 foo(a);
(gdb) step
foo (v=8) at t.c:4
4 printf(" BAR = %d
", v);
(gdb) disas
Dump of assembler code for function foo:
0x0000555555555149 <+0>: endbr64
0x000055555555514d <+4>: push %rbp
0x000055555555514e <+5>: mov %rsp,%rbp
0x0000555555555151 <+8>: sub $0x10,%rsp
0x0000555555555155 <+12>: mov %edi,-0x4(%rbp)
=> 0x0000555555555158 <+15>: mov -0x4(%rbp),%eax
0x000055555555515b <+18>: mov %eax,%esi
0x000055555555515d <+20>: lea 0xea0(%rip),%rdi # 0x555555556004
0x0000555555555164 <+27>: mov $0x0,%eax
0x0000555555555169 <+32>: call 0x555555555050 <printf@plt>
0x000055555555516e <+37>: nop
0x000055555555516f <+38>: leave
0x0000555555555170 <+39>: ret
End of assembler dump.