Boost C++ Libraries Home Libraries People FAQ More

PrevUpHomeNext

Getting Started

How to print current call stack
Handle terminates, aborts and Segmentation Faults
Better asserts
Exceptions with stacktrace
Enabling and disabling stacktraces
Saving stacktraces by specified format
Getting function information from pointer
Global control over stacktrace output format

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) {
    ::signal(signum, SIG_DFL);
    boost::stacktrace::stacktrace bt;
    if (bt) {
        std::cerr << "Signal " << signum << ", backtrace:\n" << boost::stacktrace::stacktrace() << '\n'; // [1]
    }
    _Exit(-1);
}

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);
::signal(SIGABRT, &my_signal_handler);

Now we'll get the following output on std::terminate call:

Terminate called:
 0# boost::stacktrace::detail::backend::backend(void**, unsigned long)
 1# my_terminate_handler() at /data/boost/libs/stacktrace/example/terminate_handler.cpp:37
 2# 0x7f624107a6b6
 3# 0x7f624107a701
 4# bar(int) at /data/boost/libs/stacktrace/example/terminate_handler.cpp:18
 5# foo(int) at /data/boost/libs/stacktrace/example/terminate_handler.cpp:22
 6# bar(int) at /data/boost/libs/stacktrace/example/terminate_handler.cpp:18
 7# foo(int) at /data/boost/libs/stacktrace/example/terminate_handler.cpp:22
 8# main at /data/boost/libs/stacktrace/example/terminate_handler.cpp:64
 9# __libc_start_main
10# _start

And the following output on Abort:

Signal 6, backtrace:
 0# boost::stacktrace::detail::backend::backend(void**, unsigned long)
 1# my_signal_handler(int)
 2# 0x7f6240a3a4b0
 3# gsignal
 4# abort
 5# my_terminate_handler()
 6# 0x7f624107a6b6
 7# 0x7f624107a701
 8# bar(int) at /data/boost/libs/stacktrace/example/terminate_handler.cpp:18
 9# foo(int) at /data/boost/libs/stacktrace/example/terminate_handler.cpp:22
10# bar(int) at /data/boost/libs/stacktrace/example/terminate_handler.cpp:18
11# foo(int) at /data/boost/libs/stacktrace/example/terminate_handler.cpp:22
12# main at /data/boost/libs/stacktrace/example/terminate_handler.cpp:64
13# __libc_start_main
14# _start

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 {
    inline 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';
        std::abort();
    }

    inline 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; boost::array<T, N>::reference = int&; boost::array<T, N>::size_type = long unsigned int]': out of range.
Backtrace:
 0# boost::stacktrace::detail::backend::backend(void**, unsigned long)
 1# boost::assertion_failed_msg(char const*, char const*, char const*, char const*, long) at /data/boost/libs/stacktrace/example/assert_handler.cpp:38
 2# boost::array<int, 5ul>::operator[](unsigned long)
 3# bar(int) at /data/boost/libs/stacktrace/example/assert_handler.cpp:16
 4# foo(int) at /data/boost/libs/stacktrace/example/assert_handler.cpp:24
 5# bar(int) at /data/boost/libs/stacktrace/example/assert_handler.cpp:20
 6# foo(int) at /data/boost/libs/stacktrace/example/assert_handler.cpp:24
 7# main at /data/boost/libs/stacktrace/example/assert_handler.cpp:53
 8# __libc_start_main
 9# _start

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 you will need to:

  • Write a basic class that holds the stacktrace:

[getting_stated_class_traced]

  • Write a helper class for appending class 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();
    }
};
  • Throw 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()");
  • Catch exceptions by 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 not be greater than zero in oops()
Backtrace:
 0# boost::stacktrace::detail::backend::backend(void**, unsigned long)
 1# traced::traced() at /data/boost/libs/stacktrace/example/throwing_st.cpp:20
 2# with_trace<std::logic_error>::with_trace<char const (&) [44]>(char const (&) [44]) at /data/boost/libs/stacktrace/example/throwing_st.cpp:33
 3# oops(int)
 4# bar(int) at /data/boost/libs/stacktrace/example/throwing_st.cpp:70
 5# foo(int) at /data/boost/libs/stacktrace/example/throwing_st.cpp:75
 6# bar(int) at /data/boost/libs/stacktrace/example/throwing_st.cpp:65
 7# foo(int) at /data/boost/libs/stacktrace/example/throwing_st.cpp:75
 8# main at /data/boost/libs/stacktrace/example/throwing_st.cpp:93
 9# __libc_start_main
10# _start

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:

  • link with boost_stacktrace_noop to disable backtracing
  • link with other backends to output backtraces

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:

0x7fbcfd17f6b5,0x400d4a,0x400d61,0x400d61,0x400d61,0x400d61,0x400d77,0x400cbf,0x400dc0,0x7fbcfc82d830,0x400a79,

boost::stacktrace::frame provides information about functions. You may construct that class from function pointer and get the function name at runtime:

#include <signal.h>     // ::signal
#include <boost/stacktrace/frame.hpp>
#include <iostream>     // std::cerr

void print_signal_handler_and_exit() {
    void* p = reinterpret_cast<void*>(::signal(SIGSEGV, SIG_DFL));
    boost::stacktrace::frame f(p);
    std::cout << f << std::endl;
    std::exit(0);
}

Code from above will output:

my_signal_handler(int) at /data/boost/libs/stacktrace/example/debug_function.cpp:21

You may control maximal stacktrace length using BOOST_STACKTRACE_DEFAULT_MAX_DEPTH and even override the behavior of default stacktrace output operator by defining the macro from Boost.Config BOOST_USER_CONFIG to point to a file like following:

#ifndef USER_CONFIG_HPP
#define USER_CONFIG_HPP

#define BOOST_STACKTRACE_DEFAULT_MAX_DEPTH 5
#include <boost/stacktrace/stacktrace_fwd.hpp>

#include <iosfwd>

namespace boost { namespace stacktrace {

template <class CharT, class TraitsT, std::size_t Depth>
std::basic_ostream<CharT, TraitsT>& do_stream_st(std::basic_ostream<CharT, TraitsT>& os, const basic_stacktrace<Depth>& bt);

template <class CharT, class TraitsT>
std::basic_ostream<CharT, TraitsT>& operator<<(std::basic_ostream<CharT, TraitsT>& os, const stacktrace& bt) {
    return do_stream_st(os, bt);
}

}}  // namespace boost::stacktrace
#endif // USER_CONFIG_HPP

Implementation of do_stream_st may be the following:

namespace boost { namespace stacktrace {

template <class CharT, class TraitsT, std::size_t Depth>
std::basic_ostream<CharT, TraitsT>& do_stream_st(std::basic_ostream<CharT, TraitsT>& os, const basic_stacktrace<Depth>& bt) {
    const std::streamsize w = os.width();
    const std::size_t frames = bt.size();
    for (std::size_t i = 0; i < frames; ++i) {
        os.width(2);
        os << i;
        os.width(w);
        os << "# ";
        os << bt[i].name();
        os << '\n';
    }

    return os;
}

}}  // namespace boost::stacktrace

Code from above will output:

Terminate called:
 0# boost::stacktrace::detail::backend::backend(void**, unsigned long)
 1# bar(int)
 2# foo(int)
 3# bar(int)
 4# foo(int)


[1] Strictly speaking this code is not async-signal-safe, because it uses std::cerr. 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.


PrevUpHomeNext