├── .bazeliskrc ├── .bazelrc ├── .github └── workflows │ ├── ci.yml │ └── sonarlint.yml ├── .gitignore ├── BUILD.bazel ├── CHANGELOG.md ├── CMakeLists.txt ├── CONTRIBUTING.md ├── LICENSE ├── MODULE.bazel ├── MODULE.bazel.lock ├── Makefile ├── README.md ├── SECURITY.md ├── WORKSPACE ├── benchmarking ├── CMakeLists.txt └── unwinding.cpp ├── ci ├── build-in-all-remaining-configs.py ├── dump_msvc_env.ps1 ├── setup-prerequisites-mingw.ps1 ├── setup-prerequisites-unittest-macos.sh ├── setup-prerequisites-unittest.sh ├── setup-prerequisites.sh ├── speedtest.py ├── test-all-configs.py ├── unittest.py └── util.py ├── cmake ├── Autoconfig.cmake ├── Findzstd.cmake ├── InstallRules.cmake ├── OptionVariables.cmake ├── PreventInSourceBuilds.cmake ├── ProjectIsTopLevel.cmake ├── has_attribute_packed.cpp ├── has_backtrace.cpp ├── has_cxx_exception_type.cpp ├── has_cxxabi.cpp ├── has_dl.cpp ├── has_dl_find_object.cpp ├── has_dladdr1.cpp ├── has_execinfo.cpp ├── has_mach_vm.cpp ├── has_stackwalk.cpp ├── has_unwind.cpp └── in │ ├── cpptrace-config-cmake.in │ └── version-hpp.in ├── docs ├── c-api.md └── signal-safe-tracing.md ├── include ├── cpptrace │ ├── basic.hpp │ ├── cpptrace.hpp │ ├── exceptions.hpp │ ├── formatting.hpp │ ├── forward.hpp │ ├── from_current.hpp │ ├── gdb_jit.hpp │ ├── io.hpp │ └── utils.hpp └── ctrace │ └── ctrace.h ├── res ├── demo.png ├── exception.png ├── from_current.png ├── inlining.png └── snippets.png ├── sonar-project.properties ├── src ├── binary │ ├── elf.cpp │ ├── elf.hpp │ ├── mach-o.cpp │ ├── mach-o.hpp │ ├── module_base.cpp │ ├── module_base.hpp │ ├── object.cpp │ ├── object.hpp │ ├── pe.cpp │ ├── pe.hpp │ ├── safe_dl.cpp │ └── safe_dl.hpp ├── cpptrace.cpp ├── ctrace.cpp ├── demangle │ ├── demangle.hpp │ ├── demangle_with_cxxabi.cpp │ ├── demangle_with_nothing.cpp │ └── demangle_with_winapi.cpp ├── exceptions.cpp ├── formatting.cpp ├── from_current.cpp ├── jit │ ├── jit_objects.cpp │ └── jit_objects.hpp ├── options.cpp ├── options.hpp ├── platform │ ├── dbghelp_utils.cpp │ ├── dbghelp_utils.hpp │ ├── exception_type.hpp │ ├── path.hpp │ ├── platform.hpp │ └── program_name.hpp ├── snippets │ ├── snippet.cpp │ └── snippet.hpp ├── symbols │ ├── dwarf │ │ ├── debug_map_resolver.cpp │ │ ├── dwarf.hpp │ │ ├── dwarf_options.cpp │ │ ├── dwarf_options.hpp │ │ ├── dwarf_resolver.cpp │ │ ├── dwarf_utils.hpp │ │ └── resolver.hpp │ ├── symbols.hpp │ ├── symbols_core.cpp │ ├── symbols_with_addr2line.cpp │ ├── symbols_with_dbghelp.cpp │ ├── symbols_with_dl.cpp │ ├── symbols_with_libbacktrace.cpp │ ├── symbols_with_libdwarf.cpp │ └── symbols_with_nothing.cpp ├── unwind │ ├── unwind.hpp │ ├── unwind_with_dbghelp.cpp │ ├── unwind_with_execinfo.cpp │ ├── unwind_with_libunwind.cpp │ ├── unwind_with_nothing.cpp │ ├── unwind_with_unwind.cpp │ └── unwind_with_winapi.cpp ├── utils.cpp └── utils │ ├── common.hpp │ ├── error.cpp │ ├── error.hpp │ ├── io │ ├── base_file.hpp │ ├── file.cpp │ ├── file.hpp │ ├── memory_file_view.cpp │ └── memory_file_view.hpp │ ├── lru_cache.hpp │ ├── microfmt.cpp │ ├── microfmt.hpp │ ├── optional.hpp │ ├── result.hpp │ ├── span.hpp │ ├── string_view.cpp │ ├── string_view.hpp │ ├── utils.cpp │ └── utils.hpp ├── test ├── BUILD.bazel ├── CMakeLists.txt ├── add_subdirectory-integration │ ├── CMakeLists.txt │ └── main.cpp ├── ctrace_demo.c ├── demo.cpp ├── expected │ ├── linux.libdl.txt │ ├── linux.txt │ ├── macos.clang.libdl.txt │ ├── macos.clang.txt │ ├── macos.gcc.addr2line.txt │ ├── macos.gcc.libdl.txt │ ├── macos.gcc.txt │ ├── windows.gcc.txt │ └── windows.txt ├── fetchcontent-integration │ ├── CMakeLists.txt │ └── main.cpp ├── findpackage-integration │ ├── CMakeLists.txt │ └── main.cpp ├── integration.cpp ├── jank │ ├── Dockerfile │ ├── Makefile │ └── entry.sh ├── signal_demo.cpp ├── signal_tracer.cpp ├── speedtest │ ├── CMakeLists.txt │ └── speedtest.cpp └── unit │ ├── internals │ ├── general.cpp │ ├── lru_cache.cpp │ ├── optional.cpp │ ├── result.cpp │ ├── span.cpp │ ├── string_utils.cpp │ └── string_view.cpp │ ├── lib │ ├── formatting.cpp │ └── nullable.cpp │ ├── main.cpp │ └── tracing │ ├── common.hpp │ ├── from_current.cpp │ ├── from_current_z.cpp │ ├── object_trace.cpp │ ├── raw_trace.cpp │ ├── stacktrace.cpp │ └── traced_exception.cpp └── tools ├── CMakeLists.txt ├── dwarfdump ├── CMakeLists.txt └── main.cpp ├── resolver ├── CMakeLists.txt └── main.cpp └── symbol_tables ├── CMakeLists.txt └── main.cpp /.bazeliskrc: -------------------------------------------------------------------------------- 1 | USE_BAZEL_VERSION=7.2.1 2 | -------------------------------------------------------------------------------- /.bazelrc: -------------------------------------------------------------------------------- 1 | test --strip=never 2 | test --test_output=all 3 | test --copt=-g 4 | -------------------------------------------------------------------------------- /.github/workflows/sonarlint.yml: -------------------------------------------------------------------------------- 1 | on: 2 | # Trigger analysis when pushing in master or pull requests, and when creating 3 | # a pull request. 4 | push: 5 | branches: 6 | - main 7 | - dev 8 | name: sonarlint 9 | jobs: 10 | sonarcloud: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | with: 15 | # Disabling shallow clone is recommended for improving relevancy of reporting 16 | fetch-depth: 0 17 | - name: Install sonar-scanner and build-wrapper 18 | uses: sonarsource/sonarcloud-github-c-cpp@v2 19 | - name: Run build-wrapper 20 | run: | 21 | mkdir build 22 | cd build 23 | cmake .. -DCMAKE_BUILD_TYPE=Debug -DCPPTRACE_BUILD_TESTING=On -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCMAKE_CXX_STANDARD=11 24 | make -j 25 | cd .. 26 | - name: Run sonar-scanner 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 30 | run: sonar-scanner 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | .idea/ 3 | 4 | a.out 5 | build*/ 6 | repro*/ 7 | __pycache__/ 8 | scratch 9 | tmp/ 10 | bazel-*/ 11 | cmake-build-*/ 12 | 13 | test/jank/data 14 | -------------------------------------------------------------------------------- /BUILD.bazel: -------------------------------------------------------------------------------- 1 | cc_library( 2 | name = "cpptrace", 3 | srcs = glob([ 4 | "src/**/*.hpp", 5 | "src/**/*.cpp", 6 | ]), 7 | local_defines = [ 8 | "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF", 9 | "CPPTRACE_DEMANGLE_WITH_CXXABI", 10 | "CPPTRACE_UNWIND_WITH_LIBUNWIND" 11 | ], 12 | hdrs = glob([ 13 | "include/cpptrace/*.hpp", 14 | "include/ctrace/*.h", 15 | ]), 16 | includes = [ 17 | "include", 18 | "src" 19 | ], 20 | deps = [ 21 | "@libdwarf//:libdwarf", 22 | "@libunwind//:libunwind" 23 | ], 24 | copts = [ 25 | "-Wall", 26 | "-Wextra", 27 | "-Werror=return-type", 28 | "-Wundef", 29 | "-Wuninitialized", 30 | "-fPIC", 31 | "-std=c++11" 32 | ], 33 | visibility = ["//visibility:public"], 34 | ) 35 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Welcome, thank you for your interest in the project! 4 | 5 | ## Getting started 6 | 7 | Contributions are always welcome. If you have not already, consider joining the community discord 8 | (linked in the README). There is discussion about library development there as well as a development 9 | roadmap. Github issues are also a good place to start. 10 | 11 | I'm happy to merge fixes, improvements, and features as well as help with getting pull requests 12 | (PRs) over the finish line. That being said, I can't merge stylistic changes, 13 | premature-optimizations, or micro-optimizations. 14 | 15 | When contributing, please try to match the current code style in the codebase. Style doesn't matter 16 | too much ultimately but consistency within a codebase is important. 17 | 18 | ## Local development 19 | 20 | The easiest way to get started with local development is running `make debug` (which automatically configures cmake and 21 | builds). Note: This requires ninja at the moment. 22 | 23 | For more control over how the library is built you can manually build with cmake: 24 | 25 | `cmake ..` in a `build/` folder along with any cmake configurations you desire. Then run `make -j` or `ninja` or 26 | `msbuild cpptrace.sln`. 27 | 28 | Some useful configurations: 29 | - `-DCMAKE_BUILD_TYPE=Debug|Release|RelWithDebInfo`: Build in debug / release / etc. 30 | - `-DBUILD_SHARED_LIBS=On`: Build shared library 31 | - `-DCPPTRACE_SANITIZER_BUILD=On`: Turn on sanitizers 32 | - `-DCPPTRACE_BUILD_TESTING=On`: Build small test and demo program 33 | 34 | ## Testing 35 | 36 | Unfortunately because this library is so platform-dependent the best way to test thoroughly is with 37 | github's CI. This will happen automatically when you open a PR. 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023-2024 Jeremy Rifkin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 6 | associated documentation files (the "Software"), to deal in the Software without restriction, 7 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 8 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or substantial 12 | portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 15 | NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 16 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES 17 | OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /MODULE.bazel: -------------------------------------------------------------------------------- 1 | module( 2 | name = "cpptrace", 3 | ) 4 | 5 | bazel_dep(name = "googletest", version = "1.14.0") 6 | bazel_dep(name = "bazel_skylib", version = "1.7.1") 7 | bazel_dep(name = "rules_foreign_cc", version = "0.11.1") 8 | bazel_dep(name = "zstd", version = "1.5.6") 9 | bazel_dep(name = "zlib", version = "1.3.1") 10 | bazel_dep(name = "xz", version = "5.4.5.bcr.2") 11 | bazel_dep(name = "toolchains_llvm", version = "1.1.2") 12 | 13 | # Configure and register the toolchain. 14 | llvm = use_extension("@toolchains_llvm//toolchain/extensions:llvm.bzl", "llvm", dev_dependency = True) 15 | 16 | llvm.toolchain( 17 | llvm_versions = { 18 | "": "18.1.8", 19 | }, 20 | sha256 = { 21 | "": "54ec30358afcc9fb8aa74307db3046f5187f9fb89fb37064cdde906e062ebf36", 22 | }, 23 | strip_prefix = { 24 | "": "clang+llvm-18.1.8-x86_64-linux-gnu-ubuntu-18.04", 25 | }, 26 | urls = { 27 | "": ["https://github.com/llvm/llvm-project/releases/download/llvmorg-18.1.8/clang+llvm-18.1.8-x86_64-linux-gnu-ubuntu-18.04.tar.xz"], 28 | }, 29 | ) 30 | 31 | use_repo(llvm, "llvm_toolchain") 32 | 33 | register_toolchains("@llvm_toolchain//:all", dev_dependency = True) 34 | 35 | http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 36 | 37 | http_archive( 38 | name = "libdwarf", 39 | build_file_content = 40 | """ 41 | package(default_visibility = ["//visibility:public"]) 42 | load("@rules_foreign_cc//foreign_cc:defs.bzl", "cmake") 43 | 44 | filegroup( 45 | name = "sources", 46 | srcs = glob(["**/*"]), 47 | ) 48 | cmake( 49 | name = "libdwarf", 50 | build_args = ["-j12"], 51 | lib_source = ":sources", 52 | out_static_libs = ["libdwarf.a"], 53 | copts = ["-Wall", "-Werror"], 54 | deps = [ 55 | "@zstd", 56 | "@zlib" 57 | ] 58 | ) 59 | """, 60 | sha256 = "4ab8ae7b4b7aa42453725054b348f4fdb2460d5ba644199a1305311c718ff416", 61 | strip_prefix = "libdwarf-code-0.10.1", 62 | url = "https://github.com/davea42/libdwarf-code/archive/refs/tags/v0.10.1.tar.gz", 63 | ) 64 | 65 | 66 | 67 | http_archive( 68 | name = "libunwind", 69 | build_file_content = 70 | """ 71 | package(default_visibility = ["//visibility:public"]) 72 | load("@rules_foreign_cc//foreign_cc:defs.bzl", "configure_make") 73 | 74 | filegroup( 75 | name = "sources", 76 | srcs = glob(["**/*"]), 77 | ) 78 | configure_make( 79 | name = "libunwind", 80 | args = ["-j12"], 81 | autoreconf = True, 82 | configure_in_place = True, 83 | autoreconf_options = [ 84 | "-i", 85 | ], 86 | lib_source = ":sources", 87 | out_static_libs = [ 88 | "libunwind.a", 89 | "libunwind-coredump.a", 90 | "libunwind-ptrace.a", 91 | "libunwind-x86_64.a", 92 | "libunwind-generic.a", 93 | "libunwind-setjmp.a" 94 | ], 95 | deps = [ 96 | "@xz//:lzma" 97 | ] 98 | ) 99 | """, 100 | sha256 = "38833b7b1582db7d76485a62a213706c9252b3dab7380069fea5824e823d8e41", 101 | strip_prefix = "libunwind-1.8.1", 102 | url = "https://github.com/libunwind/libunwind/archive/refs/tags/v1.8.1.tar.gz", 103 | ) 104 | 105 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default: help 2 | 3 | # The general philosophy and functionality of this makefile is shamelessly stolen from compiler explorer 4 | 5 | help: # with thanks to Ben Rady 6 | @grep -E '^[0-9a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' 7 | 8 | .PHONY: build 9 | build: debug ## build in debug mode 10 | 11 | build/configured-debug: 12 | cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On -DCPPTRACE_BUILD_TOOLS=On 13 | rm -f build/configured-release 14 | touch build/configured-debug 15 | 16 | build/configured-release: 17 | cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On -DCPPTRACE_BUILD_TOOLS=On 18 | rm -f build/configured-debug 19 | touch build/configured-release 20 | 21 | .PHONY: configure-debug 22 | configure-debug: build/configured-debug 23 | 24 | .PHONY: configure-release 25 | configure-release: build/configured-release 26 | 27 | .PHONY: debug 28 | debug: configure-debug ## build in debug mode 29 | cmake --build build 30 | 31 | .PHONY: release 32 | release: configure-release ## build in release mode (with debug info) 33 | cmake --build build 34 | 35 | .PHONY: debug-msvc 36 | debug-msvc: ## build in debug mode 37 | cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On -DCPPTRACE_BUILD_TOOLS=On 38 | cmake --build build --config Debug 39 | 40 | .PHONY: release-msvc 41 | release-msvc: ## build in release mode (with debug info) 42 | cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On -DCPPTRACE_BUILD_TOOLS=On 43 | cmake --build build --config RelWithDebInfo 44 | 45 | .PHONY: clean 46 | clean: ## clean 47 | rm -rf build 48 | 49 | .PHONY: test 50 | test: debug ## test 51 | cd build && ninja test 52 | 53 | .PHONY: test-release 54 | test-release: release ## test-release 55 | cd build && ninja test 56 | 57 | # .PHONY: test-msvc 58 | # test-msvc: debug-msvc ## test 59 | # cmake --build build --target RUN_TESTS --config Debug 60 | 61 | # .PHONY: test-msvc-release 62 | # test-msvc-release: release-msvc ## test-release 63 | # cmake --build build --target RUN_TESTS --config Release 64 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | We take security seriously and I'm grateful for reports of security vulnerabilities. 4 | 5 | ## Reporting a Vulnerability 6 | 7 | If the vulnerability can be reported without revealing exploitable specifics, please open an issue. 8 | 9 | If the vulnerability can't be reported publicly without leaving an obvious exploit in the public eye please email me 10 | at jeremy@rifkin.dev or reach out to me on [discord](https://discord.gg/7kv5AuCndG). 11 | 12 | I will do my best to get back to you within a day. 13 | -------------------------------------------------------------------------------- /WORKSPACE: -------------------------------------------------------------------------------- 1 | workspace(name = "cpptrace") 2 | -------------------------------------------------------------------------------- /benchmarking/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(CTest) 2 | 3 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) 4 | 5 | set( 6 | warning_options 7 | ${warning_options} $<$:-Wno-infinite-recursion> 8 | ) 9 | 10 | include(FetchContent) 11 | set(BENCHMARK_ENABLE_TESTING OFF) 12 | FetchContent_Declare( 13 | googlebench 14 | GIT_REPOSITORY "https://github.com/google/benchmark.git" 15 | GIT_TAG 12235e24652fc7f809373e7c11a5f73c5763fc4c # v1.9.0 16 | ) 17 | FetchContent_MakeAvailable(googlebench) 18 | 19 | add_executable(benchmark_unwinding unwinding.cpp) 20 | target_compile_features(benchmark_unwinding PRIVATE cxx_std_20) 21 | target_link_libraries(benchmark_unwinding PRIVATE ${target_name} benchmark::benchmark) 22 | -------------------------------------------------------------------------------- /benchmarking/unwinding.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | struct unwind_benchmark_info { 8 | benchmark::State& state; 9 | size_t& stack_depth; 10 | }; 11 | 12 | void unwind_loop(unwind_benchmark_info info) { 13 | auto& [state, depth] = info; 14 | depth = cpptrace::generate_raw_trace().frames.size(); 15 | for(auto _ : state) { 16 | benchmark::DoNotOptimize(cpptrace::generate_raw_trace()); 17 | } 18 | } 19 | 20 | void foo(unwind_benchmark_info info, int n) { 21 | if(n == 0) { 22 | unwind_loop(info); 23 | } else { 24 | foo(info, n - 1); 25 | } 26 | } 27 | 28 | template 29 | void foo(unwind_benchmark_info info, int, Args... args) { 30 | foo(info, args...); 31 | } 32 | 33 | void function_two(unwind_benchmark_info info, int, float) { 34 | foo(info, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 35 | } 36 | 37 | void function_one(unwind_benchmark_info info, int) { 38 | function_two(info, 0, 0); 39 | } 40 | 41 | static void unwinding(benchmark::State& state) { 42 | size_t stack_depth = 0; 43 | function_one({state, stack_depth}, 0); 44 | static bool did_print = false; 45 | if(!did_print) { 46 | did_print = true; 47 | std::cerr<<"[info] Unwinding benchmark stack depth: "< %temp%\vcvars.txt" 2 | -------------------------------------------------------------------------------- /ci/setup-prerequisites-mingw.ps1: -------------------------------------------------------------------------------- 1 | mkdir zstd 2 | cd zstd 3 | git init 4 | git remote add origin https://github.com/facebook/zstd.git 5 | git fetch --depth 1 origin 63779c798237346c2b245c546c40b72a5a5913fe # 1.5.5 6 | git checkout FETCH_HEAD 7 | cd build/cmake 8 | mkdir build 9 | cd build 10 | cmake .. -DZSTD_BUILD_SHARED=On -DZSTD_BUILD_SHARED=Off -DZSTD_LEGACY_SUPPORT=Off -DZSTD_BUILD_PROGRAMS=Off -DZSTD_BUILD_CONTRIB=Off -DZSTD_BUILD_TESTS=Off -G"Unix Makefiles" 11 | make -j 12 | make install 13 | 14 | cd ../../../.. 15 | 16 | mkdir libdwarf 17 | cd libdwarf 18 | git init 19 | git remote add origin https://github.com/jeremy-rifkin/libdwarf-lite.git 20 | git fetch --depth 1 origin fe09ca800b988e2ff21225ac5e7468ceade2a30e 21 | git checkout FETCH_HEAD 22 | mkdir build 23 | cd build 24 | cmake .. -DPIC_ALWAYS=TRUE -DBUILD_DWARFDUMP=FALSE -G"Unix Makefiles" 25 | make -j 26 | make install 27 | -------------------------------------------------------------------------------- /ci/setup-prerequisites-unittest-macos.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | mkdir zstd 4 | cd zstd 5 | git init 6 | git remote add origin https://github.com/facebook/zstd.git 7 | git fetch --depth 1 origin 794ea1b0afca0f020f4e57b6732332231fb23c70 # 1.5.6 8 | git checkout FETCH_HEAD 9 | make -j 10 | sudo make install 11 | 12 | cd .. 13 | 14 | mkdir libdwarf 15 | cd libdwarf 16 | git init 17 | git remote add origin https://github.com/jeremy-rifkin/libdwarf-lite.git 18 | git fetch --depth 1 origin fe09ca800b988e2ff21225ac5e7468ceade2a30e # 0.11.1 19 | git checkout FETCH_HEAD 20 | mkdir build 21 | cd build 22 | cmake .. -GNinja -DPIC_ALWAYS=TRUE -DBUILD_DWARFDUMP=FALSE 23 | sudo ninja install 24 | 25 | cd ../.. 26 | 27 | mkdir googletest 28 | cd googletest 29 | git init 30 | git remote add origin https://github.com/google/googletest.git 31 | git fetch --depth 1 origin f8d7d77c06936315286eb55f8de22cd23c188571 32 | git checkout FETCH_HEAD 33 | mkdir build 34 | cd build 35 | cmake .. -GNinja -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_INSTALL_PREFIX=/tmp/gtest_install 36 | sudo ninja install 37 | rm -rf * 38 | # There's a false-positive container-overflow for apple clang/relwithdebinfo/sanitizers=on if gtest isn't built with 39 | # sanitizers. https://github.com/google/sanitizers/wiki/AddressSanitizerContainerOverflow#false-positives 40 | cmake .. -GNinja -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_INSTALL_PREFIX=/tmp/gtest_asan_install -DCMAKE_CXX_FLAGS=-fsanitize=address 41 | sudo ninja install 42 | rm -rf * 43 | cmake .. -GNinja -DCMAKE_CXX_COMPILER=g++-12 -DCMAKE_INSTALL_PREFIX=/tmp/gtest_install_gcc 44 | sudo ninja install 45 | -------------------------------------------------------------------------------- /ci/setup-prerequisites-unittest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | mkdir zstd 4 | cd zstd 5 | git init 6 | git remote add origin https://github.com/facebook/zstd.git 7 | git fetch --depth 1 origin 794ea1b0afca0f020f4e57b6732332231fb23c70 # 1.5.6 8 | git checkout FETCH_HEAD 9 | make -j 10 | sudo make install 11 | 12 | cd .. 13 | 14 | mkdir libdwarf 15 | cd libdwarf 16 | git init 17 | git remote add origin https://github.com/jeremy-rifkin/libdwarf-lite.git 18 | git fetch --depth 1 origin fe09ca800b988e2ff21225ac5e7468ceade2a30e # 0.11.1 19 | git checkout FETCH_HEAD 20 | mkdir build 21 | cd build 22 | cmake .. -GNinja -DPIC_ALWAYS=TRUE -DBUILD_DWARFDUMP=FALSE 23 | sudo ninja install 24 | 25 | cd ../.. 26 | 27 | mkdir googletest 28 | cd googletest 29 | git init 30 | git remote add origin https://github.com/google/googletest.git 31 | git fetch --depth 1 origin f8d7d77c06936315286eb55f8de22cd23c188571 32 | git checkout FETCH_HEAD 33 | mkdir build 34 | cd build 35 | cmake .. -GNinja -DCMAKE_INSTALL_PREFIX=/tmp/gtest_install 36 | sudo ninja install 37 | rm -rf * 38 | cmake .. -GNinja -DCMAKE_CXX_COMPILER=clang++-18 -DCMAKE_CXX_FLAGS=-stdlib=libc++ -DCMAKE_INSTALL_PREFIX=/tmp/gtest_install_libcxx 39 | sudo ninja install 40 | -------------------------------------------------------------------------------- /ci/setup-prerequisites.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sudo apt install libgtest-dev 3 | 4 | mkdir zstd 5 | cd zstd 6 | git init 7 | git remote add origin https://github.com/facebook/zstd.git 8 | git fetch --depth 1 origin 794ea1b0afca0f020f4e57b6732332231fb23c70 # 1.5.6 9 | git checkout FETCH_HEAD 10 | make -j 11 | sudo make install 12 | 13 | cd .. 14 | 15 | mkdir libdwarf 16 | cd libdwarf 17 | git init 18 | git remote add origin https://github.com/jeremy-rifkin/libdwarf-lite.git 19 | git fetch --depth 1 origin fe09ca800b988e2ff21225ac5e7468ceade2a30e 20 | git checkout FETCH_HEAD 21 | mkdir build 22 | cd build 23 | cmake .. -DPIC_ALWAYS=TRUE -DBUILD_DWARFDUMP=FALSE 24 | make -j 25 | sudo make install 26 | -------------------------------------------------------------------------------- /ci/speedtest.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import re 3 | import platform 4 | 5 | def main(): 6 | output = sys.stdin.read() 7 | 8 | print(output) 9 | 10 | print("-" * 50) 11 | 12 | time = int(re.search(r"\d+ tests? from \d+ test suites? ran. \((\d+) ms total\)", output).group(1)) 13 | 14 | dwarf4 = any(["DWARF4" in arg for arg in sys.argv[1:]]) 15 | dwarf5 = any(["DWARF5" in arg for arg in sys.argv[1:]]) 16 | clang = any(["clang" in arg for arg in sys.argv[1:]]) 17 | # Somehow -gdwarf-4 clang is fast after a completely unrelated PR? Weird. Something to do with static linking...? 18 | # https://github.com/jeremy-rifkin/cpptrace/pull/22 19 | expect_slow = False 20 | 21 | if platform.system() == "Windows": 22 | # For some reason SymInitialize is super slow on github's windows runner and it alone takes 250ms. Nothing we 23 | # can do about that. 24 | threshold = 350 # ms 25 | else: 26 | threshold = 100 # ms 27 | 28 | if expect_slow: 29 | if time > threshold: 30 | print(f"Success (expecting slow): Test program took {time} ms") 31 | else: 32 | print(f"Error (expecting slow): Test program took {time} ms") 33 | sys.exit(1) 34 | else: 35 | if time > threshold: 36 | print(f"Error: Test program took {time} ms") 37 | sys.exit(1) 38 | else: 39 | print(f"Success: Test program took {time} ms") 40 | 41 | 42 | main() 43 | -------------------------------------------------------------------------------- /cmake/Findzstd.cmake: -------------------------------------------------------------------------------- 1 | # Libdwarf needs zstd, cpptrace doesn't, and libdwarf has its own Findzstd but it doesn't define zstd::libzstd_static / 2 | # zstd::libzstd_shared targets which leads to issues, necessitating a find_dependency(zstd) in cpptrace's cmake config 3 | # and in order to support non-cmake-module installs we need to provide a Findzstd script. 4 | # https://github.com/jeremy-rifkin/cpptrace/issues/112 5 | 6 | # This will define 7 | # zstd_FOUND 8 | # zstd_INCLUDE_DIR 9 | # zstd_LIBRARY 10 | 11 | find_path(zstd_INCLUDE_DIR NAMES zstd.h) 12 | 13 | find_library(zstd_LIBRARY_DEBUG NAMES zstdd zstd_staticd) 14 | find_library(zstd_LIBRARY_RELEASE NAMES zstd zstd_static) 15 | 16 | include(SelectLibraryConfigurations) 17 | SELECT_LIBRARY_CONFIGURATIONS(zstd) 18 | 19 | include(FindPackageHandleStandardArgs) 20 | FIND_PACKAGE_HANDLE_STANDARD_ARGS( 21 | zstd DEFAULT_MSG 22 | zstd_LIBRARY zstd_INCLUDE_DIR 23 | ) 24 | 25 | if(zstd_FOUND) 26 | message(STATUS "Found Zstd: ${zstd_LIBRARY}") 27 | endif() 28 | 29 | mark_as_advanced(zstd_INCLUDE_DIR zstd_LIBRARY) 30 | 31 | if(zstd_FOUND) 32 | # just defining them the same... cmake will figure it out 33 | if(NOT TARGET zstd::libzstd_static) 34 | add_library(zstd::libzstd_static UNKNOWN IMPORTED) 35 | set_target_properties( 36 | zstd::libzstd_static 37 | PROPERTIES 38 | IMPORTED_LOCATION "${zstd_LIBRARIES}" 39 | INTERFACE_INCLUDE_DIRECTORIES "${zstd_INCLUDE_DIR}" 40 | ) 41 | endif() 42 | if(NOT TARGET zstd::libzstd_shared) 43 | add_library(zstd::libzstd_shared UNKNOWN IMPORTED) 44 | set_target_properties( 45 | zstd::libzstd_shared 46 | PROPERTIES 47 | IMPORTED_LOCATION "${zstd_LIBRARIES}" 48 | INTERFACE_INCLUDE_DIRECTORIES "${zstd_INCLUDE_DIR}" 49 | ) 50 | endif() 51 | endif() 52 | -------------------------------------------------------------------------------- /cmake/InstallRules.cmake: -------------------------------------------------------------------------------- 1 | include(CMakePackageConfigHelpers) 2 | 3 | # copy header files to CMAKE_INSTALL_INCLUDEDIR 4 | # don't include third party header files 5 | install( 6 | DIRECTORY 7 | "${PROJECT_SOURCE_DIR}/include/" # our header files 8 | "${PROJECT_BINARY_DIR}/include/" # generated header files 9 | DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" 10 | COMPONENT ${package_name}_development 11 | # PATTERN "**/third_party" EXCLUDE # skip third party directory 12 | # PATTERN "**/third_party/**" EXCLUDE # skip third party files 13 | ) 14 | 15 | # copy target build output artifacts to OS dependent locations 16 | # (Except includes, that just sets a compiler flag with the path) 17 | install( 18 | TARGETS ${target_name} 19 | EXPORT ${package_name}-targets 20 | RUNTIME # 21 | COMPONENT ${package_name}_runtime 22 | LIBRARY # 23 | COMPONENT ${package_name}_runtime 24 | NAMELINK_COMPONENT ${package_name}_development 25 | ARCHIVE # 26 | COMPONENT ${package_name}_development 27 | INCLUDES # 28 | DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" 29 | ) 30 | 31 | # create config file that points to targets file 32 | configure_file( 33 | "${PROJECT_SOURCE_DIR}/cmake/in/cpptrace-config-cmake.in" 34 | "${PROJECT_BINARY_DIR}/cmake/${package_name}-config.cmake" 35 | @ONLY 36 | ) 37 | 38 | # copy config file for find_package to find 39 | install( 40 | FILES "${PROJECT_BINARY_DIR}/cmake/${package_name}-config.cmake" 41 | DESTINATION "${CPPTRACE_INSTALL_CMAKEDIR}" 42 | COMPONENT ${package_name}_development 43 | ) 44 | 45 | # create version file for consumer to check version in CMake 46 | write_basic_package_version_file( 47 | "${package_name}-config-version.cmake" 48 | COMPATIBILITY SameMajorVersion # a.k.a SemVer 49 | ) 50 | 51 | # copy version file for find_package to find for version check 52 | install( 53 | FILES "${PROJECT_BINARY_DIR}/${package_name}-config-version.cmake" 54 | DESTINATION "${CPPTRACE_INSTALL_CMAKEDIR}" 55 | COMPONENT ${package_name}_development 56 | ) 57 | 58 | # create targets file included by config file with targets for consumers 59 | install( 60 | EXPORT ${package_name}-targets 61 | NAMESPACE cpptrace:: 62 | DESTINATION "${CPPTRACE_INSTALL_CMAKEDIR}" 63 | COMPONENT ${package_name}_development 64 | ) 65 | 66 | if(CPPTRACE_PROVIDE_EXPORT_SET) 67 | export( 68 | TARGETS ${target_name} 69 | NAMESPACE cpptrace:: 70 | FILE "${PROJECT_BINARY_DIR}/${package_name}-targets.cmake" 71 | ) 72 | endif() 73 | 74 | # Findzstd.cmake 75 | # vcpkg doesn't like anything being put in share/, which is where this goes apparently on their setup 76 | if(NOT CPPTRACE_VCPKG) 77 | install( 78 | FILES "${PROJECT_SOURCE_DIR}/cmake/Findzstd.cmake" 79 | DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${package_name}" 80 | ) 81 | endif() 82 | 83 | # support packaging library 84 | if(PROJECT_IS_TOP_LEVEL) 85 | include(CPack) 86 | endif() 87 | -------------------------------------------------------------------------------- /cmake/PreventInSourceBuilds.cmake: -------------------------------------------------------------------------------- 1 | # In-source build guard 2 | if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) 3 | message( 4 | FATAL_ERROR 5 | "In-source builds are not supported. " 6 | "You may need to delete 'CMakeCache.txt' and 'CMakeFiles/' before rebuilding this project." 7 | ) 8 | endif() 9 | -------------------------------------------------------------------------------- /cmake/ProjectIsTopLevel.cmake: -------------------------------------------------------------------------------- 1 | # This variable is set by project() in CMake 3.21+ 2 | string( 3 | COMPARE EQUAL 4 | "${CMAKE_SOURCE_DIR}" "${PROJECT_SOURCE_DIR}" 5 | PROJECT_IS_TOP_LEVEL 6 | ) 7 | -------------------------------------------------------------------------------- /cmake/has_attribute_packed.cpp: -------------------------------------------------------------------------------- 1 | struct __attribute__((packed)) foo { 2 | int i; 3 | double d; 4 | }; 5 | 6 | int main() {} 7 | -------------------------------------------------------------------------------- /cmake/has_backtrace.cpp: -------------------------------------------------------------------------------- 1 | #ifdef CPPTRACE_BACKTRACE_PATH 2 | #include CPPTRACE_BACKTRACE_PATH 3 | #else 4 | #include 5 | #endif 6 | 7 | int main() { 8 | backtrace_state* state = backtrace_create_state(nullptr, true, nullptr, nullptr); 9 | } 10 | -------------------------------------------------------------------------------- /cmake/has_cxx_exception_type.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | std::type_info* t = abi::__cxa_current_exception_type(); 5 | (void*) t; 6 | } 7 | -------------------------------------------------------------------------------- /cmake/has_cxxabi.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | int status; 5 | abi::__cxa_demangle("_Z3foov", nullptr, nullptr, &status); 6 | } 7 | -------------------------------------------------------------------------------- /cmake/has_dl.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | Dl_info info; 5 | dladdr(nullptr, &info); 6 | } 7 | -------------------------------------------------------------------------------- /cmake/has_dl_find_object.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | dl_find_object result; 5 | _dl_find_object(reinterpret_cast(main), &result); 6 | } 7 | -------------------------------------------------------------------------------- /cmake/has_dladdr1.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { 5 | Dl_info info; 6 | link_map* link_map_info; 7 | dladdr1(reinterpret_cast(&main), &info, reinterpret_cast(&link_map_info), RTLD_DL_LINKMAP); 8 | } 9 | -------------------------------------------------------------------------------- /cmake/has_execinfo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | void* frames[10]; 5 | backtrace(frames, 10); 6 | } 7 | -------------------------------------------------------------------------------- /cmake/has_mach_vm.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main() { 6 | mach_vm_size_t vmsize; 7 | uintptr_t addr = reinterpret_cast(&vmsize); 8 | uintptr_t page_addr = addr & ~(4096 - 1); 9 | mach_vm_address_t address = (mach_vm_address_t)page_addr; 10 | vm_region_basic_info_data_t info; 11 | mach_msg_type_number_t info_count = 12 | sizeof(size_t) == 8 ? VM_REGION_BASIC_INFO_COUNT_64 : VM_REGION_BASIC_INFO_COUNT; 13 | memory_object_name_t object; 14 | mach_vm_region( 15 | mach_task_self(), 16 | &address, 17 | &vmsize, 18 | VM_REGION_BASIC_INFO, 19 | (vm_region_info_t)&info, 20 | &info_count, 21 | &object 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /cmake/has_stackwalk.cpp: -------------------------------------------------------------------------------- 1 | #define WIN32_LEAN_AND_MEAN 2 | #include 3 | #include 4 | 5 | #define IS_CLANG 0 6 | #define IS_GCC 0 7 | #define IS_MSVC 0 8 | 9 | #if defined(__clang__) 10 | #undef IS_CLANG 11 | #define IS_CLANG 1 12 | #elif defined(__GNUC__) || defined(__GNUG__) 13 | #undef IS_GCC 14 | #define IS_GCC 1 15 | #elif defined(_MSC_VER) 16 | #undef IS_MSVC 17 | #define IS_MSVC 1 18 | #else 19 | #error "Unsupported compiler" 20 | #endif 21 | 22 | int main() { 23 | HANDLE proc = GetCurrentProcess(); 24 | HANDLE thread = GetCurrentThread(); 25 | // https://jpassing.com/2008/03/12/walking-the-stack-of-the-current-thread/ 26 | 27 | // Get current thread context 28 | // GetThreadContext cannot be used on the current thread. 29 | // RtlCaptureContext doesn't work on i386 30 | CONTEXT context; 31 | #if defined(_M_IX86) || defined(__i386__) 32 | ZeroMemory(&context, sizeof(CONTEXT)); 33 | context.ContextFlags = CONTEXT_CONTROL; 34 | #if IS_MSVC 35 | __asm { 36 | label: 37 | mov [context.Ebp], ebp; 38 | mov [context.Esp], esp; 39 | mov eax, [label]; 40 | mov [context.Eip], eax; 41 | } 42 | #else 43 | asm( 44 | "label:\n\t" 45 | "mov{l %%ebp, %[cEbp] | %[cEbp], ebp};\n\t" 46 | "mov{l %%esp, %[cEsp] | %[cEsp], esp};\n\t" 47 | "mov{l $label, %%eax | eax, OFFSET label};\n\t" 48 | "mov{l %%eax, %[cEip] | %[cEip], eax};\n\t" 49 | : [cEbp] "=r" (context.Ebp), 50 | [cEsp] "=r" (context.Esp), 51 | [cEip] "=r" (context.Eip) 52 | ); 53 | #endif 54 | #else 55 | RtlCaptureContext(&context); 56 | #endif 57 | // Setup current frame 58 | STACKFRAME64 frame; 59 | ZeroMemory(&frame, sizeof(STACKFRAME64)); 60 | DWORD machine_type; 61 | #if defined(_M_IX86) || defined(__i386__) 62 | machine_type = IMAGE_FILE_MACHINE_I386; 63 | frame.AddrPC.Offset = context.Eip; 64 | frame.AddrPC.Mode = AddrModeFlat; 65 | frame.AddrFrame.Offset = context.Ebp; 66 | frame.AddrFrame.Mode = AddrModeFlat; 67 | frame.AddrStack.Offset = context.Esp; 68 | frame.AddrStack.Mode = AddrModeFlat; 69 | #elif defined(_M_X64) || defined(__x86_64__) 70 | machine_type = IMAGE_FILE_MACHINE_AMD64; 71 | frame.AddrPC.Offset = context.Rip; 72 | frame.AddrPC.Mode = AddrModeFlat; 73 | frame.AddrFrame.Offset = context.Rsp; 74 | frame.AddrFrame.Mode = AddrModeFlat; 75 | frame.AddrStack.Offset = context.Rsp; 76 | frame.AddrStack.Mode = AddrModeFlat; 77 | #elif defined(_M_IA64) || defined(__aarch64__) 78 | machine_type = IMAGE_FILE_MACHINE_IA64; 79 | frame.AddrPC.Offset = context.StIIP; 80 | frame.AddrPC.Mode = AddrModeFlat; 81 | frame.AddrFrame.Offset = context.IntSp; 82 | frame.AddrFrame.Mode = AddrModeFlat; 83 | frame.AddrBStore.Offset= context.RsBSP; 84 | frame.AddrBStore.Mode = AddrModeFlat; 85 | frame.AddrStack.Offset = context.IntSp; 86 | frame.AddrStack.Mode = AddrModeFlat; 87 | #else 88 | #error "Cpptrace: StackWalk64 not supported for this platform yet" 89 | #endif 90 | ZeroMemory(&context, sizeof(CONTEXT)); 91 | StackWalk64( 92 | machine_type, 93 | proc, 94 | thread, 95 | &frame, 96 | machine_type == IMAGE_FILE_MACHINE_I386 ? NULL : &context, 97 | NULL, 98 | SymFunctionTableAccess64, 99 | SymGetModuleBase64, 100 | NULL 101 | ); 102 | } 103 | -------------------------------------------------------------------------------- /cmake/has_unwind.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | _Unwind_Reason_Code unwind_callback(_Unwind_Context* context, void* arg) { 6 | _Unwind_GetIP(context); 7 | int is_before_instruction = 0; 8 | uintptr_t ip = _Unwind_GetIPInfo(context, &is_before_instruction); 9 | return _URC_END_OF_STACK; 10 | } 11 | 12 | int main() { 13 | _Unwind_Backtrace(unwind_callback, nullptr); 14 | } 15 | -------------------------------------------------------------------------------- /cmake/in/cpptrace-config-cmake.in: -------------------------------------------------------------------------------- 1 | # Init @ variables before doing anything else 2 | @PACKAGE_INIT@ 3 | 4 | # Dependencies 5 | if(@CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF@) 6 | include(CMakeFindDependencyMacro) 7 | # we don't go the Findzstd.cmake route on vcpkg 8 | if(@CPPTRACE_VCPKG@) 9 | find_dependency(zstd CONFIG REQUIRED) 10 | else() 11 | set(CMAKE_MODULE_PATH_OLD "${CMAKE_MODULE_PATH}") 12 | set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${CMAKE_CURRENT_LIST_DIR}") 13 | find_dependency(zstd) 14 | set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH_OLD}") 15 | unset(CMAKE_MODULE_PATH_OLD) 16 | endif() 17 | if(NOT @CPPTRACE_FIND_LIBDWARF_WITH_PKGCONFIG@) 18 | find_dependency(libdwarf REQUIRED) 19 | endif() 20 | endif() 21 | 22 | # We cannot modify an existing IMPORT target 23 | if(NOT TARGET cpptrace::cpptrace) 24 | 25 | # import targets 26 | include("${CMAKE_CURRENT_LIST_DIR}/@package_name@-targets.cmake") 27 | 28 | endif() 29 | 30 | if(@CPPTRACE_STATIC_DEFINE@) 31 | target_compile_definitions(cpptrace::cpptrace INTERFACE CPPTRACE_STATIC_DEFINE) 32 | endif() 33 | -------------------------------------------------------------------------------- /cmake/in/version-hpp.in: -------------------------------------------------------------------------------- 1 | #ifndef CPPTRACE_VERSION_HPP 2 | #define CPPTRACE_VERSION_HPP 3 | 4 | #define CPPTRACE_VERSION_MAJOR @CPPTRACE_VERSION_MAJOR@ 5 | #define CPPTRACE_VERSION_MINOR @CPPTRACE_VERSION_MINOR@ 6 | #define CPPTRACE_VERSION_PATCH @CPPTRACE_VERSION_PATCH@ 7 | 8 | #define CPPTRACE_TO_VERSION(MAJOR, MINOR, PATCH) ((MAJOR) * 10000 + (MINOR) * 100 + (PATCH)) 9 | #define CPPTRACE_VERSION CPPTRACE_TO_VERSION(CPPTRACE_VERSION_MAJOR, CPPTRACE_VERSION_MINOR, CPPTRACE_VERSION_PATCH) 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /include/cpptrace/cpptrace.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CPPTRACE_HPP 2 | #define CPPTRACE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /include/cpptrace/formatting.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CPPTRACE_FORMATTING_HPP 2 | #define CPPTRACE_FORMATTING_HPP 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace cpptrace { 10 | class CPPTRACE_EXPORT formatter { 11 | class impl; 12 | // can't be a std::unique_ptr due to msvc awfulness with dllimport/dllexport and https://stackoverflow.com/q/4145605/15675011 13 | impl* pimpl; 14 | 15 | public: 16 | formatter(); 17 | ~formatter(); 18 | 19 | formatter(formatter&&); 20 | formatter(const formatter&); 21 | formatter& operator=(formatter&&); 22 | formatter& operator=(const formatter&); 23 | 24 | formatter& header(std::string); 25 | enum class color_mode { 26 | always, 27 | none, 28 | automatic, 29 | }; 30 | formatter& colors(color_mode); 31 | enum class address_mode { 32 | raw, 33 | object, 34 | none, 35 | }; 36 | formatter& addresses(address_mode); 37 | enum class path_mode { 38 | // full path is used 39 | full, 40 | // only the file name is used 41 | basename, 42 | }; 43 | formatter& paths(path_mode); 44 | formatter& snippets(bool); 45 | formatter& snippet_context(int); 46 | formatter& columns(bool); 47 | formatter& filtered_frame_placeholders(bool); 48 | formatter& filter(std::function); 49 | formatter& transform(std::function); 50 | 51 | std::string format(const stacktrace_frame&) const; 52 | std::string format(const stacktrace_frame&, bool color) const; 53 | 54 | std::string format(const stacktrace&) const; 55 | std::string format(const stacktrace&, bool color) const; 56 | 57 | void print(const stacktrace_frame&) const; 58 | void print(const stacktrace_frame&, bool color) const; 59 | void print(std::ostream&, const stacktrace_frame&) const; 60 | void print(std::ostream&, const stacktrace_frame&, bool color) const; 61 | void print(std::FILE*, const stacktrace_frame&) const; 62 | void print(std::FILE*, const stacktrace_frame&, bool color) const; 63 | 64 | void print(const stacktrace&) const; 65 | void print(const stacktrace&, bool color) const; 66 | void print(std::ostream&, const stacktrace&) const; 67 | void print(std::ostream&, const stacktrace&, bool color) const; 68 | void print(std::FILE*, const stacktrace&) const; 69 | void print(std::FILE*, const stacktrace&, bool color) const; 70 | }; 71 | 72 | CPPTRACE_EXPORT const formatter& get_default_formatter(); 73 | } 74 | 75 | #endif 76 | -------------------------------------------------------------------------------- /include/cpptrace/forward.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CPPTRACE_FORWARD_HPP 2 | #define CPPTRACE_FORWARD_HPP 3 | 4 | #include 5 | 6 | namespace cpptrace { 7 | // Some type sufficient for an instruction pointer, currently always an alias to std::uintptr_t 8 | using frame_ptr = std::uintptr_t; 9 | 10 | struct raw_trace; 11 | struct object_trace; 12 | struct stacktrace; 13 | 14 | struct object_frame; 15 | struct stacktrace_frame; 16 | struct safe_object_frame; 17 | } 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /include/cpptrace/gdb_jit.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CPPTRACE_GDB_JIT_HPP 2 | #define CPPTRACE_GDB_JIT_HPP 3 | 4 | #include 5 | 6 | #include 7 | 8 | namespace cpptrace { 9 | namespace detail { 10 | // https://sourceware.org/gdb/current/onlinedocs/gdb.html/JIT-Interface.html 11 | extern "C" { 12 | typedef enum 13 | { 14 | JIT_NOACTION = 0, 15 | JIT_REGISTER_FN, 16 | JIT_UNREGISTER_FN 17 | } jit_actions_t; 18 | 19 | struct jit_code_entry 20 | { 21 | struct jit_code_entry *next_entry; 22 | struct jit_code_entry *prev_entry; 23 | const char *symfile_addr; 24 | uint64_t symfile_size; 25 | }; 26 | 27 | struct jit_descriptor 28 | { 29 | uint32_t version; 30 | /* This type should be jit_actions_t, but we use uint32_t 31 | to be explicit about the bitwidth. */ 32 | uint32_t action_flag; 33 | struct jit_code_entry *relevant_entry; 34 | struct jit_code_entry *first_entry; 35 | }; 36 | 37 | extern struct jit_descriptor __jit_debug_descriptor; 38 | } 39 | } 40 | 41 | namespace experimental { 42 | inline void register_jit_objects_from_gdb_jit_interface() { 43 | clear_all_jit_objects(); 44 | detail::jit_code_entry* entry = detail::__jit_debug_descriptor.first_entry; 45 | while(entry) { 46 | register_jit_object(entry->symfile_addr, entry->symfile_size); 47 | entry = entry->next_entry; 48 | } 49 | } 50 | } 51 | } 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /include/cpptrace/io.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CPPTRACE_IO_HPP 2 | #define CPPTRACE_IO_HPP 3 | 4 | #include 5 | 6 | #include 7 | 8 | #ifndef CPPTRACE_NO_STD_FORMAT 9 | #if __cplusplus >= 202002L 10 | #ifdef __has_include 11 | #if __has_include() 12 | #define CPPTRACE_STD_FORMAT 13 | #include 14 | #endif 15 | #endif 16 | #endif 17 | #endif 18 | 19 | #ifdef _MSC_VER 20 | #pragma warning(push) 21 | // warning C4251: using non-dll-exported type in dll-exported type, firing on std::vector and others for some 22 | // reason 23 | // 4275 is the same thing but for base classes 24 | #pragma warning(disable: 4251; disable: 4275) 25 | #endif 26 | 27 | namespace cpptrace { 28 | std::ostream& operator<<(std::ostream& stream, const stacktrace_frame& frame); 29 | std::ostream& operator<<(std::ostream& stream, const stacktrace& trace); 30 | } 31 | 32 | #if defined(CPPTRACE_STD_FORMAT) && defined(__cpp_lib_format) 33 | template <> 34 | struct std::formatter : std::formatter { 35 | auto format(cpptrace::stacktrace_frame frame, format_context& ctx) const { 36 | return formatter::format(frame.to_string(), ctx); 37 | } 38 | }; 39 | 40 | template <> 41 | struct std::formatter : std::formatter { 42 | auto format(cpptrace::stacktrace trace, format_context& ctx) const { 43 | return formatter::format(trace.to_string(), ctx); 44 | } 45 | }; 46 | #endif 47 | 48 | #ifdef _MSC_VER 49 | #pragma warning(pop) 50 | #endif 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /include/cpptrace/utils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CPPTRACE_UTILS_HPP 2 | #define CPPTRACE_UTILS_HPP 3 | 4 | #include 5 | 6 | #ifdef _MSC_VER 7 | #pragma warning(push) 8 | // warning C4251: using non-dll-exported type in dll-exported type, firing on std::vector and others for some 9 | // reason 10 | // 4275 is the same thing but for base classes 11 | #pragma warning(disable: 4251; disable: 4275) 12 | #endif 13 | 14 | namespace cpptrace { 15 | CPPTRACE_EXPORT std::string demangle(const std::string& name); 16 | CPPTRACE_EXPORT std::string get_snippet( 17 | const std::string& path, 18 | std::size_t line, 19 | std::size_t context_size, 20 | bool color = false 21 | ); 22 | CPPTRACE_EXPORT bool isatty(int fd); 23 | 24 | CPPTRACE_EXPORT extern const int stdin_fileno; 25 | CPPTRACE_EXPORT extern const int stderr_fileno; 26 | CPPTRACE_EXPORT extern const int stdout_fileno; 27 | 28 | CPPTRACE_EXPORT void register_terminate_handler(); 29 | 30 | // options: 31 | CPPTRACE_EXPORT void absorb_trace_exceptions(bool absorb); 32 | CPPTRACE_EXPORT void enable_inlined_call_resolution(bool enable); 33 | 34 | enum class cache_mode { 35 | // Only minimal lookup tables 36 | prioritize_memory = 0, 37 | // Build lookup tables but don't keep them around between trace calls 38 | hybrid = 1, 39 | // Build lookup tables as needed 40 | prioritize_speed = 2 41 | }; 42 | 43 | namespace experimental { 44 | CPPTRACE_EXPORT void set_cache_mode(cache_mode mode); 45 | } 46 | 47 | // dwarf options 48 | namespace experimental { 49 | CPPTRACE_EXPORT void set_dwarf_resolver_line_table_cache_size(nullable max_entries); 50 | CPPTRACE_EXPORT void set_dwarf_resolver_disable_aranges(bool disable); 51 | } 52 | } 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /res/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremy-rifkin/cpptrace/f29edecca98c964e31c2e36976ed574f6ea5d9e2/res/demo.png -------------------------------------------------------------------------------- /res/exception.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremy-rifkin/cpptrace/f29edecca98c964e31c2e36976ed574f6ea5d9e2/res/exception.png -------------------------------------------------------------------------------- /res/from_current.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremy-rifkin/cpptrace/f29edecca98c964e31c2e36976ed574f6ea5d9e2/res/from_current.png -------------------------------------------------------------------------------- /res/inlining.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremy-rifkin/cpptrace/f29edecca98c964e31c2e36976ed574f6ea5d9e2/res/inlining.png -------------------------------------------------------------------------------- /res/snippets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremy-rifkin/cpptrace/f29edecca98c964e31c2e36976ed574f6ea5d9e2/res/snippets.png -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.organization=jeremy-rifkin 2 | sonar.projectKey=jeremy-rifkin_cpptrace 3 | 4 | # relative paths to source directories. More details and properties are described 5 | # in https://sonarcloud.io/documentation/project-administration/narrowing-the-focus/ 6 | sonar.sources=src, include 7 | sonar.sourceEncoding=UTF-8 8 | sonar.cfamily.compile-commands=build/compile_commands.json 9 | -------------------------------------------------------------------------------- /src/binary/mach-o.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MACHO_HPP 2 | #define MACHO_HPP 3 | 4 | #include "utils/common.hpp" 5 | #include "utils/utils.hpp" 6 | #include "utils/span.hpp" 7 | #include "utils/io/base_file.hpp" 8 | 9 | #if IS_APPLE 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | namespace cpptrace { 23 | namespace detail { 24 | bool file_is_mach_o(cstring_view object_path) noexcept; 25 | 26 | struct load_command_entry { 27 | std::uint32_t file_offset; 28 | std::uint32_t cmd; 29 | std::uint32_t cmdsize; 30 | }; 31 | 32 | class mach_o { 33 | public: 34 | struct debug_map_entry { 35 | uint64_t source_address; 36 | uint64_t size; 37 | std::string name; 38 | }; 39 | 40 | struct symbol_entry { 41 | uint64_t address; 42 | std::string name; 43 | }; 44 | 45 | // map from object file to a vector of symbols to resolve 46 | using debug_map = std::unordered_map>; 47 | 48 | private: 49 | std::unique_ptr file; 50 | std::uint32_t magic; 51 | cpu_type_t cputype; 52 | cpu_subtype_t cpusubtype; 53 | std::uint32_t filetype; 54 | std::uint32_t n_load_commands; 55 | std::uint32_t sizeof_load_commands; 56 | std::uint32_t flags; 57 | std::size_t bits = 0; // 32 or 64 once load_mach is called 58 | 59 | std::size_t load_base = 0; 60 | std::size_t fat_index = std::numeric_limits::max(); 61 | 62 | std::vector load_commands; 63 | 64 | struct symtab_info_data { 65 | symtab_command symtab; 66 | optional> stringtab; 67 | Result get_string(std::size_t index) const; 68 | }; 69 | 70 | bool tried_to_load_symtab = false; 71 | optional symtab_info; 72 | 73 | bool tried_to_load_symbols = false; 74 | optional> symbols; 75 | 76 | mach_o(std::unique_ptr file, std::uint32_t magic) : file(std::move(file)), magic(magic) {} 77 | 78 | Result load(); 79 | 80 | static NODISCARD Result open(std::unique_ptr file); 81 | 82 | public: 83 | static NODISCARD Result open(cstring_view object_path); 84 | static NODISCARD Result open(cbspan object); 85 | 86 | mach_o(mach_o&&) = default; 87 | ~mach_o() = default; 88 | 89 | Result get_text_vmaddr(); 90 | 91 | std::size_t get_fat_index() const; 92 | 93 | void print_segments() const; 94 | 95 | struct pc_range { 96 | frame_ptr low; 97 | frame_ptr high; // not inclusive 98 | }; 99 | // for in-memory JIT mach-o's 100 | Result, internal_error> get_pc_ranges(); 101 | 102 | Result>, internal_error> get_symtab_info(); 103 | 104 | void print_symbol_table_entry( 105 | const nlist_64& entry, 106 | const char* stringtab, 107 | std::size_t stringsize, 108 | std::size_t j 109 | ) const; 110 | 111 | void print_symbol_table(); 112 | 113 | // produce information similar to dsymutil -dump-debug-map 114 | Result get_debug_map(); 115 | 116 | Result&, internal_error> symbol_table(); 117 | 118 | optional lookup_symbol(frame_ptr pc); 119 | 120 | // produce information similar to dsymutil -dump-debug-map 121 | static void print_debug_map(const debug_map& debug_map); 122 | 123 | private: 124 | template 125 | Result load_mach(); 126 | 127 | Result load_fat_mach(); 128 | 129 | template 130 | Result load_segment_command(std::uint32_t offset) const; 131 | 132 | Result load_symbol_table_command(std::uint32_t offset) const; 133 | 134 | template 135 | Result load_symtab_entry(std::uint32_t symbol_base, std::size_t index) const; 136 | 137 | Result, internal_error> load_string_table(std::uint32_t offset, std::uint32_t byte_count) const; 138 | 139 | bool should_swap() const; 140 | }; 141 | 142 | Result macho_is_fat(cstring_view object_path); 143 | 144 | NODISCARD Result, internal_error> open_mach_o_cached(const std::string& object_path); 145 | } 146 | } 147 | 148 | #endif 149 | 150 | #endif 151 | -------------------------------------------------------------------------------- /src/binary/module_base.cpp: -------------------------------------------------------------------------------- 1 | #include "binary/module_base.hpp" 2 | 3 | #include "platform/platform.hpp" 4 | #include "utils/utils.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #if IS_LINUX || IS_APPLE 11 | #include 12 | #include 13 | #if IS_APPLE 14 | #include "binary/mach-o.hpp" 15 | #else 16 | #include "binary/elf.hpp" 17 | #endif 18 | #elif IS_WINDOWS 19 | #include "binary/pe.hpp" 20 | #endif 21 | 22 | namespace cpptrace { 23 | namespace detail { 24 | #if IS_LINUX 25 | Result get_module_image_base(const std::string& object_path) { 26 | static std::mutex mutex; 27 | std::lock_guard lock(mutex); 28 | static std::unordered_map cache; 29 | auto it = cache.find(object_path); 30 | if(it == cache.end()) { 31 | // arguably it'd be better to release the lock while computing this, but also arguably it's good to not 32 | // have two threads try to do the same computation 33 | auto elf_object = open_elf_cached(object_path); 34 | // TODO: Cache the error 35 | if(!elf_object) { 36 | return elf_object.unwrap_error(); 37 | } 38 | auto base = elf_object.unwrap_value()->get_module_image_base(); 39 | if(base.is_error()) { 40 | return std::move(base).unwrap_error(); 41 | } 42 | cache.insert(it, {object_path, base.unwrap_value()}); 43 | return base; 44 | } else { 45 | return it->second; 46 | } 47 | } 48 | #elif IS_APPLE 49 | Result get_module_image_base(const std::string& object_path) { 50 | // We have to parse the Mach-O to find the offset of the text section..... 51 | // I don't know how addresses are handled if there is more than one __TEXT load command. I'm assuming for 52 | // now that there is only one, and I'm using only the first section entry within that load command. 53 | static std::mutex mutex; 54 | std::lock_guard lock(mutex); 55 | static std::unordered_map cache; 56 | auto it = cache.find(object_path); 57 | if(it == cache.end()) { 58 | // arguably it'd be better to release the lock while computing this, but also arguably it's good to not 59 | // have two threads try to do the same computation 60 | auto mach_o_object = open_mach_o_cached(object_path); 61 | // TODO: Cache the error 62 | if(!mach_o_object) { 63 | return mach_o_object.unwrap_error(); 64 | } 65 | auto base = mach_o_object.unwrap_value()->get_text_vmaddr(); 66 | if(!base) { 67 | return std::move(base).unwrap_error(); 68 | } 69 | cache.insert(it, {object_path, base.unwrap_value()}); 70 | return base; 71 | } else { 72 | return it->second; 73 | } 74 | } 75 | #else // Windows 76 | Result get_module_image_base(const std::string& object_path) { 77 | static std::mutex mutex; 78 | std::lock_guard lock(mutex); 79 | static std::unordered_map cache; 80 | auto it = cache.find(object_path); 81 | if(it == cache.end()) { 82 | // arguably it'd be better to release the lock while computing this, but also arguably it's good to not 83 | // have two threads try to do the same computation 84 | auto base = pe_get_module_image_base(object_path); 85 | // TODO: Cache the error 86 | if(!base) { 87 | return std::move(base).unwrap_error(); 88 | } 89 | cache.insert(it, {object_path, base.unwrap_value()}); 90 | return base; 91 | } else { 92 | return it->second; 93 | } 94 | } 95 | #endif 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/binary/module_base.hpp: -------------------------------------------------------------------------------- 1 | #ifndef IMAGE_MODULE_BASE_HPP 2 | #define IMAGE_MODULE_BASE_HPP 3 | 4 | #include "utils/utils.hpp" 5 | 6 | #include 7 | #include 8 | 9 | namespace cpptrace { 10 | namespace detail { 11 | Result get_module_image_base(const std::string& object_path); 12 | } 13 | } 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /src/binary/object.hpp: -------------------------------------------------------------------------------- 1 | #ifndef OBJECT_HPP 2 | #define OBJECT_HPP 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace cpptrace { 10 | 11 | namespace detail { 12 | object_frame get_frame_object_info(frame_ptr address); 13 | 14 | std::vector get_frames_object_info(const std::vector& addresses); 15 | 16 | object_frame resolve_safe_object_frame(const safe_object_frame& frame); 17 | } 18 | } 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /src/binary/pe.cpp: -------------------------------------------------------------------------------- 1 | #include "binary/pe.hpp" 2 | 3 | #include "platform/platform.hpp" 4 | #include "utils/error.hpp" 5 | #include "utils/utils.hpp" 6 | 7 | #if IS_WINDOWS 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #ifndef WIN32_LEAN_AND_MEAN 14 | #define WIN32_LEAN_AND_MEAN 15 | #endif 16 | #include 17 | 18 | namespace cpptrace { 19 | namespace detail { 20 | template::value, int>::type = 0> 21 | T pe_byteswap_if_needed(T value) { 22 | // PE header values are little endian, I think dos e_lfanew should be too 23 | if(!is_little_endian()) { 24 | return byteswap(value); 25 | } else { 26 | return value; 27 | } 28 | } 29 | 30 | Result pe_get_module_image_base(cstring_view object_path) { 31 | // https://drive.google.com/file/d/0B3_wGJkuWLytbnIxY1J5WUs4MEk/view?pli=1&resourcekey=0-n5zZ2UW39xVTH8ZSu6C2aQ 32 | // https://0xrick.github.io/win-internals/pe3/ 33 | // Endianness should always be little for dos and pe headers 34 | std::FILE* file_ptr; 35 | errno_t ret = fopen_s(&file_ptr, object_path.c_str(), "rb"); 36 | auto file = raii_wrap(std::move(file_ptr), file_deleter); 37 | if(ret != 0 || file == nullptr) { 38 | return internal_error("Unable to read object file {}", object_path); 39 | } 40 | auto magic = load_bytes>(file, 0); 41 | if(!magic) { 42 | return std::move(magic).unwrap_error(); 43 | } 44 | if(std::memcmp(magic.unwrap_value().data(), "MZ", 2) != 0) { 45 | return internal_error("File is not a PE file {}", object_path); 46 | } 47 | auto e_lfanew = load_bytes(file, 0x3c); // dos header + 0x3c 48 | if(!e_lfanew) { 49 | return std::move(e_lfanew).unwrap_error(); 50 | } 51 | DWORD nt_header_offset = pe_byteswap_if_needed(e_lfanew.unwrap_value()); 52 | auto signature = load_bytes>(file, nt_header_offset); // nt header + 0 53 | if(!signature) { 54 | return std::move(signature).unwrap_error(); 55 | } 56 | if(std::memcmp(signature.unwrap_value().data(), "PE\0\0", 4) != 0) { 57 | return internal_error("File is not a PE file {}", object_path); 58 | } 59 | auto size_of_optional_header_raw = load_bytes(file, nt_header_offset + 4 + 0x10); // file header + 0x10 60 | if(!size_of_optional_header_raw) { 61 | return std::move(size_of_optional_header_raw).unwrap_error(); 62 | } 63 | WORD size_of_optional_header = pe_byteswap_if_needed(size_of_optional_header_raw.unwrap_value()); 64 | if(size_of_optional_header == 0) { 65 | return internal_error("Unexpected optional header size for PE file"); 66 | } 67 | auto optional_header_magic_raw = load_bytes(file, nt_header_offset + 0x18); // optional header + 0x0 68 | if(!optional_header_magic_raw) { 69 | return std::move(optional_header_magic_raw).unwrap_error(); 70 | } 71 | WORD optional_header_magic = pe_byteswap_if_needed(optional_header_magic_raw.unwrap_value()); 72 | VERIFY( 73 | optional_header_magic == IMAGE_NT_OPTIONAL_HDR_MAGIC, 74 | ("PE file does not match expected bit-mode " + std::string(object_path)).c_str() 75 | ); 76 | // finally get image base 77 | if(optional_header_magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) { 78 | // 32 bit 79 | auto bytes = load_bytes(file, nt_header_offset + 0x18 + 0x1c); // optional header + 0x1c 80 | if(!bytes) { 81 | return std::move(bytes).unwrap_error(); 82 | } 83 | return to(pe_byteswap_if_needed(bytes.unwrap_value())); 84 | } else { 85 | // 64 bit 86 | // I get an "error: 'QWORD' was not declared in this scope" for some reason when using QWORD 87 | auto bytes = load_bytes(file, nt_header_offset + 0x18 + 0x18); // optional header + 0x18 88 | if(!bytes) { 89 | return std::move(bytes).unwrap_error(); 90 | } 91 | return to(pe_byteswap_if_needed(bytes.unwrap_value())); 92 | } 93 | } 94 | } 95 | } 96 | 97 | #endif 98 | -------------------------------------------------------------------------------- /src/binary/pe.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PE_HPP 2 | #define PE_HPP 3 | 4 | #include "platform/platform.hpp" 5 | #include "utils/utils.hpp" 6 | 7 | #if IS_WINDOWS 8 | #include 9 | #include 10 | 11 | namespace cpptrace { 12 | namespace detail { 13 | Result pe_get_module_image_base(cstring_view object_path); 14 | } 15 | } 16 | 17 | #endif 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /src/binary/safe_dl.cpp: -------------------------------------------------------------------------------- 1 | #include "binary/safe_dl.hpp" 2 | 3 | #include "utils/common.hpp" 4 | #include "utils/utils.hpp" 5 | #include "platform/program_name.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #ifdef CPPTRACE_HAS_DL_FIND_OBJECT 15 | #if IS_LINUX || IS_APPLE 16 | #include 17 | #include 18 | #include 19 | #endif 20 | 21 | namespace cpptrace { 22 | namespace detail { 23 | void get_safe_object_frame(frame_ptr address, safe_object_frame* out) { 24 | out->raw_address = address; 25 | dl_find_object result; 26 | if(_dl_find_object(reinterpret_cast(address), &result) == 0) { // thread-safe, signal-safe 27 | out->address_relative_to_object_start = address - to_frame_ptr(result.dlfo_link_map->l_addr); 28 | if(result.dlfo_link_map->l_name != nullptr && result.dlfo_link_map->l_name[0] != 0) { 29 | std::size_t path_length = std::strlen(result.dlfo_link_map->l_name); 30 | std::memcpy( 31 | out->object_path, 32 | result.dlfo_link_map->l_name, 33 | std::min(path_length + 1, std::size_t(CPPTRACE_PATH_MAX + 1)) 34 | ); 35 | } else { 36 | // empty l_name, this means it's the currently running executable 37 | memset(out->object_path, 0, CPPTRACE_PATH_MAX + 1); 38 | // signal-safe 39 | auto res = readlink("/proc/self/exe", out->object_path, CPPTRACE_PATH_MAX); 40 | if(res == -1) { 41 | // error handling? 42 | } 43 | // TODO: Special handling for /proc/pid/exe unlink edge case 44 | } 45 | } else { 46 | // std::cout<<"error"<address_relative_to_object_start = 0; 48 | out->object_path[0] = 0; 49 | } 50 | // TODO: Handle this part of the documentation? 51 | // The address can be a code address or data address. On architectures using function descriptors, no attempt is 52 | // made to decode the function descriptor. Depending on how these descriptors are implemented, _dl_find_object 53 | // may return the object that defines the function descriptor (and not the object that contains the code 54 | // implementing the function), or fail to find any object at all. 55 | } 56 | 57 | bool has_get_safe_object_frame() { 58 | return true; 59 | } 60 | } 61 | } 62 | #else 63 | namespace cpptrace { 64 | namespace detail { 65 | void get_safe_object_frame(frame_ptr address, safe_object_frame* out) { 66 | out->raw_address = address; 67 | out->address_relative_to_object_start = 0; 68 | out->object_path[0] = 0; 69 | } 70 | 71 | bool has_get_safe_object_frame() { 72 | return false; 73 | } 74 | } 75 | } 76 | #endif 77 | -------------------------------------------------------------------------------- /src/binary/safe_dl.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SAFE_DL_HPP 2 | #define SAFE_DL_HPP 3 | 4 | #include "utils/common.hpp" 5 | 6 | namespace cpptrace { 7 | namespace detail { 8 | void get_safe_object_frame(frame_ptr address, safe_object_frame* out); 9 | 10 | bool has_get_safe_object_frame(); 11 | } 12 | } 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /src/demangle/demangle.hpp: -------------------------------------------------------------------------------- 1 | #ifndef DEMANGLE_HPP 2 | #define DEMANGLE_HPP 3 | 4 | #include 5 | 6 | namespace cpptrace { 7 | namespace detail { 8 | std::string demangle(const std::string& name, bool check_prefix); 9 | } 10 | } 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /src/demangle/demangle_with_cxxabi.cpp: -------------------------------------------------------------------------------- 1 | #include "utils/microfmt.hpp" 2 | #ifdef CPPTRACE_DEMANGLE_WITH_CXXABI 3 | 4 | #include "demangle/demangle.hpp" 5 | 6 | #include "utils/utils.hpp" 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace cpptrace { 15 | namespace detail { 16 | std::string demangle(const std::string& name, bool check_prefix) { 17 | // https://itanium-cxx-abi.github.io/cxx-abi/abi.html#demangler 18 | // Check both _Z and __Z, apple prefixes all symbols with an underscore 19 | if(check_prefix && !(starts_with(name, "_Z") || starts_with(name, "__Z"))) { 20 | return name; 21 | } 22 | // Apple clang demangles __Z just fine but gcc doesn't, so just offset the leading underscore 23 | std::size_t offset = 0; 24 | if(starts_with(name, "__Z")) { 25 | offset = 1; 26 | } 27 | // Mangled names don't have spaces, we might add a space and some extra info somewhere but we still want it to 28 | // be demanglable. Look for a space, if there is one swap it with a null terminator briefly. 29 | auto end = name.find(' '); 30 | std::string name_copy; 31 | std::reference_wrapper to_demangle = name; 32 | std::string rest; 33 | if(end != std::string::npos) { 34 | name_copy = name.substr(0, end); 35 | rest = name.substr(end); 36 | to_demangle = name_copy; 37 | } 38 | // presumably thread-safe 39 | // it appears safe to pass nullptr for status however the docs don't explicitly say it's safe so I don't 40 | // want to rely on it 41 | int status; 42 | auto demangled = raii_wrap( 43 | abi::__cxa_demangle(to_demangle.get().c_str() + offset, nullptr, nullptr, &status), 44 | [] (char* str) { std::free(str); } 45 | ); 46 | // demangled will always be nullptr on non-zero status, and if __cxa_demangle ever fails for any reason 47 | // we'll just quietly return the mangled name 48 | if(demangled.get()) { 49 | std::string str = demangled.get(); 50 | if(!rest.empty()) { 51 | str += rest; 52 | } 53 | return str; 54 | } else { 55 | return name; 56 | } 57 | } 58 | } 59 | } 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /src/demangle/demangle_with_nothing.cpp: -------------------------------------------------------------------------------- 1 | #ifdef CPPTRACE_DEMANGLE_WITH_NOTHING 2 | 3 | #include "demangle/demangle.hpp" 4 | 5 | #include 6 | 7 | namespace cpptrace { 8 | namespace detail { 9 | std::string demangle(const std::string& name, bool) { 10 | return name; 11 | } 12 | } 13 | } 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /src/demangle/demangle_with_winapi.cpp: -------------------------------------------------------------------------------- 1 | #ifdef CPPTRACE_DEMANGLE_WITH_WINAPI 2 | 3 | #include "demangle/demangle.hpp" 4 | #include "platform/dbghelp_utils.hpp" 5 | 6 | #include 7 | 8 | #ifndef WIN32_LEAN_AND_MEAN 9 | #define WIN32_LEAN_AND_MEAN 10 | #endif 11 | #include 12 | #include 13 | 14 | namespace cpptrace { 15 | namespace detail { 16 | std::string demangle(const std::string& name, bool) { 17 | // Dbghelp is is single-threaded, so acquire a lock. 18 | auto lock = get_dbghelp_lock(); 19 | char buffer[500]; 20 | auto ret = UnDecorateSymbolName(name.c_str(), buffer, sizeof(buffer) - 1, 0); 21 | if(ret == 0) { 22 | return name; 23 | } else { 24 | buffer[ret] = 0; // just in case, ms' docs unclear if null terminator inserted 25 | return buffer; 26 | } 27 | } 28 | } 29 | } 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /src/jit/jit_objects.cpp: -------------------------------------------------------------------------------- 1 | #include "jit/jit_objects.hpp" 2 | 3 | #include "cpptrace/forward.hpp" 4 | #include "utils/error.hpp" 5 | #include "utils/optional.hpp" 6 | #include "utils/span.hpp" 7 | #include "binary/elf.hpp" 8 | #include "binary/mach-o.hpp" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace cpptrace { 17 | namespace detail { 18 | #if IS_LINUX || IS_APPLE 19 | class jit_object_manager { 20 | struct object_entry { 21 | const char* object_start; 22 | std::unique_ptr object; 23 | }; 24 | std::vector objects; 25 | 26 | struct range_entry { 27 | frame_ptr low; 28 | frame_ptr high; // not inclusive 29 | const char* object_start; 30 | jit_object_type* object; 31 | bool operator<(const range_entry& other) const { 32 | return low < other.low; 33 | } 34 | }; 35 | // TODO: Maybe use a set... 36 | std::vector range_list; 37 | 38 | public: 39 | void add_jit_object(cbspan object) { 40 | auto object_res = jit_object_type::open(object); 41 | if(object_res.is_error()) { 42 | if(!should_absorb_trace_exceptions()) { 43 | object_res.drop_error(); 44 | } 45 | return; 46 | } 47 | objects.push_back({object.data(), make_unique(std::move(object_res).unwrap_value())}); 48 | auto* object_file = objects.back().object.get(); 49 | auto ranges_res = object_file->get_pc_ranges(); 50 | if(ranges_res.is_error()) { 51 | if(!should_absorb_trace_exceptions()) { 52 | ranges_res.drop_error(); 53 | } 54 | return; 55 | } 56 | auto& ranges = ranges_res.unwrap_value(); 57 | for(auto range : ranges) { 58 | range_entry entry{range.low, range.high, object.data(), object_file}; 59 | // TODO: Perf 60 | range_list.insert(std::upper_bound(range_list.begin(), range_list.end(), entry), entry); 61 | } 62 | } 63 | 64 | void remove_jit_object(const char* ptr) { 65 | // TODO: Perf 66 | objects.erase( 67 | std::remove_if( 68 | objects.begin(), 69 | objects.end(), 70 | [&](const object_entry& entry) { return entry.object_start == ptr; } 71 | ), 72 | objects.end() 73 | ); 74 | range_list.erase( 75 | std::remove_if( 76 | range_list.begin(), 77 | range_list.end(), 78 | [&](const range_entry& entry) { return entry.object_start == ptr; } 79 | ), 80 | range_list.end() 81 | ); 82 | } 83 | 84 | optional lookup(frame_ptr pc) const { 85 | auto it = first_less_than_or_equal( 86 | range_list.begin(), 87 | range_list.end(), 88 | pc, 89 | [](frame_ptr pc, const range_entry& entry) { 90 | return pc < entry.low; 91 | } 92 | ); 93 | if(it == range_list.end()) { 94 | return nullopt; 95 | } 96 | ASSERT(pc >= it->low); 97 | if(pc < it->high) { 98 | return jit_object_lookup_result{*it->object, it->low}; 99 | } else { 100 | return nullopt; 101 | } 102 | } 103 | 104 | void clear_all_jit_objects() { 105 | objects.clear(); 106 | range_list.clear(); 107 | } 108 | }; 109 | #else 110 | class jit_object_manager { 111 | public: 112 | void add_jit_object(cbspan) {} 113 | void remove_jit_object(const char*) {} 114 | void clear_all_jit_objects() {} 115 | }; 116 | #endif 117 | 118 | jit_object_manager& get_jit_object_manager() { 119 | static jit_object_manager manager; 120 | return manager; 121 | } 122 | 123 | void register_jit_object(const char* ptr, std::size_t size) { 124 | auto& manager = get_jit_object_manager(); 125 | manager.add_jit_object(make_span(ptr, size)); 126 | } 127 | 128 | void unregister_jit_object(const char* ptr) { 129 | auto& manager = get_jit_object_manager(); 130 | manager.remove_jit_object(ptr); 131 | } 132 | 133 | void clear_all_jit_objects() { 134 | auto& manager = get_jit_object_manager(); 135 | manager.clear_all_jit_objects(); 136 | } 137 | 138 | #if IS_LINUX || IS_APPLE 139 | optional lookup_jit_object(frame_ptr pc) { 140 | return get_jit_object_manager().lookup(pc); 141 | } 142 | #endif 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/jit/jit_objects.hpp: -------------------------------------------------------------------------------- 1 | #ifndef JIT_OBJECTS_HPP 2 | #define JIT_OBJECTS_HPP 3 | 4 | #include "binary/elf.hpp" 5 | #include "binary/mach-o.hpp" 6 | #include "cpptrace/forward.hpp" 7 | #include "utils/optional.hpp" 8 | #include "platform/platform.hpp" 9 | 10 | namespace cpptrace { 11 | namespace detail { 12 | void register_jit_object(const char*, std::size_t); 13 | void unregister_jit_object(const char*); 14 | void clear_all_jit_objects(); 15 | 16 | #if IS_LINUX || IS_APPLE 17 | #if IS_LINUX 18 | using jit_object_type = elf; 19 | #elif IS_APPLE 20 | using jit_object_type = mach_o; 21 | #endif 22 | struct jit_object_lookup_result { 23 | jit_object_type& object; 24 | frame_ptr base; 25 | }; 26 | optional lookup_jit_object(frame_ptr pc); 27 | #endif 28 | } 29 | } 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /src/options.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "options.hpp" 4 | 5 | #include 6 | 7 | namespace cpptrace { 8 | namespace detail { 9 | std::atomic_bool absorb_trace_exceptions(true); // NOSONAR 10 | std::atomic_bool resolve_inlined_calls(true); // NOSONAR 11 | std::atomic current_cache_mode(cache_mode::prioritize_speed); // NOSONAR 12 | } 13 | 14 | void absorb_trace_exceptions(bool absorb) { 15 | detail::absorb_trace_exceptions = absorb; 16 | } 17 | 18 | void enable_inlined_call_resolution(bool enable) { 19 | detail::resolve_inlined_calls = enable; 20 | } 21 | 22 | namespace experimental { 23 | void set_cache_mode(cache_mode mode) { 24 | detail::current_cache_mode = mode; 25 | } 26 | } 27 | 28 | namespace detail { 29 | bool should_absorb_trace_exceptions() { 30 | return absorb_trace_exceptions; 31 | } 32 | 33 | bool should_resolve_inlined_calls() { 34 | return resolve_inlined_calls; 35 | } 36 | 37 | cache_mode get_cache_mode() { 38 | return current_cache_mode; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/options.hpp: -------------------------------------------------------------------------------- 1 | #ifndef OPTIONS_HPP 2 | #define OPTIONS_HPP 3 | 4 | #include 5 | 6 | namespace cpptrace { 7 | namespace detail { 8 | // exported for test purposes 9 | CPPTRACE_EXPORT bool should_absorb_trace_exceptions(); 10 | bool should_resolve_inlined_calls(); 11 | cache_mode get_cache_mode(); 12 | } 13 | } 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /src/platform/dbghelp_utils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef DBGHELP_UTILS_HPP 2 | #define DBGHELP_UTILS_HPP 3 | 4 | #if defined(CPPTRACE_UNWIND_WITH_DBGHELP) \ 5 | || defined(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP) \ 6 | || defined(CPPTRACE_DEMANGLE_WITH_WINAPI) 7 | 8 | #include "utils/common.hpp" 9 | 10 | #include 11 | #include 12 | 13 | namespace cpptrace { 14 | namespace detail { 15 | class dbghelp_syminit_info { 16 | // `void*` is used to avoid including the (expensive) windows.h header here 17 | void* handle = nullptr; 18 | bool should_sym_cleanup; // true if cleanup is not managed by the syminit cache 19 | bool should_close_handle; // true if cleanup is not managed by the syminit cache and the handle was duplicated 20 | dbghelp_syminit_info(void* handle, bool should_sym_cleanup, bool should_close_handle); 21 | public: 22 | ~dbghelp_syminit_info(); 23 | void release(); 24 | 25 | NODISCARD static dbghelp_syminit_info make_not_owned(void* handle); 26 | NODISCARD static dbghelp_syminit_info make_owned(void* handle, bool should_close_handle); 27 | 28 | dbghelp_syminit_info(const dbghelp_syminit_info&) = delete; 29 | dbghelp_syminit_info(dbghelp_syminit_info&&); 30 | dbghelp_syminit_info& operator=(const dbghelp_syminit_info&) = delete; 31 | dbghelp_syminit_info& operator=(dbghelp_syminit_info&&); 32 | 33 | void* get_process_handle() const; 34 | dbghelp_syminit_info make_non_owning_view() const; 35 | }; 36 | 37 | // Ensure SymInitialize is called on the process. This function either 38 | // - Finds that SymInitialize has been called for a handle to the current process already, in which case it returns 39 | // a non-owning dbghelp_syminit_info instance holding the handle 40 | // - Calls SymInitialize a handle to the current process, caches it, and returns a non-owning dbghelp_syminit_info 41 | // - Calls SymInitialize and returns an owning dbghelp_syminit_info which will handle cleanup 42 | dbghelp_syminit_info ensure_syminit(); 43 | 44 | NODISCARD std::unique_lock get_dbghelp_lock(); 45 | } 46 | } 47 | 48 | #endif 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /src/platform/exception_type.hpp: -------------------------------------------------------------------------------- 1 | #ifndef EXCEPTION_TYPE_HPP 2 | #define EXCEPTION_TYPE_HPP 3 | 4 | #include 5 | 6 | #include "platform/platform.hpp" 7 | 8 | // libstdc++ and libc++ 9 | #if defined(CPPTRACE_HAS_CXX_EXCEPTION_TYPE) && (IS_LIBSTDCXX || IS_LIBCXX) 10 | #include 11 | #include 12 | #include "demangle/demangle.hpp" 13 | #endif 14 | 15 | namespace cpptrace { 16 | namespace detail { 17 | inline std::string exception_type_name() { 18 | #if defined(CPPTRACE_HAS_CXX_EXCEPTION_TYPE) && (IS_LIBSTDCXX || IS_LIBCXX) 19 | const std::type_info* t = abi::__cxa_current_exception_type(); 20 | return t ? detail::demangle(t->name(), false) : ""; 21 | #else 22 | return ""; 23 | #endif 24 | } 25 | } 26 | } 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /src/platform/path.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PATH_HPP 2 | #define PATH_HPP 3 | 4 | #include "platform/platform.hpp" 5 | #include "utils/string_view.hpp" 6 | 7 | #include 8 | #include 9 | 10 | #if IS_WINDOWS 11 | #ifndef WIN32_LEAN_AND_MEAN 12 | #define WIN32_LEAN_AND_MEAN 13 | #endif 14 | #include 15 | #endif 16 | 17 | namespace cpptrace { 18 | namespace detail { 19 | #if IS_WINDOWS 20 | constexpr char PATH_SEP = '\\'; 21 | inline bool is_absolute(string_view path) { 22 | // I don't want to bring in shlwapi as a dependency just for PathIsRelativeA so I'm following the guidance of 23 | // https://stackoverflow.com/a/71941552/15675011 and 24 | // https://github.com/wine-mirror/wine/blob/b210a204137dec8d2126ca909d762454fd47e963/dlls/kernelbase/path.c#L982 25 | if(path.empty() || IsDBCSLeadByte(path[0])) { 26 | return false; 27 | } 28 | if(path[0] == '\\') { 29 | return true; 30 | } 31 | if(path.size() >= 2 && std::isalpha(path[0]) && path[1] == ':') { 32 | return true; 33 | } 34 | return false; 35 | } 36 | #else 37 | constexpr char PATH_SEP = '/'; 38 | inline bool is_absolute(string_view path) { 39 | if(path.empty()) { 40 | return false; 41 | } 42 | return path[0] == '/'; 43 | } 44 | #endif 45 | } 46 | } 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /src/platform/platform.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PLATFORM_HPP 2 | #define PLATFORM_HPP 3 | 4 | #define IS_WINDOWS 0 5 | #define IS_LINUX 0 6 | #define IS_APPLE 0 7 | 8 | #if defined(_WIN32) 9 | #undef IS_WINDOWS 10 | #define IS_WINDOWS 1 11 | #elif defined(__linux) 12 | #undef IS_LINUX 13 | #define IS_LINUX 1 14 | #elif defined(__APPLE__) 15 | #undef IS_APPLE 16 | #define IS_APPLE 1 17 | #else 18 | #error "Unexpected platform" 19 | #endif 20 | 21 | #define IS_CLANG 0 22 | #define IS_GCC 0 23 | #define IS_MSVC 0 24 | 25 | #if defined(__clang__) 26 | #undef IS_CLANG 27 | #define IS_CLANG 1 28 | #elif defined(__GNUC__) || defined(__GNUG__) 29 | #undef IS_GCC 30 | #define IS_GCC 1 31 | #elif defined(_MSC_VER) 32 | #undef IS_MSVC 33 | #define IS_MSVC 1 34 | #else 35 | #error "Unsupported compiler" 36 | #endif 37 | 38 | #define IS_LIBSTDCXX 0 39 | #define IS_LIBCXX 0 40 | #if defined(__GLIBCXX__) || defined(__GLIBCPP__) 41 | #undef IS_LIBSTDCXX 42 | #define IS_LIBSTDCXX 1 43 | #elif defined(_LIBCPP_VERSION) 44 | #undef IS_LIBCXX 45 | #define IS_LIBCXX 1 46 | #endif 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /src/platform/program_name.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PROGRAM_NAME_HPP 2 | #define PROGRAM_NAME_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include "platform/platform.hpp" 8 | 9 | #if IS_WINDOWS 10 | #ifndef WIN32_LEAN_AND_MEAN 11 | #define WIN32_LEAN_AND_MEAN 12 | #endif 13 | #include 14 | 15 | #define CPPTRACE_MAX_PATH MAX_PATH 16 | 17 | namespace cpptrace { 18 | namespace detail { 19 | inline const char* program_name() { 20 | static std::mutex mutex; 21 | const std::lock_guard lock(mutex); 22 | static std::string name; 23 | static bool did_init = false; 24 | static bool valid = false; 25 | if(!did_init) { 26 | did_init = true; 27 | char buffer[MAX_PATH + 1]; 28 | int res = GetModuleFileNameA(nullptr, buffer, MAX_PATH); 29 | if(res) { 30 | name = buffer; 31 | valid = true; 32 | } 33 | } 34 | return valid && !name.empty() ? name.c_str() : nullptr; 35 | } 36 | } 37 | } 38 | 39 | #elif IS_APPLE 40 | 41 | #include 42 | #include 43 | #include 44 | 45 | #define CPPTRACE_MAX_PATH CPPTRACE_PATH_MAX 46 | 47 | namespace cpptrace { 48 | namespace detail { 49 | inline const char* program_name() { 50 | static std::mutex mutex; 51 | const std::lock_guard lock(mutex); 52 | static std::string name; 53 | static bool did_init = false; 54 | static bool valid = false; 55 | if(!did_init) { 56 | did_init = true; 57 | char buffer[CPPTRACE_PATH_MAX + 1]; 58 | std::uint32_t bufferSize = sizeof buffer; 59 | if(_NSGetExecutablePath(buffer, &bufferSize) == 0) { 60 | name.assign(buffer, bufferSize); 61 | valid = true; 62 | } 63 | } 64 | return valid && !name.empty() ? name.c_str() : nullptr; 65 | } 66 | } 67 | } 68 | 69 | #elif IS_LINUX 70 | 71 | #include 72 | #include 73 | 74 | #define CPPTRACE_MAX_PATH CPPTRACE_PATH_MAX 75 | 76 | namespace cpptrace { 77 | namespace detail { 78 | inline const char* program_name() { 79 | static std::mutex mutex; 80 | const std::lock_guard lock(mutex); 81 | static std::string name; 82 | static bool did_init = false; 83 | static bool valid = false; 84 | if(!did_init) { 85 | did_init = true; 86 | char buffer[CPPTRACE_PATH_MAX + 1]; 87 | const ssize_t size = readlink("/proc/self/exe", buffer, CPPTRACE_PATH_MAX); 88 | if(size == -1) { 89 | return nullptr; 90 | } 91 | buffer[size] = 0; 92 | name = buffer; 93 | valid = true; 94 | } 95 | return valid && !name.empty() ? name.c_str() : nullptr; 96 | } 97 | } 98 | } 99 | 100 | #endif 101 | 102 | #endif 103 | -------------------------------------------------------------------------------- /src/snippets/snippet.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SNIPPET_HPP 2 | #define SNIPPET_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace cpptrace { 8 | namespace detail { 9 | // 1-indexed line 10 | std::string get_snippet(const std::string& path, std::size_t line, std::size_t context_size, bool color); 11 | } 12 | } 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /src/symbols/dwarf/dwarf_options.cpp: -------------------------------------------------------------------------------- 1 | #include "symbols/dwarf/dwarf_options.hpp" 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace cpptrace { 8 | namespace detail { 9 | std::atomic> dwarf_resolver_line_table_cache_size{nullable::null()}; 10 | std::atomic dwarf_resolver_disable_aranges{false}; 11 | 12 | optional get_dwarf_resolver_line_table_cache_size() { 13 | auto max_entries = dwarf_resolver_line_table_cache_size.load(); 14 | return max_entries.has_value() ? optional(max_entries.value()) : nullopt; 15 | } 16 | 17 | bool get_dwarf_resolver_disable_aranges() { 18 | return dwarf_resolver_disable_aranges.load(); 19 | } 20 | } 21 | 22 | namespace experimental { 23 | void set_dwarf_resolver_line_table_cache_size(nullable max_entries) { 24 | detail::dwarf_resolver_line_table_cache_size.store(max_entries); 25 | } 26 | 27 | void set_dwarf_resolver_disable_aranges(bool disable) { 28 | detail::dwarf_resolver_disable_aranges.store(disable); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/symbols/dwarf/dwarf_options.hpp: -------------------------------------------------------------------------------- 1 | #ifndef DWARF_OPTIONS_HPP 2 | #define DWARF_OPTIONS_HPP 3 | 4 | #include "utils/optional.hpp" 5 | 6 | #include 7 | 8 | namespace cpptrace { 9 | namespace detail { 10 | optional get_dwarf_resolver_line_table_cache_size(); 11 | bool get_dwarf_resolver_disable_aranges(); 12 | } 13 | } 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /src/symbols/dwarf/resolver.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SYMBOL_RESOLVER_HPP 2 | #define SYMBOL_RESOLVER_HPP 3 | 4 | #include 5 | #include "symbols/symbols.hpp" 6 | #include "platform/platform.hpp" 7 | #include "utils/string_view.hpp" 8 | 9 | #include 10 | 11 | #if false 12 | #define CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING CPPTRACE_FORCE_NO_INLINE 13 | #else 14 | #define CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING 15 | #endif 16 | 17 | namespace cpptrace { 18 | namespace detail { 19 | namespace libdwarf { 20 | class symbol_resolver { 21 | public: 22 | virtual ~symbol_resolver() = default; 23 | CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING 24 | virtual frame_with_inlines resolve_frame(const object_frame& frame_info) = 0; 25 | }; 26 | 27 | class null_resolver : public symbol_resolver { 28 | public: 29 | null_resolver() = default; 30 | null_resolver(cstring_view) {} 31 | 32 | CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING 33 | frame_with_inlines resolve_frame(const object_frame& frame_info) override { 34 | return { 35 | { 36 | frame_info.raw_address, 37 | frame_info.object_address, 38 | nullable::null(), 39 | nullable::null(), 40 | frame_info.object_path, 41 | "", 42 | false 43 | }, 44 | {} 45 | }; 46 | }; 47 | }; 48 | 49 | std::unique_ptr make_dwarf_resolver(cstring_view object_path); 50 | #if IS_APPLE 51 | std::unique_ptr make_debug_map_resolver(const std::string& object_path); 52 | #endif 53 | } 54 | } 55 | } 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /src/symbols/symbols.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SYMBOLS_HPP 2 | #define SYMBOLS_HPP 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace cpptrace { 13 | namespace detail { 14 | using collated_vec = std::vector< 15 | std::pair, std::reference_wrapper> 16 | >; 17 | struct frame_with_inlines { 18 | stacktrace_frame frame; 19 | std::vector inlines; 20 | }; 21 | using collated_vec_with_inlines = std::vector< 22 | std::pair, std::reference_wrapper> 23 | >; 24 | 25 | // These two helpers create a map from a target object to a vector of frames to resolve 26 | std::unordered_map collate_frames( 27 | const std::vector& frames, 28 | std::vector& trace 29 | ); 30 | std::unordered_map collate_frames( 31 | const std::vector& frames, 32 | std::vector& trace 33 | ); 34 | 35 | #ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE 36 | namespace libbacktrace { 37 | std::vector resolve_frames(const std::vector& frames); 38 | } 39 | #endif 40 | #ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF 41 | namespace libdwarf { 42 | std::vector resolve_frames(const std::vector& frames); 43 | } 44 | #endif 45 | #ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDL 46 | namespace libdl { 47 | std::vector resolve_frames(const std::vector& frames); 48 | } 49 | #endif 50 | #ifdef CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE 51 | namespace addr2line { 52 | std::vector resolve_frames(const std::vector& frames); 53 | } 54 | #endif 55 | #ifdef CPPTRACE_GET_SYMBOLS_WITH_DBGHELP 56 | namespace dbghelp { 57 | std::vector resolve_frames(const std::vector& frames); 58 | } 59 | #endif 60 | #ifdef CPPTRACE_GET_SYMBOLS_WITH_NOTHING 61 | namespace nothing { 62 | std::vector resolve_frames(const std::vector& frames); 63 | std::vector resolve_frames(const std::vector& frames); 64 | } 65 | #endif 66 | 67 | std::vector resolve_frames(const std::vector& frames); 68 | std::vector resolve_frames(const std::vector& frames); 69 | } 70 | } 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /src/symbols/symbols_with_dl.cpp: -------------------------------------------------------------------------------- 1 | #ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDL 2 | 3 | #include 4 | #include "symbols/symbols.hpp" 5 | #include "binary/module_base.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | namespace cpptrace { 14 | namespace detail { 15 | namespace libdl { 16 | stacktrace_frame resolve_frame(const frame_ptr addr) { 17 | Dl_info info; 18 | if(dladdr(reinterpret_cast(addr), &info)) { // thread-safe 19 | auto base = get_module_image_base(info.dli_fname); 20 | return { 21 | addr, 22 | base.has_value() 23 | ? addr - reinterpret_cast(info.dli_fbase) + base.unwrap_value() 24 | : 0, 25 | nullable::null(), 26 | nullable::null(), 27 | info.dli_fname ? info.dli_fname : "", 28 | info.dli_sname ? info.dli_sname : "", 29 | false 30 | }; 31 | } else { 32 | return { 33 | addr, 34 | 0, 35 | nullable::null(), 36 | nullable::null(), 37 | "", 38 | "", 39 | false 40 | }; 41 | } 42 | } 43 | 44 | std::vector resolve_frames(const std::vector& frames) { 45 | std::vector trace; 46 | trace.reserve(frames.size()); 47 | for(const auto frame : frames) { 48 | trace.push_back(resolve_frame(frame)); 49 | } 50 | return trace; 51 | } 52 | } 53 | } 54 | } 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /src/symbols/symbols_with_libbacktrace.cpp: -------------------------------------------------------------------------------- 1 | #ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE 2 | 3 | #include 4 | #include "symbols/symbols.hpp" 5 | #include "platform/program_name.hpp" 6 | #include "utils/error.hpp" 7 | #include "utils/common.hpp" 8 | #include "options.hpp" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #ifdef CPPTRACE_BACKTRACE_PATH 18 | #include CPPTRACE_BACKTRACE_PATH 19 | #else 20 | #include 21 | #endif 22 | 23 | namespace cpptrace { 24 | namespace detail { 25 | namespace libbacktrace { 26 | int full_callback(void* data, std::uintptr_t address, const char* file, int line, const char* symbol) { 27 | stacktrace_frame& frame = *static_cast(data); 28 | frame.raw_address = address; 29 | frame.line = line; 30 | frame.filename = file ? file : ""; 31 | frame.symbol = symbol ? symbol : ""; 32 | return 0; 33 | } 34 | 35 | void syminfo_callback(void* data, std::uintptr_t address, const char* symbol, std::uintptr_t, std::uintptr_t) { 36 | stacktrace_frame& frame = *static_cast(data); 37 | frame.raw_address = address; 38 | frame.line = 0; 39 | frame.filename = ""; 40 | frame.symbol = symbol ? symbol : ""; 41 | } 42 | 43 | void error_callback(void*, const char* msg, int errnum) { 44 | if(msg == std::string("no debug info in ELF executable")) { 45 | // https://github.com/jeremy-rifkin/cpptrace/issues/114 46 | // https://github.com/ianlancetaylor/libbacktrace/blob/ae1e707dbacd4a5cc82fcf2d3816f410e9c5fec4/elf.c#L592 47 | // not a critical error, just return 48 | return; 49 | } 50 | throw internal_error("Libbacktrace error: {}, code {}", msg, errnum); 51 | } 52 | 53 | backtrace_state* get_backtrace_state() { 54 | static std::mutex mutex; 55 | const std::lock_guard lock(mutex); 56 | // backtrace_create_state must be called only one time per program 57 | static backtrace_state* state = nullptr; 58 | static bool called = false; 59 | if(!called) { 60 | state = backtrace_create_state(program_name(), true, error_callback, nullptr); 61 | called = true; 62 | } 63 | return state; 64 | } 65 | 66 | // TODO: Handle backtrace_pcinfo calling the callback multiple times on inlined functions 67 | stacktrace_frame resolve_frame(const frame_ptr addr) { 68 | try { 69 | stacktrace_frame frame = null_frame; 70 | frame.raw_address = addr; 71 | backtrace_pcinfo( 72 | get_backtrace_state(), 73 | addr, 74 | full_callback, 75 | error_callback, 76 | &frame 77 | ); 78 | if(frame.symbol.empty()) { 79 | // fallback, try to at least recover the symbol name with backtrace_syminfo 80 | backtrace_syminfo( 81 | get_backtrace_state(), 82 | addr, 83 | syminfo_callback, 84 | error_callback, 85 | &frame 86 | ); 87 | } 88 | return frame; 89 | } catch(...) { // NOSONAR 90 | if(!should_absorb_trace_exceptions()) { 91 | throw; 92 | } 93 | return null_frame; 94 | } 95 | } 96 | 97 | std::vector resolve_frames(const std::vector& frames) { 98 | std::vector trace; 99 | trace.reserve(frames.size()); 100 | for(const auto frame : frames) { 101 | trace.push_back(resolve_frame(frame)); 102 | } 103 | return trace; 104 | } 105 | } 106 | } 107 | } 108 | 109 | #endif 110 | -------------------------------------------------------------------------------- /src/symbols/symbols_with_nothing.cpp: -------------------------------------------------------------------------------- 1 | #ifdef CPPTRACE_GET_SYMBOLS_WITH_NOTHING 2 | 3 | #include 4 | #include "symbols/symbols.hpp" 5 | #include "utils/common.hpp" 6 | 7 | #include 8 | 9 | namespace cpptrace { 10 | namespace detail { 11 | namespace nothing { 12 | std::vector resolve_frames(const std::vector& frames) { 13 | return std::vector(frames.size(), null_frame); 14 | } 15 | 16 | std::vector resolve_frames(const std::vector& frames) { 17 | return std::vector(frames.size(), null_frame); 18 | } 19 | } 20 | } 21 | } 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /src/unwind/unwind.hpp: -------------------------------------------------------------------------------- 1 | #ifndef UNWIND_HPP 2 | #define UNWIND_HPP 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace cpptrace { 10 | namespace detail { 11 | #ifdef CPPTRACE_HARD_MAX_FRAMES 12 | constexpr std::size_t hard_max_frames = CPPTRACE_HARD_MAX_FRAMES; 13 | #else 14 | constexpr std::size_t hard_max_frames = 400; 15 | #endif 16 | 17 | CPPTRACE_FORCE_NO_INLINE 18 | std::vector capture_frames(std::size_t skip, std::size_t max_depth); 19 | 20 | CPPTRACE_FORCE_NO_INLINE 21 | std::size_t safe_capture_frames(frame_ptr* buffer, std::size_t size, std::size_t skip, std::size_t max_depth); 22 | 23 | bool has_safe_unwind(); 24 | } 25 | } 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /src/unwind/unwind_with_execinfo.cpp: -------------------------------------------------------------------------------- 1 | #ifdef CPPTRACE_UNWIND_WITH_EXECINFO 2 | 3 | #include "unwind/unwind.hpp" 4 | #include "utils/common.hpp" 5 | #include "utils/utils.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | namespace cpptrace { 15 | namespace detail { 16 | CPPTRACE_FORCE_NO_INLINE 17 | std::vector capture_frames(std::size_t skip, std::size_t max_depth) { 18 | skip++; 19 | std::vector addrs(skip + std::min(hard_max_frames, max_depth), nullptr); 20 | // thread safe 21 | const int n_frames = backtrace(addrs.data(), static_cast(addrs.size())); 22 | // I hate the copy here but it's the only way that isn't UB 23 | std::vector frames(n_frames - skip, 0); 24 | for(int i = skip; i < n_frames; i++) { 25 | // On x86/x64/arm, as far as I can tell, the frame return address is always one after the call 26 | // So we just decrement to get the pc back inside the `call` / `bl` 27 | // This is done with _Unwind too but conditionally based on info from _Unwind_GetIPInfo. 28 | frames[i - skip] = reinterpret_cast(addrs[i]) - 1; 29 | } 30 | return frames; 31 | } 32 | 33 | CPPTRACE_FORCE_NO_INLINE 34 | std::size_t safe_capture_frames(frame_ptr*, std::size_t, std::size_t, std::size_t) { 35 | // Can't safe trace with execinfo 36 | return 0; 37 | } 38 | 39 | bool has_safe_unwind() { 40 | return false; 41 | } 42 | } 43 | } 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /src/unwind/unwind_with_libunwind.cpp: -------------------------------------------------------------------------------- 1 | #ifdef CPPTRACE_UNWIND_WITH_LIBUNWIND 2 | 3 | #include "unwind/unwind.hpp" 4 | #include "utils/common.hpp" 5 | #include "utils/error.hpp" 6 | #include "utils/utils.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | namespace cpptrace { 16 | namespace detail { 17 | CPPTRACE_FORCE_NO_INLINE 18 | std::vector capture_frames(std::size_t skip, std::size_t max_depth) { 19 | skip++; 20 | std::vector frames; 21 | unw_context_t context; 22 | unw_cursor_t cursor; 23 | unw_getcontext(&context); 24 | unw_init_local(&cursor, &context); 25 | do { 26 | unw_word_t pc; 27 | unw_word_t sp; 28 | unw_get_reg(&cursor, UNW_REG_IP, &pc); 29 | unw_get_reg(&cursor, UNW_REG_SP, &sp); 30 | if(skip) { 31 | skip--; 32 | } else { 33 | // pc is the instruction after the `call`, adjust back to the previous instruction 34 | frames.push_back(to_frame_ptr(pc) - 1); 35 | } 36 | } while(unw_step(&cursor) > 0 && frames.size() < max_depth); 37 | return frames; 38 | } 39 | 40 | CPPTRACE_FORCE_NO_INLINE 41 | std::size_t safe_capture_frames(frame_ptr* buffer, std::size_t size, std::size_t skip, std::size_t max_depth) { 42 | // some code duplication, but whatever 43 | skip++; 44 | unw_context_t context; 45 | unw_cursor_t cursor; 46 | // thread and signal-safe https://www.nongnu.org/libunwind/man/unw_getcontext(3).html 47 | unw_getcontext(&context); 48 | // thread and signal-safe https://www.nongnu.org/libunwind/man/unw_init_local(3).html 49 | unw_init_local(&cursor, &context); 50 | size_t i = 0; 51 | while(i < size && i < max_depth) { 52 | unw_word_t pc; 53 | unw_word_t sp; 54 | // thread and signal-safe https://www.nongnu.org/libunwind/man/unw_get_reg(3).html 55 | unw_get_reg(&cursor, UNW_REG_IP, &pc); 56 | unw_get_reg(&cursor, UNW_REG_SP, &sp); 57 | if(skip) { 58 | skip--; 59 | } else { 60 | // thread and signal-safe 61 | if(unw_is_signal_frame(&cursor)) { 62 | // pc is the instruction that caused the signal 63 | // just a cast, thread and signal safe 64 | buffer[i] = to_frame_ptr(pc); 65 | } else { 66 | // pc is the instruction after the `call`, adjust back to the previous instruction 67 | // just a cast, thread and signal safe 68 | buffer[i] = to_frame_ptr(pc) - 1; 69 | } 70 | i++; 71 | } 72 | // thread and signal-safe as long as the cursor is in the local address space, which it is 73 | // https://www.nongnu.org/libunwind/man/unw_step(3).html 74 | if(unw_step(&cursor) <= 0) { 75 | break; 76 | } 77 | } 78 | return i; 79 | } 80 | 81 | bool has_safe_unwind() { 82 | return true; 83 | } 84 | } 85 | } 86 | 87 | #endif 88 | -------------------------------------------------------------------------------- /src/unwind/unwind_with_nothing.cpp: -------------------------------------------------------------------------------- 1 | #ifdef CPPTRACE_UNWIND_WITH_NOTHING 2 | 3 | #include "unwind/unwind.hpp" 4 | 5 | #include 6 | #include 7 | 8 | namespace cpptrace { 9 | namespace detail { 10 | std::vector capture_frames(std::size_t, std::size_t) { 11 | return {}; 12 | } 13 | 14 | CPPTRACE_FORCE_NO_INLINE 15 | std::size_t safe_capture_frames(frame_ptr*, std::size_t, std::size_t, std::size_t) { 16 | return 0; 17 | } 18 | 19 | bool has_safe_unwind() { 20 | return false; 21 | } 22 | } 23 | } 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /src/unwind/unwind_with_unwind.cpp: -------------------------------------------------------------------------------- 1 | #ifdef CPPTRACE_UNWIND_WITH_UNWIND 2 | 3 | #include "unwind/unwind.hpp" 4 | #include "utils/common.hpp" 5 | #include "utils/error.hpp" 6 | #include "utils/utils.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | namespace cpptrace { 16 | namespace detail { 17 | struct unwind_state { 18 | std::size_t skip; 19 | std::size_t max_depth; 20 | std::vector& vec; 21 | }; 22 | 23 | _Unwind_Reason_Code unwind_callback(_Unwind_Context* context, void* arg) { 24 | unwind_state& state = *static_cast(arg); 25 | if(state.skip) { 26 | state.skip--; 27 | if(_Unwind_GetIP(context) == frame_ptr(0)) { 28 | return _URC_END_OF_STACK; 29 | } else { 30 | return _URC_NO_REASON; 31 | } 32 | } 33 | 34 | ASSERT( 35 | state.vec.size() < state.max_depth, 36 | "Somehow cpptrace::detail::unwind_callback is being called beyond the max_depth" 37 | ); 38 | int is_before_instruction = 0; 39 | frame_ptr ip = _Unwind_GetIPInfo(context, &is_before_instruction); 40 | if(!is_before_instruction && ip != frame_ptr(0)) { 41 | ip--; 42 | } 43 | if (ip == frame_ptr(0)) { 44 | return _URC_END_OF_STACK; 45 | } else { 46 | state.vec.push_back(ip); 47 | if(state.vec.size() >= state.max_depth) { 48 | return _URC_END_OF_STACK; 49 | } else { 50 | return _URC_NO_REASON; 51 | } 52 | } 53 | } 54 | 55 | CPPTRACE_FORCE_NO_INLINE 56 | std::vector capture_frames(std::size_t skip, std::size_t max_depth) { 57 | std::vector frames; 58 | unwind_state state{skip + 1, max_depth, frames}; 59 | _Unwind_Backtrace(unwind_callback, &state); // presumably thread-safe 60 | return frames; 61 | } 62 | 63 | CPPTRACE_FORCE_NO_INLINE 64 | std::size_t safe_capture_frames(frame_ptr*, std::size_t, std::size_t, std::size_t) { 65 | // Can't safe trace with _Unwind 66 | return 0; 67 | } 68 | 69 | bool has_safe_unwind() { 70 | return false; 71 | } 72 | } 73 | } 74 | 75 | #endif 76 | -------------------------------------------------------------------------------- /src/unwind/unwind_with_winapi.cpp: -------------------------------------------------------------------------------- 1 | #ifdef CPPTRACE_UNWIND_WITH_WINAPI 2 | 3 | #include 4 | #include "unwind/unwind.hpp" 5 | #include "utils/common.hpp" 6 | #include "utils/utils.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #ifndef WIN32_LEAN_AND_MEAN 13 | #define WIN32_LEAN_AND_MEAN 14 | #endif 15 | #include 16 | 17 | // Fucking windows headers 18 | #ifdef min 19 | #undef min 20 | #endif 21 | 22 | namespace cpptrace { 23 | namespace detail { 24 | CPPTRACE_FORCE_NO_INLINE 25 | std::vector capture_frames(std::size_t skip, std::size_t max_depth) { 26 | std::vector addrs(skip + std::min(hard_max_frames, max_depth), nullptr); 27 | std::size_t n_frames = CaptureStackBackTrace( 28 | static_cast(skip + 1), 29 | static_cast(addrs.size()), 30 | addrs.data(), 31 | NULL 32 | ); 33 | // I hate the copy here but it's the only way that isn't UB 34 | std::vector frames(n_frames, 0); 35 | for(std::size_t i = 0; i < n_frames; i++) { 36 | // On x86/x64/arm, as far as I can tell, the frame return address is always one after the call 37 | // So we just decrement to get the pc back inside the `call` / `bl` 38 | // This is done with _Unwind too but conditionally based on info from _Unwind_GetIPInfo. 39 | frames[i] = reinterpret_cast(addrs[i]) - 1; 40 | } 41 | return frames; 42 | } 43 | 44 | CPPTRACE_FORCE_NO_INLINE 45 | std::size_t safe_capture_frames(frame_ptr*, std::size_t, std::size_t, std::size_t) { 46 | // Can't safe trace with winapi 47 | return 0; 48 | } 49 | 50 | bool has_safe_unwind() { 51 | return false; 52 | } 53 | } 54 | } 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /src/utils.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include "demangle/demangle.hpp" 8 | #include "snippets/snippet.hpp" 9 | #include "utils/utils.hpp" 10 | #include "platform/exception_type.hpp" 11 | #include "options.hpp" 12 | 13 | namespace cpptrace { 14 | std::string demangle(const std::string& name) { 15 | return detail::demangle(name, false); 16 | } 17 | 18 | std::string get_snippet(const std::string& path, std::size_t line, std::size_t context_size, bool color) { 19 | return detail::get_snippet(path, line, context_size, color); 20 | } 21 | 22 | bool isatty(int fd) { 23 | return detail::isatty(fd); 24 | } 25 | 26 | extern const int stdin_fileno = detail::fileno(stdin); 27 | extern const int stdout_fileno = detail::fileno(stdout); 28 | extern const int stderr_fileno = detail::fileno(stderr); 29 | 30 | namespace detail { 31 | const formatter& get_terminate_formatter() { 32 | static formatter the_formatter = formatter{} 33 | .header("Stack trace to reach terminate handler (most recent call first):"); 34 | return the_formatter; 35 | } 36 | } 37 | 38 | CPPTRACE_FORCE_NO_INLINE void print_terminate_trace() { 39 | try { // try/catch can never be hit but it's needed to prevent TCO 40 | detail::get_terminate_formatter().print(std::cerr, generate_trace(1)); 41 | } catch(...) { 42 | if(!detail::should_absorb_trace_exceptions()) { 43 | throw; 44 | } 45 | } 46 | } 47 | 48 | [[noreturn]] void MSVC_CDECL terminate_handler() { 49 | // TODO: Support std::nested_exception? 50 | try { 51 | auto ptr = std::current_exception(); 52 | if(ptr == nullptr) { 53 | fputs("terminate called without an active exception", stderr); 54 | print_terminate_trace(); 55 | } else { 56 | std::rethrow_exception(ptr); 57 | } 58 | } catch(cpptrace::exception& e) { 59 | microfmt::print( 60 | stderr, 61 | "Terminate called after throwing an instance of {}: {}\n", 62 | demangle(typeid(e).name()), 63 | e.message() 64 | ); 65 | e.trace().print(std::cerr, isatty(stderr_fileno)); 66 | } catch(std::exception& e) { 67 | microfmt::print( 68 | stderr, "Terminate called after throwing an instance of {}: {}\n", demangle(typeid(e).name()), e.what() 69 | ); 70 | print_terminate_trace(); 71 | } catch(...) { 72 | microfmt::print( 73 | stderr, "Terminate called after throwing an instance of {}\n", detail::exception_type_name() 74 | ); 75 | print_terminate_trace(); 76 | } 77 | std::flush(std::cerr); 78 | abort(); 79 | } 80 | 81 | void register_terminate_handler() { 82 | std::set_terminate(terminate_handler); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/utils/common.hpp: -------------------------------------------------------------------------------- 1 | #ifndef COMMON_HPP 2 | #define COMMON_HPP 3 | 4 | #include 5 | 6 | #include "platform/platform.hpp" 7 | 8 | #include 9 | 10 | #define ESC "\033[" 11 | #define RESET ESC "0m" 12 | #define RED ESC "31m" 13 | #define GREEN ESC "32m" 14 | #define YELLOW ESC "33m" 15 | #define BLUE ESC "34m" 16 | #define MAGENTA ESC "35m" 17 | #define CYAN ESC "36m" 18 | 19 | #if IS_GCC || IS_CLANG 20 | #define NODISCARD __attribute__((warn_unused_result)) 21 | // #elif IS_MSVC && _MSC_VER >= 1700 22 | // #define NODISCARD _Check_return_ 23 | #else 24 | #define NODISCARD 25 | #endif 26 | 27 | // workaround a bizarre gcc bug https://godbolt.org/z/s78vnf7jv 28 | // https://github.com/jeremy-rifkin/cpptrace/issues/220 29 | #if defined(__GNUC__) && (__GNUC__ < 7) 30 | #undef NODISCARD 31 | #define NODISCARD 32 | #endif 33 | 34 | #if IS_MSVC 35 | #define MSVC_CDECL __cdecl 36 | #else 37 | #define MSVC_CDECL 38 | #endif 39 | 40 | // support is pretty good https://godbolt.org/z/djTqv7WMY, checked in cmake during config 41 | #ifdef HAS_ATTRIBUTE_PACKED 42 | #define PACKED __attribute__((packed)) 43 | #else 44 | #define PACKED 45 | #endif 46 | 47 | namespace cpptrace { 48 | namespace detail { 49 | static const stacktrace_frame null_frame { 50 | 0, 51 | 0, 52 | nullable::null(), 53 | nullable::null(), 54 | "", 55 | "", 56 | false 57 | }; 58 | } 59 | } 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /src/utils/error.cpp: -------------------------------------------------------------------------------- 1 | #include "utils/error.hpp" 2 | 3 | namespace cpptrace { 4 | namespace detail { 5 | internal_error::internal_error(std::string message) : msg("Cpptrace internal error: " + std::move(message)) {} 6 | 7 | constexpr const char* assert_actions[] = {"assertion", "verification", "panic"}; 8 | constexpr const char* assert_names[] = {"ASSERT", "VERIFY", "PANIC"}; 9 | 10 | void assert_fail( 11 | assert_type type, 12 | const char* expression, 13 | const char* signature, 14 | source_location location, 15 | const char* message 16 | ) { 17 | const char* action = assert_actions[static_cast::type>(type)]; 18 | const char* name = assert_names[static_cast::type>(type)]; 19 | if(message == nullptr) { 20 | throw internal_error( 21 | "Cpptrace {} failed at {}:{}: {}\n" 22 | " {}({});\n", 23 | action, location.file, location.line, signature, 24 | name, expression 25 | ); 26 | } else { 27 | throw internal_error( 28 | "Cpptrace {} failed at {}:{}: {}: {}\n" 29 | " {}({});\n", 30 | action, location.file, location.line, signature, message, 31 | name, expression 32 | ); 33 | } 34 | } 35 | 36 | void panic( 37 | const char* signature, 38 | source_location location, 39 | string_view message 40 | ) { 41 | if(message == "") { 42 | throw internal_error( 43 | "Cpptrace panic {}:{}: {}\n", 44 | location.file, location.line, signature 45 | ); 46 | } else { 47 | throw internal_error( 48 | "Cpptrace panic {}:{}: {}: {}\n", 49 | location.file, location.line, signature, message 50 | ); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/utils/error.hpp: -------------------------------------------------------------------------------- 1 | #ifndef ERROR_HPP 2 | #define ERROR_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "platform/platform.hpp" 9 | #include "utils/microfmt.hpp" 10 | #include "utils/string_view.hpp" 11 | 12 | #include 13 | 14 | #if IS_MSVC 15 | #define CPPTRACE_PFUNC __FUNCSIG__ 16 | #else 17 | #define CPPTRACE_PFUNC __extension__ __PRETTY_FUNCTION__ 18 | #endif 19 | 20 | namespace cpptrace { 21 | namespace detail { 22 | class internal_error : public std::exception { 23 | std::string msg; 24 | public: 25 | internal_error(std::string message); 26 | template 27 | internal_error(const char* format, Args&&... args) : internal_error(microfmt::format(format, args...)) {} 28 | const char* what() const noexcept override { 29 | return msg.c_str(); 30 | } 31 | }; 32 | 33 | // Lightweight std::source_location. 34 | struct source_location { 35 | const char* const file; 36 | const int line; 37 | constexpr source_location( 38 | const char* _file, 39 | int _line 40 | ) : file(_file), line(_line) {} 41 | }; 42 | 43 | #define CPPTRACE_CURRENT_LOCATION ::cpptrace::detail::source_location(__FILE__, __LINE__) 44 | 45 | enum class assert_type { 46 | assert, 47 | verify, 48 | panic, 49 | }; 50 | 51 | [[noreturn]] CPPTRACE_EXPORT void assert_fail( 52 | assert_type type, 53 | const char* expression, 54 | const char* signature, 55 | source_location location, 56 | const char* message 57 | ); 58 | 59 | [[noreturn]] void panic( 60 | const char* signature, 61 | source_location location, 62 | string_view message = "" 63 | ); 64 | 65 | 66 | template 67 | void nullfn(Args&&...); 68 | 69 | #define PHONY_USE(...) (static_cast(0)) 70 | 71 | // Work around a compiler warning 72 | template 73 | std::string as_string(T&& value) { 74 | return std::string(std::forward(value)); 75 | } 76 | 77 | inline std::string as_string() { 78 | return ""; 79 | } 80 | 81 | // Check condition in both debug and release. std::runtime_error on failure. 82 | #define PANIC(...) ((::cpptrace::detail::panic)(CPPTRACE_PFUNC, CPPTRACE_CURRENT_LOCATION, ::cpptrace::detail::as_string(__VA_ARGS__))) 83 | 84 | inline void assert_impl( 85 | bool condition, 86 | const char* message, 87 | assert_type type, 88 | const char* args, 89 | const char* signature, 90 | source_location location 91 | ) { 92 | if(!condition) { 93 | assert_fail(type, args, signature, location, message); 94 | } 95 | } 96 | 97 | inline void assert_impl( 98 | bool condition, 99 | assert_type type, 100 | const char* args, 101 | const char* signature, 102 | source_location location 103 | ) { 104 | assert_impl( 105 | condition, 106 | nullptr, 107 | type, 108 | args, 109 | signature, 110 | location 111 | ); 112 | } 113 | 114 | // Check condition in both debug and release. std::runtime_error on failure. 115 | #define VERIFY(...) ( \ 116 | assert_impl(__VA_ARGS__, ::cpptrace::detail::assert_type::verify, #__VA_ARGS__, CPPTRACE_PFUNC, CPPTRACE_CURRENT_LOCATION) \ 117 | ) 118 | 119 | #ifndef NDEBUG 120 | // Check condition in both debug. std::runtime_error on failure. 121 | #define ASSERT(...) ( \ 122 | assert_impl(__VA_ARGS__, ::cpptrace::detail::assert_type::assert, #__VA_ARGS__, CPPTRACE_PFUNC, CPPTRACE_CURRENT_LOCATION) \ 123 | ) 124 | #else 125 | // Check condition in both debug. std::runtime_error on failure. 126 | #define ASSERT(...) PHONY_USE(__VA_ARGS__) 127 | #endif 128 | } 129 | } 130 | 131 | #endif 132 | -------------------------------------------------------------------------------- /src/utils/io/base_file.hpp: -------------------------------------------------------------------------------- 1 | #ifndef BASE_FILE_HPP 2 | #define BASE_FILE_HPP 3 | 4 | #include "utils/span.hpp" 5 | #include "utils/utils.hpp" 6 | 7 | #include 8 | 9 | namespace cpptrace { 10 | namespace detail { 11 | class base_file { 12 | public: 13 | virtual ~base_file() = default; 14 | virtual string_view path() const = 0; 15 | virtual Result read_bytes(bspan buffer, off_t offset) const = 0; 16 | 17 | template::value && !is_span::value, int>::type = 0> 18 | Result read(off_t offset) { 19 | T object{}; 20 | auto res = read_bytes(make_bspan(object), offset); 21 | if(!res) { 22 | return res.unwrap_error(); 23 | } 24 | return object; 25 | } 26 | 27 | template::value, int>::type = 0> 28 | Result read_span(span items, off_t offset) { 29 | return read_bytes( 30 | make_span(reinterpret_cast(items.data()), reinterpret_cast(items.data() + items.size())), 31 | offset 32 | ); 33 | } 34 | }; 35 | } 36 | } 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /src/utils/io/file.cpp: -------------------------------------------------------------------------------- 1 | #define _CRT_SECURE_NO_WARNINGS 2 | #include "utils/io/file.hpp" 3 | 4 | namespace cpptrace { 5 | namespace detail { 6 | string_view file::path() const { 7 | return object_path; 8 | } 9 | 10 | Result file::open(cstring_view object_path) { 11 | auto file_obj = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter); 12 | if(file_obj == nullptr) { 13 | return internal_error("Unable to read object file {}", object_path); 14 | } 15 | return file(std::move(file_obj), object_path); 16 | } 17 | 18 | Result file::read_bytes(bspan buffer, off_t offset) const { 19 | if(std::fseek(file_obj, offset, SEEK_SET) != 0) { 20 | return internal_error("fseek error in {} at offset {}", path(), offset); 21 | } 22 | if(std::fread(buffer.data(), buffer.size(), 1, file_obj) != 1) { 23 | return internal_error("fread error in {} at offset {} for {} bytes", path(), offset, buffer.size()); 24 | } 25 | return monostate{}; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/utils/io/file.hpp: -------------------------------------------------------------------------------- 1 | #ifndef FILE_HPP 2 | #define FILE_HPP 3 | 4 | #include "utils/string_view.hpp" 5 | #include "utils/span.hpp" 6 | #include "utils/io/base_file.hpp" 7 | #include "utils/utils.hpp" 8 | 9 | namespace cpptrace { 10 | namespace detail { 11 | class file : public base_file { 12 | file_wrapper file_obj; 13 | std::string object_path; 14 | 15 | file(file_wrapper file_obj, string_view path) : file_obj(std::move(file_obj)), object_path(path) {} 16 | 17 | public: 18 | file(file&&) = default; 19 | ~file() override = default; 20 | 21 | string_view path() const override; 22 | 23 | static Result open(cstring_view object_path); 24 | 25 | virtual Result read_bytes(bspan buffer, off_t offset) const override; 26 | }; 27 | } 28 | } 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /src/utils/io/memory_file_view.cpp: -------------------------------------------------------------------------------- 1 | #include "utils/io/memory_file_view.hpp" 2 | 3 | namespace cpptrace { 4 | namespace detail { 5 | string_view memory_file_view::path() const { 6 | return object_path; 7 | } 8 | 9 | Result memory_file_view::read_bytes(bspan buffer, off_t offset) const { 10 | if(offset < 0) { 11 | return internal_error("Illegal read in memory file {}: offset {}", path(), offset); 12 | } 13 | if(offset + buffer.size() > data.size()) { 14 | return internal_error( 15 | "Illegal read in memory file {}: offset = {}, size = {}, file size = {}", 16 | path(), offset, buffer.size(), data.size() 17 | ); 18 | } 19 | std::memcpy(buffer.data(), data.data() + offset, buffer.size()); 20 | return monostate{}; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/utils/io/memory_file_view.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MEMORY_FILE_VIEW_HPP 2 | #define MEMORY_FILE_VIEW_HPP 3 | 4 | #include "utils/error.hpp" 5 | #include "utils/span.hpp" 6 | #include "utils/io/base_file.hpp" 7 | #include "utils/utils.hpp" 8 | 9 | namespace cpptrace { 10 | namespace detail { 11 | class memory_file_view : public base_file { 12 | cbspan data; 13 | std::string object_path = ""; 14 | 15 | public: 16 | memory_file_view(cbspan data) : data(data) {} 17 | ~memory_file_view() override = default; 18 | 19 | string_view path() const override; 20 | 21 | virtual Result read_bytes(bspan buffer, off_t offset) const override; 22 | }; 23 | } 24 | } 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /src/utils/lru_cache.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LRU_CACHE_HPP 2 | #define LRU_CACHE_HPP 3 | 4 | #include "utils/error.hpp" 5 | #include "utils/optional.hpp" 6 | 7 | #include 8 | #include 9 | 10 | namespace cpptrace { 11 | namespace detail { 12 | template 13 | class lru_cache { 14 | struct kvp { 15 | K key; 16 | V value; 17 | }; 18 | using list_type = std::list; 19 | using list_iterator = typename list_type::iterator; 20 | mutable list_type lru; 21 | std::unordered_map map; 22 | optional max_size; 23 | 24 | public: 25 | lru_cache() = default; 26 | lru_cache(optional max_size) : max_size(max_size) { 27 | VERIFY(!max_size || max_size.unwrap() > 0); 28 | } 29 | 30 | void set_max_size(optional size) { 31 | VERIFY(!size || size.unwrap() > 0); 32 | max_size = size; 33 | maybe_trim(); 34 | } 35 | 36 | void maybe_touch(const K& key) { 37 | auto it = map.find(key); 38 | if(it == map.end()) { 39 | return; 40 | } 41 | auto list_it = it->second; 42 | touch(list_it); 43 | } 44 | 45 | optional maybe_get(const K& key) { 46 | auto it = map.find(key); 47 | if(it == map.end()) { 48 | return nullopt; 49 | } else { 50 | touch(it->second); 51 | return it->second->value; 52 | } 53 | } 54 | 55 | optional maybe_get(const K& key) const { 56 | auto it = map.find(key); 57 | if(it == map.end()) { 58 | return nullopt; 59 | } else { 60 | touch(it->second); 61 | return it->second->value; 62 | } 63 | } 64 | 65 | void set(const K& key, V value) { 66 | auto it = map.find(key); 67 | if(it == map.end()) { 68 | insert(key, std::move(value)); 69 | } else { 70 | touch(it->second); 71 | it->second->value = std::move(value); 72 | } 73 | } 74 | 75 | optional insert(const K& key, V value) { 76 | auto pair = map.insert({key, lru.end()}); 77 | if(!pair.second) { 78 | // didn't insert 79 | return nullopt; 80 | } 81 | auto map_it = pair.first; 82 | lru.push_front({key, std::move(value)}); 83 | map_it->second = lru.begin(); 84 | maybe_trim(); 85 | return lru.front().value; 86 | } 87 | 88 | std::size_t size() const { 89 | return lru.size(); 90 | } 91 | 92 | private: 93 | void touch(list_iterator list_it) const { 94 | lru.splice(lru.begin(), lru, list_it); 95 | } 96 | 97 | void maybe_trim() { 98 | while(max_size && lru.size() > max_size.unwrap()) { 99 | const auto& to_remove = lru.back(); 100 | map.erase(to_remove.key); 101 | lru.pop_back(); 102 | } 103 | } 104 | }; 105 | } 106 | } 107 | 108 | #endif 109 | -------------------------------------------------------------------------------- /src/utils/microfmt.cpp: -------------------------------------------------------------------------------- 1 | #include "utils/microfmt.hpp" 2 | 3 | #include 4 | 5 | namespace cpptrace { 6 | namespace microfmt { 7 | namespace detail { 8 | 9 | std::ostream& get_cout() { 10 | return std::cout; 11 | } 12 | 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/utils/span.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SPAN_HPP 2 | #define SPAN_HPP 3 | 4 | #include "utils/utils.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace cpptrace { 13 | namespace detail { 14 | // basic span implementation 15 | // I haven't implemented most members because I don't need them, more will be added as needed 16 | 17 | template 18 | class span { 19 | T* ptr; 20 | std::size_t count; 21 | 22 | public: 23 | using element_type = T; 24 | using value_type = typename std::remove_cv::type; 25 | using size_type = std::size_t; 26 | using difference_type = std::ptrdiff_t; 27 | using pointer = T*; 28 | using const_pointer = const T*; 29 | using reference = T&; 30 | using const_reference = const T&; 31 | using iterator = T*; 32 | using const_iterator = const T*; 33 | using reverse_iterator = std::reverse_iterator; 34 | using const_reverse_iterator = std::reverse_iterator; 35 | using i_am_span = void; 36 | 37 | span() : ptr(nullptr), count(0) {} 38 | span(T* ptr, std::size_t count) : ptr(ptr), count(count) {} 39 | template 40 | span(It begin, It end) : ptr(std::addressof(*begin)), count(end - begin) {} 41 | 42 | T* data() const noexcept { 43 | return ptr; 44 | } 45 | std::size_t size() const noexcept { 46 | return count; 47 | } 48 | bool empty() const noexcept { 49 | return count == 0; 50 | } 51 | 52 | iterator begin() noexcept { 53 | return ptr; 54 | } 55 | iterator end() noexcept { 56 | return ptr + count; 57 | } 58 | const_iterator begin() const noexcept { 59 | return ptr; 60 | } 61 | const_iterator end() const noexcept { 62 | return ptr + count; 63 | } 64 | }; 65 | 66 | using bspan = span; 67 | using cbspan = span; 68 | 69 | template 70 | struct is_span : std::false_type {}; 71 | 72 | template 73 | struct is_span> : std::true_type {}; 74 | 75 | template 76 | auto make_span(It begin, It end) -> span::type> { 77 | return {begin, end}; 78 | } 79 | 80 | template 81 | auto make_span(It begin, std::size_t count) -> span::type> { 82 | return {begin, count}; 83 | } 84 | 85 | template::value && !is_span::value, int>::type = 0> 86 | span make_bspan(T& object) { 87 | return span(reinterpret_cast(std::addressof(object)), sizeof(object)); 88 | } 89 | } 90 | } 91 | 92 | #endif 93 | -------------------------------------------------------------------------------- /src/utils/string_view.cpp: -------------------------------------------------------------------------------- 1 | #include "utils/string_view.hpp" 2 | 3 | #include "utils/error.hpp" 4 | #include "utils/microfmt.hpp" 5 | 6 | #include 7 | #include 8 | 9 | namespace cpptrace { 10 | namespace detail { 11 | char string_view::operator[](size_t i) const { 12 | ASSERT(i < size()); 13 | return ptr[i]; 14 | } 15 | char string_view::at(size_t i) const { 16 | if(i >= size()) { 17 | throw std::runtime_error(microfmt::format("Out of bounds access {} >= {}", i, size())); 18 | } 19 | return ptr[i]; 20 | } 21 | 22 | std::size_t string_view::find_last_of(string_view chars) const { 23 | if(empty() || chars.empty()) { 24 | return npos; 25 | } 26 | std::size_t pos = size(); 27 | while(pos-- > 0) { 28 | if(std::find(chars.begin(), chars.end(), ptr[pos]) != chars.end()) { 29 | return pos; 30 | } 31 | } 32 | return npos; 33 | } 34 | 35 | bool operator==(string_view a, string_view b) { 36 | return a.size() == b.size() && std::memcmp(a.data(), b.data(), a.size()) == 0; 37 | } 38 | 39 | cstring_view cstring_view::substr(std::size_t pos) const { 40 | ASSERT(pos <= count); 41 | return {ptr + pos, count - pos}; 42 | } 43 | 44 | void cstring_view::check_null() const { 45 | ASSERT(ptr[count] == 0); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/utils/string_view.hpp: -------------------------------------------------------------------------------- 1 | #ifndef STRING_VIEW_HPP 2 | #define STRING_VIEW_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | namespace cpptrace { 12 | namespace detail { 13 | // Simple string view implementations 14 | // I haven't implemented all members because I don't need most of them currently, more may be added as needed 15 | // members exported for tests 16 | 17 | class string_view { 18 | const char* ptr; 19 | std::size_t count; 20 | 21 | public: 22 | using traits_type = std::char_traits; 23 | using value_type = char; 24 | using size_type = std::size_t; 25 | using difference_type = std::ptrdiff_t; 26 | using pointer = char*; 27 | using const_pointer = const char*; 28 | using reference = char&; 29 | using const_reference = const char&; 30 | using iterator = char*; 31 | using const_iterator = const char*; 32 | using reverse_iterator = std::reverse_iterator; 33 | using const_reverse_iterator = std::reverse_iterator; 34 | static constexpr std::size_t npos = std::string::npos; 35 | 36 | string_view() : ptr(nullptr), count(0) {} 37 | string_view(const char* str) : ptr(str), count(std::strlen(str)) {} 38 | string_view(const std::string& str) : ptr(str.c_str()), count(str.size()) {} 39 | string_view(const char* ptr, std::size_t count) : ptr(ptr), count(count) {} 40 | 41 | explicit operator std::string() { 42 | return std::string(ptr, ptr + count); 43 | } 44 | 45 | const char* data() const noexcept { 46 | return ptr; 47 | } 48 | std::size_t size() const noexcept { 49 | return count; 50 | } 51 | bool empty() const noexcept { 52 | return count == 0; 53 | } 54 | 55 | CPPTRACE_EXPORT char operator[](size_t i) const; 56 | CPPTRACE_EXPORT char at(size_t i) const; 57 | 58 | CPPTRACE_EXPORT std::size_t find_last_of(string_view chars) const; 59 | 60 | const_iterator begin() const noexcept { 61 | return ptr; 62 | } 63 | const_iterator end() const noexcept { 64 | return ptr + count; 65 | } 66 | }; 67 | 68 | bool operator==(string_view, string_view); 69 | 70 | class cstring_view { 71 | const char* ptr; 72 | std::size_t count; 73 | 74 | public: 75 | using traits_type = std::char_traits; 76 | using value_type = char; 77 | using size_type = std::size_t; 78 | using difference_type = std::ptrdiff_t; 79 | using pointer = char*; 80 | using const_pointer = const char*; 81 | using reference = char&; 82 | using const_reference = const char&; 83 | using iterator = char*; 84 | using const_iterator = const char*; 85 | using reverse_iterator = std::reverse_iterator; 86 | using const_reverse_iterator = std::reverse_iterator; 87 | static constexpr std::size_t npos = string_view::npos; 88 | 89 | cstring_view() : ptr(nullptr), count(0) {} 90 | cstring_view(const char* str) : ptr(str), count(std::strlen(str)) {} 91 | cstring_view(const std::string& str) : ptr(str.c_str()), count(str.size()) {} 92 | cstring_view(const char* ptr, std::size_t count) : ptr(ptr), count(count) { 93 | check_null(); 94 | } 95 | 96 | explicit operator std::string() { 97 | return std::string(ptr, ptr + count); 98 | } 99 | 100 | operator string_view() const noexcept { 101 | return string_view(ptr, count); 102 | } 103 | 104 | const char* data() const noexcept { 105 | return ptr; 106 | } 107 | const char* c_str() const noexcept { 108 | return ptr; 109 | } 110 | std::size_t size() const noexcept { 111 | return count; 112 | } 113 | bool empty() const noexcept { 114 | return count == 0; 115 | } 116 | 117 | char operator[](size_t i) const { 118 | return operator string_view().operator[](i); 119 | } 120 | char at(size_t i) const { 121 | return operator string_view().at(i); 122 | } 123 | 124 | std::size_t find_last_of(string_view chars) const { 125 | return operator string_view().find_last_of(chars); 126 | } 127 | 128 | CPPTRACE_EXPORT cstring_view substr(std::size_t pos) const; 129 | 130 | const_iterator begin() const noexcept { 131 | return ptr; 132 | } 133 | const_iterator end() const noexcept { 134 | return ptr + count; 135 | } 136 | private: 137 | CPPTRACE_EXPORT void check_null() const; 138 | }; 139 | } 140 | } 141 | 142 | #endif 143 | -------------------------------------------------------------------------------- /src/utils/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils/utils.hpp" 2 | #include "utils/string_view.hpp" 3 | 4 | #if IS_WINDOWS 5 | #include 6 | #ifndef WIN32_LEAN_AND_MEAN 7 | #define WIN32_LEAN_AND_MEAN 8 | #endif 9 | #include 10 | #else 11 | #include 12 | #include 13 | #endif 14 | 15 | namespace cpptrace { 16 | namespace detail { 17 | 18 | bool isatty(int fd) { 19 | #if IS_WINDOWS 20 | return _isatty(fd); 21 | #else 22 | return ::isatty(fd); 23 | #endif 24 | } 25 | 26 | int fileno(std::FILE* stream) { 27 | #if IS_WINDOWS 28 | return _fileno(stream); 29 | #else 30 | return ::fileno(stream); 31 | #endif 32 | } 33 | 34 | void enable_virtual_terminal_processing_if_needed() noexcept { 35 | // enable colors / ansi processing if necessary 36 | #if IS_WINDOWS 37 | // https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example-of-enabling-virtual-terminal-processing 38 | #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING 39 | constexpr DWORD ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4; 40 | #endif 41 | HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); 42 | DWORD dwMode = 0; 43 | if(hOut == INVALID_HANDLE_VALUE) return; 44 | if(!GetConsoleMode(hOut, &dwMode)) return; 45 | if(dwMode != (dwMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) 46 | if(!SetConsoleMode(hOut, dwMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) return; 47 | #endif 48 | } 49 | 50 | bool directory_exists(cstring_view path) { 51 | #if IS_WINDOWS 52 | DWORD dwAttrib = GetFileAttributesA(path.c_str()); 53 | return dwAttrib != INVALID_FILE_ATTRIBUTES && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY); 54 | #else 55 | struct stat sb; 56 | return stat(path.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode); 57 | #endif 58 | } 59 | 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /test/BUILD.bazel: -------------------------------------------------------------------------------- 1 | cc_test( 2 | name = "unittest", 3 | deps = [ 4 | "//:cpptrace", 5 | "@googletest//:gtest", 6 | "@googletest//:gtest_main" 7 | ], 8 | srcs = [ 9 | "unit/main.cpp", 10 | "unit/tracing/common.hpp", 11 | "unit/tracing/raw_trace.cpp", 12 | "unit/tracing/object_trace.cpp", 13 | "unit/tracing/stacktrace.cpp", 14 | "unit/tracing/from_current.cpp", 15 | "unit/tracing/from_current_z.cpp", 16 | "unit/tracing/traced_exception.cpp", 17 | "unit/internals/optional.cpp", 18 | "unit/internals/result.cpp", 19 | "unit/internals/string_utils.cpp", 20 | "unit/internals/general.cpp", 21 | "unit/lib/formatting.cpp", 22 | "unit/lib/nullable.cpp" 23 | ], 24 | local_defines = [ 25 | "CPPTRACE_NO_TEST_SNIPPETS" 26 | ], 27 | linkstatic = 1, 28 | ) -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(CTest) 2 | 3 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) 4 | 5 | set( 6 | warning_options 7 | ${warning_options} $<$:-Wno-infinite-recursion> 8 | ) 9 | 10 | macro(add_test_dependencies exec_name) 11 | target_compile_features(${exec_name} PRIVATE cxx_std_11) 12 | target_link_libraries(${exec_name} PRIVATE ${target_name}) 13 | target_compile_options(${exec_name} PRIVATE ${warning_options}) 14 | target_compile_options(${exec_name} PRIVATE ${debug}) 15 | if(CPPTRACE_BUILD_TESTING_SPLIT_DWARF) 16 | target_compile_options(${exec_name} PRIVATE -gsplit-dwarf) 17 | endif() 18 | if(NOT (CPPTRACE_BUILD_TESTING_DWARF_VERSION STREQUAL "0")) 19 | target_compile_options(${exec_name} PRIVATE -gdwarf-${CPPTRACE_BUILD_TESTING_DWARF_VERSION}) 20 | endif() 21 | # Clang has been fast to adopt dwarf 5, other tools (e.g. addr2line from binutils) have not 22 | if(NOT CPPTRACE_BUILD_NO_SYMBOLS) 23 | check_cxx_compiler_flag("-gdwarf-4" HAS_DWARF4) 24 | if(HAS_DWARF4) 25 | target_compile_options(${exec_name} PRIVATE "$<$:-gdwarf-4>") 26 | endif() 27 | endif() 28 | # TODO: add debug info for mingw clang? 29 | if(CPPTRACE_BUILD_TEST_RDYNAMIC) 30 | set_property(TARGET ${exec_name} PROPERTY ENABLE_EXPORTS ON) 31 | endif() 32 | endmacro() 33 | 34 | 35 | add_executable(integration integration.cpp) 36 | add_executable(demo demo.cpp) 37 | add_executable(c_demo ctrace_demo.c) 38 | 39 | add_test_dependencies(integration) 40 | add_test_dependencies(demo) 41 | add_test_dependencies(c_demo) 42 | 43 | if(UNIX) 44 | add_executable(signal_demo signal_demo.cpp) 45 | target_compile_features(signal_demo PRIVATE cxx_std_11) 46 | target_link_libraries(signal_demo PRIVATE ${target_name}) 47 | target_compile_options(signal_demo PRIVATE ${debug}) 48 | if(CPPTRACE_BUILD_TESTING_SPLIT_DWARF) 49 | target_compile_options(signal_demo PRIVATE -gsplit-dwarf) 50 | endif() 51 | if(NOT (CPPTRACE_BUILD_TESTING_DWARF_VERSION STREQUAL "0")) 52 | target_compile_options(signal_demo PRIVATE -gdwarf-${CPPTRACE_BUILD_TESTING_DWARF_VERSION}) 53 | endif() 54 | 55 | add_executable(signal_tracer signal_tracer.cpp) 56 | target_compile_features(signal_tracer PRIVATE cxx_std_11) 57 | target_link_libraries(signal_tracer PRIVATE ${target_name}) 58 | target_compile_options(signal_tracer PRIVATE ${debug}) 59 | endif() 60 | 61 | # primarily a workaround for github actions issue https://github.com/actions/runner-images/issues/8659 62 | if(NOT CPPTRACE_SKIP_UNIT) 63 | if(CPPTRACE_USE_EXTERNAL_GTEST) 64 | find_package(GTest) 65 | else() 66 | include(FetchContent) 67 | FetchContent_Declare( 68 | googletest 69 | GIT_REPOSITORY "https://github.com/google/googletest.git" 70 | GIT_TAG f8d7d77c06936315286eb55f8de22cd23c188571 # v1.14.0 71 | ) 72 | # For Windows: Prevent overriding the parent project's compiler/linker settings 73 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 74 | FetchContent_MakeAvailable(googletest) 75 | endif() 76 | 77 | add_executable( 78 | unittest 79 | unit/main.cpp 80 | unit/tracing/raw_trace.cpp 81 | unit/tracing/object_trace.cpp 82 | unit/tracing/stacktrace.cpp 83 | unit/tracing/from_current.cpp 84 | unit/tracing/from_current_z.cpp 85 | unit/tracing/traced_exception.cpp 86 | unit/internals/optional.cpp 87 | unit/internals/lru_cache.cpp 88 | unit/internals/result.cpp 89 | unit/internals/string_utils.cpp 90 | unit/internals/general.cpp 91 | unit/internals/span.cpp 92 | unit/internals/string_view.cpp 93 | unit/lib/formatting.cpp 94 | unit/lib/nullable.cpp 95 | ) 96 | target_compile_features(unittest PRIVATE cxx_std_20) 97 | target_link_libraries(unittest PRIVATE ${target_name} GTest::gtest_main GTest::gmock_main) 98 | target_compile_options(unittest PRIVATE ${warning_options} $<$>:-Wno-pedantic -Wno-attributes>) 99 | target_compile_options(unittest PRIVATE ${debug}) 100 | if(CPPTRACE_BUILD_TESTING_SPLIT_DWARF) 101 | target_compile_options(unittest PRIVATE -gsplit-dwarf) 102 | endif() 103 | if(NOT (CPPTRACE_BUILD_TESTING_DWARF_VERSION STREQUAL "0")) 104 | target_compile_options(unittest PRIVATE -gdwarf-${CPPTRACE_BUILD_TESTING_DWARF_VERSION}) 105 | endif() 106 | if(CPPTRACE_SANITIZER_BUILD) 107 | target_compile_definitions(unittest PRIVATE CPPTRACE_SANITIZER_BUILD) 108 | endif() 109 | if(CPPTRACE_BUILD_NO_SYMBOLS) 110 | target_compile_definitions(unittest PRIVATE CPPTRACE_BUILD_NO_SYMBOLS) 111 | endif() 112 | target_include_directories(unittest PRIVATE ../src) 113 | add_test(NAME unittest COMMAND unittest) 114 | endif() 115 | -------------------------------------------------------------------------------- /test/add_subdirectory-integration/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | 3 | project(demo_project VERSION 0.0.1 LANGUAGES CXX) 4 | 5 | add_executable(main main.cpp) 6 | 7 | add_subdirectory(cpptrace) 8 | target_link_libraries(main cpptrace::cpptrace) 9 | target_compile_features(main PRIVATE cxx_std_11) 10 | 11 | if(WIN32) 12 | add_custom_command( 13 | TARGET main POST_BUILD 14 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 15 | $ 16 | $ 17 | ) 18 | endif() 19 | -------------------------------------------------------------------------------- /test/add_subdirectory-integration/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void trace() { 4 | cpptrace::generate_trace().print(); 5 | } 6 | 7 | void foo(int) { 8 | trace(); 9 | } 10 | 11 | int main() { 12 | foo(0); 13 | } 14 | -------------------------------------------------------------------------------- /test/ctrace_demo.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | void trace(void) { 7 | ctrace_raw_trace raw_trace = ctrace_generate_raw_trace(1, INT_MAX); 8 | ctrace_object_trace obj_trace = ctrace_resolve_raw_trace_to_object_trace(&raw_trace); 9 | ctrace_stacktrace trace = ctrace_resolve_object_trace(&obj_trace); 10 | ctrace_print_stacktrace(&trace, stdout, 1); 11 | ctrace_free_stacktrace(&trace); 12 | ctrace_free_object_trace(&obj_trace); 13 | ctrace_free_raw_trace(&raw_trace); 14 | assert(raw_trace.frames == NULL && obj_trace.count == 0); 15 | } 16 | 17 | void bar(int n) { 18 | if(n == 0) { 19 | trace(); 20 | } else { 21 | bar(n - 1); 22 | } 23 | } 24 | 25 | void foo(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j) { 26 | (void)a, (void)b, (void)c, (void)d, (void)e, (void)f, (void)g, (void)h, (void)i, (void)j; 27 | bar(1); 28 | } 29 | 30 | void function_two(int a, float b) { 31 | (void)a, (void)b; 32 | foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 33 | } 34 | 35 | void function_one(int a) { 36 | (void)a; 37 | function_two(0, 0); 38 | } 39 | 40 | int main(void) { 41 | function_one(0); 42 | } 43 | -------------------------------------------------------------------------------- /test/demo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | void trace() { 10 | cpptrace::generate_trace().print(); 11 | cpptrace::generate_trace().print_with_snippets(); 12 | throw cpptrace::logic_error("foobar"); 13 | } 14 | 15 | void foo(int n) { 16 | if(n == 0) { 17 | trace(); 18 | } else { 19 | foo(n - 1); 20 | } 21 | } 22 | 23 | template 24 | void foo(int, Args... args) { 25 | foo(args...); 26 | } 27 | 28 | void function_two(int, float) { 29 | foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 30 | } 31 | 32 | void function_one(int) { 33 | function_two(0, 0); 34 | } 35 | 36 | int main() { 37 | cpptrace::absorb_trace_exceptions(false); 38 | cpptrace::register_terminate_handler(); 39 | function_one(0); 40 | } 41 | -------------------------------------------------------------------------------- /test/expected/linux.libdl.txt: -------------------------------------------------------------------------------- 1 | ./integration||4294967295||trace() 2 | ./integration||4294967295||www(std::__cxx11::basic_string, std::allocator >&&, std::__cxx11::basic_string, std::allocator > const&, std::vector, std::allocator >*, std::allocator, std::allocator >*> >&&) 3 | ./integration||4294967295||jjj(void (* const*)(float)) 4 | ./integration||4294967295||iii(Foo::Bar) 5 | ./integration||4294967295||hhh(int (* (*) [10]) [20]) 6 | ./integration||4294967295||ggg(int const* const*) 7 | ./integration||4294967295||fff(int (S::*)(float) const volatile &&) 8 | ./integration||4294967295||eee(int (*(* const* volatile (*) [10])())(float)) 9 | ./integration||4294967295||ddd(int (* (*) [10])()) 10 | ./integration||4294967295||ccc(int (*) [5][6][7][8]) 11 | ./integration||4294967295||bbb(int (* const (&) [5])(float, int const&)) 12 | ./integration||4294967295||aaa(int (&) [5]) 13 | ./integration||4294967295||foo(int) 14 | ./integration||4294967295||foo(int) 15 | ./integration||4294967295||foo(int) 16 | ./integration||4294967295||foo(int) 17 | ./integration||4294967295||foo(int) 18 | ./integration||4294967295||foo(int) 19 | ./integration||4294967295||foo(int) 20 | ./integration||4294967295||foo(int) 21 | ./integration||4294967295||foo(int) 22 | ./integration||4294967295||foo(int) 23 | ./integration||4294967295||foo(int) 24 | ./integration||4294967295||void foo(int, int) 25 | ./integration||4294967295||void foo(int, int, int) 26 | ./integration||4294967295||void foo(int, int, int, int) 27 | ./integration||4294967295||void foo(int, int, int, int, int) 28 | ./integration||4294967295||void foo(int, int, int, int, int, int) 29 | ./integration||4294967295||void foo(int, int, int, int, int, int, int) 30 | ./integration||4294967295||void foo(int, int, int, int, int, int, int, int) 31 | ./integration||4294967295||void foo(int, int, int, int, int, int, int, int, int) 32 | ./integration||4294967295||void foo(int, int, int, int, int, int, int, int, int, int) 33 | ./integration||4294967295||function_two(int, float) 34 | ./integration||4294967295||function_one(int) 35 | ./integration||4294967295||main 36 | /lib/x86_64-linux-gnu/libc.so.6||4294967295|| 37 | /lib/x86_64-linux-gnu/libc.so.6||4294967295||__libc_start_main 38 | ./integration||4294967295||_start -------------------------------------------------------------------------------- /test/expected/linux.txt: -------------------------------------------------------------------------------- 1 | test/integration.cpp||23||trace() 2 | test/integration.cpp||33||www(std::__cxx11::basic_string, std::allocator >&&, std::__cxx11::basic_string, std::allocator > const&, std::vector, std::allocator >*, std::allocator, std::allocator >*> >&&) 3 | test/integration.cpp||37||jjj(void (* const*)(float)) 4 | test/integration.cpp||45||iii(Foo::Bar) 5 | test/integration.cpp||55||hhh(int (* (*) [10]) [20]) 6 | test/integration.cpp||59||ggg(int const* const*) 7 | test/integration.cpp||63||fff(int (S::*)(float) const volatile &&) 8 | test/integration.cpp||68||eee(int (*(* const* volatile (*) [10])())(float)) 9 | test/integration.cpp||72||ddd(int (* (*) [10])()) 10 | test/integration.cpp||76||ccc(int (*) [5][6][7][8]) 11 | test/integration.cpp||80||bbb(int (* const (&) [5])(float, int const&)) 12 | test/integration.cpp||85||aaa(int (&) [5]) 13 | test/integration.cpp||94||foo(int) 14 | test/integration.cpp||98||foo(int) 15 | test/integration.cpp||98||foo(int) 16 | test/integration.cpp||98||foo(int) 17 | test/integration.cpp||98||foo(int) 18 | test/integration.cpp||98||foo(int) 19 | test/integration.cpp||98||foo(int) 20 | test/integration.cpp||98||foo(int) 21 | test/integration.cpp||98||foo(int) 22 | test/integration.cpp||98||foo(int) 23 | test/integration.cpp||98||foo(int) 24 | test/integration.cpp||106||void foo(int, int) 25 | test/integration.cpp||106||void foo(int, int, int) 26 | test/integration.cpp||106||void foo(int, int, int, int) 27 | test/integration.cpp||106||void foo(int, int, int, int, int) 28 | test/integration.cpp||106||void foo(int, int, int, int, int, int) 29 | test/integration.cpp||106||void foo(int, int, int, int, int, int, int) 30 | test/integration.cpp||106||void foo(int, int, int, int, int, int, int, int) 31 | test/integration.cpp||106||void foo(int, int, int, int, int, int, int, int, int) 32 | test/integration.cpp||106||void foo(int, int, int, int, int, int, int, int, int, int) 33 | test/integration.cpp||112||function_two(int, float) 34 | test/integration.cpp||118||function_one(int) 35 | test/integration.cpp||125||main 36 | ./csu/../sysdeps/nptl/libc_start_call_main.h||58||__libc_start_call_main 37 | ./csu/../csu/libc-start.c||392||__libc_start_main_impl 38 | ./test||4294967295|| -------------------------------------------------------------------------------- /test/expected/macos.clang.libdl.txt: -------------------------------------------------------------------------------- 1 | build/integration||4294967295||trace() 2 | build/integration||4294967295||www(std::__1::basic_string, std::__1::allocator>&&, std::__1::basic_string, std::__1::allocator> const&, std::__1::vector, std::__1::allocator>*, std::__1::allocator, std::__1::allocator>*>>&&) 3 | build/integration||4294967295||jjj(void (* const*)(float)) 4 | build/integration||4294967295||iii(Foo::Bar) 5 | build/integration||4294967295||hhh(int (* (*) [10]) [20]) 6 | build/integration||4294967295||ggg(int const* const*) 7 | build/integration||4294967295||fff(int (S::*)(float) const volatile &&) 8 | build/integration||4294967295||eee(int (* (* const* volatile (*) [10])())(float)) 9 | build/integration||4294967295||ddd(int (* (*) [10])()) 10 | build/integration||4294967295||ccc(int (*) [5][6][7][8]) 11 | build/integration||4294967295||bbb(int (* const (&) [5])(float, int const&)) 12 | build/integration||4294967295||aaa(int (&) [5]) 13 | build/integration||4294967295||foo(int) 14 | build/integration||4294967295||foo(int) 15 | build/integration||4294967295||foo(int) 16 | build/integration||4294967295||foo(int) 17 | build/integration||4294967295||foo(int) 18 | build/integration||4294967295||foo(int) 19 | build/integration||4294967295||foo(int) 20 | build/integration||4294967295||foo(int) 21 | build/integration||4294967295||foo(int) 22 | build/integration||4294967295||foo(int) 23 | build/integration||4294967295||foo(int) 24 | build/integration||4294967295||void foo(int, int) 25 | build/integration||4294967295||void foo(int, int, int) 26 | build/integration||4294967295||void foo(int, int, int, int) 27 | build/integration||4294967295||void foo(int, int, int, int, int) 28 | build/integration||4294967295||void foo(int, int, int, int, int, int) 29 | build/integration||4294967295||void foo(int, int, int, int, int, int, int) 30 | build/integration||4294967295||void foo(int, int, int, int, int, int, int, int) 31 | build/integration||4294967295||void foo(int, int, int, int, int, int, int, int, int) 32 | build/integration||4294967295||void foo(int, int, int, int, int, int, int, int, int, int) 33 | build/integration||4294967295||function_two(int, float) 34 | build/integration||4294967295||function_one(int) 35 | build/integration||4294967295||main 36 | /usr/lib/dyld||4294967295||start -------------------------------------------------------------------------------- /test/expected/macos.clang.txt: -------------------------------------------------------------------------------- 1 | test/integration.cpp||23||trace() 2 | test/integration.cpp||33||www(std::__1::basic_string, std::__1::allocator>&&, std::__1::basic_string, std::__1::allocator> const&, std::__1::vector, std::__1::allocator>*, std::__1::allocator, std::__1::allocator>*>>&&) 3 | test/integration.cpp||37||jjj(void (* const*)(float)) 4 | test/integration.cpp||45||iii(Foo::Bar) 5 | test/integration.cpp||55||hhh(int (* (*) [10]) [20]) 6 | test/integration.cpp||59||ggg(int const* const*) 7 | test/integration.cpp||63||fff(int (S::*)(float) const volatile &&) 8 | test/integration.cpp||68||eee(int (* (* const* volatile (*) [10])())(float)) 9 | test/integration.cpp||72||ddd(int (* (*) [10])()) 10 | test/integration.cpp||76||ccc(int (*) [5][6][7][8]) 11 | test/integration.cpp||80||bbb(int (* const (&) [5])(float, int const&)) 12 | test/integration.cpp||85||aaa(int (&) [5]) 13 | test/integration.cpp||94||foo(int) 14 | test/integration.cpp||98||foo(int) 15 | test/integration.cpp||98||foo(int) 16 | test/integration.cpp||98||foo(int) 17 | test/integration.cpp||98||foo(int) 18 | test/integration.cpp||98||foo(int) 19 | test/integration.cpp||98||foo(int) 20 | test/integration.cpp||98||foo(int) 21 | test/integration.cpp||98||foo(int) 22 | test/integration.cpp||98||foo(int) 23 | test/integration.cpp||98||foo(int) 24 | test/integration.cpp||106||void foo(int, int) 25 | test/integration.cpp||106||void foo(int, int, int) 26 | test/integration.cpp||106||void foo(int, int, int, int) 27 | test/integration.cpp||106||void foo(int, int, int, int, int) 28 | test/integration.cpp||106||void foo(int, int, int, int, int, int) 29 | test/integration.cpp||106||void foo(int, int, int, int, int, int, int) 30 | test/integration.cpp||106||void foo(int, int, int, int, int, int, int, int) 31 | test/integration.cpp||106||void foo(int, int, int, int, int, int, int, int, int) 32 | test/integration.cpp||106||void foo(int, int, int, int, int, int, int, int, int, int) 33 | test/integration.cpp||112||function_two(int, float) 34 | test/integration.cpp||118||function_one(int) 35 | test/integration.cpp||125||main 36 | ./csu/../sysdeps/nptl/libc_start_call_main.h||58||__libc_start_call_main 37 | ./csu/../csu/libc-start.c||392||__libc_start_main_impl 38 | ./test||4294967295|| -------------------------------------------------------------------------------- /test/expected/macos.gcc.addr2line.txt: -------------------------------------------------------------------------------- 1 | test/integration.cpp||23||trace() 2 | test/integration.cpp||33||www(std::__cxx11::basic_string, std::allocator>&&, std::__cxx11::basic_string, std::allocator> const&, std::vector, std::allocator>*, std::allocator, std::allocator>*>>&&) 3 | test/integration.cpp||37||jjj(void (* const*)(float)) 4 | test/integration.cpp||45||iii(Foo::Bar) 5 | test/integration.cpp||55||hhh(int (* (*) [10]) [20]) 6 | test/integration.cpp||59||ggg(int const* const*) 7 | test/integration.cpp||63||fff(int (S::*)(float) const volatile &&) 8 | test/integration.cpp||68||eee(int (* (* const* volatile (*) [10])())(float)) 9 | test/integration.cpp||72||ddd(int (* (*) [10])()) 10 | test/integration.cpp||76||ccc(int (*) [5][6][7][8]) 11 | test/integration.cpp||80||bbb(int (* const (&) [5])(float, int const&)) 12 | test/integration.cpp||85||aaa(int (&) [5]) 13 | test/integration.cpp||94||foo(int) 14 | test/integration.cpp||98||foo(int) 15 | test/integration.cpp||98||foo(int) 16 | test/integration.cpp||98||foo(int) 17 | test/integration.cpp||98||foo(int) 18 | test/integration.cpp||98||foo(int) 19 | test/integration.cpp||98||foo(int) 20 | test/integration.cpp||98||foo(int) 21 | test/integration.cpp||98||foo(int) 22 | test/integration.cpp||98||foo(int) 23 | test/integration.cpp||98||foo(int) 24 | test/integration.cpp||106||void foo(int, int) 25 | test/integration.cpp||106||void foo(int, int, int) 26 | test/integration.cpp||106||void foo(int, int, int, int) 27 | test/integration.cpp||106||void foo(int, int, int, int, int) 28 | test/integration.cpp||106||void foo(int, int, int, int, int, int) 29 | test/integration.cpp||106||void foo(int, int, int, int, int, int, int) 30 | test/integration.cpp||106||void foo(int, int, int, int, int, int, int, int) 31 | test/integration.cpp||106||void foo(int, int, int, int, int, int, int, int, int) 32 | test/integration.cpp||106||void foo(int, int, int, int, int, int, int, int, int, int) 33 | test/integration.cpp||112||function_two(int, float) 34 | test/integration.cpp||118||function_one(int) 35 | test/integration.cpp||125||main 36 | ./csu/../sysdeps/nptl/libc_start_call_main.h||58||__libc_start_call_main 37 | ./csu/../csu/libc-start.c||392||__libc_start_main_impl 38 | ./test||4294967295|| -------------------------------------------------------------------------------- /test/expected/macos.gcc.libdl.txt: -------------------------------------------------------------------------------- 1 | build/integration||4294967295||trace() 2 | build/integration||4294967295||www(std::__cxx11::basic_string, std::allocator >&&, std::__cxx11::basic_string, std::allocator > const&, std::vector, std::allocator >*, std::allocator, std::allocator >*> >&&) 3 | build/integration||4294967295||jjj(void (* const*)(float)) 4 | build/integration||4294967295||iii(Foo::Bar) 5 | build/integration||4294967295||hhh(int (* (*) [10]) [20]) 6 | build/integration||4294967295||ggg(int const* const*) 7 | build/integration||4294967295||fff(int (S::*)(float) const volatile &&) 8 | build/integration||4294967295||eee(int (*(* const* volatile (*) [10])())(float)) 9 | build/integration||4294967295||ddd(int (* (*) [10])()) 10 | build/integration||4294967295||ccc(int (*) [5][6][7][8]) 11 | build/integration||4294967295||bbb(int (* const (&) [5])(float, int const&)) 12 | build/integration||4294967295||aaa(int (&) [5]) 13 | build/integration||4294967295||foo(int) 14 | build/integration||4294967295||foo(int) 15 | build/integration||4294967295||foo(int) 16 | build/integration||4294967295||foo(int) 17 | build/integration||4294967295||foo(int) 18 | build/integration||4294967295||foo(int) 19 | build/integration||4294967295||foo(int) 20 | build/integration||4294967295||foo(int) 21 | build/integration||4294967295||foo(int) 22 | build/integration||4294967295||foo(int) 23 | build/integration||4294967295||foo(int) 24 | build/integration||4294967295||void foo(int, int) 25 | build/integration||4294967295||void foo(int, int, int) 26 | build/integration||4294967295||void foo(int, int, int, int) 27 | build/integration||4294967295||void foo(int, int, int, int, int) 28 | build/integration||4294967295||void foo(int, int, int, int, int, int) 29 | build/integration||4294967295||void foo(int, int, int, int, int, int, int) 30 | build/integration||4294967295||void foo(int, int, int, int, int, int, int, int) 31 | build/integration||4294967295||void foo(int, int, int, int, int, int, int, int, int) 32 | build/integration||4294967295||void foo(int, int, int, int, int, int, int, int, int, int) 33 | build/integration||4294967295||function_two(int, float) 34 | build/integration||4294967295||function_one(int) 35 | build/integration||4294967295||main 36 | /usr/lib/dyld||4294967295||start -------------------------------------------------------------------------------- /test/expected/macos.gcc.txt: -------------------------------------------------------------------------------- 1 | test/integration.cpp||23||trace() 2 | test/integration.cpp||33||www(std::__cxx11::basic_string, std::allocator >&&, std::__cxx11::basic_string, std::allocator > const&, std::vector, std::allocator >*, std::allocator, std::allocator >*> >&&) 3 | test/integration.cpp||37||jjj(void (* const*)(float)) 4 | test/integration.cpp||45||iii(Foo::Bar) 5 | test/integration.cpp||55||hhh(int (* (*) [10]) [20]) 6 | test/integration.cpp||59||ggg(int const* const*) 7 | test/integration.cpp||63||fff(int (S::*)(float) const volatile &&) 8 | test/integration.cpp||68||eee(int (*(* const* volatile (*) [10])())(float)) 9 | test/integration.cpp||72||ddd(int (* (*) [10])()) 10 | test/integration.cpp||76||ccc(int (*) [5][6][7][8]) 11 | test/integration.cpp||80||bbb(int (* const (&) [5])(float, int const&)) 12 | test/integration.cpp||85||aaa(int (&) [5]) 13 | test/integration.cpp||94||foo(int) 14 | test/integration.cpp||98||foo(int) 15 | test/integration.cpp||98||foo(int) 16 | test/integration.cpp||98||foo(int) 17 | test/integration.cpp||98||foo(int) 18 | test/integration.cpp||98||foo(int) 19 | test/integration.cpp||98||foo(int) 20 | test/integration.cpp||98||foo(int) 21 | test/integration.cpp||98||foo(int) 22 | test/integration.cpp||98||foo(int) 23 | test/integration.cpp||98||foo(int) 24 | test/integration.cpp||106||void foo(int, int) 25 | test/integration.cpp||106||void foo(int, int, int) 26 | test/integration.cpp||106||void foo(int, int, int, int) 27 | test/integration.cpp||106||void foo(int, int, int, int, int) 28 | test/integration.cpp||106||void foo(int, int, int, int, int, int) 29 | test/integration.cpp||106||void foo(int, int, int, int, int, int, int) 30 | test/integration.cpp||106||void foo(int, int, int, int, int, int, int, int) 31 | test/integration.cpp||106||void foo(int, int, int, int, int, int, int, int, int) 32 | test/integration.cpp||106||void foo(int, int, int, int, int, int, int, int, int, int) 33 | test/integration.cpp||112||function_two(int, float) 34 | test/integration.cpp||118||function_one(int) 35 | test/integration.cpp||125||main 36 | ./csu/../sysdeps/nptl/libc_start_call_main.h||58||__libc_start_call_main 37 | ./csu/../csu/libc-start.c||392||__libc_start_main_impl 38 | ./test||4294967295|| -------------------------------------------------------------------------------- /test/expected/windows.gcc.txt: -------------------------------------------------------------------------------- 1 | test/integration.cpp||23||trace() 2 | test/integration.cpp||33||www(std::__cxx11::basic_string, std::allocator >&&, std::__cxx11::basic_string, std::allocator > const&, std::vector, std::allocator >*, std::allocator, std::allocator >*> >&&) 3 | test/integration.cpp||37||jjj(void (* const*)(float)) 4 | test/integration.cpp||45||iii(Foo::Bar) 5 | test/integration.cpp||55||hhh(int (* (*) [10]) [20]) 6 | test/integration.cpp||59||ggg(int const* const*) 7 | test/integration.cpp||63||fff(int (S::*)(float) const volatile &&) 8 | test/integration.cpp||68||eee(int (*(* const* volatile (*) [10])())(float)) 9 | test/integration.cpp||72||ddd(int (* (*) [10])()) 10 | test/integration.cpp||76||ccc(int (*) [5][6][7][8]) 11 | test/integration.cpp||80||bbb(int (* const (&) [5])(float, int const&)) 12 | test/integration.cpp||85||aaa(int (&) [5]) 13 | test/integration.cpp||94||foo(int) 14 | test/integration.cpp||98||foo(int) 15 | test/integration.cpp||98||foo(int) 16 | test/integration.cpp||98||foo(int) 17 | test/integration.cpp||98||foo(int) 18 | test/integration.cpp||98||foo(int) 19 | test/integration.cpp||98||foo(int) 20 | test/integration.cpp||98||foo(int) 21 | test/integration.cpp||98||foo(int) 22 | test/integration.cpp||98||foo(int) 23 | test/integration.cpp||98||foo(int) 24 | test/integration.cpp||106||void foo(int, int) 25 | test/integration.cpp||106||void foo(int, int, int) 26 | test/integration.cpp||106||void foo(int, int, int, int) 27 | test/integration.cpp||106||void foo(int, int, int, int, int) 28 | test/integration.cpp||106||void foo(int, int, int, int, int, int) 29 | test/integration.cpp||106||void foo(int, int, int, int, int, int, int) 30 | test/integration.cpp||106||void foo(int, int, int, int, int, int, int, int) 31 | test/integration.cpp||106||void foo(int, int, int, int, int, int, int, int, int) 32 | test/integration.cpp||106||void foo(int, int, int, int, int, int, int, int, int, int) 33 | test/integration.cpp||112||function_two(int, float) 34 | test/integration.cpp||118||function_one(int) 35 | test/integration.cpp||125||main 36 | C:/M/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/crtexe.c||272||__tmainCRTStartup 37 | C:/M/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/crtexe.c||193||mainCRTStartup 38 | ||4294967295||BaseThreadInitThunk 39 | ||4294967295||RtlUserThreadStart -------------------------------------------------------------------------------- /test/expected/windows.txt: -------------------------------------------------------------------------------- 1 | test\integration.cpp||23||trace() 2 | test\integration.cpp||33||www(std::basic_string, std::allocator >*, std::basic_string, std::allocator >&, std::vector, std::allocator > *, std::allocator, std::allocator > *> >*) 3 | test\integration.cpp||37||jjj(void(*(*))(float)) 4 | test\integration.cpp||45||iii(Foo::Bar) 5 | test\integration.cpp||55||hhh(int(*(*)[10])[20]) 6 | test\integration.cpp||59||ggg(int**) 7 | test\integration.cpp||63||fff(int(S::*)(float)) 8 | test\integration.cpp||68||eee(int(*(*(*(*)[10]))())(float)) 9 | test\integration.cpp||72||ddd(int(*(*)[10])()) 10 | test\integration.cpp||76||ccc(int(*)[5][6][7][8]) 11 | test\integration.cpp||80||bbb(int(*(&)[5])(float, int&)) 12 | test\integration.cpp||85||aaa(int(&)[5]) 13 | test\integration.cpp||94||foo(int) 14 | test\integration.cpp||98||foo(int) 15 | test\integration.cpp||98||foo(int) 16 | test\integration.cpp||98||foo(int) 17 | test\integration.cpp||98||foo(int) 18 | test\integration.cpp||98||foo(int) 19 | test\integration.cpp||98||foo(int) 20 | test\integration.cpp||98||foo(int) 21 | test\integration.cpp||98||foo(int) 22 | test\integration.cpp||98||foo(int) 23 | test\integration.cpp||98||foo(int) 24 | test\integration.cpp||106||foo(int, int) 25 | test\integration.cpp||106||foo(int, int, int) 26 | test\integration.cpp||106||foo(int, int, int, int) 27 | test\integration.cpp||106||foo(int, int, int, int, int) 28 | test\integration.cpp||106||foo(int, int, int, int, int, int) 29 | test\integration.cpp||106||foo(int, int, int, int, int, int, int) 30 | test\integration.cpp||106||foo(int, int, int, int, int, int, int, int) 31 | test\integration.cpp||106||foo(int, int, int, int, int, int, int, int, int) 32 | test\integration.cpp||106||foo(int, int, int, int, int, int, int, int, int, int) 33 | test\integration.cpp||112||function_two(int, float) 34 | test\integration.cpp||118||function_one(int) 35 | test\integration.cpp||125||main() 36 | D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl||79||invoke_main() 37 | D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl||288||__scrt_common_main_seh() 38 | D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl||331||__scrt_common_main() 39 | D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_main.cpp||17||mainCRTStartup(void*) 40 | ||4294967295||BaseThreadInitThunk 41 | ||4294967295||RtlUserThreadStart -------------------------------------------------------------------------------- /test/fetchcontent-integration/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | 3 | project(demo_project VERSION 0.0.1 LANGUAGES CXX) 4 | 5 | add_executable(main main.cpp) 6 | 7 | set(CPPTRACE_TAG "" CACHE STRING "cpptrace git tag") 8 | 9 | include(FetchContent) 10 | FetchContent_Declare( 11 | cpptrace 12 | GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git 13 | GIT_TAG ${CPPTRACE_TAG} 14 | ) 15 | FetchContent_MakeAvailable(cpptrace) 16 | target_link_libraries(main cpptrace::cpptrace) 17 | target_compile_features(main PRIVATE cxx_std_11) 18 | 19 | if(WIN32) 20 | add_custom_command( 21 | TARGET main POST_BUILD 22 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 23 | $ 24 | $ 25 | ) 26 | endif() 27 | -------------------------------------------------------------------------------- /test/fetchcontent-integration/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void trace() { 4 | cpptrace::generate_trace().print(); 5 | } 6 | 7 | void foo(int) { 8 | trace(); 9 | } 10 | 11 | int main() { 12 | foo(0); 13 | } 14 | -------------------------------------------------------------------------------- /test/findpackage-integration/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | 3 | project(demo_project VERSION 0.0.1 LANGUAGES CXX) 4 | 5 | add_executable(main main.cpp) 6 | 7 | find_package(cpptrace REQUIRED) 8 | target_link_libraries(main cpptrace::cpptrace) 9 | target_compile_features(main PRIVATE cxx_std_11) 10 | 11 | if(WIN32) 12 | add_custom_command( 13 | TARGET main POST_BUILD 14 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 15 | $ 16 | $ 17 | ) 18 | endif() 19 | -------------------------------------------------------------------------------- /test/findpackage-integration/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void trace() { 4 | cpptrace::generate_trace().print(); 5 | } 6 | 7 | void foo(int) { 8 | trace(); 9 | } 10 | 11 | int main() { 12 | foo(0); 13 | } 14 | -------------------------------------------------------------------------------- /test/integration.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | std::string normalize_filename(std::string name) { 11 | if(name.find('/') == 0 || (name.find(':') == 1 && std::isupper(name[0]))) { 12 | // build/integration if the file is really an object name resolved by libdl 13 | auto p = std::min({name.rfind("test/"), name.rfind("test\\"), name.rfind("build/integration")}); 14 | return p == std::string::npos ? name : name.substr(p); 15 | } else { 16 | return name; 17 | } 18 | } 19 | 20 | void custom_print(const cpptrace::stacktrace&); 21 | 22 | void trace() { 23 | auto trace = cpptrace::generate_trace(); 24 | if(trace.empty()) { 25 | std::cerr << "" << std::endl; 26 | } 27 | custom_print(trace); 28 | } 29 | 30 | // padding to avoid upsetting existing trace expected files 31 | 32 | void www(std::string&&, const std::string&, std::vector&&) { 33 | trace(); 34 | } 35 | 36 | void jjj(void(*const[5])(float)) { 37 | www(std::string{}, "", {}); 38 | } 39 | 40 | namespace Foo { 41 | struct Bar {}; 42 | } 43 | 44 | void iii(Foo::Bar) { 45 | jjj(nullptr); 46 | } 47 | 48 | struct S { 49 | int foo(float) const volatile && { 50 | return 1; 51 | } 52 | }; 53 | 54 | void hhh(int(*(*)[10])[20]) { 55 | iii(Foo::Bar{}); 56 | } 57 | 58 | void ggg(const int * const *) { 59 | hhh(nullptr); 60 | } 61 | 62 | void fff(int(S::*)(float) const volatile &&) { 63 | ggg(nullptr); 64 | } 65 | 66 | //void eee(int(*(*(*)[10])())(float)) { 67 | void eee(int(*(* const * volatile(*)[10])())(float)) { 68 | fff(&S::foo); 69 | } 70 | 71 | void ddd(int(*(*)[10])()) { 72 | eee(nullptr); 73 | } 74 | 75 | void ccc(int(*)[5][6][7][8]) { 76 | ddd(nullptr); 77 | } 78 | 79 | void bbb(int(*const (&)[5])(float, const int&)) { 80 | ccc(nullptr); 81 | } 82 | 83 | void aaa(int(&)[5]) { 84 | int(*const (arr)[5])(float, const int&) = {}; 85 | bbb(arr); 86 | } 87 | 88 | int x; 89 | 90 | void foo(int n) { 91 | if(n == 0) { 92 | x = 0; 93 | int arr[5]; 94 | aaa(arr); 95 | x = 0; 96 | } else { 97 | x = 0; 98 | foo(n - 1); 99 | x = 0; 100 | } 101 | } 102 | 103 | template 104 | void foo(int, Args... args) { 105 | x = 0; 106 | foo(args...); 107 | x = 0; 108 | } 109 | 110 | void function_two(int, float) { 111 | x = 0; 112 | foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 113 | x = 0; 114 | } 115 | 116 | void function_one(int) { 117 | x = 0; 118 | function_two(0, 0); 119 | x = 0; 120 | } 121 | 122 | int main() { 123 | x = 0; 124 | cpptrace::absorb_trace_exceptions(false); 125 | function_one(0); 126 | x = 0; 127 | } 128 | 129 | void custom_print(const cpptrace::stacktrace& trace) { 130 | for(const auto& frame : trace) { 131 | std::cout 132 | << normalize_filename(frame.filename) 133 | << "||" 134 | << frame.line.value() 135 | << "||" 136 | << frame.symbol 137 | << std::endl; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /test/jank/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:24.10 2 | 3 | RUN apt update 4 | RUN apt install -y curl git git-lfs zip build-essential entr libssl-dev libdouble-conversion-dev pkg-config ninja-build cmake zlib1g-dev libffi-dev clang libclang-dev llvm llvm-dev libzip-dev libbz2-dev doctest-dev gcc g++ libgc-dev 5 | RUN apt install -y vim gdb lldb file valgrind 6 | 7 | # Setup a user 8 | RUN groupadd cpptracegroup 9 | RUN useradd -m -g cpptracegroup -s /bin/bash cpptrace 10 | RUN mkdir /opt/work/ && chown cpptrace:cpptracegroup /opt/work/ 11 | USER cpptrace 12 | 13 | WORKDIR /opt/ 14 | WORKDIR /opt/work 15 | 16 | # RUN git clone --recurse-submodules https://github.com/jank-lang/jank.git 17 | # WORKDIR /opt/work/jank/compiler+runtime 18 | # RUN git checkout 5668b16 19 | # RUN CC=clang CXX=clang++ ./bin/configure -GNinja -DCMAKE_BUILD_TYPE=Debug 20 | # RUN ./bin/compile 21 | 22 | # WORKDIR /opt/work/jank 23 | # WORKDIR /opt/work/jank/compiler+runtime 24 | 25 | COPY ./entry.sh . 26 | ENTRYPOINT ["./entry.sh"] 27 | # ENTRYPOINT ["/bin/bash"] 28 | 29 | # podman build -t cpptrace-container . 30 | # podman run --user=cpptrace --cap-drop=all --network none -it cpptrace-container 31 | -------------------------------------------------------------------------------- /test/jank/Makefile: -------------------------------------------------------------------------------- 1 | default: help 2 | 3 | # The general philosophy and functionality of this makefile is shamelessly stolen from compiler explorer 4 | 5 | help: # with thanks to Ben Rady 6 | @grep -E '^[0-9a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' 7 | 8 | PODMAN=podman 9 | 10 | data/.built: Dockerfile entry.sh 11 | $(PODMAN) build -t cpptrace-container . 12 | mkdir -p data 13 | touch data/.built 14 | 15 | .PHONY: build 16 | build: data/.built ## build the container 17 | 18 | .PHONY: run 19 | run: data/.built data/.cloned ## run the container 20 | rm -rfv data/jank/compiler+runtime/third-party/cpptrace/include 21 | rm -rfv data/jank/compiler+runtime/third-party/cpptrace/src 22 | cp -avp ../../include data/jank/compiler+runtime/third-party/cpptrace/include 23 | cp -avp ../../src data/jank/compiler+runtime/third-party/cpptrace/src 24 | cp -v ../../CMakeLists.txt data/jank/compiler+runtime/third-party/cpptrace/ 25 | $(PODMAN) run \ 26 | --user=cpptrace \ 27 | --cap-drop=all \ 28 | -v $(realpath data/jank):/opt/work/jank:rw \ 29 | --tmpfs /opt/work/jank/compiler+runtime/build:rw \ 30 | -it \ 31 | cpptrace-container 32 | 33 | .PHONY: clone 34 | clone: data/.cloned ## clone code 35 | 36 | data/.cloned: 37 | mkdir -p data 38 | cd data && git clone --recurse-submodules https://github.com/jank-lang/jank.git && cd jank && git checkout 5668b16 39 | touch data/.cloned 40 | 41 | .PHONY: clean 42 | clean: ## clean 43 | rm -rf data 44 | -------------------------------------------------------------------------------- /test/jank/entry.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "------------------------------------ BUILDING ------------------------------------" 4 | 5 | cd jank/compiler+runtime 6 | 7 | CC=clang CXX=clang++ ./bin/configure -GNinja -DCMAKE_BUILD_TYPE=Debug && ./bin/compile 8 | 9 | cat > test.jank << EOF 10 | (ns test) 11 | 12 | (defn foo [] 13 | (throw "meow")) 14 | 15 | (defn -main [& args] 16 | (foo)) 17 | (-main) 18 | EOF 19 | 20 | ./build/jank run test.jank 21 | # gdb --args ./build/jank run test.jank 22 | 23 | exec /bin/bash 24 | -------------------------------------------------------------------------------- /test/signal_demo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | void trace() { 15 | *(volatile char*)0 = 2; 16 | } 17 | 18 | void bar() { 19 | trace(); 20 | } 21 | 22 | void foo() { 23 | bar(); 24 | } 25 | 26 | struct pipe_t { 27 | union { 28 | struct { 29 | int read_end; 30 | int write_end; 31 | }; 32 | int data[2]; 33 | }; 34 | }; 35 | static_assert(sizeof(pipe_t) == 2 * sizeof(int), "Unexpected struct packing"); 36 | 37 | void handler(int signo, siginfo_t* info, void* context) { 38 | const char* message = "SIGSEGV occurred:\n"; 39 | write(STDERR_FILENO, message, strlen(message)); 40 | cpptrace::frame_ptr buffer[100]; 41 | std::size_t count = cpptrace::safe_generate_raw_trace(buffer, 100); 42 | pipe_t input_pipe; 43 | pipe(input_pipe.data); 44 | const pid_t pid = fork(); 45 | if(pid == -1) { 46 | const char* fork_failure_message = "fork() failed\n"; 47 | write(STDERR_FILENO, fork_failure_message, strlen(fork_failure_message)); 48 | _exit(1); 49 | } 50 | if(pid == 0) { // child 51 | dup2(input_pipe.read_end, STDIN_FILENO); 52 | close(input_pipe.read_end); 53 | close(input_pipe.write_end); 54 | execl( 55 | "signal_tracer", 56 | "signal_tracer", 57 | nullptr 58 | ); 59 | const char* exec_failure_message = "exec(signal_tracer) failed: Make sure the signal_tracer executable is in " 60 | "the current working directory and the binary's permissions are correct.\n"; 61 | write(STDERR_FILENO, exec_failure_message, strlen(exec_failure_message)); 62 | _exit(1); 63 | } 64 | for(std::size_t i = 0; i < count; i++) { 65 | cpptrace::safe_object_frame frame; 66 | cpptrace::get_safe_object_frame(buffer[i], &frame); 67 | write(input_pipe.write_end, &frame, sizeof(frame)); 68 | } 69 | close(input_pipe.read_end); 70 | close(input_pipe.write_end); 71 | waitpid(pid, nullptr, 0); 72 | _exit(1); 73 | } 74 | 75 | void warmup_cpptrace() { 76 | cpptrace::frame_ptr buffer[10]; 77 | std::size_t count = cpptrace::safe_generate_raw_trace(buffer, 10); 78 | cpptrace::safe_object_frame frame; 79 | cpptrace::get_safe_object_frame(buffer[0], &frame); 80 | } 81 | 82 | int main() { 83 | cpptrace::absorb_trace_exceptions(false); 84 | cpptrace::register_terminate_handler(); 85 | warmup_cpptrace(); 86 | 87 | struct sigaction action = { 0 }; 88 | action.sa_flags = 0; 89 | action.sa_sigaction = &handler; 90 | if (sigaction(SIGSEGV, &action, NULL) == -1) { 91 | perror("sigaction"); 92 | exit(EXIT_FAILURE); 93 | } 94 | 95 | foo(); 96 | } 97 | -------------------------------------------------------------------------------- /test/signal_tracer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | int main() { 9 | cpptrace::object_trace trace; 10 | while(true) { 11 | cpptrace::safe_object_frame frame; 12 | std::size_t res = fread(&frame, sizeof(frame), 1, stdin); 13 | if(res == 0) { 14 | break; 15 | } else if(res != 1) { 16 | std::cerr<<"Oops, size mismatch "<:-gdwarf-4>") 14 | #target_compile_options(speedtest PRIVATE "$<$:-gdwarf-4>") 15 | #target_compile_options(googletest INTERFACE "$<$:-gdwarf-4>") 16 | endif() 17 | endif() 18 | if(SPEEDTEST_DWARF5) 19 | check_cxx_compiler_flag("-gdwarf-5" HAS_DWARF5) 20 | if(HAS_DWARF5) 21 | add_compile_options("$<$:-gdwarf-5>") 22 | #target_compile_options(speedtest PRIVATE "$<$:-gdwarf-4>") 23 | #target_compile_options(googletest INTERFACE "$<$:-gdwarf-4>") 24 | endif() 25 | endif() 26 | 27 | include(FetchContent) 28 | FetchContent_Declare( 29 | googletest 30 | DOWNLOAD_EXTRACT_TIMESTAMP On 31 | URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip 32 | ) 33 | # For Windows: Prevent overriding the parent project's compiler/linker settings 34 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 35 | FetchContent_MakeAvailable(googletest) 36 | 37 | set(cpptrace_DIR "../../build/foo/lib/cmake/cpptrace") 38 | set(libdwarf_DIR "../../build/foo/lib/cmake/libdwarf") 39 | set(zstd_DIR "../../build/foo/lib/cmake/zstd") 40 | find_package(cpptrace REQUIRED) 41 | 42 | add_executable(speedtest speedtest.cpp) 43 | target_compile_features(speedtest PRIVATE cxx_std_11) 44 | target_link_libraries( 45 | speedtest 46 | PRIVATE 47 | GTest::gtest_main 48 | cpptrace::cpptrace 49 | ) 50 | 51 | if(WIN32) 52 | add_custom_command( 53 | TARGET speedtest POST_BUILD 54 | COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ 55 | COMMAND_EXPAND_LISTS 56 | ) 57 | endif() 58 | -------------------------------------------------------------------------------- /test/speedtest/speedtest.cpp: -------------------------------------------------------------------------------- 1 | // https://github.com/jeremy-rifkin/libassert/issues/43 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | 9 | TEST(TraceTest, trace_test) { 10 | ASSERT_THROW((cpptrace::generate_trace().print(), false), std::logic_error); 11 | } 12 | -------------------------------------------------------------------------------- /test/unit/internals/lru_cache.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "utils/lru_cache.hpp" 4 | 5 | using cpptrace::detail::lru_cache; 6 | using cpptrace::detail::nullopt; 7 | 8 | namespace { 9 | 10 | TEST(LruCacheTest, DefaultConstructor) { 11 | lru_cache cache; 12 | EXPECT_EQ(cache.size(), 0); 13 | } 14 | 15 | TEST(LruCacheTest, MaybeGet) { 16 | lru_cache cache; 17 | auto result = cache.maybe_get(42); 18 | EXPECT_FALSE(result.has_value()); 19 | } 20 | 21 | TEST(LruCacheTest, InsertAndGet) { 22 | lru_cache cache; 23 | cache.insert(42, 50); 24 | auto result = cache.maybe_get(42); 25 | ASSERT_TRUE(result.has_value()); 26 | EXPECT_EQ(result.unwrap(), 50); 27 | } 28 | 29 | TEST(LruCacheTest, ConstGet) { 30 | lru_cache cache; 31 | cache.insert(42, 50); 32 | const lru_cache& cache_ref = cache; 33 | auto result = cache_ref.maybe_get(42); 34 | ASSERT_TRUE(result.has_value()); 35 | EXPECT_EQ(result.unwrap(), 50); 36 | } 37 | 38 | TEST(LruCacheTest, Set) { 39 | lru_cache cache; 40 | cache.set(42, 50); 41 | auto result = cache.maybe_get(42); 42 | ASSERT_TRUE(result.has_value()); 43 | EXPECT_EQ(result.unwrap(), 50); 44 | cache.set(42, 60); 45 | auto result2 = cache.maybe_get(42); 46 | ASSERT_TRUE(result2.has_value()); 47 | EXPECT_EQ(result2.unwrap(), 60); 48 | } 49 | 50 | TEST(LruCacheTest, NoMaxSize) { 51 | lru_cache cache; 52 | for(int i = 0; i < 1000; i++) { 53 | cache.insert(i, i + 50); 54 | } 55 | EXPECT_EQ(cache.size(), 1000); 56 | for(int i = 0; i < 1000; i++) { 57 | EXPECT_EQ(cache.maybe_get(i).unwrap(), i + 50); 58 | } 59 | } 60 | 61 | TEST(LruCacheTest, MaxSize) { 62 | lru_cache cache(20); 63 | for(int i = 0; i < 1000; i++) { 64 | cache.insert(i, i + 50); 65 | } 66 | EXPECT_EQ(cache.size(), 20); 67 | for(int i = 0; i < 1000 - 20; i++) { 68 | EXPECT_FALSE(cache.maybe_get(i).has_value()); 69 | } 70 | for(int i = 1000 - 20; i < 1000; i++) { 71 | EXPECT_EQ(cache.maybe_get(i).unwrap(), i + 50); 72 | } 73 | } 74 | 75 | TEST(LruCacheTest, SizeAfterInserts) { 76 | lru_cache cache; 77 | for(int i = 0; i < 1000; i++) { 78 | cache.insert(i, i + 50); 79 | } 80 | EXPECT_EQ(cache.size(), 1000); 81 | cache.set_max_size(20); 82 | EXPECT_EQ(cache.size(), 20); 83 | for(int i = 0; i < 1000 - 20; i++) { 84 | EXPECT_FALSE(cache.maybe_get(i).has_value()); 85 | } 86 | for(int i = 1000 - 20; i < 1000; i++) { 87 | EXPECT_EQ(cache.maybe_get(i).unwrap(), i + 50); 88 | } 89 | } 90 | 91 | TEST(LruCacheTest, Touch) { 92 | lru_cache cache(20); 93 | for(int i = 0; i < 1000; i++) { 94 | cache.maybe_touch(0); 95 | cache.insert(i, i + 50); 96 | } 97 | EXPECT_EQ(cache.size(), 20); 98 | for(int i = 1000 - 19; i < 1000; i++) { 99 | EXPECT_EQ(cache.maybe_get(i).unwrap(), i + 50); 100 | } 101 | EXPECT_EQ(cache.maybe_get(0).unwrap(), 50); 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /test/unit/internals/result.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "utils/result.hpp" 4 | 5 | using cpptrace::detail::Result; 6 | 7 | namespace { 8 | 9 | // A simple custom error type that behaves like a standard exception. 10 | struct error { 11 | int x; 12 | const char* what() const { 13 | return "error..."; 14 | } 15 | }; 16 | 17 | class ResultFixture : public testing::Test { 18 | public: 19 | ResultFixture() { 20 | cpptrace::absorb_trace_exceptions(true); 21 | } 22 | 23 | ~ResultFixture() override { 24 | cpptrace::absorb_trace_exceptions(false); 25 | } 26 | }; 27 | 28 | TEST_F(ResultFixture, ConstructWithValueRValue) { 29 | cpptrace::detail::Result result("test"); 30 | EXPECT_TRUE(result.has_value()); 31 | EXPECT_FALSE(result.is_error()); 32 | EXPECT_TRUE(static_cast(result)); 33 | 34 | EXPECT_EQ(result.unwrap_value(), "test"); 35 | EXPECT_FALSE(result.error().has_value()); 36 | } 37 | 38 | TEST_F(ResultFixture, ConstructWithValueLValue) { 39 | std::string s = "test"; 40 | cpptrace::detail::Result result(s); 41 | 42 | EXPECT_TRUE(result.has_value()); 43 | EXPECT_FALSE(result.is_error()); 44 | EXPECT_EQ(result.unwrap_value(), "test"); 45 | 46 | s = "x"; 47 | EXPECT_EQ(result.unwrap_value(), "test"); 48 | 49 | cpptrace::detail::Result r2(s); 50 | EXPECT_EQ(r2.unwrap_value(), "x"); 51 | s = "y"; 52 | EXPECT_EQ(r2.unwrap_value(), "y"); 53 | } 54 | 55 | TEST_F(ResultFixture, ConstructWithErrorRValue) { 56 | cpptrace::detail::Result result(error{1}); 57 | EXPECT_FALSE(result.has_value()); 58 | EXPECT_TRUE(result.is_error()); 59 | EXPECT_FALSE(static_cast(result)); 60 | 61 | EXPECT_EQ(result.unwrap_error().x, 1); 62 | 63 | // Check that value() returns nullopt in this scenario 64 | EXPECT_FALSE(result.value().has_value()); 65 | } 66 | 67 | TEST_F(ResultFixture, ConstructWithErrorLValue) { 68 | error e{1}; 69 | cpptrace::detail::Result result(e); 70 | 71 | EXPECT_FALSE(result.has_value()); 72 | EXPECT_TRUE(result.is_error()); 73 | EXPECT_EQ(result.unwrap_error().x, 1); 74 | } 75 | 76 | TEST_F(ResultFixture, MoveConstructorValue) { 77 | cpptrace::detail::Result original(std::string("move")); 78 | cpptrace::detail::Result moved(std::move(original)); 79 | EXPECT_TRUE(moved.has_value()); 80 | EXPECT_EQ(moved.unwrap_value(), "move"); 81 | EXPECT_TRUE(original.has_value()); 82 | 83 | std::string s = "test"; 84 | cpptrace::detail::Result r1(s); 85 | cpptrace::detail::Result r2(std::move(r1)); 86 | EXPECT_TRUE(r2.has_value()); 87 | EXPECT_EQ(r2.unwrap_value(), "test"); 88 | s = "foo"; 89 | EXPECT_EQ(r2.unwrap_value(), "foo"); 90 | EXPECT_TRUE(r2.has_value()); 91 | } 92 | 93 | TEST_F(ResultFixture, MoveConstructorError) { 94 | cpptrace::detail::Result original(error{1}); 95 | cpptrace::detail::Result moved(std::move(original)); 96 | 97 | EXPECT_TRUE(moved.is_error()); 98 | EXPECT_EQ(moved.unwrap_error().x, 1); 99 | EXPECT_TRUE(original.is_error()); 100 | } 101 | 102 | TEST_F(ResultFixture, ValueOr) { 103 | { 104 | cpptrace::detail::Result res_with_value(42); 105 | EXPECT_EQ(res_with_value.value_or(-1), 42); 106 | EXPECT_EQ(std::move(res_with_value).value_or(-1), 42); 107 | } 108 | { 109 | cpptrace::detail::Result res_with_error(error{}); 110 | EXPECT_EQ(res_with_error.value_or(-1), -1); 111 | EXPECT_EQ(std::move(res_with_error).value_or(-1), -1); 112 | } 113 | { 114 | int x = 2; 115 | int y = 3; 116 | cpptrace::detail::Result res_with_value(x); 117 | EXPECT_EQ(res_with_value.value_or(y), 2); 118 | EXPECT_EQ(std::move(res_with_value).value_or(y), 2); 119 | } 120 | { 121 | int x = 2; 122 | cpptrace::detail::Result res_with_error(error{}); 123 | EXPECT_EQ(res_with_error.value_or(x), 2); 124 | EXPECT_EQ(&res_with_error.value_or(x), &x); 125 | EXPECT_EQ(std::move(res_with_error).value_or(x), 2); 126 | } 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /test/unit/internals/span.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "utils/span.hpp" 9 | 10 | using cpptrace::detail::span; 11 | using cpptrace::detail::make_span; 12 | using cpptrace::detail::make_bspan; 13 | 14 | namespace { 15 | 16 | TEST(SpanTest, Basic) { 17 | std::array arr{1, 2, 3, 4, 5}; 18 | // thanks microsoft for using horrible non-standard iterators, otherwise this would test with begin()/end() 19 | auto span = make_span(arr.data(), arr.data() + arr.size()); 20 | EXPECT_EQ(span.data(), arr.data()); 21 | EXPECT_EQ(span.size(), 5); 22 | EXPECT_EQ(span.data()[0], 1); 23 | EXPECT_EQ(span.data()[1], 2); 24 | EXPECT_EQ(span.data()[2], 3); 25 | EXPECT_EQ(span.data()[3], 4); 26 | EXPECT_EQ(span.data()[4], 5); 27 | } 28 | 29 | TEST(SpanTest, PtrSize) { 30 | std::array arr{1, 2, 3, 4, 5}; 31 | auto span = make_span(arr.data(), arr.size()); 32 | EXPECT_EQ(span.data(), arr.data()); 33 | EXPECT_EQ(span.size(), 5); 34 | EXPECT_EQ(span.data()[0], 1); 35 | EXPECT_EQ(span.data()[1], 2); 36 | EXPECT_EQ(span.data()[2], 3); 37 | EXPECT_EQ(span.data()[3], 4); 38 | EXPECT_EQ(span.data()[4], 5); 39 | } 40 | 41 | TEST(SpanTest, Bspan) { 42 | struct S { 43 | std::array data; 44 | }; 45 | S s{{'a', 'b', 'c', 'd'}}; 46 | auto span = make_bspan(s); 47 | EXPECT_EQ(span.data(), s.data.data()); 48 | EXPECT_EQ(span.size(), 4); 49 | EXPECT_EQ(span.data()[0], 'a'); 50 | EXPECT_EQ(span.data()[1], 'b'); 51 | EXPECT_EQ(span.data()[2], 'c'); 52 | EXPECT_EQ(span.data()[3], 'd'); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /test/unit/internals/string_utils.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "utils/utils.hpp" 7 | 8 | using testing::ElementsAre; 9 | using cpptrace::detail::split; 10 | using cpptrace::detail::join; 11 | using cpptrace::detail::trim; 12 | using cpptrace::detail::starts_with; 13 | 14 | namespace { 15 | 16 | TEST(SplitTest, SplitBySingleDelimiter) { 17 | std::string input = "hello,world"; 18 | auto tokens = split(input, ","); 19 | EXPECT_THAT(tokens, ElementsAre("hello", "world")); 20 | } 21 | 22 | TEST(SplitTest, SplitByMultipleDelimiters) { 23 | std::string input = "hello,world;test"; 24 | auto tokens = split(input, ",;"); 25 | EXPECT_THAT(tokens, ElementsAre("hello", "world", "test")); 26 | } 27 | 28 | TEST(SplitTest, HandlesNoDelimiterFound) { 29 | std::string input = "nodellimitershere"; 30 | auto tokens = split(input, ", "); 31 | EXPECT_THAT(tokens, ElementsAre("nodellimitershere")); 32 | } 33 | 34 | TEST(SplitTest, HandlesEmptyString) { 35 | std::string input = ""; 36 | auto tokens = split(input, ","); 37 | EXPECT_THAT(tokens, ElementsAre("")); 38 | } 39 | 40 | TEST(SplitTest, HandlesConsecutiveDelimiters) { 41 | std::string input = "one,,two,,,three"; 42 | auto tokens = split(input, ","); 43 | EXPECT_THAT(tokens, ElementsAre("one", "", "two", "", "", "three")); 44 | } 45 | 46 | TEST(SplitTest, HandlesTrailingDelimiter) { 47 | std::string input = "abc,"; 48 | auto tokens = split(input, ","); 49 | EXPECT_THAT(tokens, ElementsAre("abc", "")); 50 | } 51 | 52 | TEST(SplitTest, HandlesLeadingDelimiter) { 53 | std::string input = ",abc"; 54 | auto tokens = split(input, ","); 55 | EXPECT_THAT(tokens, ElementsAre("", "abc")); 56 | } 57 | 58 | TEST(JoinTest, EmptyContainer) { 59 | std::vector vec; 60 | EXPECT_EQ(join(vec, ","), ""); 61 | } 62 | 63 | TEST(JoinTest, SingleElements) { 64 | std::vector vec = {"one"}; 65 | EXPECT_EQ(join(vec, ","), "one"); 66 | } 67 | 68 | TEST(JoinTest, MultipleElements) { 69 | std::vector vec = {"one", "two", "three"}; 70 | EXPECT_EQ(join(vec, ","), "one,two,three"); 71 | } 72 | 73 | TEST(TrimTest, Basic) { 74 | EXPECT_EQ(trim(""), ""); 75 | EXPECT_EQ(trim("test"), "test"); 76 | EXPECT_EQ(trim(" test "), "test"); 77 | EXPECT_EQ(trim(" test\n "), "test"); 78 | EXPECT_EQ(trim("\t test\n "), "test"); 79 | } 80 | 81 | TEST(StartsWith, Basic) { 82 | EXPECT_TRUE(starts_with("", "")); 83 | EXPECT_TRUE(starts_with("abc", "")); 84 | EXPECT_FALSE(starts_with("", "abc")); 85 | EXPECT_FALSE(starts_with("ab", "abc")); 86 | EXPECT_TRUE(starts_with("test", "test")); 87 | EXPECT_TRUE(starts_with("hello_world", "hello")); 88 | EXPECT_FALSE(starts_with("hello_world", "world")); 89 | EXPECT_FALSE(starts_with("abcd", "abce")); 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /test/unit/internals/string_view.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "utils/string_view.hpp" 9 | 10 | using cpptrace::detail::string_view; 11 | using cpptrace::detail::cstring_view; 12 | 13 | namespace { 14 | 15 | TEST(StringViewTest, Basic) { 16 | string_view sv = "foo"; 17 | EXPECT_EQ(sv.size(), 3); 18 | EXPECT_EQ(sv.data(), std::string("foo")); 19 | EXPECT_EQ(sv[0], 'f'); 20 | EXPECT_EQ(sv[1], 'o'); 21 | EXPECT_EQ(sv.find_last_of("f"), 0); 22 | EXPECT_EQ(sv.find_last_of("o"), 2); 23 | EXPECT_EQ(sv.find_last_of("asfd"), 0); 24 | EXPECT_EQ(sv.find_last_of("asod"), 2); 25 | EXPECT_EQ(sv.find_last_of("bar"), sv.npos); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /test/unit/lib/nullable.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using cpptrace::nullable; 9 | 10 | namespace { 11 | 12 | TEST(NullableTest, Basic) { 13 | nullable a{12}; 14 | EXPECT_EQ(a.value(), 12); 15 | EXPECT_EQ(a.raw_value, 12); 16 | nullable b = 20; 17 | EXPECT_EQ(b.value(), 20); 18 | } 19 | 20 | TEST(NullableTest, Null) { 21 | auto a = nullable::null(); 22 | EXPECT_FALSE(a.has_value()); 23 | EXPECT_EQ(a.raw_value, (std::numeric_limits::max)()); 24 | nullable b; 25 | EXPECT_FALSE(b.has_value()); 26 | EXPECT_EQ(b.raw_value, nullable::null_value()); 27 | } 28 | 29 | TEST(NullableTest, Assignment) { 30 | nullable a; 31 | a = 12; 32 | EXPECT_EQ(a.value(), 12); 33 | nullable b = 20; 34 | a = b; 35 | EXPECT_EQ(a.value(), 20); 36 | } 37 | 38 | TEST(NullableTest, Reset) { 39 | nullable a{12}; 40 | a.reset(); 41 | EXPECT_FALSE(a.has_value()); 42 | } 43 | 44 | TEST(NullableTest, ValueOr) { 45 | auto a = nullable::null(); 46 | EXPECT_EQ(a.value_or(20), 20); 47 | } 48 | 49 | TEST(NullableTest, Comparison) { 50 | EXPECT_EQ(nullable{12}, nullable{12}); 51 | EXPECT_NE(nullable{12}, nullable{20}); 52 | EXPECT_NE(nullable{12}, nullable::null()); 53 | EXPECT_EQ(nullable::null(), nullable::null()); 54 | } 55 | 56 | TEST(NullableTest, Swap) { 57 | auto a = nullable::null(); 58 | nullable b = 12; 59 | EXPECT_FALSE(a.has_value()); 60 | EXPECT_EQ(b.value(), 12); 61 | a.swap(b); 62 | EXPECT_FALSE(b.has_value()); 63 | EXPECT_EQ(a.value(), 12); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /test/unit/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc, char** argv) { 5 | testing::InitGoogleTest(&argc, argv); 6 | cpptrace::absorb_trace_exceptions(false); 7 | return RUN_ALL_TESTS(); 8 | } 9 | -------------------------------------------------------------------------------- /test/unit/tracing/common.hpp: -------------------------------------------------------------------------------- 1 | #ifndef TRACING_COMMON_HPP 2 | #define TRACING_COMMON_HPP 3 | 4 | template using void_t = void; 5 | 6 | #ifndef CPPTRACE_BUILD_NO_SYMBOLS 7 | #define EXPECT_FILE(A, B) EXPECT_THAT((A), testing::EndsWith(B)) 8 | #define EXPECT_LINE(A, B) EXPECT_EQ((A), (B)) 9 | #else 10 | #define EXPECT_FILE(A, B) (void_t)0 11 | #define EXPECT_LINE(A, B) (void_t)0 12 | #endif 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /test/unit/tracing/object_trace.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include "common.hpp" 12 | 13 | using namespace std::literals; 14 | 15 | 16 | 17 | TEST(ObjectTrace, Empty) { 18 | cpptrace::object_trace empty; 19 | EXPECT_TRUE(empty.empty()); 20 | EXPECT_EQ(empty.resolve().to_string(), "Stack trace (most recent call first):\n\n"); 21 | } 22 | 23 | 24 | 25 | CPPTRACE_FORCE_NO_INLINE void object_basic() { 26 | static volatile int lto_guard; lto_guard = lto_guard + 1; 27 | auto trace = cpptrace::generate_object_trace(); 28 | EXPECT_FALSE(trace.empty()); 29 | EXPECT_NE(trace.frames[0].raw_address, 0); 30 | EXPECT_NE(trace.frames[0].object_address, 0); 31 | EXPECT_THAT(trace.frames[0].object_path, testing::HasSubstr("unittest")); 32 | } 33 | 34 | TEST(ObjectTrace, Basic) { 35 | object_basic(); 36 | } 37 | 38 | 39 | 40 | CPPTRACE_FORCE_NO_INLINE void object_basic_resolution() { 41 | static volatile int lto_guard; lto_guard = lto_guard + 1; 42 | auto line = __LINE__ + 1; 43 | auto trace = cpptrace::generate_object_trace().resolve(); 44 | ASSERT_GE(trace.frames.size(), 1); 45 | EXPECT_FILE(trace.frames[0].filename, "object_trace.cpp"); 46 | EXPECT_EQ(trace.frames[0].line.value(), line); 47 | EXPECT_THAT(trace.frames[0].symbol, testing::HasSubstr("object_basic_resolution")); 48 | } 49 | 50 | TEST(ObjectTrace, BasicResolution) { 51 | object_basic(); 52 | } 53 | 54 | 55 | // TODO: dbghelp uses raw address, not object 56 | #ifndef _MSC_VER 57 | CPPTRACE_FORCE_NO_INLINE int object_resolve_3(std::vector& line_numbers) { 58 | static volatile int lto_guard; lto_guard = lto_guard + 1; 59 | line_numbers.insert(line_numbers.begin(), __LINE__ + 1); 60 | auto dummy = cpptrace::generate_trace(); 61 | auto dummy_otrace = cpptrace::generate_object_trace(); 62 | cpptrace::object_trace otrace; 63 | otrace.frames.push_back( 64 | cpptrace::object_frame{0, dummy.frames[0].object_address, dummy_otrace.frames[0].object_path} 65 | ); 66 | otrace.frames.push_back( 67 | cpptrace::object_frame{0, dummy.frames[1].object_address, dummy_otrace.frames[1].object_path} 68 | ); 69 | otrace.frames.push_back( 70 | cpptrace::object_frame{0, dummy.frames[2].object_address, dummy_otrace.frames[2].object_path} 71 | ); 72 | otrace.frames.push_back( 73 | cpptrace::object_frame{0, dummy.frames[3].object_address, dummy_otrace.frames[3].object_path} 74 | ); 75 | auto trace = otrace.resolve(); 76 | if(trace.frames.size() < 4) { 77 | ADD_FAILURE() << "trace.frames.size() >= 4"; 78 | return 2; 79 | } 80 | int i = 0; 81 | EXPECT_FILE(trace.frames[i].filename, "object_trace.cpp"); 82 | EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]); 83 | EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("object_resolve_3")); 84 | i++; 85 | EXPECT_FILE(trace.frames[i].filename, "object_trace.cpp"); 86 | EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]); 87 | EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("object_resolve_2")); 88 | i++; 89 | EXPECT_FILE(trace.frames[i].filename, "object_trace.cpp"); 90 | EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]); 91 | EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("object_resolve_1")); 92 | i++; 93 | EXPECT_FILE(trace.frames[i].filename, "object_trace.cpp"); 94 | EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]); 95 | EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("ObjectTrace_Resolution_Test::TestBody")); 96 | return 2; 97 | } 98 | 99 | // NOTE: returning something and then return stacktrace_multi_3(line_numbers) * rand(); is done to prevent TCO even 100 | // under LTO https://github.com/jeremy-rifkin/cpptrace/issues/179#issuecomment-2467302052 101 | CPPTRACE_FORCE_NO_INLINE int object_resolve_2(std::vector& line_numbers) { 102 | static volatile int lto_guard; lto_guard = lto_guard + 1; 103 | line_numbers.insert(line_numbers.begin(), __LINE__ + 1); 104 | return object_resolve_3(line_numbers) * rand(); 105 | } 106 | 107 | CPPTRACE_FORCE_NO_INLINE int object_resolve_1(std::vector& line_numbers) { 108 | static volatile int lto_guard; lto_guard = lto_guard + 1; 109 | line_numbers.insert(line_numbers.begin(), __LINE__ + 1); 110 | return object_resolve_2(line_numbers) * rand(); 111 | } 112 | 113 | TEST(ObjectTrace, Resolution) { 114 | std::vector line_numbers; 115 | line_numbers.insert(line_numbers.begin(), __LINE__ + 1); 116 | object_resolve_1(line_numbers); 117 | } 118 | #endif 119 | -------------------------------------------------------------------------------- /test/unit/tracing/traced_exception.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include "common.hpp" 12 | 13 | using namespace std::literals; 14 | 15 | 16 | static volatile int truthy = 2; 17 | 18 | // NOTE: returning something and then return stacktrace_multi_3(line_numbers) * rand(); is done to prevent TCO even 19 | // under LTO https://github.com/jeremy-rifkin/cpptrace/issues/179#issuecomment-2467302052 20 | CPPTRACE_FORCE_NO_INLINE int stacktrace_traced_object_3(std::vector& line_numbers) { 21 | static volatile int lto_guard; lto_guard = lto_guard + 1; 22 | if(truthy) { // due to a MSVC warning about unreachable code 23 | line_numbers.insert(line_numbers.begin(), __LINE__ + 1); 24 | throw cpptrace::runtime_error("foobar"); 25 | } 26 | return 2; 27 | } 28 | 29 | CPPTRACE_FORCE_NO_INLINE int stacktrace_traced_object_2(std::vector& line_numbers) { 30 | static volatile int lto_guard; lto_guard = lto_guard + 1; 31 | line_numbers.insert(line_numbers.begin(), __LINE__ + 1); 32 | return stacktrace_traced_object_3(line_numbers) * rand(); 33 | } 34 | 35 | CPPTRACE_FORCE_NO_INLINE int stacktrace_traced_object_1(std::vector& line_numbers) { 36 | static volatile int lto_guard; lto_guard = lto_guard + 1; 37 | line_numbers.insert(line_numbers.begin(), __LINE__ + 1); 38 | return stacktrace_traced_object_2(line_numbers) * rand(); 39 | } 40 | 41 | TEST(TracedException, Basic) { 42 | std::vector line_numbers; 43 | try { 44 | line_numbers.insert(line_numbers.begin(), __LINE__ + 1); 45 | stacktrace_traced_object_1(line_numbers); 46 | } catch(cpptrace::exception& e) { 47 | EXPECT_EQ(e.message(), "foobar"sv); 48 | const auto& trace = e.trace(); 49 | ASSERT_GE(trace.frames.size(), 4); 50 | size_t i = 0; 51 | ASSERT_LT(i, trace.frames.size()); 52 | ASSERT_LT(i, line_numbers.size()); 53 | EXPECT_FILE(trace.frames[i].filename, "traced_exception.cpp"); 54 | EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]); 55 | EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_traced_object_3")); 56 | i++; 57 | ASSERT_LT(i, trace.frames.size()); 58 | ASSERT_LT(i, line_numbers.size()); 59 | EXPECT_FILE(trace.frames[i].filename, "traced_exception.cpp"); 60 | EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]); 61 | EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_traced_object_2")); 62 | i++; 63 | ASSERT_LT(i, trace.frames.size()); 64 | ASSERT_LT(i, line_numbers.size()); 65 | EXPECT_FILE(trace.frames[i].filename, "traced_exception.cpp"); 66 | EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]); 67 | EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_traced_object_1")); 68 | i++; 69 | ASSERT_LT(i, trace.frames.size()); 70 | ASSERT_LT(i, line_numbers.size()); 71 | EXPECT_FILE(trace.frames[i].filename, "traced_exception.cpp"); 72 | EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]); 73 | EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("TracedException_Basic_Test::TestBody")); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tools/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(FetchContent) 2 | 3 | FetchContent_Declare( 4 | lyra 5 | GIT_SHALLOW TRUE 6 | GIT_REPOSITORY "https://github.com/bfgroup/Lyra.git" 7 | GIT_TAG "ee3c076fa6b9d64c9d249a21f5b9b5a8dae92cd8" 8 | ) 9 | FetchContent_MakeAvailable(lyra) 10 | 11 | FetchContent_Declare( 12 | fmt 13 | GIT_SHALLOW TRUE 14 | GIT_REPOSITORY "https://github.com/fmtlib/fmt.git" 15 | GIT_TAG "e69e5f977d458f2650bb346dadf2ad30c5320281" # v10.2.1 16 | ) 17 | FetchContent_MakeAvailable(fmt) 18 | 19 | set( 20 | COMMON_LIBS 21 | cpptrace::cpptrace 22 | bfg::lyra 23 | fmt::fmt 24 | ) 25 | 26 | function(binary TARGET) 27 | cmake_parse_arguments(BINARY "" "" "SOURCES;LIBS;FLAGS;DEFS" ${ARGN}) 28 | add_executable(${TARGET} main.cpp) 29 | if(BINARY_SOURCES) 30 | add_library(${TARGET}_OBJ OBJECT ${BINARY_SOURCES}) 31 | target_link_libraries(${TARGET}_OBJ PUBLIC ${COMMON_LIBS}) 32 | endif() 33 | target_link_libraries(${TARGET} PUBLIC ${COMMON_LIBS}) 34 | target_link_libraries(${TARGET} PUBLIC ${BINARY_LIBS}) 35 | target_compile_definitions(${TARGET} PUBLIC ${BINARY_DEFS}) 36 | target_compile_options(${TARGET} PUBLIC ${BINARY_FLAGS} ${debug} ${warning_options}) 37 | target_include_directories(${TARGET} PUBLIC "${PROJECT_SOURCE_DIR}/src") 38 | target_compile_features(${TARGET} PRIVATE cxx_std_20) 39 | set_target_properties( 40 | ${TARGET} 41 | PROPERTIES 42 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" 43 | ) 44 | endfunction() 45 | 46 | add_subdirectory(dwarfdump) 47 | add_subdirectory(symbol_tables) 48 | add_subdirectory(resolver) 49 | -------------------------------------------------------------------------------- /tools/dwarfdump/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) 2 | binary(dwarfdump LIBS ${dwarf_lib}) 3 | endif() 4 | -------------------------------------------------------------------------------- /tools/resolver/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | binary(resolver) 2 | -------------------------------------------------------------------------------- /tools/resolver/main.cpp: -------------------------------------------------------------------------------- 1 | #include "cpptrace/basic.hpp" 2 | #include "cpptrace/formatting.hpp" 3 | #include "cpptrace/forward.hpp" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "symbols/symbols.hpp" 19 | #include "demangle/demangle.hpp" 20 | 21 | using namespace std::literals; 22 | using namespace cpptrace::detail; 23 | 24 | template<> struct fmt::formatter : ostream_formatter {}; 25 | 26 | auto formatter = cpptrace::formatter{}.addresses(cpptrace::formatter::address_mode::object); 27 | 28 | struct options { 29 | bool show_help = false; 30 | std::filesystem::path path; 31 | std::vector address_strings; 32 | bool from_stdin = false; 33 | bool keepalive = false; 34 | bool timing = false; 35 | bool disable_aranges = false; 36 | cpptrace::nullable line_table_cache_size; 37 | }; 38 | 39 | void resolve(const options& opts, cpptrace::frame_ptr address) { 40 | cpptrace::object_frame obj_frame{0, address, opts.path.string()}; 41 | auto start = std::chrono::high_resolution_clock::now(); 42 | std::vector trace = cpptrace::detail::resolve_frames({obj_frame}); 43 | auto end = std::chrono::high_resolution_clock::now(); 44 | auto duration = std::chrono::duration_cast(end - start); 45 | if(trace.size() != 1) { 46 | throw std::runtime_error("Something went wrong, trace vector size didn't match"); 47 | } 48 | trace[0].symbol = cpptrace::demangle(trace[0].symbol); 49 | formatter.print(trace[0]); 50 | std::cout<> word) { 96 | resolve(opts, std::stoi(word, nullptr, 16)); 97 | } 98 | } 99 | if(opts.keepalive) { 100 | fmt::println("Done"); 101 | while(true) { 102 | std::this_thread::sleep_for(std::chrono::seconds(60)); 103 | } 104 | } 105 | return 0; 106 | } CPPTRACE_CATCH(const std::exception& e) { 107 | fmt::println(stderr, "Caught exception {}: {}", cpptrace::demangle(typeid(e).name()), e.what()); 108 | cpptrace::from_current_exception().print(); 109 | } 110 | -------------------------------------------------------------------------------- /tools/symbol_tables/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | binary(symbol_tables) 2 | -------------------------------------------------------------------------------- /tools/symbol_tables/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include "binary/elf.hpp" 11 | #include "binary/mach-o.hpp" 12 | 13 | using namespace std::literals; 14 | using namespace cpptrace::detail; 15 | 16 | template<> struct fmt::formatter : ostream_formatter {}; 17 | 18 | #if IS_LINUX 19 | void dump_symtab_result(const Result>, internal_error>& res) { 20 | if(!res) { 21 | fmt::println(stderr, "Error loading: {}", res.unwrap_error().what()); 22 | } 23 | const auto& entries_ = res.unwrap_value(); 24 | if(!entries_) { 25 | fmt::println("Empty symbol table"); 26 | } 27 | const auto& entries = entries_.unwrap(); 28 | fmt::println("{:16} {:16} {:4} {}", "value", "size", "shdx", "symbol"); 29 | for(const auto& entry : entries) { 30 | fmt::println("{:016x} {:016x} {:04x} {}", entry.st_value, entry.st_size, entry.st_shndx, entry.st_name); 31 | } 32 | } 33 | 34 | void dump_symbols(const std::filesystem::path& path) { 35 | auto elf_ = elf::open(path.native()); 36 | if(!elf_) { 37 | fmt::println(stderr, "Error reading file: {}", elf_.unwrap_error().what()); 38 | } 39 | auto& elf = elf_.unwrap_value(); 40 | fmt::println("Symtab:"); 41 | dump_symtab_result(elf.get_symtab_entries()); 42 | fmt::println("Dynamic symtab:"); 43 | dump_symtab_result(elf.get_dynamic_symtab_entries()); 44 | } 45 | #elif IS_APPLE 46 | void dump_symbols(const std::filesystem::path& path) { 47 | fmt::println("Not implemented yet (TODO)"); 48 | } 49 | #else 50 | void dump_symbols(const std::filesystem::path&) { 51 | fmt::println("Unable to dump symbol table on this platform"); 52 | } 53 | #endif 54 | 55 | int main(int argc, char** argv) CPPTRACE_TRY { 56 | bool show_help = false; 57 | std::filesystem::path path; 58 | auto cli = lyra::cli() 59 | | lyra::help(show_help) 60 | | lyra::arg(path, "binary path")("binary to dump symbol tables for").required(); 61 | if(auto result = cli.parse({ argc, argv }); !result) { 62 | fmt::println(stderr, "Error in command line: {}", result.message()); 63 | fmt::println("{}", cli); 64 | return 1; 65 | } 66 | if(show_help) { 67 | fmt::println("{}", cli); 68 | return 0; 69 | } 70 | if(!std::filesystem::exists(path)) { 71 | fmt::println(stderr, "Error: Path doesn't exist {}", path); 72 | return 1; 73 | } 74 | if(!std::filesystem::is_regular_file(path)) { 75 | fmt::println(stderr, "Error: Path isn't a regular file {}", path); 76 | return 1; 77 | } 78 | dump_symbols(path); 79 | return 0; 80 | } CPPTRACE_CATCH(const std::exception& e) { 81 | fmt::println(stderr, "Caught exception {}: {}", cpptrace::demangle(typeid(e).name()), e.what()); 82 | cpptrace::from_current_exception().print(); 83 | } 84 | --------------------------------------------------------------------------------