如何在 C 语言中手动迭代堆栈帧?

4
在应用程序中处理信号时,我可以在调试器中正确地看到回溯信息。但是,回溯系统调用未能正确显示堆栈帧。gdb 存储堆栈帧的方式与回溯系统调用转储它们的方式是否有区别?

我正在加载一个共享对象,它会覆盖/安装自己的信号处理程序,并从中链接我的信号处理器。这个共享对象是专有的,我无法访问其代码。当没有加载共享对象时,backtrace()函数返回了正确的14个帧数量。但是当共享对象的信号处理程序被调用时,它会干净地删除信号处理程序下面的8个帧。我只是尝试找出打印正确信息的方法。 - karan
很可能这个专有的共享对象是使用优化编译的,而且没有帧指针。所以你很不幸,因为你无法对此做任何事情(除了避免使用专有软件,只使用自由软件)。 - Basile Starynkevitch
请编辑您的问题以改进它。同时,请解释为什么要对堆栈帧进行迭代... - Basile Starynkevitch
3个回答

6

您无法在C99C11中便携地迭代堆栈帧。

首先,因为C标准中没有任何调用堆栈的保证。(可以想象一些C编译器进行整个程序分析并避免使用堆栈,例如如果递归不可能发生;我不知道有这样的C编译器)。请参见这个C FAQ问题以获取奇怪的C实现。

因为编译器有时可能会执行一些优化,例如内联某些调用(甚至是未标记inline的函数,特别是在使用-flto传递给gcc以请求链接时优化时),或者有时会发出尾调用(而GCC两者都可以执行)。您可以禁用优化,但这样会损失很多性能。优化编译器将仅将某些变量放入寄存器中,并重复使用某些堆栈槽来存储多个变量。

最后,在某些体系结构上(例如32位x86),一些代码(特别是一些库代码,例如libc内部)可能会使用-fomit-frame-pointer编译,那么没有它就无法获取帧信息。
你可以在GCC中使用Ian Taylorlibbacktrace,也可以使用GNU glibc中的backtrace(3)函数;当使用GCC编译时,还可以使用可用的return address builtins。但是所有这些工具可能无法在优化代码上工作。
实际上,如果你真的需要一些回溯信息,要么自己实现(我在我的过时的MELT系统中这样做了,它的C++代码是通过将本地变量打包到某个本地struct中生成的),要么避免过度优化,例如只使用gcc -g -O1进行编译。
请注意,backtrace不是一个系统调用(列在syscalls(2)中),而是一个glibc特定的库函数。

请非常仔细地阅读signal(7)sigreturn(2)。有很少量的(异步信号安全)函数可以可靠地从信号处理程序中调用(直接或间接),backtrace(或printf)不在其中。实际上,一个便携式的信号处理程序通常只需要设置一些volatile sigatomic_t标志,您应该在其他地方测试-或调用siglongjmp(3)


0

调试器使用由gcc编译器在使用-g选项时放入二进制文件中的一组额外数据。这些数据不被回溯调用所使用,只有基本的链接器信息被使用。这意味着例如任何静态数据都无法通过回溯查看,但可以通过gdb查看,这也导致各种优化破坏了回溯,而gdb通过明确的知识来解决这个问题。

请记住,gdb是特定于某种语言和编译器的,而回溯则更具可移植性。

请参阅backtrace的man页面http://linux.die.net/man/3/backtrace

省略帧指针(如gcc(1)的任何非零优化级别所暗示的那样)可能会导致这些假设被违反。

如果回溯调用想要使用此信息,则必须强制您始终使用调试符号进行编译,并且会有更大的开销和许多其他问题。


0
可能的问题是,在执行回溯时,您的堆栈可能已经被严重破坏。
只需在函数中获取本地变量的地址。计算堆栈大小。将其从本地变量地址中添加并打印这些内容,位于加法和位置变量地址之间。
您就可以打印出堆栈啦 :)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接