如何在 C 中获取堆栈跟踪?

我知道没有标准的 C 函数可以做到这一点。我想知道在 Windows 和 * nix 上有什么技术可以解决这个问题?(WindowsXP 是我目前最重要的操作系统。)

118703 次浏览

You can do it by walking the stack backwards. In reality, though, it's frequently easier to add an identifier onto a call stack at the beginning of each function and pop it at the end, then just walk that printing the contents. It's a bit of a PITA, but it works well and will save you time in the end.

There is no platform independent way to do it.

The nearest thing you can do is to run the code without optimizations. That way you can attach to the process (using the visual c++ debugger or GDB) and get a usable stack trace.

Solaris has the pstack command, which was also copied into Linux.

For Windows check the StackWalk64() API (also on 32bit Windows). For UNIX you should use the OS' native way to do it, or fallback to glibc's backtrace(), if availabe.

Note however that taking a Stacktrace in native code is rarely a good idea - not because it is not possible, but because you're usally trying to achieve the wrong thing.

Most of the time people try to get a stacktrace in, say, an exceptional circumstance, like when an exception is caught, an assert fails or - worst and most wrong of them all - when you get a fatal "exception" or signal like a segmentation violation.

Considering the last issue, most of the APIs will require you to explicitly allocate memory or may do it internally. Doing so in the fragile state in which your program may be currently in, may acutally make things even worse. For example, the crash report (or coredump) will not reflect the actual cause of the problem, but your failed attempt to handle it).

I assume you're trying to achive that fatal-error-handling thing, as most people seem to try that when it comes to getting a stacktrace. If so, I would rely on the debugger (during development) and letting the process coredump in production (or mini-dump on windows). Together with proper symbol-management, you should have no trouble figuring the causing instruction post-mortem.

For Windows, CaptureStackBackTrace() is also an option, which requires less preparation code on the user's end than StackWalk64() does. (Also, for a similar scenario I had, CaptureStackBackTrace() ended up working better (more reliably) than StackWalk64().)

You should be using the unwind library.

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;
}

Your approach also would work fine unless you make a call from a shared library.

You can use the addr2line command on Linux to get the source function / line number of the corresponding PC.

There's backtrace(), and backtrace_symbols():

From the man page:

#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);
...

One way to use this in a more convenient/OOP way is to save the result of backtrace_symbols() in an exception class constructor. Thus, whenever you throw that type of exception you have the stack trace. Then, just provide a function for printing it out. For example:

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();
}

Ta da!

Note: enabling optimization flags may make the resulting stack trace inaccurate. Ideally, one would use this capability with debug flags on and optimization flags off.

For the past few years I have been using Ian Lance Taylor's libbacktrace. It is much cleaner than the functions in the GNU C library which require exporting all the symbols. It provides more utility for the generation of backtraces than libunwind. And last but not least, it is not defeated by ASLR as are approaches requiring external tools such as addr2line.

Libbacktrace was initially part of the GCC distribution, but it is now made available by the author as a standalone library under a BSD license:

https://github.com/ianlancetaylor/libbacktrace

At the time of writing, I would not use anything else unless I need to generate backtraces on a platform which is not supported by libbacktrace.