GDB - 它如何知道函数调用堆栈?
GDB - How does it know about function calling stack?
使用gdb调试汇编程序时,bt
会打印调用堆栈。
问题是:
- (a) gdb 是否根据
rbp
存储在当前函数的寄存器中的值以及存储在堆栈中的先前 rbp 值的值知道这一点?
- 如果是,(b-1) gdb 如何根据
rbp
值知道它是哪个函数? (b-2)编译时指定-g
选项时,栈基和函数之间的映射是否存储在可执行文件中? (b-3) 如何使用 readelf
读取映射数据?哪一部分?
- 如果没有,(c) 那么 gdb 如何跟踪函数调用堆栈?
像 GDB 这样的调试器有两种主要的遍历堆栈的方法来打印回溯。他们要么假定帧指针寄存器 (RBP) 中的值是指向堆栈帧链表开始的指针,要么使用存储在可执行文件中的特殊展开信息来描述如何遍历(展开)堆栈。
使用帧指针
使用帧指针时,假定它指向当前函数保存其调用者帧指针值的位置。它还假定就在保存的帧指针之前是存储当前函数的 return 地址的位置。这就是它如何知道调用函数的 RBP 值是什么,以及调用当前函数的函数,它可以很容易地从 return 地址确定。然后它可以通过遍历链接的 RBP 值找到堆栈中所有先前的堆栈帧和函数。
但是,这假定函数以这种方式使用帧指针,并且通常不能保证它们会这样做。基本上它假设函数序言和结尾看起来像这样:
func:
push %rbp # save previous frame pointer
mov %rsp, %rbp # new frame pointer points to previous value
sub , %rsp # allocate stack space for this funciton
...
pop %rbp # restore previous frame pointer
ret
但是在优化时,许多编译器不会这样做,因为它们很少需要使用帧指针,而是会像对待任何其他通用寄存器一样对待 RBP,并将其用于其他用途。
使用展开信息
因此,为了在不使用 RBP 作为帧指针的函数之间生成回溯,调试器可能会使用展开信息。这是存储在可执行文件(和动态库)中的特殊数据,它准确地描述了如何在函数执行过程中的任何时间点虚拟撤消函数执行的所有堆栈操作。展开信息的格式和位置因可执行文件格式和 CPU 类型而异。对于 ELF x86-64 可执行文件,展开信息以基于 DWARF debugging format's unwind info. This format is too complex to describe here, but you can read the System V AMD64 ABI 的格式存储在 .eh_frame
部分以获取更多详细信息。
使用gdb调试汇编程序时,bt
会打印调用堆栈。
问题是:
- (a) gdb 是否根据
rbp
存储在当前函数的寄存器中的值以及存储在堆栈中的先前 rbp 值的值知道这一点? - 如果是,(b-1) gdb 如何根据
rbp
值知道它是哪个函数? (b-2)编译时指定-g
选项时,栈基和函数之间的映射是否存储在可执行文件中? (b-3) 如何使用readelf
读取映射数据?哪一部分? - 如果没有,(c) 那么 gdb 如何跟踪函数调用堆栈?
像 GDB 这样的调试器有两种主要的遍历堆栈的方法来打印回溯。他们要么假定帧指针寄存器 (RBP) 中的值是指向堆栈帧链表开始的指针,要么使用存储在可执行文件中的特殊展开信息来描述如何遍历(展开)堆栈。
使用帧指针
使用帧指针时,假定它指向当前函数保存其调用者帧指针值的位置。它还假定就在保存的帧指针之前是存储当前函数的 return 地址的位置。这就是它如何知道调用函数的 RBP 值是什么,以及调用当前函数的函数,它可以很容易地从 return 地址确定。然后它可以通过遍历链接的 RBP 值找到堆栈中所有先前的堆栈帧和函数。
但是,这假定函数以这种方式使用帧指针,并且通常不能保证它们会这样做。基本上它假设函数序言和结尾看起来像这样:
func:
push %rbp # save previous frame pointer
mov %rsp, %rbp # new frame pointer points to previous value
sub , %rsp # allocate stack space for this funciton
...
pop %rbp # restore previous frame pointer
ret
但是在优化时,许多编译器不会这样做,因为它们很少需要使用帧指针,而是会像对待任何其他通用寄存器一样对待 RBP,并将其用于其他用途。
使用展开信息
因此,为了在不使用 RBP 作为帧指针的函数之间生成回溯,调试器可能会使用展开信息。这是存储在可执行文件(和动态库)中的特殊数据,它准确地描述了如何在函数执行过程中的任何时间点虚拟撤消函数执行的所有堆栈操作。展开信息的格式和位置因可执行文件格式和 CPU 类型而异。对于 ELF x86-64 可执行文件,展开信息以基于 DWARF debugging format's unwind info. This format is too complex to describe here, but you can read the System V AMD64 ABI 的格式存储在 .eh_frame
部分以获取更多详细信息。