Better crash diagnostic with stacktrace
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
andSConstruct
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.