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

时间:2008-09-19 21:11:54

标签: c windows debugging cross-platform stack-trace

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

11 个答案:

答案 0 :(得分:78)

答案 1 :(得分: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);
     ...

以更方便/ 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();
}

Ta da!

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

答案 2 :(得分:22)

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

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

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

答案 3 :(得分:20)

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

但请注意,在本机代码中使用Stacktrace很少是一个好主意 - 不是因为它不可能,而是因为你通常试图做错事。

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

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

我认为你正在努力实现致命错误处理的事情,因为大多数人在获得堆栈跟踪时都会尝试这样做。如果是这样,我会依赖调试器(在开发期间)并让进程coredump in production(或者在windows上进行小型转储)。与正确的符号管理一起,你应该毫不费力地确定事后的原因。

答案 4 :(得分:4)

没有独立于平台的方法。

您可以做的最近的事情是在没有优化的情况下运行代码。这样你就可以附加到进程(使用visual c ++调试器或GDB)并获得可用的堆栈跟踪。

答案 5 :(得分:4)

对于Windows,CaptureStackBackTrace()也是一个选项,与StackWalk64()相比,用户端的准备代码更少。 (另外,对于类似的情况,CaptureStackBackTrace()最终比StackWalk64()更好(更可靠)。)

答案 6 :(得分:4)

您应该使用unwind library

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;
}

除非您从共享库中拨打电话,否则您的方法也可以正常工作。

您可以在Linux上使用addr2line命令获取相应PC的源功能/行号。

答案 7 :(得分:2)

Solaris具有pstack命令,该命令也被复制到Linux中。

答案 8 :(得分:2)

我可以指出你的文章。它只有几行代码。

Post Mortem Debugging

虽然我目前遇到x64 implementation of this的问题。

答案 9 :(得分:0)

在过去的几年里,我一直在使用Ian Lance Taylor的libbacktrace。它比GNU C库中需要导出所有符号的函数要清晰得多。与libunwind相比,它为回溯的生成提供了更多的实用工具。最后但并非最不重要的是,ASLR并未将其视为需要addr2line等外部工具的方法。

Libbacktrace最初是GCC发行版的一部分,但作者现在可以作为BSD许可下的独立库提供:

https://github.com/ianlancetaylor/libbacktrace

在撰写本文时,除非我需要在libbacktrace不支持的平台上生成回溯,否则我不会使用任何其他内容。

答案 10 :(得分:-1)

你可以通过向后走栈来做到这一点。但实际上,在每个函数的开头将标识符添加到调用堆栈并在结尾处弹出它通常更容易,然后只需执行打印内容。它有点像PITA,但效果很好,最终会节省你的时间。