我知道没有标准的C函数可以完成这个任务。我想知道在Windows和*nix上实现这个功能的技术是什么?(现在我的最重要的操作系统是Windows XP。)
我知道没有标准的C函数可以完成这个任务。我想知道在Windows和*nix上实现这个功能的技术是什么?(现在我的最重要的操作系统是Windows XP。)
glibc 提供了 backtrace()
函数。
http://www.gnu.org/software/libc/manual/html_node/Backtraces.html
char ** backtrace_symbols (void *const *buffer, int size)
- Trevor Boyd Smithvoid backtrace_symbols_fd(void *const *buffer, int size, int fd)
函数,可以将输出直接发送到stdout/err等。 - wkzbacktrace_symbols()
很烂。它需要导出所有符号,而且不支持 DWARF(调试)符号。在许多(大多数)情况下,libbacktrace 是一个更好的选择。 - Erwan Legrand有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()。
但需要注意的是,在本地代码中获取堆栈跟踪通常不是一个好主意 - 不是因为不可能,而是因为你通常试图实现错误的目标。
大多数情况下,人们尝试在异常情况下获取堆栈跟踪,比如捕获异常、断言失败或 - 最糟糕和最错误的是 - 当程序出现致命“异常”或信号时,比如分段违规。
考虑到最后一个问题,大多数API将要求您显式分配内存,或者可能会在内部执行此操作。在您的程序可能处于脆弱状态的情况下这样做实际上可能会使情况变得更糟。例如,崩溃报告(或核心转储)将不反映问题的实际原因,而是您处理它失败的尝试。
我假设您正在尝试实现致命错误处理,因为大多数人似乎在获取堆栈跟踪时都会尝试这样做。如果是这样,我建议您在开发过程中依赖于调试器,并让进程在生产环境中生成核心转储文件(或在Windows上生成迷你转储文件)。结合适当的符号管理,您应该没有任何麻烦找出导致问题的指令。
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()
更好。
没有跨平台的方法来做到这一点。
最接近的方法是在不进行优化的情况下运行代码。这样,您可以附加到进程(使用Visual C++调试器或GDB),并获得可用的堆栈跟踪。
你可以通过反向遍历堆栈来实现。但实际上,更容易的方法是在每个函数的开头添加一个标识符,并在结尾处弹出它,然后只需遍历该标识符并打印其内容。这可能有点麻烦,但它能很好地工作,并最终节省你的时间。
addr2line
)的方法那样被ASLR击败。https://github.com/ianlancetaylor/libbacktrace
在撰写本文时,除非我需要在不受libbacktrace支持的平台上生成回溯,否则我不会使用其他任何东西。