diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 372bdcf..d947979 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ jobs: - uses: actions/checkout@v2 - name: dependencies run: | - sudo apt install gcc-10 g++-10 libgcc-10-dev + sudo apt install gcc-10 g++-10 libgcc-10-dev libunwind8-dev pip3 install colorama - name: libdwarf run: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 815347e..79666e4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v2 - name: dependencies run: | - sudo apt install gcc-10 g++-10 libgcc-10-dev + sudo apt install gcc-10 g++-10 libgcc-10-dev libunwind8-dev pip3 install colorama - name: libdwarf run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index c3e3c53..94c5b15 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,6 +83,7 @@ endif() if( NOT ( CPPTRACE_UNWIND_WITH_UNWIND OR + CPPTRACE_UNWIND_WITH_LIBUNWIND OR CPPTRACE_UNWIND_WITH_EXECINFO OR CPPTRACE_UNWIND_WITH_WINAPI OR CPPTRACE_UNWIND_WITH_DBGHELP OR @@ -195,6 +196,7 @@ target_sources( src/symbols/symbols_with_nothing.cpp src/symbols/symbols_core.cpp src/unwind/unwind_with_execinfo.cpp + src/unwind/unwind_with_libunwind.cpp src/unwind/unwind_with_nothing.cpp src/unwind/unwind_with_unwind.cpp src/unwind/unwind_with_winapi.cpp @@ -357,6 +359,34 @@ if(CPPTRACE_UNWIND_WITH_UNWIND) target_compile_definitions(${target_name} PUBLIC CPPTRACE_UNWIND_WITH_UNWIND) endif() +if(CPPTRACE_UNWIND_WITH_LIBUNWIND) + find_package(PkgConfig) + if(PkgConfig_FOUND) + pkg_check_modules(LIBUNWIND QUIET libunwind) + if(libunwind_FOUND) + target_compile_options(${target_name} PRIVATE ${LIBUNWIND_CFLAGS_OTHER}) + target_include_directories(${target_name} PRIVATE ${LIBUNWIND_INCLUDE_DIRS}) + target_link_libraries(${target_name} PRIVATE ${LIBUNWIND_LDFLAGS}) + endif() + endif() + if(NOT libunwind_FOUND) + # set_property(GLOBAL PROPERTY FIND_LIBRARY_USE_LIB64_PATHS ON) + # set_property(GLOBAL PROPERTY FIND_LIBRARY_USE_LIB32_PATHS ON) + find_path(LIBUNWIND_INCLUDE_DIR NAMES "libunwind.h") + find_library(LIBUNWIND NAMES unwind libunwind libunwind8 libunwind.so.8 REQUIRED PATHS "/usr/lib/x86_64-linux-gnu/") + if(LIBUNWIND) + target_compile_options(${target_name} PRIVATE ${LIBUNWIND_CFLAGS_OTHER}) + target_include_directories(${target_name} PRIVATE ${LIBUNWIND_INCLUDE_DIRS}) + target_link_libraries(${target_name} PRIVATE ${LIBUNWIND_LDFLAGS}) + set(libunwind_FOUND TRUE) + endif() + endif() + if(NOT libunwind_FOUND) + message(FATAL_ERROR "Unable to locate libunwind") + endif() + target_compile_definitions(${target_name} PUBLIC CPPTRACE_UNWIND_WITH_LIBUNWIND UNW_LOCAL_ONLY) +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 8d3dcb1..984715a 100644 --- a/README.md +++ b/README.md @@ -578,13 +578,14 @@ back-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 with `execinfo.h`'s `backtrace`, part of libc on linux/unix systems. | -| winapi | `CPPTRACE_UNWIND_WITH_WINAPI` | windows, mingw | Frames are captured with `CaptureStackBackTrace`. | -| dbghelp | `CPPTRACE_UNWIND_WITH_DBGHELP` | windows, mingw | Frames are captured with `StackWalk64`. | -| 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, 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 with `execinfo.h`'s `backtrace`, part of libc on linux/unix systems. | +| winapi | `CPPTRACE_UNWIND_WITH_WINAPI` | windows, mingw | Frames are captured with `CaptureStackBackTrace`. | +| dbghelp | `CPPTRACE_UNWIND_WITH_DBGHELP` | windows, mingw | Frames are captured with `StackWalk64`. | +| dbghelp | `CPPTRACE_UNWIND_WITH_LIBUNWIND` | linux, macos, windows, mingw | Frames are captured with [libunwind](https://github.com/libunwind/libunwind). **Note:** This is the only back-end that requires a library to be installed by the user, and a `CMAKE_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 and `CaptureStackBackTrace`) 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 @@ -634,6 +635,7 @@ Back-ends: - `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` diff --git a/ci/build-in-all-configs.py b/ci/build-in-all-configs.py index 9e7aa35..6a6128e 100644 --- a/ci/build-in-all-configs.py +++ b/ci/build-in-all-configs.py @@ -151,6 +151,7 @@ def main(): "unwind": [ "CPPTRACE_UNWIND_WITH_UNWIND", "CPPTRACE_UNWIND_WITH_EXECINFO", + "CPPTRACE_UNWIND_WITH_LIBUNWIND", "CPPTRACE_UNWIND_WITH_NOTHING", ], "symbols": [ diff --git a/ci/test-all-configs.py b/ci/test-all-configs.py index 4170356..2dfb9bb 100644 --- a/ci/test-all-configs.py +++ b/ci/test-all-configs.py @@ -319,6 +319,7 @@ def main(): "unwind": [ "CPPTRACE_UNWIND_WITH_EXECINFO", "CPPTRACE_UNWIND_WITH_UNWIND", + "CPPTRACE_UNWIND_WITH_LIBUNWIND", #"CPPTRACE_UNWIND_WITH_NOTHING", ], "symbols": [ diff --git a/cmake/OptionVariables.cmake b/cmake/OptionVariables.cmake index 78274d2..5bb6d80 100644 --- a/cmake/OptionVariables.cmake +++ b/cmake/OptionVariables.cmake @@ -128,6 +128,7 @@ option(CPPTRACE_GET_SYMBOLS_WITH_NOTHING "" OFF) # ---- Unwinding Options ---- option(CPPTRACE_UNWIND_WITH_UNWIND "" OFF) +option(CPPTRACE_UNWIND_WITH_LIBUNWIND "" OFF) option(CPPTRACE_UNWIND_WITH_EXECINFO "" OFF) option(CPPTRACE_UNWIND_WITH_WINAPI "" OFF) option(CPPTRACE_UNWIND_WITH_DBGHELP "" OFF) diff --git a/src/unwind/unwind_with_libunwind.cpp b/src/unwind/unwind_with_libunwind.cpp new file mode 100644 index 0000000..c01e09d --- /dev/null +++ b/src/unwind/unwind_with_libunwind.cpp @@ -0,0 +1,42 @@ +#ifdef CPPTRACE_UNWIND_WITH_LIBUNWIND + +#include "unwind.hpp" +#include "../platform/common.hpp" +#include "../platform/error.hpp" +#include "../platform/utils.hpp" + +#include +#include +#include +#include + +#include + +namespace cpptrace { +namespace detail { + CPPTRACE_FORCE_NO_INLINE + std::vector capture_frames(std::size_t skip, std::size_t max_depth) { + skip++; + std::vector frames; + unw_context_t context; + unw_cursor_t cursor; + unw_getcontext(&context); + unw_init_local(&cursor, &context); + do { + unw_word_t pc; + unw_word_t sp; + unw_get_reg(&cursor, UNW_REG_IP, &pc); + unw_get_reg(&cursor, UNW_REG_SP, &sp); + if(skip) { + skip--; + } else { + // pc is the instruction after the `call`, adjust back to the previous instruction + frames.push_back(to_frame_ptr(pc) - 1); + } + } while(unw_step(&cursor) > 0 && frames.size() < max_depth); + return frames; + } +} +} + +#endif