Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork144
Simple, portable, and self-contained stacktrace library for C++11 and newer
License
jeremy-rifkin/cpptrace
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
Cpptrace is a simple and portable C++ stacktrace library supporting C++11 and greater on Linux, macOS, and Windowsincluding MinGW and Cygwin environments. The goal: Make stack traces simple for once.
In addition to providing access to stack traces, cpptrace also provides a mechanism for getting stacktraces from thrownexceptions which is immensely valuable for debugging and triaging. More infobelow.
Cpptrace also has a C API, docshere.
- 30-Second Overview
- Prerequisites
- Basic Usage
namespace cpptrace- Stack Traces
- Object Traces
- Raw Traces
- Utilities
- Formatting
- Configuration
- Traces From All Exceptions (
CPPTRACE_TRYandCPPTRACE_CATCH) - Rethrowing Exceptions
cpptrace::try_catch- Traces from SEH exceptions
- Traced Exception Objects
- Terminate Handling
- Signal-Safe Tracing
- Utility Types
- Headers
- Libdwarf Tuning
- JIT Support
- Loading Libraries at Runtime
- ABI Versioning
- Supported Debug Formats
- How to Include The Library
- Platform Logistics
- Library Back-Ends
- Testing Methodology
- Notes About the Library
- FAQ
- Contributing
- License
Generating stack traces is as easy as:
#include<cpptrace/cpptrace.hpp>voidtrace() {cpptrace::generate_trace().print();}
Cpptrace can also retrieve function inlining information on optimized release builds:
Cpptrace provides access to resolved stack traces as well as fast and lightweight raw traces (just addresses) that canbe resolved later:
constauto raw_trace = cpptrace::generate_raw_trace();// then laterraw_trace.resolve().print();
One of the most important features cpptrace offers is the ability to retrieve stack traces on arbitrary exceptions.More information on this systembelow.
#include<cpptrace/from_current.hpp>#include<iostream>#include<stdexcept>voidfoo() {throwstd::runtime_error("foo failed");}intmain() { CPPTRACE_TRY {foo(); }CPPTRACE_CATCH(const std::exception& e) { std::cerr<<"Exception:"<<e.what()<<std::endl;cpptrace::from_current_exception().print(); }}
Cpptrace also provides a handful of traced exception objects that store stack traces when thrown. This is useful whenthe exceptions might not be caught byCPPTRACE_CATCH:
#include<cpptrace/cpptrace.hpp>voidtrace() {throwcpptrace::logic_error("This wasn't supposed to happen!");}
Additional notable features:
- Utilities for demangling
- Utilities for catching
std::exceptions and wrapping them in traced exceptions - Signal-safe stack tracing
- As far as I can tell cpptrace is the only library which can truly do this in a signal-safe manner
- Source code snippets in traces
- Extensive configuration options fortrace formatting and pretty-printing
include(FetchContent)FetchContent_Declare( cpptrace GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git GIT_TAG v1.0.4# <HASH or TAG>)FetchContent_MakeAvailable(cpptrace)target_link_libraries(your_target cpptrace::cpptrace)# Needed for shared library builds on windows: copy cpptrace.dll to the same directory as the# executable for your_targetif(WIN32) add_custom_command(TARGET your_target POST_BUILDCOMMAND${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:cpptrace::cpptrace> $<TARGET_FILE_DIR:your_target> )endif()
Be sure to configure with-DCMAKE_BUILD_TYPE=Debug or-DCMAKE_BUILD_TYPE=RelWithDebInfo for symbols and lineinformation.
On macOS it is recommended to generate a.dSYM file, seePlatform Logistics below.
For other ways to use the library, such as through package managers, a system-wide installation, or on a platformwithout internet access seeHow to Include The Library below.
Important
Debug info (-g//Z7//Zi//DEBUG/-DBUILD_TYPE=Debug/-DBUILD_TYPE=RelWithDebInfo) is required for completetrace information.
cpptrace::generate_trace() can be used to generate astacktrace object at the current call site. Resolved frames canbe accessed from this object with.frames and the trace can be printed with.print(). Cpptrace also provides amethod to get light-weight raw traces withcpptrace::generate_raw_trace(), which are just vectors of program counters,which can be resolved at a later time.
All functions are thread-safe unless otherwise noted.
The core resolved stack trace object. Generate a trace withcpptrace::generate_trace() orcpptrace::stacktrace::current(). On top of a set of helper functionsstruct stacktrace allowsdirect access to frames as well as iterators.
cpptrace::stacktrace::print can be used to print a stacktrace.cpptrace::stacktrace::print_with_snippets can be usedto print a stack trace with source code snippets.
namespacecpptrace {// Some type sufficient for an instruction pointer, currently always an alias to std::uintptr_tusing frame_ptr = std::uintptr_t;structstacktrace_frame { frame_ptr raw_address;// address in memory frame_ptr object_address;// address in the object file// nullable<T> represents a nullable integer. More docs later. nullable<std::uint32_t> line; nullable<std::uint32_t> column; std::string filename; std::string symbol;bool is_inline;booloperator==(const stacktrace_frame& other)const;booloperator!=(const stacktrace_frame& other)const; object_frameget_object_info()const;// object_address is stored but if the object_path is needed this can be used std::stringto_string()const;/* operator<<(ostream, ..) and std::format support exist for this object*/ };structstacktrace { std::vector<stacktrace_frame> frames;// here as a drop-in for std::stacktracestatic stacktracecurrent(std::size_t skip =0);static stacktracecurrent(std::size_t skip, std::size_t max_depth);voidprint()const;voidprint(std::ostream& stream)const;voidprint(std::ostream& stream,bool color)const;voidprint_with_snippets()const;voidprint_with_snippets(std::ostream& stream)const;voidprint_with_snippets(std::ostream& stream,bool color)const; std::stringto_string(bool color =false)const;voidclear();boolempty()constnoexcept;/* operator<<(ostream, ..), std::format support, and iterators exist for this object*/ }; stacktracegenerate_trace(std::size_t skip =0); stacktracegenerate_trace(std::size_t skip, std::size_t max_depth);}
Object traces contain the most basic information needed to construct a stack trace outside the currently runningexecutable. It contains the raw address, the address in the binary (ASLR and the object file's memory space and whatnotis resolved), and the path to the object the instruction pointer is located in.
namespacecpptrace {structobject_frame { std::string object_path; frame_ptr raw_address; frame_ptr object_address; };structobject_trace { std::vector<object_frame> frames;static object_tracecurrent(std::size_t skip =0);static object_tracecurrent(std::size_t skip, std::size_t max_depth); stacktraceresolve()const;voidclear();boolempty()constnoexcept;/* iterators exist for this object*/ }; object_tracegenerate_object_trace(std::size_t skip =0); object_tracegenerate_object_trace(std::size_t skip, std::size_t max_depth);}
Raw trace access: A vector of program counters. These are ideal for fast and cheap traces you want to resolve later.
Note it is important executables and shared libraries in memory aren't somehow unmapped otherwise libdl calls (andGetModuleFileName in windows) will fail to figure out where the program counter corresponds to.
namespacecpptrace {structraw_trace { std::vector<frame_ptr> frames;static raw_tracecurrent(std::size_t skip =0);static raw_tracecurrent(std::size_t skip, std::size_t max_depth); object_traceresolve_object_trace()const; stacktraceresolve()const;voidclear();boolempty()constnoexcept;/* iterators exist for this object*/ }; raw_tracegenerate_raw_trace(std::size_t skip =0); raw_tracegenerate_raw_trace(std::size_t skip, std::size_t max_depth);}
cpptrace::demangle is a helper function for name demangling, since it has to implement that helper internally anyways.
cpptrace::basename is a helper for custom formatters that extracts a base file name from a path.
cpptrace::prettify_symbol is a helper for custom formatters that applies a number of transformations to clean up longsymbol names. For example, it turnsstd::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >intostd::string.
cpptrace::prune_symbol is a helper for custom formatters that prunes demangled symbols by removing return types,template arguments, and function parameters. It also does some minimal normalization. For example, it prunesns::S<int, float>::~S() tons::S::~S. If cpptrace is unable to parse the symbol it will return the original symbol.
cpptrace::get_snippet gets a text snippet, if possible, from for the given source file for +/-context_size linesaroundline.
cpptrace::isatty and the fileno definitions are useful for deciding whether to use color when printing stack traces.
cpptrace::register_terminate_handler() is a helper function to set a customstd::terminate handler that prints astack trace from a cpptrace exception (more info below) and otherwise behaves like the normal terminate handler.
namespacecpptrace { std::stringdemangle(const std::string& name); std::stringbasename(const std::string& path); std::stringprettify_symbol(std::string symbol); std::stringprune_symbol(const std::string& symbol); std::stringget_snippet(const std::string& path, std::size_t line, std::size_t context_size,bool color =false ); std::stringget_snippet(const std::string& path, std::size_t line, nullable<std::uint32_t> column, std::size_t context_size,bool color =false );boolisatty(int fd);externconstint stdin_fileno;externconstint stderr_fileno;externconstint stdout_fileno;voidregister_terminate_handler();}
Cpptrace provides a configurable formatter for stack trace printing which supports some common options. Formatters areconfigured with a sort of builder pattern, e.g.:
auto formatter = cpptrace::formatter{} .header("Stack trace:") .addresses(cpptrace::formatter::address_mode::object) .snippets(true);
This API is available through the<cpptrace/formatting.hpp> header.
Synopsis:
namespacecpptrace {classformatter { formatter&header(std::string);enumclasscolor_mode { always, none, automatic }; formatter&colors(color_mode);enumclassaddress_mode { raw, object, none }; formatter&addresses(address_mode);enumclasspath_mode { full, basename }; formatter&paths(path_mode); formatter&snippets(bool); formatter&snippet_context(int); formatter&columns(bool);enumclasssymbol_mode { full, pretty, pruned }; formatter&symbols(symbol_mode); formatter&filtered_frame_placeholders(bool); formatter&filter(std::function<bool(const stacktrace_frame&)>); formatter&transform(std::function<stacktrace_frame(stacktrace_frame)>); formatter&break_before_filename(bool do_break =true); formatter&hide_exception_machinery(bool do_hide =true); std::stringformat(const stacktrace_frame&)const; std::stringformat(const stacktrace_frame&,bool color)const; std::stringformat(const stacktrace&)const; std::stringformat(const stacktrace&,bool color)const;voidprint(const stacktrace_frame&)const;voidprint(const stacktrace_frame&,bool color)const;voidprint(std::ostream&,const stacktrace_frame&)const;voidprint(std::ostream&,const stacktrace_frame&,bool color)const;voidprint(std::FILE*,const stacktrace_frame&)const;voidprint(std::FILE*,const stacktrace_frame&,bool color)const;voidprint(const stacktrace&)const;voidprint(const stacktrace&,bool color)const;voidprint(std::ostream&,const stacktrace&)const;voidprint(std::ostream&,const stacktrace&,bool color)const;voidprint(std::FILE*,const stacktrace&)const;voidprint(std::FILE*,const stacktrace&,bool color)const; };}
Options:
| Setting | Description | Default |
|---|---|---|
header | Header line printed before the trace | Stack trace (most recent call first): |
colors | Default color mode for the trace | automatic, which attempts to detect if the target stream is a terminal |
addresses | Raw addresses, object addresses, or no addresses | raw |
paths | Full paths or just filenames | full |
snippets | Whether to include source code snippets | false |
snippet_context | How many lines of source context to show in a snippet | 2 |
columns | Whether to include column numbers if present | true |
symbols | Full demangled symbols, pruned symbol names, or prettified symbols | full |
filtered_frame_placeholders | Whether to still print filtered frames as just#n (filtered) | true |
filter | A predicate to filter frames with | None |
transform | A transformer which takes a stacktrace frame and modifies it | None |
break_before_filename | Print symbol and line source location on different lines | false |
hide_exception_machinery | Hide exception internals for current exception traces | true |
Theautomatic color mode attempts to detect if a stream that may be attached to a terminal. As such, it will not usecolors for theformatter::format method and it may not be able to detect if some ostreams correspond to terminals ornot. For this reason,formatter::format andformatter::print methods have overloads taking a color parameter. Thiscolor parameter will override configured color mode.
Thesymbols option provides a few settings for pretty-printing symbol names:
symbol_mode::fulldefault, uses the full demangled namesymbol_mode::prettyapplies a number of transformations to clean up long symbol names. For example, it turnsstd::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >intostd::string. This isequivalent tocpptrace::prettify_symbol.symbol_mode::prunedprunes demangled symbols by removing return types, template arguments, and function parameters.It also does some minimal normalization. For example, it prunesns::S<int, float>::~S()tons::S::~S. If cpptraceis unable to parse the symbol it will use the full symbol. This is equivalent tocpptrace::prune_symbol.
Recommended practice with formatters: It's generally preferable to create formatters objects that are long-lived ratherthan to create them on the fly every time a trace needs to be formatted.
Cpptrace provides access to a formatter with default settings withget_default_formatter:
namespacecpptrace {const formatter&get_default_formatter();}
A transform function can be specified for the formatter. This function is called before the configuredfilter ischecked. For example:
auto formatter = cpptrace::formatter{} .transform([](cpptrace::stacktrace_frame frame) { frame.symbol =replace_all(frame,"std::__cxx11::","std::");return frame; });
cpptrace::absorb_trace_exceptions: Configure whether the library silently absorbs internal exceptions and continues.Default is true.
cpptrace::enable_inlined_call_resolution: Configure whether the library will attempt to resolve inlined callinformation for release builds. Default is true.
cpptrace::experimental::set_cache_mode: Control time-memory tradeoffs within the library. By default speed isprioritized. If using this function, set the cache mode at the very start of your program before any traces areperformed.
namespacecpptrace {voidabsorb_trace_exceptions(bool absorb);voidenable_inlined_call_resolution(bool enable);enumclasscache_mode {// Only minimal lookup tables prioritize_memory,// Build lookup tables but don't keep them around between trace calls hybrid,// Build lookup tables as needed prioritize_speed };namespaceexperimental {voidset_cache_mode(cache_mode mode); }}
Cpptrace attempts to gracefully recover from any internal errors in order to provide the best information it can and notinterfere with user applications. However, sometimes it's important to see what's going wrong inside cpptrace ifanything does go wrong. To facilitate this, cpptrace has an internal logger. By default it doesn't log anything out. Thefollowing configurations that can be used to set a custom logging callback or enable logging to stderr:
namespacecpptrace {enumclasslog_level { debug, info, warning, error };voidset_log_level(log_level level);voidset_log_callback(std::function<void(log_level,constchar*)>);voiduse_default_stderr_logger();}
cpptrace::set_log_level: Set cpptrace's internal log level. Default:error. Cpptrace currently only uses this loglevel internally.
cpptrace::set_log_callback: Set the callback cpptrace uses for logging messages, useful for custom loggers.
cpptrace::use_default_stderr_logger: Set's the logging callback to print to stderr.
Cpptrace providesCPPTRACE_TRY andCPPTRACE_CATCH macros that allow a stack trace to be collected from the currentthrown exception object, with no overhead in the non-throwing (happy) path:
#include<cpptrace/from_current.hpp>#include<iostream>voidfoo() {throwstd::runtime_error("foo failed");}intmain() { CPPTRACE_TRY {foo(); }CPPTRACE_CATCH(const std::exception& e) { std::cerr<<"Exception:"<<e.what()<<std::endl;cpptrace::from_current_exception().print(); }}
This functionality is entirely opt-in, to access this use#include <cpptrace/from_current.hpp>.
Any declaratorcatch accepts works withCPPTRACE_CATCH, including.... This works with any thrown object, not juststd::exceptions. It even works withthrow 0;!
API functions:
cpptrace::raw_trace_from_current_exception: Returnsconst raw_trace&from the current exception.cpptrace::from_current_exception: Returns a resolvedconst stacktrace&from the current exception. Invalidatesreferences to traces returned bycpptrace::raw_trace_from_current_exception.
In order to provide stack traces, cpptrace has to do some magic to be able to intercept C++ exception handling internalsbefore the stack is unwound. For a simpletry/catch,CPPTRACE_TRY/CPPTRACE_CATCH macros can be used. For atry/catch that has multiple handlers,cpptrace::try_catch can be used. I wish I could make a macro work, however,for multiple handlers this is the best way for cpptrace to inject the appropriate magic. E.g.:
cpptrace::try_catch( [&] {// try blockfoo(); }, [&] (const std::runtime_error& e) { std::cerr<<"Runtime error:"<<e.what()<<std::endl;cpptrace::from_current_exception().print(); }, [&] (const std::exception& e) { std::cerr<<"Exception:"<<e.what()<<std::endl;cpptrace::from_current_exception().print(); }, [&] () {// serves the same role as `catch(...)`, an any exception handler std::cerr<<"Unknown exception occurred:"<<std::endl;cpptrace::from_current_exception().print(); });
Note: The current exception is the exception most recently seen by a cpptrace try-catch macro block.
CPPTRACE_TRY {throwstd::runtime_error("foo");} CPPTRACE_CATCH(const std::exception& e) {cpptrace::from_current_exception().print();// the trace for std::runtime_error("foo") CPPTRACE_TRY {throwstd::runtime_error("bar"); }CPPTRACE_CATCH(const std::exception& e) {cpptrace::from_current_exception().print();// the trace for std::runtime_error("bar") }cpptrace::from_current_exception().print();// the trace for std::runtime_error("bar"), again}Note: Internally the trace contains some extra frames for calls like__cxa_throw,_UnwindRaiseException, etc. Theseare filtered out during stacktrace printing but they will be present if you manually inspect the vector of stacktraceframes.
Important
There is an unfortunate limitation withreturn statements in these try/catch macros: The implementation on Windowsrequires wrapping the try body in an immediately-invoked lambda and as suchreturn statements would return from thelambda not the enclosing function. Cpptrace guards against misleadingreturns compiling by requiring the lambdas toreturn a special internal type, but, if you're writing code that will be compiled on windows it's important to notwritereturn statements within CPPTRACE_TRY. For example, this is invalid:
CPPTRACE_TRY {if(condition)return40;// error, type int doesn't match cpptrace::detail::dont_return_from_try_catch_macros} CPPTRACE_CATCH(const std::exception& e) { ...}Important
There is a footgun which is mainly relevant for code that was written on an older version of cpptrace: It's possibleto write the following without getting errors
CPPTRACE_TRY { ...} CPPTRACE_CATCH(const std::runtime_error& e) { ...}catch(const std::exception& e) { ...}This code will compile and the second catch handler will work, however, cpptrace won't know about the handler and assuch it won't be able to correctly collect a trace when a type that does not matchstd::runtime_error is thrown. Norun-time errors will occur, however,from_current_exception will report a misleading trace.
CPPTRACE_TRY is a little cumbersome to type. To remove theCPPTRACE_ prefix you can use theCPPTRACE_UNPREFIXED_TRY_CATCH cmake option or theCPPTRACE_UNPREFIXED_TRY_CATCH preprocessor definition:
TRY {foo();} CATCH(const std::exception& e) { std::cerr<<"Exception:"<<e.what()<<std::endl;cpptrace::from_current_exception().print();}This is not done by default for macro safety/hygiene reasons. If you do not wantTRY/CATCH macros defined, as theyare common macro names, you can easily modify the following snippet to provide your own aliases:
#defineTRY CPPTRACE_TRY#defineCATCH(param) CPPTRACE_CATCH(param)
C++ does not provide any language support for collecting stack traces when exceptions are thrown, however, exceptionhandling under both the Itanium ABI and by SEH (used to implement C++ exceptions on Windows) involves unwinding thestack twice. The first unwind searches for an appropriatecatch handler, the second actually unwinds the stack andcalls destructors. Since the stack remains intact during the search phase it's possible to collect a stack trace withlittle to no overhead when thecatch is considered for matching the exception. The try/catch macros for cpptrace setup a special try/catch system that can collect a stack trace when considered during a search phase.
On Windows, cpptrace's try/catch macros expand along the lines of:
| Source | Expansion |
CPPTRACE_TRY {foo();} CPPTRACE_CATCH(const std::exception& e) { ...} | try { [&]() { __try { [&]() {foo(); }(); }__except(exception_filter<const std::exception&>(GetExceptionInformation() )) {} }();}catch(const std::exception& e) { ...} |
SEH's design actually makes it fairly easy to run code during the search phase. The exception filter will collect atrace if it detects the catch will match. Unfortunately, MSVC does not allow mixing C++try/catch and SEH__try/__except in the same function so a double-IILE is needed. This has implications for returning from try blocks.
On systems which use the Itanium ABI (linux, mac, etc), cpptrace's try/catch macros expand along the lines of:
| Source | Expansion |
CPPTRACE_TRY {foo();} CPPTRACE_CATCH(const std::exception& e) { ...} | try {try {foo(); }catch(const unwind_interceptor_for<const std::exception&>&) {...}}catch(const std::exception& e) { ...} |
Cpptrace does some magic to hook vtables ofunwind_interceptor_for<T> type_info objects during static-init time.
N.b.: This mechanism is also discussed inP2490R3.
The performance impact in the non-throwing happy path is zero (or as close to zero as practical) on modernarchitectures.
In the unhappy throwing path, a little more work may be done during the search phase to consider handlers cpptraceinserts but this is low-impact. Generating the trace itself is fast: Cpptrace collects a raw trace during exceptionhandling and it is resolved only when requested. In my benchmarking I have found generation of raw traces to take on theorder of100ns per frame.
On some older architectures/ABIs (e.g., 32-bit windows),try/catch itself has some overhead due to how it isimplemented with SEH. Cpptrace'stry/catch macro adds one extra layer of handler which may be relevant on suchsystems but should not be a problem outside of hot loops, where using anytry/catch is presumably already a problemon such architectures.
By defaultcpptrace::from_current_exception will correspond to a trace for the lastthrow intercepted by aCPPTRACE_CATCH. In order to rethrow an exception while preserving the original trace,cpptrace::rethrow() can beused.
namespacecpptrace {voidrethrow();voidrethrow(std::exception_ptr exception = std::current_exception());}
Note
It's important to usecpptrace::rethrow() from within aCPPTRACE_CATCH. If it is not, then no trace for theexception origin will have been collected.
Example:
voidbar() {throwstd::runtime_error("critical error in bar");}voidfoo() { CPPTRACE_TRY {bar(); }CPPTRACE_CATCH(const std::exception& e) { std::cerr<<"Exception in foo:"<<e.what()<<std::endl;cpptrace::rethrow(); }}intmain() { CPPTRACE_TRY {foo(); }CPPTRACE_CATCH(const std::exception& e) { std::cerr<<"Exception encountered while running foo:"<<e.what()<<std::endl;cpptrace::from_current_exception().print();// prints trace containing main -> foo -> bar }}
Sometimes it may be desirable to see both the trace for the exception's origin as well as the trace for where it wasrethrown. Cpptrace provides an interface for getting the last rethrow location:
namespacecpptrace {const raw_trace&raw_trace_from_current_exception_rethrow();const stacktrace&from_current_exception_rethrow();boolcurrent_exception_was_rethrown();}
If the current exception was not rethrown, these functions return references to empty traces.current_exception_was_rethrown can be used to check if the current exception was rethrown and a non-empty rethrowtrace exists.
Example usage, utilizingfoo andbar from the above example:
intmain() { CPPTRACE_TRY {foo(); }CPPTRACE_CATCH(const std::exception& e) { std::cerr<<"Exception encountered while running foo:"<<e.what()<<std::endl; std::cerr<<"Thrown from:"<<std::endl;cpptrace::from_current_exception().print();// trace containing main -> foo -> bar std::cerr<<"Rethrown from:"<<std::endl;cpptrace::from_current_exception_rethrow().print();// trace containing main -> foo }}
As mentioned above, in order to facilitatetry/catch blocks with multiple handlers while still being able to performthe magic necessary to collect stack traces on exceptions, cpptrace provides acpptrace::try_catch utility that cantake multiple handlers:
cpptrace::try_catch( [&] {// try blockfoo(); }, [&] (const std::runtime_error& e) { std::cerr<<"Runtime error:"<<e.what()<<std::endl;cpptrace::from_current_exception().print(); }, [&] (const std::exception& e) { std::cerr<<"Exception:"<<e.what()<<std::endl;cpptrace::from_current_exception().print(); }, [&] () {// serves the same role as `catch(...)`, an any exception handler std::cerr<<"Unknown exception occurred:"<<std::endl;cpptrace::from_current_exception().print(); });
The synopsis for this utility is:
namespacecpptrace {template<typename F,typename... Catches>voidtry_catch(F&& f, Catches&&... catches);}
Similar to a languagetry/catch,catch handlers will be considered in the order they are listed. Handlers shouldtake exactly one argument, equivalent to what would be written for a catch handler, except forcatch(...) which can beachieved by a handler taking no arguments.
Similar to the above section on collectingtraces from C++ exceptions,cpptrace providesCPPTRACE_SEH_TRY andCPPTRACE_SEH_EXCEPT macros that collect traces from SEH exceptions on windowswith no overhead in the non-throwing (happy) path:
#include<cpptrace/from_current.hpp>#include<iostream>#include<windows.h>voidfoo(int x,int y) {return x / y;}intdivide_zero_filter(int code) {if(code == STATUS_INTEGER_DIVIDE_BY_ZERO || code == EXCEPTION_FLT_DIVIDE_BY_ZERO) {return EXCEPTION_EXECUTE_HANDLER; }return EXCEPTION_CONTINUE_SEARCH;}intmain() { CPPTRACE_SEH_TRY {foo(10,0); }CPPTRACE_SEH_EXCEPT(divide_zero_filter(GetExceptionCode())) { std::cerr<<"Division by zero happened!"<<std::endl;cpptrace::from_current_exception().print(); }}
TheCPPTRACE_SEH_EXCEPT macro takes a filter expression as input, any expression valid in__except is valid.
Cpptrace provides a handful of traced exception classes which automatically collect stack traces when thrown. Theseare useful when throwing exceptions that may not be caught byCPPTRACE_CATCH.
The base traced exception class iscpptrace::exception and cpptrace provides a handful of helper classes for workingwith traced exceptions. These exceptions generate relatively lightweight raw traces and resolve symbols and line numberslazily if and when requested.
These are provided both as a useful utility and as a reference implementation for traced exceptions.
The basic interface is:
namespacecpptrace {classexception :publicstd::exception {public:virtualconstchar*what()constnoexcept = 0;// The what string both the message and tracevirtualconstchar*message()constnoexcept = 0;virtualconst stacktrace&trace()constnoexcept = 0; };}
There are two ways to go about traced exception objects: Traces can be resolved eagerly or lazily. Cpptrace provides thebasic implementation of exceptions as lazy exceptions. I hate to have anything about the implementation exposed in theinterface or type system but this seems to be the best way to do this.
namespacecpptrace {classlazy_exception :publicexception {// lazy_trace_holder is basically a std::variant<raw_trace, stacktrace>, more docs latermutable detail::lazy_trace_holder trace_holder;mutable std::string what_string;public:explicitlazy_exception( raw_trace&& trace = detail::get_raw_trace_and_absorb() )noexcept : trace_holder(std::move(trace)) {}constchar*what()constnoexceptoverride;constchar*message()constnoexceptoverride;const stacktrace&trace()constnoexceptoverride; };}
cpptrace::lazy_exception can be freely thrown or overridden. Generallymessage() is the only field to override.
Lastly cpptrace provides an exception class that takes a user-provided message,cpptrace::exception_with_message, aswell as a number of traced exception classes resembling<stdexcept>:
namespacecpptrace {classexception_with_message :publiclazy_exception {mutable std::string user_message;public:explicitexception_with_message( std::string&& message_arg, raw_trace&& trace = detail::get_raw_trace_and_absorb() )noexcept : lazy_exception(std::move(trace)), user_message(std::move(message_arg)) {}constchar*message()constnoexceptoverride; };// All stdexcept errors have analogs here. All but system_error have the constructor:// explicit the_error(// std::string&& message_arg,// raw_trace&& trace = detail::get_raw_trace_and_absorb()// ) noexcept// : exception_with_message(std::move(message_arg), std::move(trace)) {}classlogic_error :publicexception_with_message { ... };classdomain_error :publicexception_with_message { ... };classinvalid_argument :publicexception_with_message { ... };classlength_error :publicexception_with_message { ... };classout_of_range :publicexception_with_message { ... };classruntime_error :publicexception_with_message { ... };classrange_error :publicexception_with_message { ... };classoverflow_error :publicexception_with_message { ... };classunderflow_error :publicexception_with_message { ... };classsystem_error :publicruntime_error {public:explicitsystem_error(int error_code, std::string&& message_arg, raw_trace&& trace = detail::get_raw_trace_and_absorb() )noexcept;const std::error_code&code()constnoexcept; };}
Note
This section is largely obsolete now that cpptrace provides a better mechanism for collectingtraces from exceptions
Cpptrace exceptions can provide great information for user-controlled exceptions. For non-cpptrace::exceptions that mayoriginate outside of code you control, e.g. the standard library, cpptrace provides some wrapper utilities that canrethrow these exceptions nested in traced cpptrace exceptions. The trace won't be perfect, the trace will start wherethe wrapper caught it, but these utilities can provide good diagnostic information. Unfortunately this is the bestsolution for this problem, as far as I know.
std::vector<int> foo = {1,2,3};CPPTRACE_WRAP_BLOCK( foo.at(4) = 2; foo.at(5)++;);std::cout<<CPPTRACE_WRAP(foo.at(12))<<std::endl;
Note
This section pertains to cpptrace traced exception objects and not the mechanism for collectingtraces from arbitrary exceptions
Working with cpptrace exceptions in your code:
try {foo();}catch(cpptrace::exception& e) {// Prints the exception info and stack trace, conditionally enabling color codes depending on// whether stderr is a terminal std::cerr <<"Error:" << e.message() <<'\n'; e.trace().print(std::cerr,cpptrace::isatty(cpptrace::stderr_fileno));}catch(std::exception& e) { std::cerr <<"Error:" << e.what() <<'\n';}
Cpptrace provides a customstd::terminate handler that prints stacktraces while otherwise behaving like the normalstd::terminate handler. If a cpptrace exception object reachesstd::terminate the trace from that exception isprinted, otherwise a stack trace is generated at the point of the terminate handler. Oftenstd::terminate is calleddirectly without unwinding so the trace is preserved.
To register this custom handler:
cpptrace::register_terminate_handler();Stack traces from signal handlers can provide very helpful information for debugging application crashes, e.g. fromSIGSEGV or SIGTRAP handlers. Signal handlers are really restrictive environments as your application could beinterrupted by a signal at any point, including in the middle of malloc or buffered IO or while holding a lock.Doing a stack trace in a signal handler is possible but it requires a lot of care. This is difficult to do correctlyand most examples online do this incorrectly.
Cpptrace offers an API to walk the stack in a signal handler and produce a raw trace safely. The library also providesan interface for producing a object frame safely:
namespacecpptrace { std::size_tsafe_generate_raw_trace(frame_ptr* buffer, std::size_t size, std::size_t skip =0); std::size_tsafe_generate_raw_trace(frame_ptr* buffer, std::size_t size, std::size_t skip, std::size_t max_depth);structsafe_object_frame { frame_ptr raw_address; frame_ptr address_relative_to_object_start;char object_path[CPPTRACE_PATH_MAX +1]; object_frameresolve()const;// To be called outside a signal handler. Not signal safe. };voidget_safe_object_frame(frame_ptr address, safe_object_frame* out);boolcan_signal_safe_unwind();boolcan_get_safe_object_frame();}
It is not possible to resolve debug symbols safely in the process from a signal handler without heroic effort. In orderto produce a full trace there are three options:
- Carefully save the object trace information to be resolved at a later time outside the signal handler
- Write the object trace information to a file to be resolved later
- Spawn a new process, communicate object trace information to that process, and have that process do the traceresolution
For traces on segfaults, e.g., only options 2 and 3 are viable. For more information an implementation of approach 3,see the comprehensive overview and demo atsignal-safe-tracing.md.
Important
Currently signal-safe stack unwinding is only possible withlibunwind, which must bemanually enabled. If signal-safe unwinding isn't supported,safe_generate_raw_trace will justproduce an empty trace.can_signal_safe_unwind can be used to check for signal-safe unwinding support andcan_get_safe_object_frame can be used to checkget_safe_object_frame support. If object information can't beresolved in a signal-safe way thenget_safe_object_frame will not populate fields beyond theraw_address.
Important
_dl_find_object is required for signal-safe stack tracing. This is a relatively recent addition to glibc, added inglibc 2.35.
Caution
Calls to shared objects can be lazy-loaded where the first call to the shared object invokes non-signal-safe functionssuch asmalloc(). To avoid this, call these routines inmain() ahead of a signal handler to "warm up" the library.
A couple utility types are used to provide the library with a good interface.
nullable<T> is used for a nullable integer type. Internally the maximum value forT is used as asentinel.std::optional would be used if this library weren't c++11. But,nullable<T> providesanstd::optional-like interface and it's less heavy-duty for this use than anstd::optional.
detail::lazy_trace_holder is a utility type forlazy_exception used in place of anstd::variant<raw_trace, stacktrace>.
namespacecpptrace {template<typename T,typename std::enable_if<std::is_integral<T>::value,int>::type =0>structnullable { T raw_value;// all members are constexpr for c++17 and beyond, some are constexpr before c++17 nullable&operator=(T value)boolhas_value()constnoexcept; T&value()noexcept;const T&value()constnoexcept; Tvalue_or(T alternative)constnoexcept;voidswap(nullable& other)noexcept;voidreset()noexcept;booloperator==(const nullable& other)constnoexcept;booloperator!=(const nullable& other)constnoexcept;constexprstatic Tnull_value()noexcept;// returns the raw null valueconstexprstatic nullablenull()noexcept;// returns a null instance };namespacedetail {classlazy_trace_holder {bool resolved;union { raw_trace trace; stacktrace resolved_trace; };public:// constructorslazy_trace_holder() : trace() {}explicitlazy_trace_holder(raw_trace&& _trace);explicitlazy_trace_holder(stacktrace&& _resolved_trace);// logisticslazy_trace_holder(const lazy_trace_holder& other);lazy_trace_holder(lazy_trace_holder&& other)noexcept; lazy_trace_holder&operator=(const lazy_trace_holder& other); lazy_trace_holder&operator=(lazy_trace_holder&& other)noexcept;~lazy_trace_holder();// accessconst raw_trace&get_raw_trace()const; stacktrace&get_resolved_trace();const stacktrace&get_resolved_trace()const;// throws if not already resolvedboolis_resolved()const;private:voidclear(); }; }}
Cpptrace provides a handful of headers to make inclusion more minimal.
| Header | Contents |
|---|---|
cpptrace/forward.hpp | cpptrace::frame_ptr and a few trace class forward declarations |
cpptrace/basic.hpp | Definitions for trace classes and the basic tracing APIs (Stack Traces,Object Traces,Raw Traces, andSignal-Safe Tracing) |
cpptrace/exceptions.hpp | Traced Exception Objects and related utilities (Wrapping std::exceptions) |
cpptrace/from_current.hpp | Traces From All Exceptions |
cpptrace/io.hpp | operator<< overloads forstd::ostream andstd::formatters |
cpptrace/formatting.hpp | Configurable formatter API |
cpptrace/utils.hpp | Utility functions, configuration functions, and terminate utilities (Utilities,Configuration, andTerminate Handling) |
cpptrace/version.hpp | Library version macros |
cpptrace/gdb_jit.hpp | Provides a special utility related toJIT support |
The main cpptrace header iscpptrace/cpptrace.hpp which includes everything other thanfrom_current.hpp andversion.hpp.
For extraordinarily large binaries (multiple gigabytes), cpptrace's internal caching can result in a lot of memoryusage. Cpptrace provides some options to reduce memory usage in exchange for performance in memory-constrainedapplications.
Synopsis:
namespacecpptrace {namespaceexperimental {voidset_dwarf_resolver_line_table_cache_size(nullable<std::size_t> max_entries);voidset_dwarf_resolver_disable_aranges(bool disable); }}
Explanation:
set_dwarf_resolver_line_table_cache_sizecan be used to set a limit to the cache size with evictions done LRU.Cpptrace loads and caches line tables for dwarf compile units. These can take a lot of space for large binaries withlots of debug info. Passingnullable<std::size_t>::null()will disable the cache size (which is the defaultbehavior).set_dwarf_resolver_disable_arangescan be used to disable use of dwarf.debug_aranges, an accelerated range lookuptable for compile units emitted by many compilers. Cpptrace uses these by default if they are present since they canspeed up resolution, however, they can also result in significant memory usage.
Cpptrace has support for resolving symbols from frames in JIT-compiled code. To do this, cpptrace relies on in-memoryobject files (elf on linux or mach-o on mac) that contain symbol tables and dwarf debug information. The main reason forthis is many JIT implementations already produce these for debugger support.
These in-memory object files must be set up in such a way that the symbol table and debug symbol addresses match therun-time addresses of the JIT code.
The basic interface for informing cpptrace about these in-memory object files is as follows:
namespacecpptrace {voidregister_jit_object(constchar*, std::size_t);voidunregister_jit_object(constchar*);voidclear_all_jit_objects();}
Many JIT implementations follow the GDBJIT Compilation Interface so that JIT code can be debugged. Theinterface, at a high level, entails adding in-memory object files to a linked list of object files that GDB and otherdebuggers can reference (stored in the__jit_debug_descriptor). Cpptrace provides, as a utility, a mechanism forloading all in-memory object files present in the__jit_debug_descriptor linked list via<cpptrace/gdb_jit.hpp>:
namespacecpptrace {namespaceexperimental {voidregister_jit_objects_from_gdb_jit_interface(); }}
Note: Your program must be able to link against a global C symbol__jit_debug_descriptor.
Note: Callingcpptrace::experimental::register_jit_objects_from_gdb_jit_interface clears all jit objects previouslyregistered with cpptrace.
This section only applies to the dbghelp backend (CPPTRACE_GET_SYMBOLS_WITH_DBGHELP) on Windows.
When loading a DLL at runtime withLoadLibrary after a stacktrace has already been generated,symbols from that library may not be resolved correctly for subsequent stacktraces. To fix this,callcpptrace::load_symbols_for_file with the same path that was passed toLoadLibrary.
HMODULE hModule = LoadLibrary("mydll.dll");if (hModule) {cpptrace::load_symbols_for_file("mydll.dll");}
For backends other than dbghelp,load_symbols_for_file does nothing. For platforms other thanWindows, it is not declared.
namespacecpptrace {voidload_symbols_for_file(const std::string& filename);}
Since cpptrace v1.0.0, the library uses an inline ABI versioning namespace and all symbols part of the public interfaceare secretly under the namespacecpptrace::v1. This is done to allow for potential future library evolution in anABI-friendly manner.
| Format | Supported |
|---|---|
| DWARF in binary | ✔️ |
| GNU debug link | ️️✔️ |
| Split dwarf (debug fission) | ✔️ |
| DWARF in dSYM | ✔️ |
| DWARF via Mach-O debug map | ✔️ |
| Windows debug symbols in PDB | ✔️ |
DWARF5 added DWARF package files. As far as I can tell no compiler implements these yet.
With CMake FetchContent:
include(FetchContent)FetchContent_Declare( cpptrace GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git GIT_TAG v1.0.4# <HASH or TAG>)FetchContent_MakeAvailable(cpptrace)target_link_libraries(your_target cpptrace::cpptrace)
It's as easy as that. Cpptrace will automatically configure itself for your system. Note: On windows and macos someextra work is required, seePlatform Logistics below.
Be sure to configure with-DCMAKE_BUILD_TYPE=Debug or-DCMAKE_BUILD_TYPE=RelWithDebInfo for symbols and lineinformation.
git clone https://github.com/jeremy-rifkin/cpptrace.gitgit checkout v1.0.4mkdir cpptrace/buildcd cpptrace/buildcmake .. -DCMAKE_BUILD_TYPE=Releasemake -jsudo make installUsing through cmake:
find_package(cpptrace REQUIRED)target_link_libraries(<yourtarget> cpptrace::cpptrace)
Be sure to configure with-DCMAKE_BUILD_TYPE=Debug or-DCMAKE_BUILD_TYPE=RelWithDebInfo for symbols and lineinformation.
Or compile with-lcpptrace:
g++ main.cpp -o main -g -Wall -lcpptrace./main
Important
If you aren't using cmake and are linking statically you must manually specify-DCPPTRACE_STATIC_DEFINE.
If you get an error along the lines of
error while loading shared libraries: libcpptrace.so: cannot open shared object file: No such file or directoryYou may have to runsudo /sbin/ldconfig to create any necessary links and update caches so the system can findlibcpptrace.so (I had to do this on Ubuntu). Only when installing system-wide. Usually your package manager does this foryou when installing new libraries.
Note
Libdwarf requires a relatively new version of libdwarf. Sometimes a previously-installed system-wide libdwarf maycause issues due to being too old. Libdwarf 8 and newer is known to work.
System-wide install on windows
git clone https://github.com/jeremy-rifkin/cpptrace.gitgit checkout v1.0.4mkdir cpptrace/buildcd cpptrace/buildcmake ..-DCMAKE_BUILD_TYPE=Releasemsbuild cpptrace.slnmsbuild INSTALL.vcxproj
Note: You'll need to run as an administrator in a developer powershell, or use vcvarsall.bat distributed with visualstudio to get the correct environment variables set.
To install just for the local user (or any custom prefix):
git clone https://github.com/jeremy-rifkin/cpptrace.gitgit checkout v1.0.4mkdir cpptrace/buildcd cpptrace/buildcmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$HOME/wherevermake -jmake install
Using through cmake:
find_package(cpptrace REQUIREDPATHS$ENV{HOME}/wherever)target_link_libraries(<yourtarget> cpptrace::cpptrace)
Using manually:
g++ main.cpp -o main -g -Wall -I$HOME/wherever/include -L$HOME/wherever/lib -lcpptraceImportant
If you aren't using cmake and are linking statically you must manually specify-DCPPTRACE_STATIC_DEFINE.
To use the library without cmake first follow the installation instructions atSystem-Wide Installation,Local User Installation,orPackage Managers.
In addition to any include or library paths you'll need to specify to tell the compiler where cpptrace was installed.The typical dependencies for cpptrace are:
| Compiler | Platform | Dependencies |
|---|---|---|
| gcc, clang, intel, etc. | Linux/macos/unix | -lcpptrace -ldwarf -lz -lzstd -ldl |
| gcc | Windows | -lcpptrace -ldbghelp -ldwarf -lz -lzstd |
| msvc | Windows | cpptrace.lib dbghelp.lib |
| clang | Windows | -lcpptrace -ldbghelp |
Note: Newer libdwarf requires-lzstd, older libdwarf does not.
Important
If you are linking statically, you will additionally need to specify-DCPPTRACE_STATIC_DEFINE.
Dependencies may differ if different back-ends are manually selected.
Some users may prefer, or need to, to install cpptrace without package managers or fetchcontent (e.g. if their systemdoes not have internet access). Below are instructions for how to install libdwarf and cpptrace.
Installation Without Package Managers or FetchContent
Here is an example for how to build cpptrace and libdwarf.~/scratch/cpptrace-test is used as a working directory andthe libraries are installed to~/scratch/cpptrace-test/resources.
mkdir -p~/scratch/cpptrace-test/resourcescd~/scratch/cpptrace-testgit clone https://github.com/facebook/zstd.gitcd zstdgit checkout 63779c798237346c2b245c546c40b72a5a5913fecd build/cmakemkdir buildcd buildcmake .. -DCMAKE_INSTALL_PREFIX=~/scratch/cpptrace-test/resources -DZSTD_BUILD_PROGRAMS=On -DZSTD_BUILD_CONTRIB=On -DZSTD_BUILD_TESTS=On -DZSTD_BUILD_STATIC=On -DZSTD_BUILD_SHARED=On -DZSTD_LEGACY_SUPPORT=Onmake -jmake installcd~/scratch/cpptrace-testgit clone https://github.com/jeremy-rifkin/libdwarf-lite.gitcd libdwarf-litegit checkout 5dfb2cd2aacf2bf473e5bfea79e41289f88b3a5f# 2.1.0mkdir buildcd buildcmake .. -DPIC_ALWAYS=On -DBUILD_DWARFDUMP=Off -DCMAKE_PREFIX_PATH=~/scratch/cpptrace-test/resources -DCMAKE_INSTALL_PREFIX=~/scratch/cpptrace-test/resourcesmake -jmake installcd~/scratch/cpptrace-testgit clone https://github.com/jeremy-rifkin/cpptrace.gitcd cpptracegit checkout v1.0.4mkdir buildcd buildcmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=On -DCPPTRACE_USE_EXTERNAL_LIBDWARF=On -DCMAKE_PREFIX_PATH=~/scratch/cpptrace-test/resources -DCMAKE_INSTALL_PREFIX=~/scratch/cpptrace-test/resourcesmake -jmake install
The~/scratch/cpptrace-test/resources directory also serves as a bundle you can ship with all the installed files forcpptrace and its dependencies.
Cpptrace is available through conan athttps://conan.io/center/recipes/cpptrace.
[requires]cpptrace/1.0.4[generators]CMakeDepsCMakeToolchain[layout]cmake_layout# ...find_package(cpptrace REQUIRED)# ...target_link_libraries(YOUR_TARGET cpptrace::cpptrace)
vcpkg install cpptracefind_package(cpptrace CONFIG REQUIRED)target_link_libraries(mainPRIVATE cpptrace::cpptrace)
Cpptrace supports C++20 modules:import cpptrace;. You'll need a modern toolchain in order to use C++20 modules (i.e.relatively new compilers, cmake, etc).
For features involving macros you will have to#include headers with the macro definitions:
<cpptrace/exceptions_macros.hpp>:CPPTRACE_WRAPandCPPTRACE_WRAP_BLOCK<cpptrace/from_current_macros.hpp>:CPPTRACE_TRY,CPPTRACE_CATCH, etc.
Windows and macOS require a little extra work to get everything in the right place.
Copying the library.dll on Windows:
# Copy the cpptrace.dll on windows to the same directory as the executable for your_target.# Not required if static linking.if(WIN32) add_custom_command(TARGET your_target POST_BUILDCOMMAND${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:cpptrace::cpptrace> $<TARGET_FILE_DIR:your_target> )endif()
On macOS, it is recommended to generate adSYM file containing debug information for your program.This is not required as cpptrace makes a good effort at finding and reading the debug informationwithout this, but having adSYM file is the most robust method.
When using Xcode with CMake, this can be done with:
set_target_properties(your_target PROPERTIESXCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT"dwarf-with-dsym")
Outside of Xcode, this can be done withdsymutil yourbinary:
# Create a .dSYM file on macOSif(APPLE) add_custom_command(TARGET your_target POST_BUILDCOMMAND dsymutil $<TARGET_FILE:your_target> )endif()
Cpptrace supports a number of back-ends to produce stack traces. Stack traces are produced in roughly three steps:Unwinding, symbol resolution, and demangling.
The library's CMake automatically configures itself for what your system supports. The ideal configuration is asfollows:
| Platform | Unwinding | Symbols | Demangling |
|---|---|---|---|
| Linux | _Unwind | libdwarf | cxxabi.h |
| MacOS | _Unwind for gcc, execinfo.h for clang and apple clang | libdwarf | cxxabi.h |
| Windows | StackWalk64 | dbghelp | No demangling needed |
| MinGW | StackWalk64 | libdwarf + dbghelp | cxxabi.h |
Support for these back-ends is the main development focus and they should work well. If you want to use a differentback-end such as addr2line, for example, you can configure the library to do so.
Unwinding
| Library | CMake config | Platforms | Info |
|---|---|---|---|
| libgcc unwind | CPPTRACE_UNWIND_WITH_UNWIND | linux, macos, mingw | Frames are captured with libgcc's_Unwind_Backtrace, which currently produces the most accurate stack traces on gcc/clang/mingw. Libgcc is often linked by default, and llvm has something equivalent. |
| execinfo.h | CPPTRACE_UNWIND_WITH_EXECINFO | linux, macos | Frames are captured withexecinfo.h'sbacktrace, part of libc on linux/unix systems. |
| winapi | CPPTRACE_UNWIND_WITH_WINAPI | windows, mingw | Frames are captured withCaptureStackBackTrace. |
| dbghelp | CPPTRACE_UNWIND_WITH_DBGHELP | windows, mingw | Frames are captured withStackWalk64. |
| libunwind | CPPTRACE_UNWIND_WITH_LIBUNWIND | linux, macos, windows, mingw | Frames are captured withlibunwind.Note: This is the only back-end that requires a library to be installed by the user, and aCMAKE_PREFIX_PATH may also be needed. |
| N/A | CPPTRACE_UNWIND_WITH_NOTHING | all | Unwinding is not done, stack traces will be empty. |
Some back-ends (execinfo andCaptureStackBackTrace) require a fixed buffer has to be created to read addresses intowhile unwinding. By default the buffer can hold addresses for 400 frames (beyond theskip frames). This isconfigurable withCPPTRACE_HARD_MAX_FRAMES.
Symbol resolution
| Library | CMake config | Platforms | Info |
|---|---|---|---|
| libdwarf | CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF | linux, macos, mingw | Libdwarf is the preferred method for symbol resolution for cpptrace. Cpptrace will get it via FetchContent or find_package depending onCPPTRACE_USE_EXTERNAL_LIBDWARF. |
| dbghelp | CPPTRACE_GET_SYMBOLS_WITH_DBGHELP | windows | Dbghelp.h is the preferred method for symbol resolution on windows under msvc/clang and is supported on all windows machines. |
| libbacktrace | CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE | linux, macos*, mingw* | Libbacktrace is already installed on most systems or available through the compiler directly. For clang you must specify the absolute path tobacktrace.h usingCPPTRACE_BACKTRACE_PATH. |
| addr2line | CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE | linux, macos, mingw | Symbols are resolved by invokingaddr2line (oratos on mac) viafork() (on linux/unix, andpopen under mingw). |
| libdl | CPPTRACE_GET_SYMBOLS_WITH_LIBDL | linux, macos | Libdl uses dynamic export information. Compiling with-rdynamic is needed for symbol information to be retrievable. Line numbers won't be retrievable. |
| N/A | CPPTRACE_GET_SYMBOLS_WITH_NOTHING | all | No attempt is made to resolve symbols. |
*: Requires installation
One back-end should be used. For MinGWCPPTRACE_GET_SYMBOLS_WITH_LIBDWARF andCPPTRACE_GET_SYMBOLS_WITH_DBGHELP canbe used in conjunction.
Note for addr2line: By default cmake will resolve an absolute path to addr2line to bake into the library. This path canbe configured withCPPTRACE_ADDR2LINE_PATH, orCPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH can be used to have the librarysearch the system path foraddr2line at runtime. This is not the default to prevent against path injection attacks.
Demangling
Lastly, depending on other back-ends used a demangler back-end may be needed.
| Library | CMake config | Platforms | Info |
|---|---|---|---|
| cxxabi.h | CPPTRACE_DEMANGLE_WITH_CXXABI | Linux, macos, mingw | Should be available everywhere other thanmsvc. |
| dbghelp.h | CPPTRACE_DEMANGLE_WITH_WINAPI | Windows | Demangle withUnDecorateSymbolName. |
| N/A | CPPTRACE_DEMANGLE_WITH_NOTHING | all | Don't attempt to do anything beyond what the symbol resolution back-end does. |
More?
There are plenty more libraries that can be used for unwinding, parsing debug information, and demangling. In the futuremore back-ends can be added. Ideally this library can "just work" on systems, without additional installation work.
Summary of all library configuration options:
Back-ends:
CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF=On/OffCPPTRACE_GET_SYMBOLS_WITH_DBGHELP=On/OffCPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE=On/OffCPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE=On/OffCPPTRACE_GET_SYMBOLS_WITH_LIBDL=On/OffCPPTRACE_GET_SYMBOLS_WITH_NOTHING=On/OffCPPTRACE_UNWIND_WITH_UNWIND=On/OffCPPTRACE_UNWIND_WITH_LIBUNWIND=On/OffCPPTRACE_UNWIND_WITH_EXECINFO=On/OffCPPTRACE_UNWIND_WITH_WINAPI=On/OffCPPTRACE_UNWIND_WITH_DBGHELP=On/OffCPPTRACE_UNWIND_WITH_NOTHING=On/OffCPPTRACE_DEMANGLE_WITH_CXXABI=On/OffCPPTRACE_DEMANGLE_WITH_WINAPI=On/OffCPPTRACE_DEMANGLE_WITH_NOTHING=On/Off
Back-end configuration:
CPPTRACE_BACKTRACE_PATH=<string>: Path to libbacktrace backtrace.h, needed when compiling with clang/CPPTRACE_HARD_MAX_FRAMES=<number>: Some back-ends write to a fixed-size buffer. This is the size of that buffer.Default is400.CPPTRACE_ADDR2LINE_PATH=<string>: Specify the absolute path to the addr2line binary for cpptrace to invoke. Bydefault the config script will search for a binary and use that absolute path (this is to prevent against pathinjection).CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH=On/Off: Specifies whether cpptrace should let the system search the PATHenvironment variable directories for the binary.
Other useful configurations:
CPPTRACE_BUILD_SHARED=On/Off: Override forBUILD_SHARED_LIBS.CPPTRACE_INCLUDES_WITH_SYSTEM=On/Off: Marks cpptrace headers asSYSTEMwhich will hide any warnings that aren'tthe fault of your project. Defaults to On.CPPTRACE_INSTALL_CMAKEDIR: Override for the installation path for the cmake configs.CPPTRACE_USE_EXTERNAL_LIBDWARF=On/Off: Get libdwarf fromfind_packagerather thanFetchContent.CPPTRACE_POSITION_INDEPENDENT_CODE=On/Off: Compile the library as a position independent code (PIE). Defaults to On.CPPTRACE_STD_FORMAT=On/Off: Control inclusion of<format>and provision ofstd::formatterspecializations bycpptrace.hpp. This can also be controlled with the macroCPPTRACE_NO_STD_FORMAT.
Testing:
CPPTRACE_BUILD_TESTINGBuild small demo and test programCPPTRACE_BUILD_TEST_RDYNAMICUse-rdynamicwhen compiling the test program
Cpptrace currently uses integration and functional testing, building and running under every combination of back-endoptions. The implementation is based ongithub actions matrices and driven by python scripts located in theci/ folder. Testing used to be done by github actions matrices directly, however, launching hundreds of twosecond jobs was extremely inefficient. Test outputs are compared against expected outputs located intest/expected/. Stack trace addresses may point to the address after an instruction depending on theunwinding back-end, and the python script will check for an exact or near-match accordingly.
For the most part I'm happy with the state of the library. But I'm sure that there is room for improvement and issueswill exist. If you encounter any issue, please let me know! If you find any pain-points in the library, please let meknow that too.
A note about performance: For handling of DWARF symbols there is a lot of room to explore for performance optimizationsand time-memory tradeoffs. If you find the current implementation is either slow or using too much memory, I'd be happyto explore some of these options.
A couple things I'd like to improve in the future:
- On Windows when collecting symbols with dbghelp (msvc/clang) parameter types are almost perfect but due to limitationsin dbghelp the library cannot accurately show const and volatile qualifiers or rvalue references (these appear aspointers).
Some day C++23's<stacktrace> will be ubiquitous. And maybe one day the msvc implementation will be acceptable.The original motivation for cpptrace was to support projects using older C++ standards and as the library has grown itsfunctionality has extended beyond the standard library's implementation.
Cpptrace provides functionality beyond what the standard library provides and what implementations provide, such as:
- Walking inlined function calls
- Providing a lightweight interface for "raw traces"
- Resolving function parameter types
- Providing traced exception objects
- Providing an API for signal-safe stacktrace generation
- Providing a way to retrieve stack traces from arbitrary exceptions, not just special cpptrace traced exceptionobjects. This is a feature that has been proposed for a future version of the C++ standard,but cpptrace provides a solution for C++11.
Other C++ stacktrace libraries, such as boost stacktrace and backward-cpp, fall short when it comes to portability andease of use. In testing, I found neither to provide adequate coverage of various environments. Even when they can bemade to work in an environment they require manual configuration from the end-user, possibly requiring manualinstallation of third-party dependencies. This is a highly undesirable burden to impose on users, especially when it isfor a software package which just provides diagnostics as opposed to core functionality. Additionally, cpptrace providessupport for resolving inlined calls by default for DWARF symbols (boost does not do this, backward-cpp can do this butonly for some back-ends), better support for resolving full function signatures, and nicer API, among other features.
If you see a linker error along the lines of the following on MacOS then it's highly likely you are mixing standardlibrary ABIs.
Undefined symbols for architecture arm64: "std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::find(char, unsigned long) const", referenced from: cpptrace::detail::demangle(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, bool) in libcpptrace.a(demangle_with_cxxabi.cpp.o) cpptrace::detail::snippet_manager::build_line_table() in libcpptrace.a(snippet.cpp.o)This can happen when using apple clang to compile cpptrace and gcc to compile your code, or vice versa. The reason isthat apple clang defaults to libc++ and gcc defaults to libstdc++ and these two standard library implementations are notABI-compatible. To resolve this, ensure you are compiling both cpptrace and your code with the same standard library byeither using the same compiler for both or using-stdlib=libc++/-stdlib=libstdc++ to control which standard libraryis used.
I'm grateful for the help I've received with this library and I welcome contributions! For information on contributingplease refer toCONTRIBUTING.md.
This library is under the MIT license.
Cpptrace uses libdwarf on linux, macos, and mingw/cygwin unless configured to use something else. If this library isstatically linked with libdwarf then the library's binary will itself be LGPL.
About
Simple, portable, and self-contained stacktrace library for C++11 and newer
Topics
Resources
License
Contributing
Security policy
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Sponsor this project
Uh oh!
There was an error while loading.Please reload this page.
Packages0
Uh oh!
There was an error while loading.Please reload this page.




