From 73925368ccfc60c3372a0136e162115a2dc3c21c Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Thu, 20 Jul 2023 01:14:38 -0400 Subject: [PATCH] Libgcc unwind backend (#11) --- CMakeLists.txt | 18 ++++++++-- README.md | 11 +++--- ci/build-in-all-configs.py | 2 ++ ci/test-all-configs.py | 2 ++ cmake/has_unwind.cpp | 14 ++++++++ lint.sh | 2 +- src/unwind/unwind_with_unwind.cpp | 58 +++++++++++++++++++++++++++++++ 7 files changed, 99 insertions(+), 8 deletions(-) create mode 100644 cmake/has_unwind.cpp create mode 100644 src/unwind/unwind_with_unwind.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4645307..f6f0492 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,6 +59,7 @@ option(CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE "" OFF) option(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP "" OFF) option(CPPTRACE_GET_SYMBOLS_WITH_NOTHING "" OFF) +option(CPPTRACE_UNWIND_WITH_UNWIND "" OFF) option(CPPTRACE_UNWIND_WITH_EXECINFO "" OFF) option(CPPTRACE_UNWIND_WITH_WINAPI "" OFF) option(CPPTRACE_UNWIND_WITH_NOTHING "" OFF) @@ -100,6 +101,7 @@ function(check_support var source includes libraries definitions) set(${var} ${${var}} PARENT_SCOPE) endfunction() +check_support(HAS_UNWIND has_unwind.cpp "" "" "") check_support(HAS_EXECINFO has_execinfo.cpp "" "" "") check_support(HAS_BACKTRACE has_backtrace.cpp "" "backtrace" "${CPPTRACE_BACKTRACE_PATH_DEFINITION}") check_support(HAS_CXXABI has_cxxabi.cpp "" "" "") @@ -121,6 +123,7 @@ if( CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE OR CPPTRACE_GET_SYMBOLS_WITH_DBGHELP OR CPPTRACE_GET_SYMBOLS_WITH_NOTHING OR + CPPTRACE_UNWIND_WITH_UNWIND OR CPPTRACE_UNWIND_WITH_EXECINFO OR CPPTRACE_UNWIND_WITH_WINAPI OR CPPTRACE_UNWIND_WITH_NOTHING @@ -142,6 +145,7 @@ if( NOT ( CPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE OR CPPTRACE_FULL_TRACE_WITH_STACKTRACE OR + CPPTRACE_UNWIND_WITH_UNWIND OR CPPTRACE_UNWIND_WITH_EXECINFO OR CPPTRACE_UNWIND_WITH_WINAPI OR CPPTRACE_UNWIND_WITH_NOTHING @@ -149,12 +153,15 @@ if( ) # Attempt to auto-config if(UNIX) - if(HAS_EXECINFO) + if(HAS_UNWIND) + set(CPPTRACE_UNWIND_WITH_UNWIND On) + message(STATUS "Cpptrace auto config: Using libgcc unwind for unwinding") + elseif(HAS_EXECINFO) set(CPPTRACE_UNWIND_WITH_EXECINFO On) message(STATUS "Cpptrace auto config: Using execinfo.h for unwinding") else() set(CPPTRACE_UNWIND_WITH_NOTHING On) - message(FATAL_ERROR "Cpptrace auto config: doesn't seem to be supported, stack tracing will not work. To compile anyway set CPPTRACE_UNWIND_WITH_NOTHING.") + message(FATAL_ERROR "Cpptrace auto config: No unwinding back-end seems to be supported, stack tracing will not work. To compile anyway set CPPTRACE_UNWIND_WITH_NOTHING.") endif() elseif(WIN32) set(CPPTRACE_UNWIND_WITH_WINAPI On) @@ -266,6 +273,13 @@ if(CPPTRACE_GET_SYMBOLS_WITH_NOTHING) endif() # Unwinding +if(CPPTRACE_UNWIND_WITH_UNWIND) + if(NOT HAS_UNWIND) + message(WARNING "Cpptrace: CPPTRACE_UNWIND_WITH_UNWIND specified but libgcc unwind doesn't seem to be available.") + endif() + target_compile_definitions(cpptrace PUBLIC CPPTRACE_UNWIND_WITH_UNWIND) +endif() + if(CPPTRACE_UNWIND_WITH_EXECINFO) if(NOT HAS_EXECINFO) message(WARNING "Cpptrace: CPPTRACE_UNWIND_WITH_EXECINFO specified but execinfo.h doesn't seem to be available.") diff --git a/README.md b/README.md index 19a040a..5483b5d 100644 --- a/README.md +++ b/README.md @@ -112,11 +112,12 @@ also manually set which back-end you want used. **Unwinding** -| Library | CMake config | Platforms | Info | -| ---------- | ------------------------------- | -------------- | ------------------------------------------------------------------ | -| execinfo.h | `CPPTRACE_UNWIND_WITH_EXECINFO` | linux, macos | Frames are captured with `execinfo.h`'s `backtrace`, part of libc. | -| winapi | `CPPTRACE_UNWIND_WITH_WINAPI` | windows, mingw | Frames are captured with `CaptureStackBackTrace`. | -| N/A | `CPPTRACE_UNWIND_WITH_NOTHING` | all | Unwinding is not done, stack traces will be empty. | +| Library | CMake config | Platforms | Info | +| ------------- | ------------------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| libgcc unwind | `CPPTRACE_UNWIND_WITH_UNWIND` | linux, macos | Frames are captured with libgcc's `_Unwind_Backtrace`, which currently produces the most accurate stack traces on gcc/clang. Libgcc is often linked by default, and llvm has something equivalent. | +| execinfo.h | `CPPTRACE_UNWIND_WITH_EXECINFO` | linux, macos | Frames are captured with `execinfo.h`'s `backtrace`, part of libc. | +| winapi | `CPPTRACE_UNWIND_WITH_WINAPI` | windows, mingw | Frames are captured with `CaptureStackBackTrace`. | +| N/A | `CPPTRACE_UNWIND_WITH_NOTHING` | all | Unwinding is not done, stack traces will be empty. | These back-ends require a fixed buffer has to be created to read addresses into while unwinding. By default the buffer can hold addresses for 100 frames (beyond the `skip` frames). This is configurable with `CPPTRACE_HARD_MAX_FRAMES`. diff --git a/ci/build-in-all-configs.py b/ci/build-in-all-configs.py index 75548dc..2cd4289 100644 --- a/ci/build-in-all-configs.py +++ b/ci/build-in-all-configs.py @@ -122,6 +122,7 @@ def main(): "target": ["Debug"], "std": ["11", "20"], "unwind": [ + "CPPTRACE_UNWIND_WITH_UNWIND", "CPPTRACE_UNWIND_WITH_EXECINFO", "CPPTRACE_UNWIND_WITH_NOTHING", ], @@ -152,6 +153,7 @@ def main(): "target": ["Debug"], "std": ["11", "20"], "unwind": [ + "CPPTRACE_UNWIND_WITH_UNWIND", "CPPTRACE_UNWIND_WITH_EXECINFO", "CPPTRACE_UNWIND_WITH_NOTHING", ], diff --git a/ci/test-all-configs.py b/ci/test-all-configs.py index 9cb4599..ace7a26 100644 --- a/ci/test-all-configs.py +++ b/ci/test-all-configs.py @@ -203,6 +203,7 @@ def main(): "std": ["11", "20"], "unwind": [ "CPPTRACE_UNWIND_WITH_EXECINFO", + "CPPTRACE_UNWIND_WITH_UNWIND", #"CPPTRACE_UNWIND_WITH_NOTHING", ], "symbols": [ @@ -233,6 +234,7 @@ def main(): "std": ["11", "20"], "unwind": [ "CPPTRACE_UNWIND_WITH_EXECINFO", + "CPPTRACE_UNWIND_WITH_UNWIND", #"CPPTRACE_UNWIND_WITH_NOTHING", ], "symbols": [ diff --git a/cmake/has_unwind.cpp b/cmake/has_unwind.cpp new file mode 100644 index 0000000..503e306 --- /dev/null +++ b/cmake/has_unwind.cpp @@ -0,0 +1,14 @@ +#include + +#include + +_Unwind_Reason_Code unwind_callback(_Unwind_Context* context, void* arg) { + _Unwind_GetIP(context); + int is_before_instruction = 0; + uintptr_t ip = _Unwind_GetIPInfo(context, &is_before_instruction); + return _URC_END_OF_STACK; +} + +int main() { + _Unwind_Backtrace(unwind_callback, nullptr); +} diff --git a/lint.sh b/lint.sh index baa7a53..2cc2ae5 100644 --- a/lint.sh +++ b/lint.sh @@ -4,7 +4,7 @@ status=0 while read f do echo checking $f - flags="-DCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE -DCPPTRACE_FULL_TRACE_WITH_STACKTRACE -DCPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE -DCPPTRACE_GET_SYMBOLS_WITH_LIBDL -DCPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE -DCPPTRACE_GET_SYMBOLS_WITH_NOTHING -DCPPTRACE_UNWIND_WITH_EXECINFO -DCPPTRACE_UNWIND_WITH_NOTHING -DCPPTRACE_DEMANGLE_WITH_CXXABI -DCPPTRACE_DEMANGLE_WITH_NOTHING" + flags="-DCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE -DCPPTRACE_FULL_TRACE_WITH_STACKTRACE -DCPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE -DCPPTRACE_GET_SYMBOLS_WITH_LIBDL -DCPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE -DCPPTRACE_GET_SYMBOLS_WITH_NOTHING -DCPPTRACE_UNWIND_WITH_EXECINFO -DCPPTRACE_UNWIND_WITH_UNWIND -DCPPTRACE_UNWIND_WITH_NOTHING -DCPPTRACE_DEMANGLE_WITH_CXXABI -DCPPTRACE_DEMANGLE_WITH_NOTHING" clang-tidy $f -- -std=c++11 -Iinclude $@ $flags ret=$? if [ $ret -ne 0 ]; then diff --git a/src/unwind/unwind_with_unwind.cpp b/src/unwind/unwind_with_unwind.cpp new file mode 100644 index 0000000..6bc5e70 --- /dev/null +++ b/src/unwind/unwind_with_unwind.cpp @@ -0,0 +1,58 @@ +#ifdef CPPTRACE_UNWIND_WITH_UNWIND + +#include "cpptrace_unwind.hpp" +#include "../platform/cpptrace_common.hpp" + +#include +#include +#include +#include + +#include + +namespace cpptrace { + namespace detail { + struct unwind_state { + std::size_t skip; + std::size_t count; + std::vector& vec; + }; + + _Unwind_Reason_Code unwind_callback(_Unwind_Context* context, void* arg) { + unwind_state& state = *static_cast(arg); + if(state.skip) { + state.skip--; + if(_Unwind_GetIP(context) == uintptr_t(0)) { + return _URC_END_OF_STACK; + } else { + return _URC_NO_REASON; + } + } + + assert(state.count < state.vec.size()); + int is_before_instruction = 0; + uintptr_t ip = _Unwind_GetIPInfo(context, &is_before_instruction); + if(!is_before_instruction && ip != uintptr_t(0)) { + ip--; + } + if (ip == uintptr_t(0) || state.count == state.vec.size()) { + return _URC_END_OF_STACK; + } else { + state.vec[state.count++] = (void*)ip; + return _URC_NO_REASON; + } + } + + CPPTRACE_FORCE_NO_INLINE + std::vector capture_frames(size_t skip) { + std::vector frames(hard_max_frames, nullptr); + unwind_state state{skip + 1, 0, frames}; + _Unwind_Backtrace(unwind_callback, &state); + frames.resize(state.count); + frames.shrink_to_fit(); + return frames; + } + } +} + +#endif