panthema / 2008 / 0901-stacktrace-demangled
Instacode coloring of stacktrace

C++ Code Snippet - Print Stack Backtrace Programmatically with Demangled Function Names

Posted on 2008-09-01 22:30 by Timo Bingmann at Permlink with 35 Comments. Tags: #c++ #code-snippet #coding tricks #frontpage

Yesterday I was tasked to analyzed an inner function of a reasonably complex software package. The inner function was called thousands of times from many different parts of the program, a simple counter print-out showed that. However I was interested in which execution paths reach this inner function and how often the different parts access the function.

My straight-forward idea was to dump a stack backtrace each time the inner function is called, similar to the one printed by a debugger. However I needed some code snippet to dump the stack backtrace programmatically, without using gdb to halt the program each time.

Stack backtraces can be saved with backtrace(3), resolved into symbolic names using backtrace_symbols(3) and printed using backtrace_symbols_fd(3). These functions are well documented and fairly easy to use.

However I was debugging a C++ program, which made heavy use of templates and classes. C++ symbols names (including namespace, class and parameters) are mangled by the compiler into plain text symbols: e.g. the function N::A<int>::B::func(int) becomes the symbol _ZN1N1AIiE1B4funcEi. This makes the standard backtrace output very unreadable for C++ programs.

To demangle these strings the GNU libstdc++ library (integrated into the GNU Compiler Collection) provides a function called __cxa_demangle(). Combined with backtrace(3) a pretty stack backtrace can be outputted. The demangling function only works for programs compiled with g++.

The following header file contains a function print_stacktrace(), which uses backtrace(3), backtrace_symbols(3) and __cxa_demangle() to print a readable C++ stack backtrace.

You can freely use it for whatever purpose: download stacktrace.h. I hope you find this utility function useful.

// stacktrace.h (c) 2008, Timo Bingmann from http://idlebox.net/
// published under the WTFPL v2.0

#ifndef _STACKTRACE_H_
#define _STACKTRACE_H_

#include <stdio.h>
#include <stdlib.h>
#include <execinfo.h>
#include <cxxabi.h>

/** Print a demangled stack backtrace of the caller function to FILE* out. */
static inline void print_stacktrace(FILE *out = stderr, unsigned int max_frames = 63)
{
    fprintf(out, "stack trace:\n");

    // storage array for stack trace address data
    void* addrlist[max_frames+1];

    // retrieve current stack addresses
    int addrlen = backtrace(addrlist, sizeof(addrlist) / sizeof(void*));

    if (addrlen == 0) {
	fprintf(out, "  <empty, possibly corrupt>\n");
	return;
    }

    // resolve addresses into strings containing "filename(function+address)",
    // this array must be free()-ed
    char** symbollist = backtrace_symbols(addrlist, addrlen);

    // allocate string which will be filled with the demangled function name
    size_t funcnamesize = 256;
    char* funcname = (char*)malloc(funcnamesize);

    // iterate over the returned symbol lines. skip the first, it is the
    // address of this function.
    for (int i = 1; i < addrlen; i++)
    {
	char *begin_name = 0, *begin_offset = 0, *end_offset = 0;

	// find parentheses and +address offset surrounding the mangled name:
	// ./module(function+0x15c) [0x8048a6d]
	for (char *p = symbollist[i]; *p; ++p)
	{
	    if (*p == '(')
		begin_name = p;
	    else if (*p == '+')
		begin_offset = p;
	    else if (*p == ')' && begin_offset) {
		end_offset = p;
		break;
	    }
	}

	if (begin_name && begin_offset && end_offset
	    && begin_name < begin_offset)
	{
	    *begin_name++ = '\0';
	    *begin_offset++ = '\0';
	    *end_offset = '\0';

	    // mangled name is now in [begin_name, begin_offset) and caller
	    // offset in [begin_offset, end_offset). now apply
	    // __cxa_demangle():

	    int status;
	    char* ret = abi::__cxa_demangle(begin_name,
					    funcname, &funcnamesize, &status);
	    if (status == 0) {
		funcname = ret; // use possibly realloc()-ed string
		fprintf(out, "  %s : %s+%s\n",
			symbollist[i], funcname, begin_offset);
	    }
	    else {
		// demangling failed. Output function name as a C function with
		// no arguments.
		fprintf(out, "  %s : %s()+%s\n",
			symbollist[i], begin_name, begin_offset);
	    }
	}
	else
	{
	    // couldn't parse the line? print the whole line.
	    fprintf(out, "  %s\n", symbollist[i]);
	}
    }

    free(funcname);
    free(symbollist);
}

#endif // _STACKTRACE_H_

To demonstrate the stack trace function I wrote the following small test source code. It calls print_stacktrace() four times from different functions, that were specifically crafted to produce complex names. I left it uncommented, because the functions have no real purpose.

#include "stacktrace.h"
#include <map>

namespace Nu {

template<typename Type>
struct Alpha
{
    struct Beta
    {
	void func() {
	    print_stacktrace();
	}
	void func(Type) {
	    print_stacktrace();
	}
    };
};

struct Gamma
{
    template <int N>
    void unroll(double d) {
	unroll<N-1>(d);
    }
};

template<>
void Gamma::unroll<0>(double) {
    print_stacktrace();
}

} // namespace Nu

int main()
{
    Nu::Alpha<int>::Beta().func(42);
    Nu::Alpha<char*>::Beta().func("42");
    Nu::Alpha< Nu::Alpha< std::map<int, double> > >::Beta().func();
    Nu::Gamma().unroll<5>(42.0);
}

When compiled with g++ -rdynamic -o test test.cc the program prints the following output:

$ ./test
stack trace:
  ./test : Nu::Alpha<int>::Beta::func(int)+0x15
  ./test : main()+0x24
  /lib/libc.so.6 : __libc_start_main()+0xdc
  ./test : __gxx_personality_v0()+0x45
stack trace:
  ./test : Nu::Alpha<char*>::Beta::func(char*)+0x15
  ./test : main()+0x37
  /lib/libc.so.6 : __libc_start_main()+0xdc
  ./test : __gxx_personality_v0()+0x45
stack trace:
  ./test : Nu::Alpha<Nu::Alpha<std::map<int, double, std::less<int>, std::allocator<std::pair<int const, double> > > > >::Beta::func()+0x15
  ./test : main()+0x42
  /lib/libc.so.6 : __libc_start_main()+0xdc
  ./test : __gxx_personality_v0()+0x45
stack trace:
  ./test : void Nu::Gamma::unroll<0>(double)+0x21
  ./test : void Nu::Gamma::unroll<1>(double)+0x24
  ./test : void Nu::Gamma::unroll<2>(double)+0x24
  ./test : void Nu::Gamma::unroll<3>(double)+0x24
  ./test : void Nu::Gamma::unroll<4>(double)+0x24
  ./test : void Nu::Gamma::unroll<5>(double)+0x24
  ./test : main()+0x57
  /lib/libc.so.6 : __libc_start_main()+0xdc
  ./test : __gxx_personality_v0()+0x45
$

Comment by Prasad H. L. at 2008-10-06 10:20 UTC

Thanks a lot for sharing your code! That was very useful!

Comment by Evgeny at 2008-11-13 05:13 UTC

Thanks a lot
By the way
Do you know any ways to obtain also function's arguments from stack?

Comment by Timo at 2008-11-14 07:20 UTC

No, I know of no automated way to display arguments as well. gdb does that to a degree.
There is of course the fundamental problem of how to display arguments of unprintable types. I'm also not sure if a C/C++ function's signature is stored in a way that can be easily processed, but the mangled function name could provide at least integral type info. Walking the stack should be possible with some detailed fiddeling. Those are my basic ideas on that topic.
Greetings

Comment by Hans at 2010-08-11 08:28 UTC

That's so fantastic. Thank you!

Comment by alexei lebedev at 2010-10-10 03:05 UTC

there memory leak of the original funcname value on this line:
... funcname = ret; // use possibly realloc()-ed string

Comment by Timo at 2011-01-31 13:35 UTC

Nope. That is not a memory leak, because the ret value is either funcname itself, or a new buffer for the name realloc()-ed with a larger size. See the return value of cxa_demangle() on the man page.
Timo

Comment by Joe at 2011-03-23 13:04 UTC

Thanks, that was very usefull.

Comment by mark dufour at 2011-06-04 16:52 UTC- http://shedskin.googlecode.com

wonderful, thanks! I used it in shedskin, a restricted-python-to-c++ compiler, to enable at least some kind of exception backtraces for now. now for those filenames and line numbers.. :-)

Comment by Nicholas at 2011-08-22 00:30 UTC

Awesome code snippet.

Comment by Julia at 2011-09-28 10:43 UTC

Hello, This is great. But I need a line number in the source code as well (as the gdb-tool provides). I would like to extract it programmatically i.e. without starting gdb. Do you think it is possible? Can you give me a hint? Many thanks in advance. Kind regards :-)

Comment by Timo at 2011-10-10 09:25 UTC

Nope. Sorry, I'm not aware of any method to read the debug info. Maybe the gdb manual will help.

Comment by Bruno at 2013-01-11 09:34 UTC- http://www.bouml.fr

Hi, addr2line allows you to get the line number in the source code. Supposing a string "./module(function+0x15c) [0x8048a6d]" given by backtrace_symbols, to get the expected information just execute "echo 0x8048a6d | addr2line -e ./module". Of course "module" must not be stripped. So in case of a crash you can produce a command/script file iterating through the strings produced by backtrace_symbols and for each produce the form "echo xxx | addr2line -e yyyy". After you just have to execute the generated command/script file. However the address given by backtrace_symbols may not be the right ones inside a dynamic library. Best regards

Comment by Pino at 2013-11-16 19:57 UTC

great!
is http://panthema.net/ on facebook?

Comment by Timo at 2013-11-18 07:54 UTC

No? Why should it "be on facebook", its not about marketing or liking; I prefer real content.
Timo

Comment by Chao at 2014-03-11 16:55 UTC

this is perfect, thanks!

Comment by Alice at 2014-07-30 00:44 UTC

This code is so helpful! However, do you know how portable this is? Will this work if you use a compiler other than gcc? Will the mangled string from backtrace_symbols always be surrounded by ( and +?

And, also, is there a reason why you parsed the mangled strings with a for loop rather than using strtok?

Comment by Timo at 2014-07-30 19:22 UTC

Yes, this code depends on the compiler. Actually the symbol mangling itself depends on the compiler, while clang may follow gcc's methods, MSVC++ has a totally different schema.
About the for loop: I don't know, but calling as few functions as possible after a fault sounds like a good idea.

Comment by Kevin at 2014-10-23 20:28 UTC

I just wanted to thank you for your useful utility! I had a core function called in 4 different places, and this was PERFECT to determine which call trace was executing, without stopping the real-time programs execution / GDB.

Comment by Scott at 2014-10-28 01:15 UTC

Very handy. I'm adding it to my toolbox and expect to get much use of it. Thank you.

Comment by Ben Haller at 2015-01-06 16:33 UTC- https://github.com/MesserLab/SLiM/

Thanks for the code! It did not work on Mac OS X, because backtrace_symbols() appears to return the backtrace in a non-standard format. I have modified the code so that it now works on OS X (and should remain compatible with other Un*x systems). I introduced a little bit of C++ into it with the use of an enum class for the parse state, but that can be changed to a straight C enum without difficulty. At the moment, the new code can be found at https://github.com/MesserLab/SLiM/blob/master/core/slim_global.cpp. It might not live in that file forever, as the project evolves, but it should live on somewhere within the SLiM repository at https://github.com/MesserLab/SLiM/ so you can search for it if the previous link breaks.

Comment by Shir at 2015-05-12 12:43 UTC

Cool. Works perfectly!

Comment by Govinda Keshavdas at 2015-07-29 23:58 UTC

Great job man . Works well

Comment by Matt at 2015-08-21 02:25 UTC- http://mattdonahoe.com

Thanks! WTFPL is the best!

Comment by Silent Bill at 2015-09-05 22:16 UTC

I tested it with clang, it works like a charm. I already love this code, thanks!

@Julia
Change the following
static inline void print_stacktrace(FILE *out = stderr, unsigned int max_frames = 63)
To
static inline void print_stacktrace(FILE *out = stderr, unsigned int max_frames = 63, char*__file__, int __line__)

And
fprintf(out, " %s : %s+%s\n", symbollist[i], funcname, begin_offset);
To
fprintf(out, " %s : %s+%s @ (%s:%d)\n", symbollist[i], funcname, begin_offset, __file__, __line__);

And
fprintf(out, " %s : %s()+%s\n", symbollist[i], begin_name, begin_offset);
To
fprintf(out, " %s : %s()+%s @ (%s:%d)\n", symbollist[i], begin_name, begin_offset, __file__, __line__);

Call with
print_stacktrace(__FILE__, __LINE__);

(at least I think this will work :)

Comment by Emanuel at 2015-11-14 00:29 UTC- http://optonaut.co

After searching all over the internet for a good solution to stack traces in c++, I found it here. Thanks for sharing!

Comment by Jose Abell at 2016-01-16 19:11 UTC

Thanks for this!
This is very useful!

Comment by rahul patil at 2016-01-19 18:17 UTC

one of the great reusable snippet, thanks a lot, indeed it was useful.

Comment by Sagar at 2016-01-20 23:49 UTC

Thanks a lot for this. It works perfectly!~

Comment by Andy at 2016-01-21 19:42 UTC

Hi Timo, thanks for sharing. Could you provide me any leads as to how could I deal with the case
when there are multiple threads in my program? Given, every thread has its own stack, backtrack
would simply have the stack data of calling thread (and not all threads waiting on calling thread).

Comment by Sven K at 2016-03-14 22:21 UTC

Can someone give me a hint how to find the offset from the executable binary or compiled object files? This is a great site with the different examples http://www.thegeekstuff.com/2012/09/objdump-examples/ of objdump, but my main.o is pretty big and I am not sure that if I should search from there or the final executable. After I find the right statement from assembly, how should I turn it into a line in the file?

Otherwise this example is the most clean and perfect I have found, this has helped me to catch a lot of anomalies!

Comment by Menno at 2016-06-14 12:24 UTC

Brilliant, works like a charm. Thank you.

Comment by tux at 2016-11-21 16:27 UTC- http://www.csploit.org/

Thanks a lot for this!

Comment by Nir L at 2016-12-13 14:58 UTC

Thanks for sharing!!
I alreay used it few times for debugging and it works like charm!!
Sometimes it is even easier to copy and paste the code straight into .cpp
(without the pre-processor stuff #ifdef/#endif)
right after the last #include

I suggest to add optional call to addr2line / or just print the full command
I also suggest to surround the code with

#ifdef WIN32
// Do nothing
#else

because this code might not compile on windows

And one last thing,
if someone want to capture the content of the stack instead of printing it to stderr
this code can be used:

FILE *f = fopen("/dev/null", "w");
char buf[16384] = {0};
setbuffer(f, buf, 16384);
print_stacktrace(f);
buf[16383] = '\0';
fclose (f);

Comment by Guido Orellana at 2019-09-09 14:24 UTC

Very useful! Thank for sharing!!

Post Comment
Name:
E-Mail or Homepage:
 

URLs (http://...) are displayed, e-mails are hidden and used for Gravatar.

Many common HTML elements are allowed in the text, but no CSS style.