mr-edd.co.uk :: horsing around with the C++ programming language

Obtaining a stack trace programmatically on x86

[8th September 2007]

I have recently been struggling to find out how to get a stack trace in a gcc-compiled binary, on Mac OS X in particular. It took me a while to pull it all together from various sources (this one was particularly helpful), but I have something that seems to work nicely. So I present the code here in the hope that others will find it when they need something similar.

For each function call in the stack, it dumps the frame pointer, the name of the function (demangled!) and the binary in which the function resides.

Unfortunately, this won't work on Windows when compiled with MinGW, as the <dlfcn.h> header isn't available. However, I'm half-way to a solution that mixes functions from libbfd and the DbgHelp functions in the Win32 API.

Update: new code is available on the stack_trace project page, including a solution for MinGW.

If you're using MSVC, you can use the StackWalk64 and SymFromAddr functions in the Windows API, instead.

I'd also like to get some code that works on PowerPC machines, too. If anyone can give me a hint I'd be really appreciative! The code is C++, but you should be able to convert it to C with little difficulty.

So, without further ado, here it is!

#include <dlfcn.h>
#include <cxxabi.h>
#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>

std::string demangle(const char *name)
{
    int status = 0;
    char *d = 0;
    std::string ret = name;
    try { if ( (d = abi::__cxa_demangle(name, 0, 0, &status)) ) ret = d; }
    catch(...) {  }
    std::free(d);
    return ret;
}

void trace()
{
    Dl_info info;
    void **frame = static_cast<void **>(__builtin_frame_address(0));
    void **bp = static_cast<void **>(*frame);
    void *ip = frame[1];

    while(bp && ip && dladdr(ip, &info))
    {
        std::cout << ip << ": " <<  demangle(info.dli_sname) << " in " << info.dli_fname << '\\n';

        if(info.dli_sname && !strcmp(info.dli_sname, "main")) break;

        ip = bp[1];
        bp = static_cast<void**>(bp[0]);
    }
}

template<unsigned N> struct recurse { static void call() { recurse<N -1>::call(); } };
template<> struct recurse<0> { static void call() { trace(); } };

int main()
{
    recurse<10>::call();
    return 0;
}

You can expect the following output from the above program:

edds-mac:~/guff developer$ g++ stack.cpp -W -Wall -ansi -pedantic -o stack
edds-mac:~/guff developer$ ./stack
0x2c23: recurse<0u>::call() in /Users/developer/guff/./stack
0x2c31: recurse<1u>::call() in /Users/developer/guff/./stack
0x2c3f: recurse<2u>::call() in /Users/developer/guff/./stack
0x2c4d: recurse<3u>::call() in /Users/developer/guff/./stack
0x2c5b: recurse<4u>::call() in /Users/developer/guff/./stack
0x2c69: recurse<5u>::call() in /Users/developer/guff/./stack
0x2c77: recurse<6u>::call() in /Users/developer/guff/./stack
0x2c85: recurse<7u>::call() in /Users/developer/guff/./stack
0x2c93: recurse<8u>::call() in /Users/developer/guff/./stack
0x2ca1: recurse<9u>::call() in /Users/developer/guff/./stack
0x2caf: recurse<10u>::call() in /Users/developer/guff/./stack
0x2b49: main in /Users/developer/guff/./stack

Note that you should only really expect this to work as expected in debug builds. If I compile the above with -O3 for example, I only get the final line of output (for the main function), I guess because g++ is doing some tail-call/inlining optimisations. Somewhat humorously, if I compile with -fomit-frame-pointer, I get this:

edds-mac:~/guff developer$ g++ stack.cpp -W -Wall -ansi -pedantic -o stack -fomit-frame-pointer -ggdb3
edds-mac:~/guff developer$ ./stack
0x2205: tart in /Users/developer/guff/./stack

EDIT Oct 02 2007: This code doesn't work on Linux (neither Ubuntu 7.04 nor Fedora Core 7). If you know a fix, please let me know so I can amend this page! But it does work on all the x86 Mac machines that I've tried. But I have some updated code that works on both Mac OS X and Windows here.

Comments

anonymous

[30/05/2008 at 07:55:00]

pass the "-export-dynamic" option to gcc in linux works for me

anonymous

[30/03/2010 at 15:21:34]

Consider adapting your code to detect malformed stacks (like cycles in base pointers)

Edd

[30/03/2010 at 18:48:10]

patches welcome! ;)

alfC

[22/06/2013 at 22:19:22]

The "updated code" link is broken. And it seems that still (June 2013) doesn't work in c++ 4.7.2 or clang++ 3.3; to begin with a null pointer is systematically passed to the demangle function. After adding this `if(name == NULL) return std::string("unnamed");` the program runs but the result is rather uninformative. Did you find a workaround in these years?

0x40112f: unnamed in ./trace.hpp.x
0x40119d: unnamed in ./trace.hpp.x
0x401192: unnamed in ./trace.hpp.x
0x401187: unnamed in ./trace.hpp.x
0x40117c: unnamed in ./trace.hpp.x
0x401171: unnamed in ./trace.hpp.x
0x401166: unnamed in ./trace.hpp.x
0x40115b: unnamed in ./trace.hpp.x
0x401150: unnamed in ./trace.hpp.x
0x401145: unnamed in ./trace.hpp.x
0x40113a: unnamed in ./trace.hpp.x
0x4010cd: unnamed in ./trace.hpp.x

alfC

[22/06/2013 at 22:23:18]

ok, that was quick, compile with this options:

c++ -rdynamic -ldl
clang++ -rdynamic -ldl

The first option is for the symbols to contain usefull strings, the second is to have access to `dladdr`

0x4017c9: recurse<0u>::call() in ./trace.hpp.x
0x4017b9: recurse<1u>::call() in ./trace.hpp.x
0x4017a9: recurse<2u>::call() in ./trace.hpp.x
0x401799: recurse<3u>::call() in ./trace.hpp.x
0x401789: recurse<4u>::call() in ./trace.hpp.x
0x401779: recurse<5u>::call() in ./trace.hpp.x
0x401769: recurse<6u>::call() in ./trace.hpp.x
0x401759: recurse<7u>::call() in ./trace.hpp.x
0x401749: recurse<8u>::call() in ./trace.hpp.x
0x401739: recurse<9u>::call() in ./trace.hpp.x
0x401729: recurse<10u>::call() in ./trace.hpp.x

alfC

[22/06/2013 at 22:35:58]

Sorry for the noise, I didn't read to the point where you mention the "stack_trace" library, these things have been figured out a long time ago.

(optional)
(optional)
(required, hint)

Links can be added like [this one -> http://www.mr-edd.co.uk], to my homepage.
Phrases and blocks of code can be enclosed in {{{triple braces}}}.
Any HTML markup will be escaped.