First, a word of warning: This is not portable. Secondly… being able to produce stack traces (outside of the debugger) is something that’s usually reserved for languages like Python or Java… but it’s quite nice to have them in C++. There’s several hurdles to overcome, however.
Acquire Stack
This part is pretty easy, but unless you’re nosy with the header files in /usr/include, it’s not likely to stumble upon this by chance.
#include <execinfo.h>
void print_trace(FILE *out, const char *file, int line)
{
const size_t max_depth = 100;
size_t stack_depth;
void *stack_addrs[max_depth];
char **stack_strings;
stack_depth = backtrace(stack_addrs, max_depth);
stack_strings = backtrace_symbols(stack_addrs, stack_depth);
fprintf(out, "Call stack from %s:%d:\n", file, line);
for (size_t i = 1; i < stack_depth; i++) {
fprintf(out, " %s\n", stack_strings[i]);
}
free(stack_strings); // malloc()ed by backtrace_symbols
fflush(out);
}
Demangle C++ Names
GCC also provides access to the C++ name (de)mangler. There are some pretty hairy details to learn about memory ownership, and interfacing with the stack trace output requires a bit of string parsing, but it boils down to replacing the above inner loop with this:
#include <cxxabi.h>
...
for (size_t i = 1; i < stack.depth; i++) {
size_t sz = 200; // just a guess, template names will go much wider
char *function = static_cast(malloc(sz));
char *begin = 0, *end = 0;
// find the parentheses and address offset surrounding the mangled name
for (char *j = stack.strings[i]; *j; ++j) {
if (*j == '(') {
begin = j;
}
else if (*j == '+') {
end = j;
}
}
if (begin && end) {
*begin++ = '';
*end = '';
// found our mangled name, now in [begin, end)
int status;
char *ret = abi::__cxa_demangle(begin, function, &sz, &status);
if (ret) {
// return value may be a realloc() of the input
function = ret;
}
else {
// demangling failed, just pretend it's a C function with no args
std::strncpy(function, begin, sz);
std::strncat(function, "()", sz);
function[sz-1] = '';
}
fprintf(out, " %s:%s\n", stack.strings[i], function);
}
else
{
// didn't find the mangled name, just print the whole line
fprintf(out, " %s\n", stack.strings[i]);
}
free(function);
}
There. You could do a bit more optimization, but I’ll leave that as an exercise to the reader. The important thing is to obey exactly what the ABI requires regarding dynamic memory:
- you pass me a buffer created by
malloc, along with the current size of the buffer. - i might
reallocyour buffer to make space for the whole name, and I’ll return the result, which may be different. Or I’ll fail and returnNULL, because you didn’t pass in a mangled name I could understand. - you free my return value when you’re done (unless I returned
NULL).
Otherwise, you’ll get a segmentation fault somewhere along the line, and a stack trace that blows up the program isn’t useful except for post-mortem analysis in gdb.
Export Symbols
Even if you go through all of the other steps, if you don’t account for this in your build phase, you will get a pretty useless stack trace. Here’s what I got from my experiment initially:
Call stack from backtrace.cxx:105:
debug/backtrace:__gxx_personality_v0()
debug/backtrace:__gxx_personality_v0()
debug/backtrace:__gxx_personality_v0()
debug/backtrace:__gxx_personality_v0()
/lib/tls/i686/cmov/libc.so.6:__libc_start_main()
debug/backtrace:__gxx_personality_v0()
After poking around, I realized I had to add -rdynamic to my linker flags so that all symbols would be exported into the executable. I haven’t experimented, but I would guess this applies to shared-object building as well.
With -rdynamic, my stack trace looks a lot nicer:
Call stack from backtrace.cxx:105:
debug/backtrace:hot_potato::pass(double, double)
debug/backtrace:hot_potato::pass(int)
debug/backtrace:hot_potato::pass()
debug/backtrace:main()
/lib/tls/i686/cmov/libc.so.6:__libc_start_main()
debug/backtrace:__gxx_personality_v0()
Bingo! I only get a source file and line number from the call site, but it’s not too difficult to trace back through callers from this point. In this case, my executable is named debug/backtrace and hot_potato is just an example class that calls its own functions to give me a pretty chain to look at.
Usage
Being able to create a stack trace is only half of it. Now you actually have to use it to get any value out of it. Here’s the rub: This is most useful when an exception gets caught, but the data is already gone by that time, because the exception’s been thrown. Logically then, it seems to make sense to encapsulate this functionality into an exception class (e.g. class app_exception : public std::exception) that gets thrown. Simply replace all of the fprintf calls with something that generates a string. execute it in the exception class’s constructor, and print it out in the catch block.
Another option would be to allocate thread-local storage for stack traces (similar to the current thread-local errno), and then have all of your important functions call set_thread_stacktrace, which populates that thread-local storage. Then the exception handlers can just pull that data regardless of the type of exception thrown. I think this is better from the flexibility aspect, but I haven’t actually investigated the feasability of it nor the performance impact of recalculating this all the time.

4 comments
Comments feed for this article
August 14, 2008 at 7:55 am
gcsimon
Great!
Thanks for the info!
Helped me a lot in making error messages more useful.
October 16, 2008 at 9:29 pm
jobson
Great!
This post show me the answer that i was looking for.
But i yet have one question: is there some way we can get the lines of the code in the stack trace?
October 17, 2008 at 1:27 am
tombarta
I know the file/line number information exists in the binary (GDB uses it), but I don’t know how to extract that information. Honestly, I think if you have a stack trace, you usually have enough information to identify what’s going on anyways (or at least enough information to know where to investigate further).
January 28, 2009 at 6:42 pm
visa
Cool. This helps me even further to ge the demangled names in my C++ applications. Thanks a lot for the article.