How can one grab a stack trace in C?
Asked Answered
E

10

93

I know there's no standard C function to do this. I was wondering what are the techniques to to this on Windows and *nix? (Windows XP is my most important OS to do this on right now.)

Effect answered 19/9, 2008 at 21:11 Comment(1)
I have tested several methods in detail at: https://mcmap.net/q/64721/-how-to-print-a-stack-trace-whenever-a-certain-function-is-called/…Cajun
N
87

glibc provides backtrace() function.

http://www.gnu.org/software/libc/manual/html_node/Backtraces.html

Nassir answered 19/9, 2008 at 21:15 Comment(7)
glibc FTW... again. (This is yet another reason why I consider glibc to be the absolute gold standard when it comes to C programming (that and the compiler that goes with it).)Eider
But wait there's more! The backtrace() function only provides an array of void * pointers representing the callstack functions. "That isn't very useful. arg." Fear not! glibc provides a function that converts all the void * addresses (the callstack function addresses) into human readable string symbols. char ** backtrace_symbols (void *const *buffer, int size)Eider
I think void* to symbol name for the functions --> IMO that is some pretty awesome voodoo-blackmagic.Eider
Caveat: only works for C functions, I think. @Trevor: It's just looking up syms by address in the ELF table.Mannerly
There is also void backtrace_symbols_fd(void *const *buffer, int size, int fd) which can send the output directly to stdout/err for example.Compton
backtrace_symbols() sucks. It requires exporting all symbols and it does not support DWARF (debugging) symbols. libbacktrace is a much better option in many (most) cases.Fluoric
You shall compile with -rdynamic option to get symbolic names in backtrace_symbols.Ethereal
H
41

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.

Henghold answered 25/2, 2010 at 19:7 Comment(1)
@shuckc only for converting the address to a symbol string, which can be done externally using other tools if need be.Mohair
D
24

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.

Dressmaker answered 24/9, 2008 at 13:7 Comment(2)
You're right about it being fragile to attempt memory allocation in a signal or exception handler. One potential way out is to allocate a fixed amount of "emergency" space at program start, or use a static buffer.Eyeglasses
Another way out is creating a coredump service, which runs independentlyMasse
R
6

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.

Resent answered 31/1, 2010 at 6:31 Comment(3)
"source function/line number"? What if linking is optimized for reduced code size? I will say, though, that this looks like a useful project. A pity that's there's no way to get the registers. I will definitely look into this. Do you know that it is absolutely processor independent? Just works on anything that has a C compiler?Courage
Ok this comment was worth it, if only because of the mention of the helpful addr2line command!Snyder
addr2line fails for relocatable code on systems with ASLR (i.e. most of what people have been using during the past decade).Fluoric
C
5

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().)

Coterie answered 1/10, 2008 at 21:35 Comment(0)
D
4

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.

Debauchee answered 19/9, 2008 at 21:15 Comment(3)
That doesn't help me when a crash happens on an embedded computer in the field. :(Effect
@Kevin: Even on embedded machines, there's usually a way to get a remote debugger stub or at least a core dump. Maybe not once it's deployed to the field, though...Arrack
if you run using gcc-glibc on your platform of choice windows/linux/mac... then backtrace() and backtrace_symbols() will work on all three platforms. Given that statement, I would use the words "there is no [portable] way to do it".Eider
A
3

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

Aluminum answered 22/9, 2008 at 2:15 Comment(2)
Useful, but not really C (it's an external utility).Arrack
also, from the description (section: restrictions) ": pstack currently works only on Linux, only on an x86 machine running 32 bit ELF binaries (64 bit not supported)"Alleras
F
1

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.

Fluoric answered 8/2, 2018 at 10:57 Comment(0)
R
0

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.

Rueful answered 19/9, 2008 at 21:15 Comment(2)
Could you explain the "walking the stack backwards" more throughly?Beckmann
@Beckmann On embedded systems, sometimes this is all you have - I guess this was voted down because the platform is WinXP. But without a libc that supports stack walking, basically you have to "walk" the stack. You start with the current base pointer (on x86 this is the contents of the RBP reg). That takes you to point on the stack with 1. the saved previous RBP (that's how keep the stack walk going), and 2. the call/branch return address (calling function's saved RIP reg), which tells you what the function was. Then, if you have access to the symbol table, you can look up the function address.Mistrustful
T
0

Cpptrace supports C and is designed to be portable and easy to setup. It also facilitates some nice functionality such as signal-safe stack tracing.

Tymothy answered 13/2 at 0:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.