关于windows:如何在C中获取堆栈跟踪?

How can one grab a stack trace in C?

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


glibc提供backtrace()函数。

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


有backtrace()和backtrace_symbols():

从手册页:

1
2
3
4
5
6
7
8
9
10
11
12
     #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
"
, strs[i]);
     }
     free(strs);
     ...

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

1
2
3
4
5
6
7
8
9
10
11
<wyn>
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

1
2
3
4
5
6
try {
   throw MyException("Oops!");
} catch ( MyException e ) {
    e.printStackTrace();
}
</wyn>

塔达!

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


我们在我们的项目中使用了这个:

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

代码有点混乱,但它工作得很好。仅限Windows。


对于Windows,请检查stackwalk64()API(也在32位Windows上)。对于Unix,您应该使用操作系统的本机方式来完成它,或者回退到glibc的backtrace()(如果可用)。

但是请注意,在本机代码中使用stacktrace很少是一个好主意,这不是因为它不可能,而是因为您通常试图实现错误的事情。

大多数时候,人们试图在异常情况下获取stacktrace,比如当捕获到异常时,断言失败,或者——最糟糕也是最错误的——当您获得致命的"异常"或信号(比如分段冲突)时。

考虑到最后一个问题,大多数API将要求您显式地分配内存或在内部进行分配。在你的程序当前可能处于的脆弱状态下这样做,可能会使事情变得更糟。例如,崩溃报告(或coredump)不会反映问题的实际原因,但您尝试处理它失败了。

我假设您正在尝试实现致命的错误处理,因为大多数人在获取stacktrace时都会尝试这样做。如果是这样,我将依赖于调试器(在开发期间)并让进程在生产环境中进行coredump(或在Windows上进行mini-dump)。再加上适当的符号管理,您应该可以轻松地找到导致错误的指令。


您应该使用放卷库。

1
2
3
4
5
6
7
8
9
10
11
12
13
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的源函数/行号。


对于Windows,CaptureStackBackTrace()也是一个选项,与StackWalk64()相比,它在用户端需要的准备代码更少。(同样,在我以前的类似情况下,CaptureStackBackTrace()StackWalk64()工作得更好(更可靠)。


没有一种平台独立的方法来实现这一点。

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


我可以给你指一下我的文章吗?这只是几行代码。

死后调试

尽管我目前对X64的实现有问题。


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


在过去的几年里,我一直在使用伊恩·兰斯·泰勒的libbacktrace。它比GNUC库中需要导出所有符号的函数要干净得多。它为回溯的生成提供了比libunwind更多的实用性。最后但并非最不重要的是,它并没有被ASLR打败,因为需要外部工具(如addr2line)的方法也是如此。

libbacktrace最初是gcc发行版的一部分,但现在作者可以在BSD许可下作为独立库使用:

https://github.com/ianlancetaylor/libbacktrace

在编写时,除非需要在libbacktrace不支持的平台上生成回溯,否则我不会使用任何其他方法。


你可以通过向后移动堆栈来完成。然而,实际上,在每个函数的开始添加一个标识符到调用堆栈中,并在结束时弹出它,然后只需执行打印内容的操作,通常会更容易一些。这是一个有点皮塔,但它工作得很好,最终会节省你的时间。