如何在C中获取堆栈跟踪?

Kev*_*vin 80 c windows debugging cross-platform stack-trace

我知道没有标准的C函数来做到这一点.我想知道Windows和*nix上的技术是什么?(Windows XP是我现在最重要的操作系统.)

san*_*iyn 79

glibc提供了backtrace()函数.

http://www.gnu.org/software/libc/manual/html_node/Backtraces.html

  • glibc FTW ......再次.(这就是为什么我认为glibc在C编程(以及随之而来的编译器)中绝对金标准的另一个原因.) (6认同)
  • 但等等还有更多!backtrace()函数仅提供表示callstack函数的void*指针数组."这不是很有用."arg." 不要害怕!glibc提供了一个函数,它将所有void*地址(callstack函数地址)转换为人类可读的字符串符号.`char**backtrace_symbols(void*const*buffer,int size)` (4认同)
  • 还有`void backtrace_symbols_fd(void*const*buffer,int size,int fd)`,它可以直接将输出发送到stdout/err. (2认同)
  • `backtrace_symbols()`很糟糕.它需要导出所有符号,并且它不支持DWARF(调试)符号.在很多(大多数)情况下,libbacktrace是一个更好的选择. (2认同)

Tom*_*Tom 28

有backtrace()和backtrace_symbols():

从手册页:

     #include <execinfo.h>
     #include <stdio.h>
     ...
     void* callstack[128];
     int i, frames = backtrace(callstack, 128);
     char** strs = backtrace_symbols(callstack, frames);
     for (i = 0; i < frames; ++i) {
         printf("%s\n", strs[i]);
     }
     free(strs);
     ...
Run Code Online (Sandbox Code Playgroud)

以更方便/ OOP方式使用它的一种方法是将backtrace_symbols()的结果保存在异常类构造函数中.因此,无论何时抛出该类型的异常,都会有堆栈跟踪.然后,只提供打印出来的功能.例如:


class MyException : public std::exception {

    char ** strs;
    MyException( const std::string & message ) {
         int i, frames = backtrace(callstack, 128);
         strs = backtrace_symbols(callstack, frames);
    }

    void printStackTrace() {
        for (i = 0; i 

...


try {
   throw MyException("Oops!");
} catch ( MyException e ) {
    e.printStackTrace();
}

塔达!

注意:启用优化标志可能会导致生成的堆栈跟踪不准确.理想情况下,可以使用此功能启用调试标志并关闭优化标志.

  • gcc需要`-rdynamic`参数才能生效 (9认同)

Mar*_*ark 22

我们已经将它用于我们的项目:

https://www.codeproject.com/kb/threads/stackwalker.aspx

代码是一个混乱的恕我直言,但它运作良好.仅限Windows.

  • 顺便提一下,@ http://stackwalker.codeplex.com/维护了最近的代码,但codeproject页面仍然可用作主要文档. (3认同)
  • 链接不起作用.应该是http://www.codeproject.com/KB/threads/StackWalker.aspx吗? (2认同)

Chr*_*n.K 20

对于Windows,请检查StackWalk64()API(也在32位Windows上).对于UNIX,您应该使用操作系统的本机方式来执行此操作,或者如果可用,则回退到glibc的backtrace().

但是请注意,在本机代码中使用Stacktrace很少是一个好主意 - 不是因为它不可能,而是因为你在尝试实现错误的东西.

大多数时候人们试图在一个特殊情况下获得一个堆栈跟踪,例如当一个异常被捕获时,一个断言失败或者 - 最糟糕且最错误的是 - 当你得到致命的"异常"或信号如同分段违规.

考虑到最后一个问题,大多数API都要求您明确分配内存或者可以在内部执行.在您的程序目前所处的脆弱状态下这样做可能会使事情变得更糟.例如,崩溃报告(或coredump)不会反映问题的实际原因,但是您尝试处理它的失败.

我假设你正在努力实现致命错误处理的事情,因为大多数人似乎都在尝试使用堆栈跟踪.如果是这样,我将依赖调试器(在开发期间)并让进程coredump in production(或windows上的mini-dump).与适当的符号管理一起,您应该毫不费力地确定事后验证的原因.

  • 你是对的,在信号或异常处理程序中尝试内存分配是脆弱的.一种可能的出路是在程序启动时分配固定数量的"紧急"空间,或使用静态缓冲区. (2认同)

小智 5

对于 Windows,CaptureStackBackTrace()这也是一个选项,它在用户端需要的准备代码比StackWalk64()原来少。(另外,对于我遇到的类似情况,CaptureStackBackTrace()最终比 工作得更好(更可靠)StackWalk64()。)


小智 5

您应该使用unwind 库

unw_cursor_t cursor; unw_context_t uc;
unw_word_t ip, sp;
unw_getcontext(&uc);
unw_init_local(&cursor, &uc);
unsigned long a[100];
int ctr = 0;

while (unw_step(&cursor) > 0) {
  unw_get_reg(&cursor, UNW_REG_IP, &ip);
  unw_get_reg(&cursor, UNW_REG_SP, &sp);
  if (ctr >= 10) break;
  a[ctr++] = ip;
}
Run Code Online (Sandbox Code Playgroud)

除非您从共享库进行调用,否则您的方法也可以正常工作。

addr2lineLinux上可以使用该命令获取对应PC的源码函数/行号。


Cod*_*ous 0

您可以通过向后遍历堆栈来完成此操作。但实际上,在每个函数开头将标识符添加到调用堆栈并在末尾弹出它,然后只需打印内容通常更容易。它有点像 PITA,但效果很好,最终会节省您的时间。

  • 您能更彻底地解释一下“向后遍历堆栈”吗? (2认同)