【问题标题】:Printing backtrace within signal handler in a release/optimized binary on Linux在 Linux 上的发布/优化二进制文件中打印信号处理程序中的回溯
【发布时间】:2019-08-01 10:42:21
【问题描述】:

问题是关于在优化的二进制文件中以编程方式打印有意义的堆栈跟踪。 例如 我们可以使用backtracebacktrace_symbolsabi::__cxa_demangle 来打印堆栈跟踪。 但据我所知,我们需要使用编译器标志-g 构建二进制文件,而不是高于-O1 优化标志。我可以做到这一点。

我期待在发布二进制文件中生成具有正确函数名称的回溯,例如使用-O3 标志编译。

可行吗? 我对此做了很多研究,但没有得到任何实质性的东西。

更新 1: 有没有一种方法可以让我们拥有一个包含一些符号的辅助文件,并且可以参考该文件以从优化的二进制进程中生成堆栈跟踪?

【问题讨论】:

  • 只是翻拍。我认为使用-O3 编译可以省略帧指针,从而更难获得堆栈跟踪。 gcc 有一个标志。例如,-O3 -fno-omit-frame-pointer
  • @BiagioFesta 谢谢让我试试,对性能有影响吗?
  • 当然,它不是完全免费的。从技术上讲,省略帧指针使编译器有机会保存一个寄存器。 “-fomit-frame-pointer 选项指示编译器在函数不需要时不存储堆栈帧指针。您可以使用此选项来减小代码图像大小。”
  • 激进的优化可以做函数内联,这使得调用堆栈的一部分似乎丢失了。
  • 你想要达到的目标不兼容。想象一下基本上 any 模板代码,如<algorithm> header 的核心。有这么多函数只是为了调用另一个函数。最后,语句的操作可以非常简单,但很可能会扩展到 10 项的深度回溯。这就是-O3 通过积极内联所防止的。如果要保留调用链的每个环节,请使用-O2-Og

标签: c++ c stack-trace backtrace signal-handling


【解决方案1】:

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

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

有一些库/函数可以保证信号安全stack unwinding,以及demanglingformattingoutput,这使得这成为可能。

1 根据手册页,只要事先加载了共享 libgcc,使用 backtrace 应该 就可以了。 backtrace_symbols 有一个更安全的替代方案 backtrace_symbols_fd,它与 libgcc 有相同的警告。


有没有办法让我们拥有一个包含一些符号的辅助文件

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

GDB 支持外部符号文件,但我不知道是否/如何在程序中使用它们。我使用SymtabAPI 从二进制文件中挖掘符号;这也可能适用于外部符号文件。但据我所知,该库并不能保证信号安全。也就是说,尚不清楚为什么需要分离。调试符号不会影响性能。


只有当进程崩溃时我才会打印堆栈

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


就优化级别而言,无论您使用什么方法生成跟踪,您都可以尝试-O3 -fno-omit-frame-pointer 并希望获得最好的结果,但通常最好不要使用高于-O2 进行调试。 -Og 是理想的,但没有那么快。

【讨论】:

  • 感谢您花时间回答,我知道这一点。要求就像:只有当进程崩溃时,我才会打印堆栈。在这种情况下,我可以咬紧牙关,一旦我收到信号,我就会取消注册信号处理程序并继续打印堆栈跟踪以及其他一些诊断细节。我们可以使用 write 系统调用,但回溯是我必须使用的东西。因此,这不是 100% 正确的做法,但可以在没有任何伤害的情况下创造奇迹。 “快速”的要求也是无情的。它是一个非常低延迟的服务器。
  • @Yogesh 请注意,您不能真正安全地在信号处理程序中解开符号。 __cxa_demangle() 使用 malloc()-family 调用,而 SIGSEGV 来自 malloc()/free() et al 在堆损坏时太常见了。如果您尝试在信号处理程序中进行分解,您可能会导致进程死锁。请参阅stackoverflow.com/questions/23680297/… 以我的经验,backtrace() 没有那么危险。
  • @AndrewHenle 谢谢安德鲁,我知道这个事实。实际上,我浏览了您在 cmets 中发布的同一帖子。因此,即使我使用 eerorika 建议的库,我也不想与 O3 标志妥协。这是我的担心。
  • @eerorika 感谢您的周到,“一旦创建核心转储,生成回溯并写入一些日志” - 但该核心将没有必要的信息进行调试,因为二进制是编译的 O3 标志。虽然它是一个不错的选择,但实际上我不仅希望转储堆栈跟踪,而且在此过程中收集了太多其他信息 - 例如连接性、数据库相关、寄存器中的信息等。这里担心的是 O3 编译的二进制文件将无法承受核心转储中的信息。
  • @Yogesh but this core will not have the necessary information to debug, as the binary is an O3 flag compiled 无论你如何生成回溯,这个问题都存在,只能通过不使用 O3 来解决。在程序中生成跟踪并不能解决问题,反而会引入信号安全方面的新问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多