mirror of https://github.com/mongodb/mongo
SERVER-94540 Vendor cpptrace (#40807)
GitOrigin-RevId: c6e998d87ec2f7adfb02996a659b0e72d079709e
This commit is contained in:
parent
7af4081495
commit
6c3bf5e2a3
|
|
@ -3080,6 +3080,7 @@ WORKSPACE.bazel @10gen/devprod-build @svc-auto-approve-bot
|
|||
/src/third_party/**/benchmark @10gen/server-programmability @svc-auto-approve-bot
|
||||
/src/third_party/**/boost @10gen/server-programmability @svc-auto-approve-bot
|
||||
/src/third_party/**/cares @10gen/server-networking-and-observability @svc-auto-approve-bot
|
||||
/src/third_party/**/cpptrace @10gen/server-programmability @svc-auto-approve-bot
|
||||
/src/third_party/**/croaring @10gen/query-execution @svc-auto-approve-bot
|
||||
/src/third_party/**/fmt @10gen/server-programmability @svc-auto-approve-bot
|
||||
/src/third_party/**/folly @10gen/server-workload-resilience @svc-auto-approve-bot
|
||||
|
|
|
|||
43
sbom.json
43
sbom.json
|
|
@ -2894,6 +2894,49 @@
|
|||
]
|
||||
},
|
||||
"scope": "excluded"
|
||||
},
|
||||
{
|
||||
"name": "cpptrace",
|
||||
"supplier": {
|
||||
"name": "Organization: github"
|
||||
},
|
||||
"version": "v1.0.3",
|
||||
"licenses": [
|
||||
{
|
||||
"license": {
|
||||
"id": "MIT"
|
||||
}
|
||||
}
|
||||
],
|
||||
"purl": "pkg:github/jeremy-rifkin/cpptrace@v1.0.3",
|
||||
"properties": [
|
||||
{
|
||||
"name": "internal:team_responsible",
|
||||
"value": "Server Programmability"
|
||||
},
|
||||
{
|
||||
"name": "emits_persisted_data",
|
||||
"value": "false"
|
||||
},
|
||||
{
|
||||
"name": "info_link",
|
||||
"value": "https://github.com/jeremy-rifkin/cpptrace"
|
||||
},
|
||||
{
|
||||
"name": "import_script_path",
|
||||
"value": "src/third_party/cpptrace/scripts/import.sh"
|
||||
}
|
||||
],
|
||||
"type": "library",
|
||||
"bom-ref": "ae9c7977-70ac-4706-9231-a7d10f05542b",
|
||||
"evidence": {
|
||||
"occurrences": [
|
||||
{
|
||||
"location": "src/third_party/cpptrace"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scope": "excluded"
|
||||
}
|
||||
],
|
||||
"dependencies": [
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ filters:
|
|||
- "cares":
|
||||
approvers:
|
||||
- 10gen/server-networking-and-observability
|
||||
- "cpptrace":
|
||||
approvers:
|
||||
- 10gen/server-programmability
|
||||
- "croaring":
|
||||
approvers:
|
||||
- 10gen/query-execution
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
load("//bazel:mongo_src_rules.bzl", "mongo_cc_library")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
mongo_cc_library(
|
||||
name = "cpptrace",
|
||||
srcs = glob([
|
||||
"dist/src/**/*.hpp",
|
||||
"dist/src/**/*.cpp",
|
||||
]),
|
||||
hdrs = glob([
|
||||
"dist/include/cpptrace/*.hpp",
|
||||
"dist/include/ctrace/*.h",
|
||||
]),
|
||||
includes = [
|
||||
"dist/include",
|
||||
"dist/src",
|
||||
],
|
||||
linkopts = [
|
||||
"-ldl",
|
||||
],
|
||||
local_defines = [
|
||||
"CPPTRACE_UNWIND_WITH_LIBUNWIND",
|
||||
"CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF",
|
||||
"CPPTRACE_DEMANGLE_WITH_CXXABI",
|
||||
],
|
||||
target_compatible_with = ["@platforms//os:linux"] + select({
|
||||
"//bazel/config:release_enabled": ["@platforms//:incompatible"],
|
||||
"//bazel/config:libunwind_disabled": ["@platforms//:incompatible"],
|
||||
"//conditions:default": [],
|
||||
}),
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//src/third_party/libdwarf",
|
||||
"//src/third_party/zlib",
|
||||
"//src/third_party/zstandard:zstd",
|
||||
],
|
||||
)
|
||||
|
|
@ -0,0 +1,604 @@
|
|||
# Changelog
|
||||
|
||||
- [Changelog](#changelog)
|
||||
- [v1.0.3](#v103)
|
||||
- [v1.0.2](#v102)
|
||||
- [v1.0.1](#v101)
|
||||
- [v1.0.0](#v100)
|
||||
- [v0.8.3](#v083)
|
||||
- [v0.8.2](#v082)
|
||||
- [v0.8.1](#v081)
|
||||
- [v0.8.0](#v080)
|
||||
- [v0.7.5](#v075)
|
||||
- [v0.7.4](#v074)
|
||||
- [v0.7.3](#v073)
|
||||
- [v0.7.2](#v072)
|
||||
- [v0.7.1](#v071)
|
||||
- [v0.7.0](#v070)
|
||||
- [v0.6.3](#v063)
|
||||
- [v0.6.2](#v062)
|
||||
- [v0.6.1](#v061)
|
||||
- [v0.6.0](#v060)
|
||||
- [v0.5.4](#v054)
|
||||
- [v0.5.3](#v053)
|
||||
- [v0.5.2](#v052)
|
||||
- [v0.5.1](#v051)
|
||||
- [v0.5.0](#v050)
|
||||
- [v0.4.1](#v041)
|
||||
- [v0.4.0](#v040)
|
||||
- [v0.3.1](#v031)
|
||||
- [v0.3.0](#v030)
|
||||
- [v0.2.1](#v021)
|
||||
- [v0.2.0](#v020)
|
||||
- [v0.1.1](#v011)
|
||||
- [v0.1](#v01)
|
||||
|
||||
# v1.0.3
|
||||
|
||||
Added:
|
||||
- Added line indicator to make source code snippets in case colors aren't being used
|
||||
- Added column indicator to source code snippets
|
||||
- Added 32-bit ARM support for StackWalk64 https://github.com/jeremy-rifkin/cpptrace/pull/271 (@Xottab-DUTY)
|
||||
|
||||
# v1.0.2
|
||||
|
||||
Added:
|
||||
- Added `break_before_filename` formatting option https://github.com/jeremy-rifkin/cpptrace/issues/259 (@codeinred)
|
||||
|
||||
Fixes:
|
||||
- Fixed 32-bit clang-cl build
|
||||
- Fixed build on gcc 4.8.5
|
||||
- Fixed StackWalk64 for 64-bit arm https://github.com/jeremy-rifkin/cpptrace/pull/270 (@mcourteaux)
|
||||
- Fixed compatibility issue with cmake versions before 3.23
|
||||
|
||||
Other:
|
||||
- Added a couple notes to the README
|
||||
|
||||
# v1.0.1
|
||||
|
||||
Added:
|
||||
- Added from-current-exception utility for SEH on windows (`CPPTRACE_SEH_TRY`/`CPPTRACE_SEH_EXCEPT`)
|
||||
|
||||
Fixes:
|
||||
- Fixed a static assert without a message causing issues on some C++11 builds
|
||||
- Fixed build 32-bit build on linux
|
||||
- Fixed from-current-exception system for 32-bit windows where SEH behaves much differently
|
||||
- Fixed clang-cl build
|
||||
|
||||
# v1.0.0
|
||||
|
||||
Major changes:
|
||||
- Overhauled how the from-current-exception system and `CPPTRACE_TRY`/`CPPTRACE_CATCH` macros work. They now check the
|
||||
thrown exception type against the type the catch accepts to decide whether or not to collect a trace. This eliminates
|
||||
the need for The `TRYZ`/`CATCHZ` variants of the macros as now the normal macro is equally zero-overhead. As such, the
|
||||
`Z` variants have been removed.
|
||||
|
||||
Breaking changes:
|
||||
- `CPPTRACE_TRYZ` and `CPPTRACE_CATCHZ` have been removed, change uses to `CPPTRACE_TRY`/`CPPTRACE_CATCH`
|
||||
- `CPPTRACE_TRY`/`CPPTRACE_CATCH` macros no longer support multiple handlers and `CPPTRACE_CATCH_ALT` has been removed.
|
||||
Instead, use `cpptrace::try_catch`.
|
||||
> **Details**
|
||||
>
|
||||
> Author's note: I apologize for this non-trivial breaking change, however, it allows for a much improved
|
||||
> implementation of the from-current-exception system. The impact should be [very minimal](https://github.com/search?type=code&q=%2F%5CbCPPTRACE_CATCH_ALT%5Cb%2F+language%3Ac%2B%2B+-is%3Afork+-repo%3Ajeremy-rifkin%2Fcpptrace+-path%3Afrom_current.hpp)
|
||||
> for most codebases.
|
||||
>
|
||||
> Transitioning to `cpptrace::try_catch` is straightforward:
|
||||
> <table>
|
||||
> <thead>
|
||||
> <tr>
|
||||
> <td>
|
||||
> Before
|
||||
> </td>
|
||||
> <td>
|
||||
> Now
|
||||
> </td>
|
||||
> </tr>
|
||||
> </thead>
|
||||
> <tbody>
|
||||
> <tr>
|
||||
> <td>
|
||||
|
||||
> ```cpp
|
||||
> CPPTRACE_TRY {
|
||||
> foo();
|
||||
> } CPPTRACE_CATCH(const std::logic_error& e) {
|
||||
> handle_logic_error(e);
|
||||
> } CPPTRACE_CATCH_ALT(const std::exception& e) {
|
||||
> handle_exception(e);
|
||||
> } CPPTRACE_CATCH_ALT(...) {
|
||||
> handle_unknown_exception();
|
||||
> }
|
||||
> ```
|
||||
|
||||
> </td>
|
||||
> <td>
|
||||
|
||||
> ```cpp
|
||||
> cpptrace::try_catch(
|
||||
> [&] { // try block
|
||||
> foo();
|
||||
> },
|
||||
> [&] (const std::runtime_error& e) {
|
||||
> handle_logic_error(e);
|
||||
> },
|
||||
> [&] (const std::exception& e) {
|
||||
> handle_exception(e);
|
||||
> },
|
||||
> [&] () { // `catch(...)`
|
||||
> handle_unknown_exception();
|
||||
> }
|
||||
> );
|
||||
> ```
|
||||
|
||||
> </td>
|
||||
> </tr>
|
||||
> </tbody>
|
||||
> </table>
|
||||
>
|
||||
> Please note as well that code such as the following, which was valid before, will still compile now but may report a
|
||||
> misleading trace. The second catch handler will work but it cpptrace won't know about the handler's type and won't
|
||||
> collect a trace that doesn't match the first handler's type, `std::logic_error`.
|
||||
>
|
||||
> ```cpp
|
||||
> CPPTRACE_TRY {
|
||||
> foo();
|
||||
> } CPPTRACE_CATCH(const std::logic_error& e) {
|
||||
> ...
|
||||
> } catch(const std::exception& e) {
|
||||
> ...
|
||||
> }
|
||||
> ```
|
||||
|
||||
Potentially-breaking changes:
|
||||
- This version of cpptrace reworks the public interface to use an inline ABI versioning namespace. All symbols in the
|
||||
public interface are now secretly in the `cpptrace::v1` namespace. This is an ABI break, but any ABI mismatch will
|
||||
result in linker errors instead of silent bugs. This change is an effort to allow future evolution of cpptrace in a
|
||||
way that respects ABI.
|
||||
- This version fixes a problem with returns from `CPPTRACE_TRY` blocks on windows. Unfortunately, this macro has to use
|
||||
an IILE on windows and as such `return` statements won't properly return from the enclosing function. This was a
|
||||
footgun and now `return` statements in `CPPTRACE_TRY` blocks are prevented from compiling on windows.
|
||||
|
||||
Added
|
||||
- Added `cpptrace::try_catch` for handling multiple alternatives with access to current exception traces
|
||||
- Added `cpptrace::rethrow` utility for rethrowing exceptions from `CPPTRACE_CATCH` while preserving the stacktrace https://github.com/jeremy-rifkin/cpptrace/issues/214
|
||||
- Added utilities for getting the current trace from the last rethrow point
|
||||
(`cpptrace::raw_trace_from_current_exception_rethrow`, `cpptrace::from_current_exception_rethrow`,
|
||||
`cpptrace::current_exception_was_rethrown`)
|
||||
- Added a logger system to allow cpptrace to report errors in a configurable manner. By default, cpptrace doesn't log
|
||||
anything. The following functions can be used to change this: (`cpptrace::set_log_level`,
|
||||
`cpptrace::set_log_callback`, and `cpptrace::use_default_stderr_logger`)
|
||||
- Added `cpptrace::basename` utility
|
||||
- Added `cpptrace::prettify_type` utility
|
||||
- Added `cpptrace::prune_symbol` utility
|
||||
- Added formatter option for symbol formatting (`cpptrace::formatter::symbols`)
|
||||
- Added `cpptrace::detail::lazy_trace_holder::is_resolved`
|
||||
- Added support for C++20 modules https://github.com/jeremy-rifkin/cpptrace/pull/248
|
||||
- Added `cpptrace::load_symbols_for_file` to support DLLs loaded at runtime when using dbghelp https://github.com/jeremy-rifkin/cpptrace/pull/247
|
||||
|
||||
Removed
|
||||
- Removed `CPPTRACE_TRYZ` and `CPPTRACE_CATCHZ` macros
|
||||
- Removed the `CPPTRACE_CATCH_ALT` macro
|
||||
|
||||
Fixed
|
||||
- Fixed a problem where `CPPTRACE_TRY` blocks could contain `return` statements but not return on windows due to an IILE. This is now an error. https://github.com/jeremy-rifkin/cpptrace/issues/245
|
||||
- Fixed cases where cpptrace could print to stderr on internal errors without the user desiring so https://github.com/jeremy-rifkin/cpptrace/issues/234
|
||||
- Fixed a couple internal locking mistakes
|
||||
- Fixed a couple of code paths that could be susceptible to static init order issues
|
||||
- Fixed bug with loading elf symbol tables that contain zero-sized entries
|
||||
- Fixed an incorrect assertion regarding looking up symbols at program counters that reside before any seen subprogram DIE https://github.com/jeremy-rifkin/cpptrace/issues/250
|
||||
- Fixed issue with `cpptrace::stacktrace::to_string()` ending with a newline on empty traces
|
||||
|
||||
Other
|
||||
- Marked some paths in `CPPTRACE_CATCH` and `CPPTRACE_CATCHZ` as unreachable to improve usability in cases where the
|
||||
compiler may warn about missing returns.
|
||||
- Improved resilience to libdwarf errors https://github.com/jeremy-rifkin/cpptrace/pull/251
|
||||
- Removed internal use of `std::is_trivial` which is deprecated in C++26 https://github.com/jeremy-rifkin/cpptrace/issues/236
|
||||
- Bumped libdwarf to 2.0.0
|
||||
- Added `--address` flag for internal symbol table tool
|
||||
- Various internal work to improve the codebase and reduce complexity
|
||||
|
||||
# v0.8.3
|
||||
|
||||
Added:
|
||||
- Added basic JIT support https://github.com/jeremy-rifkin/cpptrace/issues/226
|
||||
- Added `cpptrace::formatter::transform` https://github.com/jeremy-rifkin/cpptrace/issues/227
|
||||
- Added support for gcc 4.8.5 https://github.com/jeremy-rifkin/cpptrace/issues/220
|
||||
|
||||
Fixed:
|
||||
- Fixed bug related to calling `dwarf_dealloc` on strings from `dwarf_formstring` and `dwarf_diename` https://github.com/davea42/libdwarf-code/issues/279
|
||||
- Fixed incorrect cmake version variable https://github.com/jeremy-rifkin/cpptrace/issues/231
|
||||
- Fixed `address_mode::none` not working https://github.com/jeremy-rifkin/cpptrace/issues/221
|
||||
- Fixed use of `-Wall` for clang-cl
|
||||
|
||||
Other:
|
||||
- Added ARM CI
|
||||
- Miscellaneous work on supporting old compilers
|
||||
- Updated cpptrace cmake target configuration to not add public compile definitions
|
||||
- Internal refactoring, cleanup, and code improvements
|
||||
|
||||
# v0.8.2
|
||||
|
||||
Fixed:
|
||||
- Fixed printing of internal error messages when an object file can't be loaded, mainly affecting MacOS https://github.com/jeremy-rifkin/cpptrace/issues/217
|
||||
|
||||
Other:
|
||||
- Bumped zstd via FetchContent to 1.5.7
|
||||
|
||||
# v0.8.1
|
||||
|
||||
Fixed:
|
||||
- Fixed compile error on msvc https://github.com/jeremy-rifkin/cpptrace/issues/215
|
||||
|
||||
Added:
|
||||
- Added `cpptrace::can_get_safe_object_frame()`
|
||||
|
||||
Breaking changes:
|
||||
- Renamed ctrace's `can_signal_safe_unwind` to `ctrace_can_signal_safe_unwind`. This was an oversight. Apologies for
|
||||
including a breaking change in a patch release. Github code search suggests this API isn't used in public code, at
|
||||
least.
|
||||
|
||||
Other:
|
||||
- Added CI workflow to test on old msvc
|
||||
- Made some internal improvements on robustness and cleanliness
|
||||
|
||||
# v0.8.0
|
||||
|
||||
Added:
|
||||
- Added support for resolving symbols from elf and mach-o symbol tables, allowing function names to be resolved even in
|
||||
a build that doesn't include debug information https://github.com/jeremy-rifkin/cpptrace/issues/201
|
||||
- Added a configurable stack trace formatter https://github.com/jeremy-rifkin/cpptrace/issues/164
|
||||
- Added configuration options for the libdwarf back-end that can be used to lower memory usage on memory-constrained
|
||||
systems https://github.com/jeremy-rifkin/cpptrace/issues/193
|
||||
- Added `cpptrace::nullable<T>::null_value`
|
||||
- Made `cpptrace::nullable<T>` member functions conditionally `constexpr` where possible
|
||||
|
||||
Fixed:
|
||||
- Fixed handling of `SymInitialize` when other code has already called `SymInitialize`. `SymInitialize` must only be
|
||||
called once per handle and cpptrace now attempts to duplicate the current process handle to avoid conflicts.
|
||||
https://github.com/jeremy-rifkin/cpptrace/issues/204
|
||||
- Fixed a couple of locking edge cases surrounding dbghelp functions
|
||||
- Fixed improper deallocation of `dwarf_errmsg` in the libdwarf back-end
|
||||
|
||||
Breaking changes:
|
||||
- `cpptrace::get_snippet` previously included a newline at the end but it now does not. This also affects the behavior
|
||||
of trace formatting with snippets enabled.
|
||||
|
||||
Other:
|
||||
- Significantly improved memory usage and performance of the libdwarf back-end
|
||||
- Improved implementation and organization of internal utility types, such as `optional` and `Result`
|
||||
- Improved trace printing and formatting implementation
|
||||
- Added unit tests for library internal utilities
|
||||
- Added logic to the cxxabi demangler to ensure external names begin with `_Z` or `__Z` before attempting to demangle
|
||||
- Added various internal tools and abstractions to improve maintainability and clarity
|
||||
- Various internal improvements for robustness
|
||||
- Added a small handful of utility tool programs that are useful for continued development, maintenance, and debugging
|
||||
- Improved library CI setup
|
||||
- Marked the `CPPTRACE_BUILD_BENCHMARK` option as advanced
|
||||
|
||||
# v0.7.5
|
||||
|
||||
Fixed:
|
||||
- Fixed missing `<typeinfo>` include https://github.com/jeremy-rifkin/cpptrace/pull/202
|
||||
- Added `__cdecl` to a terminate handler to appease MSVC under some configurations https://github.com/jeremy-rifkin/cpptrace/issues/197
|
||||
- Set C++ standard for cmake support checks https://github.com/jeremy-rifkin/cpptrace/issues/200
|
||||
- Changed hyphens to underscores for cmake component names due to cpack issue https://github.com/jeremy-rifkin/cpptrace/issues/203
|
||||
|
||||
# v0.7.4
|
||||
|
||||
Added:
|
||||
- Added `<cpptrace/version.hpp>` header with version macros
|
||||
|
||||
Fixes:
|
||||
- Bumped libdwarf to 0.11.0 which fixes a number of dwarf 5 debug fission issues
|
||||
|
||||
Other:
|
||||
- Various improvements to internal testing setup
|
||||
|
||||
# v0.7.3
|
||||
|
||||
Fixed:
|
||||
- Fixed missing include affecting macos https://github.com/jeremy-rifkin/cpptrace/pull/183
|
||||
- Fixed issue with cmake not using the ccache program found by `find_program` https://github.com/jeremy-rifkin/cpptrace/pull/184
|
||||
- Fixed missing include and warnings affecting mingw https://github.com/jeremy-rifkin/cpptrace/pull/186
|
||||
- Fixed issue with identifying inlined call frames when the `DW_TAG_inlined_subroutine` is under a `DW_TAG_lexical_block`
|
||||
- Fixed a typo in the README
|
||||
- Improved unittest support on various configurations
|
||||
- Improved unittest robustness under LTO
|
||||
- Fixed bug signal_demo in the event `fork()` fails
|
||||
|
||||
Added:
|
||||
- Added color overload for `stacktrace_frame::to_string`
|
||||
- Added CMake `export()` definition for cpptrace as well as a definition for libdwarf which currently doesn't provide one
|
||||
|
||||
Changed:
|
||||
- Updated documentation surrounding the signal safe API
|
||||
|
||||
# v0.7.2
|
||||
|
||||
Changes:
|
||||
- Better support for older CMake with using `FetchContent_Declare` from a URL https://github.com/jeremy-rifkin/cpptrace/pull/176
|
||||
- Better portability for page size detection https://github.com/jeremy-rifkin/cpptrace/pull/177
|
||||
- Improved compile times https://github.com/jeremy-rifkin/cpptrace/pull/172
|
||||
- Split up `cpptrace.hpp` into finer-grained headers for lower compile time impact
|
||||
- Some minor readme restructuring
|
||||
|
||||
# v0.7.1
|
||||
|
||||
Added
|
||||
- Better support for finding libunwind on macos https://github.com/jeremy-rifkin/cpptrace/pull/162
|
||||
- Support for libbacktrace under mingw https://github.com/jeremy-rifkin/cpptrace/pull/166
|
||||
|
||||
Fixed
|
||||
- Computation of object address for safe object frames https://github.com/jeremy-rifkin/cpptrace/issues/169
|
||||
- Nested microfmt in cpptrace's namespace due to an ODR problem with libassert https://github.com/jeremy-rifkin/libassert/issues/103
|
||||
- Compilation on iOS https://github.com/jeremy-rifkin/cpptrace/pull/167
|
||||
- Compilation on old MSVC https://github.com/jeremy-rifkin/cpptrace/pull/165
|
||||
- Dbghelp use on 32 bit https://github.com/jeremy-rifkin/cpptrace/issues/170
|
||||
- Warning in brand new cmake due to `FetchContent_Populate` being deprecated https://github.com/jeremy-rifkin/cpptrace/issues/171
|
||||
|
||||
Other changes
|
||||
- Bumped the buffer size for execinfo and CaptureStackBackTrace to 400 frames
|
||||
- Switched to execinfo.h for unwinding on clang/apple clang on macos due to `_Unwind` not working with `-fno-exceptions` https://github.com/jeremy-rifkin/cpptrace/issues/161
|
||||
|
||||
# v0.7.0
|
||||
|
||||
Added
|
||||
- Added `cpptrace::from_current_exception()` and associated exception handler macros to allow tracing of all exceptions,
|
||||
even without cpptrace traced exception objects.
|
||||
|
||||
Fixes:
|
||||
- Fixed issue with using `resolve_safe_object_frame` on `safe_object_frame`s with empty paths
|
||||
- Fixed handling of dwarf 4 rangelist base addresses when a `DW_AT_low_pc` is not present
|
||||
- Fixed use of `-g` with MSVC
|
||||
|
||||
Other changes:
|
||||
- Bazel is now supported on linux (https://github.com/jeremy-rifkin/cpptrace/pull/153)
|
||||
- More work on testing
|
||||
- Some internal refactoring
|
||||
|
||||
# v0.6.3
|
||||
|
||||
Added:
|
||||
- Added a flag to disable inclusion of `<format>` by cpptrace.hpp and the definition of formatter specializations
|
||||
|
||||
Fixes:
|
||||
- Fixed use after free during cleanup of split dwarf information https://github.com/jeremy-rifkin/cpptrace/issues/141
|
||||
- Fixed an issue with TCO by clang on arm interfering with unwinding skip counts for internal methods
|
||||
- Fixed issue with incorrect object addresses being reported on macos when debug maps are used
|
||||
- Fixed issue with handling of split dwarf emitted by clang under dwarf4 mode
|
||||
|
||||
Other changes:
|
||||
- Added note about signal-safe tracing requiring `_dl_find_object` to documentation and fixed errors in the signal-safe
|
||||
tracing docs
|
||||
- Added more configurations to unittest ci setup
|
||||
- Optimized unittest ci matrix setup
|
||||
- Added options for zstd and libdwarf sources if FetchContent is being used to bring the dependencies in
|
||||
- Optimized includes in cpptrace.hpp
|
||||
|
||||
# v0.6.2
|
||||
|
||||
Fixes:
|
||||
- Fix an issue with unwinding to collect stack traces during exception creation on arm https://github.com/jeremy-rifkin/cpptrace/issues/134
|
||||
- Fix issue where `dladdr1` wasn't being used even when detected
|
||||
|
||||
Robustness:
|
||||
- Setup more robust unit tests and added them to CI
|
||||
|
||||
# v0.6.1
|
||||
|
||||
Fixes:
|
||||
- Fix for detection of `dladdr1` and `_dl_find_object` support
|
||||
|
||||
# v0.6.0
|
||||
|
||||
New:
|
||||
- Added a `cpptrace::system_error` utility
|
||||
- Added support for musl https://github.com/jeremy-rifkin/cpptrace/issues/128
|
||||
- Added support for split dwarf / debug fission
|
||||
|
||||
Fixes:
|
||||
- Fixed address formatting in stack traces
|
||||
- Fixed frame pointer calculation for signal frames from libunwind https://github.com/jeremy-rifkin/cpptrace/issues/123
|
||||
- Fixed dwarf_ranges handling of lowpc == pc causing erroneous symbol resolution
|
||||
- Fixed implementation of the exception helper system/reference implementation's `lazy_trace_holder`
|
||||
|
||||
# v0.5.4
|
||||
|
||||
Fixes:
|
||||
- Fixed bug with resolving object information when `dladdr` is used and an unexpected `argv[0]` is provided to the
|
||||
binary.
|
||||
|
||||
# v0.5.3
|
||||
|
||||
Fixes:
|
||||
- Fixed bug with formatting of hex values on MSVC
|
||||
- Fixed error handling for libbacktrace back-end when debug info is not present
|
||||
- Fixed bug with cmake resolution of zstd when no zstd cmake config file is installed
|
||||
|
||||
Other changes:
|
||||
- Added error handling for an edge case in the signal tracing demo
|
||||
- Updated conan recipe to allow libunwind to be chosen
|
||||
- Improved msvc support in internal formatting system
|
||||
- Bumped libdwarf to 0.9.2
|
||||
|
||||
# v0.5.2
|
||||
|
||||
Fixes:
|
||||
- Fixed bug with resolution of inlined calls
|
||||
|
||||
Other changes:
|
||||
- Improved internal string formatting
|
||||
- Improved internal error handling
|
||||
|
||||
# v0.5.1
|
||||
|
||||
Fixes:
|
||||
- Fix MSVC warning treated as error for 32-bit windows
|
||||
- Fix MSVC issue with min/max macros
|
||||
- Fix potential null dereference issue identified by eyalgolan1337
|
||||
|
||||
# v0.5.0
|
||||
|
||||
New:
|
||||
- Traces with source code snippets with `cpptrace::stacktrace::print_with_snippets`
|
||||
- Added `cpptrace::get_snippet` utility
|
||||
- Added `cpptrace::can_signal_safe_unwind` utility
|
||||
- Added `stacktrace_frame::get_object_info`
|
||||
|
||||
Changes:
|
||||
- The library is now compiled with position-independent code by default
|
||||
|
||||
Fixes:
|
||||
- Fixed issue with `_dl_find_object` implementation
|
||||
|
||||
Misc:
|
||||
- Various refactoring, cleanup, and improvements
|
||||
|
||||
# v0.4.1
|
||||
|
||||
Changes:
|
||||
- Renamed `stacktrace_frame.address` -> `stacktrace_frame.raw_address`
|
||||
- Added `stacktrace_frame.object_address`
|
||||
- Fixed segfault due to an edge case with dwarf file table indices
|
||||
- For the libdwarf back-end: At least show object frame information if resolution fails
|
||||
- Extremely small performance improvements
|
||||
- Small documentation updates
|
||||
- Small fix for conan
|
||||
- Updated cmake to not FetchContent zstd when using CPPTRACE_USE_EXTERNAL_LIBDWARF
|
||||
- CI improvements
|
||||
- Test the default configuration first before doing the exhaustive and slow matrix of all configurations.
|
||||
- Cleanup of duplicated prerequisite installation code
|
||||
- Cleanup of built and test python scripts
|
||||
|
||||
# v0.4.0
|
||||
|
||||
What's new:
|
||||
- Cpptrace now has a C API! 🎉
|
||||
- Cpptrace is now able to parse macOS debug maps and resolve stack traces without dSYM files
|
||||
|
||||
Most notable improvements:
|
||||
- Updated cpptrace exception objects to generate traces at the callsite for improved consistency with trace output. As
|
||||
part of this cpptrace exception objects have had their constructors updated.
|
||||
- Improved dwarf back-end robustness
|
||||
- Fallback to the compilation-unit cache or walking compilation-units if aranges lookup fails
|
||||
- Eliminated reliance on a CMake-generated export header
|
||||
- Added a configuration to control resolution of inlined function calls
|
||||
- Made architecture selection in Mach-O universal binaries
|
||||
|
||||
Other improvements:
|
||||
- Improved documentation for installation and usage
|
||||
- Generally improved README content and organization
|
||||
- Fixed an MSVC workaround producing dozens of warnings
|
||||
- Better handle compiler color diagnostic arguments if compiler families differ
|
||||
- Improvements for handling libdwarf's header placement
|
||||
- Fixed issue with libunwind resolution
|
||||
- `-Werror` is now used in CI
|
||||
- More library configurations are now tested in CI
|
||||
- Updated to libdwarf 9
|
||||
- Updated the library's CMake to acquire zstd through FetchContent
|
||||
- Fixed minor issue with stacktrace printing always trying to enable virtual terminal processing, even when not actually
|
||||
printing to the terminal.
|
||||
|
||||
# v0.3.1
|
||||
|
||||
Tiny patch:
|
||||
- Fix `CPPTRACE_EXPORT` annotations
|
||||
- Add workaround for [msvc bug][msvc bug] affecting msvc 19.38.
|
||||
|
||||
[msvc bug]: https://developercommunity.visualstudio.com/t/MSVC-1938331290-preview-fails-to-comp/10505565
|
||||
|
||||
# v0.3.0
|
||||
|
||||
Interface Changes:
|
||||
- Overhauled the API for traced `cpptrace::exception` objects
|
||||
- Added `cpptrace::isatty` utility
|
||||
- Added specialized `std::terminate` handler and `cpptrace::register_terminate_handler` utility
|
||||
- Added `cpptrace::frame_ptr` as an alias for the appropriate type capable of representing an instruction pointer
|
||||
- Added signal-safe tracing support and a guide for [how to trace safely](signal-safe-tracing.md)
|
||||
- Added `cpptrace::nullable<T>` utility for better indicating when line / column information is not present
|
||||
- Added `CPPTRACE_FORCE_NO_INLINE` utility macro to cpptrace.hpp
|
||||
- Added `CPPTRACE_WRAP` and `CPPTRACE_WRAP_BLOCK` utilities to catch non-`cpptrace::exception`s and rethrow wrapped in a
|
||||
traced exception.
|
||||
- Updated `cpptrace::stacktrace::to_string` to take a `bool color` parameter
|
||||
- Eliminated uses of `std::uint_least32_t` in favor of other types
|
||||
- Updated `object_frame` data member names
|
||||
|
||||
Other changes:
|
||||
- Added object resolution with `_dl_find_object` which is much faster than `dladdr`
|
||||
- Added column support for dwarf
|
||||
- Added inlined call resolution
|
||||
- Added `cpptrace::stacktrace_frame::is_inline`
|
||||
- Added libunwind as a back-end
|
||||
- Unbundled libdwarf
|
||||
- Increased hard max frame count, used by some back-end requiring fixed buffers, from 100 to 200
|
||||
- Improved libgcc unwind backend
|
||||
- Improved trace output when information is missing
|
||||
- Added a lookup table for faster dwarf line information lookup
|
||||
|
||||
News:
|
||||
- The library is now on conan and vcpkg
|
||||
|
||||
Minor changes:
|
||||
- Assorted bug fixes
|
||||
- Various code quality improvements
|
||||
- CI improvements
|
||||
- Documentation improvements
|
||||
- CMake improvements
|
||||
- Internal refactoring
|
||||
|
||||
# v0.2.1
|
||||
|
||||
Patches:
|
||||
- Fixed uintptr_t implicit conversion issue for msvc
|
||||
- Better handling for PIC and static linkage in CMake
|
||||
- Added gcc 5 support
|
||||
- Various warning fixes
|
||||
- Added stackwalk64 support for 32-bit x86 mingw/clang and architecture detection
|
||||
- Added check for stackwalk64 support and CaptureStackBacktrace as a fallback
|
||||
- Various cmake cleanup and changes to use cpptrace through package managers
|
||||
- Added sonarlint and implemented some sonarlint fixes
|
||||
|
||||
# v0.2.0
|
||||
|
||||
Key changes:
|
||||
- Added libdwarf as a back-end so cpptrace doesn't have to rely on addr2line or libbacktrace
|
||||
- Overhauled library's public-facing interface to make the library more useful
|
||||
- Added `raw_trace` interface
|
||||
- Added `object_trace` interface
|
||||
- Added `stacktrace` interface
|
||||
- Updated `generate_trace` to return a `stacktrace` rather than a vector of frames
|
||||
- Added `generate_trace` counterparts for raw and object traces
|
||||
- Added `generate_trace` overloads with max_depth
|
||||
- Added interface for internal demangling utility
|
||||
- Added cache mode configuration
|
||||
- Added option to absorb internal trace exceptions (by default it absorbs)
|
||||
- Added `cpptrace::exception`, which automatically generates and stores a stacktrace when thrown
|
||||
- Added `exception_with_message`
|
||||
- Added traced analogs for stdexcept errors: `logic_error`, `domain_error`, `invalid_argument`, `length_error`,
|
||||
`out_of_range`, `runtime_error`, `range_error`, `overflow_error`, and `underflow_error`.
|
||||
|
||||
Other changes:
|
||||
- Bundled libdwarf with cpptrace so the library can essentially be self-contained and not have to rely on libraries that
|
||||
might not already be on a system
|
||||
- Added StackWalk64 as an unwinding back-end on windows
|
||||
- Added system for multiple symbol back-ends to be used, mainly for more complete stack traces on mingw
|
||||
- Fixed sporadic line number reporting errors due to not adjusting the program counter from the unwinder
|
||||
- Improved addr2line/atos invocation back-end on macos
|
||||
- Lots of error handling improvements
|
||||
- Performance improvements
|
||||
- Updated default back-ends for most systems
|
||||
- Removed full tracing backends
|
||||
- Cleaned up library cmake
|
||||
- Lots of internal cleanup and refactoring
|
||||
- Improved library usage instructions in README
|
||||
|
||||
# v0.1.1
|
||||
|
||||
Fixed:
|
||||
- Handle errors when object files don't exist or can't be opened for reading
|
||||
- Handle paths with spaces when using addr2line on windows
|
||||
|
||||
# v0.1
|
||||
|
||||
Initial release of the library 🎉
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
# Contributing
|
||||
|
||||
Welcome, thank you for your interest in the project!
|
||||
|
||||
## Getting started
|
||||
|
||||
Contributions are always welcome. If you have not already, consider joining the community discord
|
||||
(linked in the README). There is discussion about library development there as well as a development
|
||||
roadmap. Github issues are also a good place to start.
|
||||
|
||||
I'm happy to merge fixes, improvements, and features as well as help with getting pull requests
|
||||
(PRs) over the finish line. That being said, I can't merge stylistic changes,
|
||||
premature-optimizations, or micro-optimizations.
|
||||
|
||||
When contributing, please try to match the current code style in the codebase. Style doesn't matter
|
||||
too much ultimately but consistency within a codebase is important.
|
||||
|
||||
Please base changes against the `dev` branch, which is used for development.
|
||||
|
||||
## Code organization
|
||||
|
||||
The library's public interface is defined in headers in `include/`. Declarations for the public interface have
|
||||
definitions in .cpp files in the top-level of `src/`. Implementation for various actions such as unwinding, demangling,
|
||||
symbol resolution, etc. are put in various sub-directories of `src/`.
|
||||
|
||||
## Local development
|
||||
|
||||
The easiest way to get started with local development is running `make debug` (which automatically configures cmake and
|
||||
builds). Note: This requires ninja at the moment.
|
||||
|
||||
For more control over how the library is built you can manually build with cmake:
|
||||
|
||||
`cmake ..` in a `build/` folder along with any cmake configurations you desire. Then run `make -j` or `ninja` or
|
||||
`msbuild cpptrace.sln`.
|
||||
|
||||
Some useful configurations:
|
||||
- `-DCMAKE_BUILD_TYPE=Debug|Release|RelWithDebInfo`: Build in debug / release / etc.
|
||||
- `-DBUILD_SHARED_LIBS=On`: Build shared library
|
||||
- `-DCPPTRACE_SANITIZER_BUILD=On`: Turn on sanitizers
|
||||
- `-DCPPTRACE_BUILD_TESTING=On`: Build small test and demo program
|
||||
|
||||
## Testing
|
||||
|
||||
Unfortunately because this library is so platform-dependent the best way to test thoroughly is with
|
||||
github's CI. This will happen automatically when you open a PR.
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2023-2025 Jeremy Rifkin
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||
portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
||||
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
||||
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,12 @@
|
|||
# Security Policy
|
||||
|
||||
We take security seriously and I'm grateful for reports of security vulnerabilities.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If the vulnerability can be reported without revealing exploitable specifics, please open an issue.
|
||||
|
||||
If the vulnerability can't be reported publicly without leaving an obvious exploit in the public eye please email me
|
||||
at jeremy@rifkin.dev or reach out to me on [discord](https://discord.gg/7kv5AuCndG).
|
||||
|
||||
I will do my best to get back to you within a day.
|
||||
|
|
@ -0,0 +1,250 @@
|
|||
#ifndef CPPTRACE_BASIC_HPP
|
||||
#define CPPTRACE_BASIC_HPP
|
||||
|
||||
#include <cpptrace/forward.hpp>
|
||||
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <iosfwd>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define CPPTRACE_EXPORT_ATTR __declspec(dllexport)
|
||||
#define CPPTRACE_IMPORT_ATTR __declspec(dllimport)
|
||||
#else
|
||||
#define CPPTRACE_EXPORT_ATTR __attribute__((visibility("default")))
|
||||
#define CPPTRACE_IMPORT_ATTR __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
#ifdef CPPTRACE_STATIC_DEFINE
|
||||
# define CPPTRACE_EXPORT
|
||||
# define CPPTRACE_NO_EXPORT
|
||||
#else
|
||||
# ifndef CPPTRACE_EXPORT
|
||||
# ifdef cpptrace_lib_EXPORTS
|
||||
/* We are building this library */
|
||||
# define CPPTRACE_EXPORT CPPTRACE_EXPORT_ATTR
|
||||
# else
|
||||
/* We are using this library */
|
||||
# define CPPTRACE_EXPORT CPPTRACE_IMPORT_ATTR
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if __cplusplus >= 201703L
|
||||
#define CONSTEXPR_SINCE_CPP17 constexpr
|
||||
#else
|
||||
#define CONSTEXPR_SINCE_CPP17
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define CPPTRACE_FORCE_NO_INLINE __declspec(noinline)
|
||||
#else
|
||||
#define CPPTRACE_FORCE_NO_INLINE __attribute__((noinline))
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
// warning C4251: using non-dll-exported type in dll-exported type, firing on std::vector<frame_ptr> and others for some
|
||||
// reason
|
||||
// 4275 is the same thing but for base classes
|
||||
#pragma warning(disable: 4251; disable: 4275)
|
||||
#endif
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
struct CPPTRACE_EXPORT raw_trace {
|
||||
std::vector<frame_ptr> frames;
|
||||
static raw_trace current(std::size_t skip = 0);
|
||||
static raw_trace current(std::size_t skip, std::size_t max_depth);
|
||||
object_trace resolve_object_trace() const;
|
||||
stacktrace resolve() const;
|
||||
void clear();
|
||||
bool empty() const noexcept;
|
||||
|
||||
using iterator = std::vector<frame_ptr>::iterator;
|
||||
using const_iterator = std::vector<frame_ptr>::const_iterator;
|
||||
inline iterator begin() noexcept { return frames.begin(); }
|
||||
inline iterator end() noexcept { return frames.end(); }
|
||||
inline const_iterator begin() const noexcept { return frames.begin(); }
|
||||
inline const_iterator end() const noexcept { return frames.end(); }
|
||||
inline const_iterator cbegin() const noexcept { return frames.cbegin(); }
|
||||
inline const_iterator cend() const noexcept { return frames.cend(); }
|
||||
};
|
||||
|
||||
struct CPPTRACE_EXPORT object_frame {
|
||||
frame_ptr raw_address;
|
||||
frame_ptr object_address;
|
||||
std::string object_path;
|
||||
};
|
||||
|
||||
struct CPPTRACE_EXPORT object_trace {
|
||||
std::vector<object_frame> frames;
|
||||
static object_trace current(std::size_t skip = 0);
|
||||
static object_trace current(std::size_t skip, std::size_t max_depth);
|
||||
stacktrace resolve() const;
|
||||
void clear();
|
||||
bool empty() const noexcept;
|
||||
|
||||
using iterator = std::vector<object_frame>::iterator;
|
||||
using const_iterator = std::vector<object_frame>::const_iterator;
|
||||
inline iterator begin() noexcept { return frames.begin(); }
|
||||
inline iterator end() noexcept { return frames.end(); }
|
||||
inline const_iterator begin() const noexcept { return frames.begin(); }
|
||||
inline const_iterator end() const noexcept { return frames.end(); }
|
||||
inline const_iterator cbegin() const noexcept { return frames.cbegin(); }
|
||||
inline const_iterator cend() const noexcept { return frames.cend(); }
|
||||
};
|
||||
|
||||
// This represents a nullable integer type
|
||||
// The max value of the type is used as a sentinel
|
||||
// This is used over std::optional because the library is C++11 and also std::optional is a bit heavy-duty for this
|
||||
// use.
|
||||
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
|
||||
struct nullable {
|
||||
T raw_value = null_value();
|
||||
constexpr nullable() noexcept = default;
|
||||
constexpr nullable(T value) noexcept : raw_value(value) {}
|
||||
CONSTEXPR_SINCE_CPP17 nullable& operator=(T value) noexcept {
|
||||
raw_value = value;
|
||||
return *this;
|
||||
}
|
||||
constexpr bool has_value() const noexcept {
|
||||
return raw_value != null_value();
|
||||
}
|
||||
CONSTEXPR_SINCE_CPP17 T& value() noexcept {
|
||||
return raw_value;
|
||||
}
|
||||
constexpr const T& value() const noexcept {
|
||||
return raw_value;
|
||||
}
|
||||
constexpr T value_or(T alternative) const noexcept {
|
||||
return has_value() ? raw_value : alternative;
|
||||
}
|
||||
CONSTEXPR_SINCE_CPP17 void swap(nullable& other) noexcept {
|
||||
std::swap(raw_value, other.raw_value);
|
||||
}
|
||||
CONSTEXPR_SINCE_CPP17 void reset() noexcept {
|
||||
raw_value = (std::numeric_limits<T>::max)();
|
||||
}
|
||||
constexpr bool operator==(const nullable& other) const noexcept {
|
||||
return raw_value == other.raw_value;
|
||||
}
|
||||
constexpr bool operator!=(const nullable& other) const noexcept {
|
||||
return raw_value != other.raw_value;
|
||||
}
|
||||
constexpr static T null_value() noexcept {
|
||||
return (std::numeric_limits<T>::max)();
|
||||
}
|
||||
constexpr static nullable null() noexcept {
|
||||
return { null_value() };
|
||||
}
|
||||
};
|
||||
|
||||
struct CPPTRACE_EXPORT stacktrace_frame {
|
||||
frame_ptr raw_address;
|
||||
frame_ptr object_address;
|
||||
nullable<std::uint32_t> line;
|
||||
nullable<std::uint32_t> column;
|
||||
std::string filename;
|
||||
std::string symbol;
|
||||
bool is_inline;
|
||||
|
||||
bool operator==(const stacktrace_frame& other) const {
|
||||
return raw_address == other.raw_address
|
||||
&& object_address == other.object_address
|
||||
&& line == other.line
|
||||
&& column == other.column
|
||||
&& filename == other.filename
|
||||
&& symbol == other.symbol;
|
||||
}
|
||||
|
||||
bool operator!=(const stacktrace_frame& other) const {
|
||||
return !operator==(other);
|
||||
}
|
||||
|
||||
object_frame get_object_info() const;
|
||||
|
||||
std::string to_string() const;
|
||||
std::string to_string(bool color) const;
|
||||
friend CPPTRACE_EXPORT std::ostream& operator<<(std::ostream& stream, const stacktrace_frame& frame);
|
||||
};
|
||||
|
||||
struct CPPTRACE_EXPORT stacktrace {
|
||||
std::vector<stacktrace_frame> frames;
|
||||
static stacktrace current(std::size_t skip = 0);
|
||||
static stacktrace current(std::size_t skip, std::size_t max_depth);
|
||||
void print() const;
|
||||
void print(std::ostream& stream) const;
|
||||
void print(std::ostream& stream, bool color) const;
|
||||
void print_with_snippets() const;
|
||||
void print_with_snippets(std::ostream& stream) const;
|
||||
void print_with_snippets(std::ostream& stream, bool color) const;
|
||||
void clear();
|
||||
bool empty() const noexcept;
|
||||
std::string to_string(bool color = false) const;
|
||||
friend CPPTRACE_EXPORT std::ostream& operator<<(std::ostream& stream, const stacktrace& trace);
|
||||
|
||||
using iterator = std::vector<stacktrace_frame>::iterator;
|
||||
using const_iterator = std::vector<stacktrace_frame>::const_iterator;
|
||||
inline iterator begin() noexcept { return frames.begin(); }
|
||||
inline iterator end() noexcept { return frames.end(); }
|
||||
inline const_iterator begin() const noexcept { return frames.begin(); }
|
||||
inline const_iterator end() const noexcept { return frames.end(); }
|
||||
inline const_iterator cbegin() const noexcept { return frames.cbegin(); }
|
||||
inline const_iterator cend() const noexcept { return frames.cend(); }
|
||||
private:
|
||||
friend void print_terminate_trace();
|
||||
};
|
||||
|
||||
CPPTRACE_EXPORT raw_trace generate_raw_trace(std::size_t skip = 0);
|
||||
CPPTRACE_EXPORT raw_trace generate_raw_trace(std::size_t skip, std::size_t max_depth);
|
||||
CPPTRACE_EXPORT object_trace generate_object_trace(std::size_t skip = 0);
|
||||
CPPTRACE_EXPORT object_trace generate_object_trace(std::size_t skip, std::size_t max_depth);
|
||||
CPPTRACE_EXPORT stacktrace generate_trace(std::size_t skip = 0);
|
||||
CPPTRACE_EXPORT stacktrace generate_trace(std::size_t skip, std::size_t max_depth);
|
||||
|
||||
// Path max isn't so simple, so I'm choosing 4096 which seems to encompass what all major OS's expect and should be
|
||||
// fine in all reasonable cases.
|
||||
// https://eklitzke.org/path-max-is-tricky
|
||||
// https://insanecoding.blogspot.com/2007/11/pathmax-simply-isnt.html
|
||||
#define CPPTRACE_PATH_MAX 4096
|
||||
|
||||
// safe tracing interface
|
||||
// signal-safe
|
||||
CPPTRACE_EXPORT std::size_t safe_generate_raw_trace(
|
||||
frame_ptr* buffer,
|
||||
std::size_t size,
|
||||
std::size_t skip = 0
|
||||
);
|
||||
// signal-safe
|
||||
CPPTRACE_EXPORT std::size_t safe_generate_raw_trace(
|
||||
frame_ptr* buffer,
|
||||
std::size_t size,
|
||||
std::size_t skip,
|
||||
std::size_t max_depth
|
||||
);
|
||||
struct CPPTRACE_EXPORT safe_object_frame {
|
||||
frame_ptr raw_address;
|
||||
// This ends up being the real object address. It was named at a time when I thought the object base address
|
||||
// still needed to be added in
|
||||
frame_ptr address_relative_to_object_start;
|
||||
char object_path[CPPTRACE_PATH_MAX + 1];
|
||||
// To be called outside a signal handler. Not signal safe.
|
||||
object_frame resolve() const;
|
||||
};
|
||||
// signal-safe
|
||||
CPPTRACE_EXPORT void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
|
||||
CPPTRACE_EXPORT bool can_signal_safe_unwind();
|
||||
CPPTRACE_EXPORT bool can_get_safe_object_frame();
|
||||
|
||||
// JIT API
|
||||
CPPTRACE_EXPORT void register_jit_object(const char*, std::size_t);
|
||||
CPPTRACE_EXPORT void unregister_jit_object(const char*);
|
||||
CPPTRACE_EXPORT void clear_all_jit_objects();
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
#ifndef CPPTRACE_HPP
|
||||
#define CPPTRACE_HPP
|
||||
|
||||
#include <cpptrace/basic.hpp>
|
||||
#include <cpptrace/utils.hpp>
|
||||
#include <cpptrace/exceptions.hpp>
|
||||
#include <cpptrace/io.hpp>
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
#ifndef CPPTRACE_EXCEPTIONS_HPP
|
||||
#define CPPTRACE_EXCEPTIONS_HPP
|
||||
|
||||
#include <cpptrace/basic.hpp>
|
||||
#include <cpptrace/exceptions_macros.hpp>
|
||||
|
||||
#include <exception>
|
||||
#include <system_error>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
// warning C4251: using non-dll-exported type in dll-exported type, firing on std::vector<frame_ptr> and others for some
|
||||
// reason
|
||||
// 4275 is the same thing but for base classes
|
||||
#pragma warning(disable: 4251; disable: 4275)
|
||||
#endif
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
// tracing exceptions:
|
||||
namespace detail {
|
||||
// This is a helper utility, if the library weren't C++11 an std::variant would be used
|
||||
class CPPTRACE_EXPORT lazy_trace_holder {
|
||||
bool resolved;
|
||||
union {
|
||||
raw_trace trace;
|
||||
stacktrace resolved_trace;
|
||||
};
|
||||
public:
|
||||
// constructors
|
||||
lazy_trace_holder() : resolved(false), trace() {}
|
||||
explicit lazy_trace_holder(raw_trace&& _trace) : resolved(false), trace(std::move(_trace)) {}
|
||||
explicit lazy_trace_holder(stacktrace&& _resolved_trace) : resolved(true), resolved_trace(std::move(_resolved_trace)) {}
|
||||
// logistics
|
||||
lazy_trace_holder(const lazy_trace_holder& other);
|
||||
lazy_trace_holder(lazy_trace_holder&& other) noexcept;
|
||||
lazy_trace_holder& operator=(const lazy_trace_holder& other);
|
||||
lazy_trace_holder& operator=(lazy_trace_holder&& other) noexcept;
|
||||
~lazy_trace_holder();
|
||||
// access
|
||||
const raw_trace& get_raw_trace() const;
|
||||
stacktrace& get_resolved_trace();
|
||||
const stacktrace& get_resolved_trace() const;
|
||||
bool is_resolved() const;
|
||||
private:
|
||||
void clear();
|
||||
};
|
||||
|
||||
CPPTRACE_EXPORT raw_trace get_raw_trace_and_absorb(std::size_t skip, std::size_t max_depth);
|
||||
CPPTRACE_EXPORT raw_trace get_raw_trace_and_absorb(std::size_t skip = 0);
|
||||
}
|
||||
|
||||
// Interface for a traced exception object
|
||||
class CPPTRACE_EXPORT exception : public std::exception {
|
||||
public:
|
||||
const char* what() const noexcept override = 0;
|
||||
virtual const char* message() const noexcept = 0;
|
||||
virtual const stacktrace& trace() const noexcept = 0;
|
||||
};
|
||||
|
||||
// Cpptrace traced exception object
|
||||
// I hate to have to expose anything about implementation detail but the idea here is that
|
||||
class CPPTRACE_EXPORT lazy_exception : public exception {
|
||||
mutable detail::lazy_trace_holder trace_holder;
|
||||
mutable std::string what_string;
|
||||
|
||||
public:
|
||||
explicit lazy_exception(
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) : trace_holder(std::move(trace)) {}
|
||||
// std::exception
|
||||
const char* what() const noexcept override;
|
||||
// cpptrace::exception
|
||||
const char* message() const noexcept override;
|
||||
const stacktrace& trace() const noexcept override;
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT exception_with_message : public lazy_exception {
|
||||
mutable std::string user_message;
|
||||
|
||||
public:
|
||||
explicit exception_with_message(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept : lazy_exception(std::move(trace)), user_message(std::move(message_arg)) {}
|
||||
|
||||
const char* message() const noexcept override;
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT logic_error : public exception_with_message {
|
||||
public:
|
||||
explicit logic_error(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT domain_error : public exception_with_message {
|
||||
public:
|
||||
explicit domain_error(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT invalid_argument : public exception_with_message {
|
||||
public:
|
||||
explicit invalid_argument(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT length_error : public exception_with_message {
|
||||
public:
|
||||
explicit length_error(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT out_of_range : public exception_with_message {
|
||||
public:
|
||||
explicit out_of_range(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT runtime_error : public exception_with_message {
|
||||
public:
|
||||
explicit runtime_error(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT range_error : public exception_with_message {
|
||||
public:
|
||||
explicit range_error(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT overflow_error : public exception_with_message {
|
||||
public:
|
||||
explicit overflow_error(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT underflow_error : public exception_with_message {
|
||||
public:
|
||||
explicit underflow_error(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT nested_exception : public lazy_exception {
|
||||
std::exception_ptr ptr;
|
||||
mutable std::string message_value;
|
||||
public:
|
||||
explicit nested_exception(
|
||||
const std::exception_ptr& exception_ptr,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: lazy_exception(std::move(trace)), ptr(exception_ptr) {}
|
||||
|
||||
const char* message() const noexcept override;
|
||||
std::exception_ptr nested_ptr() const noexcept;
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT system_error : public runtime_error {
|
||||
std::error_code ec;
|
||||
public:
|
||||
explicit system_error(
|
||||
int error_code,
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept;
|
||||
const std::error_code& code() const noexcept;
|
||||
};
|
||||
|
||||
// [[noreturn]] must come first due to old clang
|
||||
[[noreturn]] CPPTRACE_EXPORT void rethrow_and_wrap_if_needed(std::size_t skip = 0);
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
#ifndef CPPTRACE_EXCEPTIONS_MACROS_HPP
|
||||
#define CPPTRACE_EXCEPTIONS_MACROS_HPP
|
||||
|
||||
// Exception wrapper utilities
|
||||
#define CPPTRACE_WRAP_BLOCK(statements) do { \
|
||||
try { \
|
||||
statements \
|
||||
} catch(...) { \
|
||||
::cpptrace::rethrow_and_wrap_if_needed(); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define CPPTRACE_WRAP(expression) [&] () -> decltype((expression)) { \
|
||||
try { \
|
||||
return expression; \
|
||||
} catch(...) { \
|
||||
::cpptrace::rethrow_and_wrap_if_needed(1); \
|
||||
} \
|
||||
} ()
|
||||
|
||||
#endif // CPPTRACE_EXCEPTIONS_MACROS_HPP
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
#ifndef CPPTRACE_FORMATTING_HPP
|
||||
#define CPPTRACE_FORMATTING_HPP
|
||||
|
||||
#include <cpptrace/basic.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
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);
|
||||
enum class symbol_mode {
|
||||
// full demangled symbol
|
||||
full,
|
||||
// symbols are prettified to clean up some especially long template argument lists
|
||||
pretty,
|
||||
// template arguments and function parameters are pruned
|
||||
pruned
|
||||
};
|
||||
formatter& symbols(symbol_mode);
|
||||
formatter& filtered_frame_placeholders(bool);
|
||||
formatter& filter(std::function<bool(const stacktrace_frame&)>);
|
||||
formatter& transform(std::function<stacktrace_frame(stacktrace_frame)>);
|
||||
formatter& break_before_filename(bool do_break = true);
|
||||
|
||||
std::string format(const stacktrace_frame&) const;
|
||||
std::string format(const stacktrace_frame&, bool color) const;
|
||||
// The last argument is the indent to use for the filename, if break_before_filename is set
|
||||
std::string format(const stacktrace_frame&, bool color, size_t filename_indent) 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;
|
||||
// The last argument is the indent to use for the filename, if break_before_filename is set
|
||||
void print(std::ostream&, const stacktrace_frame&, bool color, size_t filename_indent) const;
|
||||
void print(std::FILE*, const stacktrace_frame&) const;
|
||||
void print(std::FILE*, const stacktrace_frame&, bool color) const;
|
||||
// The last argument is the indent to use for the filename, if break_before_filename is set
|
||||
void print(std::FILE*, const stacktrace_frame&, bool color, size_t filename_indent) 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();
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
#ifndef CPPTRACE_FORWARD_HPP
|
||||
#define CPPTRACE_FORWARD_HPP
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#define CPPTRACE_BEGIN_NAMESPACE \
|
||||
namespace cpptrace { \
|
||||
inline namespace v1 {
|
||||
#define CPPTRACE_END_NAMESPACE \
|
||||
} \
|
||||
}
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
// Some type sufficient for an instruction pointer, currently always an alias to std::uintptr_t
|
||||
using frame_ptr = std::uintptr_t;
|
||||
|
||||
struct raw_trace;
|
||||
struct object_trace;
|
||||
struct stacktrace;
|
||||
|
||||
struct object_frame;
|
||||
struct stacktrace_frame;
|
||||
struct safe_object_frame;
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
#ifndef CPPTRACE_FROM_CURRENT_HPP
|
||||
#define CPPTRACE_FROM_CURRENT_HPP
|
||||
|
||||
#include <exception>
|
||||
#include <typeinfo>
|
||||
#include <utility>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include <cpptrace/basic.hpp>
|
||||
#include <cpptrace/from_current_macros.hpp>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
CPPTRACE_EXPORT const raw_trace& raw_trace_from_current_exception();
|
||||
CPPTRACE_EXPORT const stacktrace& from_current_exception();
|
||||
|
||||
CPPTRACE_EXPORT const raw_trace& raw_trace_from_current_exception_rethrow();
|
||||
CPPTRACE_EXPORT const stacktrace& from_current_exception_rethrow();
|
||||
|
||||
CPPTRACE_EXPORT bool current_exception_was_rethrown();
|
||||
|
||||
CPPTRACE_NORETURN CPPTRACE_EXPORT CPPTRACE_FORCE_NO_INLINE
|
||||
void rethrow();
|
||||
|
||||
CPPTRACE_NORETURN CPPTRACE_EXPORT CPPTRACE_FORCE_NO_INLINE
|
||||
void rethrow(std::exception_ptr exception);
|
||||
|
||||
CPPTRACE_EXPORT void clear_current_exception_traces();
|
||||
|
||||
namespace detail {
|
||||
template<typename>
|
||||
struct argument;
|
||||
template<typename R, typename Arg>
|
||||
struct argument<R(Arg)> {
|
||||
using type = Arg;
|
||||
};
|
||||
template<typename R>
|
||||
struct argument<R(...)> {
|
||||
using type = void;
|
||||
};
|
||||
|
||||
#ifdef _MSC_VER
|
||||
CPPTRACE_EXPORT CPPTRACE_FORCE_NO_INLINE
|
||||
int maybe_collect_trace(EXCEPTION_POINTERS* exception_ptrs, int filter_result);
|
||||
CPPTRACE_EXPORT CPPTRACE_FORCE_NO_INLINE
|
||||
void maybe_collect_trace(EXCEPTION_POINTERS* exception_ptrs, const std::type_info& type_info);
|
||||
template<typename E>
|
||||
CPPTRACE_FORCE_NO_INLINE inline int exception_filter(EXCEPTION_POINTERS* exception_ptrs) {
|
||||
maybe_collect_trace(exception_ptrs, typeid(E));
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
class dont_return_from_try_catch_macros {
|
||||
public:
|
||||
explicit dont_return_from_try_catch_macros() = default;
|
||||
};
|
||||
#else
|
||||
CPPTRACE_EXPORT CPPTRACE_FORCE_NO_INLINE
|
||||
void maybe_collect_trace(const std::type_info*, const std::type_info*, void**, unsigned);
|
||||
template<typename T>
|
||||
class unwind_interceptor {
|
||||
public:
|
||||
static int init;
|
||||
CPPTRACE_FORCE_NO_INLINE static bool can_catch(
|
||||
const std::type_info* /* this */,
|
||||
const std::type_info* throw_type,
|
||||
void** throw_obj,
|
||||
unsigned outer
|
||||
) {
|
||||
maybe_collect_trace(&typeid(T), throw_type, throw_obj, outer);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
CPPTRACE_EXPORT void do_prepare_unwind_interceptor(
|
||||
const std::type_info&,
|
||||
bool(*)(const std::type_info*, const std::type_info*, void**, unsigned)
|
||||
);
|
||||
template<typename T>
|
||||
inline int prepare_unwind_interceptor() {
|
||||
do_prepare_unwind_interceptor(typeid(unwind_interceptor<T>), unwind_interceptor<T>::can_catch);
|
||||
return 1;
|
||||
}
|
||||
template<typename T>
|
||||
int unwind_interceptor<T>::init = prepare_unwind_interceptor<T>();
|
||||
template<typename F>
|
||||
using unwind_interceptor_for = unwind_interceptor<typename argument<F>::type>;
|
||||
inline void nop(int) {}
|
||||
#endif
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
template<typename R, typename Arg>
|
||||
Arg get_callable_argument_helper(R(*) (Arg));
|
||||
template<typename R, typename F, typename Arg>
|
||||
Arg get_callable_argument_helper(R(F::*) (Arg));
|
||||
template<typename R, typename F, typename Arg>
|
||||
Arg get_callable_argument_helper(R(F::*) (Arg) const);
|
||||
template<typename R>
|
||||
void get_callable_argument_helper(R(*) ());
|
||||
template<typename R, typename F>
|
||||
void get_callable_argument_helper(R(F::*) ());
|
||||
template<typename R, typename F>
|
||||
void get_callable_argument_helper(R(F::*) () const);
|
||||
template<typename F>
|
||||
decltype(get_callable_argument_helper(&F::operator())) get_callable_argument_wrapper(F);
|
||||
template<typename T>
|
||||
using get_callable_argument = decltype(get_callable_argument_wrapper(std::declval<T>()));
|
||||
|
||||
template<typename E, typename F, typename Catch, typename std::enable_if<!std::is_same<E, void>::value, int>::type = 0>
|
||||
void do_try_catch(F&& f, Catch&& catcher) {
|
||||
CPPTRACE_TRY {
|
||||
std::forward<F>(f)();
|
||||
} CPPTRACE_CATCH(E e) {
|
||||
std::forward<Catch>(catcher)(std::forward<E>(e));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename E, typename F, typename Catch, typename std::enable_if<std::is_same<E, void>::value, int>::type = 0>
|
||||
void do_try_catch(F&& f, Catch&& catcher) {
|
||||
CPPTRACE_TRY {
|
||||
std::forward<F>(f)();
|
||||
} CPPTRACE_CATCH(...) {
|
||||
std::forward<Catch>(catcher)();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
void try_catch_impl(F&& f) {
|
||||
std::forward<F>(f)();
|
||||
}
|
||||
|
||||
// TODO: This could be made more efficient to reduce the number of interceptor levels that do typeid checks
|
||||
// and possible traces
|
||||
template<typename F, typename Catch, typename... Catches>
|
||||
void try_catch_impl(F&& f, Catch&& catcher, Catches&&... catches) {
|
||||
// match the first catch at the inner-most level... no real way to reverse a pack or extract from the end so
|
||||
// we have to wrap with a lambda
|
||||
auto wrapped = [&] () {
|
||||
using E = get_callable_argument<Catch>;
|
||||
do_try_catch<E>(std::forward<F>(f), std::forward<Catch>(catcher));
|
||||
};
|
||||
try_catch_impl(std::move(wrapped), std::forward<Catches>(catches)...);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename F, typename... Catches>
|
||||
void try_catch(F&& f, Catches&&... catches) {
|
||||
return detail::try_catch_impl(std::forward<F>(f), std::forward<Catches>(catches)...);
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
#ifndef CPPTRACE_FROM_CURRENT_MACROS_HPP
|
||||
#define CPPTRACE_FROM_CURRENT_MACROS_HPP
|
||||
|
||||
// https://godbolt.org/z/4MsT6KqP1
|
||||
#ifdef _MSC_VER
|
||||
#define CPPTRACE_UNREACHABLE() __assume(false)
|
||||
#else
|
||||
#define CPPTRACE_UNREACHABLE() __builtin_unreachable()
|
||||
#endif
|
||||
|
||||
// https://godbolt.org/z/7neGPEche
|
||||
// gcc added support in 4.8 but I'm too lazy to check the minor version
|
||||
#if defined(__GNUC__) && (__GNUC__ < 5)
|
||||
#define CPPTRACE_NORETURN __attribute__((noreturn))
|
||||
#else
|
||||
#define CPPTRACE_NORETURN [[noreturn]]
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#if defined(__clang__)
|
||||
#define CPPTRACE_PUSH_EXTENSION_WARNINGS \
|
||||
_Pragma("clang diagnostic push") \
|
||||
_Pragma("clang diagnostic ignored \"-Wlanguage-extension-token\"")
|
||||
#define CPPTRACE_POP_EXTENSION_WARNINGS \
|
||||
_Pragma("clang diagnostic pop")
|
||||
#else
|
||||
#define CPPTRACE_PUSH_EXTENSION_WARNINGS
|
||||
#define CPPTRACE_POP_EXTENSION_WARNINGS
|
||||
#endif
|
||||
#define CPPTRACE_TYPE_FOR(param) \
|
||||
typename ::cpptrace::detail::argument<void(param)>::type
|
||||
// this awful double-IILE is due to C2713 "You can't use structured exception handling (__try/__except) and C++
|
||||
// exception handling (try/catch) in the same function."
|
||||
#define CPPTRACE_TRY \
|
||||
try { \
|
||||
[&]() -> ::cpptrace::detail::dont_return_from_try_catch_macros { \
|
||||
CPPTRACE_PUSH_EXTENSION_WARNINGS \
|
||||
__try { \
|
||||
CPPTRACE_POP_EXTENSION_WARNINGS \
|
||||
return [&]() -> ::cpptrace::detail::dont_return_from_try_catch_macros {
|
||||
#define CPPTRACE_CATCH(param) \
|
||||
return ::cpptrace::detail::dont_return_from_try_catch_macros(); \
|
||||
}(); \
|
||||
CPPTRACE_PUSH_EXTENSION_WARNINGS \
|
||||
} __except(::cpptrace::detail::exception_filter<CPPTRACE_TYPE_FOR(param)>(GetExceptionInformation())) { \
|
||||
CPPTRACE_POP_EXTENSION_WARNINGS \
|
||||
CPPTRACE_UNREACHABLE(); \
|
||||
} \
|
||||
}(); \
|
||||
} catch(param)
|
||||
#define CPPTRACE_SEH_TRY __try
|
||||
#define CPPTRACE_SEH_EXCEPT(filter) \
|
||||
__except(::cpptrace::detail::maybe_collect_trace(GetExceptionInformation(), (filter)))
|
||||
#else
|
||||
#define CPPTRACE_UNWIND_INTERCEPTOR_FOR(param) \
|
||||
::cpptrace::detail::unwind_interceptor_for<void(param)>
|
||||
#define CPPTRACE_TRY \
|
||||
try { \
|
||||
try {
|
||||
#define CPPTRACE_CATCH(param) \
|
||||
} catch(const CPPTRACE_UNWIND_INTERCEPTOR_FOR(param)&) { \
|
||||
CPPTRACE_UNREACHABLE(); \
|
||||
/* force instantiation of the init-er */ \
|
||||
::cpptrace::detail::nop(CPPTRACE_UNWIND_INTERCEPTOR_FOR(param)::init); \
|
||||
} \
|
||||
} catch(param)
|
||||
#endif
|
||||
|
||||
#ifdef CPPTRACE_UNPREFIXED_TRY_CATCH
|
||||
#define TRY CPPTRACE_TRY
|
||||
#define CATCH(param) CPPTRACE_CATCH(param)
|
||||
#ifdef _MSC_VER
|
||||
#define SEH_TRY CPPTRACE_SEH_TRY
|
||||
#define SEH_EXCEPT(filter) CPPTRACE_SEH_EXCEPT(filter)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif // CPPTRACE_FROM_CURRENT_MACROS_HPP
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
#ifndef CPPTRACE_GDB_JIT_HPP
|
||||
#define CPPTRACE_GDB_JIT_HPP
|
||||
|
||||
#include <cpptrace/basic.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
// https://sourceware.org/gdb/current/onlinedocs/gdb.html/JIT-Interface.html
|
||||
extern "C" {
|
||||
typedef enum
|
||||
{
|
||||
JIT_NOACTION = 0,
|
||||
JIT_REGISTER_FN,
|
||||
JIT_UNREGISTER_FN
|
||||
} jit_actions_t;
|
||||
|
||||
struct jit_code_entry
|
||||
{
|
||||
struct jit_code_entry *next_entry;
|
||||
struct jit_code_entry *prev_entry;
|
||||
const char *symfile_addr;
|
||||
uint64_t symfile_size;
|
||||
};
|
||||
|
||||
struct jit_descriptor
|
||||
{
|
||||
uint32_t version;
|
||||
/* This type should be jit_actions_t, but we use uint32_t
|
||||
to be explicit about the bitwidth. */
|
||||
uint32_t action_flag;
|
||||
struct jit_code_entry *relevant_entry;
|
||||
struct jit_code_entry *first_entry;
|
||||
};
|
||||
|
||||
extern struct jit_descriptor __jit_debug_descriptor;
|
||||
}
|
||||
}
|
||||
|
||||
namespace experimental {
|
||||
inline void register_jit_objects_from_gdb_jit_interface() {
|
||||
clear_all_jit_objects();
|
||||
detail::jit_code_entry* entry = detail::__jit_debug_descriptor.first_entry;
|
||||
while(entry) {
|
||||
register_jit_object(entry->symfile_addr, entry->symfile_size);
|
||||
entry = entry->next_entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
#ifndef CPPTRACE_IO_HPP
|
||||
#define CPPTRACE_IO_HPP
|
||||
|
||||
#include <cpptrace/basic.hpp>
|
||||
|
||||
#include <iosfwd>
|
||||
|
||||
#ifndef CPPTRACE_NO_STD_FORMAT
|
||||
#if __cplusplus >= 202002L
|
||||
#ifdef __has_include
|
||||
#if __has_include(<format>)
|
||||
#define CPPTRACE_STD_FORMAT
|
||||
#include <format>
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
// warning C4251: using non-dll-exported type in dll-exported type, firing on std::vector<frame_ptr> and others for some
|
||||
// reason
|
||||
// 4275 is the same thing but for base classes
|
||||
#pragma warning(disable: 4251; disable: 4275)
|
||||
#endif
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
CPPTRACE_EXPORT std::ostream& operator<<(std::ostream& stream, const stacktrace_frame& frame);
|
||||
CPPTRACE_EXPORT std::ostream& operator<<(std::ostream& stream, const stacktrace& trace);
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#if defined(CPPTRACE_STD_FORMAT) && defined(__cpp_lib_format)
|
||||
template <>
|
||||
struct std::formatter<cpptrace::stacktrace_frame> : std::formatter<std::string> {
|
||||
auto format(cpptrace::stacktrace_frame frame, format_context& ctx) const {
|
||||
return formatter<string>::format(frame.to_string(), ctx);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct std::formatter<cpptrace::stacktrace> : std::formatter<std::string> {
|
||||
auto format(cpptrace::stacktrace trace, format_context& ctx) const {
|
||||
return formatter<string>::format(trace.to_string(), ctx);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
#ifndef CPPTRACE_UTILS_HPP
|
||||
#define CPPTRACE_UTILS_HPP
|
||||
|
||||
#include <cpptrace/basic.hpp>
|
||||
|
||||
#include <functional>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
// warning C4251: using non-dll-exported type in dll-exported type, firing on std::vector<frame_ptr> and others for some
|
||||
// reason
|
||||
// 4275 is the same thing but for base classes
|
||||
#pragma warning(disable: 4251; disable: 4275)
|
||||
#endif
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
CPPTRACE_EXPORT std::string demangle(const std::string& name);
|
||||
|
||||
CPPTRACE_EXPORT std::string basename(const std::string& path);
|
||||
CPPTRACE_EXPORT std::string prettify_symbol(std::string symbol);
|
||||
CPPTRACE_EXPORT std::string prune_symbol(const std::string& symbol);
|
||||
|
||||
CPPTRACE_EXPORT std::string get_snippet(
|
||||
const std::string& path,
|
||||
std::size_t line,
|
||||
std::size_t context_size,
|
||||
bool color = false
|
||||
);
|
||||
|
||||
CPPTRACE_EXPORT std::string get_snippet(
|
||||
const std::string& path,
|
||||
std::size_t line,
|
||||
nullable<std::uint32_t> column,
|
||||
std::size_t context_size,
|
||||
bool color = false
|
||||
);
|
||||
|
||||
CPPTRACE_EXPORT bool isatty(int fd);
|
||||
|
||||
CPPTRACE_EXPORT extern const int stdin_fileno;
|
||||
CPPTRACE_EXPORT extern const int stderr_fileno;
|
||||
CPPTRACE_EXPORT extern const int stdout_fileno;
|
||||
|
||||
CPPTRACE_EXPORT void register_terminate_handler();
|
||||
|
||||
// options:
|
||||
CPPTRACE_EXPORT void absorb_trace_exceptions(bool absorb);
|
||||
CPPTRACE_EXPORT void enable_inlined_call_resolution(bool enable);
|
||||
|
||||
enum class log_level { debug, info, warning, error };
|
||||
CPPTRACE_EXPORT void set_log_level(log_level level);
|
||||
CPPTRACE_EXPORT void set_log_callback(std::function<void(log_level, const char*)>);
|
||||
CPPTRACE_EXPORT void use_default_stderr_logger();
|
||||
CPPTRACE_EXPORT const char* to_string(log_level level);
|
||||
|
||||
enum class cache_mode {
|
||||
// Only minimal lookup tables
|
||||
prioritize_memory = 0,
|
||||
// Build lookup tables but don't keep them around between trace calls
|
||||
hybrid = 1,
|
||||
// Build lookup tables as needed
|
||||
prioritize_speed = 2
|
||||
};
|
||||
|
||||
namespace experimental {
|
||||
CPPTRACE_EXPORT void set_cache_mode(cache_mode mode);
|
||||
}
|
||||
|
||||
// dwarf options
|
||||
namespace experimental {
|
||||
CPPTRACE_EXPORT void set_dwarf_resolver_line_table_cache_size(nullable<std::size_t> max_entries);
|
||||
CPPTRACE_EXPORT void set_dwarf_resolver_disable_aranges(bool disable);
|
||||
}
|
||||
|
||||
// dbghelp
|
||||
#ifdef _WIN32
|
||||
CPPTRACE_EXPORT void load_symbols_for_file(const std::string& filename);
|
||||
#endif
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,164 @@
|
|||
#ifndef CTRACE_H
|
||||
#define CTRACE_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define CPPTRACE_EXPORT_ATTR __declspec(dllexport)
|
||||
#define CPPTRACE_IMPORT_ATTR __declspec(dllimport)
|
||||
#else
|
||||
#define CPPTRACE_EXPORT_ATTR __attribute__((visibility("default")))
|
||||
#define CPPTRACE_IMPORT_ATTR __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
#ifdef CPPTRACE_STATIC_DEFINE
|
||||
# define CPPTRACE_EXPORT
|
||||
# define CPPTRACE_NO_EXPORT
|
||||
#else
|
||||
# ifndef CPPTRACE_EXPORT
|
||||
# ifdef cpptrace_lib_EXPORTS
|
||||
/* We are building this library */
|
||||
# define CPPTRACE_EXPORT CPPTRACE_EXPORT_ATTR
|
||||
# else
|
||||
/* We are using this library */
|
||||
# define CPPTRACE_EXPORT CPPTRACE_IMPORT_ATTR
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if defined(__cplusplus)
|
||||
#define CTRACE_BEGIN_DEFINITIONS extern "C" {
|
||||
#define CTRACE_END_DEFINITIONS }
|
||||
#else
|
||||
#define CTRACE_BEGIN_DEFINITIONS
|
||||
#define CTRACE_END_DEFINITIONS
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define CTRACE_FORCE_NO_INLINE __declspec(noinline)
|
||||
#else
|
||||
#define CTRACE_FORCE_NO_INLINE __attribute__((noinline))
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define CTRACE_FORCE_INLINE __forceinline
|
||||
#elif defined(__clang__) || defined(__GNUC__)
|
||||
#define CTRACE_FORCE_INLINE __attribute__((always_inline)) inline
|
||||
#else
|
||||
#define CTRACE_FORCE_INLINE inline
|
||||
#endif
|
||||
|
||||
/* See `CPPTRACE_PATH_MAX` for more info. */
|
||||
#define CTRACE_PATH_MAX 4096
|
||||
|
||||
CTRACE_BEGIN_DEFINITIONS
|
||||
|
||||
typedef struct ctrace_raw_trace ctrace_raw_trace;
|
||||
typedef struct ctrace_object_trace ctrace_object_trace;
|
||||
typedef struct ctrace_stacktrace ctrace_stacktrace;
|
||||
|
||||
/* Represents a boolean value, ensures a consistent ABI. */
|
||||
typedef int8_t ctrace_bool;
|
||||
/* A type that can represent a pointer, alias for `uintptr_t`. */
|
||||
typedef uintptr_t ctrace_frame_ptr;
|
||||
typedef struct ctrace_object_frame ctrace_object_frame;
|
||||
typedef struct ctrace_stacktrace_frame ctrace_stacktrace_frame;
|
||||
typedef struct ctrace_safe_object_frame ctrace_safe_object_frame;
|
||||
|
||||
/* Type-safe null-terminated string wrapper */
|
||||
typedef struct {
|
||||
const char* data;
|
||||
} ctrace_owning_string;
|
||||
|
||||
struct ctrace_object_frame {
|
||||
ctrace_frame_ptr raw_address;
|
||||
ctrace_frame_ptr obj_address;
|
||||
const char* obj_path;
|
||||
};
|
||||
|
||||
struct ctrace_stacktrace_frame {
|
||||
ctrace_frame_ptr raw_address;
|
||||
ctrace_frame_ptr object_address;
|
||||
uint32_t line;
|
||||
uint32_t column;
|
||||
const char* filename;
|
||||
const char* symbol;
|
||||
ctrace_bool is_inline;
|
||||
};
|
||||
|
||||
struct ctrace_safe_object_frame {
|
||||
ctrace_frame_ptr raw_address;
|
||||
ctrace_frame_ptr relative_obj_address;
|
||||
char object_path[CTRACE_PATH_MAX + 1];
|
||||
};
|
||||
|
||||
struct ctrace_raw_trace {
|
||||
ctrace_frame_ptr* frames;
|
||||
size_t count;
|
||||
};
|
||||
|
||||
struct ctrace_object_trace {
|
||||
ctrace_object_frame* frames;
|
||||
size_t count;
|
||||
};
|
||||
|
||||
struct ctrace_stacktrace {
|
||||
ctrace_stacktrace_frame* frames;
|
||||
size_t count;
|
||||
};
|
||||
|
||||
/* ctrace::string: */
|
||||
CPPTRACE_EXPORT ctrace_owning_string ctrace_generate_owning_string(const char* raw_string);
|
||||
CPPTRACE_EXPORT void ctrace_free_owning_string(ctrace_owning_string* string);
|
||||
|
||||
/* ctrace::generation: */
|
||||
CPPTRACE_EXPORT ctrace_raw_trace ctrace_generate_raw_trace(size_t skip, size_t max_depth);
|
||||
CPPTRACE_EXPORT ctrace_object_trace ctrace_generate_object_trace(size_t skip, size_t max_depth);
|
||||
CPPTRACE_EXPORT ctrace_stacktrace ctrace_generate_trace(size_t skip, size_t max_depth);
|
||||
|
||||
/* ctrace::freeing: */
|
||||
CPPTRACE_EXPORT void ctrace_free_raw_trace(ctrace_raw_trace* trace);
|
||||
CPPTRACE_EXPORT void ctrace_free_object_trace(ctrace_object_trace* trace);
|
||||
CPPTRACE_EXPORT void ctrace_free_stacktrace(ctrace_stacktrace* trace);
|
||||
|
||||
/* ctrace::resolve: */
|
||||
CPPTRACE_EXPORT ctrace_stacktrace ctrace_resolve_raw_trace(const ctrace_raw_trace* trace);
|
||||
CPPTRACE_EXPORT ctrace_object_trace ctrace_resolve_raw_trace_to_object_trace(const ctrace_raw_trace* trace);
|
||||
CPPTRACE_EXPORT ctrace_stacktrace ctrace_resolve_object_trace(const ctrace_object_trace* trace);
|
||||
|
||||
/* ctrace::safe: */
|
||||
CPPTRACE_EXPORT size_t ctrace_safe_generate_raw_trace(ctrace_frame_ptr* buffer, size_t size, size_t skip, size_t max_depth);
|
||||
CPPTRACE_EXPORT void ctrace_get_safe_object_frame(ctrace_frame_ptr address, ctrace_safe_object_frame* out);
|
||||
CPPTRACE_EXPORT ctrace_bool ctrace_can_signal_safe_unwind(void);
|
||||
CPPTRACE_EXPORT ctrace_bool ctrace_can_get_safe_object_frame(void);
|
||||
|
||||
/* ctrace::io: */
|
||||
CPPTRACE_EXPORT ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color);
|
||||
CPPTRACE_EXPORT void ctrace_print_stacktrace(const ctrace_stacktrace* trace, FILE* to, ctrace_bool use_color);
|
||||
|
||||
/* ctrace::utility: */
|
||||
CPPTRACE_EXPORT ctrace_owning_string ctrace_demangle(const char* mangled);
|
||||
CPPTRACE_EXPORT int ctrace_stdin_fileno(void);
|
||||
CPPTRACE_EXPORT int ctrace_stderr_fileno(void);
|
||||
CPPTRACE_EXPORT int ctrace_stdout_fileno(void);
|
||||
CPPTRACE_EXPORT ctrace_bool ctrace_isatty(int fd);
|
||||
|
||||
CPPTRACE_EXPORT ctrace_object_frame ctrace_get_object_info(const ctrace_stacktrace_frame* frame);
|
||||
|
||||
/* ctrace::config: */
|
||||
typedef enum {
|
||||
/* Only minimal lookup tables */
|
||||
ctrace_prioritize_memory = 0,
|
||||
/* Build lookup tables but don't keep them around between trace calls */
|
||||
ctrace_hybrid = 1,
|
||||
/* Build lookup tables as needed */
|
||||
ctrace_prioritize_speed = 2
|
||||
} ctrace_cache_mode;
|
||||
CPPTRACE_EXPORT void ctrace_set_cache_mode(ctrace_cache_mode mode);
|
||||
CPPTRACE_EXPORT void ctrace_enable_inlined_call_resolution(ctrace_bool enable);
|
||||
|
||||
CTRACE_END_DEFINITIONS
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,488 @@
|
|||
#include "binary/elf.hpp"
|
||||
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/io/base_file.hpp"
|
||||
#include "utils/io/memory_file_view.hpp"
|
||||
#include "utils/optional.hpp"
|
||||
#include "utils/io/file.hpp"
|
||||
#include "utils/string_view.hpp"
|
||||
|
||||
#if IS_LINUX
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <elf.h>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
elf::elf(
|
||||
std::unique_ptr<base_file> file,
|
||||
bool is_little_endian,
|
||||
bool is_64
|
||||
) : file(std::move(file)), is_little_endian(is_little_endian), is_64(is_64) {}
|
||||
|
||||
Result<elf, internal_error> elf::open(std::unique_ptr<base_file> file) {
|
||||
// Initial checks/metadata
|
||||
auto magic = file->read<std::array<char, 4>>(0);
|
||||
if(magic.is_error()) {
|
||||
return std::move(magic).unwrap_error();
|
||||
}
|
||||
if(magic.unwrap_value() != (std::array<char, 4>{0x7F, 'E', 'L', 'F'})) {
|
||||
return internal_error("File is not ELF {}", file->path());
|
||||
}
|
||||
auto ei_class = file->read<std::uint8_t>(4);
|
||||
if(ei_class.is_error()) {
|
||||
return std::move(ei_class).unwrap_error();
|
||||
}
|
||||
bool is_64 = ei_class.unwrap_value() == 2;
|
||||
auto ei_data = file->read<std::uint8_t>(5);
|
||||
if(ei_data.is_error()) {
|
||||
return std::move(ei_data).unwrap_error();
|
||||
}
|
||||
bool is_little_endian = ei_data.unwrap_value() == 1;
|
||||
auto ei_version = file->read<std::uint8_t>(6);
|
||||
if(ei_version.is_error()) {
|
||||
return std::move(ei_version).unwrap_error();
|
||||
}
|
||||
if(ei_version.unwrap_value() != 1) {
|
||||
return internal_error("Unexpected ELF version {}", file->path());
|
||||
}
|
||||
return elf(std::move(file), is_little_endian, is_64);
|
||||
}
|
||||
|
||||
Result<elf, internal_error> elf::open(cstring_view object_path) {
|
||||
auto file_res = file::open(object_path);
|
||||
if(!file_res) {
|
||||
return internal_error("Unable to read object file {}", object_path);
|
||||
}
|
||||
auto& file = file_res.unwrap_value();
|
||||
return open(make_unique(std::move(file)));
|
||||
}
|
||||
|
||||
Result<elf, internal_error> elf::open(cbspan object) {
|
||||
return open(make_unique<memory_file_view>(object));
|
||||
}
|
||||
|
||||
Result<std::uintptr_t, internal_error> elf::get_module_image_base() {
|
||||
// get image base
|
||||
if(is_64) {
|
||||
return get_module_image_base_impl<64>();
|
||||
} else {
|
||||
return get_module_image_base_impl<32>();
|
||||
}
|
||||
}
|
||||
|
||||
template<std::size_t Bits>
|
||||
Result<std::uintptr_t, internal_error> elf::get_module_image_base_impl() {
|
||||
static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument");
|
||||
using PHeader = typename std::conditional<Bits == 32, Elf32_Phdr, Elf64_Phdr>::type;
|
||||
auto header = get_header_info();
|
||||
if(header.is_error()) {
|
||||
return std::move(header).unwrap_error();
|
||||
}
|
||||
const auto& header_info = header.unwrap_value();
|
||||
// PT_PHDR will occur at most once
|
||||
// Should be somewhat reliable https://stackoverflow.com/q/61568612/15675011
|
||||
// It should occur at the beginning but may as well loop just in case
|
||||
for(unsigned i = 0; i < header_info.e_phnum; i++) {
|
||||
auto loaded_ph = file->read<PHeader>(header_info.e_phoff + header_info.e_phentsize * i);
|
||||
if(loaded_ph.is_error()) {
|
||||
return std::move(loaded_ph).unwrap_error();
|
||||
}
|
||||
const PHeader& program_header = loaded_ph.unwrap_value();
|
||||
if(byteswap_if_needed(program_header.p_type) == PT_PHDR) {
|
||||
return byteswap_if_needed(program_header.p_vaddr) -
|
||||
byteswap_if_needed(program_header.p_offset);
|
||||
}
|
||||
}
|
||||
// Apparently some objects like shared objects can end up missing this header. 0 as a base seems correct.
|
||||
return 0;
|
||||
}
|
||||
|
||||
optional<std::string> elf::lookup_symbol(frame_ptr pc) {
|
||||
if(auto symtab = get_symtab()) {
|
||||
if(auto symbol = lookup_symbol(pc, symtab.unwrap_value())) {
|
||||
return symbol;
|
||||
}
|
||||
}
|
||||
if(auto dynamic_symtab = get_dynamic_symtab()) {
|
||||
if(auto symbol = lookup_symbol(pc, dynamic_symtab.unwrap_value())) {
|
||||
return symbol;
|
||||
}
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
optional<std::string> elf::lookup_symbol(frame_ptr pc, const optional<symtab_info>& maybe_symtab) {
|
||||
if(!maybe_symtab) {
|
||||
return nullopt;
|
||||
}
|
||||
auto& symtab = maybe_symtab.unwrap();
|
||||
if(symtab.strtab_link == SHN_UNDEF) {
|
||||
return nullopt;
|
||||
}
|
||||
auto strtab_ = get_strtab(symtab.strtab_link);
|
||||
if(strtab_.is_error()) {
|
||||
return nullopt;
|
||||
}
|
||||
auto& strtab = strtab_.unwrap_value();
|
||||
auto it = first_less_than_or_equal(
|
||||
symtab.entries.begin(),
|
||||
symtab.entries.end(),
|
||||
pc,
|
||||
[] (frame_ptr pc, const symtab_entry& entry) {
|
||||
return pc < entry.st_value;
|
||||
}
|
||||
);
|
||||
if(it == symtab.entries.end()) {
|
||||
return nullopt;
|
||||
}
|
||||
if(pc <= it->st_value + it->st_size) {
|
||||
return strtab.data() + it->st_name;
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
Result<std::vector<elf::pc_range>, internal_error> elf::get_pc_ranges() {
|
||||
std::vector<pc_range> vec;
|
||||
auto header_info_ = get_header_info();
|
||||
if(header_info_.is_error()) {
|
||||
return header_info_.unwrap_error();
|
||||
}
|
||||
auto& header_info = header_info_.unwrap_value();
|
||||
auto strtab_ = get_strtab(header_info.e_shstrndx);
|
||||
if(strtab_.is_error()) {
|
||||
return strtab_.unwrap_error();
|
||||
}
|
||||
auto& strtab = strtab_.unwrap_value();
|
||||
auto sections_res = get_sections();
|
||||
if(!sections_res) {
|
||||
return sections_res.unwrap_error();
|
||||
}
|
||||
const auto& sections = sections_res.unwrap_value();
|
||||
for(const auto& section : sections) {
|
||||
if(string_view(strtab.data() + section.sh_name) == ".text") {
|
||||
vec.push_back(
|
||||
pc_range{to<frame_ptr>(section.sh_addr), to<frame_ptr>(section.sh_addr + section.sh_size)}
|
||||
);
|
||||
}
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
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>
|
||||
T elf::byteswap_if_needed(T value) {
|
||||
if(detail::is_little_endian() == is_little_endian) {
|
||||
return value;
|
||||
} else {
|
||||
return byteswap(value);
|
||||
}
|
||||
}
|
||||
|
||||
Result<const elf::header_info&, internal_error> elf::get_header_info() {
|
||||
if(header) {
|
||||
return header.unwrap();
|
||||
}
|
||||
if(tried_to_load_header) {
|
||||
return internal_error("previous header load failed {}", file->path());
|
||||
}
|
||||
tried_to_load_header = true;
|
||||
if(is_64) {
|
||||
return get_header_info_impl<64>();
|
||||
} else {
|
||||
return get_header_info_impl<32>();
|
||||
}
|
||||
}
|
||||
|
||||
template<std::size_t Bits>
|
||||
Result<const elf::header_info&, internal_error> elf::get_header_info_impl() {
|
||||
static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument");
|
||||
using Header = typename std::conditional<Bits == 32, Elf32_Ehdr, Elf64_Ehdr>::type;
|
||||
auto loaded_header = file->read<Header>(0);
|
||||
if(loaded_header.is_error()) {
|
||||
return std::move(loaded_header).unwrap_error();
|
||||
}
|
||||
const Header& file_header = loaded_header.unwrap_value();
|
||||
if(file_header.e_ehsize != sizeof(Header)) {
|
||||
return internal_error("ELF file header size mismatch {}", file->path());
|
||||
}
|
||||
header_info info;
|
||||
info.e_phoff = byteswap_if_needed(file_header.e_phoff);
|
||||
info.e_phnum = byteswap_if_needed(file_header.e_phnum);
|
||||
info.e_phentsize = byteswap_if_needed(file_header.e_phentsize);
|
||||
info.e_shoff = byteswap_if_needed(file_header.e_shoff);
|
||||
info.e_shnum = byteswap_if_needed(file_header.e_shnum);
|
||||
info.e_shentsize = byteswap_if_needed(file_header.e_shentsize);
|
||||
info.e_shstrndx = byteswap_if_needed(file_header.e_shstrndx);
|
||||
header = info;
|
||||
return header.unwrap();
|
||||
}
|
||||
|
||||
Result<const std::vector<elf::section_info>&, internal_error> elf::get_sections() {
|
||||
if(did_load_sections) {
|
||||
return sections;
|
||||
}
|
||||
if(tried_to_load_sections) {
|
||||
return internal_error("previous sections load failed {}", file->path());
|
||||
}
|
||||
tried_to_load_sections = true;
|
||||
if(is_64) {
|
||||
return get_sections_impl<64>();
|
||||
} else {
|
||||
return get_sections_impl<32>();
|
||||
}
|
||||
}
|
||||
|
||||
template<std::size_t Bits>
|
||||
Result<const std::vector<elf::section_info>&, internal_error> elf::get_sections_impl() {
|
||||
static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument");
|
||||
using SHeader = typename std::conditional<Bits == 32, Elf32_Shdr, Elf64_Shdr>::type;
|
||||
auto header = get_header_info();
|
||||
if(header.is_error()) {
|
||||
return std::move(header).unwrap_error();
|
||||
}
|
||||
const auto& header_info = header.unwrap_value();
|
||||
for(unsigned i = 0; i < header_info.e_shnum; i++) {
|
||||
auto loaded_sh = file->read<SHeader>(header_info.e_shoff + header_info.e_shentsize * i);
|
||||
if(loaded_sh.is_error()) {
|
||||
return std::move(loaded_sh).unwrap_error();
|
||||
}
|
||||
const SHeader& section_header = loaded_sh.unwrap_value();
|
||||
section_info info;
|
||||
info.sh_name = byteswap_if_needed(section_header.sh_name);
|
||||
info.sh_type = byteswap_if_needed(section_header.sh_type);
|
||||
info.sh_addr = byteswap_if_needed(section_header.sh_addr);
|
||||
info.sh_offset = byteswap_if_needed(section_header.sh_offset);
|
||||
info.sh_size = byteswap_if_needed(section_header.sh_size);
|
||||
info.sh_entsize = byteswap_if_needed(section_header.sh_entsize);
|
||||
info.sh_link = byteswap_if_needed(section_header.sh_link);
|
||||
sections.push_back(info);
|
||||
}
|
||||
did_load_sections = true;
|
||||
return sections;
|
||||
}
|
||||
|
||||
Result<const std::vector<char>&, internal_error> elf::get_strtab(std::size_t index) {
|
||||
auto res = strtab_entries.insert({index, {}});
|
||||
auto it = res.first;
|
||||
auto did_insert = res.second;
|
||||
auto& entry = it->second;
|
||||
if(!did_insert) {
|
||||
if(entry.did_load_strtab) {
|
||||
return entry.data;
|
||||
}
|
||||
if(entry.tried_to_load_strtab) {
|
||||
return internal_error("previous strtab load failed {}", file->path());
|
||||
}
|
||||
}
|
||||
entry.tried_to_load_strtab = true;
|
||||
auto sections_ = get_sections();
|
||||
if(sections_.is_error()) {
|
||||
return std::move(sections_).unwrap_error();
|
||||
}
|
||||
const auto& sections = sections_.unwrap_value();
|
||||
if(index >= sections.size()) {
|
||||
return internal_error("requested strtab section index out of range");
|
||||
}
|
||||
const auto& section = sections[index];
|
||||
if(section.sh_type != SHT_STRTAB) {
|
||||
return internal_error("requested strtab section not a strtab (requested {} of {})", index, file->path());
|
||||
}
|
||||
entry.data.resize(section.sh_size + 1);
|
||||
auto read_res = file->read_bytes(
|
||||
span<char>{entry.data.data(), to<std::size_t>(section.sh_size)},
|
||||
section.sh_offset
|
||||
);
|
||||
if(!read_res) {
|
||||
return read_res.unwrap_error();
|
||||
}
|
||||
entry.data[section.sh_size] = 0; // just out of an abundance of caution
|
||||
entry.did_load_strtab = true;
|
||||
return entry.data;
|
||||
}
|
||||
|
||||
Result<const optional<elf::symtab_info>&, internal_error> elf::get_symtab() {
|
||||
if(did_load_symtab) {
|
||||
return symtab;
|
||||
}
|
||||
if(tried_to_load_symtab) {
|
||||
return internal_error("previous symtab load failed {}", file->path());
|
||||
}
|
||||
tried_to_load_symtab = true;
|
||||
if(is_64) {
|
||||
auto res = get_symtab_impl<64>(false);
|
||||
if(res.has_value()) {
|
||||
symtab = std::move(res).unwrap_value();
|
||||
did_load_symtab = true;
|
||||
return symtab;
|
||||
} else {
|
||||
return std::move(res).unwrap_error();
|
||||
}
|
||||
} else {
|
||||
auto res = get_symtab_impl<32>(false);
|
||||
if(res.has_value()) {
|
||||
symtab = std::move(res).unwrap_value();
|
||||
did_load_symtab = true;
|
||||
return symtab;
|
||||
} else {
|
||||
return std::move(res).unwrap_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result<const optional<elf::symtab_info>&, internal_error> elf::get_dynamic_symtab() {
|
||||
if(did_load_dynamic_symtab) {
|
||||
return dynamic_symtab;
|
||||
}
|
||||
if(tried_to_load_dynamic_symtab) {
|
||||
return internal_error("previous dynamic symtab load failed {}", file->path());
|
||||
}
|
||||
tried_to_load_dynamic_symtab = true;
|
||||
if(is_64) {
|
||||
auto res = get_symtab_impl<64>(true);
|
||||
if(res.has_value()) {
|
||||
dynamic_symtab = std::move(res).unwrap_value();
|
||||
did_load_dynamic_symtab = true;
|
||||
return dynamic_symtab;
|
||||
} else {
|
||||
return std::move(res).unwrap_error();
|
||||
}
|
||||
} else {
|
||||
auto res = get_symtab_impl<32>(true);
|
||||
if(res.has_value()) {
|
||||
dynamic_symtab = std::move(res).unwrap_value();
|
||||
did_load_dynamic_symtab = true;
|
||||
return dynamic_symtab;
|
||||
} else {
|
||||
return std::move(res).unwrap_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<std::size_t Bits>
|
||||
Result<optional<elf::symtab_info>, internal_error> elf::get_symtab_impl(bool dynamic) {
|
||||
// https://refspecs.linuxfoundation.org/elf/elf.pdf
|
||||
// page 66: only one sht_symtab and sht_dynsym section per file
|
||||
// page 32: symtab spec
|
||||
static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument");
|
||||
using SymEntry = typename std::conditional<Bits == 32, Elf32_Sym, Elf64_Sym>::type;
|
||||
auto sections_ = get_sections();
|
||||
if(sections_.is_error()) {
|
||||
return std::move(sections_).unwrap_error();
|
||||
}
|
||||
const auto& sections = sections_.unwrap_value();
|
||||
optional<symtab_info> symbol_table;
|
||||
for(const auto& section : sections) {
|
||||
if(section.sh_type == (dynamic ? SHT_DYNSYM : SHT_SYMTAB)) {
|
||||
if(section.sh_entsize != sizeof(SymEntry)) {
|
||||
return internal_error("elf seems corrupted, sym entry mismatch {}", file->path());
|
||||
}
|
||||
if(section.sh_size % section.sh_entsize != 0) {
|
||||
return internal_error("elf seems corrupted, sym entry vs section size mismatch {}", file->path());
|
||||
}
|
||||
std::vector<SymEntry> buffer(section.sh_size / section.sh_entsize);
|
||||
auto res = file->read_span(make_span(buffer.begin(), buffer.end()), section.sh_offset);
|
||||
if(!res) {
|
||||
return res.unwrap_error();
|
||||
}
|
||||
symbol_table = symtab_info{};
|
||||
symbol_table.unwrap().entries.reserve(buffer.size());
|
||||
for(const auto& entry : buffer) {
|
||||
symtab_entry normalized;
|
||||
normalized.st_name = byteswap_if_needed(entry.st_name);
|
||||
normalized.st_info = byteswap_if_needed(entry.st_info);
|
||||
normalized.st_other = byteswap_if_needed(entry.st_other);
|
||||
normalized.st_shndx = byteswap_if_needed(entry.st_shndx);
|
||||
normalized.st_value = byteswap_if_needed(entry.st_value);
|
||||
normalized.st_size = byteswap_if_needed(entry.st_size);
|
||||
// on arm I've observed zero-size symbols that overlap with symbols we care about
|
||||
// this interferes with some symbol lookup - that could be fixed by enhancing the logic there but
|
||||
// also it's easy to just exclude zero-size symbols here
|
||||
// 1413: 00000000000349e0 0 NOTYPE LOCAL DEFAULT 13 $x
|
||||
// 32341: 00000000000349e0 220 FUNC GLOBAL DEFAULT 13 _Z33stacktrace_from_current_rethrow_3RSt6vectorIiSaIiEE
|
||||
if(normalized.st_size != 0) {
|
||||
symbol_table.unwrap().entries.push_back(normalized);
|
||||
}
|
||||
}
|
||||
std::sort(
|
||||
symbol_table.unwrap().entries.begin(),
|
||||
symbol_table.unwrap().entries.end(),
|
||||
[] (const symtab_entry& a, const symtab_entry& b) {
|
||||
return a.st_value < b.st_value;
|
||||
}
|
||||
);
|
||||
symbol_table.unwrap().strtab_link = section.sh_link;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return symbol_table;
|
||||
}
|
||||
|
||||
Result<maybe_owned<elf>, internal_error> open_elf_cached(const std::string& object_path) {
|
||||
if(object_path.empty()) {
|
||||
return internal_error{"empty object_path"};
|
||||
}
|
||||
if(get_cache_mode() == cache_mode::prioritize_memory) {
|
||||
return elf::open(object_path)
|
||||
.transform([](elf&& obj) { return maybe_owned<elf>{detail::make_unique<elf>(std::move(obj))}; });
|
||||
} else {
|
||||
static 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.emplace(object_path, elf::open(object_path));
|
||||
VERIFY(res.second);
|
||||
it = res.first;
|
||||
}
|
||||
return it->second.transform([](elf& obj) { return maybe_owned<elf>(&obj); });
|
||||
}
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
#ifndef ELF_HPP
|
||||
#define ELF_HPP
|
||||
|
||||
#include "cpptrace/forward.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/io/base_file.hpp"
|
||||
#include "utils/span.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#if IS_LINUX
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
// TODO: make methods const and a bunch of members mutable
|
||||
class elf {
|
||||
std::unique_ptr<base_file> file;
|
||||
bool is_little_endian;
|
||||
bool is_64;
|
||||
|
||||
struct header_info {
|
||||
uint64_t e_phoff;
|
||||
uint32_t e_phnum;
|
||||
uint32_t e_phentsize;
|
||||
uint64_t e_shoff;
|
||||
uint32_t e_shnum;
|
||||
uint32_t e_shentsize;
|
||||
uint16_t e_shstrndx;
|
||||
};
|
||||
bool tried_to_load_header = false;
|
||||
optional<header_info> header;
|
||||
|
||||
struct section_info {
|
||||
uint32_t sh_name;
|
||||
uint32_t sh_type;
|
||||
uint64_t sh_addr;
|
||||
uint64_t sh_offset;
|
||||
uint64_t sh_size;
|
||||
uint64_t sh_entsize;
|
||||
uint32_t sh_link;
|
||||
};
|
||||
bool tried_to_load_sections = false;
|
||||
bool did_load_sections = false;
|
||||
std::vector<section_info> sections;
|
||||
|
||||
struct strtab_entry {
|
||||
bool tried_to_load_strtab = false;
|
||||
bool did_load_strtab = false;
|
||||
std::vector<char> data;
|
||||
};
|
||||
std::unordered_map<std::size_t, strtab_entry> strtab_entries;
|
||||
|
||||
struct symtab_entry {
|
||||
uint32_t st_name;
|
||||
unsigned char st_info;
|
||||
unsigned char st_other;
|
||||
uint16_t st_shndx;
|
||||
uint64_t st_value;
|
||||
uint64_t st_size;
|
||||
};
|
||||
struct symtab_info {
|
||||
std::vector<symtab_entry> entries;
|
||||
std::size_t strtab_link = 0;
|
||||
};
|
||||
bool tried_to_load_symtab = false;
|
||||
bool did_load_symtab = false;
|
||||
optional<symtab_info> symtab;
|
||||
|
||||
bool tried_to_load_dynamic_symtab = false;
|
||||
bool did_load_dynamic_symtab = false;
|
||||
optional<symtab_info> dynamic_symtab;
|
||||
|
||||
elf(std::unique_ptr<base_file> file, bool is_little_endian, bool is_64);
|
||||
|
||||
static NODISCARD Result<elf, internal_error> open(std::unique_ptr<base_file> file);
|
||||
|
||||
public:
|
||||
static NODISCARD Result<elf, internal_error> open(cstring_view object_path);
|
||||
static NODISCARD Result<elf, internal_error> open(cbspan object);
|
||||
|
||||
elf(elf&&) = default;
|
||||
|
||||
public:
|
||||
Result<std::uintptr_t, internal_error> get_module_image_base();
|
||||
private:
|
||||
template<std::size_t Bits>
|
||||
Result<std::uintptr_t, internal_error> get_module_image_base_impl();
|
||||
|
||||
public:
|
||||
optional<std::string> lookup_symbol(frame_ptr pc);
|
||||
private:
|
||||
optional<std::string> lookup_symbol(frame_ptr pc, const optional<symtab_info>& maybe_symtab);
|
||||
|
||||
public:
|
||||
struct pc_range {
|
||||
frame_ptr low;
|
||||
frame_ptr high; // not inclusive
|
||||
};
|
||||
// for in-memory JIT elves
|
||||
Result<std::vector<pc_range>, internal_error> get_pc_ranges();
|
||||
|
||||
struct symbol_entry {
|
||||
std::string st_name;
|
||||
uint16_t st_shndx;
|
||||
uint64_t st_value;
|
||||
uint64_t st_size;
|
||||
};
|
||||
Result<optional<std::vector<symbol_entry>>, internal_error> get_symtab_entries();
|
||||
Result<optional<std::vector<symbol_entry>>, internal_error> get_dynamic_symtab_entries();
|
||||
private:
|
||||
Result<optional<std::vector<symbol_entry>>, internal_error> resolve_symtab_entries(
|
||||
const Result<const optional<symtab_info> &, internal_error>&
|
||||
);
|
||||
|
||||
private:
|
||||
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
|
||||
T byteswap_if_needed(T value);
|
||||
|
||||
Result<const header_info&, internal_error> get_header_info();
|
||||
template<std::size_t Bits>
|
||||
Result<const header_info&, internal_error> get_header_info_impl();
|
||||
|
||||
Result<const std::vector<section_info>&, internal_error> get_sections();
|
||||
template<std::size_t Bits>
|
||||
Result<const std::vector<section_info>&, internal_error> get_sections_impl();
|
||||
|
||||
Result<const std::vector<char>&, internal_error> get_strtab(std::size_t index);
|
||||
|
||||
Result<const optional<symtab_info>&, internal_error> get_symtab();
|
||||
Result<const optional<symtab_info>&, internal_error> get_dynamic_symtab();
|
||||
template<std::size_t Bits>
|
||||
Result<optional<symtab_info>, internal_error> get_symtab_impl(bool dynamic);
|
||||
};
|
||||
|
||||
NODISCARD Result<maybe_owned<elf>, internal_error> open_elf_cached(const std::string& object_path);
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,730 @@
|
|||
#include "binary/mach-o.hpp"
|
||||
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "utils/io/file.hpp"
|
||||
#include "utils/io/memory_file_view.hpp"
|
||||
|
||||
#if IS_APPLE
|
||||
|
||||
// A number of mach-o functions are deprecated as of macos 13
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
|
||||
#include <mach-o/loader.h>
|
||||
#include <mach-o/swap.h>
|
||||
#include <mach-o/fat.h>
|
||||
#include <crt_externs.h>
|
||||
#include <mach-o/nlist.h>
|
||||
#include <mach-o/stab.h>
|
||||
#include <mach-o/arch.h>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
bool is_mach_o(std::uint32_t magic) {
|
||||
switch(magic) {
|
||||
case FAT_MAGIC:
|
||||
case FAT_CIGAM:
|
||||
case MH_MAGIC:
|
||||
case MH_CIGAM:
|
||||
case MH_MAGIC_64:
|
||||
case MH_CIGAM_64:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool file_is_mach_o(cstring_view object_path) noexcept {
|
||||
auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter);
|
||||
if(file == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto magic = load_bytes<std::uint32_t>(file, 0);
|
||||
if(magic) {
|
||||
return is_mach_o(magic.unwrap_value());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool is_fat_magic(std::uint32_t magic) {
|
||||
return magic == FAT_MAGIC || magic == FAT_CIGAM;
|
||||
}
|
||||
|
||||
// Based on https://github.com/AlexDenisov/segment_dumper/blob/master/main.c
|
||||
// and https://lowlevelbits.org/parsing-mach-o-files/
|
||||
bool is_magic_64(std::uint32_t magic) {
|
||||
return magic == MH_MAGIC_64 || magic == MH_CIGAM_64;
|
||||
}
|
||||
|
||||
bool should_swap_bytes(std::uint32_t magic) {
|
||||
return magic == MH_CIGAM || magic == MH_CIGAM_64 || magic == FAT_CIGAM;
|
||||
}
|
||||
|
||||
void swap_mach_header(mach_header_64& header) {
|
||||
swap_mach_header_64(&header, NX_UnknownByteOrder);
|
||||
}
|
||||
|
||||
void swap_mach_header(mach_header& header) {
|
||||
swap_mach_header(&header, NX_UnknownByteOrder);
|
||||
}
|
||||
|
||||
void swap_segment_command(segment_command_64& segment) {
|
||||
swap_segment_command_64(&segment, NX_UnknownByteOrder);
|
||||
}
|
||||
|
||||
void swap_segment_command(segment_command& segment) {
|
||||
swap_segment_command(&segment, NX_UnknownByteOrder);
|
||||
}
|
||||
|
||||
void swap_nlist(struct nlist& entry) {
|
||||
swap_nlist(&entry, 1, NX_UnknownByteOrder);
|
||||
}
|
||||
|
||||
void swap_nlist(struct nlist_64& entry) {
|
||||
swap_nlist_64(&entry, 1, NX_UnknownByteOrder);
|
||||
}
|
||||
|
||||
#ifdef __LP64__
|
||||
#define LP(x) x##_64
|
||||
#else
|
||||
#define LP(x) x
|
||||
#endif
|
||||
|
||||
Result<const char*, internal_error> mach_o::symtab_info_data::get_string(std::size_t index) const {
|
||||
if(stringtab && index < symtab.strsize) {
|
||||
return stringtab.unwrap().data() + index;
|
||||
} else {
|
||||
return internal_error("can't retrieve symbol from symtab");
|
||||
}
|
||||
}
|
||||
|
||||
Result<monostate, internal_error> mach_o::load() {
|
||||
if(magic == FAT_MAGIC || magic == FAT_CIGAM) {
|
||||
return load_fat_mach();
|
||||
} else {
|
||||
fat_index = 0;
|
||||
if(is_magic_64(magic)) {
|
||||
return load_mach<64>();
|
||||
} else {
|
||||
return load_mach<32>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result<mach_o, internal_error> mach_o::open(std::unique_ptr<base_file> file) {
|
||||
auto magic = file->read<std::uint32_t>(0);
|
||||
if(!magic) {
|
||||
return magic.unwrap_error();
|
||||
}
|
||||
if(!is_mach_o(magic.unwrap_value())) {
|
||||
return internal_error("File is not mach-o {}", file->path());
|
||||
}
|
||||
mach_o obj(std::move(file), magic.unwrap_value());
|
||||
auto result = obj.load();
|
||||
if(result.is_error()) {
|
||||
return result.unwrap_error();
|
||||
} else {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
Result<mach_o, internal_error> mach_o::open(cstring_view object_path) {
|
||||
auto file_res = file::open(object_path);
|
||||
if(!file_res) {
|
||||
return internal_error("Unable to read object file {}", object_path);
|
||||
}
|
||||
auto& file = file_res.unwrap_value();
|
||||
return open(make_unique(std::move(file)));
|
||||
}
|
||||
|
||||
Result<mach_o, internal_error> mach_o::open(cbspan object) {
|
||||
return open(make_unique<memory_file_view>(object));
|
||||
}
|
||||
|
||||
Result<std::uintptr_t, internal_error> mach_o::get_text_vmaddr() {
|
||||
for(const auto& command : load_commands) {
|
||||
if(command.cmd == LC_SEGMENT_64 || command.cmd == LC_SEGMENT) {
|
||||
auto segment = command.cmd == LC_SEGMENT_64
|
||||
? load_segment_command<64>(command.file_offset)
|
||||
: load_segment_command<32>(command.file_offset);
|
||||
if(segment.is_error()) {
|
||||
return std::move(segment).unwrap_error();
|
||||
}
|
||||
if(std::strcmp(segment.unwrap_value().segname, "__TEXT") == 0) {
|
||||
return segment.unwrap_value().vmaddr;
|
||||
}
|
||||
}
|
||||
}
|
||||
// somehow no __TEXT section was found...
|
||||
return internal_error("Couldn't find __TEXT section while parsing Mach-O object");
|
||||
}
|
||||
|
||||
std::size_t mach_o::get_fat_index() const {
|
||||
VERIFY(fat_index != std::numeric_limits<std::size_t>::max());
|
||||
return fat_index;
|
||||
}
|
||||
|
||||
void mach_o::print_segments() const {
|
||||
int i = 0;
|
||||
for(const auto& command : load_commands) {
|
||||
if(command.cmd == LC_SEGMENT_64 || command.cmd == LC_SEGMENT) {
|
||||
auto segment_load = command.cmd == LC_SEGMENT_64
|
||||
? load_segment_command<64>(command.file_offset)
|
||||
: load_segment_command<32>(command.file_offset);
|
||||
fprintf(stderr, "Load command %d\n", i);
|
||||
if(segment_load.is_error()) {
|
||||
fprintf(stderr, " error\n");
|
||||
segment_load.drop_error();
|
||||
continue;
|
||||
}
|
||||
auto& segment = segment_load.unwrap_value();
|
||||
fprintf(stderr, " cmd %u\n", segment.cmd);
|
||||
fprintf(stderr, " cmdsize %u\n", segment.cmdsize);
|
||||
fprintf(stderr, " segname %s\n", segment.segname);
|
||||
fprintf(stderr, " vmaddr 0x%llx\n", segment.vmaddr);
|
||||
fprintf(stderr, " vmsize 0x%llx\n", segment.vmsize);
|
||||
fprintf(stderr, " off 0x%llx\n", segment.fileoff);
|
||||
fprintf(stderr, " filesize %llu\n", segment.filesize);
|
||||
fprintf(stderr, " nsects %u\n", segment.nsects);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
Result<std::vector<mach_o::pc_range>, internal_error> mach_o::get_pc_ranges() {
|
||||
std::vector<pc_range> ranges;
|
||||
for(const auto& command : load_commands) {
|
||||
if(command.cmd == LC_SEGMENT_64 || command.cmd == LC_SEGMENT) {
|
||||
auto segment_res = command.cmd == LC_SEGMENT_64
|
||||
? load_segment_command<64>(command.file_offset)
|
||||
: load_segment_command<32>(command.file_offset);
|
||||
if(segment_res.is_error()) {
|
||||
return std::move(segment_res).unwrap_error();
|
||||
}
|
||||
auto& segment = segment_res.unwrap_value();
|
||||
if(std::strcmp(segment.segname, "__TEXT") == 0) {
|
||||
ranges.push_back({segment.vmaddr, segment.vmaddr + segment.vmsize});
|
||||
}
|
||||
}
|
||||
}
|
||||
return ranges;
|
||||
}
|
||||
|
||||
Result<std::reference_wrapper<optional<mach_o::symtab_info_data>>, internal_error> mach_o::get_symtab_info() {
|
||||
if(!symtab_info.has_value() && !tried_to_load_symtab) {
|
||||
// don't try to load the symtab again if for some reason loading here fails
|
||||
tried_to_load_symtab = true;
|
||||
for(const auto& command : load_commands) {
|
||||
if(command.cmd == LC_SYMTAB) {
|
||||
symtab_info_data info;
|
||||
auto symtab = load_symbol_table_command(command.file_offset);
|
||||
if(!symtab) {
|
||||
return std::move(symtab).unwrap_error();
|
||||
}
|
||||
info.symtab = symtab.unwrap_value();
|
||||
auto string = load_string_table(info.symtab.stroff, info.symtab.strsize);
|
||||
if(!string) {
|
||||
return std::move(string).unwrap_error();
|
||||
}
|
||||
info.stringtab = std::move(string).unwrap_value();
|
||||
symtab_info = std::move(info);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::reference_wrapper<optional<symtab_info_data>>{symtab_info};
|
||||
}
|
||||
|
||||
void mach_o::print_symbol_table_entry(
|
||||
const nlist_64& entry,
|
||||
const char* stringtab,
|
||||
std::size_t stringsize,
|
||||
std::size_t j
|
||||
) const {
|
||||
const char* type = "";
|
||||
if(entry.n_type & N_STAB) {
|
||||
switch(entry.n_type) {
|
||||
case N_SO: type = "N_SO"; break;
|
||||
case N_OSO: type = "N_OSO"; break;
|
||||
case N_BNSYM: type = "N_BNSYM"; break;
|
||||
case N_ENSYM: type = "N_ENSYM"; break;
|
||||
case N_FUN: type = "N_FUN"; break;
|
||||
}
|
||||
} else if((entry.n_type & N_TYPE) == N_SECT) {
|
||||
type = "N_SECT";
|
||||
}
|
||||
fprintf(
|
||||
stderr,
|
||||
"%5llu %8llx %2llx %7s %2llu %4llx %16llx %s\n",
|
||||
to_ull(j),
|
||||
to_ull(entry.n_un.n_strx),
|
||||
to_ull(entry.n_type),
|
||||
type,
|
||||
to_ull(entry.n_sect),
|
||||
to_ull(entry.n_desc),
|
||||
to_ull(entry.n_value),
|
||||
stringtab == nullptr
|
||||
? "Stringtab error"
|
||||
: entry.n_un.n_strx < stringsize
|
||||
? stringtab + entry.n_un.n_strx
|
||||
: "String index out of bounds"
|
||||
);
|
||||
}
|
||||
|
||||
void mach_o::print_symbol_table() {
|
||||
int i = 0;
|
||||
for(const auto& command : load_commands) {
|
||||
if(command.cmd == LC_SYMTAB) {
|
||||
auto symtab_load = load_symbol_table_command(command.file_offset);
|
||||
fprintf(stderr, "Load command %d\n", i);
|
||||
if(symtab_load.is_error()) {
|
||||
fprintf(stderr, " error\n");
|
||||
symtab_load.drop_error();
|
||||
continue;
|
||||
}
|
||||
auto& symtab = symtab_load.unwrap_value();
|
||||
fprintf(stderr, " cmd %llu\n", to_ull(symtab.cmd));
|
||||
fprintf(stderr, " cmdsize %llu\n", to_ull(symtab.cmdsize));
|
||||
fprintf(stderr, " symoff 0x%llu\n", to_ull(symtab.symoff));
|
||||
fprintf(stderr, " nsyms %llu\n", to_ull(symtab.nsyms));
|
||||
fprintf(stderr, " stroff 0x%llu\n", to_ull(symtab.stroff));
|
||||
fprintf(stderr, " strsize %llu\n", to_ull(symtab.strsize));
|
||||
auto stringtab = load_string_table(symtab.stroff, symtab.strsize);
|
||||
if(!stringtab) {
|
||||
stringtab.drop_error();
|
||||
}
|
||||
for(std::size_t j = 0; j < symtab.nsyms; j++) {
|
||||
auto entry = bits == 32
|
||||
? load_symtab_entry<32>(symtab.symoff, j)
|
||||
: load_symtab_entry<64>(symtab.symoff, j);
|
||||
if(!entry) {
|
||||
fprintf(stderr, "error loading symtab entry\n");
|
||||
entry.drop_error();
|
||||
continue;
|
||||
}
|
||||
print_symbol_table_entry(
|
||||
entry.unwrap_value(),
|
||||
stringtab ? stringtab.unwrap_value().data() : nullptr,
|
||||
symtab.strsize,
|
||||
j
|
||||
);
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// produce information similar to dsymutil -dump-debug-map
|
||||
Result<mach_o::debug_map, internal_error> mach_o::get_debug_map() {
|
||||
// we have a bunch of symbols in our binary we need to pair up with symbols from various .o files
|
||||
// first collect symbols and the objects they come from
|
||||
debug_map debug_map;
|
||||
auto symtab_info_res = get_symtab_info();
|
||||
if(!symtab_info_res) {
|
||||
return std::move(symtab_info_res).unwrap_error();
|
||||
}
|
||||
if(!symtab_info_res.unwrap_value().get()) {
|
||||
return internal_error("No symtab info");
|
||||
}
|
||||
const auto& symtab_info = symtab_info_res.unwrap_value().get().unwrap();
|
||||
const auto& symtab = symtab_info.symtab;
|
||||
// TODO: Take timestamp into account?
|
||||
std::string current_module;
|
||||
optional<debug_map_entry> current_function;
|
||||
for(std::size_t j = 0; j < symtab.nsyms; j++) {
|
||||
auto load_entry = bits == 32
|
||||
? load_symtab_entry<32>(symtab.symoff, j)
|
||||
: load_symtab_entry<64>(symtab.symoff, j);
|
||||
if(!load_entry) {
|
||||
return std::move(load_entry).unwrap_error();
|
||||
}
|
||||
auto& entry = load_entry.unwrap_value();
|
||||
// entry.n_type & N_STAB indicates symbolic debug info
|
||||
if(!(entry.n_type & N_STAB)) {
|
||||
continue;
|
||||
}
|
||||
switch(entry.n_type) {
|
||||
case N_SO:
|
||||
// pass - these encode path and filename for the module, if applicable
|
||||
break;
|
||||
case N_OSO:
|
||||
{
|
||||
// sets the module
|
||||
auto str = symtab_info.get_string(entry.n_un.n_strx);
|
||||
if(!str) {
|
||||
return std::move(str).unwrap_error();
|
||||
}
|
||||
current_module = str.unwrap_value();
|
||||
}
|
||||
break;
|
||||
case N_BNSYM: break; // pass
|
||||
case N_ENSYM: break; // pass
|
||||
case N_FUN:
|
||||
{
|
||||
auto str = symtab_info.get_string(entry.n_un.n_strx);
|
||||
if(!str) {
|
||||
return std::move(str).unwrap_error();
|
||||
}
|
||||
if(str.unwrap_value()[0] == 0) {
|
||||
// end of function scope
|
||||
if(!current_function) { /**/ }
|
||||
current_function.unwrap().size = entry.n_value;
|
||||
debug_map[current_module].push_back(std::move(current_function).unwrap());
|
||||
} else {
|
||||
current_function = debug_map_entry{};
|
||||
current_function.unwrap().source_address = entry.n_value;
|
||||
current_function.unwrap().name = str.unwrap_value();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return debug_map;
|
||||
}
|
||||
|
||||
Result<const std::vector<mach_o::symbol_entry>&, internal_error> mach_o::symbol_table() {
|
||||
if(symbols) {
|
||||
return symbols.unwrap();
|
||||
}
|
||||
if(tried_to_load_symbols) {
|
||||
return internal_error("previous symbol table load failed");
|
||||
}
|
||||
tried_to_load_symbols = true;
|
||||
std::vector<symbol_entry> symbol_table;
|
||||
// we have a bunch of symbols in our binary we need to pair up with symbols from various .o files
|
||||
// first collect symbols and the objects they come from
|
||||
auto symtab_info_res = get_symtab_info();
|
||||
if(!symtab_info_res) {
|
||||
return std::move(symtab_info_res).unwrap_error();
|
||||
}
|
||||
if(!symtab_info_res.unwrap_value().get()) {
|
||||
return internal_error("No symtab info");
|
||||
}
|
||||
const auto& symtab_info = symtab_info_res.unwrap_value().get().unwrap();
|
||||
const auto& symtab = symtab_info.symtab;
|
||||
// TODO: Take timestamp into account?
|
||||
for(std::size_t j = 0; j < symtab.nsyms; j++) {
|
||||
auto load_entry = bits == 32
|
||||
? load_symtab_entry<32>(symtab.symoff, j)
|
||||
: load_symtab_entry<64>(symtab.symoff, j);
|
||||
if(!load_entry) {
|
||||
return std::move(load_entry).unwrap_error();
|
||||
}
|
||||
auto& entry = load_entry.unwrap_value();
|
||||
if(entry.n_type & N_STAB) {
|
||||
continue;
|
||||
}
|
||||
if((entry.n_type & N_TYPE) == N_SECT) {
|
||||
auto str = symtab_info.get_string(entry.n_un.n_strx);
|
||||
if(!str) {
|
||||
return std::move(str).unwrap_error();
|
||||
}
|
||||
symbol_table.push_back({
|
||||
entry.n_value,
|
||||
str.unwrap_value()
|
||||
});
|
||||
}
|
||||
}
|
||||
std::sort(
|
||||
symbol_table.begin(),
|
||||
symbol_table.end(),
|
||||
[] (const symbol_entry& a, const symbol_entry& b) { return a.address < b.address; }
|
||||
);
|
||||
symbols = std::move(symbol_table);
|
||||
return symbols.unwrap();
|
||||
}
|
||||
|
||||
optional<std::string> mach_o::lookup_symbol(frame_ptr pc) {
|
||||
auto symtab_ = symbol_table();
|
||||
if(!symtab_) {
|
||||
return nullopt;
|
||||
}
|
||||
const auto& symtab = symtab_.unwrap_value();;
|
||||
auto it = first_less_than_or_equal(
|
||||
symtab.begin(),
|
||||
symtab.end(),
|
||||
pc,
|
||||
[] (frame_ptr pc, const symbol_entry& entry) {
|
||||
return pc < entry.address;
|
||||
}
|
||||
);
|
||||
if(it == symtab.end()) {
|
||||
return nullopt;
|
||||
}
|
||||
ASSERT(pc >= it->address);
|
||||
// TODO: We subtracted one from the address so name + diff won't show up in the objdump, decide if desirable
|
||||
// to have an easier offset to lookup
|
||||
return microfmt::format("{} + {}", it->name, pc - it->address);
|
||||
}
|
||||
|
||||
// produce information similar to dsymutil -dump-debug-map
|
||||
void mach_o::print_debug_map(const debug_map& debug_map) {
|
||||
for(const auto& entry : debug_map) {
|
||||
std::cout<<entry.first<<": "<< '\n';
|
||||
for(const auto& symbol : entry.second) {
|
||||
std::cerr
|
||||
<< " "
|
||||
<< symbol.name
|
||||
<< " "
|
||||
<< std::hex
|
||||
<< symbol.source_address
|
||||
<< " "
|
||||
<< symbol.size
|
||||
<< std::dec
|
||||
<< '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<std::size_t Bits>
|
||||
Result<monostate, internal_error> mach_o::load_mach() {
|
||||
static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument");
|
||||
bits = Bits;
|
||||
using Mach_Header = typename std::conditional<Bits == 32, mach_header, mach_header_64>::type;
|
||||
std::size_t header_size = sizeof(Mach_Header);
|
||||
auto load_header = file->read<Mach_Header>(load_base);
|
||||
if(!load_header) {
|
||||
return load_header.unwrap_error();
|
||||
}
|
||||
Mach_Header& header = load_header.unwrap_value();
|
||||
magic = header.magic;
|
||||
if(should_swap()) {
|
||||
swap_mach_header(header);
|
||||
}
|
||||
cputype = header.cputype;
|
||||
cpusubtype = header.cpusubtype;
|
||||
filetype = header.filetype;
|
||||
n_load_commands = header.ncmds;
|
||||
sizeof_load_commands = header.sizeofcmds;
|
||||
flags = header.flags;
|
||||
// handle load commands
|
||||
std::uint32_t ncmds = header.ncmds;
|
||||
std::uint32_t load_commands_offset = load_base + header_size;
|
||||
// iterate load commands
|
||||
std::uint32_t actual_offset = load_commands_offset;
|
||||
for(std::uint32_t i = 0; i < ncmds; i++) {
|
||||
auto load_cmd = file->read<load_command>(actual_offset);
|
||||
if(!load_cmd) {
|
||||
return load_cmd.unwrap_error();
|
||||
}
|
||||
load_command& cmd = load_cmd.unwrap_value();
|
||||
if(should_swap()) {
|
||||
swap_load_command(&cmd, NX_UnknownByteOrder);
|
||||
}
|
||||
load_commands.push_back({ actual_offset, cmd.cmd, cmd.cmdsize });
|
||||
actual_offset += cmd.cmdsize;
|
||||
}
|
||||
return monostate{};
|
||||
}
|
||||
|
||||
Result<monostate, internal_error> mach_o::load_fat_mach() {
|
||||
std::size_t header_size = sizeof(fat_header);
|
||||
std::size_t arch_size = sizeof(fat_arch);
|
||||
auto load_header = file->read<fat_header>(0);
|
||||
if(!load_header) {
|
||||
return load_header.unwrap_error();
|
||||
}
|
||||
fat_header& header = load_header.unwrap_value();
|
||||
if(should_swap()) {
|
||||
swap_fat_header(&header, NX_UnknownByteOrder);
|
||||
}
|
||||
// thread_local static struct LP(mach_header)* mhp = _NSGetMachExecuteHeader();
|
||||
// off_t arch_offset = (off_t)header_size;
|
||||
// for(std::size_t i = 0; i < header.nfat_arch; i++) {
|
||||
// fat_arch arch = load_bytes<fat_arch>(file, arch_offset);
|
||||
// if(should_swap()) {
|
||||
// swap_fat_arch(&arch, 1, NX_UnknownByteOrder);
|
||||
// }
|
||||
// off_t mach_header_offset = (off_t)arch.offset;
|
||||
// arch_offset += arch_size;
|
||||
// std::uint32_t magic = load_bytes<std::uint32_t>(file, mach_header_offset);
|
||||
// std::cerr<<"xxx: "<<arch.cputype<<" : "<<mhp->cputype<<std::endl;
|
||||
// std::cerr<<" "<<arch.cpusubtype<<" : "<<static_cast<cpu_subtype_t>(mhp->cpusubtype & ~CPU_SUBTYPE_MASK)<<std::endl;
|
||||
// if(
|
||||
// arch.cputype == mhp->cputype &&
|
||||
// static_cast<cpu_subtype_t>(mhp->cpusubtype & ~CPU_SUBTYPE_MASK) == arch.cpusubtype
|
||||
// ) {
|
||||
// load_base = mach_header_offset;
|
||||
// fat_index = i;
|
||||
// if(is_magic_64(magic)) {
|
||||
// load_mach<64>(true);
|
||||
// } else {
|
||||
// load_mach<32>(true);
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
std::vector<fat_arch> fat_arches;
|
||||
fat_arches.reserve(header.nfat_arch);
|
||||
off_t arch_offset = (off_t)header_size;
|
||||
for(std::size_t i = 0; i < header.nfat_arch; i++) {
|
||||
auto load_arch = file->read<fat_arch>(arch_offset);
|
||||
if(!load_arch) {
|
||||
return load_arch.unwrap_error();
|
||||
}
|
||||
fat_arch& arch = load_arch.unwrap_value();
|
||||
if(should_swap()) {
|
||||
swap_fat_arch(&arch, 1, NX_UnknownByteOrder);
|
||||
}
|
||||
fat_arches.push_back(arch);
|
||||
arch_offset += arch_size;
|
||||
}
|
||||
thread_local static struct LP(mach_header)* mhp = _NSGetMachExecuteHeader();
|
||||
fat_arch* best = NXFindBestFatArch(
|
||||
mhp->cputype,
|
||||
mhp->cpusubtype,
|
||||
fat_arches.data(),
|
||||
header.nfat_arch
|
||||
);
|
||||
if(best) {
|
||||
off_t mach_header_offset = (off_t)best->offset;
|
||||
auto magic = file->read<std::uint32_t>(mach_header_offset);
|
||||
if(!magic) {
|
||||
return magic.unwrap_error();
|
||||
}
|
||||
load_base = mach_header_offset;
|
||||
fat_index = best - fat_arches.data();
|
||||
if(is_magic_64(magic.unwrap_value())) {
|
||||
load_mach<64>();
|
||||
} else {
|
||||
load_mach<32>();
|
||||
}
|
||||
return monostate{};
|
||||
}
|
||||
// If this is reached... something went wrong. The cpu we're on wasn't found.
|
||||
return internal_error("Couldn't find appropriate architecture in fat Mach-O");
|
||||
}
|
||||
|
||||
template<std::size_t Bits>
|
||||
Result<segment_command_64, internal_error> mach_o::load_segment_command(std::uint32_t offset) const {
|
||||
using Segment_Command = typename std::conditional<Bits == 32, segment_command, segment_command_64>::type;
|
||||
auto load_segment = file->read<Segment_Command>(offset);
|
||||
if(!load_segment) {
|
||||
return load_segment.unwrap_error();
|
||||
}
|
||||
Segment_Command& segment = load_segment.unwrap_value();
|
||||
ASSERT(segment.cmd == LC_SEGMENT_64 || segment.cmd == LC_SEGMENT);
|
||||
if(should_swap()) {
|
||||
swap_segment_command(segment);
|
||||
}
|
||||
// fields match just u64 instead of u32
|
||||
segment_command_64 common;
|
||||
common.cmd = segment.cmd;
|
||||
common.cmdsize = segment.cmdsize;
|
||||
static_assert(sizeof common.segname == 16 && sizeof segment.segname == 16, "xx");
|
||||
memcpy(common.segname, segment.segname, 16);
|
||||
common.vmaddr = segment.vmaddr;
|
||||
common.vmsize = segment.vmsize;
|
||||
common.fileoff = segment.fileoff;
|
||||
common.filesize = segment.filesize;
|
||||
common.maxprot = segment.maxprot;
|
||||
common.initprot = segment.initprot;
|
||||
common.nsects = segment.nsects;
|
||||
common.flags = segment.flags;
|
||||
return common;
|
||||
}
|
||||
|
||||
Result<symtab_command, internal_error> mach_o::load_symbol_table_command(std::uint32_t offset) const {
|
||||
auto load_symtab = file->read<symtab_command>(offset);
|
||||
if(!load_symtab) {
|
||||
return load_symtab.unwrap_error();
|
||||
}
|
||||
symtab_command& symtab = load_symtab.unwrap_value();
|
||||
ASSERT(symtab.cmd == LC_SYMTAB);
|
||||
if(should_swap()) {
|
||||
swap_symtab_command(&symtab, NX_UnknownByteOrder);
|
||||
}
|
||||
return symtab;
|
||||
}
|
||||
|
||||
template<std::size_t Bits>
|
||||
Result<nlist_64, internal_error> mach_o::load_symtab_entry(std::uint32_t symbol_base, std::size_t index) const {
|
||||
using Nlist = typename std::conditional<Bits == 32, struct nlist, struct nlist_64>::type;
|
||||
uint32_t offset = load_base + symbol_base + index * sizeof(Nlist);
|
||||
auto load_entry = file->read<Nlist>(offset);
|
||||
if(!load_entry) {
|
||||
return load_entry.unwrap_error();
|
||||
}
|
||||
Nlist& entry = load_entry.unwrap_value();
|
||||
if(should_swap()) {
|
||||
swap_nlist(entry);
|
||||
}
|
||||
// fields match just u64 instead of u32
|
||||
nlist_64 common;
|
||||
common.n_un.n_strx = entry.n_un.n_strx;
|
||||
common.n_type = entry.n_type;
|
||||
common.n_sect = entry.n_sect;
|
||||
common.n_desc = entry.n_desc;
|
||||
common.n_value = entry.n_value;
|
||||
return common;
|
||||
}
|
||||
|
||||
Result<std::vector<char>, internal_error> mach_o::load_string_table(std::uint32_t offset, std::uint32_t byte_count) const {
|
||||
std::vector<char> buffer(byte_count + 1);
|
||||
auto read_res = file->read_bytes(span<char>{buffer.data(), byte_count}, load_base + offset);
|
||||
if(!read_res) {
|
||||
return read_res.unwrap_error();
|
||||
}
|
||||
buffer[byte_count] = 0; // just out of an abundance of caution
|
||||
return buffer;
|
||||
}
|
||||
|
||||
bool mach_o::should_swap() const {
|
||||
return should_swap_bytes(magic);
|
||||
}
|
||||
|
||||
Result<bool, internal_error> macho_is_fat(cstring_view object_path) {
|
||||
auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter);
|
||||
if(file == nullptr) {
|
||||
return internal_error("Unable to read object file {}", object_path);
|
||||
}
|
||||
auto magic = load_bytes<std::uint32_t>(file, 0);
|
||||
if(!magic) {
|
||||
return magic.unwrap_error();
|
||||
} else {
|
||||
return is_fat_magic(magic.unwrap_value());
|
||||
}
|
||||
}
|
||||
|
||||
Result<maybe_owned<mach_o>, internal_error> open_mach_o_cached(const std::string& object_path) {
|
||||
if(object_path.empty()) {
|
||||
return internal_error{"empty object_path"};
|
||||
}
|
||||
if(get_cache_mode() == cache_mode::prioritize_memory) {
|
||||
return mach_o::open(object_path)
|
||||
.transform([](mach_o&& obj) {
|
||||
return maybe_owned<mach_o>{detail::make_unique<mach_o>(std::move(obj))};
|
||||
});
|
||||
} else {
|
||||
static 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(object_path) });
|
||||
VERIFY(res.second);
|
||||
it = res.first;
|
||||
}
|
||||
return it->second.transform([](mach_o& obj) { return maybe_owned<mach_o>(&obj); });
|
||||
}
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
#ifndef MACHO_HPP
|
||||
#define MACHO_HPP
|
||||
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "utils/span.hpp"
|
||||
#include "utils/io/base_file.hpp"
|
||||
|
||||
#if IS_APPLE
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <mach-o/arch.h>
|
||||
#include <mach-o/loader.h>
|
||||
#include <mach-o/nlist.h>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
bool file_is_mach_o(cstring_view object_path) noexcept;
|
||||
|
||||
struct load_command_entry {
|
||||
std::uint32_t file_offset;
|
||||
std::uint32_t cmd;
|
||||
std::uint32_t cmdsize;
|
||||
};
|
||||
|
||||
class mach_o {
|
||||
public:
|
||||
struct debug_map_entry {
|
||||
uint64_t source_address;
|
||||
uint64_t size;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
struct symbol_entry {
|
||||
uint64_t address;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
// map from object file to a vector of symbols to resolve
|
||||
using debug_map = std::unordered_map<std::string, std::vector<debug_map_entry>>;
|
||||
|
||||
private:
|
||||
std::unique_ptr<base_file> file;
|
||||
std::uint32_t magic;
|
||||
cpu_type_t cputype;
|
||||
cpu_subtype_t cpusubtype;
|
||||
std::uint32_t filetype;
|
||||
std::uint32_t n_load_commands;
|
||||
std::uint32_t sizeof_load_commands;
|
||||
std::uint32_t flags;
|
||||
std::size_t bits = 0; // 32 or 64 once load_mach is called
|
||||
|
||||
std::size_t load_base = 0;
|
||||
std::size_t fat_index = std::numeric_limits<std::size_t>::max();
|
||||
|
||||
std::vector<load_command_entry> load_commands;
|
||||
|
||||
struct symtab_info_data {
|
||||
symtab_command symtab;
|
||||
optional<std::vector<char>> stringtab;
|
||||
Result<const char*, internal_error> get_string(std::size_t index) const;
|
||||
};
|
||||
|
||||
bool tried_to_load_symtab = false;
|
||||
optional<symtab_info_data> symtab_info;
|
||||
|
||||
bool tried_to_load_symbols = false;
|
||||
optional<std::vector<symbol_entry>> symbols;
|
||||
|
||||
mach_o(std::unique_ptr<base_file> file, std::uint32_t magic) : file(std::move(file)), magic(magic) {}
|
||||
|
||||
Result<monostate, internal_error> load();
|
||||
|
||||
static NODISCARD Result<mach_o, internal_error> open(std::unique_ptr<base_file> file);
|
||||
|
||||
public:
|
||||
static NODISCARD Result<mach_o, internal_error> open(cstring_view object_path);
|
||||
static NODISCARD Result<mach_o, internal_error> open(cbspan object);
|
||||
|
||||
mach_o(mach_o&&) = default;
|
||||
~mach_o() = default;
|
||||
|
||||
Result<std::uintptr_t, internal_error> get_text_vmaddr();
|
||||
|
||||
std::size_t get_fat_index() const;
|
||||
|
||||
void print_segments() const;
|
||||
|
||||
struct pc_range {
|
||||
frame_ptr low;
|
||||
frame_ptr high; // not inclusive
|
||||
};
|
||||
// for in-memory JIT mach-o's
|
||||
Result<std::vector<pc_range>, internal_error> get_pc_ranges();
|
||||
|
||||
Result<std::reference_wrapper<optional<symtab_info_data>>, internal_error> get_symtab_info();
|
||||
|
||||
void print_symbol_table_entry(
|
||||
const nlist_64& entry,
|
||||
const char* stringtab,
|
||||
std::size_t stringsize,
|
||||
std::size_t j
|
||||
) const;
|
||||
|
||||
void print_symbol_table();
|
||||
|
||||
// produce information similar to dsymutil -dump-debug-map
|
||||
Result<debug_map, internal_error> get_debug_map();
|
||||
|
||||
Result<const std::vector<symbol_entry>&, internal_error> symbol_table();
|
||||
|
||||
optional<std::string> lookup_symbol(frame_ptr pc);
|
||||
|
||||
// produce information similar to dsymutil -dump-debug-map
|
||||
static void print_debug_map(const debug_map& debug_map);
|
||||
|
||||
private:
|
||||
template<std::size_t Bits>
|
||||
Result<monostate, internal_error> load_mach();
|
||||
|
||||
Result<monostate, internal_error> load_fat_mach();
|
||||
|
||||
template<std::size_t Bits>
|
||||
Result<segment_command_64, internal_error> load_segment_command(std::uint32_t offset) const;
|
||||
|
||||
Result<symtab_command, internal_error> load_symbol_table_command(std::uint32_t offset) const;
|
||||
|
||||
template<std::size_t Bits>
|
||||
Result<nlist_64, internal_error> load_symtab_entry(std::uint32_t symbol_base, std::size_t index) const;
|
||||
|
||||
Result<std::vector<char>, internal_error> load_string_table(std::uint32_t offset, std::uint32_t byte_count) const;
|
||||
|
||||
bool should_swap() const;
|
||||
};
|
||||
|
||||
Result<bool, internal_error> macho_is_fat(cstring_view object_path);
|
||||
|
||||
NODISCARD Result<maybe_owned<mach_o>, internal_error> open_mach_o_cached(const std::string& object_path);
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
#include "binary/module_base.hpp"
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
#if IS_LINUX || IS_APPLE
|
||||
#include <unistd.h>
|
||||
#include <dlfcn.h>
|
||||
#if IS_APPLE
|
||||
#include "binary/mach-o.hpp"
|
||||
#else
|
||||
#include "binary/elf.hpp"
|
||||
#endif
|
||||
#elif IS_WINDOWS
|
||||
#include "binary/pe.hpp"
|
||||
#endif
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
#if IS_LINUX
|
||||
Result<std::uintptr_t, internal_error> get_module_image_base(const std::string& object_path) {
|
||||
static std::mutex mutex;
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
static std::unordered_map<std::string, std::uintptr_t> cache;
|
||||
auto it = cache.find(object_path);
|
||||
if(it == cache.end()) {
|
||||
// arguably it'd be better to release the lock while computing this, but also arguably it's good to not
|
||||
// have two threads try to do the same computation
|
||||
auto elf_object = open_elf_cached(object_path);
|
||||
// TODO: Cache the error
|
||||
if(!elf_object) {
|
||||
return elf_object.unwrap_error();
|
||||
}
|
||||
auto base = elf_object.unwrap_value()->get_module_image_base();
|
||||
if(base.is_error()) {
|
||||
return std::move(base).unwrap_error();
|
||||
}
|
||||
cache.insert(it, {object_path, base.unwrap_value()});
|
||||
return base;
|
||||
} else {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
#elif IS_APPLE
|
||||
Result<std::uintptr_t, internal_error> get_module_image_base(const std::string& object_path) {
|
||||
// We have to parse the Mach-O to find the offset of the text section.....
|
||||
// I don't know how addresses are handled if there is more than one __TEXT load command. I'm assuming for
|
||||
// now that there is only one, and I'm using only the first section entry within that load command.
|
||||
static std::mutex mutex;
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
static std::unordered_map<std::string, std::uintptr_t> cache;
|
||||
auto it = cache.find(object_path);
|
||||
if(it == cache.end()) {
|
||||
// arguably it'd be better to release the lock while computing this, but also arguably it's good to not
|
||||
// have two threads try to do the same computation
|
||||
auto mach_o_object = open_mach_o_cached(object_path);
|
||||
// TODO: Cache the error
|
||||
if(!mach_o_object) {
|
||||
return mach_o_object.unwrap_error();
|
||||
}
|
||||
auto base = mach_o_object.unwrap_value()->get_text_vmaddr();
|
||||
if(!base) {
|
||||
return std::move(base).unwrap_error();
|
||||
}
|
||||
cache.insert(it, {object_path, base.unwrap_value()});
|
||||
return base;
|
||||
} else {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
#else // Windows
|
||||
Result<std::uintptr_t, internal_error> get_module_image_base(const std::string& object_path) {
|
||||
static std::mutex mutex;
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
static std::unordered_map<std::string, std::uintptr_t> cache;
|
||||
auto it = cache.find(object_path);
|
||||
if(it == cache.end()) {
|
||||
// arguably it'd be better to release the lock while computing this, but also arguably it's good to not
|
||||
// have two threads try to do the same computation
|
||||
auto base = pe_get_module_image_base(object_path);
|
||||
// TODO: Cache the error
|
||||
if(!base) {
|
||||
return std::move(base).unwrap_error();
|
||||
}
|
||||
cache.insert(it, {object_path, base.unwrap_value()});
|
||||
return base;
|
||||
} else {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
#ifndef IMAGE_MODULE_BASE_HPP
|
||||
#define IMAGE_MODULE_BASE_HPP
|
||||
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
Result<std::uintptr_t, internal_error> get_module_image_base(const std::string& object_path);
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,190 @@
|
|||
#include "binary/object.hpp"
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "binary/module_base.hpp"
|
||||
#include "logging.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
#if IS_LINUX || IS_APPLE
|
||||
#include <unistd.h>
|
||||
#include <dlfcn.h>
|
||||
#if IS_LINUX
|
||||
#include <link.h> // needed for dladdr1's link_map info
|
||||
#endif
|
||||
#elif IS_WINDOWS
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
#if IS_LINUX || IS_APPLE
|
||||
#if defined(CPPTRACE_HAS_DL_FIND_OBJECT) || defined(CPPTRACE_HAS_DLADDR1)
|
||||
std::string resolve_l_name(const char* l_name) {
|
||||
if(l_name != nullptr && l_name[0] != 0) {
|
||||
return l_name;
|
||||
} else {
|
||||
// empty l_name, this means it's the currently running executable
|
||||
// TODO: Caching and proper handling
|
||||
char buffer[CPPTRACE_PATH_MAX + 1]{};
|
||||
auto res = readlink("/proc/self/exe", buffer, CPPTRACE_PATH_MAX);
|
||||
if(res == -1) {
|
||||
return ""; // TODO
|
||||
} else {
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// dladdr queries are needed to get pre-ASLR addresses and targets to run symbol resolution on
|
||||
// _dl_find_object is preferred if at all possible as it is much faster (added in glibc 2.35)
|
||||
// dladdr1 is preferred if possible because it allows for a more accurate object path to be resolved (glibc 2.3.3)
|
||||
#ifdef CPPTRACE_HAS_DL_FIND_OBJECT // we don't even check for this on apple
|
||||
object_frame get_frame_object_info(frame_ptr address) {
|
||||
// Use _dl_find_object when we can, it's orders of magnitude faster
|
||||
object_frame frame;
|
||||
frame.raw_address = address;
|
||||
frame.object_address = 0;
|
||||
dl_find_object result;
|
||||
if(_dl_find_object(reinterpret_cast<void*>(address), &result) == 0) { // thread safe
|
||||
frame.object_path = resolve_l_name(result.dlfo_link_map->l_name);
|
||||
frame.object_address = address - to_frame_ptr(result.dlfo_link_map->l_addr);
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
#elif defined(CPPTRACE_HAS_DLADDR1)
|
||||
object_frame get_frame_object_info(frame_ptr address) {
|
||||
// https://github.com/bminor/glibc/blob/91695ee4598b39d181ab8df579b888a8863c4cab/elf/dl-addr.c#L26
|
||||
Dl_info info;
|
||||
link_map* link_map_info;
|
||||
object_frame frame;
|
||||
frame.raw_address = address;
|
||||
frame.object_address = 0;
|
||||
if(
|
||||
// thread safe
|
||||
dladdr1(reinterpret_cast<void*>(address), &info, reinterpret_cast<void**>(&link_map_info), RTLD_DL_LINKMAP)
|
||||
) {
|
||||
frame.object_path = resolve_l_name(link_map_info->l_name);
|
||||
auto base = get_module_image_base(frame.object_path);
|
||||
if(base.has_value()) {
|
||||
frame.object_address = address
|
||||
- reinterpret_cast<std::uintptr_t>(info.dli_fbase)
|
||||
+ base.unwrap_value();
|
||||
} else {
|
||||
if(!should_absorb_trace_exceptions()) {
|
||||
base.drop_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
#else
|
||||
// glibc dladdr may not return an accurate dli_fname as it uses argv[0] for addresses in the main executable
|
||||
// https://github.com/bminor/glibc/blob/caed1f5c0b2e31b5f4e0f21fea4b2c9ecd3b5b30/elf/dl-addr.c#L33-L36
|
||||
// macos doesn't have dladdr1 but its dli_fname behaves more sensibly, same with some other libc's like musl
|
||||
object_frame get_frame_object_info(frame_ptr address) {
|
||||
// reference: https://github.com/bminor/glibc/blob/master/debug/backtracesyms.c
|
||||
Dl_info info;
|
||||
object_frame frame;
|
||||
frame.raw_address = address;
|
||||
frame.object_address = 0;
|
||||
if(dladdr(reinterpret_cast<void*>(address), &info)) { // thread safe
|
||||
frame.object_path = info.dli_fname;
|
||||
auto base = get_module_image_base(info.dli_fname);
|
||||
if(base.has_value()) {
|
||||
frame.object_address = address
|
||||
- reinterpret_cast<std::uintptr_t>(info.dli_fbase)
|
||||
+ base.unwrap_value();
|
||||
} else {
|
||||
if(!should_absorb_trace_exceptions()) {
|
||||
base.drop_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
std::string get_module_name(HMODULE handle) {
|
||||
static std::mutex mutex;
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
static std::unordered_map<HMODULE, std::string> cache;
|
||||
auto it = cache.find(handle);
|
||||
if(it == cache.end()) {
|
||||
char path[MAX_PATH];
|
||||
if(GetModuleFileNameA(handle, path, sizeof(path))) {
|
||||
cache.insert(it, {handle, path});
|
||||
return path;
|
||||
} else {
|
||||
log::error(std::system_error(GetLastError(), std::system_category()).what());
|
||||
cache.insert(it, {handle, ""});
|
||||
return "";
|
||||
}
|
||||
} else {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
|
||||
object_frame get_frame_object_info(frame_ptr address) {
|
||||
object_frame frame;
|
||||
frame.raw_address = address;
|
||||
frame.object_address = 0;
|
||||
HMODULE handle;
|
||||
// Multithread safe as long as another thread doesn't come along and free the module
|
||||
if(GetModuleHandleExA(
|
||||
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
|
||||
reinterpret_cast<const char*>(address),
|
||||
&handle
|
||||
)) {
|
||||
frame.object_path = get_module_name(handle);
|
||||
auto base = get_module_image_base(frame.object_path);
|
||||
if(base.has_value()) {
|
||||
frame.object_address = address
|
||||
- reinterpret_cast<std::uintptr_t>(handle)
|
||||
+ base.unwrap_value();
|
||||
} else {
|
||||
if(!should_absorb_trace_exceptions()) {
|
||||
base.drop_error();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::error(std::system_error(GetLastError(), std::system_category()).what());
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::vector<object_frame> get_frames_object_info(const std::vector<frame_ptr>& addresses) {
|
||||
std::vector<object_frame> frames;
|
||||
frames.reserve(addresses.size());
|
||||
for(const frame_ptr address : addresses) {
|
||||
frames.push_back(get_frame_object_info(address));
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
|
||||
object_frame resolve_safe_object_frame(const safe_object_frame& frame) {
|
||||
std::string object_path = frame.object_path;
|
||||
if(object_path.empty()) {
|
||||
return {
|
||||
frame.raw_address,
|
||||
0,
|
||||
""
|
||||
};
|
||||
}
|
||||
return {
|
||||
frame.raw_address,
|
||||
frame.address_relative_to_object_start,
|
||||
std::move(object_path)
|
||||
};
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
#ifndef OBJECT_HPP
|
||||
#define OBJECT_HPP
|
||||
|
||||
#include <cpptrace/forward.hpp>
|
||||
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
object_frame get_frame_object_info(frame_ptr address);
|
||||
|
||||
std::vector<object_frame> get_frames_object_info(const std::vector<frame_ptr>& addresses);
|
||||
|
||||
object_frame resolve_safe_object_frame(const safe_object_frame& frame);
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
#include "binary/pe.hpp"
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#if IS_WINDOWS
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
|
||||
T pe_byteswap_if_needed(T value) {
|
||||
// PE header values are little endian, I think dos e_lfanew should be too
|
||||
if(!is_little_endian()) {
|
||||
return byteswap(value);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
Result<std::uintptr_t, internal_error> pe_get_module_image_base(cstring_view object_path) {
|
||||
// https://drive.google.com/file/d/0B3_wGJkuWLytbnIxY1J5WUs4MEk/view?pli=1&resourcekey=0-n5zZ2UW39xVTH8ZSu6C2aQ
|
||||
// https://0xrick.github.io/win-internals/pe3/
|
||||
// Endianness should always be little for dos and pe headers
|
||||
std::FILE* file_ptr;
|
||||
errno_t ret = fopen_s(&file_ptr, object_path.c_str(), "rb");
|
||||
auto file = raii_wrap(std::move(file_ptr), file_deleter);
|
||||
if(ret != 0 || file == nullptr) {
|
||||
return internal_error("Unable to read object file {}", object_path);
|
||||
}
|
||||
auto magic = load_bytes<std::array<char, 2>>(file, 0);
|
||||
if(!magic) {
|
||||
return std::move(magic).unwrap_error();
|
||||
}
|
||||
if(std::memcmp(magic.unwrap_value().data(), "MZ", 2) != 0) {
|
||||
return internal_error("File is not a PE file {}", object_path);
|
||||
}
|
||||
auto e_lfanew = load_bytes<DWORD>(file, 0x3c); // dos header + 0x3c
|
||||
if(!e_lfanew) {
|
||||
return std::move(e_lfanew).unwrap_error();
|
||||
}
|
||||
DWORD nt_header_offset = pe_byteswap_if_needed(e_lfanew.unwrap_value());
|
||||
auto signature = load_bytes<std::array<char, 4>>(file, nt_header_offset); // nt header + 0
|
||||
if(!signature) {
|
||||
return std::move(signature).unwrap_error();
|
||||
}
|
||||
if(std::memcmp(signature.unwrap_value().data(), "PE\0\0", 4) != 0) {
|
||||
return internal_error("File is not a PE file {}", object_path);
|
||||
}
|
||||
auto size_of_optional_header_raw = load_bytes<WORD>(file, nt_header_offset + 4 + 0x10); // file header + 0x10
|
||||
if(!size_of_optional_header_raw) {
|
||||
return std::move(size_of_optional_header_raw).unwrap_error();
|
||||
}
|
||||
WORD size_of_optional_header = pe_byteswap_if_needed(size_of_optional_header_raw.unwrap_value());
|
||||
if(size_of_optional_header == 0) {
|
||||
return internal_error("Unexpected optional header size for PE file");
|
||||
}
|
||||
auto optional_header_magic_raw = load_bytes<WORD>(file, nt_header_offset + 0x18); // optional header + 0x0
|
||||
if(!optional_header_magic_raw) {
|
||||
return std::move(optional_header_magic_raw).unwrap_error();
|
||||
}
|
||||
WORD optional_header_magic = pe_byteswap_if_needed(optional_header_magic_raw.unwrap_value());
|
||||
VERIFY(
|
||||
optional_header_magic == IMAGE_NT_OPTIONAL_HDR_MAGIC,
|
||||
("PE file does not match expected bit-mode " + std::string(object_path)).c_str()
|
||||
);
|
||||
// finally get image base
|
||||
if(optional_header_magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
|
||||
// 32 bit
|
||||
auto bytes = load_bytes<DWORD>(file, nt_header_offset + 0x18 + 0x1c); // optional header + 0x1c
|
||||
if(!bytes) {
|
||||
return std::move(bytes).unwrap_error();
|
||||
}
|
||||
return to<std::uintptr_t>(pe_byteswap_if_needed(bytes.unwrap_value()));
|
||||
} else {
|
||||
// 64 bit
|
||||
// I get an "error: 'QWORD' was not declared in this scope" for some reason when using QWORD
|
||||
auto bytes = load_bytes<std::uint64_t>(file, nt_header_offset + 0x18 + 0x18); // optional header + 0x18
|
||||
if(!bytes) {
|
||||
return std::move(bytes).unwrap_error();
|
||||
}
|
||||
return to<std::uintptr_t>(pe_byteswap_if_needed(bytes.unwrap_value()));
|
||||
}
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
#ifndef PE_HPP
|
||||
#define PE_HPP
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#if IS_WINDOWS
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
Result<std::uintptr_t, internal_error> pe_get_module_image_base(cstring_view object_path);
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
#include "binary/safe_dl.hpp"
|
||||
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "platform/program_name.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#ifdef CPPTRACE_HAS_DL_FIND_OBJECT
|
||||
#if IS_LINUX || IS_APPLE
|
||||
#include <unistd.h>
|
||||
#include <dlfcn.h>
|
||||
#include <link.h>
|
||||
#endif
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
void get_safe_object_frame(frame_ptr address, safe_object_frame* out) {
|
||||
out->raw_address = address;
|
||||
dl_find_object result;
|
||||
if(_dl_find_object(reinterpret_cast<void*>(address), &result) == 0) { // thread-safe, signal-safe
|
||||
out->address_relative_to_object_start = address - to_frame_ptr(result.dlfo_link_map->l_addr);
|
||||
if(result.dlfo_link_map->l_name != nullptr && result.dlfo_link_map->l_name[0] != 0) {
|
||||
std::size_t path_length = std::strlen(result.dlfo_link_map->l_name);
|
||||
std::memcpy(
|
||||
out->object_path,
|
||||
result.dlfo_link_map->l_name,
|
||||
std::min(path_length + 1, std::size_t(CPPTRACE_PATH_MAX + 1))
|
||||
);
|
||||
} else {
|
||||
// empty l_name, this means it's the currently running executable
|
||||
memset(out->object_path, 0, CPPTRACE_PATH_MAX + 1);
|
||||
// signal-safe
|
||||
auto res = readlink("/proc/self/exe", out->object_path, CPPTRACE_PATH_MAX);
|
||||
if(res == -1) {
|
||||
// error handling?
|
||||
}
|
||||
// TODO: Special handling for /proc/pid/exe unlink edge case
|
||||
}
|
||||
} else {
|
||||
out->address_relative_to_object_start = 0;
|
||||
out->object_path[0] = 0;
|
||||
}
|
||||
// TODO: Handle this part of the documentation?
|
||||
// The address can be a code address or data address. On architectures using function descriptors, no attempt is
|
||||
// made to decode the function descriptor. Depending on how these descriptors are implemented, _dl_find_object
|
||||
// may return the object that defines the function descriptor (and not the object that contains the code
|
||||
// implementing the function), or fail to find any object at all.
|
||||
}
|
||||
|
||||
bool has_get_safe_object_frame() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
#else
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
void get_safe_object_frame(frame_ptr address, safe_object_frame* out) {
|
||||
out->raw_address = address;
|
||||
out->address_relative_to_object_start = 0;
|
||||
out->object_path[0] = 0;
|
||||
}
|
||||
|
||||
bool has_get_safe_object_frame() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
#endif
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
#ifndef SAFE_DL_HPP
|
||||
#define SAFE_DL_HPP
|
||||
|
||||
#include "utils/common.hpp"
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
|
||||
|
||||
bool has_get_safe_object_frame();
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,321 @@
|
|||
#include <cpptrace/cpptrace.hpp>
|
||||
#include <cpptrace/formatting.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
|
||||
#include "cpptrace/basic.hpp"
|
||||
#include "jit/jit_objects.hpp"
|
||||
#include "symbols/symbols.hpp"
|
||||
#include "unwind/unwind.hpp"
|
||||
#include "demangle/demangle.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/microfmt.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "binary/object.hpp"
|
||||
#include "binary/safe_dl.hpp"
|
||||
#include "snippets/snippet.hpp"
|
||||
#include "options.hpp"
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
raw_trace raw_trace::current(std::size_t skip) {
|
||||
try { // try/catch can never be hit but it's needed to prevent TCO
|
||||
return generate_raw_trace(skip + 1);
|
||||
} catch(...) {
|
||||
detail::log_and_maybe_propagate_exception(std::current_exception());
|
||||
return raw_trace{};
|
||||
}
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
raw_trace raw_trace::current(std::size_t skip, std::size_t max_depth) {
|
||||
try { // try/catch can never be hit but it's needed to prevent TCO
|
||||
return generate_raw_trace(skip + 1, max_depth);
|
||||
} catch(...) {
|
||||
detail::log_and_maybe_propagate_exception(std::current_exception());
|
||||
return raw_trace{};
|
||||
}
|
||||
}
|
||||
|
||||
object_trace raw_trace::resolve_object_trace() const {
|
||||
try {
|
||||
return object_trace{detail::get_frames_object_info(frames)};
|
||||
} catch(...) { // NOSONAR
|
||||
detail::log_and_maybe_propagate_exception(std::current_exception());
|
||||
return object_trace{};
|
||||
}
|
||||
}
|
||||
|
||||
stacktrace raw_trace::resolve() const {
|
||||
try {
|
||||
std::vector<stacktrace_frame> trace = detail::resolve_frames(frames);
|
||||
for(auto& frame : trace) {
|
||||
frame.symbol = detail::demangle(frame.symbol, true);
|
||||
}
|
||||
return {std::move(trace)};
|
||||
} catch(...) { // NOSONAR
|
||||
detail::log_and_maybe_propagate_exception(std::current_exception());
|
||||
return stacktrace{};
|
||||
}
|
||||
}
|
||||
|
||||
void raw_trace::clear() {
|
||||
frames.clear();
|
||||
}
|
||||
|
||||
bool raw_trace::empty() const noexcept {
|
||||
return frames.empty();
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
object_trace object_trace::current(std::size_t skip) {
|
||||
try { // try/catch can never be hit but it's needed to prevent TCO
|
||||
return generate_object_trace(skip + 1);
|
||||
} catch(...) {
|
||||
detail::log_and_maybe_propagate_exception(std::current_exception());
|
||||
return object_trace{};
|
||||
}
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
object_trace object_trace::current(std::size_t skip, std::size_t max_depth) {
|
||||
try { // try/catch can never be hit but it's needed to prevent TCO
|
||||
return generate_object_trace(skip + 1, max_depth);
|
||||
} catch(...) {
|
||||
detail::log_and_maybe_propagate_exception(std::current_exception());
|
||||
return object_trace{};
|
||||
}
|
||||
}
|
||||
|
||||
stacktrace object_trace::resolve() const {
|
||||
try {
|
||||
std::vector<stacktrace_frame> trace = detail::resolve_frames(frames);
|
||||
for(auto& frame : trace) {
|
||||
frame.symbol = detail::demangle(frame.symbol, true);
|
||||
}
|
||||
return {std::move(trace)};
|
||||
} catch(...) { // NOSONAR
|
||||
detail::log_and_maybe_propagate_exception(std::current_exception());
|
||||
return stacktrace();
|
||||
}
|
||||
}
|
||||
|
||||
void object_trace::clear() {
|
||||
frames.clear();
|
||||
}
|
||||
|
||||
bool object_trace::empty() const noexcept {
|
||||
return frames.empty();
|
||||
}
|
||||
|
||||
object_frame stacktrace_frame::get_object_info() const {
|
||||
return detail::get_frame_object_info(raw_address);
|
||||
}
|
||||
|
||||
std::string stacktrace_frame::to_string() const {
|
||||
return to_string(false);
|
||||
}
|
||||
|
||||
std::string stacktrace_frame::to_string(bool color) const {
|
||||
return get_default_formatter().format(*this, color);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const stacktrace_frame& frame) {
|
||||
return stream << frame.to_string();
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
stacktrace stacktrace::current(std::size_t skip) {
|
||||
try { // try/catch can never be hit but it's needed to prevent TCO
|
||||
return generate_trace(skip + 1);
|
||||
} catch(...) {
|
||||
detail::log_and_maybe_propagate_exception(std::current_exception());
|
||||
return stacktrace{};
|
||||
}
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
stacktrace stacktrace::current(std::size_t skip, std::size_t max_depth) {
|
||||
try { // try/catch can never be hit but it's needed to prevent TCO
|
||||
return generate_trace(skip + 1, max_depth);
|
||||
} catch(...) {
|
||||
detail::log_and_maybe_propagate_exception(std::current_exception());
|
||||
return stacktrace{};
|
||||
}
|
||||
}
|
||||
|
||||
void stacktrace::print() const {
|
||||
get_default_formatter().print(*this);
|
||||
}
|
||||
|
||||
void stacktrace::print(std::ostream& stream) const {
|
||||
get_default_formatter().print(stream, *this);
|
||||
}
|
||||
|
||||
void stacktrace::print(std::ostream& stream, bool color) const {
|
||||
get_default_formatter().print(stream, *this, color);
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
const formatter& get_default_snippet_formatter() {
|
||||
static formatter snippet_formatter = formatter{}.snippets(true);
|
||||
return snippet_formatter;
|
||||
}
|
||||
}
|
||||
|
||||
void stacktrace::print_with_snippets() const {
|
||||
detail::get_default_snippet_formatter().print(*this);
|
||||
}
|
||||
|
||||
void stacktrace::print_with_snippets(std::ostream& stream) const {
|
||||
detail::get_default_snippet_formatter().print(stream, *this);
|
||||
}
|
||||
|
||||
void stacktrace::print_with_snippets(std::ostream& stream, bool color) const {
|
||||
detail::get_default_snippet_formatter().print(stream, *this, color);
|
||||
}
|
||||
|
||||
void stacktrace::clear() {
|
||||
frames.clear();
|
||||
}
|
||||
|
||||
bool stacktrace::empty() const noexcept {
|
||||
return frames.empty();
|
||||
}
|
||||
|
||||
std::string stacktrace::to_string(bool color) const {
|
||||
return get_default_formatter().format(*this, color);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const stacktrace& trace) {
|
||||
get_default_formatter().print(stream, trace);
|
||||
return stream;
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
raw_trace generate_raw_trace(std::size_t skip) {
|
||||
try {
|
||||
return raw_trace{detail::capture_frames(skip + 1, SIZE_MAX)};
|
||||
} catch(...) { // NOSONAR
|
||||
detail::log_and_maybe_propagate_exception(std::current_exception());
|
||||
return raw_trace{};
|
||||
}
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
raw_trace generate_raw_trace(std::size_t skip, std::size_t max_depth) {
|
||||
try {
|
||||
return raw_trace{detail::capture_frames(skip + 1, max_depth)};
|
||||
} catch(...) { // NOSONAR
|
||||
detail::log_and_maybe_propagate_exception(std::current_exception());
|
||||
return raw_trace{};
|
||||
}
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
std::size_t safe_generate_raw_trace(frame_ptr* buffer, std::size_t size, std::size_t skip) {
|
||||
try { // try/catch can never be hit but it's needed to prevent TCO
|
||||
return detail::safe_capture_frames(buffer, size, skip + 1, SIZE_MAX);
|
||||
} catch(...) {
|
||||
detail::log_and_maybe_propagate_exception(std::current_exception());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
std::size_t safe_generate_raw_trace(
|
||||
frame_ptr* buffer,
|
||||
std::size_t size,
|
||||
std::size_t skip,
|
||||
std::size_t max_depth
|
||||
) {
|
||||
try { // try/catch can never be hit but it's needed to prevent TCO
|
||||
return detail::safe_capture_frames(buffer, size, skip + 1, max_depth);
|
||||
} catch(...) {
|
||||
detail::log_and_maybe_propagate_exception(std::current_exception());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
object_trace generate_object_trace(std::size_t skip) {
|
||||
try {
|
||||
return object_trace{detail::get_frames_object_info(detail::capture_frames(skip + 1, SIZE_MAX))};
|
||||
} catch(...) { // NOSONAR
|
||||
detail::log_and_maybe_propagate_exception(std::current_exception());
|
||||
return object_trace{};
|
||||
}
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
object_trace generate_object_trace(std::size_t skip, std::size_t max_depth) {
|
||||
try {
|
||||
return object_trace{detail::get_frames_object_info(detail::capture_frames(skip + 1, max_depth))};
|
||||
} catch(...) { // NOSONAR
|
||||
detail::log_and_maybe_propagate_exception(std::current_exception());
|
||||
return object_trace{};
|
||||
}
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
stacktrace generate_trace(std::size_t skip) {
|
||||
try { // try/catch can never be hit but it's needed to prevent TCO
|
||||
return generate_trace(skip + 1, SIZE_MAX);
|
||||
} catch(...) {
|
||||
detail::log_and_maybe_propagate_exception(std::current_exception());
|
||||
return stacktrace{};
|
||||
}
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
stacktrace generate_trace(std::size_t skip, std::size_t max_depth) {
|
||||
try {
|
||||
std::vector<frame_ptr> frames = detail::capture_frames(skip + 1, max_depth);
|
||||
std::vector<stacktrace_frame> trace = detail::resolve_frames(frames);
|
||||
for(auto& frame : trace) {
|
||||
frame.symbol = detail::demangle(frame.symbol, true);
|
||||
}
|
||||
return {std::move(trace)};
|
||||
} catch(...) { // NOSONAR
|
||||
detail::log_and_maybe_propagate_exception(std::current_exception());
|
||||
return stacktrace();
|
||||
}
|
||||
}
|
||||
|
||||
object_frame safe_object_frame::resolve() const {
|
||||
return detail::resolve_safe_object_frame(*this);
|
||||
}
|
||||
|
||||
void get_safe_object_frame(frame_ptr address, safe_object_frame* out) {
|
||||
detail::get_safe_object_frame(address, out);
|
||||
}
|
||||
|
||||
bool can_signal_safe_unwind() {
|
||||
return detail::has_safe_unwind();
|
||||
}
|
||||
|
||||
bool can_get_safe_object_frame() {
|
||||
return detail::has_get_safe_object_frame();
|
||||
}
|
||||
|
||||
void register_jit_object(const char* ptr, std::size_t size) {
|
||||
detail::register_jit_object(ptr, size);
|
||||
}
|
||||
|
||||
void unregister_jit_object(const char* ptr) {
|
||||
detail::unregister_jit_object(ptr);
|
||||
}
|
||||
|
||||
void clear_all_jit_objects() {
|
||||
detail::clear_all_jit_objects();
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
module;
|
||||
#include <cpptrace/basic.hpp>
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
#include <cpptrace/exceptions.hpp>
|
||||
#include <cpptrace/formatting.hpp>
|
||||
#include <cpptrace/forward.hpp>
|
||||
#include <cpptrace/from_current.hpp>
|
||||
|
||||
export module cpptrace;
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
// cpptrace/basic
|
||||
export using cpptrace::raw_trace;
|
||||
export using cpptrace::object_frame;
|
||||
export using cpptrace::object_trace;
|
||||
export using cpptrace::nullable;
|
||||
export using cpptrace::stacktrace_frame;
|
||||
export using cpptrace::stacktrace;
|
||||
export using cpptrace::generate_raw_trace;
|
||||
export using cpptrace::generate_object_trace;
|
||||
export using cpptrace::generate_trace;
|
||||
export using cpptrace::safe_generate_raw_trace;
|
||||
export using cpptrace::safe_object_frame;
|
||||
export using cpptrace::can_get_safe_object_frame;
|
||||
export using cpptrace::can_signal_safe_unwind;
|
||||
export using cpptrace::can_get_safe_object_frame;
|
||||
export using cpptrace::register_jit_object;
|
||||
export using cpptrace::unregister_jit_object;
|
||||
export using cpptrace::clear_all_jit_objects;
|
||||
|
||||
// cpptrace/exceptions
|
||||
export using cpptrace::exception;
|
||||
export using cpptrace::lazy_exception;
|
||||
export using cpptrace::exception_with_message;
|
||||
export using cpptrace::logic_error;
|
||||
export using cpptrace::domain_error;
|
||||
export using cpptrace::invalid_argument;
|
||||
export using cpptrace::length_error;
|
||||
export using cpptrace::out_of_range;
|
||||
export using cpptrace::runtime_error;
|
||||
export using cpptrace::range_error;
|
||||
export using cpptrace::overflow_error;
|
||||
export using cpptrace::underflow_error;
|
||||
export using cpptrace::nested_exception;
|
||||
export using cpptrace::system_error;
|
||||
export using cpptrace::rethrow_and_wrap_if_needed;
|
||||
|
||||
// cpptrace/formatting
|
||||
export using cpptrace::basename;
|
||||
export using cpptrace::prettify_symbol;
|
||||
export using cpptrace::formatter;
|
||||
export using cpptrace::get_default_formatter;
|
||||
|
||||
// cpptrace/forward
|
||||
export using cpptrace::frame_ptr;
|
||||
|
||||
// cpptrace/from_current.hpp
|
||||
export using cpptrace::raw_trace_from_current_exception;
|
||||
export using cpptrace::from_current_exception;
|
||||
export using cpptrace::raw_trace_from_current_exception_rethrow;
|
||||
export using cpptrace::from_current_exception_rethrow;
|
||||
export using cpptrace::current_exception_was_rethrown;
|
||||
export using cpptrace::rethrow;
|
||||
export using cpptrace::clear_current_exception_traces;
|
||||
export using cpptrace::try_catch;
|
||||
|
||||
namespace detail {
|
||||
#ifdef _MSC_VER
|
||||
export using cpptrace::detail::argument;
|
||||
export using cpptrace::detail::exception_filter;
|
||||
#else
|
||||
export using cpptrace::detail::unwind_interceptor;
|
||||
export using cpptrace::detail::unwind_interceptor_for;
|
||||
export using cpptrace::detail::nop;
|
||||
#endif
|
||||
}
|
||||
|
||||
// cpptrace/io
|
||||
export using cpptrace::operator<<; // FIXME: make hidden friend
|
||||
|
||||
// cpptrace/utils
|
||||
export using cpptrace::demangle;
|
||||
export using cpptrace::prune_symbol;
|
||||
export using cpptrace::get_snippet;
|
||||
export using cpptrace::isatty;
|
||||
export using cpptrace::stdin_fileno;
|
||||
export using cpptrace::stderr_fileno;
|
||||
export using cpptrace::stdout_fileno;
|
||||
export using cpptrace::register_terminate_handler;
|
||||
export using cpptrace::absorb_trace_exceptions;
|
||||
export using cpptrace::enable_inlined_call_resolution;
|
||||
export using cpptrace::cache_mode;
|
||||
export using cpptrace::log_level;
|
||||
export using cpptrace::set_log_level;
|
||||
export using cpptrace::set_log_callback;
|
||||
export using cpptrace::use_default_stderr_logger;
|
||||
export using cpptrace::to_string;
|
||||
export using cpptrace::cache_mode;
|
||||
|
||||
namespace experimental {
|
||||
export using cpptrace::experimental::set_cache_mode;
|
||||
export using cpptrace::experimental::set_dwarf_resolver_line_table_cache_size;
|
||||
export using cpptrace::experimental::set_dwarf_resolver_disable_aranges;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
export using cpptrace::load_symbols_for_file;
|
||||
#endif
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
|
@ -0,0 +1,442 @@
|
|||
#include <ctrace/ctrace.h>
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
#include <algorithm>
|
||||
|
||||
#include "symbols/symbols.hpp"
|
||||
#include "unwind/unwind.hpp"
|
||||
#include "demangle/demangle.hpp"
|
||||
#include "platform/exception_type.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "binary/object.hpp"
|
||||
#include "binary/safe_dl.hpp"
|
||||
#include "utils/string_view.hpp"
|
||||
|
||||
#define ESC "\033["
|
||||
#define RESET ESC "0m"
|
||||
#define RED ESC "31m"
|
||||
#define GREEN ESC "32m"
|
||||
#define YELLOW ESC "33m"
|
||||
#define BLUE ESC "34m"
|
||||
#define MAGENTA ESC "35m"
|
||||
#define CYAN ESC "36m"
|
||||
|
||||
#if defined(__GNUC__) && ((__GNUC__ > 2) || (__GNUC__ == 2 && __GNUC_MINOR__ >= 6))
|
||||
# define CTRACE_GNU_FORMAT(...) __attribute__((format(__VA_ARGS__)))
|
||||
#elif defined(__clang__)
|
||||
// Probably requires llvm >3.5? Not exactly sure.
|
||||
# define CTRACE_GNU_FORMAT(...) __attribute__((format(__VA_ARGS__)))
|
||||
#else
|
||||
# define CTRACE_GNU_FORMAT(...)
|
||||
#endif
|
||||
|
||||
#if defined(__clang__)
|
||||
# define CTRACE_FORMAT_PROLOGUE \
|
||||
_Pragma("clang diagnostic push") \
|
||||
_Pragma("clang diagnostic ignored \"-Wformat-security\"")
|
||||
# define CTRACE_FORMAT_EPILOGUE \
|
||||
_Pragma("clang diagnostic pop")
|
||||
#elif defined(__GNUC_MINOR__)
|
||||
# define CTRACE_FORMAT_PROLOGUE \
|
||||
_Pragma("GCC diagnostic push") \
|
||||
_Pragma("GCC diagnostic ignored \"-Wformat-security\"")
|
||||
# define CTRACE_FORMAT_EPILOGUE \
|
||||
_Pragma("GCC diagnostic pop")
|
||||
#else
|
||||
# define CTRACE_FORMAT_PROLOGUE
|
||||
# define CTRACE_FORMAT_EPILOGUE
|
||||
#endif
|
||||
|
||||
namespace ctrace {
|
||||
static constexpr std::uint32_t invalid_pos = ~0U;
|
||||
|
||||
CTRACE_FORMAT_PROLOGUE
|
||||
template <typename...Args>
|
||||
CTRACE_GNU_FORMAT(printf, 2, 0)
|
||||
static void ffprintf(std::FILE* f, const char fmt[], Args&&...args) {
|
||||
(void)std::fprintf(f, fmt, args...);
|
||||
(void)fflush(f);
|
||||
}
|
||||
CTRACE_FORMAT_EPILOGUE
|
||||
|
||||
static bool is_empty(std::uint32_t pos) noexcept {
|
||||
return pos == invalid_pos;
|
||||
}
|
||||
|
||||
static bool is_empty(const char* str) noexcept {
|
||||
return !str || std::char_traits<char>::length(str) == 0;
|
||||
}
|
||||
|
||||
static ctrace_owning_string generate_owning_string(cpptrace::detail::string_view raw_string) noexcept {
|
||||
// Returns length to the null terminator.
|
||||
char* new_string = new char[raw_string.size() + 1];
|
||||
std::char_traits<char>::copy(new_string, raw_string.data(), raw_string.size());
|
||||
new_string[raw_string.size()] = '\0';
|
||||
return { new_string };
|
||||
}
|
||||
|
||||
static void free_owning_string(const char* owned_string) noexcept {
|
||||
if(!owned_string) return; // Not necessary but eh
|
||||
delete[] owned_string;
|
||||
}
|
||||
|
||||
static void free_owning_string(ctrace_owning_string& owned_string) noexcept {
|
||||
free_owning_string(owned_string.data);
|
||||
}
|
||||
|
||||
static ctrace_object_frame convert_object_frame(const cpptrace::object_frame& frame) {
|
||||
const char* new_path = generate_owning_string(frame.object_path).data;
|
||||
return { frame.raw_address, frame.object_address, new_path };
|
||||
}
|
||||
|
||||
static ctrace_object_trace c_convert(const std::vector<cpptrace::object_frame>& trace) {
|
||||
std::size_t count = trace.size();
|
||||
auto* frames = new ctrace_object_frame[count];
|
||||
std::transform(trace.begin(), trace.end(), frames, convert_object_frame);
|
||||
return { frames, count };
|
||||
}
|
||||
|
||||
static ctrace_stacktrace_frame convert_stacktrace_frame(const cpptrace::stacktrace_frame& frame) {
|
||||
ctrace_stacktrace_frame new_frame;
|
||||
new_frame.raw_address = frame.raw_address;
|
||||
new_frame.object_address = frame.object_address;
|
||||
new_frame.line = frame.line.value_or(invalid_pos);
|
||||
new_frame.column = frame.column.value_or(invalid_pos);
|
||||
new_frame.filename = generate_owning_string(frame.filename).data;
|
||||
new_frame.symbol = generate_owning_string(cpptrace::detail::demangle(frame.symbol, true)).data;
|
||||
new_frame.is_inline = ctrace_bool(frame.is_inline);
|
||||
return new_frame;
|
||||
}
|
||||
|
||||
static cpptrace::stacktrace_frame convert_stacktrace_frame(const ctrace_stacktrace_frame& frame) {
|
||||
using nullable_type = cpptrace::nullable<std::uint32_t>;
|
||||
static constexpr auto null_v = nullable_type::null().raw_value;
|
||||
cpptrace::stacktrace_frame new_frame;
|
||||
new_frame.raw_address = frame.raw_address;
|
||||
new_frame.object_address = frame.object_address;
|
||||
new_frame.line = nullable_type{is_empty(frame.line) ? null_v : frame.line};
|
||||
new_frame.column = nullable_type{is_empty(frame.column) ? null_v : frame.column};
|
||||
new_frame.filename = frame.filename;
|
||||
new_frame.symbol = frame.symbol;
|
||||
new_frame.is_inline = bool(frame.is_inline);
|
||||
return new_frame;
|
||||
}
|
||||
|
||||
static ctrace_stacktrace c_convert(const std::vector<cpptrace::stacktrace_frame>& trace) {
|
||||
std::size_t count = trace.size();
|
||||
auto* frames = new ctrace_stacktrace_frame[count];
|
||||
std::transform(
|
||||
trace.begin(),
|
||||
trace.end(), frames,
|
||||
static_cast<ctrace_stacktrace_frame(*)(const cpptrace::stacktrace_frame&)>(convert_stacktrace_frame)
|
||||
);
|
||||
return { frames, count };
|
||||
}
|
||||
|
||||
static cpptrace::stacktrace cpp_convert(const ctrace_stacktrace* ptrace) {
|
||||
if(!ptrace || !ptrace->frames) {
|
||||
return { };
|
||||
}
|
||||
std::vector<cpptrace::stacktrace_frame> new_frames;
|
||||
new_frames.reserve(ptrace->count);
|
||||
for(std::size_t i = 0; i < ptrace->count; ++i) {
|
||||
new_frames.push_back(convert_stacktrace_frame(ptrace->frames[i]));
|
||||
}
|
||||
return cpptrace::stacktrace{std::move(new_frames)};
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
// ctrace::string
|
||||
ctrace_owning_string ctrace_generate_owning_string(const char* raw_string) {
|
||||
return ctrace::generate_owning_string(raw_string);
|
||||
}
|
||||
|
||||
void ctrace_free_owning_string(ctrace_owning_string* string) {
|
||||
if(!string) {
|
||||
return;
|
||||
}
|
||||
ctrace::free_owning_string(*string);
|
||||
string->data = nullptr;
|
||||
}
|
||||
|
||||
// ctrace::generation:
|
||||
CTRACE_FORCE_NO_INLINE
|
||||
ctrace_raw_trace ctrace_generate_raw_trace(size_t skip, size_t max_depth) {
|
||||
try {
|
||||
std::vector<cpptrace::frame_ptr> trace = cpptrace::detail::capture_frames(skip + 1, max_depth);
|
||||
std::size_t count = trace.size();
|
||||
auto* frames = new ctrace_frame_ptr[count];
|
||||
std::copy(trace.data(), trace.data() + count, frames);
|
||||
return { frames, count };
|
||||
} catch(...) {
|
||||
// Don't check rethrow condition, it's risky.
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
CTRACE_FORCE_NO_INLINE
|
||||
ctrace_object_trace ctrace_generate_object_trace(size_t skip, size_t max_depth) {
|
||||
try {
|
||||
std::vector<cpptrace::object_frame> trace = cpptrace::detail::get_frames_object_info(
|
||||
cpptrace::detail::capture_frames(skip + 1, max_depth)
|
||||
);
|
||||
return ctrace::c_convert(trace);
|
||||
} catch(...) { // NOSONAR
|
||||
// Don't check rethrow condition, it's risky.
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
CTRACE_FORCE_NO_INLINE
|
||||
ctrace_stacktrace ctrace_generate_trace(size_t skip, size_t max_depth) {
|
||||
try {
|
||||
std::vector<cpptrace::frame_ptr> frames = cpptrace::detail::capture_frames(skip + 1, max_depth);
|
||||
std::vector<cpptrace::stacktrace_frame> trace = cpptrace::detail::resolve_frames(frames);
|
||||
return ctrace::c_convert(trace);
|
||||
} catch(...) { // NOSONAR
|
||||
// Don't check rethrow condition, it's risky.
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ctrace::freeing:
|
||||
void ctrace_free_raw_trace(ctrace_raw_trace* trace) {
|
||||
if(!trace) {
|
||||
return;
|
||||
}
|
||||
ctrace_frame_ptr* frames = trace->frames;
|
||||
delete[] frames;
|
||||
trace->frames = nullptr;
|
||||
trace->count = 0;
|
||||
}
|
||||
|
||||
void ctrace_free_object_trace(ctrace_object_trace* trace) {
|
||||
if(!trace || !trace->frames) {
|
||||
return;
|
||||
}
|
||||
ctrace_object_frame* frames = trace->frames;
|
||||
for(std::size_t i = 0; i < trace->count; ++i) {
|
||||
const char* path = frames[i].obj_path;
|
||||
ctrace::free_owning_string(path);
|
||||
}
|
||||
|
||||
delete[] frames;
|
||||
trace->frames = nullptr;
|
||||
trace->count = 0;
|
||||
}
|
||||
|
||||
void ctrace_free_stacktrace(ctrace_stacktrace* trace) {
|
||||
if(!trace || !trace->frames) {
|
||||
return;
|
||||
}
|
||||
ctrace_stacktrace_frame* frames = trace->frames;
|
||||
for(std::size_t i = 0; i < trace->count; ++i) {
|
||||
ctrace::free_owning_string(frames[i].filename);
|
||||
ctrace::free_owning_string(frames[i].symbol);
|
||||
}
|
||||
|
||||
delete[] frames;
|
||||
trace->frames = nullptr;
|
||||
trace->count = 0;
|
||||
}
|
||||
|
||||
// ctrace::resolve:
|
||||
ctrace_stacktrace ctrace_resolve_raw_trace(const ctrace_raw_trace* trace) {
|
||||
if(!trace || !trace->frames) {
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
try {
|
||||
std::vector<cpptrace::frame_ptr> frames(trace->count, 0);
|
||||
std::copy(trace->frames, trace->frames + trace->count, frames.begin());
|
||||
std::vector<cpptrace::stacktrace_frame> resolved = cpptrace::detail::resolve_frames(frames);
|
||||
return ctrace::c_convert(resolved);
|
||||
} catch(...) { // NOSONAR
|
||||
// Don't check rethrow condition, it's risky.
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
ctrace_object_trace ctrace_resolve_raw_trace_to_object_trace(const ctrace_raw_trace* trace) {
|
||||
if(!trace || !trace->frames) {
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
try {
|
||||
std::vector<cpptrace::frame_ptr> frames(trace->count, 0);
|
||||
std::copy(trace->frames, trace->frames + trace->count, frames.begin());
|
||||
std::vector<cpptrace::object_frame> obj = cpptrace::detail::get_frames_object_info(frames);
|
||||
return ctrace::c_convert(obj);
|
||||
} catch(...) { // NOSONAR
|
||||
// Don't check rethrow condition, it's risky.
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
ctrace_stacktrace ctrace_resolve_object_trace(const ctrace_object_trace* trace) {
|
||||
if(!trace || !trace->frames) {
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
try {
|
||||
std::vector<cpptrace::frame_ptr> frames(trace->count, 0);
|
||||
std::transform(
|
||||
trace->frames,
|
||||
trace->frames + trace->count,
|
||||
frames.begin(),
|
||||
[] (const ctrace_object_frame& frame) -> cpptrace::frame_ptr {
|
||||
return frame.raw_address;
|
||||
}
|
||||
);
|
||||
std::vector<cpptrace::stacktrace_frame> resolved = cpptrace::detail::resolve_frames(frames);
|
||||
return ctrace::c_convert(resolved);
|
||||
} catch(...) { // NOSONAR
|
||||
// Don't check rethrow condition, it's risky.
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
// ctrace::safe:
|
||||
size_t ctrace_safe_generate_raw_trace(ctrace_frame_ptr* buffer, size_t size, size_t skip, size_t max_depth) {
|
||||
return cpptrace::safe_generate_raw_trace(buffer, size, skip, max_depth);
|
||||
}
|
||||
|
||||
void ctrace_get_safe_object_frame(ctrace_frame_ptr address, ctrace_safe_object_frame* out) {
|
||||
// TODO: change this?
|
||||
static_assert(sizeof(cpptrace::safe_object_frame) == sizeof(ctrace_safe_object_frame), "");
|
||||
cpptrace::get_safe_object_frame(address, reinterpret_cast<cpptrace::safe_object_frame*>(out));
|
||||
}
|
||||
|
||||
ctrace_bool ctrace_can_signal_safe_unwind() {
|
||||
return cpptrace::can_signal_safe_unwind();
|
||||
}
|
||||
|
||||
ctrace_bool ctrace_can_get_safe_object_frame(void) {
|
||||
return cpptrace::can_get_safe_object_frame();
|
||||
}
|
||||
|
||||
// ctrace::io:
|
||||
ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color) {
|
||||
if(!trace || !trace->frames) {
|
||||
return ctrace::generate_owning_string("<empty trace>");
|
||||
}
|
||||
auto cpp_trace = ctrace::cpp_convert(trace);
|
||||
std::string trace_string = cpp_trace.to_string(bool(use_color));
|
||||
return ctrace::generate_owning_string(trace_string);
|
||||
}
|
||||
|
||||
void ctrace_print_stacktrace(const ctrace_stacktrace* trace, FILE* to, ctrace_bool use_color) {
|
||||
if(
|
||||
use_color && (
|
||||
(to == stdout && cpptrace::isatty(cpptrace::stdout_fileno)) ||
|
||||
(to == stderr && cpptrace::isatty(cpptrace::stderr_fileno))
|
||||
)
|
||||
) {
|
||||
cpptrace::detail::enable_virtual_terminal_processing_if_needed();
|
||||
}
|
||||
ctrace::ffprintf(to, "Stack trace (most recent call first):\n");
|
||||
if(trace->count == 0 || !trace->frames) {
|
||||
ctrace::ffprintf(to, "<empty trace>\n");
|
||||
return;
|
||||
}
|
||||
const auto reset = use_color ? ESC "0m" : "";
|
||||
const auto green = use_color ? ESC "32m" : "";
|
||||
const auto yellow = use_color ? ESC "33m" : "";
|
||||
const auto blue = use_color ? ESC "34m" : "";
|
||||
const auto frame_number_width = cpptrace::detail::n_digits(cpptrace::detail::to<unsigned>(trace->count - 1));
|
||||
ctrace_stacktrace_frame* frames = trace->frames;
|
||||
for(std::size_t i = 0; i < trace->count; ++i) {
|
||||
static constexpr auto ptr_len = 2 * sizeof(cpptrace::frame_ptr);
|
||||
ctrace::ffprintf(to, "#%-*llu ", int(frame_number_width), i);
|
||||
if(frames[i].is_inline) {
|
||||
(void)std::fprintf(to, "%*s",
|
||||
int(ptr_len + 2),
|
||||
"(inlined)");
|
||||
} else {
|
||||
(void)std::fprintf(to, "%s0x%0*llx%s",
|
||||
blue,
|
||||
int(ptr_len),
|
||||
cpptrace::detail::to_ull(frames[i].raw_address),
|
||||
reset);
|
||||
}
|
||||
if(!ctrace::is_empty(frames[i].symbol)) {
|
||||
(void)std::fprintf(to, " in %s%s%s",
|
||||
yellow,
|
||||
frames[i].symbol,
|
||||
reset);
|
||||
}
|
||||
if(!ctrace::is_empty(frames[i].filename)) {
|
||||
(void)std::fprintf(to, " at %s%s%s",
|
||||
green,
|
||||
frames[i].filename,
|
||||
reset);
|
||||
if(ctrace::is_empty(frames[i].line)) {
|
||||
ctrace::ffprintf(to, "\n");
|
||||
continue;
|
||||
}
|
||||
(void)std::fprintf(to, ":%s%llu%s",
|
||||
blue,
|
||||
cpptrace::detail::to_ull(frames[i].line),
|
||||
reset);
|
||||
if(ctrace::is_empty(frames[i].column)) {
|
||||
ctrace::ffprintf(to, "\n");
|
||||
continue;
|
||||
}
|
||||
(void)std::fprintf(to, ":%s%llu%s",
|
||||
blue,
|
||||
cpptrace::detail::to_ull(frames[i].column),
|
||||
reset);
|
||||
}
|
||||
// always print newline at end :M
|
||||
ctrace::ffprintf(to, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
// utility::demangle:
|
||||
ctrace_owning_string ctrace_demangle(const char* mangled) {
|
||||
if(!mangled) {
|
||||
return ctrace::generate_owning_string("");
|
||||
}
|
||||
std::string demangled = cpptrace::demangle(mangled);
|
||||
return ctrace::generate_owning_string(demangled);
|
||||
}
|
||||
|
||||
// utility::io
|
||||
int ctrace_stdin_fileno(void) {
|
||||
return cpptrace::stdin_fileno;
|
||||
}
|
||||
|
||||
int ctrace_stderr_fileno(void) {
|
||||
return cpptrace::stderr_fileno;
|
||||
}
|
||||
|
||||
int ctrace_stdout_fileno(void) {
|
||||
return cpptrace::stdout_fileno;
|
||||
}
|
||||
|
||||
ctrace_bool ctrace_isatty(int fd) {
|
||||
return cpptrace::isatty(fd);
|
||||
}
|
||||
|
||||
// utility::cache:
|
||||
void ctrace_set_cache_mode(ctrace_cache_mode mode) {
|
||||
static constexpr auto cache_max = cpptrace::cache_mode::prioritize_speed;
|
||||
if(mode > unsigned(cache_max)) {
|
||||
return;
|
||||
}
|
||||
auto cache_mode = static_cast<cpptrace::cache_mode>(mode);
|
||||
cpptrace::experimental::set_cache_mode(cache_mode);
|
||||
}
|
||||
|
||||
void ctrace_enable_inlined_call_resolution(ctrace_bool enable) {
|
||||
cpptrace::enable_inlined_call_resolution(enable);
|
||||
}
|
||||
|
||||
ctrace_object_frame ctrace_get_object_info(const ctrace_stacktrace_frame* frame) {
|
||||
try {
|
||||
cpptrace::object_frame new_frame = cpptrace::detail::get_frame_object_info(frame->raw_address);
|
||||
return ctrace::convert_object_frame(new_frame);
|
||||
} catch(...) {
|
||||
return {0, 0, nullptr};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
#ifndef DEMANGLE_HPP
|
||||
#define DEMANGLE_HPP
|
||||
|
||||
#include <cpptrace/forward.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
std::string demangle(const std::string& name, bool check_prefix);
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
#include "utils/microfmt.hpp"
|
||||
#ifdef CPPTRACE_DEMANGLE_WITH_CXXABI
|
||||
|
||||
#include "demangle/demangle.hpp"
|
||||
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#include <cxxabi.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
std::string demangle(const std::string& name, bool check_prefix) {
|
||||
// https://itanium-cxx-abi.github.io/cxx-abi/abi.html#demangler
|
||||
// Check both _Z and __Z, apple prefixes all symbols with an underscore
|
||||
if(check_prefix && !(starts_with(name, "_Z") || starts_with(name, "__Z"))) {
|
||||
return name;
|
||||
}
|
||||
// Apple clang demangles __Z just fine but gcc doesn't, so just offset the leading underscore
|
||||
std::size_t offset = 0;
|
||||
if(starts_with(name, "__Z")) {
|
||||
offset = 1;
|
||||
}
|
||||
// Mangled names don't have spaces, we might add a space and some extra info somewhere but we still want it to
|
||||
// be demanglable. Look for a space, if there is one swap it with a null terminator briefly.
|
||||
auto end = name.find(' ');
|
||||
std::string name_copy;
|
||||
std::reference_wrapper<const std::string> to_demangle = name;
|
||||
std::string rest;
|
||||
if(end != std::string::npos) {
|
||||
name_copy = name.substr(0, end);
|
||||
rest = name.substr(end);
|
||||
to_demangle = name_copy;
|
||||
}
|
||||
// presumably thread-safe
|
||||
// it appears safe to pass nullptr for status however the docs don't explicitly say it's safe so I don't
|
||||
// want to rely on it
|
||||
int status;
|
||||
auto demangled = raii_wrap(
|
||||
abi::__cxa_demangle(to_demangle.get().c_str() + offset, nullptr, nullptr, &status),
|
||||
[] (char* str) { std::free(str); }
|
||||
);
|
||||
// demangled will always be nullptr on non-zero status, and if __cxa_demangle ever fails for any reason
|
||||
// we'll just quietly return the mangled name
|
||||
if(demangled.get()) {
|
||||
std::string str = demangled.get();
|
||||
if(!rest.empty()) {
|
||||
str += rest;
|
||||
}
|
||||
return str;
|
||||
} else {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
#ifdef CPPTRACE_DEMANGLE_WITH_NOTHING
|
||||
|
||||
#include "demangle/demangle.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
std::string demangle(const std::string& name, bool) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
#ifdef CPPTRACE_DEMANGLE_WITH_WINAPI
|
||||
|
||||
#include "demangle/demangle.hpp"
|
||||
#include "platform/dbghelp_utils.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <dbghelp.h>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
std::string demangle(const std::string& name, bool) {
|
||||
// Dbghelp is is single-threaded, so acquire a lock.
|
||||
auto lock = get_dbghelp_lock();
|
||||
char buffer[500];
|
||||
auto ret = UnDecorateSymbolName(name.c_str(), buffer, sizeof(buffer) - 1, 0);
|
||||
if(ret == 0) {
|
||||
return name;
|
||||
} else {
|
||||
buffer[ret] = 0; // just in case, ms' docs unclear if null terminator inserted
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
#include <cpptrace/exceptions.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <new>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include "platform/exception_type.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "options.hpp"
|
||||
#include "logging.hpp"
|
||||
#include "utils/error.hpp"
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
lazy_trace_holder::lazy_trace_holder(const lazy_trace_holder& other) : resolved(other.resolved) {
|
||||
if(other.resolved) {
|
||||
new (&resolved_trace) stacktrace(other.resolved_trace);
|
||||
} else {
|
||||
new (&trace) raw_trace(other.trace);
|
||||
}
|
||||
}
|
||||
lazy_trace_holder::lazy_trace_holder(lazy_trace_holder&& other) noexcept : resolved(other.resolved) {
|
||||
if(other.resolved) {
|
||||
new (&resolved_trace) stacktrace(std::move(other.resolved_trace));
|
||||
} else {
|
||||
new (&trace) raw_trace(std::move(other.trace));
|
||||
}
|
||||
}
|
||||
lazy_trace_holder& lazy_trace_holder::operator=(const lazy_trace_holder& other) {
|
||||
clear();
|
||||
resolved = other.resolved;
|
||||
if(other.resolved) {
|
||||
new (&resolved_trace) stacktrace(other.resolved_trace);
|
||||
} else {
|
||||
new (&trace) raw_trace(other.trace);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
lazy_trace_holder& lazy_trace_holder::operator=(lazy_trace_holder&& other) noexcept {
|
||||
clear();
|
||||
resolved = other.resolved;
|
||||
if(other.resolved) {
|
||||
new (&resolved_trace) stacktrace(std::move(other.resolved_trace));
|
||||
} else {
|
||||
new (&trace) raw_trace(std::move(other.trace));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
lazy_trace_holder::~lazy_trace_holder() {
|
||||
clear();
|
||||
}
|
||||
// access
|
||||
const raw_trace& lazy_trace_holder::get_raw_trace() const {
|
||||
if(resolved) {
|
||||
throw std::logic_error(
|
||||
"cpptrace::detail::lazy_trace_holder::get_resolved_trace called on resolved holder"
|
||||
);
|
||||
}
|
||||
return trace;
|
||||
}
|
||||
stacktrace& lazy_trace_holder::get_resolved_trace() {
|
||||
if(!resolved) {
|
||||
raw_trace old_trace = std::move(trace);
|
||||
*this = lazy_trace_holder(stacktrace{});
|
||||
try {
|
||||
if(!old_trace.empty()) {
|
||||
resolved_trace = old_trace.resolve();
|
||||
}
|
||||
} catch(const std::exception& e) {
|
||||
if(!should_absorb_trace_exceptions()) {
|
||||
log::error(
|
||||
"Exception occurred while resolving trace in cpptrace::detail::lazy_trace_holder: {}",
|
||||
e.what()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return resolved_trace;
|
||||
}
|
||||
const stacktrace& lazy_trace_holder::get_resolved_trace() const {
|
||||
if(!resolved) {
|
||||
throw std::logic_error(
|
||||
"cpptrace::detail::lazy_trace_holder::get_resolved_trace called on unresolved const holder"
|
||||
);
|
||||
}
|
||||
return resolved_trace;
|
||||
}
|
||||
bool lazy_trace_holder::is_resolved() const {
|
||||
return resolved;
|
||||
}
|
||||
void lazy_trace_holder::clear() {
|
||||
if(resolved) {
|
||||
resolved_trace.~stacktrace();
|
||||
} else {
|
||||
trace.~raw_trace();
|
||||
}
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
raw_trace get_raw_trace_and_absorb(std::size_t skip, std::size_t max_depth) {
|
||||
try {
|
||||
return generate_raw_trace(skip + 1, max_depth);
|
||||
} catch(const std::exception& e) {
|
||||
if(!should_absorb_trace_exceptions()) {
|
||||
log::error(
|
||||
"Exception occurred while resolving trace in cpptrace::exception object: {}",
|
||||
e.what()
|
||||
);
|
||||
}
|
||||
return raw_trace{};
|
||||
}
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
raw_trace get_raw_trace_and_absorb(std::size_t skip) {
|
||||
try { // try/catch can never be hit but it's needed to prevent TCO
|
||||
return get_raw_trace_and_absorb(skip + 1, SIZE_MAX);
|
||||
} catch(...) {
|
||||
detail::log_and_maybe_propagate_exception(std::current_exception());
|
||||
return raw_trace{};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char* lazy_exception::what() const noexcept {
|
||||
if(what_string.empty()) {
|
||||
what_string = message() + std::string(":\n") + trace_holder.get_resolved_trace().to_string();
|
||||
}
|
||||
return what_string.c_str();
|
||||
}
|
||||
|
||||
const char* lazy_exception::message() const noexcept {
|
||||
return "cpptrace::lazy_exception";
|
||||
}
|
||||
|
||||
const stacktrace& lazy_exception::trace() const noexcept {
|
||||
return trace_holder.get_resolved_trace();
|
||||
}
|
||||
|
||||
const char* exception_with_message::message() const noexcept {
|
||||
return user_message.c_str();
|
||||
}
|
||||
|
||||
system_error::system_error(int error_code, std::string&& message_arg, raw_trace&& trace) noexcept
|
||||
: runtime_error(
|
||||
message_arg + ": " + std::error_code(error_code, std::generic_category()).message(),
|
||||
std::move(trace)
|
||||
),
|
||||
ec(std::error_code(error_code, std::generic_category())) {}
|
||||
|
||||
const std::error_code& system_error::code() const noexcept {
|
||||
return ec;
|
||||
}
|
||||
|
||||
const char* nested_exception::message() const noexcept {
|
||||
if(message_value.empty()) {
|
||||
try {
|
||||
std::rethrow_exception(ptr);
|
||||
} catch(std::exception& e) {
|
||||
message_value = std::string("Nested exception: ") + e.what();
|
||||
} catch(...) {
|
||||
message_value = "Nested exception holding instance of " + detail::exception_type_name();
|
||||
}
|
||||
}
|
||||
return message_value.c_str();
|
||||
}
|
||||
|
||||
std::exception_ptr nested_exception::nested_ptr() const noexcept {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
void rethrow_and_wrap_if_needed(std::size_t skip) {
|
||||
try {
|
||||
std::rethrow_exception(std::current_exception());
|
||||
} catch(cpptrace::exception&) {
|
||||
throw; // already a cpptrace::exception
|
||||
} catch(...) {
|
||||
throw nested_exception(std::current_exception(), detail::get_raw_trace_and_absorb(skip + 1));
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
|
@ -0,0 +1,498 @@
|
|||
#include <cpptrace/formatting.hpp>
|
||||
#include <cpptrace/utils.hpp>
|
||||
|
||||
#include "utils/optional.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "utils/replace_all.hpp"
|
||||
#include "snippets/snippet.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <regex>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
std::string basename(const std::string& path) {
|
||||
return detail::basename(path, true);
|
||||
}
|
||||
|
||||
std::string prettify_symbol(std::string symbol) {
|
||||
// > > -> >> replacement
|
||||
// could put in analysis:: but the replacement is basic and this is more convenient for
|
||||
// using in the stringifier too
|
||||
detail::replace_all_dynamic(symbol, "> >", ">>");
|
||||
// "," -> ", " and " ," -> ", "
|
||||
static const std::regex comma_re(R"(\s*,\s*)");
|
||||
detail::replace_all(symbol, comma_re, ", ");
|
||||
// class C -> C for msvc
|
||||
static const std::regex class_re(R"(\b(class|struct)\s+)");
|
||||
detail::replace_all(symbol, class_re, "");
|
||||
// `anonymous namespace' -> (anonymous namespace) for msvc
|
||||
// this brings it in-line with other compilers and prevents any tokenization/highlighting issues
|
||||
static const std::regex msvc_anonymous_namespace("`anonymous namespace'");
|
||||
detail::replace_all(symbol, msvc_anonymous_namespace, "(anonymous namespace)");
|
||||
// rules to replace std::basic_string -> std::string and std::basic_string_view -> std::string
|
||||
// rule to replace ", std::allocator<whatever>"
|
||||
static const std::pair<std::regex, std::string> basic_string = {
|
||||
std::regex(R"(std(::[a-zA-Z0-9_]+)?::basic_string<char)"), "std::string"
|
||||
};
|
||||
detail::replace_all_template(symbol, basic_string);
|
||||
static const std::pair<std::regex, std::string> basic_string_view = {
|
||||
std::regex(R"(std(::[a-zA-Z0-9_]+)?::basic_string_view<char)"), "std::string_view"
|
||||
};
|
||||
detail::replace_all_template(symbol, basic_string_view);
|
||||
static const std::pair<std::regex, std::string> allocator = {
|
||||
std::regex(R"(,\s*std(::[a-zA-Z0-9_]+)?::allocator<)"), ""
|
||||
};
|
||||
detail::replace_all_template(symbol, allocator);
|
||||
static const std::pair<std::regex, std::string> default_delete = {
|
||||
std::regex(R"(,\s*std(::[a-zA-Z0-9_]+)?::default_delete<)"), ""
|
||||
};
|
||||
detail::replace_all_template(symbol, default_delete);
|
||||
// replace std::__cxx11 -> std:: for gcc dual abi
|
||||
// https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_dual_abi.html
|
||||
detail::replace_all_dynamic(symbol, "std::__cxx11::", "std::");
|
||||
return symbol;
|
||||
}
|
||||
|
||||
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;
|
||||
bool break_before_filename = false;
|
||||
int context_lines = 2;
|
||||
bool columns = true;
|
||||
symbol_mode symbols = symbol_mode::full;
|
||||
bool show_filtered_frames = true;
|
||||
std::function<bool(const stacktrace_frame&)> filter;
|
||||
std::function<stacktrace_frame(stacktrace_frame)> transform;
|
||||
} 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 symbols(symbol_mode mode) {
|
||||
options.symbols = mode;
|
||||
}
|
||||
void filtered_frame_placeholders(bool show) {
|
||||
options.show_filtered_frames = show;
|
||||
}
|
||||
void filter(std::function<bool(const stacktrace_frame&)> filter) {
|
||||
options.filter = filter;
|
||||
}
|
||||
void transform(std::function<stacktrace_frame(stacktrace_frame)> transform) {
|
||||
options.transform = std::move(transform);
|
||||
}
|
||||
void break_before_filename(bool do_break) {
|
||||
options.break_before_filename = do_break;
|
||||
}
|
||||
|
||||
std::string format(
|
||||
const stacktrace_frame& frame,
|
||||
detail::optional<bool> color_override = detail::nullopt,
|
||||
size_t filename_indent = 0
|
||||
) const {
|
||||
std::ostringstream oss;
|
||||
print_internal(oss, frame, color_override.value_or(options.color == color_mode::always), filename_indent);
|
||||
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, color_override.value_or(options.color == color_mode::always));
|
||||
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,
|
||||
size_t filename_indent = 0
|
||||
) const {
|
||||
print_internal(stream, frame, color_override, filename_indent);
|
||||
stream << "\n";
|
||||
}
|
||||
void print(
|
||||
std::FILE* file,
|
||||
const stacktrace_frame& frame,
|
||||
detail::optional<bool> color_override = detail::nullopt,
|
||||
size_t filename_indent = 0
|
||||
) const {
|
||||
auto str = format(frame, color_override, filename_indent);
|
||||
str += "\n";
|
||||
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, color_override);
|
||||
stream << "\n";
|
||||
}
|
||||
void print(
|
||||
std::FILE* file,
|
||||
const stacktrace& trace,
|
||||
detail::optional<bool> color_override = detail::nullopt
|
||||
) const {
|
||||
auto str = format(trace, color_override);
|
||||
str += "\n";
|
||||
std::fwrite(str.data(), 1, str.size(), file);
|
||||
}
|
||||
|
||||
private:
|
||||
struct color_setting {
|
||||
bool color;
|
||||
color_setting(bool color) : color(color) {}
|
||||
detail::string_view reset() const {
|
||||
return color ? RESET : "";
|
||||
}
|
||||
detail::string_view green() const {
|
||||
return color ? GREEN : "";
|
||||
}
|
||||
detail::string_view yellow() const {
|
||||
return color ? YELLOW : "";
|
||||
}
|
||||
detail::string_view blue() const {
|
||||
return color ? BLUE : "";
|
||||
}
|
||||
};
|
||||
|
||||
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)
|
||||
) {
|
||||
do_color = true;
|
||||
}
|
||||
return do_color;
|
||||
}
|
||||
|
||||
void print_internal(std::ostream& stream, const stacktrace_frame& input_frame, detail::optional<bool> color_override, size_t col_indent) const {
|
||||
bool color = should_do_color(stream, color_override);
|
||||
maybe_ensure_virtual_terminal_processing(stream, color);
|
||||
detail::optional<stacktrace_frame> transformed_frame;
|
||||
if(options.transform) {
|
||||
transformed_frame = options.transform(input_frame);
|
||||
}
|
||||
const stacktrace_frame& frame = options.transform ? transformed_frame.unwrap() : input_frame;
|
||||
write_frame(stream, frame, color, col_indent);
|
||||
}
|
||||
|
||||
void print_internal(std::ostream& stream, const stacktrace& trace, detail::optional<bool> color_override) const {
|
||||
bool color = should_do_color(stream, color_override);
|
||||
maybe_ensure_virtual_terminal_processing(stream, color);
|
||||
write_trace(stream, trace, color);
|
||||
}
|
||||
|
||||
|
||||
void write_trace(std::ostream& stream, const stacktrace& trace, 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>";
|
||||
return;
|
||||
}
|
||||
const auto frame_number_width = detail::n_digits(static_cast<int>(frames.size()) - 1);
|
||||
for(size_t i = 0; i < frames.size(); ++i) {
|
||||
detail::optional<stacktrace_frame> transformed_frame;
|
||||
if(options.transform) {
|
||||
transformed_frame = options.transform(frames[i]);
|
||||
}
|
||||
const stacktrace_frame& frame = options.transform ? transformed_frame.unwrap() : frames[i];
|
||||
bool filter_out_frame = options.filter && !options.filter(frame);
|
||||
if(filter_out_frame && !options.show_filtered_frames) {
|
||||
counter++;
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t filename_indent = write_frame_number(stream, frame_number_width, counter);
|
||||
if(filter_out_frame) {
|
||||
microfmt::print(stream, "(filtered)");
|
||||
} else {
|
||||
write_frame(stream, frame, color, filename_indent);
|
||||
if(frame.line.has_value() && !frame.filename.empty() && options.snippets) {
|
||||
auto snippet = detail::get_snippet(
|
||||
frame.filename,
|
||||
frame.line.value(),
|
||||
frame.column,
|
||||
options.context_lines,
|
||||
color
|
||||
);
|
||||
if(!snippet.empty()) {
|
||||
stream << '\n';
|
||||
stream << snippet;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(i + 1 != frames.size()) {
|
||||
stream << '\n';
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
|
||||
/// Write the frame number, and return the number of characters written
|
||||
size_t write_frame_number(std::ostream& stream, unsigned int frame_number_width, size_t counter) const
|
||||
{
|
||||
microfmt::print(stream, "#{<{}} ", frame_number_width, counter);
|
||||
return 2 + frame_number_width;
|
||||
}
|
||||
|
||||
void write_frame(std::ostream& stream, const stacktrace_frame& frame, color_setting color, size_t col) const {
|
||||
col += write_address(stream, frame, color);
|
||||
if(frame.is_inline || options.addresses != address_mode::none) {
|
||||
stream << ' ';
|
||||
col += 1;
|
||||
}
|
||||
if(!frame.symbol.empty()) {
|
||||
write_symbol(stream, frame, color);
|
||||
}
|
||||
if(!frame.symbol.empty() && !frame.filename.empty()) {
|
||||
if(options.break_before_filename) {
|
||||
microfmt::print(stream, "\n{<{}}", col, "");
|
||||
} else {
|
||||
stream << ' ';
|
||||
}
|
||||
}
|
||||
if(!frame.filename.empty()) {
|
||||
write_source_location(stream, frame, color);
|
||||
}
|
||||
}
|
||||
|
||||
/// Write the address of the frame, return the number of characters written
|
||||
size_t write_address(std::ostream& stream, const stacktrace_frame& frame, color_setting color) const {
|
||||
if(frame.is_inline) {
|
||||
microfmt::print(stream, "{<{}}", 2 * sizeof(frame_ptr) + 2, "(inlined)");
|
||||
return 2 * sizeof(frame_ptr) + 2;
|
||||
} 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}{}", color.blue(), 2 * sizeof(frame_ptr), address, color.reset());
|
||||
return 2 * sizeof(frame_ptr) + 2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void write_symbol(std::ostream& stream, const stacktrace_frame& frame, color_setting color) const {
|
||||
detail::optional<std::string> maybe_stored_string;
|
||||
detail::string_view symbol;
|
||||
switch(options.symbols) {
|
||||
case symbol_mode::full:
|
||||
symbol = frame.symbol;
|
||||
break;
|
||||
case symbol_mode::pruned:
|
||||
maybe_stored_string = prune_symbol(frame.symbol);
|
||||
symbol = maybe_stored_string.unwrap();
|
||||
break;
|
||||
case symbol_mode::pretty:
|
||||
maybe_stored_string = prettify_symbol(frame.symbol);
|
||||
symbol = maybe_stored_string.unwrap();
|
||||
break;
|
||||
default:
|
||||
PANIC("Unhandled symbol mode");
|
||||
}
|
||||
microfmt::print(stream, "in {}{}{}", color.yellow(), symbol, color.reset());
|
||||
}
|
||||
|
||||
void write_source_location(std::ostream& stream, const stacktrace_frame& frame, color_setting color) const {
|
||||
microfmt::print(
|
||||
stream,
|
||||
"at {}{}{}",
|
||||
color.green(),
|
||||
options.paths == path_mode::full ? frame.filename : detail::basename(frame.filename, true),
|
||||
color.reset()
|
||||
);
|
||||
if(frame.line.has_value()) {
|
||||
microfmt::print(stream, ":{}{}{}", color.blue(), frame.line.value(), color.reset());
|
||||
if(frame.column.has_value() && options.columns) {
|
||||
microfmt::print(stream, ":{}{}{}", color.blue(), frame.column.value(), color.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::symbols(symbol_mode mode) {
|
||||
pimpl->symbols(mode);
|
||||
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;
|
||||
}
|
||||
formatter& formatter::transform(std::function<stacktrace_frame(stacktrace_frame)> transform) {
|
||||
pimpl->transform(std::move(transform));
|
||||
return *this;
|
||||
}
|
||||
formatter& formatter::break_before_filename(bool do_break) {
|
||||
pimpl->break_before_filename(do_break);
|
||||
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_frame& frame, bool color, size_t filename_indent) const {
|
||||
return pimpl->format(frame, color, filename_indent);
|
||||
}
|
||||
|
||||
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::ostream& stream, const stacktrace_frame& frame, bool color, size_t filename_indent) const {
|
||||
pimpl->print(stream, frame, color, filename_indent);
|
||||
}
|
||||
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);
|
||||
}
|
||||
void formatter::print(std::FILE* file, const stacktrace_frame& frame, bool color, size_t filename_indent) const {
|
||||
pimpl->print(file, frame, color, filename_indent);
|
||||
}
|
||||
|
||||
const formatter& get_default_formatter() {
|
||||
static formatter formatter;
|
||||
return formatter;
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
|
@ -0,0 +1,704 @@
|
|||
#include <cpptrace/cpptrace.hpp>
|
||||
#include <cpptrace/from_current.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <system_error>
|
||||
#include <typeinfo>
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/microfmt.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "logging.hpp"
|
||||
#include "unwind/unwind.hpp"
|
||||
|
||||
#ifndef _MSC_VER
|
||||
#include <string.h>
|
||||
#if IS_WINDOWS
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#if IS_APPLE
|
||||
#include <mach/mach.h>
|
||||
#ifdef CPPTRACE_HAS_MACH_VM
|
||||
#include <mach/mach_vm.h>
|
||||
#endif
|
||||
#else
|
||||
#include <fstream>
|
||||
#include <ios>
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
thread_local lazy_trace_holder current_exception_trace;
|
||||
thread_local lazy_trace_holder saved_rethrow_trace;
|
||||
|
||||
bool& get_rethrow_switch() {
|
||||
static thread_local bool rethrow_switch = false;
|
||||
return rethrow_switch;
|
||||
}
|
||||
|
||||
void save_current_trace(raw_trace trace) {
|
||||
if(get_rethrow_switch()) {
|
||||
saved_rethrow_trace = lazy_trace_holder(std::move(trace));
|
||||
} else {
|
||||
current_exception_trace = lazy_trace_holder(std::move(trace));
|
||||
saved_rethrow_trace = lazy_trace_holder();
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(_MSC_VER) && defined(CPPTRACE_UNWIND_WITH_DBGHELP)
|
||||
CPPTRACE_FORCE_NO_INLINE void collect_current_trace(std::size_t skip, EXCEPTION_POINTERS* exception_ptrs) {
|
||||
try {
|
||||
#if defined(_M_IX86) || defined(__i386__)
|
||||
(void)skip; // don't skip any frames, the context record is at the throw point
|
||||
auto trace = raw_trace{detail::capture_frames(0, SIZE_MAX, exception_ptrs)};
|
||||
#else
|
||||
(void)exception_ptrs;
|
||||
auto trace = raw_trace{detail::capture_frames(skip + 1, SIZE_MAX)};
|
||||
#endif
|
||||
save_current_trace(std::move(trace));
|
||||
} catch(...) {
|
||||
detail::log_and_maybe_propagate_exception(std::current_exception());
|
||||
}
|
||||
}
|
||||
#else
|
||||
CPPTRACE_FORCE_NO_INLINE void collect_current_trace(std::size_t skip) {
|
||||
try {
|
||||
auto trace = raw_trace{detail::capture_frames(skip + 1, SIZE_MAX)};
|
||||
save_current_trace(std::move(trace));
|
||||
} catch(...) {
|
||||
detail::log_and_maybe_propagate_exception(std::current_exception());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
// https://www.youtube.com/watch?v=COEv2kq_Ht8
|
||||
// https://github.com/tpn/pdfs/blob/master/2018%20CppCon%20Unwinding%20the%20Stack%20-%20Exploring%20how%20C%2B%2B%20Exceptions%20work%20on%20Windows%20-%20James%20McNellis.pdf
|
||||
// https://github.com/ecatmur/stacktrace-from-exception/blob/main/stacktrace-from-exception.cpp
|
||||
// https://github.com/wine-mirror/wine/blob/7f833db11ffea4f3f4fa07be31d30559aff9c5fb/dlls/msvcrt/except.c#L371
|
||||
// https://github.com/facebook/folly/blob/d17bf897cb5bbf8f07b122a614e8cffdc38edcde/folly/lang/Exception.cpp
|
||||
|
||||
// ClangCL doesn't define ThrowInfo so we use our own
|
||||
// sources:
|
||||
// - https://github.com/ecatmur/stacktrace-from-exception/blob/main/stacktrace-from-exception.cpp
|
||||
// - https://github.com/catboost/catboost/blob/master/contrib/libs/cxxsupp/libcxx/src/support/runtime/exception_pointer_msvc.ipp
|
||||
// - https://www.geoffchappell.com/studies/msvc/language/predefined/index.htm
|
||||
#pragma pack(push, 4)
|
||||
struct CatchableType {
|
||||
std::uint32_t properties;
|
||||
std::int32_t pType;
|
||||
std::uint32_t non_virtual_adjustment; // these next three are from _PMD
|
||||
std::uint32_t offset_to_virtual_base_ptr;
|
||||
std::uint32_t virtual_base_table_index;
|
||||
std::uint32_t sizeOrOffset;
|
||||
std::int32_t copyFunction;
|
||||
};
|
||||
struct ThrowInfo {
|
||||
std::uint32_t attributes;
|
||||
std::int32_t pmfnUnwind;
|
||||
std::int32_t pForwardCompat;
|
||||
std::int32_t pCatchableTypeArray;
|
||||
};
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:4200)
|
||||
#if IS_CLANG
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wc99-extensions"
|
||||
#endif
|
||||
struct CatchableTypeArray {
|
||||
uint32_t nCatchableTypes;
|
||||
int32_t arrayOfCatchableTypes[];
|
||||
};
|
||||
#if IS_CLANG
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
#pragma warning(pop)
|
||||
#pragma pack(pop)
|
||||
|
||||
static constexpr unsigned EH_MAGIC_NUMBER1 = 0x19930520; // '?msc' version magic, see ehdata.h
|
||||
static constexpr unsigned EH_EXCEPTION_NUMBER = 0xE06D7363; // '?msc', 'msc' | 0xE0000000
|
||||
|
||||
using catchable_type_array_t = decltype(ThrowInfo::pCatchableTypeArray);
|
||||
|
||||
class catchable_type_info {
|
||||
HMODULE module_pointer = nullptr;
|
||||
const CatchableTypeArray* catchable_types = nullptr;
|
||||
public:
|
||||
catchable_type_info(const HMODULE module_pointer, catchable_type_array_t catchable_type_array)
|
||||
: module_pointer(module_pointer) {
|
||||
catchable_types = rtti_rva<const CatchableTypeArray*>(catchable_type_array);
|
||||
}
|
||||
|
||||
class iterator {
|
||||
const catchable_type_info& info;
|
||||
std::size_t i;
|
||||
public:
|
||||
iterator(const catchable_type_info& info, std::size_t i) : info(info), i(i) {}
|
||||
const std::type_info& operator*() const {
|
||||
return info.get_type_info(i);
|
||||
}
|
||||
bool operator!=(const iterator& other) const {
|
||||
return i != other.i;
|
||||
}
|
||||
iterator& operator++() {
|
||||
i++;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
using const_iterator = iterator;
|
||||
|
||||
const_iterator begin() const {
|
||||
return {*this, 0};
|
||||
}
|
||||
const_iterator end() const {
|
||||
return {*this, catchable_types ? catchable_types->nCatchableTypes : std::size_t{}};
|
||||
}
|
||||
|
||||
private:
|
||||
template<typename T, typename A>
|
||||
T rtti_rva(A address) const {
|
||||
#ifdef _WIN64
|
||||
return reinterpret_cast<T>((uintptr_t)module_pointer + (uintptr_t)address);
|
||||
#else
|
||||
(void)module_pointer;
|
||||
return reinterpret_cast<T>(address);
|
||||
#endif
|
||||
}
|
||||
|
||||
const std::type_info& get_type_info(std::size_t i) const {
|
||||
return *rtti_rva<const std::type_info*>(get_catchable_type(i)->pType);
|
||||
}
|
||||
|
||||
const CatchableType* get_catchable_type(std::size_t i) const {
|
||||
return rtti_rva<const CatchableType*>(
|
||||
reinterpret_cast<const std::int32_t*>(catchable_types->arrayOfCatchableTypes)[i]
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
catchable_type_info get_catchable_types(const EXCEPTION_RECORD* exception_record) {
|
||||
static_assert(EXCEPTION_MAXIMUM_PARAMETERS >= 4, "Unexpected EXCEPTION_MAXIMUM_PARAMETERS");
|
||||
// ExceptionInformation will contain
|
||||
// [0] EH_MAGIC_NUMBER1
|
||||
// [1] ExceptionObject
|
||||
// [2] ThrowInfo
|
||||
HMODULE module_pointer = nullptr;
|
||||
catchable_type_array_t catchable_type_array{}; // will be either an int or pointer
|
||||
if(
|
||||
exception_record->ExceptionInformation[0] == EH_MAGIC_NUMBER1
|
||||
&& exception_record->NumberParameters >= 3
|
||||
) {
|
||||
if(exception_record->NumberParameters >= 4) {
|
||||
module_pointer = reinterpret_cast<HMODULE>(exception_record->ExceptionInformation[3]);
|
||||
}
|
||||
auto throw_info = reinterpret_cast<const ThrowInfo*>(exception_record->ExceptionInformation[2]);
|
||||
if (throw_info) {
|
||||
catchable_type_array = throw_info->pCatchableTypeArray;
|
||||
}
|
||||
}
|
||||
return {module_pointer, catchable_type_array};
|
||||
}
|
||||
|
||||
bool matches_exception(EXCEPTION_RECORD* exception_record, const std::type_info& type_info) {
|
||||
if (type_info == typeid(void)) {
|
||||
return true;
|
||||
}
|
||||
for (const auto& catchable_type : get_catchable_types(exception_record)) {
|
||||
if (catchable_type == type_info) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef _MSC_VER
|
||||
#if IS_LIBSTDCXX
|
||||
constexpr size_t vtable_size = 11;
|
||||
#elif IS_LIBCXX
|
||||
constexpr size_t vtable_size = 10;
|
||||
#else
|
||||
#warning "Cpptrace from_current: Unrecognized C++ standard library, from_current() won't be supported"
|
||||
constexpr size_t vtable_size = 0;
|
||||
#endif
|
||||
|
||||
#if IS_WINDOWS
|
||||
int get_page_size() {
|
||||
SYSTEM_INFO info;
|
||||
GetSystemInfo(&info);
|
||||
return info.dwPageSize;
|
||||
}
|
||||
constexpr auto memory_readonly = PAGE_READONLY;
|
||||
constexpr auto memory_readwrite = PAGE_READWRITE;
|
||||
int mprotect_page_and_return_old_protections(void* page, int page_size, int protections) {
|
||||
DWORD old_protections;
|
||||
if(!VirtualProtect(page, page_size, protections, &old_protections)) {
|
||||
throw internal_error(
|
||||
"VirtualProtect call failed: {}",
|
||||
std::system_error(GetLastError(), std::system_category()).what()
|
||||
);
|
||||
}
|
||||
return old_protections;
|
||||
}
|
||||
void mprotect_page(void* page, int page_size, int protections) {
|
||||
mprotect_page_and_return_old_protections(page, page_size, protections);
|
||||
}
|
||||
void* allocate_page(int page_size) {
|
||||
auto page = VirtualAlloc(nullptr, page_size, MEM_COMMIT | MEM_RESERVE, memory_readwrite);
|
||||
if(!page) {
|
||||
throw internal_error(
|
||||
"VirtualAlloc call failed: {}",
|
||||
std::system_error(GetLastError(), std::system_category()).what()
|
||||
);
|
||||
}
|
||||
return page;
|
||||
}
|
||||
#else
|
||||
int get_page_size() {
|
||||
#if defined(_SC_PAGESIZE)
|
||||
return sysconf(_SC_PAGESIZE);
|
||||
#else
|
||||
return getpagesize();
|
||||
#endif
|
||||
}
|
||||
constexpr auto memory_readonly = PROT_READ;
|
||||
constexpr auto memory_readwrite = PROT_READ | PROT_WRITE;
|
||||
#if IS_APPLE
|
||||
int get_page_protections(void* page) {
|
||||
// https://stackoverflow.com/a/12627784/15675011
|
||||
#ifdef CPPTRACE_HAS_MACH_VM
|
||||
mach_vm_size_t vmsize;
|
||||
mach_vm_address_t address = (mach_vm_address_t)page;
|
||||
#else
|
||||
vm_size_t vmsize;
|
||||
vm_address_t address = (vm_address_t)page;
|
||||
#endif
|
||||
vm_region_basic_info_data_t info;
|
||||
mach_msg_type_number_t info_count =
|
||||
sizeof(size_t) == 8 ? VM_REGION_BASIC_INFO_COUNT_64 : VM_REGION_BASIC_INFO_COUNT;
|
||||
memory_object_name_t object;
|
||||
kern_return_t status =
|
||||
#ifdef CPPTRACE_HAS_MACH_VM
|
||||
mach_vm_region
|
||||
#else
|
||||
vm_region_64
|
||||
#endif
|
||||
(
|
||||
mach_task_self(),
|
||||
&address,
|
||||
&vmsize,
|
||||
VM_REGION_BASIC_INFO,
|
||||
(vm_region_info_t)&info,
|
||||
&info_count,
|
||||
&object
|
||||
);
|
||||
if(status == KERN_INVALID_ADDRESS) {
|
||||
throw internal_error("vm_region failed with KERN_INVALID_ADDRESS");
|
||||
}
|
||||
int perms = 0;
|
||||
if(info.protection & VM_PROT_READ) {
|
||||
perms |= PROT_READ;
|
||||
}
|
||||
if(info.protection & VM_PROT_WRITE) {
|
||||
perms |= PROT_WRITE;
|
||||
}
|
||||
if(info.protection & VM_PROT_EXECUTE) {
|
||||
perms |= PROT_EXEC;
|
||||
}
|
||||
return perms;
|
||||
}
|
||||
#else
|
||||
// Code for reading /proc/self/maps
|
||||
// Unfortunately this is the canonical and only way to get memory permissions on linux
|
||||
// It comes with some surprising behaviors. Because it's a pseudo-file and maps could update at any time, reads of
|
||||
// the file can tear. The surprising observable behavior here is overlapping ranges:
|
||||
// - https://unix.stackexchange.com/questions/704987/overlapping-address-ranges-in-proc-maps
|
||||
// - https://stackoverflow.com/questions/59737950/what-is-the-correct-way-to-get-a-consistent-snapshot-of-proc-pid-smaps
|
||||
// Additional info:
|
||||
// Note: reading /proc/PID/maps or /proc/PID/smaps is inherently racy (consistent
|
||||
// output can be achieved only in the single read call).
|
||||
// This typically manifests when doing partial reads of these files while the
|
||||
// memory map is being modified. Despite the races, we do provide the following
|
||||
// guarantees:
|
||||
//
|
||||
// 1) The mapped addresses never go backwards, which implies no two
|
||||
// regions will ever overlap.
|
||||
// 2) If there is something at a given vaddr during the entirety of the
|
||||
// life of the smaps/maps walk, there will be some output for it.
|
||||
//
|
||||
// https://www.kernel.org/doc/Documentation/filesystems/proc.txt
|
||||
// Ideally we could do everything as a single read() call but I don't think that's practical, especially given that
|
||||
// the kernel has limited buffers internally. While we shouldn't be modifying mapped memory while reading
|
||||
// /proc/self/maps here, it's theoretically possible that we could allocate and that could go to the OS for more
|
||||
// pages.
|
||||
// While reading this is inherently racy, as far as I can tell tears don't happen within a line but they can happen
|
||||
// between lines.
|
||||
// The code that writes /proc/pid/maps:
|
||||
// - https://github.com/torvalds/linux/blob/3d0ebc36b0b3e8486ceb6e08e8ae173aaa6d1221/fs/proc/task_mmu.c#L304-L365
|
||||
|
||||
struct address_range {
|
||||
uintptr_t low;
|
||||
uintptr_t high;
|
||||
int perms;
|
||||
bool operator<(const address_range& other) const {
|
||||
return low < other.low;
|
||||
}
|
||||
};
|
||||
|
||||
// returns nullopt on eof
|
||||
optional<address_range> read_map_entry(std::ifstream& stream) {
|
||||
uintptr_t start;
|
||||
uintptr_t stop;
|
||||
stream>>start;
|
||||
stream.ignore(1); // dash
|
||||
stream>>stop;
|
||||
if(stream.eof()) {
|
||||
return nullopt;
|
||||
}
|
||||
if(stream.fail()) {
|
||||
throw internal_error("Failure reading /proc/self/maps");
|
||||
}
|
||||
stream.ignore(1); // space
|
||||
char r, w, x; // there's a private/shared flag after these but we don't need it
|
||||
stream>>r>>w>>x;
|
||||
if(stream.fail() || stream.eof()) {
|
||||
throw internal_error("Failure reading /proc/self/maps");
|
||||
}
|
||||
int perms = 0;
|
||||
if(r == 'r') {
|
||||
perms |= PROT_READ;
|
||||
}
|
||||
if(w == 'w') {
|
||||
perms |= PROT_WRITE;
|
||||
}
|
||||
if(x == 'x') {
|
||||
perms |= PROT_EXEC;
|
||||
}
|
||||
stream.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
|
||||
return address_range{start, stop, perms};
|
||||
}
|
||||
|
||||
// returns a vector or nullopt if a tear is detected
|
||||
optional<std::vector<address_range>> try_load_mapped_region_info() {
|
||||
std::ifstream stream("/proc/self/maps");
|
||||
stream>>std::hex;
|
||||
std::vector<address_range> ranges;
|
||||
while(auto entry = read_map_entry(stream)) {
|
||||
const auto& range = entry.unwrap();
|
||||
VERIFY(range.low <= range.high);
|
||||
if(!ranges.empty()) {
|
||||
const auto& last_range = ranges.back();
|
||||
if(range.low < last_range.high) {
|
||||
return nullopt;
|
||||
}
|
||||
}
|
||||
ranges.push_back(range);
|
||||
}
|
||||
return ranges;
|
||||
}
|
||||
|
||||
// we can allocate during try_load_mapped_region_info, in theory that could cause a tear
|
||||
optional<std::vector<address_range>> try_load_mapped_region_info_with_retries(int n) {
|
||||
VERIFY(n > 0);
|
||||
for(int i = 0; i < n; i++) {
|
||||
if(auto info = try_load_mapped_region_info()) {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
throw internal_error("Couldn't successfully load /proc/self/maps after {} retries", n);
|
||||
}
|
||||
|
||||
const std::vector<address_range>& load_mapped_region_info() {
|
||||
static std::vector<address_range> regions;
|
||||
static bool has_loaded = false;
|
||||
if(!has_loaded) {
|
||||
has_loaded = true;
|
||||
if(auto info = try_load_mapped_region_info_with_retries(2)) {
|
||||
regions = std::move(info).unwrap();
|
||||
}
|
||||
}
|
||||
return regions;
|
||||
}
|
||||
|
||||
int get_page_protections(void* page) {
|
||||
const auto& mapped_region_info = load_mapped_region_info();
|
||||
auto it = first_less_than_or_equal(
|
||||
mapped_region_info.begin(),
|
||||
mapped_region_info.end(),
|
||||
reinterpret_cast<uintptr_t>(page),
|
||||
[](uintptr_t a, const address_range& b) {
|
||||
return a < b.low;
|
||||
}
|
||||
);
|
||||
if(it == mapped_region_info.end()) {
|
||||
throw internal_error(
|
||||
"Failed to find mapping for {>16:0h} in /proc/self/maps",
|
||||
reinterpret_cast<uintptr_t>(page)
|
||||
);
|
||||
}
|
||||
return it->perms;
|
||||
}
|
||||
#endif
|
||||
void mprotect_page(void* page, int page_size, int protections) {
|
||||
if(mprotect(page, page_size, protections) != 0) {
|
||||
throw internal_error("mprotect call failed: {}", strerror(errno));
|
||||
}
|
||||
}
|
||||
int mprotect_page_and_return_old_protections(void* page, int page_size, int protections) {
|
||||
auto old_protections = get_page_protections(page);
|
||||
mprotect_page(page, page_size, protections);
|
||||
return old_protections;
|
||||
}
|
||||
void* allocate_page(int page_size) {
|
||||
auto page = mmap(nullptr, page_size, memory_readwrite, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
|
||||
if(page == MAP_FAILED) {
|
||||
throw internal_error("mmap call failed: {}", strerror(errno));
|
||||
}
|
||||
return page;
|
||||
}
|
||||
#endif
|
||||
void perform_typeinfo_surgery(const std::type_info& info, bool(*do_catch_function)(const std::type_info*, const std::type_info*, void**, unsigned)) {
|
||||
if(vtable_size == 0) { // set to zero if we don't know what standard library we're working with
|
||||
return;
|
||||
}
|
||||
void* type_info_pointer = const_cast<void*>(static_cast<const void*>(&info));
|
||||
void** type_info_vtable_pointer = *static_cast<void***>(type_info_pointer);
|
||||
// the type info vtable pointer points to two pointers inside the vtable, adjust it back
|
||||
// https://itanium-cxx-abi.github.io/cxx-abi/abi.html#vtable-components (see offset to top, typeinfo ptr,
|
||||
// and the following bullet point)
|
||||
type_info_vtable_pointer -= 2;
|
||||
|
||||
// for libstdc++ the class type info vtable looks like
|
||||
// 0x7ffff7f89d18 <_ZTVN10__cxxabiv117__class_type_infoE>: 0x0000000000000000 0x00007ffff7f89d00
|
||||
// [offset ][typeinfo pointer ]
|
||||
// 0x7ffff7f89d28 <_ZTVN10__cxxabiv117__class_type_infoE+16>: 0x00007ffff7dd65a0 0x00007ffff7dd65c0
|
||||
// [base destructor ][deleting dtor ]
|
||||
// 0x7ffff7f89d38 <_ZTVN10__cxxabiv117__class_type_infoE+32>: 0x00007ffff7dd8f10 0x00007ffff7dd8f10
|
||||
// [__is_pointer_p ][__is_function_p ]
|
||||
// 0x7ffff7f89d48 <_ZTVN10__cxxabiv117__class_type_infoE+48>: 0x00007ffff7dd6640 0x00007ffff7dd6500
|
||||
// [__do_catch ][__do_upcast ]
|
||||
// 0x7ffff7f89d58 <_ZTVN10__cxxabiv117__class_type_infoE+64>: 0x00007ffff7dd65e0 0x00007ffff7dd66d0
|
||||
// [__do_upcast ][__do_dyncast ]
|
||||
// 0x7ffff7f89d68 <_ZTVN10__cxxabiv117__class_type_infoE+80>: 0x00007ffff7dd6580 0x00007ffff7f8abe8
|
||||
// [__do_find_public_src][other ]
|
||||
// In libc++ the layout is
|
||||
// [offset ][typeinfo pointer ]
|
||||
// [base destructor ][deleting dtor ]
|
||||
// [noop1 ][noop2 ]
|
||||
// [can_catch ][search_above_dst ]
|
||||
// [search_below_dst ][has_unambiguous_public_base]
|
||||
// Relevant documentation/implementation:
|
||||
// https://itanium-cxx-abi.github.io/cxx-abi/abi.html
|
||||
// libstdc++
|
||||
// https://github.com/gcc-mirror/gcc/blob/b13e34699c7d27e561fcfe1b66ced1e50e69976f/libstdc%252B%252B-v3/libsupc%252B%252B/typeinfo
|
||||
// https://github.com/gcc-mirror/gcc/blob/b13e34699c7d27e561fcfe1b66ced1e50e69976f/libstdc%252B%252B-v3/libsupc%252B%252B/class_type_info.cc
|
||||
// libc++
|
||||
// https://github.com/llvm/llvm-project/blob/648f4d0658ab00cf1e95330c8811aaea9481a274/libcxx/include/typeinfo
|
||||
// https://github.com/llvm/llvm-project/blob/648f4d0658ab00cf1e95330c8811aaea9481a274/libcxxabi/src/private_typeinfo.h
|
||||
|
||||
// shouldn't be anything other than 4096 but out of an abundance of caution
|
||||
auto page_size = get_page_size();
|
||||
if(page_size <= 0 && is_positive_power_of_two(page_size)) {
|
||||
throw internal_error("getpagesize() is not a power of 2 greater than zero (was {})", page_size);
|
||||
}
|
||||
if(static_cast<size_t>(page_size) < vtable_size * sizeof(void*)) {
|
||||
throw internal_error(
|
||||
"Page size isn't big enough for a vtable: Needed {}, got {}",
|
||||
vtable_size * sizeof(void*),
|
||||
page_size
|
||||
);
|
||||
}
|
||||
|
||||
// allocate a page for the new vtable so it can be made read-only later
|
||||
// the OS cleans this up, no cleanup done here for it
|
||||
void* new_vtable_page = allocate_page(page_size);
|
||||
|
||||
// Double-check alignment: "This address must have the alignment required for pointers"
|
||||
// https://itanium-cxx-abi.github.io/cxx-abi/abi.html#vtable-components
|
||||
constexpr auto ptr_align = alignof(void*);
|
||||
static_assert(is_positive_power_of_two(ptr_align), "alignof has to return a power of two");
|
||||
auto align_mask = ptr_align - 1;
|
||||
if((reinterpret_cast<uintptr_t>(new_vtable_page) & align_mask) != 0) {
|
||||
throw internal_error("Bad allocation alignment: {}", reinterpret_cast<uintptr_t>(new_vtable_page));
|
||||
}
|
||||
|
||||
// make our own copy of the vtable
|
||||
memcpy(new_vtable_page, type_info_vtable_pointer, vtable_size * sizeof(void*));
|
||||
// ninja in the custom __do_catch interceptor
|
||||
auto new_vtable = static_cast<void**>(new_vtable_page);
|
||||
// double cast is done here because older (and some newer gcc versions) warned about it under -Wpedantic
|
||||
new_vtable[6] = reinterpret_cast<void*>(reinterpret_cast<std::uintptr_t>(do_catch_function));
|
||||
// make the page read-only
|
||||
mprotect_page(new_vtable_page, page_size, memory_readonly);
|
||||
|
||||
// make the vtable pointer for unwind_interceptor's type_info point to the new vtable
|
||||
auto type_info_addr = reinterpret_cast<uintptr_t>(type_info_pointer);
|
||||
auto page_addr = type_info_addr & ~(page_size - 1);
|
||||
// make sure the memory we're going to set is within the page
|
||||
if(type_info_addr - page_addr + sizeof(void*) > static_cast<unsigned>(page_size)) {
|
||||
throw internal_error("pointer crosses page boundaries");
|
||||
}
|
||||
auto old_protections = mprotect_page_and_return_old_protections(
|
||||
reinterpret_cast<void*>(page_addr),
|
||||
page_size,
|
||||
memory_readwrite
|
||||
);
|
||||
*static_cast<void**>(type_info_pointer) = static_cast<void*>(new_vtable + 2);
|
||||
mprotect_page(reinterpret_cast<void*>(page_addr), page_size, old_protections);
|
||||
}
|
||||
|
||||
bool can_catch(
|
||||
const std::type_info* type,
|
||||
const std::type_info* throw_type,
|
||||
void** throw_obj,
|
||||
unsigned outer
|
||||
) {
|
||||
if (*type == typeid(void)) {
|
||||
return true;
|
||||
}
|
||||
// get the vtable for the type_info and call the function pointer in the 6th slot
|
||||
// see below: perform_typeinfo_surgery
|
||||
void* type_info_pointer = const_cast<void*>(static_cast<const void*>(type));
|
||||
void** type_info_vtable_pointer = *static_cast<void***>(type_info_pointer);
|
||||
// the type info vtable pointer points to two pointers inside the vtable, adjust it back
|
||||
type_info_vtable_pointer -= 2;
|
||||
auto* can_catch_fn =
|
||||
#if IS_GCC
|
||||
// error: ISO C++ forbids casting between pointer-to-function and pointer-to-object on old gcc
|
||||
__extension__
|
||||
#endif
|
||||
reinterpret_cast<decltype(can_catch)*>(type_info_vtable_pointer[6]);
|
||||
return can_catch_fn(type, throw_type, throw_obj, outer);
|
||||
}
|
||||
#endif
|
||||
|
||||
// called when unwinding starts after rethrowing, after search phase
|
||||
void rethrow_scope_cleanup() {
|
||||
get_rethrow_switch() = false;
|
||||
}
|
||||
|
||||
scope_guard<void(&)()> setup_rethrow() {
|
||||
get_rethrow_switch() = true;
|
||||
// will flip the switch back to true as soon as the search phase completes and the unwinding begins
|
||||
return scope_exit<void(&)()>(rethrow_scope_cleanup);
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
#ifdef _MSC_VER
|
||||
bool matches_exception(EXCEPTION_POINTERS* exception_ptrs, const std::type_info& type_info) {
|
||||
CPPTRACE_PUSH_EXTENSION_WARNINGS
|
||||
__try {
|
||||
auto* exception_record = exception_ptrs->ExceptionRecord;
|
||||
// Check if the SEH exception is a C++ exception
|
||||
if(exception_record->ExceptionCode == EH_EXCEPTION_NUMBER) {
|
||||
return detail::matches_exception(exception_record, type_info);
|
||||
}
|
||||
} __except(EXCEPTION_EXECUTE_HANDLER) {
|
||||
// pass
|
||||
}
|
||||
CPPTRACE_POP_EXTENSION_WARNINGS
|
||||
return false;
|
||||
}
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
int maybe_collect_trace(EXCEPTION_POINTERS* exception_ptrs, int filter_result) {
|
||||
if(filter_result == EXCEPTION_EXECUTE_HANDLER) {
|
||||
#ifdef CPPTRACE_UNWIND_WITH_DBGHELP
|
||||
collect_current_trace(1, exception_ptrs);
|
||||
#else
|
||||
collect_current_trace(1);
|
||||
(void)exception_ptrs;
|
||||
#endif
|
||||
}
|
||||
return filter_result;
|
||||
}
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
void maybe_collect_trace(EXCEPTION_POINTERS* exception_ptrs, const std::type_info& type_info) {
|
||||
if(matches_exception(exception_ptrs, type_info)) {
|
||||
#ifdef CPPTRACE_UNWIND_WITH_DBGHELP
|
||||
collect_current_trace(2, exception_ptrs);
|
||||
#else
|
||||
collect_current_trace(2);
|
||||
(void)exception_ptrs;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#else
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
void maybe_collect_trace(
|
||||
const std::type_info* type,
|
||||
const std::type_info* throw_type,
|
||||
void** throw_obj,
|
||||
unsigned outer
|
||||
) {
|
||||
if(detail::can_catch(type, throw_type, throw_obj, outer)) {
|
||||
collect_current_trace(2);
|
||||
}
|
||||
}
|
||||
|
||||
void do_prepare_unwind_interceptor(const std::type_info& type_info, bool(*can_catch)(const std::type_info*, const std::type_info*, void**, unsigned)) {
|
||||
try {
|
||||
detail::perform_typeinfo_surgery(
|
||||
type_info,
|
||||
can_catch
|
||||
);
|
||||
} catch(std::exception& e) {
|
||||
detail::log::error("Exception occurred while preparing from_current support: {}", e.what());
|
||||
} catch(...) {
|
||||
detail::log::error("Unknown exception occurred while preparing from_current support");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
const raw_trace& raw_trace_from_current_exception() {
|
||||
return detail::current_exception_trace.get_raw_trace();
|
||||
}
|
||||
|
||||
const stacktrace& from_current_exception() {
|
||||
return detail::current_exception_trace.get_resolved_trace();
|
||||
}
|
||||
|
||||
const raw_trace& raw_trace_from_current_exception_rethrow() {
|
||||
return detail::saved_rethrow_trace.get_raw_trace();
|
||||
}
|
||||
|
||||
const stacktrace& from_current_exception_rethrow() {
|
||||
return detail::saved_rethrow_trace.get_resolved_trace();
|
||||
}
|
||||
|
||||
bool current_exception_was_rethrown() {
|
||||
if(detail::saved_rethrow_trace.is_resolved()) {
|
||||
return !detail::saved_rethrow_trace.get_resolved_trace().empty();
|
||||
} else {
|
||||
return !detail::saved_rethrow_trace.get_raw_trace().empty();
|
||||
}
|
||||
}
|
||||
|
||||
// The non-argument overload is to serve as room for possible future optimization under Microsoft's STL
|
||||
CPPTRACE_FORCE_NO_INLINE void rethrow() {
|
||||
auto guard = detail::setup_rethrow();
|
||||
std::rethrow_exception(std::current_exception());
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE void rethrow(std::exception_ptr exception) {
|
||||
auto guard = detail::setup_rethrow();
|
||||
std::rethrow_exception(exception);
|
||||
}
|
||||
|
||||
void clear_current_exception_traces() {
|
||||
detail::current_exception_trace = detail::lazy_trace_holder{raw_trace{}};
|
||||
detail::saved_rethrow_trace = detail::lazy_trace_holder{raw_trace{}};
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
#include "jit/jit_objects.hpp"
|
||||
|
||||
#include "cpptrace/forward.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/optional.hpp"
|
||||
#include "utils/span.hpp"
|
||||
#include "binary/elf.hpp"
|
||||
#include "binary/mach-o.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
#if IS_LINUX || IS_APPLE
|
||||
class jit_object_manager {
|
||||
struct object_entry {
|
||||
const char* object_start;
|
||||
std::unique_ptr<jit_object_type> object;
|
||||
};
|
||||
std::vector<object_entry> objects;
|
||||
|
||||
struct range_entry {
|
||||
frame_ptr low;
|
||||
frame_ptr high; // not inclusive
|
||||
const char* object_start;
|
||||
jit_object_type* object;
|
||||
bool operator<(const range_entry& other) const {
|
||||
return low < other.low;
|
||||
}
|
||||
};
|
||||
// TODO: Maybe use a set...
|
||||
std::vector<range_entry> range_list;
|
||||
|
||||
public:
|
||||
void add_jit_object(cbspan object) {
|
||||
auto object_res = jit_object_type::open(object);
|
||||
if(object_res.is_error()) {
|
||||
if(!should_absorb_trace_exceptions()) {
|
||||
object_res.drop_error();
|
||||
}
|
||||
return;
|
||||
}
|
||||
objects.push_back({object.data(), make_unique<jit_object_type>(std::move(object_res).unwrap_value())});
|
||||
auto* object_file = objects.back().object.get();
|
||||
auto ranges_res = object_file->get_pc_ranges();
|
||||
if(ranges_res.is_error()) {
|
||||
if(!should_absorb_trace_exceptions()) {
|
||||
ranges_res.drop_error();
|
||||
}
|
||||
return;
|
||||
}
|
||||
auto& ranges = ranges_res.unwrap_value();
|
||||
for(auto range : ranges) {
|
||||
range_entry entry{range.low, range.high, object.data(), object_file};
|
||||
// TODO: Perf
|
||||
range_list.insert(std::upper_bound(range_list.begin(), range_list.end(), entry), entry);
|
||||
}
|
||||
}
|
||||
|
||||
void remove_jit_object(const char* ptr) {
|
||||
// TODO: Perf
|
||||
objects.erase(
|
||||
std::remove_if(
|
||||
objects.begin(),
|
||||
objects.end(),
|
||||
[&](const object_entry& entry) { return entry.object_start == ptr; }
|
||||
),
|
||||
objects.end()
|
||||
);
|
||||
range_list.erase(
|
||||
std::remove_if(
|
||||
range_list.begin(),
|
||||
range_list.end(),
|
||||
[&](const range_entry& entry) { return entry.object_start == ptr; }
|
||||
),
|
||||
range_list.end()
|
||||
);
|
||||
}
|
||||
|
||||
optional<jit_object_lookup_result> lookup(frame_ptr pc) const {
|
||||
auto it = first_less_than_or_equal(
|
||||
range_list.begin(),
|
||||
range_list.end(),
|
||||
pc,
|
||||
[](frame_ptr pc, const range_entry& entry) {
|
||||
return pc < entry.low;
|
||||
}
|
||||
);
|
||||
if(it == range_list.end()) {
|
||||
return nullopt;
|
||||
}
|
||||
ASSERT(pc >= it->low);
|
||||
if(pc < it->high) {
|
||||
return jit_object_lookup_result{*it->object, it->low};
|
||||
} else {
|
||||
return nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
void clear_all_jit_objects() {
|
||||
objects.clear();
|
||||
range_list.clear();
|
||||
}
|
||||
};
|
||||
#else
|
||||
class jit_object_manager {
|
||||
public:
|
||||
void add_jit_object(cbspan) {}
|
||||
void remove_jit_object(const char*) {}
|
||||
void clear_all_jit_objects() {}
|
||||
};
|
||||
#endif
|
||||
|
||||
jit_object_manager& get_jit_object_manager() {
|
||||
static jit_object_manager manager;
|
||||
return manager;
|
||||
}
|
||||
|
||||
void register_jit_object(const char* ptr, std::size_t size) {
|
||||
auto& manager = get_jit_object_manager();
|
||||
manager.add_jit_object(make_span(ptr, size));
|
||||
}
|
||||
|
||||
void unregister_jit_object(const char* ptr) {
|
||||
auto& manager = get_jit_object_manager();
|
||||
manager.remove_jit_object(ptr);
|
||||
}
|
||||
|
||||
void clear_all_jit_objects() {
|
||||
auto& manager = get_jit_object_manager();
|
||||
manager.clear_all_jit_objects();
|
||||
}
|
||||
|
||||
#if IS_LINUX || IS_APPLE
|
||||
optional<jit_object_lookup_result> lookup_jit_object(frame_ptr pc) {
|
||||
return get_jit_object_manager().lookup(pc);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
#ifndef JIT_OBJECTS_HPP
|
||||
#define JIT_OBJECTS_HPP
|
||||
|
||||
#include "binary/elf.hpp"
|
||||
#include "binary/mach-o.hpp"
|
||||
#include "cpptrace/forward.hpp"
|
||||
#include "utils/optional.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
void register_jit_object(const char*, std::size_t);
|
||||
void unregister_jit_object(const char*);
|
||||
void clear_all_jit_objects();
|
||||
|
||||
#if IS_LINUX || IS_APPLE
|
||||
#if IS_LINUX
|
||||
using jit_object_type = elf;
|
||||
#elif IS_APPLE
|
||||
using jit_object_type = mach_o;
|
||||
#endif
|
||||
struct jit_object_lookup_result {
|
||||
jit_object_type& object;
|
||||
frame_ptr base;
|
||||
};
|
||||
optional<jit_object_lookup_result> lookup_jit_object(frame_ptr pc);
|
||||
#endif
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
#include "logging.hpp"
|
||||
|
||||
#include <cpptrace/forward.hpp>
|
||||
#include <cpptrace/utils.hpp>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
std::atomic<log_level> current_log_level(log_level::warning); // NOSONAR
|
||||
|
||||
void default_null_logger(log_level, const char*) {}
|
||||
|
||||
void default_stderr_logger(log_level level, const char* message) {
|
||||
microfmt::print(std::cerr, "[cpptrace {}] {}\n", to_string(level), message);
|
||||
}
|
||||
|
||||
std::function<void(log_level, const char*)>& log_callback() {
|
||||
static std::function<void(log_level, const char*)> callback{default_null_logger};
|
||||
return callback;
|
||||
}
|
||||
|
||||
void do_log(log_level level, const char* message) {
|
||||
if(level < current_log_level) {
|
||||
return;
|
||||
}
|
||||
log_callback()(level, message);
|
||||
}
|
||||
|
||||
namespace log {
|
||||
void error(const char* message) {
|
||||
do_log(log_level::error, message);
|
||||
}
|
||||
|
||||
void warn(const char* message) {
|
||||
do_log(log_level::warning, message);
|
||||
}
|
||||
|
||||
void info(const char* message) {
|
||||
do_log(log_level::info, message);
|
||||
}
|
||||
|
||||
void debug(const char* message) {
|
||||
do_log(log_level::debug, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
void set_log_level(log_level level) {
|
||||
detail::current_log_level = level;
|
||||
}
|
||||
|
||||
void set_log_callback(std::function<void(log_level, const char*)> callback) {
|
||||
detail::log_callback() = std::move(callback);
|
||||
}
|
||||
|
||||
void use_default_stderr_logger() {
|
||||
detail::log_callback() = detail::default_stderr_logger;
|
||||
}
|
||||
|
||||
const char* to_string(log_level level) {
|
||||
switch(level) {
|
||||
case log_level::debug: return "debug";
|
||||
case log_level::info: return "info";
|
||||
case log_level::warning: return "warning";
|
||||
case log_level::error: return "error";
|
||||
default: return "unknown log level";
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
#ifndef LOGGING_HPP
|
||||
#define LOGGING_HPP
|
||||
|
||||
#include <cpptrace/utils.hpp>
|
||||
|
||||
#include "utils/microfmt.hpp"
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
namespace log {
|
||||
// exported for test purposes
|
||||
CPPTRACE_EXPORT void error(const char*);
|
||||
template<typename... Args>
|
||||
void error(const char* format, Args&&... args) {
|
||||
error(microfmt::format(format, args...).c_str());
|
||||
}
|
||||
|
||||
CPPTRACE_EXPORT void warn(const char*);
|
||||
template<typename... Args>
|
||||
void warn(const char* format, Args&&... args) {
|
||||
warn(microfmt::format(format, args...).c_str());
|
||||
}
|
||||
|
||||
CPPTRACE_EXPORT void info(const char*);
|
||||
template<typename... Args>
|
||||
void info(const char* format, Args&&... args) {
|
||||
info(microfmt::format(format, args...).c_str());
|
||||
}
|
||||
|
||||
CPPTRACE_EXPORT void debug(const char*);
|
||||
template<typename... Args>
|
||||
void debug(const char* format, Args&&... args) {
|
||||
debug(microfmt::format(format, args...).c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
#include <cpptrace/basic.hpp>
|
||||
|
||||
#include "options.hpp"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
std::atomic_bool absorb_trace_exceptions(true); // NOSONAR
|
||||
std::atomic_bool resolve_inlined_calls(true); // NOSONAR
|
||||
std::atomic<cache_mode> current_cache_mode(cache_mode::prioritize_speed); // NOSONAR
|
||||
|
||||
bool should_absorb_trace_exceptions() {
|
||||
return absorb_trace_exceptions;
|
||||
}
|
||||
|
||||
bool should_resolve_inlined_calls() {
|
||||
return resolve_inlined_calls;
|
||||
}
|
||||
|
||||
cache_mode get_cache_mode() {
|
||||
return current_cache_mode;
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
void absorb_trace_exceptions(bool absorb) {
|
||||
detail::absorb_trace_exceptions = absorb;
|
||||
}
|
||||
|
||||
void enable_inlined_call_resolution(bool enable) {
|
||||
detail::resolve_inlined_calls = enable;
|
||||
}
|
||||
|
||||
namespace experimental {
|
||||
void set_cache_mode(cache_mode mode) {
|
||||
detail::current_cache_mode = mode;
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
#ifndef OPTIONS_HPP
|
||||
#define OPTIONS_HPP
|
||||
|
||||
#include <cpptrace/utils.hpp>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
// exported for test purposes
|
||||
CPPTRACE_EXPORT bool should_absorb_trace_exceptions();
|
||||
bool should_resolve_inlined_calls();
|
||||
cache_mode get_cache_mode();
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
#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>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
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::unique_lock<std::recursive_mutex> get_dbghelp_lock() {
|
||||
static std::recursive_mutex mutex;
|
||||
return std::unique_lock<std::recursive_mutex>{mutex};
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
#ifndef DBGHELP_UTILS_HPP
|
||||
#define DBGHELP_UTILS_HPP
|
||||
|
||||
#if defined(CPPTRACE_UNWIND_WITH_DBGHELP) \
|
||||
|| defined(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP) \
|
||||
|| defined(CPPTRACE_DEMANGLE_WITH_WINAPI)
|
||||
|
||||
#include "utils/common.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <mutex>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
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();
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
#ifndef EXCEPTION_TYPE_HPP
|
||||
#define EXCEPTION_TYPE_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
// libstdc++ and libc++
|
||||
#if defined(CPPTRACE_HAS_CXX_EXCEPTION_TYPE) && (IS_LIBSTDCXX || IS_LIBCXX)
|
||||
#include <typeinfo>
|
||||
#include <cxxabi.h>
|
||||
#include "demangle/demangle.hpp"
|
||||
#endif
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
inline std::string exception_type_name() {
|
||||
#if defined(CPPTRACE_HAS_CXX_EXCEPTION_TYPE) && (IS_LIBSTDCXX || IS_LIBCXX)
|
||||
const std::type_info* t = abi::__cxa_current_exception_type();
|
||||
return t ? detail::demangle(t->name(), false) : "<unknown>";
|
||||
#else
|
||||
return "<unknown>";
|
||||
#endif
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
#ifndef PATH_HPP
|
||||
#define PATH_HPP
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
#include "utils/string_view.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <cctype>
|
||||
|
||||
#if IS_WINDOWS
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
#if IS_WINDOWS
|
||||
constexpr char PATH_SEP = '\\';
|
||||
inline bool is_absolute(string_view path) {
|
||||
// I don't want to bring in shlwapi as a dependency just for PathIsRelativeA so I'm following the guidance of
|
||||
// https://stackoverflow.com/a/71941552/15675011 and
|
||||
// https://github.com/wine-mirror/wine/blob/b210a204137dec8d2126ca909d762454fd47e963/dlls/kernelbase/path.c#L982
|
||||
if(path.empty() || IsDBCSLeadByte(path[0])) {
|
||||
return false;
|
||||
}
|
||||
if(path[0] == '\\') {
|
||||
return true;
|
||||
}
|
||||
if(path.size() >= 2 && std::isalpha(path[0]) && path[1] == ':') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
constexpr char PATH_SEP = '/';
|
||||
inline bool is_absolute(string_view path) {
|
||||
if(path.empty()) {
|
||||
return false;
|
||||
}
|
||||
return path[0] == '/';
|
||||
}
|
||||
#endif
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
#ifndef PLATFORM_HPP
|
||||
#define PLATFORM_HPP
|
||||
|
||||
#define IS_WINDOWS 0
|
||||
#define IS_LINUX 0
|
||||
#define IS_APPLE 0
|
||||
|
||||
#if defined(_WIN32)
|
||||
#undef IS_WINDOWS
|
||||
#define IS_WINDOWS 1
|
||||
#elif defined(__linux)
|
||||
#undef IS_LINUX
|
||||
#define IS_LINUX 1
|
||||
#elif defined(__APPLE__)
|
||||
#undef IS_APPLE
|
||||
#define IS_APPLE 1
|
||||
#else
|
||||
#error "Unexpected platform"
|
||||
#endif
|
||||
|
||||
#define IS_CLANG 0
|
||||
#define IS_GCC 0
|
||||
#define IS_MSVC 0
|
||||
|
||||
#if defined(__clang__)
|
||||
#undef IS_CLANG
|
||||
#define IS_CLANG 1
|
||||
#elif defined(__GNUC__) || defined(__GNUG__)
|
||||
#undef IS_GCC
|
||||
#define IS_GCC 1
|
||||
#elif defined(_MSC_VER)
|
||||
#undef IS_MSVC
|
||||
#define IS_MSVC 1
|
||||
#else
|
||||
#error "Unsupported compiler"
|
||||
#endif
|
||||
|
||||
#define IS_LIBSTDCXX 0
|
||||
#define IS_LIBCXX 0
|
||||
#if defined(__GLIBCXX__) || defined(__GLIBCPP__)
|
||||
#undef IS_LIBSTDCXX
|
||||
#define IS_LIBSTDCXX 1
|
||||
#elif defined(_LIBCPP_VERSION)
|
||||
#undef IS_LIBCXX
|
||||
#define IS_LIBCXX 1
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
#ifndef PROGRAM_NAME_HPP
|
||||
#define PROGRAM_NAME_HPP
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#if IS_WINDOWS
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
|
||||
#define CPPTRACE_MAX_PATH MAX_PATH
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
inline const char* program_name() {
|
||||
static std::mutex mutex;
|
||||
const std::lock_guard<std::mutex> lock(mutex);
|
||||
static std::string name;
|
||||
static bool did_init = false;
|
||||
static bool valid = false;
|
||||
if(!did_init) {
|
||||
did_init = true;
|
||||
char buffer[MAX_PATH + 1];
|
||||
int res = GetModuleFileNameA(nullptr, buffer, MAX_PATH);
|
||||
if(res) {
|
||||
name = buffer;
|
||||
valid = true;
|
||||
}
|
||||
}
|
||||
return valid && !name.empty() ? name.c_str() : nullptr;
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#elif IS_APPLE
|
||||
|
||||
#include <cstdint>
|
||||
#include <mach-o/dyld.h>
|
||||
#include <sys/syslimits.h>
|
||||
|
||||
#define CPPTRACE_MAX_PATH CPPTRACE_PATH_MAX
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
inline const char* program_name() {
|
||||
static std::mutex mutex;
|
||||
const std::lock_guard<std::mutex> lock(mutex);
|
||||
static std::string name;
|
||||
static bool did_init = false;
|
||||
static bool valid = false;
|
||||
if(!did_init) {
|
||||
did_init = true;
|
||||
char buffer[CPPTRACE_PATH_MAX + 1];
|
||||
std::uint32_t bufferSize = sizeof buffer;
|
||||
if(_NSGetExecutablePath(buffer, &bufferSize) == 0) {
|
||||
name.assign(buffer, bufferSize);
|
||||
valid = true;
|
||||
}
|
||||
}
|
||||
return valid && !name.empty() ? name.c_str() : nullptr;
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#elif IS_LINUX
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define CPPTRACE_MAX_PATH CPPTRACE_PATH_MAX
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
inline const char* program_name() {
|
||||
static std::mutex mutex;
|
||||
const std::lock_guard<std::mutex> lock(mutex);
|
||||
static std::string name;
|
||||
static bool did_init = false;
|
||||
static bool valid = false;
|
||||
if(!did_init) {
|
||||
did_init = true;
|
||||
char buffer[CPPTRACE_PATH_MAX + 1];
|
||||
const ssize_t size = readlink("/proc/self/exe", buffer, CPPTRACE_PATH_MAX);
|
||||
if(size == -1) {
|
||||
return nullptr;
|
||||
}
|
||||
buffer[size] = 0;
|
||||
name = buffer;
|
||||
valid = true;
|
||||
}
|
||||
return valid && !name.empty() ? name.c_str() : nullptr;
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,986 @@
|
|||
#include "cpptrace/forward.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <vector>
|
||||
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/optional.hpp"
|
||||
#include "utils/string_view.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
// Docs
|
||||
// https://itanium-cxx-abi.github.io/cxx-abi/abi.html
|
||||
// https://en.wikiversity.org/wiki/Visual_C%2B%2B_name_mangling
|
||||
|
||||
// Demangling
|
||||
// https://github.com/llvm/llvm-project/blob/main/libcxxabi/src/demangle/ItaniumDemangle.h
|
||||
// https://github.com/gcc-mirror/gcc/blob/b76f1fb7bf8a7b66b8acd469309257f8b18c0c51/libiberty/cp-demangle.c#L6794
|
||||
// https://github.com/wine-mirror/wine/blob/3295365ba5654d6ff2da37c1ffa84aed81291fc1/dlls/msvcrt/undname.c#L1476
|
||||
|
||||
// Mangling
|
||||
// https://github.com/llvm/llvm-project/blob/1463da8c4063cf1f1513aa5dbcedb44d2099c87f/clang/include/clang/AST/Mangle.h
|
||||
// https://github.com/llvm/llvm-project/blob/1463da8c4063cf1f1513aa5dbcedb44d2099c87f/clang/lib/AST/MicrosoftMangle.cpp#L1709-L1721
|
||||
|
||||
// Test cases
|
||||
// https://github.com/llvm/llvm-project/tree/d1b0b4bb4405c144e23be3d5c0459b03f95bd5ac/llvm/test/Demangle
|
||||
// https://github.com/llvm/llvm-project/blob/d1b0b4bb4405c144e23be3d5c0459b03f95bd5ac/libcxxabi/test/DemangleTestCases.inc
|
||||
// https://github.com/llvm/llvm-project/blob/d1b0b4bb4405c144e23be3d5c0459b03f95bd5ac/libcxxabi/test/test_demangle.pass.cpp
|
||||
// https://github.com/wine-mirror/wine/blob/3295365ba5654d6ff2da37c1ffa84aed81291fc1/dlls/msvcrt/tests/cpp.c#L108
|
||||
// https://github.com/wine-mirror/wine/blob/3295365ba5654d6ff2da37c1ffa84aed81291fc1/dlls/ucrtbase/tests/cpp.c#L57
|
||||
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
template<typename T, typename Arg>
|
||||
bool is_any(const T& value, const Arg& arg) {
|
||||
return value == arg;
|
||||
}
|
||||
|
||||
template<typename T, typename Arg, typename... Args>
|
||||
bool is_any(const T& value, const Arg& arg, const Args&... args) {
|
||||
return (value == arg) || is_any(value, args...);
|
||||
}
|
||||
|
||||
// http://eel.is/c++draft/lex.name#nt:identifier
|
||||
bool is_identifier_start(char c) {
|
||||
return isalpha(c) || c == '$' || c == '_';
|
||||
}
|
||||
bool is_identifier_continue(char c) {
|
||||
return isdigit(c) || is_identifier_start(c);
|
||||
}
|
||||
bool is_hex_digit(char c) {
|
||||
return isdigit(c) || is_any(c, 'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F');
|
||||
}
|
||||
bool is_octal_digit(char c) {
|
||||
return is_any(c, '0', '1', '2', '3', '4', '5', '6', '7');
|
||||
}
|
||||
bool is_simple_escape_char(char c) {
|
||||
return is_any(c, '\'', '"', '?', '\\', 'a', 'b', 'f', 'n', 'r', 't', 'v');
|
||||
}
|
||||
|
||||
// http://eel.is/c++draft/lex.operators#nt:operator-or-punctuator
|
||||
const std::vector<string_view> punctuators_and_operators = []() {
|
||||
std::vector<string_view> vec{
|
||||
"{", "}", "[", "]", "(", ")",
|
||||
"<:", ":>", "<%", "%>", ";", ":", "...",
|
||||
"?", "::", ".", ".*", "->", "->*", "~",
|
||||
"!", "+", "-", "*", "/", "%", "^", "&", "|",
|
||||
"=", "+=", "-=", "*=", "/=", "%=", "^=", "&=", "|=",
|
||||
"==", "!=", "<", ">", "<=", ">=", "<=>", "&&", "||",
|
||||
"<<", ">>", "<<=", ">>=", "++", "--", ",",
|
||||
// "and", "or", "xor", "not", "bitand", "bitor", "compl",
|
||||
// "and_eq", "or_eq", "xor_eq", "not_eq",
|
||||
"#", // extension for {lambda()#1}
|
||||
};
|
||||
std::sort(vec.begin(), vec.end(), [](string_view a, string_view b) { return a.size() > b.size(); });
|
||||
return vec;
|
||||
} ();
|
||||
|
||||
const std::array<string_view, 2> anonymous_namespace_spellings = {"(anonymous namespace)", "`anonymous namespace'"};
|
||||
|
||||
bool is_opening_punctuation(string_view token) {
|
||||
return token == "(" || token == "[" || token == "{" || token == "<";
|
||||
}
|
||||
|
||||
bool is_closing_punctuation(string_view token) {
|
||||
return token == ")" || token == "]" || token == "}" || token == ">";
|
||||
}
|
||||
|
||||
string_view get_corresponding_punctuation(string_view token) {
|
||||
if(token == "(") {
|
||||
return ")";
|
||||
} else if(token == "[") {
|
||||
return "]";
|
||||
} else if(token == "{") {
|
||||
return "}";
|
||||
} else if(token == "<") {
|
||||
return ">";
|
||||
}
|
||||
PANIC();
|
||||
}
|
||||
|
||||
bool is_ignored_identifier(string_view string) {
|
||||
return is_any(string, "const", "volatile", "decltype", "noexcept");
|
||||
}
|
||||
|
||||
bool is_microsoft_calling_convention(string_view string) {
|
||||
return is_any(
|
||||
string,
|
||||
"__cdecl",
|
||||
"__clrcall",
|
||||
"__stdcall",
|
||||
"__fastcall",
|
||||
"__thiscall",
|
||||
"__vectorcall"
|
||||
);
|
||||
}
|
||||
|
||||
// There are five kinds of tokens in C++: identifiers, keywords, literals, operators, and other separators
|
||||
// We tokenize a mostly-subset of this:
|
||||
// - identifiers/keywords
|
||||
// - literals: char, string, int, float. Msvc `strings' too.
|
||||
// - punctuation
|
||||
// Additionally we tokenize a few things that are useful
|
||||
// - anonymous namespace tags
|
||||
|
||||
enum class token_type {
|
||||
identifier,
|
||||
punctuation,
|
||||
literal,
|
||||
anonymous_namespace
|
||||
};
|
||||
|
||||
struct token {
|
||||
token_type type;
|
||||
string_view str;
|
||||
|
||||
bool operator==(const token& other) const {
|
||||
return type == other.type && str == other.str;
|
||||
}
|
||||
};
|
||||
|
||||
bool is_pointer_ref(const token& token) {
|
||||
return token.type == token_type::punctuation && is_any(token.str, "*", "&", "&&");
|
||||
}
|
||||
|
||||
struct parse_error {
|
||||
int x; // this works around a gcc bug with warn_unused_result and empty structs
|
||||
explicit parse_error() = default;
|
||||
string_view what() const {
|
||||
return "Parse error";
|
||||
}
|
||||
};
|
||||
|
||||
#define CONCAT_IMPL(X, Y) X##Y
|
||||
#define CONCAT(X, Y) CONCAT_IMPL(X, Y)
|
||||
#define UNIQUE(X) CONCAT(X, __COUNTER__)
|
||||
#define TRY_PARSE_IMPL(ACTION, SUCCESS, RES) \
|
||||
Result<bool, parse_error> RES = (ACTION); \
|
||||
if((RES).is_error()) { \
|
||||
return std::move((RES)).unwrap_error(); \
|
||||
} else if((RES).unwrap_value()) { \
|
||||
SUCCESS; \
|
||||
}
|
||||
#define TRY_PARSE(ACTION, SUCCESS) TRY_PARSE_IMPL(ACTION, SUCCESS, UNIQUE(res))
|
||||
|
||||
#define TRY_TOK_IMPL(RES, ACTION, TMP) \
|
||||
const auto TMP = (ACTION); \
|
||||
if((TMP).is_error()) { \
|
||||
return std::move((TMP)).unwrap_error(); \
|
||||
} \
|
||||
const auto RES = std::move((TMP)).unwrap_value()
|
||||
#define TRY_TOK(RES, ACTION) TRY_TOK_IMPL(RES, ACTION, UNIQUE(tmp))
|
||||
|
||||
class symbol_tokenizer {
|
||||
private:
|
||||
string_view source;
|
||||
optional<token> next_token;
|
||||
|
||||
bool peek(string_view text, size_t pos = 0) const {
|
||||
return text == source.substr(pos, text.size());
|
||||
}
|
||||
|
||||
NODISCARD Result<optional<token>, parse_error> peek_anonymous_namespace() const {
|
||||
for(const auto& spelling : anonymous_namespace_spellings) {
|
||||
if(peek(spelling)) {
|
||||
return token{token_type::anonymous_namespace, {source.begin(), spelling.size()}};
|
||||
}
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
NODISCARD Result<optional<token>, parse_error> peek_number() const {
|
||||
// More or less following pp-number https://eel.is/c++draft/lex.ppnumber
|
||||
auto cursor = source.begin();
|
||||
if(cursor != source.end() && std::isdigit(*cursor)) {
|
||||
while(
|
||||
cursor != source.end()
|
||||
&& (
|
||||
std::isdigit(*cursor)
|
||||
|| is_identifier_continue(*cursor)
|
||||
|| is_any(*cursor, '\'', '-', '+', '.')
|
||||
)
|
||||
) {
|
||||
cursor++;
|
||||
}
|
||||
}
|
||||
if(cursor == source.begin()) {
|
||||
return nullopt;
|
||||
}
|
||||
return token{token_type::literal, {source.begin(), cursor}};
|
||||
}
|
||||
|
||||
NODISCARD Result<optional<token>, parse_error> peek_msvc_string() const {
|
||||
// msvc strings look like `this'
|
||||
// they nest, e.g.: ``int main(void)'::`2'::<lambda_1>::operator()(void)const'
|
||||
// TODO: Escapes?
|
||||
auto cursor = source.begin();
|
||||
if(cursor != source.end() && *cursor == '`') {
|
||||
int depth = 0;
|
||||
do {
|
||||
if(*cursor == '`') {
|
||||
depth++;
|
||||
} else if(*cursor == '\'') {
|
||||
depth--;
|
||||
}
|
||||
cursor++;
|
||||
} while(cursor != source.end() && depth != 0);
|
||||
if(depth != 0) {
|
||||
return parse_error{};
|
||||
}
|
||||
}
|
||||
if(cursor == source.begin()) {
|
||||
return nullopt;
|
||||
}
|
||||
return token{token_type::literal, {source.begin(), cursor}};
|
||||
}
|
||||
|
||||
NODISCARD Result<optional<token>, parse_error> parse_quoted_string() const {
|
||||
auto cursor = source.begin();
|
||||
if(cursor != source.end() && is_any(*cursor, '\'', '"')) {
|
||||
auto closing_quote = *cursor;
|
||||
cursor++;
|
||||
while(cursor != source.end() && *cursor != closing_quote) {
|
||||
if(*cursor == '\\') {
|
||||
if(cursor + 1 == source.end()) {
|
||||
return parse_error{};
|
||||
}
|
||||
cursor += 2;
|
||||
}
|
||||
cursor++;
|
||||
}
|
||||
if(cursor == source.end() || *cursor != closing_quote) {
|
||||
return parse_error{};
|
||||
}
|
||||
cursor++;
|
||||
}
|
||||
if(cursor == source.begin()) {
|
||||
return nullopt;
|
||||
}
|
||||
return token{token_type::literal, {source.begin(), cursor}};
|
||||
}
|
||||
|
||||
NODISCARD Result<optional<token>, parse_error> peek_literal() const {
|
||||
TRY_TOK(number, peek_number());
|
||||
if(number) {
|
||||
return number;
|
||||
}
|
||||
TRY_TOK(msvc_string, peek_msvc_string());
|
||||
if(msvc_string) {
|
||||
return msvc_string;
|
||||
}
|
||||
TRY_TOK(quoted_string, parse_quoted_string());
|
||||
if(quoted_string) {
|
||||
return quoted_string;
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
NODISCARD Result<optional<token>, parse_error> peek_punctuation(size_t pos = 0) const {
|
||||
for(const auto punctuation : punctuators_and_operators) {
|
||||
if(peek(punctuation, pos)) {
|
||||
return token{token_type::punctuation, {source.begin() + pos, punctuation.size()}};
|
||||
}
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
NODISCARD Result<optional<token>, parse_error> peek_identifier(size_t pos = 0) const {
|
||||
auto start = source.begin() + std::min(pos, source.size());;
|
||||
auto cursor = start;
|
||||
if(cursor != source.end() && is_identifier_start(*cursor)) {
|
||||
while(cursor != source.end() && is_identifier_continue(*cursor)) {
|
||||
cursor++;
|
||||
}
|
||||
}
|
||||
|
||||
if(cursor == start) {
|
||||
return nullopt;
|
||||
}
|
||||
return token{token_type::identifier, {start, cursor}};
|
||||
}
|
||||
|
||||
NODISCARD token peek_misc() const {
|
||||
ASSERT(!source.empty());
|
||||
return token{token_type::punctuation, {source.begin(), 1}};
|
||||
}
|
||||
|
||||
Result<monostate, parse_error> maybe_load_next_token() {
|
||||
if(next_token.has_value()) {
|
||||
return monostate{};
|
||||
}
|
||||
while(!source.empty() && std::isspace(source[0])) {
|
||||
source.advance(1);
|
||||
}
|
||||
if(source.empty()) {
|
||||
return monostate{};
|
||||
}
|
||||
TRY_TOK(anon, peek_anonymous_namespace());
|
||||
if(anon) {
|
||||
next_token = anon.unwrap();
|
||||
return monostate{};
|
||||
}
|
||||
TRY_TOK(literal, peek_literal());
|
||||
if(literal) {
|
||||
next_token = literal.unwrap();
|
||||
return monostate{};
|
||||
}
|
||||
TRY_TOK(punctuation, peek_punctuation());
|
||||
if(punctuation) {
|
||||
next_token = punctuation.unwrap();
|
||||
return monostate{};
|
||||
}
|
||||
TRY_TOK(identifier, peek_identifier());
|
||||
if(identifier) {
|
||||
next_token = identifier.unwrap();
|
||||
return monostate{};
|
||||
}
|
||||
next_token = peek_misc();
|
||||
return monostate{};
|
||||
}
|
||||
|
||||
optional<token> get_adjusted_next_token(bool in_template_argument_list) {
|
||||
// https://eel.is/c++draft/temp.names#4 decompose >> to > when we think we're in a template argument list.
|
||||
// We don't have to do this for >>= or >=.
|
||||
if(next_token && in_template_argument_list && next_token.unwrap() == token{token_type::punctuation, ">>"}) {
|
||||
auto copy = next_token.unwrap();
|
||||
copy.str = copy.str.substr(0, 1); // ">"
|
||||
return copy;
|
||||
}
|
||||
return next_token;
|
||||
}
|
||||
|
||||
public:
|
||||
symbol_tokenizer(string_view source) : source(source) {}
|
||||
|
||||
NODISCARD Result<optional<token>, parse_error> peek(bool in_template_argument_list = false) {
|
||||
auto res = maybe_load_next_token();
|
||||
if(res.is_error()) {
|
||||
return res.unwrap_error();
|
||||
}
|
||||
return get_adjusted_next_token(in_template_argument_list);
|
||||
}
|
||||
|
||||
Result<optional<token>, parse_error> advance(bool in_template_argument_list = false) {
|
||||
TRY_TOK(next, peek(in_template_argument_list));
|
||||
if(!next) {
|
||||
return nullopt;
|
||||
}
|
||||
source.advance(next.unwrap().str.size());
|
||||
next_token.reset();
|
||||
return next;
|
||||
}
|
||||
|
||||
NODISCARD Result<optional<token>, parse_error> accept(token_type type, bool in_template_argument_list = false) {
|
||||
TRY_TOK(next, peek(in_template_argument_list));
|
||||
if(next && next.unwrap().type == type) {
|
||||
advance();
|
||||
return next;
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
NODISCARD Result<optional<token>, parse_error> accept(token token, bool in_template_argument_list = false) {
|
||||
TRY_TOK(next, peek(in_template_argument_list));
|
||||
if(next && next.unwrap() == token) {
|
||||
advance();
|
||||
return next;
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
std::string prune_symbol(string_view symbol);
|
||||
|
||||
/*
|
||||
|
||||
Approximate grammar, very hacky:
|
||||
|
||||
full-symbol := { symbol }
|
||||
|
||||
symbol := symbol-fragment { "::" ["*"] symbol-fragment }
|
||||
|
||||
symbol-fragment := symbol-base [ pointer-refs ] [ punctuation-and-trailing-modifiers ]
|
||||
|
||||
symbol-base := symbol-term function-pointer
|
||||
| symbol-term
|
||||
| function-pointer
|
||||
|
||||
punctuation-and-trailing-modifiers := { [ balanced-punctuation ] [ ignored-identifier ] [ pointer-refs ] }
|
||||
|
||||
symbol-term := anonymous-namespace
|
||||
| operator
|
||||
| name
|
||||
| lambda
|
||||
| unnamed
|
||||
|
||||
function-pointer := "(" pointer-refs-junk symbol ")"
|
||||
|
||||
pointer-refs-junk := { function-pointer-modifier } { pointer-refs } { ignored-identifier }
|
||||
|
||||
anonymous-namespace := "(anonymous namespace)" | "`anonymous namespace'"
|
||||
|
||||
operator := "operator" operator-type
|
||||
|
||||
operator-type := new-delete
|
||||
| special-decltype
|
||||
| "co_await"
|
||||
| "\"\"" IDENTIFIER
|
||||
| OPERATOR
|
||||
| matching-punctuation
|
||||
| conversion-operator
|
||||
|
||||
new-delete := ( "new" | "delete" ) [ "[" "]" ]
|
||||
|
||||
special-decltype := "decltype" "(" ( "auto" | "nullptr" ) ")"
|
||||
|
||||
matching-punctuation := "(" ")"
|
||||
| "[" "]"
|
||||
|
||||
conversion-operator := symbol [ symbol ] // kind of a hack
|
||||
|
||||
name := [ "~" ] IDENTIFIER
|
||||
|
||||
lambda := "{" "lambda" { PUNCTUATION } "#" LITERAL "}"
|
||||
| LITERAL // 'lambda*' or `symbol'
|
||||
| "<" IDENTIFIER ">" // lambda_*
|
||||
|
||||
unnamed := "{" "unnamed" "#" LITERAL "}"
|
||||
| LITERAL // 'unnamed*'
|
||||
|
||||
balanced-punctuation := "(" balanced-punctuation-innards ")"
|
||||
| "[" balanced-punctuation-innards "]"
|
||||
| "<" balanced-punctuation-innards ">"
|
||||
| "{" balanced-punctuation-innards "}"
|
||||
|
||||
balanced-punctuation-innards := { ANY-TOKEN } | balanced-punctuation
|
||||
|
||||
pointer-refs := { "*" | "&" | "&&" }
|
||||
|
||||
ignored-identifier := "const" | "volatile" | "decltype" | "noexcept"
|
||||
|
||||
function-pointer-modifier := "__cdecl" | "__clrcall" | "__stdcall" | "__fastcall" | "__thiscall" | "__vectorcall"
|
||||
|
||||
*/
|
||||
|
||||
class symbol_parser {
|
||||
symbol_tokenizer& tokenizer;
|
||||
std::string name_output;
|
||||
bool last_was_identifier = false;
|
||||
bool reset_output_flag = false;
|
||||
|
||||
void append_output(token token) {
|
||||
auto is_identifier = token.type == token_type::identifier;
|
||||
if(reset_output_flag) {
|
||||
name_output.clear();
|
||||
reset_output_flag = false;
|
||||
last_was_identifier = false;
|
||||
} else if(is_identifier && last_was_identifier) {
|
||||
name_output += ' ';
|
||||
}
|
||||
name_output += token.str;
|
||||
last_was_identifier = is_identifier;
|
||||
}
|
||||
|
||||
NODISCARD Result<bool, parse_error> accept_pointer_ref(bool append) {
|
||||
bool matched = false;
|
||||
while(true) {
|
||||
TRY_TOK(token, tokenizer.peek());
|
||||
if(!token || !is_pointer_ref(token.unwrap())) {
|
||||
break;
|
||||
}
|
||||
matched = true;
|
||||
if(append) {
|
||||
append_output(token.unwrap());
|
||||
}
|
||||
tokenizer.advance();
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
NODISCARD Result<bool, parse_error> consume_balanced(string_view opening_punctuation) {
|
||||
// priority means only consider this punctuation type and no priority means considers each independently
|
||||
bool is_in_template_list = opening_punctuation == "<";
|
||||
int depth = 1;
|
||||
auto closing_punctuation = get_corresponding_punctuation(opening_punctuation);
|
||||
while(depth > 0) {
|
||||
// first try to accept an operator, this is for things like void foo<&S::operator>(S const&)>()::test
|
||||
if(is_in_template_list) {
|
||||
TRY_PARSE(accept_operator(true), continue);
|
||||
}
|
||||
// otherwise handle arbitrary token
|
||||
TRY_TOK(next, tokenizer.advance(is_in_template_list));
|
||||
if(!next) {
|
||||
break;
|
||||
}
|
||||
if(next.unwrap().type == token_type::punctuation) {
|
||||
if(next.unwrap().str == opening_punctuation) {
|
||||
depth++;
|
||||
} else if(next.unwrap().str == closing_punctuation) {
|
||||
depth--;
|
||||
} else if(is_in_template_list && is_opening_punctuation(next.unwrap().str)) {
|
||||
// If we're in a template list, recurse into any balanced punctuation we see. This handles cases
|
||||
// like foo<(S > S)>
|
||||
TRY_PARSE(consume_balanced(next.unwrap().str), (void)0);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
NODISCARD Result<bool, parse_error> accept_balanced_punctuation() {
|
||||
TRY_TOK(token, tokenizer.peek());
|
||||
if(token && token.unwrap().type == token_type::punctuation && is_opening_punctuation(token.unwrap().str)) {
|
||||
tokenizer.advance();
|
||||
TRY_PARSE(consume_balanced(token.unwrap().str), (void)0);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
NODISCARD Result<bool, parse_error> accept_ignored_identifier() {
|
||||
TRY_TOK(token, tokenizer.peek());
|
||||
if(token && token.unwrap().type == token_type::identifier && is_ignored_identifier(token.unwrap().str)) {
|
||||
tokenizer.advance();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
NODISCARD Result<bool, parse_error> accept_anonymous_namespace() {
|
||||
TRY_TOK(token, tokenizer.accept(token_type::anonymous_namespace));
|
||||
if(token) {
|
||||
append_output({ token_type::identifier, "(anonymous namespace)" });
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
NODISCARD Result<bool, parse_error> accept_new_delete() {
|
||||
TRY_TOK(token, tokenizer.peek());
|
||||
if(token && token.unwrap().type == token_type::identifier && is_any(token.unwrap().str, "new", "delete")) {
|
||||
tokenizer.advance();
|
||||
append_output(token.unwrap());
|
||||
TRY_TOK(op, tokenizer.accept({token_type::punctuation, "["}));
|
||||
if(op) {
|
||||
append_output(op.unwrap());
|
||||
TRY_TOK(op2, tokenizer.accept({token_type::punctuation, "]"}));
|
||||
if(!op2) {
|
||||
return parse_error{};
|
||||
}
|
||||
append_output(op2.unwrap());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
NODISCARD Result<bool, parse_error> accept_special_decltype() {
|
||||
TRY_TOK(token, tokenizer.accept({token_type::identifier, "decltype"}));
|
||||
if(token) {
|
||||
append_output(token.unwrap());
|
||||
TRY_TOK(op, tokenizer.accept({token_type::punctuation, "("}));
|
||||
if(!op) {
|
||||
return parse_error{};
|
||||
}
|
||||
append_output(op.unwrap());
|
||||
TRY_TOK(ident, tokenizer.accept(token_type::identifier));
|
||||
if(!ident) {
|
||||
return parse_error{};
|
||||
}
|
||||
if(!is_any(ident.unwrap().str, "auto", "nullptr")) {
|
||||
return parse_error{};
|
||||
}
|
||||
append_output(ident.unwrap());
|
||||
TRY_TOK(op2, tokenizer.accept({token_type::punctuation, ")"}));
|
||||
if(!op2) {
|
||||
return parse_error{};
|
||||
}
|
||||
append_output(op2.unwrap());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
NODISCARD Result<bool, parse_error> accept_operator(bool is_in_template_list = false) {
|
||||
// If we're in a template argument list we skip new/delete/auto/co_await/literal/conversion and only handle
|
||||
// the operator punctuation case. We also don't append for template lists.
|
||||
TRY_TOK(token, tokenizer.accept({token_type::identifier, "operator"}));
|
||||
if(token) {
|
||||
if(!is_in_template_list) {
|
||||
append_output(token.unwrap());
|
||||
TRY_PARSE(accept_new_delete(), return true);
|
||||
TRY_PARSE(accept_special_decltype(), return true);
|
||||
TRY_TOK(coawait, tokenizer.accept({token_type::identifier, "co_await"}));
|
||||
if(coawait) {
|
||||
append_output(coawait.unwrap());
|
||||
return true;
|
||||
}
|
||||
TRY_TOK(literal, tokenizer.accept({token_type::literal, "\"\""}));
|
||||
if(literal) {
|
||||
TRY_TOK(name, tokenizer.accept(token_type::identifier));
|
||||
if(!name) {
|
||||
return parse_error{};
|
||||
}
|
||||
append_output(literal.unwrap());
|
||||
append_output(name.unwrap());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
TRY_TOK(op, tokenizer.accept(token_type::punctuation));
|
||||
if(op) {
|
||||
if(!is_in_template_list) {
|
||||
append_output(op.unwrap());
|
||||
}
|
||||
if(is_any(op.unwrap().str, "(", "[")) {
|
||||
TRY_TOK(op2, tokenizer.accept(token_type::punctuation));
|
||||
if(!op2 || op2.unwrap().str != get_corresponding_punctuation(op.unwrap().str)) {
|
||||
return parse_error{};
|
||||
}
|
||||
if(!is_in_template_list) {
|
||||
append_output(op2.unwrap());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if(!is_in_template_list) {
|
||||
// Otherwise try to parse a symbol, in the case of a conversion operator
|
||||
// There is a bit of a grammer hack here, it doesn't properly "nest," but it works
|
||||
TRY_PARSE(parse_symbol(), (void)0);
|
||||
// In the case of a member function pointer, there will be a type symbol followed by a symbol for
|
||||
// the type that the member pointer points to
|
||||
TRY_TOK(maybe_ident, tokenizer.peek());
|
||||
if(maybe_ident && maybe_ident.unwrap().type == token_type::identifier) {
|
||||
TRY_PARSE(parse_symbol(), (void)0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
NODISCARD Result<bool, parse_error> accept_identifier_token() {
|
||||
bool expect = false;
|
||||
TRY_TOK(complement, tokenizer.accept({token_type::punctuation, "~"}));
|
||||
if(complement) {
|
||||
append_output(complement.unwrap());
|
||||
expect = true;
|
||||
}
|
||||
TRY_TOK(token, tokenizer.accept(token_type::identifier));
|
||||
if(token) {
|
||||
append_output(token.unwrap());
|
||||
return true;
|
||||
} else if(expect) {
|
||||
return parse_error{};
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
NODISCARD Result<bool, parse_error> consume_punctuation() {
|
||||
bool did_consume = false;
|
||||
while(true) {
|
||||
TRY_TOK(token, tokenizer.peek());
|
||||
if(
|
||||
!token
|
||||
|| !(
|
||||
token.unwrap().type == token_type::punctuation
|
||||
&& token.unwrap().str != "::"
|
||||
&& token.unwrap().str != "#"
|
||||
)
|
||||
) {
|
||||
break;
|
||||
}
|
||||
TRY_PARSE(accept_balanced_punctuation(), {did_consume = true; continue;});
|
||||
// otherwise, if not balanced punctuation, just consume and drop
|
||||
tokenizer.advance();
|
||||
did_consume = true;
|
||||
}
|
||||
return did_consume;
|
||||
}
|
||||
|
||||
NODISCARD Result<bool, parse_error> accept_lambda_or_unnamed() {
|
||||
// LLVM does main::'lambda'<...>(...)::operator()<...>(...) -- apparently this can be 'lambda<count>'
|
||||
// GCC does main::{lambda<...>(...)#1}::operator()<...>(...)
|
||||
// MSVC does `int main(void)'::`2'::<lambda_1>::operator()<...>(...)
|
||||
// https://github.com/llvm/llvm-project/blob/90beda2aba3cac34052827c560449fcb184c7313/libcxxabi/src/demangle/ItaniumDemangle.h#L1848-L1850 TODO: What about the count?
|
||||
// https://github.com/gcc-mirror/gcc/blob/b76f1fb7bf8a7b66b8acd469309257f8b18c0c51/libiberty/cp-demangle.c#L6210-L6251 TODO: What special characters can appear?
|
||||
TRY_TOK(opening_brace, tokenizer.accept({token_type::punctuation, "{"}));
|
||||
if(opening_brace) {
|
||||
token token1{};
|
||||
token token2{};
|
||||
bool two_tokens = false; // this awfulness to work around gcc's maybe-uninitialized analysis
|
||||
TRY_TOK(lambda_token, tokenizer.accept({token_type::identifier, "lambda"}));
|
||||
if(lambda_token) {
|
||||
token1 = lambda_token.unwrap();
|
||||
} else {
|
||||
TRY_TOK(unnamed_token, tokenizer.accept({token_type::identifier, "unnamed"}));
|
||||
if(!unnamed_token) {
|
||||
return parse_error{};
|
||||
}
|
||||
TRY_TOK(type_token, tokenizer.accept({token_type::identifier, "type"}));
|
||||
if(!type_token) {
|
||||
return parse_error{};
|
||||
}
|
||||
token1 = unnamed_token.unwrap();
|
||||
token2 = type_token.unwrap();
|
||||
two_tokens = true;
|
||||
}
|
||||
TRY_PARSE(consume_punctuation(), (void)0);
|
||||
TRY_TOK(hash_token, tokenizer.accept({token_type::punctuation, "#"}));
|
||||
if(!hash_token) {
|
||||
return parse_error{};
|
||||
}
|
||||
TRY_TOK(discriminator_token, tokenizer.accept(token_type::literal));
|
||||
if(!discriminator_token) {
|
||||
return parse_error{};
|
||||
}
|
||||
TRY_TOK(closing_brace, tokenizer.accept({token_type::punctuation, "}"}));
|
||||
if(!closing_brace) {
|
||||
return parse_error{};
|
||||
}
|
||||
append_output({token_type::punctuation, "<"});
|
||||
append_output(token1);
|
||||
if(two_tokens) {
|
||||
append_output(token2);
|
||||
}
|
||||
append_output(hash_token.unwrap());
|
||||
append_output(discriminator_token.unwrap());
|
||||
append_output({token_type::punctuation, ">"});
|
||||
return true;
|
||||
}
|
||||
TRY_TOK(maybe_literal_token, tokenizer.peek());
|
||||
if(
|
||||
maybe_literal_token
|
||||
&& maybe_literal_token.unwrap().type == token_type::literal
|
||||
&& (
|
||||
maybe_literal_token.unwrap().str.starts_with("'lambda")
|
||||
|| maybe_literal_token.unwrap().str.starts_with("'unnamed")
|
||||
)
|
||||
&& maybe_literal_token.unwrap().str.ends_with("'")
|
||||
) {
|
||||
tokenizer.advance();
|
||||
append_output({token_type::punctuation, "<"});
|
||||
auto str = maybe_literal_token.unwrap().str;
|
||||
append_output({token_type::punctuation, str.substr(1, str.size() - 2)});
|
||||
append_output({token_type::punctuation, ">"});
|
||||
return true;
|
||||
}
|
||||
if(
|
||||
maybe_literal_token
|
||||
&& maybe_literal_token.unwrap().type == token_type::literal
|
||||
&& maybe_literal_token.unwrap().str.starts_with("`")
|
||||
&& maybe_literal_token.unwrap().str.ends_with("'")
|
||||
) {
|
||||
tokenizer.advance();
|
||||
// append_output(maybe_literal_token.unwrap());
|
||||
// This string is going to be another symbol, recursively reduce it
|
||||
append_output({token_type::punctuation, "`"});
|
||||
auto symbol = maybe_literal_token.unwrap().str;
|
||||
ASSERT(symbol.size() >= 2);
|
||||
auto name = detail::prune_symbol(symbol.substr(1, symbol.size() - 2));
|
||||
append_output({token_type::literal, name});
|
||||
append_output({token_type::punctuation, "'"});
|
||||
return true;
|
||||
}
|
||||
TRY_TOK(opening_bracket, tokenizer.accept({token_type::punctuation, "<"}));
|
||||
if(opening_bracket) {
|
||||
TRY_TOK(lambda_token, tokenizer.accept(token_type::identifier));
|
||||
if(!lambda_token || !lambda_token.unwrap().str.starts_with("lambda_")) {
|
||||
return parse_error{};
|
||||
}
|
||||
TRY_TOK(closing_bracket, tokenizer.accept({token_type::punctuation, ">"}));
|
||||
if(!closing_bracket) {
|
||||
return parse_error{};
|
||||
}
|
||||
append_output(opening_bracket.unwrap());
|
||||
append_output(lambda_token.unwrap());
|
||||
append_output(closing_bracket.unwrap());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// lookahead to match "(*", "(__cdecl*", etc
|
||||
NODISCARD bool lookahead_is_function_pointer() const {
|
||||
auto res = [] (symbol_tokenizer tokenizer_copy) -> Result<bool, parse_error> {
|
||||
TRY_TOK(maybe_opening_parenthesis, tokenizer_copy.accept({token_type::punctuation, "("}));
|
||||
if(maybe_opening_parenthesis) {
|
||||
while(true) {
|
||||
auto res = tokenizer_copy.advance();
|
||||
if(res.is_error()) {
|
||||
return false;
|
||||
}
|
||||
auto token = res.unwrap_value();
|
||||
if(token && is_pointer_ref(token.unwrap())) {
|
||||
return true;
|
||||
} else if(
|
||||
token
|
||||
&& token.unwrap().type == token_type::identifier
|
||||
&& is_microsoft_calling_convention(token.unwrap().str)
|
||||
) {
|
||||
// pass
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
} (tokenizer);
|
||||
if(res.is_error()) {
|
||||
return false;
|
||||
}
|
||||
return res.unwrap_value();
|
||||
}
|
||||
|
||||
NODISCARD Result<bool, parse_error> parse_function_pointer() {
|
||||
ASSERT(lookahead_is_function_pointer());
|
||||
TRY_TOK(opening_parenthesis, tokenizer.accept({token_type::punctuation, "("}));
|
||||
if(opening_parenthesis) {
|
||||
bool saw_pointer = false;
|
||||
while(true) {
|
||||
TRY_TOK(next, tokenizer.peek());
|
||||
if(next && is_pointer_ref(next.unwrap())) {
|
||||
tokenizer.advance();
|
||||
saw_pointer = true;
|
||||
} else if(
|
||||
next
|
||||
&& next.unwrap().type == token_type::identifier
|
||||
&& (
|
||||
is_microsoft_calling_convention(next.unwrap().str)
|
||||
|| is_ignored_identifier(next.unwrap().str)
|
||||
)
|
||||
) {
|
||||
tokenizer.advance();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!saw_pointer) {
|
||||
return parse_error{};
|
||||
}
|
||||
bool did_parse = false;
|
||||
reset_output_flag = true;
|
||||
TRY_PARSE(parse_symbol(), did_parse = true);
|
||||
if(!did_parse) {
|
||||
return parse_error{};
|
||||
}
|
||||
TRY_TOK(closing_parenthesis, tokenizer.accept({token_type::punctuation, ")"}));
|
||||
if(!closing_parenthesis) {
|
||||
return parse_error{};
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
NODISCARD Result<bool, parse_error> parse_symbol_term() {
|
||||
TRY_PARSE(accept_anonymous_namespace(), return true);
|
||||
TRY_PARSE(accept_operator(), return true);
|
||||
TRY_PARSE(accept_identifier_token(), return true);
|
||||
TRY_PARSE(accept_lambda_or_unnamed(), return true);
|
||||
return false;
|
||||
}
|
||||
|
||||
NODISCARD Result<bool, parse_error> consume_punctuation_and_trailing_modifiers() {
|
||||
bool did_consume = false;
|
||||
while(true) {
|
||||
TRY_TOK(token, tokenizer.peek());
|
||||
if(!token) {
|
||||
break;
|
||||
}
|
||||
TRY_PARSE(accept_balanced_punctuation(), {did_consume = true; continue;});
|
||||
TRY_PARSE(accept_ignored_identifier(), {did_consume = true; continue;});
|
||||
TRY_PARSE(accept_pointer_ref(false), {did_consume = true; continue;});
|
||||
break;
|
||||
}
|
||||
return did_consume;
|
||||
}
|
||||
|
||||
NODISCARD Result<bool, parse_error> parse_symbol() {
|
||||
bool made_progress = false;
|
||||
while(true) {
|
||||
TRY_TOK(token, tokenizer.peek());
|
||||
if(!token) {
|
||||
break;
|
||||
}
|
||||
bool did_match_term = false;
|
||||
TRY_PARSE(parse_symbol_term(), did_match_term = true);
|
||||
if(lookahead_is_function_pointer()) {
|
||||
TRY_PARSE(parse_function_pointer(), did_match_term = true);
|
||||
}
|
||||
if(did_match_term) {
|
||||
made_progress = true;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
TRY_PARSE(accept_pointer_ref(true), made_progress = true);
|
||||
TRY_PARSE(consume_punctuation_and_trailing_modifiers(), made_progress = true);
|
||||
TRY_TOK(scope_resolution, tokenizer.accept({token_type::punctuation, "::"}));
|
||||
if(scope_resolution) {
|
||||
append_output(scope_resolution.unwrap());
|
||||
made_progress = true;
|
||||
// for pointer to members
|
||||
TRY_TOK(star, tokenizer.accept({token_type::punctuation, "*"}));
|
||||
if(star) {
|
||||
append_output(star.unwrap());
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return made_progress;
|
||||
}
|
||||
|
||||
public:
|
||||
symbol_parser(symbol_tokenizer& tokenizer) : tokenizer(tokenizer) {}
|
||||
|
||||
NODISCARD Result<monostate, parse_error> parse() {
|
||||
while(true) {
|
||||
TRY_TOK(token, tokenizer.peek());
|
||||
if(!token) {
|
||||
break;
|
||||
}
|
||||
reset_output_flag = true;
|
||||
bool made_progress = false;
|
||||
TRY_PARSE(parse_symbol(), made_progress = true);
|
||||
if(!made_progress) {
|
||||
return parse_error{};
|
||||
}
|
||||
}
|
||||
return monostate{};
|
||||
}
|
||||
|
||||
NODISCARD std::string name() && {
|
||||
return std::move(name_output);
|
||||
}
|
||||
};
|
||||
|
||||
NODISCARD std::string prune_symbol(string_view symbol) {
|
||||
detail::symbol_tokenizer tokenizer(symbol);
|
||||
detail::symbol_parser parser(tokenizer);
|
||||
auto res = parser.parse();
|
||||
if(res.is_error()) {
|
||||
// error
|
||||
return std::string(symbol);
|
||||
}
|
||||
auto name = std::move(parser).name();
|
||||
if(name.empty()) {
|
||||
return std::string(symbol);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
std::string prune_symbol(const std::string& symbol) {
|
||||
try {
|
||||
return detail::prune_symbol(symbol);
|
||||
} catch(...) {
|
||||
detail::log_and_maybe_propagate_exception(std::current_exception());
|
||||
return symbol;
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
|
@ -0,0 +1,210 @@
|
|||
#include "snippets/snippet.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/microfmt.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
constexpr std::int64_t max_size = 1024 * 1024 * 10; // 10 MiB
|
||||
|
||||
struct line_range {
|
||||
std::size_t begin;
|
||||
std::size_t end; // one past the end
|
||||
};
|
||||
|
||||
class snippet_manager {
|
||||
bool loaded_contents;
|
||||
std::string contents;
|
||||
// 1-based indexing
|
||||
std::vector<line_range> line_table;
|
||||
public:
|
||||
snippet_manager(const std::string& path) : loaded_contents(false) {
|
||||
std::ifstream file;
|
||||
try {
|
||||
file.open(path, std::ios::ate);
|
||||
if(file.is_open()) {
|
||||
std::ifstream::pos_type size = file.tellg();
|
||||
if(size == std::ifstream::pos_type(-1) || size > max_size) {
|
||||
return;
|
||||
}
|
||||
// else load file
|
||||
file.seekg(0, std::ios::beg);
|
||||
contents.resize(to<std::size_t>(size));
|
||||
if(!file.read(&contents[0], size)) {
|
||||
// error ...
|
||||
}
|
||||
build_line_table();
|
||||
loaded_contents = true;
|
||||
}
|
||||
} catch(const std::ifstream::failure&) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
// takes a 1-index line
|
||||
std::string get_line(std::size_t line) const {
|
||||
if(!loaded_contents || line > line_table.size()) {
|
||||
return "";
|
||||
} else {
|
||||
return contents.substr(line_table[line].begin, line_table[line].end - line_table[line].begin);
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t num_lines() const {
|
||||
return line_table.size();
|
||||
}
|
||||
|
||||
bool ok() const {
|
||||
return loaded_contents;
|
||||
}
|
||||
private:
|
||||
void build_line_table() {
|
||||
line_table.push_back({0, 0});
|
||||
std::size_t pos = 0; // stores the start of the current line
|
||||
while(true) {
|
||||
// find the end of the current line
|
||||
std::size_t terminator_pos = contents.find('\n', pos);
|
||||
if(terminator_pos == std::string::npos) {
|
||||
line_table.push_back({pos, contents.size()});
|
||||
break;
|
||||
} else {
|
||||
std::size_t end_pos = terminator_pos; // one past the end of the current line
|
||||
if(end_pos > 0 && contents[end_pos - 1] == '\r') {
|
||||
end_pos--;
|
||||
}
|
||||
line_table.push_back({pos, end_pos});
|
||||
pos = terminator_pos + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const snippet_manager& get_manager(const std::string& path) {
|
||||
static std::mutex snippet_manager_mutex;
|
||||
static std::unordered_map<std::string, const snippet_manager> snippet_managers;
|
||||
std::unique_lock<std::mutex> lock(snippet_manager_mutex);
|
||||
auto it = snippet_managers.find(path);
|
||||
if(it == snippet_managers.end()) {
|
||||
return snippet_managers.insert({path, snippet_manager(path)}).first->second;
|
||||
} else {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
|
||||
// how wide the margin for the line number should be
|
||||
constexpr std::size_t margin_width = 8;
|
||||
|
||||
struct snippet_context {
|
||||
std::size_t original_begin;
|
||||
std::size_t begin;
|
||||
std::size_t end;
|
||||
std::vector<std::string> lines;
|
||||
};
|
||||
|
||||
optional<snippet_context> get_lines(const std::string& path, std::size_t target_line, std::size_t context_size) {
|
||||
const auto& manager = get_manager(path);
|
||||
if(!manager.ok()) {
|
||||
return nullopt;
|
||||
}
|
||||
auto begin = target_line <= context_size + 1 ? 1 : target_line - context_size;
|
||||
auto original_begin = begin;
|
||||
auto end = std::min(target_line + context_size, manager.num_lines() - 1);
|
||||
std::vector<std::string> lines;
|
||||
for(auto line = begin; line <= end; line++) {
|
||||
lines.push_back(manager.get_line(line));
|
||||
}
|
||||
// trim blank lines
|
||||
while(begin < target_line && lines[begin - original_begin].empty()) {
|
||||
begin++;
|
||||
}
|
||||
while(end > target_line && lines[end - original_begin].empty()) {
|
||||
end--;
|
||||
}
|
||||
return snippet_context{original_begin, begin, end, std::move(lines)};
|
||||
}
|
||||
|
||||
std::string write_line_number(std::size_t line, std::size_t target_line, bool color) {
|
||||
std::string snippet;
|
||||
auto line_str = std::to_string(line);
|
||||
if(line == target_line) {
|
||||
if(color) {
|
||||
snippet += YELLOW;
|
||||
}
|
||||
auto line_width = line_str.size() + 3;
|
||||
snippet += microfmt::format(
|
||||
"{>{}} > {}: ",
|
||||
line_width > margin_width ? 0 : margin_width - line_width,
|
||||
"",
|
||||
line_str
|
||||
);
|
||||
if(color) {
|
||||
snippet += RESET;
|
||||
}
|
||||
} else {
|
||||
snippet += microfmt::format("{>{}}: ", margin_width, line_str);
|
||||
}
|
||||
return snippet;
|
||||
}
|
||||
|
||||
std::string write_carrot(std::uint32_t column, bool color) {
|
||||
std::string snippet;
|
||||
snippet += microfmt::format("\n{>{}}", margin_width + 2 + column - 1, "");
|
||||
if(color) {
|
||||
snippet += YELLOW;
|
||||
}
|
||||
snippet += "^";
|
||||
if(color) {
|
||||
snippet += RESET;
|
||||
}
|
||||
return snippet;
|
||||
}
|
||||
|
||||
std::string write_line(
|
||||
std::size_t line,
|
||||
nullable<std::uint32_t> column,
|
||||
std::size_t target_line,
|
||||
bool color,
|
||||
const snippet_context& context
|
||||
) {
|
||||
std::string snippet;
|
||||
snippet += write_line_number(line, target_line, color);
|
||||
snippet += context.lines[line - context.original_begin];
|
||||
if(line == target_line && column.has_value()) {
|
||||
snippet += write_carrot(column.value(), color);
|
||||
}
|
||||
return snippet;
|
||||
}
|
||||
|
||||
// 1-indexed line
|
||||
std::string get_snippet(
|
||||
const std::string& path,
|
||||
std::size_t target_line,
|
||||
nullable<std::uint32_t> column,
|
||||
std::size_t context_size,
|
||||
bool color
|
||||
) {
|
||||
const auto context_res = get_lines(path, target_line, context_size);
|
||||
if(!context_res) {
|
||||
return "";
|
||||
}
|
||||
const auto& context = context_res.unwrap();
|
||||
std::string snippet;
|
||||
for(auto line = context.begin; line <= context.end; line++) {
|
||||
snippet += write_line(line, column, target_line, color, context);
|
||||
if(line != context.end) {
|
||||
snippet += '\n';
|
||||
}
|
||||
}
|
||||
return snippet;
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
#ifndef SNIPPET_HPP
|
||||
#define SNIPPET_HPP
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
|
||||
#include <cpptrace/basic.hpp>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
// 1-indexed line
|
||||
std::string get_snippet(
|
||||
const std::string& path,
|
||||
std::size_t line,
|
||||
nullable<std::uint32_t> column,
|
||||
std::size_t context_size,
|
||||
bool color
|
||||
);
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,208 @@
|
|||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF
|
||||
|
||||
#include "symbols/dwarf/resolver.hpp"
|
||||
|
||||
#include <cpptrace/basic.hpp>
|
||||
#include "symbols/symbols.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "binary/object.hpp"
|
||||
#include "binary/mach-o.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
namespace libdwarf {
|
||||
#if IS_APPLE
|
||||
struct target_object {
|
||||
std::string object_path;
|
||||
bool path_ok = true;
|
||||
optional<std::unordered_map<std::string, uint64_t>> symbols;
|
||||
std::unique_ptr<symbol_resolver> resolver;
|
||||
|
||||
target_object(std::string object_path) : object_path(std::move(object_path)) {}
|
||||
|
||||
std::unique_ptr<symbol_resolver>& get_resolver() {
|
||||
if(!resolver) {
|
||||
// this seems silly but it's an attempt to not repeatedly try to initialize new dwarf_resolvers if
|
||||
// exceptions are thrown, e.g. if the path doesn't exist
|
||||
resolver = detail::make_unique<null_resolver>();
|
||||
resolver = make_dwarf_resolver(object_path);
|
||||
}
|
||||
return resolver;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, uint64_t>& get_symbols() {
|
||||
if(!symbols) {
|
||||
// this is an attempt to not repeatedly try to reprocess mach-o files if exceptions are thrown, e.g. if
|
||||
// the path doesn't exist
|
||||
std::unordered_map<std::string, uint64_t> symbols;
|
||||
this->symbols = symbols;
|
||||
auto mach_o_object = open_mach_o_cached(object_path);
|
||||
if(!mach_o_object) {
|
||||
return this->symbols.unwrap();
|
||||
}
|
||||
const auto& symbol_table = mach_o_object.unwrap_value()->symbol_table();
|
||||
if(!symbol_table) {
|
||||
return this->symbols.unwrap();
|
||||
}
|
||||
for(const auto& symbol : symbol_table.unwrap_value()) {
|
||||
symbols[symbol.name] = symbol.address;
|
||||
}
|
||||
this->symbols = std::move(symbols);
|
||||
}
|
||||
return symbols.unwrap();
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
|
||||
frame_with_inlines resolve_frame(
|
||||
const object_frame& frame_info,
|
||||
const std::string& symbol_name,
|
||||
std::size_t offset
|
||||
) {
|
||||
const auto& symbol_table = get_symbols();
|
||||
auto it = symbol_table.find(symbol_name);
|
||||
if(it != symbol_table.end()) {
|
||||
auto frame = frame_info;
|
||||
// substitute a translated address object for the target file in
|
||||
frame.object_address = it->second + offset;
|
||||
auto res = get_resolver()->resolve_frame(frame);
|
||||
// replace the translated address with the object address in the binary
|
||||
res.frame.object_address = frame_info.object_address;
|
||||
return res;
|
||||
} else {
|
||||
return {
|
||||
{
|
||||
frame_info.raw_address,
|
||||
frame_info.object_address,
|
||||
nullable<std::uint32_t>::null(),
|
||||
nullable<std::uint32_t>::null(),
|
||||
frame_info.object_path,
|
||||
symbol_name,
|
||||
false
|
||||
},
|
||||
{}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct debug_map_symbol_info {
|
||||
uint64_t source_address;
|
||||
uint64_t size;
|
||||
std::string name;
|
||||
nullable<uint64_t> target_address; // T(-1) is used as a sentinel
|
||||
std::size_t object_index; // index into target_objects
|
||||
};
|
||||
|
||||
class debug_map_resolver : public symbol_resolver {
|
||||
std::vector<target_object> target_objects;
|
||||
std::vector<debug_map_symbol_info> symbols;
|
||||
public:
|
||||
debug_map_resolver(const std::string& source_object_path) {
|
||||
// load mach-o
|
||||
// TODO: Cache somehow?
|
||||
auto mach_o_object = open_mach_o_cached(source_object_path);
|
||||
if(!mach_o_object) {
|
||||
return;
|
||||
}
|
||||
mach_o& source_mach = *mach_o_object.unwrap_value();
|
||||
auto source_debug_map = source_mach.get_debug_map();
|
||||
if(!source_debug_map) {
|
||||
return;
|
||||
}
|
||||
// get symbol entries from debug map, as well as the various object files used to make this binary
|
||||
for(auto& entry : source_debug_map.unwrap_value()) {
|
||||
// object it came from
|
||||
target_objects.push_back({entry.first});
|
||||
// push the symbols
|
||||
auto& map_entry_symbols = entry.second;
|
||||
symbols.reserve(symbols.size() + map_entry_symbols.size());
|
||||
for(auto& symbol : map_entry_symbols) {
|
||||
symbols.push_back({
|
||||
symbol.source_address,
|
||||
symbol.size,
|
||||
std::move(symbol.name),
|
||||
nullable<uint64_t>::null(),
|
||||
target_objects.size() - 1
|
||||
});
|
||||
}
|
||||
}
|
||||
// sort for binary lookup later
|
||||
// TODO: Redundant?
|
||||
std::sort(
|
||||
symbols.begin(),
|
||||
symbols.end(),
|
||||
[] (
|
||||
const debug_map_symbol_info& a,
|
||||
const debug_map_symbol_info& b
|
||||
) {
|
||||
return a.source_address < b.source_address;
|
||||
}
|
||||
);
|
||||
}
|
||||
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
|
||||
frame_with_inlines resolve_frame(const object_frame& frame_info) override {
|
||||
// resolve object frame:
|
||||
// find the symbol in this executable corresponding to the object address
|
||||
// resolve the symbol in the object it came from, based on the symbol name
|
||||
auto closest_symbol_it = first_less_than_or_equal(
|
||||
symbols.begin(),
|
||||
symbols.end(),
|
||||
frame_info.object_address,
|
||||
[] (
|
||||
uint64_t pc,
|
||||
const debug_map_symbol_info& symbol
|
||||
) {
|
||||
return pc < symbol.source_address;
|
||||
}
|
||||
);
|
||||
if(closest_symbol_it != symbols.end()) {
|
||||
if(frame_info.object_address <= closest_symbol_it->source_address + closest_symbol_it->size) {
|
||||
return target_objects[closest_symbol_it->object_index].resolve_frame(
|
||||
{
|
||||
frame_info.raw_address,
|
||||
// the resolver doesn't care about the object address here, only the offset from the start
|
||||
// of the symbol and it'll lookup the symbol's base-address
|
||||
frame_info.object_address,
|
||||
frame_info.object_path
|
||||
},
|
||||
closest_symbol_it->name,
|
||||
frame_info.object_address - closest_symbol_it->source_address
|
||||
);
|
||||
}
|
||||
}
|
||||
// There was either no closest symbol or the closest symbol didn't end up containing the address we're
|
||||
// looking for, so just return a blank frame
|
||||
return {
|
||||
{
|
||||
frame_info.raw_address,
|
||||
frame_info.object_address,
|
||||
nullable<std::uint32_t>::null(),
|
||||
nullable<std::uint32_t>::null(),
|
||||
frame_info.object_path,
|
||||
"",
|
||||
false
|
||||
},
|
||||
{}
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
std::unique_ptr<symbol_resolver> make_debug_map_resolver(const std::string& object_path) {
|
||||
return detail::make_unique<debug_map_resolver>(object_path);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,542 @@
|
|||
#ifndef DWARF_HPP
|
||||
#define DWARF_HPP
|
||||
|
||||
#include <cpptrace/basic.hpp>
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/microfmt.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
|
||||
#ifdef CPPTRACE_USE_NESTED_LIBDWARF_HEADER_PATH
|
||||
#include <libdwarf/libdwarf.h>
|
||||
#include <libdwarf/dwarf.h>
|
||||
#else
|
||||
#include <libdwarf.h>
|
||||
#include <dwarf.h>
|
||||
#endif
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
namespace libdwarf {
|
||||
static_assert(std::is_pointer<Dwarf_Die>::value, "Dwarf_Die not a pointer");
|
||||
static_assert(std::is_pointer<Dwarf_Debug>::value, "Dwarf_Debug not a pointer");
|
||||
|
||||
using rangelist_entries = std::vector<std::pair<Dwarf_Addr, Dwarf_Addr>>;
|
||||
|
||||
[[noreturn]] inline void handle_dwarf_error(Dwarf_Debug dbg, Dwarf_Error error) {
|
||||
Dwarf_Unsigned ev = dwarf_errno(error);
|
||||
// dwarf_dealloc_error deallocates the message, attaching to msg is convenient
|
||||
auto msg = raii_wrap(dwarf_errmsg(error), [dbg, error] (char*) { dwarf_dealloc_error(dbg, error); });
|
||||
throw internal_error("dwarf error {} {}", ev, msg.get());
|
||||
}
|
||||
|
||||
struct die_object {
|
||||
Dwarf_Debug dbg = nullptr;
|
||||
Dwarf_Die die = 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
|
||||
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;
|
||||
}
|
||||
|
||||
die_object(Dwarf_Debug dbg, Dwarf_Die die) : dbg(dbg), die(die) {
|
||||
ASSERT(dbg != nullptr);
|
||||
}
|
||||
|
||||
~die_object() {
|
||||
release();
|
||||
}
|
||||
|
||||
void release() {
|
||||
if(die) {
|
||||
dwarf_dealloc_die(exchange(die, nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
die_object(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
|
||||
// segfault. A valid use otherwise would be moved_from.get_sibling() which would get the next CU.
|
||||
die_object(die_object&& other) noexcept
|
||||
: dbg(exchange(other.dbg, nullptr)), die(exchange(other.die, nullptr)) {}
|
||||
|
||||
die_object& operator=(die_object&& other) noexcept {
|
||||
release();
|
||||
dbg = exchange(other.dbg, nullptr);
|
||||
die = exchange(other.die, nullptr);
|
||||
return *this;
|
||||
}
|
||||
|
||||
die_object clone() const {
|
||||
Dwarf_Off global_offset = get_global_offset();
|
||||
Dwarf_Bool is_info = dwarf_get_die_infotypes_flag(die);
|
||||
Dwarf_Die die_copy = nullptr;
|
||||
VERIFY(wrap(dwarf_offdie_b, dbg, global_offset, is_info, &die_copy) == DW_DLV_OK);
|
||||
return {dbg, die_copy};
|
||||
}
|
||||
|
||||
die_object get_child() const {
|
||||
Dwarf_Die child = nullptr;
|
||||
int ret = wrap(dwarf_child, die, &child);
|
||||
if(ret == DW_DLV_OK) {
|
||||
return die_object(dbg, child);
|
||||
} else if(ret == DW_DLV_NO_ENTRY) {
|
||||
return die_object(dbg, nullptr);
|
||||
} else {
|
||||
PANIC();
|
||||
}
|
||||
}
|
||||
|
||||
die_object get_sibling() const {
|
||||
Dwarf_Die sibling = nullptr;
|
||||
int ret = wrap(dwarf_siblingof_b, dbg, die, true, &sibling);
|
||||
if(ret == DW_DLV_OK) {
|
||||
return die_object(dbg, sibling);
|
||||
} else if(ret == DW_DLV_NO_ENTRY) {
|
||||
return die_object(dbg, nullptr);
|
||||
} else {
|
||||
PANIC();
|
||||
}
|
||||
}
|
||||
|
||||
operator bool() const {
|
||||
return die != nullptr;
|
||||
}
|
||||
|
||||
Dwarf_Die get() const {
|
||||
return die;
|
||||
}
|
||||
|
||||
std::string get_name() const {
|
||||
char empty[] = "";
|
||||
char* name = empty;
|
||||
// Note: It's important to not free the string from this function.
|
||||
int ret = wrap(dwarf_diename, die, &name);
|
||||
std::string str;
|
||||
if(ret != DW_DLV_NO_ENTRY) {
|
||||
str = name;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
optional<std::string> get_string_attribute(Dwarf_Half attr_num) const {
|
||||
Dwarf_Attribute attr;
|
||||
if(wrap(dwarf_attr, die, attr_num, &attr) == DW_DLV_OK) {
|
||||
auto attwrapper = raii_wrap(attr, [] (Dwarf_Attribute attr) { dwarf_dealloc_attribute(attr); });
|
||||
char* raw_str;
|
||||
// Note: It's important to not free the string from this function.
|
||||
// https://github.com/davea42/libdwarf-code/issues/279
|
||||
VERIFY(wrap(dwarf_formstring, attr, &raw_str) == DW_DLV_OK);
|
||||
std::string str = raw_str;
|
||||
return str;
|
||||
} else {
|
||||
return nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
optional<Dwarf_Unsigned> get_unsigned_attribute(Dwarf_Half attr_num) const {
|
||||
Dwarf_Attribute attr;
|
||||
if(wrap(dwarf_attr, die, attr_num, &attr) == DW_DLV_OK) {
|
||||
auto attwrapper = raii_wrap(attr, [] (Dwarf_Attribute attr) { dwarf_dealloc_attribute(attr); });
|
||||
// Dwarf_Half form = 0;
|
||||
// VERIFY(wrap(dwarf_whatform, attr, &form) == DW_DLV_OK);
|
||||
Dwarf_Unsigned val;
|
||||
VERIFY(wrap(dwarf_formudata, attr, &val) == DW_DLV_OK);
|
||||
return val;
|
||||
} else {
|
||||
return nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
bool has_attr(Dwarf_Half attr_num) const {
|
||||
Dwarf_Bool present = false;
|
||||
VERIFY(wrap(dwarf_hasattr, die, attr_num, &present) == DW_DLV_OK);
|
||||
return present;
|
||||
}
|
||||
|
||||
Dwarf_Half get_tag() const {
|
||||
Dwarf_Half tag = 0;
|
||||
VERIFY(wrap(dwarf_tag, die, &tag) == DW_DLV_OK);
|
||||
return tag;
|
||||
}
|
||||
|
||||
const char* get_tag_name() const {
|
||||
const char* tag_name;
|
||||
if(dwarf_get_TAG_name(get_tag(), &tag_name) == DW_DLV_OK) {
|
||||
return tag_name;
|
||||
} else {
|
||||
return "<unknown tag name>";
|
||||
}
|
||||
}
|
||||
|
||||
Dwarf_Off get_global_offset() const {
|
||||
Dwarf_Off off;
|
||||
VERIFY(wrap(dwarf_dieoffset, die, &off) == DW_DLV_OK);
|
||||
return off;
|
||||
}
|
||||
|
||||
die_object resolve_reference_attribute(Dwarf_Half attr_num) const {
|
||||
Dwarf_Attribute attr;
|
||||
VERIFY(dwarf_attr(die, attr_num, &attr, nullptr) == DW_DLV_OK);
|
||||
auto wrapper = raii_wrap(attr, [] (Dwarf_Attribute attr) { dwarf_dealloc_attribute(attr); });
|
||||
Dwarf_Half form = 0;
|
||||
VERIFY(wrap(dwarf_whatform, attr, &form) == DW_DLV_OK);
|
||||
switch(form) {
|
||||
case DW_FORM_ref1:
|
||||
case DW_FORM_ref2:
|
||||
case DW_FORM_ref4:
|
||||
case DW_FORM_ref8:
|
||||
case DW_FORM_ref_udata:
|
||||
{
|
||||
Dwarf_Off off = 0;
|
||||
Dwarf_Bool is_info = dwarf_get_die_infotypes_flag(die);
|
||||
VERIFY(wrap(dwarf_formref, attr, &off, &is_info) == DW_DLV_OK);
|
||||
Dwarf_Off global_offset = 0;
|
||||
VERIFY(wrap(dwarf_convert_to_global_offset, attr, off, &global_offset) == DW_DLV_OK);
|
||||
Dwarf_Die target = nullptr;
|
||||
VERIFY(wrap(dwarf_offdie_b, dbg, global_offset, is_info, &target) == DW_DLV_OK);
|
||||
return die_object(dbg, target);
|
||||
}
|
||||
case DW_FORM_ref_addr:
|
||||
{
|
||||
Dwarf_Off off;
|
||||
VERIFY(wrap(dwarf_global_formref, attr, &off) == DW_DLV_OK);
|
||||
int is_info = dwarf_get_die_infotypes_flag(die);
|
||||
Dwarf_Die target = nullptr;
|
||||
VERIFY(wrap(dwarf_offdie_b, dbg, off, is_info, &target) == DW_DLV_OK);
|
||||
return die_object(dbg, target);
|
||||
}
|
||||
case DW_FORM_ref_sig8:
|
||||
{
|
||||
Dwarf_Sig8 signature;
|
||||
VERIFY(wrap(dwarf_formsig8, attr, &signature) == DW_DLV_OK);
|
||||
Dwarf_Die target = nullptr;
|
||||
Dwarf_Bool targ_is_info = false;
|
||||
VERIFY(wrap(dwarf_find_die_given_sig8, dbg, &signature, &target, &targ_is_info) == DW_DLV_OK);
|
||||
return die_object(dbg, target);
|
||||
}
|
||||
default:
|
||||
PANIC(microfmt::format("unknown form for attribute {} {}\n", attr_num, form));
|
||||
}
|
||||
}
|
||||
|
||||
Dwarf_Unsigned get_ranges_base_address(const die_object& cu_die) const {
|
||||
// After libdwarf v0.11.0 this can use dwarf_get_ranges_baseaddress, however, in the interest of not
|
||||
// requiring v0.11.0 just yet the logic is implemented here too.
|
||||
// The base address is:
|
||||
// - If the die has a rangelist, use the low_pc for that die
|
||||
// - Otherwise use the low_pc from the CU if present
|
||||
// - Otherwise 0
|
||||
if(has_attr(DW_AT_ranges)) {
|
||||
if(has_attr(DW_AT_low_pc)) {
|
||||
Dwarf_Addr lowpc;
|
||||
if(wrap(dwarf_lowpc, die, &lowpc) == DW_DLV_OK) {
|
||||
return lowpc;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(cu_die.has_attr(DW_AT_low_pc)) {
|
||||
Dwarf_Addr lowpc;
|
||||
if(wrap(dwarf_lowpc, cu_die.get(), &lowpc) == DW_DLV_OK) {
|
||||
return lowpc;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Dwarf_Unsigned get_ranges_offset(Dwarf_Attribute attr) const {
|
||||
Dwarf_Unsigned off = 0;
|
||||
Dwarf_Half form = 0;
|
||||
VERIFY(wrap(dwarf_whatform, attr, &form) == DW_DLV_OK);
|
||||
if (form == DW_FORM_rnglistx) {
|
||||
VERIFY(wrap(dwarf_formudata, attr, &off) == DW_DLV_OK);
|
||||
} else {
|
||||
VERIFY(wrap(dwarf_global_formref, attr, &off) == DW_DLV_OK);
|
||||
}
|
||||
return off;
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
// callback should return true to keep going
|
||||
void dwarf5_ranges(F callback) const {
|
||||
Dwarf_Attribute attr = nullptr;
|
||||
if(wrap(dwarf_attr, die, DW_AT_ranges, &attr) != DW_DLV_OK) {
|
||||
return;
|
||||
}
|
||||
auto attrwrapper = raii_wrap(attr, [] (Dwarf_Attribute attr) { dwarf_dealloc_attribute(attr); });
|
||||
Dwarf_Unsigned offset = get_ranges_offset(attr);
|
||||
Dwarf_Half form = 0;
|
||||
VERIFY(wrap(dwarf_whatform, attr, &form) == DW_DLV_OK);
|
||||
// get .debug_rnglists info
|
||||
Dwarf_Rnglists_Head head = nullptr;
|
||||
Dwarf_Unsigned rnglists_entries = 0;
|
||||
Dwarf_Unsigned dw_global_offset_of_rle_set = 0;
|
||||
int res = wrap(
|
||||
dwarf_rnglists_get_rle_head,
|
||||
attr,
|
||||
form,
|
||||
offset,
|
||||
&head,
|
||||
&rnglists_entries,
|
||||
&dw_global_offset_of_rle_set
|
||||
);
|
||||
auto headwrapper = raii_wrap(head, [] (Dwarf_Rnglists_Head head) { dwarf_dealloc_rnglists_head(head); });
|
||||
if(res == DW_DLV_NO_ENTRY) {
|
||||
return;
|
||||
}
|
||||
VERIFY(res == DW_DLV_OK);
|
||||
for(std::size_t i = 0 ; i < rnglists_entries; i++) {
|
||||
unsigned entrylen = 0;
|
||||
unsigned rle_value_out = 0;
|
||||
Dwarf_Unsigned raw1 = 0;
|
||||
Dwarf_Unsigned raw2 = 0;
|
||||
Dwarf_Bool unavailable = 0;
|
||||
Dwarf_Unsigned cooked1 = 0;
|
||||
Dwarf_Unsigned cooked2 = 0;
|
||||
res = wrap(
|
||||
dwarf_get_rnglists_entry_fields_a,
|
||||
head,
|
||||
i,
|
||||
&entrylen,
|
||||
&rle_value_out,
|
||||
&raw1,
|
||||
&raw2,
|
||||
&unavailable,
|
||||
&cooked1,
|
||||
&cooked2
|
||||
);
|
||||
if(res == DW_DLV_NO_ENTRY) {
|
||||
continue;
|
||||
}
|
||||
VERIFY(res == DW_DLV_OK);
|
||||
if(unavailable) {
|
||||
continue;
|
||||
}
|
||||
switch(rle_value_out) {
|
||||
// Following the same scheme from libdwarf-addr2line
|
||||
case DW_RLE_end_of_list:
|
||||
case DW_RLE_base_address:
|
||||
case DW_RLE_base_addressx:
|
||||
// Already handled
|
||||
break;
|
||||
case DW_RLE_offset_pair:
|
||||
case DW_RLE_startx_endx:
|
||||
case DW_RLE_start_end:
|
||||
case DW_RLE_startx_length:
|
||||
case DW_RLE_start_length:
|
||||
if(!callback(cooked1, cooked2)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
PANIC("Something is wrong");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
// callback should return true to keep going
|
||||
void dwarf4_ranges(Dwarf_Addr baseaddr, F callback) const {
|
||||
Dwarf_Attribute attr = nullptr;
|
||||
if(wrap(dwarf_attr, die, DW_AT_ranges, &attr) != DW_DLV_OK) {
|
||||
return;
|
||||
}
|
||||
auto attrwrapper = raii_wrap(attr, [] (Dwarf_Attribute attr) { dwarf_dealloc_attribute(attr); });
|
||||
Dwarf_Unsigned offset;
|
||||
if(wrap(dwarf_global_formref, attr, &offset) != DW_DLV_OK) {
|
||||
return;
|
||||
}
|
||||
Dwarf_Addr baseaddr_original = baseaddr;
|
||||
Dwarf_Ranges* ranges = nullptr;
|
||||
Dwarf_Signed count = 0;
|
||||
VERIFY(
|
||||
wrap(
|
||||
dwarf_get_ranges_b,
|
||||
dbg,
|
||||
offset,
|
||||
die,
|
||||
nullptr,
|
||||
&ranges,
|
||||
&count,
|
||||
nullptr
|
||||
) == DW_DLV_OK
|
||||
);
|
||||
auto rangeswrapper = raii_wrap(
|
||||
ranges,
|
||||
[this, count] (Dwarf_Ranges* ranges) { dwarf_dealloc_ranges(dbg, ranges, count); }
|
||||
);
|
||||
for(int i = 0; i < count; i++) {
|
||||
if(ranges[i].dwr_type == DW_RANGES_ENTRY) {
|
||||
if(!callback(baseaddr + ranges[i].dwr_addr1, baseaddr + ranges[i].dwr_addr2)) {
|
||||
return;
|
||||
}
|
||||
} else if(ranges[i].dwr_type == DW_RANGES_ADDRESS_SELECTION) {
|
||||
baseaddr = ranges[i].dwr_addr2;
|
||||
} else {
|
||||
ASSERT(ranges[i].dwr_type == DW_RANGES_END);
|
||||
baseaddr = baseaddr_original;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
// callback should return true to keep going
|
||||
void dwarf_ranges(const die_object& cu_die, int version, F callback) const {
|
||||
Dwarf_Addr lowpc;
|
||||
if(wrap(dwarf_lowpc, die, &lowpc) == DW_DLV_OK) {
|
||||
Dwarf_Addr highpc = 0;
|
||||
enum Dwarf_Form_Class return_class;
|
||||
if(wrap(dwarf_highpc_b, die, &highpc, nullptr, &return_class) == DW_DLV_OK) {
|
||||
if(return_class == DW_FORM_CLASS_CONSTANT) {
|
||||
highpc += lowpc;
|
||||
}
|
||||
if(!callback(lowpc, highpc)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(version >= 5) {
|
||||
dwarf5_ranges(callback);
|
||||
} else {
|
||||
dwarf4_ranges(get_ranges_base_address(cu_die), callback);
|
||||
}
|
||||
}
|
||||
|
||||
rangelist_entries get_rangelist_entries(const die_object& cu_die, int version) const {
|
||||
rangelist_entries vec;
|
||||
dwarf_ranges(cu_die, version, [&vec] (Dwarf_Addr low, Dwarf_Addr high) {
|
||||
// Simple coalescing optimization:
|
||||
// Sometimes the range list entries are really continuous: [100, 200), [200, 300)
|
||||
// Other times there's just one byte of separation [300, 399), [400, 500)
|
||||
// Those are the main two cases I've observed.
|
||||
// This will not catch all cases, presumably, as the range lists aren't sorted. But compilers/linkers
|
||||
// seem to like to emit the ranges in sorted order.
|
||||
if(!vec.empty() && low - vec.back().second <= 1) {
|
||||
vec.back().second = high;
|
||||
} else {
|
||||
vec.push_back({low, high});
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return vec;
|
||||
}
|
||||
|
||||
Dwarf_Bool pc_in_die(const die_object& cu_die, int version, Dwarf_Addr pc) const {
|
||||
bool found = false;
|
||||
dwarf_ranges(cu_die, version, [&found, pc] (Dwarf_Addr low, Dwarf_Addr high) {
|
||||
if(pc >= low && pc < high) {
|
||||
found = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return found;
|
||||
}
|
||||
|
||||
void print() const {
|
||||
std::fprintf(
|
||||
stderr,
|
||||
"%08llx %s %s\n",
|
||||
to_ull(get_global_offset()),
|
||||
get_tag_name(),
|
||||
get_name().c_str()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// walk die list, callback is called on each die and should return true to
|
||||
// continue traversal
|
||||
// returns true if traversal should continue
|
||||
inline bool walk_die_list(
|
||||
const die_object& die,
|
||||
const std::function<bool(const die_object&)>& fn
|
||||
) {
|
||||
// TODO: Refactor so there is only one fn call
|
||||
bool continue_traversal = true;
|
||||
if(fn(die)) {
|
||||
die_object current = die.get_sibling();
|
||||
while(current) {
|
||||
if(fn(current)) {
|
||||
current = current.get_sibling();
|
||||
} else {
|
||||
continue_traversal = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return continue_traversal;
|
||||
}
|
||||
|
||||
// walk die list, recursing into children, callback is called on each die
|
||||
// and should return true to continue traversal
|
||||
// returns true if traversal should continue
|
||||
inline bool walk_die_list_recursive(
|
||||
const die_object& die,
|
||||
const std::function<bool(const die_object&)>& fn
|
||||
) {
|
||||
return walk_die_list(
|
||||
die,
|
||||
[&fn](const die_object& die) {
|
||||
auto child = die.get_child();
|
||||
if(child) {
|
||||
if(!walk_die_list_recursive(child, fn)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return fn(die);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
class maybe_owned_die_object {
|
||||
// Hacky... I wish std::variant existed.
|
||||
optional<die_object> owned_die;
|
||||
optional<const die_object*> ref_die;
|
||||
maybe_owned_die_object(die_object&& die) : owned_die(std::move(die)) {}
|
||||
maybe_owned_die_object(const die_object& die) : ref_die(&die) {}
|
||||
public:
|
||||
static maybe_owned_die_object owned(die_object&& die) {
|
||||
return maybe_owned_die_object{std::move(die)};
|
||||
}
|
||||
static maybe_owned_die_object ref(const die_object& die) {
|
||||
return maybe_owned_die_object{die};
|
||||
}
|
||||
const die_object& get() {
|
||||
ASSERT(owned_die || ref_die, "Mal-formed maybe_owned_die_object");
|
||||
if(owned_die) {
|
||||
return owned_die.unwrap();
|
||||
} else {
|
||||
return *ref_die.unwrap();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
#include "symbols/dwarf/dwarf_options.hpp"
|
||||
|
||||
#include <cpptrace/utils.hpp>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
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();
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
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);
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
#ifndef DWARF_OPTIONS_HPP
|
||||
#define DWARF_OPTIONS_HPP
|
||||
|
||||
#include "utils/optional.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
optional<std::size_t> get_dwarf_resolver_line_table_cache_size();
|
||||
bool get_dwarf_resolver_disable_aranges();
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,163 @@
|
|||
#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"
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
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("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(
|
||||
"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;
|
||||
}
|
||||
};
|
||||
|
||||
// container of items which are keyed by ranges
|
||||
template<typename K, typename V>
|
||||
class range_map {
|
||||
public:
|
||||
struct handle {
|
||||
std::uint32_t index;
|
||||
};
|
||||
private:
|
||||
struct PACKED range_entry {
|
||||
handle item;
|
||||
K low;
|
||||
K high;
|
||||
};
|
||||
std::vector<V> items;
|
||||
std::vector<range_entry> range_entries;
|
||||
public:
|
||||
handle add_item(V&& item) {
|
||||
items.push_back(std::move(item));
|
||||
VERIFY(items.size() < std::numeric_limits<std::uint32_t>::max());
|
||||
return handle{static_cast<std::uint32_t>(items.size() - 1)};
|
||||
}
|
||||
void insert(handle handle, K low, K high) {
|
||||
range_entries.push_back({handle, low, high});
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
optional<const V&> lookup(K key) const {
|
||||
auto vec_it = first_less_than_or_equal(
|
||||
range_entries.begin(),
|
||||
range_entries.end(),
|
||||
key,
|
||||
[] (K key, const range_entry& entry) {
|
||||
return key < entry.low;
|
||||
}
|
||||
);
|
||||
if(vec_it == range_entries.end()) {
|
||||
return nullopt;
|
||||
}
|
||||
return items.at(vec_it->item.index);
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
#ifndef SYMBOL_RESOLVER_HPP
|
||||
#define SYMBOL_RESOLVER_HPP
|
||||
|
||||
#include <cpptrace/basic.hpp>
|
||||
#include "symbols/symbols.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
#include "utils/string_view.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#if false
|
||||
#define CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING CPPTRACE_FORCE_NO_INLINE
|
||||
#else
|
||||
#define CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
|
||||
#endif
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
namespace libdwarf {
|
||||
class symbol_resolver {
|
||||
public:
|
||||
virtual ~symbol_resolver() = default;
|
||||
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
|
||||
virtual frame_with_inlines resolve_frame(const object_frame& frame_info) = 0;
|
||||
};
|
||||
|
||||
class null_resolver : public symbol_resolver {
|
||||
public:
|
||||
explicit null_resolver() = default;
|
||||
null_resolver(cstring_view) {}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
|
||||
frame_with_inlines resolve_frame(const object_frame& frame_info) override {
|
||||
return {
|
||||
{
|
||||
frame_info.raw_address,
|
||||
frame_info.object_address,
|
||||
nullable<std::uint32_t>::null(),
|
||||
nullable<std::uint32_t>::null(),
|
||||
frame_info.object_path,
|
||||
"",
|
||||
false
|
||||
},
|
||||
{}
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
std::unique_ptr<symbol_resolver> make_dwarf_resolver(cstring_view object_path);
|
||||
#if IS_APPLE
|
||||
std::unique_ptr<symbol_resolver> make_debug_map_resolver(const std::string& object_path);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
#ifndef SYMBOLS_HPP
|
||||
#define SYMBOLS_HPP
|
||||
|
||||
#include <cpptrace/basic.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
using collated_vec = std::vector<
|
||||
std::pair<std::reference_wrapper<const object_frame>, std::reference_wrapper<stacktrace_frame>>
|
||||
>;
|
||||
struct frame_with_inlines {
|
||||
stacktrace_frame frame;
|
||||
std::vector<stacktrace_frame> inlines;
|
||||
};
|
||||
using collated_vec_with_inlines = std::vector<
|
||||
std::pair<std::reference_wrapper<const object_frame>, std::reference_wrapper<frame_with_inlines>>
|
||||
>;
|
||||
|
||||
// These two helpers create a map from a target object to a vector of frames to resolve
|
||||
std::unordered_map<std::string, collated_vec> collate_frames(
|
||||
const std::vector<object_frame>& frames,
|
||||
std::vector<stacktrace_frame>& trace
|
||||
);
|
||||
std::unordered_map<std::string, collated_vec_with_inlines> collate_frames(
|
||||
const std::vector<object_frame>& frames,
|
||||
std::vector<frame_with_inlines>& trace
|
||||
);
|
||||
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE
|
||||
namespace libbacktrace {
|
||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<frame_ptr>& frames);
|
||||
}
|
||||
#endif
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF
|
||||
namespace libdwarf {
|
||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<object_frame>& frames);
|
||||
}
|
||||
#endif
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDL
|
||||
namespace libdl {
|
||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<frame_ptr>& frames);
|
||||
}
|
||||
#endif
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE
|
||||
namespace addr2line {
|
||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<object_frame>& frames);
|
||||
}
|
||||
#endif
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_DBGHELP
|
||||
namespace dbghelp {
|
||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<frame_ptr>& frames);
|
||||
}
|
||||
#endif
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_NOTHING
|
||||
namespace nothing {
|
||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<object_frame>& frames);
|
||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<frame_ptr>& frames);
|
||||
}
|
||||
#endif
|
||||
|
||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<object_frame>& frames);
|
||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<frame_ptr>& frames);
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
#include <cpptrace/basic.hpp>
|
||||
#include <cpptrace/utils.hpp>
|
||||
|
||||
#include "cpptrace/forward.hpp"
|
||||
#include "symbols/symbols.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "utils/error.hpp"
|
||||
#include "binary/object.hpp"
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
template<typename CollatedVec, typename Entry>
|
||||
std::unordered_map<std::string, CollatedVec> collate_frames(
|
||||
const std::vector<object_frame>& frames,
|
||||
std::vector<Entry>& trace
|
||||
) {
|
||||
std::unordered_map<std::string, CollatedVec> entries;
|
||||
for(std::size_t i = 0; i < frames.size(); i++) {
|
||||
const auto& entry = frames[i];
|
||||
// The path may be empty. This can happens if libdl fails to find the shared object for a frame, e.g. I've
|
||||
// observed this on macos when looking up the shared object containing `start`.
|
||||
// It can also happen for JIT frames. As such, we don't exclude them from the output.
|
||||
entries[entry.object_path].emplace_back(
|
||||
entry,
|
||||
trace[i]
|
||||
);
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, collated_vec> collate_frames(
|
||||
const std::vector<object_frame>& frames,
|
||||
std::vector<stacktrace_frame>& trace
|
||||
) {
|
||||
return collate_frames<collated_vec>(frames, trace);
|
||||
}
|
||||
std::unordered_map<std::string, collated_vec_with_inlines> collate_frames(
|
||||
const std::vector<object_frame>& frames,
|
||||
std::vector<frame_with_inlines>& trace
|
||||
) {
|
||||
return collate_frames<collated_vec_with_inlines>(frames, trace);
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*
|
||||
* All the code here is awful and I'm not proud of it.
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
// Resolver must not support walking inlines
|
||||
void fill_blanks(
|
||||
std::vector<stacktrace_frame>& vec,
|
||||
std::vector<stacktrace_frame> (*resolver)(const std::vector<frame_ptr>&)
|
||||
) {
|
||||
std::vector<frame_ptr> addresses;
|
||||
for(const auto& frame : vec) {
|
||||
if(frame.symbol.empty() || frame.filename.empty()) {
|
||||
addresses.push_back(frame.raw_address);
|
||||
}
|
||||
}
|
||||
std::vector<stacktrace_frame> new_frames = resolver(addresses);
|
||||
std::size_t i = 0;
|
||||
for(auto& frame : vec) {
|
||||
if(frame.symbol.empty() || frame.filename.empty()) {
|
||||
// three cases to handle, either partially overwrite or fully overwrite
|
||||
if(frame.symbol.empty() && frame.filename.empty()) {
|
||||
frame = new_frames[i];
|
||||
} else if(frame.symbol.empty() && !frame.filename.empty()) {
|
||||
frame.symbol = new_frames[i].symbol;
|
||||
} else {
|
||||
ASSERT(!frame.symbol.empty() && frame.filename.empty());
|
||||
frame.filename = new_frames[i].filename;
|
||||
frame.line = new_frames[i].line;
|
||||
frame.column = new_frames[i].column;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Symbol resolution code should probably handle when object addresses are 0
|
||||
|
||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<object_frame>& frames) {
|
||||
#if defined(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) && defined(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP)
|
||||
std::vector<stacktrace_frame> trace = libdwarf::resolve_frames(frames);
|
||||
fill_blanks(trace, dbghelp::resolve_frames);
|
||||
return trace;
|
||||
#else
|
||||
#if defined(CPPTRACE_GET_SYMBOLS_WITH_LIBDL) \
|
||||
|| defined(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP) \
|
||||
|| defined(CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE)
|
||||
// actually need to go backwards to a void*
|
||||
std::vector<frame_ptr> raw_frames(frames.size());
|
||||
for(std::size_t i = 0; i < frames.size(); i++) {
|
||||
raw_frames[i] = frames[i].raw_address;
|
||||
}
|
||||
#endif
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDL
|
||||
return libdl::resolve_frames(raw_frames);
|
||||
#endif
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF
|
||||
return libdwarf::resolve_frames(frames);
|
||||
#endif
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_DBGHELP
|
||||
return dbghelp::resolve_frames(raw_frames);
|
||||
#endif
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE
|
||||
return addr2line::resolve_frames(frames);
|
||||
#endif
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE
|
||||
return libbacktrace::resolve_frames(raw_frames);
|
||||
#endif
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_NOTHING
|
||||
return nothing::resolve_frames(frames);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<frame_ptr>& frames) {
|
||||
#if defined(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) \
|
||||
|| defined(CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE)
|
||||
auto dlframes = get_frames_object_info(frames);
|
||||
#endif
|
||||
#if defined(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) && defined(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP)
|
||||
std::vector<stacktrace_frame> trace = libdwarf::resolve_frames(dlframes);
|
||||
fill_blanks(trace, dbghelp::resolve_frames);
|
||||
return trace;
|
||||
#else
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDL
|
||||
return libdl::resolve_frames(frames);
|
||||
#endif
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF
|
||||
return libdwarf::resolve_frames(dlframes);
|
||||
#endif
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_DBGHELP
|
||||
return dbghelp::resolve_frames(frames);
|
||||
#endif
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE
|
||||
return addr2line::resolve_frames(dlframes);
|
||||
#endif
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE
|
||||
return libbacktrace::resolve_frames(frames);
|
||||
#endif
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_NOTHING
|
||||
return nothing::resolve_frames(frames);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
|
@ -0,0 +1,328 @@
|
|||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE
|
||||
|
||||
#include <cpptrace/basic.hpp>
|
||||
#include "symbols/symbols.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/microfmt.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#if IS_LINUX || IS_APPLE
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#endif
|
||||
|
||||
#include "binary/object.hpp"
|
||||
#include "options.hpp"
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
namespace addr2line {
|
||||
#if IS_LINUX || IS_APPLE
|
||||
bool has_addr2line() {
|
||||
static std::mutex mutex;
|
||||
static bool has_addr2line = false;
|
||||
static bool checked = false;
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
if(!checked) {
|
||||
checked = true;
|
||||
// Detects if addr2line exists by trying to invoke addr2line --help
|
||||
constexpr int magic = 42;
|
||||
const pid_t pid = fork();
|
||||
if(pid == -1) { return false; }
|
||||
if(pid == 0) { // child
|
||||
close(STDOUT_FILENO);
|
||||
close(STDERR_FILENO); // atos --help writes to stderr
|
||||
#ifdef CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH
|
||||
#if !IS_APPLE
|
||||
execlp("addr2line", "addr2line", "--help", nullptr);
|
||||
#else
|
||||
execlp("atos", "atos", "--help", nullptr);
|
||||
#endif
|
||||
#else
|
||||
#ifndef CPPTRACE_ADDR2LINE_PATH
|
||||
#error "CPPTRACE_ADDR2LINE_PATH must be defined if CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH is not"
|
||||
#endif
|
||||
execl(CPPTRACE_ADDR2LINE_PATH, CPPTRACE_ADDR2LINE_PATH, "--help", nullptr);
|
||||
#endif
|
||||
_exit(magic);
|
||||
}
|
||||
int status;
|
||||
waitpid(pid, &status, 0);
|
||||
has_addr2line = WEXITSTATUS(status) == 0;
|
||||
}
|
||||
return has_addr2line;
|
||||
}
|
||||
|
||||
struct pipe_ends {
|
||||
int read;
|
||||
int write;
|
||||
};
|
||||
|
||||
struct pipe_t {
|
||||
union {
|
||||
pipe_ends end;
|
||||
int data[2];
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(pipe_t) == 2 * sizeof(int), "Unexpected struct packing");
|
||||
|
||||
std::string resolve_addresses(const std::string& addresses, const std::string& executable) {
|
||||
pipe_t output_pipe;
|
||||
pipe_t input_pipe;
|
||||
if(pipe(output_pipe.data) != 0 || pipe(input_pipe.data) != 0) {
|
||||
throw internal_error("call to pipe failed: {}", errno);
|
||||
}
|
||||
const pid_t pid = fork();
|
||||
if(pid == -1) { return ""; } // error? TODO: Diagnostic
|
||||
if(pid == 0) { // child
|
||||
dup2(output_pipe.end.write, STDOUT_FILENO);
|
||||
dup2(input_pipe.end.read, STDIN_FILENO);
|
||||
close(output_pipe.end.read);
|
||||
close(output_pipe.end.write);
|
||||
close(input_pipe.end.read);
|
||||
close(input_pipe.end.write);
|
||||
close(STDERR_FILENO); // TODO: Might be worth conditionally enabling or piping
|
||||
#ifdef CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH
|
||||
#if !IS_APPLE
|
||||
execlp("addr2line", "addr2line", "-e", executable.c_str(), "-f", "-C", "-p", nullptr);
|
||||
#else
|
||||
execlp("atos", "atos", "-o", executable.c_str(), "-fullPath", nullptr);
|
||||
#endif
|
||||
#else
|
||||
#ifndef CPPTRACE_ADDR2LINE_PATH
|
||||
#error "CPPTRACE_ADDR2LINE_PATH must be defined if CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH is not"
|
||||
#endif
|
||||
#if !IS_APPLE
|
||||
execl(
|
||||
CPPTRACE_ADDR2LINE_PATH,
|
||||
CPPTRACE_ADDR2LINE_PATH,
|
||||
"-e",
|
||||
executable.c_str(),
|
||||
"-f",
|
||||
"-C",
|
||||
"-p",
|
||||
nullptr
|
||||
);
|
||||
#else
|
||||
execl(
|
||||
CPPTRACE_ADDR2LINE_PATH,
|
||||
CPPTRACE_ADDR2LINE_PATH,
|
||||
"-o", executable.c_str(),
|
||||
"-fullPath",
|
||||
nullptr
|
||||
);
|
||||
#endif
|
||||
#endif
|
||||
_exit(1); // TODO: Diagnostic?
|
||||
}
|
||||
if(write(input_pipe.end.write, addresses.data(), addresses.size()) == -1) {
|
||||
throw internal_error("call to write failed: {}", errno);
|
||||
}
|
||||
close(input_pipe.end.read);
|
||||
close(input_pipe.end.write);
|
||||
close(output_pipe.end.write);
|
||||
std::string output;
|
||||
constexpr int buffer_size = 4096;
|
||||
char buffer[buffer_size];
|
||||
std::size_t count = 0;
|
||||
while((count = read(output_pipe.end.read, buffer, buffer_size)) > 0) {
|
||||
output.insert(output.end(), buffer, buffer + count);
|
||||
}
|
||||
// TODO: check status from addr2line?
|
||||
waitpid(pid, nullptr, 0);
|
||||
return output;
|
||||
}
|
||||
#elif IS_WINDOWS
|
||||
bool has_addr2line() {
|
||||
static std::mutex mutex;
|
||||
static bool has_addr2line = false;
|
||||
static bool checked = false;
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
if(!checked) {
|
||||
// TODO: Popen is a hack. Implement properly with CreateProcess and pipes later.
|
||||
checked = true;
|
||||
#ifdef CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH
|
||||
std::FILE* p = popen("addr2line --version", "r");
|
||||
#else
|
||||
#ifndef CPPTRACE_ADDR2LINE_PATH
|
||||
#error "CPPTRACE_ADDR2LINE_PATH must be defined if CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH is not"
|
||||
#endif
|
||||
std::FILE* p = popen(CPPTRACE_ADDR2LINE_PATH " --version", "r");
|
||||
#endif
|
||||
if(p) {
|
||||
has_addr2line = pclose(p) == 0;
|
||||
}
|
||||
}
|
||||
return has_addr2line;
|
||||
}
|
||||
|
||||
std::string resolve_addresses(const std::string& addresses, const std::string& executable) {
|
||||
// TODO: Popen is a hack. Implement properly with CreateProcess and pipes later.
|
||||
///fprintf(stderr, ("addr2line -e " + executable + " -fCp " + addresses + "\n").c_str());
|
||||
#ifdef CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH
|
||||
std::FILE* p = popen(("addr2line -e \"" + executable + "\" -fCp " + addresses).c_str(), "r");
|
||||
#else
|
||||
#ifndef CPPTRACE_ADDR2LINE_PATH
|
||||
#error "CPPTRACE_ADDR2LINE_PATH must be defined if CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH is not"
|
||||
#endif
|
||||
std::FILE* p = popen(
|
||||
(CPPTRACE_ADDR2LINE_PATH " -e \"" + executable + "\" -fCp " + addresses).c_str(),
|
||||
"r"
|
||||
);
|
||||
#endif
|
||||
std::string output;
|
||||
constexpr int buffer_size = 4096;
|
||||
char buffer[buffer_size];
|
||||
std::size_t count = 0;
|
||||
while((count = std::fread(buffer, 1, buffer_size, p)) > 0) {
|
||||
output.insert(output.end(), buffer, buffer + count);
|
||||
}
|
||||
pclose(p);
|
||||
///fprintf(stderr, "%s\n", output.c_str());
|
||||
return output;
|
||||
}
|
||||
#endif
|
||||
|
||||
void update_trace(const std::string& line, std::size_t entry_index, const collated_vec& entries_vec) {
|
||||
#if !IS_APPLE
|
||||
// Result will be of the form "<symbol> at path:line"
|
||||
// The path may be ?? if addr2line cannot resolve, line may be ?
|
||||
// Edge cases:
|
||||
// ?? ??:0
|
||||
// symbol :?
|
||||
const std::size_t at_location = line.find(" at ");
|
||||
std::size_t symbol_end;
|
||||
std::size_t filename_start;
|
||||
if(at_location != std::string::npos) {
|
||||
symbol_end = at_location;
|
||||
filename_start = at_location + 4;
|
||||
} else {
|
||||
VERIFY(line.find("?? ") == 0, "Unexpected edge case while processing addr2line output");
|
||||
symbol_end = 2;
|
||||
filename_start = 3;
|
||||
}
|
||||
auto symbol = line.substr(0, symbol_end);
|
||||
auto colon = line.rfind(':');
|
||||
VERIFY(colon != std::string::npos);
|
||||
VERIFY(colon >= filename_start); // :? to deal with "symbol :?" edge case
|
||||
auto filename = line.substr(filename_start, colon - filename_start);
|
||||
auto line_number = line.substr(colon + 1);
|
||||
if(line_number != "?") {
|
||||
entries_vec[entry_index].second.get().line = std::stoi(line_number);
|
||||
}
|
||||
if(!filename.empty() && filename != "??") {
|
||||
entries_vec[entry_index].second.get().filename = filename;
|
||||
}
|
||||
if(!symbol.empty()) {
|
||||
entries_vec[entry_index].second.get().symbol = symbol;
|
||||
}
|
||||
#else
|
||||
// Result will be of the form "<symbol> (in <object name>) (file:line)"
|
||||
// The symbol may just be the given address if atos can't resolve it
|
||||
// Examples:
|
||||
// trace() (in demo) (demo.cpp:8)
|
||||
// 0x100003b70 (in demo)
|
||||
// 0xffffffffffffffff
|
||||
// foo (in bar) + 14
|
||||
// I'm making some assumptions here. Support may need to be improved later. This is tricky output to
|
||||
// parse.
|
||||
const std::size_t in_location = line.find(" (in ");
|
||||
if(in_location == std::string::npos) {
|
||||
// presumably the 0xffffffffffffffff case
|
||||
return;
|
||||
}
|
||||
const std::size_t symbol_end = in_location;
|
||||
entries_vec[entry_index].second.get().symbol = line.substr(0, symbol_end);
|
||||
const std::size_t object_end = line.find(")", in_location);
|
||||
VERIFY(
|
||||
object_end != std::string::npos,
|
||||
"Unexpected edge case while processing addr2line/atos output"
|
||||
);
|
||||
const std::size_t filename_start = line.find(") (", object_end);
|
||||
if(filename_start == std::string::npos) {
|
||||
// presumably something like 0x100003b70 (in demo) or foo (in bar) + 14
|
||||
return;
|
||||
}
|
||||
const std::size_t filename_end = line.find(":", filename_start);
|
||||
VERIFY(
|
||||
filename_end != std::string::npos,
|
||||
"Unexpected edge case while processing addr2line/atos output"
|
||||
);
|
||||
entries_vec[entry_index].second.get().filename = line.substr(
|
||||
filename_start + 3,
|
||||
filename_end - filename_start - 3
|
||||
);
|
||||
const std::size_t line_start = filename_end + 1;
|
||||
const std::size_t line_end = line.find(")", filename_end);
|
||||
VERIFY(
|
||||
line_end == line.size() - 1,
|
||||
"Unexpected edge case while processing addr2line/atos output"
|
||||
);
|
||||
entries_vec[entry_index].second.get().line = std::stoi(line.substr(line_start, line_end - line_start));
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<object_frame>& frames) {
|
||||
// TODO: Refactor better
|
||||
std::vector<stacktrace_frame> trace(frames.size(), null_frame());
|
||||
for(std::size_t i = 0; i < frames.size(); i++) {
|
||||
trace[i].raw_address = frames[i].raw_address;
|
||||
trace[i].object_address = frames[i].object_address;
|
||||
// Set what is known for now, and resolutions from addr2line should overwrite
|
||||
trace[i].filename = frames[i].object_path;
|
||||
}
|
||||
if(has_addr2line()) {
|
||||
const auto entries = collate_frames(frames, trace);
|
||||
for(const auto& entry : entries) {
|
||||
try {
|
||||
const auto& object_name = entry.first;
|
||||
if(object_name.empty()) {
|
||||
continue;
|
||||
}
|
||||
const auto& entries_vec = entry.second;
|
||||
// You may ask why it'd ever happen that there could be an empty entries_vec array, if there're
|
||||
// no addresses why would get_addr2line_targets do anything? The reason is because if things in
|
||||
// get_addr2line_targets fail it will silently skip. This is partly an optimization but also an
|
||||
// assertion below will fail if addr2line is given an empty input.
|
||||
if(entries_vec.empty()) {
|
||||
continue;
|
||||
}
|
||||
std::string address_input;
|
||||
for(const auto& pair : entries_vec) {
|
||||
address_input += microfmt::format(
|
||||
"{:h}{}",
|
||||
pair.first.get().object_address,
|
||||
#if !IS_WINDOWS
|
||||
'\n'
|
||||
#else
|
||||
' '
|
||||
#endif
|
||||
);
|
||||
}
|
||||
auto output = split(trim(resolve_addresses(address_input, object_name)), "\n");
|
||||
VERIFY(output.size() == entries_vec.size());
|
||||
for(std::size_t i = 0; i < output.size(); i++) {
|
||||
update_trace(output[i], i, entries_vec);
|
||||
}
|
||||
} catch(...) { // NOSONAR
|
||||
detail::log_and_maybe_propagate_exception(std::current_exception());
|
||||
}
|
||||
}
|
||||
}
|
||||
return trace;
|
||||
}
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,512 @@
|
|||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_DBGHELP
|
||||
|
||||
#include <cpptrace/basic.hpp>
|
||||
#include <cpptrace/utils.hpp>
|
||||
#include "symbols/symbols.hpp"
|
||||
#include "platform/dbghelp_utils.hpp"
|
||||
#include "binary/object.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "options.hpp"
|
||||
#include "logging.hpp"
|
||||
|
||||
#include <regex>
|
||||
#include <system_error>
|
||||
#include <vector>
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <dbghelp.h>
|
||||
#include <psapi.h>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
namespace dbghelp {
|
||||
// SymFromAddr only returns the function's name. In order to get information about parameters,
|
||||
// important for C++ stack traces where functions may be overloaded, we have to manually use
|
||||
// Windows DIA to walk debug info structures. Resources:
|
||||
// https://web.archive.org/web/20201027025750/http://www.debuginfo.com/articles/dbghelptypeinfo.html
|
||||
// https://web.archive.org/web/20201203160805/http://www.debuginfo.com/articles/dbghelptypeinfofigures.html
|
||||
// https://github.com/DynamoRIO/dynamorio/blob/master/ext/drsyms/drsyms_windows.c#L1370-L1439
|
||||
// TODO: Currently unable to detect rvalue references
|
||||
// TODO: Currently unable to detect const
|
||||
enum class SymTagEnum {
|
||||
SymTagNull, SymTagExe, SymTagCompiland, SymTagCompilandDetails, SymTagCompilandEnv,
|
||||
SymTagFunction, SymTagBlock, SymTagData, SymTagAnnotation, SymTagLabel, SymTagPublicSymbol,
|
||||
SymTagUDT, SymTagEnum, SymTagFunctionType, SymTagPointerType, SymTagArrayType,
|
||||
SymTagBaseType, SymTagTypedef, SymTagBaseClass, SymTagFriend, SymTagFunctionArgType,
|
||||
SymTagFuncDebugStart, SymTagFuncDebugEnd, SymTagUsingNamespace, SymTagVTableShape,
|
||||
SymTagVTable, SymTagCustom, SymTagThunk, SymTagCustomType, SymTagManagedType,
|
||||
SymTagDimension, SymTagCallSite, SymTagInlineSite, SymTagBaseInterface, SymTagVectorType,
|
||||
SymTagMatrixType, SymTagHLSLType, SymTagCaller, SymTagCallee, SymTagExport,
|
||||
SymTagHeapAllocationSite, SymTagCoffGroup, SymTagMax
|
||||
};
|
||||
|
||||
enum class IMAGEHLP_SYMBOL_TYPE_INFO {
|
||||
TI_GET_SYMTAG, TI_GET_SYMNAME, TI_GET_LENGTH, TI_GET_TYPE, TI_GET_TYPEID, TI_GET_BASETYPE,
|
||||
TI_GET_ARRAYINDEXTYPEID, TI_FINDCHILDREN, TI_GET_DATAKIND, TI_GET_ADDRESSOFFSET,
|
||||
TI_GET_OFFSET, TI_GET_VALUE, TI_GET_COUNT, TI_GET_CHILDRENCOUNT, TI_GET_BITPOSITION,
|
||||
TI_GET_VIRTUALBASECLASS, TI_GET_VIRTUALTABLESHAPEID, TI_GET_VIRTUALBASEPOINTEROFFSET,
|
||||
TI_GET_CLASSPARENTID, TI_GET_NESTED, TI_GET_SYMINDEX, TI_GET_LEXICALPARENT, TI_GET_ADDRESS,
|
||||
TI_GET_THISADJUST, TI_GET_UDTKIND, TI_IS_EQUIV_TO, TI_GET_CALLING_CONVENTION,
|
||||
TI_IS_CLOSE_EQUIV_TO, TI_GTIEX_REQS_VALID, TI_GET_VIRTUALBASEOFFSET,
|
||||
TI_GET_VIRTUALBASEDISPINDEX, TI_GET_IS_REFERENCE, TI_GET_INDIRECTVIRTUALBASECLASS,
|
||||
TI_GET_VIRTUALBASETABLETYPE, TI_GET_OBJECTPOINTERTYPE, IMAGEHLP_SYMBOL_TYPE_INFO_MAX
|
||||
};
|
||||
|
||||
enum class BasicType {
|
||||
btNoType = 0, btVoid = 1, btChar = 2, btWChar = 3, btInt = 6, btUInt = 7, btFloat = 8,
|
||||
btBCD = 9, btBool = 10, btLong = 13, btULong = 14, btCurrency = 25, btDate = 26,
|
||||
btVariant = 27, btComplex = 28, btBit = 29, btBSTR = 30, btHresult = 31
|
||||
};
|
||||
|
||||
// SymGetTypeInfo utility
|
||||
template<typename T, IMAGEHLP_SYMBOL_TYPE_INFO SymType, bool FAILABLE = false>
|
||||
T get_info(ULONG type_index, HANDLE proc, ULONG64 modbase) {
|
||||
T info;
|
||||
if(
|
||||
!SymGetTypeInfo(
|
||||
proc,
|
||||
modbase,
|
||||
type_index,
|
||||
static_cast<::IMAGEHLP_SYMBOL_TYPE_INFO>(SymType),
|
||||
&info
|
||||
)
|
||||
) {
|
||||
if(FAILABLE) {
|
||||
return (T)-1;
|
||||
} else {
|
||||
throw internal_error(
|
||||
"SymGetTypeInfo failed: {}", std::system_error(GetLastError(), std::system_category()).what()
|
||||
);
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
template<IMAGEHLP_SYMBOL_TYPE_INFO SymType, bool FAILABLE = false>
|
||||
std::string get_info_wchar(ULONG type_index, HANDLE proc, ULONG64 modbase) {
|
||||
WCHAR* info;
|
||||
if(
|
||||
!SymGetTypeInfo(proc, modbase, type_index, static_cast<::IMAGEHLP_SYMBOL_TYPE_INFO>(SymType), &info)
|
||||
) {
|
||||
throw internal_error(
|
||||
"SymGetTypeInfo failed: {}", std::system_error(GetLastError(), std::system_category()).what()
|
||||
);
|
||||
}
|
||||
// special case to properly free a buffer and convert string to narrow chars, only used for
|
||||
// TI_GET_SYMNAME
|
||||
static_assert(
|
||||
SymType == IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_SYMNAME,
|
||||
"get_info_wchar called with unexpected IMAGEHLP_SYMBOL_TYPE_INFO"
|
||||
);
|
||||
std::wstring wstr(info);
|
||||
std::string str;
|
||||
str.reserve(wstr.size());
|
||||
for(const auto c : wstr) {
|
||||
str.push_back(static_cast<char>(c));
|
||||
}
|
||||
LocalFree(info);
|
||||
return str;
|
||||
}
|
||||
|
||||
// Translate basic types to string
|
||||
static std::string get_basic_type(ULONG type_index, HANDLE proc, ULONG64 modbase) {
|
||||
auto basic_type = get_info<BasicType, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_BASETYPE>(
|
||||
type_index,
|
||||
proc,
|
||||
modbase
|
||||
);
|
||||
//auto length = get_info<ULONG64, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_LENGTH>(type_index, proc, modbase);
|
||||
switch(basic_type) {
|
||||
case BasicType::btNoType:
|
||||
return "<no basic type>";
|
||||
case BasicType::btVoid:
|
||||
return "void";
|
||||
case BasicType::btChar:
|
||||
return "char";
|
||||
case BasicType::btWChar:
|
||||
return "wchar_t";
|
||||
case BasicType::btInt:
|
||||
return "int";
|
||||
case BasicType::btUInt:
|
||||
return "unsigned int";
|
||||
case BasicType::btFloat:
|
||||
return "float";
|
||||
case BasicType::btBool:
|
||||
return "bool";
|
||||
case BasicType::btLong:
|
||||
return "long";
|
||||
case BasicType::btULong:
|
||||
return "unsigned long";
|
||||
default:
|
||||
return "<unknown basic type>";
|
||||
}
|
||||
}
|
||||
|
||||
static std::string resolve_type(ULONG type_index, HANDLE proc, ULONG64 modbase);
|
||||
|
||||
struct class_name_result {
|
||||
bool has_class_name;
|
||||
std::string name;
|
||||
};
|
||||
// Helper for member pointers
|
||||
static class_name_result lookup_class_name(ULONG type_index, HANDLE proc, ULONG64 modbase) {
|
||||
DWORD class_parent_id = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_CLASSPARENTID, true>(
|
||||
type_index,
|
||||
proc,
|
||||
modbase
|
||||
);
|
||||
if(class_parent_id == (DWORD)-1) {
|
||||
return {false, ""};
|
||||
} else {
|
||||
return {true, resolve_type(class_parent_id, proc, modbase)};
|
||||
}
|
||||
}
|
||||
|
||||
struct type_result {
|
||||
std::string base;
|
||||
std::string extent;
|
||||
};
|
||||
// Resolve more complex types
|
||||
// returns [base, extent]
|
||||
static type_result lookup_type(ULONG type_index, HANDLE proc, ULONG64 modbase) {
|
||||
auto tag = get_info<SymTagEnum, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_SYMTAG>(type_index, proc, modbase);
|
||||
switch(tag) {
|
||||
case SymTagEnum::SymTagBaseType:
|
||||
return {get_basic_type(type_index, proc, modbase), ""};
|
||||
case SymTagEnum::SymTagPointerType: {
|
||||
DWORD underlying_type_id = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_TYPEID>(
|
||||
type_index,
|
||||
proc,
|
||||
modbase
|
||||
);
|
||||
bool is_ref = get_info<BOOL, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_IS_REFERENCE>(
|
||||
type_index,
|
||||
proc,
|
||||
modbase
|
||||
);
|
||||
std::string pp = is_ref ? "&" : "*"; // pointer punctuator
|
||||
auto class_name_res = lookup_class_name(type_index, proc, modbase);
|
||||
if(class_name_res.has_class_name) {
|
||||
pp = class_name_res.name + "::" + pp;
|
||||
}
|
||||
const auto type = lookup_type(underlying_type_id, proc, modbase);
|
||||
if(type.extent.empty()) {
|
||||
return {type.base + (pp.size() > 1 ? " " : "") + pp, ""};
|
||||
} else {
|
||||
return {type.base + "(" + pp, ")" + type.extent};
|
||||
}
|
||||
}
|
||||
case SymTagEnum::SymTagArrayType: {
|
||||
DWORD underlying_type_id = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_TYPEID>(
|
||||
type_index,
|
||||
proc,
|
||||
modbase
|
||||
);
|
||||
DWORD length = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_COUNT>(
|
||||
type_index,
|
||||
proc,
|
||||
modbase
|
||||
);
|
||||
const auto type = lookup_type(underlying_type_id, proc, modbase);
|
||||
return {type.base, "[" + std::to_string(length) + "]" + type.extent};
|
||||
}
|
||||
case SymTagEnum::SymTagFunctionType: {
|
||||
DWORD return_type_id = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_TYPEID>(
|
||||
type_index,
|
||||
proc,
|
||||
modbase
|
||||
);
|
||||
DWORD n_children = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_COUNT, true>(
|
||||
type_index,
|
||||
proc,
|
||||
modbase
|
||||
);
|
||||
DWORD class_parent_id = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_CLASSPARENTID, true>(
|
||||
type_index,
|
||||
proc,
|
||||
modbase
|
||||
);
|
||||
int n_ignore = class_parent_id != (DWORD)-1; // ignore this param
|
||||
// this must be ignored before TI_FINDCHILDREN_PARAMS::Count is set, else error
|
||||
n_children -= n_ignore;
|
||||
// return type
|
||||
const auto return_type = lookup_type(return_type_id, proc, modbase);
|
||||
if(n_children == 0) {
|
||||
return {return_type.base, "()" + return_type.extent};
|
||||
} else {
|
||||
// alignment should be fine
|
||||
std::size_t sz = sizeof(TI_FINDCHILDREN_PARAMS) +
|
||||
(n_children) * sizeof(TI_FINDCHILDREN_PARAMS::ChildId[0]);
|
||||
TI_FINDCHILDREN_PARAMS* children = (TI_FINDCHILDREN_PARAMS*) new char[sz];
|
||||
auto guard = scope_exit([&] {
|
||||
delete[] (char*) children;
|
||||
});
|
||||
children->Start = 0;
|
||||
children->Count = n_children;
|
||||
if(
|
||||
!SymGetTypeInfo(
|
||||
proc, modbase, type_index,
|
||||
static_cast<::IMAGEHLP_SYMBOL_TYPE_INFO>(
|
||||
IMAGEHLP_SYMBOL_TYPE_INFO::TI_FINDCHILDREN
|
||||
),
|
||||
children
|
||||
)
|
||||
) {
|
||||
throw internal_error(
|
||||
"SymGetTypeInfo failed: {}",
|
||||
std::system_error(GetLastError(), std::system_category()).what()
|
||||
);
|
||||
}
|
||||
// get children type
|
||||
std::string extent = "(";
|
||||
if(children->Start != 0) {
|
||||
throw internal_error("Error: children->Start == 0");
|
||||
}
|
||||
for(std::size_t i = 0; i < n_children; i++) {
|
||||
extent += (i == 0 ? "" : ", ") + resolve_type(children->ChildId[i], proc, modbase);
|
||||
}
|
||||
extent += ")";
|
||||
return {return_type.base, extent + return_type.extent};
|
||||
}
|
||||
}
|
||||
case SymTagEnum::SymTagFunctionArgType: {
|
||||
DWORD underlying_type_id =
|
||||
get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_TYPEID>(type_index, proc, modbase);
|
||||
return {resolve_type(underlying_type_id, proc, modbase), ""};
|
||||
}
|
||||
case SymTagEnum::SymTagTypedef:
|
||||
case SymTagEnum::SymTagEnum:
|
||||
case SymTagEnum::SymTagUDT:
|
||||
case SymTagEnum::SymTagBaseClass:
|
||||
return {
|
||||
get_info_wchar<IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_SYMNAME>(type_index, proc, modbase), ""
|
||||
};
|
||||
default:
|
||||
return {
|
||||
"<unknown type " +
|
||||
std::to_string(static_cast<std::underlying_type<SymTagEnum>::type>(tag)) +
|
||||
">",
|
||||
""
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
static std::string resolve_type(ULONG type_index, HANDLE proc, ULONG64 modbase) {
|
||||
const auto type = lookup_type(type_index, proc, modbase);
|
||||
return type.base + type.extent;
|
||||
}
|
||||
|
||||
struct function_info {
|
||||
HANDLE proc;
|
||||
ULONG64 modbase;
|
||||
int counter;
|
||||
int n_children;
|
||||
int n_ignore;
|
||||
std::string str;
|
||||
};
|
||||
|
||||
// Enumerates function parameters
|
||||
static BOOL __stdcall enumerator_callback(
|
||||
PSYMBOL_INFO symbol_info,
|
||||
ULONG,
|
||||
PVOID data
|
||||
) {
|
||||
function_info* ctx = (function_info*)data;
|
||||
if(ctx->counter++ >= ctx->n_children) {
|
||||
return false;
|
||||
}
|
||||
if(ctx->n_ignore-- > 0) {
|
||||
return true; // just skip
|
||||
}
|
||||
ctx->str += resolve_type(symbol_info->TypeIndex, ctx->proc, ctx->modbase);
|
||||
if(ctx->counter < ctx->n_children) {
|
||||
ctx->str += ", ";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: Handle backtrace_pcinfo calling the callback multiple times on inlined functions
|
||||
stacktrace_frame resolve_frame(HANDLE proc, frame_ptr addr) {
|
||||
// The get_frame_object_info() ends up being inexpensive, at on my machine
|
||||
// debug release
|
||||
// uncached trace resolution (29 frames) 1.9-2.1 ms 1.4-1.8 ms
|
||||
// cached trace resolution (29 frames) 1.1-1.2 ms 0.2-0.4 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.
|
||||
auto object_frame = get_frame_object_info(addr);
|
||||
// Dbghelp is is single-threaded, so acquire a lock.
|
||||
auto lock = get_dbghelp_lock();
|
||||
alignas(SYMBOL_INFO) char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
|
||||
SYMBOL_INFO* symbol = (SYMBOL_INFO*)buffer;
|
||||
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
|
||||
symbol->MaxNameLen = MAX_SYM_NAME;
|
||||
union { DWORD64 a; DWORD b; } displacement;
|
||||
IMAGEHLP_LINE line;
|
||||
bool got_line = SymGetLineFromAddr(proc, addr, &displacement.b, &line);
|
||||
if(SymFromAddr(proc, addr, &displacement.a, symbol)) {
|
||||
if(got_line) {
|
||||
IMAGEHLP_STACK_FRAME frame;
|
||||
frame.InstructionOffset = symbol->Address;
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symsetcontext
|
||||
// "If you call SymSetContext to set the context to its current value, the
|
||||
// function fails but GetLastError returns ERROR_SUCCESS."
|
||||
// This is the stupidest fucking api I've ever worked with.
|
||||
if(SymSetContext(proc, &frame, nullptr) == FALSE && GetLastError() != ERROR_SUCCESS) {
|
||||
log::error("Stack trace: Internal error while calling SymSetContext");
|
||||
return {
|
||||
addr,
|
||||
object_frame.object_address,
|
||||
{ static_cast<std::uint32_t>(line.LineNumber) },
|
||||
nullable<std::uint32_t>::null(),
|
||||
line.FileName,
|
||||
symbol->Name,
|
||||
false
|
||||
};
|
||||
}
|
||||
DWORD n_children = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_COUNT, true>(
|
||||
symbol->TypeIndex,
|
||||
proc,
|
||||
symbol->ModBase
|
||||
);
|
||||
DWORD class_parent_id = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_CLASSPARENTID, true>(
|
||||
symbol->TypeIndex,
|
||||
proc,
|
||||
symbol->ModBase
|
||||
);
|
||||
function_info fi {
|
||||
proc,
|
||||
symbol->ModBase,
|
||||
0,
|
||||
int(n_children),
|
||||
class_parent_id != (DWORD)-1,
|
||||
""
|
||||
};
|
||||
SymEnumSymbols(proc, 0, nullptr, enumerator_callback, &fi);
|
||||
std::string signature = symbol->Name + std::string("(") + fi.str + ")";
|
||||
// There's a phenomina with DIA not inserting commas after template parameters. Fix them here.
|
||||
static std::regex comma_re(R"(,(?=\S))");
|
||||
signature = std::regex_replace(signature, comma_re, ", ");
|
||||
return {
|
||||
addr,
|
||||
object_frame.object_address,
|
||||
{ static_cast<std::uint32_t>(line.LineNumber) },
|
||||
nullable<std::uint32_t>::null(),
|
||||
line.FileName,
|
||||
signature,
|
||||
false,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
addr,
|
||||
object_frame.object_address,
|
||||
nullable<std::uint32_t>::null(),
|
||||
nullable<std::uint32_t>::null(),
|
||||
"",
|
||||
symbol->Name,
|
||||
false
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
addr,
|
||||
object_frame.object_address,
|
||||
nullable<std::uint32_t>::null(),
|
||||
nullable<std::uint32_t>::null(),
|
||||
"",
|
||||
"",
|
||||
false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<frame_ptr>& frames) {
|
||||
// Dbghelp is is single-threaded, so acquire a lock.
|
||||
auto lock = get_dbghelp_lock();
|
||||
std::vector<stacktrace_frame> trace;
|
||||
trace.reserve(frames.size());
|
||||
|
||||
// TODO: When does this need to be called? Can it be moved to the symbolizer?
|
||||
SymSetOptions(SYMOPT_ALLOW_ABSOLUTE_SYMBOLS);
|
||||
|
||||
auto syminit_info = ensure_syminit();
|
||||
for(const auto frame : frames) {
|
||||
try {
|
||||
trace.push_back(resolve_frame(syminit_info.get_process_handle() , frame));
|
||||
} catch(...) { // NOSONAR
|
||||
detail::log_and_maybe_propagate_exception(std::current_exception());
|
||||
auto entry = null_frame();
|
||||
entry.raw_address = frame;
|
||||
trace.push_back(entry);
|
||||
}
|
||||
}
|
||||
return trace;
|
||||
}
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
/*
|
||||
When a module was loaded at runtime with LoadLibrary after SymInitialize was already called,
|
||||
it is necessary to manually load the symbols from that module with SymLoadModuleEx.
|
||||
|
||||
See "Symbol Handler Initialization" in Microsoft documentation at
|
||||
https://learn.microsoft.com/en-us/windows/win32/debug/symbol-handler-initialization
|
||||
*/
|
||||
void load_symbols_for_file(const std::string& filename) {
|
||||
HMODULE hModule = GetModuleHandleA(filename.c_str());
|
||||
if (hModule == NULL) {
|
||||
throw detail::internal_error(
|
||||
"Unable to get module handle for file '{}' : {}",
|
||||
filename,
|
||||
std::system_error(GetLastError(), std::system_category()).what()
|
||||
);
|
||||
}
|
||||
|
||||
// SymLoadModuleEx needs the module's base address and size, so get these with GetModuleInformation.
|
||||
MODULEINFO module_info;
|
||||
if (
|
||||
!GetModuleInformation(
|
||||
GetCurrentProcess(),
|
||||
hModule,
|
||||
&module_info,
|
||||
sizeof(module_info)
|
||||
)
|
||||
) {
|
||||
throw detail::internal_error(
|
||||
"Unable to get module information for file '{}' : {}",
|
||||
filename,
|
||||
std::system_error(GetLastError(), std::system_category()).what()
|
||||
);
|
||||
}
|
||||
|
||||
auto lock = detail::get_dbghelp_lock();
|
||||
HANDLE syminit_handle = detail::ensure_syminit().get_process_handle();
|
||||
if (
|
||||
!SymLoadModuleEx(
|
||||
syminit_handle,
|
||||
NULL,
|
||||
filename.c_str(),
|
||||
NULL,
|
||||
(DWORD64)module_info.lpBaseOfDll,
|
||||
// The documentation says this is optional, but if omitted (0), symbol loading fails
|
||||
module_info.SizeOfImage,
|
||||
NULL,
|
||||
0
|
||||
)
|
||||
) {
|
||||
throw detail::internal_error(
|
||||
"Unable to load symbols for file '{}' : {}",
|
||||
filename,
|
||||
std::system_error(GetLastError(), std::system_category()).what()
|
||||
);
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDL
|
||||
|
||||
#include <cpptrace/basic.hpp>
|
||||
#include "symbols/symbols.hpp"
|
||||
#include "binary/module_base.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <dlfcn.h>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
namespace libdl {
|
||||
stacktrace_frame resolve_frame(const frame_ptr addr) {
|
||||
Dl_info info;
|
||||
if(dladdr(reinterpret_cast<void*>(addr), &info)) { // thread-safe
|
||||
auto base = get_module_image_base(info.dli_fname);
|
||||
return {
|
||||
addr,
|
||||
base.has_value()
|
||||
? addr - reinterpret_cast<std::uintptr_t>(info.dli_fbase) + base.unwrap_value()
|
||||
: 0,
|
||||
nullable<std::uint32_t>::null(),
|
||||
nullable<std::uint32_t>::null(),
|
||||
info.dli_fname ? info.dli_fname : "",
|
||||
info.dli_sname ? info.dli_sname : "",
|
||||
false
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
addr,
|
||||
0,
|
||||
nullable<std::uint32_t>::null(),
|
||||
nullable<std::uint32_t>::null(),
|
||||
"",
|
||||
"",
|
||||
false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<frame_ptr>& frames) {
|
||||
std::vector<stacktrace_frame> trace;
|
||||
trace.reserve(frames.size());
|
||||
for(const auto frame : frames) {
|
||||
trace.push_back(resolve_frame(frame));
|
||||
}
|
||||
return trace;
|
||||
}
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE
|
||||
|
||||
#include <cpptrace/basic.hpp>
|
||||
#include "symbols/symbols.hpp"
|
||||
#include "platform/program_name.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "options.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#ifdef CPPTRACE_BACKTRACE_PATH
|
||||
#include CPPTRACE_BACKTRACE_PATH
|
||||
#else
|
||||
#include <backtrace.h>
|
||||
#endif
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
namespace libbacktrace {
|
||||
int full_callback(void* data, std::uintptr_t address, const char* file, int line, const char* symbol) {
|
||||
stacktrace_frame& frame = *static_cast<stacktrace_frame*>(data);
|
||||
frame.raw_address = address;
|
||||
frame.line = line;
|
||||
frame.filename = file ? file : "";
|
||||
frame.symbol = symbol ? symbol : "";
|
||||
return 0;
|
||||
}
|
||||
|
||||
void syminfo_callback(void* data, std::uintptr_t address, const char* symbol, std::uintptr_t, std::uintptr_t) {
|
||||
stacktrace_frame& frame = *static_cast<stacktrace_frame*>(data);
|
||||
frame.raw_address = address;
|
||||
frame.line = 0;
|
||||
frame.filename = "";
|
||||
frame.symbol = symbol ? symbol : "";
|
||||
}
|
||||
|
||||
void error_callback(void*, const char* msg, int errnum) {
|
||||
if(msg == std::string("no debug info in ELF executable")) {
|
||||
// https://github.com/jeremy-rifkin/cpptrace/issues/114
|
||||
// https://github.com/ianlancetaylor/libbacktrace/blob/ae1e707dbacd4a5cc82fcf2d3816f410e9c5fec4/elf.c#L592
|
||||
// not a critical error, just return
|
||||
return;
|
||||
}
|
||||
throw internal_error("Libbacktrace error: {}, code {}", msg, errnum);
|
||||
}
|
||||
|
||||
backtrace_state* get_backtrace_state() {
|
||||
static std::mutex mutex;
|
||||
const std::lock_guard<std::mutex> lock(mutex);
|
||||
// backtrace_create_state must be called only one time per program
|
||||
static backtrace_state* state = nullptr;
|
||||
static bool called = false;
|
||||
if(!called) {
|
||||
state = backtrace_create_state(program_name(), true, error_callback, nullptr);
|
||||
called = true;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
// TODO: Handle backtrace_pcinfo calling the callback multiple times on inlined functions
|
||||
stacktrace_frame resolve_frame(const frame_ptr addr) {
|
||||
try {
|
||||
stacktrace_frame frame = null_frame();
|
||||
frame.raw_address = addr;
|
||||
backtrace_pcinfo(
|
||||
get_backtrace_state(),
|
||||
addr,
|
||||
full_callback,
|
||||
error_callback,
|
||||
&frame
|
||||
);
|
||||
if(frame.symbol.empty()) {
|
||||
// fallback, try to at least recover the symbol name with backtrace_syminfo
|
||||
backtrace_syminfo(
|
||||
get_backtrace_state(),
|
||||
addr,
|
||||
syminfo_callback,
|
||||
error_callback,
|
||||
&frame
|
||||
);
|
||||
}
|
||||
return frame;
|
||||
} catch(...) { // NOSONAR
|
||||
detail::log_and_maybe_propagate_exception(std::current_exception());
|
||||
return null_frame();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<frame_ptr>& frames) {
|
||||
std::vector<stacktrace_frame> trace;
|
||||
trace.reserve(frames.size());
|
||||
for(const auto frame : frames) {
|
||||
trace.push_back(resolve_frame(frame));
|
||||
}
|
||||
return trace;
|
||||
}
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF
|
||||
|
||||
#include "symbols/symbols.hpp"
|
||||
|
||||
#include <cpptrace/basic.hpp>
|
||||
|
||||
#include "dwarf/resolver.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "binary/elf.hpp"
|
||||
#include "binary/mach-o.hpp"
|
||||
#include "jit/jit_objects.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
namespace libdwarf {
|
||||
std::unique_ptr<symbol_resolver> get_resolver_for_object(const std::string& object_path) {
|
||||
#if IS_APPLE
|
||||
// Check if dSYM exist, if not fallback to debug map
|
||||
if(!directory_exists(object_path + ".dSYM")) {
|
||||
return make_debug_map_resolver(object_path);
|
||||
}
|
||||
#endif
|
||||
return make_dwarf_resolver(object_path);
|
||||
}
|
||||
|
||||
// not thread-safe, replies on caller to lock
|
||||
maybe_owned<symbol_resolver> get_resolver(const std::string& object_name) {
|
||||
// cache resolvers since objects are likely to be traced more than once
|
||||
static std::unordered_map<std::string, std::unique_ptr<symbol_resolver>> resolver_map;
|
||||
auto it = resolver_map.find(object_name);
|
||||
if(it != resolver_map.end()) {
|
||||
return it->second.get();
|
||||
} else {
|
||||
std::unique_ptr<symbol_resolver> resolver_object = get_resolver_for_object(object_name);
|
||||
if(get_cache_mode() == cache_mode::prioritize_speed) {
|
||||
// .emplace needed, for some reason .insert tries to copy <= gcc 7.2
|
||||
return resolver_map.emplace(object_name, std::move(resolver_object)).first->second.get();
|
||||
} else {
|
||||
// gcc 4 has trouble with automatic moves of locals here https://godbolt.org/z/9oWdWjbf8
|
||||
return maybe_owned<symbol_resolver>{std::move(resolver_object)};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// flatten trace with inlines
|
||||
std::vector<stacktrace_frame> flatten_inlines(std::vector<frame_with_inlines>& trace) {
|
||||
std::vector<stacktrace_frame> final_trace;
|
||||
for(auto& entry : trace) {
|
||||
// most recent call first
|
||||
if(!entry.inlines.empty()) {
|
||||
// insert in reverse order
|
||||
final_trace.insert(
|
||||
final_trace.end(),
|
||||
std::make_move_iterator(entry.inlines.rbegin()),
|
||||
std::make_move_iterator(entry.inlines.rend())
|
||||
);
|
||||
}
|
||||
final_trace.push_back(std::move(entry.frame));
|
||||
if(!entry.inlines.empty()) {
|
||||
// rotate line info due to quirk of how dwarf stores this stuff
|
||||
// inclusive range
|
||||
auto begin = final_trace.end() - (1 + entry.inlines.size());
|
||||
auto end = final_trace.end() - 1;
|
||||
auto carry_line = end->line;
|
||||
auto carry_column = end->column;
|
||||
std::string carry_filename = std::move(end->filename);
|
||||
for(auto it = end; it != begin; it--) {
|
||||
it->line = (it - 1)->line;
|
||||
it->column = (it - 1)->column;
|
||||
it->filename = std::move((it - 1)->filename);
|
||||
}
|
||||
begin->line = carry_line;
|
||||
begin->column = carry_column;
|
||||
begin->filename = std::move(carry_filename);
|
||||
}
|
||||
}
|
||||
return final_trace;
|
||||
}
|
||||
|
||||
#if IS_LINUX || IS_APPLE
|
||||
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
|
||||
void try_resolve_jit_frame(const cpptrace::object_frame& dlframe, frame_with_inlines& frame) {
|
||||
auto object_res = lookup_jit_object(dlframe.raw_address);
|
||||
// TODO: At some point, dwarf resolution
|
||||
if(object_res) {
|
||||
frame.frame.symbol = object_res.unwrap().object
|
||||
.lookup_symbol(dlframe.raw_address - object_res.unwrap().base).value_or("");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
|
||||
void try_resolve_frame(
|
||||
symbol_resolver* resolver,
|
||||
const cpptrace::object_frame& dlframe,
|
||||
frame_with_inlines& frame
|
||||
) {
|
||||
try {
|
||||
frame = resolver->resolve_frame(dlframe);
|
||||
} catch(...) {
|
||||
detail::log_and_maybe_propagate_exception(std::current_exception());
|
||||
frame.frame.raw_address = dlframe.raw_address;
|
||||
frame.frame.object_address = dlframe.object_address;
|
||||
frame.frame.filename = dlframe.object_path;
|
||||
}
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
|
||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<object_frame>& frames) {
|
||||
std::vector<frame_with_inlines> trace(frames.size(), {null_frame(), {}});
|
||||
// Locking around all libdwarf interaction per https://github.com/davea42/libdwarf-code/discussions/184
|
||||
// And also locking for interactions with get_resolver
|
||||
static std::mutex mutex;
|
||||
const std::lock_guard<std::mutex> lock(mutex);
|
||||
for(const auto& group : collate_frames(frames, trace)) {
|
||||
try {
|
||||
const auto& object_name = group.first;
|
||||
if(object_name.empty()) {
|
||||
#if IS_LINUX || IS_APPLE
|
||||
for(const auto& entry : group.second) {
|
||||
try_resolve_jit_frame(entry.first.get(), entry.second.get());
|
||||
}
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
// TODO PERF: Potentially a duplicate open and parse with module base stuff (and debug map resolver)
|
||||
#if IS_LINUX
|
||||
auto object = open_elf_cached(object_name);
|
||||
#elif IS_APPLE
|
||||
auto object = open_mach_o_cached(object_name);
|
||||
#endif
|
||||
auto resolver = get_resolver(object_name);
|
||||
for(const auto& entry : group.second) {
|
||||
const auto& dlframe = entry.first.get();
|
||||
auto& frame = entry.second.get();
|
||||
try_resolve_frame(resolver.get(), dlframe, frame);
|
||||
#if IS_LINUX || IS_APPLE
|
||||
// fallback to symbol tables
|
||||
if(frame.frame.symbol.empty() && object.has_value()) {
|
||||
frame.frame.symbol = object
|
||||
.unwrap_value()
|
||||
->lookup_symbol(dlframe.object_address).value_or("");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
} catch(...) { // NOSONAR
|
||||
detail::log_and_maybe_propagate_exception(std::current_exception());
|
||||
}
|
||||
}
|
||||
// fill in basic info for any frames where there were resolution issues
|
||||
for(std::size_t i = 0; i < frames.size(); i++) {
|
||||
const auto& dlframe = frames[i];
|
||||
auto& frame = trace[i];
|
||||
if(frame.frame == null_frame()) {
|
||||
frame = {
|
||||
{
|
||||
dlframe.raw_address,
|
||||
dlframe.object_address,
|
||||
nullable<std::uint32_t>::null(),
|
||||
nullable<std::uint32_t>::null(),
|
||||
dlframe.object_path,
|
||||
"",
|
||||
false
|
||||
},
|
||||
{}
|
||||
};
|
||||
}
|
||||
}
|
||||
// flatten and finish
|
||||
return flatten_inlines(trace);
|
||||
}
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_NOTHING
|
||||
|
||||
#include <cpptrace/basic.hpp>
|
||||
#include "symbols/symbols.hpp"
|
||||
#include "utils/common.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
namespace nothing {
|
||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<frame_ptr>& frames) {
|
||||
return std::vector<stacktrace_frame>(frames.size(), null_frame());
|
||||
}
|
||||
|
||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<object_frame>& frames) {
|
||||
return std::vector<stacktrace_frame>(frames.size(), null_frame());
|
||||
}
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
#ifndef UNWIND_HPP
|
||||
#define UNWIND_HPP
|
||||
|
||||
#include <cpptrace/basic.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
#ifdef CPPTRACE_UNWIND_WITH_DBGHELP
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
#ifdef CPPTRACE_HARD_MAX_FRAMES
|
||||
constexpr std::size_t hard_max_frames = CPPTRACE_HARD_MAX_FRAMES;
|
||||
#else
|
||||
constexpr std::size_t hard_max_frames = 400;
|
||||
#endif
|
||||
|
||||
#ifdef CPPTRACE_UNWIND_WITH_DBGHELP
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
std::vector<frame_ptr> capture_frames(
|
||||
std::size_t skip,
|
||||
std::size_t max_depth,
|
||||
EXCEPTION_POINTERS* exception_pointers = nullptr
|
||||
);
|
||||
#else
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
std::vector<frame_ptr> capture_frames(std::size_t skip, std::size_t max_depth);
|
||||
#endif
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
std::size_t safe_capture_frames(frame_ptr* buffer, std::size_t size, std::size_t skip, std::size_t max_depth);
|
||||
|
||||
bool has_safe_unwind();
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
#ifdef CPPTRACE_UNWIND_WITH_DBGHELP
|
||||
|
||||
#include <cpptrace/basic.hpp>
|
||||
#include "unwind/unwind.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "platform/dbghelp_utils.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <cstddef>
|
||||
|
||||
#include <windows.h>
|
||||
#include <dbghelp.h>
|
||||
|
||||
// Fucking windows headers
|
||||
#ifdef min
|
||||
#undef min
|
||||
#endif
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
#if IS_MSVC
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4740) // warning C4740: flow in or out of inline asm code suppresses global optimization
|
||||
#endif
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
std::vector<frame_ptr> capture_frames(
|
||||
std::size_t skip,
|
||||
std::size_t max_depth,
|
||||
EXCEPTION_POINTERS* exception_pointers
|
||||
) {
|
||||
// https://jpassing.com/2008/03/12/walking-the-stack-of-the-current-thread/
|
||||
|
||||
// Get current thread context
|
||||
// GetThreadContext cannot be used on the current thread.
|
||||
// RtlCaptureContext doesn't work on i386
|
||||
CONTEXT context;
|
||||
ZeroMemory(&context, sizeof(CONTEXT));
|
||||
if(exception_pointers) {
|
||||
context = *exception_pointers->ContextRecord;
|
||||
} else {
|
||||
skip++; // we're unwinding from the capture_frames frame, skip it
|
||||
#if defined(_M_IX86) || defined(__i386__)
|
||||
context.ContextFlags = CONTEXT_CONTROL;
|
||||
#if IS_MSVC
|
||||
__asm {
|
||||
label:
|
||||
mov [context.Ebp], ebp;
|
||||
mov [context.Esp], esp;
|
||||
mov eax, [label];
|
||||
mov [context.Eip], eax;
|
||||
}
|
||||
#else
|
||||
asm(
|
||||
"label:\n\t"
|
||||
"mov{l %%ebp, %[cEbp] | %[cEbp], ebp};\n\t"
|
||||
"mov{l %%esp, %[cEsp] | %[cEsp], esp};\n\t"
|
||||
"mov{l $label, %%eax | eax, OFFSET label};\n\t"
|
||||
"mov{l %%eax, %[cEip] | %[cEip], eax};\n\t"
|
||||
: [cEbp] "=r" (context.Ebp),
|
||||
[cEsp] "=r" (context.Esp),
|
||||
[cEip] "=r" (context.Eip)
|
||||
);
|
||||
#endif
|
||||
#else
|
||||
RtlCaptureContext(&context);
|
||||
#endif
|
||||
}
|
||||
// Setup current frame
|
||||
STACKFRAME64 frame;
|
||||
ZeroMemory(&frame, sizeof(STACKFRAME64));
|
||||
DWORD machine_type;
|
||||
#if defined(_M_IX86) || defined(__i386__)
|
||||
machine_type = IMAGE_FILE_MACHINE_I386;
|
||||
frame.AddrPC.Offset = context.Eip;
|
||||
frame.AddrPC.Mode = AddrModeFlat;
|
||||
frame.AddrFrame.Offset = context.Ebp;
|
||||
frame.AddrFrame.Mode = AddrModeFlat;
|
||||
frame.AddrStack.Offset = context.Esp;
|
||||
frame.AddrStack.Mode = AddrModeFlat;
|
||||
#elif defined(_M_X64) || defined(__x86_64__)
|
||||
machine_type = IMAGE_FILE_MACHINE_AMD64;
|
||||
frame.AddrPC.Offset = context.Rip;
|
||||
frame.AddrPC.Mode = AddrModeFlat;
|
||||
frame.AddrFrame.Offset = context.Rsp;
|
||||
frame.AddrFrame.Mode = AddrModeFlat;
|
||||
frame.AddrStack.Offset = context.Rsp;
|
||||
frame.AddrStack.Mode = AddrModeFlat;
|
||||
#elif defined(_M_IA64)
|
||||
machine_type = IMAGE_FILE_MACHINE_IA64;
|
||||
frame.AddrPC.Offset = context.StIIP;
|
||||
frame.AddrPC.Mode = AddrModeFlat;
|
||||
frame.AddrFrame.Offset = context.IntSp;
|
||||
frame.AddrFrame.Mode = AddrModeFlat;
|
||||
frame.AddrBStore.Offset= context.RsBSP;
|
||||
frame.AddrBStore.Mode = AddrModeFlat;
|
||||
frame.AddrStack.Offset = context.IntSp;
|
||||
frame.AddrStack.Mode = AddrModeFlat;
|
||||
#elif defined(_M_ARM) || defined(__arm__)
|
||||
machine_type = IMAGE_FILE_MACHINE_ARM;
|
||||
frame.AddrPC.Offset = context.Pc;
|
||||
frame.AddrPC.Mode = AddrModeFlat;
|
||||
frame.AddrFrame.Offset = context.R11;
|
||||
frame.AddrFrame.Mode = AddrModeFlat;
|
||||
frame.AddrStack.Offset = context.Sp;
|
||||
frame.AddrStack.Mode = AddrModeFlat;
|
||||
#elif defined(_M_ARM64) || defined(__aarch64__)
|
||||
machine_type = IMAGE_FILE_MACHINE_ARM64;
|
||||
frame.AddrPC.Offset = context.Pc;
|
||||
frame.AddrPC.Mode = AddrModeFlat;
|
||||
frame.AddrFrame.Offset = context.Fp;
|
||||
frame.AddrFrame.Mode = AddrModeFlat;
|
||||
frame.AddrStack.Offset = context.Sp;
|
||||
frame.AddrStack.Mode = AddrModeFlat;
|
||||
#else
|
||||
#error "Cpptrace: StackWalk64 not supported for this platform yet"
|
||||
#endif
|
||||
|
||||
std::vector<frame_ptr> trace;
|
||||
|
||||
// Dbghelp is is single-threaded, so acquire a lock.
|
||||
auto lock = get_dbghelp_lock();
|
||||
// For some reason SymInitialize must be called before StackWalk64
|
||||
// Note that the code assumes that
|
||||
// SymInitialize( GetCurrentProcess(), NULL, TRUE ) has
|
||||
// already been called.
|
||||
//
|
||||
auto syminit_info = ensure_syminit();
|
||||
HANDLE thread = GetCurrentThread();
|
||||
while(trace.size() < max_depth) {
|
||||
if(
|
||||
!StackWalk64(
|
||||
machine_type,
|
||||
syminit_info.get_process_handle(),
|
||||
thread,
|
||||
&frame,
|
||||
machine_type == IMAGE_FILE_MACHINE_I386 ? NULL : &context,
|
||||
NULL,
|
||||
SymFunctionTableAccess64,
|
||||
SymGetModuleBase64,
|
||||
NULL
|
||||
)
|
||||
) {
|
||||
// Either failed or finished walking
|
||||
break;
|
||||
}
|
||||
if(frame.AddrPC.Offset != 0) {
|
||||
// Valid frame
|
||||
if(skip) {
|
||||
skip--;
|
||||
} else {
|
||||
// On x86/x64/arm, as far as I can tell, the frame return address is always one after the call
|
||||
// So we just decrement to get the pc back inside the `call` / `bl`
|
||||
// This is done with _Unwind too but conditionally based on info from _Unwind_GetIPInfo.
|
||||
trace.push_back(to_frame_ptr(frame.AddrPC.Offset) - 1);
|
||||
}
|
||||
} else {
|
||||
// base
|
||||
break;
|
||||
}
|
||||
}
|
||||
return trace;
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
std::size_t safe_capture_frames(frame_ptr*, std::size_t, std::size_t, std::size_t) {
|
||||
// Can't safe trace with dbghelp
|
||||
return 0;
|
||||
}
|
||||
#if IS_MSVC
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
bool has_safe_unwind() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
#ifdef CPPTRACE_UNWIND_WITH_EXECINFO
|
||||
|
||||
#include "unwind/unwind.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
#include <execinfo.h>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
std::vector<frame_ptr> capture_frames(std::size_t skip, std::size_t max_depth) {
|
||||
skip++;
|
||||
std::vector<void*> addrs(skip + std::min(hard_max_frames, max_depth), nullptr);
|
||||
// thread safe
|
||||
const int n_frames = backtrace(addrs.data(), static_cast<int>(addrs.size()));
|
||||
// I hate the copy here but it's the only way that isn't UB
|
||||
std::vector<frame_ptr> frames(n_frames - skip, 0);
|
||||
for(int i = skip; i < n_frames; i++) {
|
||||
// On x86/x64/arm, as far as I can tell, the frame return address is always one after the call
|
||||
// So we just decrement to get the pc back inside the `call` / `bl`
|
||||
// This is done with _Unwind too but conditionally based on info from _Unwind_GetIPInfo.
|
||||
frames[i - skip] = reinterpret_cast<frame_ptr>(addrs[i]) - 1;
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
std::size_t safe_capture_frames(frame_ptr*, std::size_t, std::size_t, std::size_t) {
|
||||
// Can't safe trace with execinfo
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool has_safe_unwind() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
#ifdef CPPTRACE_UNWIND_WITH_LIBUNWIND
|
||||
|
||||
#include "unwind/unwind.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
#define UNW_LOCAL_ONLY
|
||||
#include <libunwind.h>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
std::vector<frame_ptr> capture_frames(std::size_t skip, std::size_t max_depth) {
|
||||
skip++;
|
||||
std::vector<frame_ptr> frames;
|
||||
unw_context_t context;
|
||||
unw_cursor_t cursor;
|
||||
unw_getcontext(&context);
|
||||
unw_init_local(&cursor, &context);
|
||||
do {
|
||||
unw_word_t pc;
|
||||
unw_word_t sp;
|
||||
unw_get_reg(&cursor, UNW_REG_IP, &pc);
|
||||
unw_get_reg(&cursor, UNW_REG_SP, &sp);
|
||||
if(skip) {
|
||||
skip--;
|
||||
} else {
|
||||
// pc is the instruction after the `call`, adjust back to the previous instruction
|
||||
frames.push_back(to_frame_ptr(pc) - 1);
|
||||
}
|
||||
} while(unw_step(&cursor) > 0 && frames.size() < max_depth);
|
||||
return frames;
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
std::size_t safe_capture_frames(frame_ptr* buffer, std::size_t size, std::size_t skip, std::size_t max_depth) {
|
||||
// some code duplication, but whatever
|
||||
skip++;
|
||||
unw_context_t context;
|
||||
unw_cursor_t cursor;
|
||||
// thread and signal-safe https://www.nongnu.org/libunwind/man/unw_getcontext(3).html
|
||||
unw_getcontext(&context);
|
||||
// thread and signal-safe https://www.nongnu.org/libunwind/man/unw_init_local(3).html
|
||||
unw_init_local(&cursor, &context);
|
||||
size_t i = 0;
|
||||
while(i < size && i < max_depth) {
|
||||
unw_word_t pc;
|
||||
unw_word_t sp;
|
||||
// thread and signal-safe https://www.nongnu.org/libunwind/man/unw_get_reg(3).html
|
||||
unw_get_reg(&cursor, UNW_REG_IP, &pc);
|
||||
unw_get_reg(&cursor, UNW_REG_SP, &sp);
|
||||
if(skip) {
|
||||
skip--;
|
||||
} else {
|
||||
// thread and signal-safe
|
||||
if(unw_is_signal_frame(&cursor)) {
|
||||
// pc is the instruction that caused the signal
|
||||
// just a cast, thread and signal safe
|
||||
buffer[i] = to_frame_ptr(pc);
|
||||
} else {
|
||||
// pc is the instruction after the `call`, adjust back to the previous instruction
|
||||
// just a cast, thread and signal safe
|
||||
buffer[i] = to_frame_ptr(pc) - 1;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
// thread and signal-safe as long as the cursor is in the local address space, which it is
|
||||
// https://www.nongnu.org/libunwind/man/unw_step(3).html
|
||||
if(unw_step(&cursor) <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
bool has_safe_unwind() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
#ifdef CPPTRACE_UNWIND_WITH_NOTHING
|
||||
|
||||
#include "unwind/unwind.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
std::vector<frame_ptr> capture_frames(std::size_t, std::size_t) {
|
||||
return {};
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
std::size_t safe_capture_frames(frame_ptr*, std::size_t, std::size_t, std::size_t) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool has_safe_unwind() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
#ifdef CPPTRACE_UNWIND_WITH_UNWIND
|
||||
|
||||
#include "unwind/unwind.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
#include <unwind.h>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
struct unwind_state {
|
||||
std::size_t skip;
|
||||
std::size_t max_depth;
|
||||
std::vector<frame_ptr>& vec;
|
||||
};
|
||||
|
||||
_Unwind_Reason_Code unwind_callback(_Unwind_Context* context, void* arg) {
|
||||
unwind_state& state = *static_cast<unwind_state*>(arg);
|
||||
if(state.skip) {
|
||||
state.skip--;
|
||||
if(_Unwind_GetIP(context) == frame_ptr(0)) {
|
||||
return _URC_END_OF_STACK;
|
||||
} else {
|
||||
return _URC_NO_REASON;
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT(
|
||||
state.vec.size() < state.max_depth,
|
||||
"Somehow cpptrace::detail::unwind_callback is being called beyond the max_depth"
|
||||
);
|
||||
int is_before_instruction = 0;
|
||||
frame_ptr ip = _Unwind_GetIPInfo(context, &is_before_instruction);
|
||||
if(!is_before_instruction && ip != frame_ptr(0)) {
|
||||
ip--;
|
||||
}
|
||||
if (ip == frame_ptr(0)) {
|
||||
return _URC_END_OF_STACK;
|
||||
} else {
|
||||
state.vec.push_back(ip);
|
||||
if(state.vec.size() >= state.max_depth) {
|
||||
return _URC_END_OF_STACK;
|
||||
} else {
|
||||
return _URC_NO_REASON;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
std::vector<frame_ptr> capture_frames(std::size_t skip, std::size_t max_depth) {
|
||||
std::vector<frame_ptr> frames;
|
||||
unwind_state state{skip + 1, max_depth, frames};
|
||||
_Unwind_Backtrace(unwind_callback, &state); // presumably thread-safe
|
||||
return frames;
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
std::size_t safe_capture_frames(frame_ptr*, std::size_t, std::size_t, std::size_t) {
|
||||
// Can't safe trace with _Unwind
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool has_safe_unwind() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
#ifdef CPPTRACE_UNWIND_WITH_WINAPI
|
||||
|
||||
#include <cpptrace/basic.hpp>
|
||||
#include "unwind/unwind.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
|
||||
// Fucking windows headers
|
||||
#ifdef min
|
||||
#undef min
|
||||
#endif
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
std::vector<frame_ptr> capture_frames(std::size_t skip, std::size_t max_depth) {
|
||||
std::vector<void*> addrs(skip + std::min(hard_max_frames, max_depth), nullptr);
|
||||
std::size_t n_frames = CaptureStackBackTrace(
|
||||
static_cast<ULONG>(skip + 1),
|
||||
static_cast<ULONG>(addrs.size()),
|
||||
addrs.data(),
|
||||
NULL
|
||||
);
|
||||
// I hate the copy here but it's the only way that isn't UB
|
||||
std::vector<frame_ptr> frames(n_frames, 0);
|
||||
for(std::size_t i = 0; i < n_frames; i++) {
|
||||
// On x86/x64/arm, as far as I can tell, the frame return address is always one after the call
|
||||
// So we just decrement to get the pc back inside the `call` / `bl`
|
||||
// This is done with _Unwind too but conditionally based on info from _Unwind_GetIPInfo.
|
||||
frames[i] = reinterpret_cast<frame_ptr>(addrs[i]) - 1;
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
std::size_t safe_capture_frames(frame_ptr*, std::size_t, std::size_t, std::size_t) {
|
||||
// Can't safe trace with winapi
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool has_safe_unwind() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
#include <cpptrace/utils.hpp>
|
||||
#include <cpptrace/exceptions.hpp>
|
||||
#include <cpptrace/formatting.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "demangle/demangle.hpp"
|
||||
#include "snippets/snippet.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "platform/exception_type.hpp"
|
||||
#include "options.hpp"
|
||||
|
||||
#if !IS_WINDOWS
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
std::string demangle(const std::string& name) {
|
||||
return detail::demangle(name, false);
|
||||
}
|
||||
|
||||
std::string get_snippet(const std::string& path, std::size_t line, std::size_t context_size, bool color) {
|
||||
return detail::get_snippet(path, line, nullable<std::uint32_t>::null(), context_size, color);
|
||||
}
|
||||
|
||||
std::string get_snippet(
|
||||
const std::string& path,
|
||||
std::size_t line,
|
||||
nullable<std::uint32_t> column,
|
||||
std::size_t context_size,
|
||||
bool color
|
||||
) {
|
||||
return detail::get_snippet(path, line, column, context_size, color);
|
||||
}
|
||||
|
||||
bool isatty(int fd) {
|
||||
return detail::isatty(fd);
|
||||
}
|
||||
|
||||
#if IS_WINDOWS
|
||||
extern const int stdin_fileno = detail::fileno(stdin);
|
||||
extern const int stdout_fileno = detail::fileno(stdout);
|
||||
extern const int stderr_fileno = detail::fileno(stderr);
|
||||
#else
|
||||
extern const int stdin_fileno = STDIN_FILENO;
|
||||
extern const int stdout_fileno = STDOUT_FILENO;
|
||||
extern const int stderr_fileno = STDERR_FILENO;
|
||||
#endif
|
||||
|
||||
namespace detail {
|
||||
const formatter& get_terminate_formatter() {
|
||||
static formatter the_formatter = formatter{}
|
||||
.header("Stack trace to reach terminate handler (most recent call first):");
|
||||
return the_formatter;
|
||||
}
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE void print_terminate_trace() {
|
||||
try { // try/catch can never be hit but it's needed to prevent TCO
|
||||
detail::get_terminate_formatter().print(std::cerr, generate_trace(1));
|
||||
} catch(...) {
|
||||
detail::log_and_maybe_propagate_exception(std::current_exception());
|
||||
}
|
||||
}
|
||||
|
||||
[[noreturn]] void MSVC_CDECL terminate_handler() {
|
||||
// TODO: Support std::nested_exception?
|
||||
try {
|
||||
auto ptr = std::current_exception();
|
||||
if(ptr == nullptr) {
|
||||
fputs("terminate called without an active exception", stderr);
|
||||
print_terminate_trace();
|
||||
} else {
|
||||
std::rethrow_exception(ptr);
|
||||
}
|
||||
} catch(cpptrace::exception& e) {
|
||||
microfmt::print(
|
||||
stderr,
|
||||
"Terminate called after throwing an instance of {}: {}\n",
|
||||
demangle(typeid(e).name()),
|
||||
e.message()
|
||||
);
|
||||
e.trace().print(std::cerr, isatty(stderr_fileno));
|
||||
} catch(std::exception& e) {
|
||||
microfmt::print(
|
||||
stderr, "Terminate called after throwing an instance of {}: {}\n", demangle(typeid(e).name()), e.what()
|
||||
);
|
||||
print_terminate_trace();
|
||||
} catch(...) {
|
||||
microfmt::print(
|
||||
stderr, "Terminate called after throwing an instance of {}\n", detail::exception_type_name()
|
||||
);
|
||||
print_terminate_trace();
|
||||
}
|
||||
std::flush(std::cerr);
|
||||
abort();
|
||||
}
|
||||
|
||||
void register_terminate_handler() {
|
||||
std::set_terminate(terminate_handler);
|
||||
}
|
||||
|
||||
#if defined(_WIN32) && !defined(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP)
|
||||
void load_symbols_for_file(const std::string&) {
|
||||
// nop
|
||||
}
|
||||
#endif
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
#ifndef COMMON_HPP
|
||||
#define COMMON_HPP
|
||||
|
||||
#include <cpptrace/basic.hpp>
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#define ESC "\033["
|
||||
#define RESET ESC "0m"
|
||||
#define RED ESC "31m"
|
||||
#define GREEN ESC "32m"
|
||||
#define YELLOW ESC "33m"
|
||||
#define BLUE ESC "34m"
|
||||
#define MAGENTA ESC "35m"
|
||||
#define CYAN ESC "36m"
|
||||
|
||||
#if IS_GCC || IS_CLANG
|
||||
#define NODISCARD __attribute__((warn_unused_result))
|
||||
// #elif IS_MSVC && _MSC_VER >= 1700
|
||||
// #define NODISCARD _Check_return_
|
||||
#else
|
||||
#define NODISCARD
|
||||
#endif
|
||||
|
||||
// workaround a bizarre gcc bug https://godbolt.org/z/s78vnf7jv
|
||||
// https://github.com/jeremy-rifkin/cpptrace/issues/220
|
||||
#if defined(__GNUC__) && (__GNUC__ < 7)
|
||||
#undef NODISCARD
|
||||
#define NODISCARD
|
||||
#endif
|
||||
|
||||
#if IS_MSVC
|
||||
#define MSVC_CDECL __cdecl
|
||||
#else
|
||||
#define MSVC_CDECL
|
||||
#endif
|
||||
|
||||
// support is pretty good https://godbolt.org/z/djTqv7WMY, checked in cmake during config
|
||||
#ifdef HAS_ATTRIBUTE_PACKED
|
||||
#define PACKED __attribute__((packed))
|
||||
#else
|
||||
#define PACKED
|
||||
#endif
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
inline stacktrace_frame null_frame() {
|
||||
return {
|
||||
0,
|
||||
0,
|
||||
nullable<std::uint32_t>::null(),
|
||||
nullable<std::uint32_t>::null(),
|
||||
"",
|
||||
"",
|
||||
false
|
||||
};
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
#include "utils/error.hpp"
|
||||
|
||||
#include <cpptrace/utils.hpp>
|
||||
|
||||
#include <exception>
|
||||
|
||||
#include "platform/exception_type.hpp"
|
||||
#include "logging.hpp"
|
||||
#include "options.hpp"
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
internal_error::internal_error(std::string message) : msg("Cpptrace internal error: " + std::move(message)) {}
|
||||
|
||||
constexpr const char* assert_actions[] = {"assertion", "verification", "panic"};
|
||||
constexpr const char* assert_names[] = {"ASSERT", "VERIFY", "PANIC"};
|
||||
|
||||
void assert_fail(
|
||||
assert_type type,
|
||||
const char* expression,
|
||||
const char* signature,
|
||||
source_location location,
|
||||
const char* message
|
||||
) {
|
||||
const char* action = assert_actions[static_cast<std::underlying_type<assert_type>::type>(type)];
|
||||
const char* name = assert_names[static_cast<std::underlying_type<assert_type>::type>(type)];
|
||||
if(message == nullptr) {
|
||||
throw internal_error(
|
||||
"Cpptrace {} failed at {}:{}: {}\n"
|
||||
" {}({});\n",
|
||||
action, location.file, location.line, signature,
|
||||
name, expression
|
||||
);
|
||||
} else {
|
||||
throw internal_error(
|
||||
"Cpptrace {} failed at {}:{}: {}: {}\n"
|
||||
" {}({});\n",
|
||||
action, location.file, location.line, signature, message,
|
||||
name, expression
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void panic(
|
||||
const char* signature,
|
||||
source_location location,
|
||||
string_view message
|
||||
) {
|
||||
if(message == "") {
|
||||
throw internal_error(
|
||||
"Cpptrace panic {}:{}: {}\n",
|
||||
location.file, location.line, signature
|
||||
);
|
||||
} else {
|
||||
throw internal_error(
|
||||
"Cpptrace panic {}:{}: {}: {}\n",
|
||||
location.file, location.line, signature, message
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void log_and_maybe_propagate_exception(std::exception_ptr ptr) {
|
||||
try {
|
||||
if(ptr) {
|
||||
std::rethrow_exception(ptr);
|
||||
}
|
||||
} catch(const internal_error& e) {
|
||||
log::error("Unhandled cpptrace internal error: {}", e.what());
|
||||
} catch(const std::exception& e) {
|
||||
log::error(
|
||||
"Unhandled cpptrace internal error of type {}: {}",
|
||||
cpptrace::demangle(typeid(e).name()),
|
||||
e.what()
|
||||
);
|
||||
} catch(...) {
|
||||
log::error("Unhandled cpptrace internal error of type {}", detail::exception_type_name());
|
||||
}
|
||||
if(!should_absorb_trace_exceptions()) {
|
||||
if(ptr) {
|
||||
std::rethrow_exception(ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
#ifndef ERROR_HPP
|
||||
#define ERROR_HPP
|
||||
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
#include "utils/microfmt.hpp"
|
||||
#include "utils/string_view.hpp"
|
||||
|
||||
#include <cpptrace/utils.hpp>
|
||||
|
||||
#if IS_MSVC
|
||||
#define CPPTRACE_PFUNC __FUNCSIG__
|
||||
#else
|
||||
#define CPPTRACE_PFUNC __extension__ __PRETTY_FUNCTION__
|
||||
#endif
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
class internal_error : public std::exception {
|
||||
std::string msg;
|
||||
public:
|
||||
internal_error(std::string message);
|
||||
template<typename... Args>
|
||||
internal_error(const char* format, Args&&... args) : internal_error(microfmt::format(format, args...)) {}
|
||||
const char* what() const noexcept override {
|
||||
return msg.c_str();
|
||||
}
|
||||
};
|
||||
|
||||
// Lightweight std::source_location.
|
||||
struct source_location {
|
||||
const char* const file;
|
||||
const int line;
|
||||
constexpr source_location(
|
||||
const char* _file,
|
||||
int _line
|
||||
) : file(_file), line(_line) {}
|
||||
};
|
||||
|
||||
#define CPPTRACE_CURRENT_LOCATION ::cpptrace::detail::source_location(__FILE__, __LINE__)
|
||||
|
||||
enum class assert_type {
|
||||
assert,
|
||||
verify,
|
||||
panic,
|
||||
};
|
||||
|
||||
[[noreturn]] CPPTRACE_EXPORT void assert_fail(
|
||||
assert_type type,
|
||||
const char* expression,
|
||||
const char* signature,
|
||||
source_location location,
|
||||
const char* message
|
||||
);
|
||||
|
||||
[[noreturn]] void panic(
|
||||
const char* signature,
|
||||
source_location location,
|
||||
string_view message = ""
|
||||
);
|
||||
|
||||
|
||||
template<typename... Args>
|
||||
void nullfn(Args&&...);
|
||||
|
||||
#define PHONY_USE(...) (static_cast<decltype(nullfn(__VA_ARGS__))>(0))
|
||||
|
||||
// Work around a compiler warning
|
||||
template<typename T>
|
||||
std::string as_string(T&& value) {
|
||||
return std::string(std::forward<T>(value));
|
||||
}
|
||||
|
||||
inline std::string as_string() {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Check condition in both debug and release. std::runtime_error on failure.
|
||||
#define PANIC(...) ((::cpptrace::detail::panic)(CPPTRACE_PFUNC, CPPTRACE_CURRENT_LOCATION, ::cpptrace::detail::as_string(__VA_ARGS__)))
|
||||
|
||||
inline void assert_impl(
|
||||
bool condition,
|
||||
const char* message,
|
||||
assert_type type,
|
||||
const char* args,
|
||||
const char* signature,
|
||||
source_location location
|
||||
) {
|
||||
if(!condition) {
|
||||
assert_fail(type, args, signature, location, message);
|
||||
}
|
||||
}
|
||||
|
||||
inline void assert_impl(
|
||||
bool condition,
|
||||
assert_type type,
|
||||
const char* args,
|
||||
const char* signature,
|
||||
source_location location
|
||||
) {
|
||||
assert_impl(
|
||||
condition,
|
||||
nullptr,
|
||||
type,
|
||||
args,
|
||||
signature,
|
||||
location
|
||||
);
|
||||
}
|
||||
|
||||
// Check condition in both debug and release. std::runtime_error on failure.
|
||||
#define VERIFY(...) ( \
|
||||
assert_impl(__VA_ARGS__, ::cpptrace::detail::assert_type::verify, #__VA_ARGS__, CPPTRACE_PFUNC, CPPTRACE_CURRENT_LOCATION) \
|
||||
)
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Check condition in both debug. std::runtime_error on failure.
|
||||
#define ASSERT(...) ( \
|
||||
assert_impl(__VA_ARGS__, ::cpptrace::detail::assert_type::assert, #__VA_ARGS__, CPPTRACE_PFUNC, CPPTRACE_CURRENT_LOCATION) \
|
||||
)
|
||||
#else
|
||||
// Check condition in both debug. std::runtime_error on failure.
|
||||
#define ASSERT(...) PHONY_USE(__VA_ARGS__)
|
||||
#endif
|
||||
|
||||
void log_and_maybe_propagate_exception(std::exception_ptr);
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
#ifndef BASE_FILE_HPP
|
||||
#define BASE_FILE_HPP
|
||||
|
||||
#include "utils/span.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
class base_file {
|
||||
public:
|
||||
virtual ~base_file() = default;
|
||||
virtual string_view path() const = 0;
|
||||
virtual Result<monostate, internal_error> read_bytes(bspan buffer, off_t offset) const = 0;
|
||||
|
||||
template<
|
||||
typename T,
|
||||
typename std::enable_if<
|
||||
std::is_standard_layout<T>::value && is_trivially_copyable<T>::value && !is_span<T>::value,
|
||||
int
|
||||
>::type = 0
|
||||
>
|
||||
Result<T, internal_error> read(off_t offset) {
|
||||
T object{};
|
||||
auto res = read_bytes(make_bspan(object), offset);
|
||||
if(!res) {
|
||||
return res.unwrap_error();
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
template<
|
||||
typename T,
|
||||
typename std::enable_if<
|
||||
std::is_standard_layout<T>::value && is_trivially_copyable<T>::value,
|
||||
int
|
||||
>::type = 0
|
||||
>
|
||||
Result<monostate, internal_error> read_span(span<T> items, off_t offset) {
|
||||
return read_bytes(
|
||||
make_span(reinterpret_cast<char*>(items.data()), reinterpret_cast<char*>(items.data() + items.size())),
|
||||
offset
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#include "utils/io/file.hpp"
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
string_view file::path() const {
|
||||
return object_path;
|
||||
}
|
||||
|
||||
Result<file, internal_error> file::open(cstring_view object_path) {
|
||||
auto file_obj = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter);
|
||||
if(file_obj == nullptr) {
|
||||
return internal_error("Unable to read object file {}", object_path);
|
||||
}
|
||||
return file(std::move(file_obj), object_path);
|
||||
}
|
||||
|
||||
Result<monostate, internal_error> file::read_bytes(bspan buffer, off_t offset) const {
|
||||
if(std::fseek(file_obj, offset, SEEK_SET) != 0) {
|
||||
return internal_error("fseek error in {} at offset {}", path(), offset);
|
||||
}
|
||||
if(std::fread(buffer.data(), buffer.size(), 1, file_obj) != 1) {
|
||||
return internal_error("fread error in {} at offset {} for {} bytes", path(), offset, buffer.size());
|
||||
}
|
||||
return monostate{};
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
#ifndef FILE_HPP
|
||||
#define FILE_HPP
|
||||
|
||||
#include "utils/string_view.hpp"
|
||||
#include "utils/span.hpp"
|
||||
#include "utils/io/base_file.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
class file : public base_file {
|
||||
file_wrapper file_obj;
|
||||
std::string object_path;
|
||||
|
||||
file(file_wrapper file_obj, string_view path) : file_obj(std::move(file_obj)), object_path(path) {}
|
||||
|
||||
public:
|
||||
file(file&&) = default;
|
||||
~file() override = default;
|
||||
|
||||
string_view path() const override;
|
||||
|
||||
static Result<file, internal_error> open(cstring_view object_path);
|
||||
|
||||
virtual Result<monostate, internal_error> read_bytes(bspan buffer, off_t offset) const override;
|
||||
};
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
#include "utils/io/memory_file_view.hpp"
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
string_view memory_file_view::path() const {
|
||||
return object_path;
|
||||
}
|
||||
|
||||
Result<monostate, internal_error> memory_file_view::read_bytes(bspan buffer, off_t offset) const {
|
||||
if(offset < 0) {
|
||||
return internal_error("Illegal read in memory file {}: offset {}", path(), offset);
|
||||
}
|
||||
if(offset + buffer.size() > data.size()) {
|
||||
return internal_error(
|
||||
"Illegal read in memory file {}: offset = {}, size = {}, file size = {}",
|
||||
path(), offset, buffer.size(), data.size()
|
||||
);
|
||||
}
|
||||
std::memcpy(buffer.data(), data.data() + offset, buffer.size());
|
||||
return monostate{};
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
#ifndef MEMORY_FILE_VIEW_HPP
|
||||
#define MEMORY_FILE_VIEW_HPP
|
||||
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/span.hpp"
|
||||
#include "utils/io/base_file.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
class memory_file_view : public base_file {
|
||||
cbspan data;
|
||||
std::string object_path = "<memory file>";
|
||||
|
||||
public:
|
||||
memory_file_view(cbspan data) : data(data) {}
|
||||
~memory_file_view() override = default;
|
||||
|
||||
string_view path() const override;
|
||||
|
||||
virtual Result<monostate, internal_error> read_bytes(bspan buffer, off_t offset) const override;
|
||||
};
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
#ifndef LRU_CACHE_HPP
|
||||
#define LRU_CACHE_HPP
|
||||
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/optional.hpp"
|
||||
|
||||
#include <list>
|
||||
#include <unordered_map>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
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();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
#include "utils/microfmt.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace microfmt {
|
||||
namespace detail {
|
||||
|
||||
std::ostream& get_cout() {
|
||||
return std::cout;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
|
@ -0,0 +1,312 @@
|
|||
#ifndef MICROFMT_HPP
|
||||
#define MICROFMT_HPP
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <initializer_list>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
|
||||
#include <string_view>
|
||||
#endif
|
||||
#ifdef _MSC_VER
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
|
||||
#include "utils/string_view.hpp"
|
||||
|
||||
// https://github.com/jeremy-rifkin/microfmt
|
||||
// Format: {[align][width][:[fill][base]]} # width: number or {}
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace microfmt {
|
||||
namespace detail {
|
||||
inline std::uint64_t clz(std::uint64_t value) {
|
||||
#ifdef _MSC_VER
|
||||
unsigned long out = 0;
|
||||
#ifdef _WIN64
|
||||
_BitScanReverse64(&out, value);
|
||||
#else
|
||||
if(_BitScanReverse(&out, std::uint32_t(value >> 32))) {
|
||||
return 63 - int(out + 32);
|
||||
}
|
||||
_BitScanReverse(&out, std::uint32_t(value));
|
||||
#endif
|
||||
return 63 - out;
|
||||
#else
|
||||
return __builtin_clzll(value);
|
||||
#endif
|
||||
}
|
||||
|
||||
template<typename U, typename V> U to(V v) {
|
||||
return static_cast<U>(v); // A way to cast to U without "warning: useless cast to type"
|
||||
}
|
||||
|
||||
enum class alignment { left, right };
|
||||
|
||||
struct format_options {
|
||||
alignment align = alignment::left;
|
||||
char fill = ' ';
|
||||
size_t width = 0;
|
||||
char base = 'd';
|
||||
};
|
||||
|
||||
template<typename OutputIt, typename InputIt>
|
||||
void do_write(OutputIt out, InputIt begin, InputIt end, const format_options& options) {
|
||||
auto size = end - begin;
|
||||
if(static_cast<std::size_t>(size) >= options.width) {
|
||||
std::copy(begin, end, out);
|
||||
} else {
|
||||
if(options.align == alignment::left) {
|
||||
std::copy(begin, end, out);
|
||||
std::fill_n(out, options.width - size, options.fill);
|
||||
} else {
|
||||
std::fill_n(out, options.width - size, options.fill);
|
||||
std::copy(begin, end, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<int shift, int mask>
|
||||
std::string to_string(std::uint64_t value, const char* digits = "0123456789abcdef") {
|
||||
if(value == 0) {
|
||||
return "0";
|
||||
} else {
|
||||
// digits = floor(1 + log_base(x))
|
||||
// log_base(x) = log_2(x) / log_2(base)
|
||||
// log_2(x) == 63 - clz(x)
|
||||
// 1 + (63 - clz(value)) / (63 - clz(1 << shift))
|
||||
// 63 - clz(1 << shift) is the same as shift
|
||||
auto n_digits = to<std::size_t>(1 + (63 - clz(value)) / shift);
|
||||
std::string number;
|
||||
number.resize(n_digits);
|
||||
std::size_t i = n_digits - 1;
|
||||
while(value > 0) {
|
||||
number[i--] = digits[value & mask];
|
||||
value >>= shift;
|
||||
}
|
||||
return number;
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string to_string(std::uint64_t value, const format_options& options) {
|
||||
switch(options.base) {
|
||||
case 'H': return to_string<4, 0xf>(value, "0123456789ABCDEF");
|
||||
case 'h': return to_string<4, 0xf>(value);
|
||||
case 'o': return to_string<3, 0x7>(value);
|
||||
case 'b': return to_string<1, 0x1>(value);
|
||||
default: return std::to_string(value); // failure: decimal
|
||||
}
|
||||
}
|
||||
|
||||
struct string_view {
|
||||
const char* data;
|
||||
std::size_t size;
|
||||
};
|
||||
|
||||
class format_value {
|
||||
enum class value_type {
|
||||
char_value,
|
||||
int64_value,
|
||||
uint64_value,
|
||||
string_value,
|
||||
string_view_value,
|
||||
c_string_value,
|
||||
};
|
||||
union {
|
||||
char char_value;
|
||||
std::int64_t int64_value;
|
||||
std::uint64_t uint64_value;
|
||||
const std::string* string_value;
|
||||
string_view string_view_value;
|
||||
const char* c_string_value;
|
||||
};
|
||||
value_type value;
|
||||
|
||||
public:
|
||||
format_value(char c) : char_value(c), value(value_type::char_value) {}
|
||||
format_value(short int_val) : int64_value(int_val), value(value_type::int64_value) {}
|
||||
format_value(int int_val) : int64_value(int_val), value(value_type::int64_value) {}
|
||||
format_value(long int_val) : int64_value(int_val), value(value_type::int64_value) {}
|
||||
format_value(long long int_val) : int64_value(int_val), value(value_type::int64_value) {}
|
||||
format_value(unsigned char int_val) : uint64_value(int_val), value(value_type::uint64_value) {}
|
||||
format_value(unsigned short int_val) : uint64_value(int_val), value(value_type::uint64_value) {}
|
||||
format_value(unsigned int int_val) : uint64_value(int_val), value(value_type::uint64_value) {}
|
||||
format_value(unsigned long int_val) : uint64_value(int_val), value(value_type::uint64_value) {}
|
||||
format_value(unsigned long long int_val) : uint64_value(int_val), value(value_type::uint64_value) {}
|
||||
format_value(const std::string& string) : string_value(&string), value(value_type::string_value) {}
|
||||
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
|
||||
format_value(std::string_view sv)
|
||||
: string_view_value{sv.data(), sv.size()}, value(value_type::string_view_value) {}
|
||||
#endif
|
||||
format_value(cpptrace::detail::string_view sv)
|
||||
: string_view_value{sv.data(), sv.size()}, value(value_type::string_view_value) {}
|
||||
format_value(const char* c_string) : c_string_value(c_string), value(value_type::c_string_value) {}
|
||||
|
||||
int unwrap_int() const {
|
||||
switch(value) {
|
||||
case value_type::int64_value: return static_cast<int>(int64_value);
|
||||
case value_type::uint64_value: return static_cast<int>(uint64_value);
|
||||
default: return 0; // failure: just 0
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
template<typename OutputIt>
|
||||
void write(OutputIt out, const format_options& options) const {
|
||||
switch(value) {
|
||||
case value_type::char_value:
|
||||
do_write(out, &char_value, &char_value + 1, options);
|
||||
break;
|
||||
case value_type::int64_value:
|
||||
{
|
||||
std::string str;
|
||||
std::int64_t val = int64_value;
|
||||
if(val < 0) {
|
||||
str += '-';
|
||||
val *= -1;
|
||||
}
|
||||
str += to_string(static_cast<std::uint64_t>(val), options);
|
||||
do_write(out, str.begin(), str.end(), options);
|
||||
}
|
||||
break;
|
||||
case value_type::uint64_value:
|
||||
{
|
||||
std::string str = to_string(uint64_value, options);
|
||||
do_write(out, str.begin(), str.end(), options);
|
||||
}
|
||||
break;
|
||||
case value_type::string_value:
|
||||
do_write(out, string_value->begin(), string_value->end(), options);
|
||||
break;
|
||||
case value_type::string_view_value:
|
||||
do_write(out, string_view_value.data, string_view_value.data + string_view_value.size, options);
|
||||
break;
|
||||
case value_type::c_string_value:
|
||||
do_write(out, c_string_value, c_string_value + std::strlen(c_string_value), options);
|
||||
break;
|
||||
} // failure: nop
|
||||
}
|
||||
};
|
||||
|
||||
// note: previously used std::array and there was a bug with std::array<T, 0> affecting old msvc
|
||||
// https://godbolt.org/z/88T8hrzzq mre: https://godbolt.org/z/drd8echbP
|
||||
template<typename OutputIt, typename InputIt>
|
||||
void format(OutputIt out, InputIt fmt_begin, InputIt fmt_end, const std::initializer_list<format_value>& args) {
|
||||
std::size_t arg_i = 0;
|
||||
auto it = fmt_begin;
|
||||
auto peek = [&] (std::size_t dist) -> char { // 0 on failure
|
||||
return fmt_end - it > signed(dist) ? *(it + dist) : 0;
|
||||
};
|
||||
auto read_number = [&] () -> int { // -1 on failure
|
||||
auto scan = it;
|
||||
int num = 0;
|
||||
while(scan != fmt_end && isdigit(*scan)) {
|
||||
num *= 10;
|
||||
num += *scan - '0';
|
||||
scan++;
|
||||
}
|
||||
if(scan != it) {
|
||||
it = scan;
|
||||
return num;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
for(; it != fmt_end; it++) {
|
||||
if((*it == '{' || *it == '}') && peek(1) == *it) { // parse {{ and }} escapes
|
||||
it++;
|
||||
} else if(*it == '{' && it + 1 != fmt_end) {
|
||||
auto saved_it = it;
|
||||
auto handle_formatter = [&] () {
|
||||
it++;
|
||||
format_options options;
|
||||
// try to parse alignment
|
||||
if(*it == '<' || *it == '>') {
|
||||
options.align = *it++ == '<' ? alignment::left : alignment::right;
|
||||
}
|
||||
// try to parse width
|
||||
auto width = read_number(); // handles fmt_end check
|
||||
if(width != -1) {
|
||||
options.width = width;
|
||||
} else if(it != fmt_end && *it == '{') { // try to parse variable width
|
||||
if(peek(1) != '}') {
|
||||
return false;
|
||||
}
|
||||
it += 2;
|
||||
options.width = arg_i < args.size() ? args.begin()[arg_i++].unwrap_int() : 0;
|
||||
}
|
||||
// try to parse fill/base
|
||||
if(it != fmt_end && *it == ':') {
|
||||
it++;
|
||||
if(fmt_end - it > 1 && *it != '}' && peek(1) != '}') { // two chars before the }, fill+base
|
||||
options.fill = *it++;
|
||||
options.base = *it++;
|
||||
} else if(it != fmt_end && *it != '}') { // one char before the }, just base
|
||||
if(*it == 'd' || *it == 'h' || *it == 'H' || *it == 'o' || *it == 'b') {
|
||||
options.base = *it++;
|
||||
} else {
|
||||
options.fill = *it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(it == fmt_end || *it != '}') {
|
||||
return false;
|
||||
}
|
||||
if(arg_i < args.size()) {
|
||||
args.begin()[arg_i++].write(out, options);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
if(handle_formatter()) {
|
||||
continue; // If reached here, successfully parsed and wrote a formatter. Don't write *it.
|
||||
}
|
||||
it = saved_it; // go back
|
||||
}
|
||||
*out++ = *it;
|
||||
}
|
||||
}
|
||||
|
||||
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
|
||||
template<typename OutputIt>
|
||||
void format(OutputIt out, std::string_view fmt, const std::initializer_list<format_value>& args) {
|
||||
return format(out, fmt.begin(), fmt.end(), args);
|
||||
}
|
||||
#endif
|
||||
|
||||
template<typename OutputIt>
|
||||
void format(OutputIt out, const char* fmt, const std::initializer_list<format_value>& args) {
|
||||
return format(out, fmt, fmt + std::strlen(fmt), args);
|
||||
}
|
||||
|
||||
std::ostream& get_cout();
|
||||
}
|
||||
|
||||
template<typename S, typename... Args>
|
||||
std::string format(const S& fmt, Args&&... args) {
|
||||
std::string str;
|
||||
detail::format(std::back_inserter(str), fmt, {detail::format_value(args)...});
|
||||
return str;
|
||||
}
|
||||
|
||||
template<typename S, typename... Args>
|
||||
void print(const S& fmt, Args&&... args) {
|
||||
detail::format(std::ostream_iterator<char>(detail::get_cout()), fmt, {args...});
|
||||
}
|
||||
|
||||
template<typename S, typename... Args>
|
||||
void print(std::ostream& ostream, const S& fmt, Args&&... args) {
|
||||
detail::format(std::ostream_iterator<char>(ostream), fmt, {args...});
|
||||
}
|
||||
|
||||
template<typename S, typename... Args>
|
||||
void print(std::FILE* stream, const S& fmt, Args&&... args) {
|
||||
auto str = format(fmt, args...);
|
||||
fwrite(str.data(), 1, str.size(), stream);
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
#ifndef OPTIONAL_HPP
|
||||
#define OPTIONAL_HPP
|
||||
|
||||
#include <functional>
|
||||
#include <new>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/error.hpp"
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
struct nullopt_t {};
|
||||
|
||||
static constexpr nullopt_t nullopt;
|
||||
|
||||
template<
|
||||
typename T,
|
||||
typename std::enable_if<
|
||||
!std::is_same<typename std::decay<T>::type, void>::value && !std::is_rvalue_reference<T>::value,
|
||||
int
|
||||
>::type = 0
|
||||
>
|
||||
using well_behaved = typename std::conditional<
|
||||
std::is_reference<T>::value, std::reference_wrapper<typename std::remove_reference<T>::type>, T
|
||||
>::type;
|
||||
|
||||
template<
|
||||
typename T,
|
||||
typename std::enable_if<!std::is_same<typename std::decay<T>::type, void>::value, int>::type = 0
|
||||
>
|
||||
class optional {
|
||||
using value_type = well_behaved<T>;
|
||||
|
||||
union {
|
||||
char x{};
|
||||
value_type uvalue;
|
||||
};
|
||||
|
||||
bool holds_value = false;
|
||||
|
||||
public:
|
||||
optional() noexcept {}
|
||||
|
||||
optional(nullopt_t) noexcept {}
|
||||
|
||||
~optional() {
|
||||
reset();
|
||||
}
|
||||
|
||||
optional(const optional& other) : holds_value(other.holds_value) {
|
||||
if(holds_value) {
|
||||
new (static_cast<void*>(std::addressof(uvalue))) value_type(other.uvalue);
|
||||
}
|
||||
}
|
||||
|
||||
optional(optional&& other)
|
||||
noexcept(std::is_nothrow_move_constructible<value_type>::value)
|
||||
: holds_value(other.holds_value)
|
||||
{
|
||||
if(holds_value) {
|
||||
new (static_cast<void*>(std::addressof(uvalue))) value_type(std::move(other.uvalue));
|
||||
}
|
||||
}
|
||||
|
||||
optional& operator=(const optional& other) {
|
||||
optional copy(other);
|
||||
swap(copy);
|
||||
return *this;
|
||||
}
|
||||
|
||||
optional& operator=(optional&& other)
|
||||
noexcept(std::is_nothrow_move_assignable<value_type>::value && std::is_nothrow_move_constructible<value_type>::value)
|
||||
{
|
||||
if (this != &other) {
|
||||
reset();
|
||||
if (other.holds_value) {
|
||||
new (static_cast<void*>(std::addressof(uvalue))) value_type(std::move(other.uvalue));
|
||||
holds_value = true;
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<
|
||||
typename U = T,
|
||||
typename std::enable_if<!std::is_same<typename std::decay<U>::type, optional<T>>::value, int>::type = 0
|
||||
>
|
||||
optional(U&& value) : holds_value(true) {
|
||||
new (static_cast<void*>(std::addressof(uvalue))) value_type(std::forward<U>(value));
|
||||
}
|
||||
|
||||
template<
|
||||
typename U = T,
|
||||
typename std::enable_if<!std::is_same<typename std::decay<U>::type, optional<T>>::value, int>::type = 0
|
||||
>
|
||||
optional& operator=(U&& value) {
|
||||
optional o(std::forward<U>(value));
|
||||
swap(o);
|
||||
return *this;
|
||||
}
|
||||
|
||||
optional& operator=(nullopt_t) noexcept {
|
||||
reset();
|
||||
return *this;
|
||||
}
|
||||
|
||||
void swap(optional& other) noexcept {
|
||||
if(holds_value && other.holds_value) {
|
||||
std::swap(uvalue, other.uvalue);
|
||||
} else if(holds_value && !other.holds_value) {
|
||||
new (&other.uvalue) value_type(std::move(uvalue));
|
||||
uvalue.~value_type();
|
||||
} else if(!holds_value && other.holds_value) {
|
||||
new (static_cast<void*>(std::addressof(uvalue))) value_type(std::move(other.uvalue));
|
||||
other.uvalue.~value_type();
|
||||
}
|
||||
std::swap(holds_value, other.holds_value);
|
||||
}
|
||||
|
||||
bool has_value() const {
|
||||
return holds_value;
|
||||
}
|
||||
|
||||
explicit operator bool() const {
|
||||
return holds_value;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
if(holds_value) {
|
||||
uvalue.~value_type();
|
||||
}
|
||||
holds_value = false;
|
||||
}
|
||||
|
||||
NODISCARD T& unwrap() & {
|
||||
ASSERT(holds_value, "Optional does not contain a value");
|
||||
return uvalue;
|
||||
}
|
||||
|
||||
NODISCARD const T& unwrap() const & {
|
||||
ASSERT(holds_value, "Optional does not contain a value");
|
||||
return uvalue;
|
||||
}
|
||||
|
||||
NODISCARD T&& unwrap() && {
|
||||
ASSERT(holds_value, "Optional does not contain a value");
|
||||
return std::move(uvalue);
|
||||
}
|
||||
|
||||
NODISCARD const T&& unwrap() const && {
|
||||
ASSERT(holds_value, "Optional does not contain a value");
|
||||
return std::move(uvalue);
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
NODISCARD T value_or(U&& default_value) const & {
|
||||
return holds_value ? static_cast<T>(uvalue) : static_cast<T>(std::forward<U>(default_value));
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
NODISCARD T value_or(U&& default_value) && {
|
||||
return holds_value ? static_cast<T>(std::move(uvalue)) : static_cast<T>(std::forward<U>(default_value));
|
||||
}
|
||||
};
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
#include "utils/replace_all.hpp"
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
void replace_all(std::string& str, string_view substr, string_view replacement) {
|
||||
std::string::size_type pos = 0;
|
||||
while((pos = str.find(substr.data(), pos, substr.size())) != std::string::npos) {
|
||||
str.replace(pos, substr.size(), replacement.data(), replacement.size());
|
||||
pos += replacement.size();
|
||||
}
|
||||
}
|
||||
|
||||
void replace_all(std::string& str, const std::regex& re, string_view replacement) {
|
||||
std::smatch match;
|
||||
std::size_t i = 0;
|
||||
while(std::regex_search(str.cbegin() + i, str.cend(), match, re)) {
|
||||
str.replace(i + match.position(), match.length(), replacement.data(), replacement.size());
|
||||
i += match.position() + replacement.size();
|
||||
}
|
||||
}
|
||||
|
||||
void replace_all_dynamic(std::string& str, string_view substr, string_view replacement) {
|
||||
std::string::size_type pos = 0;
|
||||
while((pos = str.find(substr.data(), pos, substr.size())) != std::string::npos) {
|
||||
str.replace(pos, substr.size(), replacement.data(), replacement.size());
|
||||
// advancing by one rather than replacement.length() in case replacement leads to
|
||||
// another replacement opportunity, e.g. folding > > > to >> > then >>>
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
void replace_all_template(std::string& str, const std::pair<std::regex, string_view>& rule) {
|
||||
const auto& re = rule.first;
|
||||
const auto& replacement = rule.second;
|
||||
std::smatch match;
|
||||
std::size_t cursor = 0;
|
||||
while(std::regex_search(str.cbegin() + cursor, str.cend(), match, re)) {
|
||||
// find matching >
|
||||
const std::size_t match_begin = cursor + match.position();
|
||||
std::size_t end = match_begin + match.length();
|
||||
for(int c = 1; end < str.size() && c > 0; end++) {
|
||||
if(str[end] == '<') {
|
||||
c++;
|
||||
} else if(str[end] == '>') {
|
||||
c--;
|
||||
}
|
||||
}
|
||||
// make the replacement
|
||||
str.replace(match_begin, end - match_begin, replacement.data(), replacement.size());
|
||||
cursor = match_begin + replacement.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
#ifndef REPLACE_ALL
|
||||
#define REPLACE_ALL
|
||||
|
||||
#include <string>
|
||||
#include <regex>
|
||||
|
||||
#include "utils/string_view.hpp"
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
// replace all instances of substr with the replacement
|
||||
void replace_all(std::string& str, string_view substr, string_view replacement);
|
||||
|
||||
// replace all regex matches with the replacement
|
||||
void replace_all(std::string& str, const std::regex& re, string_view replacement);
|
||||
|
||||
// replace all instances of substr with the replacement, including new instances introduced by the replacement
|
||||
void replace_all_dynamic(std::string& str, string_view substr, string_view replacement);
|
||||
|
||||
// replace all matches of a regex including template parameters
|
||||
void replace_all_template(std::string& str, const std::pair<std::regex, string_view>& rule);
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
#ifndef RESULT_HPP
|
||||
#define RESULT_HPP
|
||||
|
||||
#include <new>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/optional.hpp"
|
||||
#include "options.hpp"
|
||||
#include "logging.hpp"
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
template<typename T, typename E, typename std::enable_if<!std::is_same<T, E>::value, int>::type = 0>
|
||||
class Result {
|
||||
using value_type = well_behaved<T>;
|
||||
union {
|
||||
value_type value_;
|
||||
E error_;
|
||||
};
|
||||
enum class member { value, error };
|
||||
member active;
|
||||
void destroy() {
|
||||
if(active == member::value) {
|
||||
value_.~value_type();
|
||||
} else {
|
||||
error_.~E();
|
||||
}
|
||||
}
|
||||
public:
|
||||
Result(value_type&& value) : value_(std::move(value)), active(member::value) {}
|
||||
Result(E&& error) : error_(std::move(error)), active(member::error) {
|
||||
log::debug("Error result constructed: {}", unwrap_error().what());
|
||||
}
|
||||
Result(const value_type& value) : value_(value_type(value)), active(member::value) {}
|
||||
Result(const E& error) : error_(E(error)), active(member::error) {
|
||||
log::debug("Error result constructed: {}", unwrap_error().what());
|
||||
}
|
||||
template<
|
||||
typename U = T,
|
||||
typename std::enable_if<
|
||||
!std::is_same<typename std::decay<U>::type, Result<T, E>>::value &&
|
||||
std::is_constructible<value_type, U>::value &&
|
||||
!std::is_constructible<E, U>::value,
|
||||
int
|
||||
>::type = 0
|
||||
>
|
||||
Result(U&& u) : Result(value_type(std::forward<U>(u))) {}
|
||||
Result(Result&& other) : active(other.active) {
|
||||
if(other.active == member::value) {
|
||||
new (&value_) value_type(std::move(other.value_));
|
||||
} else {
|
||||
new (&error_) E(std::move(other.error_));
|
||||
}
|
||||
}
|
||||
~Result() {
|
||||
destroy();
|
||||
}
|
||||
Result& operator=(const Result& other) {
|
||||
if (this != &other) {
|
||||
destroy();
|
||||
if(other.active == member::value) {
|
||||
new (&value_) value_type(other.value_);
|
||||
} else {
|
||||
new (&error_) E(other.error_);
|
||||
}
|
||||
active = other.active;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
Result& operator=(Result&& other)
|
||||
noexcept(
|
||||
std::is_nothrow_move_constructible<value_type>::value && std::is_nothrow_move_constructible<E>::value
|
||||
)
|
||||
{
|
||||
if (this != &other) {
|
||||
destroy();
|
||||
if(other.active == member::value) {
|
||||
new (&value_) value_type(std::move(other.value_));
|
||||
} else {
|
||||
new (&error_) E(std::move(other.error_));
|
||||
}
|
||||
active = other.active;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool has_value() const {
|
||||
return active == member::value;
|
||||
}
|
||||
|
||||
bool is_error() const {
|
||||
return active == member::error;
|
||||
}
|
||||
|
||||
explicit operator bool() const {
|
||||
return has_value();
|
||||
}
|
||||
|
||||
NODISCARD optional<T> value() const & {
|
||||
return has_value() ? optional<T>(value_) : nullopt;
|
||||
}
|
||||
|
||||
NODISCARD optional<E> error() const & {
|
||||
return is_error() ? optional<E>(error_) : nullopt;
|
||||
}
|
||||
|
||||
NODISCARD optional<T> value() && {
|
||||
return has_value() ? optional<T>(std::move(value_)) : nullopt;
|
||||
}
|
||||
|
||||
NODISCARD optional<E> error() && {
|
||||
return is_error() ? optional<E>(std::move(error_)) : nullopt;
|
||||
}
|
||||
|
||||
NODISCARD T& unwrap_value() & {
|
||||
ASSERT(has_value(), "Result does not contain a value");
|
||||
return value_;
|
||||
}
|
||||
|
||||
NODISCARD const T& unwrap_value() const & {
|
||||
ASSERT(has_value(), "Result does not contain a value");
|
||||
return value_;
|
||||
}
|
||||
|
||||
NODISCARD T unwrap_value() && {
|
||||
ASSERT(has_value(), "Result does not contain a value");
|
||||
return std::move(value_);
|
||||
}
|
||||
|
||||
NODISCARD E& unwrap_error() & {
|
||||
ASSERT(is_error(), "Result does not contain an error");
|
||||
return error_;
|
||||
}
|
||||
|
||||
NODISCARD const E& unwrap_error() const & {
|
||||
ASSERT(is_error(), "Result does not contain an error");
|
||||
return error_;
|
||||
}
|
||||
|
||||
NODISCARD E unwrap_error() && {
|
||||
ASSERT(is_error(), "Result does not contain an error");
|
||||
return std::move(error_);
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
NODISCARD T value_or(U&& default_value) const & {
|
||||
return has_value() ? static_cast<T>(value_) : static_cast<T>(std::forward<U>(default_value));
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
NODISCARD T value_or(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 {
|
||||
if(is_error()) {
|
||||
log::error(unwrap_error().what());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
#ifndef SPAN_HPP
|
||||
#define SPAN_HPP
|
||||
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
// basic span implementation
|
||||
// I haven't implemented most members because I don't need them, more will be added as needed
|
||||
|
||||
template<typename T>
|
||||
class span {
|
||||
T* ptr;
|
||||
std::size_t count;
|
||||
|
||||
public:
|
||||
using element_type = T;
|
||||
using value_type = typename std::remove_cv<T>::type;
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = T*;
|
||||
using const_pointer = const T*;
|
||||
using reference = T&;
|
||||
using const_reference = const T&;
|
||||
using iterator = T*;
|
||||
using const_iterator = const T*;
|
||||
using reverse_iterator = std::reverse_iterator<iterator>;
|
||||
using const_reverse_iterator = std::reverse_iterator<reverse_iterator>;
|
||||
using i_am_span = void;
|
||||
|
||||
span() : ptr(nullptr), count(0) {}
|
||||
span(T* ptr, std::size_t count) : ptr(ptr), count(count) {}
|
||||
template<typename It>
|
||||
span(It begin, It end) : ptr(std::addressof(*begin)), count(end - begin) {}
|
||||
|
||||
T* data() const noexcept {
|
||||
return ptr;
|
||||
}
|
||||
std::size_t size() const noexcept {
|
||||
return count;
|
||||
}
|
||||
bool empty() const noexcept {
|
||||
return count == 0;
|
||||
}
|
||||
|
||||
iterator begin() noexcept {
|
||||
return ptr;
|
||||
}
|
||||
iterator end() noexcept {
|
||||
return ptr + count;
|
||||
}
|
||||
const_iterator begin() const noexcept {
|
||||
return ptr;
|
||||
}
|
||||
const_iterator end() const noexcept {
|
||||
return ptr + count;
|
||||
}
|
||||
};
|
||||
|
||||
using bspan = span<char>;
|
||||
using cbspan = span<const char>;
|
||||
|
||||
template<typename T, typename = int>
|
||||
struct is_span : std::false_type {};
|
||||
|
||||
template<typename T>
|
||||
struct is_span<T, void_t<typename T::i_am_span>> : std::true_type {};
|
||||
|
||||
template<typename It>
|
||||
auto make_span(It begin, It end) -> span<typename std::remove_reference<decltype(*begin)>::type> {
|
||||
return {begin, end};
|
||||
}
|
||||
|
||||
template<typename It>
|
||||
auto make_span(It begin, std::size_t count) -> span<typename std::remove_reference<decltype(*begin)>::type> {
|
||||
return {begin, count};
|
||||
}
|
||||
|
||||
template<
|
||||
typename T,
|
||||
typename std::enable_if<
|
||||
std::is_standard_layout<T>::value && is_trivially_copyable<T>::value && !is_span<T>::value,
|
||||
int
|
||||
>::type = 0
|
||||
>
|
||||
span<char> make_bspan(T& object) {
|
||||
return span<char>(reinterpret_cast<char*>(std::addressof(object)), sizeof(object));
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
#include "utils/string_view.hpp"
|
||||
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/microfmt.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
constexpr std::size_t string_view::npos;
|
||||
|
||||
char string_view::operator[](size_t i) const {
|
||||
ASSERT(i < size());
|
||||
return ptr[i];
|
||||
}
|
||||
char string_view::at(size_t i) const {
|
||||
if(i >= size()) {
|
||||
throw std::runtime_error(microfmt::format("Out of bounds access {} >= {}", i, size()));
|
||||
}
|
||||
return ptr[i];
|
||||
}
|
||||
|
||||
bool string_view::starts_with(string_view str) const {
|
||||
return substr(0, str.size()) == str;
|
||||
}
|
||||
|
||||
bool string_view::ends_with(string_view str) const {
|
||||
if(size() < str.size()) {
|
||||
return false;
|
||||
}
|
||||
return substr(size() - str.size(), str.size()) == str;
|
||||
}
|
||||
|
||||
std::size_t string_view::find_last_of(string_view chars) const {
|
||||
if(empty() || chars.empty()) {
|
||||
return npos;
|
||||
}
|
||||
std::size_t pos = size();
|
||||
while(pos-- > 0) {
|
||||
if(std::find(chars.begin(), chars.end(), ptr[pos]) != chars.end()) {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
return npos;
|
||||
}
|
||||
|
||||
bool operator==(string_view a, string_view b) {
|
||||
return a.size() == b.size() && std::memcmp(a.data(), b.data(), a.size()) == 0;
|
||||
}
|
||||
|
||||
constexpr std::size_t cstring_view::npos;
|
||||
|
||||
cstring_view cstring_view::substr(std::size_t pos) const {
|
||||
ASSERT(pos <= count);
|
||||
return {ptr + pos, count - pos};
|
||||
}
|
||||
|
||||
void cstring_view::check_null() const {
|
||||
ASSERT(ptr[count] == 0);
|
||||
}
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
|
@ -0,0 +1,179 @@
|
|||
#ifndef STRING_VIEW_HPP
|
||||
#define STRING_VIEW_HPP
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
|
||||
#include <cpptrace/utils.hpp>
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
// Simple string view implementations
|
||||
// I haven't implemented all members because I don't need most of them currently, more may be added as needed
|
||||
// members exported for tests
|
||||
|
||||
class string_view {
|
||||
const char* ptr;
|
||||
std::size_t count;
|
||||
|
||||
public:
|
||||
using traits_type = std::char_traits<char>;
|
||||
using value_type = char;
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = char*;
|
||||
using const_pointer = const char*;
|
||||
using reference = char&;
|
||||
using const_reference = const char&;
|
||||
using iterator = char*;
|
||||
using const_iterator = const char*;
|
||||
using reverse_iterator = std::reverse_iterator<iterator>;
|
||||
using const_reverse_iterator = std::reverse_iterator<reverse_iterator>;
|
||||
CPPTRACE_EXPORT static constexpr std::size_t npos = std::string::npos;
|
||||
|
||||
string_view() : ptr(nullptr), count(0) {}
|
||||
string_view(const char* str) : ptr(str), count(std::strlen(str)) {}
|
||||
string_view(const std::string& str) : ptr(str.c_str()), count(str.size()) {}
|
||||
string_view(const char* ptr, std::size_t count) : ptr(ptr), count(count) {}
|
||||
string_view(const char* begin, const char* end) : ptr(begin), count(end - begin) {}
|
||||
|
||||
explicit operator std::string() {
|
||||
return std::string(ptr, ptr + count);
|
||||
}
|
||||
|
||||
const char* data() const noexcept {
|
||||
return ptr;
|
||||
}
|
||||
std::size_t size() const noexcept {
|
||||
return count;
|
||||
}
|
||||
bool empty() const noexcept {
|
||||
return count == 0;
|
||||
}
|
||||
|
||||
CPPTRACE_EXPORT char operator[](size_t i) const;
|
||||
CPPTRACE_EXPORT char at(size_t i) const;
|
||||
|
||||
char front() {
|
||||
return operator[](0);
|
||||
}
|
||||
char back() {
|
||||
return operator[](size() - 1);
|
||||
}
|
||||
|
||||
string_view substr(size_t pos = 0, size_t n = npos) const {
|
||||
return {ptr + pos, (std::min)(size() - pos, n)};
|
||||
}
|
||||
|
||||
void advance(size_t n) {
|
||||
*this = substr(n);
|
||||
}
|
||||
|
||||
CPPTRACE_EXPORT bool starts_with(string_view chars) const;
|
||||
CPPTRACE_EXPORT bool ends_with(string_view chars) const;
|
||||
CPPTRACE_EXPORT std::size_t find_last_of(string_view chars) const;
|
||||
|
||||
const_iterator begin() const noexcept {
|
||||
return ptr;
|
||||
}
|
||||
const_iterator end() const noexcept {
|
||||
return ptr + count;
|
||||
}
|
||||
};
|
||||
|
||||
CPPTRACE_EXPORT bool operator==(string_view, string_view);
|
||||
inline bool operator!=(string_view a, string_view b) {
|
||||
return !(a == b);
|
||||
}
|
||||
inline void operator+=(std::string& str, string_view sv) {
|
||||
str.append(sv.data(), sv.size());
|
||||
}
|
||||
|
||||
class cstring_view {
|
||||
const char* ptr;
|
||||
std::size_t count;
|
||||
|
||||
public:
|
||||
using traits_type = std::char_traits<char>;
|
||||
using value_type = char;
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = char*;
|
||||
using const_pointer = const char*;
|
||||
using reference = char&;
|
||||
using const_reference = const char&;
|
||||
using iterator = char*;
|
||||
using const_iterator = const char*;
|
||||
using reverse_iterator = std::reverse_iterator<iterator>;
|
||||
using const_reverse_iterator = std::reverse_iterator<reverse_iterator>;
|
||||
CPPTRACE_EXPORT static constexpr std::size_t npos = string_view::npos;
|
||||
|
||||
cstring_view() : ptr(nullptr), count(0) {}
|
||||
cstring_view(const char* str) : ptr(str), count(std::strlen(str)) {}
|
||||
cstring_view(const std::string& str) : ptr(str.c_str()), count(str.size()) {}
|
||||
cstring_view(const char* ptr, std::size_t count) : ptr(ptr), count(count) {
|
||||
check_null();
|
||||
}
|
||||
|
||||
explicit operator std::string() {
|
||||
return std::string(ptr, ptr + count);
|
||||
}
|
||||
|
||||
operator string_view() const noexcept {
|
||||
return string_view(ptr, count);
|
||||
}
|
||||
|
||||
const char* data() const noexcept {
|
||||
return ptr;
|
||||
}
|
||||
const char* c_str() const noexcept {
|
||||
return ptr;
|
||||
}
|
||||
std::size_t size() const noexcept {
|
||||
return count;
|
||||
}
|
||||
bool empty() const noexcept {
|
||||
return count == 0;
|
||||
}
|
||||
|
||||
char operator[](size_t i) const {
|
||||
return operator string_view().operator[](i);
|
||||
}
|
||||
char at(size_t i) const {
|
||||
return operator string_view().at(i);
|
||||
}
|
||||
|
||||
char front() {
|
||||
return operator string_view().front();
|
||||
}
|
||||
char back() {
|
||||
return operator string_view().back();
|
||||
}
|
||||
|
||||
bool starts_with(string_view chars) const {
|
||||
return operator string_view().starts_with(chars);
|
||||
}
|
||||
bool ends_with(string_view chars) const {
|
||||
return operator string_view().ends_with(chars);
|
||||
}
|
||||
std::size_t find_last_of(string_view chars) const {
|
||||
return operator string_view().find_last_of(chars);
|
||||
}
|
||||
|
||||
CPPTRACE_EXPORT cstring_view substr(std::size_t pos) const;
|
||||
|
||||
const_iterator begin() const noexcept {
|
||||
return ptr;
|
||||
}
|
||||
const_iterator end() const noexcept {
|
||||
return ptr + count;
|
||||
}
|
||||
private:
|
||||
CPPTRACE_EXPORT void check_null() const;
|
||||
};
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
#include "utils/utils.hpp"
|
||||
#include "utils/string_view.hpp"
|
||||
|
||||
#if IS_WINDOWS
|
||||
#include <io.h>
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
CPPTRACE_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
bool isatty(int fd) {
|
||||
#if IS_WINDOWS
|
||||
return _isatty(fd);
|
||||
#else
|
||||
return ::isatty(fd);
|
||||
#endif
|
||||
}
|
||||
|
||||
int fileno(std::FILE* stream) {
|
||||
#if IS_WINDOWS
|
||||
return _fileno(stream);
|
||||
#else
|
||||
return ::fileno(stream);
|
||||
#endif
|
||||
}
|
||||
|
||||
void enable_virtual_terminal_processing_if_needed() noexcept {
|
||||
// enable colors / ansi processing if necessary
|
||||
#if IS_WINDOWS
|
||||
// https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example-of-enabling-virtual-terminal-processing
|
||||
#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||
constexpr DWORD ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4;
|
||||
#endif
|
||||
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
DWORD dwMode = 0;
|
||||
if(hOut == INVALID_HANDLE_VALUE) return;
|
||||
if(!GetConsoleMode(hOut, &dwMode)) return;
|
||||
if(dwMode != (dwMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING))
|
||||
if(!SetConsoleMode(hOut, dwMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) return;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool directory_exists(cstring_view path) {
|
||||
#if IS_WINDOWS
|
||||
DWORD dwAttrib = GetFileAttributesA(path.c_str());
|
||||
return dwAttrib != INVALID_FILE_ATTRIBUTES && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY);
|
||||
#else
|
||||
struct stat sb;
|
||||
return stat(path.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode);
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
CPPTRACE_END_NAMESPACE
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue