我知道没有标准的 C 函数可以做到这一点。我想知道在 Windows 和 *nix 上对此有什么技术?(Windows XP 是我现在最重要的操作系统。)
9 回答
glibc 提供backtrace()
函数。
http://www.gnu.org/software/libc/manual/html_node/Backtraces.html
有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 < frames; ++i) {
printf("%s\n", strs[i]);
}
free(strs);
}
};
...
try {
throw MyException("Oops!");
} catch ( MyException e ) {
e.printStackTrace();
}
达达!
注意:启用优化标志可能会使生成的堆栈跟踪不准确。理想情况下,可以在打开调试标志和关闭优化标志的情况下使用此功能。
对于 Windows,请检查 StackWalk64() API(也在 32 位 Windows 上)。对于 UNIX,您应该使用操作系统的本地方式来执行此操作,或者回退到 glibc 的 backtrace()(如果可用)。
但是请注意,在本机代码中使用 Stacktrace 很少是一个好主意 - 不是因为它不可能,而是因为您通常试图实现错误的事情。
大多数时候,人们试图在异常情况下获取堆栈跟踪,例如当捕获到异常时,断言失败或者 - 最糟糕和最错误的 - 当您遇到致命的“异常”或信号时分割违规。
考虑到最后一个问题,大多数 API 将要求您显式分配内存,或者可能在内部进行。在您的程序当前可能处于的脆弱状态下这样做可能会使事情变得更糟。例如,崩溃报告(或 coredump)不会反映问题的实际原因,但您尝试处理它失败)。
我假设您正在尝试实现致命错误处理,因为大多数人似乎都在尝试获取堆栈跟踪。如果是这样,我将依赖调试器(在开发期间)并让进程在生产中进行核心转储(或在 Windows 上进行迷你转储)。加上适当的符号管理,您应该可以毫不费力地找出导致指令的事后分析。
对于 Windows,CaptureStackBackTrace()
这也是一种选择,它需要的用户端准备代码比StackWalk64()
它少。(此外,对于我遇到的类似情况,CaptureStackBackTrace()
最终效果更好(更可靠)StackWalk64()
。)
您应该使用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;
}
除非您从共享库进行调用,否则您的方法也可以正常工作。
在 Linux 上可以使用该addr2line
命令获取对应 PC 的源函数/行号。
没有平台独立的方式来做到这一点。
您可以做的最接近的事情是在没有优化的情况下运行代码。这样,您可以附加到进程(使用可视化 c++ 调试器或 GDB)并获得可用的堆栈跟踪。
Solaris 有pstack命令,该命令也被复制到 Linux 中。
你可以通过向后走堆栈来做到这一点。但实际上,通常更容易在每个函数的开头将标识符添加到调用堆栈并在结尾弹出它,然后只需执行打印内容即可。这有点像 PITA,但效果很好,最终会节省你的时间。
在过去的几年里,我一直在使用 Ian Lance Taylor 的 libbacktrace。它比 GNU C 库中需要导出所有符号的函数要干净得多。它比 libunwind 提供了更多用于生成回溯的实用程序。最后但并非最不重要的一点是,它并没有像需要外部工具(如addr2line
.
Libbacktrace 最初是 GCC 发行版的一部分,但现在作者在 BSD 许可下将其作为独立库提供:
https://github.com/ianlancetaylor/libbacktrace
在撰写本文时,除非我需要在 libbacktrace 不支持的平台上生成回溯,否则我不会使用其他任何东西。