Compare commits
91 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fac4d08fd0 | ||
|
|
c0354799c7 | ||
|
|
c37b5ed736 | ||
|
|
a32e22aa44 | ||
|
|
6cec10601e | ||
|
|
c9dc51aa61 | ||
|
|
e6d55b5e7d | ||
|
|
1940dc607a | ||
|
|
477aecec01 | ||
|
|
03b292c20b | ||
|
|
5bfcf280a5 | ||
|
|
9b02fc6f74 | ||
|
|
3a4da8ccf0 | ||
|
|
9a2ae3c96f | ||
|
|
6877782d96 | ||
|
|
98ea78445c | ||
|
|
3aa080d536 | ||
|
|
34ea9572b8 | ||
|
|
525ce871d5 | ||
|
|
daed105fef | ||
|
|
69875cde19 | ||
|
|
b59a08634d | ||
|
|
2bb29f71bc | ||
|
|
d6fff9022e | ||
|
|
e86f4eba8f | ||
|
|
ca8416ea1e | ||
|
|
6d62d01496 | ||
|
|
74b5ac6e07 | ||
|
|
dff5b8f18e | ||
|
|
c375a72efc | ||
|
|
d8a0097c43 | ||
|
|
350382bb93 | ||
|
|
f7675eac91 | ||
|
|
832c3014b0 | ||
|
|
b0d12daf22 | ||
|
|
cebca81aa9 | ||
|
|
c2b3b7e0a1 | ||
|
|
27107556f8 | ||
|
|
26ef617c25 | ||
|
|
9c0d0db884 | ||
|
|
5e4ea9a88f | ||
|
|
99814905be | ||
|
|
6180399996 | ||
|
|
e7f8521936 | ||
|
|
e77f16031b | ||
|
|
261ca9d554 | ||
|
|
5073cc218a | ||
|
|
34be9f2f19 | ||
|
|
6d41ea0135 | ||
|
|
27924487dc | ||
|
|
728cefab55 | ||
|
|
aed47df73e | ||
|
|
e22300b36d | ||
|
|
a4faef7f1e | ||
|
|
83527947a2 | ||
|
|
1d79dbcf42 | ||
|
|
8c7b1dc6aa | ||
|
|
111f8e6aec | ||
|
|
ead3f128aa | ||
|
|
8963639639 | ||
|
|
b762ee7ec6 | ||
|
|
87f2fd4c43 | ||
|
|
87b14c87f8 | ||
|
|
62548497a8 | ||
|
|
0f990f05a1 | ||
|
|
4ab78f7a69 | ||
|
|
bc0164224e | ||
|
|
d18d6ee77d | ||
|
|
8f2193f35a | ||
|
|
457bc4b8a1 | ||
|
|
ce97e0004d | ||
|
|
000168b93c | ||
|
|
156ede9aab | ||
|
|
2b7d47d627 | ||
|
|
b724d1328c | ||
|
|
8e7b4a953f | ||
|
|
b2180ae797 | ||
|
|
f0a9e12e88 | ||
|
|
eb83ee2a1c | ||
|
|
d1ce9c8896 | ||
|
|
00c7feb2e0 | ||
|
|
32f0d5d273 | ||
|
|
85be7c32a4 | ||
|
|
248ad447b1 | ||
|
|
eb9ebc31a7 | ||
|
|
e2b1252438 | ||
|
|
3557d7b885 | ||
|
|
9077430b6a | ||
|
|
06c9c14995 | ||
|
|
7f6945c7d9 | ||
|
|
73ee7aa3a1 |
71
.github/workflows/ci.yml
vendored
71
.github/workflows/ci.yml
vendored
@ -25,6 +25,26 @@ jobs:
|
||||
- name: build and test
|
||||
run: |
|
||||
python3 ci/test-all-configs.py --${{matrix.compiler}} --default-config
|
||||
test-linux-arm:
|
||||
runs-on: ubuntu-22.04-arm
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
compiler: [gcc, clang]
|
||||
shared: [--shared, ""]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: dependencies
|
||||
run: |
|
||||
sudo apt install gcc-10 g++-10 libgcc-10-dev libunwind8-dev
|
||||
pip3 install colorama
|
||||
- name: libdwarf
|
||||
run: |
|
||||
cd ..
|
||||
cpptrace/ci/setup-prerequisites.sh
|
||||
- name: build and test
|
||||
run: |
|
||||
python3 ci/test-all-configs.py --${{matrix.compiler}} --default-config
|
||||
test-macos:
|
||||
runs-on: macos-14
|
||||
strategy:
|
||||
@ -68,6 +88,31 @@ jobs:
|
||||
- name: build and test
|
||||
run: |
|
||||
python3 ci/test-all-configs.py --${{matrix.compiler}} --default-config
|
||||
test-windows-old:
|
||||
runs-on: windows-2022
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
compiler: [msvc]
|
||||
shared: [--shared, ""]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Enable Developer Command Prompt
|
||||
uses: ilammy/msvc-dev-cmd@v1.13.0
|
||||
with:
|
||||
toolset: 14.29 # vc 2019
|
||||
- name: dependencies
|
||||
run: |
|
||||
pip3 install colorama
|
||||
- name: libdwarf
|
||||
run: |
|
||||
if("${{matrix.compiler}}" -eq "gcc") {
|
||||
cd ..
|
||||
cpptrace/ci/setup-prerequisites-mingw.ps1
|
||||
}
|
||||
- name: build and test
|
||||
run: |
|
||||
python3 ci/test-all-configs.py --${{matrix.compiler}} --default-config
|
||||
test-linux-all-configurations:
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
@ -537,6 +582,32 @@ jobs:
|
||||
- name: test opt
|
||||
run: |
|
||||
bazel test //... -c opt
|
||||
unittest-linux-arm:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
compiler: [g++-10, clang++-18]
|
||||
stdlib: [libstdc++, libc++]
|
||||
dwarf_version: [4, 5]
|
||||
split_dwarf: [OFF, ON]
|
||||
exclude:
|
||||
- compiler: g++-10
|
||||
stdlib: libc++
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: dependencies
|
||||
run: |
|
||||
sudo apt install gcc-10 g++-10 libgcc-10-dev ninja-build libc++-dev
|
||||
cd ..
|
||||
cpptrace/ci/setup-prerequisites-unittest.sh
|
||||
- name: build and test
|
||||
run: |
|
||||
python3 ci/unittest.py \
|
||||
--slice=compiler:${{matrix.compiler}} \
|
||||
--slice=stdlib:${{matrix.stdlib}} \
|
||||
--slice=dwarf_version:${{matrix.dwarf_version}} \
|
||||
--slice=split_dwarf:${{matrix.split_dwarf}}
|
||||
unittest-macos:
|
||||
runs-on: macos-14
|
||||
steps:
|
||||
|
||||
62
CHANGELOG.md
62
CHANGELOG.md
@ -1,6 +1,9 @@
|
||||
# Changelog
|
||||
|
||||
- [Changelog](#changelog)
|
||||
- [v0.8.2](#v082)
|
||||
- [v0.8.1](#v081)
|
||||
- [v0.8.0](#v080)
|
||||
- [v0.7.5](#v075)
|
||||
- [v0.7.4](#v074)
|
||||
- [v0.7.3](#v073)
|
||||
@ -25,6 +28,65 @@
|
||||
- [v0.1.1](#v011)
|
||||
- [v0.1](#v01)
|
||||
|
||||
# v0.8.2
|
||||
|
||||
Fixed:
|
||||
- Fixed printing of internal error messages when an object file can't be loaded, mainly affecting MacOS https://github.com/jeremy-rifkin/cpptrace/issues/217
|
||||
|
||||
Other:
|
||||
- Bumped zstd via FetchContent to 1.5.7
|
||||
|
||||
# v0.8.1
|
||||
|
||||
Fixed:
|
||||
- Fixed compile error on msvc https://github.com/jeremy-rifkin/cpptrace/issues/215
|
||||
|
||||
Added:
|
||||
- Added `cpptrace::can_get_safe_object_frame()`
|
||||
|
||||
Breaking changes:
|
||||
- Renamed ctrace's `can_signal_safe_unwind` to `ctrace_can_signal_safe_unwind`. This was an oversight. Apologies for
|
||||
including a breaking change in a patch release. Github code search suggests this API isn't used in public code, at
|
||||
least.
|
||||
|
||||
Other:
|
||||
- Added CI workflow to test on old msvc
|
||||
- Made some internal improvements on robustness and cleanliness
|
||||
|
||||
# v0.8.0
|
||||
|
||||
Added:
|
||||
- Added support for resolving symbols from elf and mach-o symbol tables, allowing function names to be resolved even in
|
||||
a build that doesn't include debug information https://github.com/jeremy-rifkin/cpptrace/issues/201
|
||||
- Added a configurable stack trace formatter https://github.com/jeremy-rifkin/cpptrace/issues/164
|
||||
- Added configuration options for the libdwarf back-end that can be used to lower memory usage on memory-constrained
|
||||
systems https://github.com/jeremy-rifkin/cpptrace/issues/193
|
||||
- Added `cpptrace::nullable<T>::null_value`
|
||||
- Made `cpptrace::nullable<T>` member functions conditionally `constexpr` where possible
|
||||
|
||||
Fixed:
|
||||
- Fixed handling of `SymInitialize` when other code has already called `SymInitialize`. `SymInitialize` must only be
|
||||
called once per handle and cpptrace now attempts to duplicate the current process handle to avoid conflicts.
|
||||
https://github.com/jeremy-rifkin/cpptrace/issues/204
|
||||
- Fixed a couple of locking edge cases surrounding dbghelp functions
|
||||
- Fixed improper deallocation of `dwarf_errmsg` in the libdwarf back-end
|
||||
|
||||
Breaking changes:
|
||||
- `cpptrace::get_snippet` previously included a newline at the end but it now does not. This also affects the behavior
|
||||
of trace formatting with snippets enabled.
|
||||
|
||||
Other:
|
||||
- Significantly improved memory usage and performance of the libdwarf back-end
|
||||
- Improved implementation and organization of internal utility types, such as `optional` and `Result`
|
||||
- Improved trace printing and formatting implementation
|
||||
- Added unit tests for library internal utilities
|
||||
- Added logic to the cxxabi demangler to ensure external names begin with `_Z` or `__Z` before attempting to demangle
|
||||
- Added various internal tools and abstractions to improve maintainability and clarity
|
||||
- Various internal improvements for robustness
|
||||
- Added a small handful of utility tool programs that are useful for continued development, maintenance, and debugging
|
||||
- Improved library CI setup
|
||||
- Marked the `CPPTRACE_BUILD_BENCHMARK` option as advanced
|
||||
|
||||
# v0.7.5
|
||||
|
||||
Fixed:
|
||||
|
||||
@ -9,7 +9,7 @@ set(package_name "cpptrace")
|
||||
|
||||
project(
|
||||
cpptrace
|
||||
VERSION 0.7.5
|
||||
VERSION 0.8.2
|
||||
DESCRIPTION "Simple, portable, and self-contained stacktrace library for C++11 and newer "
|
||||
HOMEPAGE_URL "https://github.com/jeremy-rifkin/cpptrace"
|
||||
LANGUAGES C CXX
|
||||
@ -113,6 +113,7 @@ target_sources(
|
||||
src/ctrace.cpp
|
||||
src/exceptions.cpp
|
||||
src/from_current.cpp
|
||||
src/formatting.cpp
|
||||
src/options.cpp
|
||||
src/utils.cpp
|
||||
src/demangle/demangle_with_cxxabi.cpp
|
||||
@ -120,6 +121,7 @@ target_sources(
|
||||
src/demangle/demangle_with_winapi.cpp
|
||||
src/snippets/snippet.cpp
|
||||
src/symbols/dwarf/debug_map_resolver.cpp
|
||||
src/symbols/dwarf/dwarf_options.cpp
|
||||
src/symbols/dwarf/dwarf_resolver.cpp
|
||||
src/symbols/symbols_core.cpp
|
||||
src/symbols/symbols_with_addr2line.cpp
|
||||
@ -136,7 +138,7 @@ target_sources(
|
||||
src/unwind/unwind_with_winapi.cpp
|
||||
src/utils/microfmt.cpp
|
||||
src/utils/utils.cpp
|
||||
src/platform/dbghelp_syminit_manager.cpp
|
||||
src/platform/dbghelp_utils.cpp
|
||||
)
|
||||
|
||||
target_include_directories(
|
||||
@ -221,6 +223,10 @@ target_compile_features(
|
||||
|
||||
target_compile_definitions(${target_name} PRIVATE NOMINMAX)
|
||||
|
||||
if(HAS_ATTRIBUTE_PACKED)
|
||||
target_compile_definitions(${target_name} PRIVATE HAS_ATTRIBUTE_PACKED)
|
||||
endif()
|
||||
|
||||
if(NOT CPPTRACE_STD_FORMAT)
|
||||
target_compile_definitions(${target_name} PUBLIC CPPTRACE_NO_STD_FORMAT)
|
||||
endif()
|
||||
@ -349,11 +355,14 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF)
|
||||
endif()
|
||||
endif()
|
||||
if(CPPTRACE_CONAN)
|
||||
set(dwarf_lib libdwarf::libdwarf)
|
||||
target_link_libraries(${target_name} PRIVATE libdwarf::libdwarf)
|
||||
elseif(CPPTRACE_VCPKG)
|
||||
set(dwarf_lib libdwarf::dwarf)
|
||||
target_link_libraries(${target_name} PRIVATE libdwarf::dwarf)
|
||||
elseif(CPPTRACE_USE_EXTERNAL_LIBDWARF)
|
||||
if(DEFINED LIBDWARF_LIBRARIES)
|
||||
set(dwarf_lib ${LIBDWARF_LIBRARIES})
|
||||
target_link_libraries(${target_name} PRIVATE ${LIBDWARF_LIBRARIES})
|
||||
else()
|
||||
# if LIBDWARF_LIBRARIES wasn't set by find_package, try looking for libdwarf::dwarf-static,
|
||||
@ -371,6 +380,7 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF)
|
||||
else()
|
||||
message(FATAL_ERROR "Couldn't find libdwarf target name to link against")
|
||||
endif()
|
||||
set(dwarf_lib ${LIBDWARF_LIBRARIES})
|
||||
target_link_libraries(${target_name} PRIVATE ${LIBDWARF_LIBRARIES})
|
||||
endif()
|
||||
# There seems to be no consistency at all about where libdwarf decides to place its headers........ Figure out if
|
||||
@ -393,6 +403,7 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF)
|
||||
message(FATAL_ERROR "Couldn't find libdwarf.h")
|
||||
endif()
|
||||
else()
|
||||
set(dwarf_lib libdwarf::dwarf-static)
|
||||
target_link_libraries(${target_name} PRIVATE libdwarf::dwarf-static)
|
||||
endif()
|
||||
if(UNIX)
|
||||
@ -508,7 +519,7 @@ if(NOT CMAKE_SKIP_INSTALL_RULES)
|
||||
include(cmake/InstallRules.cmake)
|
||||
endif()
|
||||
|
||||
# ===================================================== Demo/test ======================================================
|
||||
# ================================================== Demo/test/tools ===================================================
|
||||
|
||||
if(CPPTRACE_BUILD_TESTING)
|
||||
if(PROJECT_IS_TOP_LEVEL)
|
||||
@ -520,3 +531,7 @@ endif()
|
||||
if(CPPTRACE_BUILD_BENCHMARKING)
|
||||
add_subdirectory(benchmarking)
|
||||
endif()
|
||||
|
||||
if(CPPTRACE_BUILD_TOOLS)
|
||||
add_subdirectory(tools)
|
||||
endif()
|
||||
|
||||
8
Makefile
8
Makefile
@ -9,12 +9,12 @@ help: # with thanks to Ben Rady
|
||||
build: debug ## build in debug mode
|
||||
|
||||
build/configured-debug:
|
||||
cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On
|
||||
cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On -DCPPTRACE_BUILD_TOOLS=On
|
||||
rm -f build/configured-release
|
||||
touch build/configured-debug
|
||||
|
||||
build/configured-release:
|
||||
cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On
|
||||
cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On -DCPPTRACE_BUILD_TOOLS=On
|
||||
rm -f build/configured-debug
|
||||
touch build/configured-release
|
||||
|
||||
@ -34,12 +34,12 @@ release: configure-release ## build in release mode (with debug info)
|
||||
|
||||
.PHONY: debug-msvc
|
||||
debug-msvc: ## build in debug mode
|
||||
cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On
|
||||
cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On -DCPPTRACE_BUILD_TOOLS=On
|
||||
cmake --build build --config Debug
|
||||
|
||||
.PHONY: release-msvc
|
||||
release-msvc: ## build in release mode (with debug info)
|
||||
cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On
|
||||
cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On -DCPPTRACE_BUILD_TOOLS=On
|
||||
cmake --build build --config RelWithDebInfo
|
||||
|
||||
.PHONY: clean
|
||||
|
||||
215
README.md
215
README.md
@ -16,9 +16,6 @@ Cpptrace also has a C API, docs [here](docs/c-api.md).
|
||||
|
||||
- [30-Second Overview](#30-second-overview)
|
||||
- [CMake FetchContent Usage](#cmake-fetchcontent-usage)
|
||||
- [FAQ](#faq)
|
||||
- [What about C++23 `<stacktrace>`?](#what-about-c23-stacktrace)
|
||||
- [What does cpptrace have over other C++ stacktrace libraries?](#what-does-cpptrace-have-over-other-c-stacktrace-libraries)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Basic Usage](#basic-usage)
|
||||
- [`namespace cpptrace`](#namespace-cpptrace)
|
||||
@ -26,6 +23,7 @@ Cpptrace also has a C API, docs [here](docs/c-api.md).
|
||||
- [Object Traces](#object-traces)
|
||||
- [Raw Traces](#raw-traces)
|
||||
- [Utilities](#utilities)
|
||||
- [Formatting](#formatting)
|
||||
- [Configuration](#configuration)
|
||||
- [Traces From All Exceptions](#traces-from-all-exceptions)
|
||||
- [Removing the `CPPTRACE_` prefix](#removing-the-cpptrace_-prefix)
|
||||
@ -38,6 +36,7 @@ Cpptrace also has a C API, docs [here](docs/c-api.md).
|
||||
- [Signal-Safe Tracing](#signal-safe-tracing)
|
||||
- [Utility Types](#utility-types)
|
||||
- [Headers](#headers)
|
||||
- [Libdwarf Tuning](#libdwarf-tuning)
|
||||
- [Supported Debug Formats](#supported-debug-formats)
|
||||
- [How to Include The Library](#how-to-include-the-library)
|
||||
- [CMake FetchContent](#cmake-fetchcontent)
|
||||
@ -55,6 +54,10 @@ Cpptrace also has a C API, docs [here](docs/c-api.md).
|
||||
- [Summary of Library Configurations](#summary-of-library-configurations)
|
||||
- [Testing Methodology](#testing-methodology)
|
||||
- [Notes About the Library](#notes-about-the-library)
|
||||
- [FAQ](#faq)
|
||||
- [What about C++23 `<stacktrace>`?](#what-about-c23-stacktrace)
|
||||
- [What does cpptrace have over other C++ stacktrace libraries?](#what-does-cpptrace-have-over-other-c-stacktrace-libraries)
|
||||
- [I'm getting undefined standard library symbols like `std::__1::basic_string` on MacOS](#im-getting-undefined-standard-library-symbols-like-std__1basic_string-on-macos)
|
||||
- [Contributing](#contributing)
|
||||
- [License](#license)
|
||||
|
||||
@ -125,6 +128,7 @@ Additional notable features:
|
||||
- Utilities for catching `std::exception`s and wrapping them in traced exceptions
|
||||
- Signal-safe stack tracing
|
||||
- Source code snippets in traces
|
||||
- Extensive configuration options for [trace formatting](#formatting)
|
||||
|
||||

|
||||
|
||||
@ -135,7 +139,7 @@ include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
cpptrace
|
||||
GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git
|
||||
GIT_TAG v0.7.5 # <HASH or TAG>
|
||||
GIT_TAG v0.8.2 # <HASH or TAG>
|
||||
)
|
||||
FetchContent_MakeAvailable(cpptrace)
|
||||
target_link_libraries(your_target cpptrace::cpptrace)
|
||||
@ -160,33 +164,6 @@ On macOS it is recommended to generate a `.dSYM` file, see [Platform Logistics](
|
||||
For other ways to use the library, such as through package managers, a system-wide installation, or on a platform
|
||||
without internet access see [How to Include The Library](#how-to-include-the-library) below.
|
||||
|
||||
# FAQ
|
||||
|
||||
## What about C++23 `<stacktrace>`?
|
||||
|
||||
Some day C++23's `<stacktrace>` will be ubiquitous. And maybe one day the msvc implementation will be acceptable.
|
||||
The original motivation for cpptrace was to support projects using older C++ standards and as the library has grown its
|
||||
functionality has extended beyond the standard library's implementation.
|
||||
|
||||
Cpptrace provides functionality beyond what the standard library provides and what implementations provide, such as:
|
||||
- Walking inlined function calls
|
||||
- Providing a lightweight interface for "raw traces"
|
||||
- Resolving function parameter types
|
||||
- Providing traced exception objects
|
||||
- Providing an API for signal-safe stacktrace generation
|
||||
- Providing a way to retrieve stack traces from arbitrary exceptions, not just special cpptrace traced exception
|
||||
objects. This is a feature coming to C++26, but cpptrace provides a solution for C++11.
|
||||
|
||||
## What does cpptrace have over other C++ stacktrace libraries?
|
||||
|
||||
Other C++ stacktrace libraries, such as boost stacktrace and backward-cpp, fall short when it comes to portability and
|
||||
ease of use. In testing, I found neither to provide adaquate coverage of various environments. Even when they can be
|
||||
made to work in an environment they require manual configuration from the end-user, possibly requiring manual
|
||||
installation of third-party dependencies. This is a highly undesirable burden to impose on users, especially when it is
|
||||
for a software package which just provides diagnostics as opposed to core functionality. Additionally, cpptrace provides
|
||||
support for resolving inlined calls by default for DWARF symbols (boost does not do this, backward-cpp can do this but
|
||||
only for some back-ends), better support for resolving full function signatures, and nicer API, among other features.
|
||||
|
||||
# Prerequisites
|
||||
|
||||
> [!IMPORTANT]
|
||||
@ -342,6 +319,87 @@ namespace cpptrace {
|
||||
}
|
||||
```
|
||||
|
||||
## Formatting
|
||||
|
||||
Cpptrace provides a configurable formatter for stack trace printing which supports some common options. Formatters are
|
||||
configured with a sort of builder pattern, e.g.:
|
||||
```cpp
|
||||
auto formatter = cpptrace::formatter{}
|
||||
.header("Stack trace:")
|
||||
.addresses(cpptrace::formatter::address_mode::object)
|
||||
.snippets(true);
|
||||
```
|
||||
|
||||
This API is available through the `<cpptrace/formatting.hpp>` header.
|
||||
|
||||
Synopsis:
|
||||
```cpp
|
||||
namespace cpptrace {
|
||||
class formatter {
|
||||
formatter& header(std::string);
|
||||
enum class color_mode { always, none, automatic };
|
||||
formatter& colors(color_mode);
|
||||
enum class address_mode { raw, object, none };
|
||||
formatter& addresses(address_mode);
|
||||
enum class path_mode { full, basename };
|
||||
formatter& paths(path_mode);
|
||||
formatter& snippets(bool);
|
||||
formatter& snippet_context(int);
|
||||
formatter& columns(bool);
|
||||
formatter& filtered_frame_placeholders(bool);
|
||||
formatter& filter(std::function<bool(const stacktrace_frame&)>);
|
||||
|
||||
std::string format(const stacktrace_frame&) const;
|
||||
std::string format(const stacktrace_frame&, bool color) const;
|
||||
|
||||
std::string format(const stacktrace&) const;
|
||||
std::string format(const stacktrace&, bool color) const;
|
||||
|
||||
void print(const stacktrace_frame&) const;
|
||||
void print(const stacktrace_frame&, bool color) const;
|
||||
void print(std::ostream&, const stacktrace_frame&) const;
|
||||
void print(std::ostream&, const stacktrace_frame&, bool color) const;
|
||||
void print(std::FILE*, const stacktrace_frame&) const;
|
||||
void print(std::FILE*, const stacktrace_frame&, bool color) const;
|
||||
|
||||
void print(const stacktrace&) const;
|
||||
void print(const stacktrace&, bool color) const;
|
||||
void print(std::ostream&, const stacktrace&) const;
|
||||
void print(std::ostream&, const stacktrace&, bool color) const;
|
||||
void print(std::FILE*, const stacktrace&) const;
|
||||
void print(std::FILE*, const stacktrace&, bool color) const;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Options:
|
||||
| Setting | Description | Default |
|
||||
| ----------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------ |
|
||||
| `header` | Header line printed before the trace | `Stack trace (most recent call first):` |
|
||||
| `colors` | Default color mode for the trace | `automatic`, which attempts to detect if the target stream is a terminal |
|
||||
| `addresses` | Raw addresses, object addresses, or no addresses | `raw` |
|
||||
| `paths` | Full paths or just filenames | `full` |
|
||||
| `snippets` | Whether to include source code snippets | `false` |
|
||||
| `snippet_context` | How many lines of source context to show in a snippet | `2` |
|
||||
| `columns` | Whether to include column numbers if present | `true` |
|
||||
| `filtered_frame_placeholders` | Whether to still print filtered frames as just `#n (filtered)` | `true` |
|
||||
| `filter` | A predicate to filter frames with | None |
|
||||
|
||||
The `automatic` color mode attempts to detect if a stream that may be attached to a terminal. As such, it will not use
|
||||
colors for the `formatter::format` method and it may not be able to detect if some ostreams correspond to terminals or
|
||||
not. For this reason, `formatter::format` and `formatter::print` methods have overloads taking a color parameter. This
|
||||
color parameter will override configured color mode.
|
||||
|
||||
Recommended practice with formatters: It's generally preferable to create formatters objects that are long-lived rather
|
||||
than to create them on the fly every time a trace needs to be formatted.
|
||||
|
||||
Cpptrace provides access to a formatter with default settings with `get_default_formatter`:
|
||||
```cpp
|
||||
namespace cpptrace {
|
||||
const formatter& get_default_formatter();
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
`cpptrace::absorb_trace_exceptions`: Configure whether the library silently absorbs internal exceptions and continues.
|
||||
@ -381,6 +439,8 @@ thrown exception object, with minimal or no overhead in the non-throwing path:
|
||||
|
||||
```cpp
|
||||
#include <cpptrace/from_current.hpp>
|
||||
#include <iostream>
|
||||
|
||||
void foo() {
|
||||
throw std::runtime_error("foo failed");
|
||||
}
|
||||
@ -690,6 +750,7 @@ namespace cpptrace {
|
||||
};
|
||||
void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
|
||||
bool can_signal_safe_unwind();
|
||||
bool can_get_safe_object_frame();
|
||||
}
|
||||
```
|
||||
|
||||
@ -706,9 +767,9 @@ see the comprehensive overview and demo at [signal-safe-tracing.md](docs/signal-
|
||||
> [!IMPORTANT]
|
||||
> Currently signal-safe stack unwinding is only possible with `libunwind`, which must be
|
||||
> [manually enabled](#library-back-ends). If signal-safe unwinding isn't supported, `safe_generate_raw_trace` will just
|
||||
> produce an empty trace. `can_signal_safe_unwind` can be used to check for signal-safe unwinding support. If object
|
||||
> information can't be resolved in a signal-safe way then `get_safe_object_frame` will not populate fields beyond the
|
||||
> `raw_address`.
|
||||
> produce an empty trace. `can_signal_safe_unwind` can be used to check for signal-safe unwinding support and
|
||||
> `can_get_safe_object_frame` can be used to check `get_safe_object_frame` support. If object information can't be
|
||||
> resolved in a signal-safe way then `get_safe_object_frame` will not populate fields beyond the `raw_address`.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> `_dl_find_object` is required for signal-safe stack tracing. This is a relatively recent addition to glibc, added in
|
||||
@ -734,6 +795,7 @@ namespace cpptrace {
|
||||
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
|
||||
struct nullable {
|
||||
T raw_value;
|
||||
// all members are constexpr for c++17 and beyond, some are constexpr before c++17
|
||||
nullable& operator=(T value)
|
||||
bool has_value() const noexcept;
|
||||
T& value() noexcept;
|
||||
@ -743,6 +805,7 @@ namespace cpptrace {
|
||||
void reset() noexcept;
|
||||
bool operator==(const nullable& other) const noexcept;
|
||||
bool operator!=(const nullable& other) const noexcept;
|
||||
constexpr static T null_value() noexcept; // returns the raw null value
|
||||
constexpr static nullable null() noexcept; // returns a null instance
|
||||
};
|
||||
|
||||
@ -785,12 +848,39 @@ Cpptrace provides a handful of headers to make inclusion more minimal.
|
||||
| `cpptrace/exceptions.hpp` | [Traced Exception Objects](#traced-exception-objects) and related utilities ([Wrapping std::exceptions](#wrapping-stdexceptions)) |
|
||||
| `cpptrace/from_current.hpp` | [Traces From All Exceptions](#traces-from-all-exceptions) |
|
||||
| `cpptrace/io.hpp` | `operator<<` overloads for `std::ostream` and `std::formatter`s |
|
||||
| `cpptrace/formatting.hpp` | Configurable formatter API |
|
||||
| `cpptrace/utils.hpp` | Utility functions, configuration functions, and terminate utilities ([Utilities](#utilities), [Configuration](#configuration), and [Terminate Handling](#terminate-handling)) |
|
||||
| `cpptrace/version.hpp` | Library version macros |
|
||||
|
||||
The main cpptrace header is `cpptrace/cpptrace.hpp` which includes everything other than `from_current.hpp` and
|
||||
`version.hpp`.
|
||||
|
||||
## Libdwarf Tuning
|
||||
|
||||
For extraordinarily large binaries (multiple gigabytes), cpptrace's internal caching can result in a lot of memory
|
||||
usage. Cpptrace provides some options to reduce memory usage in exchange for performance in memory-constrained
|
||||
applications.
|
||||
|
||||
Synopsis:
|
||||
|
||||
```cpp
|
||||
namespace cpptrace {
|
||||
namespace experimental {
|
||||
void set_dwarf_resolver_line_table_cache_size(nullable<std::size_t> max_entries);
|
||||
void set_dwarf_resolver_disable_aranges(bool disable);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Explanation:
|
||||
- `set_dwarf_resolver_line_table_cache_size` can be used to set a limit to the cache size with evictions done LRU.
|
||||
Cpptrace loads and caches line tables for dwarf compile units. These can take a lot of space for large binaries with
|
||||
lots of debug info. Passing `nullable<std::size_t>::null()` will disable the cache size (which is the default
|
||||
behavior).
|
||||
- `set_dwarf_resolver_disable_aranges` can be used to disable use of dwarf `.debug_aranges`, an accelerated range lookup
|
||||
table for compile units emitted by many compilers. Cpptrace uses these by default if they are present since they can
|
||||
speed up resolution, however, they can also result in significant memory usage.
|
||||
|
||||
# Supported Debug Formats
|
||||
|
||||
| Format | Supported |
|
||||
@ -815,7 +905,7 @@ include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
cpptrace
|
||||
GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git
|
||||
GIT_TAG v0.7.5 # <HASH or TAG>
|
||||
GIT_TAG v0.8.2 # <HASH or TAG>
|
||||
)
|
||||
FetchContent_MakeAvailable(cpptrace)
|
||||
target_link_libraries(your_target cpptrace::cpptrace)
|
||||
@ -831,7 +921,7 @@ information.
|
||||
|
||||
```sh
|
||||
git clone https://github.com/jeremy-rifkin/cpptrace.git
|
||||
git checkout v0.7.5
|
||||
git checkout v0.8.2
|
||||
mkdir cpptrace/build
|
||||
cd cpptrace/build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release
|
||||
@ -874,7 +964,7 @@ you when installing new libraries.
|
||||
|
||||
```ps1
|
||||
git clone https://github.com/jeremy-rifkin/cpptrace.git
|
||||
git checkout v0.7.5
|
||||
git checkout v0.8.2
|
||||
mkdir cpptrace/build
|
||||
cd cpptrace/build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release
|
||||
@ -892,7 +982,7 @@ To install just for the local user (or any custom prefix):
|
||||
|
||||
```sh
|
||||
git clone https://github.com/jeremy-rifkin/cpptrace.git
|
||||
git checkout v0.7.5
|
||||
git checkout v0.8.2
|
||||
mkdir cpptrace/build
|
||||
cd cpptrace/build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$HOME/wherever
|
||||
@ -975,7 +1065,7 @@ make install
|
||||
cd ~/scratch/cpptrace-test
|
||||
git clone https://github.com/jeremy-rifkin/cpptrace.git
|
||||
cd cpptrace
|
||||
git checkout v0.7.5
|
||||
git checkout v0.8.2
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=On -DCPPTRACE_USE_EXTERNAL_LIBDWARF=On -DCMAKE_PREFIX_PATH=~/scratch/cpptrace-test/resources -DCMAKE_INSTALL_PREFIX=~/scratch/cpptrace-test/resources
|
||||
@ -995,7 +1085,7 @@ cpptrace and its dependencies.
|
||||
Cpptrace is available through conan at https://conan.io/center/recipes/cpptrace.
|
||||
```
|
||||
[requires]
|
||||
cpptrace/0.7.5
|
||||
cpptrace/0.8.2
|
||||
[generators]
|
||||
CMakeDeps
|
||||
CMakeToolchain
|
||||
@ -1204,6 +1294,51 @@ A couple things I'd like to improve in the future:
|
||||
in dbghelp the library cannot accurately show const and volatile qualifiers or rvalue references (these appear as
|
||||
pointers).
|
||||
|
||||
# FAQ
|
||||
|
||||
## What about C++23 `<stacktrace>`?
|
||||
|
||||
Some day C++23's `<stacktrace>` will be ubiquitous. And maybe one day the msvc implementation will be acceptable.
|
||||
The original motivation for cpptrace was to support projects using older C++ standards and as the library has grown its
|
||||
functionality has extended beyond the standard library's implementation.
|
||||
|
||||
Cpptrace provides functionality beyond what the standard library provides and what implementations provide, such as:
|
||||
- Walking inlined function calls
|
||||
- Providing a lightweight interface for "raw traces"
|
||||
- Resolving function parameter types
|
||||
- Providing traced exception objects
|
||||
- Providing an API for signal-safe stacktrace generation
|
||||
- Providing a way to retrieve stack traces from arbitrary exceptions, not just special cpptrace traced exception
|
||||
objects. This is a feature coming to C++26, but cpptrace provides a solution for C++11.
|
||||
|
||||
## What does cpptrace have over other C++ stacktrace libraries?
|
||||
|
||||
Other C++ stacktrace libraries, such as boost stacktrace and backward-cpp, fall short when it comes to portability and
|
||||
ease of use. In testing, I found neither to provide adaquate coverage of various environments. Even when they can be
|
||||
made to work in an environment they require manual configuration from the end-user, possibly requiring manual
|
||||
installation of third-party dependencies. This is a highly undesirable burden to impose on users, especially when it is
|
||||
for a software package which just provides diagnostics as opposed to core functionality. Additionally, cpptrace provides
|
||||
support for resolving inlined calls by default for DWARF symbols (boost does not do this, backward-cpp can do this but
|
||||
only for some back-ends), better support for resolving full function signatures, and nicer API, among other features.
|
||||
|
||||
## I'm getting undefined standard library symbols like `std::__1::basic_string` on MacOS
|
||||
|
||||
If you see a linker error along the lines of the following on MacOS then it's highly likely you are mixing standard
|
||||
library ABIs.
|
||||
|
||||
```
|
||||
Undefined symbols for architecture arm64:
|
||||
"std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::find(char, unsigned long) const", referenced from:
|
||||
cpptrace::detail::demangle(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, bool) in libcpptrace.a(demangle_with_cxxabi.cpp.o)
|
||||
cpptrace::detail::snippet_manager::build_line_table() in libcpptrace.a(snippet.cpp.o)
|
||||
```
|
||||
|
||||
This can happen when using apple clang to compile cpptrace and gcc to compile your code, or vice versa. The reason is
|
||||
that apple clang defaults to libc++ and gcc defaults to libstdc++ and these two standard library implementations are not
|
||||
ABI-compatible. To resolve this, ensure you are compiling both cpptrace and your code with the same standard library by
|
||||
either using the same compiler for both or using `-stdlib=libc++`/`-stdlib=libstdc++` to control which standard library
|
||||
is used.
|
||||
|
||||
# Contributing
|
||||
|
||||
I'm grateful for the help I've received with this library and I welcome contributions! For information on contributing
|
||||
|
||||
@ -66,60 +66,6 @@ def build(runner: MatrixRunner):
|
||||
|
||||
return succeeded
|
||||
|
||||
def build_full_or_auto(runner: MatrixRunner):
|
||||
matrix = runner.current_config()
|
||||
|
||||
if os.path.exists("build"):
|
||||
shutil.rmtree("build", ignore_errors=True)
|
||||
|
||||
os.makedirs("build", exist_ok=True)
|
||||
os.chdir("build")
|
||||
|
||||
if platform.system() != "Windows":
|
||||
args = [
|
||||
"cmake",
|
||||
"..",
|
||||
"-GNinja",
|
||||
f"-DCMAKE_BUILD_TYPE={matrix['target']}",
|
||||
f"-DCMAKE_CXX_COMPILER={matrix['compiler']}",
|
||||
f"-DCMAKE_CXX_STANDARD={matrix['std']}",
|
||||
f"-DCPPTRACE_USE_EXTERNAL_LIBDWARF=On",
|
||||
f"-DCPPTRACE_USE_EXTERNAL_ZSTD=On",
|
||||
f"-DCPPTRACE_WERROR_BUILD=On",
|
||||
f"-DCPPTRACE_BACKTRACE_PATH=/usr/lib/gcc/x86_64-linux-gnu/10/include/backtrace.h",
|
||||
]
|
||||
if matrix["config"] != "":
|
||||
args.append(f"{matrix['config']}")
|
||||
succeeded = runner.run_command(*args)
|
||||
if succeeded:
|
||||
succeeded = runner.run_command("ninja")
|
||||
else:
|
||||
args = [
|
||||
"cmake",
|
||||
"..",
|
||||
f"-DCMAKE_BUILD_TYPE={matrix['target']}",
|
||||
f"-DCMAKE_CXX_COMPILER={matrix['compiler']}",
|
||||
f"-DCMAKE_CXX_STANDARD={matrix['std']}",
|
||||
f"-DCPPTRACE_USE_EXTERNAL_LIBDWARF=On",
|
||||
f"-DCPPTRACE_USE_EXTERNAL_ZSTD=On",
|
||||
f"-DCPPTRACE_WERROR_BUILD=On",
|
||||
]
|
||||
if matrix["config"] != "":
|
||||
args.append(f"{matrix['config']}")
|
||||
if matrix["compiler"] == "g++":
|
||||
args.append("-GUnix Makefiles")
|
||||
succeeded = runner.run_command(*args)
|
||||
if succeeded:
|
||||
if matrix["compiler"] == "g++":
|
||||
succeeded = runner.run_command("make", "-j")
|
||||
else:
|
||||
succeeded = runner.run_command("msbuild", "cpptrace.sln")
|
||||
|
||||
os.chdir("..")
|
||||
print()
|
||||
|
||||
return succeeded
|
||||
|
||||
def run_linux_matrix(compilers: list):
|
||||
MatrixRunner(
|
||||
matrix = {
|
||||
|
||||
@ -15,6 +15,10 @@ if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||
check_support(HAS_CXXABI has_cxxabi.cpp "" "" "")
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||
check_support(HAS_ATTRIBUTE_PACKED has_attribute_packed.cpp "" "" "")
|
||||
endif()
|
||||
|
||||
if(NOT WIN32)
|
||||
check_support(HAS_UNWIND has_unwind.cpp "" "" "")
|
||||
check_support(HAS_EXECINFO has_execinfo.cpp "" "" "")
|
||||
|
||||
@ -151,6 +151,7 @@ option(CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH "" OFF)
|
||||
|
||||
if(PROJECT_IS_TOP_LEVEL)
|
||||
option(CPPTRACE_BUILD_TESTING "" OFF)
|
||||
option(CPPTRACE_BUILD_TOOLS "" OFF)
|
||||
option(CPPTRACE_BUILD_BENCHMARK "" OFF)
|
||||
option(CPPTRACE_BUILD_NO_SYMBOLS "" OFF)
|
||||
option(CPPTRACE_BUILD_TESTING_SPLIT_DWARF "" OFF)
|
||||
@ -158,6 +159,7 @@ if(PROJECT_IS_TOP_LEVEL)
|
||||
option(CPPTRACE_BUILD_TEST_RDYNAMIC "" OFF)
|
||||
mark_as_advanced(
|
||||
CPPTRACE_BUILD_TESTING
|
||||
CPPTRACE_BUILD_TOOLS
|
||||
CPPTRACE_BUILD_BENCHMARK
|
||||
CPPTRACE_BUILD_NO_SYMBOLS
|
||||
CPPTRACE_BUILD_TESTING_SPLIT_DWARF
|
||||
@ -178,7 +180,7 @@ option(CPPTRACE_SKIP_UNIT "" OFF)
|
||||
option(CPPTRACE_STD_FORMAT "" ON)
|
||||
option(CPPTRACE_UNPREFIXED_TRY_CATCH "" OFF)
|
||||
option(CPPTRACE_USE_EXTERNAL_GTEST "" OFF)
|
||||
set(CPPTRACE_ZSTD_URL "https://github.com/facebook/zstd/releases/download/v1.5.6/zstd-1.5.6.tar.gz" CACHE STRING "")
|
||||
set(CPPTRACE_ZSTD_URL "https://github.com/facebook/zstd/releases/download/v1.5.7/zstd-1.5.7.tar.gz" CACHE STRING "")
|
||||
set(CPPTRACE_LIBDWARF_REPO "https://github.com/jeremy-rifkin/libdwarf-lite.git" CACHE STRING "")
|
||||
set(CPPTRACE_LIBDWARF_TAG "fe09ca800b988e2ff21225ac5e7468ceade2a30e" CACHE STRING "") # v0.11.1
|
||||
set(CPPTRACE_LIBDWARF_SHALLOW "1" CACHE STRING "")
|
||||
|
||||
6
cmake/has_attribute_packed.cpp
Normal file
6
cmake/has_attribute_packed.cpp
Normal file
@ -0,0 +1,6 @@
|
||||
struct __attribute__((packed)) foo {
|
||||
int i;
|
||||
double d;
|
||||
};
|
||||
|
||||
int main() {}
|
||||
@ -168,5 +168,6 @@ struct ctrace_safe_object_frame {
|
||||
};
|
||||
size_t ctrace_safe_generate_raw_trace(ctrace_frame_ptr* buffer, size_t size, size_t skip, size_t max_depth);
|
||||
void ctrace_get_safe_object_frame(ctrace_frame_ptr address, ctrace_safe_object_frame* out);
|
||||
ctrace_bool can_signal_safe_unwind();
|
||||
ctrace_bool ctrace_can_signal_safe_unwind();
|
||||
ctrace_bool ctrace_can_get_safe_object_frame();
|
||||
```
|
||||
|
||||
@ -73,6 +73,7 @@ namespace cpptrace {
|
||||
void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
|
||||
// signal-safe
|
||||
bool can_signal_safe_unwind();
|
||||
bool can_get_safe_object_frame();
|
||||
}
|
||||
```
|
||||
|
||||
@ -104,6 +105,9 @@ only ways to do this safely as far as I can tell.
|
||||
`safe_generate_raw_trace` will just produce an empty trace and if object information can't be resolved in a signal-safe
|
||||
way then `get_safe_object_frame` will not populate fields beyond the `raw_address`.
|
||||
|
||||
`cpptrace::can_signal_safe_unwind` and `cpptrace::can_get_safe_object_frame` can be used to check for safe tracing
|
||||
support.
|
||||
|
||||
Currently the only back-end that can unwind safely is libunwind. Currently, the only way I know to get `dladdr`'s
|
||||
information in a signal-safe manner is `_dl_find_object`, which doesn't exist on macos (or windows of course). If anyone
|
||||
knows ways to do these safely on other platforms, I'd be much appreciative.
|
||||
|
||||
@ -31,6 +31,12 @@
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if __cplusplus >= 201703L
|
||||
#define CONSTEXPR_SINCE_CPP17 constexpr
|
||||
#else
|
||||
#define CONSTEXPR_SINCE_CPP17
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define CPPTRACE_FORCE_NO_INLINE __declspec(noinline)
|
||||
#else
|
||||
@ -95,37 +101,42 @@ namespace cpptrace {
|
||||
// use.
|
||||
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
|
||||
struct nullable {
|
||||
T raw_value;
|
||||
nullable& operator=(T value) {
|
||||
T raw_value = null_value();
|
||||
constexpr nullable() noexcept = default;
|
||||
constexpr nullable(T value) noexcept : raw_value(value) {}
|
||||
CONSTEXPR_SINCE_CPP17 nullable& operator=(T value) noexcept {
|
||||
raw_value = value;
|
||||
return *this;
|
||||
}
|
||||
bool has_value() const noexcept {
|
||||
return raw_value != (std::numeric_limits<T>::max)();
|
||||
constexpr bool has_value() const noexcept {
|
||||
return raw_value != null_value();
|
||||
}
|
||||
T& value() noexcept {
|
||||
CONSTEXPR_SINCE_CPP17 T& value() noexcept {
|
||||
return raw_value;
|
||||
}
|
||||
const T& value() const noexcept {
|
||||
constexpr const T& value() const noexcept {
|
||||
return raw_value;
|
||||
}
|
||||
T value_or(T alternative) const noexcept {
|
||||
constexpr T value_or(T alternative) const noexcept {
|
||||
return has_value() ? raw_value : alternative;
|
||||
}
|
||||
void swap(nullable& other) noexcept {
|
||||
CONSTEXPR_SINCE_CPP17 void swap(nullable& other) noexcept {
|
||||
std::swap(raw_value, other.raw_value);
|
||||
}
|
||||
void reset() noexcept {
|
||||
CONSTEXPR_SINCE_CPP17 void reset() noexcept {
|
||||
raw_value = (std::numeric_limits<T>::max)();
|
||||
}
|
||||
bool operator==(const nullable& other) const noexcept {
|
||||
constexpr bool operator==(const nullable& other) const noexcept {
|
||||
return raw_value == other.raw_value;
|
||||
}
|
||||
bool operator!=(const nullable& other) const noexcept {
|
||||
constexpr bool operator!=(const nullable& other) const noexcept {
|
||||
return raw_value != other.raw_value;
|
||||
}
|
||||
constexpr static T null_value() noexcept {
|
||||
return (std::numeric_limits<T>::max)();
|
||||
}
|
||||
constexpr static nullable null() noexcept {
|
||||
return { (std::numeric_limits<T>::max)() };
|
||||
return { null_value() };
|
||||
}
|
||||
};
|
||||
|
||||
@ -182,8 +193,6 @@ namespace cpptrace {
|
||||
inline const_iterator cbegin() const noexcept { return frames.cbegin(); }
|
||||
inline const_iterator cend() const noexcept { return frames.cend(); }
|
||||
private:
|
||||
void print(std::ostream& stream, bool color, bool newline_at_end, const char* header) const;
|
||||
void print_with_snippets(std::ostream& stream, bool color, bool newline_at_end, const char* header) const;
|
||||
friend void print_terminate_trace();
|
||||
};
|
||||
|
||||
@ -226,6 +235,7 @@ namespace cpptrace {
|
||||
// signal-safe
|
||||
CPPTRACE_EXPORT void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
|
||||
CPPTRACE_EXPORT bool can_signal_safe_unwind();
|
||||
CPPTRACE_EXPORT bool can_get_safe_object_frame();
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
74
include/cpptrace/formatting.hpp
Normal file
74
include/cpptrace/formatting.hpp
Normal file
@ -0,0 +1,74 @@
|
||||
#ifndef CPPTRACE_FORMATTING_HPP
|
||||
#define CPPTRACE_FORMATTING_HPP
|
||||
|
||||
#include <cpptrace/basic.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
namespace cpptrace {
|
||||
class CPPTRACE_EXPORT formatter {
|
||||
class impl;
|
||||
// can't be a std::unique_ptr due to msvc awfulness with dllimport/dllexport and https://stackoverflow.com/q/4145605/15675011
|
||||
impl* pimpl;
|
||||
|
||||
public:
|
||||
formatter();
|
||||
~formatter();
|
||||
|
||||
formatter(formatter&&);
|
||||
formatter(const formatter&);
|
||||
formatter& operator=(formatter&&);
|
||||
formatter& operator=(const formatter&);
|
||||
|
||||
formatter& header(std::string);
|
||||
enum class color_mode {
|
||||
always,
|
||||
none,
|
||||
automatic,
|
||||
};
|
||||
formatter& colors(color_mode);
|
||||
enum class address_mode {
|
||||
raw,
|
||||
object,
|
||||
none,
|
||||
};
|
||||
formatter& addresses(address_mode);
|
||||
enum class path_mode {
|
||||
// full path is used
|
||||
full,
|
||||
// only the file name is used
|
||||
basename,
|
||||
};
|
||||
formatter& paths(path_mode);
|
||||
formatter& snippets(bool);
|
||||
formatter& snippet_context(int);
|
||||
formatter& columns(bool);
|
||||
formatter& filtered_frame_placeholders(bool);
|
||||
formatter& filter(std::function<bool(const stacktrace_frame&)>);
|
||||
|
||||
std::string format(const stacktrace_frame&) const;
|
||||
std::string format(const stacktrace_frame&, bool color) const;
|
||||
|
||||
std::string format(const stacktrace&) const;
|
||||
std::string format(const stacktrace&, bool color) const;
|
||||
|
||||
void print(const stacktrace_frame&) const;
|
||||
void print(const stacktrace_frame&, bool color) const;
|
||||
void print(std::ostream&, const stacktrace_frame&) const;
|
||||
void print(std::ostream&, const stacktrace_frame&, bool color) const;
|
||||
void print(std::FILE*, const stacktrace_frame&) const;
|
||||
void print(std::FILE*, const stacktrace_frame&, bool color) const;
|
||||
|
||||
void print(const stacktrace&) const;
|
||||
void print(const stacktrace&, bool color) const;
|
||||
void print(std::ostream&, const stacktrace&) const;
|
||||
void print(std::ostream&, const stacktrace&, bool color) const;
|
||||
void print(std::FILE*, const stacktrace&) const;
|
||||
void print(std::FILE*, const stacktrace&, bool color) const;
|
||||
};
|
||||
|
||||
CPPTRACE_EXPORT const formatter& get_default_formatter();
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -43,6 +43,12 @@ namespace cpptrace {
|
||||
namespace experimental {
|
||||
CPPTRACE_EXPORT void set_cache_mode(cache_mode mode);
|
||||
}
|
||||
|
||||
// dwarf options
|
||||
namespace experimental {
|
||||
CPPTRACE_EXPORT void set_dwarf_resolver_line_table_cache_size(nullable<std::size_t> max_entries);
|
||||
CPPTRACE_EXPORT void set_dwarf_resolver_disable_aranges(bool disable);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -131,7 +131,8 @@ CTRACE_BEGIN_DEFINITIONS
|
||||
/* ctrace::safe: */
|
||||
CPPTRACE_EXPORT size_t ctrace_safe_generate_raw_trace(ctrace_frame_ptr* buffer, size_t size, size_t skip, size_t max_depth);
|
||||
CPPTRACE_EXPORT void ctrace_get_safe_object_frame(ctrace_frame_ptr address, ctrace_safe_object_frame* out);
|
||||
CPPTRACE_EXPORT ctrace_bool can_signal_safe_unwind(void);
|
||||
CPPTRACE_EXPORT ctrace_bool ctrace_can_signal_safe_unwind(void);
|
||||
CPPTRACE_EXPORT ctrace_bool ctrace_can_get_safe_object_frame(void);
|
||||
|
||||
/* ctrace::io: */
|
||||
CPPTRACE_EXPORT ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color);
|
||||
|
||||
@ -1,138 +0,0 @@
|
||||
#ifndef ELF_DEFS_HPP
|
||||
#define ELF_DEFS_HPP
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
|
||||
// https://man7.org/linux/man-pages/man5/elf.5.html
|
||||
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/elf.h
|
||||
|
||||
/* 32-bit ELF base types. */
|
||||
typedef std::uint32_t Elf32_Addr;
|
||||
typedef std::uint16_t Elf32_Half;
|
||||
typedef std::uint32_t Elf32_Off;
|
||||
typedef std::int32_t Elf32_Sword;
|
||||
typedef std::uint32_t Elf32_Word;
|
||||
|
||||
/* 64-bit ELF base types. */
|
||||
typedef std::uint64_t Elf64_Addr;
|
||||
typedef std::uint16_t Elf64_Half;
|
||||
typedef std::int16_t Elf64_SHalf;
|
||||
typedef std::uint64_t Elf64_Off;
|
||||
typedef std::int32_t Elf64_Sword;
|
||||
typedef std::uint32_t Elf64_Word;
|
||||
typedef std::uint64_t Elf64_Xword;
|
||||
typedef std::int64_t Elf64_Sxword;
|
||||
|
||||
#define PT_PHDR 6
|
||||
#define EI_NIDENT 16
|
||||
#define SHT_SYMTAB 2
|
||||
#define SHT_STRTAB 3
|
||||
#define SHT_DYNAMIC 6
|
||||
|
||||
typedef struct {
|
||||
std::uint32_t p_type;
|
||||
Elf32_Off p_offset;
|
||||
Elf32_Addr p_vaddr;
|
||||
Elf32_Addr p_paddr;
|
||||
std::uint32_t p_filesz;
|
||||
std::uint32_t p_memsz;
|
||||
std::uint32_t p_flags;
|
||||
std::uint32_t p_align;
|
||||
} Elf32_Phdr;
|
||||
|
||||
typedef struct {
|
||||
std::uint32_t p_type;
|
||||
std::uint32_t p_flags;
|
||||
Elf64_Off p_offset;
|
||||
Elf64_Addr p_vaddr;
|
||||
Elf64_Addr p_paddr;
|
||||
std::uint64_t p_filesz;
|
||||
std::uint64_t p_memsz;
|
||||
std::uint64_t p_align;
|
||||
} Elf64_Phdr;
|
||||
|
||||
typedef struct elf32_hdr {
|
||||
unsigned char e_ident[EI_NIDENT];
|
||||
Elf32_Half e_type;
|
||||
Elf32_Half e_machine;
|
||||
Elf32_Word e_version;
|
||||
Elf32_Addr e_entry; /* Entry point */
|
||||
Elf32_Off e_phoff;
|
||||
Elf32_Off e_shoff;
|
||||
Elf32_Word e_flags;
|
||||
Elf32_Half e_ehsize;
|
||||
Elf32_Half e_phentsize;
|
||||
Elf32_Half e_phnum;
|
||||
Elf32_Half e_shentsize;
|
||||
Elf32_Half e_shnum;
|
||||
Elf32_Half e_shstrndx;
|
||||
} Elf32_Ehdr;
|
||||
|
||||
typedef struct elf64_hdr {
|
||||
unsigned char e_ident[EI_NIDENT]; /* ELF "magic number" */
|
||||
Elf64_Half e_type;
|
||||
Elf64_Half e_machine;
|
||||
Elf64_Word e_version;
|
||||
Elf64_Addr e_entry; /* Entry point virtual address */
|
||||
Elf64_Off e_phoff; /* Program header table file offset */
|
||||
Elf64_Off e_shoff; /* Section header table file offset */
|
||||
Elf64_Word e_flags;
|
||||
Elf64_Half e_ehsize;
|
||||
Elf64_Half e_phentsize;
|
||||
Elf64_Half e_phnum;
|
||||
Elf64_Half e_shentsize;
|
||||
Elf64_Half e_shnum;
|
||||
Elf64_Half e_shstrndx;
|
||||
} Elf64_Ehdr;
|
||||
|
||||
typedef struct elf32_shdr {
|
||||
Elf32_Word sh_name;
|
||||
Elf32_Word sh_type;
|
||||
Elf32_Word sh_flags;
|
||||
Elf32_Addr sh_addr;
|
||||
Elf32_Off sh_offset;
|
||||
Elf32_Word sh_size;
|
||||
Elf32_Word sh_link;
|
||||
Elf32_Word sh_info;
|
||||
Elf32_Word sh_addralign;
|
||||
Elf32_Word sh_entsize;
|
||||
} Elf32_Shdr;
|
||||
|
||||
typedef struct elf64_shdr {
|
||||
Elf64_Word sh_name; /* Section name, index in string tbl */
|
||||
Elf64_Word sh_type; /* Type of section */
|
||||
Elf64_Xword sh_flags; /* Miscellaneous section attributes */
|
||||
Elf64_Addr sh_addr; /* Section virtual addr at execution */
|
||||
Elf64_Off sh_offset; /* Section file offset */
|
||||
Elf64_Xword sh_size; /* Size of section in bytes */
|
||||
Elf64_Word sh_link; /* Index of another section */
|
||||
Elf64_Word sh_info; /* Additional section information */
|
||||
Elf64_Xword sh_addralign; /* Section alignment */
|
||||
Elf64_Xword sh_entsize; /* Entry size if section holds table */
|
||||
} Elf64_Shdr;
|
||||
|
||||
typedef struct elf32_sym {
|
||||
Elf32_Word st_name;
|
||||
Elf32_Addr st_value;
|
||||
Elf32_Word st_size;
|
||||
unsigned char st_info;
|
||||
unsigned char st_other;
|
||||
Elf32_Half st_shndx;
|
||||
} Elf32_Sym;
|
||||
|
||||
typedef struct elf64_sym {
|
||||
Elf64_Word st_name; /* Symbol name, index in string tbl */
|
||||
unsigned char st_info; /* Type and binding attributes */
|
||||
unsigned char st_other; /* No defined meaning, 0 */
|
||||
Elf64_Half st_shndx; /* Associated section index */
|
||||
Elf64_Addr st_value; /* Value of the symbol */
|
||||
Elf64_Xword st_size; /* Associated symbol size */
|
||||
} Elf64_Sym;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -1,242 +0,0 @@
|
||||
#ifndef MACH_O_DEFS_HPP
|
||||
#define MACH_O_DEFS_HPP
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
// This file contains definitons from various apple headers licensed under APSL
|
||||
// https://opensource.apple.com/apsl/
|
||||
|
||||
typedef int32_t integer_t; // https://developer.apple.com/documentation/driverkit/integer_t
|
||||
typedef integer_t cpu_type_t; // https://developer.apple.com/documentation/kernel/cpu_type_t
|
||||
typedef integer_t cpu_subtype_t; // https://developer.apple.com/documentation/kernel/cpu_subtype_t
|
||||
|
||||
// https://github.com/apple-oss-distributions/xnu/blob/8d741a5de7ff4191bf97d57b9f54c2f6d4a15585/osfmk/mach/vm_prot.h#L75
|
||||
typedef int vm_prot_t; // https://developer.apple.com/documentation/kernel/vm_prot_t
|
||||
|
||||
// https://github.com/apple-oss-distributions/xnu/blob/8d741a5de7ff4191bf97d57b9f54c2f6d4a15585/EXTERNAL_HEADERS/mach-o/loader.h#L65
|
||||
#define MH_MAGIC 0xfeedface /* the mach magic number */
|
||||
#define MH_CIGAM 0xcefaedfe /* NXSwapInt(MH_MAGIC) */
|
||||
|
||||
struct mach_header {
|
||||
uint32_t magic; /* mach magic number identifier */
|
||||
cpu_type_t cputype; /* cpu specifier */
|
||||
cpu_subtype_t cpusubtype; /* machine specifier */
|
||||
uint32_t filetype; /* type of file */
|
||||
uint32_t ncmds; /* number of load commands */
|
||||
uint32_t sizeofcmds; /* the size of all the load commands */
|
||||
uint32_t flags; /* flags */
|
||||
};
|
||||
|
||||
/* Constant for the magic field of the mach_header_64 (64-bit architectures) */
|
||||
#define MH_MAGIC_64 0xfeedfacf /* the 64-bit mach magic number */
|
||||
#define MH_CIGAM_64 0xcffaedfe /* NXSwapInt(MH_MAGIC_64) */
|
||||
|
||||
struct mach_header_64 {
|
||||
uint32_t magic; /* mach magic number identifier */
|
||||
cpu_type_t cputype; /* cpu specifier */
|
||||
cpu_subtype_t cpusubtype; /* machine specifier */
|
||||
uint32_t filetype; /* type of file */
|
||||
uint32_t ncmds; /* number of load commands */
|
||||
uint32_t sizeofcmds; /* the size of all the load commands */
|
||||
uint32_t flags; /* flags */
|
||||
uint32_t reserved; /* reserved */
|
||||
};
|
||||
|
||||
// https://github.com/apple-oss-distributions/xnu/blob/8d741a5de7ff4191bf97d57b9f54c2f6d4a15585/EXTERNAL_HEADERS/mach-o/loader.h#L247C1-L250C3
|
||||
struct load_command {
|
||||
uint32_t cmd; /* type of load command */
|
||||
uint32_t cmdsize; /* total size of command in bytes */
|
||||
};
|
||||
|
||||
// https://github.com/apple-oss-distributions/xnu/blob/8d741a5de7ff4191bf97d57b9f54c2f6d4a15585/EXTERNAL_HEADERS/mach-o/fat.h#L48
|
||||
#define FAT_MAGIC 0xcafebabe
|
||||
#define FAT_CIGAM 0xbebafeca /* NXSwapLong(FAT_MAGIC) */
|
||||
#define FAT_MAGIC_64 0xcafebabf
|
||||
#define FAT_CIGAM_64 0xbfbafeca /* NXSwapLong(FAT_MAGIC_64) */
|
||||
|
||||
struct fat_header {
|
||||
uint32_t magic; /* FAT_MAGIC */
|
||||
uint32_t nfat_arch; /* number of structs that follow */
|
||||
};
|
||||
|
||||
struct fat_arch {
|
||||
cpu_type_t cputype; /* cpu specifier (int) */
|
||||
cpu_subtype_t cpusubtype; /* machine specifier (int) */
|
||||
uint32_t offset; /* file offset to this object file */
|
||||
uint32_t size; /* size of this object file */
|
||||
uint32_t align; /* alignment as a power of 2 */
|
||||
};
|
||||
|
||||
struct fat_arch_64 {
|
||||
cpu_type_t cputype; /* cpu specifier (int) */
|
||||
cpu_subtype_t cpusubtype; /* machine specifier (int) */
|
||||
uint64_t offset; /* file offset to this object file */
|
||||
uint64_t size; /* size of this object file */
|
||||
uint32_t align; /* alignment as a power of 2 */
|
||||
uint32_t reserved; /* reserved */
|
||||
};
|
||||
|
||||
// https://github.com/apple-oss-distributions/xnu/blob/8d741a5de7ff4191bf97d57b9f54c2f6d4a15585/EXTERNAL_HEADERS/mach-o/loader.h#L355
|
||||
struct segment_command { /* for 32-bit architectures */
|
||||
uint32_t cmd; /* LC_SEGMENT */
|
||||
uint32_t cmdsize; /* includes sizeof section structs */
|
||||
char segname[16]; /* segment name */
|
||||
uint32_t vmaddr; /* memory address of this segment */
|
||||
uint32_t vmsize; /* memory size of this segment */
|
||||
uint32_t fileoff; /* file offset of this segment */
|
||||
uint32_t filesize; /* amount to map from the file */
|
||||
vm_prot_t maxprot; /* maximum VM protection */
|
||||
vm_prot_t initprot; /* initial VM protection */
|
||||
uint32_t nsects; /* number of sections in segment */
|
||||
uint32_t flags; /* flags */
|
||||
};
|
||||
|
||||
struct segment_command_64 { /* for 64-bit architectures */
|
||||
uint32_t cmd; /* LC_SEGMENT_64 */
|
||||
uint32_t cmdsize; /* includes sizeof section_64 structs */
|
||||
char segname[16]; /* segment name */
|
||||
uint64_t vmaddr; /* memory address of this segment */
|
||||
uint64_t vmsize; /* memory size of this segment */
|
||||
uint64_t fileoff; /* file offset of this segment */
|
||||
uint64_t filesize; /* amount to map from the file */
|
||||
vm_prot_t maxprot; /* maximum VM protection */
|
||||
vm_prot_t initprot; /* initial VM protection */
|
||||
uint32_t nsects; /* number of sections in segment */
|
||||
uint32_t flags; /* flags */
|
||||
};
|
||||
|
||||
// https://github.com/apple-oss-distributions/xnu/blob/8d741a5de7ff4191bf97d57b9f54c2f6d4a15585/EXTERNAL_HEADERS/mach-o/loader.h#L868
|
||||
struct symtab_command {
|
||||
uint32_t cmd; /* LC_SYMTAB */
|
||||
uint32_t cmdsize; /* sizeof(struct symtab_command) */
|
||||
uint32_t symoff; /* symbol table offset */
|
||||
uint32_t nsyms; /* number of symbol table entries */
|
||||
uint32_t stroff; /* string table offset */
|
||||
uint32_t strsize; /* string table size in bytes */
|
||||
};
|
||||
|
||||
// https://github.com/apple-oss-distributions/xnu/blob/8d741a5de7ff4191bf97d57b9f54c2f6d4a15585/EXTERNAL_HEADERS/mach-o/nlist.h#L92
|
||||
struct nlist {
|
||||
union {
|
||||
// #ifndef __LP64__
|
||||
// char *n_name; /* for use when in-core */
|
||||
// #endif
|
||||
uint32_t n_strx; /* index into the string table */
|
||||
} n_un;
|
||||
uint8_t n_type; /* type flag, see below */
|
||||
uint8_t n_sect; /* section number or NO_SECT */
|
||||
int16_t n_desc; /* see <mach-o/stab.h> */
|
||||
uint32_t n_value; /* value of this symbol (or stab offset) */
|
||||
};
|
||||
|
||||
struct nlist_64 {
|
||||
union {
|
||||
uint32_t n_strx; /* index into the string table */
|
||||
} n_un;
|
||||
uint8_t n_type; /* type flag, see below */
|
||||
uint8_t n_sect; /* section number or NO_SECT */
|
||||
uint16_t n_desc; /* see <mach-o/stab.h> */
|
||||
uint64_t n_value; /* value of this symbol (or stab offset) */
|
||||
};
|
||||
|
||||
// https://github.com/apple-oss-distributions/xnu/blob/8d741a5de7ff4191bf97d57b9f54c2f6d4a15585/EXTERNAL_HEADERS/mach-o/loader.h#L263
|
||||
/* Constants for the cmd field of all load commands, the type */
|
||||
#define LC_SEGMENT 0x1 /* segment of this file to be mapped */
|
||||
#define LC_SYMTAB 0x2 /* link-edit stab symbol table info */
|
||||
#define LC_SYMSEG 0x3 /* link-edit gdb symbol table info (obsolete) */
|
||||
#define LC_THREAD 0x4 /* thread */
|
||||
#define LC_UNIXTHREAD 0x5 /* unix thread (includes a stack) */
|
||||
#define LC_LOADFVMLIB 0x6 /* load a specified fixed VM shared library */
|
||||
#define LC_IDFVMLIB 0x7 /* fixed VM shared library identification */
|
||||
#define LC_IDENT 0x8 /* object identification info (obsolete) */
|
||||
#define LC_FVMFILE 0x9 /* fixed VM file inclusion (internal use) */
|
||||
#define LC_PREPAGE 0xa /* prepage command (internal use) */
|
||||
#define LC_DYSYMTAB 0xb /* dynamic link-edit symbol table info */
|
||||
#define LC_LOAD_DYLIB 0xc /* load a dynamically linked shared library */
|
||||
#define LC_ID_DYLIB 0xd /* dynamically linked shared lib ident */
|
||||
#define LC_LOAD_DYLINKER 0xe /* load a dynamic linker */
|
||||
#define LC_ID_DYLINKER 0xf /* dynamic linker identification */
|
||||
#define LC_PREBOUND_DYLIB 0x10 /* modules prebound for a dynamically */
|
||||
/* linked shared library */
|
||||
#define LC_ROUTINES 0x11 /* image routines */
|
||||
#define LC_SUB_FRAMEWORK 0x12 /* sub framework */
|
||||
#define LC_SUB_UMBRELLA 0x13 /* sub umbrella */
|
||||
#define LC_SUB_CLIENT 0x14 /* sub client */
|
||||
#define LC_SUB_LIBRARY 0x15 /* sub library */
|
||||
#define LC_TWOLEVEL_HINTS 0x16 /* two-level namespace lookup hints */
|
||||
#define LC_PREBIND_CKSUM 0x17 /* prebind checksum */
|
||||
|
||||
#define LC_SEGMENT_64 0x19 /* 64-bit segment of this file to be */
|
||||
#define LC_ROUTINES_64 0x1a /* 64-bit image routines */
|
||||
#define LC_UUID 0x1b /* the uuid */
|
||||
|
||||
// https://github.com/apple-oss-distributions/xnu/blob/8d741a5de7ff4191bf97d57b9f54c2f6d4a15585/EXTERNAL_HEADERS/mach-o/nlist.h#L117
|
||||
#define N_STAB 0xe0 /* if any of these bits set, a symbolic debugging entry */
|
||||
#define N_PEXT 0x10 /* private external symbol bit */
|
||||
#define N_TYPE 0x0e /* mask for the type bits */
|
||||
#define N_EXT 0x01 /* external symbol bit, set for external symbols */
|
||||
|
||||
#define N_UNDF 0x0 /* undefined, n_sect == NO_SECT */
|
||||
#define N_ABS 0x2 /* absolute, n_sect == NO_SECT */
|
||||
#define N_SECT 0xe /* defined in section number n_sect */
|
||||
#define N_PBUD 0xc /* prebound undefined (defined in a dylib) */
|
||||
#define N_INDR 0xa /* indirect */
|
||||
|
||||
// https://github.com/apple-oss-distributions/xnu/blob/8d741a5de7ff4191bf97d57b9f54c2f6d4a15585/EXTERNAL_HEADERS/mach-o/stab.h#L87C1-L116C68
|
||||
#define N_GSYM 0x20 /* global symbol: name,,NO_SECT,type,0 */
|
||||
#define N_FNAME 0x22 /* procedure name (f77 kludge): name,,NO_SECT,0,0 */
|
||||
#define N_FUN 0x24 /* procedure: name,,n_sect,linenumber,address */
|
||||
#define N_STSYM 0x26 /* static symbol: name,,n_sect,type,address */
|
||||
#define N_LCSYM 0x28 /* .lcomm symbol: name,,n_sect,type,address */
|
||||
#define N_BNSYM 0x2e /* begin nsect sym: 0,,n_sect,0,address */
|
||||
#define N_AST 0x32 /* AST file path: name,,NO_SECT,0,0 */
|
||||
#define N_OPT 0x3c /* emitted with gcc2_compiled and in gcc source */
|
||||
#define N_RSYM 0x40 /* register sym: name,,NO_SECT,type,register */
|
||||
#define N_SLINE 0x44 /* src line: 0,,n_sect,linenumber,address */
|
||||
#define N_ENSYM 0x4e /* end nsect sym: 0,,n_sect,0,address */
|
||||
#define N_SSYM 0x60 /* structure elt: name,,NO_SECT,type,struct_offset */
|
||||
#define N_SO 0x64 /* source file name: name,,n_sect,0,address */
|
||||
#define N_OSO 0x66 /* object file name: name,,0,0,st_mtime */
|
||||
#define N_LSYM 0x80 /* local sym: name,,NO_SECT,type,offset */
|
||||
#define N_BINCL 0x82 /* include file beginning: name,,NO_SECT,0,sum */
|
||||
#define N_SOL 0x84 /* #included file name: name,,n_sect,0,address */
|
||||
#define N_PARAMS 0x86 /* compiler parameters: name,,NO_SECT,0,0 */
|
||||
#define N_VERSION 0x88 /* compiler version: name,,NO_SECT,0,0 */
|
||||
#define N_OLEVEL 0x8A /* compiler -O level: name,,NO_SECT,0,0 */
|
||||
#define N_PSYM 0xa0 /* parameter: name,,NO_SECT,type,offset */
|
||||
#define N_EINCL 0xa2 /* include file end: name,,NO_SECT,0,0 */
|
||||
#define N_ENTRY 0xa4 /* alternate entry: name,,n_sect,linenumber,address */
|
||||
#define N_LBRAC 0xc0 /* left bracket: 0,,NO_SECT,nesting level,address */
|
||||
#define N_EXCL 0xc2 /* deleted include file: name,,NO_SECT,0,sum */
|
||||
#define N_RBRAC 0xe0 /* right bracket: 0,,NO_SECT,nesting level,address */
|
||||
#define N_BCOMM 0xe2 /* begin common: name,,NO_SECT,0,0 */
|
||||
#define N_ECOMM 0xe4 /* end common: name,,n_sect,0,0 */
|
||||
#define N_ECOML 0xe8 /* end common (local name): 0,,n_sect,0,address */
|
||||
#define N_LENG 0xfe /* second stab entry with length information */
|
||||
|
||||
extern "C" {
|
||||
// There is exceedingly little evidence that this function actually exists. I think I discovered it through
|
||||
// https://github.com/ruby/ruby/blob/ff64806ae51c2813f0c6334c0c52082b027c255c/addr2line.c#L2359.
|
||||
// from MacOSX13.1.sdk/usr/include/crt_externs.h
|
||||
#ifdef __LP64__
|
||||
extern struct mach_header_64* _NSGetMachExecuteHeader(void);
|
||||
#else /* !__LP64__ */
|
||||
extern struct mach_header* _NSGetMachExecuteHeader(void);
|
||||
#endif /* __LP64__ */
|
||||
|
||||
// MacOSX13.1.sdk/usr/include/mach-o/arch.h
|
||||
extern struct fat_arch* NXFindBestFatArch(
|
||||
cpu_type_t cputype,
|
||||
cpu_subtype_t cpusubtype,
|
||||
struct fat_arch* fat_archs,
|
||||
uint32_t nfat_archs
|
||||
);
|
||||
extern struct fat_arch_64* NXFindBestFatArch_64(
|
||||
cpu_type_t cputype,
|
||||
cpu_subtype_t cpusubtype,
|
||||
struct fat_arch_64* fat_archs64,
|
||||
uint32_t nfat_archs
|
||||
); // __CCTOOLS_DEPRECATED_MSG("use macho_best_slice()")
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -1,4 +1,5 @@
|
||||
#include "binary/elf.hpp"
|
||||
#include "utils/optional.hpp"
|
||||
|
||||
#if IS_LINUX
|
||||
|
||||
@ -6,9 +7,11 @@
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "binary/defs/elf_defs.hpp"
|
||||
#include <elf.h>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
@ -88,16 +91,31 @@ namespace detail {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string elf::lookup_symbol(frame_ptr pc) {
|
||||
// TODO: Also search the SHT_DYNSYM at some point, maybe
|
||||
auto symtab_ = get_symtab();
|
||||
if(symtab_.is_error()) {
|
||||
return "";
|
||||
optional<std::string> elf::lookup_symbol(frame_ptr pc) {
|
||||
if(auto symtab = get_symtab()) {
|
||||
if(auto symbol = lookup_symbol(pc, symtab.unwrap_value())) {
|
||||
return symbol;
|
||||
}
|
||||
}
|
||||
if(auto dynamic_symtab = get_dynamic_symtab()) {
|
||||
if(auto symbol = lookup_symbol(pc, dynamic_symtab.unwrap_value())) {
|
||||
return symbol;
|
||||
}
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
optional<std::string> elf::lookup_symbol(frame_ptr pc, const optional<symtab_info>& maybe_symtab) {
|
||||
if(!maybe_symtab) {
|
||||
return nullopt;
|
||||
}
|
||||
auto& symtab = maybe_symtab.unwrap();
|
||||
if(symtab.strtab_link == SHN_UNDEF) {
|
||||
return nullopt;
|
||||
}
|
||||
auto& symtab = symtab_.unwrap_value();
|
||||
auto strtab_ = get_strtab(symtab.strtab_link);
|
||||
if(strtab_.is_error()) {
|
||||
return "";
|
||||
return nullopt;
|
||||
}
|
||||
auto& strtab = strtab_.unwrap_value();
|
||||
auto it = first_less_than_or_equal(
|
||||
@ -109,12 +127,49 @@ namespace detail {
|
||||
}
|
||||
);
|
||||
if(it == symtab.entries.end()) {
|
||||
return "";
|
||||
return nullopt;
|
||||
}
|
||||
if(pc <= it->st_value + it->st_size) {
|
||||
return strtab.data() + it->st_name;
|
||||
}
|
||||
return "";
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
Result<optional<std::vector<elf::symbol_entry>>, internal_error> elf::get_symtab_entries() {
|
||||
return resolve_symtab_entries(get_symtab());
|
||||
}
|
||||
Result<optional<std::vector<elf::symbol_entry>>, internal_error> elf::get_dynamic_symtab_entries() {
|
||||
return resolve_symtab_entries(get_dynamic_symtab());
|
||||
}
|
||||
|
||||
Result<optional<std::vector<elf::symbol_entry>>, internal_error> elf::resolve_symtab_entries(
|
||||
const Result<const optional<elf::symtab_info> &, internal_error>& symtab
|
||||
) {
|
||||
if(!symtab) {
|
||||
return symtab.unwrap_error();
|
||||
}
|
||||
if(!symtab.unwrap_value()) {
|
||||
return nullopt;
|
||||
}
|
||||
const auto& info = symtab.unwrap_value().unwrap();
|
||||
optional<const std::vector<char>&> strtab;
|
||||
if(info.strtab_link != SHN_UNDEF) {
|
||||
auto strtab_ = get_strtab(info.strtab_link);
|
||||
if(strtab_.is_error()) {
|
||||
return strtab_.unwrap_error();
|
||||
}
|
||||
strtab = strtab_.unwrap_value();
|
||||
}
|
||||
std::vector<symbol_entry> res;
|
||||
for(const auto& entry : info.entries) {
|
||||
res.push_back({
|
||||
strtab.has_value() ? strtab.unwrap().data() + entry.st_name : "<strtab error>",
|
||||
entry.st_shndx,
|
||||
entry.st_value,
|
||||
entry.st_size
|
||||
});
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type>
|
||||
@ -246,23 +301,66 @@ namespace detail {
|
||||
return entry.data;
|
||||
}
|
||||
|
||||
Result<const elf::symtab_info&, internal_error> elf::get_symtab() {
|
||||
Result<const optional<elf::symtab_info>&, internal_error> elf::get_symtab() {
|
||||
if(did_load_symtab) {
|
||||
return symtab;
|
||||
}
|
||||
if(tried_to_load_symtab) {
|
||||
return internal_error("previous strtab load failed {}", object_path);
|
||||
return internal_error("previous symtab load failed {}", object_path);
|
||||
}
|
||||
tried_to_load_symtab = true;
|
||||
if(is_64) {
|
||||
return get_symtab_impl<64>();
|
||||
auto res = get_symtab_impl<64>(false);
|
||||
if(res.has_value()) {
|
||||
symtab = std::move(res).unwrap_value();
|
||||
did_load_symtab = true;
|
||||
return symtab;
|
||||
} else {
|
||||
return get_symtab_impl<32>();
|
||||
return std::move(res).unwrap_error();
|
||||
}
|
||||
} else {
|
||||
auto res = get_symtab_impl<32>(false);
|
||||
if(res.has_value()) {
|
||||
symtab = std::move(res).unwrap_value();
|
||||
did_load_symtab = true;
|
||||
return symtab;
|
||||
} else {
|
||||
return std::move(res).unwrap_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result<const optional<elf::symtab_info>&, internal_error> elf::get_dynamic_symtab() {
|
||||
if(did_load_dynamic_symtab) {
|
||||
return dynamic_symtab;
|
||||
}
|
||||
if(tried_to_load_dynamic_symtab) {
|
||||
return internal_error("previous dynamic symtab load failed {}", object_path);
|
||||
}
|
||||
tried_to_load_dynamic_symtab = true;
|
||||
if(is_64) {
|
||||
auto res = get_symtab_impl<64>(true);
|
||||
if(res.has_value()) {
|
||||
dynamic_symtab = std::move(res).unwrap_value();
|
||||
did_load_dynamic_symtab = true;
|
||||
return dynamic_symtab;
|
||||
} else {
|
||||
return std::move(res).unwrap_error();
|
||||
}
|
||||
} else {
|
||||
auto res = get_symtab_impl<32>(true);
|
||||
if(res.has_value()) {
|
||||
dynamic_symtab = std::move(res).unwrap_value();
|
||||
did_load_dynamic_symtab = true;
|
||||
return dynamic_symtab;
|
||||
} else {
|
||||
return std::move(res).unwrap_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<std::size_t Bits>
|
||||
Result<const elf::symtab_info&, internal_error> elf::get_symtab_impl() {
|
||||
Result<optional<elf::symtab_info>, internal_error> elf::get_symtab_impl(bool dynamic) {
|
||||
// https://refspecs.linuxfoundation.org/elf/elf.pdf
|
||||
// page 66: only one sht_symtab and sht_dynsym section per file
|
||||
// page 32: symtab spec
|
||||
@ -273,8 +371,9 @@ namespace detail {
|
||||
return std::move(sections_).unwrap_error();
|
||||
}
|
||||
const auto& sections = sections_.unwrap_value();
|
||||
optional<symtab_info> symbol_table;
|
||||
for(const auto& section : sections) {
|
||||
if(section.sh_type == SHT_SYMTAB) {
|
||||
if(section.sh_type == (dynamic ? SHT_DYNSYM : SHT_SYMTAB)) {
|
||||
if(section.sh_entsize != sizeof(SymEntry)) {
|
||||
return internal_error("elf seems corrupted, sym entry mismatch {}", object_path);
|
||||
}
|
||||
@ -288,7 +387,8 @@ namespace detail {
|
||||
if(std::fread(buffer.data(), section.sh_entsize, buffer.size(), file) != buffer.size()) {
|
||||
return internal_error("fread error while loading elf symbol table");
|
||||
}
|
||||
symtab.entries.reserve(buffer.size());
|
||||
symbol_table = symtab_info{};
|
||||
symbol_table.unwrap().entries.reserve(buffer.size());
|
||||
for(const auto& entry : buffer) {
|
||||
symtab_entry normalized;
|
||||
normalized.st_name = byteswap_if_needed(entry.st_name);
|
||||
@ -297,19 +397,39 @@ namespace detail {
|
||||
normalized.st_shndx = byteswap_if_needed(entry.st_shndx);
|
||||
normalized.st_value = byteswap_if_needed(entry.st_value);
|
||||
normalized.st_size = byteswap_if_needed(entry.st_size);
|
||||
symtab.entries.push_back(normalized);
|
||||
symbol_table.unwrap().entries.push_back(normalized);
|
||||
}
|
||||
std::sort(symtab.entries.begin(), symtab.entries.end(), [] (const symtab_entry& a, const symtab_entry& b) {
|
||||
std::sort(
|
||||
symbol_table.unwrap().entries.begin(),
|
||||
symbol_table.unwrap().entries.end(),
|
||||
[] (const symtab_entry& a, const symtab_entry& b) {
|
||||
return a.st_value < b.st_value;
|
||||
});
|
||||
symtab.strtab_link = section.sh_link;
|
||||
did_load_symtab = true;
|
||||
return symtab;
|
||||
}
|
||||
);
|
||||
symbol_table.unwrap().strtab_link = section.sh_link;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// OK to not have a symbol table
|
||||
did_load_symtab = true;
|
||||
return symtab;
|
||||
return symbol_table;
|
||||
}
|
||||
|
||||
Result<maybe_owned<elf>, internal_error> open_elf_cached(const std::string& object_path) {
|
||||
if(get_cache_mode() == cache_mode::prioritize_memory) {
|
||||
return elf::open_elf(object_path)
|
||||
.transform([](elf&& obj) { return maybe_owned<elf>{detail::make_unique<elf>(std::move(obj))}; });
|
||||
} else {
|
||||
std::mutex m;
|
||||
std::unique_lock<std::mutex> lock{m};
|
||||
// TODO: Re-evaluate storing the error
|
||||
static std::unordered_map<std::string, Result<elf, internal_error>> cache;
|
||||
auto it = cache.find(object_path);
|
||||
if(it == cache.end()) {
|
||||
auto res = cache.insert({ object_path, elf::open_elf(object_path) });
|
||||
VERIFY(res.second);
|
||||
it = res.first;
|
||||
}
|
||||
return it->second.transform([](elf& obj) { return maybe_owned<elf>(&obj); });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,13 +62,19 @@ namespace detail {
|
||||
};
|
||||
bool tried_to_load_symtab = false;
|
||||
bool did_load_symtab = false;
|
||||
symtab_info symtab;
|
||||
optional<symtab_info> symtab;
|
||||
|
||||
bool tried_to_load_dynamic_symtab = false;
|
||||
bool did_load_dynamic_symtab = false;
|
||||
optional<symtab_info> dynamic_symtab;
|
||||
|
||||
elf(file_wrapper file, const std::string& object_path, bool is_little_endian, bool is_64);
|
||||
|
||||
public:
|
||||
static NODISCARD Result<elf, internal_error> open_elf(const std::string& object_path);
|
||||
|
||||
elf(elf&&) = default;
|
||||
|
||||
public:
|
||||
Result<std::uintptr_t, internal_error> get_module_image_base();
|
||||
private:
|
||||
@ -76,7 +82,23 @@ namespace detail {
|
||||
Result<std::uintptr_t, internal_error> get_module_image_base_impl();
|
||||
|
||||
public:
|
||||
std::string lookup_symbol(frame_ptr pc);
|
||||
optional<std::string> lookup_symbol(frame_ptr pc);
|
||||
private:
|
||||
optional<std::string> lookup_symbol(frame_ptr pc, const optional<symtab_info>& maybe_symtab);
|
||||
|
||||
public:
|
||||
struct symbol_entry {
|
||||
std::string st_name;
|
||||
uint16_t st_shndx;
|
||||
uint64_t st_value;
|
||||
uint64_t st_size;
|
||||
};
|
||||
Result<optional<std::vector<symbol_entry>>, internal_error> get_symtab_entries();
|
||||
Result<optional<std::vector<symbol_entry>>, internal_error> get_dynamic_symtab_entries();
|
||||
private:
|
||||
Result<optional<std::vector<symbol_entry>>, internal_error> resolve_symtab_entries(
|
||||
const Result<const optional<symtab_info> &, internal_error>&
|
||||
);
|
||||
|
||||
private:
|
||||
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
|
||||
@ -92,10 +114,13 @@ namespace detail {
|
||||
|
||||
Result<const std::vector<char>&, internal_error> get_strtab(std::size_t index);
|
||||
|
||||
Result<const symtab_info&, internal_error> get_symtab();
|
||||
Result<const optional<symtab_info>&, internal_error> get_symtab();
|
||||
Result<const optional<symtab_info>&, internal_error> get_dynamic_symtab();
|
||||
template<std::size_t Bits>
|
||||
Result<const symtab_info&, internal_error> get_symtab_impl();
|
||||
Result<optional<symtab_info>, internal_error> get_symtab_impl(bool dynamic);
|
||||
};
|
||||
|
||||
NODISCARD Result<maybe_owned<elf>, internal_error> open_elf_cached(const std::string& object_path);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
@ -19,20 +20,16 @@
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
|
||||
#include "binary/defs/mach-o-defs.hpp"
|
||||
|
||||
// #include <mach-o/loader.h>
|
||||
// #include <mach-o/swap.h>
|
||||
// #include <mach-o/fat.h>
|
||||
// #include <crt_externs.h>
|
||||
// #include <mach-o/nlist.h>
|
||||
// #include <mach-o/stab.h>
|
||||
// #include <mach-o/arch.h>
|
||||
#include <mach-o/loader.h>
|
||||
#include <mach-o/swap.h>
|
||||
#include <mach-o/fat.h>
|
||||
#include <crt_externs.h>
|
||||
#include <mach-o/nlist.h>
|
||||
#include <mach-o/stab.h>
|
||||
#include <mach-o/arch.h>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
// TODO: 64-bit fat??
|
||||
|
||||
bool is_mach_o(std::uint32_t magic) {
|
||||
switch(magic) {
|
||||
case FAT_MAGIC:
|
||||
@ -75,89 +72,27 @@ namespace detail {
|
||||
}
|
||||
|
||||
void swap_mach_header(mach_header_64& header) {
|
||||
header.magic = byteswap(header.magic);
|
||||
header.cputype = byteswap(header.cputype);
|
||||
header.cpusubtype = byteswap(header.cpusubtype);
|
||||
header.filetype = byteswap(header.filetype);
|
||||
header.ncmds = byteswap(header.ncmds);
|
||||
header.sizeofcmds = byteswap(header.sizeofcmds);
|
||||
header.flags = byteswap(header.flags);
|
||||
header.reserved = byteswap(header.reserved);
|
||||
swap_mach_header_64(&header, NX_UnknownByteOrder);
|
||||
}
|
||||
|
||||
void swap_mach_header(mach_header& header) {
|
||||
header.magic = byteswap(header.magic);
|
||||
header.cputype = byteswap(header.cputype);
|
||||
header.cpusubtype = byteswap(header.cpusubtype);
|
||||
header.filetype = byteswap(header.filetype);
|
||||
header.ncmds = byteswap(header.ncmds);
|
||||
header.sizeofcmds = byteswap(header.sizeofcmds);
|
||||
header.flags = byteswap(header.flags);
|
||||
swap_mach_header(&header, NX_UnknownByteOrder);
|
||||
}
|
||||
|
||||
void swap_segment_command(segment_command_64& segment) {
|
||||
segment.cmd = byteswap(segment.cmd);
|
||||
segment.cmdsize = byteswap(segment.cmdsize);
|
||||
segment.vmaddr = byteswap(segment.vmaddr);
|
||||
segment.vmsize = byteswap(segment.vmsize);
|
||||
segment.fileoff = byteswap(segment.fileoff);
|
||||
segment.filesize = byteswap(segment.filesize);
|
||||
segment.maxprot = byteswap(segment.maxprot);
|
||||
segment.initprot = byteswap(segment.initprot);
|
||||
segment.nsects = byteswap(segment.nsects);
|
||||
segment.flags = byteswap(segment.flags);
|
||||
swap_segment_command_64(&segment, NX_UnknownByteOrder);
|
||||
}
|
||||
|
||||
void swap_segment_command(segment_command& segment) {
|
||||
segment.cmd = byteswap(segment.cmd);
|
||||
segment.cmdsize = byteswap(segment.cmdsize);
|
||||
segment.vmaddr = byteswap(segment.vmaddr);
|
||||
segment.vmsize = byteswap(segment.vmsize);
|
||||
segment.fileoff = byteswap(segment.fileoff);
|
||||
segment.filesize = byteswap(segment.filesize);
|
||||
segment.maxprot = byteswap(segment.maxprot);
|
||||
segment.initprot = byteswap(segment.initprot);
|
||||
segment.nsects = byteswap(segment.nsects);
|
||||
segment.flags = byteswap(segment.flags);
|
||||
swap_segment_command(&segment, NX_UnknownByteOrder);
|
||||
}
|
||||
|
||||
void swap_symtab_command(symtab_command& symtab) {
|
||||
symtab.cmd = byteswap(symtab.cmd);
|
||||
symtab.cmdsize = byteswap(symtab.cmdsize);
|
||||
symtab.symoff = byteswap(symtab.symoff);
|
||||
symtab.nsyms = byteswap(symtab.nsyms);
|
||||
symtab.stroff = byteswap(symtab.stroff);
|
||||
symtab.strsize = byteswap(symtab.strsize);
|
||||
void swap_nlist(struct nlist& entry) {
|
||||
swap_nlist(&entry, 1, NX_UnknownByteOrder);
|
||||
}
|
||||
|
||||
void swap_nlist(nlist& entry) {
|
||||
entry.n_un.n_strx = byteswap(entry.n_un.n_strx);
|
||||
entry.n_desc = byteswap(entry.n_desc);
|
||||
entry.n_value = byteswap(entry.n_value);
|
||||
}
|
||||
|
||||
void swap_nlist(nlist_64& entry) {
|
||||
entry.n_un.n_strx = byteswap(entry.n_un.n_strx);
|
||||
entry.n_desc = byteswap(entry.n_desc);
|
||||
entry.n_value = byteswap(entry.n_value);
|
||||
}
|
||||
|
||||
void swap_load_command(load_command& command) {
|
||||
command.cmd = byteswap(command.cmd);
|
||||
command.cmdsize = byteswap(command.cmdsize);
|
||||
}
|
||||
|
||||
void swap_fat_header(fat_header& header) {
|
||||
header.magic = byteswap(header.magic);
|
||||
header.nfat_arch = byteswap(header.nfat_arch);
|
||||
}
|
||||
|
||||
void swap_fat_arch(fat_arch& arch) {
|
||||
arch.cputype = byteswap(arch.cputype);
|
||||
arch.cpusubtype = byteswap(arch.cpusubtype);
|
||||
arch.offset = byteswap(arch.offset);
|
||||
arch.size = byteswap(arch.size);
|
||||
arch.align = byteswap(arch.align);
|
||||
void swap_nlist(struct nlist_64& entry) {
|
||||
swap_nlist_64(&entry, 1, NX_UnknownByteOrder);
|
||||
}
|
||||
|
||||
#ifdef __LP64__
|
||||
@ -168,7 +103,7 @@ namespace detail {
|
||||
|
||||
Result<const char*, internal_error> mach_o::symtab_info_data::get_string(std::size_t index) const {
|
||||
if(stringtab && index < symtab.strsize) {
|
||||
return stringtab.get() + index;
|
||||
return stringtab.unwrap().data() + index;
|
||||
} else {
|
||||
return internal_error("can't retrieve symbol from symtab");
|
||||
}
|
||||
@ -352,7 +287,7 @@ namespace detail {
|
||||
}
|
||||
print_symbol_table_entry(
|
||||
entry.unwrap_value(),
|
||||
stringtab ? stringtab.unwrap_value().get() : nullptr,
|
||||
stringtab ? stringtab.unwrap_value().data() : nullptr,
|
||||
symtab.strsize,
|
||||
j
|
||||
);
|
||||
@ -482,10 +417,10 @@ namespace detail {
|
||||
return symbols.unwrap();
|
||||
}
|
||||
|
||||
std::string mach_o::lookup_symbol(frame_ptr pc) {
|
||||
optional<std::string> mach_o::lookup_symbol(frame_ptr pc) {
|
||||
auto symtab_ = symbol_table();
|
||||
if(!symtab_) {
|
||||
return "";
|
||||
return nullopt;
|
||||
}
|
||||
const auto& symtab = symtab_.unwrap_value();;
|
||||
auto it = first_less_than_or_equal(
|
||||
@ -497,7 +432,7 @@ namespace detail {
|
||||
}
|
||||
);
|
||||
if(it == symtab.end()) {
|
||||
return "";
|
||||
return nullopt;
|
||||
}
|
||||
ASSERT(pc >= it->address);
|
||||
// TODO: We subtracted one from the address so name + diff won't show up in the objdump, decide if desirable
|
||||
@ -557,7 +492,7 @@ namespace detail {
|
||||
}
|
||||
load_command& cmd = load_cmd.unwrap_value();
|
||||
if(should_swap()) {
|
||||
swap_load_command(cmd);
|
||||
swap_load_command(&cmd, NX_UnknownByteOrder);
|
||||
}
|
||||
load_commands.push_back({ actual_offset, cmd.cmd, cmd.cmdsize });
|
||||
actual_offset += cmd.cmdsize;
|
||||
@ -574,7 +509,7 @@ namespace detail {
|
||||
}
|
||||
fat_header& header = load_header.unwrap_value();
|
||||
if(should_swap()) {
|
||||
swap_fat_header(header);
|
||||
swap_fat_header(&header, NX_UnknownByteOrder);
|
||||
}
|
||||
// thread_local static struct LP(mach_header)* mhp = _NSGetMachExecuteHeader();
|
||||
// off_t arch_offset = (off_t)header_size;
|
||||
@ -612,7 +547,7 @@ namespace detail {
|
||||
}
|
||||
fat_arch& arch = load_arch.unwrap_value();
|
||||
if(should_swap()) {
|
||||
swap_fat_arch(arch);
|
||||
swap_fat_arch(&arch, 1, NX_UnknownByteOrder);
|
||||
}
|
||||
fat_arches.push_back(arch);
|
||||
arch_offset += arch_size;
|
||||
@ -680,7 +615,7 @@ namespace detail {
|
||||
symtab_command& symtab = load_symtab.unwrap_value();
|
||||
ASSERT(symtab.cmd == LC_SYMTAB);
|
||||
if(should_swap()) {
|
||||
swap_symtab_command(symtab);
|
||||
swap_symtab_command(&symtab, NX_UnknownByteOrder);
|
||||
}
|
||||
return symtab;
|
||||
}
|
||||
@ -707,12 +642,12 @@ namespace detail {
|
||||
return common;
|
||||
}
|
||||
|
||||
Result<std::unique_ptr<char[]>, internal_error> mach_o::load_string_table(std::uint32_t offset, std::uint32_t byte_count) const {
|
||||
std::unique_ptr<char[]> buffer(new char[byte_count + 1]);
|
||||
Result<std::vector<char>, internal_error> mach_o::load_string_table(std::uint32_t offset, std::uint32_t byte_count) const {
|
||||
std::vector<char> buffer(byte_count + 1);
|
||||
if(std::fseek(file, load_base + offset, SEEK_SET) != 0) {
|
||||
return internal_error("fseek error while loading mach-o symbol table");
|
||||
}
|
||||
if(std::fread(buffer.get(), sizeof(char), byte_count, file) != byte_count) {
|
||||
if(std::fread(buffer.data(), sizeof(char), byte_count, file) != byte_count) {
|
||||
return internal_error("fread error while loading mach-o symbol table");
|
||||
}
|
||||
buffer[byte_count] = 0; // just out of an abundance of caution
|
||||
@ -735,6 +670,27 @@ namespace detail {
|
||||
return is_fat_magic(magic.unwrap_value());
|
||||
}
|
||||
}
|
||||
|
||||
Result<maybe_owned<mach_o>, internal_error> open_mach_o_cached(const std::string& object_path) {
|
||||
if(get_cache_mode() == cache_mode::prioritize_memory) {
|
||||
return mach_o::open_mach_o(object_path)
|
||||
.transform([](mach_o&& obj) {
|
||||
return maybe_owned<mach_o>{detail::make_unique<mach_o>(std::move(obj))};
|
||||
});
|
||||
} else {
|
||||
std::mutex m;
|
||||
std::unique_lock<std::mutex> lock{m};
|
||||
// TODO: Re-evaluate storing the error
|
||||
static std::unordered_map<std::string, Result<mach_o, internal_error>> cache;
|
||||
auto it = cache.find(object_path);
|
||||
if(it == cache.end()) {
|
||||
auto res = cache.insert({ object_path, mach_o::open_mach_o(object_path) });
|
||||
VERIFY(res.second);
|
||||
it = res.first;
|
||||
}
|
||||
return it->second.transform([](mach_o& obj) { return maybe_owned<mach_o>(&obj); });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -13,7 +13,9 @@
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "binary/defs/mach-o-defs.hpp"
|
||||
#include <mach-o/arch.h>
|
||||
#include <mach-o/loader.h>
|
||||
#include <mach-o/nlist.h>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
@ -61,7 +63,7 @@ namespace detail {
|
||||
|
||||
struct symtab_info_data {
|
||||
symtab_command symtab;
|
||||
std::unique_ptr<char[]> stringtab;
|
||||
optional<std::vector<char>> stringtab;
|
||||
Result<const char*, internal_error> get_string(std::size_t index) const;
|
||||
};
|
||||
|
||||
@ -110,7 +112,7 @@ namespace detail {
|
||||
|
||||
Result<const std::vector<symbol_entry>&, internal_error> symbol_table();
|
||||
|
||||
std::string lookup_symbol(frame_ptr pc);
|
||||
optional<std::string> lookup_symbol(frame_ptr pc);
|
||||
|
||||
// produce information similar to dsymutil -dump-debug-map
|
||||
static void print_debug_map(const debug_map& debug_map);
|
||||
@ -129,12 +131,14 @@ namespace detail {
|
||||
template<std::size_t Bits>
|
||||
Result<nlist_64, internal_error> load_symtab_entry(std::uint32_t symbol_base, std::size_t index) const;
|
||||
|
||||
Result<std::unique_ptr<char[]>, internal_error> load_string_table(std::uint32_t offset, std::uint32_t byte_count) const;
|
||||
Result<std::vector<char>, internal_error> load_string_table(std::uint32_t offset, std::uint32_t byte_count) const;
|
||||
|
||||
bool should_swap() const;
|
||||
};
|
||||
|
||||
Result<bool, internal_error> macho_is_fat(const std::string& object_path);
|
||||
|
||||
NODISCARD Result<maybe_owned<mach_o>, internal_error> open_mach_o_cached(const std::string& object_path);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -30,12 +30,12 @@ namespace detail {
|
||||
if(it == cache.end()) {
|
||||
// arguably it'd be better to release the lock while computing this, but also arguably it's good to not
|
||||
// have two threads try to do the same computation
|
||||
auto obj = elf::open_elf(object_path);
|
||||
auto elf_object = open_elf_cached(object_path);
|
||||
// TODO: Cache the error
|
||||
if(!obj) {
|
||||
return obj.unwrap_error();
|
||||
if(!elf_object) {
|
||||
return elf_object.unwrap_error();
|
||||
}
|
||||
auto base = obj.unwrap_value().get_module_image_base();
|
||||
auto base = elf_object.unwrap_value()->get_module_image_base();
|
||||
if(base.is_error()) {
|
||||
return std::move(base).unwrap_error();
|
||||
}
|
||||
@ -57,12 +57,12 @@ namespace detail {
|
||||
if(it == cache.end()) {
|
||||
// arguably it'd be better to release the lock while computing this, but also arguably it's good to not
|
||||
// have two threads try to do the same computation
|
||||
auto obj = mach_o::open_mach_o(object_path);
|
||||
auto mach_o_object = open_mach_o_cached(object_path);
|
||||
// TODO: Cache the error
|
||||
if(!obj) {
|
||||
return obj.unwrap_error();
|
||||
if(!mach_o_object) {
|
||||
return mach_o_object.unwrap_error();
|
||||
}
|
||||
auto base = obj.unwrap_value().get_text_vmaddr();
|
||||
auto base = mach_o_object.unwrap_value()->get_text_vmaddr();
|
||||
if(!base) {
|
||||
return std::move(base).unwrap_error();
|
||||
}
|
||||
|
||||
@ -78,9 +78,11 @@ namespace detail {
|
||||
- reinterpret_cast<std::uintptr_t>(info.dli_fbase)
|
||||
+ base.unwrap_value();
|
||||
} else {
|
||||
if(!should_absorb_trace_exceptions()) {
|
||||
base.drop_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
#else
|
||||
@ -101,9 +103,11 @@ namespace detail {
|
||||
- reinterpret_cast<std::uintptr_t>(info.dli_fbase)
|
||||
+ base.unwrap_value();
|
||||
} else {
|
||||
if(!should_absorb_trace_exceptions()) {
|
||||
base.drop_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
#endif
|
||||
@ -146,8 +150,10 @@ namespace detail {
|
||||
- reinterpret_cast<std::uintptr_t>(handle)
|
||||
+ base.unwrap_value();
|
||||
} else {
|
||||
if(!should_absorb_trace_exceptions()) {
|
||||
base.drop_error();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::fprintf(stderr, "%s\n", std::system_error(GetLastError(), std::system_category()).what());
|
||||
}
|
||||
|
||||
@ -53,6 +53,10 @@ namespace detail {
|
||||
// may return the object that defines the function descriptor (and not the object that contains the code
|
||||
// implementing the function), or fail to find any object at all.
|
||||
}
|
||||
|
||||
bool has_get_safe_object_frame() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
@ -63,6 +67,10 @@ namespace detail {
|
||||
out->address_relative_to_object_start = 0;
|
||||
out->object_path[0] = 0;
|
||||
}
|
||||
|
||||
bool has_get_safe_object_frame() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -6,6 +6,8 @@
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
|
||||
|
||||
bool has_get_safe_object_frame();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
126
src/cpptrace.cpp
126
src/cpptrace.cpp
@ -1,4 +1,5 @@
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
#include <cpptrace/formatting.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
@ -9,6 +10,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "cpptrace/basic.hpp"
|
||||
#include "symbols/symbols.hpp"
|
||||
#include "unwind/unwind.hpp"
|
||||
#include "demangle/demangle.hpp"
|
||||
@ -18,6 +20,7 @@
|
||||
#include "binary/object.hpp"
|
||||
#include "binary/safe_dl.hpp"
|
||||
#include "snippets/snippet.hpp"
|
||||
#include "options.hpp"
|
||||
|
||||
namespace cpptrace {
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
@ -59,7 +62,7 @@ namespace cpptrace {
|
||||
try {
|
||||
std::vector<stacktrace_frame> trace = detail::resolve_frames(frames);
|
||||
for(auto& frame : trace) {
|
||||
frame.symbol = detail::demangle(frame.symbol);
|
||||
frame.symbol = detail::demangle(frame.symbol, true);
|
||||
}
|
||||
return {std::move(trace)};
|
||||
} catch(...) { // NOSONAR
|
||||
@ -106,7 +109,7 @@ namespace cpptrace {
|
||||
try {
|
||||
std::vector<stacktrace_frame> trace = detail::resolve_frames(frames);
|
||||
for(auto& frame : trace) {
|
||||
frame.symbol = detail::demangle(frame.symbol);
|
||||
frame.symbol = detail::demangle(frame.symbol, true);
|
||||
}
|
||||
return {std::move(trace)};
|
||||
} catch(...) { // NOSONAR
|
||||
@ -129,41 +132,12 @@ namespace cpptrace {
|
||||
return detail::get_frame_object_info(raw_address);
|
||||
}
|
||||
|
||||
static std::string frame_to_string(
|
||||
bool color,
|
||||
const stacktrace_frame& frame
|
||||
) {
|
||||
const auto reset = color ? RESET : "";
|
||||
const auto green = color ? GREEN : "";
|
||||
const auto yellow = color ? YELLOW : "";
|
||||
const auto blue = color ? BLUE : "";
|
||||
std::string str;
|
||||
if(frame.is_inline) {
|
||||
str += microfmt::format("{<{}}", 2 * sizeof(frame_ptr) + 2, "(inlined)");
|
||||
} else {
|
||||
str += microfmt::format("{}0x{>{}:0h}{}", blue, 2 * sizeof(frame_ptr), frame.raw_address, reset);
|
||||
}
|
||||
if(!frame.symbol.empty()) {
|
||||
str += microfmt::format(" in {}{}{}", yellow, frame.symbol, reset);
|
||||
}
|
||||
if(!frame.filename.empty()) {
|
||||
str += microfmt::format(" at {}{}{}", green, frame.filename, reset);
|
||||
if(frame.line.has_value()) {
|
||||
str += microfmt::format(":{}{}{}", blue, frame.line.value(), reset);
|
||||
if(frame.column.has_value()) {
|
||||
str += microfmt::format(":{}{}{}", blue, frame.column.value(), reset);
|
||||
}
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
std::string stacktrace_frame::to_string() const {
|
||||
return to_string(false);
|
||||
}
|
||||
|
||||
std::string stacktrace_frame::to_string(bool color) const {
|
||||
return frame_to_string(color, *this);
|
||||
return get_default_formatter().format(*this, color);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const stacktrace_frame& frame) {
|
||||
@ -195,89 +169,34 @@ namespace cpptrace {
|
||||
}
|
||||
|
||||
void stacktrace::print() const {
|
||||
print(std::cerr, true);
|
||||
get_default_formatter().print(*this);
|
||||
}
|
||||
|
||||
void stacktrace::print(std::ostream& stream) const {
|
||||
print(stream, true);
|
||||
get_default_formatter().print(stream, *this);
|
||||
}
|
||||
|
||||
void stacktrace::print(std::ostream& stream, bool color) const {
|
||||
print(stream, color, true, nullptr);
|
||||
get_default_formatter().print(stream, *this, color);
|
||||
}
|
||||
|
||||
static void print_frame(
|
||||
std::ostream& stream,
|
||||
bool color,
|
||||
unsigned frame_number_width,
|
||||
std::size_t counter,
|
||||
const stacktrace_frame& frame
|
||||
) {
|
||||
std::string line = microfmt::format("#{<{}} {}", frame_number_width, counter, frame.to_string(color));
|
||||
stream << line;
|
||||
}
|
||||
|
||||
void stacktrace::print(std::ostream& stream, bool color, bool newline_at_end, const char* header) const {
|
||||
if(
|
||||
color && (
|
||||
(&stream == &std::cout && isatty(stdout_fileno)) || (&stream == &std::cerr && isatty(stderr_fileno))
|
||||
)
|
||||
) {
|
||||
detail::enable_virtual_terminal_processing_if_needed();
|
||||
}
|
||||
stream << (header ? header : "Stack trace (most recent call first):") << '\n';
|
||||
std::size_t counter = 0;
|
||||
if(frames.empty()) {
|
||||
stream << "<empty trace>\n";
|
||||
return;
|
||||
}
|
||||
const auto frame_number_width = detail::n_digits(static_cast<int>(frames.size()) - 1);
|
||||
for(const auto& frame : frames) {
|
||||
print_frame(stream, color, frame_number_width, counter, frame);
|
||||
if(newline_at_end || &frame != &frames.back()) {
|
||||
stream << '\n';
|
||||
}
|
||||
counter++;
|
||||
namespace detail {
|
||||
const formatter& get_default_snippet_formatter() {
|
||||
static formatter snippet_formatter = formatter{}.snippets(true);
|
||||
return snippet_formatter;
|
||||
}
|
||||
}
|
||||
|
||||
void stacktrace::print_with_snippets() const {
|
||||
print_with_snippets(std::cerr, true);
|
||||
detail::get_default_snippet_formatter().print(*this);
|
||||
}
|
||||
|
||||
void stacktrace::print_with_snippets(std::ostream& stream) const {
|
||||
print_with_snippets(stream, true);
|
||||
detail::get_default_snippet_formatter().print(stream, *this);
|
||||
}
|
||||
|
||||
void stacktrace::print_with_snippets(std::ostream& stream, bool color) const {
|
||||
print_with_snippets(stream, color, true, nullptr);
|
||||
}
|
||||
|
||||
void stacktrace::print_with_snippets(std::ostream& stream, bool color, bool newline_at_end, const char* header) const {
|
||||
if(
|
||||
color && (
|
||||
(&stream == &std::cout && isatty(stdout_fileno)) || (&stream == &std::cerr && isatty(stderr_fileno))
|
||||
)
|
||||
) {
|
||||
detail::enable_virtual_terminal_processing_if_needed();
|
||||
}
|
||||
stream << (header ? header : "Stack trace (most recent call first):") << '\n';
|
||||
std::size_t counter = 0;
|
||||
if(frames.empty()) {
|
||||
stream << "<empty trace>" << '\n';
|
||||
return;
|
||||
}
|
||||
const auto frame_number_width = detail::n_digits(static_cast<int>(frames.size()) - 1);
|
||||
for(const auto& frame : frames) {
|
||||
print_frame(stream, color, frame_number_width, counter, frame);
|
||||
if(newline_at_end || &frame != &frames.back()) {
|
||||
stream << '\n';
|
||||
}
|
||||
if(frame.line.has_value() && !frame.filename.empty()) {
|
||||
stream << detail::get_snippet(frame.filename, frame.line.value(), 2, color);
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
detail::get_default_snippet_formatter().print(stream, *this, color);
|
||||
}
|
||||
|
||||
void stacktrace::clear() {
|
||||
@ -289,13 +208,12 @@ namespace cpptrace {
|
||||
}
|
||||
|
||||
std::string stacktrace::to_string(bool color) const {
|
||||
std::ostringstream oss;
|
||||
print(oss, color, false, nullptr);
|
||||
return std::move(oss).str();
|
||||
return get_default_formatter().format(*this, color);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const stacktrace& trace) {
|
||||
return stream << trace.to_string();
|
||||
get_default_formatter().print(stream, trace);
|
||||
return stream;
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
@ -393,7 +311,7 @@ namespace cpptrace {
|
||||
std::vector<frame_ptr> frames = detail::capture_frames(skip + 1, max_depth);
|
||||
std::vector<stacktrace_frame> trace = detail::resolve_frames(frames);
|
||||
for(auto& frame : trace) {
|
||||
frame.symbol = detail::demangle(frame.symbol);
|
||||
frame.symbol = detail::demangle(frame.symbol, true);
|
||||
}
|
||||
return {std::move(trace)};
|
||||
} catch(...) { // NOSONAR
|
||||
@ -415,4 +333,8 @@ namespace cpptrace {
|
||||
bool can_signal_safe_unwind() {
|
||||
return detail::has_safe_unwind();
|
||||
}
|
||||
|
||||
bool can_get_safe_object_frame() {
|
||||
return detail::has_get_safe_object_frame();
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,7 +107,7 @@ CTRACE_FORMAT_EPILOGUE
|
||||
new_frame.line = frame.line.value_or(invalid_pos);
|
||||
new_frame.column = frame.column.value_or(invalid_pos);
|
||||
new_frame.filename = generate_owning_string(frame.filename).data;
|
||||
new_frame.symbol = generate_owning_string(cpptrace::detail::demangle(frame.symbol)).data;
|
||||
new_frame.symbol = generate_owning_string(cpptrace::detail::demangle(frame.symbol, true)).data;
|
||||
new_frame.is_inline = ctrace_bool(frame.is_inline);
|
||||
return new_frame;
|
||||
}
|
||||
@ -310,10 +310,14 @@ extern "C" {
|
||||
cpptrace::get_safe_object_frame(address, reinterpret_cast<cpptrace::safe_object_frame*>(out));
|
||||
}
|
||||
|
||||
ctrace_bool can_signal_safe_unwind() {
|
||||
ctrace_bool ctrace_can_signal_safe_unwind() {
|
||||
return cpptrace::can_signal_safe_unwind();
|
||||
}
|
||||
|
||||
ctrace_bool ctrace_can_get_safe_object_frame(void) {
|
||||
return cpptrace::can_get_safe_object_frame();
|
||||
}
|
||||
|
||||
// ctrace::io:
|
||||
ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color) {
|
||||
if(!trace || !trace->frames) {
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
std::string demangle(const std::string&);
|
||||
std::string demangle(const std::string& name, bool check_prefix);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -13,10 +13,10 @@
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
std::string demangle(const std::string& name) {
|
||||
std::string demangle(const std::string& name, bool check_prefix) {
|
||||
// https://itanium-cxx-abi.github.io/cxx-abi/abi.html#demangler
|
||||
// check both _Z and __Z, apple prefixes all symbols with an underscore
|
||||
if(!(starts_with(name, "_Z") || starts_with(name, "__Z"))) {
|
||||
// Check both _Z and __Z, apple prefixes all symbols with an underscore
|
||||
if(check_prefix && !(starts_with(name, "_Z") || starts_with(name, "__Z"))) {
|
||||
return name;
|
||||
}
|
||||
// Apple clang demangles __Z just fine but gcc doesn't, so just offset the leading underscore
|
||||
@ -39,13 +39,14 @@ namespace detail {
|
||||
// it appears safe to pass nullptr for status however the docs don't explicitly say it's safe so I don't
|
||||
// want to rely on it
|
||||
int status;
|
||||
char* const demangled = abi::__cxa_demangle(to_demangle.get().c_str() + offset, nullptr, nullptr, &status);
|
||||
auto demangled = raii_wrap(
|
||||
abi::__cxa_demangle(to_demangle.get().c_str() + offset, nullptr, nullptr, &status),
|
||||
[] (char* str) { std::free(str); }
|
||||
);
|
||||
// demangled will always be nullptr on non-zero status, and if __cxa_demangle ever fails for any reason
|
||||
// we'll just quietly return the mangled name
|
||||
if(demangled) {
|
||||
// TODO: raii_wrap the char*?
|
||||
std::string str = demangled;
|
||||
std::free(demangled);
|
||||
if(demangled.get()) {
|
||||
std::string str = demangled.get();
|
||||
if(!rest.empty()) {
|
||||
str += rest;
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
std::string demangle(const std::string& name) {
|
||||
std::string demangle(const std::string& name, bool) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#ifdef CPPTRACE_DEMANGLE_WITH_WINAPI
|
||||
|
||||
#include "demangle/demangle.hpp"
|
||||
#include "platform/dbghelp_utils.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
@ -12,7 +13,9 @@
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
std::string demangle(const std::string& name) {
|
||||
std::string demangle(const std::string& name, bool) {
|
||||
// Dbghelp is is single-threaded, so acquire a lock.
|
||||
auto lock = get_dbghelp_lock();
|
||||
char buffer[500];
|
||||
auto ret = UnDecorateSymbolName(name.c_str(), buffer, sizeof(buffer) - 1, 0);
|
||||
if(ret == 0) {
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
|
||||
#include "platform/exception_type.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "options.hpp"
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
|
||||
353
src/formatting.cpp
Normal file
353
src/formatting.cpp
Normal file
@ -0,0 +1,353 @@
|
||||
#include <cpptrace/formatting.hpp>
|
||||
#include <cpptrace/utils.hpp>
|
||||
|
||||
#include "utils/optional.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "snippets/snippet.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
namespace cpptrace {
|
||||
class formatter::impl {
|
||||
struct {
|
||||
std::string header = "Stack trace (most recent call first):";
|
||||
color_mode color = color_mode::automatic;
|
||||
address_mode addresses = address_mode::raw;
|
||||
path_mode paths = path_mode::full;
|
||||
bool snippets = false;
|
||||
int context_lines = 2;
|
||||
bool columns = true;
|
||||
bool show_filtered_frames = true;
|
||||
std::function<bool(const stacktrace_frame&)> filter;
|
||||
} options;
|
||||
|
||||
public:
|
||||
void header(std::string header) {
|
||||
options.header = std::move(header);
|
||||
}
|
||||
void colors(formatter::color_mode mode) {
|
||||
options.color = mode;
|
||||
}
|
||||
void addresses(formatter::address_mode mode) {
|
||||
options.addresses = mode;
|
||||
}
|
||||
void paths(path_mode mode) {
|
||||
options.paths = mode;
|
||||
}
|
||||
void snippets(bool snippets) {
|
||||
options.snippets = snippets;
|
||||
}
|
||||
void snippet_context(int lines) {
|
||||
options.context_lines = lines;
|
||||
}
|
||||
void columns(bool columns) {
|
||||
options.columns = columns;
|
||||
}
|
||||
void filtered_frame_placeholders(bool show) {
|
||||
options.show_filtered_frames = show;
|
||||
}
|
||||
void filter(std::function<bool(const stacktrace_frame&)> filter) {
|
||||
options.filter = filter;
|
||||
}
|
||||
|
||||
std::string format(const stacktrace_frame& frame, detail::optional<bool> color_override = detail::nullopt) const {
|
||||
std::ostringstream oss;
|
||||
print_frame_inner(oss, frame, color_override.value_or(options.color == color_mode::always));
|
||||
return std::move(oss).str();
|
||||
}
|
||||
|
||||
std::string format(const stacktrace& trace, detail::optional<bool> color_override = detail::nullopt) const {
|
||||
std::ostringstream oss;
|
||||
print_internal(oss, trace, false, color_override);
|
||||
return std::move(oss).str();
|
||||
}
|
||||
|
||||
void print(const stacktrace_frame& frame, detail::optional<bool> color_override = detail::nullopt) const {
|
||||
print(std::cout, frame, color_override);
|
||||
}
|
||||
void print(
|
||||
std::ostream& stream,
|
||||
const stacktrace_frame& frame,
|
||||
detail::optional<bool> color_override = detail::nullopt
|
||||
) const {
|
||||
print_frame_internal(stream, frame, color_override);
|
||||
}
|
||||
void print(
|
||||
std::FILE* file,
|
||||
const stacktrace_frame& frame,
|
||||
detail::optional<bool> color_override = detail::nullopt
|
||||
) const {
|
||||
auto str = format(frame, color_override);
|
||||
std::fwrite(str.data(), 1, str.size(), file);
|
||||
}
|
||||
|
||||
void print(const stacktrace& trace, detail::optional<bool> color_override = detail::nullopt) const {
|
||||
print(std::cout, trace, color_override);
|
||||
}
|
||||
void print(
|
||||
std::ostream& stream,
|
||||
const stacktrace& trace,
|
||||
detail::optional<bool> color_override = detail::nullopt
|
||||
) const {
|
||||
print_internal(stream, trace, true, color_override);
|
||||
}
|
||||
void print(
|
||||
std::FILE* file,
|
||||
const stacktrace& trace,
|
||||
detail::optional<bool> color_override = detail::nullopt
|
||||
) const {
|
||||
auto str = format(trace, color_override);
|
||||
std::fwrite(str.data(), 1, str.size(), file);
|
||||
}
|
||||
|
||||
private:
|
||||
bool stream_is_tty(std::ostream& stream) const {
|
||||
// not great, but it'll have to do
|
||||
return (&stream == &std::cout && isatty(stdout_fileno))
|
||||
|| (&stream == &std::cerr && isatty(stderr_fileno));
|
||||
}
|
||||
|
||||
void maybe_ensure_virtual_terminal_processing(std::ostream& stream, bool color) const {
|
||||
if(color && stream_is_tty(stream)) {
|
||||
detail::enable_virtual_terminal_processing_if_needed();
|
||||
}
|
||||
}
|
||||
|
||||
bool should_do_color(std::ostream& stream, detail::optional<bool> color_override) const {
|
||||
bool do_color = options.color == color_mode::always || color_override.value_or(false);
|
||||
if(
|
||||
(options.color == color_mode::automatic || options.color == color_mode::always) &&
|
||||
(!color_override || color_override.unwrap() != false) &&
|
||||
stream_is_tty(stream)
|
||||
) {
|
||||
detail::enable_virtual_terminal_processing_if_needed();
|
||||
do_color = true;
|
||||
}
|
||||
return do_color;
|
||||
}
|
||||
|
||||
void print_internal(std::ostream& stream, const stacktrace& trace, bool newline_at_end, detail::optional<bool> color_override) const {
|
||||
bool do_color = should_do_color(stream, color_override);
|
||||
maybe_ensure_virtual_terminal_processing(stream, do_color);
|
||||
print_internal(stream, trace, newline_at_end, do_color);
|
||||
}
|
||||
|
||||
void print_internal(std::ostream& stream, const stacktrace& trace, bool newline_at_end, bool color) const {
|
||||
if(!options.header.empty()) {
|
||||
stream << options.header << '\n';
|
||||
}
|
||||
std::size_t counter = 0;
|
||||
const auto& frames = trace.frames;
|
||||
if(frames.empty()) {
|
||||
stream << "<empty trace>\n";
|
||||
return;
|
||||
}
|
||||
const auto frame_number_width = detail::n_digits(static_cast<int>(frames.size()) - 1);
|
||||
for(const auto& frame : frames) {
|
||||
if(options.filter && !options.filter(frame)) {
|
||||
if(!options.show_filtered_frames) {
|
||||
counter++;
|
||||
continue;
|
||||
}
|
||||
print_placeholder_frame(stream, frame_number_width, counter);
|
||||
} else {
|
||||
print_frame_internal(stream, frame, color, frame_number_width, counter);
|
||||
if(frame.line.has_value() && !frame.filename.empty() && options.snippets) {
|
||||
auto snippet = detail::get_snippet(
|
||||
frame.filename,
|
||||
frame.line.value(),
|
||||
options.context_lines,
|
||||
color
|
||||
);
|
||||
if(!snippet.empty()) {
|
||||
stream << '\n';
|
||||
stream << snippet;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(newline_at_end || &frame != &frames.back()) {
|
||||
stream << '\n';
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
|
||||
void print_frame_internal(
|
||||
std::ostream& stream,
|
||||
const stacktrace_frame& frame,
|
||||
bool color,
|
||||
unsigned frame_number_width,
|
||||
std::size_t counter
|
||||
) const {
|
||||
microfmt::print(stream, "#{<{}} ", frame_number_width, counter);
|
||||
print_frame_inner(stream, frame, color);
|
||||
}
|
||||
|
||||
void print_placeholder_frame(std::ostream& stream, unsigned frame_number_width, std::size_t counter) const {
|
||||
microfmt::print(stream, "#{<{}} (filtered)", frame_number_width, counter);
|
||||
}
|
||||
|
||||
void print_frame_internal(
|
||||
std::ostream& stream,
|
||||
const stacktrace_frame& frame,
|
||||
detail::optional<bool> color_override
|
||||
) const {
|
||||
bool do_color = should_do_color(stream, color_override);
|
||||
maybe_ensure_virtual_terminal_processing(stream, do_color);
|
||||
print_frame_inner(stream, frame, do_color);
|
||||
}
|
||||
|
||||
void print_frame_inner(std::ostream& stream, const stacktrace_frame& frame, bool color) const {
|
||||
const auto reset = color ? RESET : "";
|
||||
const auto green = color ? GREEN : "";
|
||||
const auto yellow = color ? YELLOW : "";
|
||||
const auto blue = color ? BLUE : "";
|
||||
if(frame.is_inline) {
|
||||
microfmt::print(stream, "{<{}} ", 2 * sizeof(frame_ptr) + 2, "(inlined)");
|
||||
} else if(options.addresses != address_mode::none) {
|
||||
auto address = options.addresses == address_mode::raw ? frame.raw_address : frame.object_address;
|
||||
microfmt::print(stream, "{}0x{>{}:0h}{} ", blue, 2 * sizeof(frame_ptr), address, reset);
|
||||
}
|
||||
if(!frame.symbol.empty()) {
|
||||
microfmt::print(stream, "in {}{}{}", yellow, frame.symbol, reset);
|
||||
}
|
||||
if(!frame.filename.empty()) {
|
||||
microfmt::print(
|
||||
stream,
|
||||
"{}at {}{}{}",
|
||||
frame.symbol.empty() ? "" : " ",
|
||||
green,
|
||||
options.paths == path_mode::full ? frame.filename : detail::basename(frame.filename, true),
|
||||
reset
|
||||
);
|
||||
if(frame.line.has_value()) {
|
||||
microfmt::print(stream, ":{}{}{}", blue, frame.line.value(), reset);
|
||||
if(frame.column.has_value() && options.columns) {
|
||||
microfmt::print(stream, ":{}{}{}", blue, frame.column.value(), reset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
formatter::formatter() : pimpl(new impl) {}
|
||||
formatter::~formatter() {
|
||||
delete pimpl;
|
||||
}
|
||||
|
||||
formatter::formatter(formatter&& other) : pimpl(detail::exchange(other.pimpl, nullptr)) {}
|
||||
formatter::formatter(const formatter& other) : pimpl(new impl(*other.pimpl)) {}
|
||||
formatter& formatter::operator=(formatter&& other) {
|
||||
if(pimpl) {
|
||||
delete pimpl;
|
||||
}
|
||||
pimpl = detail::exchange(other.pimpl, nullptr);
|
||||
return *this;
|
||||
}
|
||||
formatter& formatter::operator=(const formatter& other) {
|
||||
if(pimpl) {
|
||||
delete pimpl;
|
||||
}
|
||||
pimpl = new impl(*other.pimpl);
|
||||
return *this;
|
||||
}
|
||||
|
||||
formatter& formatter::header(std::string header) {
|
||||
pimpl->header(std::move(header));
|
||||
return *this;
|
||||
}
|
||||
formatter& formatter::colors(color_mode mode) {
|
||||
pimpl->colors(mode);
|
||||
return *this;
|
||||
}
|
||||
formatter& formatter::addresses(address_mode mode) {
|
||||
pimpl->addresses(mode);
|
||||
return *this;
|
||||
}
|
||||
formatter& formatter::paths(path_mode mode) {
|
||||
pimpl->paths(mode);
|
||||
return *this;
|
||||
}
|
||||
formatter& formatter::snippets(bool snippets) {
|
||||
pimpl->snippets(snippets);
|
||||
return *this;
|
||||
}
|
||||
formatter& formatter::snippet_context(int lines) {
|
||||
pimpl->snippet_context(lines);
|
||||
return *this;
|
||||
}
|
||||
formatter& formatter::columns(bool columns) {
|
||||
pimpl->columns(columns);
|
||||
return *this;
|
||||
}
|
||||
formatter& formatter::filtered_frame_placeholders(bool show) {
|
||||
pimpl->filtered_frame_placeholders(show);
|
||||
return *this;
|
||||
}
|
||||
formatter& formatter::filter(std::function<bool(const stacktrace_frame&)> filter) {
|
||||
pimpl->filter(std::move(filter));
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::string formatter::format(const stacktrace_frame& frame) const {
|
||||
return pimpl->format(frame);
|
||||
}
|
||||
std::string formatter::format(const stacktrace_frame& frame, bool color) const {
|
||||
return pimpl->format(frame, color);
|
||||
}
|
||||
|
||||
std::string formatter::format(const stacktrace& trace) const {
|
||||
return pimpl->format(trace);
|
||||
}
|
||||
std::string formatter::format(const stacktrace& trace, bool color) const {
|
||||
return pimpl->format(trace, color);
|
||||
}
|
||||
|
||||
void formatter::print(const stacktrace& trace) const {
|
||||
pimpl->print(trace);
|
||||
}
|
||||
void formatter::print(const stacktrace& trace, bool color) const {
|
||||
pimpl->print(trace, color);
|
||||
}
|
||||
void formatter::print(std::ostream& stream, const stacktrace& trace) const {
|
||||
pimpl->print(stream, trace);
|
||||
}
|
||||
void formatter::print(std::ostream& stream, const stacktrace& trace, bool color) const {
|
||||
pimpl->print(stream, trace, color);
|
||||
}
|
||||
void formatter::print(std::FILE* file, const stacktrace& trace) const {
|
||||
pimpl->print(file, trace);
|
||||
}
|
||||
void formatter::print(std::FILE* file, const stacktrace& trace, bool color) const {
|
||||
pimpl->print(file, trace, color);
|
||||
}
|
||||
|
||||
void formatter::print(const stacktrace_frame& frame) const {
|
||||
pimpl->print(frame);
|
||||
}
|
||||
void formatter::print(const stacktrace_frame& frame, bool color) const {
|
||||
pimpl->print(frame, color);
|
||||
}
|
||||
void formatter::print(std::ostream& stream, const stacktrace_frame& frame) const {
|
||||
pimpl->print(stream, frame);
|
||||
}
|
||||
void formatter::print(std::ostream& stream, const stacktrace_frame& frame, bool color) const {
|
||||
pimpl->print(stream, frame, color);
|
||||
}
|
||||
void formatter::print(std::FILE* file, const stacktrace_frame& frame) const {
|
||||
pimpl->print(file, frame);
|
||||
}
|
||||
void formatter::print(std::FILE* file, const stacktrace_frame& frame, bool color) const {
|
||||
pimpl->print(file, frame, color);
|
||||
}
|
||||
|
||||
const formatter& get_default_formatter() {
|
||||
static formatter formatter;
|
||||
return formatter;
|
||||
}
|
||||
}
|
||||
@ -1,59 +0,0 @@
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#if IS_WINDOWS
|
||||
|
||||
#include "platform/dbghelp_syminit_manager.hpp"
|
||||
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/microfmt.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <dbghelp.h>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
|
||||
dbghelp_syminit_manager::~dbghelp_syminit_manager() {
|
||||
for(auto kvp : cache) {
|
||||
if(!SymCleanup(kvp.second)) {
|
||||
ASSERT(false, microfmt::format("Cpptrace SymCleanup failed with code {}\n", GetLastError()).c_str());
|
||||
}
|
||||
if(!CloseHandle(kvp.second)) {
|
||||
ASSERT(false, microfmt::format("Cpptrace CloseHandle failed with code {}\n", GetLastError()).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HANDLE dbghelp_syminit_manager::init(HANDLE proc) {
|
||||
auto itr = cache.find(proc);
|
||||
|
||||
if(itr != cache.end()) {
|
||||
return itr->second;
|
||||
}
|
||||
HANDLE duplicated_handle = nullptr;
|
||||
if(!DuplicateHandle(proc, proc, proc, &duplicated_handle, 0, FALSE, DUPLICATE_SAME_ACCESS)) {
|
||||
throw internal_error("DuplicateHandle failed {}", GetLastError());
|
||||
}
|
||||
|
||||
if(!SymInitialize(duplicated_handle, NULL, TRUE)) {
|
||||
throw internal_error("SymInitialize failed {}", GetLastError());
|
||||
}
|
||||
cache[proc] = duplicated_handle;
|
||||
return duplicated_handle;
|
||||
}
|
||||
|
||||
// Thread-safety: Must only be called from symbols_with_dbghelp while the dbghelp_lock lock is held
|
||||
dbghelp_syminit_manager& get_syminit_manager() {
|
||||
static dbghelp_syminit_manager syminit_manager;
|
||||
return syminit_manager;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -1,22 +0,0 @@
|
||||
#ifndef DBGHELP_SYMINIT_MANAGER_HPP
|
||||
#define DBGHELP_SYMINIT_MANAGER_HPP
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
struct dbghelp_syminit_manager {
|
||||
// The set below contains Windows `HANDLE` objects, `void*` is used to avoid
|
||||
// including the (expensive) Windows header here
|
||||
std::unordered_map<void*, void*> cache;
|
||||
|
||||
~dbghelp_syminit_manager();
|
||||
void* init(void* proc);
|
||||
};
|
||||
|
||||
// Thread-safety: Must only be called from symbols_with_dbghelp while the dbghelp_lock lock is held
|
||||
dbghelp_syminit_manager& get_syminit_manager();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
149
src/platform/dbghelp_utils.cpp
Normal file
149
src/platform/dbghelp_utils.cpp
Normal file
@ -0,0 +1,149 @@
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#if IS_WINDOWS
|
||||
|
||||
#include "platform/dbghelp_utils.hpp"
|
||||
|
||||
#if defined(CPPTRACE_UNWIND_WITH_DBGHELP) \
|
||||
|| defined(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP) \
|
||||
|| defined(CPPTRACE_DEMANGLE_WITH_WINAPI)
|
||||
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/microfmt.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <dbghelp.h>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
dbghelp_syminit_info::dbghelp_syminit_info(void* handle, bool should_sym_cleanup, bool should_close_handle)
|
||||
: handle(handle), should_sym_cleanup(should_sym_cleanup), should_close_handle(should_close_handle) {}
|
||||
|
||||
dbghelp_syminit_info::~dbghelp_syminit_info() {
|
||||
release();
|
||||
}
|
||||
|
||||
void dbghelp_syminit_info::release() {
|
||||
if(!handle) {
|
||||
return;
|
||||
}
|
||||
if(should_sym_cleanup) {
|
||||
if(!SymCleanup(handle)) {
|
||||
throw internal_error("SymCleanup failed with code {}\n", GetLastError());
|
||||
}
|
||||
}
|
||||
if(should_close_handle) {
|
||||
if(!CloseHandle(handle)) {
|
||||
throw internal_error("CloseHandle failed with code {}\n", GetLastError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dbghelp_syminit_info dbghelp_syminit_info::make_not_owned(void* handle) {
|
||||
return dbghelp_syminit_info(handle, false, false);
|
||||
}
|
||||
|
||||
dbghelp_syminit_info dbghelp_syminit_info::make_owned(void* handle, bool should_close_handle) {
|
||||
return dbghelp_syminit_info(handle, true, should_close_handle);
|
||||
}
|
||||
|
||||
dbghelp_syminit_info::dbghelp_syminit_info(dbghelp_syminit_info&& other) {
|
||||
handle = exchange(other.handle, nullptr);
|
||||
should_sym_cleanup = other.should_sym_cleanup;
|
||||
should_close_handle = other.should_close_handle;
|
||||
}
|
||||
|
||||
dbghelp_syminit_info& dbghelp_syminit_info::operator=(dbghelp_syminit_info&& other) {
|
||||
release();
|
||||
handle = exchange(other.handle, nullptr);
|
||||
should_sym_cleanup = other.should_sym_cleanup;
|
||||
should_close_handle = other.should_close_handle;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void* dbghelp_syminit_info::get_process_handle() const {
|
||||
return handle;
|
||||
}
|
||||
|
||||
dbghelp_syminit_info dbghelp_syminit_info::make_non_owning_view() const {
|
||||
return make_not_owned(handle);
|
||||
}
|
||||
|
||||
std::unordered_map<HANDLE, dbghelp_syminit_info>& get_syminit_cache() {
|
||||
static std::unordered_map<HANDLE, dbghelp_syminit_info> syminit_cache;
|
||||
return syminit_cache;
|
||||
}
|
||||
|
||||
dbghelp_syminit_info ensure_syminit() {
|
||||
auto lock = get_dbghelp_lock(); // locking around the entire access of the cache unordered_map
|
||||
HANDLE proc = GetCurrentProcess();
|
||||
if(get_cache_mode() == cache_mode::prioritize_speed) {
|
||||
auto& syminit_cache = get_syminit_cache();
|
||||
auto it = syminit_cache.find(proc);
|
||||
if(it != syminit_cache.end()) {
|
||||
return it->second.make_non_owning_view();
|
||||
}
|
||||
}
|
||||
|
||||
auto duplicated_handle = raii_wrap<void*>(nullptr, [] (void* handle) {
|
||||
if(handle) {
|
||||
if(!CloseHandle(handle)) {
|
||||
throw internal_error("CloseHandle failed with code {}\n", GetLastError());
|
||||
}
|
||||
}
|
||||
});
|
||||
// https://github.com/jeremy-rifkin/cpptrace/issues/204
|
||||
// https://github.com/jeremy-rifkin/cpptrace/pull/206
|
||||
// https://learn.microsoft.com/en-us/windows/win32/debug/initializing-the-symbol-handler
|
||||
// Apparently duplicating the process handle is the idiomatic thing to do and this avoids issues of
|
||||
// SymInitialize being called twice.
|
||||
// DuplicateHandle requires the PROCESS_DUP_HANDLE access right. If for some reason DuplicateHandle we fall back
|
||||
// to calling SymInitialize on the process handle.
|
||||
optional<DWORD> maybe_duplicate_handle_error_code;
|
||||
if(!DuplicateHandle(proc, proc, proc, &duplicated_handle.get(), 0, FALSE, DUPLICATE_SAME_ACCESS)) {
|
||||
maybe_duplicate_handle_error_code = GetLastError();
|
||||
}
|
||||
if(!SymInitialize(maybe_duplicate_handle_error_code ? proc : duplicated_handle.get(), NULL, TRUE)) {
|
||||
if(maybe_duplicate_handle_error_code) {
|
||||
throw internal_error(
|
||||
"SymInitialize failed with error code {} after DuplicateHandle failed with error code {}",
|
||||
GetLastError(),
|
||||
maybe_duplicate_handle_error_code.unwrap()
|
||||
);
|
||||
} else {
|
||||
throw internal_error("SymInitialize failed with error code {}", GetLastError());
|
||||
}
|
||||
}
|
||||
|
||||
auto info = dbghelp_syminit_info::make_owned(
|
||||
maybe_duplicate_handle_error_code ? proc : exchange(duplicated_handle.get(), nullptr),
|
||||
!maybe_duplicate_handle_error_code
|
||||
);
|
||||
// either cache and return a view or return the owning wrapper
|
||||
if(get_cache_mode() == cache_mode::prioritize_speed) {
|
||||
auto& syminit_cache = get_syminit_cache();
|
||||
auto pair = syminit_cache.insert({proc, std::move(info)});
|
||||
VERIFY(pair.second);
|
||||
return pair.first->second.make_non_owning_view();
|
||||
} else {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
std::recursive_mutex dbghelp_lock;
|
||||
|
||||
std::unique_lock<std::recursive_mutex> get_dbghelp_lock() {
|
||||
return std::unique_lock<std::recursive_mutex>{dbghelp_lock};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
50
src/platform/dbghelp_utils.hpp
Normal file
50
src/platform/dbghelp_utils.hpp
Normal file
@ -0,0 +1,50 @@
|
||||
#ifndef DBGHELP_UTILS_HPP
|
||||
#define DBGHELP_UTILS_HPP
|
||||
|
||||
#if defined(CPPTRACE_UNWIND_WITH_DBGHELP) \
|
||||
|| defined(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP) \
|
||||
|| defined(CPPTRACE_DEMANGLE_WITH_WINAPI)
|
||||
|
||||
#include "utils/common.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <mutex>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
class dbghelp_syminit_info {
|
||||
// `void*` is used to avoid including the (expensive) windows.h header here
|
||||
void* handle = nullptr;
|
||||
bool should_sym_cleanup; // true if cleanup is not managed by the syminit cache
|
||||
bool should_close_handle; // true if cleanup is not managed by the syminit cache and the handle was duplicated
|
||||
dbghelp_syminit_info(void* handle, bool should_sym_cleanup, bool should_close_handle);
|
||||
public:
|
||||
~dbghelp_syminit_info();
|
||||
void release();
|
||||
|
||||
NODISCARD static dbghelp_syminit_info make_not_owned(void* handle);
|
||||
NODISCARD static dbghelp_syminit_info make_owned(void* handle, bool should_close_handle);
|
||||
|
||||
dbghelp_syminit_info(const dbghelp_syminit_info&) = delete;
|
||||
dbghelp_syminit_info(dbghelp_syminit_info&&);
|
||||
dbghelp_syminit_info& operator=(const dbghelp_syminit_info&) = delete;
|
||||
dbghelp_syminit_info& operator=(dbghelp_syminit_info&&);
|
||||
|
||||
void* get_process_handle() const;
|
||||
dbghelp_syminit_info make_non_owning_view() const;
|
||||
};
|
||||
|
||||
// Ensure SymInitialize is called on the process. This function either
|
||||
// - Finds that SymInitialize has been called for a handle to the current process already, in which case it returns
|
||||
// a non-owning dbghelp_syminit_info instance holding the handle
|
||||
// - Calls SymInitialize a handle to the current process, caches it, and returns a non-owning dbghelp_syminit_info
|
||||
// - Calls SymInitialize and returns an owning dbghelp_syminit_info which will handle cleanup
|
||||
dbghelp_syminit_info ensure_syminit();
|
||||
|
||||
NODISCARD std::unique_lock<std::recursive_mutex> get_dbghelp_lock();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@ -17,7 +17,7 @@ namespace detail {
|
||||
inline std::string exception_type_name() {
|
||||
#if defined(CPPTRACE_HAS_CXX_EXCEPTION_TYPE) && (IS_LIBSTDCXX || IS_LIBCXX)
|
||||
const std::type_info* t = abi::__cxa_current_exception_type();
|
||||
return t ? detail::demangle(t->name()) : "<unknown>";
|
||||
return t ? detail::demangle(t->name(), false) : "<unknown>";
|
||||
#else
|
||||
return "<unknown>";
|
||||
#endif
|
||||
|
||||
@ -39,13 +39,9 @@ namespace detail {
|
||||
#elif IS_APPLE
|
||||
|
||||
#include <cstdint>
|
||||
// #include <mach-o/dyld.h>
|
||||
#include <mach-o/dyld.h>
|
||||
#include <sys/syslimits.h>
|
||||
|
||||
// https://github.com/opensource-apple/dyld/blob/3f928f32597888c5eac6003b9199d972d49857b5/include/mach-o/dyld.h#L92C1-L92C62
|
||||
// https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/dyld.3.html
|
||||
extern int _NSGetExecutablePath(char* buf, uint32_t* bufsize);
|
||||
|
||||
#define CPPTRACE_MAX_PATH CPPTRACE_PATH_MAX
|
||||
|
||||
namespace cpptrace {
|
||||
|
||||
@ -135,7 +135,10 @@ namespace detail {
|
||||
if(color && line == target_line) {
|
||||
snippet += RESET;
|
||||
}
|
||||
snippet += lines[line - original_begin] + "\n";
|
||||
snippet += lines[line - original_begin];
|
||||
if(line != end) {
|
||||
snippet += '\n';
|
||||
}
|
||||
}
|
||||
return snippet;
|
||||
}
|
||||
|
||||
@ -34,7 +34,7 @@ namespace libdwarf {
|
||||
if(!resolver) {
|
||||
// this seems silly but it's an attempt to not repeatedly try to initialize new dwarf_resolvers if
|
||||
// exceptions are thrown, e.g. if the path doesn't exist
|
||||
resolver = std::unique_ptr<null_resolver>(new null_resolver);
|
||||
resolver = detail::make_unique<null_resolver>();
|
||||
resolver = make_dwarf_resolver(object_path);
|
||||
}
|
||||
return resolver;
|
||||
@ -46,11 +46,11 @@ namespace libdwarf {
|
||||
// the path doesn't exist
|
||||
std::unordered_map<std::string, uint64_t> symbols;
|
||||
this->symbols = symbols;
|
||||
auto obj = mach_o::open_mach_o(object_path);
|
||||
if(!obj) {
|
||||
auto mach_o_object = open_mach_o_cached(object_path);
|
||||
if(!mach_o_object) {
|
||||
return this->symbols.unwrap();
|
||||
}
|
||||
const auto& symbol_table = obj.unwrap_value().symbol_table();
|
||||
const auto& symbol_table = mach_o_object.unwrap_value()->symbol_table();
|
||||
if(!symbol_table) {
|
||||
return this->symbols.unwrap();
|
||||
}
|
||||
@ -110,11 +110,11 @@ namespace libdwarf {
|
||||
debug_map_resolver(const std::string& source_object_path) {
|
||||
// load mach-o
|
||||
// TODO: Cache somehow?
|
||||
auto obj = mach_o::open_mach_o(source_object_path);
|
||||
if(!obj) {
|
||||
auto mach_o_object = open_mach_o_cached(source_object_path);
|
||||
if(!mach_o_object) {
|
||||
return;
|
||||
}
|
||||
mach_o& source_mach = obj.unwrap_value();
|
||||
mach_o& source_mach = *mach_o_object.unwrap_value();
|
||||
auto source_debug_map = source_mach.get_debug_map();
|
||||
if(!source_debug_map) {
|
||||
return;
|
||||
@ -198,7 +198,7 @@ namespace libdwarf {
|
||||
};
|
||||
|
||||
std::unique_ptr<symbol_resolver> make_debug_map_resolver(const std::string& object_path) {
|
||||
return std::unique_ptr<debug_map_resolver>(new debug_map_resolver(object_path));
|
||||
return detail::make_unique<debug_map_resolver>(object_path);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -27,10 +27,9 @@ namespace libdwarf {
|
||||
|
||||
[[noreturn]] inline void handle_dwarf_error(Dwarf_Debug dbg, Dwarf_Error error) {
|
||||
Dwarf_Unsigned ev = dwarf_errno(error);
|
||||
char* msg = dwarf_errmsg(error);
|
||||
(void)dbg;
|
||||
// dwarf_dealloc_error(dbg, error);
|
||||
throw internal_error("dwarf error {} {}", ev, msg);
|
||||
// dwarf_dealloc_error deallocates the message, attaching to msg is convenient
|
||||
auto msg = raii_wrap(dwarf_errmsg(error), [dbg, error] (char*) { dwarf_dealloc_error(dbg, error); });
|
||||
throw internal_error(microfmt::format("dwarf error {} {}", ev, msg.get()));
|
||||
}
|
||||
|
||||
struct die_object {
|
||||
@ -67,8 +66,12 @@ namespace libdwarf {
|
||||
}
|
||||
|
||||
~die_object() {
|
||||
release();
|
||||
}
|
||||
|
||||
void release() {
|
||||
if(die) {
|
||||
dwarf_dealloc_die(die);
|
||||
dwarf_dealloc_die(exchange(die, nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,16 +79,15 @@ namespace libdwarf {
|
||||
|
||||
die_object& operator=(const die_object&) = delete;
|
||||
|
||||
die_object(die_object&& other) noexcept : dbg(other.dbg), die(other.die) {
|
||||
// done for finding mistakes, attempts to use the die_object after this should segfault
|
||||
// a valid use otherwise would be moved_from.get_sibling() which would get the next CU
|
||||
other.dbg = nullptr;
|
||||
other.die = nullptr;
|
||||
}
|
||||
// dbg doesn't strictly have to be st to null but it helps ensure attempts to use the die_object after this to
|
||||
// segfault. A valid use otherwise would be moved_from.get_sibling() which would get the next CU.
|
||||
die_object(die_object&& other) noexcept
|
||||
: dbg(exchange(other.dbg, nullptr)), die(exchange(other.die, nullptr)) {}
|
||||
|
||||
die_object& operator=(die_object&& other) noexcept {
|
||||
std::swap(dbg, other.dbg);
|
||||
std::swap(die, other.die);
|
||||
release();
|
||||
dbg = exchange(other.dbg, nullptr);
|
||||
die = exchange(other.die, nullptr);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
31
src/symbols/dwarf/dwarf_options.cpp
Normal file
31
src/symbols/dwarf/dwarf_options.cpp
Normal file
@ -0,0 +1,31 @@
|
||||
#include "symbols/dwarf/dwarf_options.hpp"
|
||||
|
||||
#include <cpptrace/utils.hpp>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
std::atomic<nullable<std::size_t>> dwarf_resolver_line_table_cache_size{nullable<std::size_t>::null()};
|
||||
std::atomic<bool> dwarf_resolver_disable_aranges{false};
|
||||
|
||||
optional<std::size_t> get_dwarf_resolver_line_table_cache_size() {
|
||||
auto max_entries = dwarf_resolver_line_table_cache_size.load();
|
||||
return max_entries.has_value() ? optional<std::size_t>(max_entries.value()) : nullopt;
|
||||
}
|
||||
|
||||
bool get_dwarf_resolver_disable_aranges() {
|
||||
return dwarf_resolver_disable_aranges.load();
|
||||
}
|
||||
}
|
||||
|
||||
namespace experimental {
|
||||
void set_dwarf_resolver_line_table_cache_size(nullable<std::size_t> max_entries) {
|
||||
detail::dwarf_resolver_line_table_cache_size.store(max_entries);
|
||||
}
|
||||
|
||||
void set_dwarf_resolver_disable_aranges(bool disable) {
|
||||
detail::dwarf_resolver_disable_aranges.store(disable);
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/symbols/dwarf/dwarf_options.hpp
Normal file
15
src/symbols/dwarf/dwarf_options.hpp
Normal file
@ -0,0 +1,15 @@
|
||||
#ifndef DWARF_OPTIONS_HPP
|
||||
#define DWARF_OPTIONS_HPP
|
||||
|
||||
#include "utils/optional.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
optional<std::size_t> get_dwarf_resolver_line_table_cache_size();
|
||||
bool get_dwarf_resolver_disable_aranges();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -4,10 +4,13 @@
|
||||
|
||||
#include <cpptrace/basic.hpp>
|
||||
#include "symbols/dwarf/dwarf.hpp" // has dwarf #includes
|
||||
#include "symbols/dwarf/dwarf_utils.hpp"
|
||||
#include "symbols/dwarf/dwarf_options.hpp"
|
||||
#include "symbols/symbols.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "utils/lru_cache.hpp"
|
||||
#include "platform/path.hpp"
|
||||
#include "platform/program_name.hpp" // For CPPTRACE_MAX_PATH
|
||||
|
||||
@ -19,6 +22,7 @@
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
@ -37,38 +41,6 @@ namespace libdwarf {
|
||||
constexpr bool dump_dwarf = false;
|
||||
constexpr bool trace_dwarf = false;
|
||||
|
||||
struct subprogram_entry {
|
||||
die_object die;
|
||||
Dwarf_Addr low;
|
||||
Dwarf_Addr high;
|
||||
};
|
||||
|
||||
struct cu_entry {
|
||||
die_object die;
|
||||
Dwarf_Half dwversion;
|
||||
Dwarf_Addr low;
|
||||
Dwarf_Addr high;
|
||||
};
|
||||
|
||||
struct line_entry {
|
||||
Dwarf_Addr low;
|
||||
// Dwarf_Addr high;
|
||||
// int i;
|
||||
Dwarf_Line line;
|
||||
optional<std::string> path;
|
||||
optional<std::uint32_t> line_number;
|
||||
optional<std::uint32_t> column_number;
|
||||
line_entry(Dwarf_Addr low, Dwarf_Line line) : low(low), line(line) {}
|
||||
};
|
||||
|
||||
struct line_table_info {
|
||||
Dwarf_Unsigned version;
|
||||
Dwarf_Line_Context line_context;
|
||||
// sorted by low_addr
|
||||
// TODO: Make this optional at some point, it may not be generated if cache mode switches during program exec...
|
||||
std::vector<line_entry> line_entries;
|
||||
};
|
||||
|
||||
class dwarf_resolver;
|
||||
|
||||
// used to describe data from an upstream binary to a resolver for the .dwo
|
||||
@ -80,20 +52,24 @@ namespace libdwarf {
|
||||
|
||||
class dwarf_resolver : public symbol_resolver {
|
||||
std::string object_path;
|
||||
Dwarf_Debug dbg = nullptr;
|
||||
// dwarf_finish needs to be called after all other dwarf stuff is cleaned up, e.g. `srcfiles` and aranges etc
|
||||
// raii_wrapping ensures this is the last thing done after the destructor logic and all other data members are
|
||||
// cleaned up
|
||||
raii_wrapper<Dwarf_Debug, void(*)(Dwarf_Debug)> dbg{nullptr, [](Dwarf_Debug dbg) { dwarf_finish(dbg); }};
|
||||
bool ok = false;
|
||||
// .debug_aranges cache
|
||||
Dwarf_Arange* aranges = nullptr;
|
||||
Dwarf_Signed arange_count = 0;
|
||||
// Map from CU -> Line context
|
||||
std::unordered_map<Dwarf_Off, line_table_info> line_tables;
|
||||
lru_cache<Dwarf_Off, line_table_info> line_tables{get_dwarf_resolver_line_table_cache_size()};
|
||||
// Map from CU -> Sorted subprograms vector
|
||||
std::unordered_map<Dwarf_Off, std::vector<subprogram_entry>> subprograms_cache;
|
||||
std::unordered_map<Dwarf_Off, die_cache<monostate>> subprograms_cache;
|
||||
// Vector of ranges and their corresponding CU offsets
|
||||
std::vector<cu_entry> cu_cache;
|
||||
// data stored for each cache entry is a Dwarf_Half dwversion
|
||||
die_cache<Dwarf_Half> cu_cache;
|
||||
bool generated_cu_cache = false;
|
||||
// Map from CU -> {srcfiles, count}
|
||||
std::unordered_map<Dwarf_Off, std::pair<char**, Dwarf_Signed>> srcfiles_cache;
|
||||
std::unordered_map<Dwarf_Off, srcfiles> srcfiles_cache;
|
||||
// Map from CU -> split full cu resolver
|
||||
std::unordered_map<Dwarf_Off, std::unique_ptr<dwarf_resolver>> split_full_cu_resolvers;
|
||||
// info for resolving a dwo object
|
||||
@ -151,12 +127,12 @@ namespace libdwarf {
|
||||
if(result.is_error()) {
|
||||
result.drop_error();
|
||||
} else if(result.unwrap_value()) {
|
||||
auto obj = mach_o::open_mach_o(object_path);
|
||||
if(!obj) {
|
||||
auto mach_o_object = open_mach_o_cached(object_path);
|
||||
if(!mach_o_object) {
|
||||
ok = false;
|
||||
return;
|
||||
}
|
||||
universal_number = obj.unwrap_value().get_fat_index();
|
||||
universal_number = mach_o_object.unwrap_value()->get_fat_index();
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -166,6 +142,7 @@ namespace libdwarf {
|
||||
if(use_buffer) {
|
||||
buffer = std::unique_ptr<char[]>(new char[CPPTRACE_MAX_PATH]);
|
||||
}
|
||||
dwarf_set_de_alloc_flag(0);
|
||||
auto ret = wrap(
|
||||
dwarf_init_path_a,
|
||||
object_path.c_str(),
|
||||
@ -175,7 +152,7 @@ namespace libdwarf {
|
||||
universal_number,
|
||||
nullptr,
|
||||
nullptr,
|
||||
&dbg
|
||||
&dbg.get()
|
||||
);
|
||||
if(ret == DW_DLV_OK) {
|
||||
ok = true;
|
||||
@ -191,7 +168,7 @@ namespace libdwarf {
|
||||
VERIFY(wrap(dwarf_set_tied_dbg, dbg, skeleton.unwrap().resolver.dbg) == DW_DLV_OK);
|
||||
}
|
||||
|
||||
if(ok) {
|
||||
if(ok && !get_dwarf_resolver_disable_aranges()) {
|
||||
// Check for .debug_aranges for fast lookup
|
||||
wrap(dwarf_get_aranges, dbg, &aranges, &arange_count);
|
||||
}
|
||||
@ -199,23 +176,13 @@ namespace libdwarf {
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
|
||||
~dwarf_resolver() override {
|
||||
// TODO: Maybe redundant since dwarf_finish(dbg); will clean up the line stuff anyway but may as well just
|
||||
// for thoroughness
|
||||
for(auto& entry : line_tables) {
|
||||
dwarf_srclines_dealloc_b(entry.second.line_context);
|
||||
}
|
||||
for(auto& entry : srcfiles_cache) {
|
||||
dwarf_dealloc(dbg, entry.second.first, DW_DLA_LIST);
|
||||
}
|
||||
// subprograms_cache needs to be destroyed before dbg otherwise there will be another use after free
|
||||
subprograms_cache.clear();
|
||||
split_full_cu_resolvers.clear();
|
||||
skeleton.reset();
|
||||
if(aranges) {
|
||||
for(int i = 0; i < arange_count; i++) {
|
||||
dwarf_dealloc(dbg, aranges[i], DW_DLA_ARANGE);
|
||||
aranges[i] = nullptr;
|
||||
}
|
||||
dwarf_dealloc(dbg, aranges, DW_DLA_LIST);
|
||||
}
|
||||
cu_cache.clear();
|
||||
dwarf_finish(dbg);
|
||||
}
|
||||
|
||||
dwarf_resolver(const dwarf_resolver&) = delete;
|
||||
@ -277,30 +244,32 @@ namespace libdwarf {
|
||||
walk_compilation_units([this] (const die_object& cu_die) {
|
||||
Dwarf_Half offset_size = 0;
|
||||
Dwarf_Half dwversion = 0;
|
||||
dwarf_get_version_of_die(cu_die.get(), &dwversion, &offset_size);
|
||||
VERIFY(dwarf_get_version_of_die(cu_die.get(), &dwversion, &offset_size) == DW_DLV_OK);
|
||||
if(skeleton) {
|
||||
// NOTE: If we have a corresponding skeleton, we assume we have one CU matching the skeleton CU
|
||||
// Precedence for this assumption is https://dwarfstd.org/doc/DWARF5.pdf#subsection.3.1.3
|
||||
// TODO: Also assuming same dwversion
|
||||
const auto& skeleton_cu = skeleton.unwrap().cu_die;
|
||||
auto ranges_vec = skeleton_cu.get_rangelist_entries(skeleton_cu, dwversion);
|
||||
if(!ranges_vec.empty()) {
|
||||
auto cu_die_handle = cu_cache.add_die(cu_die.clone());
|
||||
for(auto range : ranges_vec) {
|
||||
// TODO: Reduce cloning here
|
||||
cu_cache.push_back({ cu_die.clone(), dwversion, range.first, range.second });
|
||||
cu_cache.insert(cu_die_handle, range.first, range.second, dwversion);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
auto ranges_vec = cu_die.get_rangelist_entries(cu_die, dwversion);
|
||||
if(!ranges_vec.empty()) {
|
||||
auto cu_die_handle = cu_cache.add_die(cu_die.clone());
|
||||
for(auto range : ranges_vec) {
|
||||
// TODO: Reduce cloning here
|
||||
cu_cache.push_back({ cu_die.clone(), dwversion, range.first, range.second });
|
||||
cu_cache.insert(cu_die_handle, range.first, range.second, dwversion);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
std::sort(cu_cache.begin(), cu_cache.end(), [] (const cu_entry& a, const cu_entry& b) {
|
||||
return a.low < b.low;
|
||||
});
|
||||
cu_cache.finalize();
|
||||
generated_cu_cache = true;
|
||||
}
|
||||
}
|
||||
@ -343,11 +312,11 @@ namespace libdwarf {
|
||||
char** dw_srcfiles;
|
||||
Dwarf_Signed dw_filecount;
|
||||
VERIFY(wrap(dwarf_srcfiles, cu_die.get(), &dw_srcfiles, &dw_filecount) == DW_DLV_OK);
|
||||
srcfiles srcfiles(cu_die.dbg, dw_srcfiles, dw_filecount);
|
||||
if(Dwarf_Signed(file_i) < dw_filecount) {
|
||||
// dwarf is using 1-indexing
|
||||
filename = dw_srcfiles[file_i];
|
||||
filename = srcfiles.get(file_i);
|
||||
}
|
||||
dwarf_dealloc(cu_die.dbg, dw_srcfiles, DW_DLA_LIST);
|
||||
} else {
|
||||
auto off = cu_die.get_global_offset();
|
||||
auto it = srcfiles_cache.find(off);
|
||||
@ -355,13 +324,11 @@ namespace libdwarf {
|
||||
char** dw_srcfiles;
|
||||
Dwarf_Signed dw_filecount;
|
||||
VERIFY(wrap(dwarf_srcfiles, cu_die.get(), &dw_srcfiles, &dw_filecount) == DW_DLV_OK);
|
||||
it = srcfiles_cache.insert(it, {off, {dw_srcfiles, dw_filecount}});
|
||||
it = srcfiles_cache.insert(it, {off, srcfiles{cu_die.dbg, dw_srcfiles, dw_filecount}});
|
||||
}
|
||||
char** dw_srcfiles = it->second.first;
|
||||
Dwarf_Signed dw_filecount = it->second.second;
|
||||
if(Dwarf_Signed(file_i) < dw_filecount) {
|
||||
if(file_i < it->second.count()) {
|
||||
// dwarf is using 1-indexing
|
||||
filename = dw_srcfiles[file_i];
|
||||
filename = it->second.get(file_i);
|
||||
}
|
||||
}
|
||||
return filename;
|
||||
@ -397,7 +364,7 @@ namespace libdwarf {
|
||||
if(file_i) {
|
||||
// for dwarf 2, 3, 4, and experimental line table version 0xfe06 1-indexing is used
|
||||
// for dwarf 5 0-indexing is used
|
||||
optional<std::reference_wrapper<line_table_info>> line_table_opt;
|
||||
optional<line_table_info&> line_table_opt;
|
||||
if(skeleton) {
|
||||
line_table_opt = skeleton.unwrap().resolver.get_line_table(
|
||||
skeleton.unwrap().cu_die
|
||||
@ -406,7 +373,7 @@ namespace libdwarf {
|
||||
line_table_opt = get_line_table(cu_die);
|
||||
}
|
||||
if(line_table_opt) {
|
||||
auto& line_table = line_table_opt.unwrap().get();
|
||||
auto& line_table = line_table_opt.unwrap();
|
||||
if(line_table.version != 5) {
|
||||
if(file_i.unwrap() == 0) {
|
||||
file_i.reset(); // 0 means no name to be found
|
||||
@ -531,26 +498,28 @@ namespace libdwarf {
|
||||
const die_object& cu_die,
|
||||
const die_object& die,
|
||||
Dwarf_Half dwversion,
|
||||
std::vector<subprogram_entry>& vec
|
||||
die_cache<monostate>& subprogram_cache
|
||||
) {
|
||||
walk_die_list(
|
||||
die,
|
||||
[this, &cu_die, dwversion, &vec] (const die_object& die) {
|
||||
[this, &cu_die, dwversion, &subprogram_cache] (const die_object& die) {
|
||||
switch(die.get_tag()) {
|
||||
case DW_TAG_subprogram:
|
||||
{
|
||||
auto ranges_vec = die.get_rangelist_entries(cu_die, dwversion);
|
||||
// TODO: Feels super inefficient and some day should maybe use an interval tree.
|
||||
if(!ranges_vec.empty()) {
|
||||
auto die_handle = subprogram_cache.add_die(die.clone());
|
||||
for(auto range : ranges_vec) {
|
||||
// TODO: Reduce cloning here
|
||||
vec.push_back({ die.clone(), range.first, range.second });
|
||||
subprogram_cache.insert(die_handle, range.first, range.second);
|
||||
}
|
||||
}
|
||||
// Walk children to get things like lambdas
|
||||
// TODO: Somehow find a way to get better names here? For gcc it's just "operator()"
|
||||
// On clang it's better
|
||||
auto child = die.get_child();
|
||||
if(child) {
|
||||
preprocess_subprograms(cu_die, child, dwversion, vec);
|
||||
preprocess_subprograms(cu_die, child, dwversion, subprogram_cache);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -563,7 +532,7 @@ namespace libdwarf {
|
||||
{
|
||||
auto child = die.get_child();
|
||||
if(child) {
|
||||
preprocess_subprograms(cu_die, child, dwversion, vec);
|
||||
preprocess_subprograms(cu_die, child, dwversion, subprogram_cache);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -593,41 +562,32 @@ namespace libdwarf {
|
||||
auto it = subprograms_cache.find(off);
|
||||
if(it == subprograms_cache.end()) {
|
||||
// TODO: Refactor. Do the sort in the preprocess function and return the vec directly.
|
||||
std::vector<subprogram_entry> vec;
|
||||
preprocess_subprograms(cu_die, cu_die, dwversion, vec);
|
||||
std::sort(vec.begin(), vec.end(), [] (const subprogram_entry& a, const subprogram_entry& b) {
|
||||
return a.low < b.low;
|
||||
});
|
||||
subprograms_cache.emplace(off, std::move(vec));
|
||||
die_cache<monostate> subprogram_cache;
|
||||
preprocess_subprograms(cu_die, cu_die, dwversion, subprogram_cache);
|
||||
subprogram_cache.finalize();
|
||||
subprograms_cache.emplace(off, std::move(subprogram_cache));
|
||||
it = subprograms_cache.find(off);
|
||||
}
|
||||
auto& vec = it->second;
|
||||
auto vec_it = first_less_than_or_equal(
|
||||
vec.begin(),
|
||||
vec.end(),
|
||||
pc,
|
||||
[] (Dwarf_Addr pc, const subprogram_entry& entry) {
|
||||
return pc < entry.low;
|
||||
}
|
||||
);
|
||||
const auto& subprogram_cache = it->second;
|
||||
auto maybe_die = subprogram_cache.lookup(pc);
|
||||
// If the vector has been empty this can happen
|
||||
if(vec_it != vec.end()) {
|
||||
if(vec_it->die.pc_in_die(cu_die, dwversion, pc)) {
|
||||
frame.symbol = retrieve_symbol_for_subprogram(cu_die, vec_it->die, pc, dwversion, inlines);
|
||||
if(maybe_die.has_value()) {
|
||||
if(maybe_die.unwrap().pc_in_die(cu_die, dwversion, pc)) {
|
||||
frame.symbol = retrieve_symbol_for_subprogram(cu_die, maybe_die.unwrap(), pc, dwversion, inlines);
|
||||
}
|
||||
} else {
|
||||
ASSERT(vec.size() == 0, "Vec should be empty?");
|
||||
ASSERT(subprogram_cache.ranges_count() == 0, "subprogram_cache.ranges_count() should be 0?");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// returns a reference to a CU's line table, may be invalidated if the line_tables map is modified
|
||||
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
|
||||
optional<std::reference_wrapper<line_table_info>> get_line_table(const die_object& cu_die) {
|
||||
optional<line_table_info&> get_line_table(const die_object& cu_die) {
|
||||
auto off = cu_die.get_global_offset();
|
||||
auto it = line_tables.find(off);
|
||||
if(it != line_tables.end()) {
|
||||
return it->second;
|
||||
auto res = line_tables.maybe_get(off);
|
||||
if(res) {
|
||||
return res;
|
||||
} else {
|
||||
Dwarf_Unsigned version;
|
||||
Dwarf_Small table_count;
|
||||
@ -682,24 +642,6 @@ namespace libdwarf {
|
||||
}
|
||||
}
|
||||
line = line_buffer[j - 1];
|
||||
// {
|
||||
// Dwarf_Unsigned line_number = 0;
|
||||
// VERIFY(wrap(dwarf_lineno, line, &line_number) == DW_DLV_OK);
|
||||
// frame.line = static_cast<std::uint32_t>(line_number);
|
||||
// char* filename = nullptr;
|
||||
// VERIFY(wrap(dwarf_linesrc, line, &filename) == DW_DLV_OK);
|
||||
// auto wrapper = raii_wrap(
|
||||
// filename,
|
||||
// [this] (char* str) { if(str) dwarf_dealloc(dbg, str, DW_DLA_STRING); }
|
||||
// );
|
||||
// frame.filename = filename;
|
||||
// printf("%s : %d\n", filename, line_number);
|
||||
// Dwarf_Bool is_line_end;
|
||||
// VERIFY(wrap(dwarf_lineendsequence, line, &is_line_end) == DW_DLV_OK);
|
||||
// if(is_line_end) {
|
||||
// puts("Line end");
|
||||
// }
|
||||
// }
|
||||
line_entries.push_back({
|
||||
low_addr,
|
||||
line
|
||||
@ -712,8 +654,7 @@ namespace libdwarf {
|
||||
});
|
||||
}
|
||||
|
||||
it = line_tables.insert({off, {version, line_context, std::move(line_entries)}}).first;
|
||||
return it->second;
|
||||
return line_tables.insert(off, line_table_info{version, line_context, std::move(line_entries)});
|
||||
}
|
||||
}
|
||||
|
||||
@ -731,7 +672,7 @@ namespace libdwarf {
|
||||
if(!table_info_opt) {
|
||||
return; // failing silently for now
|
||||
}
|
||||
auto& table_info = table_info_opt.unwrap().get();
|
||||
auto& table_info = table_info_opt.unwrap();
|
||||
if(get_cache_mode() == cache_mode::prioritize_speed) {
|
||||
// Lookup in the table
|
||||
auto& line_entries = table_info.line_entries;
|
||||
@ -916,17 +857,13 @@ namespace libdwarf {
|
||||
} else {
|
||||
lazy_generate_cu_cache();
|
||||
// look up the cu
|
||||
auto vec_it = first_less_than_or_equal(
|
||||
cu_cache.begin(),
|
||||
cu_cache.end(),
|
||||
pc,
|
||||
[] (Dwarf_Addr pc, const cu_entry& entry) {
|
||||
return pc < entry.low;
|
||||
}
|
||||
);
|
||||
// TODO: Vec-it is already range-based, this range check is redundant
|
||||
// If the vector has been empty this can happen
|
||||
if(vec_it != cu_cache.end()) {
|
||||
auto res = cu_cache.lookup(pc);
|
||||
// res can be nullopt if the cu_cache vector is empty
|
||||
// It can also happen for something like _start, where there is a cached CU for the object but
|
||||
// _start is outside of the CU's PC range
|
||||
if(res) {
|
||||
const auto& die = res.unwrap().die;
|
||||
const auto dwversion = res.unwrap().data;
|
||||
// TODO: Cache the range list?
|
||||
// NOTE: If we have a corresponding skeleton, we assume we have one CU matching the skeleton CU
|
||||
if(
|
||||
@ -937,14 +874,10 @@ namespace libdwarf {
|
||||
skeleton.unwrap().dwversion,
|
||||
pc
|
||||
)
|
||||
) || vec_it->die.pc_in_die(vec_it->die, vec_it->dwversion, pc)
|
||||
) || die.pc_in_die(die, dwversion, pc)
|
||||
) {
|
||||
return cu_info{maybe_owned_die_object::ref(vec_it->die), vec_it->dwversion};
|
||||
return cu_info{maybe_owned_die_object::ref(die), dwversion};
|
||||
}
|
||||
} else {
|
||||
// I've had this happen for _start, where there is a cached CU for the object but _start is outside
|
||||
// of the CU's PC range
|
||||
// ASSERT(cu_cache.size() == 0, "Vec should be empty?");
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
@ -999,12 +932,7 @@ namespace libdwarf {
|
||||
if(it == split_full_cu_resolvers.end()) {
|
||||
it = split_full_cu_resolvers.emplace(
|
||||
off,
|
||||
std::unique_ptr<dwarf_resolver>(
|
||||
new dwarf_resolver(
|
||||
path,
|
||||
skeleton_info{cu_die.clone(), dwversion, *this}
|
||||
)
|
||||
)
|
||||
detail::make_unique<dwarf_resolver>(path, skeleton_info{cu_die.clone(), dwversion, *this})
|
||||
).first;
|
||||
}
|
||||
res = it->second->resolve_frame(object_frame_info);
|
||||
@ -1082,7 +1010,7 @@ namespace libdwarf {
|
||||
};
|
||||
|
||||
std::unique_ptr<symbol_resolver> make_dwarf_resolver(const std::string& object_path) {
|
||||
return std::unique_ptr<dwarf_resolver>(new dwarf_resolver(object_path));
|
||||
return detail::make_unique<dwarf_resolver>(object_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
208
src/symbols/dwarf/dwarf_utils.hpp
Normal file
208
src/symbols/dwarf/dwarf_utils.hpp
Normal file
@ -0,0 +1,208 @@
|
||||
#ifndef DWARF_UTILS_HPP
|
||||
#define DWARF_UTILS_HPP
|
||||
|
||||
#include <cpptrace/basic.hpp>
|
||||
#include "symbols/dwarf/dwarf.hpp" // has dwarf #includes
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/microfmt.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
namespace libdwarf {
|
||||
class srcfiles {
|
||||
Dwarf_Debug dbg = nullptr;
|
||||
char** dw_srcfiles = nullptr;
|
||||
Dwarf_Unsigned dw_filecount = 0;
|
||||
|
||||
public:
|
||||
srcfiles(Dwarf_Debug dbg, char** dw_srcfiles, Dwarf_Signed filecount)
|
||||
: dbg(dbg), dw_srcfiles(dw_srcfiles), dw_filecount(static_cast<Dwarf_Unsigned>(filecount))
|
||||
{
|
||||
if(filecount < 0) {
|
||||
throw internal_error(microfmt::format("Unexpected dw_filecount {}", filecount));
|
||||
}
|
||||
}
|
||||
~srcfiles() {
|
||||
release();
|
||||
}
|
||||
void release() {
|
||||
if(dw_srcfiles) {
|
||||
for(unsigned i = 0; i < dw_filecount; i++) {
|
||||
dwarf_dealloc(dbg, dw_srcfiles[i], DW_DLA_STRING);
|
||||
dw_srcfiles[i] = nullptr;
|
||||
}
|
||||
dwarf_dealloc(dbg, dw_srcfiles, DW_DLA_LIST);
|
||||
dw_srcfiles = nullptr;
|
||||
}
|
||||
}
|
||||
srcfiles(const srcfiles&) = delete;
|
||||
srcfiles(srcfiles&& other) {
|
||||
*this = std::move(other);
|
||||
}
|
||||
srcfiles& operator=(const srcfiles&) = delete;
|
||||
srcfiles& operator=(srcfiles&& other) {
|
||||
release();
|
||||
dbg = exchange(other.dbg, nullptr);
|
||||
dw_srcfiles = exchange(other.dw_srcfiles, nullptr);
|
||||
dw_filecount = exchange(other.dw_filecount, 0);
|
||||
return *this;
|
||||
}
|
||||
// note: dwarf uses 1-indexing
|
||||
const char* get(Dwarf_Unsigned file_i) const {
|
||||
if(file_i >= dw_filecount) {
|
||||
throw internal_error(microfmt::format(
|
||||
"Error while accessing the srcfiles list, requested index {} is out of bounds (count = {})",
|
||||
file_i,
|
||||
dw_filecount
|
||||
));
|
||||
}
|
||||
return dw_srcfiles[file_i];
|
||||
}
|
||||
Dwarf_Unsigned count() const {
|
||||
return dw_filecount;
|
||||
}
|
||||
};
|
||||
|
||||
// sorted range entries for dies
|
||||
template<
|
||||
typename T,
|
||||
typename std::enable_if<std::is_trivially_copyable<T>::value && sizeof(T) <= 16, int>::type = 0
|
||||
>
|
||||
class die_cache {
|
||||
public:
|
||||
struct die_handle {
|
||||
std::uint32_t die_index;
|
||||
};
|
||||
private:
|
||||
struct PACKED basic_range_entry {
|
||||
die_handle die;
|
||||
Dwarf_Addr low;
|
||||
Dwarf_Addr high;
|
||||
};
|
||||
struct PACKED annotated_range_entry {
|
||||
die_handle die;
|
||||
Dwarf_Addr low;
|
||||
Dwarf_Addr high;
|
||||
T data;
|
||||
};
|
||||
using range_entry = typename std::conditional<
|
||||
std::is_same<T, monostate>::value,
|
||||
basic_range_entry,
|
||||
annotated_range_entry
|
||||
>::type;
|
||||
std::vector<die_object> dies;
|
||||
std::vector<range_entry> range_entries;
|
||||
public:
|
||||
die_handle add_die(die_object&& die) {
|
||||
dies.push_back(std::move(die));
|
||||
VERIFY(dies.size() < std::numeric_limits<std::uint32_t>::max());
|
||||
return die_handle{static_cast<std::uint32_t>(dies.size() - 1)};
|
||||
}
|
||||
template<typename Void = void>
|
||||
auto insert(die_handle die, Dwarf_Addr low, Dwarf_Addr high)
|
||||
-> typename std::enable_if<std::is_same<T, monostate>::value, Void>::type
|
||||
{
|
||||
range_entries.push_back({die, low, high});
|
||||
}
|
||||
template<typename Void = void>
|
||||
auto insert(die_handle die, Dwarf_Addr low, Dwarf_Addr high, const T& t)
|
||||
-> typename std::enable_if<!std::is_same<T, monostate>::value, Void>::type
|
||||
{
|
||||
range_entries.push_back({die, low, high, t});
|
||||
}
|
||||
void finalize() {
|
||||
std::sort(range_entries.begin(), range_entries.end(), [] (const range_entry& a, const range_entry& b) {
|
||||
return a.low < b.low;
|
||||
});
|
||||
}
|
||||
std::size_t ranges_count() const {
|
||||
return range_entries.size();
|
||||
}
|
||||
|
||||
struct die_and_data {
|
||||
const die_object& die;
|
||||
T data;
|
||||
};
|
||||
template<typename Ret = const die_object&>
|
||||
auto make_lookup_result(typename std::vector<range_entry>::const_iterator vec_it) const
|
||||
-> typename std::enable_if<std::is_same<T, monostate>::value, Ret>::type
|
||||
{
|
||||
return dies.at(vec_it->die.die_index);
|
||||
}
|
||||
template<typename Ret = die_and_data>
|
||||
auto make_lookup_result(typename std::vector<range_entry>::const_iterator vec_it) const
|
||||
-> typename std::enable_if<!std::is_same<T, monostate>::value, Ret>::type
|
||||
{
|
||||
return die_and_data{dies.at(vec_it->die.die_index), vec_it->data};
|
||||
}
|
||||
using lookup_result = typename std::conditional<
|
||||
std::is_same<T, monostate>::value,
|
||||
const die_object&,
|
||||
die_and_data
|
||||
>::type;
|
||||
optional<lookup_result> lookup(Dwarf_Addr pc) const {
|
||||
auto vec_it = first_less_than_or_equal(
|
||||
range_entries.begin(),
|
||||
range_entries.end(),
|
||||
pc,
|
||||
[] (Dwarf_Addr pc, const range_entry& entry) {
|
||||
return pc < entry.low;
|
||||
}
|
||||
);
|
||||
if(vec_it == range_entries.end()) {
|
||||
return nullopt;
|
||||
}
|
||||
// This would be an if constexpr if only C++17...
|
||||
return make_lookup_result(vec_it);
|
||||
}
|
||||
};
|
||||
|
||||
struct line_entry {
|
||||
Dwarf_Addr low;
|
||||
// Dwarf_Addr high;
|
||||
// int i;
|
||||
Dwarf_Line line;
|
||||
optional<std::string> path;
|
||||
optional<std::uint32_t> line_number;
|
||||
optional<std::uint32_t> column_number;
|
||||
line_entry(Dwarf_Addr low, Dwarf_Line line) : low(low), line(line) {}
|
||||
};
|
||||
|
||||
struct line_table_info {
|
||||
Dwarf_Unsigned version = 0;
|
||||
Dwarf_Line_Context line_context = nullptr;
|
||||
// sorted by low_addr
|
||||
// TODO: Make this optional at some point, it may not be generated if cache mode switches during program exec...
|
||||
std::vector<line_entry> line_entries;
|
||||
|
||||
line_table_info(
|
||||
Dwarf_Unsigned version,
|
||||
Dwarf_Line_Context line_context,
|
||||
std::vector<line_entry>&& line_entries
|
||||
) : version(version), line_context(line_context), line_entries(std::move(line_entries)) {}
|
||||
~line_table_info() {
|
||||
release();
|
||||
}
|
||||
void release() {
|
||||
dwarf_srclines_dealloc_b(line_context);
|
||||
line_context = nullptr;
|
||||
}
|
||||
line_table_info(const line_table_info&) = delete;
|
||||
line_table_info(line_table_info&& other) {
|
||||
*this = std::move(other);
|
||||
}
|
||||
line_table_info& operator=(const line_table_info&) = delete;
|
||||
line_table_info& operator=(line_table_info&& other) {
|
||||
release();
|
||||
version = other.version;
|
||||
line_context = exchange(other.line_context, nullptr);
|
||||
line_entries = std::move(other.line_entries);
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -83,6 +83,8 @@ namespace detail {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Symbol resolution code should probably handle when object addresses are 0
|
||||
|
||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<object_frame>& frames) {
|
||||
#if defined(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) && defined(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP)
|
||||
std::vector<stacktrace_frame> trace = libdwarf::resolve_frames(frames);
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
#endif
|
||||
|
||||
#include "binary/object.hpp"
|
||||
#include "options.hpp"
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
|
||||
@ -2,12 +2,13 @@
|
||||
|
||||
#include <cpptrace/basic.hpp>
|
||||
#include "symbols/symbols.hpp"
|
||||
#include "platform/dbghelp_syminit_manager.hpp"
|
||||
#include "platform/dbghelp_utils.hpp"
|
||||
#include "binary/object.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "options.hpp"
|
||||
|
||||
#include <mutex>
|
||||
#include <regex>
|
||||
#include <system_error>
|
||||
#include <vector>
|
||||
@ -239,6 +240,9 @@ namespace dbghelp {
|
||||
std::size_t sz = sizeof(TI_FINDCHILDREN_PARAMS) +
|
||||
(n_children) * sizeof(TI_FINDCHILDREN_PARAMS::ChildId[0]);
|
||||
TI_FINDCHILDREN_PARAMS* children = (TI_FINDCHILDREN_PARAMS*) new char[sz];
|
||||
auto guard = scope_exit([&] {
|
||||
delete[] (char*) children;
|
||||
});
|
||||
children->Start = 0;
|
||||
children->Count = n_children;
|
||||
if(
|
||||
@ -264,7 +268,6 @@ namespace dbghelp {
|
||||
extent += (i == 0 ? "" : ", ") + resolve_type(children->ChildId[i], proc, modbase);
|
||||
}
|
||||
extent += ")";
|
||||
delete[] (char*) children;
|
||||
return {return_type.base, extent + return_type.extent};
|
||||
}
|
||||
}
|
||||
@ -324,8 +327,6 @@ namespace dbghelp {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::recursive_mutex dbghelp_lock;
|
||||
|
||||
// TODO: Handle backtrace_pcinfo calling the callback multiple times on inlined functions
|
||||
stacktrace_frame resolve_frame(HANDLE proc, frame_ptr addr) {
|
||||
// The get_frame_object_info() ends up being inexpensive, at on my machine
|
||||
@ -335,7 +336,8 @@ namespace dbghelp {
|
||||
// get_frame_object_info() 0.001-0.002 ms 0.0003-0.0006 ms
|
||||
// At some point it might make sense to make an option to control this.
|
||||
auto object_frame = get_frame_object_info(addr);
|
||||
const std::lock_guard<std::recursive_mutex> lock(dbghelp_lock); // all dbghelp functions are not thread safe
|
||||
// Dbghelp is is single-threaded, so acquire a lock.
|
||||
auto lock = get_dbghelp_lock();
|
||||
alignas(SYMBOL_INFO) char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
|
||||
SYMBOL_INFO* symbol = (SYMBOL_INFO*)buffer;
|
||||
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
|
||||
@ -420,28 +422,18 @@ namespace dbghelp {
|
||||
}
|
||||
|
||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<frame_ptr>& frames) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(dbghelp_lock); // all dbghelp functions are not thread safe
|
||||
// Dbghelp is is single-threaded, so acquire a lock.
|
||||
auto lock = get_dbghelp_lock();
|
||||
std::vector<stacktrace_frame> trace;
|
||||
trace.reserve(frames.size());
|
||||
|
||||
// TODO: When does this need to be called? Can it be moved to the symbolizer?
|
||||
SymSetOptions(SYMOPT_ALLOW_ABSOLUTE_SYMBOLS);
|
||||
|
||||
HANDLE duplicated_handle = nullptr;
|
||||
HANDLE proc = GetCurrentProcess();
|
||||
if(get_cache_mode() == cache_mode::prioritize_speed) {
|
||||
duplicated_handle = get_syminit_manager().init(proc);
|
||||
} else {
|
||||
if(!DuplicateHandle(proc, proc, proc, &duplicated_handle, 0, FALSE, DUPLICATE_SAME_ACCESS)) {
|
||||
throw internal_error("DuplicateHandle failed {}", GetLastError());
|
||||
}
|
||||
if(!SymInitialize(duplicated_handle, NULL, TRUE)) {
|
||||
throw internal_error("SymInitialize failed {}", GetLastError());
|
||||
}
|
||||
}
|
||||
auto syminit_info = ensure_syminit();
|
||||
for(const auto frame : frames) {
|
||||
try {
|
||||
trace.push_back(resolve_frame(duplicated_handle , frame));
|
||||
trace.push_back(resolve_frame(syminit_info.get_process_handle() , frame));
|
||||
} catch(...) { // NOSONAR
|
||||
if(!detail::should_absorb_trace_exceptions()) {
|
||||
throw;
|
||||
@ -451,14 +443,6 @@ namespace dbghelp {
|
||||
trace.push_back(entry);
|
||||
}
|
||||
}
|
||||
if(get_cache_mode() != cache_mode::prioritize_speed) {
|
||||
if(!SymCleanup(duplicated_handle)) {
|
||||
throw internal_error("SymCleanup failed");
|
||||
}
|
||||
if(!CloseHandle(duplicated_handle)) {
|
||||
throw internal_error("CloseHandle failed");
|
||||
}
|
||||
}
|
||||
return trace;
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#include "platform/program_name.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "options.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
|
||||
@ -96,9 +96,9 @@ namespace libdwarf {
|
||||
const auto& object_name = group.first;
|
||||
// TODO PERF: Potentially a duplicate open and parse with module base stuff (and debug map resolver)
|
||||
#if IS_LINUX
|
||||
auto object = elf::open_elf(object_name);
|
||||
auto object = open_elf_cached(object_name);
|
||||
#elif IS_APPLE
|
||||
auto object = mach_o::open_mach_o(object_name);
|
||||
auto object = open_mach_o_cached(object_name);
|
||||
#endif
|
||||
auto resolver = get_resolver(object_name);
|
||||
for(const auto& entry : group.second) {
|
||||
@ -116,7 +116,9 @@ namespace libdwarf {
|
||||
}
|
||||
#if IS_LINUX || IS_APPLE
|
||||
if(frame.frame.symbol.empty() && object.has_value()) {
|
||||
frame.frame.symbol = object.unwrap_value().lookup_symbol(dlframe.object_address);
|
||||
frame.frame.symbol = object
|
||||
.unwrap_value()
|
||||
->lookup_symbol(dlframe.object_address).value_or("");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -4,10 +4,9 @@
|
||||
#include "unwind/unwind.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "platform/dbghelp_syminit_manager.hpp"
|
||||
#include "platform/dbghelp_utils.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <cstddef>
|
||||
|
||||
#include <windows.h>
|
||||
@ -96,31 +95,19 @@ namespace detail {
|
||||
std::vector<frame_ptr> trace;
|
||||
|
||||
// Dbghelp is is single-threaded, so acquire a lock.
|
||||
static std::mutex mutex;
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
auto lock = get_dbghelp_lock();
|
||||
// For some reason SymInitialize must be called before StackWalk64
|
||||
// Note that the code assumes that
|
||||
// SymInitialize( GetCurrentProcess(), NULL, TRUE ) has
|
||||
// already been called.
|
||||
//
|
||||
HANDLE duplicated_handle = nullptr;
|
||||
HANDLE proc = GetCurrentProcess();
|
||||
auto syminit_info = ensure_syminit();
|
||||
HANDLE thread = GetCurrentThread();
|
||||
if(get_cache_mode() == cache_mode::prioritize_speed) {
|
||||
duplicated_handle = get_syminit_manager().init(proc);
|
||||
} else {
|
||||
if(!DuplicateHandle(proc, proc, proc, &duplicated_handle, 0, FALSE, DUPLICATE_SAME_ACCESS)) {
|
||||
throw internal_error("DuplicateHandle failed{}", GetLastError());
|
||||
}
|
||||
if(!SymInitialize(duplicated_handle, NULL, TRUE)) {
|
||||
throw internal_error("SymInitialize failed{}", GetLastError());
|
||||
}
|
||||
}
|
||||
while(trace.size() < max_depth) {
|
||||
if(
|
||||
!StackWalk64(
|
||||
machine_type,
|
||||
duplicated_handle,
|
||||
syminit_info.get_process_handle(),
|
||||
thread,
|
||||
&frame,
|
||||
machine_type == IMAGE_FILE_MACHINE_I386 ? NULL : &context,
|
||||
@ -148,14 +135,6 @@ namespace detail {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(get_cache_mode() != cache_mode::prioritize_speed) {
|
||||
if(!SymCleanup(duplicated_handle)) {
|
||||
throw internal_error("SymCleanup failed");
|
||||
}
|
||||
if(!CloseHandle(duplicated_handle)) {
|
||||
throw internal_error("CloseHandle failed");
|
||||
}
|
||||
}
|
||||
return trace;
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#include <cpptrace/utils.hpp>
|
||||
#include <cpptrace/exceptions.hpp>
|
||||
#include <cpptrace/formatting.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
@ -7,10 +8,11 @@
|
||||
#include "snippets/snippet.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "platform/exception_type.hpp"
|
||||
#include "options.hpp"
|
||||
|
||||
namespace cpptrace {
|
||||
std::string demangle(const std::string& name) {
|
||||
return detail::demangle(name);
|
||||
return detail::demangle(name, false);
|
||||
}
|
||||
|
||||
std::string get_snippet(const std::string& path, std::size_t line, std::size_t context_size, bool color) {
|
||||
@ -25,14 +27,17 @@ namespace cpptrace {
|
||||
extern const int stdout_fileno = detail::fileno(stdout);
|
||||
extern const int stderr_fileno = detail::fileno(stderr);
|
||||
|
||||
namespace detail {
|
||||
const formatter& get_terminate_formatter() {
|
||||
static formatter the_formatter = formatter{}
|
||||
.header("Stack trace to reach terminate handler (most recent call first):");
|
||||
return the_formatter;
|
||||
}
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE void print_terminate_trace() {
|
||||
try { // try/catch can never be hit but it's needed to prevent TCO
|
||||
generate_trace(1).print(
|
||||
std::cerr,
|
||||
isatty(stderr_fileno),
|
||||
true,
|
||||
"Stack trace to reach terminate handler (most recent call first):"
|
||||
);
|
||||
detail::get_terminate_formatter().print(std::cerr, generate_trace(1));
|
||||
} catch(...) {
|
||||
if(!detail::should_absorb_trace_exceptions()) {
|
||||
throw;
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
#include <cpptrace/basic.hpp>
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
#include "options.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
@ -31,6 +30,13 @@
|
||||
#define MSVC_CDECL
|
||||
#endif
|
||||
|
||||
// support is pretty good https://godbolt.org/z/djTqv7WMY, checked in cmake during config
|
||||
#ifdef HAS_ATTRIBUTE_PACKED
|
||||
#define PACKED __attribute__((packed))
|
||||
#else
|
||||
#define PACKED
|
||||
#endif
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
static const stacktrace_frame null_frame {
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
#ifndef ERROR_HPP
|
||||
#define ERROR_HPP
|
||||
|
||||
#include <atomic>
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@ -165,8 +164,6 @@ namespace detail {
|
||||
// Check condition in both debug. std::runtime_error on failure.
|
||||
#define ASSERT(...) PHONY_USE(__VA_ARGS__)
|
||||
#endif
|
||||
|
||||
extern std::atomic_bool absorb_trace_exceptions;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
108
src/utils/lru_cache.hpp
Normal file
108
src/utils/lru_cache.hpp
Normal file
@ -0,0 +1,108 @@
|
||||
#ifndef LRU_CACHE_HPP
|
||||
#define LRU_CACHE_HPP
|
||||
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/optional.hpp"
|
||||
|
||||
#include <list>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
template<typename K, typename V>
|
||||
class lru_cache {
|
||||
struct kvp {
|
||||
K key;
|
||||
V value;
|
||||
};
|
||||
using list_type = std::list<kvp>;
|
||||
using list_iterator = typename list_type::iterator;
|
||||
mutable list_type lru;
|
||||
std::unordered_map<K, list_iterator> map;
|
||||
optional<std::size_t> max_size;
|
||||
|
||||
public:
|
||||
lru_cache() = default;
|
||||
lru_cache(optional<std::size_t> max_size) : max_size(max_size) {
|
||||
VERIFY(!max_size || max_size.unwrap() > 0);
|
||||
}
|
||||
|
||||
void set_max_size(optional<std::size_t> size) {
|
||||
VERIFY(!size || size.unwrap() > 0);
|
||||
max_size = size;
|
||||
maybe_trim();
|
||||
}
|
||||
|
||||
void maybe_touch(const K& key) {
|
||||
auto it = map.find(key);
|
||||
if(it == map.end()) {
|
||||
return;
|
||||
}
|
||||
auto list_it = it->second;
|
||||
touch(list_it);
|
||||
}
|
||||
|
||||
optional<V&> maybe_get(const K& key) {
|
||||
auto it = map.find(key);
|
||||
if(it == map.end()) {
|
||||
return nullopt;
|
||||
} else {
|
||||
touch(it->second);
|
||||
return it->second->value;
|
||||
}
|
||||
}
|
||||
|
||||
optional<const V&> maybe_get(const K& key) const {
|
||||
auto it = map.find(key);
|
||||
if(it == map.end()) {
|
||||
return nullopt;
|
||||
} else {
|
||||
touch(it->second);
|
||||
return it->second->value;
|
||||
}
|
||||
}
|
||||
|
||||
void set(const K& key, V value) {
|
||||
auto it = map.find(key);
|
||||
if(it == map.end()) {
|
||||
insert(key, std::move(value));
|
||||
} else {
|
||||
touch(it->second);
|
||||
it->second->value = std::move(value);
|
||||
}
|
||||
}
|
||||
|
||||
optional<V&> insert(const K& key, V value) {
|
||||
auto pair = map.insert({key, lru.end()});
|
||||
if(!pair.second) {
|
||||
// didn't insert
|
||||
return nullopt;
|
||||
}
|
||||
auto map_it = pair.first;
|
||||
lru.push_front({key, std::move(value)});
|
||||
map_it->second = lru.begin();
|
||||
maybe_trim();
|
||||
return lru.front().value;
|
||||
}
|
||||
|
||||
std::size_t size() const {
|
||||
return lru.size();
|
||||
}
|
||||
|
||||
private:
|
||||
void touch(list_iterator list_it) const {
|
||||
lru.splice(lru.begin(), lru, list_it);
|
||||
}
|
||||
|
||||
void maybe_trim() {
|
||||
while(max_size && lru.size() > max_size.unwrap()) {
|
||||
const auto& to_remove = lru.back();
|
||||
map.erase(to_remove.key);
|
||||
lru.pop_back();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -8,6 +8,7 @@
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/optional.hpp"
|
||||
#include "options.hpp"
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
@ -27,8 +28,8 @@ namespace detail {
|
||||
std::fprintf(stderr, "%s\n", unwrap_error().what());
|
||||
}
|
||||
}
|
||||
Result(value_type& value) : value_(value_type(value)), active(member::value) {}
|
||||
Result(E& error) : error_(E(error)), active(member::error) {
|
||||
Result(const value_type& value) : value_(value_type(value)), active(member::value) {}
|
||||
Result(const E& error) : error_(E(error)), active(member::error) {
|
||||
if(!should_absorb_trace_exceptions()) {
|
||||
std::fprintf(stderr, "%s\n", unwrap_error().what());
|
||||
}
|
||||
@ -126,6 +127,24 @@ namespace detail {
|
||||
return has_value() ? static_cast<T>(std::move(value_)) : static_cast<T>(std::forward<U>(default_value));
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
NODISCARD auto transform(F&& f) & -> Result<decltype(f(std::declval<T&>())), E> {
|
||||
if(has_value()) {
|
||||
return f(unwrap_value());
|
||||
} else {
|
||||
return unwrap_error();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
NODISCARD auto transform(F&& f) && -> Result<decltype(f(std::declval<T&&>())), E> {
|
||||
if(has_value()) {
|
||||
return f(std::move(unwrap_value()));
|
||||
} else {
|
||||
return unwrap_error();
|
||||
}
|
||||
}
|
||||
|
||||
void drop_error() const {
|
||||
if(is_error()) {
|
||||
std::fprintf(stderr, "%s\n", unwrap_error().what());
|
||||
|
||||
@ -7,7 +7,6 @@
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
@ -163,9 +162,9 @@ namespace detail {
|
||||
// shamelessly stolen from stackoverflow
|
||||
bool directory_exists(const std::string& path);
|
||||
|
||||
inline std::string basename(const std::string& path) {
|
||||
inline std::string basename(const std::string& path, bool maybe_windows = false) {
|
||||
// Assumes no trailing /'s
|
||||
auto pos = path.rfind('/');
|
||||
auto pos = path.find_last_of(maybe_windows ? "/\\" : "/");
|
||||
if(pos == std::string::npos) {
|
||||
return path;
|
||||
} else {
|
||||
@ -189,22 +188,24 @@ namespace detail {
|
||||
return static_cast<U>(v);
|
||||
}
|
||||
|
||||
template<typename T, typename U = T>
|
||||
T exchange(T& obj, U&& value) {
|
||||
T old = std::move(obj);
|
||||
obj = std::forward<U>(value);
|
||||
return old;
|
||||
}
|
||||
|
||||
struct monostate {};
|
||||
|
||||
// TODO: Rework some stuff here. Not sure deleters should be optional or moved.
|
||||
// Also allow file_wrapper file = std::fopen(object_path.c_str(), "rb");
|
||||
template<
|
||||
typename T,
|
||||
typename D
|
||||
// workaround for:
|
||||
typename D,
|
||||
// Note: Previously checked if D was invocable and returned void but this kept causing problems for MSVC
|
||||
// == 19.38-specific msvc bug https://developercommunity.visualstudio.com/t/MSVC-1938331290-preview-fails-to-comp/10505565
|
||||
// <= 19.23 msvc also appears to fail (but for a different reason https://godbolt.org/z/6Y5EvdWPK)
|
||||
#if !defined(_MSC_VER) || !(_MSC_VER <= 1923 || _MSC_VER == 1938)
|
||||
,
|
||||
typename std::enable_if<
|
||||
std::is_same<decltype(std::declval<D>()(std::declval<T>())), void>::value,
|
||||
int
|
||||
>::type = 0,
|
||||
// <= 19.39 msvc also has trouble with it for different reasons https://godbolt.org/z/aPPPT7z3z
|
||||
typename std::enable_if<
|
||||
std::is_standard_layout<T>::value && std::is_trivial<T>::value,
|
||||
int
|
||||
@ -213,7 +214,6 @@ namespace detail {
|
||||
std::is_nothrow_move_constructible<T>::value,
|
||||
int
|
||||
>::type = 0
|
||||
#endif
|
||||
>
|
||||
class raii_wrapper {
|
||||
T obj;
|
||||
@ -245,22 +245,7 @@ namespace detail {
|
||||
}
|
||||
};
|
||||
|
||||
template<
|
||||
typename T,
|
||||
typename D
|
||||
// workaround a msvc bug https://developercommunity.visualstudio.com/t/MSVC-1938331290-preview-fails-to-comp/10505565
|
||||
#if !defined(_MSC_VER) || _MSC_VER != 1938
|
||||
,
|
||||
typename std::enable_if<
|
||||
std::is_same<decltype(std::declval<D>()(std::declval<T>())), void>::value,
|
||||
int
|
||||
>::type = 0,
|
||||
typename std::enable_if<
|
||||
std::is_standard_layout<T>::value && std::is_trivial<T>::value,
|
||||
int
|
||||
>::type = 0
|
||||
#endif
|
||||
>
|
||||
template<typename T, typename D>
|
||||
raii_wrapper<typename std::remove_reference<T>::type, D> raii_wrap(T obj, D deleter) {
|
||||
return raii_wrapper<typename std::remove_reference<T>::type, D>(obj, deleter);
|
||||
}
|
||||
@ -273,6 +258,11 @@ namespace detail {
|
||||
|
||||
using file_wrapper = raii_wrapper<std::FILE*, void(*)(std::FILE*)>;
|
||||
|
||||
template<class T, class... Args>
|
||||
auto make_unique(Args&&... args) -> typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::type {
|
||||
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
class maybe_owned {
|
||||
std::unique_ptr<T> owned;
|
||||
@ -283,7 +273,40 @@ namespace detail {
|
||||
T* operator->() {
|
||||
return ptr;
|
||||
}
|
||||
T& operator*() {
|
||||
return *ptr;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename F>
|
||||
class scope_guard {
|
||||
F f;
|
||||
bool active;
|
||||
public:
|
||||
template<
|
||||
typename G,
|
||||
typename std::enable_if<!std::is_same<typename std::decay<G>::type, scope_guard<F>>::value, int>::type = 0
|
||||
>
|
||||
scope_guard(G&& f) : f(std::forward<F>(f)), active(true) {}
|
||||
~scope_guard() {
|
||||
if(active) {
|
||||
f();
|
||||
}
|
||||
}
|
||||
scope_guard(const scope_guard&) = delete;
|
||||
scope_guard(scope_guard&& other) : f(std::move(other.f)), active(exchange(other.active, false)) {}
|
||||
scope_guard& operator=(const scope_guard&) = delete;
|
||||
scope_guard& operator=(scope_guard&& other) {
|
||||
f = std::move(other.f);
|
||||
active = exchange(other.active, false);
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename F>
|
||||
NODISCARD auto scope_exit(F&& f) -> scope_guard<F> {
|
||||
return scope_guard<F>(std::forward<F>(f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -17,7 +17,12 @@ cc_test(
|
||||
"unit/internals/optional.cpp",
|
||||
"unit/internals/result.cpp",
|
||||
"unit/internals/string_utils.cpp",
|
||||
"unit/internals/general.cpp"
|
||||
"unit/internals/general.cpp",
|
||||
"unit/lib/formatting.cpp",
|
||||
"unit/lib/nullable.cpp"
|
||||
],
|
||||
local_defines = [
|
||||
"CPPTRACE_NO_TEST_SNIPPETS"
|
||||
],
|
||||
linkstatic = 1,
|
||||
)
|
||||
@ -84,9 +84,12 @@ if(NOT CPPTRACE_SKIP_UNIT)
|
||||
unit/tracing/from_current_z.cpp
|
||||
unit/tracing/traced_exception.cpp
|
||||
unit/internals/optional.cpp
|
||||
unit/internals/lru_cache.cpp
|
||||
unit/internals/result.cpp
|
||||
unit/internals/string_utils.cpp
|
||||
unit/internals/general.cpp
|
||||
unit/lib/formatting.cpp
|
||||
unit/lib/nullable.cpp
|
||||
)
|
||||
target_compile_features(unittest PRIVATE cxx_std_20)
|
||||
target_link_libraries(unittest PRIVATE ${target_name} GTest::gtest_main GTest::gmock_main)
|
||||
|
||||
104
test/unit/internals/lru_cache.cpp
Normal file
104
test/unit/internals/lru_cache.cpp
Normal file
@ -0,0 +1,104 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "utils/lru_cache.hpp"
|
||||
|
||||
using cpptrace::detail::lru_cache;
|
||||
using cpptrace::detail::nullopt;
|
||||
|
||||
namespace {
|
||||
|
||||
TEST(LruCacheTest, DefaultConstructor) {
|
||||
lru_cache<int, int> cache;
|
||||
EXPECT_EQ(cache.size(), 0);
|
||||
}
|
||||
|
||||
TEST(LruCacheTest, MaybeGet) {
|
||||
lru_cache<int, int> cache;
|
||||
auto result = cache.maybe_get(42);
|
||||
EXPECT_FALSE(result.has_value());
|
||||
}
|
||||
|
||||
TEST(LruCacheTest, InsertAndGet) {
|
||||
lru_cache<int, int> cache;
|
||||
cache.insert(42, 50);
|
||||
auto result = cache.maybe_get(42);
|
||||
ASSERT_TRUE(result.has_value());
|
||||
EXPECT_EQ(result.unwrap(), 50);
|
||||
}
|
||||
|
||||
TEST(LruCacheTest, ConstGet) {
|
||||
lru_cache<int, int> cache;
|
||||
cache.insert(42, 50);
|
||||
const lru_cache<int, int>& cache_ref = cache;
|
||||
auto result = cache_ref.maybe_get(42);
|
||||
ASSERT_TRUE(result.has_value());
|
||||
EXPECT_EQ(result.unwrap(), 50);
|
||||
}
|
||||
|
||||
TEST(LruCacheTest, Set) {
|
||||
lru_cache<int, int> cache;
|
||||
cache.set(42, 50);
|
||||
auto result = cache.maybe_get(42);
|
||||
ASSERT_TRUE(result.has_value());
|
||||
EXPECT_EQ(result.unwrap(), 50);
|
||||
cache.set(42, 60);
|
||||
auto result2 = cache.maybe_get(42);
|
||||
ASSERT_TRUE(result2.has_value());
|
||||
EXPECT_EQ(result2.unwrap(), 60);
|
||||
}
|
||||
|
||||
TEST(LruCacheTest, NoMaxSize) {
|
||||
lru_cache<int, int> cache;
|
||||
for(int i = 0; i < 1000; i++) {
|
||||
cache.insert(i, i + 50);
|
||||
}
|
||||
EXPECT_EQ(cache.size(), 1000);
|
||||
for(int i = 0; i < 1000; i++) {
|
||||
EXPECT_EQ(cache.maybe_get(i).unwrap(), i + 50);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(LruCacheTest, MaxSize) {
|
||||
lru_cache<int, int> cache(20);
|
||||
for(int i = 0; i < 1000; i++) {
|
||||
cache.insert(i, i + 50);
|
||||
}
|
||||
EXPECT_EQ(cache.size(), 20);
|
||||
for(int i = 0; i < 1000 - 20; i++) {
|
||||
EXPECT_FALSE(cache.maybe_get(i).has_value());
|
||||
}
|
||||
for(int i = 1000 - 20; i < 1000; i++) {
|
||||
EXPECT_EQ(cache.maybe_get(i).unwrap(), i + 50);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(LruCacheTest, SizeAfterInserts) {
|
||||
lru_cache<int, int> cache;
|
||||
for(int i = 0; i < 1000; i++) {
|
||||
cache.insert(i, i + 50);
|
||||
}
|
||||
EXPECT_EQ(cache.size(), 1000);
|
||||
cache.set_max_size(20);
|
||||
EXPECT_EQ(cache.size(), 20);
|
||||
for(int i = 0; i < 1000 - 20; i++) {
|
||||
EXPECT_FALSE(cache.maybe_get(i).has_value());
|
||||
}
|
||||
for(int i = 1000 - 20; i < 1000; i++) {
|
||||
EXPECT_EQ(cache.maybe_get(i).unwrap(), i + 50);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(LruCacheTest, Touch) {
|
||||
lru_cache<int, int> cache(20);
|
||||
for(int i = 0; i < 1000; i++) {
|
||||
cache.maybe_touch(0);
|
||||
cache.insert(i, i + 50);
|
||||
}
|
||||
EXPECT_EQ(cache.size(), 20);
|
||||
for(int i = 1000 - 19; i < 1000; i++) {
|
||||
EXPECT_EQ(cache.maybe_get(i).unwrap(), i + 50);
|
||||
}
|
||||
EXPECT_EQ(cache.maybe_get(0).unwrap(), 50);
|
||||
}
|
||||
|
||||
}
|
||||
318
test/unit/lib/formatting.cpp
Normal file
318
test/unit/lib/formatting.cpp
Normal file
@ -0,0 +1,318 @@
|
||||
#include <cpptrace/formatting.hpp>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <gtest/gtest-matchers.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gmock/gmock-matchers.h>
|
||||
|
||||
#include "utils/microfmt.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
using cpptrace::detail::split;
|
||||
using testing::ElementsAre;
|
||||
|
||||
namespace {
|
||||
|
||||
cpptrace::stacktrace make_test_stacktrace() {
|
||||
cpptrace::stacktrace trace;
|
||||
trace.frames.push_back({0x1, 0x1001, {20}, {30}, "foo.cpp", "foo()", false});
|
||||
trace.frames.push_back({0x2, 0x1002, {30}, {40}, "bar.cpp", "bar()", false});
|
||||
trace.frames.push_back({0x3, 0x1003, {40}, {25}, "foo.cpp", "main", false});
|
||||
return trace;
|
||||
}
|
||||
|
||||
TEST(FormatterTest, Basic) {
|
||||
auto res = split(cpptrace::get_default_formatter().format(make_test_stacktrace()), "\n");
|
||||
EXPECT_THAT(
|
||||
res,
|
||||
ElementsAre(
|
||||
"Stack trace (most recent call first):",
|
||||
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
|
||||
"#1 0x0000000000000002 in bar() at bar.cpp:30:40",
|
||||
"#2 0x0000000000000003 in main at foo.cpp:40:25"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
TEST(FormatterTest, Inlines) {
|
||||
auto trace = make_test_stacktrace();
|
||||
trace.frames[1].is_inline = true;
|
||||
trace.frames[1].raw_address = 0;
|
||||
trace.frames[1].object_address = 0;
|
||||
auto res = split(cpptrace::get_default_formatter().format(trace), "\n");
|
||||
EXPECT_THAT(
|
||||
res,
|
||||
ElementsAre(
|
||||
"Stack trace (most recent call first):",
|
||||
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
|
||||
"#1 (inlined) in bar() at bar.cpp:30:40",
|
||||
"#2 0x0000000000000003 in main at foo.cpp:40:25"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
TEST(FormatterTest, Header) {
|
||||
auto formatter = cpptrace::formatter{}
|
||||
.header("Stack trace:");
|
||||
auto res = split(formatter.format(make_test_stacktrace()), "\n");
|
||||
EXPECT_THAT(
|
||||
res,
|
||||
ElementsAre(
|
||||
"Stack trace:",
|
||||
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
|
||||
"#1 0x0000000000000002 in bar() at bar.cpp:30:40",
|
||||
"#2 0x0000000000000003 in main at foo.cpp:40:25"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
TEST(FormatterTest, NoColumn) {
|
||||
auto formatter = cpptrace::formatter{}
|
||||
.columns(false);
|
||||
auto res = split(formatter.format(make_test_stacktrace()), "\n");
|
||||
EXPECT_THAT(
|
||||
res,
|
||||
ElementsAre(
|
||||
"Stack trace (most recent call first):",
|
||||
"#0 0x0000000000000001 in foo() at foo.cpp:20",
|
||||
"#1 0x0000000000000002 in bar() at bar.cpp:30",
|
||||
"#2 0x0000000000000003 in main at foo.cpp:40"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
TEST(FormatterTest, ObjectAddresses) {
|
||||
auto formatter = cpptrace::formatter{}
|
||||
.addresses(cpptrace::formatter::address_mode::object);
|
||||
auto res = split(formatter.format(make_test_stacktrace()), "\n");
|
||||
EXPECT_THAT(
|
||||
res,
|
||||
ElementsAre(
|
||||
"Stack trace (most recent call first):",
|
||||
"#0 0x0000000000001001 in foo() at foo.cpp:20:30",
|
||||
"#1 0x0000000000001002 in bar() at bar.cpp:30:40",
|
||||
"#2 0x0000000000001003 in main at foo.cpp:40:25"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
TEST(FormatterTest, NoAddresses) {
|
||||
auto formatter = cpptrace::formatter{}
|
||||
.addresses(cpptrace::formatter::address_mode::none);
|
||||
auto res = split(formatter.format(make_test_stacktrace()), "\n");
|
||||
EXPECT_THAT(
|
||||
res,
|
||||
ElementsAre(
|
||||
"Stack trace (most recent call first):",
|
||||
"#0 in foo() at foo.cpp:20:30",
|
||||
"#1 in bar() at bar.cpp:30:40",
|
||||
"#2 in main at foo.cpp:40:25"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
TEST(FormatterTest, PathShortening) {
|
||||
cpptrace::stacktrace trace;
|
||||
trace.frames.push_back({0x1, 0x1001, {20}, {30}, "/home/foo/foo.cpp", "foo()", false});
|
||||
trace.frames.push_back({0x2, 0x1002, {30}, {40}, "/bar.cpp", "bar()", false});
|
||||
trace.frames.push_back({0x3, 0x1003, {40}, {25}, "baz/foo.cpp", "main", false});
|
||||
trace.frames.push_back({0x3, 0x1003, {50}, {25}, "C:\\foo\\bar\\baz.cpp", "main", false});
|
||||
auto formatter = cpptrace::formatter{}
|
||||
.paths(cpptrace::formatter::path_mode::basename);
|
||||
auto res = split(formatter.format(trace), "\n");
|
||||
EXPECT_THAT(
|
||||
res,
|
||||
ElementsAre(
|
||||
"Stack trace (most recent call first):",
|
||||
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
|
||||
"#1 0x0000000000000002 in bar() at bar.cpp:30:40",
|
||||
"#2 0x0000000000000003 in main at foo.cpp:40:25",
|
||||
"#3 0x0000000000000003 in main at baz.cpp:50:25"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#ifndef CPPTRACE_NO_TEST_SNIPPETS
|
||||
TEST(FormatterTest, Snippets) {
|
||||
cpptrace::stacktrace trace;
|
||||
unsigned line = __LINE__ + 1;
|
||||
trace.frames.push_back({0x1, 0x1001, {line}, {20}, __FILE__, "foo()", false});
|
||||
trace.frames.push_back({0x2, 0x1002, {line + 1}, {20}, __FILE__, "foo()", false});
|
||||
auto formatter = cpptrace::formatter{}
|
||||
.snippets(true);
|
||||
auto res = split(formatter.format(trace), "\n");
|
||||
EXPECT_THAT(
|
||||
res,
|
||||
ElementsAre(
|
||||
"Stack trace (most recent call first):",
|
||||
// frame 1
|
||||
cpptrace::microfmt::format("#0 0x0000000000000001 in foo() at {}:{}:20", __FILE__, line),
|
||||
cpptrace::microfmt::format(" {}: cpptrace::stacktrace trace;", line - 2),
|
||||
cpptrace::microfmt::format(" {}: unsigned line = __LINE__ + 1;", line - 1),
|
||||
cpptrace::microfmt::format(
|
||||
" {}: trace.frames.push_back({0x1, 0x1001, {line}, {{20}}, __FILE__, \"foo()\", false});",
|
||||
line
|
||||
),
|
||||
cpptrace::microfmt::format(
|
||||
" {}: trace.frames.push_back({0x2, 0x1002, {line + 1}, {{20}}, __FILE__, \"foo()\", false});",
|
||||
line + 1
|
||||
),
|
||||
cpptrace::microfmt::format(" {}: auto formatter = cpptrace::formatter{{}}", line + 2),
|
||||
// frame 2
|
||||
cpptrace::microfmt::format("#1 0x0000000000000002 in foo() at {}:{}:20", __FILE__, line + 1),
|
||||
cpptrace::microfmt::format(" {}: unsigned line = __LINE__ + 1;", line - 1),
|
||||
cpptrace::microfmt::format(
|
||||
" {}: trace.frames.push_back({0x1, 0x1001, {line}, {{20}}, __FILE__, \"foo()\", false});",
|
||||
line
|
||||
),
|
||||
cpptrace::microfmt::format(
|
||||
" {}: trace.frames.push_back({0x2, 0x1002, {line + 1}, {{20}}, __FILE__, \"foo()\", false});",
|
||||
line + 1
|
||||
),
|
||||
cpptrace::microfmt::format(" {}: auto formatter = cpptrace::formatter{{}}", line + 2),
|
||||
cpptrace::microfmt::format(" {}: .snippets(true);", line + 3)
|
||||
)
|
||||
);
|
||||
formatter.snippet_context(1);
|
||||
res = split(formatter.format(trace), "\n");
|
||||
EXPECT_THAT(
|
||||
res,
|
||||
ElementsAre(
|
||||
"Stack trace (most recent call first):",
|
||||
// frame 1
|
||||
cpptrace::microfmt::format("#0 0x0000000000000001 in foo() at {}:{}:20", __FILE__, line),
|
||||
cpptrace::microfmt::format(" {}: unsigned line = __LINE__ + 1;", line - 1),
|
||||
cpptrace::microfmt::format(
|
||||
" {}: trace.frames.push_back({0x1, 0x1001, {line}, {{20}}, __FILE__, \"foo()\", false});",
|
||||
line
|
||||
),
|
||||
cpptrace::microfmt::format(
|
||||
" {}: trace.frames.push_back({0x2, 0x1002, {line + 1}, {{20}}, __FILE__, \"foo()\", false});",
|
||||
line + 1
|
||||
),
|
||||
// frame 2
|
||||
cpptrace::microfmt::format("#1 0x0000000000000002 in foo() at {}:{}:20", __FILE__, line + 1),
|
||||
cpptrace::microfmt::format(
|
||||
" {}: trace.frames.push_back({0x1, 0x1001, {line}, {{20}}, __FILE__, \"foo()\", false});",
|
||||
line
|
||||
),
|
||||
cpptrace::microfmt::format(
|
||||
" {}: trace.frames.push_back({0x2, 0x1002, {line + 1}, {{20}}, __FILE__, \"foo()\", false});",
|
||||
line + 1
|
||||
),
|
||||
cpptrace::microfmt::format(" {}: auto formatter = cpptrace::formatter{{}}", line + 2)
|
||||
)
|
||||
);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(FormatterTest, Colors) {
|
||||
auto formatter = cpptrace::formatter{}
|
||||
.colors(cpptrace::formatter::color_mode::always);
|
||||
auto res = split(formatter.format(make_test_stacktrace()), "\n");
|
||||
EXPECT_THAT(
|
||||
res,
|
||||
ElementsAre(
|
||||
"Stack trace (most recent call first):",
|
||||
"#0 \x1B[34m0x0000000000000001\x1B[0m in \x1B[33mfoo()\x1B[0m at \x1B[32mfoo.cpp\x1B[0m:\x1B[34m20\x1B[0m:\x1B[34m30\x1B[0m",
|
||||
"#1 \x1B[34m0x0000000000000002\x1B[0m in \x1B[33mbar()\x1B[0m at \x1B[32mbar.cpp\x1B[0m:\x1B[34m30\x1B[0m:\x1B[34m40\x1B[0m",
|
||||
"#2 \x1B[34m0x0000000000000003\x1B[0m in \x1B[33mmain\x1B[0m at \x1B[32mfoo.cpp\x1B[0m:\x1B[34m40\x1B[0m:\x1B[34m25\x1B[0m"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
TEST(FormatterTest, Filtering) {
|
||||
auto formatter = cpptrace::formatter{}
|
||||
.filter([] (const cpptrace::stacktrace_frame& frame) -> bool {
|
||||
return frame.filename.find("foo.cpp") != std::string::npos;
|
||||
});
|
||||
auto res = split(formatter.format(make_test_stacktrace()), "\n");
|
||||
EXPECT_THAT(
|
||||
res,
|
||||
ElementsAre(
|
||||
"Stack trace (most recent call first):",
|
||||
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
|
||||
"#1 (filtered)",
|
||||
"#2 0x0000000000000003 in main at foo.cpp:40:25"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
TEST(FormatterTest, DontShowFilteredFrames) {
|
||||
auto formatter = cpptrace::formatter{}
|
||||
.filter([] (const cpptrace::stacktrace_frame& frame) -> bool {
|
||||
return frame.filename.find("foo.cpp") != std::string::npos;
|
||||
})
|
||||
.filtered_frame_placeholders(false);
|
||||
auto res = split(formatter.format(make_test_stacktrace()), "\n");
|
||||
EXPECT_THAT(
|
||||
res,
|
||||
ElementsAre(
|
||||
"Stack trace (most recent call first):",
|
||||
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
|
||||
"#2 0x0000000000000003 in main at foo.cpp:40:25"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
TEST(FormatterTest, MoveSemantics) {
|
||||
auto formatter = cpptrace::formatter{}
|
||||
.filter([] (const cpptrace::stacktrace_frame& frame) -> bool {
|
||||
return frame.filename.find("foo.cpp") != std::string::npos;
|
||||
});
|
||||
auto formatter2 = std::move(formatter);
|
||||
auto res = split(formatter2.format(make_test_stacktrace()), "\n");
|
||||
EXPECT_THAT(
|
||||
res,
|
||||
ElementsAre(
|
||||
"Stack trace (most recent call first):",
|
||||
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
|
||||
"#1 (filtered)",
|
||||
"#2 0x0000000000000003 in main at foo.cpp:40:25"
|
||||
)
|
||||
);
|
||||
cpptrace::formatter formatter3;
|
||||
formatter3 = std::move(formatter);
|
||||
auto res2 = split(formatter2.format(make_test_stacktrace()), "\n");
|
||||
EXPECT_THAT(
|
||||
res2,
|
||||
ElementsAre(
|
||||
"Stack trace (most recent call first):",
|
||||
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
|
||||
"#1 (filtered)",
|
||||
"#2 0x0000000000000003 in main at foo.cpp:40:25"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
TEST(FormatterTest, CopySemantics) {
|
||||
auto formatter = cpptrace::formatter{}
|
||||
.filter([] (const cpptrace::stacktrace_frame& frame) -> bool {
|
||||
return frame.filename.find("foo.cpp") != std::string::npos;
|
||||
});
|
||||
auto formatter2 = formatter;
|
||||
auto res = split(formatter2.format(make_test_stacktrace()), "\n");
|
||||
EXPECT_THAT(
|
||||
res,
|
||||
ElementsAre(
|
||||
"Stack trace (most recent call first):",
|
||||
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
|
||||
"#1 (filtered)",
|
||||
"#2 0x0000000000000003 in main at foo.cpp:40:25"
|
||||
)
|
||||
);
|
||||
cpptrace::formatter formatter3;
|
||||
formatter3 = formatter;
|
||||
auto res2 = split(formatter2.format(make_test_stacktrace()), "\n");
|
||||
EXPECT_THAT(
|
||||
res2,
|
||||
ElementsAre(
|
||||
"Stack trace (most recent call first):",
|
||||
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
|
||||
"#1 (filtered)",
|
||||
"#2 0x0000000000000003 in main at foo.cpp:40:25"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
66
test/unit/lib/nullable.cpp
Normal file
66
test/unit/lib/nullable.cpp
Normal file
@ -0,0 +1,66 @@
|
||||
#include <cpptrace/basic.hpp>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <gtest/gtest-matchers.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gmock/gmock-matchers.h>
|
||||
|
||||
using cpptrace::nullable;
|
||||
|
||||
namespace {
|
||||
|
||||
TEST(NullableTest, Basic) {
|
||||
nullable<std::uint32_t> a{12};
|
||||
EXPECT_EQ(a.value(), 12);
|
||||
EXPECT_EQ(a.raw_value, 12);
|
||||
nullable<std::uint32_t> b = 20;
|
||||
EXPECT_EQ(b.value(), 20);
|
||||
}
|
||||
|
||||
TEST(NullableTest, Null) {
|
||||
auto a = nullable<std::uint32_t>::null();
|
||||
EXPECT_FALSE(a.has_value());
|
||||
EXPECT_EQ(a.raw_value, (std::numeric_limits<std::uint32_t>::max)());
|
||||
nullable<std::uint32_t> b;
|
||||
EXPECT_FALSE(b.has_value());
|
||||
EXPECT_EQ(b.raw_value, nullable<std::uint32_t>::null_value());
|
||||
}
|
||||
|
||||
TEST(NullableTest, Assignment) {
|
||||
nullable<std::uint32_t> a;
|
||||
a = 12;
|
||||
EXPECT_EQ(a.value(), 12);
|
||||
nullable<std::uint32_t> b = 20;
|
||||
a = b;
|
||||
EXPECT_EQ(a.value(), 20);
|
||||
}
|
||||
|
||||
TEST(NullableTest, Reset) {
|
||||
nullable<std::uint32_t> a{12};
|
||||
a.reset();
|
||||
EXPECT_FALSE(a.has_value());
|
||||
}
|
||||
|
||||
TEST(NullableTest, ValueOr) {
|
||||
auto a = nullable<std::uint32_t>::null();
|
||||
EXPECT_EQ(a.value_or(20), 20);
|
||||
}
|
||||
|
||||
TEST(NullableTest, Comparison) {
|
||||
EXPECT_EQ(nullable<std::uint32_t>{12}, nullable<std::uint32_t>{12});
|
||||
EXPECT_NE(nullable<std::uint32_t>{12}, nullable<std::uint32_t>{20});
|
||||
EXPECT_NE(nullable<std::uint32_t>{12}, nullable<std::uint32_t>::null());
|
||||
EXPECT_EQ(nullable<std::uint32_t>::null(), nullable<std::uint32_t>::null());
|
||||
}
|
||||
|
||||
TEST(NullableTest, Swap) {
|
||||
auto a = nullable<std::uint32_t>::null();
|
||||
nullable<std::uint32_t> b = 12;
|
||||
EXPECT_FALSE(a.has_value());
|
||||
EXPECT_EQ(b.value(), 12);
|
||||
a.swap(b);
|
||||
EXPECT_FALSE(b.has_value());
|
||||
EXPECT_EQ(a.value(), 12);
|
||||
}
|
||||
|
||||
}
|
||||
48
tools/CMakeLists.txt
Normal file
48
tools/CMakeLists.txt
Normal file
@ -0,0 +1,48 @@
|
||||
include(FetchContent)
|
||||
|
||||
FetchContent_Declare(
|
||||
lyra
|
||||
GIT_SHALLOW TRUE
|
||||
GIT_REPOSITORY "https://github.com/bfgroup/Lyra.git"
|
||||
GIT_TAG "ee3c076fa6b9d64c9d249a21f5b9b5a8dae92cd8"
|
||||
)
|
||||
FetchContent_MakeAvailable(lyra)
|
||||
|
||||
FetchContent_Declare(
|
||||
fmt
|
||||
GIT_SHALLOW TRUE
|
||||
GIT_REPOSITORY "https://github.com/fmtlib/fmt.git"
|
||||
GIT_TAG "e69e5f977d458f2650bb346dadf2ad30c5320281" # v10.2.1
|
||||
)
|
||||
FetchContent_MakeAvailable(fmt)
|
||||
|
||||
set(
|
||||
COMMON_LIBS
|
||||
cpptrace::cpptrace
|
||||
bfg::lyra
|
||||
fmt::fmt
|
||||
)
|
||||
|
||||
function(binary TARGET)
|
||||
cmake_parse_arguments(BINARY "" "" "SOURCES;LIBS;FLAGS;DEFS" ${ARGN})
|
||||
add_executable(${TARGET} main.cpp)
|
||||
if(BINARY_SOURCES)
|
||||
add_library(${TARGET}_OBJ OBJECT ${BINARY_SOURCES})
|
||||
target_link_libraries(${TARGET}_OBJ PUBLIC ${COMMON_LIBS})
|
||||
endif()
|
||||
target_link_libraries(${TARGET} PUBLIC ${COMMON_LIBS})
|
||||
target_link_libraries(${TARGET} PUBLIC ${BINARY_LIBS})
|
||||
target_compile_definitions(${TARGET} PUBLIC ${BINARY_DEFS})
|
||||
target_compile_options(${TARGET} PUBLIC ${BINARY_FLAGS} ${debug} ${warning_options})
|
||||
target_include_directories(${TARGET} PUBLIC "${PROJECT_SOURCE_DIR}/src")
|
||||
target_compile_features(${TARGET} PRIVATE cxx_std_20)
|
||||
set_target_properties(
|
||||
${TARGET}
|
||||
PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
|
||||
)
|
||||
endfunction()
|
||||
|
||||
add_subdirectory(dwarfdump)
|
||||
add_subdirectory(symbol_tables)
|
||||
add_subdirectory(resolver)
|
||||
3
tools/dwarfdump/CMakeLists.txt
Normal file
3
tools/dwarfdump/CMakeLists.txt
Normal file
@ -0,0 +1,3 @@
|
||||
if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF)
|
||||
binary(dwarfdump LIBS ${dwarf_lib})
|
||||
endif()
|
||||
179
tools/dwarfdump/main.cpp
Normal file
179
tools/dwarfdump/main.cpp
Normal file
@ -0,0 +1,179 @@
|
||||
#include <lyra/lyra.hpp>
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/std.h>
|
||||
#include <fmt/ostream.h>
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
#include <cpptrace/from_current.hpp>
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include "symbols/dwarf/dwarf.hpp"
|
||||
|
||||
using namespace std::literals;
|
||||
using namespace cpptrace::detail::libdwarf;
|
||||
|
||||
template<> struct fmt::formatter<lyra::cli> : ostream_formatter {};
|
||||
|
||||
class DwarfDumper {
|
||||
std::string object_path;
|
||||
Dwarf_Debug dbg = nullptr;
|
||||
|
||||
// Error handling helper
|
||||
// For some reason R (*f)(Args..., void*)-style deduction isn't possible, seems like a bug in all compilers
|
||||
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56190
|
||||
// TODO: Duplicate
|
||||
template<
|
||||
typename... Args,
|
||||
typename... Args2,
|
||||
typename std::enable_if<
|
||||
std::is_same<
|
||||
decltype(
|
||||
(void)std::declval<int(Args...)>()(std::forward<Args2>(std::declval<Args2>())..., nullptr)
|
||||
),
|
||||
void
|
||||
>::value,
|
||||
int
|
||||
>::type = 0
|
||||
>
|
||||
int wrap(int (*f)(Args...), Args2&&... args) const {
|
||||
Dwarf_Error error = nullptr;
|
||||
int ret = f(std::forward<Args2>(args)..., &error);
|
||||
if(ret == DW_DLV_ERROR) {
|
||||
handle_dwarf_error(dbg, error);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// TODO: Duplicate
|
||||
// walk all CU's in a dbg, callback is called on each die and should return true to
|
||||
// continue traversal
|
||||
void walk_compilation_units(const std::function<bool(const die_object&)>& fn) {
|
||||
// libdwarf keeps track of where it is in the file, dwarf_next_cu_header_d is statefull
|
||||
Dwarf_Unsigned next_cu_header;
|
||||
Dwarf_Half header_cu_type;
|
||||
while(true) {
|
||||
int ret = wrap(
|
||||
dwarf_next_cu_header_d,
|
||||
dbg,
|
||||
true,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
&next_cu_header,
|
||||
&header_cu_type
|
||||
);
|
||||
if(ret == DW_DLV_NO_ENTRY) {
|
||||
fmt::println("End walk_dbg");
|
||||
return;
|
||||
}
|
||||
if(ret != DW_DLV_OK) {
|
||||
PANIC("Unexpected return code from dwarf_next_cu_header_d");
|
||||
return;
|
||||
}
|
||||
// 0 passed as the die to the first call of dwarf_siblingof_b immediately after dwarf_next_cu_header_d
|
||||
// to fetch the cu die
|
||||
die_object cu_die(dbg, nullptr);
|
||||
cu_die = cu_die.get_sibling();
|
||||
if(!cu_die) {
|
||||
break;
|
||||
}
|
||||
if(!walk_die_list(cu_die, fn)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
fmt::println("End walk_compilation_units");
|
||||
}
|
||||
|
||||
void dump_die_tree(const die_object& die, int depth) {
|
||||
walk_die_list(
|
||||
die,
|
||||
[this, depth] (const die_object& die) {
|
||||
fmt::println("{:016x}{: <{}} {}", die.get_global_offset(), "", depth * 2, die.get_tag_name());
|
||||
fmt::println("{: <16}{: <{}} name: {}", "", "", depth * 2, die.get_name());
|
||||
fmt::println("");
|
||||
auto child = die.get_child();
|
||||
if(child) {
|
||||
dump_die_tree(child, depth + 1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void dump_cu(const die_object& cu_die) {
|
||||
Dwarf_Half offset_size = 0;
|
||||
Dwarf_Half dwversion = 0;
|
||||
dwarf_get_version_of_die(cu_die.get(), &dwversion, &offset_size);
|
||||
fmt::println("{:016x} Compile Unit: version = {}, unit type = {}", cu_die.get_global_offset(), dwversion, cu_die.get_tag_name());
|
||||
dump_die_tree(cu_die, 0);
|
||||
}
|
||||
|
||||
public:
|
||||
DwarfDumper() = default;
|
||||
~DwarfDumper() {
|
||||
dwarf_finish(dbg);
|
||||
}
|
||||
|
||||
void dump(std::filesystem::path path) {
|
||||
object_path = path;
|
||||
auto ret = wrap(
|
||||
dwarf_init_path_a,
|
||||
object_path.c_str(),
|
||||
nullptr,
|
||||
0,
|
||||
DW_GROUPNUMBER_ANY,
|
||||
0,
|
||||
nullptr,
|
||||
nullptr,
|
||||
&dbg
|
||||
);
|
||||
if(ret == DW_DLV_OK) {
|
||||
// ok
|
||||
} else if(ret == DW_DLV_NO_ENTRY) {
|
||||
// fail, no debug info
|
||||
fmt::println(stderr, "No debug info");
|
||||
std::exit(1);
|
||||
} else {
|
||||
fmt::println(stderr, "Error: Unknown return code from dwarf_init_path {}", ret);
|
||||
std::exit(1);
|
||||
}
|
||||
walk_compilation_units([this] (const die_object& cu_die) {
|
||||
dump_cu(cu_die);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, char** argv) CPPTRACE_TRY {
|
||||
bool show_help = false;
|
||||
std::filesystem::path path;
|
||||
auto cli = lyra::cli()
|
||||
| lyra::help(show_help)
|
||||
| lyra::arg(path, "binary path")("binary to dwarfdump").required();
|
||||
if(auto result = cli.parse({ argc, argv }); !result) {
|
||||
fmt::println(stderr, "Error in command line: {}", result.message());
|
||||
fmt::println("{}", cli);
|
||||
return 1;
|
||||
}
|
||||
if(show_help) {
|
||||
fmt::println("{}", cli);
|
||||
return 0;
|
||||
}
|
||||
if(!std::filesystem::exists(path)) {
|
||||
fmt::println(stderr, "Error: Path doesn't exist {}", path);
|
||||
return 1;
|
||||
}
|
||||
if(!std::filesystem::is_regular_file(path)) {
|
||||
fmt::println(stderr, "Error: Path isn't a regular file {}", path);
|
||||
return 1;
|
||||
}
|
||||
DwarfDumper{}.dump(path);
|
||||
} CPPTRACE_CATCH(const std::exception& e) {
|
||||
fmt::println(stderr, "Caught exception {}: {}", cpptrace::demangle(typeid(e).name()), e.what());
|
||||
cpptrace::from_current_exception().print();
|
||||
}
|
||||
1
tools/resolver/CMakeLists.txt
Normal file
1
tools/resolver/CMakeLists.txt
Normal file
@ -0,0 +1 @@
|
||||
binary(resolver)
|
||||
109
tools/resolver/main.cpp
Normal file
109
tools/resolver/main.cpp
Normal file
@ -0,0 +1,109 @@
|
||||
#include "cpptrace/basic.hpp"
|
||||
#include "cpptrace/formatting.hpp"
|
||||
#include "cpptrace/forward.hpp"
|
||||
#include <chrono>
|
||||
#include <lyra/lyra.hpp>
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/std.h>
|
||||
#include <fmt/ostream.h>
|
||||
#include <fmt/chrono.h>
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
#include <cpptrace/from_current.hpp>
|
||||
|
||||
#include <filesystem>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include "symbols/symbols.hpp"
|
||||
#include "demangle/demangle.hpp"
|
||||
|
||||
using namespace std::literals;
|
||||
using namespace cpptrace::detail;
|
||||
|
||||
template<> struct fmt::formatter<lyra::cli> : ostream_formatter {};
|
||||
|
||||
auto formatter = cpptrace::formatter{}.addresses(cpptrace::formatter::address_mode::object);
|
||||
|
||||
struct options {
|
||||
bool show_help = false;
|
||||
std::filesystem::path path;
|
||||
std::vector<std::string> address_strings;
|
||||
bool from_stdin = false;
|
||||
bool keepalive = false;
|
||||
bool timing = false;
|
||||
bool disable_aranges = false;
|
||||
cpptrace::nullable<std::size_t> line_table_cache_size;
|
||||
};
|
||||
|
||||
void resolve(const options& opts, cpptrace::frame_ptr address) {
|
||||
cpptrace::object_frame obj_frame{0, address, opts.path.string()};
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
std::vector<cpptrace::stacktrace_frame> trace = cpptrace::detail::resolve_frames({obj_frame});
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
||||
if(trace.size() != 1) {
|
||||
throw std::runtime_error("Something went wrong, trace vector size didn't match");
|
||||
}
|
||||
trace[0].symbol = cpptrace::demangle(trace[0].symbol);
|
||||
formatter.print(trace[0]);
|
||||
std::cout<<std::endl;
|
||||
if(opts.timing) {
|
||||
fmt::println("resolve time: {}", duration);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) CPPTRACE_TRY {
|
||||
options opts;
|
||||
auto cli = lyra::cli()
|
||||
| lyra::help(opts.show_help)
|
||||
| lyra::opt(opts.from_stdin)["--stdin"]("read addresses from stdin")
|
||||
| lyra::opt(opts.keepalive)["--keepalive"]("keep the program alive after resolution finishes (useful for debugging)")
|
||||
| lyra::opt(opts.timing)["--timing"]("provide timing stats")
|
||||
| lyra::opt(opts.disable_aranges)["--disable-aranges"]("don't use the .debug_aranges accelerated address lookup table")
|
||||
| lyra::opt(opts.line_table_cache_size.raw_value, "line table cache size")["--line-table-cache-size"]("limit the size of cpptrace's line table cache")
|
||||
| lyra::arg(opts.path, "binary path")("binary to look in").required()
|
||||
| lyra::arg(opts.address_strings, "addresses")("addresses");
|
||||
if(auto result = cli.parse({ argc, argv }); !result) {
|
||||
fmt::println(stderr, "Error in command line: {}", result.message());
|
||||
fmt::println("{}", cli);
|
||||
return 1;
|
||||
}
|
||||
if(opts.show_help) {
|
||||
fmt::println("{}", cli);
|
||||
return 0;
|
||||
}
|
||||
if(!std::filesystem::exists(opts.path)) {
|
||||
fmt::println(stderr, "Error: Path doesn't exist {}", opts.path);
|
||||
return 1;
|
||||
}
|
||||
if(!std::filesystem::is_regular_file(opts.path)) {
|
||||
fmt::println(stderr, "Error: Path isn't a regular file {}", opts.path);
|
||||
return 1;
|
||||
}
|
||||
if(opts.disable_aranges) {
|
||||
cpptrace::experimental::set_dwarf_resolver_disable_aranges(true);
|
||||
}
|
||||
if(opts.line_table_cache_size.has_value()) {
|
||||
cpptrace::experimental::set_dwarf_resolver_line_table_cache_size(opts.line_table_cache_size);
|
||||
}
|
||||
for(const auto& address : opts.address_strings) {
|
||||
resolve(opts, std::stoi(address, nullptr, 16));
|
||||
}
|
||||
if(opts.from_stdin) {
|
||||
std::string word;
|
||||
while(std::cin >> word) {
|
||||
resolve(opts, std::stoi(word, nullptr, 16));
|
||||
}
|
||||
}
|
||||
if(opts.keepalive) {
|
||||
fmt::println("Done");
|
||||
while(true) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(60));
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
} CPPTRACE_CATCH(const std::exception& e) {
|
||||
fmt::println(stderr, "Caught exception {}: {}", cpptrace::demangle(typeid(e).name()), e.what());
|
||||
cpptrace::from_current_exception().print();
|
||||
}
|
||||
1
tools/symbol_tables/CMakeLists.txt
Normal file
1
tools/symbol_tables/CMakeLists.txt
Normal file
@ -0,0 +1 @@
|
||||
binary(symbol_tables)
|
||||
83
tools/symbol_tables/main.cpp
Normal file
83
tools/symbol_tables/main.cpp
Normal file
@ -0,0 +1,83 @@
|
||||
#include <lyra/lyra.hpp>
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/std.h>
|
||||
#include <fmt/ostream.h>
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
#include <cpptrace/from_current.hpp>
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include "binary/elf.hpp"
|
||||
#include "binary/mach-o.hpp"
|
||||
|
||||
using namespace std::literals;
|
||||
using namespace cpptrace::detail;
|
||||
|
||||
template<> struct fmt::formatter<lyra::cli> : ostream_formatter {};
|
||||
|
||||
#if IS_LINUX
|
||||
void dump_symtab_result(const Result<optional<std::vector<elf::symbol_entry>>, internal_error>& res) {
|
||||
if(!res) {
|
||||
fmt::println(stderr, "Error loading: {}", res.unwrap_error().what());
|
||||
}
|
||||
const auto& entries_ = res.unwrap_value();
|
||||
if(!entries_) {
|
||||
fmt::println("Empty symbol table");
|
||||
}
|
||||
const auto& entries = entries_.unwrap();
|
||||
fmt::println("{:16} {:16} {:4} {}", "value", "size", "shdx", "symbol");
|
||||
for(const auto& entry : entries) {
|
||||
fmt::println("{:016x} {:016x} {:04x} {}", entry.st_value, entry.st_size, entry.st_shndx, entry.st_name);
|
||||
}
|
||||
}
|
||||
|
||||
void dump_symbols(const std::filesystem::path& path) {
|
||||
auto elf_ = elf::open_elf(path);
|
||||
if(!elf_) {
|
||||
fmt::println(stderr, "Error reading file: {}", elf_.unwrap_error().what());
|
||||
}
|
||||
auto& elf = elf_.unwrap_value();
|
||||
fmt::println("Symtab:");
|
||||
dump_symtab_result(elf.get_symtab_entries());
|
||||
fmt::println("Dynamic symtab:");
|
||||
dump_symtab_result(elf.get_dynamic_symtab_entries());
|
||||
}
|
||||
#elif IS_APPLE
|
||||
void dump_symbols(const std::filesystem::path& path) {
|
||||
fmt::println("Not implemented yet (TODO)");
|
||||
}
|
||||
#else
|
||||
void dump_symbols(const std::filesystem::path&) {
|
||||
fmt::println("Unable to dump symbol table on this platform");
|
||||
}
|
||||
#endif
|
||||
|
||||
int main(int argc, char** argv) CPPTRACE_TRY {
|
||||
bool show_help = false;
|
||||
std::filesystem::path path;
|
||||
auto cli = lyra::cli()
|
||||
| lyra::help(show_help)
|
||||
| lyra::arg(path, "binary path")("binary to dump symbol tables for").required();
|
||||
if(auto result = cli.parse({ argc, argv }); !result) {
|
||||
fmt::println(stderr, "Error in command line: {}", result.message());
|
||||
fmt::println("{}", cli);
|
||||
return 1;
|
||||
}
|
||||
if(show_help) {
|
||||
fmt::println("{}", cli);
|
||||
return 0;
|
||||
}
|
||||
if(!std::filesystem::exists(path)) {
|
||||
fmt::println(stderr, "Error: Path doesn't exist {}", path);
|
||||
return 1;
|
||||
}
|
||||
if(!std::filesystem::is_regular_file(path)) {
|
||||
fmt::println(stderr, "Error: Path isn't a regular file {}", path);
|
||||
return 1;
|
||||
}
|
||||
dump_symbols(path);
|
||||
return 0;
|
||||
} CPPTRACE_CATCH(const std::exception& e) {
|
||||
fmt::println(stderr, "Caught exception {}: {}", cpptrace::demangle(typeid(e).name()), e.what());
|
||||
cpptrace::from_current_exception().print();
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user