Stack Backtracing Inside Your Program

How to use a backtrace to follow the execution path and find out what went wrong and where.

If you usually work with non-trivial C sources, you may have wondered which execution path (that is, which sequence of function calls) brought you to a certain point in your program. Also, it would be even more useful if you could have that piece of information whenever your beautiful, bug-free program suddenly crashes, and you have no debugger at hand. What is needed is a stack backtrace and, thanks to a little known feature of the GNU C library, obtaining it is a fairly easy task.

Stack Frames and Backtraces

Before diving into the article, let's briefly go over how function calls and parameters pass work in C. In order to prepare for the function call, parameters are pushed on the stack in reverse order. Afterwards, the caller's return address also is pushed on the stack and the function is called. Finally, the called function's entry code creates some more space on the stack for storage of automatic variables. This layout commonly is called a stack frame for that particular instance of the function call. When more function calls are nested, the whole procedure is repeated, causing the stack to keep growing downwards and building a chain of stack frames (see Figure 1). Thus, at any given point in a program it theoretically is possible to backtrace the sequence of stack frames to the originating calling point, up to the main() function (to be exact, up to the libc function, which calls main() when the process starts up).

Figure 1. Nested Function Calls

Stack Backtracing from within GDB

Getting the stack backtrace with GDB (or an equivalent graphical front end) for a program that crashed while running is straightforward: you simply issue the bt command, which returns the list of functions called up to the point of the crash. As this is a standard practice, we do not provide any more details here; have a look at the GDB info page if you need specifics (info gdb stack gets you there).

Stack Backtracing Using libc

If for some reason you're not running inside a debugger, two options are available for tracing what the program is doing. The first method is to disseminate it with print and log messages in order to pinpoint the execution path. In a complex program, this option can become cumbersome and tedious even if, with the help of some GCC-specific macros, it can be simplified a bit. Consider, for example, a debug macro such as

#define TRACE_MSG fprintf(stderr, __FUNCTION__     \
                                         "() [%s:%d] here I am\n", \
                         __FILE__, __LINE__)

You can propagate this macro quickly throughout your program by cutting and pasting it. When you do not need it anymore, switch it off simply by defining it to no-op.

A nicer way to get a stack backtrace, however, is to use some of the specific support functions provided by glibc. The key one is backtrace(), which navigates the stack frames from the calling point to the beginning of the program and provides an array of return addresses. You then can map each address to the body of a particular function in your code by having a look at the object file with the nm command. Or, you can do it a simpler way--use backtrace_symbols(). This function transforms a list of return addresses, as returned by backtrace(), into a list of strings, each containing the function name offset within the function and the return address. The list of strings is allocated from your heap space (as if you called malloc()), so you should free() it as soon as you are done with it.

If you prefer to avoid dynamic memory allocation during the backtrace--reasonable, as the backtrace is likely to happen under faulty conditions--you can resort to backtrace_symbols_fd(). This prints the strings directly to the given file descriptor and does not allocate new memory for strings storage. It is a safer choice in those cases where memory heap potentially is corrupted.

In order to convert an address to a function name, the last two functions rely on symbol information to be available inside the program itself. To enable this feature, compile your program with the -rdynamic option (see man dlopen for more details).

Listing 1. How to Use the Backtrace Functions

Listing 1 demonstrates how to use these functions. The test() function calls either func_low() or func_high(), both of which call show_stackframe() to print out the execution path. The program is compiled with

gcc -rdynamic listing1.c -o listing1

The output should look something like:

Execution path:
./listing1(show_stackframe+0x2e) [0x80486de]
./listing1(func_high+0x11) [0x8048799]
./listing1(test+0x43) [0x80487eb]
./listing1(main+0x13) [0x8048817]
/lib/ [0x4003e17d]
./listing1(backtrace_symbols+0x31) [0x80485f1]
First call: 167
Execution path:
./listing1(show_stackframe+0x2e) [0x80486de]
./listing1(func_low+0x11) [0x8048779]
./listing1(test+0x21) [0x80487c9]
./listing1(main+0x33) [0x8048837]
/lib/ [0x4003e17d]
./listing1(backtrace_symbols+0x31) [0x80485f1]
Second call: -3

By the way, function prototypes for the backtrace functions reside in the header file execinfo.h.



Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Hi, Excellent article. Is

Anonymous's picture

Excellent article. Is there a way to resolve addresses to symbols when the executable is statically linked. It should be possible by reading through elf section i guess. Any pointers pls ?

m68k hacker

Anonymous's picture

on linux it works well, like all other stuff.
better tell me how to do this on m68k, guru!

kernel function like backtrace

Zhengju Sha's picture

The function backtrace() is a user space function, does there exist kernel function like that?

I'm modifying some kernel modules(.ko), but it still have bugs :-( .After reading this, I want to
print my stack frame. But I don't know how. Looking forward to your advice.


lineno is off by one

Vinayak Datar's picture

This is helpful.

However, after passing the address to addr2line, it give line no of the next line of the calling statement.

So, in the same example, it gives does not give line no of "func_high()". Instead, it give line no of "return 2*p1;"

Any ideas to get correct line no as well?

Using 3rd arg in sigcontext doesn't work on ARM

MMysore's picture

Thanks for your article. V. helpful indeed.

I'm trying to get this method to work on ARM running embedded linux (running glibc-2.3.2... I am trying to get the PC/EIP from typecasting the 3rd argument of the signal handler as ucontext_t and finding the appropriate arch-dependent register. However, in ARM none of the registers seem to contain anything that looks like the program counter (not even R15, which should actually be the PC). So, I'm unable to find out where the exact crash happened. Any thoughts/ideas?


backtrace_symbols for ARM

Sharan's picture

I appriciate the great work that people have put up on this page.I am having a serious problem with getting the call stack information on ARM. I have tried same kind of code, which works fine for linux on x386 with -rdynamic option of gcc but fails on ARM. It would be great if you could help me on this.

Are there any specific options for compiling this on ARM,if YES then what is that option?.

Thanks in advance,


Re: Stack Backtracing Inside Your Program

Anonymous's picture

My problem is to backtrace on a powerPC based Linux platform.
Where can I find address of interrupted program?
powerPC has no eip, nor program counter at all ...
Thank you.

I have tried this and it work

Anonymous's picture

I have tried this and it works great, but now I have no core files generated. Any ideas on how to fix?

Re: Stack Backtracing Inside Your Program

Anonymous's picture

on linux it works well, like all other stuff.
better tell me how to do this on tru64, guru!

Re: Stack Backtracing Inside Your Program

Anonymous's picture

backtrace is neat when it works.

It still lacks many features you get for free in gdb,
or in the Win32 StackWalk facilities:

. It can't seem to properly convert addresses to names
when dealing with static functions.

. It doesn't demangle C++ names, which obliges to
call the obscure and ill-documented __cxa_demangle

. It can't get you your local variables

. It can't get you source references (file+line no)

Re: Stack Backtracing Inside Your Program

Anonymous's picture

printf(get the value of i=1,i<=5,i=1)

what will be reasulte

Re: Stack Backtracing Inside Your Program

Anonymous's picture

printf(get the value of i=1,i<=5,i=1)

what will be reasulte


Anonymous's picture

same question here. really wondering if you can answer that, guru?

What about locals -vs- arguments?

Anonymous's picture

Is there any easy way to display the rest of the local frame? I'd love to be able to get at the parameters passed. I could just dump the memory added to the stack before the address, but I don't know how far to go.

Re: Stack Backtracing Inside Your Program

Anonymous's picture

You can access the stack in C, but it is still true that some platform-specific knowledge is needed to make sense of it. Just take the address of an argument or automatic variable and work from there:
void func( int arg )
void *stackframe = &arg;

Stack arguments ot a backtrace

Otto Wyss's picture

I'd appreciate if you could detail the stack argument access a little better. I'm written a wxCrashPrint component for the wxWidgets (wxWindows) framework (here) and would like to show arguments as well.

Re: Stack Backtracing Inside Your Program

Anonymous's picture

Very cool!

I can see where this could help in situations where bugs disappear when debug mode is turned on.

Re: Stack Backtracing Inside Your Program

Anonymous's picture

printf in a signal handler? All y'all are just asking for trouble in the future.

Geek Guide
The DevOps Toolbox

Tools and Technologies for Scale and Reliability
by Linux Journal Editor Bill Childers

Get your free copy today

Sponsored by IBM

Upcoming Webinar
8 Signs You're Beyond Cron

Scheduling Crontabs With an Enterprise Scheduler
11am CDT, April 29th
Moderated by Linux Journal Contributor Mike Diehl

Sign up now

Sponsored by Skybot