在Linux上的发布/优化二进制文件中,在信号处理程序中打印回溯信息

4
这个问题是关于在优化二进制文件中通过程序化的方式打印有意义的堆栈跟踪。 例如: 我们可以使用backtracebacktrace_symbolsabi::__cxa_demangle来打印堆栈跟踪。 但据我所知,我们需要使用编译器标志-g构建二进制文件,而不是高于-O1的优化标志。我可以做到这一点。
我希望能够在发布二进制文件(例如使用-O3标志进行编译)中生成具有正确函数名称的回溯。 这可行吗? 我对此进行了大量研究,但没有得到实质性的东西。
更新1: 是否有一种方法可以有一个包含某些符号的辅助文件,可以从优化二进制进程内部引用以生成堆栈跟踪?

2
只是一个重新制作。我认为使用-O3编译可以省略帧指针,使得获取堆栈跟踪更加困难。gcc有一个标志可以解决这个问题。例如,-O3 -fno-omit-frame-pointer - BiagioF
@BiagioFesta 谢谢,让我试试,这会对性能有影响吗? - Yogesh
当然,它并不是完全免费的。从技术上讲,省略帧指针为编译器提供了保存一个寄存器的机会。 "-fomit-frame-pointer"选项指示编译器在函数不需要时不要存储堆栈帧指针。您可以使用此选项来减小代码映像大小。 - BiagioF
1
激进的优化可能会进行函数内联,这会使部分调用栈似乎丢失。 - Some programmer dude
1
你想要实现的目标是不兼容的。想象一下基本上任何模板代码,比如 <algorithm> header 的核心部分。有这么多函数只是为了调用另一个函数。最终语句的操作可能非常简单,但可能会扩展到10项深度的回溯。这就是 -O3 通过积极内联来防止的。如果您想保留每个调用链的链接,请使用 -O2-Og - The Vee
显示剩余7条评论
1个回答

8

在信号处理程序中打印回溯信息

无论优化级别如何,在信号处理程序中调用 backtrace1, backtrace_symbols1, 或者 abi::__cxa_demangle 都不是安全的。它们不是异步安全函数,如果在信号处理程序中使用可能会导致程序崩溃、内存损坏或冻结。关于打印,如果您计划使用任何 printf 函数族,则需知道它们在信号处理程序中也不安全(至少 POSIX 指定的所有函数都是如此)。

有些库/函数承诺提供信号安全的栈展开符号重组格式化输出,从而实现这一目标。

1 根据 man 手册,只要加载了共享的 libgcc,使用 backtrace 理应是安全的。 backtrace_symbols 有一个更安全的替代品 backtrace_symbols_fd,它在 libgcc 方面有相同的注意点。


是否可以有一个包含一些符号的次要文件?

您可以使用 objcopy 将调试符号从可执行文件中复制出来并使用 strip 从可执行文件中删除符号。

GDB 支持外部符号文件,但我不知道 / 如何从程序内部使用它们。我使用过 SymtabAPI 从二进制文件中提取符号;这可能也适用于外部符号文件。但据我所知,该库不能承诺信号安全性。话虽如此,不清楚为什么需要分离;调试符号不影响性能。


如果进程崩溃,我只打算打印栈信息

在这种情况下,可能更好的方法是让操作系统生成一个核心转储文件,并有一个单独的进程监听文件系统事件,一旦创建了核心转储,则会生成回溯并写入某个日志。无需担心信号安全性,无需延迟生成跟踪信息时原始进程的重启,也无需对服务器进程进行额外的依赖。


在优化级别方面,无论您使用什么方法生成跟踪,都可以尝试使用-O3 -fno-omit-frame-pointer进行优化,并期望最佳效果,但通常最好不要在调试时使用高于-O2的级别。 -Og是理想的选择,但速度较慢。


感谢您抽出时间回答,我已经了解了这一点。要求如下:只有在进程崩溃时才打印堆栈。在这种情况下,我可以咬紧牙关,一旦收到信号,我就取消注册信号处理程序并继续打印堆栈跟踪和其他诊断详细信息。我们可以使用write系统调用,但是backtrace是我必须使用的东西。因此,这样做不是100%正确的,但可以在没有任何伤害的情况下产生奇迹。 同时,“快速”要求是不可避免的。这是一个非常低延迟的服务器。 - Yogesh
@Yogesh 注意,在信号处理程序中无法安全地解码符号。__cxa_demangle()使用malloc()系列调用,如果堆被破坏,malloc()/free()等函数中的SIGSEGV异常非常常见。如果您尝试在信号处理程序中解码,可能会导致进程死锁。请参阅https://dev59.com/s3_aa4cB1Zd3GeqP67mG?noredirect=1&lq=1。根据我的经验,`backtrace()`不是那么危险。 - Andrew Henle
@AndrewHenle 谢谢你,安德鲁。我已经知道这个事实了。实际上,我已经阅读了你在评论中发布的相同帖子。因此,即使我使用eerorika建议的库,我也不想妥协O3标志。这是我的担忧。 - Yogesh
@eerorika 谢谢你的关心,“一旦创建了核心转储,请生成回溯并写入某些日志”-但是这个核心不会有足够的信息进行调试,因为二进制文件是使用O3标志编译的。尽管它是一个不错的选择,但实际上我不仅想转储堆栈跟踪,还要在此过程中收集太多其他信息-例如连接性,与数据库相关的信息,在寄存器中的信息等等。问题在于O3编译的二进制文件在核心转储中不会包含足够的信息。 - Yogesh
1
@Yogesh,“但是这个核心将没有足够的信息进行调试,因为二进制文件是使用O3标志编译的。”无论如何生成回溯,这个问题都存在,并且只能通过不使用O3来解决。在程序内生成跟踪并不能解决问题,反而引入了新的信号安全问题。 - eerorika
显示剩余4条评论

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