GDB - 它如何知道函数调用堆栈?

时间:2016-07-02 08:13:52

标签: assembly gdb stack-trace backtrace

使用gdb调试汇编程序时,bt将打印调用堆栈。

问题是:

  • (a) gdb根据存储在当前函数的寄存器中的rbp值以及先前rbp值的堆栈中是否知道这些值?
  • 如果是,(b-1) gdb如何根据rbp值知道它是哪个函数? (b-2)堆栈基础与放大器之间的映射。在编译时指定-g选项时存储在可执行文件中的函数? (b-3)以及如何使用readelf读取该映射数据?哪一部分?
  • 如果没有,(c)那么gdb如何跟踪函数调用堆栈?

1 个答案:

答案 0 :(得分:7)

像GDB这样的调试器有两种主要的方法来遍历堆栈以打印回溯。它们或者假设帧指针寄存器(RBP)中的值是指向堆栈帧链接列表的开始的指针,或者它们使用存储在可执行文件中的特殊展开信息来描述如何遍历(展开)堆栈。

使用帧指针

使用帧指针时,假设它指向当前函数保存其调用者帧指针值的位置。它还假设在保存的帧指针之前就是存储当前函数的返回地址的位置。这就是它如何知道调用函数的RBP值是什么,以及什么函数称为当前函数,它可以从返回地址轻松确定。然后,它可以通过遍历链接的RBP值找到堆栈中的所有先前堆栈帧和函数。

然而,这假设函数以这种方式使用帧指针,并且通常不能保证它们会这样。基本上它假设函数序言和结语看起来像这样:

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

但是当优化许多编译器时不会这样做,因为他们很少需要使用帧指针,而是将RBP视为任何其他通用寄存器,并将其用于其他内容。

使用展开信息

因此,要在不使用RBP作为帧指针的函数之间生成回溯,调试器可能会使用展开信息。这是存储在可执行文件(和动态库)中的特殊数据,它精确地描述了如何在执行该函数的过程中虚拟地撤消函数执行的所有堆栈操作。展开信息的格式和位置因可执行格式和CPU类型而异。对于ELF x86-64可执行文件,展开信息以基于DWARF调试格式的展开信息的格式存储在.eh_frame部分中。此格式过于复杂,无法在此处进行描述,但您可以阅读System V AMD64 ABI了解更多详情。