Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GDB shows incorrect arguments of functions for stack frames

Whenever GDB steps in functions, instead of showing correct arguments in frame info, it prints garbage data

gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
$ gcc t.c -g #no other flags are used
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04) 9.2

The program (t.c):

#include<stdio.h>

void foo(int v){
    printf("  BAR = %d\n", v);
    }

int main(){
    int a = 8;
    foo(a);
    a = 33;
    foo(a);
    foo(85);
    }

GDB output:

Breakpoint 1, main () at t.c:7
7   int main(){
(gdb) step
8       int a = 8;
(gdb) step
9       foo(a);
(gdb) step
foo (v=21845) at t.c:3
3   void foo(int v){
(gdb) finish
Run till exit from #0  foo (v=21845) at t.c:3
  BAR = 8
main () at t.c:10
10      a = 33;
(gdb) s
11      foo(a);
(gdb) 
foo (v=8) at t.c:3
3   void foo(int v){
(gdb) fin
Run till exit from #0  foo (v=8) at t.c:3
  BAR = 33
main () at t.c:12
12      foo(85);
(gdb) s
foo (v=33) at t.c:3
3   void foo(int v){
(gdb) fin
Run till exit from #0  foo (v=33) at t.c:3
  BAR = 85
0x00005555555551a9 in main () at t.c:12
12      foo(85);
(gdb) s
13      }

But after performing a step after stepping in functions, arguments are written with correct data:

Breakpoint 1, main () at t.c:7
7   int main(){
(gdb) s
8       int a = 8;
(gdb) 
9       foo(a);
(gdb) 
foo (v=21845) at t.c:3
3   void foo(int v){
(gdb) info args
v = 21845
(gdb) s
4       printf("  BAR = %d\n", v);
(gdb) info args
v = 8

Is there any way to fix this so GDB show correct arguments of functions?

like image 371
ShyFly f Avatar asked Nov 05 '20 12:11

ShyFly f


People also ask

What GDB command verifies and displays the stack frames?

Selects a stack frame or displays the currently selected stack frame.

What are the three commands used in GDB to examine and move within the stack?

Able to view and traverse the function call stack using the where, up, down and frame commands.

What is stack in GDB?

The call stack is divided up into contiguous pieces called stack frames , or frames for short; each frame is the data associated with one call to one function. The frame contains the arguments given to the function, the function's local variables, and the address at which the function is executing.

How to navigate the call stack in gdb?

A common example of when we would need to navigate the call stack within GDB is if execution stops within library code. We can use, the where, up and frame commands to inspect variables within code that we wrote. It is assumed that you have the knowledge introduced in the Basic Use, Breakpoints, Viewing Data and Execution modules.

Where do function arguments go in a stack frame?

Function arguments - if present and not in registers - will appear in the previous stack frame, above the return address. So our picture of the frame extends from the argument with the highest address, through the saved return address and rbp, to the current rsp.

How do I set print frame arguments with visualgdb?

Do not use the set print frame-arguments command with VisualGDB. Instead right-click on the Call Stack pane in Visual Studio and select the information displayed about each frame via a context menu.

How do you debug a function in gdb?

In order to debug programs with functions (i.e. most programs), it is helpful to inspect the variables of all functions in the current call stack, i.e. the functions called to get to the current point in the program. GDB can only directly inspect local variables of the current function.


1 Answers

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\n", 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\n", 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\n", 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.
like image 137
Mark Plotnick Avatar answered Oct 21 '22 15:10

Mark Plotnick