![]() |
Home | Libraries | People | FAQ | More |
boost::stacktrace::stacktrace
contains methods for working with call-stack/backtraces/stacktraces. Here's
a small example:
#include <boost/stacktrace.hpp> // ... somewere inside the `bar(int)` function that is called recursively: std::cout << boost::stacktrace::stacktrace();
In that example:
boost::stacktrace::
is the namespace that has all the classes and functions to work with
stacktraces
stacktrace()
is the default constructor call; constructor stores the current function
call sequence inside the stacktrace class.
Code from above will output something like this:
0# bar(int) 1# bar(int) 2# bar(int) 3# bar(int) 4# main 5# __libc_start_main 6# _start
Segmentation Faults and std::terminate
calls sometimes happen in programs. Programmers usually wish to get as much
information as possible on such incidents, so having a stacktrace will significantly
improve debugging and fixing.
To deal with Segmentation Faults and std::terminate
calls we would need to write handlers:
#include <exception> // std::set_terminate, std::abort #include <signal.h> // ::signal #include <boost/stacktrace.hpp> #include <iostream> // std::cerr void my_terminate_handler() { std::cerr << "Terminate called:\n" << boost::stacktrace::stacktrace() << '\n'; std::abort(); } void my_signal_handler(int signum) { boost::stacktrace::stacktrace bt; if (bt) { std::cerr << "Signal " << signum << ", backtrace:\n" << boost::stacktrace::stacktrace() << '\n'; // [1] } std::abort(); }
After that we can set them as a default handlers and get some more information on incidents:
std::set_terminate(&my_terminate_handler); ::signal(SIGSEGV, &my_signal_handler);
Now we'll get the following output on std::terminate
call:
Terminate called: 0# my_terminate_handler() 1# std::rethrow_exception(std::__exception_ptr::exception_ptr) 2# std::terminate() 3# bar(int) 4# bar(int) 5# foo(int) 6# bar(int) 7# foo(int) 8# bar(int) 9# foo(int) 10# bar(int) 11# foo(int) 12# bar(int) 13# foo(int) 14# bar(int) 15# foo(int) 16# main 17# __libc_start_main 18# _start 19# ??
And the following output on Segmentation Fault:
Signal 11, backtrace: 0# my_signal_handler(int) 1# killpg 2# ?? 3# bar(int) 4# foo(int) 5# bar(int) 6# foo(int) 7# bar(int) 8# foo(int) 9# bar(int) 10# foo(int) 11# bar(int) 12# foo(int) 13# bar(int) 14# foo(int) 15# main 16# __libc_start_main 17# _start 18# ??
The output stacktrace may be corrupted by previous actions. But now at least some basic information is available to work with.
Pretty often assertions provide not enough information to locate the problem. For example you can see the following message on out-of-range access:
../../../boost/array.hpp:123: T& boost::array<T, N>::operator[](boost::array<T, N>::size_type) [with T = int; long unsigned int N = 5ul]: Assertion '(i < N)&&("out of range")' failed. Aborted (core dumped)
That's not enough to locate the problem without debugger. There may be thousand code lines in real world examples and hundred places where that assertion could happen. Let's try to improve the assertions, and make them more informative:
// BOOST_ENABLE_ASSERT_DEBUG_HANDLER is defined for the whole project #include <stdexcept> // std::logic_error #include <iostream> // std::cerr #include <boost/stacktrace.hpp> namespace boost { void assertion_failed_msg(char const* expr, char const* msg, char const* function, char const* file, long line) { std::cerr << "Expression '" << expr << "' is false in function '" << function << "': " << (msg ? msg : "<...>") << ".\n" << "Backtrace:\n" << boost::stacktrace::stacktrace() << '\n'; throw std::logic_error("assertion"); } void assertion_failed(char const* expr, char const* function, char const* file, long line) { ::boost::assertion_failed_msg(expr, 0 /*nullptr*/, function, file, line); } } // namespace boost
We've defined the BOOST_ENABLE_ASSERT_DEBUG_HANDLER
macro for the whole project. Now all the BOOST_ASSERT
and BOOST_ASSERT_MSG will
call our functions assertion_failed
and assertion_failed_msg
in case of failure. In assertion_failed_msg
we output information that was provided by the assertion macro and boost::stacktrace::stacktrace:
Expression 'i < N' is false in function 'T& boost::array<T, N>::operator[](boost::array<T, N>::size_type) [with T = int; long unsigned int N = 5ul]': out of range. Backtrace: 0# boost::assertion_failed_msg(char const*, char const*, char const*, char const*, long) 1# boost::array<int, 5ul>::operator[](unsigned long) 2# oops(unsigned long) 3# bar(int) 4# foo(int) 5# bar(int) 6# foo(int) 7# bar(int) 8# foo(int) 9# bar(int) 10# foo(int) 11# bar(int) 12# foo(int) 13# bar(int) 14# foo(int) 15# main 16# __libc_start_main 17# _start 18# ??
Now we do know the steps that led to the assertion and can find the error without debugger.
You can provide more information along with exception by embedding stacktraces into the exception. For that we will need:
#include <boost/stacktrace.hpp> struct traced { const boost::stacktrace::stacktrace trace; virtual const char* what() const noexcept = 0; virtual ~traced(){} };
traced
to any exception:
template <class Exception> struct with_trace : public Exception, public traced { template <class... Args> with_trace(Args&&... args) : Exception(std::forward<Args>(args)...) {} const char* what() const noexcept { return Exception::what(); } };
with_trace<Exception>
instead of just Exception:
if (i >= 4) throw with_trace<std::out_of_range>("'i' must be less than 4 in oops()"); if (i <= 0) throw with_trace<std::logic_error>("'i' must not be greater than zero in oops()");
traced:
try { foo(5); // testing assert handler } catch (const traced& e) { std::cerr << e.what() << '\n'; if (e.trace) { std::cerr << "Backtrace:\n" << e.trace << '\n'; } } catch (const std::exception& e) { std::cerr << e.what() << '\n'; }
Code from above will output:
'i' must be less than 4 in oops() Backtrace: 0# with_trace<std::out_of_range>::with_trace<char const (&) [34]>(char const (&) [34]) 1# oops(unsigned long) 2# bar(int) 3# foo(int) 4# bar(int) 5# foo(int) 6# bar(int) 7# foo(int) 8# bar(int) 9# foo(int) 10# bar(int) 11# foo(int) 12# bar(int) 13# foo(int) 14# main 15# __libc_start_main 16# _start 17# ??
At some point arises a requirement to easily enable/disable stacktraces for a whole project. That could be easily achived.
Just define BOOST_STACKTRACE_LINK for a whole project. Now you can enable/disable stacktraces by just linking with different backends:
boost_stacktrace_noop
to disable backtracing
See section "Build, Macros and Backends" for more info.
boost::stacktrace::stacktrace
provides access to individual frames
of the stacktrace, so that you could save stacktrace information in your
own format. Consider the example, that saves only function addresses of each
frame:
#include <boost/stacktrace.hpp> #include <iostream> // std::cout namespace bs = boost::stacktrace; void dump_compact(const bs::stacktrace& st) { for (bs::frame frame: st) { std::cout << frame.address() << ','; } std::cout << std::endl; }
Code from above will output:
0x401a25,0x401a25,0x401a25,0x401a25,0x401a25,0x401a25,0x4019cb,0x401a7f,0x7f9da8a46e50,0x4013e0,0,
[1] Strictly speaking this code is not async-signal-safe, but we have SIGSEGV already it could hardly become worse. Section "Build, Macros and Backends" describes async-signal-safe backends, so if you will use the noop backend code becomes absolutely valid as that backens always returns 0 frames and operator<< will be never called.