符号如何解决在 x86 调试中使用 FPO 遍历堆栈?

How do symbols solve walking the stack with FPO in x86 debugging?

在这个答案中:,解释了在调试 x86 代码时,即使使用 FPO(帧指针省略),符号也允许调试器显示调用堆栈。

给出的解释是:

On the x86 PDBs contain FPO information, which allows the debugger to reliably unwind a call stack.

我的问题是这些信息是什么?据我了解,仅知道函数是否具有 FPO 并不能帮助您找到堆栈指针的原始值,因为这取决于运行时信息。

我在这里错过了什么?

从根本上说,总是可以用足够的信息遍历堆栈1,除非堆栈或执行上下文已被不可恢复地损坏。

例如,即使 rbp 没有被用作帧指针,return 地址仍然在堆栈的某个地方,你只需要知道它在哪里。对于在函数体中不修改 rsp(间接或直接)的函数,它将位于 rsp 的简单固定偏移处。对于在函数体中修改 rsp 的函数(即具有可变堆栈大小的函数),rsp 的偏移量可能取决于函数中的确切位置。

PDB 文件仅包含此 "side band" 信息,它允许某人确定函数中任何指令的 return 地址。 Hans 链接了一个相关的内存结构 above - 你可以看到,因为它知道局部变量的大小等等,所以它可以计算 rsp 和框架底部之间的偏移量,因此到达 return 地址。它还知道有多少指令字节是 "prolog" 的一部分,这很重要,因为如果 IP 仍在该区域中,则适用不同的规则(即,堆栈尚未调整以反映此函数中的局部变量) ).

在 64 位 Windows 中,确切的函数调用 ABI 已经变得更加具体,并且 所有 函数通常必须提供展开信息:不在a .pdb 但直接在二进制文件中包含的部分中。因此,即使没有 .pdb 文件,您也应该能够展开结构正确的 64 位 Windows 程序。它允许 any 寄存器用作帧指针,并且仍然允许省略帧指针(有一些限制)。详情请见start here.


1 如果这不是真的,问问自己当前的 运行 函数怎么会 return?现在,技术上你可以设计一个程序,它以一种它不能return的方式破坏或忘记堆栈,并且永远不会退出或使用像exit()这样的方法或 abort() 终止。这是非常不寻常的,不可能在装配之外。