我知道没有标准的C函数可以做到这一点。我想知道在Windows和* nix上有什么技巧?(Windows XP是我目前最重要的操作系统。)
Answers:
glibc提供了backtrace()函数。
http://www.gnu.org/software/libc/manual/html_node/Backtraces.html
char ** backtrace_symbols (void *const *buffer, int size)
void backtrace_symbols_fd(void *const *buffer, int size, int fd)
可以将输出直接发送到例如stdout / err。
backtrace_symbols()
很烂。它需要导出所有符号,并且不支持DWARF(调试)符号。在许多(大多数)情况下,libbacktrace是一个更好的选择。
有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();
}
da!
注意:启用优化标志可能会使生成的堆栈跟踪不准确。理想情况下,可以在调试标志打开和优化标志关闭的情况下使用此功能。
对于Windows,请检查StackWalk64()API(也在32位Windows上)。对于UNIX,您应该使用操作系统的本机方式执行此操作,如果可用,则应使用glibc的backtrace()。
但是请注意,在本机代码中使用Stacktrace几乎不是一个好主意-不是因为不可能,而是因为您通常试图实现错误的目标。
在大多数情况下,人们尝试在异常情况下获取堆栈跟踪,例如当捕获到异常,断言失败或-当所有情况中最严重和最错误的情况-当您收到致命的“异常”或类似信号时,细分违规。
考虑到最后一个问题,大多数API都将要求您显式分配内存或可以在内部进行分配。在您的程序可能处于当前脆弱的状态下这样做可能会使情况变得更糟。例如,崩溃报告(或coredump)不会反映问题的实际原因,但是您处理该问题的尝试失败。
我认为您正在尝试实现致命错误处理,因为大多数人在获取堆栈跟踪时似乎都尝试这样做。如果是这样,我将依靠调试器(在开发过程中)并使进程在生产环境中转储(或在Windows上进行小型转储)。结合适当的符号管理,您应该可以容易地确定事后验明指令。
您应该使用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;
}
除非您从共享库进行调用,否则您的方法也可以正常工作。
您可以addr2line
在Linux上使用该命令来获取相应PC的源功能/行号。
没有平台独立的方法可以做到这一点。
您可以做的最接近的事情是无需优化即可运行代码。这样,您可以附加到进程(使用可视c ++调试器或GDB)并获得可用的堆栈跟踪。
Solaris具有pstack命令,该命令也已复制到Linux中。
您可以通过向后移动堆栈来实现。但是,实际上,在每个函数的开头将标识符添加到调用堆栈中并在结尾将其弹出通常会更容易,然后只需遍历打印内容即可。它有点像PITA,但是效果很好,最终可以节省您的时间。
在过去的几年中,我一直在使用Ian Lance Taylor的libbacktrace。它比需要导出所有符号的GNU C库中的函数干净得多。它比libunwind提供了更多的回溯生成工具。最后但并非最不重要的一点是,它并没有像ASLR这样需要外部工具的方法所击败addr2line
。
Libbacktrace最初是GCC发行版的一部分,但现在由BSD许可的作者以独立库的形式提供:
https://github.com/ianlancetaylor/libbacktrace
在撰写本文时,除非需要在libbacktrace不支持的平台上生成回溯,否则我不会使用其他任何东西。