Backward is a beautiful stack trace pretty printer for C++.
If you are bored to see this:
Backward will spice it up for you:
There is not much to say. Of course it will be able to display the code snippets only if the source files are accessible (else see trace #4 in the example).
All "Source" lines and code snippet prefixed by a pipe "|" are frames inline
the next frame.
You can see that for the trace #1 in the example, the function
you_shall_not_pass()
was inlined in the function ...read2::do_test()
by the
compiler.
Backward is a header only library. So installing Backward is easy, simply drop
a copy of backward.hpp
along with your other source files in your C++ project.
You can also use a git submodule or really any other way that best fits your
environment, as long as you can include backward.hpp
.
If you want Backward to automatically print a stack trace on most common fatal
errors (segfault, abort, un-handled exception...), simply add a copy of
backward.cpp
to your project, and don't forget to tell your build system.
The code in backward.cpp
is trivial anyway, you can simply copy what it's
doing at your convenience.
Note for folly library users: must define backward::SignalHandling sh;
after folly::init(&argc, &argv);
.
If you are using CMake and want to use its configuration abilities to save you the trouble, you can easily integrate Backward, depending on how you obtained the library.
Notice that all approaches are equivalent in the way Backward is added to a CMake target, the difference is in how CMake is pointed to the Backward sources. Backward defines three targets:
Backward::Interface
is an interface target that brings compiler definition flags, include directory, and external libraries. This is all you need to use thebackward.hpp
header library.Backward::Object
bringsBackward::Interface
andbackward.cpp
as anOBJECT
CMake library. This target cannot be exported, so it is not available when Backward is used viafind_package
.Backward::Backward
bringsBackward::Interface
andbackward.cpp
as eitherSTATIC
orSHARED
library (depending on theBACKWARD_SHARED
option). This target is exported and always available, however note that the linker will not include unused objects from a static library, unless the-Wl,--whole-archive
option (or similar) is used.
If you are using a recent version of CMake, you can integrate backward
via FetchContent
like below:
include(FetchContent)
# Also requires one of: libbfd (gnu binutils), libdwarf, libdw (elfutils)
FetchContent_Declare(backward
GIT_REPOSITORY https://github.com/bombela/backward-cpp
GIT_TAG master # or a version tag, such as v1.6
SYSTEM # optional, the Backward include directory will be treated as system directory
)
FetchContent_MakeAvailable(backward)
# Add Backward to your target (either Backward::Interface, Backward::Object, or Backward::Backward)
target_link_libraries(mytarget PUBLIC Backward::Interface)
In this case you have a subdirectory containing the whole repository of Backward (e.g. using git-submodule), in this case you can do:
add_subdirectory(/path/to/backward-cpp)
# Add Backward to your target (either Backward::Interface, Backward::Object, or Backward::Backward)
target_link_libraries(mytarget PUBLIC Backward::Interface)
In this case you can have Backward installed as a subdirectory:
list(APPEND CMAKE_MODULE_PATH /path/to/backward-cpp)
find_package(Backward)
# Add Backward to your target (either Backward::Interface or Backward::Backward)
target_link_libraries(mytarget PUBLIC Backward::Interface)
In this case you have obtained Backward through a package manager.
Packages currently available:
find_package(Backward)
# Add Backward to your target (either Backward::Interface or Backward::Backward)
target_link_libraries(mytarget PUBLIC Backward::Interface)
On Linux and macOS, backtrace can back-trace or "walk" the stack using the following libraries:
Unwind comes from libgcc, but there is an equivalent inside clang itself. With unwind, the stacktrace is as accurate as it can possibly be, since this is used by the C++ runtine in gcc/clang for stack unwinding on exception.
Normally libgcc is already linked to your program by default.
libunwind from the libunwind project
apt-get install binutils-dev (or equivalent)
Libunwind provides, in some cases, a more accurate stacktrace as it knows to decode signal handler frames and lets us edit the context registers when unwinding, allowing stack traces over bad function references.
For best results make sure you are using libunwind 1.3 or later, which added
unw_init_local2
and support for handling signal frames.
CMake will warn you when configuring if your libunwind version doesn't support signal frames.
On macOS clang provides a libunwind API compatible library as part of its environment, so no third party libraries are necessary.
You need to compile your project with generation of debug symbols enabled,
usually -g
with clang++ and g++.
Note that you can use -g
with any level of optimization, with modern debug
information encoding like DWARF, it only takes space in the binary (it's not
loaded in memory until your debugger or Backward makes use of it, don't worry),
and it doesn't impact the code generation (at least on GNU/Linux x86_64 for
what I know).
If you are missing debug information, the stack trace will lack details about your sources.
Backward supports pretty printed stack traces on GNU/Linux, macOS and Windows, it will compile fine under other platforms but will not do anything. Pull requests are welcome :)
Also, by default you will get a really basic stack trace, based on the
backtrace_symbols
API:
You will need to install some dependencies to get the ultimate stack trace. Three libraries are currently supported, the only difference is which one is the easiest for you to install, so pick your poison:
libbfd from the GNU/binutils
apt-get install binutils-dev (or equivalent)
And do not forget to link with the lib: g++/clang++ -lbfd -ldl ...
This library requires dynamic loading. Which is provided by the library dl
.
Hence why we also link with -ldl
.
Then define the following before every inclusion of backward.hpp
(don't
forget to update backward.cpp
as well):
#define BACKWARD_HAS_BFD 1
libdw from the elfutils
apt-get install libdw-dev (or equivalent)
And do not forget to link with the lib and inform Backward to use it:
#define BACKWARD_HAS_DW 1
Of course you can simply add the define (-DBACKWARD_HAS_...=1
) and the
linkage details in your build system and even auto-detect which library is
installed, it's up to you.
apt-get install libdwarf-dev (or equivalent)
And do not forget to link with the lib and inform Backward to use it:
#define BACKWARD_HAS_DWARF 1
There are several alternative implementations of libdwarf and libelf that are API compatible so it's possible, although it hasn't been tested, to replace the ones used when developing backward (in bold, below):
- libelf by Michael "Tired" Riepe
- libdwarf by David Anderson
- libelf from elfutils
- libelf and libdwarf from FreeBSD's ELF Tool Chain project
Of course you can simply add the define (-DBACKWARD_HAS_...=1
) and the
linkage details in your build system and even auto-detect which library is
installed, it's up to you.
That's it, you are all set, you should be getting nice stack traces like the one at the beginning of this document.
If you don't want to limit yourself to the defaults offered by backward.cpp
,
and you want to take some random stack traces for whatever reason and pretty
print them the way you love or you decide to send them all to your buddies over
the Internet, you will appreciate the simplicity of Backward's API.
The StackTrace class lets you take a "snapshot" of the current stack. You can use it like this:
using namespace backward;
StackTrace st; st.load_here(32);
Printer p; p.print(st);
The public methods are:
class StackTrace { public:
// Take a snapshot of the current stack, with at most "trace_cnt_max"
// traces in it. The first trace is the most recent (ie the current
// frame). You can also provide a trace address to load_from() assuming
// the address is a valid stack frame (useful for signal handling traces).
// Both function return size().
size_t load_here(size_t trace_cnt_max)
size_t load_from(void* address, size_t trace_cnt_max)
// The number of traces loaded. This can be less than "trace_cnt_max".
size_t size() const
// A unique id for the thread in which the trace was taken. The value
// 0 means the stack trace comes from the main thread.
size_t thread_id() const
// Retrieve a trace by index. 0 is the most recent trace, size()-1 is
// the oldest one.
Trace operator[](size_t trace_idx)
};
The TraceResolver
does the heavy lifting, and intends to transform a simple
Trace
from its address into a fully detailed ResolvedTrace
with the
filename of the source, line numbers, inlined functions and so on.
You can use it like this:
using namespace backward;
StackTrace st; st.load_here(32);
TraceResolver tr; tr.load_stacktrace(st);
for (size_t i = 0; i < st.size(); ++i) {
ResolvedTrace trace = tr.resolve(st[i]);
std::cout << "#" << i
<< " " << trace.object_filename
<< " " << trace.object_function
<< " [" << trace.addr << "]"
<< std::endl;
}
The public methods are:
class TraceResolver { public:
// Pre-load whatever is necessary from the stack trace.
template <class ST>
void load_stacktrace(ST&)
// Resolve a trace. It takes a ResolvedTrace, because a `Trace` is
// implicitly convertible to it.
ResolvedTrace resolve(ResolvedTrace t)
};
The SnippetFactory is a simple helper class to automatically load and cache source files in order to extract code snippets.
class SnippetFactory { public:
// A snippet is a list of line numbers and line contents.
typedef std::vector<std::pair<size_t, std::string> > lines_t;
// Return a snippet starting at line_start with up to context_size lines.
lines_t get_snippet(const std::string& filename,
size_t line_start, size_t context_size)
// Return a combined snippet from two different locations and combine them.
// context_size / 2 lines will be extracted from each location.
lines_t get_combined_snippet(
const std::string& filename_a, size_t line_a,
const std::string& filename_b, size_t line_b,
size_t context_size)
// Tries to return a unified snippet if the two locations from the same
// file are close enough to fit inside one context_size, else returns
// the equivalent of get_combined_snippet().
lines_t get_coalesced_snippet(const std::string& filename,
size_t line_a, size_t line_b, size_t context_size)
A simpler way to pretty print a stack trace to the terminal. It will automatically resolve the traces for you:
using namespace backward;
StackTrace st; st.load_here(32);
Printer p;
p.object = true;
p.color_mode = ColorMode::always;
p.address = true;
p.print(st, stderr);
You can set a few options:
class Printer { public:
// Print a little snippet of code if possible.
bool snippet = true;
// Colorize the trace
// - ColorMode::automatic: Activate colors if possible. For example, when using a TTY on linux.
// - ColorMode::always: Always use colors.
// - ColorMode::never: Never use colors.
bool color_mode = ColorMode::automatic;
// Add the addresses of every source location to the trace.
bool address = false;
// Even if there is a source location, also prints the object
// from where the trace came from.
bool object = false;
// Resolve and print a stack trace to the given C FILE* object.
// On linux, if the FILE* object is attached to a TTY,
// color will be used if color_mode is set to automatic.
template <typename StackTrace>
FILE* print(StackTrace& st, FILE* fp = stderr);
// Resolve and print a stack trace to the given std::ostream object.
// Color will only be used if color_mode is set to always.
template <typename ST>
std::ostream& print(ST& st, std::ostream& os);
A simple helper class that registers for you the most common signals and other callbacks to segfault, hardware exception, un-handled exception etc.
backward.cpp
simply uses it like that:
backward::SignalHandling sh;
Creating the object registers all the different signals and hooks. Destroying this object doesn't do anything. It exposes only one method:
bool loaded() const // true if loaded with success
Warning: The registered signal handlers are not
signal safe,
mostly because backward-cpp and the DWARF decoding libraries are using malloc
.
In case a signal is raised while malloc is holding a lock, a deadlock will
occur.
To keep the memory footprint of a loaded StackTrace
on the low-side, there a
hierarchy of trace object, from a minimal Trace
to a ResolvedTrace
.
struct Trace {
void* addr; // address of the trace
size_t idx; // its index (0 == most recent)
};
A ResolvedTrace
should contains a maximum of details about the location of
the trace in the source code. Note that not all fields might be set.
struct ResolvedTrace: public Trace {
struct SourceLoc {
std::string function;
std::string filename;
unsigned line;
unsigned col;
};
// In which binary object this trace is located.
std::string object_filename;
// The function in the object that contains the trace. This is not the same
// as source.function which can be an function inlined in object_function.
std::string object_function;
// The source location of this trace. It is possible for filename to be
// empty and for line/col to be invalid (value 0) if this information
// couldn't be deduced, for example if there is no debug information in the
// binary object.
SourceLoc source;
// An optional list of "inliners". All of these sources locations where
// inlined in the source location of the trace (the attribute right above).
// This is especially useful when you compile with optimizations turned on.
typedef std::vector<SourceLoc> source_locs_t;
source_locs_t inliners;
};
François-Xavier Bourlet bombela@gmail.com
Copyright 2013-2017 Google Inc. All Rights Reserved. MIT License.
Although this project is owned by Google Inc. this is not a Google supported or affiliated project.