【问题标题】:How to get more detailed backtrace [duplicate]如何获得更详细的回溯[重复]
【发布时间】:2011-08-22 04:37:40
【问题描述】:

当我的 C++ 程序终止时,我正在尝试打印回溯。函数打印回溯如下;

   void print_backtrace(void){

       void *tracePtrs[10];
       size_t count;

       count = backtrace(tracePtrs, 10);

       char** funcNames = backtrace_symbols(tracePtrs, count);

       for (int i = 0; i < count; i++)
           syslog(LOG_INFO,"%s\n", funcNames[i]);

       free(funcNames);

}

它给出的输出类似于 ;

   desktop program: Received SIGSEGV signal, last error is : Success
   desktop program: ./program() [0x422225]
   desktop program: ./program() [0x422371]
   desktop program: /lib/libc.so.6(+0x33af0) [0x7f0710f75af0]
   desktop program: /lib/libc.so.6(+0x12a08e) [0x7f071106c08e]
   desktop program: ./program() [0x428895]
   desktop program: /lib/libc.so.6(__libc_start_main+0xfd) [0x7f0710f60c4d]
   desktop program: ./program() [0x4082c9]

有没有办法通过函数名称和行获得更详细的回溯,例如 gdb 输出?

【问题讨论】:

  • 你安装了调试libc吗?如果您将命令行上的 -g 传递给 GCC,IIRC Linux 将为此使用带有调试符号的 libc。
  • 为什么不使用gdb,请问? Backtraces section of the GNU libc manual 看起来也很有用。

标签: c linux


【解决方案1】:

是 - 将 -rdynamic 标志传递给链接器。它将导致链接器将代码中所有非静态函数的名称放入链接表中,而不仅仅是导出的函数。

您付出的代价是程序的启动时间会稍长一些。对于中小型程序,您不会注意到它。您得到的是 backtrace() 能够为您提供回溯中所有非静态函数的名称。

但是 - 请注意:您需要注意几个问题:

  1. backtrace_symbols 从 malloc 分配内存。如果您由于 malloc arena 损坏(很常见)而进入 SIGSEGV,您将在这里出现双重错误并且永远看不到您的回溯。

  2. 根据运行平台的不同(例如 x86),您崩溃的确切函数的地址/函数名称将在堆栈上替换为信号处理程序的返回地址。您需要从这些平台的信号处理程序参数中获取崩溃函数的正确 EIP。

  3. syslog 不是异步信号安全功能。它可能会在内部使用锁,如果在发生崩溃时使用了该锁(因为您在另一个 syslog 调用过程中崩溃了),那么您就有了死锁

如果你想了解所有血淋淋的细节,请观看我在 OLS 发表演讲的视频:http://free-electrons.com/pub/video/2008/ols/ols2008-gilad-ben-yossef-fault-handlers.ogg

【讨论】:

  • 嗯...希望 Objective-C 的 os_log 没有这样的锁?
【解决方案2】:

将地址输入addr2line,它会显示文件名、行号和函数名。

【讨论】:

    【解决方案3】:

    如果您在运行 valgrind 时只获得适当的回溯没问题,那么这可能是您的一个选择:

    VALGRIND_PRINTF_BACKTRACE(格式,...):

    它将为您提供所有函数的回溯,包括静态函数。

    【讨论】:

      【解决方案4】:

      我发现的更好的选择是 Ian Lance Taylor 的 libbacktrace:

      https://github.com/ianlancetaylor/libbacktrace

      backtrace_symbols() 只打印导出的符号,并且因为它需要 GNU libc,所以便携性不能降低。

      addr2line 很好,因为它包含文件名和行号。但是一旦加载器执行重定位,它就会失败。现在 ASLR 很常见,它会经常失败。

      单独的libunwind 不允许打印文件名和行号。为此,需要解析 ELF 二进制文件中的 DWARF 调试信息。不过,这可以使用 libdwarf 来完成。但是,当 libbacktrace 免费为您提供所需的一切时,何必费心呢?

      【讨论】:

        【解决方案5】:
        1. 创建管道
        2. fork()
        3. 让子进程执行addr2line
        4. 在父进程中,将 backtrace() 返回的地址转换为十六进制
        5. 将十六进制地址写入管道
        6. 从 addr2line 读回输出并打印/记录它

        由于您是通过信号处理程序执行所有这些操作,因此请确保不要使用非异步信号安全的功能。您可以看到异步信号安全 POSIX 函数列表here

        【讨论】:

          【解决方案6】:

          如果您不想采用“向在您身上运行 gdb 的不同进程发出信号”的方法,我认为 gby 提倡这种方法,您还可以稍微更改代码以在崩溃日志文件上调用 open() 并然后 backtrace_symbols_fd() 和 open() 返回的 fd - 根据 glibc 手册,这两个函数都是异步信号安全的。当然,您仍然需要 -rdynamic。此外,据我所见,您有时仍需要在 backtrace*() 函数无法解码的某些地址上运行 addr2line。

          还要注意 fork() 不是异步信号安全的:http://article.gmane.org/gmane.linux.man/1893/match=fork+async,至少在 Linux 上不是。正如有人已经指出的那样,syslog() 也不是。

          【讨论】:

          【解决方案7】:

          如果你想要一个非常详细的回溯,你应该使用 ptrace(2) 来追踪你想要回溯的进程。

          您将能够看到您的进程使用的所有功能,但您需要一些基本的 asm 知识

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2018-06-11
            • 1970-01-01
            • 2021-11-26
            • 2020-03-26
            • 2015-07-31
            • 1970-01-01
            • 2014-11-27
            相关资源
            最近更新 更多