Compare commits

...

235 Commits
v0.6.2 ... main

Author SHA1 Message Date
Tsche
fac4d08fd0
fix #221 (#223) 2025-02-28 10:55:44 -06:00
Patrick Quist
c0354799c7
add arm test with gh arm runners (#222) 2025-02-25 13:23:09 -06:00
Jeremy Rifkin
c37b5ed736
Bump to v0.8.2 2025-02-23 14:03:25 -06:00
Jeremy Rifkin
a32e22aa44
Don't print internal errors when image base resolution fails, this can happen on mac since platform dylibs don't actually exist on disk. Closes #217. 2025-02-22 13:20:03 -06:00
Jeremy Rifkin
6cec10601e
Bump zstd, #219 2025-02-22 12:18:01 -06:00
Jeremy Rifkin
c9dc51aa61
Move FAQ and add entry on standard library symbol linker errors on macos, related to #216 2025-02-22 00:06:27 -06:00
Jeremy Rifkin
e6d55b5e7d
Add missing iostream include in a README example, closes #218 2025-02-22 00:01:02 -06:00
Jeremy Rifkin
1940dc607a
Bump to v0.8.1 2025-02-20 22:46:25 -06:00
Jeremy Rifkin
477aecec01
Add guard on forwarding constructor 2025-02-20 22:22:46 -06:00
Jeremy Rifkin
03b292c20b
mach-o fixes 2025-02-20 22:18:42 -06:00
Jeremy Rifkin
5bfcf280a5
Fix scope_guard forwarding 2025-02-20 22:04:07 -06:00
Jeremy Rifkin
9b02fc6f74
Replaced some un-ergonomic unique_ptr use with make_unique, switched some unique_ptr<T[]> use to just std::vector<T>, and added some RAII protection to dbghelp symbol resolution TI_FINDCHILDREN_PARAMS management 2025-02-20 22:02:01 -06:00
Jeremy Rifkin
3a4da8ccf0
Fix for an auto return type 2025-02-20 21:47:36 -06:00
Jeremy Rifkin
9a2ae3c96f
Add some NODISCARD attributes and uncomment scope_exit utility code 2025-02-20 21:43:25 -06:00
Jeremy Rifkin
6877782d96
Add cpptrace::can_get_safe_object_frame and add ctrace prefix for can_signal_safe_unwind 2025-02-20 21:36:19 -06:00
Jeremy Rifkin
98ea78445c
Add CI workflow for older msvc 2025-02-20 21:13:54 -06:00
Jeremy Rifkin
3aa080d536
Remove a SFINAE check that keeps surfacing msvc bugs, fixes #215 2025-02-20 21:07:04 -06:00
Jeremy Rifkin
34ea9572b8
Bump to v0.8.0 2025-02-19 23:31:36 -06:00
Jeremy Rifkin
525ce871d5
Add missing include 2025-02-19 22:55:31 -06:00
Jeremy Rifkin
daed105fef
Cache elf and mach-o file objects 2025-02-19 22:49:55 -06:00
Jeremy Rifkin
69875cde19
Rework some move constructors and assignment operators to not simply swap 2025-02-19 21:33:03 -06:00
Jeremy Rifkin
b59a08634d
Small whitespace fix 2025-02-19 21:21:21 -06:00
Jeremy Rifkin
2bb29f71bc
Use a slightly clearer name
Co-authored-by: 9291Sam <sam.skinr@gmail.com>
2025-02-19 21:04:58 -06:00
Jeremy Rifkin
d6fff9022e
Fallback to the process handle in case DuplicateHandle fails 2025-02-19 20:52:50 -06:00
Jeremy Rifkin
e86f4eba8f
Fix some msvc warnings 2025-02-19 20:41:19 -06:00
Jeremy Rifkin
ca8416ea1e
Abstract out an de-duplicate syminit and symcleanup logic for dbghelp 2025-02-19 20:40:58 -06:00
Jeremy Rifkin
6d62d01496
Improve locking surrounding dbghelp and lock in the dbghelp demangler 2025-02-18 23:34:08 -06:00
Jeremy Rifkin
74b5ac6e07
Fixes for msvc 2025-02-18 23:11:34 -06:00
Jeremy Rifkin
dff5b8f18e
Revert "Don't CI on pr separately"
This reverts commit 350382bb93.
2025-02-18 21:04:48 -06:00
Jeremy Rifkin
c375a72efc
Revert "Update CI to not run on dev pr"
This reverts commit d8a0097c43.
2025-02-18 21:04:43 -06:00
Jeremy Rifkin
d8a0097c43
Update CI to not run on dev pr 2025-02-18 21:00:17 -06:00
Jeremy Rifkin
350382bb93
Don't CI on pr separately 2025-02-18 20:57:40 -06:00
Jeremy Rifkin
f7675eac91
Refactor some dwarf utility abstractions into their own header 2025-02-18 20:54:43 -06:00
Jeremy Rifkin
832c3014b0
Don't create formatters on the fly in print_terminate_trace() 2025-02-18 20:15:12 -06:00
Jeremy Rifkin
b0d12daf22
Remove extern declaration for the absorb_trace_exceptions flag 2025-02-18 20:04:42 -06:00
Jeremy Rifkin
cebca81aa9
Add a newline for slightly improved visual separation 2025-02-18 20:02:15 -06:00
Jeremy Rifkin
c2b3b7e0a1
Small consistency improvement for FetchContent_Declare use for tool building 2025-02-18 19:58:16 -06:00
Jeremy Rifkin
27107556f8
Remove unused function 2025-02-18 19:54:55 -06:00
Jeremy Rifkin
26ef617c25
Use a raii_wrapper for the char array returned by abi::__cxa_demangle 2025-02-18 19:48:53 -06:00
Jeremy Rifkin
9c0d0db884
Address an msvc warning/error 2025-02-18 00:14:42 -06:00
Jeremy Rifkin
5e4ea9a88f
Add lru cache unit tests 2025-02-18 00:01:30 -06:00
Jeremy Rifkin
99814905be
Add --line-table-cache-size option to the resolver tool 2025-02-17 23:42:20 -06:00
Jeremy Rifkin
6180399996
Fix some typos and do some minor rewording 2025-02-17 23:22:27 -06:00
Jeremy Rifkin
e7f8521936
Update table of contents 2025-02-17 23:16:17 -06:00
Jeremy Rifkin
e77f16031b
Add --disable-aranges option to the resolver tool 2025-02-17 23:14:36 -06:00
Jeremy Rifkin
261ca9d554
Add libdwarf tuning section with information about libdwarf-specific settings 2025-02-17 23:13:46 -06:00
Jeremy Rifkin
5073cc218a
Update documentation for cpptrace::nullable 2025-02-17 23:01:00 -06:00
Jeremy Rifkin
34be9f2f19
Add a check for dwarf_get_version_of_die's return code 2025-02-17 23:00:12 -06:00
Jeremy Rifkin
6d41ea0135
Fix UB due to forming a reference to a packed struct member 2025-02-17 22:59:48 -06:00
Jeremy Rifkin
27924487dc
Document the formatter API 2025-02-17 22:38:04 -06:00
Jeremy Rifkin
728cefab55
Add setting to disable the use of dwarf aranges 2025-02-17 22:27:44 -06:00
Jeremy Rifkin
aed47df73e
Slightly improve memory usage by packing some structs used in the die cache 2025-02-17 22:18:15 -06:00
Jeremy Rifkin
e22300b36d
Refactor to not use if constexpr 2025-02-17 21:41:16 -06:00
Jeremy Rifkin
a4faef7f1e
Refactor cu_cache to use die_cache abstraction and reduce cu die cloning 2025-02-17 21:13:08 -06:00
Jeremy Rifkin
83527947a2
Add an abstraction for die caches / lookup and reduce die cloning 2025-02-17 20:40:46 -06:00
Jeremy Rifkin
1d79dbcf42
Add a configurable cache size for the line tables cache, related to #193 2025-02-17 19:10:46 -06:00
Jeremy Rifkin
8c7b1dc6aa
Silence a gcc warning about unitialized values 2025-02-17 18:22:58 -06:00
Jeremy Rifkin
111f8e6aec
Simplify to two move constructors, just use the move assignment operator 2025-02-17 13:51:26 -06:00
Jeremy Rifkin
ead3f128aa
Make line_table_info serve as a raii utility for dwarf line table stuff 2025-02-17 13:50:06 -06:00
Jeremy Rifkin
8963639639
Fix an issue with destruction order of dwarf stuff and clean up cleanup logic 2025-02-17 13:23:58 -06:00
Jeremy Rifkin
b762ee7ec6
Remove some commented code 2025-02-17 13:05:57 -06:00
Jeremy Rifkin
87f2fd4c43
Resolver tool improvements 2025-02-17 12:46:25 -06:00
Jeremy Rifkin
87b14c87f8
Remove a comment 2025-02-17 01:04:45 -06:00
Jeremy Rifkin
62548497a8
Create a helper wrapper / abstraction for managing srcfiles lists 2025-02-17 01:02:28 -06:00
Jeremy Rifkin
0f990f05a1
Use raii_wrap for dwarf_dealloc_error in handle_dwarf_error 2025-02-17 01:01:42 -06:00
Jeremy Rifkin
4ab78f7a69
Fix srcfiles deallocation 2025-02-16 18:34:49 -06:00
Jeremy Rifkin
bc0164224e
Try using dwarf_set_de_alloc_flag(0) to improve performance and memory usage 2025-02-16 17:53:40 -06:00
Jeremy Rifkin
d18d6ee77d
Fix cleanup of dwarf aranges 2025-02-16 17:53:10 -06:00
Jeremy Rifkin
8f2193f35a
Fix handling of strtab in a code path in the elf code 2025-02-13 23:47:16 -06:00
Jeremy Rifkin
457bc4b8a1
Check external symbols start with _Z before demangling 2025-02-13 23:39:05 -06:00
Jeremy Rifkin
ce97e0004d
Add basic option for shortening paths in the formatter 2025-02-13 23:22:12 -06:00
Jeremy Rifkin
000168b93c
Add some extra functionality to the resolver tool for some testing 2025-02-02 23:50:16 -06:00
Jeremy Rifkin
156ede9aab
Add simple resolver tool 2025-02-02 22:03:03 -06:00
Jeremy Rifkin
2b7d47d627
Add elf symtab dumping tool 2025-02-02 21:37:22 -06:00
Jeremy Rifkin
b724d1328c
Add very basic dwarfdump tool 2025-02-02 19:28:01 -06:00
Jeremy Rifkin
8e7b4a953f
Roll back the cxxabi demangle _Z check 2025-02-02 18:32:15 -06:00
Jeremy Rifkin
b2180ae797
Rename builder methods 2025-02-02 16:58:22 -06:00
Jeremy Rifkin
f0a9e12e88
Format frames directly to the stream 2025-02-02 16:45:49 -06:00
Jeremy Rifkin
eb83ee2a1c
Add an option to show / hide filtered frames vs printing a placeholder 2025-02-02 16:20:47 -06:00
Jeremy Rifkin
d1ce9c8896
Oops, quick fixes 2025-02-02 14:45:49 -06:00
Jeremy Rifkin
00c7feb2e0
Remove some unused includes 2025-02-01 20:03:50 -06:00
Jeremy Rifkin
32f0d5d273
Constexpr fix 2025-02-01 20:00:06 -06:00
Jeremy Rifkin
85be7c32a4
Some improvements to the nullable interface 2025-02-01 19:10:19 -06:00
Jeremy Rifkin
248ad447b1
Work around bazel crap 2025-02-01 18:54:44 -06:00
Jeremy Rifkin
eb9ebc31a7
Add tests for nullable 2025-02-01 17:51:17 -06:00
Jeremy Rifkin
e2b1252438
A new formatting setup (#208)
Resolves #164
2025-02-01 17:31:49 -06:00
Jeremy Rifkin
3557d7b885
Fix handling of DW_DLV_ERROR 2025-02-01 11:51:08 -06:00
Jeremy Rifkin
9077430b6a
Search both the elf symbol table and dynamic symbol table 2025-01-28 23:40:08 -06:00
Jeremy Rifkin
06c9c14995
Return an optional from lookup_symbol 2025-01-28 23:26:28 -06:00
Jeremy Rifkin
7f6945c7d9
Better handle symtab loading / optionality 2025-01-28 23:14:37 -06:00
Jeremy Rifkin
73ee7aa3a1
Don't try to load string table if strtab_link is SHN_UNDEF or the symbol table is empty 2025-01-28 22:21:43 -06:00
Jeremy Rifkin
18046688f6
Move cmake integration and performance check into ci workflow 2025-01-28 21:41:27 -06:00
Jeremy Rifkin
1429cfc429
Hopefully final ci fixes 2025-01-28 12:53:40 -06:00
Jeremy Rifkin
82ef2287dd
Fix CI 2025-01-28 11:32:58 -06:00
Jeremy Rifkin
25eaa832d1
Remove a superfluous inline 2025-01-28 00:50:42 -06:00
Jeremy Rifkin
f16c36e0b6
Do a unit test without debug symbols 2025-01-28 00:48:38 -06:00
Jeremy Rifkin
b498dead92
Update readme badges 2025-01-28 00:42:50 -06:00
Jeremy Rifkin
6971f6a5ca
Remove mostly redundant build workflow and turn the test workflow into a general ci workflow 2025-01-28 00:31:01 -06:00
Jeremy Rifkin
247474389f
Allow cpptrace::detail::demangle to demangle strings that have non-identifier characters at the end 2025-01-27 23:57:05 -06:00
Jeremy Rifkin
dc0c683804
Remove elf::byteswap_if_needed's little endian parameter 2025-01-27 23:49:27 -06:00
Jeremy Rifkin
c5c785db89
Check that names passed to cpptrace::detail::demangle look like mangled names before passing to abi::__cxa_demangle 2025-01-27 23:48:13 -06:00
Jeremy Rifkin
a28cc3a3a0
Split up unittest jobs further 2025-01-27 23:36:38 -06:00
Jeremy Rifkin
08306c12a5
Implement symbol table lookup for mach-o and fix mingw build 2025-01-27 23:12:30 -06:00
Jeremy Rifkin
68c9d33e94
Use tried_to_load_header 2025-01-26 23:22:03 -06:00
Jeremy Rifkin
8edb419342
Implement elf symbol table parsing and fall back to the elf symbol table during symbol resolution 2025-01-26 23:07:33 -06:00
Jeremy Rifkin
dc118dcb6d
Move monostate 2025-01-26 23:03:42 -06:00
Jeremy Rifkin
7a1eb4d5bf
Add a Result<T> converting constructor 2025-01-26 20:29:13 -06:00
Jeremy Rifkin
6a9d2c5bbd
Fixes for C++11 / older gcc 2025-01-26 19:31:20 -06:00
Jeremy Rifkin
c9826616b5
Sendit support optional<T&> and Result<T&, E> 2025-01-26 19:18:47 -06:00
Jeremy Rifkin
293f4d1593
Export should_absorb_trace_exceptions to fix tests 2025-01-26 18:15:21 -06:00
Jeremy Rifkin
01ea4e9ea7
Update BUILD.bazel for test 2025-01-26 17:41:23 -06:00
Jeremy Rifkin
06732add17
Try to fix tests on CI 2025-01-26 17:34:02 -06:00
Jeremy Rifkin
78086d0267
Some more unit tests for test cases 2025-01-26 17:29:38 -06:00
Jeremy Rifkin
906784284f
Add unit tests for optional and result helper types 2025-01-26 16:02:34 -06:00
Jeremy Rifkin
b705afba69
Templated lambdas aren't a thing in C++11 :( 2025-01-26 14:52:31 -06:00
Jeremy Rifkin
76a21d266a
Refactor out optional and result 2025-01-26 13:30:54 -06:00
firesgc
1bcfcff021
Incompatible with JNI on Windows: use unique handle for SymInitialize #204 (#206)
WinDbg: Duplicate the process handle before using it in SymInitialize,
because other libraries may have already called SymInitialize for the
process
(https://learn.microsoft.com/en-us/windows/win32/debug/initializing-the-symbol-handler)


Fixes https://github.com/jeremy-rifkin/cpptrace/issues/204

---------

Co-authored-by: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com>
2025-01-25 22:25:54 -06:00
Jeremy Rifkin
485d9a6f21
A little elf refactoring, separate out header loading 2025-01-24 20:16:46 -06:00
Jeremy Rifkin
d063784abc
Refactor elf_get_module_image_base, setup an elf class in preparation for more elf parsing 2025-01-23 00:12:31 -06:00
Jeremy Rifkin
6689d14c20
Bump to v0.7.5 2025-01-04 23:45:49 -06:00
Jeremy Rifkin
f42ed3500d
Use underscores in component names instead of hyphens due to cpack, resolves #203 2025-01-04 23:16:37 -06:00
Jeremy Rifkin
f35ef65ed6
Merge branch 'dev' 2025-01-04 22:57:40 -06:00
Jeremy Rifkin
0c40839009
Specify __cdecl for the terminate handler on MSVC, fixes #197 2024-12-30 23:07:15 -06:00
Jeremy Rifkin
d5a2043fd3
Set C++ standard for old compiler versions in check_support, fixes #200 2024-12-30 22:41:54 -06:00
Dominic Koepke
b127cfb176
Add missing typeinfo include (#202)
Hello,
as you know, I'm working on an integration of ``cpptrace`` into my own
mocking framework. I've an exhaustive build-workflow with various
compiler-configurations. One of these configs fails compiling due to
this error:
```src/platform/exception_type.hpp:19:39: error: member access into incomplete type 'const std::type_info'```

This happens to be the case, when ``clang-16`` with c++23 and `-stdlib=libc++` is used.
see: https://github.com/DNKpp/mimicpp/actions/runs/12549146490/job/34989686008 and
https://github.com/DNKpp/mimicpp/actions/runs/12549146490/job/34989688648

This fixes the issue.
2024-12-30 19:12:39 -06:00
Sergei Zimmerman
b23bc42df4
test(stacktrace): make CPPTRACE_FORCE_INLINE static (#196)
This fixes compilation error:

```
> error: inlining failed in call to 'always_inline' 'int stacktrace_inline_resolution_2(std::vector<int>&)': function body can be overwritten at link time
```
2024-12-23 12:21:30 -07:00
Jeremy Rifkin
5b3f2fb0b1
Bump to v0.7.4 2024-12-21 16:13:10 -06:00
Jeremy Rifkin
43b0ed1b21
Add version macros header 2024-12-21 15:41:13 -06:00
Jeremy Rifkin
27bbe75bd3
Re-enable some dwarf 5 test cases now that upstream issues are resolved (#194)
Re-enables test cases that were disabled due to
https://github.com/davea42/libdwarf-code/issues/259 and
https://github.com/davea42/libdwarf-code/issues/267
2024-12-05 19:11:42 -06:00
Jeremy Rifkin
fe97f8f0f0
Split CI matrix run over several runners 2024-12-04 23:44:37 -06:00
Jeremy Rifkin
cc497fb62b
Revert "Re-enable some dwarf 5 unit tests now that things are fixed upstream"
This reverts commit acddc383a4.
2024-12-04 23:18:58 -06:00
Jeremy Rifkin
acddc383a4
Re-enable some dwarf 5 unit tests now that things are fixed upstream 2024-12-04 22:56:08 -06:00
Jeremy Rifkin
61d06d56d6
Oops 2024-12-03 23:56:10 -06:00
Jeremy Rifkin
4227fc4abe
Better microfmt implementation 2024-12-03 23:53:28 -06:00
Jeremy Rifkin
ddec65195d
Bump to libdwarf 0.11.1 2024-12-03 23:11:25 -06:00
Jeremy Rifkin
379a0fa594
Add a test main that disables cpptrace exception absorption 2024-11-20 23:29:29 -06:00
Jeremy Rifkin
4354eb21ea
Bump to v0.7.3 2024-11-15 17:42:37 -06:00
Jeremy Rifkin
6945c2167e
Change a return back to an _exit(1). It's not different functionally but I decided it's more clear. 2024-11-15 17:21:22 -06:00
Jeremy Rifkin
d43318aa92
Add cmake export set, fixes #189 2024-11-15 17:14:08 -06:00
Jeremy Rifkin
e10dbf431e
Add a color overload for stacktrace_frame::to_string, fixes #190 2024-11-14 23:54:52 -06:00
Jeremy Rifkin
8866d70a32
Fix bug in signal_demo and add error message on fork failure to signal tracing guide 2024-11-14 22:08:09 -06:00
Jeremy Rifkin
5a9a4f314d
Updating wording and docs arround the safe trace API 2024-11-14 22:07:32 -06:00
Jeremy Rifkin
88df27aa32
Disable a unittest config until a libdwarf bug is fixed 2024-11-14 21:38:49 -06:00
Jeremy Rifkin
bbae57bd22
Fix typo 2024-11-14 21:23:42 -06:00
Jeremy Rifkin
5c9f5150fb
Followup fixes 2024-11-11 22:41:58 -06:00
Jeremy Rifkin
addaf02387
Fix a test issue on MSVC due to TCO 2024-11-11 21:57:04 -06:00
Jeremy Rifkin
855210e624
Resolve msvc warning/error 2024-11-11 21:46:48 -06:00
Jeremy Rifkin
e41f89ee82
Bump libdwarf used in testing due to dwarf 5 bug 2024-11-11 21:37:36 -06:00
Jeremy Rifkin
4cb425fb35
LTO hackery, make unittests pass under LTO, closes #179 2024-11-11 00:05:01 -06:00
Jeremy Rifkin
90da0563f9
Recurse into DW_TAG_lexical_block DIEs while searching for inlining information 2024-11-10 16:09:55 -06:00
Jeremy Rifkin
04b85a0dfa
Refactor auto-config logic out of the main CMakeLists.txt 2024-11-03 18:42:36 -06:00
Jeremy Rifkin
f152788abd
Merge branch 'main' into dev 2024-10-29 22:23:28 -05:00
Pavol Gono
9269a72c54
Fixed compiler warnings and errors under MSYS2+MINGW64 platform. (#186)
Fixed compiler warnings and errors under MSYS2+MINGW64 platform.
2024-10-27 10:30:36 -05:00
mhx
124dba5254
fix: actually use ccache binary returned by find_program (#184)
The original code assumed that if `find_program` found `ccache`, then
`ccache` must be in the `PATH`. However, `find_program` searches in a
lot of different places. This can lead to situations where `ccache` is
found, but the compile will fail because it's not in the path.

This change also uses `CMAKE_XXX_COMPILER_LAUNCHER` instead of
`RULE_LAUNCH_COMPILE`, as the latter is for internal use only per the
CMake docs.
2024-10-20 23:54:50 -05:00
Reimu NotMoe
557a4a6fab
Add missing #include <algorithm> in microfmt.hpp (#183)
This fixes building latest cpptrace in macOS
(https://github.com/jeremy-rifkin/microfmt/issues/1)
2024-10-20 23:52:29 -05:00
Mathias Hasselmann
7a0c6ec78d
Clarify library intro in README (#180)
---------

Co-authored-by: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com>
2024-10-15 23:55:23 -05:00
Jeremy Rifkin
81d4776a22
Differentiate stacktrace_from_current_z_* from the non-z versions in another test so they aren't merged during LTO/ICF, related to #179 2024-10-10 20:03:03 -05:00
Jeremy Rifkin
0d53defcd9
Only do the inline resolution test under libdwarf, #178 2024-10-06 17:16:18 -05:00
Jeremy Rifkin
a1fa0a1b81
Use forward declaration header in object.hpp 2024-10-06 16:50:39 -05:00
Jeremy Rifkin
e89eb61a5f
Bump to v0.7.2 2024-10-06 16:17:36 -05:00
Jeremy Rifkin
2d5842164b
Include info on headers in the readme, and a couple other tweaks 2024-10-06 14:54:58 -05:00
Jeremy Rifkin
0d2cb217bf
Another small readme update 2024-10-06 14:35:54 -05:00
Jeremy Rifkin
daa2c53042
Some readme restructuring 2024-10-06 14:26:05 -05:00
Jeremy Rifkin
757c0f87fe
Quick correction to get_cout 2024-10-06 14:06:52 -05:00
Jeremy Rifkin
4c59e73a01
Split up cpptrace.hpp 2024-10-05 18:01:12 -05:00
Jeremy Rifkin
c95ab97a48
Merge branch 'main' into dev 2024-10-02 20:20:39 -05:00
Patrick Quist
ae86a79f16
getpagesize() was removed from OSX (and posix) (#177) 2024-10-02 20:18:16 -05:00
Sofie
7b2a994559
fix: mangled url for FetchContent in cmake 3.16 (#176)
Fixes #174

Seems to be that older versions of CMake has a tendency to mangle URLs
as per described in https://stackoverflow.com/questions/74996365/
2024-10-02 20:18:16 -05:00
Vittorio Romeo
0ddbbf43cb
Improve compilation times on Windows (#172)
Thank you for the very useful library!

Few improvements:
- Better header hygiene
- Isolate `windows.h` to `.cpp` whenever possible
- Use `WIN32_LEAN_AND_MEAN`
- Remove unused headers

Tested on Windows with 
```
cmake .. -DCMAKE_BUILD_TYPE=Debug -GNinja -DCMAKE_EXPORT_COMPILE_COMMANDS=1 
  -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_FLAGS="-ftime-trace -Wall -Wextra -Wpedantic 
  -Wno-ignored-attributes" -DCMAKE_COLOR_DIAGNOSTICS=1 -DCPPTRACE_BUILD_TESTING=1 
  -DCPPTRACE_BUILD_BENCHMARKING=0
```

There's a lot more that can be improved if you are interested.

---------

Co-authored-by: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com>
2024-10-02 10:55:13 -05:00
Patrick Quist
ce8214bb16
getpagesize() was removed from OSX (and posix) (#177) 2024-09-30 00:25:58 -05:00
Sofie
54a3e6fdf7
fix: mangled url for FetchContent in cmake 3.16 (#176)
Fixes #174

Seems to be that older versions of CMake has a tendency to mangle URLs
as per described in https://stackoverflow.com/questions/74996365/
2024-09-17 08:25:23 -05:00
Jeremy Rifkin
06eb15bda6
Some changelog corrections for v0.7.1 2024-09-13 08:21:28 -05:00
Jeremy Rifkin
3890a70cb4
Bump to v0.7.1 2024-09-13 08:19:24 -05:00
Jeremy Rifkin
4ed90c1585
Switch away from FetchContent_Populated, #171 2024-09-13 00:42:17 -05:00
Jeremy Rifkin
142e0b9ea4
Per 0blu's suggestion, use the SymGetLineFromAddr macro instead of SymGetLineFromAddr64 2024-09-13 00:06:05 -05:00
Jeremy Rifkin
d09378c8e8
Use execinfo.h by default for clang/apple clang on macos, #161 2024-09-12 23:56:09 -05:00
Jeremy Rifkin
f9ab949a9e
Bump hard_max_frames to 400 2024-09-12 23:12:11 -05:00
Jeremy Rifkin
4e9f0da95f
Nest microfmt in the cpptrace namespace due to conditionally-enabled C++17 behavior causing ODR issues, related to https://github.com/jeremy-rifkin/libassert/issues/103 2024-09-12 21:06:12 -05:00
Jeremy Rifkin
4f94f20d41
Fix computation of object address for safe object frames. I forgot to do this as part of a528aa8e0b, also related to #104 2024-09-07 12:02:52 -05:00
Jeremy Rifkin
499bea182c
Reduce bazel build to -std=c++11 2024-09-06 17:24:52 -05:00
_BLU
0d89be4fbe
VS2015 fixes (#165)
Fixes the compilation on VS14 / 2015.
2024-09-04 22:32:00 -05:00
Jeremy Rifkin
7fdbbfdf67
Fix compilation on ios (#167)
This aims to address #163
2024-09-04 17:22:32 -05:00
Pavol Gono
26093d5791
MSYS2+MINGW64 platform with libbacktrace (#166)
I've updated cmake conditions, to allow libbacktrace usage under
mingw64. And fixed one compilation issue.
Tested with package
https://packages.msys2.org/package/mingw-w64-x86_64-libbacktrace
with this setup:
```
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local -DCPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE=On
```
2024-09-03 19:58:39 -05:00
lzw
f671819510
build: support build with libunwind on osx (#162)
macOS provides a libunwind in SDK, so we can skip the library search
process.

---------

Co-authored-by: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com>
2024-08-30 18:15:38 -05:00
Jeremy Rifkin
231960d472
Merge branch 'main' into dev 2024-08-30 17:18:12 -05:00
Jeremy Rifkin
cf08f2578c
Somehow forgot to commit this 2024-08-29 22:38:50 -05:00
Jeremy Rifkin
c7ff8cbf79
Add a bullet point about traces from exceptions 2024-08-29 22:32:47 -05:00
Jeremy Rifkin
9a7bd8f95e
Clarify copying of cpptrace target on windows 2024-08-28 20:02:21 -05:00
Jeremy Rifkin
0742b42dad
Update changelog and bump to v0.7.0 2024-08-21 08:47:23 -05:00
Jeremy Rifkin
31ebb3ca23
Fix a couple file endings and also make note of lazy_trace_holder::get_raw_trace in the readme 2024-08-21 00:07:36 -05:00
Jeremy Rifkin
4dcfdf5281
Bump zstd and libdwarf versions in ci scripts 2024-08-20 23:19:44 -05:00
Jeremy Rifkin
3b0eb54797
Implement a system for preventing redundant tracing during search, and other various improvements for the from_current system (#159) 2024-08-20 23:10:17 -05:00
WSUFan
0249b50698
fix Bazel build (#158) 2024-08-18 21:46:34 -05:00
Jeremy Rifkin
fddbe72a66
Quick readme update 2024-08-18 19:17:53 -05:00
WSUFan
88d681d986
Enable Bazel build system support (#153)
This pull request enables Bazel build support for cpptrace. Please note
that currently, only Linux is supported. Additional platform support
will be added if necessary.
2024-08-18 19:16:53 -05:00
Jeremy Rifkin
3e131ce8c6
Experiment with some benchmarking 2024-08-18 18:59:50 -05:00
Jeremy Rifkin
5e30d2ae60
Disable a configuration broken upstream 2024-08-18 18:18:20 -05:00
Jeremy Rifkin
b87be87c66
Fix CPPTRACE_BUILD_TESTING_DWARF_VERSION use 2024-08-18 16:56:37 -05:00
Jeremy Rifkin
b364d37f78
Implement better dwarf rangelist base address logic (#157)
Related to https://github.com/davea42/libdwarf-code/issues/255, allows
re-enabling the clang+sanitizer+rel+dsym tests.
2024-08-18 16:51:40 -05:00
Jeremy Rifkin
a4d75a3894
Unittest under libc++ (#156) 2024-08-18 14:11:51 -05:00
Jeremy Rifkin
d64a935fb4
Bump to libdwarf 0.11.0 2024-08-18 14:11:16 -05:00
Jeremy Rifkin
64e0210449
Better handle resolution of safe object frames with empty object paths 2024-08-18 13:38:06 -05:00
Jeremy Rifkin
0e2c3a130d
Fix windows build 2024-08-18 12:11:20 -05:00
Jeremy Rifkin
973309cb22
Some more platform refactoring/cleanup, also add a note to readme 2024-08-18 12:07:34 -05:00
Jeremy Rifkin
164cc75681
A little refactoring and reorganization. Remove relative includes. 2024-08-18 11:58:49 -05:00
Jeremy Rifkin
0bb5aa2318
Add a basic test for traced exception objects 2024-08-18 11:41:38 -05:00
Jeremy Rifkin
3f68171d0b
Some makefile improvements 2024-08-18 11:33:57 -05:00
Jeremy Rifkin
7621f2b277
A couple notes/comments/documentation tweaks 2024-08-18 11:20:59 -05:00
Jeremy Rifkin
5558210cbe
Bump zstd to 1.5.6, #150 2024-08-18 11:20:21 -05:00
Jeremy Rifkin
26e009c688
Add cpptrace::from_current (#155) 2024-08-18 11:11:47 -05:00
Jeremy Rifkin
d5eed55dcd
Fix -g on msvc 2024-08-18 00:56:28 -05:00
Jeremy Rifkin
df1d78d1eb
Revert "Fix -g on msvc"
This reverts commit 51551ab741.
2024-08-18 00:55:49 -05:00
Jeremy Rifkin
51551ab741
Fix -g on msvc 2024-08-18 00:53:44 -05:00
Jeremy Rifkin
70e78e3c8a
Remove some commented code 2024-08-16 10:29:00 -05:00
Degen's Regens
767ddeb10c
size_t cannot be negative (#154) 2024-08-16 10:24:40 -05:00
Jeremy
90de25f1df
Bump to v0.6.3 2024-07-13 23:16:13 -05:00
Jeremy
2ae193256d
Try using ninja in the build ci setup 2024-07-13 22:50:18 -05:00
Jeremy
a2f1812896
Disable configuration that's due to a libdwarf bug 2024-07-13 22:38:25 -05:00
Jeremy
95a233958c
Allow non-string values in matrix 2024-07-13 21:35:38 -05:00
Jeremy
59f74320fc
Add dSYM to the macos unittest matrix 2024-07-13 21:22:14 -05:00
Jeremy Rifkin
0b95a6afb9
Macos unit testing (#148) 2024-07-13 19:37:55 -05:00
Jeremy
cc2b3fce40
Add another try/catch to prevent any potential TCO 2024-07-13 19:07:58 -05:00
Jeremy
dcf89743f6
Block TCO on wrapper functions 2024-07-13 19:06:33 -05:00
Jeremy
c35ad6887a
Fix issue with incorrect reported object addresses on macos when using debug maps 2024-07-13 19:06:13 -05:00
Jeremy
f8fee797cf
Use iosfwd instead of iostream 2024-07-12 17:51:06 -05:00
Jeremy
e8fce3fad1
Fix broken links and the example code in signal-safe-tracing.md, and also add a note to the documentation about _dl_find_object being required for signal-safe tracing 2024-07-12 17:33:35 -05:00
Jeremy
15b6d9a2cd
Try turning off <format> stuff for the unittest matrix 2024-07-12 17:24:20 -05:00
Jeremy
aae6091395
Add a flag to disable the inclusion of <format> and the provision for std::formatter specializations 2024-07-12 17:22:58 -05:00
Jeremy
dbeb12b71d
Fix zstd/libdwarf setting placement, they need to not just be top-level 2024-07-12 16:59:27 -05:00
Jeremy Rifkin
7497bf2a6f
Unit test more thoroughly and rework matrix ci system (#147) 2024-07-12 00:59:22 -05:00
Jeremy
e1fb3527a4
Fix an issue with split dwarf on clang 2024-07-11 19:10:16 -05:00
Jeremy
611660de5b
Fix use after free during cleanup of split dwarf information, fixes #141 2024-07-02 21:56:35 -06:00
Jeremy
b93028921b
Turn on -g unconditionally 2024-07-02 13:34:15 -06:00
Jeremy
060272be5a
Merge branch 'main' into dev 2024-07-02 13:20:20 -06:00
Jeremy
bf0551b781
Add some extra checks to unit tests 2024-06-22 19:48:04 -05:00
137 changed files with 9843 additions and 3567 deletions

1
.bazeliskrc Normal file
View File

@ -0,0 +1 @@
USE_BAZEL_VERSION=7.2.1

3
.bazelrc Normal file
View File

@ -0,0 +1,3 @@
test --strip=never
test --test_output=all
test --copt=-g

View File

@ -1,130 +0,0 @@
name: build
on:
push:
pull_request:
jobs:
build-linux:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
compiler: [gcc, clang]
steps:
- uses: actions/checkout@v4
- name: dependencies
run: |
sudo apt install gcc-10 g++-10 libgcc-10-dev libunwind8-dev
pip3 install colorama
- name: libdwarf
run: |
cd ..
cpptrace/ci/setup-prerequisites.sh
- name: build
run: |
python3 ci/build-in-all-configs.py --${{matrix.compiler}} --default-config
build-macos:
runs-on: macos-14
strategy:
fail-fast: false
matrix:
compiler: [gcc, clang]
steps:
- uses: actions/checkout@v4
- name: dependencies
run: |
python3 -m venv env
env/bin/pip install colorama
- name: libdwarf
run: |
cd ..
cpptrace/ci/setup-prerequisites.sh
- name: build
run: |
env/bin/python ci/build-in-all-configs.py --${{matrix.compiler}} --default-config
build-windows:
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
compiler: [msvc, clang, gcc]
steps:
- uses: actions/checkout@v4
- name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@v1.13.0
- name: dependencies
run: |
pip3 install colorama
- name: libdwarf
run: |
if("${{matrix.compiler}}" -eq "gcc") {
cd ..
cpptrace/ci/setup-prerequisites-mingw.ps1
}
- name: build
run: |
python3 ci/build-in-all-configs.py --${{matrix.compiler}} --default-config
build-linux-all-configurations:
runs-on: ubuntu-22.04
needs: build-linux
strategy:
fail-fast: false
matrix:
compiler: [gcc, clang]
steps:
- uses: actions/checkout@v4
- name: dependencies
run: |
sudo apt install gcc-10 g++-10 libgcc-10-dev libunwind8-dev
pip3 install colorama
- name: libdwarf
run: |
cd ..
cpptrace/ci/setup-prerequisites.sh
- name: build
run: |
python3 ci/build-in-all-configs.py --${{matrix.compiler}}
build-macos-all-configurations:
runs-on: macos-14
needs: build-macos
strategy:
fail-fast: false
matrix:
compiler: [gcc, clang]
steps:
- uses: actions/checkout@v4
- name: dependencies
run: |
python3 -m venv env
env/bin/pip install colorama
- name: libdwarf
run: |
cd ..
cpptrace/ci/setup-prerequisites.sh
- name: build
run: |
env/bin/python ci/build-in-all-configs.py --${{matrix.compiler}}
build-windows-all-configurations:
runs-on: windows-2022
needs: build-windows
strategy:
fail-fast: false
matrix:
compiler: [msvc, clang, gcc]
steps:
- uses: actions/checkout@v4
- name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@v1.13.0
- name: dependencies
run: |
pip3 install colorama
- name: libdwarf
run: |
if("${{matrix.compiler}}" -eq "gcc") {
cd ..
cpptrace/ci/setup-prerequisites-mingw.ps1
}
- name: build
run: |
python3 ci/build-in-all-configs.py --${{matrix.compiler}}

652
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,652 @@
name: ci
on:
push:
pull_request:
jobs:
test-linux:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
compiler: [gcc, clang]
shared: [--shared, ""]
steps:
- uses: actions/checkout@v4
- name: dependencies
run: |
sudo apt install gcc-10 g++-10 libgcc-10-dev libunwind8-dev
pip3 install colorama
- name: libdwarf
run: |
cd ..
cpptrace/ci/setup-prerequisites.sh
- name: build and test
run: |
python3 ci/test-all-configs.py --${{matrix.compiler}} --default-config
test-linux-arm:
runs-on: ubuntu-22.04-arm
strategy:
fail-fast: false
matrix:
compiler: [gcc, clang]
shared: [--shared, ""]
steps:
- uses: actions/checkout@v4
- name: dependencies
run: |
sudo apt install gcc-10 g++-10 libgcc-10-dev libunwind8-dev
pip3 install colorama
- name: libdwarf
run: |
cd ..
cpptrace/ci/setup-prerequisites.sh
- name: build and test
run: |
python3 ci/test-all-configs.py --${{matrix.compiler}} --default-config
test-macos:
runs-on: macos-14
strategy:
fail-fast: false
matrix:
compiler: [gcc, clang]
shared: [--shared, ""]
steps:
- uses: actions/checkout@v4
- name: libdwarf
run: |
cd ..
cpptrace/ci/setup-prerequisites.sh
- name: dependencies
run: |
python3 -m venv env
env/bin/pip install colorama
- name: build and test
run: |
env/bin/python ci/test-all-configs.py --${{matrix.compiler}} --default-config
test-windows:
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
compiler: [msvc, clang, gcc]
shared: [--shared, ""]
steps:
- uses: actions/checkout@v4
- name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@v1.13.0
- name: dependencies
run: |
pip3 install colorama
- name: libdwarf
run: |
if("${{matrix.compiler}}" -eq "gcc") {
cd ..
cpptrace/ci/setup-prerequisites-mingw.ps1
}
- name: build and test
run: |
python3 ci/test-all-configs.py --${{matrix.compiler}} --default-config
test-windows-old:
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
compiler: [msvc]
shared: [--shared, ""]
steps:
- uses: actions/checkout@v4
- name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@v1.13.0
with:
toolset: 14.29 # vc 2019
- name: dependencies
run: |
pip3 install colorama
- name: libdwarf
run: |
if("${{matrix.compiler}}" -eq "gcc") {
cd ..
cpptrace/ci/setup-prerequisites-mingw.ps1
}
- name: build and test
run: |
python3 ci/test-all-configs.py --${{matrix.compiler}} --default-config
test-linux-all-configurations:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
compiler: [gcc, clang]
shared: [--shared, ""]
needs: test-linux
steps:
- uses: actions/checkout@v4
- name: dependencies
run: |
sudo apt install gcc-10 g++-10 libgcc-10-dev libunwind8-dev
pip3 install colorama
- name: libdwarf
run: |
cd ..
cpptrace/ci/setup-prerequisites.sh
- name: build and test
run: |
python3 ci/test-all-configs.py --${{matrix.compiler}}
test-macos-all-configurations:
runs-on: macos-14
strategy:
fail-fast: false
matrix:
compiler: [gcc, clang]
shared: [--shared, ""]
needs: test-macos
steps:
- uses: actions/checkout@v4
- name: libdwarf
run: |
cd ..
cpptrace/ci/setup-prerequisites.sh
- name: dependencies
run: |
python3 -m venv env
env/bin/pip install colorama
- name: build and test
run: |
env/bin/python ci/test-all-configs.py --${{matrix.compiler}}
test-windows-all-configurations:
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
compiler: [msvc, clang, gcc]
shared: [--shared, ""]
needs: test-windows
steps:
- uses: actions/checkout@v4
- name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@v1.13.0
- name: dependencies
run: |
pip3 install colorama
- name: libdwarf
run: |
if("${{matrix.compiler}}" -eq "gcc") {
cd ..
cpptrace/ci/setup-prerequisites-mingw.ps1
}
- name: build and test
run: |
python3 ci/test-all-configs.py --${{matrix.compiler}}
build-linux-all-remaining-configurations:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
compiler: [gcc, clang]
needs: test-linux-all-configurations
steps:
- uses: actions/checkout@v4
- name: dependencies
run: |
sudo apt install gcc-10 g++-10 libgcc-10-dev libunwind8-dev ninja-build
pip3 install colorama
- name: libdwarf
run: |
cd ..
cpptrace/ci/setup-prerequisites.sh
- name: build
run: |
python3 ci/build-in-all-remaining-configs.py --${{matrix.compiler}}
build-macos-all-remaining-configurations:
runs-on: macos-14
strategy:
fail-fast: false
matrix:
compiler: [gcc, clang]
needs: test-macos-all-configurations
steps:
- uses: actions/checkout@v4
- name: dependencies
run: |
brew install ninja
python3 -m venv env
env/bin/pip install colorama
- name: libdwarf
run: |
cd ..
cpptrace/ci/setup-prerequisites.sh
- name: build
run: |
env/bin/python ci/build-in-all-remaining-configs.py --${{matrix.compiler}}
build-windows-all-remaining-configurations:
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
compiler: [msvc, clang, gcc]
needs: test-windows-all-configurations
steps:
- uses: actions/checkout@v4
- name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@v1.13.0
- name: dependencies
run: |
pip3 install colorama
- name: libdwarf
run: |
if("${{matrix.compiler}}" -eq "gcc") {
cd ..
cpptrace/ci/setup-prerequisites-mingw.ps1
}
- name: build
run: |
python3 ci/build-in-all-remaining-configs.py --${{matrix.compiler}}
performancetest-linux:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
compiler: [g++-11, clang++-14]
config: [
-DSPEEDTEST_DWARF4=On,
-DSPEEDTEST_DWARF5=On
]
needs: test-linux-all-configurations
steps:
- uses: actions/checkout@v4
- name: dependencies
run: sudo apt install gcc-11 g++-11 libgcc-11-dev
- name: build
run: |
mkdir -p build
cd build
cmake .. -DCMAKE_CXX_COMPILER=${{matrix.compiler}} -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=foo
make -j
make install
mkdir -p ../test/speedtest/build
cd ../test/speedtest/build
cmake .. \
-DCMAKE_BUILD_TYPE=Debug \
${{matrix.config}}
make -j
- name: speedtest
working-directory: test/speedtest/build
run: |
./speedtest | python3 ../../../ci/speedtest.py ${{matrix.compiler}} ${{matrix.config}}
test-linux-fetchcontent:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
shared: [On, Off]
needs: test-linux-all-configurations
steps:
- uses: actions/checkout@v4
- name: test
run: |
tag=$(git rev-parse --abbrev-ref HEAD)
echo $tag
cd ..
cp -rv cpptrace/test/fetchcontent-integration .
mkdir fetchcontent-integration/build
cd fetchcontent-integration/build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DCPPTRACE_TAG=$tag -DBUILD_SHARED_LIBS=${{matrix.shared}} -DCPPTRACE_WERROR_BUILD=On
make
./main
test-linux-findpackage:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
shared: [On, Off]
needs: test-linux-all-configurations
steps:
- uses: actions/checkout@v4
- name: test
run: |
tag=$(git rev-parse --abbrev-ref HEAD)
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=${{matrix.shared}} -DCPPTRACE_WERROR_BUILD=On
sudo make -j install
cd ../..
cp -rv cpptrace/test/findpackage-integration .
mkdir findpackage-integration/build
cd findpackage-integration/build
cmake .. -DCMAKE_BUILD_TYPE=Debug
make
./main
test-linux-add_subdirectory:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
shared: [On, Off]
needs: test-linux-all-configurations
steps:
- uses: actions/checkout@v4
- name: build
run: |
cd ..
cp -rv cpptrace/test/add_subdirectory-integration .
cp -rv cpptrace add_subdirectory-integration
mkdir add_subdirectory-integration/build
cd add_subdirectory-integration/build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=${{matrix.shared}} -DCPPTRACE_WERROR_BUILD=On
make
./main
test-macos-fetchcontent:
runs-on: macos-14
strategy:
fail-fast: false
matrix:
shared: [On, Off]
needs: test-macos-all-configurations
steps:
- uses: actions/checkout@v4
- name: test
run: |
tag=$(git rev-parse --abbrev-ref HEAD)
echo $tag
cd ..
cp -rv cpptrace/test/fetchcontent-integration .
mkdir fetchcontent-integration/build
cd fetchcontent-integration/build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DCPPTRACE_TAG=$tag -DBUILD_SHARED_LIBS=${{matrix.shared}} -DCPPTRACE_WERROR_BUILD=On
make
./main
test-macos-findpackage:
runs-on: macos-14
strategy:
fail-fast: false
matrix:
shared: [On, Off]
needs: test-macos-all-configurations
steps:
- uses: actions/checkout@v4
- name: test
run: |
tag=$(git rev-parse --abbrev-ref HEAD)
echo $tag
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=${{matrix.shared}} -DCPPTRACE_WERROR_BUILD=On
sudo make -j install
cd ../..
cp -rv cpptrace/test/findpackage-integration .
mkdir findpackage-integration/build
cd findpackage-integration/build
cmake .. -DCMAKE_BUILD_TYPE=Debug
make
./main
test-macos-add_subdirectory:
runs-on: macos-14
strategy:
fail-fast: false
matrix:
shared: [On, Off]
needs: test-macos-all-configurations
steps:
- uses: actions/checkout@v4
- name: test
run: |
cd ..
cp -rv cpptrace/test/add_subdirectory-integration .
cp -rv cpptrace add_subdirectory-integration
mkdir add_subdirectory-integration/build
cd add_subdirectory-integration/build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=${{matrix.shared}} -DCPPTRACE_WERROR_BUILD=On
make
./main
test-mingw-fetchcontent:
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
shared: [On, Off]
needs: test-windows-all-configurations
steps:
- uses: actions/checkout@v4
- name: test
run: |
$tag=$(git rev-parse --abbrev-ref HEAD)
echo $tag
cd ..
cp -Recurse cpptrace/test/fetchcontent-integration .
mkdir fetchcontent-integration/build
cd fetchcontent-integration/build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DCPPTRACE_TAG="$tag" "-GUnix Makefiles" -DBUILD_SHARED_LIBS=${{matrix.shared}} -DCPPTRACE_WERROR_BUILD=On
make
.\main.exe
test-mingw-findpackage:
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
shared: [On, Off]
needs: test-windows-all-configurations
steps:
- uses: actions/checkout@v4
- name: test
run: |
$tag=$(git rev-parse --abbrev-ref HEAD)
echo $tag
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=${{matrix.shared}} "-GUnix Makefiles" -DCMAKE_INSTALL_PREFIX=C:/foo -DCPPTRACE_WERROR_BUILD=On
make -j install
cd ../..
mv cpptrace/test/findpackage-integration .
mkdir findpackage-integration/build
cd findpackage-integration/build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_PREFIX_PATH=C:/foo "-GUnix Makefiles"
make
./main
test-mingw-add_subdirectory:
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
shared: [On, Off]
needs: test-windows-all-configurations
steps:
- uses: actions/checkout@v4
- name: test
run: |
cd ..
cp -Recurse cpptrace/test/add_subdirectory-integration .
cp -Recurse cpptrace add_subdirectory-integration
mkdir add_subdirectory-integration/build
cd add_subdirectory-integration/build
cmake .. -DCMAKE_BUILD_TYPE=Debug "-GUnix Makefiles" -DBUILD_SHARED_LIBS=${{matrix.shared}} -DCPPTRACE_WERROR_BUILD=On
make
.\main.exe
test-windows-fetchcontent:
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
shared: [On, Off]
needs: test-windows-all-configurations
steps:
- uses: actions/checkout@v4
- name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@v1.13.0
- name: test
run: |
$tag=$(git rev-parse --abbrev-ref HEAD)
echo $tag
cd ..
cp -Recurse cpptrace/test/fetchcontent-integration .
mkdir fetchcontent-integration/build
cd fetchcontent-integration/build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DCPPTRACE_TAG="$tag" -DBUILD_SHARED_LIBS=${{matrix.shared}} -DCPPTRACE_WERROR_BUILD=On
msbuild demo_project.sln
.\Debug\main.exe
test-windows-findpackage:
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
shared: [On, Off]
needs: test-windows-all-configurations
steps:
- uses: actions/checkout@v4
- name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@v1.13.0
- name: test
run: |
$tag=$(git rev-parse --abbrev-ref HEAD)
echo $tag
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=${{matrix.shared}} -DCMAKE_INSTALL_PREFIX=C:/foo -DCPPTRACE_WERROR_BUILD=On
msbuild cpptrace.sln
msbuild INSTALL.vcxproj
cd ../..
mv cpptrace/test/findpackage-integration .
mkdir findpackage-integration/build
cd findpackage-integration/build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_PREFIX_PATH=C:/foo
msbuild demo_project.sln
.\Debug\main.exe
test-windows-add_subdirectory:
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
shared: [On, Off]
needs: test-windows-all-configurations
steps:
- uses: actions/checkout@v4
- name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@v1.13.0
- name: test
run: |
cd ..
cp -Recurse cpptrace/test/add_subdirectory-integration .
cp -Recurse cpptrace add_subdirectory-integration
mkdir add_subdirectory-integration/build
cd add_subdirectory-integration/build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=${{matrix.shared}} -DCPPTRACE_WERROR_BUILD=On
msbuild demo_project.sln
.\Debug\main.exe
unittest-linux:
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
compiler: [g++-10, clang++-18]
stdlib: [libstdc++, libc++]
dwarf_version: [4, 5]
split_dwarf: [OFF, ON]
exclude:
- compiler: g++-10
stdlib: libc++
steps:
- uses: actions/checkout@v4
- name: dependencies
run: |
sudo apt install gcc-10 g++-10 libgcc-10-dev ninja-build libc++-dev
cd ..
cpptrace/ci/setup-prerequisites-unittest.sh
- name: build and test
run: |
python3 ci/unittest.py \
--slice=compiler:${{matrix.compiler}} \
--slice=stdlib:${{matrix.stdlib}} \
--slice=dwarf_version:${{matrix.dwarf_version}} \
--slice=split_dwarf:${{matrix.split_dwarf}}
unittest-linux-bazel:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: dependencies
run: |
sudo apt install -y libtool libncurses5
- name: test dbg
run: |
bazel test //... -c dbg
- name: test opt
run: |
bazel test //... -c opt
unittest-linux-arm:
runs-on: ubuntu-24.04-arm
strategy:
fail-fast: false
matrix:
compiler: [g++-10, clang++-18]
stdlib: [libstdc++, libc++]
dwarf_version: [4, 5]
split_dwarf: [OFF, ON]
exclude:
- compiler: g++-10
stdlib: libc++
steps:
- uses: actions/checkout@v4
- name: dependencies
run: |
sudo apt install gcc-10 g++-10 libgcc-10-dev ninja-build libc++-dev
cd ..
cpptrace/ci/setup-prerequisites-unittest.sh
- name: build and test
run: |
python3 ci/unittest.py \
--slice=compiler:${{matrix.compiler}} \
--slice=stdlib:${{matrix.stdlib}} \
--slice=dwarf_version:${{matrix.dwarf_version}} \
--slice=split_dwarf:${{matrix.split_dwarf}}
unittest-macos:
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: "15.4"
- name: dependencies
run: |
brew install ninja
python3 -m venv env
env/bin/pip install colorama
cd ..
cpptrace/ci/setup-prerequisites-unittest-macos.sh
- name: build and test
run: |
env/bin/python ci/unittest.py
unittest-windows:
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
compiler: [cl, clang++]
shared: [OFF] # TODO: Re-enable shared
build_type: [Debug, RelWithDebInfo]
steps:
- uses: actions/checkout@v4
- name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@v1.13.0
- name: build and test
run: |
mkdir build
cd build
cmake .. `
-DCMAKE_CXX_COMPILER=${{matrix.compiler}} `
-DCMAKE_C_COMPILER=${{matrix.compiler == 'clang++' && 'clang' || matrix.compiler}} `
-DBUILD_SHARED_LIBS=${{matrix.shared}} `
-DCPPTRACE_WERROR_BUILD=On `
-DCPPTRACE_BUILD_TESTING=On
cmake --build . --config ${{matrix.build_type}}
./${{matrix.build_type}}/unittest
# TODO: Macos, mingw

View File

@ -1,255 +0,0 @@
name: cmake-integration
on:
push:
pull_request:
jobs:
test-linux-fetchcontent:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
shared: [On, Off]
steps:
- uses: actions/checkout@v4
- name: test
run: |
tag=$(git rev-parse --abbrev-ref HEAD)
echo $tag
cd ..
cp -rv cpptrace/test/fetchcontent-integration .
mkdir fetchcontent-integration/build
cd fetchcontent-integration/build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DCPPTRACE_TAG=$tag -DBUILD_SHARED_LIBS=${{matrix.shared}} -DCPPTRACE_WERROR_BUILD=On
make
./main
test-linux-findpackage:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
shared: [On, Off]
steps:
- uses: actions/checkout@v4
- name: test
run: |
tag=$(git rev-parse --abbrev-ref HEAD)
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=${{matrix.shared}} -DCPPTRACE_WERROR_BUILD=On
sudo make -j install
cd ../..
cp -rv cpptrace/test/findpackage-integration .
mkdir findpackage-integration/build
cd findpackage-integration/build
cmake .. -DCMAKE_BUILD_TYPE=Debug
make
./main
test-linux-add_subdirectory:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
shared: [On, Off]
steps:
- uses: actions/checkout@v4
- name: build
run: |
cd ..
cp -rv cpptrace/test/add_subdirectory-integration .
cp -rv cpptrace add_subdirectory-integration
mkdir add_subdirectory-integration/build
cd add_subdirectory-integration/build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=${{matrix.shared}} -DCPPTRACE_WERROR_BUILD=On
make
./main
test-macos-fetchcontent:
runs-on: macos-14
strategy:
fail-fast: false
matrix:
shared: [On, Off]
steps:
- uses: actions/checkout@v4
- name: test
run: |
tag=$(git rev-parse --abbrev-ref HEAD)
echo $tag
cd ..
cp -rv cpptrace/test/fetchcontent-integration .
mkdir fetchcontent-integration/build
cd fetchcontent-integration/build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DCPPTRACE_TAG=$tag -DBUILD_SHARED_LIBS=${{matrix.shared}} -DCPPTRACE_WERROR_BUILD=On
make
./main
test-macos-findpackage:
runs-on: macos-14
strategy:
fail-fast: false
matrix:
shared: [On, Off]
steps:
- uses: actions/checkout@v4
- name: test
run: |
tag=$(git rev-parse --abbrev-ref HEAD)
echo $tag
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=${{matrix.shared}} -DCPPTRACE_WERROR_BUILD=On
sudo make -j install
cd ../..
cp -rv cpptrace/test/findpackage-integration .
mkdir findpackage-integration/build
cd findpackage-integration/build
cmake .. -DCMAKE_BUILD_TYPE=Debug
make
./main
test-macos-add_subdirectory:
runs-on: macos-14
strategy:
fail-fast: false
matrix:
shared: [On, Off]
steps:
- uses: actions/checkout@v4
- name: test
run: |
cd ..
cp -rv cpptrace/test/add_subdirectory-integration .
cp -rv cpptrace add_subdirectory-integration
mkdir add_subdirectory-integration/build
cd add_subdirectory-integration/build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=${{matrix.shared}} -DCPPTRACE_WERROR_BUILD=On
make
./main
test-mingw-fetchcontent:
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
shared: [On, Off]
steps:
- uses: actions/checkout@v4
- name: test
run: |
$tag=$(git rev-parse --abbrev-ref HEAD)
echo $tag
cd ..
cp -Recurse cpptrace/test/fetchcontent-integration .
mkdir fetchcontent-integration/build
cd fetchcontent-integration/build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DCPPTRACE_TAG="$tag" "-GUnix Makefiles" -DBUILD_SHARED_LIBS=${{matrix.shared}} -DCPPTRACE_WERROR_BUILD=On
make
.\main.exe
test-mingw-findpackage:
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
shared: [On, Off]
steps:
- uses: actions/checkout@v4
- name: test
run: |
$tag=$(git rev-parse --abbrev-ref HEAD)
echo $tag
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=${{matrix.shared}} "-GUnix Makefiles" -DCMAKE_INSTALL_PREFIX=C:/foo -DCPPTRACE_WERROR_BUILD=On
make -j install
cd ../..
mv cpptrace/test/findpackage-integration .
mkdir findpackage-integration/build
cd findpackage-integration/build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_PREFIX_PATH=C:/foo "-GUnix Makefiles"
make
./main
test-mingw-add_subdirectory:
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
shared: [On, Off]
steps:
- uses: actions/checkout@v4
- name: test
run: |
cd ..
cp -Recurse cpptrace/test/add_subdirectory-integration .
cp -Recurse cpptrace add_subdirectory-integration
mkdir add_subdirectory-integration/build
cd add_subdirectory-integration/build
cmake .. -DCMAKE_BUILD_TYPE=Debug "-GUnix Makefiles" -DBUILD_SHARED_LIBS=${{matrix.shared}} -DCPPTRACE_WERROR_BUILD=On
make
.\main.exe
test-windows-fetchcontent:
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
shared: [On, Off]
steps:
- uses: actions/checkout@v4
- name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@v1.13.0
- name: test
run: |
$tag=$(git rev-parse --abbrev-ref HEAD)
echo $tag
cd ..
cp -Recurse cpptrace/test/fetchcontent-integration .
mkdir fetchcontent-integration/build
cd fetchcontent-integration/build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DCPPTRACE_TAG="$tag" -DBUILD_SHARED_LIBS=${{matrix.shared}} -DCPPTRACE_WERROR_BUILD=On
msbuild demo_project.sln
.\Debug\main.exe
test-windows-findpackage:
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
shared: [On, Off]
steps:
- uses: actions/checkout@v4
- name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@v1.13.0
- name: test
run: |
$tag=$(git rev-parse --abbrev-ref HEAD)
echo $tag
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=${{matrix.shared}} -DCMAKE_INSTALL_PREFIX=C:/foo -DCPPTRACE_WERROR_BUILD=On
msbuild cpptrace.sln
msbuild INSTALL.vcxproj
cd ../..
mv cpptrace/test/findpackage-integration .
mkdir findpackage-integration/build
cd findpackage-integration/build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_PREFIX_PATH=C:/foo
msbuild demo_project.sln
.\Debug\main.exe
test-windows-add_subdirectory:
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
shared: [On, Off]
steps:
- uses: actions/checkout@v4
- name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@v1.13.0
- name: test
run: |
cd ..
cp -Recurse cpptrace/test/add_subdirectory-integration .
cp -Recurse cpptrace add_subdirectory-integration
mkdir add_subdirectory-integration/build
cd add_subdirectory-integration/build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=${{matrix.shared}} -DCPPTRACE_WERROR_BUILD=On
msbuild demo_project.sln
.\Debug\main.exe

View File

@ -1,68 +0,0 @@
name: performance-test
on:
push:
pull_request:
jobs:
performancetest-linux:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
compiler: [g++-11, clang++-14]
config: [
-DSPEEDTEST_DWARF4=On,
-DSPEEDTEST_DWARF5=On
]
steps:
- uses: actions/checkout@v4
- name: dependencies
run: sudo apt install gcc-11 g++-11 libgcc-11-dev
- name: build
run: |
mkdir -p build
cd build
cmake .. -DCMAKE_CXX_COMPILER=${{matrix.compiler}} -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=foo
make -j
make install
mkdir -p ../test/speedtest/build
cd ../test/speedtest/build
cmake .. \
-DCMAKE_BUILD_TYPE=Debug \
${{matrix.config}}
make -j
- name: speedtest
working-directory: test/speedtest/build
run: |
./speedtest | python3 ../../../ci/speedtest.py ${{matrix.compiler}} ${{matrix.config}}
# I give up. For some reason SymInitialize is super slow on github's windows runner and it alone takes hundreds of ms.
# Nothing I can do about that.
#performancetest-windows:
# runs-on: windows-2022
# strategy:
# fail-fast: false
# matrix:
# compiler: [cl, clang++]
# steps:
# - uses: actions/checkout@v4
# - name: Enable Developer Command Prompt
# uses: ilammy/msvc-dev-cmd@v1.13.0
# - name: build
# run: |
# mkdir -p build
# cd build
# cmake .. -DCMAKE_CXX_COMPILER=${{matrix.compiler}} -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=foo
# msbuild .\cpptrace.sln /property:Configuration=Release
# msbuild .\INSTALL.vcxproj
# mkdir -p ../test/speedtest/build
# cd ../test/speedtest/build
# cmake .. `
# -DCMAKE_BUILD_TYPE=Debug `
# ${{matrix.config}}
# msbuild .\cpptrace-speedtest.sln
# - name: speedtest
# working-directory: test/speedtest/build
# run: |
# .\Debug\speedtest.exe | python3 ../../../ci/speedtest.py ${{matrix.config}}

View File

@ -1,195 +0,0 @@
name: test
on:
push:
pull_request:
# TODO: Test statically linked
jobs:
test-linux:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
compiler: [gcc, clang]
shared: [--shared, ""]
steps:
- uses: actions/checkout@v4
- name: dependencies
run: |
sudo apt install gcc-10 g++-10 libgcc-10-dev libunwind8-dev
pip3 install colorama
- name: libdwarf
run: |
cd ..
cpptrace/ci/setup-prerequisites.sh
- name: build and test
run: |
python3 ci/test-all-configs.py --${{matrix.compiler}} --default-config
test-macos:
runs-on: macos-14
strategy:
fail-fast: false
matrix:
compiler: [gcc, clang]
shared: [--shared, ""]
steps:
- uses: actions/checkout@v4
- name: libdwarf
run: |
cd ..
cpptrace/ci/setup-prerequisites.sh
- name: dependencies
run: |
python3 -m venv env
env/bin/pip install colorama
- name: build and test
run: |
env/bin/python ci/test-all-configs.py --${{matrix.compiler}} --default-config
test-windows:
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
compiler: [msvc, clang, gcc]
shared: [--shared, ""]
steps:
- uses: actions/checkout@v4
- name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@v1.13.0
- name: dependencies
run: |
pip3 install colorama
- name: libdwarf
run: |
if("${{matrix.compiler}}" -eq "gcc") {
cd ..
cpptrace/ci/setup-prerequisites-mingw.ps1
}
- name: build and test
run: |
python3 ci/test-all-configs.py --${{matrix.compiler}} --default-config
test-linux-all-configurations:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
compiler: [gcc, clang]
shared: [--shared, ""]
needs: test-linux
steps:
- uses: actions/checkout@v4
- name: dependencies
run: |
sudo apt install gcc-10 g++-10 libgcc-10-dev libunwind8-dev
pip3 install colorama
- name: libdwarf
run: |
cd ..
cpptrace/ci/setup-prerequisites.sh
- name: build and test
run: |
python3 ci/test-all-configs.py --${{matrix.compiler}}
test-macos-all-configurations:
runs-on: macos-14
strategy:
fail-fast: false
matrix:
compiler: [gcc, clang]
shared: [--shared, ""]
needs: test-macos
steps:
- uses: actions/checkout@v4
- name: libdwarf
run: |
cd ..
cpptrace/ci/setup-prerequisites.sh
- name: dependencies
run: |
python3 -m venv env
env/bin/pip install colorama
- name: build and test
run: |
env/bin/python ci/test-all-configs.py --${{matrix.compiler}}
test-windows-all-configurations:
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
compiler: [msvc, clang, gcc]
shared: [--shared, ""]
needs: test-windows
steps:
- uses: actions/checkout@v4
- name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@v1.13.0
- name: dependencies
run: |
pip3 install colorama
- name: libdwarf
run: |
if("${{matrix.compiler}}" -eq "gcc") {
cd ..
cpptrace/ci/setup-prerequisites-mingw.ps1
}
- name: build and test
run: |
python3 ci/test-all-configs.py --${{matrix.compiler}}
unittest-linux:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
compiler: [g++-10, clang++-14]
shared: [OFF, ON]
build_type: [Debug, RelWithDebInfo]
has_dl_find_object: [OFF, ON]
steps:
- uses: actions/checkout@v4
- name: dependencies
run: |
sudo apt install gcc-10 g++-10 libgcc-10-dev ninja-build
- name: build and test
run: |
mkdir build
cd build
cmake .. \
-GNinja \
-DCMAKE_CXX_COMPILER=${{matrix.compiler}} \
-DCMAKE_C_COMPILER=${{matrix.compiler == 'g++-10' && 'gcc-10' || 'clang-14'}} \
-DCMAKE_BUILD_TYPE=${{matrix.build_type}} \
-DBUILD_SHARED_LIBS=${{matrix.shared}} \
-DHAS_DL_FIND_OBJECT=${{matrix.has_dl_find_object}} \
-DCPPTRACE_WERROR_BUILD=On \
-DCPPTRACE_BUILD_TESTING=On
ninja
./unittest
bash -c "exec -a u ./unittest"
unittest-windows:
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
compiler: [cl, clang++]
shared: [OFF] # TODO: Re-enable shared
build_type: [Debug, RelWithDebInfo]
steps:
- uses: actions/checkout@v4
- name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@v1.13.0
- name: build and test
run: |
mkdir build
cd build
cmake .. `
-DCMAKE_CXX_COMPILER=${{matrix.compiler}} `
-DCMAKE_C_COMPILER=${{matrix.compiler == 'clang++' && 'clang' || matrix.compiler}} `
-DBUILD_SHARED_LIBS=${{matrix.shared}} `
-DCPPTRACE_WERROR_BUILD=On `
-DCPPTRACE_BUILD_TESTING=On
cmake --build . --config ${{matrix.build_type}}
./${{matrix.build_type}}/unittest
# TODO: Macos, mingw

11
.gitignore vendored
View File

@ -1,8 +1,11 @@
.vscode
.vscode/
.idea/
a.out
build*/
repro*/
__pycache__
__pycache__/
scratch
.vscode
tmp
tmp/
bazel-*/
cmake-build-*/

34
BUILD.bazel Normal file
View File

@ -0,0 +1,34 @@
cc_library(
name = "cpptrace",
srcs = glob([
"src/**/*.hpp",
"src/**/*.cpp",
]),
local_defines = [
"CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF",
"CPPTRACE_DEMANGLE_WITH_CXXABI",
"CPPTRACE_UNWIND_WITH_LIBUNWIND"
],
hdrs = glob([
"include/cpptrace/*.hpp",
"include/ctrace/*.h",
]),
includes = [
"include",
"src"
],
deps = [
"@libdwarf//:libdwarf",
"@libunwind//:libunwind"
],
copts = [
"-Wall",
"-Wextra",
"-Werror=return-type",
"-Wundef",
"-Wuninitialized",
"-fPIC",
"-std=c++11"
],
visibility = ["//visibility:public"],
)

View File

@ -1,6 +1,16 @@
# Changelog
- [Changelog](#changelog)
- [v0.8.2](#v082)
- [v0.8.1](#v081)
- [v0.8.0](#v080)
- [v0.7.5](#v075)
- [v0.7.4](#v074)
- [v0.7.3](#v073)
- [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)
@ -18,6 +28,165 @@
- [v0.1.1](#v011)
- [v0.1](#v01)
# v0.8.2
Fixed:
- Fixed printing of internal error messages when an object file can't be loaded, mainly affecting MacOS https://github.com/jeremy-rifkin/cpptrace/issues/217
Other:
- Bumped zstd via FetchContent to 1.5.7
# v0.8.1
Fixed:
- Fixed compile error on msvc https://github.com/jeremy-rifkin/cpptrace/issues/215
Added:
- Added `cpptrace::can_get_safe_object_frame()`
Breaking changes:
- Renamed ctrace's `can_signal_safe_unwind` to `ctrace_can_signal_safe_unwind`. This was an oversight. Apologies for
including a breaking change in a patch release. Github code search suggests this API isn't used in public code, at
least.
Other:
- Added CI workflow to test on old msvc
- Made some internal improvements on robustness and cleanliness
# v0.8.0
Added:
- Added support for resolving symbols from elf and mach-o symbol tables, allowing function names to be resolved even in
a build that doesn't include debug information https://github.com/jeremy-rifkin/cpptrace/issues/201
- Added a configurable stack trace formatter https://github.com/jeremy-rifkin/cpptrace/issues/164
- Added configuration options for the libdwarf back-end that can be used to lower memory usage on memory-constrained
systems https://github.com/jeremy-rifkin/cpptrace/issues/193
- Added `cpptrace::nullable<T>::null_value`
- Made `cpptrace::nullable<T>` member functions conditionally `constexpr` where possible
Fixed:
- Fixed handling of `SymInitialize` when other code has already called `SymInitialize`. `SymInitialize` must only be
called once per handle and cpptrace now attempts to duplicate the current process handle to avoid conflicts.
https://github.com/jeremy-rifkin/cpptrace/issues/204
- Fixed a couple of locking edge cases surrounding dbghelp functions
- Fixed improper deallocation of `dwarf_errmsg` in the libdwarf back-end
Breaking changes:
- `cpptrace::get_snippet` previously included a newline at the end but it now does not. This also affects the behavior
of trace formatting with snippets enabled.
Other:
- Significantly improved memory usage and performance of the libdwarf back-end
- Improved implementation and organization of internal utility types, such as `optional` and `Result`
- Improved trace printing and formatting implementation
- Added unit tests for library internal utilities
- Added logic to the cxxabi demangler to ensure external names begin with `_Z` or `__Z` before attempting to demangle
- Added various internal tools and abstractions to improve maintainability and clarity
- Various internal improvements for robustness
- Added a small handful of utility tool programs that are useful for continued development, maintenance, and debugging
- Improved library CI setup
- Marked the `CPPTRACE_BUILD_BENCHMARK` option as advanced
# v0.7.5
Fixed:
- 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:

View File

@ -9,7 +9,7 @@ set(package_name "cpptrace")
project(
cpptrace
VERSION 0.6.2
VERSION 0.8.2
DESCRIPTION "Simple, portable, and self-contained stacktrace library for C++11 and newer "
HOMEPAGE_URL "https://github.com/jeremy-rifkin/cpptrace"
LANGUAGES C CXX
@ -24,9 +24,10 @@ include(CheckCXXSourceCompiles)
include(CheckCXXCompilerFlag)
if(PROJECT_IS_TOP_LEVEL)
find_program(CCACHE_FOUND ccache)
if(CCACHE_FOUND)
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE_PROGRAM})
set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE_PROGRAM})
endif()
endif()
@ -64,127 +65,23 @@ else()
set(CPPTRACE_BACKTRACE_PATH_DEFINITION "")
endif()
# =============================================== Platform Support ===============================================
function(check_support var source includes libraries definitions)
set(CMAKE_REQUIRED_INCLUDES "${includes}")
list(APPEND CMAKE_REQUIRED_INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
set(CMAKE_REQUIRED_LIBRARIES "${libraries}")
set(CMAKE_REQUIRED_DEFINITIONS "${definitions}")
string(CONCAT full_source "#include \"${source}\"" ${nonce})
check_cxx_source_compiles(${full_source} ${var})
set(${var} ${${var}} PARENT_SCOPE)
endfunction()
# ========================================== Platform Support and Auto-config ==========================================
include(cmake/Autoconfig.cmake)
if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
check_support(HAS_CXXABI has_cxxabi.cpp "" "" "")
endif()
# =================================================== Library Setup ====================================================
if(NOT WIN32)
check_support(HAS_UNWIND has_unwind.cpp "" "" "")
check_support(HAS_EXECINFO has_execinfo.cpp "" "" "")
check_support(HAS_BACKTRACE has_backtrace.cpp "" "backtrace" "${CPPTRACE_BACKTRACE_PATH_DEFINITION}")
set(STACKTRACE_LINK_LIB "stdc++_libbacktrace")
else()
check_support(HAS_STACKWALK has_stackwalk.cpp "" "dbghelp" "")
endif()
if(NOT WIN32 OR MINGW)
check_support(HAS_CXX_EXCEPTION_TYPE has_cxx_exception_type.cpp "" "" "")
endif()
if(UNIX AND NOT APPLE)
check_support(HAS_DL_FIND_OBJECT has_dl_find_object.cpp "" "dl" "")
if(NOT HAS_DL_FIND_OBJECT)
check_support(HAS_DLADDR1 has_dladdr1.cpp "" "dl" "")
endif()
endif()
# =============================================== Autoconfig unwinding ===============================================
# Unwind back-ends
if(
NOT (
CPPTRACE_UNWIND_WITH_UNWIND OR
CPPTRACE_UNWIND_WITH_LIBUNWIND OR
CPPTRACE_UNWIND_WITH_EXECINFO OR
CPPTRACE_UNWIND_WITH_WINAPI OR
CPPTRACE_UNWIND_WITH_DBGHELP OR
CPPTRACE_UNWIND_WITH_NOTHING
if(NOT CPPTRACE_BUILD_NO_SYMBOLS)
set(
debug
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-g>
$<$<CXX_COMPILER_ID:MSVC>:/DEBUG>
)
else()
add_compile_options($<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-g0>)
set(
debug
)
# Attempt to auto-config
if(UNIX)
if(HAS_UNWIND)
set(CPPTRACE_UNWIND_WITH_UNWIND On)
message(STATUS "Cpptrace auto config: Using libgcc unwind for unwinding")
elseif(HAS_EXECINFO)
set(CPPTRACE_UNWIND_WITH_EXECINFO On)
message(STATUS "Cpptrace auto config: Using execinfo.h for unwinding")
else()
set(CPPTRACE_UNWIND_WITH_NOTHING On)
message(FATAL_ERROR "Cpptrace auto config: No unwinding back-end seems to be supported, stack tracing will not work. To compile anyway set CPPTRACE_UNWIND_WITH_NOTHING.")
endif()
elseif(MINGW OR WIN32)
if(HAS_STACKWALK)
set(CPPTRACE_UNWIND_WITH_DBGHELP On)
message(STATUS "Cpptrace auto config: Using dbghelp for unwinding")
else()
set(CPPTRACE_UNWIND_WITH_WINAPI On)
message(STATUS "Cpptrace auto config: Using winapi for unwinding")
endif()
endif()
else()
#message(STATUS "MANUAL CONFIG SPECIFIED")
endif()
# =============================================== Autoconfig symbols ===============================================
if(
NOT (
CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE OR
CPPTRACE_GET_SYMBOLS_WITH_LIBDL OR
CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE OR
CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF OR
CPPTRACE_GET_SYMBOLS_WITH_DBGHELP OR
CPPTRACE_GET_SYMBOLS_WITH_NOTHING
)
)
if(UNIX)
message(STATUS "Cpptrace auto config: Using libdwarf for symbols")
set(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF On)
elseif(MINGW)
message(STATUS "Cpptrace auto config: Using libdwarf + dbghelp for symbols")
# Use both dbghelp and libdwarf under mingw: Some files may use pdb symbols, e.g. system dlls like KERNEL32.dll and
# ntdll.dll at the very least, but also other libraries linked with may have pdb symbols.
set(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF On)
set(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP On)
else()
message(STATUS "Cpptrace auto config: Using dbghelp for symbols")
set(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP On)
endif()
endif()
# =============================================== Autoconfig demangling ===============================================
# Handle demangle configuration
if(
NOT (
CPPTRACE_DEMANGLE_WITH_CXXABI OR
CPPTRACE_DEMANGLE_WITH_WINAPI OR
CPPTRACE_DEMANGLE_WITH_NOTHING
)
)
if(HAS_CXXABI)
message(STATUS "Cpptrace auto config: Using cxxabi for demangling")
set(CPPTRACE_DEMANGLE_WITH_CXXABI On)
elseif(WIN32 AND NOT MINGW)
message(STATUS "Cpptrace auto config: Using dbghelp for demangling")
set(CPPTRACE_DEMANGLE_WITH_WINAPI On)
else()
set(CPPTRACE_DEMANGLE_WITH_NOTHING On)
endif()
else()
#message(STATUS "Manual demangling back-end specified")
endif()
# =============================================== Now define the library ===============================================
# Target that we can modify (can't modify ALIAS targets)
# Target name should not be the same as ${PROJECT_NAME}, causes add_subdirectory issues
@ -214,11 +111,17 @@ target_sources(
src/binary/safe_dl.cpp
src/cpptrace.cpp
src/ctrace.cpp
src/exceptions.cpp
src/from_current.cpp
src/formatting.cpp
src/options.cpp
src/utils.cpp
src/demangle/demangle_with_cxxabi.cpp
src/demangle/demangle_with_nothing.cpp
src/demangle/demangle_with_winapi.cpp
src/snippets/snippet.cpp
src/symbols/dwarf/debug_map_resolver.cpp
src/symbols/dwarf/dwarf_options.cpp
src/symbols/dwarf/dwarf_resolver.cpp
src/symbols/symbols_core.cpp
src/symbols/symbols_with_addr2line.cpp
@ -233,6 +136,9 @@ target_sources(
src/unwind/unwind_with_nothing.cpp
src/unwind/unwind_with_unwind.cpp
src/unwind/unwind_with_winapi.cpp
src/utils/microfmt.cpp
src/utils/utils.cpp
src/platform/dbghelp_utils.cpp
)
target_include_directories(
@ -242,6 +148,12 @@ target_include_directories(
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/cpptrace/>
)
target_include_directories(
${target_name}
PRIVATE
src
)
set(
warning_options
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wall -Wextra -Werror=return-type -Wundef>
@ -264,6 +176,11 @@ target_compile_options(
${warning_options}
)
set(CPPTRACE_VERSION_MAJOR ${CMAKE_PROJECT_VERSION_MAJOR})
set(CPPTRACE_VERSION_MINOR ${CMAKE_PROJECT_VERSION_MINOR})
set(CPPTRACE_VERSION_PATCH ${CMAKE_PROJECT_VERSION_PATCH})
configure_file("${PROJECT_SOURCE_DIR}/cmake/in/version-hpp.in" "${PROJECT_BINARY_DIR}/include/cpptrace/version.hpp")
# ---- Generate Build Info Headers ----
if(build_type STREQUAL "STATIC")
@ -306,12 +223,24 @@ target_compile_features(
target_compile_definitions(${target_name} PRIVATE NOMINMAX)
if(HAS_ATTRIBUTE_PACKED)
target_compile_definitions(${target_name} PRIVATE HAS_ATTRIBUTE_PACKED)
endif()
if(NOT CPPTRACE_STD_FORMAT)
target_compile_definitions(${target_name} PUBLIC CPPTRACE_NO_STD_FORMAT)
endif()
if(CPPTRACE_UNPREFIXED_TRY_CATCH)
target_compile_definitions(${target_name} PUBLIC CPPTRACE_UNPREFIXED_TRY_CATCH)
endif()
if(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
SET(CMAKE_C_ARCHIVE_FINISH "<CMAKE_RANLIB> -no_warning_for_no_symbols -c <TARGET>")
SET(CMAKE_CXX_ARCHIVE_FINISH "<CMAKE_RANLIB> -no_warning_for_no_symbols -c <TARGET>")
endif()
# =============================================== Apply options to build ===============================================
# =================================================== Back-end setup ===================================================
if(HAS_CXX_EXCEPTION_TYPE)
target_compile_definitions(${target_name} PUBLIC CPPTRACE_HAS_CXX_EXCEPTION_TYPE)
@ -325,6 +254,10 @@ if(HAS_DLADDR1)
target_compile_definitions(${target_name} PUBLIC CPPTRACE_HAS_DLADDR1)
endif()
if(HAS_MACH_VM)
target_compile_definitions(${target_name} PUBLIC HAS_MACH_VM)
endif()
# Symbols
if(CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE)
if(NOT HAS_BACKTRACE)
@ -383,66 +316,53 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF)
find_package(zstd)
else()
cmake_policy(SET CMP0074 NEW)
FetchContent_Declare(
zstd
GIT_REPOSITORY https://github.com/facebook/zstd.git
GIT_TAG 63779c798237346c2b245c546c40b72a5a5913fe # v1.5.5
GIT_SHALLOW 1
SOURCE_SUBDIR build/cmake
)
# FetchContent_MakeAvailable(zstd)
FetchContent_GetProperties(zstd)
if(NOT zstd_POPULATED)
FetchContent_Populate(zstd)
set(ZSTD_BUILD_PROGRAMS OFF)
set(ZSTD_BUILD_CONTRIB OFF)
set(ZSTD_BUILD_TESTS OFF)
set(ZSTD_BUILD_STATIC ON)
set(ZSTD_BUILD_SHARED OFF)
set(ZSTD_LEGACY_SUPPORT OFF)
add_subdirectory("${zstd_SOURCE_DIR}/build/cmake" "${zstd_BINARY_DIR}")
endif()
FetchContent_Declare(
zstd
SOURCE_SUBDIR build/cmake
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
URL "${CPPTRACE_ZSTD_URL}"
)
FetchContent_MakeAvailable(zstd)
endif()
# Libdwarf itself
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
# set(PIC_ALWAYS TRUE)
# set(BUILD_DWARFDUMP FALSE)
FetchContent_Declare(
libdwarf
# GIT_REPOSITORY https://github.com/davea42/libdwarf-code.git
# GIT_TAG 6216e185863f41d6f19ab850caabfff7326020d7 # v0.8.0
# GIT_TAG 8b0bd09d8c77d45a68cb1bb00a54186a92b683d9 # v0.9.0
# GIT_TAG 8cdcc531f310d1c5ae61da469d8056bdd36b77e7 # v0.9.1 + some cmake changes
# Using a lightweight mirror that's optimized for clone + configure speed
# GIT_TAG ee53f0b6c99fc8cdaa3ae77af0196fb20e16177a # main 5.10.24
GIT_REPOSITORY https://github.com/jeremy-rifkin/libdwarf-lite.git
# GIT_TAG c78e984f3abbd20f6e01d6f51819e826b1691f65 # v0.8.0
# GIT_TAG 71090c680b4c943448ba87a0f1f864f174e4edda # v0.9.0
# GIT_TAG 5c0cb251f94b27e90184e6b2d9a0c9c62593babc # v0.9.1 + some cmake changes
GIT_TAG 87401f22cd05628d23059cb29ee6448a55c3a88a # v0.9.2
GIT_SHALLOW 1
)
# FetchContent_MakeAvailable(libdwarf)
FetchContent_GetProperties(libdwarf)
if(NOT libdwarf_POPULATED)
set(PIC_ALWAYS TRUE)
set(BUILD_DWARFDUMP FALSE)
# set(ENABLE_DECOMPRESSION FALSE)
FetchContent_Populate(libdwarf)
add_subdirectory("${libdwarf_SOURCE_DIR}" "${libdwarf_BINARY_DIR}")
FetchContent_Declare(
libdwarf
GIT_REPOSITORY ${CPPTRACE_LIBDWARF_REPO}
GIT_TAG ${CPPTRACE_LIBDWARF_TAG}
GIT_SHALLOW ${CPPTRACE_LIBDWARF_SHALLOW}
)
FetchContent_MakeAvailable(libdwarf)
target_include_directories(
dwarf
PRIVATE
${zstd_SOURCE_DIR}/lib
)
if(CPPTRACE_PROVIDE_EXPORT_SET_FOR_LIBDWARF)
export(
TARGETS dwarf
NAMESPACE libdwarf::
FILE "${PROJECT_BINARY_DIR}/libdwarf-targets.cmake"
)
endif()
endif()
if(CPPTRACE_CONAN)
set(dwarf_lib libdwarf::libdwarf)
target_link_libraries(${target_name} PRIVATE libdwarf::libdwarf)
elseif(CPPTRACE_VCPKG)
set(dwarf_lib libdwarf::dwarf)
target_link_libraries(${target_name} PRIVATE libdwarf::dwarf)
elseif(CPPTRACE_USE_EXTERNAL_LIBDWARF)
if(DEFINED LIBDWARF_LIBRARIES)
set(dwarf_lib ${LIBDWARF_LIBRARIES})
target_link_libraries(${target_name} PRIVATE ${LIBDWARF_LIBRARIES})
else()
# if LIBDWARF_LIBRARIES wasn't set by find_package, try looking for libdwarf::dwarf-static,
@ -460,6 +380,7 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF)
else()
message(FATAL_ERROR "Couldn't find libdwarf target name to link against")
endif()
set(dwarf_lib ${LIBDWARF_LIBRARIES})
target_link_libraries(${target_name} PRIVATE ${LIBDWARF_LIBRARIES})
endif()
# There seems to be no consistency at all about where libdwarf decides to place its headers........ Figure out if
@ -482,6 +403,7 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF)
message(FATAL_ERROR "Couldn't find libdwarf.h")
endif()
else()
set(dwarf_lib libdwarf::dwarf-static)
target_link_libraries(${target_name} PRIVATE libdwarf::dwarf-static)
endif()
if(UNIX)
@ -517,6 +439,7 @@ if(CPPTRACE_UNWIND_WITH_LIBUNWIND)
endif()
endif()
if(NOT libunwind_FOUND)
if (NOT APPLE)
# set_property(GLOBAL PROPERTY FIND_LIBRARY_USE_LIB64_PATHS ON)
# set_property(GLOBAL PROPERTY FIND_LIBRARY_USE_LIB32_PATHS ON)
find_path(LIBUNWIND_INCLUDE_DIRS NAMES "libunwind.h")
@ -537,6 +460,7 @@ if(CPPTRACE_UNWIND_WITH_LIBUNWIND)
target_compile_options(${target_name} PRIVATE ${LIBUNWIND_CFLAGS_OTHER})
target_include_directories(${target_name} PRIVATE ${LIBUNWIND_INCLUDE_DIRS})
target_link_libraries(${target_name} PRIVATE ${LIBUNWIND_LDFLAGS})
endif()
target_compile_definitions(${target_name} PUBLIC CPPTRACE_UNWIND_WITH_LIBUNWIND UNW_LOCAL_ONLY)
endif()
endif()
@ -589,13 +513,13 @@ if(NOT "${CPPTRACE_HARD_MAX_FRAMES}" STREQUAL "")
target_compile_definitions(${target_name} PUBLIC CPPTRACE_HARD_MAX_FRAMES=${CPPTRACE_HARD_MAX_FRAMES})
endif()
# =============================================== Install ===============================================
# ====================================================== Install =======================================================
if(NOT CMAKE_SKIP_INSTALL_RULES)
include(cmake/InstallRules.cmake)
endif()
# =============================================== Demo/test ===============================================
# ================================================== Demo/test/tools ===================================================
if(CPPTRACE_BUILD_TESTING)
if(PROJECT_IS_TOP_LEVEL)
@ -603,3 +527,11 @@ if(CPPTRACE_BUILD_TESTING)
endif()
add_subdirectory(test)
endif()
if(CPPTRACE_BUILD_BENCHMARKING)
add_subdirectory(benchmarking)
endif()
if(CPPTRACE_BUILD_TOOLS)
add_subdirectory(tools)
endif()

104
MODULE.bazel Normal file
View File

@ -0,0 +1,104 @@
module(
name = "cpptrace",
)
bazel_dep(name = "googletest", version = "1.14.0")
bazel_dep(name = "bazel_skylib", version = "1.7.1")
bazel_dep(name = "rules_foreign_cc", version = "0.11.1")
bazel_dep(name = "zstd", version = "1.5.6")
bazel_dep(name = "zlib", version = "1.3.1")
bazel_dep(name = "xz", version = "5.4.5.bcr.2")
bazel_dep(name = "toolchains_llvm", version = "1.1.2")
# Configure and register the toolchain.
llvm = use_extension("@toolchains_llvm//toolchain/extensions:llvm.bzl", "llvm", dev_dependency = True)
llvm.toolchain(
llvm_versions = {
"": "18.1.8",
},
sha256 = {
"": "54ec30358afcc9fb8aa74307db3046f5187f9fb89fb37064cdde906e062ebf36",
},
strip_prefix = {
"": "clang+llvm-18.1.8-x86_64-linux-gnu-ubuntu-18.04",
},
urls = {
"": ["https://github.com/llvm/llvm-project/releases/download/llvmorg-18.1.8/clang+llvm-18.1.8-x86_64-linux-gnu-ubuntu-18.04.tar.xz"],
},
)
use_repo(llvm, "llvm_toolchain")
register_toolchains("@llvm_toolchain//:all", dev_dependency = True)
http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "libdwarf",
build_file_content =
"""
package(default_visibility = ["//visibility:public"])
load("@rules_foreign_cc//foreign_cc:defs.bzl", "cmake")
filegroup(
name = "sources",
srcs = glob(["**/*"]),
)
cmake(
name = "libdwarf",
build_args = ["-j12"],
lib_source = ":sources",
out_static_libs = ["libdwarf.a"],
copts = ["-Wall", "-Werror"],
deps = [
"@zstd",
"@zlib"
]
)
""",
sha256 = "4ab8ae7b4b7aa42453725054b348f4fdb2460d5ba644199a1305311c718ff416",
strip_prefix = "libdwarf-code-0.10.1",
url = "https://github.com/davea42/libdwarf-code/archive/refs/tags/v0.10.1.tar.gz",
)
http_archive(
name = "libunwind",
build_file_content =
"""
package(default_visibility = ["//visibility:public"])
load("@rules_foreign_cc//foreign_cc:defs.bzl", "configure_make")
filegroup(
name = "sources",
srcs = glob(["**/*"]),
)
configure_make(
name = "libunwind",
args = ["-j12"],
autoreconf = True,
configure_in_place = True,
autoreconf_options = [
"-i",
],
lib_source = ":sources",
out_static_libs = [
"libunwind.a",
"libunwind-coredump.a",
"libunwind-ptrace.a",
"libunwind-x86_64.a",
"libunwind-generic.a",
"libunwind-setjmp.a"
],
deps = [
"@xz//:lzma"
]
)
""",
sha256 = "38833b7b1582db7d76485a62a213706c9252b3dab7380069fea5824e823d8e41",
strip_prefix = "libunwind-1.8.1",
url = "https://github.com/libunwind/libunwind/archive/refs/tags/v1.8.1.tar.gz",
)

851
MODULE.bazel.lock generated Normal file
View File

@ -0,0 +1,851 @@
{
"lockFileVersion": 11,
"registryFileHashes": {
"https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497",
"https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2",
"https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589",
"https://bcr.bazel.build/modules/abseil-cpp/20230125.1/MODULE.bazel": "89047429cb0207707b2dface14ba7f8df85273d484c2572755be4bab7ce9c3a0",
"https://bcr.bazel.build/modules/abseil-cpp/20230125.1/source.json": "06cc0842d241da0c5edc755edb3c7d0d008d304330e57ecf2d6449fb0b633a82",
"https://bcr.bazel.build/modules/apple_support/1.5.0/MODULE.bazel": "50341a62efbc483e8a2a6aec30994a58749bd7b885e18dd96aa8c33031e558ef",
"https://bcr.bazel.build/modules/apple_support/1.5.0/source.json": "eb98a7627c0bc486b57f598ad8da50f6625d974c8f723e9ea71bd39f709c9862",
"https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8",
"https://bcr.bazel.build/modules/bazel_features/1.11.0/source.json": "c9320aa53cd1c441d24bd6b716da087ad7e4ff0d9742a9884587596edfe53015",
"https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8",
"https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a",
"https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5",
"https://bcr.bazel.build/modules/bazel_skylib/1.4.1/MODULE.bazel": "a0dcb779424be33100dcae821e9e27e4f2901d9dfd5333efe5ac6a8d7ab75e1d",
"https://bcr.bazel.build/modules/bazel_skylib/1.5.0/MODULE.bazel": "32880f5e2945ce6a03d1fbd588e9198c0a959bb42297b2cfaf1685b7bc32e138",
"https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917",
"https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b",
"https://bcr.bazel.build/modules/bazel_skylib/1.7.1/source.json": "f121b43eeefc7c29efbd51b83d08631e2347297c95aac9764a701f2a6a2bb953",
"https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84",
"https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8",
"https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4",
"https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f",
"https://bcr.bazel.build/modules/googletest/1.14.0/source.json": "2478949479000fdd7de9a3d0107ba2c85bb5f961c3ecb1aa448f52549ce310b5",
"https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee",
"https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37",
"https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615",
"https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814",
"https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d",
"https://bcr.bazel.build/modules/platforms/0.0.9/MODULE.bazel": "4a87a60c927b56ddd67db50c89acaa62f4ce2a1d2149ccb63ffd871d5ce29ebc",
"https://bcr.bazel.build/modules/platforms/0.0.9/source.json": "cd74d854bf16a9e002fb2ca7b1a421f4403cda29f824a765acd3a8c56f8d43e6",
"https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7",
"https://bcr.bazel.build/modules/protobuf/21.7/source.json": "bbe500720421e582ff2d18b0802464205138c06056f443184de39fbb8187b09b",
"https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0",
"https://bcr.bazel.build/modules/protobuf/3.19.6/MODULE.bazel": "9233edc5e1f2ee276a60de3eaa47ac4132302ef9643238f23128fea53ea12858",
"https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647",
"https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c",
"https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f",
"https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e",
"https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5",
"https://bcr.bazel.build/modules/rules_cc/0.0.9/source.json": "1f1ba6fea244b616de4a554a0f4983c91a9301640c8fe0dd1d410254115c8430",
"https://bcr.bazel.build/modules/rules_foreign_cc/0.11.1/MODULE.bazel": "beeb0dd8d488d3cff57fa12ab3378051a7299aa9de2476d61c1d46f664d6398d",
"https://bcr.bazel.build/modules/rules_foreign_cc/0.11.1/source.json": "be2106be697115c10c03c6505a07bd4e259719c6608f08a61d600a560b8cf172",
"https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74",
"https://bcr.bazel.build/modules/rules_java/7.6.5/MODULE.bazel": "481164be5e02e4cab6e77a36927683263be56b7e36fef918b458d7a8a1ebadb1",
"https://bcr.bazel.build/modules/rules_java/7.6.5/source.json": "a805b889531d1690e3c72a7a7e47a870d00323186a9904b36af83aa3d053ee8d",
"https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7",
"https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/source.json": "a075731e1b46bc8425098512d038d416e966ab19684a10a34f4741295642fc35",
"https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0",
"https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d",
"https://bcr.bazel.build/modules/rules_license/0.0.7/source.json": "355cc5737a0f294e560d52b1b7a6492d4fff2caf0bef1a315df5a298fca2d34a",
"https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc",
"https://bcr.bazel.build/modules/rules_pkg/0.7.0/source.json": "c2557066e0c0342223ba592510ad3d812d4963b9024831f7f66fd0584dd8c66c",
"https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06",
"https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7",
"https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/source.json": "d57902c052424dfda0e71646cb12668d39c4620ee0544294d9d941e7d12bc3a9",
"https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f",
"https://bcr.bazel.build/modules/rules_python/0.22.1/MODULE.bazel": "26114f0c0b5e93018c0c066d6673f1a2c3737c7e90af95eff30cfee38d0bbac7",
"https://bcr.bazel.build/modules/rules_python/0.23.1/MODULE.bazel": "49ffccf0511cb8414de28321f5fcf2a31312b47c40cc21577144b7447f2bf300",
"https://bcr.bazel.build/modules/rules_python/0.23.1/source.json": "a6d9965700e3bd75df4e19140c0e651851bb720d8b9eb280ecd1ee44b92d7646",
"https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c",
"https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8",
"https://bcr.bazel.build/modules/stardoc/0.5.1/source.json": "a96f95e02123320aa015b956f29c00cb818fa891ef823d55148e1a362caacf29",
"https://bcr.bazel.build/modules/toolchains_llvm/1.1.2/MODULE.bazel": "402101d6f73115ec49a3a765a3361c1dd90ba3959fa688ccdcd465c36dbbbc52",
"https://bcr.bazel.build/modules/toolchains_llvm/1.1.2/source.json": "27f3cf531bc654c719b50411cac94613b7676d63e60962243d485af63e13b9ff",
"https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43",
"https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/source.json": "f1ef7d3f9e0e26d4b23d1c39b5f5de71f584dd7d1b4ef83d9bbba6ec7a6a6459",
"https://bcr.bazel.build/modules/xz/5.4.5.bcr.2/MODULE.bazel": "463976fb85f578a2535421ba4c38fe90657ab348e4b5d5404b75c061602705d0",
"https://bcr.bazel.build/modules/xz/5.4.5.bcr.2/source.json": "e735da8a3f396bf200ed06c585f670f7667e08c4e1ed2849bae7c2691bcb10cf",
"https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0",
"https://bcr.bazel.build/modules/zlib/1.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27",
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/MODULE.bazel": "af322bc08976524477c79d1e45e241b6efbeb918c497e8840b8ab116802dda79",
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/source.json": "2be409ac3c7601245958cd4fcdff4288be79ed23bd690b4b951f500d54ee6e7d",
"https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198",
"https://bcr.bazel.build/modules/zstd/1.5.6/MODULE.bazel": "471ebe7d3cdd8c6469390fcf623eb4779ff55fbee0a87f1dc57a1def468b96d4",
"https://bcr.bazel.build/modules/zstd/1.5.6/source.json": "02010c3333fc89b44fe861db049968decb6e688411f7f9d4f6791d74f9adfb51"
},
"selectedYankedVersions": {},
"moduleExtensions": {
"@@apple_support~//crosstool:setup.bzl%apple_cc_configure_extension": {
"general": {
"bzlTransitiveDigest": "PjIds3feoYE8SGbbIq2SFTZy3zmxeO2tQevJZNDo7iY=",
"usagesDigest": "aLmqbvowmHkkBPve05yyDNGN7oh7QE9kBADr3QIZTZs=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
"envVariables": {},
"generatedRepoSpecs": {
"local_config_apple_cc": {
"bzlFile": "@@apple_support~//crosstool:setup.bzl",
"ruleClassName": "_apple_cc_autoconf",
"attributes": {}
},
"local_config_apple_cc_toolchains": {
"bzlFile": "@@apple_support~//crosstool:setup.bzl",
"ruleClassName": "_apple_cc_autoconf_toolchains",
"attributes": {}
}
},
"recordedRepoMappingEntries": [
[
"apple_support~",
"bazel_tools",
"bazel_tools"
]
]
}
},
"@@platforms//host:extension.bzl%host_platform": {
"general": {
"bzlTransitiveDigest": "xelQcPZH8+tmuOHVjL9vDxMnnQNMlwj0SlvgoqBkm4U=",
"usagesDigest": "meSzxn3DUCcYEhq4HQwExWkWtU4EjriRBQLsZN+Q0SU=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
"envVariables": {},
"generatedRepoSpecs": {
"host_platform": {
"bzlFile": "@@platforms//host:extension.bzl",
"ruleClassName": "host_platform_repo",
"attributes": {}
}
},
"recordedRepoMappingEntries": []
}
},
"@@rules_foreign_cc~//foreign_cc:extensions.bzl%tools": {
"general": {
"bzlTransitiveDigest": "5Xt39wqg6Xufojy5gN4ke9V2Mv5ANvdeLlAL1hp6+ic=",
"usagesDigest": "WnYOMrYXMz7KcDu0mMpDP5Eue8sHuFMA1dmDT6I124Q=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
"envVariables": {},
"generatedRepoSpecs": {
"cmake-3.23.2-linux-aarch64": {
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
"ruleClassName": "http_archive",
"attributes": {
"urls": [
"https://github.com/Kitware/CMake/releases/download/v3.23.2/cmake-3.23.2-linux-aarch64.tar.gz"
],
"sha256": "f2654bf780b53f170bbbec44d8ac67d401d24788e590faa53036a89476efa91e",
"strip_prefix": "cmake-3.23.2-linux-aarch64",
"build_file_content": "load(\"@rules_foreign_cc//toolchains/native_tools:native_tools_toolchain.bzl\", \"native_tool_toolchain\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nfilegroup(\n name = \"cmake_bin\",\n srcs = [\"bin/cmake\"],\n)\n\nfilegroup(\n name = \"cmake_data\",\n srcs = glob(\n [\n \"**\",\n ],\n exclude = [\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"**/* *\",\n ],\n ),\n)\n\nnative_tool_toolchain(\n name = \"cmake_tool\",\n path = \"bin/cmake\",\n target = \":cmake_data\",\n env = {\"CMAKE\": \"$(execpath :cmake_bin)\"},\n tools = [\":cmake_bin\"],\n)\n"
}
},
"rules_foreign_cc_framework_toolchain_macos": {
"bzlFile": "@@rules_foreign_cc~//foreign_cc/private/framework:toolchain.bzl",
"ruleClassName": "framework_toolchain_repository",
"attributes": {
"commands_src": "@rules_foreign_cc//foreign_cc/private/framework/toolchains:macos_commands.bzl",
"exec_compatible_with": [
"@platforms//os:macos"
]
}
},
"ninja_1.12.0_mac": {
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
"ruleClassName": "http_archive",
"attributes": {
"urls": [
"https://github.com/ninja-build/ninja/releases/download/v1.12.0/ninja-mac.zip"
],
"sha256": "19806019c9623a062c3d9fa0d5f45b633a3d150f88e73fbd6c0ff6ea5534df10",
"strip_prefix": "",
"build_file_content": "load(\"@rules_foreign_cc//toolchains/native_tools:native_tools_toolchain.bzl\", \"native_tool_toolchain\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nfilegroup(\n name = \"ninja_bin\",\n srcs = [\"ninja\"],\n)\n\nnative_tool_toolchain(\n name = \"ninja_tool\",\n env = {\"NINJA\": \"$(execpath :ninja_bin)\"},\n path = \"$(execpath :ninja_bin)\",\n target = \":ninja_bin\",\n)\n"
}
},
"ninja_1.12.0_mac_aarch64": {
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
"ruleClassName": "http_archive",
"attributes": {
"urls": [
"https://github.com/ninja-build/ninja/releases/download/v1.12.0/ninja-mac.zip"
],
"sha256": "19806019c9623a062c3d9fa0d5f45b633a3d150f88e73fbd6c0ff6ea5534df10",
"strip_prefix": "",
"build_file_content": "load(\"@rules_foreign_cc//toolchains/native_tools:native_tools_toolchain.bzl\", \"native_tool_toolchain\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nfilegroup(\n name = \"ninja_bin\",\n srcs = [\"ninja\"],\n)\n\nnative_tool_toolchain(\n name = \"ninja_tool\",\n env = {\"NINJA\": \"$(execpath :ninja_bin)\"},\n path = \"$(execpath :ninja_bin)\",\n target = \":ninja_bin\",\n)\n"
}
},
"gnumake_src": {
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
"ruleClassName": "http_archive",
"attributes": {
"build_file_content": "filegroup(\n name = \"all_srcs\",\n srcs = glob([\"**\"]),\n visibility = [\"//visibility:public\"],\n)\n",
"sha256": "dd16fb1d67bfab79a72f5e8390735c49e3e8e70b4945a15ab1f81ddb78658fb3",
"strip_prefix": "make-4.4.1",
"urls": [
"https://mirror.bazel.build/ftpmirror.gnu.org/gnu/make/make-4.4.1.tar.gz",
"http://ftpmirror.gnu.org/gnu/make/make-4.4.1.tar.gz"
]
}
},
"gettext_runtime": {
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
"ruleClassName": "http_archive",
"attributes": {
"build_file_content": "\ncc_import(\n name = \"gettext_runtime\",\n shared_library = \"bin/libintl-8.dll\",\n visibility = [\"//visibility:public\"],\n)\n ",
"sha256": "1f4269c0e021076d60a54e98da6f978a3195013f6de21674ba0edbc339c5b079",
"urls": [
"https://mirror.bazel.build/download.gnome.org/binaries/win64/dependencies/gettext-runtime_0.18.1.1-2_win64.zip",
"https://download.gnome.org/binaries/win64/dependencies/gettext-runtime_0.18.1.1-2_win64.zip"
]
}
},
"cmake_src": {
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
"ruleClassName": "http_archive",
"attributes": {
"build_file_content": "filegroup(\n name = \"all_srcs\",\n srcs = glob([\"**\"]),\n visibility = [\"//visibility:public\"],\n)\n",
"sha256": "f316b40053466f9a416adf981efda41b160ca859e97f6a484b447ea299ff26aa",
"strip_prefix": "cmake-3.23.2",
"urls": [
"https://github.com/Kitware/CMake/releases/download/v3.23.2/cmake-3.23.2.tar.gz"
],
"patches": [
"@@rules_foreign_cc~//toolchains:cmake-c++11.patch"
]
}
},
"bazel_skylib": {
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
"ruleClassName": "http_archive",
"attributes": {
"urls": [
"https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.2.1/bazel-skylib-1.2.1.tar.gz",
"https://github.com/bazelbuild/bazel-skylib/releases/download/1.2.1/bazel-skylib-1.2.1.tar.gz"
],
"sha256": "f7be3474d42aae265405a592bb7da8e171919d74c16f082a5457840f06054728"
}
},
"cmake-3.23.2-macos-universal": {
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
"ruleClassName": "http_archive",
"attributes": {
"urls": [
"https://github.com/Kitware/CMake/releases/download/v3.23.2/cmake-3.23.2-macos-universal.tar.gz"
],
"sha256": "853a0f9af148c5ef47282ffffee06c4c9f257be2635936755f39ca13c3286c88",
"strip_prefix": "cmake-3.23.2-macos-universal/CMake.app/Contents",
"build_file_content": "load(\"@rules_foreign_cc//toolchains/native_tools:native_tools_toolchain.bzl\", \"native_tool_toolchain\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nfilegroup(\n name = \"cmake_bin\",\n srcs = [\"bin/cmake\"],\n)\n\nfilegroup(\n name = \"cmake_data\",\n srcs = glob(\n [\n \"**\",\n ],\n exclude = [\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"**/* *\",\n ],\n ),\n)\n\nnative_tool_toolchain(\n name = \"cmake_tool\",\n path = \"bin/cmake\",\n target = \":cmake_data\",\n env = {\"CMAKE\": \"$(execpath :cmake_bin)\"},\n tools = [\":cmake_bin\"],\n)\n"
}
},
"ninja_1.12.0_win": {
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
"ruleClassName": "http_archive",
"attributes": {
"urls": [
"https://github.com/ninja-build/ninja/releases/download/v1.12.0/ninja-win.zip"
],
"sha256": "51d99be9ceea8835edf536d52d47fa4c316aa332e57f71a08df5bd059da11417",
"strip_prefix": "",
"build_file_content": "load(\"@rules_foreign_cc//toolchains/native_tools:native_tools_toolchain.bzl\", \"native_tool_toolchain\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nfilegroup(\n name = \"ninja_bin\",\n srcs = [\"ninja.exe\"],\n)\n\nnative_tool_toolchain(\n name = \"ninja_tool\",\n env = {\"NINJA\": \"$(execpath :ninja_bin)\"},\n path = \"$(execpath :ninja_bin)\",\n target = \":ninja_bin\",\n)\n"
}
},
"meson_src": {
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
"ruleClassName": "http_archive",
"attributes": {
"build_file_content": "exports_files([\"meson.py\"])\n\nfilegroup(\n name = \"runtime\",\n srcs = glob([\"mesonbuild/**\"]),\n visibility = [\"//visibility:public\"],\n)\n",
"sha256": "d04b541f97ca439fb82fab7d0d480988be4bd4e62563a5ca35fadb5400727b1c",
"strip_prefix": "meson-1.1.1",
"urls": [
"https://mirror.bazel.build/github.com/mesonbuild/meson/releases/download/1.1.1/meson-1.1.1.tar.gz",
"https://github.com/mesonbuild/meson/releases/download/1.1.1/meson-1.1.1.tar.gz"
]
}
},
"rules_foreign_cc_framework_toolchain_freebsd": {
"bzlFile": "@@rules_foreign_cc~//foreign_cc/private/framework:toolchain.bzl",
"ruleClassName": "framework_toolchain_repository",
"attributes": {
"commands_src": "@rules_foreign_cc//foreign_cc/private/framework/toolchains:freebsd_commands.bzl",
"exec_compatible_with": [
"@platforms//os:freebsd"
]
}
},
"rules_foreign_cc_framework_toolchain_linux": {
"bzlFile": "@@rules_foreign_cc~//foreign_cc/private/framework:toolchain.bzl",
"ruleClassName": "framework_toolchain_repository",
"attributes": {
"commands_src": "@rules_foreign_cc//foreign_cc/private/framework/toolchains:linux_commands.bzl",
"exec_compatible_with": [
"@platforms//os:linux"
]
}
},
"rules_python": {
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
"ruleClassName": "http_archive",
"attributes": {
"sha256": "84aec9e21cc56fbc7f1335035a71c850d1b9b5cc6ff497306f84cced9a769841",
"strip_prefix": "rules_python-0.23.1",
"url": "https://github.com/bazelbuild/rules_python/archive/refs/tags/0.23.1.tar.gz"
}
},
"pkgconfig_src": {
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
"ruleClassName": "http_archive",
"attributes": {
"build_file_content": "filegroup(\n name = \"all_srcs\",\n srcs = glob([\"**\"]),\n visibility = [\"//visibility:public\"],\n)\n",
"sha256": "6fc69c01688c9458a57eb9a1664c9aba372ccda420a02bf4429fe610e7e7d591",
"strip_prefix": "pkg-config-0.29.2",
"patches": [
"@@rules_foreign_cc~//toolchains:pkgconfig-detectenv.patch",
"@@rules_foreign_cc~//toolchains:pkgconfig-makefile-vc.patch",
"@@rules_foreign_cc~//toolchains:pkgconfig-builtin-glib-int-conversion.patch"
],
"urls": [
"https://pkgconfig.freedesktop.org/releases/pkg-config-0.29.2.tar.gz",
"https://mirror.bazel.build/pkgconfig.freedesktop.org/releases/pkg-config-0.29.2.tar.gz"
]
}
},
"ninja_build_src": {
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
"ruleClassName": "http_archive",
"attributes": {
"build_file_content": "filegroup(\n name = \"all_srcs\",\n srcs = glob([\"**\"]),\n visibility = [\"//visibility:public\"],\n)\n",
"integrity": "sha256-iyyGzUg9x/y3l1xexzKRNdIQCZqJvH2wWQoHsLv+SaU=",
"strip_prefix": "ninja-1.12.0",
"urls": [
"https://mirror.bazel.build/github.com/ninja-build/ninja/archive/v1.12.0.tar.gz",
"https://github.com/ninja-build/ninja/archive/v1.12.0.tar.gz"
]
}
},
"glib_src": {
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
"ruleClassName": "http_archive",
"attributes": {
"build_file_content": "\ncc_import(\n name = \"msvc_hdr\",\n hdrs = [\"msvc_recommended_pragmas.h\"],\n visibility = [\"//visibility:public\"],\n)\n ",
"sha256": "bc96f63112823b7d6c9f06572d2ad626ddac7eb452c04d762592197f6e07898e",
"strip_prefix": "glib-2.26.1",
"urls": [
"https://mirror.bazel.build/download.gnome.org/sources/glib/2.26/glib-2.26.1.tar.gz",
"https://download.gnome.org/sources/glib/2.26/glib-2.26.1.tar.gz"
]
}
},
"cmake-3.23.2-windows-x86_64": {
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
"ruleClassName": "http_archive",
"attributes": {
"urls": [
"https://github.com/Kitware/CMake/releases/download/v3.23.2/cmake-3.23.2-windows-x86_64.zip"
],
"sha256": "2329387f3166b84c25091c86389fb891193967740c9bcf01e7f6d3306f7ffda0",
"strip_prefix": "cmake-3.23.2-windows-x86_64",
"build_file_content": "load(\"@rules_foreign_cc//toolchains/native_tools:native_tools_toolchain.bzl\", \"native_tool_toolchain\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nfilegroup(\n name = \"cmake_bin\",\n srcs = [\"bin/cmake.exe\"],\n)\n\nfilegroup(\n name = \"cmake_data\",\n srcs = glob(\n [\n \"**\",\n ],\n exclude = [\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"**/* *\",\n ],\n ),\n)\n\nnative_tool_toolchain(\n name = \"cmake_tool\",\n path = \"bin/cmake.exe\",\n target = \":cmake_data\",\n env = {\"CMAKE\": \"$(execpath :cmake_bin)\"},\n tools = [\":cmake_bin\"],\n)\n"
}
},
"glib_runtime": {
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
"ruleClassName": "http_archive",
"attributes": {
"build_file_content": "\nexports_files(\n [\n \"bin/libgio-2.0-0.dll\",\n \"bin/libglib-2.0-0.dll\",\n \"bin/libgmodule-2.0-0.dll\",\n \"bin/libgobject-2.0-0.dll\",\n \"bin/libgthread-2.0-0.dll\",\n ],\n visibility = [\"//visibility:public\"],\n)\n ",
"sha256": "88d857087e86f16a9be651ee7021880b3f7ba050d34a1ed9f06113b8799cb973",
"urls": [
"https://mirror.bazel.build/download.gnome.org/binaries/win64/glib/2.26/glib_2.26.1-1_win64.zip",
"https://download.gnome.org/binaries/win64/glib/2.26/glib_2.26.1-1_win64.zip"
]
}
},
"rules_foreign_cc_framework_toolchains": {
"bzlFile": "@@rules_foreign_cc~//foreign_cc/private/framework:toolchain.bzl",
"ruleClassName": "framework_toolchain_repository_hub",
"attributes": {}
},
"glib_dev": {
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
"ruleClassName": "http_archive",
"attributes": {
"build_file_content": "\nload(\"@rules_cc//cc:defs.bzl\", \"cc_library\")\n\ncc_import(\n name = \"glib_dev\",\n hdrs = glob([\"include/**\"]),\n shared_library = \"@glib_runtime//:bin/libglib-2.0-0.dll\",\n visibility = [\"//visibility:public\"],\n)\n ",
"sha256": "bdf18506df304d38be98a4b3f18055b8b8cca81beabecad0eece6ce95319c369",
"urls": [
"https://mirror.bazel.build/download.gnome.org/binaries/win64/glib/2.26/glib-dev_2.26.1-1_win64.zip",
"https://download.gnome.org/binaries/win64/glib/2.26/glib-dev_2.26.1-1_win64.zip"
]
}
},
"cmake_3.23.2_toolchains": {
"bzlFile": "@@rules_foreign_cc~//toolchains:prebuilt_toolchains_repository.bzl",
"ruleClassName": "prebuilt_toolchains_repository",
"attributes": {
"repos": {
"cmake-3.23.2-linux-aarch64": [
"@platforms//cpu:aarch64",
"@platforms//os:linux"
],
"cmake-3.23.2-linux-x86_64": [
"@platforms//cpu:x86_64",
"@platforms//os:linux"
],
"cmake-3.23.2-macos-universal": [
"@platforms//os:macos"
],
"cmake-3.23.2-windows-i386": [
"@platforms//cpu:x86_32",
"@platforms//os:windows"
],
"cmake-3.23.2-windows-x86_64": [
"@platforms//cpu:x86_64",
"@platforms//os:windows"
]
},
"tool": "cmake"
}
},
"ninja_1.12.0_linux-aarch64": {
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
"ruleClassName": "http_archive",
"attributes": {
"urls": [
"https://github.com/ninja-build/ninja/releases/download/v1.12.0/ninja-linux-aarch64.zip"
],
"sha256": "375a49c79095334c88338ff15f90730e08a4d03997ef660f48f11ee7e450db7a",
"strip_prefix": "",
"build_file_content": "load(\"@rules_foreign_cc//toolchains/native_tools:native_tools_toolchain.bzl\", \"native_tool_toolchain\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nfilegroup(\n name = \"ninja_bin\",\n srcs = [\"ninja\"],\n)\n\nnative_tool_toolchain(\n name = \"ninja_tool\",\n env = {\"NINJA\": \"$(execpath :ninja_bin)\"},\n path = \"$(execpath :ninja_bin)\",\n target = \":ninja_bin\",\n)\n"
}
},
"cmake-3.23.2-windows-i386": {
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
"ruleClassName": "http_archive",
"attributes": {
"urls": [
"https://github.com/Kitware/CMake/releases/download/v3.23.2/cmake-3.23.2-windows-i386.zip"
],
"sha256": "6a4fcd6a2315b93cb23c93507efccacc30c449c2bf98f14d6032bb226c582e07",
"strip_prefix": "cmake-3.23.2-windows-i386",
"build_file_content": "load(\"@rules_foreign_cc//toolchains/native_tools:native_tools_toolchain.bzl\", \"native_tool_toolchain\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nfilegroup(\n name = \"cmake_bin\",\n srcs = [\"bin/cmake.exe\"],\n)\n\nfilegroup(\n name = \"cmake_data\",\n srcs = glob(\n [\n \"**\",\n ],\n exclude = [\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"**/* *\",\n ],\n ),\n)\n\nnative_tool_toolchain(\n name = \"cmake_tool\",\n path = \"bin/cmake.exe\",\n target = \":cmake_data\",\n env = {\"CMAKE\": \"$(execpath :cmake_bin)\"},\n tools = [\":cmake_bin\"],\n)\n"
}
},
"ninja_1.12.0_linux": {
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
"ruleClassName": "http_archive",
"attributes": {
"urls": [
"https://github.com/ninja-build/ninja/releases/download/v1.12.0/ninja-linux.zip"
],
"sha256": "ddc96efa3c7c9d41de733d15e2eda07a8a212555cb43f35d727e080d2ca687ab",
"strip_prefix": "",
"build_file_content": "load(\"@rules_foreign_cc//toolchains/native_tools:native_tools_toolchain.bzl\", \"native_tool_toolchain\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nfilegroup(\n name = \"ninja_bin\",\n srcs = [\"ninja\"],\n)\n\nnative_tool_toolchain(\n name = \"ninja_tool\",\n env = {\"NINJA\": \"$(execpath :ninja_bin)\"},\n path = \"$(execpath :ninja_bin)\",\n target = \":ninja_bin\",\n)\n"
}
},
"cmake-3.23.2-linux-x86_64": {
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
"ruleClassName": "http_archive",
"attributes": {
"urls": [
"https://github.com/Kitware/CMake/releases/download/v3.23.2/cmake-3.23.2-linux-x86_64.tar.gz"
],
"sha256": "aaced6f745b86ce853661a595bdac6c5314a60f8181b6912a0a4920acfa32708",
"strip_prefix": "cmake-3.23.2-linux-x86_64",
"build_file_content": "load(\"@rules_foreign_cc//toolchains/native_tools:native_tools_toolchain.bzl\", \"native_tool_toolchain\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nfilegroup(\n name = \"cmake_bin\",\n srcs = [\"bin/cmake\"],\n)\n\nfilegroup(\n name = \"cmake_data\",\n srcs = glob(\n [\n \"**\",\n ],\n exclude = [\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"**/* *\",\n ],\n ),\n)\n\nnative_tool_toolchain(\n name = \"cmake_tool\",\n path = \"bin/cmake\",\n target = \":cmake_data\",\n env = {\"CMAKE\": \"$(execpath :cmake_bin)\"},\n tools = [\":cmake_bin\"],\n)\n"
}
},
"ninja_1.12.0_toolchains": {
"bzlFile": "@@rules_foreign_cc~//toolchains:prebuilt_toolchains_repository.bzl",
"ruleClassName": "prebuilt_toolchains_repository",
"attributes": {
"repos": {
"ninja_1.12.0_linux": [
"@platforms//cpu:x86_64",
"@platforms//os:linux"
],
"ninja_1.12.0_linux-aarch64": [
"@platforms//cpu:aarch64",
"@platforms//os:linux"
],
"ninja_1.12.0_mac": [
"@platforms//cpu:x86_64",
"@platforms//os:macos"
],
"ninja_1.12.0_mac_aarch64": [
"@platforms//cpu:aarch64",
"@platforms//os:macos"
],
"ninja_1.12.0_win": [
"@platforms//cpu:x86_64",
"@platforms//os:windows"
]
},
"tool": "ninja"
}
},
"rules_foreign_cc_framework_toolchain_windows": {
"bzlFile": "@@rules_foreign_cc~//foreign_cc/private/framework:toolchain.bzl",
"ruleClassName": "framework_toolchain_repository",
"attributes": {
"commands_src": "@rules_foreign_cc//foreign_cc/private/framework/toolchains:windows_commands.bzl",
"exec_compatible_with": [
"@platforms//os:windows"
]
}
}
},
"recordedRepoMappingEntries": [
[
"rules_foreign_cc~",
"bazel_tools",
"bazel_tools"
],
[
"rules_foreign_cc~",
"rules_foreign_cc",
"rules_foreign_cc~"
]
]
}
},
"@@rules_python~//python/extensions:python.bzl%python": {
"general": {
"bzlTransitiveDigest": "XaaZIw4dO4l6naftU5IBdrfCE1mOmelaT/Sq9uyBnhs=",
"usagesDigest": "XRXGQ1YSlgZzzO0pux+3DEHfP/c70L/kznvRIwakvlw=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
"envVariables": {},
"generatedRepoSpecs": {
"python_3_11_aarch64-unknown-linux-gnu": {
"bzlFile": "@@rules_python~//python:repositories.bzl",
"ruleClassName": "python_repository",
"attributes": {
"sha256": "debf15783bdcb5530504f533d33fda75a7b905cec5361ae8f33da5ba6599f8b4",
"patches": [],
"platform": "aarch64-unknown-linux-gnu",
"python_version": "3.11.1",
"release_filename": "20230116/cpython-3.11.1+20230116-aarch64-unknown-linux-gnu-install_only.tar.gz",
"urls": [
"https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1+20230116-aarch64-unknown-linux-gnu-install_only.tar.gz"
],
"distutils_content": "",
"strip_prefix": "python",
"ignore_root_user_error": false
}
},
"python_3_9": {
"bzlFile": "@@rules_python~//python/private:toolchains_repo.bzl",
"ruleClassName": "toolchain_aliases",
"attributes": {
"python_version": "3.9.16",
"user_repository_name": "python_3_9"
}
},
"python_3_11_aarch64-apple-darwin": {
"bzlFile": "@@rules_python~//python:repositories.bzl",
"ruleClassName": "python_repository",
"attributes": {
"sha256": "4918cdf1cab742a90f85318f88b8122aeaa2d04705803c7b6e78e81a3dd40f80",
"patches": [],
"platform": "aarch64-apple-darwin",
"python_version": "3.11.1",
"release_filename": "20230116/cpython-3.11.1+20230116-aarch64-apple-darwin-install_only.tar.gz",
"urls": [
"https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1+20230116-aarch64-apple-darwin-install_only.tar.gz"
],
"distutils_content": "",
"strip_prefix": "python",
"ignore_root_user_error": false
}
},
"pythons_hub": {
"bzlFile": "@@rules_python~//python/extensions/private:pythons_hub.bzl",
"ruleClassName": "hub_repo",
"attributes": {
"toolchain_prefixes": [
"_0000_python_3_9_",
"_0001_python_3_11_"
],
"toolchain_python_versions": [
"3.9",
"3.11"
],
"toolchain_set_python_version_constraints": [
"True",
"False"
],
"toolchain_user_repository_names": [
"python_3_9",
"python_3_11"
]
}
},
"python_3_11_x86_64-pc-windows-msvc": {
"bzlFile": "@@rules_python~//python:repositories.bzl",
"ruleClassName": "python_repository",
"attributes": {
"sha256": "edc08979cb0666a597466176511529c049a6f0bba8adf70df441708f766de5bf",
"patches": [],
"platform": "x86_64-pc-windows-msvc",
"python_version": "3.11.1",
"release_filename": "20230116/cpython-3.11.1+20230116-x86_64-pc-windows-msvc-shared-install_only.tar.gz",
"urls": [
"https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1+20230116-x86_64-pc-windows-msvc-shared-install_only.tar.gz"
],
"distutils_content": "",
"strip_prefix": "python",
"ignore_root_user_error": false
}
},
"python_3_9_aarch64-apple-darwin": {
"bzlFile": "@@rules_python~//python:repositories.bzl",
"ruleClassName": "python_repository",
"attributes": {
"sha256": "c1de1d854717a6245f45262ef1bb17b09e2c587590e7e3f406593c143ff875bd",
"patches": [],
"platform": "aarch64-apple-darwin",
"python_version": "3.9.16",
"release_filename": "20230507/cpython-3.9.16+20230507-aarch64-apple-darwin-install_only.tar.gz",
"urls": [
"https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16+20230507-aarch64-apple-darwin-install_only.tar.gz"
],
"distutils_content": "",
"strip_prefix": "python",
"ignore_root_user_error": false
}
},
"python_3_9_x86_64-pc-windows-msvc": {
"bzlFile": "@@rules_python~//python:repositories.bzl",
"ruleClassName": "python_repository",
"attributes": {
"sha256": "cdabb47204e96ce7ea31fbd0b5ed586114dd7d8f8eddf60a509a7f70b48a1c5e",
"patches": [],
"platform": "x86_64-pc-windows-msvc",
"python_version": "3.9.16",
"release_filename": "20230507/cpython-3.9.16+20230507-x86_64-pc-windows-msvc-shared-install_only.tar.gz",
"urls": [
"https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16+20230507-x86_64-pc-windows-msvc-shared-install_only.tar.gz"
],
"distutils_content": "",
"strip_prefix": "python",
"ignore_root_user_error": false
}
},
"python_3_9_ppc64le-unknown-linux-gnu": {
"bzlFile": "@@rules_python~//python:repositories.bzl",
"ruleClassName": "python_repository",
"attributes": {
"sha256": "ff3ac35c58f67839aff9b5185a976abd3d1abbe61af02089f7105e876c1fe284",
"patches": [],
"platform": "ppc64le-unknown-linux-gnu",
"python_version": "3.9.16",
"release_filename": "20230507/cpython-3.9.16+20230507-ppc64le-unknown-linux-gnu-install_only.tar.gz",
"urls": [
"https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16+20230507-ppc64le-unknown-linux-gnu-install_only.tar.gz"
],
"distutils_content": "",
"strip_prefix": "python",
"ignore_root_user_error": false
}
},
"python_3_9_aarch64-unknown-linux-gnu": {
"bzlFile": "@@rules_python~//python:repositories.bzl",
"ruleClassName": "python_repository",
"attributes": {
"sha256": "f629b75ebfcafe9ceee2e796b7e4df5cf8dbd14f3c021afca078d159ab797acf",
"patches": [],
"platform": "aarch64-unknown-linux-gnu",
"python_version": "3.9.16",
"release_filename": "20230507/cpython-3.9.16+20230507-aarch64-unknown-linux-gnu-install_only.tar.gz",
"urls": [
"https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16+20230507-aarch64-unknown-linux-gnu-install_only.tar.gz"
],
"distutils_content": "",
"strip_prefix": "python",
"ignore_root_user_error": false
}
},
"python_aliases": {
"bzlFile": "@@rules_python~//python/private:toolchains_repo.bzl",
"ruleClassName": "multi_toolchain_aliases",
"attributes": {
"python_versions": {
"3.9": "python_3_9",
"3.11": "python_3_11"
}
}
},
"python_3_11": {
"bzlFile": "@@rules_python~//python/private:toolchains_repo.bzl",
"ruleClassName": "toolchain_aliases",
"attributes": {
"python_version": "3.11.1",
"user_repository_name": "python_3_11"
}
},
"python_3_11_x86_64-apple-darwin": {
"bzlFile": "@@rules_python~//python:repositories.bzl",
"ruleClassName": "python_repository",
"attributes": {
"sha256": "20a4203d069dc9b710f70b09e7da2ce6f473d6b1110f9535fb6f4c469ed54733",
"patches": [],
"platform": "x86_64-apple-darwin",
"python_version": "3.11.1",
"release_filename": "20230116/cpython-3.11.1+20230116-x86_64-apple-darwin-install_only.tar.gz",
"urls": [
"https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1+20230116-x86_64-apple-darwin-install_only.tar.gz"
],
"distutils_content": "",
"strip_prefix": "python",
"ignore_root_user_error": false
}
},
"python_3_9_x86_64-apple-darwin": {
"bzlFile": "@@rules_python~//python:repositories.bzl",
"ruleClassName": "python_repository",
"attributes": {
"sha256": "3abc4d5fbbc80f5f848f280927ac5d13de8dc03aabb6ae65d8247cbb68e6f6bf",
"patches": [],
"platform": "x86_64-apple-darwin",
"python_version": "3.9.16",
"release_filename": "20230507/cpython-3.9.16+20230507-x86_64-apple-darwin-install_only.tar.gz",
"urls": [
"https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16+20230507-x86_64-apple-darwin-install_only.tar.gz"
],
"distutils_content": "",
"strip_prefix": "python",
"ignore_root_user_error": false
}
},
"python_3_9_x86_64-unknown-linux-gnu": {
"bzlFile": "@@rules_python~//python:repositories.bzl",
"ruleClassName": "python_repository",
"attributes": {
"sha256": "2b6e146234a4ef2a8946081fc3fbfffe0765b80b690425a49ebe40b47c33445b",
"patches": [],
"platform": "x86_64-unknown-linux-gnu",
"python_version": "3.9.16",
"release_filename": "20230507/cpython-3.9.16+20230507-x86_64-unknown-linux-gnu-install_only.tar.gz",
"urls": [
"https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16+20230507-x86_64-unknown-linux-gnu-install_only.tar.gz"
],
"distutils_content": "",
"strip_prefix": "python",
"ignore_root_user_error": false
}
},
"python_3_11_x86_64-unknown-linux-gnu": {
"bzlFile": "@@rules_python~//python:repositories.bzl",
"ruleClassName": "python_repository",
"attributes": {
"sha256": "02a551fefab3750effd0e156c25446547c238688a32fabde2995c941c03a6423",
"patches": [],
"platform": "x86_64-unknown-linux-gnu",
"python_version": "3.11.1",
"release_filename": "20230116/cpython-3.11.1+20230116-x86_64-unknown-linux-gnu-install_only.tar.gz",
"urls": [
"https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1+20230116-x86_64-unknown-linux-gnu-install_only.tar.gz"
],
"distutils_content": "",
"strip_prefix": "python",
"ignore_root_user_error": false
}
}
},
"recordedRepoMappingEntries": [
[
"rules_python~",
"bazel_tools",
"bazel_tools"
]
]
}
},
"@@toolchains_llvm~//toolchain/extensions:llvm.bzl%llvm": {
"general": {
"bzlTransitiveDigest": "y9h5L2NtWbogyWSOJgqnUaU50MTPWAW+waelXSirMVg=",
"usagesDigest": "cWIs+RUBBwv1tE2cNVULmSLCA/wGvHWaQzdrujSEO74=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
"envVariables": {},
"generatedRepoSpecs": {
"llvm_toolchain": {
"bzlFile": "@@toolchains_llvm~//toolchain:rules.bzl",
"ruleClassName": "toolchain",
"attributes": {
"absolute_paths": false,
"archive_flags": {},
"compile_flags": {},
"coverage_compile_flags": {},
"coverage_link_flags": {},
"cxx_builtin_include_directories": {},
"cxx_flags": {},
"cxx_standard": {},
"dbg_compile_flags": {},
"exec_arch": "",
"exec_os": "",
"link_flags": {},
"link_libs": {},
"llvm_versions": {
"": "18.1.8"
},
"opt_compile_flags": {},
"opt_link_flags": {},
"stdlib": {},
"target_settings": {},
"unfiltered_compile_flags": {},
"toolchain_roots": {},
"sysroot": {}
}
},
"llvm_toolchain_llvm": {
"bzlFile": "@@toolchains_llvm~//toolchain:rules.bzl",
"ruleClassName": "llvm",
"attributes": {
"alternative_llvm_sources": [],
"auth_patterns": {},
"distribution": "auto",
"exec_arch": "",
"exec_os": "",
"llvm_mirror": "",
"llvm_version": "",
"llvm_versions": {
"": "18.1.8"
},
"netrc": "",
"sha256": {
"": "54ec30358afcc9fb8aa74307db3046f5187f9fb89fb37064cdde906e062ebf36"
},
"strip_prefix": {
"": "clang+llvm-18.1.8-x86_64-linux-gnu-ubuntu-18.04"
},
"urls": {
"": [
"https://github.com/llvm/llvm-project/releases/download/llvmorg-18.1.8/clang+llvm-18.1.8-x86_64-linux-gnu-ubuntu-18.04.tar.xz"
]
}
}
}
},
"recordedRepoMappingEntries": [
[
"toolchains_llvm~",
"bazel_skylib",
"bazel_skylib~"
],
[
"toolchains_llvm~",
"bazel_tools",
"bazel_tools"
],
[
"toolchains_llvm~",
"toolchains_llvm",
"toolchains_llvm~"
]
]
}
}
}
}

View File

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

554
README.md
View File

@ -1,14 +1,13 @@
# Cpptrace <!-- omit in toc -->
[![build](https://github.com/jeremy-rifkin/cpptrace/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/jeremy-rifkin/cpptrace/actions/workflows/build.yml)
[![test](https://github.com/jeremy-rifkin/cpptrace/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/jeremy-rifkin/cpptrace/actions/workflows/test.yml)
[![CI](https://github.com/jeremy-rifkin/cpptrace/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/jeremy-rifkin/cpptrace/actions/workflows/ci.yml)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=jeremy-rifkin_cpptrace&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=jeremy-rifkin_cpptrace)
<br/>
[![Community Discord Link](https://img.shields.io/badge/Chat%20on%20the%20(very%20small)-Community%20Discord-blue?labelColor=2C3239&color=7289DA&style=flat&logo=discord&logoColor=959DA5)](https://discord.gg/frjaAZvqUZ)
<br/>
[![Try on Compiler Explorer](https://img.shields.io/badge/-Compiler%20Explorer-brightgreen?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAQCAYAAAAmlE46AAAACXBIWXMAAACwAAAAsAEUaqtpAAABSElEQVQokYVTsU7DMBB9QMTCEJbOMLB5oF0tRfUPIPIJZctYJkZYu3WMxNL+ARUfQKpImcPgDYnsXWBgYQl61TkYyxI3Wef37j3fnQ/6vkcsikY9AbiWq0mpbevDBmLRqDEAA4CEHMADgFRwrwDmch6X2i73RCFVHvC/WCeCMAFpC2AFoPPu5x4md4rnAN4luS61nYWSgauNU8ydkr0bLTMYAoIYtWqxM4LtEumeERDtfUjlMDrp7L67iddyyJtOvUIu2rquVn4iiVSOKXYhiMSJWLwUJZLuQ2CWmVldV4MT11UmXgB8fr0dX3WP6VHMiVrscim6Da2mJxffzwSU2v6xWzSKmzQ4cUTOaCBTvWgU14xkzjhckKm/q3wnrRAcAhksxMZNAdxEf0fRKI6E8zqT1C0X28ccRpqAUltW5pu4sxv5Mb8B4AciE3bHMxz/+gAAAABJRU5ErkJggg==&labelColor=2C3239&style=flat&label=Try+it+on&color=30C452)](https://godbolt.org/z/c6TqTzqcf)
Cpptrace is a simple, portable, and self-contained C++ stacktrace library supporting C++11 and greater on Linux, macOS,
Cpptrace is a simple and portable C++ stacktrace library supporting C++11 and greater on Linux, macOS,
and Windows including MinGW and Cygwin environments. The goal: Make stack traces simple for once.
Cpptrace also has a C API, docs [here](docs/c-api.md).
@ -17,24 +16,29 @@ Cpptrace also has a C API, docs [here](docs/c-api.md).
- [30-Second Overview](#30-second-overview)
- [CMake FetchContent Usage](#cmake-fetchcontent-usage)
- [FAQ](#faq)
- [What about C++23 `<stacktrace>`?](#what-about-c23-stacktrace)
- [What does cpptrace have over other C++ stacktrace libraries?](#what-does-cpptrace-have-over-other-c-stacktrace-libraries)
- [In-Depth Documentation](#in-depth-documentation)
- [Prerequisites](#prerequisites)
- [Basic Usage](#basic-usage)
- [`namespace cpptrace`](#namespace-cpptrace)
- [Stack Traces](#stack-traces)
- [Object Traces](#object-traces)
- [Raw Traces](#raw-traces)
- [Utilities](#utilities)
- [Formatting](#formatting)
- [Configuration](#configuration)
- [Traced Exceptions](#traced-exceptions)
- [Traces From All Exceptions](#traces-from-all-exceptions)
- [Removing the `CPPTRACE_` prefix](#removing-the-cpptrace_-prefix)
- [How it works](#how-it-works)
- [Performance](#performance)
- [Traced Exception Objects](#traced-exception-objects)
- [Wrapping std::exceptions](#wrapping-stdexceptions)
- [Exception handling with cpptrace](#exception-handling-with-cpptrace)
- [Exception handling with cpptrace exception objects](#exception-handling-with-cpptrace-exception-objects)
- [Terminate Handling](#terminate-handling)
- [Signal-Safe Tracing](#signal-safe-tracing)
- [Utility Types](#utility-types)
- [Headers](#headers)
- [Libdwarf Tuning](#libdwarf-tuning)
- [Supported Debug Formats](#supported-debug-formats)
- [Usage](#usage)
- [How to Include The Library](#how-to-include-the-library)
- [CMake FetchContent](#cmake-fetchcontent)
- [System-Wide Installation](#system-wide-installation)
- [Local User Installation](#local-user-installation)
@ -50,6 +54,10 @@ Cpptrace also has a C API, docs [here](docs/c-api.md).
- [Summary of Library Configurations](#summary-of-library-configurations)
- [Testing Methodology](#testing-methodology)
- [Notes About the Library](#notes-about-the-library)
- [FAQ](#faq)
- [What about C++23 `<stacktrace>`?](#what-about-c23-stacktrace)
- [What does cpptrace have over other C++ stacktrace libraries?](#what-does-cpptrace-have-over-other-c-stacktrace-libraries)
- [I'm getting undefined standard library symbols like `std::__1::basic_string` on MacOS](#im-getting-undefined-standard-library-symbols-like-std__1basic_string-on-macos)
- [Contributing](#contributing)
- [License](#license)
@ -80,7 +88,30 @@ const auto raw_trace = cpptrace::generate_raw_trace();
raw_trace.resolve().print();
```
Cpptrace also provides exception types that store stack traces:
Cpptrace provides a way to produce stack traces on arbitrary exceptions. More information on this system
[below](#traces-from-all-exceptions).
```cpp
#include <cpptrace/from_current.hpp>
void foo() {
throw std::runtime_error("foo failed");
}
int main() {
CPPTRACE_TRY {
foo();
} CPPTRACE_CATCH(const std::exception& e) {
std::cerr<<"Exception: "<<e.what()<<std::endl;
cpptrace::from_current_exception().print();
}
}
```
![from_current](res/from_current.png)
There are a few extraneous frames at the top of the stack corresponding to internals of exception handling in the
standard library. These are a small price to pay for stack traces on all exceptions.
Cpptrace also provides a handful of traced exception objects that store stack traces when thrown. This is useful when
the exceptions might not be caught by `CPPTRACE_CATCH`:
```cpp
#include <cpptrace/cpptrace.hpp>
@ -89,7 +120,7 @@ void trace() {
}
```
![Inlining](res/exception.png)
![Exception](res/exception.png)
Additional notable features:
@ -97,6 +128,7 @@ Additional notable features:
- Utilities for catching `std::exception`s and wrapping them in traced exceptions
- Signal-safe stack tracing
- Source code snippets in traces
- Extensive configuration options for [trace formatting](#formatting)
![Snippets](res/snippets.png)
@ -107,12 +139,13 @@ include(FetchContent)
FetchContent_Declare(
cpptrace
GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git
GIT_TAG v0.6.2 # <HASH or TAG>
GIT_TAG v0.8.2 # <HASH or TAG>
)
FetchContent_MakeAvailable(cpptrace)
target_link_libraries(your_target cpptrace::cpptrace)
# On windows copy cpptrace.dll to the same directory as the executable for your_target
# Needed for shared library builds on windows: copy cpptrace.dll to the same directory as the
# executable for your_target
if(WIN32)
add_custom_command(
TARGET your_target POST_BUILD
@ -129,50 +162,26 @@ information.
On macOS it is recommended to generate a `.dSYM` file, see [Platform Logistics](#platform-logistics) below.
For other ways to use the library, such as through package managers, a system-wide installation, or on a platform
without internet access see [Usage](#usage) below.
without internet access see [How to Include The Library](#how-to-include-the-library) below.
# FAQ
## What about C++23 `<stacktrace>`?
Some day C++23's `<stacktrace>` will be ubiquitous. And maybe one day the msvc implementation will be acceptable.
The original motivation for cpptrace was to support projects using older C++ standards and as the library has grown its
functionality has extended beyond the standard library's implementation.
Cpptrace provides functionality beyond what the standard library provides and what implementations provide, such as:
- Walking inlined function calls
- Providing a lightweight interface for "raw traces"
- Resolving function parameter types
- Providing traced exception objects
- Providing an API for signal-safe stacktrace generation
## What does cpptrace have over other C++ stacktrace libraries?
Other C++ stacktrace libraries, such as boost stacktrace and backward-cpp, fall short when it comes to portability and
ease of use. In testing, I found neither to provide adaquate coverage of various environments. Even when they can be
made to work in an environment they require manual configuration from the end-user, possibly requiring manual
installation of third-party dependencies. This is a highly undesirable burden to impose on users, especially when it is
for a software package which just provides diagnostics as opposed to core functionality. Additionally, cpptrace provides
support for resolving inlined calls by default for DWARF symbols (boost does not do this, backward-cpp can do this but
only for some back-ends), better support for resolving full function signatures, and nicer API, among other features.
# In-Depth Documentation
## Prerequisites
# Prerequisites
> [!IMPORTANT]
> Debug info (`-g`/`/Z7`/`/Zi`/`/DEBUG`/`-DBUILD_TYPE=Debug`/`-DBUILD_TYPE=RelWithDebInfo`) is required for complete
> trace information.
## `namespace cpptrace`
# Basic Usage
`cpptrace::generate_trace()` can be used to generate a stacktrace object at the current call site. Resolved frames can
be accessed from this object with `.frames` and also the trace can be printed with `.print()`. Cpptrace also provides a
method to get lightweight raw traces, which are just vectors of program counters, which can be resolved at a later time.
`cpptrace::generate_trace()` can be used to generate a `stacktrace` object at the current call site. Resolved frames can
be accessed from this object with `.frames` and the trace can be printed with `.print()`. Cpptrace also provides a
method to get light-weight raw traces with `cpptrace::generate_raw_trace()`, which are just vectors of program counters,
which can be resolved at a later time.
# `namespace cpptrace`
All functions are thread-safe unless otherwise noted.
### Stack Traces
## Stack Traces
The core resolved stack trace object. Generate a trace with `cpptrace::generate_trace()` or
`cpptrace::stacktrace::current()`. On top of a set of helper functions `struct stacktrace` allows
@ -224,7 +233,7 @@ namespace cpptrace {
}
```
### Object Traces
## Object Traces
Object traces contain the most basic information needed to construct a stack trace outside the currently running
executable. It contains the raw address, the address in the binary (ASLR and the object file's memory space and whatnot
@ -253,7 +262,7 @@ namespace cpptrace {
}
```
### Raw Traces
## Raw Traces
Raw trace access: A vector of program counters. These are ideal for fast and cheap traces you want to resolve later.
@ -278,7 +287,7 @@ namespace cpptrace {
}
```
### Utilities
## Utilities
`cpptrace::demangle` provides a helper function for name demangling, since it has to implement that helper internally
anyways.
@ -310,12 +319,93 @@ namespace cpptrace {
}
```
### Configuration
## Formatting
Cpptrace provides a configurable formatter for stack trace printing which supports some common options. Formatters are
configured with a sort of builder pattern, e.g.:
```cpp
auto formatter = cpptrace::formatter{}
.header("Stack trace:")
.addresses(cpptrace::formatter::address_mode::object)
.snippets(true);
```
This API is available through the `<cpptrace/formatting.hpp>` header.
Synopsis:
```cpp
namespace cpptrace {
class formatter {
formatter& header(std::string);
enum class color_mode { always, none, automatic };
formatter& colors(color_mode);
enum class address_mode { raw, object, none };
formatter& addresses(address_mode);
enum class path_mode { full, basename };
formatter& paths(path_mode);
formatter& snippets(bool);
formatter& snippet_context(int);
formatter& columns(bool);
formatter& filtered_frame_placeholders(bool);
formatter& filter(std::function<bool(const stacktrace_frame&)>);
std::string format(const stacktrace_frame&) const;
std::string format(const stacktrace_frame&, bool color) const;
std::string format(const stacktrace&) const;
std::string format(const stacktrace&, bool color) const;
void print(const stacktrace_frame&) const;
void print(const stacktrace_frame&, bool color) const;
void print(std::ostream&, const stacktrace_frame&) const;
void print(std::ostream&, const stacktrace_frame&, bool color) const;
void print(std::FILE*, const stacktrace_frame&) const;
void print(std::FILE*, const stacktrace_frame&, bool color) const;
void print(const stacktrace&) const;
void print(const stacktrace&, bool color) const;
void print(std::ostream&, const stacktrace&) const;
void print(std::ostream&, const stacktrace&, bool color) const;
void print(std::FILE*, const stacktrace&) const;
void print(std::FILE*, const stacktrace&, bool color) const;
};
}
```
Options:
| Setting | Description | Default |
| ----------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------ |
| `header` | Header line printed before the trace | `Stack trace (most recent call first):` |
| `colors` | Default color mode for the trace | `automatic`, which attempts to detect if the target stream is a terminal |
| `addresses` | Raw addresses, object addresses, or no addresses | `raw` |
| `paths` | Full paths or just filenames | `full` |
| `snippets` | Whether to include source code snippets | `false` |
| `snippet_context` | How many lines of source context to show in a snippet | `2` |
| `columns` | Whether to include column numbers if present | `true` |
| `filtered_frame_placeholders` | Whether to still print filtered frames as just `#n (filtered)` | `true` |
| `filter` | A predicate to filter frames with | None |
The `automatic` color mode attempts to detect if a stream that may be attached to a terminal. As such, it will not use
colors for the `formatter::format` method and it may not be able to detect if some ostreams correspond to terminals or
not. For this reason, `formatter::format` and `formatter::print` methods have overloads taking a color parameter. This
color parameter will override configured color mode.
Recommended practice with formatters: It's generally preferable to create formatters objects that are long-lived rather
than to create them on the fly every time a trace needs to be formatted.
Cpptrace provides access to a formatter with default settings with `get_default_formatter`:
```cpp
namespace cpptrace {
const formatter& get_default_formatter();
}
```
## Configuration
`cpptrace::absorb_trace_exceptions`: Configure whether the library silently absorbs internal exceptions and continues.
Default is true.
`cpptrace::ctrace_enable_inlined_call_resolution`: Configure whether the library will attempt to resolve inlined call
`cpptrace::enable_inlined_call_resolution`: Configure whether the library will attempt to resolve inlined call
information for release builds. Default is true.
`cpptrace::experimental::set_cache_mode`: Control time-memory tradeoffs within the library. By default speed is
@ -325,7 +415,7 @@ performed.
```cpp
namespace cpptrace {
void absorb_trace_exceptions(bool absorb);
void ctrace_enable_inlined_call_resolution(bool enable);
void enable_inlined_call_resolution(bool enable);
enum class cache_mode {
// Only minimal lookup tables
@ -342,11 +432,167 @@ namespace cpptrace {
}
```
### Traced Exceptions
## Traces From All Exceptions
Cpptrace provides an interface for a traced exceptions, `cpptrace::exception`, as well as a set of exception classes
that that generate stack traces when thrown. These exceptions generate relatively lightweight raw traces and resolve
symbols and line numbers lazily if and when requested.
Cpptrace provides `CPPTRACE_TRY` and `CPPTRACE_CATCH` macros that allow a stack trace to be collected from the current
thrown exception object, with minimal or no overhead in the non-throwing path:
```cpp
#include <cpptrace/from_current.hpp>
#include <iostream>
void foo() {
throw std::runtime_error("foo failed");
}
int main() {
CPPTRACE_TRY {
foo();
} CPPTRACE_CATCH(const std::exception& e) {
std::cerr<<"Exception: "<<e.what()<<std::endl;
cpptrace::from_current_exception().print();
}
}
```
This functionality is entirely opt-in, to access this use `#include <cpptrace/from_current.hpp>`.
Any declarator `catch` accepts works with `CPPTRACE_CATCH`, including `...`. This works with any thrown object, not just
`std::exceptions`, it even works with `throw 0;`
![from_current](res/from_current.png)
There are a few extraneous frames at the top of the stack corresponding to standard library exception handling
internals. These are a small price to pay for stack traces on all exceptions.
API functions:
- `cpptrace::raw_trace_from_current_exception`: Returns `const raw_trace&` from the current exception.
- `cpptrace::from_current_exception`: Returns a resolved `const stacktrace&` from the current exception. Invalidates
references to traces returned by `cpptrace::raw_trace_from_current_exception`.
There is a performance tradeoff with this functionality: Either the try-block can be zero overhead in the
non-throwing path with potential expense in the throwing path, or the try-block can have very minimal overhead
in the non-throwing path due to bookkeeping with guarantees about the expense of the throwing path. More details on
this tradeoff [below](#performance). Cpptrace provides macros for both sides of this tradeoff:
- `CPPTRACE_TRY`/`CPPTRACE_CATCH`: Minimal overhead in the non-throwing path (one `mov` on x86, and this may be
optimized out if the compiler is able)
- `CPPTRACE_TRYZ`/`CPPTRACE_CATCHZ`: Zero overhead in the non-throwing path, potential extra cost in the throwing path
Note: It's important to not mix the `Z` variants with the non-`Z` variants.
Unfortunately the try/catch macros are needed to insert some magic to perform a trace during the unwinding search phase.
In order to have multiple catch alternatives, either `CPPTRACE_CATCH_ALT` or a normal `catch` must be used:
```cpp
CPPTRACE_TRY {
foo();
} CPPTRACE_CATCH(const std::exception&) { // <- First catch must be CPPTRACE_CATCH
// ...
} CPPTRACE_CATCH_ALT(const std::exception&) { // <- Ok
// ...
} catch(const std::exception&) { // <- Also Ok
// ...
} CPPTRACE_CATCH(const std::exception&) { // <- Not Ok
// ...
}
```
Note: The current exception is the exception most recently seen by a cpptrace try-catch macro block.
```cpp
CPPTRACE_TRY {
throw std::runtime_error("foo");
} CPPTRACE_CATCH(const std::exception& e) {
cpptrace::from_current_exception().print(); // the trace for std::runtime_error("foo")
CPPTRACE_TRY {
throw std::runtime_error("bar");
} CPPTRACE_CATCH(const std::exception& e) {
cpptrace::from_current_exception().print(); // the trace for std::runtime_error("bar")
}
cpptrace::from_current_exception().print(); // the trace for std::runtime_error("bar"), again
}
```
### Removing the `CPPTRACE_` prefix
`CPPTRACE_TRY` is a little cumbersome to type. To remove the `CPPTRACE_` prefix you can use the
`CPPTRACE_UNPREFIXED_TRY_CATCH` cmake option or the `CPPTRACE_UNPREFIXED_TRY_CATCH` preprocessor definition:
```cpp
TRY {
foo();
} CATCH(const std::exception& e) {
std::cerr<<"Exception: "<<e.what()<<std::endl;
cpptrace::from_current_exception().print();
}
```
This is not done by default for macro safety/hygiene reasons. If you do not want `TRY`/`CATCH` macros defined, as they
are common macro names, you can easily modify the following snippet to provide your own aliases:
```cpp
#define TRY CPPTRACE_TRY
#define CATCH(param) CPPTRACE_CATCH(param)
#define TRYZ CPPTRACE_TRYZ
#define CATCHZ(param) CPPTRACE_CATCHZ(param)
#define CATCH_ALT(param) CPPTRACE_CATCH_ALT(param)
```
### How it works
C++ does not provide any language support for collecting stack traces when exceptions are thrown, however, exception
handling under both the Itanium ABI and by SEH (used to implement C++ exceptions on windows) involves unwinding the
stack twice. The first unwind searches for an appropriate `catch` handler, the second actually unwinds the stack and
calls destructors. Since the stack remains intact during the search phase it's possible to collect a stack trace with
little to no overhead when the `catch` is considered for matching the exception. The try/catch macros for cpptrace set
up a special try/catch system that can collect a stack trace when considered during a search phase.
N.b.: This mechanism is also discussed in [P2490R3][P2490R3].
### Performance
The fundamental mechanism for this functionality is generating a trace when a catch block is considered during an
exception handler search phase. Internally a lightweight raw trace is generated upon consideration, which is quite
fast. This raw trace is only resolved when `cpptrace::from_current_exception` is called, or when the user manually
resolves a trace from `cpptrace::raw_trace_from_current_exception`.
It's tricky, however, from the library's standpoint to check if the catch will end up matching. The library could simply
generate a trace every time a `CPPTRACE_CATCH` is considered, however, in a deep nesting of catch's, e.g. as a result of
recusion, where a matching handler is not found quickly this could introduce a non-trivial cost in the throwing pat due
to tracing the stack multiple times. Thus, there is a performance tradeoff between a little book keeping to prevent
duplicate tracing or biting the bullet, so to speak, in the throwing path and unwinding multiple times.
> [!TIP]
> The choice between the `Z` and non-`Z` (zero-overhead and non-zero-overhead) variants of the exception handlers should
> not matter 99% of the time, however, both are provided in the rare case that it does.
>
> `CPPTRACE_TRY`/`CPPTRACE_CATCH` could only hurt performance if used in a hot loop where the compiler can't optimize
> away the internal bookkeeping, otherwise the bookkeeping should be completely negligible.
>
> `CPPTRACE_TRYZ`/`CPPTRACE_CATCHZ` could only hurt performance when there is an exceptionally deep nesting of exception
> handlers in a call stack before a matching handler.
More information on performance considerations with the zero-overhead variant:
Tracing the stack multiple times in throwing paths should not matter for the vast majority applications given that:
1. Performance very rarely is critical in throwing paths and exceptions should be exceptionally rare
2. Exception handling is not usually used in such a way that you could have a deep nesting of handlers before finding a
matching handler
3. Most call stacks are fairly shallow
To put the scale of this performance consideration into perspective: In my benchmarking I have found generation of raw
traces to take on the order of `100ns` per frame. Thus, even if there were 100 non-matching handlers before a matching
handler in a 100-deep call stack the total time would stil be on the order of one millisecond.
Nonetheless, I chose a default bookkeeping behavior for `CPPTRACE_TRY`/`CPPTRACE_CATCH` since it is safer with better
performance guarantees for the most general possible set of users.
## Traced Exception Objects
Cpptrace provides a handful of traced exception classes which automatically collect stack traces when thrown. These
are useful when throwing exceptions that may not be caught by `CPPTRACE_CATCH`.
The base traced exception class is `cpptrace::exception` and cpptrace provides a handful of helper classes for working
with traced exceptions. These exceptions generate relatively lightweight raw traces and resolve symbols and line numbers
lazily if and when requested.
These are provided both as a useful utility and as a reference implementation for traced exceptions.
@ -427,7 +673,11 @@ namespace cpptrace {
}
```
## Wrapping std::exceptions
### Wrapping std::exceptions
> [!NOTE]
> This section is largely obsolete now that cpptrace provides a better mechanism for collecting
> [traces from exceptions](#traces-from-exceptions)
Cpptrace exceptions can provide great information for user-controlled exceptions. For non-cpptrace::exceptions that may
originate outside of code you control, e.g. the standard library, cpptrace provides some wrapper utilities that can
@ -444,7 +694,11 @@ CPPTRACE_WRAP_BLOCK(
std::cout<<CPPTRACE_WRAP(foo.at(12))<<std::endl;
```
## Exception handling with cpptrace
### Exception handling with cpptrace exception objects
> [!NOTE]
> This section pertains to cpptrace traced exception objects and not the mechanism for collecting
> [traces from arbitrary exceptions](#traces-from-exceptions)
Working with cpptrace exceptions in your code:
```cpp
@ -460,10 +714,12 @@ try {
}
```
Additionally cpptrace provides a custom `std::terminate` handler that prints a stack trace from a cpptrace exception and otherwise behaves like the normal terminate handler and prints the stack trace involved in reaching `std::terminate`.
The stack trace to `std::terminate` may be helpful or it may not, it depends on the implementation, but often if an
implementation can't find an appropriate `catch` while unwinding it will jump directly to `std::terminate` giving
good information.
## Terminate Handling
Cpptrace provides a custom `std::terminate` handler that prints stacktraces while otherwise behaving like the normal
`std::terminate` handler. If a cpptrace exception object reaches `std::terminate` the trace from that exception is
printed, otherwise a stack trace is generated at the point of the terminate handler. Often `std::terminate` is called
directly without unwinding so the trace is preserved.
To register this custom handler:
@ -473,17 +729,14 @@ cpptrace::register_terminate_handler();
## Signal-Safe Tracing
Signal-safe stack tracing is very useful for debugging application crashes, e.g. SIGSEGVs or
SIGTRAPs, but it's very difficult to do correctly and most implementations I see online do this
incorrectly.
Stack traces from signal handlers can provide very helpful information for debugging application crashes, e.g. from
SIGSEGV or SIGTRAP handlers. Signal handlers are really restrictive environments as your application could be
interrupted by a signal at any point, including in the middle of malloc or buffered IO or while holding a lock.
Doing a stack trace in a signal handler is possible but it requires a lot of care. This is difficult to do correctly
and most examples online do this incorrectly.
In order to do this full process safely the way to go is collecting basic information in the signal
handler and then either resolving later or handing that information to another process to resolve.
It's not as simple as calling `cpptrace::generate_trace().print()`, though you might be able to get
away with that, but this is what is needed to really do this safely.
The safe API is as follows:
Cpptrace offers an API to walk the stack in a signal handler and produce a raw trace safely. The library also provides
an interface for producing a object frame safely:
```cpp
namespace cpptrace {
@ -491,29 +744,41 @@ namespace cpptrace {
std::size_t safe_generate_raw_trace(frame_ptr* buffer, std::size_t size, std::size_t skip, std::size_t max_depth);
struct safe_object_frame {
frame_ptr raw_address;
frame_ptr address_relative_to_object_start; // object base address must yet be added
frame_ptr address_relative_to_object_start;
char object_path[CPPTRACE_PATH_MAX + 1];
object_frame resolve() const; // To be called outside a signal handler. Not signal safe.
};
void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
bool can_signal_safe_unwind();
bool can_get_safe_object_frame();
}
```
It is not possible to resolve debug symbols safely in the process from a signal handler without heroic effort. In order
to produce a full trace there are three options:
1. Carefully save the object trace information to be resolved at a later time outside the signal handler
2. Write the object trace information to a file to be resolved later
3. Spawn a new process, communicate object trace information to that process, and have that process do the trace
resolution
For traces on segfaults, e.g., only options 2 and 3 are viable. For more information an implementation of approach 3,
see the comprehensive overview and demo at [signal-safe-tracing.md](docs/signal-safe-tracing.md).
> [!IMPORTANT]
> Currently signal-safe stack unwinding is only possible with `libunwind`, which must be
> [manually enabled](#library-back-ends). If signal-safe unwinding isn't supported, `safe_generate_raw_trace` will just
> produce an empty trace. `can_signal_safe_unwind` can be used to check for signal-safe unwinding support. If object
> information can't be resolved in a signal-safe way then `get_safe_object_frame` will not populate fields beyond the
> `raw_address`.
> produce an empty trace. `can_signal_safe_unwind` can be used to check for signal-safe unwinding support and
> `can_get_safe_object_frame` can be used to check `get_safe_object_frame` support. If object information can't be
> resolved in a signal-safe way then `get_safe_object_frame` will not populate fields beyond the `raw_address`.
> [!IMPORTANT]
> `_dl_find_object` is required for signal-safe stack tracing. This is a relatively recent addition to glibc, added in
> glibc 2.35.
> [!CAUTION]
> Calls to shared objects can be lazy-loaded where the first call to the shared object invokes non-signal-safe functions
> such as `malloc()`. To avoid this, call these routines in `main()` ahead of a signal handler to "warm up" the library.
Because signal-safe tracing is an involved process, I have written up a comprehensive overview of
what is involved at [signal-safe-tracing.md](docs/signal-safe-tracing.md).
## Utility Types
A couple utility types are used to provide the library with a good interface.
@ -530,6 +795,7 @@ namespace cpptrace {
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
struct nullable {
T raw_value;
// all members are constexpr for c++17 and beyond, some are constexpr before c++17
nullable& operator=(T value)
bool has_value() const noexcept;
T& value() noexcept;
@ -539,6 +805,7 @@ namespace cpptrace {
void reset() noexcept;
bool operator==(const nullable& other) const noexcept;
bool operator!=(const nullable& other) const noexcept;
constexpr static T null_value() noexcept; // returns the raw null value
constexpr static nullable null() noexcept; // returns a null instance
};
@ -561,6 +828,7 @@ namespace cpptrace {
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; // throws if not already resolved
private:
@ -570,22 +838,63 @@ namespace cpptrace {
}
```
## Headers
Cpptrace provides a handful of headers to make inclusion more minimal.
| Header | Contents |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `cpptrace/forward.hpp` | `cpptrace::frame_ptr` and a few trace class forward declarations |
| `cpptrace/basic.hpp` | Definitions for trace classes and the basic tracing APIs ([Stack Traces](#stack-traces), [Object Traces](#object-traces), [Raw Traces](#raw-traces), and [Signal-Safe Tracing](#signal-safe-tracing)) |
| `cpptrace/exceptions.hpp` | [Traced Exception Objects](#traced-exception-objects) and related utilities ([Wrapping std::exceptions](#wrapping-stdexceptions)) |
| `cpptrace/from_current.hpp` | [Traces From All Exceptions](#traces-from-all-exceptions) |
| `cpptrace/io.hpp` | `operator<<` overloads for `std::ostream` and `std::formatter`s |
| `cpptrace/formatting.hpp` | Configurable formatter API |
| `cpptrace/utils.hpp` | Utility functions, configuration functions, and terminate utilities ([Utilities](#utilities), [Configuration](#configuration), and [Terminate Handling](#terminate-handling)) |
| `cpptrace/version.hpp` | Library version macros |
The main cpptrace header is `cpptrace/cpptrace.hpp` which includes everything other than `from_current.hpp` and
`version.hpp`.
## Libdwarf Tuning
For extraordinarily large binaries (multiple gigabytes), cpptrace's internal caching can result in a lot of memory
usage. Cpptrace provides some options to reduce memory usage in exchange for performance in memory-constrained
applications.
Synopsis:
```cpp
namespace cpptrace {
namespace experimental {
void set_dwarf_resolver_line_table_cache_size(nullable<std::size_t> max_entries);
void set_dwarf_resolver_disable_aranges(bool disable);
}
}
```
Explanation:
- `set_dwarf_resolver_line_table_cache_size` can be used to set a limit to the cache size with evictions done LRU.
Cpptrace loads and caches line tables for dwarf compile units. These can take a lot of space for large binaries with
lots of debug info. Passing `nullable<std::size_t>::null()` will disable the cache size (which is the default
behavior).
- `set_dwarf_resolver_disable_aranges` can be used to disable use of dwarf `.debug_aranges`, an accelerated range lookup
table for compile units emitted by many compilers. Cpptrace uses these by default if they are present since they can
speed up resolution, however, they can also result in significant memory usage.
# Supported Debug Formats
| Format | Supported |
| --------------------------------- | --------- |
| ---------------------------- | --------- |
| DWARF in binary | ✔️ |
| GNU debug link | ️️✔️ |
| Split dwarf (debug fission) | ✔️* |
| Split dwarf (debug fission) | ✔️ |
| DWARF in dSYM | ✔️ |
| DWARF via Mach-O debug map | ✔️ |
| Windows debug symbols in PDB | ✔️ |
*There seem to be a couple issues upstream with libdwarf however they will hopefully be resolved soon.
DWARF5 added DWARF package files. As far as I can tell no compiler implements these yet.
# Usage
# How to Include The Library
## CMake FetchContent
@ -596,7 +905,7 @@ include(FetchContent)
FetchContent_Declare(
cpptrace
GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git
GIT_TAG v0.6.2 # <HASH or TAG>
GIT_TAG v0.8.2 # <HASH or TAG>
)
FetchContent_MakeAvailable(cpptrace)
target_link_libraries(your_target cpptrace::cpptrace)
@ -612,7 +921,7 @@ information.
```sh
git clone https://github.com/jeremy-rifkin/cpptrace.git
git checkout v0.6.2
git checkout v0.8.2
mkdir cpptrace/build
cd cpptrace/build
cmake .. -DCMAKE_BUILD_TYPE=Release
@ -655,7 +964,7 @@ you when installing new libraries.
```ps1
git clone https://github.com/jeremy-rifkin/cpptrace.git
git checkout v0.6.2
git checkout v0.8.2
mkdir cpptrace/build
cd cpptrace/build
cmake .. -DCMAKE_BUILD_TYPE=Release
@ -673,12 +982,12 @@ To install just for the local user (or any custom prefix):
```sh
git clone https://github.com/jeremy-rifkin/cpptrace.git
git checkout v0.6.2
git checkout v0.8.2
mkdir cpptrace/build
cd cpptrace/build
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$HOME/wherever
make -j
sudo make install
make install
```
Using through cmake:
@ -746,7 +1055,7 @@ make install
cd ~/scratch/cpptrace-test
git clone https://github.com/jeremy-rifkin/libdwarf-lite.git
cd libdwarf-lite
git checkout 5c0cb251f94b27e90184e6b2d9a0c9c62593babc
git checkout fe09ca800b988e2ff21225ac5e7468ceade2a30e
mkdir build
cd build
cmake .. -DPIC_ALWAYS=On -DBUILD_DWARFDUMP=Off -DCMAKE_PREFIX_PATH=~/scratch/cpptrace-test/resources -DCMAKE_INSTALL_PREFIX=~/scratch/cpptrace-test/resources
@ -756,7 +1065,7 @@ make install
cd ~/scratch/cpptrace-test
git clone https://github.com/jeremy-rifkin/cpptrace.git
cd cpptrace
git checkout v0.6.2
git checkout v0.8.2
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=On -DCPPTRACE_USE_EXTERNAL_LIBDWARF=On -DCMAKE_PREFIX_PATH=~/scratch/cpptrace-test/resources -DCMAKE_INSTALL_PREFIX=~/scratch/cpptrace-test/resources
@ -776,7 +1085,7 @@ cpptrace and its dependencies.
Cpptrace is available through conan at https://conan.io/center/recipes/cpptrace.
```
[requires]
cpptrace/0.6.2
cpptrace/0.8.2
[generators]
CMakeDeps
CMakeToolchain
@ -855,9 +1164,9 @@ The library's CMake automatically configures itself for what your system support
follows:
| Platform | Unwinding | Symbols | Demangling |
| -------- | ------------- | ------------------ | -------------------- |
| -------- | ------------------------------------------------------- | ------------------ | -------------------- |
| Linux | `_Unwind` | libdwarf | cxxabi.h |
| MacOS | `_Unwind` | libdwarf | cxxabi.h |
| MacOS | `_Unwind` for gcc, execinfo.h for clang and apple clang | libdwarf | cxxabi.h |
| Windows | `StackWalk64` | dbghelp | No demangling needed |
| MinGW | `StackWalk64` | libdwarf + dbghelp | cxxabi.h |
@ -876,7 +1185,7 @@ back-end such as addr2line, for example, you can configure the library to do so.
| N/A | `CPPTRACE_UNWIND_WITH_NOTHING` | all | Unwinding is not done, stack traces will be empty. |
Some back-ends (execinfo and `CaptureStackBackTrace`) require a fixed buffer has to be created to read addresses into
while unwinding. By default the buffer can hold addresses for 200 frames (beyond the `skip` frames). This is
while unwinding. By default the buffer can hold addresses for 400 frames (beyond the `skip` frames). This is
configurable with `CPPTRACE_HARD_MAX_FRAMES`.
**Symbol resolution**
@ -938,7 +1247,7 @@ Back-ends:
Back-end configuration:
- `CPPTRACE_BACKTRACE_PATH=<string>`: Path to libbacktrace backtrace.h, needed when compiling with clang/
- `CPPTRACE_HARD_MAX_FRAMES=<number>`: Some back-ends write to a fixed-size buffer. This is the size of that buffer.
Default is `200`.
Default is `400`.
- `CPPTRACE_ADDR2LINE_PATH=<string>`: Specify the absolute path to the addr2line binary for cpptrace to invoke. By
default the config script will search for a binary and use that absolute path (this is to prevent against path
injection).
@ -952,6 +1261,8 @@ Other useful configurations:
- `CPPTRACE_INSTALL_CMAKEDIR`: Override for the installation path for the cmake configs.
- `CPPTRACE_USE_EXTERNAL_LIBDWARF=On/Off`: Get libdwarf from `find_package` rather than `FetchContent`.
- `CPPTRACE_POSITION_INDEPENDENT_CODE=On/Off`: Compile the library as a position independent code (PIE). Defaults to On.
- `CPPTRACE_STD_FORMAT=On/Off`: Control inclusion of `<format>` and provision of `std::formatter` specializations by
cpptrace.hpp. This can also be controlled with the macro `CPPTRACE_NO_STD_FORMAT`.
Testing:
- `CPPTRACE_BUILD_TESTING` Build small demo and test program
@ -983,6 +1294,51 @@ A couple things I'd like to improve in the future:
in dbghelp the library cannot accurately show const and volatile qualifiers or rvalue references (these appear as
pointers).
# FAQ
## What about C++23 `<stacktrace>`?
Some day C++23's `<stacktrace>` will be ubiquitous. And maybe one day the msvc implementation will be acceptable.
The original motivation for cpptrace was to support projects using older C++ standards and as the library has grown its
functionality has extended beyond the standard library's implementation.
Cpptrace provides functionality beyond what the standard library provides and what implementations provide, such as:
- Walking inlined function calls
- Providing a lightweight interface for "raw traces"
- Resolving function parameter types
- Providing traced exception objects
- Providing an API for signal-safe stacktrace generation
- Providing a way to retrieve stack traces from arbitrary exceptions, not just special cpptrace traced exception
objects. This is a feature coming to C++26, but cpptrace provides a solution for C++11.
## What does cpptrace have over other C++ stacktrace libraries?
Other C++ stacktrace libraries, such as boost stacktrace and backward-cpp, fall short when it comes to portability and
ease of use. In testing, I found neither to provide adaquate coverage of various environments. Even when they can be
made to work in an environment they require manual configuration from the end-user, possibly requiring manual
installation of third-party dependencies. This is a highly undesirable burden to impose on users, especially when it is
for a software package which just provides diagnostics as opposed to core functionality. Additionally, cpptrace provides
support for resolving inlined calls by default for DWARF symbols (boost does not do this, backward-cpp can do this but
only for some back-ends), better support for resolving full function signatures, and nicer API, among other features.
## I'm getting undefined standard library symbols like `std::__1::basic_string` on MacOS
If you see a linker error along the lines of the following on MacOS then it's highly likely you are mixing standard
library ABIs.
```
Undefined symbols for architecture arm64:
"std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::find(char, unsigned long) const", referenced from:
cpptrace::detail::demangle(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, bool) in libcpptrace.a(demangle_with_cxxabi.cpp.o)
cpptrace::detail::snippet_manager::build_line_table() in libcpptrace.a(snippet.cpp.o)
```
This can happen when using apple clang to compile cpptrace and gcc to compile your code, or vice versa. The reason is
that apple clang defaults to libc++ and gcc defaults to libstdc++ and these two standard library implementations are not
ABI-compatible. To resolve this, ensure you are compiling both cpptrace and your code with the same standard library by
either using the same compiler for both or using `-stdlib=libc++`/`-stdlib=libstdc++` to control which standard library
is used.
# Contributing
I'm grateful for the help I've received with this library and I welcome contributions! For information on contributing
@ -994,3 +1350,5 @@ This library is under the MIT license.
Cpptrace uses libdwarf on linux, macos, and mingw/cygwin unless configured to use something else. If this library is
statically linked with libdwarf then the library's binary will itself be LGPL.
[P2490R3]: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2490r3.html

1
WORKSPACE Normal file
View File

@ -0,0 +1 @@
workspace(name = "cpptrace")

View File

@ -0,0 +1,21 @@
include(CTest)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
set(
warning_options
${warning_options} $<$<CXX_COMPILER_ID:GNU>:-Wno-infinite-recursion>
)
include(FetchContent)
set(BENCHMARK_ENABLE_TESTING OFF)
FetchContent_Declare(
googlebench
GIT_REPOSITORY "https://github.com/google/benchmark.git"
GIT_TAG 12235e24652fc7f809373e7c11a5f73c5763fc4c # v1.9.0
)
FetchContent_MakeAvailable(googlebench)
add_executable(benchmark_unwinding unwinding.cpp)
target_compile_features(benchmark_unwinding PRIVATE cxx_std_20)
target_link_libraries(benchmark_unwinding PRIVATE ${target_name} benchmark::benchmark)

View File

@ -0,0 +1,55 @@
#include <cpptrace/cpptrace.hpp>
#include <benchmark/benchmark.h>
#include <iostream>
struct unwind_benchmark_info {
benchmark::State& state;
size_t& stack_depth;
};
void unwind_loop(unwind_benchmark_info info) {
auto& [state, depth] = info;
depth = cpptrace::generate_raw_trace().frames.size();
for(auto _ : state) {
benchmark::DoNotOptimize(cpptrace::generate_raw_trace());
}
}
void foo(unwind_benchmark_info info, int n) {
if(n == 0) {
unwind_loop(info);
} else {
foo(info, n - 1);
}
}
template<typename... Args>
void foo(unwind_benchmark_info info, int, Args... args) {
foo(info, args...);
}
void function_two(unwind_benchmark_info info, int, float) {
foo(info, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
}
void function_one(unwind_benchmark_info info, int) {
function_two(info, 0, 0);
}
static void unwinding(benchmark::State& state) {
size_t stack_depth = 0;
function_one({state, stack_depth}, 0);
static bool did_print = false;
if(!did_print) {
did_print = true;
std::cerr<<"[info] Unwinding benchmark stack depth: "<<stack_depth<<std::endl;
}
}
// Register the function as a benchmark
BENCHMARK(unwinding);
// Run the benchmark
BENCHMARK_MAIN();

View File

@ -1,345 +0,0 @@
import argparse
import os
import platform
import shutil
import subprocess
import sys
from colorama import Fore, Back, Style
from pathlib import Path
from util import *
sys.stdout.reconfigure(encoding='utf-8') # for windows gh runner
failed = False
def run_command(*args: List[str]):
print(f"{Fore.CYAN}{Style.BRIGHT}Running Command \"{' '.join(args)}\"{Style.RESET_ALL}")
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
print(Style.RESET_ALL, end="") # makefile in parallel sometimes messes up colors
if p.returncode != 0:
print(f"{Fore.RED}{Style.BRIGHT}Command failed{Style.RESET_ALL}")
print("stdout:")
print(stdout.decode("utf-8"), end="")
print("stderr:")
print(stderr.decode("utf-8"), end="")
global failed
failed = True
return False
else:
print(f"{Fore.GREEN}{Style.BRIGHT}Command succeeded{Style.RESET_ALL}")
return True
def build(matrix):
print(f"{Fore.BLUE}{Style.BRIGHT}{'=' * 10} Running build with config {', '.join(matrix.values())} {'=' * 10}{Style.RESET_ALL}")
if os.path.exists("build"):
shutil.rmtree("build", ignore_errors=True)
os.makedirs("build", exist_ok=True)
os.chdir("build")
if platform.system() != "Windows":
succeeded = run_command(
"cmake",
"..",
f"-DCMAKE_BUILD_TYPE={matrix['target']}",
f"-DCMAKE_CXX_COMPILER={matrix['compiler']}",
f"-DCMAKE_CXX_STANDARD={matrix['std']}",
f"-DCPPTRACE_USE_EXTERNAL_LIBDWARF=On",
f"-DCPPTRACE_USE_EXTERNAL_ZSTD=On",
f"-DCPPTRACE_WERROR_BUILD=On",
f"-D{matrix['unwind']}=On",
f"-D{matrix['symbols']}=On",
f"-D{matrix['demangle']}=On",
"-DCPPTRACE_BACKTRACE_PATH=/usr/lib/gcc/x86_64-linux-gnu/10/include/backtrace.h",
)
if succeeded:
succeeded = run_command("make", "-j", "VERBOSE=1")
else:
args = [
"cmake",
"..",
f"-DCMAKE_BUILD_TYPE={matrix['target']}",
f"-DCMAKE_CXX_COMPILER={matrix['compiler']}",
f"-DCMAKE_CXX_STANDARD={matrix['std']}",
f"-DCPPTRACE_USE_EXTERNAL_LIBDWARF=On",
f"-DCPPTRACE_USE_EXTERNAL_ZSTD=On",
f"-DCPPTRACE_WERROR_BUILD=On",
f"-D{matrix['unwind']}=On",
f"-D{matrix['symbols']}=On",
f"-D{matrix['demangle']}=On",
]
if matrix["compiler"] == "g++":
args.append("-GUnix Makefiles")
succeeded = run_command(*args)
if succeeded:
if matrix["compiler"] == "g++":
succeeded = run_command("make", "-j", "VERBOSE=1")
else:
succeeded = run_command("msbuild", "cpptrace.sln")
os.chdir("..")
print()
return succeeded
def build_full_or_auto(matrix):
print(f"{Fore.BLUE}{Style.BRIGHT}{'=' * 10} Running build with config {'<auto>' if matrix['config'] == '' else ', '.join(matrix.values())} {'=' * 10}{Style.RESET_ALL}")
if os.path.exists("build"):
shutil.rmtree("build", ignore_errors=True)
os.makedirs("build", exist_ok=True)
os.chdir("build")
if platform.system() != "Windows":
args = [
"cmake",
"..",
f"-DCMAKE_BUILD_TYPE={matrix['target']}",
f"-DCMAKE_CXX_COMPILER={matrix['compiler']}",
f"-DCMAKE_CXX_STANDARD={matrix['std']}",
f"-DCPPTRACE_USE_EXTERNAL_LIBDWARF=On",
f"-DCPPTRACE_USE_EXTERNAL_ZSTD=On",
f"-DCPPTRACE_WERROR_BUILD=On",
f"-DCPPTRACE_BACKTRACE_PATH=/usr/lib/gcc/x86_64-linux-gnu/10/include/backtrace.h",
]
if matrix["config"] != "":
args.append(f"{matrix['config']}")
succeeded = run_command(*args)
if succeeded:
succeeded = run_command("make", "-j")
else:
args = [
"cmake",
"..",
f"-DCMAKE_BUILD_TYPE={matrix['target']}",
f"-DCMAKE_CXX_COMPILER={matrix['compiler']}",
f"-DCMAKE_CXX_STANDARD={matrix['std']}",
f"-DCPPTRACE_USE_EXTERNAL_LIBDWARF=On",
f"-DCPPTRACE_USE_EXTERNAL_ZSTD=On",
f"-DCPPTRACE_WERROR_BUILD=On",
]
if matrix["config"] != "":
args.append(f"{matrix['config']}")
if matrix["compiler"] == "g++":
args.append("-GUnix Makefiles")
succeeded = run_command(*args)
if succeeded:
if matrix["compiler"] == "g++":
succeeded = run_command("make", "-j")
else:
succeeded = run_command("msbuild", "cpptrace.sln")
os.chdir("..")
print()
return succeeded
def run_linux_matrix(compilers: list):
matrix = {
"compiler": compilers,
"target": ["Debug"],
"std": ["11", "20"],
"unwind": [
"CPPTRACE_UNWIND_WITH_UNWIND",
"CPPTRACE_UNWIND_WITH_EXECINFO",
"CPPTRACE_UNWIND_WITH_LIBUNWIND",
"CPPTRACE_UNWIND_WITH_NOTHING",
],
"symbols": [
"CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE",
"CPPTRACE_GET_SYMBOLS_WITH_LIBDL",
"CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF",
"CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE",
"CPPTRACE_GET_SYMBOLS_WITH_NOTHING",
],
"demangle": [
"CPPTRACE_DEMANGLE_WITH_CXXABI",
"CPPTRACE_DEMANGLE_WITH_NOTHING",
],
}
exclude = []
run_matrix(matrix, exclude, build)
def run_linux_default(compilers: list):
matrix = {
"compiler": compilers,
"target": ["Debug"],
"std": ["11", "20"],
"config": [""]
}
exclude = []
run_matrix(matrix, exclude, build_full_or_auto)
def run_macos_matrix(compilers: list):
matrix = {
"compiler": compilers,
"target": ["Debug"],
"std": ["11", "20"],
"unwind": [
"CPPTRACE_UNWIND_WITH_UNWIND",
"CPPTRACE_UNWIND_WITH_EXECINFO",
"CPPTRACE_UNWIND_WITH_NOTHING",
],
"symbols": [
#"CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE",
"CPPTRACE_GET_SYMBOLS_WITH_LIBDL",
"CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF",
"CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE",
"CPPTRACE_GET_SYMBOLS_WITH_NOTHING",
],
"demangle": [
"CPPTRACE_DEMANGLE_WITH_CXXABI",
"CPPTRACE_DEMANGLE_WITH_NOTHING",
]
}
exclude = []
run_matrix(matrix, exclude, build)
def run_macos_default(compilers: list):
matrix = {
"compiler": compilers,
"target": ["Debug"],
"std": ["11", "20"],
"config": [""]
}
exclude = []
run_matrix(matrix, exclude, build_full_or_auto)
def run_windows_matrix(compilers: list):
matrix = {
"compiler": compilers,
"target": ["Debug"],
"std": ["11", "20"],
"unwind": [
"CPPTRACE_UNWIND_WITH_WINAPI",
"CPPTRACE_UNWIND_WITH_DBGHELP",
"CPPTRACE_UNWIND_WITH_UNWIND",
"CPPTRACE_UNWIND_WITH_NOTHING",
],
"symbols": [
"CPPTRACE_GET_SYMBOLS_WITH_DBGHELP",
"CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF",
"CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE",
"CPPTRACE_GET_SYMBOLS_WITH_NOTHING",
],
"demangle": [
#"CPPTRACE_DEMANGLE_WITH_CXXABI",
"CPPTRACE_DEMANGLE_WITH_NOTHING",
]
}
exclude = [
{
"demangle": "CPPTRACE_DEMANGLE_WITH_CXXABI",
"compiler": "cl"
},
{
"unwind": "CPPTRACE_UNWIND_WITH_UNWIND",
"compiler": "cl"
},
{
"unwind": "CPPTRACE_UNWIND_WITH_UNWIND",
"compiler": "clang++"
},
{
"symbols": "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE",
"compiler": "cl"
},
{
"symbols": "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE",
"compiler": "clang++"
},
{
"symbols": "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF",
"compiler": "cl"
},
{
"symbols": "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF",
"compiler": "clang++"
},
{
"symbols": "CPPTRACE_GET_SYMBOLS_WITH_DBGHELP",
"compiler": "g++"
},
]
run_matrix(matrix, exclude, build)
def run_windows_default(compilers: list):
matrix = {
"compiler": compilers,
"target": ["Debug"],
"std": ["11", "20"],
"config": [""]
}
exclude = []
run_matrix(matrix, exclude, build_full_or_auto)
def main():
parser = argparse.ArgumentParser(
prog="Build in all configs",
description="Try building the library in all possible configurations for the current host"
)
parser.add_argument(
"--clang",
action="store_true"
)
parser.add_argument(
"--gcc",
action="store_true"
)
parser.add_argument(
"--msvc",
action="store_true"
)
parser.add_argument(
"--all",
action="store_true"
)
parser.add_argument(
"--default-config",
action="store_true"
)
args = parser.parse_args()
if platform.system() == "Linux":
compilers = []
if args.clang or args.all:
compilers.append("clang++-14")
if args.gcc or args.all:
compilers.append("g++-10")
if args.default_config:
run_linux_default(compilers)
else:
run_linux_matrix(compilers)
if platform.system() == "Darwin":
compilers = []
if args.clang or args.all:
compilers.append("clang++")
if args.gcc or args.all:
compilers.append("g++-12")
if args.default_config:
run_macos_default(compilers)
else:
run_macos_matrix(compilers)
if platform.system() == "Windows":
compilers = []
if args.clang or args.all:
compilers.append("clang++")
if args.msvc or args.all:
compilers.append("cl")
if args.gcc or args.all:
compilers.append("g++")
if args.default_config:
run_windows_default(compilers)
else:
run_windows_matrix(compilers)
global failed
if failed:
print("🔴 Some checks failed")
sys.exit(1)
main()

View File

@ -0,0 +1,195 @@
import argparse
import os
import platform
import shutil
import subprocess
import sys
from colorama import Fore, Back, Style
from pathlib import Path
from util import *
sys.stdout.reconfigure(encoding='utf-8') # for windows gh runner
def build(runner: MatrixRunner):
matrix = runner.current_config()
if os.path.exists("build"):
shutil.rmtree("build", ignore_errors=True)
os.makedirs("build", exist_ok=True)
os.chdir("build")
if platform.system() != "Windows":
succeeded = runner.run_command(
"cmake",
"..",
"-GNinja",
f"-DCMAKE_BUILD_TYPE={matrix['target']}",
f"-DCMAKE_CXX_COMPILER={matrix['compiler']}",
f"-DCMAKE_CXX_STANDARD={matrix['std']}",
f"-DCPPTRACE_USE_EXTERNAL_LIBDWARF=On",
f"-DCPPTRACE_USE_EXTERNAL_ZSTD=On",
f"-DCPPTRACE_WERROR_BUILD=On",
f"-D{matrix['unwind']}=On",
f"-D{matrix['symbols']}=On",
f"-D{matrix['demangle']}=On",
"-DCPPTRACE_BACKTRACE_PATH=/usr/lib/gcc/x86_64-linux-gnu/10/include/backtrace.h",
)
if succeeded:
succeeded = runner.run_command("ninja")
else:
args = [
"cmake",
"..",
f"-DCMAKE_BUILD_TYPE={matrix['target']}",
f"-DCMAKE_CXX_COMPILER={matrix['compiler']}",
f"-DCMAKE_CXX_STANDARD={matrix['std']}",
f"-DCPPTRACE_USE_EXTERNAL_LIBDWARF=On",
f"-DCPPTRACE_USE_EXTERNAL_ZSTD=On",
f"-DCPPTRACE_WERROR_BUILD=On",
f"-D{matrix['unwind']}=On",
f"-D{matrix['symbols']}=On",
f"-D{matrix['demangle']}=On",
]
if matrix["compiler"] == "g++":
args.append("-GUnix Makefiles")
succeeded = runner.run_command(*args)
if succeeded:
if matrix["compiler"] == "g++":
succeeded = runner.run_command("make", "-j", "VERBOSE=1")
else:
succeeded = runner.run_command("msbuild", "cpptrace.sln")
os.chdir("..")
print()
return succeeded
def run_linux_matrix(compilers: list):
MatrixRunner(
matrix = {
"compiler": compilers,
"target": ["Debug"],
"std": ["11", "20"],
"unwind": [
# "CPPTRACE_UNWIND_WITH_UNWIND",
# "CPPTRACE_UNWIND_WITH_EXECINFO",
# "CPPTRACE_UNWIND_WITH_LIBUNWIND",
"CPPTRACE_UNWIND_WITH_NOTHING",
],
"symbols": [
"CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE",
# "CPPTRACE_GET_SYMBOLS_WITH_LIBDL",
# "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF",
# "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE",
"CPPTRACE_GET_SYMBOLS_WITH_NOTHING",
],
"demangle": [
# "CPPTRACE_DEMANGLE_WITH_CXXABI",
"CPPTRACE_DEMANGLE_WITH_NOTHING",
],
},
exclude = []
).run(build)
def run_macos_matrix(compilers: list):
MatrixRunner(
matrix = {
"compiler": compilers,
"target": ["Debug"],
"std": ["11", "20"],
"unwind": [
# "CPPTRACE_UNWIND_WITH_UNWIND",
# "CPPTRACE_UNWIND_WITH_EXECINFO",
"CPPTRACE_UNWIND_WITH_NOTHING",
],
"symbols": [
# "CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE",
# "CPPTRACE_GET_SYMBOLS_WITH_LIBDL",
# "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF",
# "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE",
"CPPTRACE_GET_SYMBOLS_WITH_NOTHING",
],
"demangle": [
# "CPPTRACE_DEMANGLE_WITH_CXXABI",
"CPPTRACE_DEMANGLE_WITH_NOTHING",
]
},
exclude = []
).run(build)
def run_windows_matrix(compilers: list):
MatrixRunner(
matrix = {
"compiler": compilers,
"target": ["Debug"],
"std": ["11", "20"],
"unwind": [
# "CPPTRACE_UNWIND_WITH_WINAPI",
# "CPPTRACE_UNWIND_WITH_DBGHELP",
# "CPPTRACE_UNWIND_WITH_UNWIND",
"CPPTRACE_UNWIND_WITH_NOTHING",
],
"symbols": [
# "CPPTRACE_GET_SYMBOLS_WITH_DBGHELP",
# "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF",
# "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE",
"CPPTRACE_GET_SYMBOLS_WITH_NOTHING",
],
"demangle": [
# "CPPTRACE_DEMANGLE_WITH_CXXABI",
"CPPTRACE_DEMANGLE_WITH_NOTHING",
]
},
exclude = []
).run(build)
def main():
parser = argparse.ArgumentParser(
prog="Build in all configs",
description="Try building the library in all possible configurations for the current host"
)
parser.add_argument(
"--clang",
action="store_true"
)
parser.add_argument(
"--gcc",
action="store_true"
)
parser.add_argument(
"--msvc",
action="store_true"
)
parser.add_argument(
"--all",
action="store_true"
)
args = parser.parse_args()
if platform.system() == "Linux":
compilers = []
if args.clang or args.all:
compilers.append("clang++-14")
if args.gcc or args.all:
compilers.append("g++-10")
run_linux_matrix(compilers)
if platform.system() == "Darwin":
compilers = []
if args.clang or args.all:
compilers.append("clang++")
if args.gcc or args.all:
compilers.append("g++-12")
run_macos_matrix(compilers)
if platform.system() == "Windows":
compilers = []
if args.clang or args.all:
compilers.append("clang++")
if args.msvc or args.all:
compilers.append("cl")
if args.gcc or args.all:
compilers.append("g++")
run_windows_matrix(compilers)
main()

View File

@ -17,7 +17,7 @@ mkdir libdwarf
cd libdwarf
git init
git remote add origin https://github.com/jeremy-rifkin/libdwarf-lite.git
git fetch --depth 1 origin 5c0cb251f94b27e90184e6b2d9a0c9c62593babc
git fetch --depth 1 origin fe09ca800b988e2ff21225ac5e7468ceade2a30e
git checkout FETCH_HEAD
mkdir build
cd build

View File

@ -0,0 +1,44 @@
#!/bin/bash
mkdir zstd
cd zstd
git init
git remote add origin https://github.com/facebook/zstd.git
git fetch --depth 1 origin 794ea1b0afca0f020f4e57b6732332231fb23c70 # 1.5.6
git checkout FETCH_HEAD
make -j
sudo make install
cd ..
mkdir libdwarf
cd libdwarf
git init
git remote add origin https://github.com/jeremy-rifkin/libdwarf-lite.git
git fetch --depth 1 origin fe09ca800b988e2ff21225ac5e7468ceade2a30e # 0.11.1
git checkout FETCH_HEAD
mkdir build
cd build
cmake .. -GNinja -DPIC_ALWAYS=TRUE -DBUILD_DWARFDUMP=FALSE
sudo ninja install
cd ../..
mkdir googletest
cd googletest
git init
git remote add origin https://github.com/google/googletest.git
git fetch --depth 1 origin f8d7d77c06936315286eb55f8de22cd23c188571
git checkout FETCH_HEAD
mkdir build
cd build
cmake .. -GNinja -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_INSTALL_PREFIX=/tmp/gtest_install
sudo ninja install
rm -rf *
# There's a false-positive container-overflow for apple clang/relwithdebinfo/sanitizers=on if gtest isn't built with
# sanitizers. https://github.com/google/sanitizers/wiki/AddressSanitizerContainerOverflow#false-positives
cmake .. -GNinja -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_INSTALL_PREFIX=/tmp/gtest_asan_install -DCMAKE_CXX_FLAGS=-fsanitize=address
sudo ninja install
rm -rf *
cmake .. -GNinja -DCMAKE_CXX_COMPILER=g++-12 -DCMAKE_INSTALL_PREFIX=/tmp/gtest_install_gcc
sudo ninja install

View File

@ -0,0 +1,39 @@
#!/bin/bash
mkdir zstd
cd zstd
git init
git remote add origin https://github.com/facebook/zstd.git
git fetch --depth 1 origin 794ea1b0afca0f020f4e57b6732332231fb23c70 # 1.5.6
git checkout FETCH_HEAD
make -j
sudo make install
cd ..
mkdir libdwarf
cd libdwarf
git init
git remote add origin https://github.com/jeremy-rifkin/libdwarf-lite.git
git fetch --depth 1 origin fe09ca800b988e2ff21225ac5e7468ceade2a30e # 0.11.1
git checkout FETCH_HEAD
mkdir build
cd build
cmake .. -GNinja -DPIC_ALWAYS=TRUE -DBUILD_DWARFDUMP=FALSE
sudo ninja install
cd ../..
mkdir googletest
cd googletest
git init
git remote add origin https://github.com/google/googletest.git
git fetch --depth 1 origin f8d7d77c06936315286eb55f8de22cd23c188571
git checkout FETCH_HEAD
mkdir build
cd build
cmake .. -GNinja -DCMAKE_INSTALL_PREFIX=/tmp/gtest_install
sudo ninja install
rm -rf *
cmake .. -GNinja -DCMAKE_CXX_COMPILER=clang++-18 -DCMAKE_CXX_FLAGS=-stdlib=libc++ -DCMAKE_INSTALL_PREFIX=/tmp/gtest_install_libcxx
sudo ninja install

View File

@ -5,7 +5,7 @@ mkdir zstd
cd zstd
git init
git remote add origin https://github.com/facebook/zstd.git
git fetch --depth 1 origin 63779c798237346c2b245c546c40b72a5a5913fe # 1.5.5
git fetch --depth 1 origin 794ea1b0afca0f020f4e57b6732332231fb23c70 # 1.5.6
git checkout FETCH_HEAD
make -j
sudo make install
@ -16,7 +16,7 @@ mkdir libdwarf
cd libdwarf
git init
git remote add origin https://github.com/jeremy-rifkin/libdwarf-lite.git
git fetch --depth 1 origin 5c0cb251f94b27e90184e6b2d9a0c9c62593babc
git fetch --depth 1 origin fe09ca800b988e2ff21225ac5e7468ceade2a30e
git checkout FETCH_HEAD
mkdir build
cd build

View File

@ -11,8 +11,6 @@ from util import *
sys.stdout.reconfigure(encoding='utf-8') # for windows gh runner
failed = False
expected_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../test/expected/")
def get_c_compiler_counterpart(compiler: str) -> str:
@ -110,57 +108,13 @@ def output_matches(raw_output: str, params: Tuple[str]):
return not errored
def run_command(*args: List[str], always_output=False):
global failed
print(f"{Fore.CYAN}{Style.BRIGHT}Running Command \"{' '.join(args)}\"{Style.RESET_ALL}")
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
print(Style.RESET_ALL, end="") # makefile in parallel sometimes messes up colors
if p.returncode != 0:
print(f"{Fore.RED}{Style.BRIGHT}Command failed{Style.RESET_ALL}")
print("stdout:")
print(stdout.decode("utf-8"), end="")
print("stderr:")
print(stderr.decode("utf-8"), end="")
failed = True
return False
else:
print(f"{Fore.GREEN}{Style.BRIGHT}Command succeeded{Style.RESET_ALL}")
if always_output:
print("stdout:")
print(stdout.decode("utf-8"), end="")
print("stderr:")
print(stderr.decode("utf-8"), end="")
return True
def run_test(runner: MatrixRunner, test_binary, params: Tuple[str]):
def output_matcher(output: str):
return output_matches(output, params)
return runner.run_command(test_binary, output_matcher=output_matcher)
def run_test(test_binary, params: Tuple[str]):
global failed
print(f"{Fore.CYAN}{Style.BRIGHT}Running test{Style.RESET_ALL}")
test = subprocess.Popen([test_binary], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
test_stdout, test_stderr = test.communicate()
print(Style.RESET_ALL, end="") # makefile in parallel sometimes messes up colors
if test.returncode != 0:
print(f"[🔴 Test command failed with code {test.returncode}]")
print("stderr:")
print(test_stderr.decode("utf-8"), end="")
print("stdout:")
print(test_stdout.decode("utf-8"), end="")
failed = True
return False
else:
if len(test_stderr) != 0:
print("stderr:")
print(test_stderr.decode("utf-8"), end="")
if output_matches(test_stdout.decode("utf-8"), params):
print(f"{Fore.GREEN}{Style.BRIGHT}Test succeeded{Style.RESET_ALL}")
return True
else:
print(f"{Fore.RED}{Style.BRIGHT}Test failed{Style.RESET_ALL}")
failed = True
return False
def build(matrix):
def build(runner: MatrixRunner):
matrix = runner.current_config()
if platform.system() != "Windows":
args = [
"cmake",
@ -182,9 +136,9 @@ def build(matrix):
]
if matrix['symbols'] == "CPPTRACE_GET_SYMBOLS_WITH_LIBDL":
args.append("-DCPPTRACE_BUILD_TEST_RDYNAMIC=On")
succeeded = run_command(*args)
succeeded = runner.run_command(*args)
if succeeded:
return run_command("make", "-j")
return runner.run_command("make", "-j")
else:
args = [
"cmake",
@ -205,15 +159,16 @@ def build(matrix):
]
if matrix["compiler"] == "g++":
args.append("-GUnix Makefiles")
succeeded = run_command(*args)
succeeded = runner.run_command(*args)
if succeeded:
if matrix["compiler"] == "g++":
return run_command("make", "-j")
return runner.run_command("make", "-j")
else:
return run_command("msbuild", "cpptrace.sln")
return runner.run_command("msbuild", "cpptrace.sln")
return False
def build_full_or_auto(matrix):
def build_full_or_auto(runner: MatrixRunner):
matrix = runner.current_config()
if platform.system() != "Windows":
args = [
"cmake",
@ -232,9 +187,9 @@ def build_full_or_auto(matrix):
]
if matrix["config"] != "":
args.append(f"{matrix['config']}")
succeeded = run_command(*args)
succeeded = runner.run_command(*args)
if succeeded:
return run_command("make", "-j")
return runner.run_command("make", "-j")
else:
args = [
"cmake",
@ -254,52 +209,60 @@ def build_full_or_auto(matrix):
args.append(f"{matrix['config']}")
if matrix["compiler"] == "g++":
args.append("-GUnix Makefiles")
succeeded = run_command(*args)
succeeded = runner.run_command(*args)
if succeeded:
if matrix["compiler"] == "g++":
return run_command("make", "-j")
return runner.run_command("make", "-j")
else:
return run_command("msbuild", "cpptrace.sln")
return runner.run_command("msbuild", "cpptrace.sln")
return False
def test(matrix):
def test(runner: MatrixRunner):
matrix = runner.current_config()
if platform.system() != "Windows":
return run_test(
runner,
"./integration",
(matrix["compiler"], matrix["unwind"], matrix["symbols"], matrix["demangle"])
)
else:
if matrix["compiler"] == "g++":
return run_test(
runner,
f".\\integration.exe",
(matrix["compiler"], matrix["unwind"], matrix["symbols"], matrix["demangle"])
)
else:
return run_test(
runner,
f".\\{matrix['target']}\\integration.exe",
(matrix["compiler"], matrix["unwind"], matrix["symbols"], matrix["demangle"])
)
def test_full_or_auto(matrix):
def test_full_or_auto(runner: MatrixRunner):
matrix = runner.current_config()
if platform.system() != "Windows":
return run_test(
runner,
"./integration",
(matrix["compiler"],)
)
else:
if matrix["compiler"] == "g++":
return run_test(
runner,
f".\\integration.exe",
(matrix["compiler"],)
)
else:
return run_test(
runner,
f".\\{matrix['target']}\\integration.exe",
(matrix["compiler"],)
)
def build_and_test(matrix):
print(f"{Fore.BLUE}{Style.BRIGHT}{'=' * 10} Running build and test with config {', '.join(matrix.values())} {'=' * 10}{Style.RESET_ALL}")
def build_and_test(runner: MatrixRunner):
matrix = runner.current_config()
if os.path.exists("build"):
shutil.rmtree("build", ignore_errors=True)
@ -309,16 +272,16 @@ def build_and_test(matrix):
os.chdir("build")
good = False
if build(matrix):
good = test(matrix)
if build(runner):
good = test(runner)
os.chdir("..")
print()
return good
def build_and_test_full_or_auto(matrix):
print(f"{Fore.BLUE}{Style.BRIGHT}{'=' * 10} Running build and test with config {'<auto>' if matrix['config'] == '' else ', '.join(matrix.values())} {'=' * 10}{Style.RESET_ALL}")
def build_and_test_full_or_auto(runner: MatrixRunner):
matrix = runner.current_config()
if os.path.exists("build"):
shutil.rmtree("build", ignore_errors=True)
@ -328,8 +291,8 @@ def build_and_test_full_or_auto(matrix):
os.chdir("build")
good = False
if build_full_or_auto(matrix):
good = test_full_or_auto(matrix)
if build_full_or_auto(runner):
good = test_full_or_auto(runner)
os.chdir("..")
print()
@ -337,6 +300,7 @@ def build_and_test_full_or_auto(matrix):
return good
def run_linux_matrix(compilers: list, shared: bool):
MatrixRunner(
matrix = {
"compiler": compilers,
"target": ["Debug"],
@ -360,22 +324,24 @@ def run_linux_matrix(compilers: list, shared: bool):
#"CPPTRACE_DEMANGLE_WITH_NOTHING",
],
"shared": ["On" if shared else "Off"]
}
},
exclude = []
run_matrix(matrix, exclude, build_and_test)
).run(build_and_test)
def run_linux_default(compilers: list, shared: bool):
MatrixRunner(
matrix = {
"compiler": compilers,
"target": ["Debug"],
"std": ["11", "20"],
"config": [""],
"shared": ["On" if shared else "Off"]
}
},
exclude = []
run_matrix(matrix, exclude, build_and_test_full_or_auto)
).run(build_and_test_full_or_auto)
def run_macos_matrix(compilers: list, shared: bool):
MatrixRunner(
matrix = {
"compiler": compilers,
"target": ["Debug"],
@ -397,22 +363,24 @@ def run_macos_matrix(compilers: list, shared: bool):
#"CPPTRACE_DEMANGLE_WITH_NOTHING",
],
"shared": ["On" if shared else "Off"]
}
},
exclude = []
run_matrix(matrix, exclude, build_and_test)
).run(build_and_test)
def run_macos_default(compilers: list, shared: bool):
MatrixRunner(
matrix = {
"compiler": compilers,
"target": ["Debug"],
"std": ["11", "20"],
"config": [""],
"shared": ["On" if shared else "Off"]
}
},
exclude = []
run_matrix(matrix, exclude, build_and_test_full_or_auto)
).run(build_and_test_full_or_auto)
def run_windows_matrix(compilers: list, shared: bool):
MatrixRunner(
matrix = {
"compiler": compilers,
"target": ["Debug"],
@ -434,7 +402,7 @@ def run_windows_matrix(compilers: list, shared: bool):
"CPPTRACE_DEMANGLE_WITH_NOTHING",
],
"shared": ["On" if shared else "Off"]
}
},
exclude = [
{
"demangle": "CPPTRACE_DEMANGLE_WITH_CXXABI",
@ -481,18 +449,19 @@ def run_windows_matrix(compilers: list, shared: bool):
"demangle": "CPPTRACE_DEMANGLE_WITH_NOTHING"
}
]
run_matrix(matrix, exclude, build_and_test)
).run(build_and_test)
def run_windows_default(compilers: list, shared: bool):
MatrixRunner(
matrix = {
"compiler": compilers,
"target": ["Debug"],
"std": ["11", "20"],
"config": [""],
"shared": ["On" if shared else "Off"]
}
},
exclude = []
run_matrix(matrix, exclude, build_and_test_full_or_auto)
).run(build_and_test_full_or_auto)
def main():
parser = argparse.ArgumentParser(
@ -558,9 +527,4 @@ def main():
else:
run_windows_matrix(compilers, args.shared)
global failed
if failed:
print("🔴 Some checks failed")
sys.exit(1)
main()

168
ci/unittest.py Normal file
View File

@ -0,0 +1,168 @@
import argparse
import os
import platform
import shutil
import subprocess
import sys
from typing import Tuple
from colorama import Fore, Back, Style
from util import *
sys.stdout.reconfigure(encoding='utf-8') # for windows gh runner
def get_c_compiler_counterpart(compiler: str) -> str:
return compiler.replace("clang++", "clang").replace("g++", "gcc")
def build(runner: MatrixRunner):
if platform.system() == "Linux":
matrix = runner.current_config()
if "stdlib" in matrix and matrix["stdlib"] == "libc++":
gtest_path = "/tmp/gtest_install_libcxx"
else:
gtest_path = "/tmp/gtest_install"
args = [
"cmake",
"..",
"-GNinja",
f"-DCMAKE_CXX_COMPILER={matrix['compiler']}",
f"-DCMAKE_C_COMPILER={get_c_compiler_counterpart(matrix['compiler'])}",
f"-DCMAKE_BUILD_TYPE={matrix['build_type']}",
f"-DCPPTRACE_BUILD_SHARED={matrix['shared']}",
f"-DHAS_DL_FIND_OBJECT={matrix['has_dl_find_object']}",
"-DCPPTRACE_WERROR_BUILD=On",
"-DCPPTRACE_STD_FORMAT=Off",
"-DCPPTRACE_BUILD_TESTING=On",
f"-DCPPTRACE_SANITIZER_BUILD={matrix['sanitizers']}",
f"-DCPPTRACE_BUILD_NO_SYMBOLS={matrix['symbols']}",
f"-DCPPTRACE_BUILD_TESTING_SPLIT_DWARF={matrix['split_dwarf']}",
f"-DCPPTRACE_BUILD_TESTING_DWARF_VERSION={matrix['dwarf_version']}",
f"-DCPPTRACE_USE_EXTERNAL_LIBDWARF=On",
f"-DCPPTRACE_USE_EXTERNAL_ZSTD=On",
f"-DCPPTRACE_USE_EXTERNAL_GTEST=On",
f"-DCMAKE_PREFIX_PATH={gtest_path}",
*(["-DCMAKE_CXX_FLAGS=-stdlib=libc++"] if "stdlib" in matrix and matrix["stdlib"] == "libc++" else [])
]
return runner.run_command(*args) and runner.run_command("ninja")
elif platform.system() == "Darwin":
matrix = runner.current_config()
if "clang++" in matrix["compiler"]:
gtest_path = "/tmp/gtest_asan_install" if matrix['sanitizers'] == "ON" else "/tmp/gtest_install"
else:
gtest_path = "/tmp/gtest_install_gcc"
args = [
"cmake",
"..",
"-GNinja",
f"-DCMAKE_CXX_COMPILER={matrix['compiler']}",
f"-DCMAKE_C_COMPILER={get_c_compiler_counterpart(matrix['compiler'])}",
f"-DCMAKE_BUILD_TYPE={matrix['build_type']}",
f"-DCPPTRACE_BUILD_SHARED={matrix['shared']}",
"-DCPPTRACE_WERROR_BUILD=On",
"-DCPPTRACE_STD_FORMAT=Off",
"-DCPPTRACE_BUILD_TESTING=On",
f"-DCPPTRACE_SANITIZER_BUILD={matrix['sanitizers']}",
f"-DCPPTRACE_BUILD_NO_SYMBOLS={matrix['symbols']}",
# f"-DCPPTRACE_BUILD_TESTING_SPLIT_DWARF={matrix['split_dwarf']}",
# f"-DCPPTRACE_BUILD_TESTING_SPLIT_DWARF={matrix['dwarf_version']}",
f"-DCPPTRACE_USE_EXTERNAL_LIBDWARF=On",
f"-DCPPTRACE_USE_EXTERNAL_ZSTD=On",
f"-DCPPTRACE_USE_EXTERNAL_GTEST=On",
f"-DCMAKE_PREFIX_PATH={gtest_path}",
]
return runner.run_command(*args) and runner.run_command("ninja")
else:
raise ValueError()
def test(runner: MatrixRunner):
if platform.system() == "Linux":
return runner.run_command("./unittest") and runner.run_command("bash", "-c", "exec -a u ./unittest")
elif platform.system() == "Darwin":
if runner.current_config()["dSYM"]:
if not runner.run_command("dsymutil", "unittest"):
return False
good = runner.run_command("./unittest") and runner.run_command("bash", "-c", "exec -a u ./unittest")
if runner.current_config()["dSYM"]:
shutil.rmtree("unittest.dSYM")
return good
else:
raise ValueError()
def build_and_test(runner: MatrixRunner):
# the build directory has to be purged on compiler or shared change
last = runner.last_config()
current = runner.current_config()
if (
last is None
or last["compiler"] != current["compiler"]
or ("stdlib" in current and last["stdlib"] != current["stdlib"])
or (platform.system() == "Darwin" and last["sanitizers"] != current["sanitizers"])
) and os.path.exists("build"):
shutil.rmtree("build", ignore_errors=True)
if not os.path.exists("build"):
os.mkdir("build")
os.chdir("build")
good = False
if build(runner):
good = test(runner)
os.chdir("..")
print(flush=True)
return good
def run_linux_matrix():
MatrixRunner(
matrix = {
"compiler": ["g++-10", "clang++-18"],
"stdlib": ["libstdc++", "libc++"],
"sanitizers": ["OFF", "ON"],
"build_type": ["Debug", "RelWithDebInfo"],
"shared": ["OFF", "ON"],
"has_dl_find_object": ["OFF", "ON"],
"split_dwarf": ["OFF", "ON"],
"dwarf_version": ["4", "5"],
"symbols": ["On", "Off"],
},
exclude = [
{
"compiler": "g++-10",
"stdlib": "libc++",
},
{
# need to workaround https://github.com/llvm/llvm-project/issues/59432 later
"stdlib": "libc++",
"sanitizers": "ON",
},
]
).run(build_and_test)
def run_macos_matrix():
MatrixRunner(
matrix = {
"compiler": ["g++-12", "clang++"],
"sanitizers": ["OFF", "ON"],
"build_type": ["Debug", "RelWithDebInfo"],
"shared": ["OFF", "ON"],
"dSYM": [True, False],
"symbols": ["On", "Off"],
},
exclude = [
{
"compiler": "g++-12",
"sanitizers": "ON",
},
]
).run(build_and_test)
def main():
if platform.system() == "Linux":
run_linux_matrix()
if platform.system() == "Darwin":
run_macos_matrix()
if platform.system() == "Windows":
raise ValueError() # run_windows_matrix()
main()

View File

@ -1,9 +1,10 @@
import subprocess
import sys
import itertools
from typing import List
from typing import List, Dict
from colorama import Fore, Back, Style
import re
import time
# https://stackoverflow.com/a/14693789/15675011
ansi_escape = re.compile(r'''
@ -18,95 +19,141 @@ ansi_escape = re.compile(r'''
)
''', re.VERBOSE)
def adj_width(text):
return len(text) - len(ansi_escape.sub("", text))
class MatrixRunner:
def __init__(self, matrix, exclude):
self.matrix = matrix
self.exclude = exclude
self.include = self.parse_includes()
self.keys = [*matrix.keys()]
self.values = [*matrix.values()]
self.results = {} # insertion-ordered
self.failed = False
self.work = self.get_work()
def do_exclude(matrix_config, exclude):
self.last_matrix_config = None
self.current_matrix_config = None
def parse_includes(self) -> Dict[str, List[str]]:
includes: Dict[str, List[str]] = dict()
for arg in sys.argv:
if arg.startswith("--slice="):
rest = arg[len("--slice="):]
key, value = rest.split(":")
if key not in includes:
includes[key] = []
includes[key].append(value)
return includes
def run_command(self, *args: List[str], always_output=False, output_matcher=None) -> bool:
self.log(f"{Fore.CYAN}{Style.BRIGHT}Running Command \"{' '.join(args)}\"{Style.RESET_ALL}")
start_time = time.time()
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
runtime = time.time() - start_time
self.log(Style.RESET_ALL, end="") # makefile in parallel sometimes messes up colors
if p.returncode != 0:
self.log(f"{Fore.RED}{Style.BRIGHT}Command failed{Style.RESET_ALL} {Fore.MAGENTA}(time: {runtime:.2f}s){Style.RESET_ALL}")
self.log("stdout:")
self.log(stdout.decode("utf-8"), end="")
self.log("stderr:")
self.log(stderr.decode("utf-8"), end="")
self.failed = True
return False
else:
self.log(f"{Fore.GREEN}{Style.BRIGHT}Command succeeded{Style.RESET_ALL} {Fore.MAGENTA}(time: {runtime:.2f}s){Style.RESET_ALL}")
if always_output:
self.log("stdout:")
self.log(stdout.decode("utf-8"), end="")
self.log("stderr:")
self.log(stderr.decode("utf-8"), end="")
elif len(stderr) != 0:
self.log("stderr:")
self.log(stderr.decode("utf-8"), end="")
if output_matcher is not None:
if not output_matcher(stdout.decode("utf-8")):
self.failed = True
return False
return True
def set_fail(self):
self.failed = True
def current_config(self):
return self.current_matrix_config
def last_config(self):
return self.last_matrix_config
def log(self, *args, **kwargs):
print(*args, **kwargs, flush=True)
def do_exclude(self, matrix_config, exclude):
return all(map(lambda k: matrix_config[k] == exclude[k], exclude.keys()))
def print_table(table):
def do_include(self, matrix_config, include):
if len(include) == 0:
return True
return all(map(lambda k: matrix_config[k] in include[k], include.keys()))
def assignment_to_matrix_config(self, assignment):
matrix_config = {}
for k, v in zip(self.matrix.keys(), assignment):
matrix_config[k] = v
return matrix_config
def get_work(self):
work = []
for assignment in itertools.product(*self.matrix.values()):
config = self.assignment_to_matrix_config(assignment)
if any(map(lambda ex: self.do_exclude(config, ex), self.exclude)):
continue
if not self.do_include(config, self.include):
continue
work.append(assignment)
return work
def run(self, fn):
for i, assignment in enumerate(self.work):
matrix_config = self.assignment_to_matrix_config(assignment)
config_tuple = tuple(self.values[i].index(p) for i, p in enumerate(assignment))
config_str = ', '.join(map(lambda v: str(v), matrix_config.values()))
if config_str == "":
self.log(f"{Fore.BLUE}{Style.BRIGHT}{'=' * 10} [{i + 1}/{len(self.work)}] Running with blank config {'=' * 10}{Style.RESET_ALL}")
else:
self.log(f"{Fore.BLUE}{Style.BRIGHT}{'=' * 10} [{i + 1}/{len(self.work)}] Running with config {config_str} {'=' * 10}{Style.RESET_ALL}")
self.last_matrix_config = self.current_matrix_config
self.current_matrix_config = matrix_config
self.results[config_tuple] = fn(self)
self.print_results()
if self.failed:
self.log("🔴 Some checks failed")
sys.exit(1)
else:
self.log("🟢 All checks passed")
def adj_width(self, text):
return len(text) - len(ansi_escape.sub("", text))
def print_table(self, table):
columns = len(table[0])
column_widths = [1 for _ in range(columns)]
for row in table:
for i, cell in enumerate(row):
column_widths[i] = max(column_widths[i], len(ansi_escape.sub("", cell)))
for j, cell in enumerate(table[0]):
print("| {cell:{width}} ".format(cell=cell, width=column_widths[j] + adj_width(cell)), end="")
print("|")
self.log("| {cell:{width}} ".format(cell=cell, width=column_widths[j] + self.adj_width(cell)), end="")
self.log("|")
for i, row in enumerate(table[1:]):
for j, cell in enumerate(row):
print("| {cell:{width}} ".format(cell=cell, width=column_widths[j] + adj_width(cell)), end="")
print("|")
self.log("| {cell:{width}} ".format(cell=cell, width=column_widths[j] + self.adj_width(cell)), end="")
self.log("|")
def run_matrix(matrix, exclude, fn):
keys = [*matrix.keys()]
values = [*matrix.values()]
#print("Values:", values)
results = {} # insertion-ordered
for config in itertools.product(*matrix.values()):
#print(config)
matrix_config = {}
for k, v in zip(matrix.keys(), config):
matrix_config[k] = v
#print(matrix_config)
if any(map(lambda ex: do_exclude(matrix_config, ex), exclude)):
continue
else:
config_tuple = tuple(values[i].index(p) for i, p in enumerate(config))
results[config_tuple] = fn(matrix_config)
# Fudged data for testing
#print(config_tuple)
#if "symbols" not in matrix_config:
# results[config_tuple] = matrix_config["compiler"] != "g++-10"
#else:
# results[config_tuple] = not (matrix_config["compiler"] == "clang++-14" and matrix_config["symbols"] == "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE")
# I had an idea for printing 2d slices of the n-dimensional matrix, but it didn't pan out as much as I'd hoped
dimensions = len(values)
# # Output diagnostic tables
# print("Results:", results)
# if dimensions >= 2:
# for iteraxes in itertools.combinations(range(dimensions), dimensions - 2):
# # iteraxes are the axes we iterate over to slice, these fixed axes are the axes of the table
# # just the complement of axes, these are the two fixed axes
# fixed = [x for x in range(dimensions) if x not in iteraxes]
# assert(len(fixed) == 2)
# if any([len(values[i]) == 1 for i in fixed]):
# continue
# print("Fixed:", fixed)
# for iteraxesvalues in itertools.product(
# *[range(len(values[i])) if i in iteraxes else [-1] for i in range(dimensions)]
# ):
# print(">>", iteraxesvalues)
# # Now that we have our iteraxes values we have a unique plane
# table = [
# ["", *[value for value in values[fixed[0]]]]
# ]
# #print(values[fixed[1]])
# for row_i, row_value in enumerate(values[fixed[1]]):
# row = [row_value]
# for col_i in range(len(values[fixed[0]])):
# iteraxesvaluescopy = [x for x in iteraxesvalues]
# iteraxesvaluescopy[fixed[1]] = row_i
# iteraxesvaluescopy[fixed[0]] = col_i
# #print("----->", iteraxesvaluescopy)
# row.append(
# f"{Fore.GREEN}{Style.BRIGHT}Good{Style.RESET_ALL}"
# if results[tuple(iteraxesvaluescopy)]
# else f"{Fore.RED}{Style.BRIGHT}Bad{Style.RESET_ALL}"
# if tuple(iteraxesvaluescopy) in results else ""
# )
# table.append(row)
# print_table(table)
# Better idea would be looking for m<n tuples that are consistently failing and reporting on those
#for fixed_axes in itertools.product(range(dimensions), 2):
# pass
print("Results:")
table = [keys]
for result in results:
def print_results(self):
self.log("Results:")
table = [self.keys]
for result in self.results:
table.append([
f"{Fore.GREEN if results[result] else Fore.RED}{Style.BRIGHT}{values[i][v]}{Style.RESET_ALL}"
f"{Fore.GREEN if self.results[result] else Fore.RED}{Style.BRIGHT}{self.values[i][v]}{Style.RESET_ALL}"
for i, v in enumerate(result)
])
print_table(table)
self.print_table(table)

137
cmake/Autoconfig.cmake Normal file
View File

@ -0,0 +1,137 @@
# ================================================== Platform Support ==================================================
function(check_support var source includes libraries definitions)
set(CMAKE_REQUIRED_INCLUDES "${includes}")
list(APPEND CMAKE_REQUIRED_INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
set(CMAKE_REQUIRED_LIBRARIES "${libraries}")
set(CMAKE_REQUIRED_DEFINITIONS "${definitions}")
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
string(CONCAT full_source "#include \"${source}\"" ${nonce})
check_cxx_source_compiles(${full_source} ${var})
set(${var} ${${var}} PARENT_SCOPE)
endfunction()
if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
check_support(HAS_CXXABI has_cxxabi.cpp "" "" "")
endif()
if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
check_support(HAS_ATTRIBUTE_PACKED has_attribute_packed.cpp "" "" "")
endif()
if(NOT WIN32)
check_support(HAS_UNWIND has_unwind.cpp "" "" "")
check_support(HAS_EXECINFO has_execinfo.cpp "" "" "")
else()
check_support(HAS_STACKWALK has_stackwalk.cpp "" "dbghelp" "")
endif()
if(NOT WIN32 OR MINGW)
check_support(HAS_BACKTRACE has_backtrace.cpp "" "backtrace" "${CPPTRACE_BACKTRACE_PATH_DEFINITION}")
set(STACKTRACE_LINK_LIB "stdc++_libbacktrace")
check_support(HAS_CXX_EXCEPTION_TYPE has_cxx_exception_type.cpp "" "" "")
endif()
if(UNIX AND NOT APPLE)
check_support(HAS_DL_FIND_OBJECT has_dl_find_object.cpp "" "dl" "")
if(NOT HAS_DL_FIND_OBJECT)
check_support(HAS_DLADDR1 has_dladdr1.cpp "" "dl" "")
endif()
endif()
if(APPLE)
check_support(HAS_MACH_VM has_mach_vm.cpp "" "" "")
endif()
# ================================================ Autoconfig unwinding ================================================
# Unwind back-ends
if(
NOT (
CPPTRACE_UNWIND_WITH_UNWIND OR
CPPTRACE_UNWIND_WITH_LIBUNWIND OR
CPPTRACE_UNWIND_WITH_EXECINFO OR
CPPTRACE_UNWIND_WITH_WINAPI OR
CPPTRACE_UNWIND_WITH_DBGHELP OR
CPPTRACE_UNWIND_WITH_NOTHING
)
)
# Attempt to auto-config
if(APPLE AND ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang"))
if(HAS_EXECINFO)
set(CPPTRACE_UNWIND_WITH_EXECINFO On)
message(STATUS "Cpptrace auto config: Using execinfo.h for unwinding")
else()
set(CPPTRACE_UNWIND_WITH_NOTHING On)
message(FATAL_ERROR "Cpptrace auto config: No unwinding back-end seems to be supported, stack tracing will not work. To compile anyway set CPPTRACE_UNWIND_WITH_NOTHING.")
endif()
elseif(UNIX)
if(HAS_UNWIND)
set(CPPTRACE_UNWIND_WITH_UNWIND On)
message(STATUS "Cpptrace auto config: Using libgcc unwind for unwinding")
elseif(HAS_EXECINFO)
set(CPPTRACE_UNWIND_WITH_EXECINFO On)
message(STATUS "Cpptrace auto config: Using execinfo.h for unwinding")
else()
set(CPPTRACE_UNWIND_WITH_NOTHING On)
message(FATAL_ERROR "Cpptrace auto config: No unwinding back-end seems to be supported, stack tracing will not work. To compile anyway set CPPTRACE_UNWIND_WITH_NOTHING.")
endif()
elseif(MINGW OR WIN32)
if(HAS_STACKWALK)
set(CPPTRACE_UNWIND_WITH_DBGHELP On)
message(STATUS "Cpptrace auto config: Using dbghelp for unwinding")
else()
set(CPPTRACE_UNWIND_WITH_WINAPI On)
message(STATUS "Cpptrace auto config: Using winapi for unwinding")
endif()
endif()
else()
#message(STATUS "MANUAL CONFIG SPECIFIED")
endif()
# ================================================= Autoconfig symbols =================================================
if(
NOT (
CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE OR
CPPTRACE_GET_SYMBOLS_WITH_LIBDL OR
CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE OR
CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF OR
CPPTRACE_GET_SYMBOLS_WITH_DBGHELP OR
CPPTRACE_GET_SYMBOLS_WITH_NOTHING
)
)
if(UNIX)
message(STATUS "Cpptrace auto config: Using libdwarf for symbols")
set(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF On)
elseif(MINGW)
message(STATUS "Cpptrace auto config: Using libdwarf + dbghelp for symbols")
# Use both dbghelp and libdwarf under mingw: Some files may use pdb symbols, e.g. system dlls like KERNEL32.dll and
# ntdll.dll at the very least, but also other libraries linked with may have pdb symbols.
set(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF On)
set(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP On)
else()
message(STATUS "Cpptrace auto config: Using dbghelp for symbols")
set(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP On)
endif()
endif()
# =============================================== Autoconfig demangling ================================================
# Handle demangle configuration
if(
NOT (
CPPTRACE_DEMANGLE_WITH_CXXABI OR
CPPTRACE_DEMANGLE_WITH_WINAPI OR
CPPTRACE_DEMANGLE_WITH_NOTHING
)
)
if(HAS_CXXABI)
message(STATUS "Cpptrace auto config: Using cxxabi for demangling")
set(CPPTRACE_DEMANGLE_WITH_CXXABI On)
elseif(WIN32 AND NOT MINGW)
message(STATUS "Cpptrace auto config: Using dbghelp for demangling")
set(CPPTRACE_DEMANGLE_WITH_WINAPI On)
else()
set(CPPTRACE_DEMANGLE_WITH_NOTHING On)
endif()
else()
#message(STATUS "Manual demangling back-end specified")
endif()

View File

@ -5,8 +5,9 @@ include(CMakePackageConfigHelpers)
install(
DIRECTORY
"${PROJECT_SOURCE_DIR}/include/" # our header files
"${PROJECT_BINARY_DIR}/include/" # generated header files
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
COMPONENT ${package_name}-development
COMPONENT ${package_name}_development
# PATTERN "**/third_party" EXCLUDE # skip third party directory
# PATTERN "**/third_party/**" EXCLUDE # skip third party files
)
@ -17,12 +18,12 @@ install(
TARGETS ${target_name}
EXPORT ${package_name}-targets
RUNTIME #
COMPONENT ${package_name}-runtime
COMPONENT ${package_name}_runtime
LIBRARY #
COMPONENT ${package_name}-runtime
NAMELINK_COMPONENT ${package_name}-development
COMPONENT ${package_name}_runtime
NAMELINK_COMPONENT ${package_name}_development
ARCHIVE #
COMPONENT ${package_name}-development
COMPONENT ${package_name}_development
INCLUDES #
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
)
@ -38,7 +39,7 @@ configure_file(
install(
FILES "${PROJECT_BINARY_DIR}/cmake/${package_name}-config.cmake"
DESTINATION "${CPPTRACE_INSTALL_CMAKEDIR}"
COMPONENT ${package_name}-development
COMPONENT ${package_name}_development
)
# create version file for consumer to check version in CMake
@ -51,7 +52,7 @@ write_basic_package_version_file(
install(
FILES "${PROJECT_BINARY_DIR}/${package_name}-config-version.cmake"
DESTINATION "${CPPTRACE_INSTALL_CMAKEDIR}"
COMPONENT ${package_name}-development
COMPONENT ${package_name}_development
)
# create targets file included by config file with targets for consumers
@ -59,9 +60,17 @@ install(
EXPORT ${package_name}-targets
NAMESPACE cpptrace::
DESTINATION "${CPPTRACE_INSTALL_CMAKEDIR}"
COMPONENT ${package_name}-development
COMPONENT ${package_name}_development
)
if(CPPTRACE_PROVIDE_EXPORT_SET)
export(
TARGETS ${target_name}
NAMESPACE cpptrace::
FILE "${PROJECT_BINARY_DIR}/${package_name}-targets.cmake"
)
endif()
# Findzstd.cmake
# vcpkg doesn't like anything being put in share/, which is where this goes apparently on their setup
if(NOT CPPTRACE_VCPKG)

View File

@ -143,7 +143,7 @@ option(CPPTRACE_DEMANGLE_WITH_NOTHING "" OFF)
# ---- Back-end configurations ----
set(CPPTRACE_BACKTRACE_PATH "" CACHE STRING "Path to backtrace.h, if the compiler doesn't already know it. Check /usr/lib/gcc/x86_64-linux-gnu/*/include.")
set(CPPTRACE_HARD_MAX_FRAMES "" CACHE STRING "Hard limit on unwinding depth. Default is 200.")
set(CPPTRACE_HARD_MAX_FRAMES "" CACHE STRING "Hard limit on unwinding depth. Default is 400.")
set(CPPTRACE_ADDR2LINE_PATH "" CACHE STRING "Absolute path to the addr2line executable you want to use.")
option(CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH "" OFF)
@ -151,9 +151,19 @@ option(CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH "" OFF)
if(PROJECT_IS_TOP_LEVEL)
option(CPPTRACE_BUILD_TESTING "" OFF)
option(CPPTRACE_BUILD_TOOLS "" OFF)
option(CPPTRACE_BUILD_BENCHMARK "" OFF)
option(CPPTRACE_BUILD_NO_SYMBOLS "" OFF)
option(CPPTRACE_BUILD_TESTING_SPLIT_DWARF "" OFF)
set(CPPTRACE_BUILD_TESTING_DWARF_VERSION "0" CACHE STRING "")
option(CPPTRACE_BUILD_TEST_RDYNAMIC "" OFF)
mark_as_advanced(
CPPTRACE_BUILD_TESTING
CPPTRACE_BUILD_TOOLS
CPPTRACE_BUILD_BENCHMARK
CPPTRACE_BUILD_NO_SYMBOLS
CPPTRACE_BUILD_TESTING_SPLIT_DWARF
CPPTRACE_BUILD_TESTING_DWARF_VERSION
CPPTRACE_BUILD_TEST_RDYNAMIC
)
endif()
@ -167,7 +177,15 @@ option(CPPTRACE_SANITIZER_BUILD "" OFF)
option(CPPTRACE_WERROR_BUILD "" OFF)
option(CPPTRACE_POSITION_INDEPENDENT_CODE "" ON)
option(CPPTRACE_SKIP_UNIT "" OFF)
option(CPPTRACE_STD_FORMAT "" ON)
option(CPPTRACE_UNPREFIXED_TRY_CATCH "" OFF)
option(CPPTRACE_USE_EXTERNAL_GTEST "" OFF)
set(CPPTRACE_ZSTD_URL "https://github.com/facebook/zstd/releases/download/v1.5.7/zstd-1.5.7.tar.gz" CACHE STRING "")
set(CPPTRACE_LIBDWARF_REPO "https://github.com/jeremy-rifkin/libdwarf-lite.git" CACHE STRING "")
set(CPPTRACE_LIBDWARF_TAG "fe09ca800b988e2ff21225ac5e7468ceade2a30e" CACHE STRING "") # v0.11.1
set(CPPTRACE_LIBDWARF_SHALLOW "1" CACHE STRING "")
option(CPPTRACE_PROVIDE_EXPORT_SET "" ON)
option(CPPTRACE_PROVIDE_EXPORT_SET_FOR_LIBDWARF "" OFF)
mark_as_advanced(
CPPTRACE_BACKTRACE_PATH
@ -179,4 +197,12 @@ mark_as_advanced(
CPPTRACE_VCPKG
CPPTRACE_SKIP_UNIT
CPPTRACE_USE_EXTERNAL_GTEST
CPPTRACE_ZSTD_REPO
CPPTRACE_ZSTD_TAG
CPPTRACE_ZSTD_SHALLOW
CPPTRACE_LIBDWARF_REPO
CPPTRACE_LIBDWARF_TAG
CPPTRACE_LIBDWARF_SHALLOW
CPPTRACE_PROVIDE_EXPORT_SET
CPPTRACE_PROVIDE_EXPORT_SET_FOR_LIBDWARF
)

View File

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

23
cmake/has_mach_vm.cpp Normal file
View File

@ -0,0 +1,23 @@
#include <mach/mach.h>
#include <mach/mach_vm.h>
#include <cstdint>
int main() {
mach_vm_size_t vmsize;
uintptr_t addr = reinterpret_cast<uintptr_t>(&vmsize);
uintptr_t page_addr = addr & ~(4096 - 1);
mach_vm_address_t address = (mach_vm_address_t)page_addr;
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;
mach_vm_region(
mach_task_self(),
&address,
&vmsize,
VM_REGION_BASIC_INFO,
(vm_region_info_t)&info,
&info_count,
&object
);
}

View File

@ -1,3 +1,4 @@
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <dbghelp.h>

11
cmake/in/version-hpp.in Normal file
View File

@ -0,0 +1,11 @@
#ifndef CPPTRACE_VERSION_HPP
#define CPPTRACE_VERSION_HPP
#define CPPTRACE_VERSION_MAJOR @CPPTRACE_VERSION_MAJOR@
#define CPPTRACE_VERSION_MINOR @CPPTRACE_VERSION_MINOR@
#define CPPTRACE_VERSION_PATCH @CPPTRACE_VERSION_PATCH@
#define CPPTRACE_TO_VERSION(MAJOR, MINOR, PATCH) ((MAJOR) * 10000 + (MINOR) * 100 + (PATCH))
#define CPPTRACE_VERSION CPPTRACE_TO_VERSION(CPPTRACE_VERSION_MAJOR, CPPTRACE_VERSION_MINOR, CPPTRACE_VERSION_PATCH)
#endif

View File

@ -168,5 +168,6 @@ struct ctrace_safe_object_frame {
};
size_t ctrace_safe_generate_raw_trace(ctrace_frame_ptr* buffer, size_t size, size_t skip, size_t max_depth);
void ctrace_get_safe_object_frame(ctrace_frame_ptr address, ctrace_safe_object_frame* out);
ctrace_bool can_signal_safe_unwind();
ctrace_bool ctrace_can_signal_safe_unwind();
ctrace_bool ctrace_can_get_safe_object_frame();
```

View File

@ -11,14 +11,21 @@
# Overview
Signal-safe stack tracing is very useful for debugging application crashes, e.g. SIGSEGVs or
SIGTRAPs, but it's very difficult to do correctly and most implementations I see online do this
incorrectly.
Stack traces from signal handlers can provide very helpful information for debugging application crashes, e.g. from
SIGSEGV or SIGTRAP handlers. Signal handlers are really restrictive environments as your application could be
interrupted by a signal at any point, including in the middle of malloc or buffered IO or while holding a lock.
Doing a stack trace in a signal handler is possible but it requires a lot of care. This is difficult to do correctly
and most examples online do this incorrectly.
Signal-safe tracing is difficult because most methods for unwinding are not signal-safe, figuring
out what shared objects addresses are in is tricky to do in a signal-safe manner (`dladdr` isn't
safe), and then the symbol/line resolution process is pretty much impossible to do safely (parsing
dwarf will not be safe).
It is not possible to resolve debug symbols safely in the process from a signal handler without heroic effort. In order
to produce a full trace there are three options:
1. Carefully save the object trace information to be resolved at a later time outside the signal handler
2. Write the object trace information to a file to be resolved later
3. Spawn a new process, communicate object trace information to that process, and have that process do the trace
resolution
For traces on segfaults, e.g., only options 2 and 3 are viable. The this guide will go over approach 3 and the cpptrace
safe API.
# Big-Picture
@ -35,6 +42,10 @@ memory corruption.
> [!IMPORTANT]
> Currently signal-safe stack unwinding is only possible with `libunwind`, more details later.
> [!IMPORTANT]
> `_dl_find_object` is required for signal-safe stack tracing. This is a relatively recent addition to glibc, added in
> glibc 2.35.
> [!CAUTION]
> Calls to shared objects can be lazy-loaded where the first call to the shared object invokes non-signal-safe functions
> such as `malloc()`. Because of this, the signal safe api must be "warmed up" ahead of a signal handler.
@ -53,7 +64,7 @@ namespace cpptrace {
struct safe_object_frame {
frame_ptr raw_address;
frame_ptr address_relative_to_object_start; // object base address must yet be added
frame_ptr address_relative_to_object_start;
char object_path[CPPTRACE_PATH_MAX + 1];
object_frame resolve() const; // To be called outside a signal handler. Not signal safe.
};
@ -62,6 +73,7 @@ namespace cpptrace {
void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
// signal-safe
bool can_signal_safe_unwind();
bool can_get_safe_object_frame();
}
```
@ -93,6 +105,9 @@ only ways to do this safely as far as I can tell.
`safe_generate_raw_trace` will just produce an empty trace and if object information can't be resolved in a signal-safe
way then `get_safe_object_frame` will not populate fields beyond the `raw_address`.
`cpptrace::can_signal_safe_unwind` and `cpptrace::can_get_safe_object_frame` can be used to check for safe tracing
support.
Currently the only back-end that can unwind safely is libunwind. Currently, the only way I know to get `dladdr`'s
information in a signal-safe manner is `_dl_find_object`, which doesn't exist on macos (or windows of course). If anyone
knows ways to do these safely on other platforms, I'd be much appreciative.
@ -102,8 +117,8 @@ knows ways to do these safely on other platforms, I'd be much appreciative.
Of the three strategies, `fork()` + `exec()`, is the most technically involved and the only way to resolve while the
signal handler is running. I think it's worthwhile to do a deep-dive into how to do this.
In the source code, [`signal_demo.cpp`](signal_demo.cpp) and [`signal_tracer.cpp`](signal_tracer.cpp) provide a working
example for what is described here.
In the source code, [`signal_demo.cpp`](../test/signal_demo.cpp) and [`signal_tracer.cpp`](../test/signal_tracer.cpp)
provide a working example for what is described here.
## In the main program
@ -136,12 +151,16 @@ struct pipe_t {
};
};
void do_signal_safe_trace(cpptrace::frame_ptr* buffer, std::size_t size) {
void do_signal_safe_trace(cpptrace::frame_ptr* buffer, std::size_t count) {
// Setup pipe and spawn child
pipe_t input_pipe;
pipe(input_pipe.data);
const pid_t pid = fork();
if(pid == -1) { return; /* Some error occurred */ }
if(pid == -1) {
const char* fork_failure_message = "fork() failed\n";
write(STDERR_FILENO, fork_failure_message, strlen(fork_failure_message));
return;
}
if(pid == 0) { // child
dup2(input_pipe.read_end, STDIN_FILENO);
close(input_pipe.read_end);
@ -172,7 +191,7 @@ void handler(int signo, siginfo_t* info, void* context) {
constexpr std::size_t N = 100;
cpptrace::frame_ptr buffer[N];
std::size_t count = cpptrace::safe_generate_raw_trace(buffer, N);
do_signal_safe_trace(buffer, N);
do_signal_safe_trace(buffer, count);
// Up to you if you want to exit or continue or whatever
_exit(1);
}
@ -219,9 +238,6 @@ int main() {
std::size_t res = fread(&frame, sizeof(frame), 1, stdin);
if(res == 0) {
break;
} else if(res == -1) {
perror("Something went wrong while reading from the pipe");
break;
} else if(res != 1) {
std::cerr<<"Something went wrong while reading from the pipe"<<res<<" "<<std::endl;
break;

245
include/cpptrace/basic.hpp Normal file
View File

@ -0,0 +1,245 @@
#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
namespace cpptrace {
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 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 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();
}
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#endif

View File

@ -1,493 +1,9 @@
#ifndef CPPTRACE_HPP
#define CPPTRACE_HPP
#include <cstdint>
#include <exception>
#include <limits>
#include <ostream>
#include <string>
#include <system_error>
#include <type_traits>
#include <utility>
#include <vector>
#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 >= 202002L
#ifdef __has_include
#if __has_include(<format>)
#define CPPTRACE_STD_FORMAT
#include <format>
#endif
#endif
#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
namespace cpptrace {
struct object_trace;
struct stacktrace;
// Some type sufficient for an instruction pointer, currently always an alias to std::uintptr_t
using frame_ptr = std::uintptr_t;
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;
nullable& operator=(T value) {
raw_value = value;
return *this;
}
bool has_value() const noexcept {
return raw_value != (std::numeric_limits<T>::max)();
}
T& value() noexcept {
return raw_value;
}
const T& value() const noexcept {
return raw_value;
}
T value_or(T alternative) const noexcept {
return has_value() ? raw_value : alternative;
}
void swap(nullable& other) noexcept {
std::swap(raw_value, other.raw_value);
}
void reset() noexcept {
raw_value = (std::numeric_limits<T>::max)();
}
bool operator==(const nullable& other) const noexcept {
return raw_value == other.raw_value;
}
bool operator!=(const nullable& other) const noexcept {
return raw_value != other.raw_value;
}
constexpr static nullable null() noexcept {
return { (std::numeric_limits<T>::max)() };
}
};
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;
friend 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 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:
void print(std::ostream& stream, bool color, bool newline_at_end, const char* header) const;
void print_with_snippets(std::ostream& stream, bool color, bool newline_at_end, const char* header) const;
friend void print_terminate_trace();
};
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;
frame_ptr address_relative_to_object_start; // base must still be added
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();
// utilities:
CPPTRACE_EXPORT std::string demangle(const std::string& name);
CPPTRACE_EXPORT std::string get_snippet(
const std::string& path,
std::size_t line,
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();
// configuration:
CPPTRACE_EXPORT void absorb_trace_exceptions(bool absorb);
CPPTRACE_EXPORT void enable_inlined_call_resolution(bool enable);
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);
}
// 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
stacktrace& get_resolved_trace();
const stacktrace& get_resolved_trace() 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);
}
#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
// 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); \
} \
} ()
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#include <cpptrace/basic.hpp>
#include <cpptrace/utils.hpp>
#include <cpptrace/exceptions.hpp>
#include <cpptrace/io.hpp>
#endif

View File

@ -0,0 +1,218 @@
#ifndef CPPTRACE_EXCEPTIONS_HPP
#define CPPTRACE_EXCEPTIONS_HPP
#include <cpptrace/basic.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
namespace cpptrace {
// 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;
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);
}
// 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); \
} \
} ()
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#endif

View File

@ -0,0 +1,74 @@
#ifndef CPPTRACE_FORMATTING_HPP
#define CPPTRACE_FORMATTING_HPP
#include <cpptrace/basic.hpp>
#include <string>
#include <functional>
namespace cpptrace {
class CPPTRACE_EXPORT formatter {
class impl;
// can't be a std::unique_ptr due to msvc awfulness with dllimport/dllexport and https://stackoverflow.com/q/4145605/15675011
impl* pimpl;
public:
formatter();
~formatter();
formatter(formatter&&);
formatter(const formatter&);
formatter& operator=(formatter&&);
formatter& operator=(const formatter&);
formatter& header(std::string);
enum class color_mode {
always,
none,
automatic,
};
formatter& colors(color_mode);
enum class address_mode {
raw,
object,
none,
};
formatter& addresses(address_mode);
enum class path_mode {
// full path is used
full,
// only the file name is used
basename,
};
formatter& paths(path_mode);
formatter& snippets(bool);
formatter& snippet_context(int);
formatter& columns(bool);
formatter& filtered_frame_placeholders(bool);
formatter& filter(std::function<bool(const stacktrace_frame&)>);
std::string format(const stacktrace_frame&) const;
std::string format(const stacktrace_frame&, bool color) const;
std::string format(const stacktrace&) const;
std::string format(const stacktrace&, bool color) const;
void print(const stacktrace_frame&) const;
void print(const stacktrace_frame&, bool color) const;
void print(std::ostream&, const stacktrace_frame&) const;
void print(std::ostream&, const stacktrace_frame&, bool color) const;
void print(std::FILE*, const stacktrace_frame&) const;
void print(std::FILE*, const stacktrace_frame&, bool color) const;
void print(const stacktrace&) const;
void print(const stacktrace&, bool color) const;
void print(std::ostream&, const stacktrace&) const;
void print(std::ostream&, const stacktrace&, bool color) const;
void print(std::FILE*, const stacktrace&) const;
void print(std::FILE*, const stacktrace&, bool color) const;
};
CPPTRACE_EXPORT const formatter& get_default_formatter();
}
#endif

View File

@ -0,0 +1,19 @@
#ifndef CPPTRACE_FORWARD_HPP
#define CPPTRACE_FORWARD_HPP
#include <cstdint>
namespace cpptrace {
// 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;
}
#endif

View File

@ -0,0 +1,131 @@
#ifndef CPPTRACE_FROM_CURRENT_HPP
#define CPPTRACE_FROM_CURRENT_HPP
#include <cpptrace/basic.hpp>
namespace cpptrace {
CPPTRACE_EXPORT const raw_trace& raw_trace_from_current_exception();
CPPTRACE_EXPORT const stacktrace& from_current_exception();
namespace detail {
// Trace switch is to prevent multiple tracing of stacks on call stacks with multiple catches that don't
// immediately match
inline bool& get_trace_switch() {
static thread_local bool trace_switch = true;
return trace_switch;
}
class CPPTRACE_EXPORT try_canary {
public:
~try_canary() {
// Fires when we exit a try block, either via normal means or during unwinding.
// Either way: Flip the switch.
get_trace_switch() = true;
}
};
CPPTRACE_EXPORT CPPTRACE_FORCE_NO_INLINE void collect_current_trace(std::size_t skip);
// this function can be void, however, a char return is used to prevent TCO of the collect_current_trace
CPPTRACE_FORCE_NO_INLINE inline char exception_unwind_interceptor(std::size_t skip) {
if(get_trace_switch()) {
// Done during a search phase. Flip the switch off, no more traces until an unwind happens
get_trace_switch() = false;
collect_current_trace(skip + 1);
}
return 42;
}
#ifdef _MSC_VER
CPPTRACE_FORCE_NO_INLINE inline int exception_filter() {
exception_unwind_interceptor(1);
return 0; // EXCEPTION_CONTINUE_SEARCH
}
CPPTRACE_FORCE_NO_INLINE inline int unconditional_exception_filter() {
collect_current_trace(1);
return 0; // EXCEPTION_CONTINUE_SEARCH
}
#else
class CPPTRACE_EXPORT unwind_interceptor {
public:
virtual ~unwind_interceptor();
};
class CPPTRACE_EXPORT unconditional_unwind_interceptor {
public:
virtual ~unconditional_unwind_interceptor();
};
CPPTRACE_EXPORT void do_prepare_unwind_interceptor(char(*)(std::size_t));
#ifndef CPPTRACE_DONT_PREPARE_UNWIND_INTERCEPTOR_ON
__attribute__((constructor)) inline void prepare_unwind_interceptor() {
// __attribute__((constructor)) inline functions can be called for every source file they're #included in
// there is still only one copy of the inline function in the final executable, though
// LTO can make the redundant constructs fire only once
// do_prepare_unwind_interceptor prevents against multiple preparations however it makes sense to guard
// against it here too as a fast path, not that this should matter for performance
static bool did_prepare = false;
if(!did_prepare) {
do_prepare_unwind_interceptor(exception_unwind_interceptor);
did_prepare = true;
}
}
#endif
#endif
}
}
#ifdef _MSC_VER
// 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::try_canary cpptrace_try_canary; \
[&]() { \
__try { \
[&]() {
#define CPPTRACE_CATCH(param) \
}(); \
} __except(::cpptrace::detail::exception_filter()) {} \
}(); \
} catch(param)
#define CPPTRACE_TRYZ \
try { \
[&]() { \
__try { \
[&]() {
#define CPPTRACE_CATCHZ(param) \
}(); \
} __except(::cpptrace::detail::unconditional_exception_filter()) {} \
}(); \
} catch(param)
#else
#define CPPTRACE_TRY \
try { \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wshadow\"") \
::cpptrace::detail::try_canary cpptrace_try_canary; \
_Pragma("GCC diagnostic pop") \
try {
#define CPPTRACE_CATCH(param) \
} catch(::cpptrace::detail::unwind_interceptor&) {} \
} catch(param)
#define CPPTRACE_TRYZ \
try { \
try {
#define CPPTRACE_CATCHZ(param) \
} catch(::cpptrace::detail::unconditional_unwind_interceptor&) {} \
} catch(param)
#endif
#define CPPTRACE_CATCH_ALT(param) catch(param)
#ifdef CPPTRACE_UNPREFIXED_TRY_CATCH
#define TRY CPPTRACE_TRY
#define CATCH(param) CPPTRACE_CATCH(param)
#define TRYZ CPPTRACE_TRYZ
#define CATCHZ(param) CPPTRACE_CATCHZ(param)
#define CATCH_ALT(param) CPPTRACE_CATCH_ALT(param)
#endif
#endif

52
include/cpptrace/io.hpp Normal file
View File

@ -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
namespace cpptrace {
std::ostream& operator<<(std::ostream& stream, const stacktrace_frame& frame);
std::ostream& operator<<(std::ostream& stream, const stacktrace& trace);
}
#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

View File

@ -0,0 +1,54 @@
#ifndef CPPTRACE_UTILS_HPP
#define CPPTRACE_UTILS_HPP
#include <cpptrace/basic.hpp>
#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
namespace cpptrace {
CPPTRACE_EXPORT std::string demangle(const std::string& name);
CPPTRACE_EXPORT std::string get_snippet(
const std::string& path,
std::size_t line,
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 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);
}
}
#endif

View File

@ -131,7 +131,8 @@ CTRACE_BEGIN_DEFINITIONS
/* ctrace::safe: */
CPPTRACE_EXPORT size_t ctrace_safe_generate_raw_trace(ctrace_frame_ptr* buffer, size_t size, size_t skip, size_t max_depth);
CPPTRACE_EXPORT void ctrace_get_safe_object_frame(ctrace_frame_ptr address, ctrace_safe_object_frame* out);
CPPTRACE_EXPORT ctrace_bool can_signal_safe_unwind(void);
CPPTRACE_EXPORT ctrace_bool ctrace_can_signal_safe_unwind(void);
CPPTRACE_EXPORT ctrace_bool ctrace_can_get_safe_object_frame(void);
/* ctrace::io: */
CPPTRACE_EXPORT ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color);

BIN
res/from_current.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@ -1,4 +1,5 @@
#include "elf.hpp"
#include "binary/elf.hpp"
#include "utils/optional.hpp"
#if IS_LINUX
@ -6,60 +7,25 @@
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <mutex>
#include <type_traits>
#include <unordered_map>
#include <elf.h>
namespace cpptrace {
namespace detail {
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
T elf_byteswap_if_needed(T value, bool elf_is_little) {
if(is_little_endian() == elf_is_little) {
return value;
} else {
return byteswap(value);
}
}
template<std::size_t Bits>
static Result<std::uintptr_t, internal_error> elf_get_module_image_base_from_program_table(
elf::elf(
file_wrapper file,
const std::string& object_path,
std::FILE* file,
bool is_little_endian
) {
static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument");
using Header = typename std::conditional<Bits == 32, Elf32_Ehdr, Elf64_Ehdr>::type;
using PHeader = typename std::conditional<Bits == 32, Elf32_Phdr, Elf64_Phdr>::type;
auto loaded_header = load_bytes<Header>(file, 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" + object_path);
}
// 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(int i = 0; i < file_header.e_phnum; i++) {
auto loaded_ph = load_bytes<PHeader>(file, file_header.e_phoff + file_header.e_phentsize * i);
if(loaded_ph.is_error()) {
return std::move(loaded_ph).unwrap_error();
}
const PHeader& program_header = loaded_ph.unwrap_value();
if(elf_byteswap_if_needed(program_header.p_type, is_little_endian) == PT_PHDR) {
return elf_byteswap_if_needed(program_header.p_vaddr, is_little_endian) -
elf_byteswap_if_needed(program_header.p_offset, is_little_endian);
}
}
// Apparently some objects like shared objects can end up missing this header. 0 as a base seems correct.
return 0;
}
bool is_little_endian,
bool is_64
) : file(std::move(file)), object_path(object_path), is_little_endian(is_little_endian), is_64(is_64) {}
Result<std::uintptr_t, internal_error> elf_get_module_image_base(const std::string& object_path) {
Result<elf, internal_error> elf::open_elf(const std::string& 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);
return internal_error("Unable to read object file {}", object_path);
}
// Initial checks/metadata
auto magic = load_bytes<std::array<char, 4>>(file, 0);
@ -86,14 +52,385 @@ namespace detail {
if(ei_version.unwrap_value() != 1) {
return internal_error("Unexpected ELF version " + object_path);
}
return elf(std::move(file), object_path, is_little_endian, is_64);
}
Result<std::uintptr_t, internal_error> elf::get_module_image_base() {
// get image base
if(is_64) {
return elf_get_module_image_base_from_program_table<64>(object_path, file, is_little_endian);
return get_module_image_base_impl<64>();
} else {
return elf_get_module_image_base_from_program_table<32>(object_path, file, is_little_endian);
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 = load_bytes<PHeader>(file, 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<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(cpptrace::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) {
Result<const elf::header_info&, internal_error> r = header.unwrap();
return std::ref(header.unwrap());
}
if(tried_to_load_header) {
return internal_error("previous header load failed " + object_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 = load_bytes<Header>(file, 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" + object_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);
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 " + object_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 = load_bytes<SHeader>(file, 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_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 {}", object_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, object_path);
}
entry.data.resize(section.sh_size + 1);
if(std::fseek(file, section.sh_offset, SEEK_SET) != 0) {
return internal_error("fseek error while loading elf string table");
}
if(std::fread(entry.data.data(), sizeof(char), section.sh_size, file) != section.sh_size) {
return internal_error("fread error while loading elf string table");
}
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 {}", object_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 {}", object_path);
}
tried_to_load_dynamic_symtab = true;
if(is_64) {
auto res = get_symtab_impl<64>(true);
if(res.has_value()) {
dynamic_symtab = std::move(res).unwrap_value();
did_load_dynamic_symtab = true;
return dynamic_symtab;
} else {
return std::move(res).unwrap_error();
}
} else {
auto res = get_symtab_impl<32>(true);
if(res.has_value()) {
dynamic_symtab = std::move(res).unwrap_value();
did_load_dynamic_symtab = true;
return dynamic_symtab;
} else {
return std::move(res).unwrap_error();
}
}
}
template<std::size_t Bits>
Result<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 {}", object_path);
}
if(section.sh_size % section.sh_entsize != 0) {
return internal_error("elf seems corrupted, sym entry vs section size mismatch {}", object_path);
}
std::vector<SymEntry> buffer(section.sh_size / section.sh_entsize);
if(std::fseek(file, section.sh_offset, SEEK_SET) != 0) {
return internal_error("fseek error while loading elf symbol table");
}
if(std::fread(buffer.data(), section.sh_entsize, buffer.size(), file) != buffer.size()) {
return internal_error("fread error while loading elf symbol table");
}
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);
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(get_cache_mode() == cache_mode::prioritize_memory) {
return elf::open_elf(object_path)
.transform([](elf&& obj) { return maybe_owned<elf>{detail::make_unique<elf>(std::move(obj))}; });
} else {
std::mutex m;
std::unique_lock<std::mutex> lock{m};
// TODO: Re-evaluate storing the error
static std::unordered_map<std::string, Result<elf, internal_error>> cache;
auto it = cache.find(object_path);
if(it == cache.end()) {
auto res = cache.insert({ object_path, elf::open_elf(object_path) });
VERIFY(res.second);
it = res.first;
}
return it->second.transform([](elf& obj) { return maybe_owned<elf>(&obj); });
}
}
}
}

View File

@ -1,17 +1,126 @@
#ifndef ELF_HPP
#define ELF_HPP
#include "../utils/common.hpp"
#include "../utils/utils.hpp"
#include "utils/common.hpp"
#include "utils/utils.hpp"
#if IS_LINUX
#include <cstdint>
#include <string>
#include <unordered_map>
namespace cpptrace {
namespace detail {
Result<std::uintptr_t, internal_error> elf_get_module_image_base(const std::string& object_path);
class elf {
file_wrapper file;
std::string object_path;
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;
};
bool tried_to_load_header = false;
optional<header_info> header;
struct section_info {
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(file_wrapper file, const std::string& object_path, bool is_little_endian, bool is_64);
public:
static NODISCARD Result<elf, internal_error> open_elf(const std::string& object_path);
elf(elf&&) = default;
public:
Result<std::uintptr_t, internal_error> get_module_image_base();
private:
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 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);
}
}

View File

@ -1,7 +1,7 @@
#include "mach-o.hpp"
#include "binary/mach-o.hpp"
#include "../utils/common.hpp"
#include "../utils/utils.hpp"
#include "utils/common.hpp"
#include "utils/utils.hpp"
#if IS_APPLE
@ -11,6 +11,7 @@
#include <cstdio>
#include <cstring>
#include <mutex>
#include <string>
#include <type_traits>
#include <unordered_map>
@ -102,7 +103,7 @@ namespace detail {
Result<const char*, internal_error> mach_o::symtab_info_data::get_string(std::size_t index) const {
if(stringtab && index < symtab.strsize) {
return stringtab.get() + index;
return stringtab.unwrap().data() + index;
} else {
return internal_error("can't retrieve symbol from symtab");
}
@ -219,7 +220,7 @@ namespace detail {
void mach_o::print_symbol_table_entry(
const nlist_64& entry,
const std::unique_ptr<char[]>& stringtab,
const char* stringtab,
std::size_t stringsize,
std::size_t j
) const {
@ -248,7 +249,7 @@ namespace detail {
stringtab == nullptr
? "Stringtab error"
: entry.n_un.n_strx < stringsize
? stringtab.get() + entry.n_un.n_strx
? stringtab + entry.n_un.n_strx
: "String index out of bounds"
);
}
@ -286,7 +287,7 @@ namespace detail {
}
print_symbol_table_entry(
entry.unwrap_value(),
std::move(stringtab).value_or(std::unique_ptr<char[]>(nullptr)),
stringtab ? stringtab.unwrap_value().data() : nullptr,
symtab.strsize,
j
);
@ -364,10 +365,17 @@ namespace detail {
return debug_map;
}
Result<std::vector<mach_o::symbol_entry>, internal_error> mach_o::symbol_table() {
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
std::vector<symbol_entry> symbols;
auto symtab_info_res = get_symtab_info();
if(!symtab_info_res) {
return std::move(symtab_info_res).unwrap_error();
@ -394,13 +402,42 @@ namespace detail {
if(!str) {
return std::move(str).unwrap_error();
}
symbols.push_back({
symbol_table.push_back({
entry.n_value,
str.unwrap_value()
});
}
}
return symbols;
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
@ -605,12 +642,12 @@ namespace detail {
return common;
}
Result<std::unique_ptr<char[]>, internal_error> mach_o::load_string_table(std::uint32_t offset, std::uint32_t byte_count) const {
std::unique_ptr<char[]> buffer(new char[byte_count + 1]);
Result<std::vector<char>, internal_error> mach_o::load_string_table(std::uint32_t offset, std::uint32_t byte_count) const {
std::vector<char> buffer(byte_count + 1);
if(std::fseek(file, load_base + offset, SEEK_SET) != 0) {
return internal_error("fseek error while loading mach-o symbol table");
}
if(std::fread(buffer.get(), sizeof(char), byte_count, file) != byte_count) {
if(std::fread(buffer.data(), sizeof(char), byte_count, file) != byte_count) {
return internal_error("fread error while loading mach-o symbol table");
}
buffer[byte_count] = 0; // just out of an abundance of caution
@ -633,6 +670,27 @@ namespace detail {
return is_fat_magic(magic.unwrap_value());
}
}
Result<maybe_owned<mach_o>, internal_error> open_mach_o_cached(const std::string& object_path) {
if(get_cache_mode() == cache_mode::prioritize_memory) {
return mach_o::open_mach_o(object_path)
.transform([](mach_o&& obj) {
return maybe_owned<mach_o>{detail::make_unique<mach_o>(std::move(obj))};
});
} else {
std::mutex m;
std::unique_lock<std::mutex> lock{m};
// TODO: Re-evaluate storing the error
static std::unordered_map<std::string, Result<mach_o, internal_error>> cache;
auto it = cache.find(object_path);
if(it == cache.end()) {
auto res = cache.insert({ object_path, mach_o::open_mach_o(object_path) });
VERIFY(res.second);
it = res.first;
}
return it->second.transform([](mach_o& obj) { return maybe_owned<mach_o>(&obj); });
}
}
}
}

View File

@ -1,8 +1,8 @@
#ifndef MACHO_HPP
#define MACHO_HPP
#include "../utils/common.hpp"
#include "../utils/utils.hpp"
#include "utils/common.hpp"
#include "utils/utils.hpp"
#if IS_APPLE
@ -28,6 +28,23 @@ namespace detail {
};
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:
file_wrapper file;
std::string object_path;
std::uint32_t magic;
@ -46,13 +63,16 @@ namespace detail {
struct symtab_info_data {
symtab_command symtab;
std::unique_ptr<char[]> stringtab;
optional<std::vector<char>> stringtab;
Result<const char*, internal_error> get_string(std::size_t index) const;
};
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(
file_wrapper file,
const std::string& object_path,
@ -80,31 +100,19 @@ namespace detail {
void print_symbol_table_entry(
const nlist_64& entry,
const std::unique_ptr<char[]>& stringtab,
const char* stringtab,
std::size_t stringsize,
std::size_t j
) const;
void print_symbol_table();
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>>;
// produce information similar to dsymutil -dump-debug-map
Result<debug_map, internal_error> get_debug_map();
Result<std::vector<symbol_entry>, internal_error> symbol_table();
Result<const std::vector<symbol_entry>&, internal_error> symbol_table();
optional<std::string> lookup_symbol(frame_ptr pc);
// produce information similar to dsymutil -dump-debug-map
static void print_debug_map(const debug_map& debug_map);
@ -123,12 +131,14 @@ namespace detail {
template<std::size_t Bits>
Result<nlist_64, internal_error> load_symtab_entry(std::uint32_t symbol_base, std::size_t index) const;
Result<std::unique_ptr<char[]>, internal_error> load_string_table(std::uint32_t offset, std::uint32_t byte_count) const;
Result<std::vector<char>, internal_error> load_string_table(std::uint32_t offset, std::uint32_t byte_count) const;
bool should_swap() const;
};
Result<bool, internal_error> macho_is_fat(const std::string& object_path);
NODISCARD Result<maybe_owned<mach_o>, internal_error> open_mach_o_cached(const std::string& object_path);
}
}

View File

@ -1,10 +1,9 @@
#include "module_base.hpp"
#include "binary/module_base.hpp"
#include "../utils/common.hpp"
#include "../utils/utils.hpp"
#include "platform/platform.hpp"
#include "utils/utils.hpp"
#include <string>
#include <vector>
#include <mutex>
#include <unordered_map>
@ -12,13 +11,12 @@
#include <unistd.h>
#include <dlfcn.h>
#if IS_APPLE
#include "mach-o.hpp"
#include "binary/mach-o.hpp"
#else
#include "elf.hpp"
#include "binary/elf.hpp"
#endif
#elif IS_WINDOWS
#include <windows.h>
#include "pe.hpp"
#include "binary/pe.hpp"
#endif
namespace cpptrace {
@ -32,8 +30,12 @@ namespace detail {
if(it == cache.end()) {
// arguably it'd be better to release the lock while computing this, but also arguably it's good to not
// have two threads try to do the same computation
auto base = elf_get_module_image_base(object_path);
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();
}
@ -55,12 +57,12 @@ namespace detail {
if(it == cache.end()) {
// arguably it'd be better to release the lock while computing this, but also arguably it's good to not
// have two threads try to do the same computation
auto obj = mach_o::open_mach_o(object_path);
auto mach_o_object = open_mach_o_cached(object_path);
// TODO: Cache the error
if(!obj) {
return obj.unwrap_error();
if(!mach_o_object) {
return mach_o_object.unwrap_error();
}
auto base = obj.unwrap_value().get_text_vmaddr();
auto base = mach_o_object.unwrap_value()->get_text_vmaddr();
if(!base) {
return std::move(base).unwrap_error();
}

View File

@ -1,8 +1,7 @@
#ifndef IMAGE_MODULE_BASE_HPP
#define IMAGE_MODULE_BASE_HPP
#include "../utils/common.hpp"
#include "../utils/utils.hpp"
#include "utils/utils.hpp"
#include <cstdint>
#include <string>

View File

@ -1,10 +1,11 @@
#include "object.hpp"
#include "binary/object.hpp"
#include "../utils/common.hpp"
#include "../utils/utils.hpp"
#include "module_base.hpp"
#include "platform/platform.hpp"
#include "utils/utils.hpp"
#include "binary/module_base.hpp"
#include <string>
#include <system_error>
#include <vector>
#include <mutex>
#include <unordered_map>
@ -16,6 +17,9 @@
#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
@ -74,9 +78,11 @@ namespace detail {
- reinterpret_cast<std::uintptr_t>(info.dli_fbase)
+ base.unwrap_value();
} else {
if(!should_absorb_trace_exceptions()) {
base.drop_error();
}
}
}
return frame;
}
#else
@ -97,9 +103,11 @@ namespace detail {
- reinterpret_cast<std::uintptr_t>(info.dli_fbase)
+ base.unwrap_value();
} else {
if(!should_absorb_trace_exceptions()) {
base.drop_error();
}
}
}
return frame;
}
#endif
@ -142,8 +150,10 @@ namespace detail {
- reinterpret_cast<std::uintptr_t>(handle)
+ base.unwrap_value();
} else {
if(!should_absorb_trace_exceptions()) {
base.drop_error();
}
}
} else {
std::fprintf(stderr, "%s\n", std::system_error(GetLastError(), std::system_category()).what());
}
@ -161,14 +171,18 @@ namespace detail {
}
object_frame resolve_safe_object_frame(const safe_object_frame& frame) {
auto base = get_module_image_base(frame.object_path);
if(base.is_error()) {
throw base.unwrap_error(); // This throw is intentional
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 + base.unwrap_value(),
frame.object_path
frame.address_relative_to_object_start,
std::move(object_path)
};
}
}

View File

@ -1,14 +1,13 @@
#ifndef OBJECT_HPP
#define OBJECT_HPP
#include "../utils/common.hpp"
#include "../utils/utils.hpp"
#include "module_base.hpp"
#include <cpptrace/forward.hpp>
#include <string>
#include <vector>
#include <cstdint>
namespace cpptrace {
namespace detail {
object_frame get_frame_object_info(frame_ptr address);

View File

@ -1,16 +1,18 @@
#include "pe.hpp"
#include "binary/pe.hpp"
#include "../utils/common.hpp"
#include "../utils/error.hpp"
#include "../utils/utils.hpp"
#include "platform/platform.hpp"
#include "utils/error.hpp"
#include "utils/utils.hpp"
#if IS_WINDOWS
#include <array>
#include <cstddef>
#include <cstdio>
#include <cstring>
#include <string>
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
namespace cpptrace {

View File

@ -1,8 +1,8 @@
#ifndef PE_HPP
#define PE_HPP
#include "../utils/common.hpp"
#include "../utils/utils.hpp"
#include "platform/platform.hpp"
#include "utils/utils.hpp"
#if IS_WINDOWS
#include <cstdint>

View File

@ -1,8 +1,8 @@
#include "safe_dl.hpp"
#include "binary/safe_dl.hpp"
#include "../utils/common.hpp"
#include "../utils/utils.hpp"
#include "../utils/program_name.hpp"
#include "utils/common.hpp"
#include "utils/utils.hpp"
#include "platform/program_name.hpp"
#include <string>
#include <vector>
@ -53,6 +53,10 @@ namespace detail {
// may return the object that defines the function descriptor (and not the object that contains the code
// implementing the function), or fail to find any object at all.
}
bool has_get_safe_object_frame() {
return true;
}
}
}
#else
@ -63,6 +67,10 @@ namespace detail {
out->address_relative_to_object_start = 0;
out->object_path[0] = 0;
}
bool has_get_safe_object_frame() {
return false;
}
}
}
#endif

View File

@ -1,11 +1,13 @@
#ifndef SAFE_DL_HPP
#define SAFE_DL_HPP
#include "../utils/common.hpp"
#include "utils/common.hpp"
namespace cpptrace {
namespace detail {
void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
bool has_get_safe_object_frame();
}
}

View File

@ -1,35 +1,50 @@
#include <cpptrace/cpptrace.hpp>
#include <cpptrace/formatting.hpp>
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <new>
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>
#include "cpptrace/basic.hpp"
#include "symbols/symbols.hpp"
#include "unwind/unwind.hpp"
#include "demangle/demangle.hpp"
#include "utils/exception_type.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"
namespace cpptrace {
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(...) {
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
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(...) {
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return raw_trace{};
}
}
object_trace raw_trace::resolve_object_trace() const {
@ -47,7 +62,7 @@ namespace cpptrace {
try {
std::vector<stacktrace_frame> trace = detail::resolve_frames(frames);
for(auto& frame : trace) {
frame.symbol = detail::demangle(frame.symbol);
frame.symbol = detail::demangle(frame.symbol, true);
}
return {std::move(trace)};
} catch(...) { // NOSONAR
@ -68,19 +83,33 @@ namespace cpptrace {
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(...) {
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
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(...) {
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
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);
frame.symbol = detail::demangle(frame.symbol, true);
}
return {std::move(trace)};
} catch(...) { // NOSONAR
@ -104,25 +133,11 @@ namespace cpptrace {
}
std::string stacktrace_frame::to_string() const {
std::string str;
if(is_inline) {
str += microfmt::format("{<{}}", 2 * sizeof(frame_ptr) + 2, "(inlined)");
} else {
str += microfmt::format("0x{>{}:0h}", 2 * sizeof(frame_ptr), raw_address);
return to_string(false);
}
if(!symbol.empty()) {
str += microfmt::format(" in {}", symbol);
}
if(!filename.empty()) {
str += microfmt::format(" at {}", filename);
if(line.has_value()) {
str += microfmt::format(":{}", line.value());
if(column.has_value()) {
str += microfmt::format(":{}", column.value());
}
}
}
return str;
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) {
@ -131,119 +146,57 @@ namespace cpptrace {
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(...) {
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
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(...) {
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return stacktrace{};
}
}
void stacktrace::print() const {
print(std::cerr, true);
get_default_formatter().print(*this);
}
void stacktrace::print(std::ostream& stream) const {
print(stream, true);
get_default_formatter().print(stream, *this);
}
void stacktrace::print(std::ostream& stream, bool color) const {
print(stream, color, true, nullptr);
get_default_formatter().print(stream, *this, color);
}
void print_frame(
std::ostream& stream,
bool color,
unsigned frame_number_width,
std::size_t counter,
const stacktrace_frame& frame
) {
const auto reset = color ? RESET : "";
const auto green = color ? GREEN : "";
const auto yellow = color ? YELLOW : "";
const auto blue = color ? BLUE : "";
std::string line = microfmt::format("#{<{}} ", frame_number_width, counter);
if(frame.is_inline) {
line += microfmt::format("{<{}}", 2 * sizeof(frame_ptr) + 2, "(inlined)");
} else {
line += microfmt::format("{}0x{>{}:0h}{}", blue, 2 * sizeof(frame_ptr), frame.raw_address, reset);
}
if(!frame.symbol.empty()) {
line += microfmt::format(" in {}{}{}", yellow, frame.symbol, reset);
}
if(!frame.filename.empty()) {
line += microfmt::format(" at {}{}{}", green, frame.filename, reset);
if(frame.line.has_value()) {
line += microfmt::format(":{}{}{}", blue, frame.line.value(), reset);
if(frame.column.has_value()) {
line += microfmt::format(":{}{}{}", blue, frame.column.value(), reset);
}
}
}
stream << line;
}
void stacktrace::print(std::ostream& stream, bool color, bool newline_at_end, const char* header) const {
if(
color && (
(&stream == &std::cout && isatty(stdout_fileno)) || (&stream == &std::cerr && isatty(stderr_fileno))
)
) {
detail::enable_virtual_terminal_processing_if_needed();
}
stream << (header ? header : "Stack trace (most recent call first):") << '\n';
std::size_t counter = 0;
if(frames.empty()) {
stream << "<empty trace>\n";
return;
}
const auto frame_number_width = detail::n_digits(static_cast<int>(frames.size()) - 1);
for(const auto& frame : frames) {
print_frame(stream, color, frame_number_width, counter, frame);
if(newline_at_end || &frame != &frames.back()) {
stream << '\n';
}
counter++;
namespace detail {
const formatter& get_default_snippet_formatter() {
static formatter snippet_formatter = formatter{}.snippets(true);
return snippet_formatter;
}
}
void stacktrace::print_with_snippets() const {
print_with_snippets(std::cerr, true);
detail::get_default_snippet_formatter().print(*this);
}
void stacktrace::print_with_snippets(std::ostream& stream) const {
print_with_snippets(stream, true);
detail::get_default_snippet_formatter().print(stream, *this);
}
void stacktrace::print_with_snippets(std::ostream& stream, bool color) const {
print_with_snippets(stream, color, true, nullptr);
}
void stacktrace::print_with_snippets(std::ostream& stream, bool color, bool newline_at_end, const char* header) const {
if(
color && (
(&stream == &std::cout && isatty(stdout_fileno)) || (&stream == &std::cerr && isatty(stderr_fileno))
)
) {
detail::enable_virtual_terminal_processing_if_needed();
}
stream << (header ? header : "Stack trace (most recent call first):") << '\n';
std::size_t counter = 0;
if(frames.empty()) {
stream << "<empty trace>" << '\n';
return;
}
const auto frame_number_width = detail::n_digits(static_cast<int>(frames.size()) - 1);
for(const auto& frame : frames) {
print_frame(stream, color, frame_number_width, counter, frame);
if(newline_at_end || &frame != &frames.back()) {
stream << '\n';
}
if(frame.line.has_value() && !frame.filename.empty()) {
stream << detail::get_snippet(frame.filename, frame.line.value(), 2, color);
}
counter++;
}
detail::get_default_snippet_formatter().print(stream, *this, color);
}
void stacktrace::clear() {
@ -255,13 +208,12 @@ namespace cpptrace {
}
std::string stacktrace::to_string(bool color) const {
std::ostringstream oss;
print(oss, color, false, nullptr);
return std::move(oss).str();
return get_default_formatter().format(*this, color);
}
std::ostream& operator<<(std::ostream& stream, const stacktrace& trace) {
return stream << trace.to_string();
get_default_formatter().print(stream, trace);
return stream;
}
CPPTRACE_FORCE_NO_INLINE
@ -290,7 +242,14 @@ namespace cpptrace {
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(...) {
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return 0;
}
}
CPPTRACE_FORCE_NO_INLINE
@ -300,7 +259,14 @@ namespace cpptrace {
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(...) {
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return 0;
}
}
CPPTRACE_FORCE_NO_INLINE
@ -329,7 +295,14 @@ namespace cpptrace {
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(...) {
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return stacktrace{};
}
}
CPPTRACE_FORCE_NO_INLINE
@ -338,7 +311,7 @@ namespace cpptrace {
std::vector<frame_ptr> frames = detail::capture_frames(skip + 1, max_depth);
std::vector<stacktrace_frame> trace = detail::resolve_frames(frames);
for(auto& frame : trace) {
frame.symbol = detail::demangle(frame.symbol);
frame.symbol = detail::demangle(frame.symbol, true);
}
return {std::move(trace)};
} catch(...) { // NOSONAR
@ -361,254 +334,7 @@ namespace cpptrace {
return detail::has_safe_unwind();
}
std::string demangle(const std::string& name) {
return detail::demangle(name);
}
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, context_size, color);
}
bool isatty(int fd) {
return detail::isatty(fd);
}
extern const int stdin_fileno = detail::fileno(stdin);
extern const int stdout_fileno = detail::fileno(stdout);
extern const int stderr_fileno = detail::fileno(stderr);
CPPTRACE_FORCE_NO_INLINE void print_terminate_trace() {
generate_trace(1).print(
std::cerr,
isatty(stderr_fileno),
true,
"Stack trace to reach terminate handler (most recent call first):"
);
}
[[noreturn]] void 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);
}
namespace detail {
std::atomic_bool absorb_trace_exceptions(true); // NOSONAR
std::atomic_bool resolve_inlined_calls(true); // NOSONAR
std::atomic<enum cache_mode> cache_mode(cache_mode::prioritize_speed); // NOSONAR
}
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::cache_mode = mode;
}
}
namespace detail {
bool should_absorb_trace_exceptions() {
return absorb_trace_exceptions;
}
bool should_resolve_inlined_calls() {
return resolve_inlined_calls;
}
enum cache_mode get_cache_mode() {
return cache_mode;
}
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(!detail::should_absorb_trace_exceptions()) {
// TODO: Append to message somehow
std::fprintf(
stderr,
"Cpptrace: Exception occurred while resolving trace in cpptrace::exception object:\n%s\n",
e.what()
);
}
return raw_trace{};
}
}
CPPTRACE_FORCE_NO_INLINE
raw_trace get_raw_trace_and_absorb(std::size_t skip) {
return get_raw_trace_and_absorb(skip + 1, SIZE_MAX);
}
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
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(!detail::should_absorb_trace_exceptions()) {
// TODO: Append to message somehow?
std::fprintf(
stderr,
"Exception occurred while resolving trace in cpptrace::exception object:\n%s\n",
e.what()
);
}
}
}
return resolved_trace;
}
const stacktrace& lazy_trace_holder::get_resolved_trace() const {
if(!resolved) {
throw std::logic_error(
"cpptrace::detaillazy_trace_holder::get_resolved_trace called on unresolved const object"
);
}
return resolved_trace;
}
void lazy_trace_holder::clear() {
if(resolved) {
resolved_trace.~stacktrace();
} else {
trace.~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));
}
bool can_get_safe_object_frame() {
return detail::has_get_safe_object_frame();
}
}

View File

@ -5,7 +5,7 @@
#include "symbols/symbols.hpp"
#include "unwind/unwind.hpp"
#include "demangle/demangle.hpp"
#include "utils/exception_type.hpp"
#include "platform/exception_type.hpp"
#include "utils/common.hpp"
#include "utils/utils.hpp"
#include "binary/object.hpp"
@ -107,7 +107,7 @@ CTRACE_FORMAT_EPILOGUE
new_frame.line = frame.line.value_or(invalid_pos);
new_frame.column = frame.column.value_or(invalid_pos);
new_frame.filename = generate_owning_string(frame.filename).data;
new_frame.symbol = generate_owning_string(cpptrace::detail::demangle(frame.symbol)).data;
new_frame.symbol = generate_owning_string(cpptrace::detail::demangle(frame.symbol, true)).data;
new_frame.is_inline = ctrace_bool(frame.is_inline);
return new_frame;
}
@ -310,10 +310,14 @@ extern "C" {
cpptrace::get_safe_object_frame(address, reinterpret_cast<cpptrace::safe_object_frame*>(out));
}
ctrace_bool can_signal_safe_unwind() {
ctrace_bool ctrace_can_signal_safe_unwind() {
return cpptrace::can_signal_safe_unwind();
}
ctrace_bool ctrace_can_get_safe_object_frame(void) {
return cpptrace::can_get_safe_object_frame();
}
// ctrace::io:
ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color) {
if(!trace || !trace->frames) {

View File

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

View File

@ -1,25 +1,55 @@
#include "utils/microfmt.hpp"
#ifdef CPPTRACE_DEMANGLE_WITH_CXXABI
#include "demangle.hpp"
#include "demangle/demangle.hpp"
#include "utils/utils.hpp"
#include <cxxabi.h>
#include <cstdlib>
#include <functional>
#include <string>
namespace cpptrace {
namespace detail {
std::string demangle(const std::string& name) {
int status;
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
char* const demangled = abi::__cxa_demangle(name.c_str(), nullptr, nullptr, &status);
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) {
std::string str = demangled;
std::free(demangled);
if(demangled.get()) {
std::string str = demangled.get();
if(!rest.empty()) {
str += rest;
}
return str;
} else {
return name;

View File

@ -1,12 +1,12 @@
#ifdef CPPTRACE_DEMANGLE_WITH_NOTHING
#include "demangle.hpp"
#include "demangle/demangle.hpp"
#include <string>
namespace cpptrace {
namespace detail {
std::string demangle(const std::string& name) {
std::string demangle(const std::string& name, bool) {
return name;
}
}

View File

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

187
src/exceptions.cpp Normal file
View File

@ -0,0 +1,187 @@
#include <cpptrace/exceptions.hpp>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <new>
#include <stdexcept>
#include <string>
#include "platform/exception_type.hpp"
#include "utils/common.hpp"
#include "options.hpp"
namespace cpptrace {
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(!detail::should_absorb_trace_exceptions()) {
// TODO: Append to message somehow?
std::fprintf(
stderr,
"Exception occurred while resolving trace in cpptrace::detail::lazy_trace_holder:\n%s\n",
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;
}
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(!detail::should_absorb_trace_exceptions()) {
// TODO: Append to message somehow
std::fprintf(
stderr,
"Cpptrace: Exception occurred while resolving trace in cpptrace::exception object:\n%s\n",
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(...) {
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
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));
}
}
}

353
src/formatting.cpp Normal file
View File

@ -0,0 +1,353 @@
#include <cpptrace/formatting.hpp>
#include <cpptrace/utils.hpp>
#include "utils/optional.hpp"
#include "utils/utils.hpp"
#include "snippets/snippet.hpp"
#include <memory>
#include <cstdio>
#include <string>
#include <functional>
#include <iostream>
#include <sstream>
namespace cpptrace {
class formatter::impl {
struct {
std::string header = "Stack trace (most recent call first):";
color_mode color = color_mode::automatic;
address_mode addresses = address_mode::raw;
path_mode paths = path_mode::full;
bool snippets = false;
int context_lines = 2;
bool columns = true;
bool show_filtered_frames = true;
std::function<bool(const stacktrace_frame&)> filter;
} options;
public:
void header(std::string header) {
options.header = std::move(header);
}
void colors(formatter::color_mode mode) {
options.color = mode;
}
void addresses(formatter::address_mode mode) {
options.addresses = mode;
}
void paths(path_mode mode) {
options.paths = mode;
}
void snippets(bool snippets) {
options.snippets = snippets;
}
void snippet_context(int lines) {
options.context_lines = lines;
}
void columns(bool columns) {
options.columns = columns;
}
void filtered_frame_placeholders(bool show) {
options.show_filtered_frames = show;
}
void filter(std::function<bool(const stacktrace_frame&)> filter) {
options.filter = filter;
}
std::string format(const stacktrace_frame& frame, detail::optional<bool> color_override = detail::nullopt) const {
std::ostringstream oss;
print_frame_inner(oss, frame, color_override.value_or(options.color == color_mode::always));
return std::move(oss).str();
}
std::string format(const stacktrace& trace, detail::optional<bool> color_override = detail::nullopt) const {
std::ostringstream oss;
print_internal(oss, trace, false, color_override);
return std::move(oss).str();
}
void print(const stacktrace_frame& frame, detail::optional<bool> color_override = detail::nullopt) const {
print(std::cout, frame, color_override);
}
void print(
std::ostream& stream,
const stacktrace_frame& frame,
detail::optional<bool> color_override = detail::nullopt
) const {
print_frame_internal(stream, frame, color_override);
}
void print(
std::FILE* file,
const stacktrace_frame& frame,
detail::optional<bool> color_override = detail::nullopt
) const {
auto str = format(frame, color_override);
std::fwrite(str.data(), 1, str.size(), file);
}
void print(const stacktrace& trace, detail::optional<bool> color_override = detail::nullopt) const {
print(std::cout, trace, color_override);
}
void print(
std::ostream& stream,
const stacktrace& trace,
detail::optional<bool> color_override = detail::nullopt
) const {
print_internal(stream, trace, true, color_override);
}
void print(
std::FILE* file,
const stacktrace& trace,
detail::optional<bool> color_override = detail::nullopt
) const {
auto str = format(trace, color_override);
std::fwrite(str.data(), 1, str.size(), file);
}
private:
bool stream_is_tty(std::ostream& stream) const {
// not great, but it'll have to do
return (&stream == &std::cout && isatty(stdout_fileno))
|| (&stream == &std::cerr && isatty(stderr_fileno));
}
void maybe_ensure_virtual_terminal_processing(std::ostream& stream, bool color) const {
if(color && stream_is_tty(stream)) {
detail::enable_virtual_terminal_processing_if_needed();
}
}
bool should_do_color(std::ostream& stream, detail::optional<bool> color_override) const {
bool do_color = options.color == color_mode::always || color_override.value_or(false);
if(
(options.color == color_mode::automatic || options.color == color_mode::always) &&
(!color_override || color_override.unwrap() != false) &&
stream_is_tty(stream)
) {
detail::enable_virtual_terminal_processing_if_needed();
do_color = true;
}
return do_color;
}
void print_internal(std::ostream& stream, const stacktrace& trace, bool newline_at_end, detail::optional<bool> color_override) const {
bool do_color = should_do_color(stream, color_override);
maybe_ensure_virtual_terminal_processing(stream, do_color);
print_internal(stream, trace, newline_at_end, do_color);
}
void print_internal(std::ostream& stream, const stacktrace& trace, bool newline_at_end, bool color) const {
if(!options.header.empty()) {
stream << options.header << '\n';
}
std::size_t counter = 0;
const auto& frames = trace.frames;
if(frames.empty()) {
stream << "<empty trace>\n";
return;
}
const auto frame_number_width = detail::n_digits(static_cast<int>(frames.size()) - 1);
for(const auto& frame : frames) {
if(options.filter && !options.filter(frame)) {
if(!options.show_filtered_frames) {
counter++;
continue;
}
print_placeholder_frame(stream, frame_number_width, counter);
} else {
print_frame_internal(stream, frame, color, frame_number_width, counter);
if(frame.line.has_value() && !frame.filename.empty() && options.snippets) {
auto snippet = detail::get_snippet(
frame.filename,
frame.line.value(),
options.context_lines,
color
);
if(!snippet.empty()) {
stream << '\n';
stream << snippet;
}
}
}
if(newline_at_end || &frame != &frames.back()) {
stream << '\n';
}
counter++;
}
}
void print_frame_internal(
std::ostream& stream,
const stacktrace_frame& frame,
bool color,
unsigned frame_number_width,
std::size_t counter
) const {
microfmt::print(stream, "#{<{}} ", frame_number_width, counter);
print_frame_inner(stream, frame, color);
}
void print_placeholder_frame(std::ostream& stream, unsigned frame_number_width, std::size_t counter) const {
microfmt::print(stream, "#{<{}} (filtered)", frame_number_width, counter);
}
void print_frame_internal(
std::ostream& stream,
const stacktrace_frame& frame,
detail::optional<bool> color_override
) const {
bool do_color = should_do_color(stream, color_override);
maybe_ensure_virtual_terminal_processing(stream, do_color);
print_frame_inner(stream, frame, do_color);
}
void print_frame_inner(std::ostream& stream, const stacktrace_frame& frame, bool color) const {
const auto reset = color ? RESET : "";
const auto green = color ? GREEN : "";
const auto yellow = color ? YELLOW : "";
const auto blue = color ? BLUE : "";
if(frame.is_inline) {
microfmt::print(stream, "{<{}} ", 2 * sizeof(frame_ptr) + 2, "(inlined)");
} else if(options.addresses != address_mode::none) {
auto address = options.addresses == address_mode::raw ? frame.raw_address : frame.object_address;
microfmt::print(stream, "{}0x{>{}:0h}{} ", blue, 2 * sizeof(frame_ptr), address, reset);
}
if(!frame.symbol.empty()) {
microfmt::print(stream, "in {}{}{}", yellow, frame.symbol, reset);
}
if(!frame.filename.empty()) {
microfmt::print(
stream,
"{}at {}{}{}",
frame.symbol.empty() ? "" : " ",
green,
options.paths == path_mode::full ? frame.filename : detail::basename(frame.filename, true),
reset
);
if(frame.line.has_value()) {
microfmt::print(stream, ":{}{}{}", blue, frame.line.value(), reset);
if(frame.column.has_value() && options.columns) {
microfmt::print(stream, ":{}{}{}", blue, frame.column.value(), reset);
}
}
}
}
};
formatter::formatter() : pimpl(new impl) {}
formatter::~formatter() {
delete pimpl;
}
formatter::formatter(formatter&& other) : pimpl(detail::exchange(other.pimpl, nullptr)) {}
formatter::formatter(const formatter& other) : pimpl(new impl(*other.pimpl)) {}
formatter& formatter::operator=(formatter&& other) {
if(pimpl) {
delete pimpl;
}
pimpl = detail::exchange(other.pimpl, nullptr);
return *this;
}
formatter& formatter::operator=(const formatter& other) {
if(pimpl) {
delete pimpl;
}
pimpl = new impl(*other.pimpl);
return *this;
}
formatter& formatter::header(std::string header) {
pimpl->header(std::move(header));
return *this;
}
formatter& formatter::colors(color_mode mode) {
pimpl->colors(mode);
return *this;
}
formatter& formatter::addresses(address_mode mode) {
pimpl->addresses(mode);
return *this;
}
formatter& formatter::paths(path_mode mode) {
pimpl->paths(mode);
return *this;
}
formatter& formatter::snippets(bool snippets) {
pimpl->snippets(snippets);
return *this;
}
formatter& formatter::snippet_context(int lines) {
pimpl->snippet_context(lines);
return *this;
}
formatter& formatter::columns(bool columns) {
pimpl->columns(columns);
return *this;
}
formatter& formatter::filtered_frame_placeholders(bool show) {
pimpl->filtered_frame_placeholders(show);
return *this;
}
formatter& formatter::filter(std::function<bool(const stacktrace_frame&)> filter) {
pimpl->filter(std::move(filter));
return *this;
}
std::string formatter::format(const stacktrace_frame& frame) const {
return pimpl->format(frame);
}
std::string formatter::format(const stacktrace_frame& frame, bool color) const {
return pimpl->format(frame, color);
}
std::string formatter::format(const stacktrace& trace) const {
return pimpl->format(trace);
}
std::string formatter::format(const stacktrace& trace, bool color) const {
return pimpl->format(trace, color);
}
void formatter::print(const stacktrace& trace) const {
pimpl->print(trace);
}
void formatter::print(const stacktrace& trace, bool color) const {
pimpl->print(trace, color);
}
void formatter::print(std::ostream& stream, const stacktrace& trace) const {
pimpl->print(stream, trace);
}
void formatter::print(std::ostream& stream, const stacktrace& trace, bool color) const {
pimpl->print(stream, trace, color);
}
void formatter::print(std::FILE* file, const stacktrace& trace) const {
pimpl->print(file, trace);
}
void formatter::print(std::FILE* file, const stacktrace& trace, bool color) const {
pimpl->print(file, trace, color);
}
void formatter::print(const stacktrace_frame& frame) const {
pimpl->print(frame);
}
void formatter::print(const stacktrace_frame& frame, bool color) const {
pimpl->print(frame, color);
}
void formatter::print(std::ostream& stream, const stacktrace_frame& frame) const {
pimpl->print(stream, frame);
}
void formatter::print(std::ostream& stream, const stacktrace_frame& frame, bool color) const {
pimpl->print(stream, frame, color);
}
void formatter::print(std::FILE* file, const stacktrace_frame& frame) const {
pimpl->print(file, frame);
}
void formatter::print(std::FILE* file, const stacktrace_frame& frame, bool color) const {
pimpl->print(file, frame, color);
}
const formatter& get_default_formatter() {
static formatter formatter;
return formatter;
}
}

327
src/from_current.cpp Normal file
View File

@ -0,0 +1,327 @@
#include <cpptrace/cpptrace.hpp>
#define CPPTRACE_DONT_PREPARE_UNWIND_INTERCEPTOR_ON
#include <cpptrace/from_current.hpp>
#include <system_error>
#include <typeinfo>
#include "platform/platform.hpp"
#include "utils/microfmt.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 HAS_MACH_VM
#include <mach/mach_vm.h>
#endif
#else
#include <fstream>
#include <ios>
#endif
#endif
#endif
namespace cpptrace {
namespace detail {
thread_local lazy_trace_holder current_exception_trace;
CPPTRACE_FORCE_NO_INLINE void collect_current_trace(std::size_t skip) {
current_exception_trace = lazy_trace_holder(cpptrace::generate_raw_trace(skip + 1));
}
#ifndef _MSC_VER
// set only once by do_prepare_unwind_interceptor
char (*intercept_unwind_handler)(std::size_t) = nullptr;
CPPTRACE_FORCE_NO_INLINE
bool intercept_unwind(const std::type_info*, const std::type_info*, void**, unsigned) {
if(intercept_unwind_handler) {
intercept_unwind_handler(1);
}
return false;
}
CPPTRACE_FORCE_NO_INLINE
bool unconditional_exception_unwind_interceptor(const std::type_info*, const std::type_info*, void**, unsigned) {
collect_current_trace(1);
return false;
}
using do_catch_fn = decltype(intercept_unwind);
unwind_interceptor::~unwind_interceptor() = default;
unconditional_unwind_interceptor::~unconditional_unwind_interceptor() = default;
#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 std::runtime_error(
microfmt::format(
"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 std::runtime_error(
microfmt::format(
"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 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 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 std::runtime_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
int get_page_protections(void* page) {
auto page_addr = reinterpret_cast<uintptr_t>(page);
std::ifstream stream("/proc/self/maps");
stream>>std::hex;
while(!stream.eof()) {
uintptr_t start;
uintptr_t stop;
stream>>start;
stream.ignore(1); // dash
stream>>stop;
if(stream.eof()) {
break;
}
if(stream.fail()) {
throw std::runtime_error("Failure reading /proc/self/maps");
}
if(page_addr >= start && page_addr < stop) {
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 std::runtime_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;
}
// std::cerr<<"--parsed: "<<std::hex<<start<<" "<<stop<<" "<<r<<w<<x<<std::endl;
return perms;
}
stream.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
throw std::runtime_error("Failed to find mapping with page in /proc/self/maps");
}
#endif
void mprotect_page(void* page, int page_size, int protections) {
if(mprotect(page, page_size, protections) != 0) {
throw std::runtime_error(microfmt::format("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 std::runtime_error(microfmt::format("mmap call failed: {}", strerror(errno)));
}
return page;
}
#endif
void perform_typeinfo_surgery(const std::type_info& info, do_catch_fn* do_catch_function) {
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
type_info_vtable_pointer = static_cast<void*>(static_cast<void**>(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 && (page_size & (page_size - 1)) != 0) {
throw std::runtime_error(
microfmt::format("getpagesize() is not a power of 2 greater than zero (was {})", 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);
// 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);
new_vtable[6] = reinterpret_cast<void*>(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 std::runtime_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);
}
void do_prepare_unwind_interceptor(char(*intercept_unwind_handler)(std::size_t)) {
static bool did_prepare = false;
if(!did_prepare) {
cpptrace::detail::intercept_unwind_handler = intercept_unwind_handler;
try {
perform_typeinfo_surgery(typeid(cpptrace::detail::unwind_interceptor), intercept_unwind);
perform_typeinfo_surgery(
typeid(cpptrace::detail::unconditional_unwind_interceptor),
unconditional_exception_unwind_interceptor
);
} catch(std::exception& e) {
std::fprintf(
stderr,
"Cpptrace: Exception occurred while preparing from_current support: %s\n",
e.what()
);
} catch(...) {
std::fprintf(stderr, "Cpptrace: Unknown exception occurred while preparing from_current support\n");
}
did_prepare = true;
}
}
#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();
}
}

41
src/options.cpp Normal file
View File

@ -0,0 +1,41 @@
#include <cpptrace/basic.hpp>
#include "options.hpp"
#include <atomic>
namespace cpptrace {
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
}
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;
}
}
namespace detail {
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;
}
}
}

15
src/options.hpp Normal file
View File

@ -0,0 +1,15 @@
#ifndef OPTIONS_HPP
#define OPTIONS_HPP
#include <cpptrace/utils.hpp>
namespace cpptrace {
namespace detail {
// exported for test purposes
CPPTRACE_EXPORT bool should_absorb_trace_exceptions();
bool should_resolve_inlined_calls();
cache_mode get_cache_mode();
}
}
#endif

View File

@ -0,0 +1,149 @@
#include "platform/platform.hpp"
#if IS_WINDOWS
#include "platform/dbghelp_utils.hpp"
#if defined(CPPTRACE_UNWIND_WITH_DBGHELP) \
|| defined(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP) \
|| defined(CPPTRACE_DEMANGLE_WITH_WINAPI)
#include "utils/error.hpp"
#include "utils/microfmt.hpp"
#include "utils/utils.hpp"
#include <unordered_map>
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <dbghelp.h>
namespace cpptrace {
namespace detail {
dbghelp_syminit_info::dbghelp_syminit_info(void* handle, bool should_sym_cleanup, bool should_close_handle)
: handle(handle), should_sym_cleanup(should_sym_cleanup), should_close_handle(should_close_handle) {}
dbghelp_syminit_info::~dbghelp_syminit_info() {
release();
}
void dbghelp_syminit_info::release() {
if(!handle) {
return;
}
if(should_sym_cleanup) {
if(!SymCleanup(handle)) {
throw internal_error("SymCleanup failed with code {}\n", GetLastError());
}
}
if(should_close_handle) {
if(!CloseHandle(handle)) {
throw internal_error("CloseHandle failed with code {}\n", GetLastError());
}
}
}
dbghelp_syminit_info dbghelp_syminit_info::make_not_owned(void* handle) {
return dbghelp_syminit_info(handle, false, false);
}
dbghelp_syminit_info dbghelp_syminit_info::make_owned(void* handle, bool should_close_handle) {
return dbghelp_syminit_info(handle, true, should_close_handle);
}
dbghelp_syminit_info::dbghelp_syminit_info(dbghelp_syminit_info&& other) {
handle = exchange(other.handle, nullptr);
should_sym_cleanup = other.should_sym_cleanup;
should_close_handle = other.should_close_handle;
}
dbghelp_syminit_info& dbghelp_syminit_info::operator=(dbghelp_syminit_info&& other) {
release();
handle = exchange(other.handle, nullptr);
should_sym_cleanup = other.should_sym_cleanup;
should_close_handle = other.should_close_handle;
return *this;
}
void* dbghelp_syminit_info::get_process_handle() const {
return handle;
}
dbghelp_syminit_info dbghelp_syminit_info::make_non_owning_view() const {
return make_not_owned(handle);
}
std::unordered_map<HANDLE, dbghelp_syminit_info>& get_syminit_cache() {
static std::unordered_map<HANDLE, dbghelp_syminit_info> syminit_cache;
return syminit_cache;
}
dbghelp_syminit_info ensure_syminit() {
auto lock = get_dbghelp_lock(); // locking around the entire access of the cache unordered_map
HANDLE proc = GetCurrentProcess();
if(get_cache_mode() == cache_mode::prioritize_speed) {
auto& syminit_cache = get_syminit_cache();
auto it = syminit_cache.find(proc);
if(it != syminit_cache.end()) {
return it->second.make_non_owning_view();
}
}
auto duplicated_handle = raii_wrap<void*>(nullptr, [] (void* handle) {
if(handle) {
if(!CloseHandle(handle)) {
throw internal_error("CloseHandle failed with code {}\n", GetLastError());
}
}
});
// https://github.com/jeremy-rifkin/cpptrace/issues/204
// https://github.com/jeremy-rifkin/cpptrace/pull/206
// https://learn.microsoft.com/en-us/windows/win32/debug/initializing-the-symbol-handler
// Apparently duplicating the process handle is the idiomatic thing to do and this avoids issues of
// SymInitialize being called twice.
// DuplicateHandle requires the PROCESS_DUP_HANDLE access right. If for some reason DuplicateHandle we fall back
// to calling SymInitialize on the process handle.
optional<DWORD> maybe_duplicate_handle_error_code;
if(!DuplicateHandle(proc, proc, proc, &duplicated_handle.get(), 0, FALSE, DUPLICATE_SAME_ACCESS)) {
maybe_duplicate_handle_error_code = GetLastError();
}
if(!SymInitialize(maybe_duplicate_handle_error_code ? proc : duplicated_handle.get(), NULL, TRUE)) {
if(maybe_duplicate_handle_error_code) {
throw internal_error(
"SymInitialize failed with error code {} after DuplicateHandle failed with error code {}",
GetLastError(),
maybe_duplicate_handle_error_code.unwrap()
);
} else {
throw internal_error("SymInitialize failed with error code {}", GetLastError());
}
}
auto info = dbghelp_syminit_info::make_owned(
maybe_duplicate_handle_error_code ? proc : exchange(duplicated_handle.get(), nullptr),
!maybe_duplicate_handle_error_code
);
// either cache and return a view or return the owning wrapper
if(get_cache_mode() == cache_mode::prioritize_speed) {
auto& syminit_cache = get_syminit_cache();
auto pair = syminit_cache.insert({proc, std::move(info)});
VERIFY(pair.second);
return pair.first->second.make_non_owning_view();
} else {
return info;
}
}
std::recursive_mutex dbghelp_lock;
std::unique_lock<std::recursive_mutex> get_dbghelp_lock() {
return std::unique_lock<std::recursive_mutex>{dbghelp_lock};
}
}
}
#endif
#endif

View File

@ -0,0 +1,50 @@
#ifndef DBGHELP_UTILS_HPP
#define DBGHELP_UTILS_HPP
#if defined(CPPTRACE_UNWIND_WITH_DBGHELP) \
|| defined(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP) \
|| defined(CPPTRACE_DEMANGLE_WITH_WINAPI)
#include "utils/common.hpp"
#include <unordered_map>
#include <mutex>
namespace cpptrace {
namespace detail {
class dbghelp_syminit_info {
// `void*` is used to avoid including the (expensive) windows.h header here
void* handle = nullptr;
bool should_sym_cleanup; // true if cleanup is not managed by the syminit cache
bool should_close_handle; // true if cleanup is not managed by the syminit cache and the handle was duplicated
dbghelp_syminit_info(void* handle, bool should_sym_cleanup, bool should_close_handle);
public:
~dbghelp_syminit_info();
void release();
NODISCARD static dbghelp_syminit_info make_not_owned(void* handle);
NODISCARD static dbghelp_syminit_info make_owned(void* handle, bool should_close_handle);
dbghelp_syminit_info(const dbghelp_syminit_info&) = delete;
dbghelp_syminit_info(dbghelp_syminit_info&&);
dbghelp_syminit_info& operator=(const dbghelp_syminit_info&) = delete;
dbghelp_syminit_info& operator=(dbghelp_syminit_info&&);
void* get_process_handle() const;
dbghelp_syminit_info make_non_owning_view() const;
};
// Ensure SymInitialize is called on the process. This function either
// - Finds that SymInitialize has been called for a handle to the current process already, in which case it returns
// a non-owning dbghelp_syminit_info instance holding the handle
// - Calls SymInitialize a handle to the current process, caches it, and returns a non-owning dbghelp_syminit_info
// - Calls SymInitialize and returns an owning dbghelp_syminit_info which will handle cleanup
dbghelp_syminit_info ensure_syminit();
NODISCARD std::unique_lock<std::recursive_mutex> get_dbghelp_lock();
}
}
#endif
#endif

View File

@ -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
namespace cpptrace {
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
}
}
}
#endif

View File

@ -1,9 +1,15 @@
#ifndef PATH_HPP
#define PATH_HPP
#include "common.hpp"
#include "platform/platform.hpp"
#include <string>
#include <cctype>
#if IS_WINDOWS
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#endif

48
src/platform/platform.hpp Normal file
View File

@ -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

View File

@ -4,14 +4,19 @@
#include <mutex>
#include <string>
#if defined(_WIN32)
#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
namespace cpptrace {
namespace detail {
inline std::string program_name() {
inline const char* program_name() {
static std::mutex mutex;
const std::lock_guard<std::mutex> lock(mutex);
static std::string name;
@ -31,7 +36,7 @@ namespace detail {
}
}
#elif defined(__APPLE__)
#elif IS_APPLE
#include <cstdint>
#include <mach-o/dyld.h>
@ -61,7 +66,7 @@ namespace detail {
}
}
#elif defined(__linux__)
#elif IS_LINUX
#include <linux/limits.h>
#include <sys/types.h>

View File

@ -1,4 +1,4 @@
#include "snippet.hpp"
#include "snippets/snippet.hpp"
#include <algorithm>
#include <cstdint>
@ -8,8 +8,9 @@
#include <fstream>
#include <iostream>
#include "../utils/common.hpp"
#include "../utils/utils.hpp"
#include "utils/common.hpp"
#include "utils/microfmt.hpp"
#include "utils/utils.hpp"
namespace cpptrace {
namespace detail {
@ -134,7 +135,10 @@ namespace detail {
if(color && line == target_line) {
snippet += RESET;
}
snippet += lines[line - original_begin] + "\n";
snippet += lines[line - original_begin];
if(line != end) {
snippet += '\n';
}
}
return snippet;
}

View File

@ -1,14 +1,14 @@
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF
#include "resolver.hpp"
#include "symbols/dwarf/resolver.hpp"
#include <cpptrace/cpptrace.hpp>
#include "../symbols.hpp"
#include "../../utils/common.hpp"
#include "../../utils/error.hpp"
#include "../../binary/object.hpp"
#include "../../binary/mach-o.hpp"
#include "../../utils/utils.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>
@ -34,7 +34,7 @@ namespace libdwarf {
if(!resolver) {
// this seems silly but it's an attempt to not repeatedly try to initialize new dwarf_resolvers if
// exceptions are thrown, e.g. if the path doesn't exist
resolver = std::unique_ptr<null_resolver>(new null_resolver);
resolver = detail::make_unique<null_resolver>();
resolver = make_dwarf_resolver(object_path);
}
return resolver;
@ -46,11 +46,11 @@ namespace libdwarf {
// the path doesn't exist
std::unordered_map<std::string, uint64_t> symbols;
this->symbols = symbols;
auto obj = mach_o::open_mach_o(object_path);
if(!obj) {
auto mach_o_object = open_mach_o_cached(object_path);
if(!mach_o_object) {
return this->symbols.unwrap();
}
auto symbol_table = obj.unwrap_value().symbol_table();
const auto& symbol_table = mach_o_object.unwrap_value()->symbol_table();
if(!symbol_table) {
return this->symbols.unwrap();
}
@ -72,8 +72,12 @@ namespace libdwarf {
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;
return get_resolver()->resolve_frame(frame);
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 {
{
@ -96,7 +100,7 @@ namespace libdwarf {
uint64_t size;
std::string name;
nullable<uint64_t> target_address; // T(-1) is used as a sentinel
std::size_t object_index;
std::size_t object_index; // index into target_objects
};
class debug_map_resolver : public symbol_resolver {
@ -106,11 +110,11 @@ namespace libdwarf {
debug_map_resolver(const std::string& source_object_path) {
// load mach-o
// TODO: Cache somehow?
auto obj = mach_o::open_mach_o(source_object_path);
if(!obj) {
auto mach_o_object = open_mach_o_cached(source_object_path);
if(!mach_o_object) {
return;
}
mach_o& source_mach = obj.unwrap_value();
mach_o& source_mach = *mach_o_object.unwrap_value();
auto source_debug_map = source_mach.get_debug_map();
if(!source_debug_map) {
return;
@ -133,6 +137,7 @@ namespace libdwarf {
}
}
// sort for binary lookup later
// TODO: Redundant?
std::sort(
symbols.begin(),
symbols.end(),
@ -167,7 +172,7 @@ namespace libdwarf {
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
0,
frame_info.object_address,
frame_info.object_path
},
closest_symbol_it->name,
@ -193,7 +198,7 @@ namespace libdwarf {
};
std::unique_ptr<symbol_resolver> make_debug_map_resolver(const std::string& object_path) {
return std::unique_ptr<debug_map_resolver>(new debug_map_resolver(object_path));
return detail::make_unique<debug_map_resolver>(object_path);
}
#endif
}

View File

@ -1,12 +1,12 @@
#ifndef DWARF_HPP
#define DWARF_HPP
#include <cpptrace/cpptrace.hpp>
#include "../utils/error.hpp"
#include "../utils/utils.hpp"
#include <cpptrace/basic.hpp>
#include "utils/error.hpp"
#include "utils/microfmt.hpp"
#include "utils/utils.hpp"
#include <functional>
#include <stdexcept>
#include <type_traits>
#ifdef CPPTRACE_USE_NESTED_LIBDWARF_HEADER_PATH
@ -27,10 +27,9 @@ namespace libdwarf {
[[noreturn]] inline void handle_dwarf_error(Dwarf_Debug dbg, Dwarf_Error error) {
Dwarf_Unsigned ev = dwarf_errno(error);
char* msg = dwarf_errmsg(error);
(void)dbg;
// dwarf_dealloc_error(dbg, error);
throw internal_error("dwarf error {} {}", ev, msg);
// dwarf_dealloc_error deallocates the message, attaching to msg is convenient
auto msg = raii_wrap(dwarf_errmsg(error), [dbg, error] (char*) { dwarf_dealloc_error(dbg, error); });
throw internal_error(microfmt::format("dwarf error {} {}", ev, msg.get()));
}
struct die_object {
@ -67,8 +66,12 @@ namespace libdwarf {
}
~die_object() {
release();
}
void release() {
if(die) {
dwarf_dealloc_die(die);
dwarf_dealloc_die(exchange(die, nullptr));
}
}
@ -76,16 +79,15 @@ namespace libdwarf {
die_object& operator=(const die_object&) = delete;
die_object(die_object&& other) noexcept : dbg(other.dbg), die(other.die) {
// done for finding mistakes, attempts to use the die_object after this should segfault
// a valid use otherwise would be moved_from.get_sibling() which would get the next CU
other.dbg = nullptr;
other.die = nullptr;
}
// dbg doesn't strictly have to be st to null but it helps ensure attempts to use the die_object after this to
// segfault. A valid use otherwise would be moved_from.get_sibling() which would get the next CU.
die_object(die_object&& other) noexcept
: dbg(exchange(other.dbg, nullptr)), die(exchange(other.die, nullptr)) {}
die_object& operator=(die_object&& other) noexcept {
std::swap(dbg, other.dbg);
std::swap(die, other.die);
release();
dbg = exchange(other.dbg, nullptr);
die = exchange(other.die, nullptr);
return *this;
}
@ -241,6 +243,30 @@ namespace libdwarf {
}
}
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;
@ -334,7 +360,7 @@ namespace libdwarf {
template<typename F>
// callback should return true to keep going
void dwarf4_ranges(Dwarf_Addr lowpc, F callback) const {
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;
@ -344,10 +370,7 @@ namespace libdwarf {
if(wrap(dwarf_global_formref, attr, &offset) != DW_DLV_OK) {
return;
}
Dwarf_Addr baseaddr = 0;
if(lowpc != (std::numeric_limits<Dwarf_Addr>::max)()) {
baseaddr = lowpc;
}
Dwarf_Addr baseaddr_original = baseaddr;
Dwarf_Ranges* ranges = nullptr;
Dwarf_Signed count = 0;
VERIFY(
@ -375,15 +398,15 @@ namespace libdwarf {
baseaddr = ranges[i].dwr_addr2;
} else {
ASSERT(ranges[i].dwr_type == DW_RANGES_END);
baseaddr = lowpc;
baseaddr = baseaddr_original;
}
}
}
template<typename F>
// callback should return true to keep going
void dwarf_ranges(int version, F callback) const {
Dwarf_Addr lowpc = (std::numeric_limits<Dwarf_Addr>::max)();
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;
@ -399,13 +422,13 @@ namespace libdwarf {
if(version >= 5) {
dwarf5_ranges(callback);
} else {
dwarf4_ranges(lowpc, callback);
dwarf4_ranges(get_ranges_base_address(cu_die), callback);
}
}
rangelist_entries get_rangelist_entries(int version) const {
rangelist_entries get_rangelist_entries(const die_object& cu_die, int version) const {
rangelist_entries vec;
dwarf_ranges(version, [&vec] (Dwarf_Addr low, Dwarf_Addr high) {
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)
@ -422,9 +445,9 @@ namespace libdwarf {
return vec;
}
Dwarf_Bool pc_in_die(int version, Dwarf_Addr pc) const {
Dwarf_Bool pc_in_die(const die_object& cu_die, int version, Dwarf_Addr pc) const {
bool found = false;
dwarf_ranges(version, [&found, pc] (Dwarf_Addr low, Dwarf_Addr high) {
dwarf_ranges(cu_die, version, [&found, pc] (Dwarf_Addr low, Dwarf_Addr high) {
if(pc >= low && pc < high) {
found = true;
return false;

View File

@ -0,0 +1,31 @@
#include "symbols/dwarf/dwarf_options.hpp"
#include <cpptrace/utils.hpp>
#include <atomic>
namespace cpptrace {
namespace detail {
std::atomic<nullable<std::size_t>> dwarf_resolver_line_table_cache_size{nullable<std::size_t>::null()};
std::atomic<bool> dwarf_resolver_disable_aranges{false};
optional<std::size_t> get_dwarf_resolver_line_table_cache_size() {
auto max_entries = dwarf_resolver_line_table_cache_size.load();
return max_entries.has_value() ? optional<std::size_t>(max_entries.value()) : nullopt;
}
bool get_dwarf_resolver_disable_aranges() {
return dwarf_resolver_disable_aranges.load();
}
}
namespace experimental {
void set_dwarf_resolver_line_table_cache_size(nullable<std::size_t> max_entries) {
detail::dwarf_resolver_line_table_cache_size.store(max_entries);
}
void set_dwarf_resolver_disable_aranges(bool disable) {
detail::dwarf_resolver_disable_aranges.store(disable);
}
}
}

View File

@ -0,0 +1,15 @@
#ifndef DWARF_OPTIONS_HPP
#define DWARF_OPTIONS_HPP
#include "utils/optional.hpp"
#include <cstddef>
namespace cpptrace {
namespace detail {
optional<std::size_t> get_dwarf_resolver_line_table_cache_size();
bool get_dwarf_resolver_disable_aranges();
}
}
#endif

View File

@ -1,24 +1,29 @@
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF
#include "resolver.hpp"
#include "symbols/dwarf/resolver.hpp"
#include <cpptrace/cpptrace.hpp>
#include "../symbols.hpp"
#include "../../utils/common.hpp"
#include "../../utils/dwarf.hpp" // has dwarf #includes
#include "../../utils/error.hpp"
#include "../../utils/utils.hpp"
#include "../../utils/path.hpp"
#include "../../utils/program_name.hpp" // For CPPTRACE_MAX_PATH
#include "../../binary/mach-o.hpp"
#include <cpptrace/basic.hpp>
#include "symbols/dwarf/dwarf.hpp" // has dwarf #includes
#include "symbols/dwarf/dwarf_utils.hpp"
#include "symbols/dwarf/dwarf_options.hpp"
#include "symbols/symbols.hpp"
#include "utils/common.hpp"
#include "utils/error.hpp"
#include "utils/utils.hpp"
#include "utils/lru_cache.hpp"
#include "platform/path.hpp"
#include "platform/program_name.hpp" // For CPPTRACE_MAX_PATH
#if IS_APPLE
#include "binary/mach-o.hpp"
#endif
#include <algorithm>
#include <cstdint>
#include <cstdio>
#include <functional>
#include <iterator>
#include <limits>
#include <memory>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <unordered_map>
@ -36,38 +41,6 @@ namespace libdwarf {
constexpr bool dump_dwarf = false;
constexpr bool trace_dwarf = false;
struct subprogram_entry {
die_object die;
Dwarf_Addr low;
Dwarf_Addr high;
};
struct cu_entry {
die_object die;
Dwarf_Half dwversion;
Dwarf_Addr low;
Dwarf_Addr high;
};
struct line_entry {
Dwarf_Addr low;
// Dwarf_Addr high;
// int i;
Dwarf_Line line;
optional<std::string> path;
optional<std::uint32_t> line_number;
optional<std::uint32_t> column_number;
line_entry(Dwarf_Addr low, Dwarf_Line line) : low(low), line(line) {}
};
struct line_table_info {
Dwarf_Unsigned version;
Dwarf_Line_Context line_context;
// sorted by low_addr
// TODO: Make this optional at some point, it may not be generated if cache mode switches during program exec...
std::vector<line_entry> line_entries;
};
class dwarf_resolver;
// used to describe data from an upstream binary to a resolver for the .dwo
@ -79,20 +52,24 @@ namespace libdwarf {
class dwarf_resolver : public symbol_resolver {
std::string object_path;
Dwarf_Debug dbg = nullptr;
// dwarf_finish needs to be called after all other dwarf stuff is cleaned up, e.g. `srcfiles` and aranges etc
// raii_wrapping ensures this is the last thing done after the destructor logic and all other data members are
// cleaned up
raii_wrapper<Dwarf_Debug, void(*)(Dwarf_Debug)> dbg{nullptr, [](Dwarf_Debug dbg) { dwarf_finish(dbg); }};
bool ok = false;
// .debug_aranges cache
Dwarf_Arange* aranges = nullptr;
Dwarf_Signed arange_count = 0;
// Map from CU -> Line context
std::unordered_map<Dwarf_Off, line_table_info> line_tables;
lru_cache<Dwarf_Off, line_table_info> line_tables{get_dwarf_resolver_line_table_cache_size()};
// Map from CU -> Sorted subprograms vector
std::unordered_map<Dwarf_Off, std::vector<subprogram_entry>> subprograms_cache;
std::unordered_map<Dwarf_Off, die_cache<monostate>> subprograms_cache;
// Vector of ranges and their corresponding CU offsets
std::vector<cu_entry> cu_cache;
// data stored for each cache entry is a Dwarf_Half dwversion
die_cache<Dwarf_Half> cu_cache;
bool generated_cu_cache = false;
// Map from CU -> {srcfiles, count}
std::unordered_map<Dwarf_Off, std::pair<char**, Dwarf_Signed>> srcfiles_cache;
std::unordered_map<Dwarf_Off, srcfiles> srcfiles_cache;
// Map from CU -> split full cu resolver
std::unordered_map<Dwarf_Off, std::unique_ptr<dwarf_resolver>> split_full_cu_resolvers;
// info for resolving a dwo object
@ -150,12 +127,12 @@ namespace libdwarf {
if(result.is_error()) {
result.drop_error();
} else if(result.unwrap_value()) {
auto obj = mach_o::open_mach_o(object_path);
if(!obj) {
auto mach_o_object = open_mach_o_cached(object_path);
if(!mach_o_object) {
ok = false;
return;
}
universal_number = obj.unwrap_value().get_fat_index();
universal_number = mach_o_object.unwrap_value()->get_fat_index();
}
#endif
@ -165,6 +142,7 @@ namespace libdwarf {
if(use_buffer) {
buffer = std::unique_ptr<char[]>(new char[CPPTRACE_MAX_PATH]);
}
dwarf_set_de_alloc_flag(0);
auto ret = wrap(
dwarf_init_path_a,
object_path.c_str(),
@ -174,7 +152,7 @@ namespace libdwarf {
universal_number,
nullptr,
nullptr,
&dbg
&dbg.get()
);
if(ret == DW_DLV_OK) {
ok = true;
@ -190,7 +168,7 @@ namespace libdwarf {
VERIFY(wrap(dwarf_set_tied_dbg, dbg, skeleton.unwrap().resolver.dbg) == DW_DLV_OK);
}
if(ok) {
if(ok && !get_dwarf_resolver_disable_aranges()) {
// Check for .debug_aranges for fast lookup
wrap(dwarf_get_aranges, dbg, &aranges, &arange_count);
}
@ -198,21 +176,13 @@ namespace libdwarf {
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
~dwarf_resolver() override {
// TODO: Maybe redundant since dwarf_finish(dbg); will clean up the line stuff anyway but may as well just
// for thoroughness
for(auto& entry : line_tables) {
dwarf_srclines_dealloc_b(entry.second.line_context);
}
for(auto& entry : srcfiles_cache) {
dwarf_dealloc(dbg, entry.second.first, DW_DLA_LIST);
}
// subprograms_cache needs to be destroyed before dbg otherwise there will be another use after free
subprograms_cache.clear();
if(aranges) {
for(int i = 0; i < arange_count; i++) {
dwarf_dealloc(dbg, aranges[i], DW_DLA_ARANGE);
aranges[i] = nullptr;
}
dwarf_dealloc(dbg, aranges, DW_DLA_LIST);
}
cu_cache.clear();
dwarf_finish(dbg);
}
dwarf_resolver(const dwarf_resolver&) = delete;
@ -274,29 +244,32 @@ namespace libdwarf {
walk_compilation_units([this] (const die_object& cu_die) {
Dwarf_Half offset_size = 0;
Dwarf_Half dwversion = 0;
dwarf_get_version_of_die(cu_die.get(), &dwversion, &offset_size);
VERIFY(dwarf_get_version_of_die(cu_die.get(), &dwversion, &offset_size) == DW_DLV_OK);
if(skeleton) {
// NOTE: If we have a corresponding skeleton, we assume we have one CU matching the skeleton CU
// Precedence for this assumption is https://dwarfstd.org/doc/DWARF5.pdf#subsection.3.1.3
// TODO: Also assuming same dwversion
auto ranges_vec = skeleton.unwrap().cu_die.get_rangelist_entries(dwversion);
const auto& skeleton_cu = skeleton.unwrap().cu_die;
auto ranges_vec = skeleton_cu.get_rangelist_entries(skeleton_cu, dwversion);
if(!ranges_vec.empty()) {
auto cu_die_handle = cu_cache.add_die(cu_die.clone());
for(auto range : ranges_vec) {
// TODO: Reduce cloning here
cu_cache.push_back({ cu_die.clone(), dwversion, range.first, range.second });
cu_cache.insert(cu_die_handle, range.first, range.second, dwversion);
}
}
return false;
} else {
auto ranges_vec = cu_die.get_rangelist_entries(dwversion);
auto ranges_vec = cu_die.get_rangelist_entries(cu_die, dwversion);
if(!ranges_vec.empty()) {
auto cu_die_handle = cu_cache.add_die(cu_die.clone());
for(auto range : ranges_vec) {
// TODO: Reduce cloning here
cu_cache.push_back({ cu_die.clone(), dwversion, range.first, range.second });
cu_cache.insert(cu_die_handle, range.first, range.second, dwversion);
}
}
return true;
}
});
std::sort(cu_cache.begin(), cu_cache.end(), [] (const cu_entry& a, const cu_entry& b) {
return a.low < b.low;
});
cu_cache.finalize();
generated_cu_cache = true;
}
}
@ -339,11 +312,11 @@ namespace libdwarf {
char** dw_srcfiles;
Dwarf_Signed dw_filecount;
VERIFY(wrap(dwarf_srcfiles, cu_die.get(), &dw_srcfiles, &dw_filecount) == DW_DLV_OK);
srcfiles srcfiles(cu_die.dbg, dw_srcfiles, dw_filecount);
if(Dwarf_Signed(file_i) < dw_filecount) {
// dwarf is using 1-indexing
filename = dw_srcfiles[file_i];
filename = srcfiles.get(file_i);
}
dwarf_dealloc(cu_die.dbg, dw_srcfiles, DW_DLA_LIST);
} else {
auto off = cu_die.get_global_offset();
auto it = srcfiles_cache.find(off);
@ -351,13 +324,11 @@ namespace libdwarf {
char** dw_srcfiles;
Dwarf_Signed dw_filecount;
VERIFY(wrap(dwarf_srcfiles, cu_die.get(), &dw_srcfiles, &dw_filecount) == DW_DLV_OK);
it = srcfiles_cache.insert(it, {off, {dw_srcfiles, dw_filecount}});
it = srcfiles_cache.insert(it, {off, srcfiles{cu_die.dbg, dw_srcfiles, dw_filecount}});
}
char** dw_srcfiles = it->second.first;
Dwarf_Signed dw_filecount = it->second.second;
if(Dwarf_Signed(file_i) < dw_filecount) {
if(file_i < it->second.count()) {
// dwarf is using 1-indexing
filename = dw_srcfiles[file_i];
filename = it->second.get(file_i);
}
}
return filename;
@ -386,14 +357,14 @@ namespace libdwarf {
walk_die_list(
child,
[this, &cu_die, pc, dwversion, &inlines, &target_die, &current_obj_holder] (const die_object& die) {
if(die.get_tag() == DW_TAG_inlined_subroutine && die.pc_in_die(dwversion, pc)) {
if(die.get_tag() == DW_TAG_inlined_subroutine && die.pc_in_die(cu_die, dwversion, pc)) {
const auto name = subprogram_symbol(die, dwversion);
auto file_i = die.get_unsigned_attribute(DW_AT_call_file);
// TODO: Refactor.... Probably put logic in resolve_filename.
if(file_i) {
// for dwarf 2, 3, 4, and experimental line table version 0xfe06 1-indexing is used
// for dwarf 5 0-indexing is used
optional<std::reference_wrapper<line_table_info>> line_table_opt;
optional<line_table_info&> line_table_opt;
if(skeleton) {
line_table_opt = skeleton.unwrap().resolver.get_line_table(
skeleton.unwrap().cu_die
@ -402,7 +373,7 @@ namespace libdwarf {
line_table_opt = get_line_table(cu_die);
}
if(line_table_opt) {
auto& line_table = line_table_opt.unwrap().get();
auto& line_table = line_table_opt.unwrap();
if(line_table.version != 5) {
if(file_i.unwrap() == 0) {
file_i.reset(); // 0 means no name to be found
@ -430,6 +401,10 @@ namespace libdwarf {
current_obj_holder = die.clone();
target_die = current_obj_holder;
return false;
} else if(die.get_tag() == DW_TAG_lexical_block && die.pc_in_die(cu_die, dwversion, pc)) {
current_obj_holder = die.clone();
target_die = current_obj_holder;
return false;
} else {
return true;
}
@ -478,7 +453,7 @@ namespace libdwarf {
die.get_name().c_str()
);
}
if(!(die.get_tag() == DW_TAG_namespace || die.pc_in_die(dwversion, pc))) {
if(!(die.get_tag() == DW_TAG_namespace || die.pc_in_die(cu_die, dwversion, pc))) {
if(dump_dwarf) {
std::fprintf(stderr, "pc not in die\n");
}
@ -520,28 +495,31 @@ namespace libdwarf {
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
void preprocess_subprograms(
const die_object& cu_die,
const die_object& die,
Dwarf_Half dwversion,
std::vector<subprogram_entry>& vec
die_cache<monostate>& subprogram_cache
) {
walk_die_list(
die,
[this, dwversion, &vec] (const die_object& die) {
[this, &cu_die, dwversion, &subprogram_cache] (const die_object& die) {
switch(die.get_tag()) {
case DW_TAG_subprogram:
{
auto ranges_vec = die.get_rangelist_entries(dwversion);
auto ranges_vec = die.get_rangelist_entries(cu_die, dwversion);
// TODO: Feels super inefficient and some day should maybe use an interval tree.
if(!ranges_vec.empty()) {
auto die_handle = subprogram_cache.add_die(die.clone());
for(auto range : ranges_vec) {
// TODO: Reduce cloning here
vec.push_back({ die.clone(), range.first, range.second });
subprogram_cache.insert(die_handle, range.first, range.second);
}
}
// Walk children to get things like lambdas
// TODO: Somehow find a way to get better names here? For gcc it's just "operator()"
// On clang it's better
auto child = die.get_child();
if(child) {
preprocess_subprograms(child, dwversion, vec);
preprocess_subprograms(cu_die, child, dwversion, subprogram_cache);
}
}
break;
@ -554,7 +532,7 @@ namespace libdwarf {
{
auto child = die.get_child();
if(child) {
preprocess_subprograms(child, dwversion, vec);
preprocess_subprograms(cu_die, child, dwversion, subprogram_cache);
}
}
break;
@ -584,41 +562,32 @@ namespace libdwarf {
auto it = subprograms_cache.find(off);
if(it == subprograms_cache.end()) {
// TODO: Refactor. Do the sort in the preprocess function and return the vec directly.
std::vector<subprogram_entry> vec;
preprocess_subprograms(cu_die, dwversion, vec);
std::sort(vec.begin(), vec.end(), [] (const subprogram_entry& a, const subprogram_entry& b) {
return a.low < b.low;
});
subprograms_cache.emplace(off, std::move(vec));
die_cache<monostate> subprogram_cache;
preprocess_subprograms(cu_die, cu_die, dwversion, subprogram_cache);
subprogram_cache.finalize();
subprograms_cache.emplace(off, std::move(subprogram_cache));
it = subprograms_cache.find(off);
}
auto& vec = it->second;
auto vec_it = first_less_than_or_equal(
vec.begin(),
vec.end(),
pc,
[] (Dwarf_Addr pc, const subprogram_entry& entry) {
return pc < entry.low;
}
);
const auto& subprogram_cache = it->second;
auto maybe_die = subprogram_cache.lookup(pc);
// If the vector has been empty this can happen
if(vec_it != vec.end()) {
if(vec_it->die.pc_in_die(dwversion, pc)) {
frame.symbol = retrieve_symbol_for_subprogram(cu_die, vec_it->die, pc, dwversion, inlines);
if(maybe_die.has_value()) {
if(maybe_die.unwrap().pc_in_die(cu_die, dwversion, pc)) {
frame.symbol = retrieve_symbol_for_subprogram(cu_die, maybe_die.unwrap(), pc, dwversion, inlines);
}
} else {
ASSERT(vec.size() == 0, "Vec should be empty?");
ASSERT(subprogram_cache.ranges_count() == 0, "subprogram_cache.ranges_count() should be 0?");
}
}
}
// returns a reference to a CU's line table, may be invalidated if the line_tables map is modified
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
optional<std::reference_wrapper<line_table_info>> get_line_table(const die_object& cu_die) {
optional<line_table_info&> get_line_table(const die_object& cu_die) {
auto off = cu_die.get_global_offset();
auto it = line_tables.find(off);
if(it != line_tables.end()) {
return it->second;
auto res = line_tables.maybe_get(off);
if(res) {
return res;
} else {
Dwarf_Unsigned version;
Dwarf_Small table_count;
@ -673,24 +642,6 @@ namespace libdwarf {
}
}
line = line_buffer[j - 1];
// {
// Dwarf_Unsigned line_number = 0;
// VERIFY(wrap(dwarf_lineno, line, &line_number) == DW_DLV_OK);
// frame.line = static_cast<std::uint32_t>(line_number);
// char* filename = nullptr;
// VERIFY(wrap(dwarf_linesrc, line, &filename) == DW_DLV_OK);
// auto wrapper = raii_wrap(
// filename,
// [this] (char* str) { if(str) dwarf_dealloc(dbg, str, DW_DLA_STRING); }
// );
// frame.filename = filename;
// printf("%s : %d\n", filename, line_number);
// Dwarf_Bool is_line_end;
// VERIFY(wrap(dwarf_lineendsequence, line, &is_line_end) == DW_DLV_OK);
// if(is_line_end) {
// puts("Line end");
// }
// }
line_entries.push_back({
low_addr,
line
@ -703,8 +654,7 @@ namespace libdwarf {
});
}
it = line_tables.insert({off, {version, line_context, std::move(line_entries)}}).first;
return it->second;
return line_tables.insert(off, line_table_info{version, line_context, std::move(line_entries)});
}
}
@ -722,7 +672,7 @@ namespace libdwarf {
if(!table_info_opt) {
return; // failing silently for now
}
auto& table_info = table_info_opt.unwrap().get();
auto& table_info = table_info_opt.unwrap();
if(get_cache_mode() == cache_mode::prioritize_speed) {
// Lookup in the table
auto& line_entries = table_info.line_entries;
@ -880,8 +830,14 @@ namespace libdwarf {
}
// NOTE: If we have a corresponding skeleton, we assume we have one CU matching the skeleton CU
if(
(skeleton && skeleton.unwrap().cu_die.pc_in_die(skeleton.unwrap().dwversion, pc))
|| cu_die.pc_in_die(dwversion, pc)
(
skeleton
&& skeleton.unwrap().cu_die.pc_in_die(
skeleton.unwrap().cu_die,
skeleton.unwrap().dwversion,
pc
)
) || cu_die.pc_in_die(cu_die, dwversion, pc)
) {
if(trace_dwarf) {
std::fprintf(
@ -901,29 +857,27 @@ namespace libdwarf {
} else {
lazy_generate_cu_cache();
// look up the cu
auto vec_it = first_less_than_or_equal(
cu_cache.begin(),
cu_cache.end(),
pc,
[] (Dwarf_Addr pc, const cu_entry& entry) {
return pc < entry.low;
}
);
// TODO: Vec-it is already range-based, this range check is redundant
// If the vector has been empty this can happen
if(vec_it != cu_cache.end()) {
auto res = cu_cache.lookup(pc);
// res can be nullopt if the cu_cache vector is empty
// It can also happen for something like _start, where there is a cached CU for the object but
// _start is outside of the CU's PC range
if(res) {
const auto& die = res.unwrap().die;
const auto dwversion = res.unwrap().data;
// TODO: Cache the range list?
// NOTE: If we have a corresponding skeleton, we assume we have one CU matching the skeleton CU
if(
(skeleton && skeleton.unwrap().cu_die.pc_in_die(skeleton.unwrap().dwversion, pc))
|| vec_it->die.pc_in_die(vec_it->dwversion, pc)
(
skeleton
&& skeleton.unwrap().cu_die.pc_in_die(
skeleton.unwrap().cu_die,
skeleton.unwrap().dwversion,
pc
)
) || die.pc_in_die(die, dwversion, pc)
) {
return cu_info{maybe_owned_die_object::ref(vec_it->die), vec_it->dwversion};
return cu_info{maybe_owned_die_object::ref(die), dwversion};
}
} else {
// I've had this happen for _start, where there is a cached CU for the object but _start is outside
// of the CU's PC range
// ASSERT(cu_cache.size() == 0, "Vec should be empty?");
}
return nullopt;
}
@ -978,12 +932,7 @@ namespace libdwarf {
if(it == split_full_cu_resolvers.end()) {
it = split_full_cu_resolvers.emplace(
off,
std::unique_ptr<dwarf_resolver>(
new dwarf_resolver(
path,
skeleton_info{cu_die.clone(), dwversion, *this}
)
)
detail::make_unique<dwarf_resolver>(path, skeleton_info{cu_die.clone(), dwversion, *this})
).first;
}
res = it->second->resolve_frame(object_frame_info);
@ -1008,8 +957,11 @@ namespace libdwarf {
if(cu) {
const auto& cu_die = cu.unwrap().cu_die.get();
// gnu non-standard debug-fission may create non-skeleton CU DIEs and just add dwo attributes
// clang emits dwo names in the split CUs, so guard against going down the dwarf fission path (which
// doesn't infinitely recurse because it's not emitted as an absolute path and there's no comp dir but
// it's good to guard against the infinite recursion anyway)
auto dwo_name = get_dwo_name(cu_die);
if(cu_die.get_tag() == DW_TAG_skeleton_unit || dwo_name) {
if(cu_die.get_tag() == DW_TAG_skeleton_unit || (dwo_name && !skeleton)) {
perform_dwarf_fission_resolution(cu_die, dwo_name, object_frame_info, frame, inlines);
} else {
retrieve_line_info(cu_die, pc, frame);
@ -1058,7 +1010,7 @@ namespace libdwarf {
};
std::unique_ptr<symbol_resolver> make_dwarf_resolver(const std::string& object_path) {
return std::unique_ptr<dwarf_resolver>(new dwarf_resolver(object_path));
return detail::make_unique<dwarf_resolver>(object_path);
}
}
}

View File

@ -0,0 +1,208 @@
#ifndef DWARF_UTILS_HPP
#define DWARF_UTILS_HPP
#include <cpptrace/basic.hpp>
#include "symbols/dwarf/dwarf.hpp" // has dwarf #includes
#include "utils/error.hpp"
#include "utils/microfmt.hpp"
#include "utils/utils.hpp"
namespace cpptrace {
namespace detail {
namespace libdwarf {
class srcfiles {
Dwarf_Debug dbg = nullptr;
char** dw_srcfiles = nullptr;
Dwarf_Unsigned dw_filecount = 0;
public:
srcfiles(Dwarf_Debug dbg, char** dw_srcfiles, Dwarf_Signed filecount)
: dbg(dbg), dw_srcfiles(dw_srcfiles), dw_filecount(static_cast<Dwarf_Unsigned>(filecount))
{
if(filecount < 0) {
throw internal_error(microfmt::format("Unexpected dw_filecount {}", filecount));
}
}
~srcfiles() {
release();
}
void release() {
if(dw_srcfiles) {
for(unsigned i = 0; i < dw_filecount; i++) {
dwarf_dealloc(dbg, dw_srcfiles[i], DW_DLA_STRING);
dw_srcfiles[i] = nullptr;
}
dwarf_dealloc(dbg, dw_srcfiles, DW_DLA_LIST);
dw_srcfiles = nullptr;
}
}
srcfiles(const srcfiles&) = delete;
srcfiles(srcfiles&& other) {
*this = std::move(other);
}
srcfiles& operator=(const srcfiles&) = delete;
srcfiles& operator=(srcfiles&& other) {
release();
dbg = exchange(other.dbg, nullptr);
dw_srcfiles = exchange(other.dw_srcfiles, nullptr);
dw_filecount = exchange(other.dw_filecount, 0);
return *this;
}
// note: dwarf uses 1-indexing
const char* get(Dwarf_Unsigned file_i) const {
if(file_i >= dw_filecount) {
throw internal_error(microfmt::format(
"Error while accessing the srcfiles list, requested index {} is out of bounds (count = {})",
file_i,
dw_filecount
));
}
return dw_srcfiles[file_i];
}
Dwarf_Unsigned count() const {
return dw_filecount;
}
};
// sorted range entries for dies
template<
typename T,
typename std::enable_if<std::is_trivially_copyable<T>::value && sizeof(T) <= 16, int>::type = 0
>
class die_cache {
public:
struct die_handle {
std::uint32_t die_index;
};
private:
struct PACKED basic_range_entry {
die_handle die;
Dwarf_Addr low;
Dwarf_Addr high;
};
struct PACKED annotated_range_entry {
die_handle die;
Dwarf_Addr low;
Dwarf_Addr high;
T data;
};
using range_entry = typename std::conditional<
std::is_same<T, monostate>::value,
basic_range_entry,
annotated_range_entry
>::type;
std::vector<die_object> dies;
std::vector<range_entry> range_entries;
public:
die_handle add_die(die_object&& die) {
dies.push_back(std::move(die));
VERIFY(dies.size() < std::numeric_limits<std::uint32_t>::max());
return die_handle{static_cast<std::uint32_t>(dies.size() - 1)};
}
template<typename Void = void>
auto insert(die_handle die, Dwarf_Addr low, Dwarf_Addr high)
-> typename std::enable_if<std::is_same<T, monostate>::value, Void>::type
{
range_entries.push_back({die, low, high});
}
template<typename Void = void>
auto insert(die_handle die, Dwarf_Addr low, Dwarf_Addr high, const T& t)
-> typename std::enable_if<!std::is_same<T, monostate>::value, Void>::type
{
range_entries.push_back({die, low, high, t});
}
void finalize() {
std::sort(range_entries.begin(), range_entries.end(), [] (const range_entry& a, const range_entry& b) {
return a.low < b.low;
});
}
std::size_t ranges_count() const {
return range_entries.size();
}
struct die_and_data {
const die_object& die;
T data;
};
template<typename Ret = const die_object&>
auto make_lookup_result(typename std::vector<range_entry>::const_iterator vec_it) const
-> typename std::enable_if<std::is_same<T, monostate>::value, Ret>::type
{
return dies.at(vec_it->die.die_index);
}
template<typename Ret = die_and_data>
auto make_lookup_result(typename std::vector<range_entry>::const_iterator vec_it) const
-> typename std::enable_if<!std::is_same<T, monostate>::value, Ret>::type
{
return die_and_data{dies.at(vec_it->die.die_index), vec_it->data};
}
using lookup_result = typename std::conditional<
std::is_same<T, monostate>::value,
const die_object&,
die_and_data
>::type;
optional<lookup_result> lookup(Dwarf_Addr pc) const {
auto vec_it = first_less_than_or_equal(
range_entries.begin(),
range_entries.end(),
pc,
[] (Dwarf_Addr pc, const range_entry& entry) {
return pc < entry.low;
}
);
if(vec_it == range_entries.end()) {
return nullopt;
}
// This would be an if constexpr if only C++17...
return make_lookup_result(vec_it);
}
};
struct line_entry {
Dwarf_Addr low;
// Dwarf_Addr high;
// int i;
Dwarf_Line line;
optional<std::string> path;
optional<std::uint32_t> line_number;
optional<std::uint32_t> column_number;
line_entry(Dwarf_Addr low, Dwarf_Line line) : low(low), line(line) {}
};
struct line_table_info {
Dwarf_Unsigned version = 0;
Dwarf_Line_Context line_context = nullptr;
// sorted by low_addr
// TODO: Make this optional at some point, it may not be generated if cache mode switches during program exec...
std::vector<line_entry> line_entries;
line_table_info(
Dwarf_Unsigned version,
Dwarf_Line_Context line_context,
std::vector<line_entry>&& line_entries
) : version(version), line_context(line_context), line_entries(std::move(line_entries)) {}
~line_table_info() {
release();
}
void release() {
dwarf_srclines_dealloc_b(line_context);
line_context = nullptr;
}
line_table_info(const line_table_info&) = delete;
line_table_info(line_table_info&& other) {
*this = std::move(other);
}
line_table_info& operator=(const line_table_info&) = delete;
line_table_info& operator=(line_table_info&& other) {
release();
version = other.version;
line_context = exchange(other.line_context, nullptr);
line_entries = std::move(other.line_entries);
return *this;
}
};
}
}
}
#endif

View File

@ -1,9 +1,9 @@
#ifndef SYMBOL_RESOLVER_HPP
#define SYMBOL_RESOLVER_HPP
#include <cpptrace/cpptrace.hpp>
#include "../symbols.hpp"
#include "../../utils/common.hpp"
#include <cpptrace/basic.hpp>
#include "symbols/symbols.hpp"
#include "platform/platform.hpp"
#include <memory>

View File

@ -1,13 +1,13 @@
#ifndef SYMBOLS_HPP
#define SYMBOLS_HPP
#include <cpptrace/cpptrace.hpp>
#include <cpptrace/basic.hpp>
#include <memory>
#include <vector>
#include <functional>
#include <string>
#include <unordered_map>
#include "../binary/object.hpp"
#include <utility>
#include <vector>
namespace cpptrace {
namespace detail {

View File

@ -1,10 +1,12 @@
#include "symbols.hpp"
#include <cpptrace/basic.hpp>
#include "symbols/symbols.hpp"
#include <vector>
#include <unordered_map>
#include "../utils/common.hpp"
#include "../binary/object.hpp"
#include "utils/error.hpp"
#include "binary/object.hpp"
namespace cpptrace {
namespace detail {
@ -81,6 +83,8 @@ namespace detail {
}
}
// TODO: Symbol resolution code should probably handle when object addresses are 0
std::vector<stacktrace_frame> resolve_frames(const std::vector<object_frame>& frames) {
#if defined(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) && defined(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP)
std::vector<stacktrace_frame> trace = libdwarf::resolve_frames(frames);

View File

@ -1,9 +1,10 @@
#ifdef CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE
#include <cpptrace/cpptrace.hpp>
#include "symbols.hpp"
#include "../utils/common.hpp"
#include "../utils/utils.hpp"
#include <cpptrace/basic.hpp>
#include "symbols/symbols.hpp"
#include "utils/common.hpp"
#include "utils/microfmt.hpp"
#include "utils/utils.hpp"
#include <cstdint>
#include <cstdio>
@ -20,7 +21,8 @@
#include <sys/wait.h>
#endif
#include "../binary/object.hpp"
#include "binary/object.hpp"
#include "options.hpp"
namespace cpptrace {
namespace detail {

View File

@ -1,16 +1,21 @@
#ifdef CPPTRACE_GET_SYMBOLS_WITH_DBGHELP
#include <cpptrace/cpptrace.hpp>
#include "symbols.hpp"
#include "../utils/dbghelp_syminit_manager.hpp"
#include <cpptrace/basic.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 <memory>
#include <mutex>
#include <regex>
#include <stdexcept>
#include <system_error>
#include <vector>
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <dbghelp.h>
@ -235,6 +240,9 @@ namespace dbghelp {
std::size_t sz = sizeof(TI_FINDCHILDREN_PARAMS) +
(n_children) * sizeof(TI_FINDCHILDREN_PARAMS::ChildId[0]);
TI_FINDCHILDREN_PARAMS* children = (TI_FINDCHILDREN_PARAMS*) new char[sz];
auto guard = scope_exit([&] {
delete[] (char*) children;
});
children->Start = 0;
children->Count = n_children;
if(
@ -260,7 +268,6 @@ namespace dbghelp {
extent += (i == 0 ? "" : ", ") + resolve_type(children->ChildId[i], proc, modbase);
}
extent += ")";
delete[] (char*) children;
return {return_type.base, extent + return_type.extent};
}
}
@ -320,8 +327,6 @@ namespace dbghelp {
return true;
}
std::recursive_mutex dbghelp_lock;
// TODO: Handle backtrace_pcinfo calling the callback multiple times on inlined functions
stacktrace_frame resolve_frame(HANDLE proc, frame_ptr addr) {
// The get_frame_object_info() ends up being inexpensive, at on my machine
@ -331,14 +336,15 @@ namespace dbghelp {
// get_frame_object_info() 0.001-0.002 ms 0.0003-0.0006 ms
// At some point it might make sense to make an option to control this.
auto object_frame = get_frame_object_info(addr);
const std::lock_guard<std::recursive_mutex> lock(dbghelp_lock); // all dbghelp functions are not thread safe
// Dbghelp is is single-threaded, so acquire a lock.
auto lock = get_dbghelp_lock();
alignas(SYMBOL_INFO) char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
SYMBOL_INFO* symbol = (SYMBOL_INFO*)buffer;
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
symbol->MaxNameLen = MAX_SYM_NAME;
union { DWORD64 a; DWORD b; } displacement;
IMAGEHLP_LINE64 line;
bool got_line = SymGetLineFromAddr64(proc, addr, &displacement.b, &line);
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;
@ -416,23 +422,18 @@ namespace dbghelp {
}
std::vector<stacktrace_frame> resolve_frames(const std::vector<frame_ptr>& frames) {
const std::lock_guard<std::recursive_mutex> lock(dbghelp_lock); // all dbghelp functions are not thread safe
// Dbghelp is is single-threaded, so acquire a lock.
auto lock = get_dbghelp_lock();
std::vector<stacktrace_frame> trace;
trace.reserve(frames.size());
// TODO: When does this need to be called? Can it be moved to the symbolizer?
SymSetOptions(SYMOPT_ALLOW_ABSOLUTE_SYMBOLS);
HANDLE proc = GetCurrentProcess();
if(get_cache_mode() == cache_mode::prioritize_speed) {
get_syminit_manager().init(proc);
} else {
if(!SymInitialize(proc, NULL, TRUE)) {
throw internal_error("SymInitialize failed");
}
}
auto syminit_info = ensure_syminit();
for(const auto frame : frames) {
try {
trace.push_back(resolve_frame(proc, frame));
trace.push_back(resolve_frame(syminit_info.get_process_handle() , frame));
} catch(...) { // NOSONAR
if(!detail::should_absorb_trace_exceptions()) {
throw;
@ -442,11 +443,6 @@ namespace dbghelp {
trace.push_back(entry);
}
}
if(get_cache_mode() != cache_mode::prioritize_speed) {
if(!SymCleanup(proc)) {
throw internal_error("SymCleanup failed");
}
}
return trace;
}
}

View File

@ -1,7 +1,8 @@
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDL
#include <cpptrace/cpptrace.hpp>
#include "symbols.hpp"
#include <cpptrace/basic.hpp>
#include "symbols/symbols.hpp"
#include "binary/module_base.hpp"
#include <cstdint>
#include <memory>

View File

@ -1,8 +1,11 @@
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE
#include <cpptrace/cpptrace.hpp>
#include "symbols.hpp"
#include "../utils/program_name.hpp"
#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>

View File

@ -1,12 +1,13 @@
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF
#include "symbols.hpp"
#include "symbols/symbols.hpp"
#include <cpptrace/cpptrace.hpp>
#include <cpptrace/basic.hpp>
#include "dwarf/resolver.hpp"
#include "../utils/common.hpp"
#include "../utils/error.hpp"
#include "../utils/utils.hpp"
#include "utils/common.hpp"
#include "utils/utils.hpp"
#include "binary/elf.hpp"
#include "binary/mach-o.hpp"
#include <cstdint>
#include <cstdio>
@ -15,8 +16,6 @@
#include <unordered_map>
#include <vector>
#include <iostream>
#include <iomanip>
namespace cpptrace {
namespace detail {
@ -95,6 +94,12 @@ namespace libdwarf {
for(const auto& group : collate_frames(frames, trace)) {
try {
const auto& object_name = group.first;
// TODO PERF: Potentially a duplicate open and parse with module base stuff (and debug map resolver)
#if IS_LINUX
auto object = 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();
@ -109,6 +114,13 @@ namespace libdwarf {
throw;
}
}
#if IS_LINUX || IS_APPLE
if(frame.frame.symbol.empty() && object.has_value()) {
frame.frame.symbol = object
.unwrap_value()
->lookup_symbol(dlframe.object_address).value_or("");
}
#endif
}
} catch(...) { // NOSONAR
if(!should_absorb_trace_exceptions()) {

View File

@ -1,7 +1,8 @@
#ifdef CPPTRACE_GET_SYMBOLS_WITH_NOTHING
#include <cpptrace/cpptrace.hpp>
#include "symbols.hpp"
#include <cpptrace/basic.hpp>
#include "symbols/symbols.hpp"
#include "utils/common.hpp"
#include <vector>

View File

@ -1,8 +1,7 @@
#ifndef UNWIND_HPP
#define UNWIND_HPP
#include "../utils/common.hpp"
#include "../utils/utils.hpp"
#include <cpptrace/basic.hpp>
#include <cstddef>
#include <vector>
@ -12,7 +11,7 @@ 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 = 200;
constexpr std::size_t hard_max_frames = 400;
#endif
CPPTRACE_FORCE_NO_INLINE

View File

@ -1,15 +1,13 @@
#ifdef CPPTRACE_UNWIND_WITH_DBGHELP
#include <cpptrace/cpptrace.hpp>
#include "unwind.hpp"
#include "../utils/common.hpp"
#include "../utils/utils.hpp"
#include "../utils/dbghelp_syminit_manager.hpp"
#include <cpptrace/basic.hpp>
#include "unwind/unwind.hpp"
#include "utils/common.hpp"
#include "utils/utils.hpp"
#include "platform/dbghelp_utils.hpp"
#include <algorithm>
#include <cstdint>
#include <vector>
#include <mutex>
#include <cstddef>
#include <windows.h>
#include <dbghelp.h>
@ -97,27 +95,19 @@ namespace detail {
std::vector<frame_ptr> trace;
// Dbghelp is is single-threaded, so acquire a lock.
static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex);
auto lock = get_dbghelp_lock();
// For some reason SymInitialize must be called before StackWalk64
// Note that the code assumes that
// SymInitialize( GetCurrentProcess(), NULL, TRUE ) has
// already been called.
//
HANDLE proc = GetCurrentProcess();
auto syminit_info = ensure_syminit();
HANDLE thread = GetCurrentThread();
if(get_cache_mode() == cache_mode::prioritize_speed) {
get_syminit_manager().init(proc);
} else {
if(!SymInitialize(proc, NULL, TRUE)) {
throw internal_error("SymInitialize failed");
}
}
while(trace.size() < max_depth) {
if(
!StackWalk64(
machine_type,
proc,
syminit_info.get_process_handle(),
thread,
&frame,
machine_type == IMAGE_FILE_MACHINE_I386 ? NULL : &context,
@ -145,11 +135,6 @@ namespace detail {
break;
}
}
if(get_cache_mode() != cache_mode::prioritize_speed) {
if(!SymCleanup(proc)) {
throw internal_error("SymCleanup failed");
}
}
return trace;
}

View File

@ -1,8 +1,8 @@
#ifdef CPPTRACE_UNWIND_WITH_EXECINFO
#include "unwind.hpp"
#include "../utils/common.hpp"
#include "../utils/utils.hpp"
#include "unwind/unwind.hpp"
#include "utils/common.hpp"
#include "utils/utils.hpp"
#include <algorithm>
#include <climits>

View File

@ -1,9 +1,9 @@
#ifdef CPPTRACE_UNWIND_WITH_LIBUNWIND
#include "unwind.hpp"
#include "../utils/common.hpp"
#include "../utils/error.hpp"
#include "../utils/utils.hpp"
#include "unwind/unwind.hpp"
#include "utils/common.hpp"
#include "utils/error.hpp"
#include "utils/utils.hpp"
#include <algorithm>
#include <cassert>

View File

@ -1,6 +1,6 @@
#ifdef CPPTRACE_UNWIND_WITH_NOTHING
#include "unwind.hpp"
#include "unwind/unwind.hpp"
#include <cstddef>
#include <vector>

View File

@ -1,9 +1,9 @@
#ifdef CPPTRACE_UNWIND_WITH_UNWIND
#include "unwind.hpp"
#include "../utils/common.hpp"
#include "../utils/error.hpp"
#include "../utils/utils.hpp"
#include "unwind/unwind.hpp"
#include "utils/common.hpp"
#include "utils/error.hpp"
#include "utils/utils.hpp"
#include <algorithm>
#include <cassert>

View File

@ -1,14 +1,17 @@
#ifdef CPPTRACE_UNWIND_WITH_WINAPI
#include <cpptrace/cpptrace.hpp>
#include "unwind.hpp"
#include "../utils/common.hpp"
#include "../utils/utils.hpp"
#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

84
src/utils.cpp Normal file
View File

@ -0,0 +1,84 @@
#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"
namespace cpptrace {
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, context_size, color);
}
bool isatty(int fd) {
return detail::isatty(fd);
}
extern const int stdin_fileno = detail::fileno(stdin);
extern const int stdout_fileno = detail::fileno(stdout);
extern const int stderr_fileno = detail::fileno(stderr);
namespace detail {
const formatter& get_terminate_formatter() {
static formatter the_formatter = formatter{}
.header("Stack trace to reach terminate handler (most recent call first):");
return the_formatter;
}
}
CPPTRACE_FORCE_NO_INLINE void print_terminate_trace() {
try { // try/catch can never be hit but it's needed to prevent TCO
detail::get_terminate_formatter().print(std::cerr, generate_trace(1));
} catch(...) {
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
}
}
[[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);
}
}

View File

@ -1,45 +1,11 @@
#ifndef COMMON_HPP
#define COMMON_HPP
#define IS_WINDOWS 0
#define IS_LINUX 0
#define IS_APPLE 0
#include <cpptrace/basic.hpp>
#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
#include "platform/platform.hpp"
#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
#include <cstdio>
#include <stdexcept>
#include <string>
#include <cpptrace/cpptrace.hpp>
#include <cstdint>
#define ESC "\033["
#define RESET ESC "0m"
@ -58,21 +24,30 @@
#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
namespace cpptrace {
namespace detail {
static const stacktrace_frame null_frame {
0,
0,
nullable<uint32_t>::null(),
nullable<uint32_t>::null(),
nullable<std::uint32_t>::null(),
nullable<std::uint32_t>::null(),
"",
"",
false
};
bool should_absorb_trace_exceptions();
bool should_resolve_inlined_calls();
enum cache_mode get_cache_mode();
}
}

Some files were not shown because too many files have changed in this diff Show More