../
Looking for a senior C++ dev? I'm looking for work. Hire me!

Summary

It is always nice to have the ability to log a proper call stack backtrace when doing things like signal handling, C++ exception handling, or simply debug logging. Bonus points if the C++ backtraces are demangled. This is how I get backtraces and C++ demangling to work for me when developing with GCC:

Backtrace

Everything you need to get access to the call stack from C/C++ is made available through #include <execinfo.h>, and passing in -rdynamic to your GCC compiler. From a code perspective, there are two function calls you need to make: the first will return some void* pointers, while the second will take those pointers and give you back the symbolic names and offsets:

#include <execinfo.h> ... const int size = 20; void *buffer[size]; // get the void* pointers to functions; "ret" is the number of items returned int ret = backtrace( buffer, size ); // now we want to convert those pointers to text char **ptr = backtrace_symbols( buffer, ret ); // ... do something here // once done, remember to de-allocate ptr free( ptr )

The "do something here" is the trick. At this point, ptr is now an array of text strings. You can access them as ptr[0], ptr[1], etc. What they look like somewhat depends on whether or not you're using C or C++. Here is the output from the example compiled with gcc:

$ gcc -rdynamic 2010-01-25_Backtrace.c && ./a.out 0: ./a.out(displayBacktrace+0x72) [0x4009d6] 1: ./a.out(bar+0x9) [0x400a5c] 2: ./a.out(foo+0x9) [0x400a6c] 3: ./a.out(main+0x9) [0x400a77] 4: /lib/libc.so.6(__libc_start_main+0xfd) [0x7f840c120abd] 5: ./a.out [0x4008a9]

Here is that same example compile with g++:

$ g++ -rdynamic 2010-01-25_Backtrace.c && ./a.out 0: ./a.out(_Z16displayBacktracev+0x26) [0x4009da] 1: ./a.out(_Z3barv+0x9) [0x400a4a] 2: ./a.out(_Z3foov+0x9) [0x400a5a] 3: ./a.out(main+0x9) [0x400a65] 4: /lib/libc.so.6(__libc_start_main+0xfd) [0x7f4ecbea1abd] 5: ./a.out [0x4008f9]
(If not obvious at first glance, see the function names; in C, the names will look like displayBacktrace() while in C++ the name is _Z16displayBacktracev().)

If you're working with gcc, then your work is done; you can take the text pointers and parse them, display them, or simply log them as you see fit.

But if you're working with g++ then all you have at this point is the mangled names. It takes a little bit more work to demangle the call stack.

C++ Demangling

C++ of course mangles all symbols. If you're not already familiar with how mangling works, please refer to Wikipedia as a starting point.

Luckily for us, there is a function that will take a g++ mangled name, and return to us the full prototype including namespace or class if applicable. When combined with the backtrace, this can be a useful feature.

Though this is a trivial example, take the backtrace obtained in the example above, and note this line:

0: ./a.out(_Z16displayBacktracev+0x26) [0x4009da]

...we can extract the mangled function name _Z16displayBacktracev. To demangle this, we'd call the following code:

#include <cxxabi.h> ... int status = -1; char *demangledName = abi::__cxa_demangle( "_Z16displayBacktracev", NULL, NULL, &status ); if ( status == 0 ) { std::cout << demangledName << std::endl; } free( demangledName );

The mangled name _Z16displayBacktracev is demangled as expected to displayBacktrace().

It is important to note that demangledName is a malloc()'d buffer, so remember to free it. Also note the text strings returned from backtrace_symbols() cannot be used directly as input to abi::__cxa_demangle(). You'll need to parse each backtrace line to extract just the mangled name.

Combine backtrace with C++ demangling, and you've got a powerful tool to use when you need to log where in the code something happened.

Last modified: 2010-01-24
Stéphane Charette, stephanecharette@gmail.com
../