diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1dabc75..68b12c5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -44,6 +44,42 @@ jobs: -D${{matrix.demangle}}=On \ -DCPPTRACE_BACKTRACE_PATH=/usr/lib/gcc/x86_64-linux-gnu/10/include/backtrace.h make + build-macos: + runs-on: macos-13 + strategy: + fail-fast: false + matrix: + compiler: [g++-13, clang++] + target: [Debug] + std: [11, 20] + unwind: [ + CPPTRACE_UNWIND_WITH_EXECINFO, + CPPTRACE_UNWIND_WITH_NOTHING, + ] + symbols: [ + #CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE, + CPPTRACE_GET_SYMBOLS_WITH_LIBDL, + CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE, + CPPTRACE_GET_SYMBOLS_WITH_NOTHING, + ] + demangle: [ + CPPTRACE_DEMANGLE_WITH_CXXABI, + CPPTRACE_DEMANGLE_WITH_NOTHING, + ] + steps: + - uses: actions/checkout@v2 + - name: build + run: | + mkdir -p build + cd build + cmake .. \ + -DCMAKE_BUILD_TYPE=${{matrix.target}} \ + -DCMAKE_CXX_COMPILER=${{matrix.compiler}} \ + -DCMAKE_CXX_STANDARD=${{matrix.std}} \ + -D${{matrix.unwind}}=On \ + -D${{matrix.symbols}}=On \ + -D${{matrix.demangle}}=On + make build-windows: runs-on: windows-2019 strategy: @@ -108,6 +144,27 @@ jobs: -DCPPTRACE_BACKTRACE_PATH=/usr/lib/gcc/x86_64-linux-gnu/10/include/backtrace.h \ ${{matrix.config}} make + build-macos-full-or-auto: + runs-on: macos-13 + strategy: + fail-fast: false + matrix: + compiler: [g++-13, clang++] + target: [Debug] + std: [11, 20] + config: [""] + steps: + - uses: actions/checkout@v2 + - name: build + run: | + mkdir -p build + cd build + cmake .. \ + -DCMAKE_BUILD_TYPE=${{matrix.target}} \ + -DCMAKE_CXX_COMPILER=${{matrix.compiler}} \ + -DCMAKE_CXX_STANDARD=${{matrix.std}} \ + ${{matrix.config}} + make # TODO: -DCMAKE_CXX_STANDARD isn't being honored? # build-linux-full-or-auto-23: # runs-on: ubuntu-22.04 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d95afdb..0f911ee 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -53,6 +53,49 @@ jobs: working-directory: build run: | ./test | python3 ../test/test.py "${{matrix.compiler}}" "${{matrix.unwind}}" "${{matrix.symbols}}" "${{matrix.demangle}}" + test-macos: + runs-on: macos-13 + strategy: + fail-fast: false + matrix: + compiler: [g++-13, clang++] + target: [Debug] + std: [11, 20] + unwind: [ + CPPTRACE_UNWIND_WITH_EXECINFO, + #CPPTRACE_UNWIND_WITH_NOTHING, + ] + symbols: [ + #CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE, + CPPTRACE_GET_SYMBOLS_WITH_LIBDL, + #CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE, + #CPPTRACE_GET_SYMBOLS_WITH_NOTHING, + ] + demangle: [ + CPPTRACE_DEMANGLE_WITH_CXXABI, + #CPPTRACE_DEMANGLE_WITH_NOTHING, + ] + steps: + - uses: actions/checkout@v2 + - name: build + run: | + mkdir -p build + cd build + cmake .. \ + -DCMAKE_BUILD_TYPE=${{matrix.target}} \ + -DCMAKE_CXX_COMPILER=${{matrix.compiler}} \ + -DCMAKE_CXX_STANDARD=${{matrix.std}} \ + -D${{matrix.unwind}}=On \ + -D${{matrix.symbols}}=On \ + -D${{matrix.demangle}}=On \ + -DCPPTRACE_BUILD_TEST=On \ + $(if [ "${{matrix.symbols}}" = "CPPTRACE_GET_SYMBOLS_WITH_LIBDL" ]; then echo "-DCPPTRACE_BUILD_TEST_RDYNAMIC=On"; else echo ""; fi) \ + -DBUILD_SHARED_LIBS=On + make + - name: test + working-directory: build + run: | + ./test | python3 ../test/test.py "${{matrix.compiler}}" "${{matrix.unwind}}" "${{matrix.symbols}}" "${{matrix.demangle}}" test-windows: runs-on: windows-2019 strategy: @@ -130,6 +173,33 @@ jobs: working-directory: build run: | ./test | python3 ../test/test.py "${{matrix.compiler}}" + test-macos-full-or-auto: + runs-on: macos-13 + strategy: + fail-fast: false + matrix: + compiler: [g++-13, clang++] + target: [Debug] + std: [11, 20] + config: [""] + steps: + - uses: actions/checkout@v2 + - name: build + run: | + mkdir -p build + cd build + cmake .. \ + -DCMAKE_BUILD_TYPE=${{matrix.target}} \ + -DCMAKE_CXX_COMPILER=${{matrix.compiler}} \ + -DCMAKE_CXX_STANDARD=${{matrix.std}} \ + ${{matrix.config}} \ + -DCPPTRACE_BUILD_TEST=On \ + -DBUILD_SHARED_LIBS=On + make + - name: test + working-directory: build + run: | + ./test | python3 ../test/test.py "${{matrix.compiler}}" test-windows-full-or-auto: runs-on: windows-2019 strategy: diff --git a/README.md b/README.md index c750e11..255cffd 100644 --- a/README.md +++ b/README.md @@ -54,33 +54,33 @@ also manually set which back-end you want used. **Unwinding** | Library | CMake config | Windows | Linux | macOS | Info | -|------------|---------------------------------|---------|-------|-------|--------------------------------------------------------------------| -| execinfo.h | `CPPTRACE_UNWIND_WITH_EXECINFO` | ❌ | ✔️ | ✔️ | Frames are captured with `execinfo.h`'s `backtrace`, part of libc. | -| winapi | `CPPTRACE_UNWIND_WITH_WINAPI` | ✔️ | ❌ | ❌ | Frames are captured with `CaptureStackBackTrace`. | -| N/A | `CPPTRACE_UNWIND_WITH_NOTHING` | ✔️ | ✔️ | ✔️ | Unwinding is not done, stack traces will be empty. | +| ---------- | ------------------------------- | ------- | ----- | ----- | ------------------------------------------------------------------ | +| execinfo.h | `CPPTRACE_UNWIND_WITH_EXECINFO` | ❌ | ✔️ | ✔️ | Frames are captured with `execinfo.h`'s `backtrace`, part of libc. | +| winapi | `CPPTRACE_UNWIND_WITH_WINAPI` | ✔️ | ❌ | ❌ | Frames are captured with `CaptureStackBackTrace`. | +| N/A | `CPPTRACE_UNWIND_WITH_NOTHING` | ✔️ | ✔️ | ✔️ | 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`. **Symbol resolution** -| Library | CMake config | Windows | Linux | macOS | Info | -|--------------|------------------------------------------|---------|-------|-------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| libbacktrace | `CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE` | ❌ | ✔️ | ❌ | Libbacktrace is already installed on most systems, or available through the compiler directly. If it is installed but backtrace.h is not already in the include path (this can happen when using clang when backtrace lives in gcc's include folder), `CPPTRACE_BACKTRACE_PATH` can be used to specify where the library should be looked for. | -| libdl | `CPPTRACE_GET_SYMBOLS_WITH_LIBDL` | ❌ | ✔️ | ✔️ | Libdl uses dynamic export information. Compiling with `-rdynamic` is needed for symbol information to be retrievable. | -| addr2line | `CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE` | ❌ | ✔️ | ❌ | Symbols are resolved by invoking `addr2line` via `fork()`. | -| dbghelp | `CPPTRACE_GET_SYMBOLS_WITH_DBGHELP` | ✔️ | ❌ | ❌ | Dbghelp.h allows access to symbols via debug info. | -| N/A | `CPPTRACE_GET_SYMBOLS_WITH_NOTHING` | ✔️ | ✔️ | ✔️ | No attempt is made to resolve symbols. | +| Library | CMake config | Windows | Linux | macOS | Info | +| ------------ | ---------------------------------------- | ------- | ----- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| libbacktrace | `CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE` | ❌ | ✔️ | ❌ | Libbacktrace is already installed on most systems, or available through the compiler directly. If it is installed but backtrace.h is not already in the include path (this can happen when using clang when backtrace lives in gcc's include folder), `CPPTRACE_BACKTRACE_PATH` can be used to specify where the library should be looked for. | +| libdl | `CPPTRACE_GET_SYMBOLS_WITH_LIBDL` | ❌ | ✔️ | ✔️ | Libdl uses dynamic export information. Compiling with `-rdynamic` is needed for symbol information to be retrievable. | +| addr2line | `CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE` | ❌ | ✔️ | ❌ (TODO) | Symbols are resolved by invoking `addr2line` via `fork()`. | +| dbghelp | `CPPTRACE_GET_SYMBOLS_WITH_DBGHELP` | ✔️ | ❌ | ❌ | Dbghelp.h allows access to symbols via debug info. | +| N/A | `CPPTRACE_GET_SYMBOLS_WITH_NOTHING` | ✔️ | ✔️ | ✔️ | No attempt is made to resolve symbols. | **Demangling** Lastly, depending on other back-ends used a demangler back-end may be needed. A demangler back-end is not needed when doing full traces with libbacktrace, getting symbols with addr2line, or getting symbols with dbghelp. -| Library | CMake config | Windows | Linux | Info | -|---------|--------------|---------|-------|------| -| cxxabi.h | `CPPTRACE_DEMANGLE_WITH_CXXABI` | | | Should be available everywhere other than [msvc](https://godbolt.org/z/93ca9rcdz). | -| N/A | `CPPTRACE_DEMANGLE_WITH_NOTHING` | | | Don't attempt to do anything beyond what the symbol resolution back-end does. | +| Library | CMake config | Windows | Linux | Info | +| -------- | -------------------------------- | ------- | ----- | ---------------------------------------------------------------------------------- | +| cxxabi.h | `CPPTRACE_DEMANGLE_WITH_CXXABI` | | | Should be available everywhere other than [msvc](https://godbolt.org/z/93ca9rcdz). | +| N/A | `CPPTRACE_DEMANGLE_WITH_NOTHING` | | | Don't attempt to do anything beyond what the symbol resolution back-end does. | **Full tracing** diff --git a/src/platform/cpptrace_common.hpp b/src/platform/cpptrace_common.hpp index b966dbe..1ff59cc 100644 --- a/src/platform/cpptrace_common.hpp +++ b/src/platform/cpptrace_common.hpp @@ -102,6 +102,9 @@ constexpr const char* const ws = " \t\n\r\f\v"; CPPTRACE_MAYBE_UNUSED static std::string trim(const std::string& s) { + if(s == "") { + return ""; + } size_t l = s.find_first_not_of(ws); size_t r = s.find_last_not_of(ws) + 1; return s.substr(l, r - l); diff --git a/src/platform/cpptrace_program_name.hpp b/src/platform/cpptrace_program_name.hpp index 3058eab..444c937 100644 --- a/src/platform/cpptrace_program_name.hpp +++ b/src/platform/cpptrace_program_name.hpp @@ -22,6 +22,7 @@ namespace cpptrace { #elif __APPLE__ +#include #include #include diff --git a/src/symbols/symbols_with_addr2line.cpp b/src/symbols/symbols_with_addr2line.cpp index fd34378..89e90ea 100644 --- a/src/symbols/symbols_with_addr2line.cpp +++ b/src/symbols/symbols_with_addr2line.cpp @@ -136,16 +136,27 @@ namespace cpptrace { auto output = split(trim(resolve_addresses(address_input, object_name)), "\n"); internal_verify(output.size() == entries_vec.size()); for(size_t i = 0; i < output.size(); i++) { - // result will be of the form " at " path:line - // path may be ?? if addr2line cannot resolve, line may be ? + // Result will be of the form " at " path:line + // The path may be ?? if addr2line cannot resolve, line may be ? + // Edge cases: + // ?? ??:0 const auto& line = output[i]; - auto at_location = line.find(" at "); - internal_verify(at_location != std::string::npos); - auto symbol = line.substr(0, at_location); + std::size_t at_location = line.find(" at "); + std::size_t symbol_end; + std::size_t filename_start; + if(at_location != std::string::npos) { + symbol_end = at_location; + filename_start = at_location + 4; + } else { + internal_verify(line.find("?? ") == 0, "Unexpected edge case while processing addr2line output"); + symbol_end = 2; + filename_start = 3; + } + auto symbol = line.substr(0, symbol_end); auto colon = line.rfind(":"); internal_verify(colon != std::string::npos); - internal_verify(colon > at_location); - auto filename = line.substr(at_location + 4, colon - at_location - 4); + internal_verify(colon > filename_start); + auto filename = line.substr(filename_start, colon - filename_start); auto line_number = line.substr(colon + 1); if(line_number != "?") { entries_vec[i].second.get().line = std::stoi(line_number); diff --git a/test/expected/macos.txt b/test/expected/macos.txt new file mode 100644 index 0000000..4882533 --- /dev/null +++ b/test/expected/macos.txt @@ -0,0 +1,25 @@ +build/test||0||trace() +build/test||0||foo(int) +build/test||0||foo(int) +build/test||0||foo(int) +build/test||0||foo(int) +build/test||0||foo(int) +build/test||0||foo(int) +build/test||0||foo(int) +build/test||0||foo(int) +build/test||0||foo(int) +build/test||0||foo(int) +build/test||0||foo(int) +build/test||0||void foo(int, int) +build/test||0||void foo(int, int, int) +build/test||0||void foo(int, int, int, int) +build/test||0||void foo(int, int, int, int, int) +build/test||0||void foo(int, int, int, int, int, int) +build/test||0||void foo(int, int, int, int, int, int, int) +build/test||0||void foo(int, int, int, int, int, int, int, int) +build/test||0||void foo(int, int, int, int, int, int, int, int, int) +build/test||0||void foo(int, int, int, int, int, int, int, int, int, int) +build/test||0||function_two(int, float) +build/test||0||function_one(int) +build/test||0||main +/usr/lib/dyld||0||start \ No newline at end of file diff --git a/test/test.cpp b/test/test.cpp index 0f4ea76..9c267f9 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -7,7 +7,8 @@ std::string normalize_filename(std::string name) { if(name.find('/') == 0 || (name.find(':') == 1 && std::isupper(name[0]))) { - auto p = std::min(name.rfind("test/"), name.rfind("test\\")); + // build/test if the file is really an object name resolved by libdl + auto p = std::min({name.rfind("test/"), name.rfind("test\\"), name.rfind("build/test")}); return p == std::string::npos ? name : name.substr(p); } else { return name; diff --git a/test/test.py b/test/test.py index e3213eb..dd0cf6b 100644 --- a/test/test.py +++ b/test/test.py @@ -1,5 +1,6 @@ import os import sys +import platform from typing import List MAX_LINE_DIFF = 2 @@ -28,8 +29,10 @@ def main(): elif sys.argv[1].startswith("cl"): target.append("msvc") - if os.name == "nt": + if platform.system() == "Windows": target.append("windows") + elif platform.system() == "Darwin": + target.append("macos") else: target.append("linux")