cpptrace

repository·main·Indexed 23 days ago

https://github.com/jeremy-rifkin/cpptrace

A portable C++ stacktrace library supporting C++11 and greater on Linux, macOS, and Windows. It provides resolved stack traces, lightweight raw traces, and specialized mechanisms for capturing traces from exceptions using CPPTRACE_TRY, CPPTRACE_CATCH, and cpptrace::try_catch. The library includes utilities for symbol demangling, source snippet retrieval, and a configurable formatter for customizing trace output.

Tokens
17.4K
Snippets
38
Records
58
Agent score
79%

What's inside cpptrace

  1. Understand cpptrace licensing and dependencies

    main

    The cpptrace library is licensed under the MIT license.

    Dependency Note: cpptrace uses libdwarf on Linux, macOS, and MinGW/Cygwin by default. If you statically link cpptrace with libdwarf, the resulting library binary will be subject to the LGPL license.

  2. Compare cpptrace with C++23 <stacktrace>

    main

    While C++23 introduces <stacktrace>, cpptrace is designed for projects using older C++ standards and provides features that extend beyond the current standard library implementations. Key advantages include:

    • Inlined Function Support: Walking inlined function calls.
    • Raw Traces: A lightweight interface for "raw traces".
    • Type Resolution: Resolving function parameter types and full function signatures.
    • Exception Tracing: Providing traced exception objects and retrieving stack traces from arbitrary exceptions (a feature proposed for future C++ standards but available in cpptrace for C++11).
    • Signal Safety: An API for signal-safe stacktrace generation.
  3. Understand signal-safe stack tracing

    main

    Stack traces in signal handlers (e.g., SIGSEGV, SIGTRAP) are difficult because signal handlers are highly restrictive environments. Calling standard tracing functions like cpptrace::generate_trace().print() inside a handler risks deadlocks or memory corruption because the handler might interrupt the application while it is holding a lock or performing malloc.

    To perform tracing safely, you must collect minimal information in the signal handler and resolve it later using one of three strategies:

    1. Resolve in-process later: Call safe_generate_raw_trace in the handler, then resolve the trace outside the handler in the same process.
    2. Write to file: Call safe_generate_raw_trace and write cpptrace::safe_object_frame data to a file for later resolution.
    3. Spawn a tracer process: Use fork() and exec() to spawn a separate process that receives the trace information via a pipe and performs the resolution.

    Important Requirements:

    • Unwinding: Currently, signal-safe stack unwinding is only possible with libunwind.
    • Object Resolution: Signal-safe object resolution requires _dl_find_object (available in glibc 2.35+). This is not currently supported on macOS or Windows.
    • Warmup: Because shared objects may be lazy-loaded (triggering non-signal-safe functions like malloc on first call), you must "warm up" the library in your main() function before any signals occur.
  4. Configure symbol modes in the formatter

    main

    The symbols option in the formatter allows you to choose how much detail is shown for function names:

    • symbol_mode::full (Default): Uses the full demangled name.
    • symbol_mode::pretty: Applies transformations to clean up long names (e.g., std::string instead of internal template expansions). This is equivalent to calling cpptrace::prettify_symbol.
    • symbol_mode::pruned: Removes return types, template arguments, and parameters (e.g., ns::S::~S). This is equivalent to calling cpptrace::prune_symbol.
  5. How Cpptrace produces stack traces

    main

    Cpptrace generates stack traces through a three-step pipeline:

    1. Unwinding: Capturing the sequence of stack frames (addresses).
    2. Symbol Resolution: Mapping those addresses to function names, file paths, and line numbers.
    3. Demangling: Converting mangled C++ symbol names into human-readable formats.

    The library is designed to automatically select the best back-ends for your platform (Linux, macOS, Windows, or MinGW) during the CMake configuration step.

  6. Quickstart: Generate and print a stack trace

    main

    To generate a resolved stack trace at the current call site and print it to standard output, include <cpptrace/cpptrace.hpp> and use cpptrace::generate_trace().print(). This is the simplest way to get human-readable trace information.

    #include <cpptrace/cpptrace.hpp>
    
    void trace() {
        cpptrace::generate_trace().print();
    }
  7. Perform signal-safe stack tracing in signal handlers

    main

    When handling signals (like SIGSEGV or SIGTRAP), you must avoid non-signal-safe functions like malloc or buffered I/O. Cpptrace provides a specialized API to walk the stack and produce a raw trace safely.

    Important Requirements & Limitations:

    • Backend: Signal-safe unwinding currently requires libunwind to be manually enabled. Use cpptrace::can_signal_safe_unwind() to check support.
    • glibc: Requires _dl_find_object (available in glibc 2.35+).
    • Warm-up: To avoid lazy-loading shared objects (which might trigger malloc during a signal), call routines that use your required libraries in main() before the signal handler is active.
    • Resolution: You cannot resolve debug symbols (names, lines) safely inside the signal handler. You must save the safe_object_frame data and call resolve() outside the handler.

    Workflow:

    1. Check support using can_signal_safe_unwind() and can_get_safe_object_frame().
    2. Use safe_generate_raw_trace to get the raw addresses.
    3. Use get_safe_object_frame to capture object metadata.
    4. Call safe_object_frame::resolve() outside the signal handler to get human-readable information.
    namespace cpptrace {
        std::size_t safe_generate_raw_trace(frame_ptr* buffer, std::size_t size, std::size_t skip = 0);
        std::size_t safe_generate_raw_trace(frame_ptr* buffer, std::size_t size, std::size_t skip, std::size_t max_depth);
        struct safe_object_frame {
            frame_ptr raw_address;
            frame_ptr address_relative_to_object_start;
            char object_path[CPPTRACE_PATH_MAX + 1];
            object_frame resolve(); // To be called outside a signal handler. Not signal safe.
        };
        void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
        bool can_signal_safe_unwind();
        bool can_get_safe_object_frame();
    }
  8. Collect stack traces from exceptions using CPPTRACE_TRY and CPPTRACE_CATCH

    main

    To collect a stack trace from a thrown exception with zero overhead in the non-throwing path, use the CPPTRACE_TRY and CPPTRACE_CATCH macros. This functionality is opt-in and requires including <cpptrace/from_current.hpp>.

    Usage Example:

    #include <cpptrace/from_current.hpp>
    #include <iostream>
    
    void foo() {
        throw std::runtime_error("foo failed");
    }
    
    int main() {
        CPPTRACE_TRY {
            foo();
        } CPPTRACE_CATCH(const std::exception& e) {
            std::cerr << "Exception: " << e.what() << std::endl;
            cpptrace::from_current_exception().print();
        }
    }

    Key API Functions:

    • cpptrace::raw_trace_from_current_exception(): Returns a const raw_trace& from the current exception.
    • cpptrace::from_current_exception(): Returns a resolved const stacktrace& from the current exception. Note that calling this invalidates references to traces returned by raw_trace_from_current_exception().

    Important Limitations:

    • Windows return restriction: On Windows, the implementation uses immediately-invoked lambdas. You must not use return statements inside a CPPTRACE_TRY block, as they will return from the lambda rather than the enclosing function. The compiler will attempt to prevent this by checking for a specific internal type.
    • Catch Handler Matching: Ensure all catch blocks are wrapped in CPPTRACE_CATCH. If you use a standard C++ catch block alongside a CPPTRACE_CATCH block, cpptrace will not be aware of the standard handler and may report misleading traces.
    #include <cpptrace/from_current.hpp>
    #include <iostream>
    
    void foo() {
        throw std::runtime_error("foo failed");
    }
    
    int main() {
        CPPTRACE_TRY {
            foo();
        } CPPTRACE_CATCH(const std::exception& e) {
            std::cerr << "Exception: " << e.what() << std::endl;
            cpptrace::from_current_exception().print();
        }
    }
  9. Implement signal-safe tracing with fork() and exec()

    main

    This is the most robust method for resolving traces while a signal handler is running. It involves two parts: a main program that collects data and a tracer program that resolves it.

    1. In the main program

    • Warmup: Call safe_generate_raw_trace and get_safe_object_frame in main() to ensure all shared libraries are loaded.
    • Handler: In the signal handler, generate a raw trace, then use get_safe_object_frame to convert raw pointers into safe_object_frame structures.
    • Communication: fork() a child process, pass the safe_object_frame data to it via a pipe, and exec() a separate tracer executable.

    2. In the tracer program

    • Read safe_object_frame structures from stdin (the pipe).
    • Call .resolve() on each frame to convert it to a standard object_frame.
    • Add them to a cpptrace::object_trace and call .resolve().print() to output the final trace.
    // --- MAIN PROGRAM SNIPPET ---
    void do_signal_safe_trace(cpptrace::frame_ptr* buffer, std::size_t count) {
        pipe_t input_pipe;
        pipe(input_pipe.data);
        const pid_t pid = fork();
        if(pid == 0) { // child
            dup2(input_pipe.read_end, STDIN_FILENO);
            close(input_pipe.read_end);
            close(input_pipe.write_end);
            execl("signal_tracer", "signal_tracer", nullptr);
            _exit(1);
        }
        for(std::size_t i = 0; i < count; i++) {
            cpptrace::safe_object_frame frame;
            cpptrace::get_safe_object_frame(buffer[i], &frame);
            write(input_pipe.write_end, &frame, sizeof(frame));
        }
        close(input_pipe.read_end);
        close(input_pipe.write_end);
        waitpid(pid, nullptr, 0);
    }
    
    void warmup_cpptrace() {
        cpptrace::frame_ptr buffer[10];
        std::size_t count = cpptrace::safe_generate_raw_trace(buffer, 10);
        cpptrace::safe_object_frame frame;
        cpptrace::get_safe_object_frame(buffer[0], &frame);
    }
    
    // --- TRACER PROGRAM SNIPPET ---
    int main() {
        cpptrace::object_trace trace;
        while(true) {
            cpptrace::safe_object_frame frame;
            std::size_t res = fread(&frame, sizeof(frame), 1, stdin);
            if(res == 0) break;
            else if(res == 1) {
                trace.frames.push_back(frame.resolve());
            } else {
                break;
            }
        }
        trace.resolve().print();
    }
  10. Install cpptrace for a local user

    main

    To install cpptrace to a custom prefix (e.g., for a local user), use the -DCMAKE_INSTALL_PREFIX flag during configuration.

    Build steps:

    git clone https://github.com/jeremy-rifkin/cpptrace.git
    git checkout v1.0.4
    mkdir cpptrace/build
    cd cpptrace/build
    cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$HOME/wherever
    make -j
    make install

    Using with CMake:

    find_package(cpptrace REQUIRED PATHS $ENV{HOME}/wherever)
    target_link_libraries(<your target> cpptrace::cpptrace)

    Using manually with g++:

    g++ main.cpp -o main -g -Wall -I$HOME/wherever/include -L$HOME/wherever/lib -lcpptrace
    cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$HOME/wherever
    make -j
    make install
  11. Install cpptrace via CMake FetchContent

    main

    Use CMake's FetchContent to automatically download and configure cpptrace for your project. Ensure you set CMAKE_BUILD_TYPE to Debug or RelWithDebInfo to ensure symbols and line information are available.

    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)
    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)
  12. Install cpptrace system-wide

    main

    To install cpptrace globally on your system, clone the repository, build it with CMake, and run make install.

    Linux/macOS:

    git clone https://github.com/jeremy-rifkin/cpptrace.git
    git checkout v1.0.4
    mkdir cpptrace/build
    cd cpptrace/build
    cmake .. -DCMAKE_BUILD_TYPE=Release
    make -j
    sudo make install

    Windows (PowerShell/Developer Shell): Note: You must run as an administrator or use vcvarsall.bat from Visual Studio.

    git clone https://github.com/jeremy-rifkin/cpptrace.git
    git checkout v1.0.4
    mkdir cpptrace/build
    cd cpptrace/build
    cmake .. -DCMAKE_BUILD_TYPE=Release
    msbuild cpptrace.sln
    msbuild INSTALL.vcxproj

    After system-wide installation on Linux, if you encounter shared library loading errors, run sudo /sbin/ldconfig to update the library cache.

    git clone https://github.com/jeremy-rifkin/cpptrace.git
    git checkout v1.0.4
    mkdir cpptrace/build
    cd cpptrace/build
    cmake .. -DCMAKE_BUILD_TYPE=Release
    make -j
    sudo make install