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 |
号
以更方便的/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上使用
对于Windows,
没有一种平台独立的方法来实现这一点。
你能做的最接近的事情就是在没有优化的情况下运行代码。这样,您就可以附加到进程(使用VisualC++调试程序或GDB)并获得可用的堆栈跟踪。
我可以给你指一下我的文章吗?这只是几行代码。
死后调试
尽管我目前对X64的实现有问题。
Solaris有pstack命令,该命令也被复制到Linux中。
在过去的几年里,我一直在使用伊恩·兰斯·泰勒的libbacktrace。它比GNUC库中需要导出所有符号的函数要干净得多。它为回溯的生成提供了比libunwind更多的实用性。最后但并非最不重要的是,它并没有被ASLR打败,因为需要外部工具(如
libbacktrace最初是gcc发行版的一部分,但现在作者可以在BSD许可下作为独立库使用:
https://github.com/ianlancetaylor/libbacktrace
在编写时,除非需要在libbacktrace不支持的平台上生成回溯,否则我不会使用任何其他方法。
你可以通过向后移动堆栈来完成。然而,实际上,在每个函数的开始添加一个标识符到调用堆栈中,并在结束时弹出它,然后只需执行打印内容的操作,通常会更容易一些。这是一个有点皮塔,但它工作得很好,最终会节省你的时间。