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
[30/03/2010 at 15:21:34]
Consider adapting your code to detect malformed stacks (like cycles in base pointers)
[30/03/2010 at 18:48:10]
patches welcome! ;)
All original content copyright© Edd Dawson.
All source code appearing on this website that was written by Edd Dawson is made available under the terms of the Boost software license version 1.0 unless otherwise stated or implied by the license associated with the work from which the code is derived.

anonymous
[30/05/2008 at 07:55:00]
pass the "-export-dynamic" option to gcc in linux works for me