Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Simple, portable, and self-contained stacktrace library for C++11 and newer

License

NotificationsYou must be signed in to change notification settings

jeremy-rifkin/cpptrace

Repository files navigation

CIQuality Gate Status
Community Discord Link
Try on Compiler Explorer

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.

Table of Contents

30-Second Overview

Generating stack traces is as easy as:

#include<cpptrace/cpptrace.hpp>voidtrace() {cpptrace::generate_trace().print();}

Demo

Cpptrace can also retrieve function inlining information on optimized release builds:

Inlining

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();    }}

from_current

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!");}

Exception

Additional notable features:

  • Utilities for demangling
  • Utilities for catchingstd::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

Snippets

CMake FetchContent Usage

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.

Prerequisites

Important

Debug info (-g//Z7//Zi//DEBUG/-DBUILD_TYPE=Debug/-DBUILD_TYPE=RelWithDebInfo) is required for completetrace information.

Basic Usage

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.

namespace cpptrace

All functions are thread-safe unless otherwise noted.

Stack Traces

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

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 Traces

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);}

Utilities

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();}

Formatting

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:

SettingDescriptionDefault
headerHeader line printed before the traceStack trace (most recent call first):
colorsDefault color mode for the traceautomatic, which attempts to detect if the target stream is a terminal
addressesRaw addresses, object addresses, or no addressesraw
pathsFull paths or just filenamesfull
snippetsWhether to include source code snippetsfalse
snippet_contextHow many lines of source context to show in a snippet2
columnsWhether to include column numbers if presenttrue
symbolsFull demangled symbols, pruned symbol names, or prettified symbolsfull
filtered_frame_placeholdersWhether to still print filtered frames as just#n (filtered)true
filterA predicate to filter frames withNone
transformA transformer which takes a stacktrace frame and modifies itNone
break_before_filenamePrint symbol and line source location on different linesfalse
hide_exception_machineryHide exception internals for current exception tracestrue

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::full default, uses the full demangled name
  • symbol_mode::pretty applies 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::pruned 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 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();}

Transforms

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;    });

Configuration

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);    }}

Logging

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.

Traces From All Exceptions (CPPTRACE_TRY andCPPTRACE_CATCH)

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;!

from_current

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.

Removing theCPPTRACE_ prefix

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)

How it works

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:

SourceExpansion
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:

SourceExpansion
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.

Performance

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.

Rethrowing Exceptions

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    }}

cpptrace::try_catch

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.

Traces from SEH exceptions

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.

Traced Exception Objects

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;    };}

Wrapping std::exceptions

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;

Exception handling with cpptrace exception objects

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';}

Terminate Handling

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();

Signal-Safe Tracing

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:

  1. Carefully save the object trace information to be resolved at a later time outside the signal handler
  2. Write the object trace information to a file to be resolved later
  3. 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.

Utility Types

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();        };    }}

Headers

Cpptrace provides a handful of headers to make inclusion more minimal.

HeaderContents
cpptrace/forward.hppcpptrace::frame_ptr and a few trace class forward declarations
cpptrace/basic.hppDefinitions for trace classes and the basic tracing APIs (Stack Traces,Object Traces,Raw Traces, andSignal-Safe Tracing)
cpptrace/exceptions.hppTraced Exception Objects and related utilities (Wrapping std::exceptions)
cpptrace/from_current.hppTraces From All Exceptions
cpptrace/io.hppoperator<< overloads forstd::ostream andstd::formatters
cpptrace/formatting.hppConfigurable formatter API
cpptrace/utils.hppUtility functions, configuration functions, and terminate utilities (Utilities,Configuration, andTerminate Handling)
cpptrace/version.hppLibrary version macros
cpptrace/gdb_jit.hppProvides a special utility related toJIT support

The main cpptrace header iscpptrace/cpptrace.hpp which includes everything other thanfrom_current.hpp andversion.hpp.

Libdwarf Tuning

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_size can 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_aranges can 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.

JIT Support

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.

Loading Libraries at Runtime

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);}

ABI Versioning

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.

Supported Debug Formats

FormatSupported
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.

How to Include The Library

CMake FetchContent

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.

System-Wide Installation

git clone https://github.com/jeremy-rifkin/cpptrace.gitgit checkout v1.0.4mkdir cpptrace/buildcd cpptrace/buildcmake .. -DCMAKE_BUILD_TYPE=Releasemake -jsudo make install

Using 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 directory

You 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.

Local User Installation

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 -lcpptrace

Important

If you aren't using cmake and are linking statically you must manually specify-DCPPTRACE_STATIC_DEFINE.

Use Without CMake

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:

CompilerPlatformDependencies
gcc, clang, intel, etc.Linux/macos/unix-lcpptrace -ldwarf -lz -lzstd -ldl
gccWindows-lcpptrace -ldbghelp -ldwarf -lz -lzstd
msvcWindowscpptrace.lib dbghelp.lib
clangWindows-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.

Installation Without Package Managers or FetchContent

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.

Package Managers

Conan

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

vcpkg install cpptrace
find_package(cpptrace CONFIG REQUIRED)target_link_libraries(mainPRIVATE cpptrace::cpptrace)

C++20 Modules

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_WRAP andCPPTRACE_WRAP_BLOCK
  • <cpptrace/from_current_macros.hpp>:CPPTRACE_TRY,CPPTRACE_CATCH, etc.

Platform Logistics

Windows and macOS require a little extra work to get everything in the right place.

Windows

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()

macOS

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()

Library Back-Ends

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:

PlatformUnwindingSymbolsDemangling
Linux_Unwindlibdwarfcxxabi.h
MacOS_Unwind for gcc, execinfo.h for clang and apple clanglibdwarfcxxabi.h
WindowsStackWalk64dbghelpNo demangling needed
MinGWStackWalk64libdwarf + dbghelpcxxabi.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

LibraryCMake configPlatformsInfo
libgcc unwindCPPTRACE_UNWIND_WITH_UNWINDlinux, macos, mingwFrames 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.hCPPTRACE_UNWIND_WITH_EXECINFOlinux, macosFrames are captured withexecinfo.h'sbacktrace, part of libc on linux/unix systems.
winapiCPPTRACE_UNWIND_WITH_WINAPIwindows, mingwFrames are captured withCaptureStackBackTrace.
dbghelpCPPTRACE_UNWIND_WITH_DBGHELPwindows, mingwFrames are captured withStackWalk64.
libunwindCPPTRACE_UNWIND_WITH_LIBUNWINDlinux, macos, windows, mingwFrames 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/ACPPTRACE_UNWIND_WITH_NOTHINGallUnwinding 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

LibraryCMake configPlatformsInfo
libdwarfCPPTRACE_GET_SYMBOLS_WITH_LIBDWARFlinux, macos, mingwLibdwarf is the preferred method for symbol resolution for cpptrace. Cpptrace will get it via FetchContent or find_package depending onCPPTRACE_USE_EXTERNAL_LIBDWARF.
dbghelpCPPTRACE_GET_SYMBOLS_WITH_DBGHELPwindowsDbghelp.h is the preferred method for symbol resolution on windows under msvc/clang and is supported on all windows machines.
libbacktraceCPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACElinux, 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.
addr2lineCPPTRACE_GET_SYMBOLS_WITH_ADDR2LINElinux, macos, mingwSymbols are resolved by invokingaddr2line (oratos on mac) viafork() (on linux/unix, andpopen under mingw).
libdlCPPTRACE_GET_SYMBOLS_WITH_LIBDLlinux, macosLibdl uses dynamic export information. Compiling with-rdynamic is needed for symbol information to be retrievable. Line numbers won't be retrievable.
N/ACPPTRACE_GET_SYMBOLS_WITH_NOTHINGallNo 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.

LibraryCMake configPlatformsInfo
cxxabi.hCPPTRACE_DEMANGLE_WITH_CXXABILinux, macos, mingwShould be available everywhere other thanmsvc.
dbghelp.hCPPTRACE_DEMANGLE_WITH_WINAPIWindowsDemangle withUnDecorateSymbolName.
N/ACPPTRACE_DEMANGLE_WITH_NOTHINGallDon'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 Library Configurations

Summary of all library configuration options:

Back-ends:

  • CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF=On/Off
  • CPPTRACE_GET_SYMBOLS_WITH_DBGHELP=On/Off
  • CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE=On/Off
  • CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE=On/Off
  • CPPTRACE_GET_SYMBOLS_WITH_LIBDL=On/Off
  • CPPTRACE_GET_SYMBOLS_WITH_NOTHING=On/Off
  • CPPTRACE_UNWIND_WITH_UNWIND=On/Off
  • CPPTRACE_UNWIND_WITH_LIBUNWIND=On/Off
  • CPPTRACE_UNWIND_WITH_EXECINFO=On/Off
  • CPPTRACE_UNWIND_WITH_WINAPI=On/Off
  • CPPTRACE_UNWIND_WITH_DBGHELP=On/Off
  • CPPTRACE_UNWIND_WITH_NOTHING=On/Off
  • CPPTRACE_DEMANGLE_WITH_CXXABI=On/Off
  • CPPTRACE_DEMANGLE_WITH_WINAPI=On/Off
  • CPPTRACE_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 asSYSTEM which 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_package rather 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::formatter specializations bycpptrace.hpp. This can also be controlled with the macroCPPTRACE_NO_STD_FORMAT.

Testing:

  • CPPTRACE_BUILD_TESTING Build small demo and test program
  • CPPTRACE_BUILD_TEST_RDYNAMIC Use-rdynamic when compiling the test program

Testing Methodology

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.

Notes About the Library

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).

FAQ

What about C++23<stacktrace>?

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.

What does cpptrace have over other C++ stacktrace libraries?

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.

I'm getting undefined standard library symbols likestd::__1::basic_string on MacOS

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.

Contributing

I'm grateful for the help I've received with this library and I welcome contributions! For information on contributingplease refer toCONTRIBUTING.md.

License

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

Stars

Watchers

Forks

Sponsor this project

 

Packages

No packages published

[8]ページ先頭

©2009-2026 Movatter.jp