Have you ever seen

$ ./hello 
Segmentation fault (core dumped)

in your program?

Wouldn’t be great to see

$ ./hello 
signal 'Segmentation fault' (11) caught
stacktrace:
 0# verbose_signal_handler(int) at /home/ja/devel/test/boost/stacktrace/hello.cpp:12
 1# 0x00007FB3CE555950 in /lib/x86_64-linux-gnu/libc.so.6
 2# gsignal in /lib/x86_64-linux-gnu/libc.so.6
 3# foo::seg_fault() at /home/ja/devel/test/boost/stacktrace/hello.cpp:21
 4# goo() at /home/ja/devel/test/boost/stacktrace/hello.cpp:27
 5# main at /home/ja/devel/test/boost/stacktrace/hello.cpp:32
 6# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
 7# _start in ./hello

instead of previous output? If both of you answers was YES, then continue reading, because in the post I will show how it can be done with boost::stacktrace (as always the easy way!).

Actually, with boost::stacktrace having stacktrace on a signal it is pretty easy, the hardest part is to know that boost::stacktrace even exist.

Signal handler setup

The first step, setup signal handler with std::signal() function

void verbose_signal_handler(int signal) {
	// ...
}

int main() {
	signal(SIGSEGV, verbose_signal_handler);
	// ...
}

in our case we setuped handler for SIGSEGV (Segmentation fault) signal. You know when you are trying to write/read memory you don’t own, like this

int * p = nullptr;
// a lot of lines of code ...
*p = 42;  // (1)

where we tried wrote to 0x0 memory address (1).

tip: posix defines more feature rich sigaction() function to setup custom signal handler

Generate stacktrace output

The second step is to create boost::stacktrace::stacktrace instance and use it with stream object (like cout), this way

void verbose_signal_handler(int signal) {
	cout << "signal '" << strsignal(signal) << "' (" << signal << ") caught\n"
		<< "stacktrace:\n"
		<< boost::stacktrace::stacktrace{} 
		<< endl;

	exit(signal);
}

There it is full blown sample

// boost stackstrace sample
#include <iostream>
#include <csignal>
#include <boost/stacktrace.hpp>
#include <string.h>  // for posix strsignal()

using std::cout, std::endl;

void verbose_signal_handler(int signal) {
	cout << "signal '" << strsignal(signal) << "' (" << signal << ") caught\n"
		<< "stacktrace:\n"
		<< boost::stacktrace::stacktrace{} 
		<< endl;

	exit(signal);
}

struct foo {
	void seg_fault() {
		raise(SIGSEGV);
	}
};

void goo() {
	foo f;
	f.seg_fault();
}

int main(int argc, char * argv[]) {
	signal(SIGSEGV, verbose_signal_handler);
	goo();
	cout << "done!\n";
	return 0;
}

Linker setup

To see function names instead of raw addresses and file names with line instead of program binary name, boost::stacktrace needs little bit of help from linker and other libraries.

The sample was build this way

$ scons hello
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
g++ -o hello.o -c -std=c++17 -Wall -g -O0 -DBOOST_STACKTRACE_USE_BACKTRACE hello.cpp
g++ -o hello -rdynamic hello.o -lboost_stacktrace_backtrace -lboost_system -lboost_filesystem -ldl
scons: done building targets.

see hello.cpp and SConstruct build script for further informations

and as you can see you need to define BOOST_STACKTRACE_USE_BACKTRACE and link program with lboost_stacktrace_backtrace and dl libraries and add all symbols with -rdynamic linker option.

-rdynamic: Pass the flag -export-dynamic to the ELF linker, on targets that support it. This instructs the linker to add all symbols, not only used ones, to the dynamic symbol table. This option is needed for some uses of dlopen or to allow obtaining backtraces from within a program.