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.
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 Cordesrip
values that help to track function calling stacks with the help ofrbp
I guess. Yes Ross's answer is great, I thinkunwind
info is actually used when the executable don't follow the rbp/rsp convention. – user218867RIP
values) that a backtrace needs to find. You're right that in the old-stylepush rbp
stack-frame-making convention where the savedrbp
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. TheRIP
values are just data in each node, not a linked list of their own. Only therbp
values point to the next node.) Also, I should have saidEIP
/EBP
value, because I think the.eh_frame
data is used by default for 64bit. – Peter Cordes