Compare commits

..

1 Commits

70 changed files with 1117 additions and 2827 deletions

View File

@ -25,26 +25,6 @@ jobs:
- name: build and test - name: build and test
run: | run: |
python3 ci/test-all-configs.py --${{matrix.compiler}} --default-config 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: test-macos:
runs-on: macos-14 runs-on: macos-14
strategy: strategy:
@ -88,31 +68,6 @@ jobs:
- name: build and test - name: build and test
run: | run: |
python3 ci/test-all-configs.py --${{matrix.compiler}} --default-config 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: test-linux-all-configurations:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
strategy: strategy:
@ -582,32 +537,6 @@ jobs:
- name: test opt - name: test opt
run: | run: |
bazel test //... -c opt 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: unittest-macos:
runs-on: macos-14 runs-on: macos-14
steps: steps:

View File

@ -1,9 +1,6 @@
# Changelog # Changelog
- [Changelog](#changelog) - [Changelog](#changelog)
- [v0.8.2](#v082)
- [v0.8.1](#v081)
- [v0.8.0](#v080)
- [v0.7.5](#v075) - [v0.7.5](#v075)
- [v0.7.4](#v074) - [v0.7.4](#v074)
- [v0.7.3](#v073) - [v0.7.3](#v073)
@ -28,65 +25,6 @@
- [v0.1.1](#v011) - [v0.1.1](#v011)
- [v0.1](#v01) - [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 # v0.7.5
Fixed: Fixed:

View File

@ -9,7 +9,7 @@ set(package_name "cpptrace")
project( project(
cpptrace cpptrace
VERSION 0.8.2 VERSION 0.7.5
DESCRIPTION "Simple, portable, and self-contained stacktrace library for C++11 and newer " DESCRIPTION "Simple, portable, and self-contained stacktrace library for C++11 and newer "
HOMEPAGE_URL "https://github.com/jeremy-rifkin/cpptrace" HOMEPAGE_URL "https://github.com/jeremy-rifkin/cpptrace"
LANGUAGES C CXX LANGUAGES C CXX
@ -113,7 +113,6 @@ target_sources(
src/ctrace.cpp src/ctrace.cpp
src/exceptions.cpp src/exceptions.cpp
src/from_current.cpp src/from_current.cpp
src/formatting.cpp
src/options.cpp src/options.cpp
src/utils.cpp src/utils.cpp
src/demangle/demangle_with_cxxabi.cpp src/demangle/demangle_with_cxxabi.cpp
@ -121,7 +120,6 @@ target_sources(
src/demangle/demangle_with_winapi.cpp src/demangle/demangle_with_winapi.cpp
src/snippets/snippet.cpp src/snippets/snippet.cpp
src/symbols/dwarf/debug_map_resolver.cpp src/symbols/dwarf/debug_map_resolver.cpp
src/symbols/dwarf/dwarf_options.cpp
src/symbols/dwarf/dwarf_resolver.cpp src/symbols/dwarf/dwarf_resolver.cpp
src/symbols/symbols_core.cpp src/symbols/symbols_core.cpp
src/symbols/symbols_with_addr2line.cpp src/symbols/symbols_with_addr2line.cpp
@ -138,7 +136,7 @@ target_sources(
src/unwind/unwind_with_winapi.cpp src/unwind/unwind_with_winapi.cpp
src/utils/microfmt.cpp src/utils/microfmt.cpp
src/utils/utils.cpp src/utils/utils.cpp
src/platform/dbghelp_utils.cpp src/platform/dbghelp_syminit_manager.cpp
) )
target_include_directories( target_include_directories(
@ -223,10 +221,6 @@ target_compile_features(
target_compile_definitions(${target_name} PRIVATE NOMINMAX) 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) if(NOT CPPTRACE_STD_FORMAT)
target_compile_definitions(${target_name} PUBLIC CPPTRACE_NO_STD_FORMAT) target_compile_definitions(${target_name} PUBLIC CPPTRACE_NO_STD_FORMAT)
endif() endif()
@ -355,14 +349,11 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF)
endif() endif()
endif() endif()
if(CPPTRACE_CONAN) if(CPPTRACE_CONAN)
set(dwarf_lib libdwarf::libdwarf)
target_link_libraries(${target_name} PRIVATE libdwarf::libdwarf) target_link_libraries(${target_name} PRIVATE libdwarf::libdwarf)
elseif(CPPTRACE_VCPKG) elseif(CPPTRACE_VCPKG)
set(dwarf_lib libdwarf::dwarf)
target_link_libraries(${target_name} PRIVATE libdwarf::dwarf) target_link_libraries(${target_name} PRIVATE libdwarf::dwarf)
elseif(CPPTRACE_USE_EXTERNAL_LIBDWARF) elseif(CPPTRACE_USE_EXTERNAL_LIBDWARF)
if(DEFINED LIBDWARF_LIBRARIES) if(DEFINED LIBDWARF_LIBRARIES)
set(dwarf_lib ${LIBDWARF_LIBRARIES})
target_link_libraries(${target_name} PRIVATE ${LIBDWARF_LIBRARIES}) target_link_libraries(${target_name} PRIVATE ${LIBDWARF_LIBRARIES})
else() else()
# if LIBDWARF_LIBRARIES wasn't set by find_package, try looking for libdwarf::dwarf-static, # if LIBDWARF_LIBRARIES wasn't set by find_package, try looking for libdwarf::dwarf-static,
@ -380,7 +371,6 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF)
else() else()
message(FATAL_ERROR "Couldn't find libdwarf target name to link against") message(FATAL_ERROR "Couldn't find libdwarf target name to link against")
endif() endif()
set(dwarf_lib ${LIBDWARF_LIBRARIES})
target_link_libraries(${target_name} PRIVATE ${LIBDWARF_LIBRARIES}) target_link_libraries(${target_name} PRIVATE ${LIBDWARF_LIBRARIES})
endif() endif()
# There seems to be no consistency at all about where libdwarf decides to place its headers........ Figure out if # There seems to be no consistency at all about where libdwarf decides to place its headers........ Figure out if
@ -403,7 +393,6 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF)
message(FATAL_ERROR "Couldn't find libdwarf.h") message(FATAL_ERROR "Couldn't find libdwarf.h")
endif() endif()
else() else()
set(dwarf_lib libdwarf::dwarf-static)
target_link_libraries(${target_name} PRIVATE libdwarf::dwarf-static) target_link_libraries(${target_name} PRIVATE libdwarf::dwarf-static)
endif() endif()
if(UNIX) if(UNIX)
@ -519,7 +508,7 @@ if(NOT CMAKE_SKIP_INSTALL_RULES)
include(cmake/InstallRules.cmake) include(cmake/InstallRules.cmake)
endif() endif()
# ================================================== Demo/test/tools =================================================== # ===================================================== Demo/test ======================================================
if(CPPTRACE_BUILD_TESTING) if(CPPTRACE_BUILD_TESTING)
if(PROJECT_IS_TOP_LEVEL) if(PROJECT_IS_TOP_LEVEL)
@ -531,7 +520,3 @@ endif()
if(CPPTRACE_BUILD_BENCHMARKING) if(CPPTRACE_BUILD_BENCHMARKING)
add_subdirectory(benchmarking) add_subdirectory(benchmarking)
endif() endif()
if(CPPTRACE_BUILD_TOOLS)
add_subdirectory(tools)
endif()

View File

@ -9,12 +9,12 @@ help: # with thanks to Ben Rady
build: debug ## build in debug mode build: debug ## build in debug mode
build/configured-debug: build/configured-debug:
cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On -DCPPTRACE_BUILD_TOOLS=On cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On
rm -f build/configured-release rm -f build/configured-release
touch build/configured-debug touch build/configured-debug
build/configured-release: build/configured-release:
cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On -DCPPTRACE_BUILD_TOOLS=On cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On
rm -f build/configured-debug rm -f build/configured-debug
touch build/configured-release touch build/configured-release
@ -34,12 +34,12 @@ release: configure-release ## build in release mode (with debug info)
.PHONY: debug-msvc .PHONY: debug-msvc
debug-msvc: ## build in debug mode debug-msvc: ## build in debug mode
cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On -DCPPTRACE_BUILD_TOOLS=On cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On
cmake --build build --config Debug cmake --build build --config Debug
.PHONY: release-msvc .PHONY: release-msvc
release-msvc: ## build in release mode (with debug info) release-msvc: ## build in release mode (with debug info)
cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On -DCPPTRACE_BUILD_TOOLS=On cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On
cmake --build build --config RelWithDebInfo cmake --build build --config RelWithDebInfo
.PHONY: clean .PHONY: clean

215
README.md
View File

@ -16,6 +16,9 @@ Cpptrace also has a C API, docs [here](docs/c-api.md).
- [30-Second Overview](#30-second-overview) - [30-Second Overview](#30-second-overview)
- [CMake FetchContent Usage](#cmake-fetchcontent-usage) - [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) - [Prerequisites](#prerequisites)
- [Basic Usage](#basic-usage) - [Basic Usage](#basic-usage)
- [`namespace cpptrace`](#namespace-cpptrace) - [`namespace cpptrace`](#namespace-cpptrace)
@ -23,7 +26,6 @@ Cpptrace also has a C API, docs [here](docs/c-api.md).
- [Object Traces](#object-traces) - [Object Traces](#object-traces)
- [Raw Traces](#raw-traces) - [Raw Traces](#raw-traces)
- [Utilities](#utilities) - [Utilities](#utilities)
- [Formatting](#formatting)
- [Configuration](#configuration) - [Configuration](#configuration)
- [Traces From All Exceptions](#traces-from-all-exceptions) - [Traces From All Exceptions](#traces-from-all-exceptions)
- [Removing the `CPPTRACE_` prefix](#removing-the-cpptrace_-prefix) - [Removing the `CPPTRACE_` prefix](#removing-the-cpptrace_-prefix)
@ -36,7 +38,6 @@ Cpptrace also has a C API, docs [here](docs/c-api.md).
- [Signal-Safe Tracing](#signal-safe-tracing) - [Signal-Safe Tracing](#signal-safe-tracing)
- [Utility Types](#utility-types) - [Utility Types](#utility-types)
- [Headers](#headers) - [Headers](#headers)
- [Libdwarf Tuning](#libdwarf-tuning)
- [Supported Debug Formats](#supported-debug-formats) - [Supported Debug Formats](#supported-debug-formats)
- [How to Include The Library](#how-to-include-the-library) - [How to Include The Library](#how-to-include-the-library)
- [CMake FetchContent](#cmake-fetchcontent) - [CMake FetchContent](#cmake-fetchcontent)
@ -54,10 +55,6 @@ Cpptrace also has a C API, docs [here](docs/c-api.md).
- [Summary of Library Configurations](#summary-of-library-configurations) - [Summary of Library Configurations](#summary-of-library-configurations)
- [Testing Methodology](#testing-methodology) - [Testing Methodology](#testing-methodology)
- [Notes About the Library](#notes-about-the-library) - [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) - [Contributing](#contributing)
- [License](#license) - [License](#license)
@ -128,7 +125,6 @@ Additional notable features:
- Utilities for catching `std::exception`s and wrapping them in traced exceptions - Utilities for catching `std::exception`s and wrapping them in traced exceptions
- Signal-safe stack tracing - Signal-safe stack tracing
- Source code snippets in traces - Source code snippets in traces
- Extensive configuration options for [trace formatting](#formatting)
![Snippets](res/snippets.png) ![Snippets](res/snippets.png)
@ -139,7 +135,7 @@ include(FetchContent)
FetchContent_Declare( FetchContent_Declare(
cpptrace cpptrace
GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git
GIT_TAG v0.8.2 # <HASH or TAG> GIT_TAG v0.7.5 # <HASH or TAG>
) )
FetchContent_MakeAvailable(cpptrace) FetchContent_MakeAvailable(cpptrace)
target_link_libraries(your_target cpptrace::cpptrace) target_link_libraries(your_target cpptrace::cpptrace)
@ -164,6 +160,33 @@ 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 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. 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 # Prerequisites
> [!IMPORTANT] > [!IMPORTANT]
@ -319,87 +342,6 @@ 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 ## Configuration
`cpptrace::absorb_trace_exceptions`: Configure whether the library silently absorbs internal exceptions and continues. `cpptrace::absorb_trace_exceptions`: Configure whether the library silently absorbs internal exceptions and continues.
@ -439,8 +381,6 @@ thrown exception object, with minimal or no overhead in the non-throwing path:
```cpp ```cpp
#include <cpptrace/from_current.hpp> #include <cpptrace/from_current.hpp>
#include <iostream>
void foo() { void foo() {
throw std::runtime_error("foo failed"); throw std::runtime_error("foo failed");
} }
@ -750,7 +690,6 @@ namespace cpptrace {
}; };
void get_safe_object_frame(frame_ptr address, safe_object_frame* out); void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
bool can_signal_safe_unwind(); bool can_signal_safe_unwind();
bool can_get_safe_object_frame();
} }
``` ```
@ -767,9 +706,9 @@ see the comprehensive overview and demo at [signal-safe-tracing.md](docs/signal-
> [!IMPORTANT] > [!IMPORTANT]
> Currently signal-safe stack unwinding is only possible with `libunwind`, which must be > 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 > [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 and > produce an empty trace. `can_signal_safe_unwind` can be used to check for signal-safe unwinding support. If object
> `can_get_safe_object_frame` can be used to check `get_safe_object_frame` support. If object information can't be > information can't be resolved in a signal-safe way then `get_safe_object_frame` will not populate fields beyond the
> resolved in a signal-safe way then `get_safe_object_frame` will not populate fields beyond the `raw_address`. > `raw_address`.
> [!IMPORTANT] > [!IMPORTANT]
> `_dl_find_object` is required for signal-safe stack tracing. This is a relatively recent addition to glibc, added in > `_dl_find_object` is required for signal-safe stack tracing. This is a relatively recent addition to glibc, added in
@ -795,7 +734,6 @@ namespace cpptrace {
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0> template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
struct nullable { struct nullable {
T raw_value; T raw_value;
// all members are constexpr for c++17 and beyond, some are constexpr before c++17
nullable& operator=(T value) nullable& operator=(T value)
bool has_value() const noexcept; bool has_value() const noexcept;
T& value() noexcept; T& value() noexcept;
@ -805,7 +743,6 @@ namespace cpptrace {
void reset() noexcept; void reset() noexcept;
bool operator==(const nullable& other) const noexcept; bool operator==(const nullable& other) const 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 constexpr static nullable null() noexcept; // returns a null instance
}; };
@ -848,39 +785,12 @@ 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/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/from_current.hpp` | [Traces From All Exceptions](#traces-from-all-exceptions) |
| `cpptrace/io.hpp` | `operator<<` overloads for `std::ostream` and `std::formatter`s | | `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/utils.hpp` | Utility functions, configuration functions, and terminate utilities ([Utilities](#utilities), [Configuration](#configuration), and [Terminate Handling](#terminate-handling)) |
| `cpptrace/version.hpp` | Library version macros | | `cpptrace/version.hpp` | Library version macros |
The main cpptrace header is `cpptrace/cpptrace.hpp` which includes everything other than `from_current.hpp` and The main cpptrace header is `cpptrace/cpptrace.hpp` which includes everything other than `from_current.hpp` and
`version.hpp`. `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 # Supported Debug Formats
| Format | Supported | | Format | Supported |
@ -905,7 +815,7 @@ include(FetchContent)
FetchContent_Declare( FetchContent_Declare(
cpptrace cpptrace
GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git
GIT_TAG v0.8.2 # <HASH or TAG> GIT_TAG v0.7.5 # <HASH or TAG>
) )
FetchContent_MakeAvailable(cpptrace) FetchContent_MakeAvailable(cpptrace)
target_link_libraries(your_target cpptrace::cpptrace) target_link_libraries(your_target cpptrace::cpptrace)
@ -921,7 +831,7 @@ information.
```sh ```sh
git clone https://github.com/jeremy-rifkin/cpptrace.git git clone https://github.com/jeremy-rifkin/cpptrace.git
git checkout v0.8.2 git checkout v0.7.5
mkdir cpptrace/build mkdir cpptrace/build
cd cpptrace/build cd cpptrace/build
cmake .. -DCMAKE_BUILD_TYPE=Release cmake .. -DCMAKE_BUILD_TYPE=Release
@ -964,7 +874,7 @@ you when installing new libraries.
```ps1 ```ps1
git clone https://github.com/jeremy-rifkin/cpptrace.git git clone https://github.com/jeremy-rifkin/cpptrace.git
git checkout v0.8.2 git checkout v0.7.5
mkdir cpptrace/build mkdir cpptrace/build
cd cpptrace/build cd cpptrace/build
cmake .. -DCMAKE_BUILD_TYPE=Release cmake .. -DCMAKE_BUILD_TYPE=Release
@ -982,7 +892,7 @@ To install just for the local user (or any custom prefix):
```sh ```sh
git clone https://github.com/jeremy-rifkin/cpptrace.git git clone https://github.com/jeremy-rifkin/cpptrace.git
git checkout v0.8.2 git checkout v0.7.5
mkdir cpptrace/build mkdir cpptrace/build
cd cpptrace/build cd cpptrace/build
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$HOME/wherever cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$HOME/wherever
@ -1065,7 +975,7 @@ make install
cd ~/scratch/cpptrace-test cd ~/scratch/cpptrace-test
git clone https://github.com/jeremy-rifkin/cpptrace.git git clone https://github.com/jeremy-rifkin/cpptrace.git
cd cpptrace cd cpptrace
git checkout v0.8.2 git checkout v0.7.5
mkdir build mkdir build
cd 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 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
@ -1085,7 +995,7 @@ cpptrace and its dependencies.
Cpptrace is available through conan at https://conan.io/center/recipes/cpptrace. Cpptrace is available through conan at https://conan.io/center/recipes/cpptrace.
``` ```
[requires] [requires]
cpptrace/0.8.2 cpptrace/0.7.5
[generators] [generators]
CMakeDeps CMakeDeps
CMakeToolchain CMakeToolchain
@ -1294,51 +1204,6 @@ 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 in dbghelp the library cannot accurately show const and volatile qualifiers or rvalue references (these appear as
pointers). 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 # Contributing
I'm grateful for the help I've received with this library and I welcome contributions! For information on contributing I'm grateful for the help I've received with this library and I welcome contributions! For information on contributing

View File

@ -66,6 +66,60 @@ def build(runner: MatrixRunner):
return succeeded 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): def run_linux_matrix(compilers: list):
MatrixRunner( MatrixRunner(
matrix = { matrix = {

View File

@ -15,10 +15,6 @@ if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
check_support(HAS_CXXABI has_cxxabi.cpp "" "" "") check_support(HAS_CXXABI has_cxxabi.cpp "" "" "")
endif() endif()
if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
check_support(HAS_ATTRIBUTE_PACKED has_attribute_packed.cpp "" "" "")
endif()
if(NOT WIN32) if(NOT WIN32)
check_support(HAS_UNWIND has_unwind.cpp "" "" "") check_support(HAS_UNWIND has_unwind.cpp "" "" "")
check_support(HAS_EXECINFO has_execinfo.cpp "" "" "") check_support(HAS_EXECINFO has_execinfo.cpp "" "" "")

View File

@ -151,7 +151,6 @@ option(CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH "" OFF)
if(PROJECT_IS_TOP_LEVEL) if(PROJECT_IS_TOP_LEVEL)
option(CPPTRACE_BUILD_TESTING "" OFF) option(CPPTRACE_BUILD_TESTING "" OFF)
option(CPPTRACE_BUILD_TOOLS "" OFF)
option(CPPTRACE_BUILD_BENCHMARK "" OFF) option(CPPTRACE_BUILD_BENCHMARK "" OFF)
option(CPPTRACE_BUILD_NO_SYMBOLS "" OFF) option(CPPTRACE_BUILD_NO_SYMBOLS "" OFF)
option(CPPTRACE_BUILD_TESTING_SPLIT_DWARF "" OFF) option(CPPTRACE_BUILD_TESTING_SPLIT_DWARF "" OFF)
@ -159,7 +158,6 @@ if(PROJECT_IS_TOP_LEVEL)
option(CPPTRACE_BUILD_TEST_RDYNAMIC "" OFF) option(CPPTRACE_BUILD_TEST_RDYNAMIC "" OFF)
mark_as_advanced( mark_as_advanced(
CPPTRACE_BUILD_TESTING CPPTRACE_BUILD_TESTING
CPPTRACE_BUILD_TOOLS
CPPTRACE_BUILD_BENCHMARK CPPTRACE_BUILD_BENCHMARK
CPPTRACE_BUILD_NO_SYMBOLS CPPTRACE_BUILD_NO_SYMBOLS
CPPTRACE_BUILD_TESTING_SPLIT_DWARF CPPTRACE_BUILD_TESTING_SPLIT_DWARF
@ -180,7 +178,7 @@ option(CPPTRACE_SKIP_UNIT "" OFF)
option(CPPTRACE_STD_FORMAT "" ON) option(CPPTRACE_STD_FORMAT "" ON)
option(CPPTRACE_UNPREFIXED_TRY_CATCH "" OFF) option(CPPTRACE_UNPREFIXED_TRY_CATCH "" OFF)
option(CPPTRACE_USE_EXTERNAL_GTEST "" OFF) option(CPPTRACE_USE_EXTERNAL_GTEST "" OFF)
set(CPPTRACE_ZSTD_URL "https://github.com/facebook/zstd/releases/download/v1.5.7/zstd-1.5.7.tar.gz" CACHE STRING "") set(CPPTRACE_ZSTD_URL "https://github.com/facebook/zstd/releases/download/v1.5.6/zstd-1.5.6.tar.gz" CACHE STRING "")
set(CPPTRACE_LIBDWARF_REPO "https://github.com/jeremy-rifkin/libdwarf-lite.git" 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_TAG "fe09ca800b988e2ff21225ac5e7468ceade2a30e" CACHE STRING "") # v0.11.1
set(CPPTRACE_LIBDWARF_SHALLOW "1" CACHE STRING "") set(CPPTRACE_LIBDWARF_SHALLOW "1" CACHE STRING "")

View File

@ -1,6 +0,0 @@
struct __attribute__((packed)) foo {
int i;
double d;
};
int main() {}

View File

@ -168,6 +168,5 @@ 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); 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); void ctrace_get_safe_object_frame(ctrace_frame_ptr address, ctrace_safe_object_frame* out);
ctrace_bool ctrace_can_signal_safe_unwind(); ctrace_bool can_signal_safe_unwind();
ctrace_bool ctrace_can_get_safe_object_frame();
``` ```

View File

@ -73,7 +73,6 @@ namespace cpptrace {
void get_safe_object_frame(frame_ptr address, safe_object_frame* out); void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
// signal-safe // signal-safe
bool can_signal_safe_unwind(); bool can_signal_safe_unwind();
bool can_get_safe_object_frame();
} }
``` ```
@ -105,9 +104,6 @@ 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 `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`. 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 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 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. knows ways to do these safely on other platforms, I'd be much appreciative.

View File

@ -31,12 +31,6 @@
# endif # endif
#endif #endif
#if __cplusplus >= 201703L
#define CONSTEXPR_SINCE_CPP17 constexpr
#else
#define CONSTEXPR_SINCE_CPP17
#endif
#ifdef _MSC_VER #ifdef _MSC_VER
#define CPPTRACE_FORCE_NO_INLINE __declspec(noinline) #define CPPTRACE_FORCE_NO_INLINE __declspec(noinline)
#else #else
@ -101,42 +95,37 @@ namespace cpptrace {
// use. // use.
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0> template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
struct nullable { struct nullable {
T raw_value = null_value(); T raw_value;
constexpr nullable() noexcept = default; nullable& operator=(T value) {
constexpr nullable(T value) noexcept : raw_value(value) {}
CONSTEXPR_SINCE_CPP17 nullable& operator=(T value) noexcept {
raw_value = value; raw_value = value;
return *this; return *this;
} }
constexpr bool has_value() const noexcept { bool has_value() const noexcept {
return raw_value != null_value(); return raw_value != (std::numeric_limits<T>::max)();
} }
CONSTEXPR_SINCE_CPP17 T& value() noexcept { T& value() noexcept {
return raw_value; return raw_value;
} }
constexpr const T& value() const noexcept { const T& value() const noexcept {
return raw_value; return raw_value;
} }
constexpr T value_or(T alternative) const noexcept { T value_or(T alternative) const noexcept {
return has_value() ? raw_value : alternative; return has_value() ? raw_value : alternative;
} }
CONSTEXPR_SINCE_CPP17 void swap(nullable& other) noexcept { void swap(nullable& other) noexcept {
std::swap(raw_value, other.raw_value); std::swap(raw_value, other.raw_value);
} }
CONSTEXPR_SINCE_CPP17 void reset() noexcept { void reset() noexcept {
raw_value = (std::numeric_limits<T>::max)(); raw_value = (std::numeric_limits<T>::max)();
} }
constexpr bool operator==(const nullable& other) const noexcept { bool operator==(const nullable& other) const noexcept {
return raw_value == other.raw_value; return raw_value == other.raw_value;
} }
constexpr bool operator!=(const nullable& other) const noexcept { bool operator!=(const nullable& other) const noexcept {
return raw_value != other.raw_value; return raw_value != other.raw_value;
} }
constexpr static T null_value() noexcept {
return (std::numeric_limits<T>::max)();
}
constexpr static nullable null() noexcept { constexpr static nullable null() noexcept {
return { null_value() }; return { (std::numeric_limits<T>::max)() };
} }
}; };
@ -193,6 +182,8 @@ namespace cpptrace {
inline const_iterator cbegin() const noexcept { return frames.cbegin(); } inline const_iterator cbegin() const noexcept { return frames.cbegin(); }
inline const_iterator cend() const noexcept { return frames.cend(); } inline const_iterator cend() const noexcept { return frames.cend(); }
private: 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(); friend void print_terminate_trace();
}; };
@ -235,7 +226,6 @@ namespace cpptrace {
// signal-safe // signal-safe
CPPTRACE_EXPORT void get_safe_object_frame(frame_ptr address, safe_object_frame* out); 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_signal_safe_unwind();
CPPTRACE_EXPORT bool can_get_safe_object_frame();
} }
#ifdef _MSC_VER #ifdef _MSC_VER

View File

@ -1,74 +0,0 @@
#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

View File

@ -43,12 +43,6 @@ namespace cpptrace {
namespace experimental { namespace experimental {
CPPTRACE_EXPORT void set_cache_mode(cache_mode mode); 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 #endif

View File

@ -131,8 +131,7 @@ CTRACE_BEGIN_DEFINITIONS
/* ctrace::safe: */ /* 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 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 void ctrace_get_safe_object_frame(ctrace_frame_ptr address, ctrace_safe_object_frame* out);
CPPTRACE_EXPORT ctrace_bool ctrace_can_signal_safe_unwind(void); CPPTRACE_EXPORT ctrace_bool can_signal_safe_unwind(void);
CPPTRACE_EXPORT ctrace_bool ctrace_can_get_safe_object_frame(void);
/* ctrace::io: */ /* ctrace::io: */
CPPTRACE_EXPORT ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color); CPPTRACE_EXPORT ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color);

View File

@ -0,0 +1,138 @@
#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

View File

@ -0,0 +1,242 @@
#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

View File

@ -1,5 +1,4 @@
#include "binary/elf.hpp" #include "binary/elf.hpp"
#include "utils/optional.hpp"
#if IS_LINUX #if IS_LINUX
@ -7,11 +6,9 @@
#include <cstdint> #include <cstdint>
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
#include <mutex>
#include <type_traits> #include <type_traits>
#include <unordered_map>
#include <elf.h> #include "binary/defs/elf_defs.hpp"
namespace cpptrace { namespace cpptrace {
namespace detail { namespace detail {
@ -91,31 +88,16 @@ namespace detail {
return 0; return 0;
} }
optional<std::string> elf::lookup_symbol(frame_ptr pc) { std::string elf::lookup_symbol(frame_ptr pc) {
if(auto symtab = get_symtab()) { // TODO: Also search the SHT_DYNSYM at some point, maybe
if(auto symbol = lookup_symbol(pc, symtab.unwrap_value())) { auto symtab_ = get_symtab();
return symbol; if(symtab_.is_error()) {
} return "";
}
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); auto strtab_ = get_strtab(symtab.strtab_link);
if(strtab_.is_error()) { if(strtab_.is_error()) {
return nullopt; return "";
} }
auto& strtab = strtab_.unwrap_value(); auto& strtab = strtab_.unwrap_value();
auto it = first_less_than_or_equal( auto it = first_less_than_or_equal(
@ -127,49 +109,12 @@ namespace detail {
} }
); );
if(it == symtab.entries.end()) { if(it == symtab.entries.end()) {
return nullopt; return "";
} }
if(pc <= it->st_value + it->st_size) { if(pc <= it->st_value + it->st_size) {
return strtab.data() + it->st_name; return strtab.data() + it->st_name;
} }
return nullopt; return "";
}
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> template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type>
@ -301,66 +246,23 @@ namespace detail {
return entry.data; return entry.data;
} }
Result<const optional<elf::symtab_info>&, internal_error> elf::get_symtab() { Result<const elf::symtab_info&, internal_error> elf::get_symtab() {
if(did_load_symtab) { if(did_load_symtab) {
return symtab; return symtab;
} }
if(tried_to_load_symtab) { if(tried_to_load_symtab) {
return internal_error("previous symtab load failed {}", object_path); return internal_error("previous strtab load failed {}", object_path);
} }
tried_to_load_symtab = true; tried_to_load_symtab = true;
if(is_64) { if(is_64) {
auto res = get_symtab_impl<64>(false); return get_symtab_impl<64>();
if(res.has_value()) {
symtab = std::move(res).unwrap_value();
did_load_symtab = true;
return symtab;
} else { } else {
return std::move(res).unwrap_error(); return get_symtab_impl<32>();
}
} 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> template<std::size_t Bits>
Result<optional<elf::symtab_info>, internal_error> elf::get_symtab_impl(bool dynamic) { Result<const elf::symtab_info&, internal_error> elf::get_symtab_impl() {
// https://refspecs.linuxfoundation.org/elf/elf.pdf // https://refspecs.linuxfoundation.org/elf/elf.pdf
// page 66: only one sht_symtab and sht_dynsym section per file // page 66: only one sht_symtab and sht_dynsym section per file
// page 32: symtab spec // page 32: symtab spec
@ -371,9 +273,8 @@ namespace detail {
return std::move(sections_).unwrap_error(); return std::move(sections_).unwrap_error();
} }
const auto& sections = sections_.unwrap_value(); const auto& sections = sections_.unwrap_value();
optional<symtab_info> symbol_table;
for(const auto& section : sections) { for(const auto& section : sections) {
if(section.sh_type == (dynamic ? SHT_DYNSYM : SHT_SYMTAB)) { if(section.sh_type == SHT_SYMTAB) {
if(section.sh_entsize != sizeof(SymEntry)) { if(section.sh_entsize != sizeof(SymEntry)) {
return internal_error("elf seems corrupted, sym entry mismatch {}", object_path); return internal_error("elf seems corrupted, sym entry mismatch {}", object_path);
} }
@ -387,8 +288,7 @@ namespace detail {
if(std::fread(buffer.data(), section.sh_entsize, buffer.size(), file) != buffer.size()) { if(std::fread(buffer.data(), section.sh_entsize, buffer.size(), file) != buffer.size()) {
return internal_error("fread error while loading elf symbol table"); return internal_error("fread error while loading elf symbol table");
} }
symbol_table = symtab_info{}; symtab.entries.reserve(buffer.size());
symbol_table.unwrap().entries.reserve(buffer.size());
for(const auto& entry : buffer) { for(const auto& entry : buffer) {
symtab_entry normalized; symtab_entry normalized;
normalized.st_name = byteswap_if_needed(entry.st_name); normalized.st_name = byteswap_if_needed(entry.st_name);
@ -397,39 +297,19 @@ namespace detail {
normalized.st_shndx = byteswap_if_needed(entry.st_shndx); normalized.st_shndx = byteswap_if_needed(entry.st_shndx);
normalized.st_value = byteswap_if_needed(entry.st_value); normalized.st_value = byteswap_if_needed(entry.st_value);
normalized.st_size = byteswap_if_needed(entry.st_size); normalized.st_size = byteswap_if_needed(entry.st_size);
symbol_table.unwrap().entries.push_back(normalized); symtab.entries.push_back(normalized);
} }
std::sort( std::sort(symtab.entries.begin(), symtab.entries.end(), [] (const symtab_entry& a, const symtab_entry& b) {
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; return a.st_value < b.st_value;
} });
); symtab.strtab_link = section.sh_link;
symbol_table.unwrap().strtab_link = section.sh_link; did_load_symtab = true;
break; return symtab;
} }
} }
return symbol_table; // OK to not have a symbol table
} did_load_symtab = true;
return symtab;
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); });
}
} }
} }
} }

View File

@ -62,19 +62,13 @@ namespace detail {
}; };
bool tried_to_load_symtab = false; bool tried_to_load_symtab = false;
bool did_load_symtab = false; bool did_load_symtab = false;
optional<symtab_info> symtab; 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); elf(file_wrapper file, const std::string& object_path, bool is_little_endian, bool is_64);
public: public:
static NODISCARD Result<elf, internal_error> open_elf(const std::string& object_path); static NODISCARD Result<elf, internal_error> open_elf(const std::string& object_path);
elf(elf&&) = default;
public: public:
Result<std::uintptr_t, internal_error> get_module_image_base(); Result<std::uintptr_t, internal_error> get_module_image_base();
private: private:
@ -82,23 +76,7 @@ namespace detail {
Result<std::uintptr_t, internal_error> get_module_image_base_impl(); Result<std::uintptr_t, internal_error> get_module_image_base_impl();
public: public:
optional<std::string> lookup_symbol(frame_ptr pc); 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: private:
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0> template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
@ -114,13 +92,10 @@ namespace detail {
Result<const std::vector<char>&, internal_error> get_strtab(std::size_t index); Result<const std::vector<char>&, internal_error> get_strtab(std::size_t index);
Result<const optional<symtab_info>&, internal_error> get_symtab(); Result<const symtab_info&, internal_error> get_symtab();
Result<const optional<symtab_info>&, internal_error> get_dynamic_symtab();
template<std::size_t Bits> template<std::size_t Bits>
Result<optional<symtab_info>, internal_error> get_symtab_impl(bool dynamic); Result<const symtab_info&, internal_error> get_symtab_impl();
}; };
NODISCARD Result<maybe_owned<elf>, internal_error> open_elf_cached(const std::string& object_path);
} }
} }

View File

@ -11,7 +11,6 @@
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
#include <mutex>
#include <string> #include <string>
#include <type_traits> #include <type_traits>
#include <unordered_map> #include <unordered_map>
@ -20,16 +19,20 @@
#include <iostream> #include <iostream>
#include <iomanip> #include <iomanip>
#include <mach-o/loader.h> #include "binary/defs/mach-o-defs.hpp"
#include <mach-o/swap.h>
#include <mach-o/fat.h> // #include <mach-o/loader.h>
#include <crt_externs.h> // #include <mach-o/swap.h>
#include <mach-o/nlist.h> // #include <mach-o/fat.h>
#include <mach-o/stab.h> // #include <crt_externs.h>
#include <mach-o/arch.h> // #include <mach-o/nlist.h>
// #include <mach-o/stab.h>
// #include <mach-o/arch.h>
namespace cpptrace { namespace cpptrace {
namespace detail { namespace detail {
// TODO: 64-bit fat??
bool is_mach_o(std::uint32_t magic) { bool is_mach_o(std::uint32_t magic) {
switch(magic) { switch(magic) {
case FAT_MAGIC: case FAT_MAGIC:
@ -72,27 +75,89 @@ namespace detail {
} }
void swap_mach_header(mach_header_64& header) { void swap_mach_header(mach_header_64& header) {
swap_mach_header_64(&header, NX_UnknownByteOrder); 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);
} }
void swap_mach_header(mach_header& header) { void swap_mach_header(mach_header& header) {
swap_mach_header(&header, NX_UnknownByteOrder); 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);
} }
void swap_segment_command(segment_command_64& segment) { void swap_segment_command(segment_command_64& segment) {
swap_segment_command_64(&segment, NX_UnknownByteOrder); 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);
} }
void swap_segment_command(segment_command& segment) { void swap_segment_command(segment_command& segment) {
swap_segment_command(&segment, NX_UnknownByteOrder); 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);
} }
void swap_nlist(struct nlist& entry) { void swap_symtab_command(symtab_command& symtab) {
swap_nlist(&entry, 1, NX_UnknownByteOrder); 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_64& entry) { void swap_nlist(nlist& entry) {
swap_nlist_64(&entry, 1, NX_UnknownByteOrder); 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);
} }
#ifdef __LP64__ #ifdef __LP64__
@ -103,7 +168,7 @@ namespace detail {
Result<const char*, internal_error> mach_o::symtab_info_data::get_string(std::size_t index) const { Result<const char*, internal_error> mach_o::symtab_info_data::get_string(std::size_t index) const {
if(stringtab && index < symtab.strsize) { if(stringtab && index < symtab.strsize) {
return stringtab.unwrap().data() + index; return stringtab.get() + index;
} else { } else {
return internal_error("can't retrieve symbol from symtab"); return internal_error("can't retrieve symbol from symtab");
} }
@ -287,7 +352,7 @@ namespace detail {
} }
print_symbol_table_entry( print_symbol_table_entry(
entry.unwrap_value(), entry.unwrap_value(),
stringtab ? stringtab.unwrap_value().data() : nullptr, stringtab ? stringtab.unwrap_value().get() : nullptr,
symtab.strsize, symtab.strsize,
j j
); );
@ -417,10 +482,10 @@ namespace detail {
return symbols.unwrap(); return symbols.unwrap();
} }
optional<std::string> mach_o::lookup_symbol(frame_ptr pc) { std::string mach_o::lookup_symbol(frame_ptr pc) {
auto symtab_ = symbol_table(); auto symtab_ = symbol_table();
if(!symtab_) { if(!symtab_) {
return nullopt; return "";
} }
const auto& symtab = symtab_.unwrap_value();; const auto& symtab = symtab_.unwrap_value();;
auto it = first_less_than_or_equal( auto it = first_less_than_or_equal(
@ -432,7 +497,7 @@ namespace detail {
} }
); );
if(it == symtab.end()) { if(it == symtab.end()) {
return nullopt; return "";
} }
ASSERT(pc >= it->address); ASSERT(pc >= it->address);
// TODO: We subtracted one from the address so name + diff won't show up in the objdump, decide if desirable // TODO: We subtracted one from the address so name + diff won't show up in the objdump, decide if desirable
@ -492,7 +557,7 @@ namespace detail {
} }
load_command& cmd = load_cmd.unwrap_value(); load_command& cmd = load_cmd.unwrap_value();
if(should_swap()) { if(should_swap()) {
swap_load_command(&cmd, NX_UnknownByteOrder); swap_load_command(cmd);
} }
load_commands.push_back({ actual_offset, cmd.cmd, cmd.cmdsize }); load_commands.push_back({ actual_offset, cmd.cmd, cmd.cmdsize });
actual_offset += cmd.cmdsize; actual_offset += cmd.cmdsize;
@ -509,7 +574,7 @@ namespace detail {
} }
fat_header& header = load_header.unwrap_value(); fat_header& header = load_header.unwrap_value();
if(should_swap()) { if(should_swap()) {
swap_fat_header(&header, NX_UnknownByteOrder); swap_fat_header(header);
} }
// thread_local static struct LP(mach_header)* mhp = _NSGetMachExecuteHeader(); // thread_local static struct LP(mach_header)* mhp = _NSGetMachExecuteHeader();
// off_t arch_offset = (off_t)header_size; // off_t arch_offset = (off_t)header_size;
@ -547,7 +612,7 @@ namespace detail {
} }
fat_arch& arch = load_arch.unwrap_value(); fat_arch& arch = load_arch.unwrap_value();
if(should_swap()) { if(should_swap()) {
swap_fat_arch(&arch, 1, NX_UnknownByteOrder); swap_fat_arch(arch);
} }
fat_arches.push_back(arch); fat_arches.push_back(arch);
arch_offset += arch_size; arch_offset += arch_size;
@ -615,7 +680,7 @@ namespace detail {
symtab_command& symtab = load_symtab.unwrap_value(); symtab_command& symtab = load_symtab.unwrap_value();
ASSERT(symtab.cmd == LC_SYMTAB); ASSERT(symtab.cmd == LC_SYMTAB);
if(should_swap()) { if(should_swap()) {
swap_symtab_command(&symtab, NX_UnknownByteOrder); swap_symtab_command(symtab);
} }
return symtab; return symtab;
} }
@ -642,12 +707,12 @@ namespace detail {
return common; return common;
} }
Result<std::vector<char>, internal_error> mach_o::load_string_table(std::uint32_t offset, std::uint32_t byte_count) const { Result<std::unique_ptr<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); std::unique_ptr<char[]> buffer(new char[byte_count + 1]);
if(std::fseek(file, load_base + offset, SEEK_SET) != 0) { if(std::fseek(file, load_base + offset, SEEK_SET) != 0) {
return internal_error("fseek error while loading mach-o symbol table"); return internal_error("fseek error while loading mach-o symbol table");
} }
if(std::fread(buffer.data(), sizeof(char), byte_count, file) != byte_count) { if(std::fread(buffer.get(), sizeof(char), byte_count, file) != byte_count) {
return internal_error("fread error while loading mach-o symbol table"); return internal_error("fread error while loading mach-o symbol table");
} }
buffer[byte_count] = 0; // just out of an abundance of caution buffer[byte_count] = 0; // just out of an abundance of caution
@ -670,27 +735,6 @@ namespace detail {
return is_fat_magic(magic.unwrap_value()); 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); });
}
}
} }
} }

View File

@ -13,9 +13,7 @@
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include <mach-o/arch.h> #include "binary/defs/mach-o-defs.hpp"
#include <mach-o/loader.h>
#include <mach-o/nlist.h>
namespace cpptrace { namespace cpptrace {
namespace detail { namespace detail {
@ -63,7 +61,7 @@ namespace detail {
struct symtab_info_data { struct symtab_info_data {
symtab_command symtab; symtab_command symtab;
optional<std::vector<char>> stringtab; std::unique_ptr<char[]> stringtab;
Result<const char*, internal_error> get_string(std::size_t index) const; Result<const char*, internal_error> get_string(std::size_t index) const;
}; };
@ -112,7 +110,7 @@ namespace detail {
Result<const std::vector<symbol_entry>&, internal_error> symbol_table(); Result<const std::vector<symbol_entry>&, internal_error> symbol_table();
optional<std::string> lookup_symbol(frame_ptr pc); std::string lookup_symbol(frame_ptr pc);
// produce information similar to dsymutil -dump-debug-map // produce information similar to dsymutil -dump-debug-map
static void print_debug_map(const debug_map& debug_map); static void print_debug_map(const debug_map& debug_map);
@ -131,14 +129,12 @@ namespace detail {
template<std::size_t Bits> template<std::size_t Bits>
Result<nlist_64, internal_error> load_symtab_entry(std::uint32_t symbol_base, std::size_t index) const; Result<nlist_64, internal_error> load_symtab_entry(std::uint32_t symbol_base, std::size_t index) const;
Result<std::vector<char>, internal_error> load_string_table(std::uint32_t offset, std::uint32_t byte_count) const; Result<std::unique_ptr<char[]>, internal_error> load_string_table(std::uint32_t offset, std::uint32_t byte_count) const;
bool should_swap() const; bool should_swap() const;
}; };
Result<bool, internal_error> macho_is_fat(const std::string& object_path); 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);
} }
} }

View File

@ -30,12 +30,12 @@ namespace detail {
if(it == cache.end()) { if(it == cache.end()) {
// arguably it'd be better to release the lock while computing this, but also arguably it's good to not // 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 // have two threads try to do the same computation
auto elf_object = open_elf_cached(object_path); auto obj = elf::open_elf(object_path);
// TODO: Cache the error // TODO: Cache the error
if(!elf_object) { if(!obj) {
return elf_object.unwrap_error(); return obj.unwrap_error();
} }
auto base = elf_object.unwrap_value()->get_module_image_base(); auto base = obj.unwrap_value().get_module_image_base();
if(base.is_error()) { if(base.is_error()) {
return std::move(base).unwrap_error(); return std::move(base).unwrap_error();
} }
@ -57,12 +57,12 @@ namespace detail {
if(it == cache.end()) { if(it == cache.end()) {
// arguably it'd be better to release the lock while computing this, but also arguably it's good to not // 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 // have two threads try to do the same computation
auto mach_o_object = open_mach_o_cached(object_path); auto obj = mach_o::open_mach_o(object_path);
// TODO: Cache the error // TODO: Cache the error
if(!mach_o_object) { if(!obj) {
return mach_o_object.unwrap_error(); return obj.unwrap_error();
} }
auto base = mach_o_object.unwrap_value()->get_text_vmaddr(); auto base = obj.unwrap_value().get_text_vmaddr();
if(!base) { if(!base) {
return std::move(base).unwrap_error(); return std::move(base).unwrap_error();
} }

View File

@ -78,11 +78,9 @@ namespace detail {
- reinterpret_cast<std::uintptr_t>(info.dli_fbase) - reinterpret_cast<std::uintptr_t>(info.dli_fbase)
+ base.unwrap_value(); + base.unwrap_value();
} else { } else {
if(!should_absorb_trace_exceptions()) {
base.drop_error(); base.drop_error();
} }
} }
}
return frame; return frame;
} }
#else #else
@ -103,11 +101,9 @@ namespace detail {
- reinterpret_cast<std::uintptr_t>(info.dli_fbase) - reinterpret_cast<std::uintptr_t>(info.dli_fbase)
+ base.unwrap_value(); + base.unwrap_value();
} else { } else {
if(!should_absorb_trace_exceptions()) {
base.drop_error(); base.drop_error();
} }
} }
}
return frame; return frame;
} }
#endif #endif
@ -150,10 +146,8 @@ namespace detail {
- reinterpret_cast<std::uintptr_t>(handle) - reinterpret_cast<std::uintptr_t>(handle)
+ base.unwrap_value(); + base.unwrap_value();
} else { } else {
if(!should_absorb_trace_exceptions()) {
base.drop_error(); base.drop_error();
} }
}
} else { } else {
std::fprintf(stderr, "%s\n", std::system_error(GetLastError(), std::system_category()).what()); std::fprintf(stderr, "%s\n", std::system_error(GetLastError(), std::system_category()).what());
} }

View File

@ -53,10 +53,6 @@ namespace detail {
// may return the object that defines the function descriptor (and not the object that contains the code // 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. // implementing the function), or fail to find any object at all.
} }
bool has_get_safe_object_frame() {
return true;
}
} }
} }
#else #else
@ -67,10 +63,6 @@ namespace detail {
out->address_relative_to_object_start = 0; out->address_relative_to_object_start = 0;
out->object_path[0] = 0; out->object_path[0] = 0;
} }
bool has_get_safe_object_frame() {
return false;
}
} }
} }
#endif #endif

View File

@ -6,8 +6,6 @@
namespace cpptrace { namespace cpptrace {
namespace detail { namespace detail {
void get_safe_object_frame(frame_ptr address, safe_object_frame* out); void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
bool has_get_safe_object_frame();
} }
} }

View File

@ -1,5 +1,4 @@
#include <cpptrace/cpptrace.hpp> #include <cpptrace/cpptrace.hpp>
#include <cpptrace/formatting.hpp>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
@ -10,7 +9,6 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "cpptrace/basic.hpp"
#include "symbols/symbols.hpp" #include "symbols/symbols.hpp"
#include "unwind/unwind.hpp" #include "unwind/unwind.hpp"
#include "demangle/demangle.hpp" #include "demangle/demangle.hpp"
@ -20,7 +18,6 @@
#include "binary/object.hpp" #include "binary/object.hpp"
#include "binary/safe_dl.hpp" #include "binary/safe_dl.hpp"
#include "snippets/snippet.hpp" #include "snippets/snippet.hpp"
#include "options.hpp"
namespace cpptrace { namespace cpptrace {
CPPTRACE_FORCE_NO_INLINE CPPTRACE_FORCE_NO_INLINE
@ -62,7 +59,7 @@ namespace cpptrace {
try { try {
std::vector<stacktrace_frame> trace = detail::resolve_frames(frames); std::vector<stacktrace_frame> trace = detail::resolve_frames(frames);
for(auto& frame : trace) { for(auto& frame : trace) {
frame.symbol = detail::demangle(frame.symbol, true); frame.symbol = detail::demangle(frame.symbol);
} }
return {std::move(trace)}; return {std::move(trace)};
} catch(...) { // NOSONAR } catch(...) { // NOSONAR
@ -109,7 +106,7 @@ namespace cpptrace {
try { try {
std::vector<stacktrace_frame> trace = detail::resolve_frames(frames); std::vector<stacktrace_frame> trace = detail::resolve_frames(frames);
for(auto& frame : trace) { for(auto& frame : trace) {
frame.symbol = detail::demangle(frame.symbol, true); frame.symbol = detail::demangle(frame.symbol);
} }
return {std::move(trace)}; return {std::move(trace)};
} catch(...) { // NOSONAR } catch(...) { // NOSONAR
@ -132,12 +129,41 @@ namespace cpptrace {
return detail::get_frame_object_info(raw_address); 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 { std::string stacktrace_frame::to_string() const {
return to_string(false); return to_string(false);
} }
std::string stacktrace_frame::to_string(bool color) const { std::string stacktrace_frame::to_string(bool color) const {
return get_default_formatter().format(*this, color); return frame_to_string(color, *this);
} }
std::ostream& operator<<(std::ostream& stream, const stacktrace_frame& frame) { std::ostream& operator<<(std::ostream& stream, const stacktrace_frame& frame) {
@ -169,34 +195,89 @@ namespace cpptrace {
} }
void stacktrace::print() const { void stacktrace::print() const {
get_default_formatter().print(*this); print(std::cerr, true);
} }
void stacktrace::print(std::ostream& stream) const { void stacktrace::print(std::ostream& stream) const {
get_default_formatter().print(stream, *this); print(stream, true);
} }
void stacktrace::print(std::ostream& stream, bool color) const { void stacktrace::print(std::ostream& stream, bool color) const {
get_default_formatter().print(stream, *this, color); print(stream, color, true, nullptr);
} }
namespace detail { static void print_frame(
const formatter& get_default_snippet_formatter() { std::ostream& stream,
static formatter snippet_formatter = formatter{}.snippets(true); bool color,
return snippet_formatter; 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++;
} }
} }
void stacktrace::print_with_snippets() const { void stacktrace::print_with_snippets() const {
detail::get_default_snippet_formatter().print(*this); print_with_snippets(std::cerr, true);
} }
void stacktrace::print_with_snippets(std::ostream& stream) const { void stacktrace::print_with_snippets(std::ostream& stream) const {
detail::get_default_snippet_formatter().print(stream, *this); print_with_snippets(stream, true);
} }
void stacktrace::print_with_snippets(std::ostream& stream, bool color) const { void stacktrace::print_with_snippets(std::ostream& stream, bool color) const {
detail::get_default_snippet_formatter().print(stream, *this, color); 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++;
}
} }
void stacktrace::clear() { void stacktrace::clear() {
@ -208,12 +289,13 @@ namespace cpptrace {
} }
std::string stacktrace::to_string(bool color) const { std::string stacktrace::to_string(bool color) const {
return get_default_formatter().format(*this, color); std::ostringstream oss;
print(oss, color, false, nullptr);
return std::move(oss).str();
} }
std::ostream& operator<<(std::ostream& stream, const stacktrace& trace) { std::ostream& operator<<(std::ostream& stream, const stacktrace& trace) {
get_default_formatter().print(stream, trace); return stream << trace.to_string();
return stream;
} }
CPPTRACE_FORCE_NO_INLINE CPPTRACE_FORCE_NO_INLINE
@ -311,7 +393,7 @@ namespace cpptrace {
std::vector<frame_ptr> frames = detail::capture_frames(skip + 1, max_depth); std::vector<frame_ptr> frames = detail::capture_frames(skip + 1, max_depth);
std::vector<stacktrace_frame> trace = detail::resolve_frames(frames); std::vector<stacktrace_frame> trace = detail::resolve_frames(frames);
for(auto& frame : trace) { for(auto& frame : trace) {
frame.symbol = detail::demangle(frame.symbol, true); frame.symbol = detail::demangle(frame.symbol);
} }
return {std::move(trace)}; return {std::move(trace)};
} catch(...) { // NOSONAR } catch(...) { // NOSONAR
@ -333,8 +415,4 @@ namespace cpptrace {
bool can_signal_safe_unwind() { bool can_signal_safe_unwind() {
return detail::has_safe_unwind(); return detail::has_safe_unwind();
} }
bool can_get_safe_object_frame() {
return detail::has_get_safe_object_frame();
}
} }

View File

@ -107,7 +107,7 @@ CTRACE_FORMAT_EPILOGUE
new_frame.line = frame.line.value_or(invalid_pos); new_frame.line = frame.line.value_or(invalid_pos);
new_frame.column = frame.column.value_or(invalid_pos); new_frame.column = frame.column.value_or(invalid_pos);
new_frame.filename = generate_owning_string(frame.filename).data; new_frame.filename = generate_owning_string(frame.filename).data;
new_frame.symbol = generate_owning_string(cpptrace::detail::demangle(frame.symbol, true)).data; new_frame.symbol = generate_owning_string(cpptrace::detail::demangle(frame.symbol)).data;
new_frame.is_inline = ctrace_bool(frame.is_inline); new_frame.is_inline = ctrace_bool(frame.is_inline);
return new_frame; return new_frame;
} }
@ -310,14 +310,10 @@ extern "C" {
cpptrace::get_safe_object_frame(address, reinterpret_cast<cpptrace::safe_object_frame*>(out)); cpptrace::get_safe_object_frame(address, reinterpret_cast<cpptrace::safe_object_frame*>(out));
} }
ctrace_bool ctrace_can_signal_safe_unwind() { ctrace_bool can_signal_safe_unwind() {
return cpptrace::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::io:
ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color) { ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color) {
if(!trace || !trace->frames) { if(!trace || !trace->frames) {

View File

@ -5,7 +5,7 @@
namespace cpptrace { namespace cpptrace {
namespace detail { namespace detail {
std::string demangle(const std::string& name, bool check_prefix); std::string demangle(const std::string&);
} }
} }

View File

@ -13,10 +13,10 @@
namespace cpptrace { namespace cpptrace {
namespace detail { namespace detail {
std::string demangle(const std::string& name, bool check_prefix) { std::string demangle(const std::string& name) {
// https://itanium-cxx-abi.github.io/cxx-abi/abi.html#demangler // https://itanium-cxx-abi.github.io/cxx-abi/abi.html#demangler
// Check both _Z and __Z, apple prefixes all symbols with an underscore // check both _Z and __Z, apple prefixes all symbols with an underscore
if(check_prefix && !(starts_with(name, "_Z") || starts_with(name, "__Z"))) { if(!(starts_with(name, "_Z") || starts_with(name, "__Z"))) {
return name; return name;
} }
// Apple clang demangles __Z just fine but gcc doesn't, so just offset the leading underscore // Apple clang demangles __Z just fine but gcc doesn't, so just offset the leading underscore
@ -39,14 +39,13 @@ namespace detail {
// it appears safe to pass nullptr for status however the docs don't explicitly say it's safe so I don't // 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 // want to rely on it
int status; int status;
auto demangled = raii_wrap( char* const demangled = abi::__cxa_demangle(to_demangle.get().c_str() + offset, nullptr, nullptr, &status);
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 // 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 // we'll just quietly return the mangled name
if(demangled.get()) { if(demangled) {
std::string str = demangled.get(); // TODO: raii_wrap the char*?
std::string str = demangled;
std::free(demangled);
if(!rest.empty()) { if(!rest.empty()) {
str += rest; str += rest;
} }

View File

@ -6,7 +6,7 @@
namespace cpptrace { namespace cpptrace {
namespace detail { namespace detail {
std::string demangle(const std::string& name, bool) { std::string demangle(const std::string& name) {
return name; return name;
} }
} }

View File

@ -1,7 +1,6 @@
#ifdef CPPTRACE_DEMANGLE_WITH_WINAPI #ifdef CPPTRACE_DEMANGLE_WITH_WINAPI
#include "demangle/demangle.hpp" #include "demangle/demangle.hpp"
#include "platform/dbghelp_utils.hpp"
#include <string> #include <string>
@ -13,9 +12,7 @@
namespace cpptrace { namespace cpptrace {
namespace detail { namespace detail {
std::string demangle(const std::string& name, bool) { std::string demangle(const std::string& name) {
// Dbghelp is is single-threaded, so acquire a lock.
auto lock = get_dbghelp_lock();
char buffer[500]; char buffer[500];
auto ret = UnDecorateSymbolName(name.c_str(), buffer, sizeof(buffer) - 1, 0); auto ret = UnDecorateSymbolName(name.c_str(), buffer, sizeof(buffer) - 1, 0);
if(ret == 0) { if(ret == 0) {

View File

@ -10,7 +10,6 @@
#include "platform/exception_type.hpp" #include "platform/exception_type.hpp"
#include "utils/common.hpp" #include "utils/common.hpp"
#include "options.hpp"
namespace cpptrace { namespace cpptrace {
namespace detail { namespace detail {

View File

@ -1,353 +0,0 @@
#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;
}
}

View File

@ -0,0 +1,59 @@
#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

View File

@ -0,0 +1,22 @@
#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

View File

@ -1,149 +0,0 @@
#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

View File

@ -1,50 +0,0 @@
#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

View File

@ -17,7 +17,7 @@ namespace detail {
inline std::string exception_type_name() { inline std::string exception_type_name() {
#if defined(CPPTRACE_HAS_CXX_EXCEPTION_TYPE) && (IS_LIBSTDCXX || IS_LIBCXX) #if defined(CPPTRACE_HAS_CXX_EXCEPTION_TYPE) && (IS_LIBSTDCXX || IS_LIBCXX)
const std::type_info* t = abi::__cxa_current_exception_type(); const std::type_info* t = abi::__cxa_current_exception_type();
return t ? detail::demangle(t->name(), false) : "<unknown>"; return t ? detail::demangle(t->name()) : "<unknown>";
#else #else
return "<unknown>"; return "<unknown>";
#endif #endif

View File

@ -39,9 +39,13 @@ namespace detail {
#elif IS_APPLE #elif IS_APPLE
#include <cstdint> #include <cstdint>
#include <mach-o/dyld.h> // #include <mach-o/dyld.h>
#include <sys/syslimits.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 #define CPPTRACE_MAX_PATH CPPTRACE_PATH_MAX
namespace cpptrace { namespace cpptrace {

View File

@ -135,10 +135,7 @@ namespace detail {
if(color && line == target_line) { if(color && line == target_line) {
snippet += RESET; snippet += RESET;
} }
snippet += lines[line - original_begin]; snippet += lines[line - original_begin] + "\n";
if(line != end) {
snippet += '\n';
}
} }
return snippet; return snippet;
} }

View File

@ -34,7 +34,7 @@ namespace libdwarf {
if(!resolver) { if(!resolver) {
// this seems silly but it's an attempt to not repeatedly try to initialize new dwarf_resolvers if // 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 // exceptions are thrown, e.g. if the path doesn't exist
resolver = detail::make_unique<null_resolver>(); resolver = std::unique_ptr<null_resolver>(new null_resolver);
resolver = make_dwarf_resolver(object_path); resolver = make_dwarf_resolver(object_path);
} }
return resolver; return resolver;
@ -46,11 +46,11 @@ namespace libdwarf {
// the path doesn't exist // the path doesn't exist
std::unordered_map<std::string, uint64_t> symbols; std::unordered_map<std::string, uint64_t> symbols;
this->symbols = symbols; this->symbols = symbols;
auto mach_o_object = open_mach_o_cached(object_path); auto obj = mach_o::open_mach_o(object_path);
if(!mach_o_object) { if(!obj) {
return this->symbols.unwrap(); return this->symbols.unwrap();
} }
const auto& symbol_table = mach_o_object.unwrap_value()->symbol_table(); const auto& symbol_table = obj.unwrap_value().symbol_table();
if(!symbol_table) { if(!symbol_table) {
return this->symbols.unwrap(); return this->symbols.unwrap();
} }
@ -110,11 +110,11 @@ namespace libdwarf {
debug_map_resolver(const std::string& source_object_path) { debug_map_resolver(const std::string& source_object_path) {
// load mach-o // load mach-o
// TODO: Cache somehow? // TODO: Cache somehow?
auto mach_o_object = open_mach_o_cached(source_object_path); auto obj = mach_o::open_mach_o(source_object_path);
if(!mach_o_object) { if(!obj) {
return; return;
} }
mach_o& source_mach = *mach_o_object.unwrap_value(); mach_o& source_mach = obj.unwrap_value();
auto source_debug_map = source_mach.get_debug_map(); auto source_debug_map = source_mach.get_debug_map();
if(!source_debug_map) { if(!source_debug_map) {
return; return;
@ -198,7 +198,7 @@ namespace libdwarf {
}; };
std::unique_ptr<symbol_resolver> make_debug_map_resolver(const std::string& object_path) { std::unique_ptr<symbol_resolver> make_debug_map_resolver(const std::string& object_path) {
return detail::make_unique<debug_map_resolver>(object_path); return std::unique_ptr<debug_map_resolver>(new debug_map_resolver(object_path));
} }
#endif #endif
} }

View File

@ -27,9 +27,10 @@ namespace libdwarf {
[[noreturn]] inline void handle_dwarf_error(Dwarf_Debug dbg, Dwarf_Error error) { [[noreturn]] inline void handle_dwarf_error(Dwarf_Debug dbg, Dwarf_Error error) {
Dwarf_Unsigned ev = dwarf_errno(error); Dwarf_Unsigned ev = dwarf_errno(error);
// dwarf_dealloc_error deallocates the message, attaching to msg is convenient char* msg = dwarf_errmsg(error);
auto msg = raii_wrap(dwarf_errmsg(error), [dbg, error] (char*) { dwarf_dealloc_error(dbg, error); }); (void)dbg;
throw internal_error(microfmt::format("dwarf error {} {}", ev, msg.get())); // dwarf_dealloc_error(dbg, error);
throw internal_error("dwarf error {} {}", ev, msg);
} }
struct die_object { struct die_object {
@ -66,12 +67,8 @@ namespace libdwarf {
} }
~die_object() { ~die_object() {
release();
}
void release() {
if(die) { if(die) {
dwarf_dealloc_die(exchange(die, nullptr)); dwarf_dealloc_die(die);
} }
} }
@ -79,15 +76,16 @@ namespace libdwarf {
die_object& operator=(const die_object&) = delete; die_object& operator=(const die_object&) = delete;
// dbg doesn't strictly have to be st to null but it helps ensure attempts to use the die_object after this to die_object(die_object&& other) noexcept : dbg(other.dbg), die(other.die) {
// segfault. A valid use otherwise would be moved_from.get_sibling() which would get the next CU. // done for finding mistakes, attempts to use the die_object after this should segfault
die_object(die_object&& other) noexcept // a valid use otherwise would be moved_from.get_sibling() which would get the next CU
: dbg(exchange(other.dbg, nullptr)), die(exchange(other.die, nullptr)) {} other.dbg = nullptr;
other.die = nullptr;
}
die_object& operator=(die_object&& other) noexcept { die_object& operator=(die_object&& other) noexcept {
release(); std::swap(dbg, other.dbg);
dbg = exchange(other.dbg, nullptr); std::swap(die, other.die);
die = exchange(other.die, nullptr);
return *this; return *this;
} }

View File

@ -1,31 +0,0 @@
#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);
}
}
}

View File

@ -1,15 +0,0 @@
#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

View File

@ -4,13 +4,10 @@
#include <cpptrace/basic.hpp> #include <cpptrace/basic.hpp>
#include "symbols/dwarf/dwarf.hpp" // has dwarf #includes #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 "symbols/symbols.hpp"
#include "utils/common.hpp" #include "utils/common.hpp"
#include "utils/error.hpp" #include "utils/error.hpp"
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include "utils/lru_cache.hpp"
#include "platform/path.hpp" #include "platform/path.hpp"
#include "platform/program_name.hpp" // For CPPTRACE_MAX_PATH #include "platform/program_name.hpp" // For CPPTRACE_MAX_PATH
@ -22,7 +19,6 @@
#include <cstdint> #include <cstdint>
#include <cstdio> #include <cstdio>
#include <functional> #include <functional>
#include <limits>
#include <memory> #include <memory>
#include <string> #include <string>
#include <type_traits> #include <type_traits>
@ -41,6 +37,38 @@ namespace libdwarf {
constexpr bool dump_dwarf = false; constexpr bool dump_dwarf = false;
constexpr bool trace_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; class dwarf_resolver;
// used to describe data from an upstream binary to a resolver for the .dwo // used to describe data from an upstream binary to a resolver for the .dwo
@ -52,24 +80,20 @@ namespace libdwarf {
class dwarf_resolver : public symbol_resolver { class dwarf_resolver : public symbol_resolver {
std::string object_path; std::string object_path;
// dwarf_finish needs to be called after all other dwarf stuff is cleaned up, e.g. `srcfiles` and aranges etc Dwarf_Debug dbg = nullptr;
// 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; bool ok = false;
// .debug_aranges cache // .debug_aranges cache
Dwarf_Arange* aranges = nullptr; Dwarf_Arange* aranges = nullptr;
Dwarf_Signed arange_count = 0; Dwarf_Signed arange_count = 0;
// Map from CU -> Line context // Map from CU -> Line context
lru_cache<Dwarf_Off, line_table_info> line_tables{get_dwarf_resolver_line_table_cache_size()}; std::unordered_map<Dwarf_Off, line_table_info> line_tables;
// Map from CU -> Sorted subprograms vector // Map from CU -> Sorted subprograms vector
std::unordered_map<Dwarf_Off, die_cache<monostate>> subprograms_cache; std::unordered_map<Dwarf_Off, std::vector<subprogram_entry>> subprograms_cache;
// Vector of ranges and their corresponding CU offsets // Vector of ranges and their corresponding CU offsets
// data stored for each cache entry is a Dwarf_Half dwversion std::vector<cu_entry> cu_cache;
die_cache<Dwarf_Half> cu_cache;
bool generated_cu_cache = false; bool generated_cu_cache = false;
// Map from CU -> {srcfiles, count} // Map from CU -> {srcfiles, count}
std::unordered_map<Dwarf_Off, srcfiles> srcfiles_cache; std::unordered_map<Dwarf_Off, std::pair<char**, Dwarf_Signed>> srcfiles_cache;
// Map from CU -> split full cu resolver // Map from CU -> split full cu resolver
std::unordered_map<Dwarf_Off, std::unique_ptr<dwarf_resolver>> split_full_cu_resolvers; std::unordered_map<Dwarf_Off, std::unique_ptr<dwarf_resolver>> split_full_cu_resolvers;
// info for resolving a dwo object // info for resolving a dwo object
@ -127,12 +151,12 @@ namespace libdwarf {
if(result.is_error()) { if(result.is_error()) {
result.drop_error(); result.drop_error();
} else if(result.unwrap_value()) { } else if(result.unwrap_value()) {
auto mach_o_object = open_mach_o_cached(object_path); auto obj = mach_o::open_mach_o(object_path);
if(!mach_o_object) { if(!obj) {
ok = false; ok = false;
return; return;
} }
universal_number = mach_o_object.unwrap_value()->get_fat_index(); universal_number = obj.unwrap_value().get_fat_index();
} }
#endif #endif
@ -142,7 +166,6 @@ namespace libdwarf {
if(use_buffer) { if(use_buffer) {
buffer = std::unique_ptr<char[]>(new char[CPPTRACE_MAX_PATH]); buffer = std::unique_ptr<char[]>(new char[CPPTRACE_MAX_PATH]);
} }
dwarf_set_de_alloc_flag(0);
auto ret = wrap( auto ret = wrap(
dwarf_init_path_a, dwarf_init_path_a,
object_path.c_str(), object_path.c_str(),
@ -152,7 +175,7 @@ namespace libdwarf {
universal_number, universal_number,
nullptr, nullptr,
nullptr, nullptr,
&dbg.get() &dbg
); );
if(ret == DW_DLV_OK) { if(ret == DW_DLV_OK) {
ok = true; ok = true;
@ -168,7 +191,7 @@ namespace libdwarf {
VERIFY(wrap(dwarf_set_tied_dbg, dbg, skeleton.unwrap().resolver.dbg) == DW_DLV_OK); VERIFY(wrap(dwarf_set_tied_dbg, dbg, skeleton.unwrap().resolver.dbg) == DW_DLV_OK);
} }
if(ok && !get_dwarf_resolver_disable_aranges()) { if(ok) {
// Check for .debug_aranges for fast lookup // Check for .debug_aranges for fast lookup
wrap(dwarf_get_aranges, dbg, &aranges, &arange_count); wrap(dwarf_get_aranges, dbg, &aranges, &arange_count);
} }
@ -176,13 +199,23 @@ namespace libdwarf {
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
~dwarf_resolver() override { ~dwarf_resolver() override {
if(aranges) { // TODO: Maybe redundant since dwarf_finish(dbg); will clean up the line stuff anyway but may as well just
for(int i = 0; i < arange_count; i++) { // for thoroughness
dwarf_dealloc(dbg, aranges[i], DW_DLA_ARANGE); for(auto& entry : line_tables) {
aranges[i] = nullptr; 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) {
dwarf_dealloc(dbg, aranges, DW_DLA_LIST); dwarf_dealloc(dbg, aranges, DW_DLA_LIST);
} }
cu_cache.clear();
dwarf_finish(dbg);
} }
dwarf_resolver(const dwarf_resolver&) = delete; dwarf_resolver(const dwarf_resolver&) = delete;
@ -244,32 +277,30 @@ namespace libdwarf {
walk_compilation_units([this] (const die_object& cu_die) { walk_compilation_units([this] (const die_object& cu_die) {
Dwarf_Half offset_size = 0; Dwarf_Half offset_size = 0;
Dwarf_Half dwversion = 0; Dwarf_Half dwversion = 0;
VERIFY(dwarf_get_version_of_die(cu_die.get(), &dwversion, &offset_size) == DW_DLV_OK); dwarf_get_version_of_die(cu_die.get(), &dwversion, &offset_size);
if(skeleton) { if(skeleton) {
// NOTE: If we have a corresponding skeleton, we assume we have one CU matching the skeleton CU // 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 // Precedence for this assumption is https://dwarfstd.org/doc/DWARF5.pdf#subsection.3.1.3
// TODO: Also assuming same dwversion // TODO: Also assuming same dwversion
const auto& skeleton_cu = skeleton.unwrap().cu_die; const auto& skeleton_cu = skeleton.unwrap().cu_die;
auto ranges_vec = skeleton_cu.get_rangelist_entries(skeleton_cu, dwversion); 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) { for(auto range : ranges_vec) {
cu_cache.insert(cu_die_handle, range.first, range.second, dwversion); // TODO: Reduce cloning here
} cu_cache.push_back({ cu_die.clone(), dwversion, range.first, range.second });
} }
return false; return false;
} else { } else {
auto ranges_vec = cu_die.get_rangelist_entries(cu_die, dwversion); 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) { for(auto range : ranges_vec) {
cu_cache.insert(cu_die_handle, range.first, range.second, dwversion); // TODO: Reduce cloning here
} cu_cache.push_back({ cu_die.clone(), dwversion, range.first, range.second });
} }
return true; return true;
} }
}); });
cu_cache.finalize(); std::sort(cu_cache.begin(), cu_cache.end(), [] (const cu_entry& a, const cu_entry& b) {
return a.low < b.low;
});
generated_cu_cache = true; generated_cu_cache = true;
} }
} }
@ -312,11 +343,11 @@ namespace libdwarf {
char** dw_srcfiles; char** dw_srcfiles;
Dwarf_Signed dw_filecount; Dwarf_Signed dw_filecount;
VERIFY(wrap(dwarf_srcfiles, cu_die.get(), &dw_srcfiles, &dw_filecount) == DW_DLV_OK); 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) { if(Dwarf_Signed(file_i) < dw_filecount) {
// dwarf is using 1-indexing // dwarf is using 1-indexing
filename = srcfiles.get(file_i); filename = dw_srcfiles[file_i];
} }
dwarf_dealloc(cu_die.dbg, dw_srcfiles, DW_DLA_LIST);
} else { } else {
auto off = cu_die.get_global_offset(); auto off = cu_die.get_global_offset();
auto it = srcfiles_cache.find(off); auto it = srcfiles_cache.find(off);
@ -324,11 +355,13 @@ namespace libdwarf {
char** dw_srcfiles; char** dw_srcfiles;
Dwarf_Signed dw_filecount; Dwarf_Signed dw_filecount;
VERIFY(wrap(dwarf_srcfiles, cu_die.get(), &dw_srcfiles, &dw_filecount) == DW_DLV_OK); VERIFY(wrap(dwarf_srcfiles, cu_die.get(), &dw_srcfiles, &dw_filecount) == DW_DLV_OK);
it = srcfiles_cache.insert(it, {off, srcfiles{cu_die.dbg, dw_srcfiles, dw_filecount}}); it = srcfiles_cache.insert(it, {off, {dw_srcfiles, dw_filecount}});
} }
if(file_i < it->second.count()) { char** dw_srcfiles = it->second.first;
Dwarf_Signed dw_filecount = it->second.second;
if(Dwarf_Signed(file_i) < dw_filecount) {
// dwarf is using 1-indexing // dwarf is using 1-indexing
filename = it->second.get(file_i); filename = dw_srcfiles[file_i];
} }
} }
return filename; return filename;
@ -364,7 +397,7 @@ namespace libdwarf {
if(file_i) { if(file_i) {
// for dwarf 2, 3, 4, and experimental line table version 0xfe06 1-indexing is used // for dwarf 2, 3, 4, and experimental line table version 0xfe06 1-indexing is used
// for dwarf 5 0-indexing is used // for dwarf 5 0-indexing is used
optional<line_table_info&> line_table_opt; optional<std::reference_wrapper<line_table_info>> line_table_opt;
if(skeleton) { if(skeleton) {
line_table_opt = skeleton.unwrap().resolver.get_line_table( line_table_opt = skeleton.unwrap().resolver.get_line_table(
skeleton.unwrap().cu_die skeleton.unwrap().cu_die
@ -373,7 +406,7 @@ namespace libdwarf {
line_table_opt = get_line_table(cu_die); line_table_opt = get_line_table(cu_die);
} }
if(line_table_opt) { if(line_table_opt) {
auto& line_table = line_table_opt.unwrap(); auto& line_table = line_table_opt.unwrap().get();
if(line_table.version != 5) { if(line_table.version != 5) {
if(file_i.unwrap() == 0) { if(file_i.unwrap() == 0) {
file_i.reset(); // 0 means no name to be found file_i.reset(); // 0 means no name to be found
@ -498,28 +531,26 @@ namespace libdwarf {
const die_object& cu_die, const die_object& cu_die,
const die_object& die, const die_object& die,
Dwarf_Half dwversion, Dwarf_Half dwversion,
die_cache<monostate>& subprogram_cache std::vector<subprogram_entry>& vec
) { ) {
walk_die_list( walk_die_list(
die, die,
[this, &cu_die, dwversion, &subprogram_cache] (const die_object& die) { [this, &cu_die, dwversion, &vec] (const die_object& die) {
switch(die.get_tag()) { switch(die.get_tag()) {
case DW_TAG_subprogram: case DW_TAG_subprogram:
{ {
auto ranges_vec = die.get_rangelist_entries(cu_die, dwversion); auto ranges_vec = die.get_rangelist_entries(cu_die, dwversion);
// TODO: Feels super inefficient and some day should maybe use an interval tree. // 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) { for(auto range : ranges_vec) {
subprogram_cache.insert(die_handle, range.first, range.second); // TODO: Reduce cloning here
} vec.push_back({ die.clone(), range.first, range.second });
} }
// Walk children to get things like lambdas // Walk children to get things like lambdas
// TODO: Somehow find a way to get better names here? For gcc it's just "operator()" // TODO: Somehow find a way to get better names here? For gcc it's just "operator()"
// On clang it's better // On clang it's better
auto child = die.get_child(); auto child = die.get_child();
if(child) { if(child) {
preprocess_subprograms(cu_die, child, dwversion, subprogram_cache); preprocess_subprograms(cu_die, child, dwversion, vec);
} }
} }
break; break;
@ -532,7 +563,7 @@ namespace libdwarf {
{ {
auto child = die.get_child(); auto child = die.get_child();
if(child) { if(child) {
preprocess_subprograms(cu_die, child, dwversion, subprogram_cache); preprocess_subprograms(cu_die, child, dwversion, vec);
} }
} }
break; break;
@ -562,32 +593,41 @@ namespace libdwarf {
auto it = subprograms_cache.find(off); auto it = subprograms_cache.find(off);
if(it == subprograms_cache.end()) { if(it == subprograms_cache.end()) {
// TODO: Refactor. Do the sort in the preprocess function and return the vec directly. // TODO: Refactor. Do the sort in the preprocess function and return the vec directly.
die_cache<monostate> subprogram_cache; std::vector<subprogram_entry> vec;
preprocess_subprograms(cu_die, cu_die, dwversion, subprogram_cache); preprocess_subprograms(cu_die, cu_die, dwversion, vec);
subprogram_cache.finalize(); std::sort(vec.begin(), vec.end(), [] (const subprogram_entry& a, const subprogram_entry& b) {
subprograms_cache.emplace(off, std::move(subprogram_cache)); return a.low < b.low;
});
subprograms_cache.emplace(off, std::move(vec));
it = subprograms_cache.find(off); it = subprograms_cache.find(off);
} }
const auto& subprogram_cache = it->second; auto& vec = it->second;
auto maybe_die = subprogram_cache.lookup(pc); auto vec_it = first_less_than_or_equal(
vec.begin(),
vec.end(),
pc,
[] (Dwarf_Addr pc, const subprogram_entry& entry) {
return pc < entry.low;
}
);
// If the vector has been empty this can happen // If the vector has been empty this can happen
if(maybe_die.has_value()) { if(vec_it != vec.end()) {
if(maybe_die.unwrap().pc_in_die(cu_die, dwversion, pc)) { if(vec_it->die.pc_in_die(cu_die, dwversion, pc)) {
frame.symbol = retrieve_symbol_for_subprogram(cu_die, maybe_die.unwrap(), pc, dwversion, inlines); frame.symbol = retrieve_symbol_for_subprogram(cu_die, vec_it->die, pc, dwversion, inlines);
} }
} else { } else {
ASSERT(subprogram_cache.ranges_count() == 0, "subprogram_cache.ranges_count() should be 0?"); ASSERT(vec.size() == 0, "Vec should be empty?");
} }
} }
} }
// returns a reference to a CU's line table, may be invalidated if the line_tables map is modified // 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 CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
optional<line_table_info&> get_line_table(const die_object& cu_die) { optional<std::reference_wrapper<line_table_info>> get_line_table(const die_object& cu_die) {
auto off = cu_die.get_global_offset(); auto off = cu_die.get_global_offset();
auto res = line_tables.maybe_get(off); auto it = line_tables.find(off);
if(res) { if(it != line_tables.end()) {
return res; return it->second;
} else { } else {
Dwarf_Unsigned version; Dwarf_Unsigned version;
Dwarf_Small table_count; Dwarf_Small table_count;
@ -642,6 +682,24 @@ namespace libdwarf {
} }
} }
line = line_buffer[j - 1]; 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({ line_entries.push_back({
low_addr, low_addr,
line line
@ -654,7 +712,8 @@ namespace libdwarf {
}); });
} }
return line_tables.insert(off, line_table_info{version, line_context, std::move(line_entries)}); it = line_tables.insert({off, {version, line_context, std::move(line_entries)}}).first;
return it->second;
} }
} }
@ -672,7 +731,7 @@ namespace libdwarf {
if(!table_info_opt) { if(!table_info_opt) {
return; // failing silently for now return; // failing silently for now
} }
auto& table_info = table_info_opt.unwrap(); auto& table_info = table_info_opt.unwrap().get();
if(get_cache_mode() == cache_mode::prioritize_speed) { if(get_cache_mode() == cache_mode::prioritize_speed) {
// Lookup in the table // Lookup in the table
auto& line_entries = table_info.line_entries; auto& line_entries = table_info.line_entries;
@ -857,13 +916,17 @@ namespace libdwarf {
} else { } else {
lazy_generate_cu_cache(); lazy_generate_cu_cache();
// look up the cu // look up the cu
auto res = cu_cache.lookup(pc); auto vec_it = first_less_than_or_equal(
// res can be nullopt if the cu_cache vector is empty cu_cache.begin(),
// It can also happen for something like _start, where there is a cached CU for the object but cu_cache.end(),
// _start is outside of the CU's PC range pc,
if(res) { [] (Dwarf_Addr pc, const cu_entry& entry) {
const auto& die = res.unwrap().die; return pc < entry.low;
const auto dwversion = res.unwrap().data; }
);
// 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()) {
// TODO: Cache the range list? // TODO: Cache the range list?
// NOTE: If we have a corresponding skeleton, we assume we have one CU matching the skeleton CU // NOTE: If we have a corresponding skeleton, we assume we have one CU matching the skeleton CU
if( if(
@ -874,10 +937,14 @@ namespace libdwarf {
skeleton.unwrap().dwversion, skeleton.unwrap().dwversion,
pc pc
) )
) || die.pc_in_die(die, dwversion, pc) ) || vec_it->die.pc_in_die(vec_it->die, vec_it->dwversion, pc)
) { ) {
return cu_info{maybe_owned_die_object::ref(die), dwversion}; return cu_info{maybe_owned_die_object::ref(vec_it->die), vec_it->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; return nullopt;
} }
@ -932,7 +999,12 @@ namespace libdwarf {
if(it == split_full_cu_resolvers.end()) { if(it == split_full_cu_resolvers.end()) {
it = split_full_cu_resolvers.emplace( it = split_full_cu_resolvers.emplace(
off, off,
detail::make_unique<dwarf_resolver>(path, skeleton_info{cu_die.clone(), dwversion, *this}) std::unique_ptr<dwarf_resolver>(
new dwarf_resolver(
path,
skeleton_info{cu_die.clone(), dwversion, *this}
)
)
).first; ).first;
} }
res = it->second->resolve_frame(object_frame_info); res = it->second->resolve_frame(object_frame_info);
@ -1010,7 +1082,7 @@ namespace libdwarf {
}; };
std::unique_ptr<symbol_resolver> make_dwarf_resolver(const std::string& object_path) { std::unique_ptr<symbol_resolver> make_dwarf_resolver(const std::string& object_path) {
return detail::make_unique<dwarf_resolver>(object_path); return std::unique_ptr<dwarf_resolver>(new dwarf_resolver(object_path));
} }
} }
} }

View File

@ -1,208 +0,0 @@
#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

View File

@ -83,8 +83,6 @@ 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) { 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) #if defined(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) && defined(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP)
std::vector<stacktrace_frame> trace = libdwarf::resolve_frames(frames); std::vector<stacktrace_frame> trace = libdwarf::resolve_frames(frames);

View File

@ -22,7 +22,6 @@
#endif #endif
#include "binary/object.hpp" #include "binary/object.hpp"
#include "options.hpp"
namespace cpptrace { namespace cpptrace {
namespace detail { namespace detail {

View File

@ -2,13 +2,12 @@
#include <cpptrace/basic.hpp> #include <cpptrace/basic.hpp>
#include "symbols/symbols.hpp" #include "symbols/symbols.hpp"
#include "platform/dbghelp_utils.hpp" #include "platform/dbghelp_syminit_manager.hpp"
#include "binary/object.hpp" #include "binary/object.hpp"
#include "utils/common.hpp" #include "utils/common.hpp"
#include "utils/error.hpp" #include "utils/error.hpp"
#include "utils/utils.hpp"
#include "options.hpp"
#include <mutex>
#include <regex> #include <regex>
#include <system_error> #include <system_error>
#include <vector> #include <vector>
@ -240,9 +239,6 @@ namespace dbghelp {
std::size_t sz = sizeof(TI_FINDCHILDREN_PARAMS) + std::size_t sz = sizeof(TI_FINDCHILDREN_PARAMS) +
(n_children) * sizeof(TI_FINDCHILDREN_PARAMS::ChildId[0]); (n_children) * sizeof(TI_FINDCHILDREN_PARAMS::ChildId[0]);
TI_FINDCHILDREN_PARAMS* children = (TI_FINDCHILDREN_PARAMS*) new char[sz]; TI_FINDCHILDREN_PARAMS* children = (TI_FINDCHILDREN_PARAMS*) new char[sz];
auto guard = scope_exit([&] {
delete[] (char*) children;
});
children->Start = 0; children->Start = 0;
children->Count = n_children; children->Count = n_children;
if( if(
@ -268,6 +264,7 @@ namespace dbghelp {
extent += (i == 0 ? "" : ", ") + resolve_type(children->ChildId[i], proc, modbase); extent += (i == 0 ? "" : ", ") + resolve_type(children->ChildId[i], proc, modbase);
} }
extent += ")"; extent += ")";
delete[] (char*) children;
return {return_type.base, extent + return_type.extent}; return {return_type.base, extent + return_type.extent};
} }
} }
@ -327,6 +324,8 @@ namespace dbghelp {
return true; return true;
} }
std::recursive_mutex dbghelp_lock;
// TODO: Handle backtrace_pcinfo calling the callback multiple times on inlined functions // TODO: Handle backtrace_pcinfo calling the callback multiple times on inlined functions
stacktrace_frame resolve_frame(HANDLE proc, frame_ptr addr) { stacktrace_frame resolve_frame(HANDLE proc, frame_ptr addr) {
// The get_frame_object_info() ends up being inexpensive, at on my machine // The get_frame_object_info() ends up being inexpensive, at on my machine
@ -336,8 +335,7 @@ namespace dbghelp {
// get_frame_object_info() 0.001-0.002 ms 0.0003-0.0006 ms // 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. // At some point it might make sense to make an option to control this.
auto object_frame = get_frame_object_info(addr); auto object_frame = get_frame_object_info(addr);
// Dbghelp is is single-threaded, so acquire a lock. const std::lock_guard<std::recursive_mutex> lock(dbghelp_lock); // all dbghelp functions are not thread safe
auto lock = get_dbghelp_lock();
alignas(SYMBOL_INFO) char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; alignas(SYMBOL_INFO) char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
SYMBOL_INFO* symbol = (SYMBOL_INFO*)buffer; SYMBOL_INFO* symbol = (SYMBOL_INFO*)buffer;
symbol->SizeOfStruct = sizeof(SYMBOL_INFO); symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
@ -422,18 +420,28 @@ namespace dbghelp {
} }
std::vector<stacktrace_frame> resolve_frames(const std::vector<frame_ptr>& frames) { std::vector<stacktrace_frame> resolve_frames(const std::vector<frame_ptr>& frames) {
// Dbghelp is is single-threaded, so acquire a lock. const std::lock_guard<std::recursive_mutex> lock(dbghelp_lock); // all dbghelp functions are not thread safe
auto lock = get_dbghelp_lock();
std::vector<stacktrace_frame> trace; std::vector<stacktrace_frame> trace;
trace.reserve(frames.size()); trace.reserve(frames.size());
// TODO: When does this need to be called? Can it be moved to the symbolizer? // TODO: When does this need to be called? Can it be moved to the symbolizer?
SymSetOptions(SYMOPT_ALLOW_ABSOLUTE_SYMBOLS); SymSetOptions(SYMOPT_ALLOW_ABSOLUTE_SYMBOLS);
auto syminit_info = ensure_syminit(); 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());
}
}
for(const auto frame : frames) { for(const auto frame : frames) {
try { try {
trace.push_back(resolve_frame(syminit_info.get_process_handle() , frame)); trace.push_back(resolve_frame(duplicated_handle , frame));
} catch(...) { // NOSONAR } catch(...) { // NOSONAR
if(!detail::should_absorb_trace_exceptions()) { if(!detail::should_absorb_trace_exceptions()) {
throw; throw;
@ -443,6 +451,14 @@ namespace dbghelp {
trace.push_back(entry); 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; return trace;
} }
} }

View File

@ -5,7 +5,6 @@
#include "platform/program_name.hpp" #include "platform/program_name.hpp"
#include "utils/error.hpp" #include "utils/error.hpp"
#include "utils/common.hpp" #include "utils/common.hpp"
#include "options.hpp"
#include <cstdint> #include <cstdint>
#include <cstdio> #include <cstdio>

View File

@ -96,9 +96,9 @@ namespace libdwarf {
const auto& object_name = group.first; const auto& object_name = group.first;
// TODO PERF: Potentially a duplicate open and parse with module base stuff (and debug map resolver) // TODO PERF: Potentially a duplicate open and parse with module base stuff (and debug map resolver)
#if IS_LINUX #if IS_LINUX
auto object = open_elf_cached(object_name); auto object = elf::open_elf(object_name);
#elif IS_APPLE #elif IS_APPLE
auto object = open_mach_o_cached(object_name); auto object = mach_o::open_mach_o(object_name);
#endif #endif
auto resolver = get_resolver(object_name); auto resolver = get_resolver(object_name);
for(const auto& entry : group.second) { for(const auto& entry : group.second) {
@ -116,9 +116,7 @@ namespace libdwarf {
} }
#if IS_LINUX || IS_APPLE #if IS_LINUX || IS_APPLE
if(frame.frame.symbol.empty() && object.has_value()) { if(frame.frame.symbol.empty() && object.has_value()) {
frame.frame.symbol = object frame.frame.symbol = object.unwrap_value().lookup_symbol(dlframe.object_address);
.unwrap_value()
->lookup_symbol(dlframe.object_address).value_or("");
} }
#endif #endif
} }

View File

@ -4,9 +4,10 @@
#include "unwind/unwind.hpp" #include "unwind/unwind.hpp"
#include "utils/common.hpp" #include "utils/common.hpp"
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include "platform/dbghelp_utils.hpp" #include "platform/dbghelp_syminit_manager.hpp"
#include <vector> #include <vector>
#include <mutex>
#include <cstddef> #include <cstddef>
#include <windows.h> #include <windows.h>
@ -95,19 +96,31 @@ namespace detail {
std::vector<frame_ptr> trace; std::vector<frame_ptr> trace;
// Dbghelp is is single-threaded, so acquire a lock. // Dbghelp is is single-threaded, so acquire a lock.
auto lock = get_dbghelp_lock(); static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex);
// For some reason SymInitialize must be called before StackWalk64 // For some reason SymInitialize must be called before StackWalk64
// Note that the code assumes that // Note that the code assumes that
// SymInitialize( GetCurrentProcess(), NULL, TRUE ) has // SymInitialize( GetCurrentProcess(), NULL, TRUE ) has
// already been called. // already been called.
// //
auto syminit_info = ensure_syminit(); HANDLE duplicated_handle = nullptr;
HANDLE proc = GetCurrentProcess();
HANDLE thread = GetCurrentThread(); 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) { while(trace.size() < max_depth) {
if( if(
!StackWalk64( !StackWalk64(
machine_type, machine_type,
syminit_info.get_process_handle(), duplicated_handle,
thread, thread,
&frame, &frame,
machine_type == IMAGE_FILE_MACHINE_I386 ? NULL : &context, machine_type == IMAGE_FILE_MACHINE_I386 ? NULL : &context,
@ -135,6 +148,14 @@ namespace detail {
break; 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; return trace;
} }

View File

@ -1,6 +1,5 @@
#include <cpptrace/utils.hpp> #include <cpptrace/utils.hpp>
#include <cpptrace/exceptions.hpp> #include <cpptrace/exceptions.hpp>
#include <cpptrace/formatting.hpp>
#include <iostream> #include <iostream>
@ -8,11 +7,10 @@
#include "snippets/snippet.hpp" #include "snippets/snippet.hpp"
#include "utils/utils.hpp" #include "utils/utils.hpp"
#include "platform/exception_type.hpp" #include "platform/exception_type.hpp"
#include "options.hpp"
namespace cpptrace { namespace cpptrace {
std::string demangle(const std::string& name) { std::string demangle(const std::string& name) {
return detail::demangle(name, false); return detail::demangle(name);
} }
std::string get_snippet(const std::string& path, std::size_t line, std::size_t context_size, bool color) { std::string get_snippet(const std::string& path, std::size_t line, std::size_t context_size, bool color) {
@ -27,17 +25,14 @@ namespace cpptrace {
extern const int stdout_fileno = detail::fileno(stdout); extern const int stdout_fileno = detail::fileno(stdout);
extern const int stderr_fileno = detail::fileno(stderr); 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() { CPPTRACE_FORCE_NO_INLINE void print_terminate_trace() {
try { // try/catch can never be hit but it's needed to prevent TCO try { // try/catch can never be hit but it's needed to prevent TCO
detail::get_terminate_formatter().print(std::cerr, generate_trace(1)); generate_trace(1).print(
std::cerr,
isatty(stderr_fileno),
true,
"Stack trace to reach terminate handler (most recent call first):"
);
} catch(...) { } catch(...) {
if(!detail::should_absorb_trace_exceptions()) { if(!detail::should_absorb_trace_exceptions()) {
throw; throw;

View File

@ -4,6 +4,7 @@
#include <cpptrace/basic.hpp> #include <cpptrace/basic.hpp>
#include "platform/platform.hpp" #include "platform/platform.hpp"
#include "options.hpp"
#include <cstdint> #include <cstdint>
@ -30,13 +31,6 @@
#define MSVC_CDECL #define MSVC_CDECL
#endif #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 cpptrace {
namespace detail { namespace detail {
static const stacktrace_frame null_frame { static const stacktrace_frame null_frame {

View File

@ -1,6 +1,7 @@
#ifndef ERROR_HPP #ifndef ERROR_HPP
#define ERROR_HPP #define ERROR_HPP
#include <atomic>
#include <exception> #include <exception>
#include <string> #include <string>
#include <utility> #include <utility>
@ -164,6 +165,8 @@ namespace detail {
// Check condition in both debug. std::runtime_error on failure. // Check condition in both debug. std::runtime_error on failure.
#define ASSERT(...) PHONY_USE(__VA_ARGS__) #define ASSERT(...) PHONY_USE(__VA_ARGS__)
#endif #endif
extern std::atomic_bool absorb_trace_exceptions;
} }
} }

View File

@ -1,108 +0,0 @@
#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

View File

@ -8,7 +8,6 @@
#include "utils/common.hpp" #include "utils/common.hpp"
#include "utils/error.hpp" #include "utils/error.hpp"
#include "utils/optional.hpp" #include "utils/optional.hpp"
#include "options.hpp"
namespace cpptrace { namespace cpptrace {
namespace detail { namespace detail {
@ -28,8 +27,8 @@ namespace detail {
std::fprintf(stderr, "%s\n", unwrap_error().what()); std::fprintf(stderr, "%s\n", unwrap_error().what());
} }
} }
Result(const value_type& value) : value_(value_type(value)), active(member::value) {} Result(value_type& value) : value_(value_type(value)), active(member::value) {}
Result(const E& error) : error_(E(error)), active(member::error) { Result(E& error) : error_(E(error)), active(member::error) {
if(!should_absorb_trace_exceptions()) { if(!should_absorb_trace_exceptions()) {
std::fprintf(stderr, "%s\n", unwrap_error().what()); std::fprintf(stderr, "%s\n", unwrap_error().what());
} }
@ -127,24 +126,6 @@ namespace detail {
return has_value() ? static_cast<T>(std::move(value_)) : static_cast<T>(std::forward<U>(default_value)); 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 { void drop_error() const {
if(is_error()) { if(is_error()) {
std::fprintf(stderr, "%s\n", unwrap_error().what()); std::fprintf(stderr, "%s\n", unwrap_error().what());

View File

@ -7,6 +7,7 @@
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <memory> #include <memory>
#include <new>
#include <string> #include <string>
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
@ -162,9 +163,9 @@ namespace detail {
// shamelessly stolen from stackoverflow // shamelessly stolen from stackoverflow
bool directory_exists(const std::string& path); bool directory_exists(const std::string& path);
inline std::string basename(const std::string& path, bool maybe_windows = false) { inline std::string basename(const std::string& path) {
// Assumes no trailing /'s // Assumes no trailing /'s
auto pos = path.find_last_of(maybe_windows ? "/\\" : "/"); auto pos = path.rfind('/');
if(pos == std::string::npos) { if(pos == std::string::npos) {
return path; return path;
} else { } else {
@ -188,24 +189,22 @@ namespace detail {
return static_cast<U>(v); 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 {}; struct monostate {};
// TODO: Rework some stuff here. Not sure deleters should be optional or moved. // 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"); // Also allow file_wrapper file = std::fopen(object_path.c_str(), "rb");
template< template<
typename T, typename T,
typename D, typename D
// Note: Previously checked if D was invocable and returned void but this kept causing problems for MSVC // workaround for:
// == 19.38-specific msvc bug https://developercommunity.visualstudio.com/t/MSVC-1938331290-preview-fails-to-comp/10505565 // == 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) // <= 19.23 msvc also appears to fail (but for a different reason https://godbolt.org/z/6Y5EvdWPK)
// <= 19.39 msvc also has trouble with it for different reasons https://godbolt.org/z/aPPPT7z3z #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,
typename std::enable_if< typename std::enable_if<
std::is_standard_layout<T>::value && std::is_trivial<T>::value, std::is_standard_layout<T>::value && std::is_trivial<T>::value,
int int
@ -214,6 +213,7 @@ namespace detail {
std::is_nothrow_move_constructible<T>::value, std::is_nothrow_move_constructible<T>::value,
int int
>::type = 0 >::type = 0
#endif
> >
class raii_wrapper { class raii_wrapper {
T obj; T obj;
@ -245,7 +245,22 @@ namespace detail {
} }
}; };
template<typename T, typename D> 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
>
raii_wrapper<typename std::remove_reference<T>::type, D> raii_wrap(T obj, D deleter) { 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); return raii_wrapper<typename std::remove_reference<T>::type, D>(obj, deleter);
} }
@ -258,11 +273,6 @@ namespace detail {
using file_wrapper = raii_wrapper<std::FILE*, void(*)(std::FILE*)>; 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> template<typename T>
class maybe_owned { class maybe_owned {
std::unique_ptr<T> owned; std::unique_ptr<T> owned;
@ -273,40 +283,7 @@ namespace detail {
T* operator->() { T* operator->() {
return ptr; 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));
}
} }
} }

View File

@ -17,12 +17,7 @@ cc_test(
"unit/internals/optional.cpp", "unit/internals/optional.cpp",
"unit/internals/result.cpp", "unit/internals/result.cpp",
"unit/internals/string_utils.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, linkstatic = 1,
) )

View File

@ -84,12 +84,9 @@ if(NOT CPPTRACE_SKIP_UNIT)
unit/tracing/from_current_z.cpp unit/tracing/from_current_z.cpp
unit/tracing/traced_exception.cpp unit/tracing/traced_exception.cpp
unit/internals/optional.cpp unit/internals/optional.cpp
unit/internals/lru_cache.cpp
unit/internals/result.cpp unit/internals/result.cpp
unit/internals/string_utils.cpp unit/internals/string_utils.cpp
unit/internals/general.cpp unit/internals/general.cpp
unit/lib/formatting.cpp
unit/lib/nullable.cpp
) )
target_compile_features(unittest PRIVATE cxx_std_20) target_compile_features(unittest PRIVATE cxx_std_20)
target_link_libraries(unittest PRIVATE ${target_name} GTest::gtest_main GTest::gmock_main) target_link_libraries(unittest PRIVATE ${target_name} GTest::gtest_main GTest::gmock_main)

View File

@ -1,104 +0,0 @@
#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);
}
}

View File

@ -1,318 +0,0 @@
#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"
)
);
}
}

View File

@ -1,66 +0,0 @@
#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);
}
}

View File

@ -1,48 +0,0 @@
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)

View File

@ -1,3 +0,0 @@
if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF)
binary(dwarfdump LIBS ${dwarf_lib})
endif()

View File

@ -1,179 +0,0 @@
#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();
}

View File

@ -1 +0,0 @@
binary(resolver)

View File

@ -1,109 +0,0 @@
#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();
}

View File

@ -1 +0,0 @@
binary(symbol_tables)

View File

@ -1,83 +0,0 @@
#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();
}