SourceXR

C/C++ Cross-Reference Tool

Display the Call Stack upon a Segmentation Fault

We wrote a small snippet to print the call stack when a segmentation fault occurs (or any other signal or specific event). This is quite useful especially when you want to quickly identify some crash condition. It can be useful as well if for some reason core files are not enabled on your system.

Getting the Callstack

The GNU C library comes with several functions to get the call stack and corresponding symbols: backtrace() and backtrace_symbols() functions which return an array containing the stack pointers at the calling point.

Below is the basic use of these functions within a signal handler:

#include <signal.h>
#include <execinfo.h> // backtrace(), etc

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

void (*defaultSigHandler) (int);

void sigHandler (int i) {
    const int size = 64;
    void *buffer[size];

    // append pid to output file
    const pid_t p = getpid ();
    size_t fnsize = 64;
    char fn[fnsize];
    snprintf (fn, fnsize, "stack.%i", p);
    int out = open (fn, O_CREAT | O_WRONLY, S_IRUSR | S_IRUSR);
    if (out == -1) {
        perror ("open");
        return;
    }

    const int btsize = backtrace (buffer, size);

    if (btsize > 1)
    {
        // remove the first stack frame since it is the signal handler

        // do not call backtrace_symbols() since it calls malloc
        // which is not async-safe
        backtrace_symbols_fd (buffer + 1, btsize - 1, out);
    }
    close (out);

    // executes the default signal handler action
    defaultSigHandler (i);
}

int main () {
    // register signal handler
    struct sigaction sa, oldsa;
    sa.sa_handler = sigHandler;
    sigemptyset (&sa.sa_mask);
    sa.sa_flags = 0;
    sigaction (SIGSEGV, &sa, &oldsa);
    defaultSigHandler = oldsa.sa_handler;

    // ...

    return 0;
}

Sample Program

We add some code that fails in a segmentation fault like the following:

void fun () {
    int *i = 0;
    *i = 3; // SEGV
}

void fun1 () {
    fun();
}

void fun2() {
    fun1();
}

If we run the previous program along with the stack printer we would get the following output (make sure to compile with the -g flag to have debug symbols):

./a.out[0x400a33]
/lib/x86_64-linux-gnu/libc.so.6(+0x324f0)[0x7fcb8076f4f0]
./a.out[0x400a8e]
./a.out[0x400a9f]
./a.out[0x400aaa]
./a.out[0x400b0d]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xfd)[0x7fcb8075bead]
./a.out[0x400879]

In order to get the line and file information we run it through addr2line utility (which comes with the binutils package), wrapped in this small script:

#!/usr/bin/env perl

use strict;
use warnings;

while (<>)
{
    if (/(.*?)(\(.*?\))?\[(0x[0123456789abcdef]+)\]/) {
        my $bin = $1;
        my $addr = $3;

        my $line = `addr2line -f -C -p -e $bin $addr`;
        print $line;
    }
}

The output would be like the following one:

??
??:0
fun() at backtrace.cpp:41
fun1() at backtrace.cpp:46
fun2() at backtrace.cpp:50
main at backtrace.cpp:63
??
??:0
_start at ??:0

Comments !