Skip to main content

Printing a backtrace from inside a C program

While digging in the latest code of htop in hunt of a resizing bug I came across calls to functions backtrace(3) and backtrace_symbols_fd(3), which I didn't know about before. htop installs a custom segfault handler which tries to shut down curses and print a stacktrace using the backtrace(3) function. Example output looks like this:

htop 0.9 aborting. Please report bug at http://htop.sf.net
Backtrace: 
htop[0x4081cf]
/lib64/libc.so.6(+0x324c0)[0x7fbbca4a54c0]
/lib64/libc.so.6(__ **sprintf** _chk+0x87)[0x7fbbca558237]
Aborted

While that could be more helpful, it does give ahint that sprintf is involved in the crash - it actually is. Things get interesting when you deal with memory corruption so that the call to backtrace inside your segfault handler segfaults itself. In that case you get:

htop 0.9 aborting. Please report bug at http://htop.sf.net
Segmentation fault

That's not helpful anymore. Still, not all segfaults are memory corruptions!

If you're interested in adding this feature to your own program or at least would like to play with it, here is some dedicated demo code:

/* Licensed as Public Domain */
#include <stdio.h>
#include <execinfo.h>
#include <signal.h>
#include <stdlib.h>

static void dump_trace() {
    void * buffer[255];
    const int calls = backtrace(buffer,
        sizeof(buffer) / sizeof(void *));
    backtrace_symbols_fd(buffer, calls, 1);
    exit(EXIT_FAILURE);
}

void boom() {
    int a[10];
    printf("%d", a[-100000]);
}

void two() { boom(); }
void one() { two(); }

int main() {
    signal(SIGSEGV, dump_trace);
    one();
    return 0;
}

In my Makefile I have

NUMBERS: backtrace.c
    $(CC) -o $@ $<

NAMES: backtrace.c
    $(CC) **-rdynamic** -o $@ $ <

Without -rdynamic you get numbers, only -- like this:

# ./NUMBERS 
./NUMBERS[0x400683]
/lib64/libc.so.6(+0x324c0)[0x7f129eda04c0]
./NUMBERS[0x4006b1]
./NUMBERS[0x4006db]
./NUMBERS[0x4006eb]
./NUMBERS[0x40070d]
/lib64/libc.so.6(__libc_start_main+0xfd)[0x7f129ed8cd2d]
./NUMBERS[0x4005c9]

In contrast, with -rdynamic you get function names where available:

# ./NAMES 
./NAMES[0x400953]
/lib64/libc.so.6(+0x324c0)[0x7f59be5d84c0]
./NAMES(boom+0x8)[0x400981]
./NAMES(two+0xe)[0x4009ab]
./NAMES(one+0xe)[0x4009bb]
./NAMES(main+0x20)[0x4009dd]
/lib64/libc.so.6(__libc_start_main+0xfd)[0x7f59be5c4d2d]
./NAMES[0x400899]

The same job (and a bit more) is also done by library (and tool) stacktrace. I played with it (and put an ebuild into the betagarden overlay) but it's not working for me as expected, yet. I have contacted upstream about it and hope for some enlightening reply in the next few days.