2
votes

When use gdb to debug assembly program, bt will print the calling stack.

The questions are:

  • (a) Does gdb know about that according to rbp values stored in register for current function, and in stack for previous rbp values?
  • If yes, (b-1) how gdb know which function it is according to a rbp value? (b-2) Is the mapping between stack base & function stored in executable file when -g option is specified on compiling? (b-3) And how to read that mapping data, with readelf? Which part?
  • If no, (c) then how gdb track the function calling stack?
1
rbp values are stack address, and don't have a 1:1 mapping with function code addresses/names. You have to look at return addresses for that. Ross's answer explains how to find return addresses on the stack while backtracing.Peter Cordes
@PeterCordes It's the rip values that help to track function calling stacks with the help of rbp I guess. Yes Ross's answer is great, I think unwind info is actually used when the executable don't follow the rbp/rsp convention.user218867
Yes, it is the saved return addresses (RIP values) that a backtrace needs to find. You're right that in the old-style push rbp stack-frame-making convention where the saved rbp values form a linked list, the return addresses are at a known position within each frame. (It's not just a metaphor, BTW; it literally is a linked list. The RIP values are just data in each node, not a linked list of their own. Only the rbp values point to the next node.) Also, I should have said EIP/EBP value, because I think the .eh_frame data is used by default for 64bit.Peter Cordes

1 Answers

9
votes

Debuggers like GDB have two primary means of walking the stack in order to print a backtrace. They either assume the value in the frame pointer register (RBP) is a pointer to start of a linked list of stack frames, or they use special unwind info stored in the executable that describes how to walk (unwind) the stack.

Using the frame pointer

When using the frame pointer, the assumption is that it points to where the current function saved the value of its caller's frame pointer. It also assumes that just before that saved frame pointer is where the return address for the current function is stored. So that's how it knows both what the RBP value of the calling function was, and what function called the current function, which it can easily determine from the return address. It can then find all the previous stack frames and functions on the stack by walking the linked RBP values.

However, this assumes that functions use the frame pointer this way, and generally there's no guarantee that they will. Basically it's assuming that the function prologue and epilogue looks something like this:

func:
    push %rbp         # save previous frame pointer
    mov  %rsp, %rbp   # new frame pointer points to previous value
    sub  $24, %rsp    # allocate stack space for this funciton

    ...

    pop %rbp          # restore previous frame pointer
    ret

But when optimizing many compilers won't do this, because they rarely need to use a frame pointer and instead will treat RBP like any other general purpose register and use it for something else.

Using unwind info

So to generate a backtrace across functions that don't use RBP as a frame pointer, a debugger can potentially use unwind information. This is special data stored in executables (and dynamic libraries) that describes exactly how to virtually undo all the stack operations performed by a function at any point in the execution of that function. The format and location of the unwind info varies based on the executable format and CPU type. For ELF x86-64 executables the unwind info is stored in the .eh_frame section in a format based on the DWARF debugging format's unwind info. This format is too complex to describe here, but you can read the System V AMD64 ABI for more details.