Compare commits

...

756 Commits
v0.1.1 ... 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
Jeremy
ea566778fc
Bump to v0.6.2 2024-06-19 20:41:34 -05:00
Jeremy Rifkin
7a9ed920d5
Add unit tests to CI (#140) 2024-06-19 20:33:32 -05:00
Jeremy
ce075b056f
Make unit testing more robust 2024-06-19 17:11:24 -05:00
Jeremy
1f19c31e02
Fix bug with dladdr1 not being used, silly typo 2024-06-19 15:15:51 -05:00
Jeremy Rifkin
2fb0a6fd2e
Fix trace generation for cpptrace exception objects on armv7hf (#138)
noexcept on get_raw_trace_and_absorb seems to somehow interfere with
unwinding on armv7hf, possibly due to unwind tables not being generated
for the function. Fixes #134.
2024-06-13 11:17:44 -05:00
Jeremy
7543677d6f
Bump to v0.6.1 2024-06-11 22:36:32 -05:00
Jeremy
bb83fa922c
Merge branch 'dev' 2024-06-11 22:30:27 -05:00
Jeremy
4456623391
Fix dladdr1 and dl_find_object tests 2024-06-11 22:22:52 -05:00
Jeremy
8d7a85c447
Update flatten_inlines to properly move inline frames instead of copying them and also clarify a comment 2024-06-04 23:21:14 -05:00
Infko
72b0e467d7
fix compile error in gcc 4.8.5. (#133) 2024-06-04 23:16:34 -05:00
Jeremy
b8a6e8446a
Quick microfmt fix for an old msvc bug 2024-06-02 12:47:53 -05:00
Jeremy
44ddc1b72f
Merge branch 'main' into dev 2024-06-02 11:35:01 -05:00
Jeremy
59ef49f1a3
Update microfmt 2024-06-02 11:31:43 -05:00
Jeremy
06226ee2aa
Bump to 0.6.0 2024-05-28 22:23:48 -05:00
Jeremy
1ab65a2040
Remove /Wx from msvc outside of a werror build 2024-05-28 21:55:38 -05:00
Jeremy
70634cb6f4
Undo temporary change printing object addresses in stack traces 2024-05-28 21:44:13 -05:00
Jeremy
9d1c592f0c
Remove CPPTRACE_CONDA_LIBDWARF_WEIRDNESS bodge and better support libdwarf via pkgconfig, resolves #131 2024-05-28 21:32:54 -05:00
Jeremy
2499fa4a67
Add option to build tests with external gtest, closes #130 2024-05-28 20:56:54 -05:00
Jeremy Rifkin
b2bff57d81
Debug Fission (#132)
Implement support for debug fission, closes #129
2024-05-28 20:39:34 -05:00
Jeremy
5556aedddb
Refactor cu lookup out of pc resolution 2024-05-25 16:20:41 -05:00
Jeremy
8bff5dc9fe
Quick fix 2024-05-25 14:32:34 -05:00
Jeremy
810b74ecaa
Refactor symbols_with_libdwarf indo separate files 2024-05-25 14:29:55 -05:00
Jeremy
5171e87a51
Refactor logic in the libdwarf resolver's resolve_frames, pull out trace flattening 2024-05-25 13:38:06 -05:00
Jeremy
a9f72c6f67
Refactor logic in the libdwarf resolver's resolve_frames, pull out resolver management 2024-05-25 13:02:25 -05:00
Jeremy
dcd9a31ce4
Refactor collate_frames 2024-05-25 12:38:34 -05:00
Jeremy
583c0efdb3
Undo ee6787f0ed which pointed to a non-release libdwarf commit 2024-05-25 00:43:48 -05:00
Jeremy
dad3bd1843
Some comments regarding dlfcn logistics and comment cleanup 2024-05-25 00:40:08 -05:00
Jeremy
eb5417baae
Quick readme update 2024-05-25 00:36:12 -05:00
Jeremy
7916f10278
Attempt to fallback to dladdr if dladdr1 is not available 2024-05-24 23:04:20 -05:00
Jeremy
f8d28a6469
Fix incorrect handling of lazy_trace_holder's union, I can't believe I wrote such egregious code 2024-05-23 22:33:56 -05:00
Jeremy
6307700710
Fix ASSERT macro under -Wpedantic 2024-05-23 22:33:33 -05:00
Jeremy
470b0c8eb0
Only use -Wpedantic during a -Werror build 2024-05-23 22:28:02 -05:00
Jeremy
d288609b42
Fix for lazy_trace_holder's default constructor 2024-05-23 22:18:16 -05:00
Jeremy Rifkin
b3836e9318
Support -Wpedantic if someone sets the flag globally while using FetchContent (#127)
Closes #107
2024-05-23 21:18:11 -05:00
Jeremy
38f3d04784
Merge branch 'main' into dev 2024-05-21 23:36:58 -05:00
Jeremy
040d131cef
Update docs to make note of a relatively new libdwarf being needed, related to #124 2024-05-21 23:36:42 -05:00
Jeremy
32275cd679
Quick patch for when LIBUNWIND_LDFLAGS isn't set 2024-05-21 22:40:47 -05:00
Jeremy
7c9b24ca72
Fix dwarf_ranges handling of lowpc == pc, it's not correct to jump the gun and not check the high-pc. I think I erroneously added this in c94b03ae6e. Discovered as part of #123. 2024-05-19 17:00:44 -05:00
Jeremy
4884c4e99b
Fix frame pointer calculation for symbol frames under libunwind, #123 2024-05-19 16:36:07 -05:00
Jeremy
b2fe396d70
Fix address formatting, somehow it was left-aligned 2024-05-19 16:28:41 -05:00
Jeremy
10e7a5e442
Fix again 2024-05-15 20:00:53 -05:00
Jeremy
2172164478
Unbreak dev 2024-05-15 19:44:26 -05:00
Jeremy
2e1f14c7de
Add a warning flag 2024-05-12 22:02:34 -05:00
Jeremy
323ab5814e
Merge branch 'main' into dev 2024-05-12 22:00:45 -05:00
Ben Dunkin
a70cf7935a
Fix missing cxxabi.h include on Windows when using libc++ (#122)
Make operator precedence explicit so the
`CPPTRACE_HAS_CXX_EXCEPTION_TYPE` define is a dominant switch to use
cxxabi.h, rather than one that only applies when `__GLIBCXX__` is
defined.

Fixes https://github.com/jeremy-rifkin/cpptrace/issues/121
2024-05-12 21:56:38 -05:00
Jeremy
ee6787f0ed
Update dev to point at latest libdwarf dev commit with a pe heuristic fix 2024-05-10 12:06:56 -05:00
Jeremy
74ed6afc0a
Add cpptrace::system_error 2024-05-06 23:01:11 -05:00
Tamás Solymos
d2b940ab07
Fix typos in README.md (#117) 2024-05-06 14:37:32 -05:00
Jeremy
b1e3179a97
A couple quick fixes 2024-05-05 17:51:50 -05:00
Jeremy
da0aa4d5c0
Split a bunch of internal headers into .cpp/.hpp 2024-05-05 17:44:27 -05:00
Jeremy
9275f62fc5
Fix ci? 2024-05-05 15:30:24 -05:00
Jeremy
93b1d1c76a
Add basic unittest 2024-05-04 22:34:25 -05:00
Jeremy
f8ebdd26be
Small test refactor 2024-05-04 11:52:45 -05:00
Jeremy
ee572226f7
Update contributing instructions 2024-05-03 20:23:33 -05:00
Jeremy
4feeb6dfe2
Add godboltian makefile 2024-05-03 20:14:21 -05:00
Jeremy
d7c19a5544
Updates for conda 2024-05-03 20:07:09 -05:00
Jeremy
0db934caf7
Bump to 0.5.4 2024-05-02 11:30:54 -05:00
Jeremy
abbc77dcea
Clean up logic a bit 2024-05-02 11:17:41 -05:00
Jeremy
d3c7e7351c
Remove guard for resolve_l_name 2024-05-02 11:09:37 -05:00
Jeremy
ce19421bb5
Update for macos 2024-05-02 11:05:22 -05:00
Jeremy
b125248b32
Update dladdr path to not use dli_fname as it's unreliable in the case of a non-standard argv[0] 2024-05-02 10:47:31 -05:00
Jeremy
beb5506233
Add additional notes about CPPTRACE_STATIC_DEFINE 2024-04-30 23:18:30 -05:00
Jeremy
14d25fd89a
Bump to v0.5.3 and update changelog 2024-04-29 23:12:53 -05:00
Jeremy
24d1f7780f
Typo fix 2024-04-29 21:13:21 -05:00
Jeremy Rifkin
ed790ea82a
Improve zstd handling (#115)
This PR adds a Findzstd.cmake script to cpptrace's install so that
`find_dependency(zstd)` can succeed on installations that don't include
zstdConfig.cmake/zstd-config.cmake.

The reason `find_dependency(zstd)` is needed in cpptrace's config,
despite `find_dependency(zstd)` also being in libdwarf's cmake and
cpptrace not needing zstd, is that libdwarf's cmake doesn't define
`zstd::libzstd_static` / `zstd::libzstd_shared` targets.

This should fix #112.
2024-04-28 23:56:27 -05:00
Jeremy
db0738332e
Bump libdwarf 2024-04-28 19:58:43 -05:00
Jeremy
0527cea39f
For the libbacktrace back-end don't treat missing debug info in ELFs as a critical error; fixes #114 2024-04-28 19:51:52 -05:00
Jeremy
c871c2a43c
Grab new microfmt changes and fix #113 2024-04-27 01:29:39 -05:00
Jeremy
5a1f4b6d37
Add an error message for exec failures to the signal_demo, resolves #111 2024-04-18 19:38:19 -05:00
Jeremy
2526a38c16
README tweak: Add question mark to a title 2024-04-04 23:23:40 -05:00
Jeremy
531a8c13d9
Update README 2024-04-04 23:22:38 -05:00
Jeremy
599d6abd6c
Workaround bug for old msvc 2024-03-31 23:51:41 -05:00
Jeremy
0dd71cebb7
Mac fixes 2024-03-31 22:01:14 -05:00
Jeremy
06372b772f
Fix another warning 2024-03-31 16:42:51 -05:00
Jeremy
40696e8d02
Handle MSVC warning treated as error 2024-03-31 16:37:29 -05:00
Jeremy
c1b7be9f83
Bump to v0.5.2 2024-03-31 16:19:27 -05:00
Jeremy
ffb1b8ed68
A little more error handling cleanup 2024-03-31 16:02:39 -05:00
Jeremy
dc9cee98fb
Move error when returning 2024-03-31 15:52:45 -05:00
Jeremy
124607e7bf
Resolve some TODOs with error handling for mach-o stuff 2024-03-31 15:24:01 -05:00
Jeremy
b04fc09682
Update internal error messages 2024-03-31 14:18:21 -05:00
Jeremy
8007413ff6
Print Result(E) if not absorbing trace exceptions 2024-03-31 14:15:19 -05:00
Jeremy
4db73b1856
Fix assertion failure format string 2024-03-30 23:19:13 -05:00
Jeremy
50483783cb
Fix bug with non-recursive inline walking 2024-03-30 23:18:55 -05:00
Jeremy
ab63c34827
Replace most stream use with new formatting system 2024-03-30 20:25:06 -05:00
Jeremy Rifkin
acaa4f42e6
Add a Result type and replace some exceptions with it (#109) 2024-03-30 13:35:52 -05:00
Jeremy
47e7ee79eb
Formatting update 2024-03-30 11:11:07 -05:00
Jeremy
fa6155ff47
Remove awful stringf system 2024-03-29 23:12:26 -05:00
Jeremy
a3899781dc
Try to fix ci 2024-03-29 22:50:30 -05:00
Jeremy
e65edcf91a
Make note about -DCPPTRACE_STATIC_DEFINE more prominent 2024-03-28 19:51:53 -05:00
Jeremy
b72164b39b
Bump to v0.5.1 2024-03-20 22:12:26 -05:00
Jeremy
411fdff8fa
Merge branch 'dev' 2024-03-20 22:08:33 -05:00
Jeremy
d1199dc325
Fix potential null dereference issue, thanks @eyalgolan1337. Resolves #106. 2024-03-20 22:08:21 -05:00
Jeremy
8f8e1e34e2
Re-add CE button on readme 2024-03-19 22:59:50 -05:00
Billy O'Neal
37e6cef4f9
Defend against min and max macros from windows.h (#105)
Alternative to
https://github.com/microsoft/vcpkg/pull/37512/files#diff-9f533b43a5faabaa6b5a0e046f0ae425cd85736808604dd61dc9a955db3d060aR9

I left the examples in mach-o.hpp as they are guarded by #if IS_APPLE
2024-03-18 23:03:26 -05:00
Jeremy
2985cb1d6c
Merge branch 'main' into dev 2024-03-16 23:31:57 -05:00
Jeremy
fdf4499259
Update docs to showcase source code snippets 2024-03-16 23:31:07 -05:00
Jeremy
c5138e2f40
Add nominmax for windows.h 2024-03-16 19:34:16 -05:00
Jeremy
a841a1b74a
Fix msvc warning treated as error 2024-03-16 19:23:03 -05:00
Jeremy
9b69d200c1
Bump to v0.5.0 2024-03-16 18:03:34 -05:00
Jeremy
8407adf6d3
Add print_with_snippets to docs 2024-03-16 17:52:38 -05:00
Jeremy
a528aa8e0b
Fix object address resolution with _dl_find_object, #104 2024-03-16 16:17:27 -05:00
Jeremy
92be6c23f1
Update note about debug symbols being needed 2024-03-15 17:39:23 -05:00
Jeremy
6a8ff75fc1
Merge branch 'main' into dev 2024-03-09 22:26:17 -06:00
Jeremy
c35392d20b
Fix comment 2024-03-09 22:25:51 -06:00
Jeremy
754c588464
Disable CE button temporarily 2024-03-09 22:25:04 -06:00
Jeremy
3e0689a5e6
Resolve object address for dbghelp frames by default, handles #100 2024-03-09 22:15:41 -06:00
Jeremy
8d6326da07
Update locking in dbghelp_syminit_manager 2024-03-08 17:39:08 -06:00
Jeremy
c896d70f79
Small reorganization 2024-03-05 17:47:55 -06:00
Adrien Cassagne
d8e23c1e93
Add 'POSITION_INDEPENDENT_CODE ON' (also known as '-fPIE'). (#99)
First thx for these very useful lib!

I encountered some issues when linking with `libcpptrace`. Typically,
here is the error I got:
```
/usr/bin/ld: lib/cpptrace/lib/libcpptrace.a(cpptrace.cpp.o): relocation R_X86_64_32S against symbol `_ZTVNSt7__cxx1115basic_stringbufIcSt11char_traitsIcESaIcEEE@@GLIBCXX_3.4.21' can not be used when making a PIE object; recompile with -fPIE
```
I just added the `POSITION_INDEPENDENT_CODE ON` flag on the targets, and
now it works like a charm :-).
2024-03-05 08:57:04 -06:00
Jeremy
3054b3bebd
Add tmp to gitignore 2024-03-03 14:37:14 -06:00
Jeremy
da739d30c5
Add can_signal_safe_unwind and update some documentation surrounding signal-safe stack tracing 2024-03-03 12:15:27 -06:00
Jeremy
ec264aa0eb
Cleanup and improve snippet code a bit 2024-03-03 11:49:15 -06:00
Jeremy
862b546532
Update margin handling 2024-03-03 11:34:09 -06:00
Jeremy
9da2b3f556
Refactor object.hpp, separate out module base logic which greatly cleans things up 2024-03-03 11:31:06 -06:00
Jeremy
f11e119d32
Refactor stacktrace conversion code 2024-03-03 10:54:23 -06:00
Jeremy
389f788b57
Add get_object_info method to stacktrace frames, adding onto the previous work for #97 2024-03-03 10:54:03 -06:00
Jeremy
123e7df4f4
Mention CPPTRACE_STATIC_DEFINE for non-cmake instructions 2024-02-28 23:32:48 -06:00
Jeremy
42a91af570
Remove some completed features from the todo list 2024-02-28 23:30:45 -06:00
Jeremy Rifkin
d99f1745d2
Basic source code snippet system (#98) 2024-02-28 23:29:24 -06:00
Jeremy
736643358b
Implement a non-recursive get_inlines_info 2024-02-28 22:30:47 -06:00
Jeremy
055e0d94cf
Use ccache if possible, at top-level 2024-02-28 22:09:54 -06:00
Jeremy
0fda4a88ed
Refactor stack trace frame printing 2024-02-28 20:53:28 -06:00
Jeremy
58992cbeb6
Bump to 0.4.1 2024-02-27 22:53:59 -06:00
Jeremy
a700b7f7ef
Update documentation for stacktrace_frames 2024-02-27 22:53:16 -06:00
Jeremy
c6a60f7172
Stratify test ci 2024-02-27 20:10:35 -06:00
Jeremy
76aff1c30e
Revert "Try something fancy"
This reverts commit df8b8eb4c8.
2024-02-27 19:55:42 -06:00
Jeremy
df8b8eb4c8
Try something fancy 2024-02-27 19:54:06 -06:00
Jeremy
6e01f7225d
Try to rework ci to test first the default configuration, then test all configs after that passes. Also refactor the build in all tests script. 2024-02-27 19:51:52 -06:00
Jeremy
e0b50c96b4
Refactor out prerequisite setup to scripts in ci/ 2024-02-27 19:41:34 -06:00
Jeremy
a31d35c04e
Fixes 2024-02-27 19:12:55 -06:00
Jeremy
a24c140baf
Add object address resolution for libdl backend 2024-02-27 19:00:08 -06:00
Jeremy
d17fe33abf
Try to fix build errors 2024-02-27 00:39:58 -06:00
Jeremy
3c7a677689
Add an object address to stacktrace_frame entries, addresses #97 (no pun intended) 2024-02-27 00:34:45 -06:00
Jeremy
d5b2646283
Merge branch 'main' into dev 2024-02-27 00:23:10 -06:00
Jeremy
fc770a764e
Small readme fix 2024-02-27 00:22:50 -06:00
Jeremy
b7d14bc952
Only deal with zstd if not using CPPTRACE_USE_EXTERNAL_LIBDWARF 2024-02-19 16:46:48 -06:00
Jeremy
a162423800
Update conan config to not use nested dwarf header path 2024-02-19 10:39:07 -06:00
Bruce Mitchener
f879cd8b7b
README: improve text about macOS and dsymutil (#93)
According to the release notes, I think this is true ... if not,
disregard!
2024-02-19 02:11:33 -06:00
Bruce Mitchener
a144002bf0
Tweaks from clang-tidy (#92)
This is (perhaps) mainly for discussion as I saw you removed
`clang-tidy` checks last year. These fix a variety of minor things.

With one of the options that I was using, these still remain:

```
/Users/bruce/Development/custodian/cpptrace/src/symbols/../utils/utils.hpp:235:22: warning: noexcept specifier on the move constructor evaluates to 'false' [performance-noexcept-move-constructor]
  235 |             noexcept(std::is_nothrow_move_constructible<T>::value)
      |                      ^
/Users/bruce/Development/custodian/cpptrace/src/symbols/../utils/utils.hpp:250:64: warning: noexcept specifier on the move assignment operator evaluates to 'false' [performance-noexcept-move-constructor]
  250 |             noexcept(std::is_nothrow_move_assignable<T>::value && std::is_nothrow_move_constructible<T>::value)
      |                                                                ^
```
2024-02-18 11:21:48 -06:00
Bruce Mitchener
1488460172
Use defined(CPPTRACE_HAS_CXX_EXCEPTION_TYPE). (#94)
This is set by the build system when it is available and not set when
not available (and not set to 1/0 respectively).

The current code can generate a warning when this preprocessor
definition is not defined.
2024-02-18 11:16:04 -06:00
Bruce Mitchener
9296b892ac
ci: Update ilammy/msvc-dev-cmd to 1.13 from 1.10. (#91)
This should remove most of the warnings about using older versions of
NodeJS.
2024-02-18 10:48:12 -06:00
Bruce Mitchener
aa446b0540
Fix typos. (#90) 2024-02-18 00:10:21 -06:00
ethanol20924
c3b38381ab
Small fix to allow for compiling with Homebrew installed Clang on Mac (#89)
When building with Clang installed with Homebrew:

```
-- The C compiler identification is Clang 17.0.6
-- The CXX compiler identification is Clang 17.0.6
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /opt/homebrew/opt/llvm/bin/clang - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /opt/homebrew/opt/llvm/bin/clang++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
```

I get the following error:

```
/opt/homebrew/opt/llvm/bin/llvm-ranlib: error: Invalid option: '-no_warning_for_no_symbols'
make[2]: *** [_deps/zstd-build/lib/libzstd.a] Error 1
make[2]: *** Deleting file `_deps/zstd-build/lib/libzstd.a'
make[1]: *** [_deps/zstd-build/lib/CMakeFiles/libzstd_static.dir/all] Error 2
make: *** [all] Error 2
```

If I instead build with AppleClang:

```
-- The C compiler identification is AppleClang 15.0.0.15000100
-- The CXX compiler identification is AppleClang 15.0.0.15000100
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
```

It works as expected.

Source of the problem is found at `CMakeLists.txt:282`:

```CMake
if(APPLE)
  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()
```

Changing it to check specifically for AppleClang appears to fix the
problem for me:

```CMake
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()
```
2024-02-16 11:28:38 -06:00
Jeremy
d52b80301a
Make sure to at least show object frame info even if resolution fails for libdwarf, resolves #87
Co-authored-by: eyalgolan1337
2024-02-15 19:40:11 -06:00
Leandro SQ
8d2728d0f0
fix: Broken link on README.md (#88)
This pull request includes a minor change to the `README.md` file. The
change updates the link to the `signal-safe-tracing.md` file, moving it
into the `docs` directory.
2024-02-15 12:36:28 -06:00
Jeremy
3cd8e92e66
Handle copying libcpptrace.dll better for findpackage integration 2024-02-12 22:14:03 -06:00
Jeremy
ddf155b122
Add note about -lzstd to docs 2024-02-11 22:35:41 -06:00
Jeremy
22b326ba7e
Small optimization, doesn't really matter but doesn't hurt 2024-02-11 22:22:29 -06:00
Jeremy
e6627b760d
Implement better logic for handling scrfile indices, related to #86 2024-02-11 21:02:48 -06:00
Jeremy
cb92c9fdfa
Small optimization to reduce unnecessary frame copying 2024-02-11 15:59:56 -06:00
Jeremy
36a16df075
Handle an edge case with dwarf file indices, identified in #86 2024-02-11 15:25:18 -06:00
Jeremy
7fda402638
Bump to 0.4.0 2024-02-11 12:41:21 -06:00
Jeremy
203dbb524e
Merge branch 'dev' into main 2024-02-11 12:09:13 -06:00
Jeremy
bc662438f3
Small cmake adjustments 2024-02-11 11:53:33 -06:00
Jeremy
c771c4d3f5
Update #include for ctrace docs 2024-02-11 11:44:31 -06:00
Jeremy
e166c9804d
Update standalone instructions 2024-02-11 11:43:32 -06:00
Jeremy
90802c6be1
deal with a warning under testing build that's annoyed me for a while 2024-02-11 02:11:12 -06:00
Jeremy
58d09dd010
Remove CPPTRACE_NO_EXPORT_ATTR / CPPTRACE_DEPRECATED_ATTR as they aren't used 2024-02-11 02:05:21 -06:00
Jeremy
9a7c45bda7
remove return that wasn't needed 2024-02-11 02:05:00 -06:00
Jeremy
40bc2992b2
re-add something that's useful during testing 2024-02-11 02:00:53 -06:00
Jeremy
cdaab64be2
Revert "Printbug"
This reverts commit 2e9c586ba7.
2024-02-11 01:58:29 -06:00
Jeremy
612d5e6d65
Fix architecture handling in load_mach 2024-02-11 01:53:57 -06:00
Jeremy
2e9c586ba7
Printbug 2024-02-11 01:49:46 -06:00
Jeremy
37630f479d
Try to make fat mach architecture handling more robust 2024-02-08 00:38:56 -06:00
Jeremy
59272bccd6
printbug... 2024-02-07 22:37:55 -06:00
Jeremy
41f37a3446
Run on the new macos 14 runners with a newer default xcode to try to workaround a linker internal error 2024-02-07 22:24:48 -06:00
Jeremy
df6e02087e
Update for vcpkg 2024-02-07 22:08:45 -06:00
Jeremy
60dd6b5065
Fix issues related to export directives 2024-02-07 21:40:52 -06:00
Jeremy
adefa5c234
Bump libdwarf hashes and use the lightweight mirror 2024-02-07 21:34:41 -06:00
Jeremy
683c046f87
Forgot to commit readme update 2024-02-07 21:26:03 -06:00
Jeremy
68f919f292
Add ctrace documentation 2024-02-07 00:11:48 -06:00
Jeremy
78dd053f21
Light ctrace refactoring 2024-02-06 23:10:02 -06:00
Jeremy
ead847ad70
Fix main invocation 2024-02-06 09:08:22 -06:00
Jeremy
8b8bd1b5df
Test findpackage for msvc too 2024-02-06 00:00:45 -06:00
Jeremy
f972c43420
Copy the dll 2024-02-05 23:51:02 -06:00
Jeremy
6525b125ef
Two small fixes 2024-02-05 23:40:08 -06:00
Jeremy
74ed63f3ff
Fix some funny business 2024-02-05 23:37:33 -06:00
Jeremy
7c9b3ed635
Try to test mingw findpackage integration 2024-02-05 23:35:06 -06:00
Jeremy
f13e2a0d7b
Test cmake integration with shared too 2024-02-05 00:03:22 -06:00
Jeremy
5232bb04fd
Use CPPTRACE_USE_EXTERNAL_ZSTD in CI 2024-02-04 14:42:05 -06:00
Jeremy
5e65ccecc0
Add external zstd option 2024-02-04 14:35:49 -06:00
Jeremy
eea0fcd118
Update table of contents 2024-02-04 14:20:04 -06:00
Jeremy
46069760c6
Fix a readme editing error, somehow I cropped a paragraph 2024-02-04 14:18:13 -06:00
Jeremy
8f9e8c5c1b
Some formatting consistency tweaks 2024-02-04 13:58:57 -06:00
Jeremy
a3e3916daa
Fix issue with trying to call detail::enable_virtual_terminal_processing_if_needed unconditionally on print 2024-02-04 13:56:46 -06:00
Jeremy
ab2d440a00
Update cpptrace exceptions to defer trace generation to the callsite with a default argument 2024-02-04 13:40:12 -06:00
Jeremy
2f7f5107a5
Add ctrace interface for enable_inlined_call_resolution 2024-02-04 11:10:37 -06:00
Jeremy
36d1dbf9e6
Add ctrace export annotations 2024-02-04 11:08:18 -06:00
Jeremy
d7aac52f8b
Add configuration to control resolution of inlined calls 2024-02-04 11:03:03 -06:00
Jeremy
589d87063a
Small header refactor 2024-02-04 10:48:55 -06:00
Jeremy
c161293e89
Update for zstd target handling again 2024-02-04 00:17:25 -06:00
Jeremy
ab9a832a30
Try to get some updated libdwarf/zstd handling 2024-02-04 00:00:12 -06:00
Jeremy Rifkin
a65aed2752
Aquire zstd with fetchcontent for libdwarf 9 (#85) 2024-02-01 00:14:48 -06:00
Jeremy
6b87927acf
No longer try to install build/include 2024-01-29 23:31:40 -06:00
Jeremy
5fde4081dc
Replace the cmake generated export header 2024-01-29 23:26:18 -06:00
Jeremy
b1ff59b59c
Update ctrace demo to be a .c 2024-01-29 23:18:21 -06:00
Jeremy
b8c66cd855
Remove old comment 2024-01-29 22:29:35 -06:00
Jeremy
58837d7069
Fix cpptrace-config-cmake issue 2024-01-29 22:18:15 -06:00
Jeremy Rifkin
79931c8823
Parsing of mach-o symbol tables, generation of debug maps, and resolution through object files (#82) 2024-01-29 22:12:59 -06:00
Jeremy
ea30c99f35
Fix formatting of a table 2024-01-23 01:10:00 -06:00
Jeremy
ac89001bad
Update copyright 2024-01-23 01:09:46 -06:00
eightfold
218957dfb0
New C api (#80)
Updated C API following the requested scheme. May implement "exceptions"
later...

Closes #38

---------

Co-authored-by: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com>
2024-01-23 00:16:20 -06:00
Jeremy
448c325d2e
Make -fdiagnostics-color/-fcolor-diagnostics logic more robust, pulling in changes from dev 2024-01-20 09:05:57 -06:00
Jeremy
cfdd311e15
Fix warning for gcc on windows 2024-01-14 23:51:41 -06:00
Jeremy
3d74da8df1
Turn on -Werror for CI pipelines 2024-01-14 23:42:15 -06:00
Jeremy Rifkin
a654f2082e
Mach-o refactoring (#77)
This is the first step towards a more comprehensive mach-o system. Next
step will be to add support for symbol table parsing.
2024-01-14 23:36:30 -06:00
Jeremy
7f6e91e0ff
Update to installation and usage instructions 2024-01-14 22:28:01 -06:00
Jeremy
e8c857d6c2
Small tweak for a couple aggregate initializations 2024-01-14 22:27:25 -06:00
Jeremy
f4faf00a8b
Add some more instructions for using the library 2024-01-14 20:44:07 -06:00
Jeremy
2100723312
Another attempt to resolve libdwarf header issues 2024-01-14 19:56:11 -06:00
Jeremy
3e46d2a570
Fix typo 2024-01-14 18:05:31 -06:00
Jeremy
399e52f460
Try again to fix libdwarf #includes 2024-01-14 15:23:02 -06:00
Jeremy
5d64a98d08
Handle libdwarf's sporadic header placement better 2024-01-14 03:11:33 -06:00
Jeremy
4743e95988
Merge branch 'main' into dev 2024-01-14 02:53:59 -06:00
Jeremy
7720df748a
Updates to urls and tags for libdwarf 2024-01-14 02:53:35 -06:00
hades
965505e465
fix locating libunwind without pkgconfig (#76)
The call to find_path used an incorrect variable name, that wasn't
included in the target_include_directories call.
2024-01-13 13:18:25 -06:00
Jeremy
ca76080968
Updates to better support external libdwarf, because nothing about how libdwarf's cmake is setup is consistent 2024-01-13 01:31:10 -06:00
Jeremy
4361e7c3df
Merge branch 'main' into dev 2024-01-13 01:10:29 -06:00
Jeremy
a3dc33d8ca
Clean up and reorganize the README 2024-01-12 01:26:50 -06:00
Bruce Mitchener
9dad2463b7
ci: Update to actions/checkout@v4 from v2 and v3. (#72)
This uses deprecated versions of Node within GitHub Actions.
2024-01-01 12:08:32 -06:00
Jeremy
e8fd01bbe1
Fallback to the cu cache or walking cu's if aranges lookup fails 2023-12-06 00:21:44 -05:00
Jeremy
aa5315769e
Handle color arguments better if C and C++ compiler families differ 2023-12-06 00:16:24 -05:00
Jeremy
71366555b0
Merge branch 'main' into dev 2023-12-05 23:01:01 -05:00
Jeremy
5745399120
Small wording update 2023-12-05 11:09:10 -05:00
Jeremy
1f7c14ebb6
Update target names, pointed out in #66 2023-12-05 10:24:02 -05:00
Jeremy
56b50d279a
Update msvc bug workaround to not produce build warnings 2023-12-04 11:30:37 -05:00
Jeremy
e1f91f1877
Update changelog 2023-12-03 21:34:50 -05:00
Jeremy
6db9794dc6
Workaround a msvc bug affecting msvc 19.38 2023-12-03 21:33:31 -05:00
Jeremy
94470259c2
Merge branch 'main' into dev 2023-12-03 13:29:19 -05:00
Jeremy
ab389c36bd
Bump to 0.3.1 2023-12-03 13:27:58 -05:00
Jeremy
8a0a1234be
Merge branch 'dev' into main 2023-12-03 13:27:38 -05:00
Jeremy
07dea09dfc
Add CPPTRACE_EXPORT tags to exception classes 2023-12-03 13:14:35 -05:00
Jeremy
d8ebfef347
Merge branch 'main' into dev 2023-11-26 14:24:22 -05:00
Jeremy
3ed0bd287d
Update try on CE button for 0.3.0 2023-11-26 14:24:04 -05:00
Jeremy
b3b9d5fda6
Remove CPPTRACE_EXPORT from nullable 2023-11-23 00:06:09 -06:00
Jeremy
d18809f807
Add inlining info demo to README 2023-11-22 22:33:09 -06:00
Jeremy
4a37069655
Merge branch 'main' into dev 2023-11-22 20:01:27 -06:00
Jeremy
4ec11d00de
Bump versions to 0.3.0 2023-11-22 19:54:18 -06:00
Jeremy
604e476151
Merge branch 'dev' 2023-11-22 01:02:57 -06:00
Jeremy
299b2d942c
Update changelog 2023-11-22 01:00:54 -06:00
Jeremy
594dd720b4
Update setup for finding and linking libunwind stuff 2023-11-21 23:22:15 -06:00
Jeremy
e7aae33cec
A couple fixes 2023-11-21 22:56:11 -06:00
Jeremy
728ec2cb40
Add another link for vcpkg 2023-11-21 22:44:13 -06:00
Jeremy
bb4550e582
Update a #include in cpptrace.hpp 2023-11-21 22:39:12 -06:00
Jeremy
27c8878fa9
Fix 2023-11-21 21:57:40 -06:00
Jeremy
ae2a85c3f0
Suppress a msvc warning 2023-11-21 21:47:13 -06:00
Jeremy
3a0db500d0
Fixes 2023-11-21 21:32:42 -06:00
Jeremy
a106bc4680
Use libdwarf::libdwarf on conan 2023-11-21 15:17:38 -06:00
Jeremy
64a60f227d
Resolve a couple compilation warnings 2023-11-21 12:11:16 -06:00
Jeremy
92c3b63fc0
Install headers to the normal place for now 2023-11-21 12:08:53 -06:00
Jeremy
cbf8b87644
Add shared to test matrix 2023-11-21 11:02:48 -06:00
Jeremy
f59747bad6
Fix 2023-11-21 10:53:22 -06:00
Jeremy
e4c498e844
Adding shared testing 2023-11-21 10:51:00 -06:00
Jeremy
fcef25276e
Remove some old commented out stuff 2023-11-21 10:50:21 -06:00
Jeremy
c80abed1cd
Try to re-enable testing with _Unwind on windows 2023-11-21 10:21:09 -06:00
Jeremy
e889fa7acf
Use dlfo_link_map->l_addr over dlfo_map_start 2023-11-21 09:41:56 -06:00
Jeremy
0ee29d0855
Use CPPTRACE_PATH_MAX more consistently, and another small refactor 2023-11-20 23:37:49 -06:00
Jeremy
87cd24438b
Use _dl_find_object over dladdr when possible in object resolution, it's so much faster 2023-11-20 23:36:55 -06:00
Jeremy
9113cc5ffc
Some refactoring, bring object_frame in line with safe_object_frame. Also renamed address_relative_to_object_base_in_memory. 2023-11-20 23:13:21 -06:00
Jeremy
2a4a8066d3
Rework the object trace interface a bit and clarify their purpose. Also rename minimal_object_trace to safe_object_trace 2023-11-20 23:01:19 -06:00
Jeremy
a04f19a484
Explain the purpose of raw trace information vs minimal object frame info 2023-11-20 22:49:07 -06:00
Jeremy
a6a64b5671
Add shared library warmup 2023-11-20 22:41:24 -06:00
Jeremy
7c49e64ba6
Updated documentation, added documentation for new stuff, and updated cpptrace.hpp a bit 2023-11-20 22:14:13 -06:00
Jeremy
cd59ab5478
Added signal tracing demo 2023-11-19 23:46:49 -06:00
Jeremy
6c6d915414
Removed raw_trace::from_buffer, not happy with that api 2023-11-19 23:45:51 -06:00
Jeremy
4a9b24b31b
Only build signal tracer on unix 2023-11-19 23:27:27 -06:00
Jeremy
0b48df7f22
Fix windows build warning 2023-11-19 23:20:31 -06:00
Jeremy
24e64ab385
Check for _dl_find_object support 2023-11-19 23:17:11 -06:00
Jeremy
ac7db48cef
Really fix it this time 2023-11-19 23:07:41 -06:00
Jeremy
f4237c75df
Try to fix build 2023-11-19 22:54:08 -06:00
Jeremy
16ec7c5855
Fix oversight, demangle in object_trace resolution 2023-11-19 22:52:43 -06:00
Jeremy
9647a6d591
Forgot to stage path max 2023-11-19 22:52:20 -06:00
Jeremy
ac13e71877
Implement signal-safe resolution of basic object information 2023-11-19 22:49:58 -06:00
Jeremy
327a6e8318
Fix two more includes 2023-11-18 23:42:49 -06:00
Jeremy
88319b13a7
Turns out one of the includes was needed 2023-11-18 23:39:59 -06:00
Jeremy
6da8ee0773
Fix a couple includes 2023-11-18 23:38:03 -06:00
Jeremy
5657a07ed4
Remove some no longer needed includes 2023-11-18 23:35:43 -06:00
Jeremy
0e462c7b03
Some restructuring of the directory layout 2023-11-18 23:28:48 -06:00
Jeremy
77a2e3a22b
Rework the exception interface 2023-11-18 09:00:57 -06:00
Jeremy
0de366f7d6
Add column support for libdwarf 2023-11-15 22:50:10 -05:00
Jeremy
140166a4a7
Updated nested exception what message 2023-11-15 21:50:10 -05:00
Jeremy
a518cd8874
Walk subprogram children to handle lambdas better and also cast better 2023-11-15 21:30:11 -05:00
Jeremy
ab7e71f1b2
Add exception wrapping utilities, will help issues like #60 2023-11-15 21:24:26 -05:00
Jeremy
61d536bc02
Put noinline macro in header 2023-11-15 15:22:59 -05:00
Jeremy
520962162c
Improvement for trace printing with missing symbols or filenames 2023-11-15 15:10:22 -05:00
Jeremy
79bc580519
Handle multiple symbol back-ends better 2023-11-15 15:09:08 -05:00
Jeremy
fec5324502
Fix 2023-11-15 12:50:37 -05:00
Jeremy
f6468b7a01
Update _Unwind to not rely on a hard max frames 2023-11-15 12:48:01 -05:00
Jeremy
15572b029d
Bump hard max frames from 100 to 200 2023-11-15 12:47:36 -05:00
Jeremy
0084de0f05
Some tweaks and fixes 2023-11-15 12:17:18 -05:00
Jeremy
49c86921ce
Update test case expected outputs 2023-11-15 12:16:43 -05:00
Jeremy
feef9a3265
Fix 2023-11-15 11:55:59 -05:00
Jeremy
aed456bc63
Handle null lines / columns better 2023-11-15 11:52:24 -05:00
Jeremy
37a3ea7978
Adjust indentation 2023-11-15 00:36:02 -05:00
Jeremy
bb487d3e56
Conditionally find_dependency(libdwarf) 2023-11-15 00:34:45 -05:00
Jeremy
c6ea891629
Address -Wmissing-field-initializers warnings 2023-11-14 22:58:10 -05:00
Jeremy
81eddcce3a
Try to bump to windows 2022 runner 2023-11-14 18:56:18 -05:00
Jeremy
8297d234f6
Use an optimized mirror for libdwarf 2023-11-13 11:48:15 -05:00
Jeremy
fd5dc92a2c
Forgot to update CI/docs 2023-11-13 10:50:53 -05:00
Jeremy
b28cb54ced
Rename demo/test options to CPPTRACE_BUILD_TESTING 2023-11-13 10:44:50 -05:00
Jeremy
96a55fc686
Fix windows build, and prevent future warnings 2023-11-12 23:42:26 -05:00
Jeremy
e80afd460b
Add safe tracing interface 2023-11-12 23:31:41 -05:00
Jeremy Rifkin
44ba826f67
Try to split up CI a bit more so it runs faster (#63) 2023-11-12 17:45:25 -05:00
Jeremy Rifkin
5541ec5519
Add libunwind back-end (#62) 2023-11-12 17:15:59 -05:00
Jeremy
259d596f76
Re-enable warnings 2023-11-08 23:59:48 -05:00
Jeremy
4c1c42c61d
Add frame_ptr alias 2023-11-08 21:32:34 -05:00
Jeremy
7929d239bd
Create a cache for CU srcfiles 2023-11-08 20:47:29 -05:00
Jeremy
336a4ac7bb
Add CONTRIBUTING.md 2023-11-08 20:15:56 -05:00
Jeremy
2f8e1a5ff7
More work to get inline resolution going 2023-11-08 19:46:31 -05:00
Jeremy
1ced7c8dc3
Minor tweaks 2023-11-08 19:41:13 -05:00
Jeremy
6fbedd0ed6
Commend out prints 2023-11-08 14:59:53 -05:00
Jeremy
5bf66156cd
Minor fixes 2023-11-08 14:47:15 -05:00
Jeremy
7ffec7b3c0
Inital work to walk inline tree and show inlined calls 2023-11-08 13:05:39 -05:00
Jeremy
f4cf8c198b
Update README 2023-11-08 13:03:05 -05:00
Jeremy
f21874cbf7
Use CMAKE_DL_LIBS 2023-11-07 10:44:42 -05:00
Jeremy
93de7b7060
spacing fix 2023-11-07 00:05:50 -05:00
Jeremy
6d62936c7b
cache path and line number 2023-11-07 00:05:32 -05:00
Jeremy
9096531e23
Either LUT or walk for line info based on cache mode 2023-11-06 23:20:11 -05:00
Jeremy
fef039ba26
Setup a lookup table for line info 2023-11-06 22:38:21 -05:00
Jeremy
f4a71c2d23
Some small cmake fixes 2023-11-06 22:37:47 -05:00
Jeremy
0536669dc0
Some readme fixes 2023-11-06 22:37:28 -05:00
Jeremy
516b0f44ad
Update readme 2023-11-06 17:56:34 -05:00
Jeremy
01ac14ba92
Try another way to set no_warning_for_no_symbols 2023-11-06 17:18:57 -05:00
Jeremy
cd9d29bf2b
Suppress ranlib "has no symbols" warnings 2023-11-06 17:14:54 -05:00
Jeremy
3489ed1f72
Fix 2023-11-06 16:51:18 -05:00
Jeremy
d0bc2526e7
try again 2023-11-06 15:57:26 -05:00
Jeremy
046b23f3a1
Try again 2023-11-06 15:54:57 -05:00
Jeremy
faea88265d
Bump 2023-11-06 15:44:19 -05:00
Jeremy
781c30f63f
Fix build 2023-11-06 15:16:33 -05:00
Jeremy
9d617d8c3c
Fix 2023-11-06 15:08:20 -05:00
Jeremy
c0aebe002e
Fix 2023-11-06 15:05:38 -05:00
Jeremy
a623c40f3b
Initial work to setup external libdwarf for CI 2023-11-06 14:58:38 -05:00
Jeremy Rifkin
e717930f5d
Unbundle libdwarf (#58)
Pending #57
2023-11-06 13:04:58 -05:00
Jeremy
eb35a6a7ba
Fix test 2023-11-06 12:42:06 -05:00
Jeremy
372271c837
Print tag 2023-11-06 12:27:54 -05:00
Jeremy Rifkin
98368fb417
CMake improvements (#57) 2023-11-06 12:12:07 -05:00
Jeremy
e14eac364f
Merge branch 'main' into dev 2023-11-04 17:48:17 -04:00
Jeremy
b29ccb7bc3
Add vcpkg instructions 2023-11-01 18:14:27 -04:00
Jeremy
d06d8b6122
Add package manager info 2023-11-01 12:02:14 -04:00
Jeremy
e7c2e9c8fc
Add package manager info 2023-11-01 12:01:07 -04:00
Jeremy
ad4f9c4060
Make CPPTRACE_USE_SYSTEM_LIBDWARF non-advanced 2023-11-01 11:54:25 -04:00
Jeremy
a01ac3705d
Add bindir change back in 2023-10-09 01:21:07 -04:00
Jeremy
a0779beec9
Add try on CE button 2023-10-09 01:19:53 -04:00
Jeremy
652c204b94
Add try on CE button 2023-10-09 01:13:44 -04:00
Jeremy
ae5d2392fe
More std:: 2023-10-08 22:48:12 -04:00
Jeremy
e5ed083996
Bump libdwarf and update pull script 2023-10-08 22:18:10 -04:00
Jeremy
41ab5fdb7e
Add bindir change back in 2023-10-08 22:06:43 -04:00
Jeremy
5bbadbf5b2
Revert "Try bindir, might revert"
This reverts commit ff8f761a2b.
2023-10-08 22:03:14 -04:00
Jeremy
ff8f761a2b
Try bindir, might revert 2023-10-08 14:58:05 -04:00
Jeremy
ae484f0ed2
Improve terminate handler behavior when there's no active exception and also try to print a trace for non-cpptrace terminates 2023-10-08 14:55:21 -04:00
Jeremy
f6f64954de
Sonar suggestion 2023-10-07 17:43:39 -04:00
Jeremy
1d514c1532
Windows fix 2023-10-07 17:42:11 -04:00
Jeremy
05c3f7160b
Add specialized terminate handler and add error handling info to the README. Also updated demo. 2023-10-07 17:34:49 -04:00
Jeremy
2e0fa880bc
Small fixes 2023-10-07 15:05:50 -04:00
Jeremy
8bb8a2020c
Add some utilities for exception handling and detecting whether stderr is a terminal 2023-10-07 14:59:04 -04:00
Jeremy
df6c22f640
Sonar fix 2023-10-07 14:04:29 -04:00
Jeremy
4d2580185f
Sonar fixes 2023-10-07 13:59:57 -04:00
Jeremy
a422e132d4
Comments and sonar fixes 2023-10-07 13:43:39 -04:00
Jeremy
18da699aed
Run sonar on dev 2023-10-06 20:51:22 -04:00
Jeremy Rifkin
b26c5e4ec8
Refactor cpptrace exception objects and simplify interface (#54) 2023-10-06 20:25:30 -04:00
Jeremy
bda3e2b169
Fix for msvc 2023-10-06 01:23:46 -04:00
Jeremy
23b0b57d08
Fix the discord readme badge 2023-10-06 01:19:05 -04:00
Jeremy
3143181612
Bump version and make changelog notes 2023-10-06 01:03:12 -04:00
Jeremy
2b1a2c7a19
Fix useless cast warning 2023-10-06 00:53:40 -04:00
Jeremy
2f507cc24d
Fix msvc build 2023-10-05 12:55:44 -04:00
Jeremy
0c03ac7a01
Refactor stacktrace::print 2023-10-05 12:41:52 -04:00
Jeremy
c3a27d15fb
Update trace objects to just be aggregates 2023-10-05 12:33:43 -04:00
Jeremy
5079aef62f
Try to lint include/ as well 2023-10-05 12:21:51 -04:00
Jeremy
a9964f3a5e
Merge branch 'jr/sonar-fixes' into main 2023-10-05 12:11:30 -04:00
Jeremy
53ceb99b46
Sonar fixes: More nullptr literal use, take std::fuction by const&, and [[noreturn]] 2023-10-05 12:08:47 -04:00
Jeremy Rifkin
ddad92b1f8
Sonar fixes (#52) 2023-10-05 11:17:39 -04:00
Jeremy
af181bf981
Disabled on sonar 2023-10-05 11:16:55 -04:00
Jeremy
1b9e5916f6
Some fixes 2023-10-05 11:07:46 -04:00
Jeremy
12b80f7af1
Some fixes 2023-10-05 11:01:03 -04:00
Jeremy
3b5064f1ff
Fix forwarding issue 2023-10-05 10:58:03 -04:00
Jeremy Rifkin
75677dda70
Add sonarlint workflow (#51) 2023-10-05 09:57:53 -04:00
Jeremy
ab630c052b
Update has_stackwalk 2023-10-05 09:55:07 -04:00
Jeremy
f38b693e74
Update zlib handling again 2023-10-04 22:26:07 -04:00
Jeremy
e529aaa94f
Update zlib checks 2023-10-04 22:11:02 -04:00
Jeremy
7f787167be
Update zlib findpackage 2023-10-04 22:08:24 -04:00
Jeremy
58130d7ed2
Remove old check 2023-10-04 20:15:15 -04:00
Jeremy
1ee7f68850
Allow zlib through find_package 2023-10-04 19:47:14 -04:00
Jeremy
cd4fa0dd0c
Remove HAS_ADDR2LINE check 2023-10-04 18:49:33 -04:00
Jeremy
435b3cd001
Small fixes 2023-10-04 18:23:19 -04:00
Jeremy
79fc0df281
Update readme again 2023-10-04 17:05:09 -04:00
Jeremy
fbb581556c
Update readme 2023-10-04 17:03:46 -04:00
Jeremy
22bba739bd
Don't try to export dwarf if not using dwarf 2023-10-04 16:05:04 -04:00
Jeremy
b690d3805d
Add stackwalk64 check/fallback 2023-10-04 15:44:39 -04:00
Jeremy
4324901cd1
Fix stackwalk64 architecture detection 2023-10-04 15:31:59 -04:00
Jeremy
262f024332
Don't try to install dwarf target 2023-10-04 15:13:29 -04:00
Jeremy
3cdcc847c9
Fix gcc 5 2023-10-04 14:30:17 -04:00
Jeremy
15b2f36934
Update warning supression 2023-10-04 13:14:21 -04:00
Jeremy
81b2e46df1
Support stackwalk64 for 32-bit x86 2023-10-04 13:13:48 -04:00
Jeremy
b80026596f
Some warning fixes 2023-10-04 13:09:45 -04:00
Jeremy
e28cda9a35
Remove -Wnonnull-compare, it's included in -Wall 2023-10-04 12:05:06 -04:00
Jeremy
bbcd8db054
Better handle PIC / static linking 2023-10-04 10:27:03 -04:00
Jeremy
b236da4a0b
Patch for compilation on 32-bit msvc 2023-10-02 17:30:06 -04:00
Jeremy
26ba20acac
Update README and add changelog 2023-10-02 11:37:17 -04:00
Jeremy
909ce8155f
Remove some old junk from the universal mach-o bodge 2023-10-02 11:01:21 -04:00
Jeremy Rifkin
fcd0dcc62b
Adjust program counters for execinfo and capturestackbacktrace too (#50) 2023-10-02 11:00:13 -04:00
Jeremy
63a7c471cd
Update platform logistic instructions for mac 2023-10-01 17:41:16 -04:00
Jeremy
190a8d6bbb
Libdwarf upstream changes 2023-10-01 12:00:47 -04:00
Jeremy
b46ebe7f54
Update libdwarf code to remove fat mach-o bodge and properly handle fat mach-o files 2023-09-30 19:28:35 -04:00
Jeremy
1d0a6642cf
Libdwarf updates from upstream, including bugfixes and proper mach-o universal binaru support. Huge thanks to the awesome maintainer. 2023-09-30 19:24:27 -04:00
Jeremy
d4bc913607
Setup global dwarf_resolver cache for dwarf resolving 2023-09-30 03:37:16 -04:00
Jeremy
e82f6e360f
Two small refactors 2023-09-30 03:36:07 -04:00
Jeremy
d6f6617c14
Update demo 2023-09-30 03:35:11 -04:00
Jeremy
d964842bdb
Refactor some helper functions and wrappers for dawrf into another file 2023-09-29 15:13:35 -04:00
Jeremy
556d7fe33d
Use StackWalk64 for mingw and cleanup checks performed in cmake 2023-09-29 13:10:21 -04:00
Jeremy
d84a3167d9
Add cache mode config 2023-09-29 12:50:47 -04:00
Jeremy
93dde93802
Caching and optimization work for dwarf 2023-09-29 11:09:17 -04:00
Jeremy
59b5e80256
Adjust pc for better line number reporting with StackWalk64 2023-09-24 18:21:41 -04:00
Jeremy
97de21e977
Bump tag in README 2023-09-24 17:33:26 -04:00
Jeremy Rifkin
6de61e7755
Add StackWalk64 backend (#48) 2023-09-24 17:30:56 -04:00
Jeremy
edf55395d7
Cleanup CPPTRACE_USE_SYSTEM_LIBDWARF and add note to the README. TODO for some reason the include path still doesn't work. 2023-09-24 03:49:56 -04:00
Jeremy Rifkin
c94b03ae6e
Refactor and rewrite a number of dwarf handling methods (#47)
Discard old implementations in favor of new implementations that are
safer and cleaner
2023-09-24 03:25:23 -04:00
Jeremy Rifkin
183cdf5a1c
Error handling improvements (#46) 2023-09-23 17:46:26 -04:00
Jeremy Rifkin
1c3e0e92f4
Add some test failure reporting stuff and fix a bug in test cases (#45) 2023-09-23 17:25:41 -04:00
Jeremy Rifkin
76fc93639e
Properly handle dwarf errors (#43) 2023-09-22 19:54:56 -04:00
Jeremy
f69448f781
Try to fix test and build workflows 2023-09-22 12:17:07 -04:00
Jeremy
dd928b249b
Add max_depth overloads 2023-09-22 12:04:25 -04:00
Jeremy Rifkin
3e18bd3c10
Test resolution of more complex types (#42) 2023-09-22 10:31:19 -04:00
Jeremy
631d00470d
Handle an edge case in the dwarf code 2023-09-22 10:22:58 -04:00
Jeremy
7aa89296a1
README fixes and updates 2023-09-22 10:07:51 -04:00
Jeremy
b583f021b8
Update documentation 2023-09-21 22:11:35 -04:00
Jeremy
1b0b7a43ea
Added trace::current() methods 2023-09-21 21:45:01 -04:00
Jeremy
a10e27a720
Update std::format check 2023-09-21 21:21:13 -04:00
Jeremy
08d831df5f
Update README 2023-09-21 19:24:40 -04:00
Jeremy
99230cb8fe
Add support for gnu debuglink 2023-09-21 19:23:10 -04:00
Jeremy Rifkin
1689487978
Improve Mach-O support (#40) 2023-09-21 19:09:37 -04:00
Jeremy
214dd1df36
Lock around libdwarf calls 2023-09-20 18:28:55 -04:00
Jeremy
99790a03c3
Fix std::formatter specializations 2023-09-20 18:20:52 -04:00
Jeremy
d1af192a3c
Fix for exception::exception init list exception handling, for real 2023-09-20 18:10:54 -04:00
Jeremy
96df7a1876
Fix for exception::exception init list exception handling 2023-09-20 18:04:48 -04:00
Jeremy
33c8cea2f9
Quick fixes 2023-09-20 17:46:34 -04:00
Jeremy
ddcfbe0a88
Column handling fixes 2023-09-20 17:39:51 -04:00
Jeremy
209ce45157
Library interface improvements 2023-09-20 17:38:57 -04:00
Jeremy
216246332c
Update readme and bump instructions to v0.2.0-beta 2023-09-20 10:40:55 -04:00
Jeremy
2324563290
Temp fix for CI 2023-09-20 09:44:37 -04:00
Jeremy
55941eaf7e
More quick fixes 2023-09-19 19:13:50 -04:00
Jeremy
08e5b021de
More quick fixes 2023-09-19 19:07:47 -04:00
Jeremy
c0a15d2677
More quick fixes 2023-09-19 18:57:03 -04:00
Jeremy
5d3755353f
Quick fixes 2023-09-19 18:52:52 -04:00
Jeremy
36174f9216
Improve error handling for object file parsing as well as a lot of cleanup and refactoring 2023-09-19 18:40:39 -04:00
Jeremy
a31fe3dc00
Make stacktrace_frame an aggregate again 2023-09-19 15:36:34 -04:00
Jeremy
799e7a7705
Remove clang-tidy 2023-09-19 15:26:51 -04:00
Jeremy
5c3df2571e
Rename stacktrace_frame.col to column, set a proper sentinel value, and update stacktrace.to_string to not have a tailing newline 2023-09-19 15:17:39 -04:00
Jeremy
137dc78710
Improve dbghelp symbolization thread safety 2023-09-19 11:05:04 -04:00
Jeremy
7195ea43a2
Quick update to docs: Build in release for library installation 2023-09-19 11:04:21 -04:00
Jeremy
0620197285
Add winapi demangling 2023-09-19 11:03:45 -04:00
Jeremy
bdf3e89863
Quick fix 2023-09-19 00:31:10 -04:00
Jeremy
704cba5e97
Some updates and cleanup 2023-09-19 00:21:00 -04:00
Jeremy
a68277fc2b
Update README to include windows dll copying instructions 2023-09-18 23:03:03 -04:00
Jeremy
ccd0ea5e17
Deal with some conversion warnings 2023-09-18 22:41:34 -04:00
Jeremy Rifkin
0b32df64e4
Expand cpptrace API (#37) 2023-09-18 20:33:46 -04:00
Jeremy
43a50c734c
Bump to libdwarf 0.8.0 2023-09-18 19:25:48 -04:00
Jeremy
5fc39fbcea
Small change: Log demangler config 2023-09-18 19:24:51 -04:00
Jeremy
640aeadf91
Remove full tracing options 2023-09-18 14:37:27 -04:00
Jeremy
f020265e09
Update documentation 2023-09-18 11:17:27 -04:00
Jeremy Rifkin
94f902e644
Try to get msvc speedtest working (#36) 2023-09-18 01:31:01 -04:00
Jeremy Rifkin
6dac6da7b3
Refactor how the speedtest is built (#35) 2023-09-17 23:12:03 -04:00
Jeremy
734c84400c
Fix symbol lookup bug for subprogram cache 2023-09-17 19:42:03 -04:00
Jeremy
eb86ae2131
Optimize DWARF symbol lookup: Preprocessor CUs when they're first queried 2023-09-17 19:23:36 -04:00
Jeremy
cc0876dc94
Fix use after free 2023-09-17 17:18:19 -04:00
Jeremy
c36b47b5bf
Handle DW_AT_abstract_origin 2023-09-17 16:03:34 -04:00
Jeremy
6498c837f5
Forgot to check CU dwarf version 2023-09-17 15:19:24 -04:00
Jeremy
5fcfb34335
Support .debug_aranges for CU lookups 2023-09-17 14:41:10 -04:00
Jeremy
2c4f271e23
Improve die_object::has_attr implementation 2023-09-17 01:51:35 -04:00
Jeremy
d9eadf981d
More refactoring and cleanup 2023-09-17 01:26:34 -04:00
Jeremy
077c839839
Dwarf refactoring: Move walking, pc_in_die, and retrieve_symbol_for_subprogram into the dwarf_resolver. Eliminate passing Dwarf_Debug pointers. 2023-09-17 01:15:03 -04:00
Jeremy
e4eab1d426
Optimize dwarf handling by caching line contexts so the line info table only has to be computed once per TU per object per trace 2023-09-17 00:54:11 -04:00
Jeremy
e47cb7147d
Move some commented out type dwarf tree walking code to the end of the file 2023-09-17 00:52:31 -04:00
Jeremy
5e4e842704
Refactor frame collation from addr2line 2023-09-16 21:54:44 -04:00
Jeremy
fdbc69e18e
Refactor nested namespaces 2023-09-16 21:19:08 -04:00
Jeremy
cc43a23987
Lint fixes 2023-09-16 20:52:30 -04:00
Jeremy
278ee3fcee
Work on improving error handling and some general refactoring. Also trying to bring everything into cpptrace::detail. 2023-09-16 20:46:30 -04:00
Eczbek
4d04352189
Slightly improve cxxabi demangler for real this time (#34) 2023-09-16 15:04:30 -04:00
Jeremy
94514cb259
Fix #32 2023-09-16 14:19:21 -04:00
Jeremy
c8e3f67bc0
DWARF traversal improvements and optimizations 2023-09-16 14:18:34 -04:00
Jeremy
29b3140ddd
Some misc cleanup 2023-09-15 19:55:10 -04:00
Jeremy
9cb840c723
Unconditionally walk DW_TAG_namespace 2023-09-15 16:47:01 -04:00
Jeremy
8208f43e8a
Experiments with DW_AT_rnglists_base 2023-09-15 16:46:48 -04:00
Jeremy
07a3ac1e41
Cleanup and refactoring of dwarf code. Improve handling of stuff. 2023-09-15 16:46:22 -04:00
Jeremy
f5fd6942e2
Support DW_FORM_rnglistx for dwarf5 ranges computation 2023-09-13 23:07:33 -04:00
Jeremy
e068a3dadd
Add static option and overhaul README a bit 2023-09-12 23:59:10 -04:00
Jeremy
906687e2db
Lint fix 2023-09-12 18:23:56 -04:00
Jeremy
8bcd2491ee
Bump minimum cmake version to 3.5 2023-09-12 18:09:14 -04:00
Jeremy
5aadb58492
Don't use POSIX basename 2023-09-12 18:08:24 -04:00
Jeremy
d714e70cf6
A couple small cleanup things in libdwarf code, hopefully fixing tests 2023-09-12 17:59:37 -04:00
Jeremy
f95b25f0d6
Handle DW_AT_specification 2023-09-12 13:03:54 -04:00
Jeremy
ffe5fa2524
Fix cmake issue - work around weird bug with parsing the URL 2023-09-12 13:02:54 -04:00
Jeremy
a862f3d2c8
More thoroughly use -fullPath for atos on macos 2023-09-12 02:38:33 -04:00
Jeremy
70bc51d371
Use -fullPath for atos on macos 2023-09-12 02:07:24 -04:00
Jeremy
c7f6f76f54
Fix cmake warning 2023-09-12 01:31:40 -04:00
Jeremy
579842cf32
Update macos expected 2023-09-12 01:28:46 -04:00
Jeremy
3e556e370a
Fix performance-tests.yml 2023-09-12 01:21:53 -04:00
Jeremy
2545e6c2e5
Use .dSYM files on macos. Should fix test cases. 2023-09-12 01:19:58 -04:00
Jeremy
54b4701462
Refactor linkage name querying 2023-09-12 00:16:40 -04:00
Jeremy
4b0af6b6d0
Refactor of some dwarf handling and debug improvements 2023-09-11 23:58:00 -04:00
Jeremy
851199d46d
Update middle-end ordering 2023-09-11 12:40:37 -04:00
Jeremy
db4f67d352
Lint fixes 2023-09-11 12:12:01 -04:00
Jeremy
e16f88817e
Use both dbghelp and libdwarf under mingw 2023-09-11 12:06:16 -04:00
Jeremy
5dc819186e
Baseline for middle-end system 2023-09-11 11:57:01 -04:00
Jeremy
09ccc95814
Fix lint 2023-09-11 10:36:57 -04:00
Jeremy
55e4aaceb0
Update optional implementation 2023-09-11 10:31:41 -04:00
Jeremy
dd7b8eba9c
basic optional implementation 2023-09-11 09:37:11 -04:00
Jeremy Rifkin
95ee2a815c
CI and dwarf work (#30) 2023-09-10 19:46:01 -04:00
Jeremy
5080fb33d7
Add note to readme about copying the .dll when using fetchcontent 2023-09-09 18:09:38 -04:00
Jeremy
57031e4c6b
Fix cmake integration tests 2023-09-09 18:09:07 -04:00
Jeremy
cf0bf6e02d
Specify a tag in readme instructions 2023-09-02 21:31:03 -04:00
Jeremy
46ac51bf0e
Fix a couple step names 2023-09-01 18:18:40 -04:00
Jeremy
01d0fef78f
Re-enable windows cmake integration tests 2023-09-01 18:17:25 -04:00
Jeremy
ac95720789
Remove some old stuff from ci workflows 2023-09-01 17:55:09 -04:00
Jeremy
976428b8e5
Update install rules for libdwarf 2023-09-01 17:53:45 -04:00
Jeremy
be6f473007
Update to static link libdwarf and also add a note to the readme about licensing of the compiled binary 2023-09-01 17:43:51 -04:00
Jeremy
b4d8fda9e2
Update libdwarf cmake 2023-09-01 13:09:05 -04:00
Jeremy
e3cba004b7
Bundle libdwarf 2023-09-01 13:00:45 -04:00
Jeremy
0e27da8c11
Some symbol backend refactoring, get rid of pimpl nonsense 2023-08-31 01:21:22 -04:00
Jeremy
871344f7ae
Some cmake cleanup 2023-08-31 01:08:06 -04:00
Jeremy
843681f716
Temporarily turn off a couple tests 2023-08-30 14:24:46 -04:00
Jeremy Rifkin
6ed6e623ed
Initial work to add libdwarf as a back-end (#24) 2023-08-30 12:14:17 -04:00
188 changed files with 19599 additions and 3510 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,6 +0,0 @@
---
Checks: '-*,clang-diagnostic-*,clang-analyzer-*,bugprone-*,cert-*,clang-analyzer-*,concurrency-*,cppcoreguidelines-*,misc-*,modernize-*,performance-*,portability-*,readability-*,-cppcoreguidelines-macro-usage,-modernize-use-trailing-return-type,-misc-non-private-member-variables-in-classes,-cppcoreguidelines-pro-type-reinterpret-cast,-cppcoreguidelines-pro-type-vararg,-cppcoreguidelines-avoid-c-arrays,-modernize-avoid-c-arrays,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-readability-else-after-return,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-cert-dcl50-cpp,-cppcoreguidelines-init-variables,-readability-implicit-bool-conversion,-cppcoreguidelines-pro-bounds-constant-array-index,-cppcoreguidelines-owning-memory,-cppcoreguidelines-pro-type-member-init,-readability-isolate-declaration,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,-cppcoreguidelines-pro-type-union-access,-cppcoreguidelines-pro-type-cstyle-cast,-readability-named-parameter,-cppcoreguidelines-avoid-goto,-readability-uppercase-literal-suffix,-performance-avoid-endl,-bugprone-easily-swappable-parameters'
WarningsAsErrors: '*'
HeaderFilterRegex: ''
AnalyzeTemporaryDtors: false
FormatStyle: none

View File

@ -1,55 +0,0 @@
name: build
on:
push:
pull_request:
jobs:
build-linux:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v2
- name: dependencies
run: sudo apt install gcc-10 g++-10 libgcc-10-dev
- name: build
run: |
pip3 install colorama
python3 ci/build-in-all-configs.py
build-macos:
runs-on: macos-13
steps:
- uses: actions/checkout@v2
- name: build
run: |
pip3 install colorama
python3 ci/build-in-all-configs.py
build-windows-msvc:
runs-on: windows-2019
steps:
- uses: actions/checkout@v2
- name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@v1.10.0
- name: build
run: |
pip3 install colorama
python3 ci/build-in-all-configs.py --msvc-only
build-windows-clang:
runs-on: windows-2019
steps:
- uses: actions/checkout@v2
- name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@v1.10.0
- name: build
run: |
pip3 install colorama
python3 ci/build-in-all-configs.py --clang-only
build-windows-mingw:
runs-on: windows-2019
steps:
- uses: actions/checkout@v2
- name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@v1.10.0
- name: build
run: |
pip3 install colorama
python3 ci/build-in-all-configs.py --mingw-only

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,161 +0,0 @@
name: cmake-integration
on:
push:
pull_request:
jobs:
test-linux-fetchcontent:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v2
- name: test
run: |
tag=$(git rev-parse --abbrev-ref HEAD)
cd ..
cp -rv cpptrace/test/fetchcontent-integration .
mkdir fetchcontent-integration/build
cd fetchcontent-integration/build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DCPPTRACE_TAG=$tag
make
./main
test-linux-findpackage:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v2
- name: build
run: |
tag=$(git rev-parse --abbrev-ref HEAD)
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Debug
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
steps:
- uses: actions/checkout@v2
- 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
make
./main
test-macos-fetchcontent:
runs-on: macos-13
steps:
- uses: actions/checkout@v2
- name: test
run: |
tag=$(git rev-parse --abbrev-ref HEAD)
cd ..
cp -rv cpptrace/test/fetchcontent-integration .
mkdir fetchcontent-integration/build
cd fetchcontent-integration/build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DCPPTRACE_TAG=$tag
make
./main
test-macos-findpackage:
runs-on: macos-13
steps:
- uses: actions/checkout@v2
- name: build
run: |
tag=$(git rev-parse --abbrev-ref HEAD)
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Debug
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-13
steps:
- uses: actions/checkout@v2
- 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
make
./main
test-mingw-fetchcontent:
runs-on: windows-2019
steps:
- uses: actions/checkout@v2
- name: test
run: |
$tag=$(git rev-parse --abbrev-ref HEAD)
cd ..
cp -Recurse cpptrace/test/fetchcontent-integration .
mkdir fetchcontent-integration/build
cd fetchcontent-integration/build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DCPPTRACE_TAG="$tag" -DCMAKE_BUILD_TYPE=g++ "-GUnix Makefiles"
make
.\main.exe
test-mingw-add_subdirectory:
runs-on: windows-2019
steps:
- uses: actions/checkout@v2
- 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 -DCMAKE_BUILD_TYPE=g++ "-GUnix Makefiles"
make
.\main.exe
test-windows-fetchcontent:
runs-on: windows-2019
steps:
- uses: actions/checkout@v2
- name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@v1.10.0
- name: test
run: |
$tag=$(git rev-parse --abbrev-ref HEAD)
cd ..
cp -Recurse cpptrace/test/fetchcontent-integration .
mkdir fetchcontent-integration/build
cd fetchcontent-integration/build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DCPPTRACE_TAG="$tag"
msbuild demo_project.sln
.\Debug\main.exe
test-windows-add_subdirectory:
runs-on: windows-2019
steps:
- uses: actions/checkout@v2
- name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@v1.10.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
msbuild demo_project.sln
.\Debug\main.exe

View File

@ -1,18 +0,0 @@
name: lint
on:
push:
pull_request:
jobs:
lint:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: dependencies
run: |
sudo apt install clang-tidy
- name: clang-tidy
run: |
chmod +x lint.sh
./lint.sh -DCPPTRACE_BACKTRACE_PATH=\"/usr/lib/gcc/x86_64-linux-gnu/10/include/backtrace.h\"

View File

@ -1,74 +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]
target: [Debug]
std: [11, 20]
config: [
"-DCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE=On",
"-DCPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE=On -DCPPTRACE_BUILD_SPEEDTEST_DWARF4=On",
"-DCPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE=On -DCPPTRACE_BUILD_SPEEDTEST_DWARF5=On"
]
exclude:
- config: -DCPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE=On -DCPPTRACE_BUILD_SPEEDTEST_DWARF5=On
compiler: g++-11
steps:
- uses: actions/checkout@v2
- name: dependencies
run: sudo apt install gcc-11 g++-11 libgcc-11-dev
- name: build
run: |
mkdir -p build
cd build
cmake .. \
-DCMAKE_BUILD_TYPE=${{matrix.target}} \
-DCMAKE_CXX_COMPILER=${{matrix.compiler}} \
-DCMAKE_CXX_STANDARD=${{matrix.std}} \
${{matrix.config}} \
-DCPPTRACE_BACKTRACE_PATH=/usr/lib/gcc/x86_64-linux-gnu/11/include/backtrace.h \
-DCPPTRACE_BUILD_SPEEDTEST=On
make -j
- name: test
working-directory: build
run: |
./speedtest | python3 ../ci/speedtest.py ${{matrix.compiler}} ${{matrix.config}}
# TODO: For some reason this is slow on github's runner
#performancetest-windows:
# runs-on: windows-2019
# strategy:
# fail-fast: false
# matrix:
# compiler: [cl, clang++]
# target: [Debug]
# std: [11, 20]
# config: [
# "-DCPPTRACE_GET_SYMBOLS_WITH_DBGHELP=On"
# ]
# steps:
# - uses: actions/checkout@v2
# - name: Enable Developer Command Prompt
# uses: ilammy/msvc-dev-cmd@v1.10.0
# - name: build
# run: |
# mkdir -p build
# cd build
# cmake .. `
# -DCMAKE_BUILD_TYPE=Debug `
# -DCMAKE_CXX_COMPILER=${{matrix.compiler}} `
# -DCMAKE_CXX_STANDARD=${{matrix.std}} `
# ${{matrix.config}} `
# -DCPPTRACE_BUILD_SPEEDTEST=On
# msbuild .\cpptrace.sln
# - name: test
# working-directory: build
# run: |
# .\${{matrix.target}}\speedtest.exe | python3 ../ci/speedtest.py ${{matrix.config}}

32
.github/workflows/sonarlint.yml vendored Normal file
View File

@ -0,0 +1,32 @@
on:
# Trigger analysis when pushing in master or pull requests, and when creating
# a pull request.
push:
branches:
- main
- dev
pull_request:
types: [opened, synchronize, reopened]
name: sonarlint
jobs:
sonarcloud:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# Disabling shallow clone is recommended for improving relevancy of reporting
fetch-depth: 0
- name: Install sonar-scanner and build-wrapper
uses: sonarsource/sonarcloud-github-c-cpp@v2
- name: Run build-wrapper
run: |
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Debug -DCPPTRACE_BUILD_TESTING=On -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCMAKE_CXX_STANDARD=11
make -j
cd ..
- name: Run sonar-scanner
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: sonar-scanner

View File

@ -1,57 +0,0 @@
name: test
on:
push:
pull_request:
# TODO: Test statically linked
jobs:
test-linux:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v2
- name: dependencies
run: sudo apt install gcc-10 g++-10 libgcc-10-dev
- name: build
run: |
pip3 install colorama
python3 ci/test-all-configs.py
test-macos:
runs-on: macos-13
steps:
- uses: actions/checkout@v2
- name: build
run: |
pip3 install colorama
python3 ci/test-all-configs.py
test-windows-msvc:
runs-on: windows-2019
steps:
- uses: actions/checkout@v2
- name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@v1.10.0
- name: build
run: |
pip3 install colorama
python3 ci/test-all-configs.py --msvc-only
test-windows-clang:
runs-on: windows-2019
steps:
- uses: actions/checkout@v2
- name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@v1.10.0
- name: build
run: |
pip3 install colorama
python3 ci/test-all-configs.py --clang-only
test-windows-mingw:
runs-on: windows-2019
steps:
- uses: actions/checkout@v2
- name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@v1.10.0
- name: build
run: |
pip3 install colorama
python3 ci/test-all-configs.py --mingw-only

10
.gitignore vendored
View File

@ -1,5 +1,11 @@
.vscode
.vscode/
.idea/
a.out
build*/
repro*/
__pycache__
__pycache__/
scratch
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"],
)

415
CHANGELOG.md Normal file
View File

@ -0,0 +1,415 @@
# 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)
- [v0.5.4](#v054)
- [v0.5.3](#v053)
- [v0.5.2](#v052)
- [v0.5.1](#v051)
- [v0.5.0](#v050)
- [v0.4.1](#v041)
- [v0.4.0](#v040)
- [v0.3.1](#v031)
- [v0.3.0](#v030)
- [v0.2.1](#v021)
- [v0.2.0](#v020)
- [v0.1.1](#v011)
- [v0.1](#v01)
# v0.8.2
Fixed:
- Fixed printing of internal error messages when an object file can't be loaded, mainly affecting MacOS https://github.com/jeremy-rifkin/cpptrace/issues/217
Other:
- Bumped zstd via FetchContent to 1.5.7
# v0.8.1
Fixed:
- Fixed compile error on msvc https://github.com/jeremy-rifkin/cpptrace/issues/215
Added:
- Added `cpptrace::can_get_safe_object_frame()`
Breaking changes:
- Renamed ctrace's `can_signal_safe_unwind` to `ctrace_can_signal_safe_unwind`. This was an oversight. Apologies for
including a breaking change in a patch release. Github code search suggests this API isn't used in public code, at
least.
Other:
- Added CI workflow to test on old msvc
- Made some internal improvements on robustness and cleanliness
# v0.8.0
Added:
- Added support for resolving symbols from elf and mach-o symbol tables, allowing function names to be resolved even in
a build that doesn't include debug information https://github.com/jeremy-rifkin/cpptrace/issues/201
- Added a configurable stack trace formatter https://github.com/jeremy-rifkin/cpptrace/issues/164
- Added configuration options for the libdwarf back-end that can be used to lower memory usage on memory-constrained
systems https://github.com/jeremy-rifkin/cpptrace/issues/193
- Added `cpptrace::nullable<T>::null_value`
- Made `cpptrace::nullable<T>` member functions conditionally `constexpr` where possible
Fixed:
- Fixed handling of `SymInitialize` when other code has already called `SymInitialize`. `SymInitialize` must only be
called once per handle and cpptrace now attempts to duplicate the current process handle to avoid conflicts.
https://github.com/jeremy-rifkin/cpptrace/issues/204
- Fixed a couple of locking edge cases surrounding dbghelp functions
- Fixed improper deallocation of `dwarf_errmsg` in the libdwarf back-end
Breaking changes:
- `cpptrace::get_snippet` previously included a newline at the end but it now does not. This also affects the behavior
of trace formatting with snippets enabled.
Other:
- Significantly improved memory usage and performance of the libdwarf back-end
- Improved implementation and organization of internal utility types, such as `optional` and `Result`
- Improved trace printing and formatting implementation
- Added unit tests for library internal utilities
- Added logic to the cxxabi demangler to ensure external names begin with `_Z` or `__Z` before attempting to demangle
- Added various internal tools and abstractions to improve maintainability and clarity
- Various internal improvements for robustness
- Added a small handful of utility tool programs that are useful for continued development, maintenance, and debugging
- Improved library CI setup
- Marked the `CPPTRACE_BUILD_BENCHMARK` option as advanced
# v0.7.5
Fixed:
- Fixed missing `<typeinfo>` include https://github.com/jeremy-rifkin/cpptrace/pull/202
- Added `__cdecl` to a terminate handler to appease MSVC under some configurations https://github.com/jeremy-rifkin/cpptrace/issues/197
- Set C++ standard for cmake support checks https://github.com/jeremy-rifkin/cpptrace/issues/200
- Changed hyphens to underscores for cmake component names due to cpack issue https://github.com/jeremy-rifkin/cpptrace/issues/203
# v0.7.4
Added:
- Added `<cpptrace/version.hpp>` header with version macros
Fixes:
- Bumped libdwarf to 0.11.0 which fixes a number of dwarf 5 debug fission issues
Other:
- Various improvements to internal testing setup
# v0.7.3
Fixed:
- Fixed missing include affecting macos https://github.com/jeremy-rifkin/cpptrace/pull/183
- Fixed issue with cmake not using the ccache program found by `find_program` https://github.com/jeremy-rifkin/cpptrace/pull/184
- Fixed missing include and warnings affecting mingw https://github.com/jeremy-rifkin/cpptrace/pull/186
- Fixed issue with identifying inlined call frames when the `DW_TAG_inlined_subroutine` is under a `DW_TAG_lexical_block`
- Fixed a typo in the README
- Improved unittest support on various configurations
- Improved unittest robustness under LTO
- Fixed bug signal_demo in the event `fork()` fails
Added:
- Added color overload for `stacktrace_frame::to_string`
- Added CMake `export()` definition for cpptrace as well as a definition for libdwarf which currently doesn't provide one
Changed:
- Updated documentation surrounding the signal safe API
# v0.7.2
Changes:
- Better support for older CMake with using `FetchContent_Declare` from a URL https://github.com/jeremy-rifkin/cpptrace/pull/176
- Better portability for page size detection https://github.com/jeremy-rifkin/cpptrace/pull/177
- Improved compile times https://github.com/jeremy-rifkin/cpptrace/pull/172
- Split up `cpptrace.hpp` into finer-grained headers for lower compile time impact
- Some minor readme restructuring
# v0.7.1
Added
- Better support for finding libunwind on macos https://github.com/jeremy-rifkin/cpptrace/pull/162
- Support for libbacktrace under mingw https://github.com/jeremy-rifkin/cpptrace/pull/166
Fixed
- Computation of object address for safe object frames https://github.com/jeremy-rifkin/cpptrace/issues/169
- Nested microfmt in cpptrace's namespace due to an ODR problem with libassert https://github.com/jeremy-rifkin/libassert/issues/103
- Compilation on iOS https://github.com/jeremy-rifkin/cpptrace/pull/167
- Compilation on old MSVC https://github.com/jeremy-rifkin/cpptrace/pull/165
- Dbghelp use on 32 bit https://github.com/jeremy-rifkin/cpptrace/issues/170
- Warning in brand new cmake due to `FetchContent_Populate` being deprecated https://github.com/jeremy-rifkin/cpptrace/issues/171
Other changes
- Bumped the buffer size for execinfo and CaptureStackBackTrace to 400 frames
- Switched to execinfo.h for unwinding on clang/apple clang on macos due to `_Unwind` not working with `-fno-exceptions` https://github.com/jeremy-rifkin/cpptrace/issues/161
# v0.7.0
Added
- Added `cpptrace::from_current_exception()` and associated exception handler macros to allow tracing of all exceptions,
even without cpptrace traced exception objects.
Fixes:
- Fixed issue with using `resolve_safe_object_frame` on `safe_object_frame`s with empty paths
- Fixed handling of dwarf 4 rangelist base addresses when a `DW_AT_low_pc` is not present
- Fixed use of `-g` with MSVC
Other changes:
- Bazel is now supported on linux (https://github.com/jeremy-rifkin/cpptrace/pull/153)
- More work on testing
- Some internal refactoring
# v0.6.3
Added:
- Added a flag to disable inclusion of `<format>` by cpptrace.hpp and the definition of formatter specializations
Fixes:
- Fixed use after free during cleanup of split dwarf information https://github.com/jeremy-rifkin/cpptrace/issues/141
- Fixed an issue with TCO by clang on arm interfering with unwinding skip counts for internal methods
- Fixed issue with incorrect object addresses being reported on macos when debug maps are used
- Fixed issue with handling of split dwarf emitted by clang under dwarf4 mode
Other changes:
- Added note about signal-safe tracing requiring `_dl_find_object` to documentation and fixed errors in the signal-safe
tracing docs
- Added more configurations to unittest ci setup
- Optimized unittest ci matrix setup
- Added options for zstd and libdwarf sources if FetchContent is being used to bring the dependencies in
- Optimized includes in cpptrace.hpp
# v0.6.2
Fixes:
- Fix an issue with unwinding to collect stack traces during exception creation on arm https://github.com/jeremy-rifkin/cpptrace/issues/134
- Fix issue where `dladdr1` wasn't being used even when detected
Robustness:
- Setup more robust unit tests and added them to CI
# v0.6.1
Fixes:
- Fix for detection of `dladdr1` and `_dl_find_object` support
# v0.6.0
New:
- Added a `cpptrace::system_error` utility
- Added support for musl https://github.com/jeremy-rifkin/cpptrace/issues/128
- Added support for split dwarf / debug fission
Fixes:
- Fixed address formatting in stack traces
- Fixed frame pointer calculation for signal frames from libunwind https://github.com/jeremy-rifkin/cpptrace/issues/123
- Fixed dwarf_ranges handling of lowpc == pc causing erroneous symbol resolution
- Fixed implementation of the exception helper system/reference implementation's `lazy_trace_holder`
# v0.5.4
Fixes:
- Fixed bug with resolving object information when `dladdr` is used and an unexpected `argv[0]` is provided to the
binary.
# v0.5.3
Fixes:
- Fixed bug with formatting of hex values on MSVC
- Fixed error handling for libbacktrace back-end when debug info is not present
- Fixed bug with cmake resolution of zstd when no zstd cmake config file is installed
Other changes:
- Added error handling for an edge case in the signal tracing demo
- Updated conan recipe to allow libunwind to be chosen
- Improved msvc support in internal formatting system
- Bumped libdwarf to 0.9.2
# v0.5.2
Fixes:
- Fixed bug with resolution of inlined calls
Other changes:
- Improved internal string formatting
- Improved internal error handling
# v0.5.1
Fixes:
- Fix MSVC warning treated as error for 32-bit windows
- Fix MSVC issue with min/max macros
- Fix potential null dereference issue identified by eyalgolan1337
# v0.5.0
New:
- Traces with source code snippets with `cpptrace::stacktrace::print_with_snippets`
- Added `cpptrace::get_snippet` utility
- Added `cpptrace::can_signal_safe_unwind` utility
- Added `stacktrace_frame::get_object_info`
Changes:
- The library is now compiled with position-independent code by default
Fixes:
- Fixed issue with `_dl_find_object` implementation
Misc:
- Various refactoring, cleanup, and improvements
# v0.4.1
Changes:
- Renamed `stacktrace_frame.address` -> `stacktrace_frame.raw_address`
- Added `stacktrace_frame.object_address`
- Fixed segfault due to an edge case with dwarf file table indices
- For the libdwarf back-end: At least show object frame information if resolution fails
- Extremely small performance improvements
- Small documentation updates
- Small fix for conan
- Updated cmake to not FetchContent zstd when using CPPTRACE_USE_EXTERNAL_LIBDWARF
- CI improvements
- Test the default configuration first before doing the exhaustive and slow matrix of all configurations.
- Cleanup of duplicated prerequisite installation code
- Cleanup of built and test python scripts
# v0.4.0
What's new:
- Cpptrace now has a C API! 🎉
- Cpptrace is now able to parse macOS debug maps and resolve stack traces without dSYM files
Most notable improvements:
- Updated cpptrace exception objects to generate traces at the callsite for improved consistency with trace output. As
part of this cpptrace exception objects have had their constructors updated.
- Improved dwarf back-end robustness
- Fallback to the compilation-unit cache or walking compilation-units if aranges lookup fails
- Eliminated reliance on a CMake-generated export header
- Added a configuration to control resolution of inlined function calls
- Made architecture selection in Mach-O universal binaries
Other improvements:
- Improved documentation for installation and usage
- Generally improved README content and organization
- Fixed an MSVC workaround producing dozens of warnings
- Better handle compiler color diagnostic arguments if compiler families differ
- Improvements for handling libdwarf's header placement
- Fixed issue with libunwind resolution
- `-Werror` is now used in CI
- More library configurations are now tested in CI
- Updated to libdwarf 9
- Updated the library's CMake to acquire zstd through FetchContent
- Fixed minor issue with stacktrace printing always trying to enable virtual terminal processing, even when not actually
printing to the terminal.
# v0.3.1
Tiny patch:
- Fix `CPPTRACE_EXPORT` annotations
- Add workaround for [msvc bug][msvc bug] affecting msvc 19.38.
[msvc bug]: https://developercommunity.visualstudio.com/t/MSVC-1938331290-preview-fails-to-comp/10505565
# v0.3.0
Interface Changes:
- Overhauled the API for traced `cpptrace::exception` objects
- Added `cpptrace::isatty` utility
- Added specialized `std::terminate` handler and `cpptrace::register_terminate_handler` utility
- Added `cpptrace::frame_ptr` as an alias for the appropriate type capable of representing an instruction pointer
- Added signal-safe tracing support and a guide for [how to trace safely](signal-safe-tracing.md)
- Added `cpptrace::nullable<T>` utility for better indicating when line / column information is not present
- Added `CPPTRACE_FORCE_NO_INLINE` utility macro to cpptrace.hpp
- Added `CPPTRACE_WRAP` and `CPPTRACE_WRAP_BLOCK` utilities to catch non-`cpptrace::exception`s and rethrow wrapped in a
traced exception.
- Updated `cpptrace::stacktrace::to_string` to take a `bool color` parameter
- Eliminated uses of `std::uint_least32_t` in favor of other types
- Updated `object_frame` data member names
Other changes:
- Added object resolution with `_dl_find_object` which is much faster than `dladdr`
- Added column support for dwarf
- Added inlined call resolution
- Added `cpptrace::stacktrace_frame::is_inline`
- Added libunwind as a back-end
- Unbundled libdwarf
- Increased hard max frame count, used by some back-end requiring fixed buffers, from 100 to 200
- Improved libgcc unwind backend
- Improved trace output when information is missing
- Added a lookup table for faster dwarf line information lookup
News:
- The library is now on conan and vcpkg
Minor changes:
- Assorted bug fixes
- Various code quality improvements
- CI improvements
- Documentation improvements
- CMake improvements
- Internal refactoring
# v0.2.1
Patches:
- Fixed uintptr_t implicit conversion issue for msvc
- Better handling for PIC and static linkage in CMake
- Added gcc 5 support
- Various warning fixes
- Added stackwalk64 support for 32-bit x86 mingw/clang and architecture detection
- Added check for stackwalk64 support and CaptureStackBacktrace as a fallback
- Various cmake cleanup and changes to use cpptrace through package managers
- Added sonarlint and implemented some sonarlint fixes
# v0.2.0
Key changes:
- Added libdwarf as a back-end so cpptrace doesn't have to rely on addr2line or libbacktrace
- Overhauled library's public-facing interface to make the library more useful
- Added `raw_trace` interface
- Added `object_trace` interface
- Added `stacktrace` interface
- Updated `generate_trace` to return a `stacktrace` rather than a vector of frames
- Added `generate_trace` counterparts for raw and object traces
- Added `generate_trace` overloads with max_depth
- Added interface for internal demangling utility
- Added cache mode configuration
- Added option to absorb internal trace exceptions (by default it absorbs)
- Added `cpptrace::exception`, which automatically generates and stores a stacktrace when thrown
- Added `exception_with_message`
- Added traced analogs for stdexcept errors: `logic_error`, `domain_error`, `invalid_argument`, `length_error`,
`out_of_range`, `runtime_error`, `range_error`, `overflow_error`, and `underflow_error`.
Other changes:
- Bundled libdwarf with cpptrace so the library can essentially be self-contained and not have to rely on libraries that
might not already be on a system
- Added StackWalk64 as an unwinding back-end on windows
- Added system for multiple symbol back-ends to be used, mainly for more complete stack traces on mingw
- Fixed sporadic line number reporting errors due to not adjusting the program counter from the unwinder
- Improved addr2line/atos invocation back-end on macos
- Lots of error handling improvements
- Performance improvements
- Updated default back-ends for most systems
- Removed full tracing backends
- Cleaned up library cmake
- Lots of internal cleanup and refactoring
- Improved library usage instructions in README
# v0.1.1
Fixed:
- Handle errors when object files don't exist or can't be opened for reading
- Handle paths with spaces when using addr2line on windows
# v0.1
Initial release of the library 🎉

View File

@ -1,48 +1,55 @@
cmake_minimum_required(VERSION 3.4)
cmake_minimum_required(VERSION 3.14)
if(${CMAKE_VERSION} VERSION_LESS 3.12)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
endif()
include(cmake/PreventInSourceBuilds.cmake)
# ---- Initialize Project ----
# Used to support find_package
set(package_name "cpptrace")
project(
cpptrace
VERSION 0.1.0
LANGUAGES CXX
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
)
# Don't change include order, OptionVariables checks if project is top level
include(cmake/ProjectIsTopLevel.cmake)
include(cmake/OptionVariables.cmake)
include(GNUInstallDirs)
include(CheckCXXSourceCompiles)
include(CheckCXXCompilerFlag)
option(CPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE "" OFF)
option(CPPTRACE_FULL_TRACE_WITH_STACKTRACE "" OFF)
if(PROJECT_IS_TOP_LEVEL)
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE_PROGRAM})
set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE_PROGRAM})
endif()
endif()
option(CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE "" OFF)
option(CPPTRACE_GET_SYMBOLS_WITH_LIBDL "" OFF)
option(CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE "" OFF)
option(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP "" OFF)
option(CPPTRACE_GET_SYMBOLS_WITH_NOTHING "" OFF)
if(PROJECT_IS_TOP_LEVEL)
if(CMAKE_GENERATOR STREQUAL "Ninja")
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color=always")
elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcolor-diagnostics")
endif()
if("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fdiagnostics-color=always")
elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang")
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fcolor-diagnostics")
endif()
endif()
endif()
option(CPPTRACE_UNWIND_WITH_UNWIND "" OFF)
option(CPPTRACE_UNWIND_WITH_EXECINFO "" OFF)
option(CPPTRACE_UNWIND_WITH_WINAPI "" OFF)
option(CPPTRACE_UNWIND_WITH_NOTHING "" OFF)
option(CPPTRACE_DEMANGLE_WITH_CXXABI "" OFF)
option(CPPTRACE_DEMANGLE_WITH_NOTHING "" OFF)
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 100.")
set(CPPTRACE_ADDR2LINE_PATH "" CACHE STRING "Absolute path to the addr2line executable you want to use.")
option(CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH "" OFF)
option(CPPTRACE_BUILD_TEST "" OFF)
option(CPPTRACE_BUILD_DEMO "" OFF)
option(CPPTRACE_BUILD_TEST_RDYNAMIC "" OFF)
option(CPPTRACE_BUILD_SPEEDTEST "" OFF)
option(CPPTRACE_BUILD_SPEEDTEST_DWARF4 "" OFF)
option(CPPTRACE_BUILD_SPEEDTEST_DWARF5 "" OFF)
if(CPPTRACE_SANITIZER_BUILD)
add_compile_options(-fsanitize=address)
add_link_options(-fsanitize=address)
endif()
if(NOT "${CPPTRACE_BACKTRACE_PATH}" STREQUAL "")
# quotes used over <> because of a macro substitution issue where
@ -58,264 +65,222 @@ else()
set(CPPTRACE_BACKTRACE_PATH_DEFINITION "")
endif()
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(MINGW OR NOT WIN32) # No need to bother checking in msvc, but do check in minngw
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}")
check_support(HAS_DL has_dl.cpp "" "dl" "")
check_support(HAS_CXXABI has_cxxabi.cpp "" "" "")
if(NOT MSVC)
set(STACKTRACE_LINK_LIB "stdc++_libbacktrace")
else()
set(STACKTRACE_LINK_LIB "")
endif()
check_support(HAS_STACKTRACE has_stacktrace.cpp "" "${STACKTRACE_LINK_LIB}" "")
if(APPLE)
find_program(ADDR2LINE_PATH atos PATHS ENV PATH)
else()
find_program(ADDR2LINE_PATH addr2line PATHS ENV PATH)
endif()
if("${ADDR2LINE_PATH}" STREQUAL "ADDR2LINE_PATH-NOTFOUND")
set(HAS_ADDR2LINE FALSE)
else()
set(HAS_ADDR2LINE TRUE)
endif()
endif()
# =================================================== Library Setup ====================================================
# =============================================== Autoconfig full dump ===============================================
# If nothing is specified, attempt to use libbacktrace's full dump
if(
NOT (
CPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE OR
CPPTRACE_FULL_TRACE_WITH_STACKTRACE OR
CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE OR
CPPTRACE_GET_SYMBOLS_WITH_LIBDL OR
CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE OR
CPPTRACE_GET_SYMBOLS_WITH_DBGHELP OR
CPPTRACE_GET_SYMBOLS_WITH_NOTHING OR
CPPTRACE_UNWIND_WITH_UNWIND OR
CPPTRACE_UNWIND_WITH_EXECINFO OR
CPPTRACE_UNWIND_WITH_WINAPI OR
CPPTRACE_UNWIND_WITH_NOTHING
if(NOT CPPTRACE_BUILD_NO_SYMBOLS)
set(
debug
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-g>
$<$<CXX_COMPILER_ID:MSVC>:/DEBUG>
)
)
# Attempt to auto-config
if(MINGW OR NOT WIN32) # Our trace is better than msvc's <stacktrace>
if(HAS_STACKTRACE)
set(CPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE On)
message(STATUS "Cpptrace auto config: Using C++23 <stacktrace> for the full trace")
elseif(HAS_BACKTRACE AND NOT WIN32) # Mingw libbacktrace doesn't seem to be working
set(CPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE On)
message(STATUS "Cpptrace auto config: Using libbacktrace for the full trace")
endif()
endif()
endif()
# =============================================== Autoconfig unwinding ===============================================
# Unwind back-ends (If not doing CPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE)
if(
NOT (
CPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE OR
CPPTRACE_FULL_TRACE_WITH_STACKTRACE OR
CPPTRACE_UNWIND_WITH_UNWIND OR
CPPTRACE_UNWIND_WITH_EXECINFO OR
CPPTRACE_UNWIND_WITH_WINAPI OR
CPPTRACE_UNWIND_WITH_NOTHING
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)
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_WINAPI On)
message(STATUS "Cpptrace auto config: Using winapi for unwinding")
endif()
elseif(WIN32)
set(CPPTRACE_UNWIND_WITH_WINAPI On)
message(STATUS "Cpptrace auto config: Using winapi for unwinding")
endif()
else()
#message(STATUS "MANUAL CONFIG SPECIFIED")
endif()
# =============================================== Autoconfig symbols ===============================================
# Symbol back-ends (If not doing CPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE)
if(
NOT (
CPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE OR
CPPTRACE_FULL_TRACE_WITH_STACKTRACE OR
CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE OR
CPPTRACE_GET_SYMBOLS_WITH_LIBDL OR
CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE OR
CPPTRACE_GET_SYMBOLS_WITH_DBGHELP OR
CPPTRACE_GET_SYMBOLS_WITH_NOTHING
)
)
# Attempt to auto-config
if(UNIX OR MINGW)
if(HAS_BACKTRACE AND NOT MINGW) # not working on mingw at the moment
set(CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE On)
message(STATUS "Cpptrace auto config: Using libbacktrace for symbols")
elseif(HAS_ADDR2LINE)
set(CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE On)
message(STATUS "Cpptrace auto config: Using addr2line for symbols")
elseif(HAS_DL)
set(CPPTRACE_GET_SYMBOLS_WITH_LIBDL On)
message(STATUS "Cpptrace auto config: Using libdl for symbols")
else()
message(FATAL_ERROR "Cpptrace auto config: No symbol back-end could be automatically configured. To compile anyway set CPPTRACE_GET_SYMBOLS_WITH_NOTHING.")
endif()
elseif(WIN32)
set(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP On)
message(STATUS "Cpptrace auto config: Using dbghelp for symbols")
endif()
else()
#message(STATUS "MANUAL CONFIG SPECIFIED")
endif()
# Target that we can modify (can't modify ALIAS targets)
# Target name should not be the same as ${PROJECT_NAME}, causes add_subdirectory issues
set(target_name "cpptrace-lib")
add_library(${target_name} ${build_type})
# =============================================== Autoconfig demangling ===============================================
# Handle demangle configuration
if(
NOT (
CPPTRACE_DEMANGLE_WITH_CXXABI OR
CPPTRACE_DEMANGLE_WITH_NOTHING
)
)
if(HAS_CXXABI)
set(CPPTRACE_DEMANGLE_WITH_CXXABI On)
else()
set(CPPTRACE_DEMANGLE_WITH_NOTHING On)
endif()
else()
#message(STATUS "Manual demangling back-end specified")
endif()
# Alias to cause error at configuration time instead of link time if target is missing
add_library(cpptrace::cpptrace ALIAS ${target_name})
# =============================================== Now define the library ===============================================
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(
sources
src/cpptrace.cpp
src/demangle/demangle_with_cxxabi.cpp
src/demangle/demangle_with_nothing.cpp
src/full/full_trace_with_libbacktrace.cpp
src/full/full_trace_with_stacktrace.cpp
src/symbols/symbols_with_addr2line.cpp
src/symbols/symbols_with_dbghelp.cpp
src/symbols/symbols_with_dl.cpp
src/symbols/symbols_with_libbacktrace.cpp
src/symbols/symbols_with_nothing.cpp
src/unwind/unwind_with_execinfo.cpp
src/unwind/unwind_with_nothing.cpp
src/unwind/unwind_with_unwind.cpp
src/unwind/unwind_with_winapi.cpp
# Add /include files to target
# This is solely for IDE benefit, doesn't affect building
target_sources(
${target_name} PRIVATE
include/cpptrace/cpptrace.hpp
include/ctrace/ctrace.h
)
# TODO: This feels like a hack.
if(CPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE OR CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE)
add_library(cpptrace SHARED ${sources} include/cpptrace/cpptrace.hpp)
else()
add_library(cpptrace ${sources} include/cpptrace/cpptrace.hpp)
endif()
# add /src files to target
target_sources(
${target_name} PRIVATE
# src
src/binary/elf.cpp
src/binary/mach-o.cpp
src/binary/module_base.cpp
src/binary/object.cpp
src/binary/pe.cpp
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
src/symbols/symbols_with_dbghelp.cpp
src/symbols/symbols_with_dl.cpp
src/symbols/symbols_with_libbacktrace.cpp
src/symbols/symbols_with_libdwarf.cpp
src/symbols/symbols_with_nothing.cpp
src/unwind/unwind_with_dbghelp.cpp
src/unwind/unwind_with_execinfo.cpp
src/unwind/unwind_with_libunwind.cpp
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(
cpptrace
${target_name}
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include/>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/cpptrace/>
)
# TODO
target_compile_features(cpptrace PRIVATE cxx_range_for cxx_constexpr cxx_nullptr cxx_static_assert)
set_target_properties(
cpptrace
PROPERTIES
CXX_STANDARD_REQUIRED TRUE
CXX_EXTENSIONS OFF
)
target_compile_options(
cpptrace
target_include_directories(
${target_name}
PRIVATE
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wall -Wextra -Werror=return-type -Wshadow -Wundef>
$<$<CXX_COMPILER_ID:GNU>:-Wuseless-cast -Wnonnull-compare>
$<$<CXX_COMPILER_ID:MSVC>:/W4 /WX /permissive->
src
)
# =============================================== Apply options to build ===============================================
set(
warning_options
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wall -Wextra -Werror=return-type -Wundef>
$<$<CXX_COMPILER_ID:GNU>:-Wuseless-cast -Wmaybe-uninitialized>
$<$<CXX_COMPILER_ID:MSVC>:/W4 /permissive->
)
function(check_backtrace_error)
if(NOT HAS_BACKTRACE)
if(NOT "${CPPTRACE_BACKTRACE_PATH}" STREQUAL "")
message(WARNING "Cpptrace: CPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE specified but libbacktrace doesn't appear installed or configured properly.")
else()
message(WARNING "Cpptrace: CPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE specified but libbacktrace doesn't appear installed or configured properly. You may need to specify CPPTRACE_BACKTRACE_PATH.")
endif()
endif()
endfunction()
# Full
if(CPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE)
check_backtrace_error()
target_compile_definitions(cpptrace PUBLIC CPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE)
target_link_libraries(cpptrace PRIVATE backtrace)
if(CPPTRACE_WERROR_BUILD)
set(
warning_options
${warning_options}
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Werror -Wpedantic>
$<$<CXX_COMPILER_ID:MSVC>:/WX>
)
endif()
if(CPPTRACE_FULL_TRACE_WITH_STACKTRACE)
if(NOT HAS_STACKTRACE)
message(WARNING "Cpptrace: CPPTRACE_FULL_TRACE_WITH_STACKTRACE specified but <stacktrace> doesn't seem to be available.")
endif()
target_compile_definitions(cpptrace PUBLIC CPPTRACE_FULL_TRACE_WITH_STACKTRACE)
target_link_libraries(cpptrace PRIVATE "${STACKTRACE_LINK_LIB}")
target_compile_options(
${target_name}
PRIVATE
${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")
target_compile_definitions(${target_name} PUBLIC CPPTRACE_STATIC_DEFINE)
set(CPPTRACE_STATIC_DEFINE TRUE)
endif()
# ---- Library Properties ----
# Hide all symbols by default
# Use SameMajorVersion versioning for shared library runtime linker lookup
set_target_properties(
${target_name} PROPERTIES
CXX_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN YES
VERSION "${PROJECT_VERSION}"
SOVERSION "${PROJECT_VERSION_MAJOR}"
EXPORT_NAME "cpptrace"
OUTPUT_NAME "cpptrace"
POSITION_INDEPENDENT_CODE ${CPPTRACE_POSITION_INDEPENDENT_CODE}
)
# Header files generated by CMake
target_include_directories(
${target_name} SYSTEM PUBLIC
"$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/include>"
)
# Header files from /include
target_include_directories(
${target_name} ${warning_guard} PUBLIC
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>"
)
# Require C++11 support
target_compile_features(
${target_name}
PRIVATE cxx_std_11
)
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()
# =================================================== Back-end setup ===================================================
if(HAS_CXX_EXCEPTION_TYPE)
target_compile_definitions(${target_name} PUBLIC CPPTRACE_HAS_CXX_EXCEPTION_TYPE)
endif()
if(HAS_DL_FIND_OBJECT)
target_compile_definitions(${target_name} PUBLIC CPPTRACE_HAS_DL_FIND_OBJECT)
endif()
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)
check_backtrace_error()
target_compile_definitions(cpptrace PUBLIC CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE)
target_link_libraries(cpptrace PRIVATE backtrace)
if(NOT HAS_BACKTRACE)
if(NOT "${CPPTRACE_BACKTRACE_PATH}" STREQUAL "")
message(WARNING "Cpptrace: Using libbacktrace for symbols but libbacktrace doesn't appear installed or configured properly.")
else()
message(WARNING "Cpptrace: Using libbacktrace for symbols but libbacktrace doesn't appear installed or configured properly. You may need to specify CPPTRACE_BACKTRACE_PATH.")
endif()
endif()
target_compile_definitions(${target_name} PUBLIC CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE)
target_link_libraries(${target_name} PRIVATE backtrace ${CMAKE_DL_LIBS})
endif()
if(CPPTRACE_GET_SYMBOLS_WITH_LIBDL)
target_compile_definitions(cpptrace PUBLIC CPPTRACE_GET_SYMBOLS_WITH_LIBDL)
target_link_libraries(cpptrace PRIVATE dl)
target_compile_definitions(${target_name} PUBLIC CPPTRACE_GET_SYMBOLS_WITH_LIBDL)
target_link_libraries(${target_name} PRIVATE ${CMAKE_DL_LIBS})
endif()
if(CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE)
# set(CPPTRACE_ADDR2LINE_PATH "" CACHE STRING "Absolute path to the addr2line executable you want to use.")
# option(CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH "" OFF)
if(CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH)
target_compile_definitions(cpptrace PUBLIC CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH)
target_compile_definitions(${target_name} PUBLIC CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH)
else()
if("${CPPTRACE_ADDR2LINE_PATH}" STREQUAL "")
if(APPLE)
@ -327,21 +292,132 @@ if(CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE)
set(CPPTRACE_ADDR2LINE_PATH_FINAL "${CPPTRACE_ADDR2LINE_PATH}")
endif()
message(STATUS "Cpptrace: Using ${CPPTRACE_ADDR2LINE_PATH_FINAL} for addr2line path")
target_compile_definitions(cpptrace PUBLIC CPPTRACE_ADDR2LINE_PATH="${CPPTRACE_ADDR2LINE_PATH_FINAL}")
target_compile_definitions(${target_name} PUBLIC CPPTRACE_ADDR2LINE_PATH="${CPPTRACE_ADDR2LINE_PATH_FINAL}")
endif()
target_compile_definitions(cpptrace PUBLIC CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE)
target_compile_definitions(${target_name} PUBLIC CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE)
if(UNIX)
target_link_libraries(cpptrace PRIVATE dl)
target_link_libraries(${target_name} PRIVATE ${CMAKE_DL_LIBS})
endif()
endif()
if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF)
target_compile_definitions(${target_name} PUBLIC CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF)
if(CPPTRACE_USE_EXTERNAL_LIBDWARF)
if(NOT CPPTRACE_FIND_LIBDWARF_WITH_PKGCONFIG)
find_package(libdwarf REQUIRED)
else()
find_package(PkgConfig)
pkg_check_modules(LIBDWARF REQUIRED libdwarf)
endif()
else()
include(FetchContent)
# First, dependencies: Zstd and zlib (currently relying on system zlib)
if(CPPTRACE_USE_EXTERNAL_ZSTD)
find_package(zstd)
else()
cmake_policy(SET CMP0074 NEW)
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)
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 ${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,
# libdwarf::dwarf-shared, libdwarf::dwarf, then libdwarf
# libdwarf v0.8.0 installs with the target libdwarf::dwarf somehow, despite creating libdwarf::dwarf-static or
# libdwarf::dwarf-shared under fetchcontent
if(TARGET libdwarf::dwarf-static)
set(LIBDWARF_LIBRARIES libdwarf::dwarf-static)
elseif(TARGET libdwarf::dwarf-shared)
set(LIBDWARF_LIBRARIES libdwarf::dwarf-shared)
elseif(TARGET libdwarf::dwarf)
set(LIBDWARF_LIBRARIES libdwarf::dwarf)
elseif(TARGET libdwarf)
set(LIBDWARF_LIBRARIES 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
# it's libdwarf/libdwarf.h and libdwarf/dwarf.h or just libdwarf.h and dwarf.h
include(CheckIncludeFileCXX)
# libdwarf's cmake doesn't properly set variables to indicate where its libraries live
if(NOT CPPTRACE_FIND_LIBDWARF_WITH_PKGCONFIG)
get_target_property(LIBDWARF_INCLUDE_DIRS ${LIBDWARF_LIBRARIES} INTERFACE_INCLUDE_DIRECTORIES)
else()
target_include_directories(${target_name} PRIVATE ${LIBDWARF_INCLUDE_DIRS})
endif()
set(CMAKE_REQUIRED_INCLUDES ${LIBDWARF_INCLUDE_DIRS})
CHECK_INCLUDE_FILE_CXX("libdwarf/libdwarf.h" LIBDWARF_IS_NESTED)
CHECK_INCLUDE_FILE_CXX("libdwarf.h" LIBDWARF_IS_NOT_NESTED)
# check_include_file("libdwarf/libdwarf.h" LIBDWARF_IS_NESTED)
# check_support(LIBDWARF_IS_NESTED nested_libdwarf_include.cpp "" "" "")
if(${LIBDWARF_IS_NESTED})
target_compile_definitions(${target_name} PRIVATE CPPTRACE_USE_NESTED_LIBDWARF_HEADER_PATH)
elseif(NOT LIBDWARF_IS_NOT_NESTED)
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)
target_link_libraries(${target_name} PRIVATE ${CMAKE_DL_LIBS})
endif()
endif()
if(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP)
target_compile_definitions(cpptrace PUBLIC CPPTRACE_GET_SYMBOLS_WITH_DBGHELP)
target_link_libraries(cpptrace PRIVATE dbghelp)
target_compile_definitions(${target_name} PUBLIC CPPTRACE_GET_SYMBOLS_WITH_DBGHELP)
target_link_libraries(${target_name} PRIVATE dbghelp)
endif()
if(CPPTRACE_GET_SYMBOLS_WITH_NOTHING)
target_compile_definitions(cpptrace PUBLIC CPPTRACE_GET_SYMBOLS_WITH_NOTHING)
target_compile_definitions(${target_name} PUBLIC CPPTRACE_GET_SYMBOLS_WITH_NOTHING)
endif()
# Unwinding
@ -349,22 +425,67 @@ if(CPPTRACE_UNWIND_WITH_UNWIND)
if(NOT HAS_UNWIND)
message(WARNING "Cpptrace: CPPTRACE_UNWIND_WITH_UNWIND specified but libgcc unwind doesn't seem to be available.")
endif()
target_compile_definitions(cpptrace PUBLIC CPPTRACE_UNWIND_WITH_UNWIND)
target_compile_definitions(${target_name} PUBLIC CPPTRACE_UNWIND_WITH_UNWIND)
endif()
if(CPPTRACE_UNWIND_WITH_LIBUNWIND)
find_package(PkgConfig)
if(PkgConfig_FOUND)
pkg_check_modules(LIBUNWIND QUIET libunwind)
if(libunwind_FOUND)
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()
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")
find_library(LIBUNWIND NAMES unwind libunwind libunwind8 libunwind.so.8 REQUIRED PATHS "/usr/lib/x86_64-linux-gnu/")
if(LIBUNWIND)
set(libunwind_FOUND TRUE)
endif()
if(NOT libunwind_FOUND)
# message(FATAL_ERROR "Unable to locate libunwind")
# Try to link with it if it's where it should be
# This path can be entered if libunwind was installed via the system package manager, sometimes. I probably messed
# up the find_library above.
set(LIBUNWIND_LDFLAGS "-lunwind")
endif()
if(NOT LIBUNWIND_LDFLAGS)
set(LIBUNWIND_LDFLAGS "${LIBUNWIND}")
endif()
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()
if(CPPTRACE_UNWIND_WITH_EXECINFO)
if(NOT HAS_EXECINFO)
message(WARNING "Cpptrace: CPPTRACE_UNWIND_WITH_EXECINFO specified but execinfo.h doesn't seem to be available.")
endif()
target_compile_definitions(cpptrace PUBLIC CPPTRACE_UNWIND_WITH_EXECINFO)
target_compile_definitions(${target_name} PUBLIC CPPTRACE_UNWIND_WITH_EXECINFO)
endif()
if(CPPTRACE_UNWIND_WITH_WINAPI)
target_compile_definitions(cpptrace PUBLIC CPPTRACE_UNWIND_WITH_WINAPI)
target_compile_definitions(${target_name} PUBLIC CPPTRACE_UNWIND_WITH_WINAPI)
endif()
if(CPPTRACE_UNWIND_WITH_DBGHELP)
if(NOT HAS_STACKWALK)
message(WARNING "Cpptrace: CPPTRACE_UNWIND_WITH_DBGHELP specified but dbghelp stackwalk64 doesn't seem to be available.")
endif()
target_compile_definitions(${target_name} PUBLIC CPPTRACE_UNWIND_WITH_DBGHELP)
target_link_libraries(${target_name} PRIVATE dbghelp)
endif()
if(CPPTRACE_UNWIND_WITH_NOTHING)
target_compile_definitions(cpptrace PUBLIC CPPTRACE_UNWIND_WITH_NOTHING)
target_compile_definitions(${target_name} PUBLIC CPPTRACE_UNWIND_WITH_NOTHING)
endif()
# Demangling
@ -372,144 +493,45 @@ if(CPPTRACE_DEMANGLE_WITH_CXXABI)
if(NOT HAS_CXXABI)
message(WARNING "Cpptrace: CPPTRACE_DEMANGLE_WITH_CXXABI specified but cxxabi.h doesn't seem to be available.")
endif()
target_compile_definitions(cpptrace PUBLIC CPPTRACE_DEMANGLE_WITH_CXXABI)
target_compile_definitions(${target_name} PUBLIC CPPTRACE_DEMANGLE_WITH_CXXABI)
endif()
if(CPPTRACE_DEMANGLE_WITH_WINAPI)
target_compile_definitions(${target_name} PUBLIC CPPTRACE_DEMANGLE_WITH_WINAPI)
target_link_libraries(${target_name} PRIVATE dbghelp)
endif()
if(CPPTRACE_DEMANGLE_WITH_NOTHING)
target_compile_definitions(cpptrace PUBLIC CPPTRACE_DEMANGLE_WITH_NOTHING)
target_compile_definitions(${target_name} PUBLIC CPPTRACE_DEMANGLE_WITH_NOTHING)
endif()
if(NOT "${CPPTRACE_BACKTRACE_PATH}" STREQUAL "")
target_compile_definitions(cpptrace PUBLIC CPPTRACE_BACKTRACE_PATH=${CPPTRACE_BACKTRACE_PATH})
target_compile_definitions(${target_name} PUBLIC CPPTRACE_BACKTRACE_PATH=${CPPTRACE_BACKTRACE_PATH})
endif()
if(NOT "${CPPTRACE_HARD_MAX_FRAMES}" STREQUAL "")
target_compile_definitions(cpptrace PUBLIC CPPTRACE_HARD_MAX_FRAMES=${CPPTRACE_HARD_MAX_FRAMES})
target_compile_definitions(${target_name} PUBLIC CPPTRACE_HARD_MAX_FRAMES=${CPPTRACE_HARD_MAX_FRAMES})
endif()
# ======================================================================================================================
if(CMAKE_BUILD_TYPE STREQUAL "")
message(FATAL_ERROR "Setting CMAKE_BUILD_TYPE is required")
endif()
# ====================================================== Install =======================================================
if(NOT CMAKE_SKIP_INSTALL_RULES)
include(CMakePackageConfigHelpers)
install(
TARGETS cpptrace
EXPORT cpptrace_targets
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
install(
FILES
include/cpptrace/cpptrace.hpp
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/cpptrace
)
export(
EXPORT cpptrace_targets
FILE ${CMAKE_CURRENT_BINARY_DIR}/cpptrace/cpptrace_targets.cmake
NAMESPACE cpptrace::
)
configure_package_config_file(
cmake/cpptrace-config.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/cpptrace/cpptrace-config.cmake
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cpptrace
)
write_basic_package_version_file(
${CMAKE_CURRENT_BINARY_DIR}/cpptrace/cpptrace-config-version.cmake
VERSION ${PACKAGE_VERSION}
COMPATIBILITY SameMajorVersion
)
install(
EXPORT cpptrace_targets
FILE cpptrace_targets.cmake
NAMESPACE cpptrace::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cpptrace
)
install(
FILES
${CMAKE_CURRENT_BINARY_DIR}/cpptrace/cpptrace-config.cmake
${CMAKE_CURRENT_BINARY_DIR}/cpptrace/cpptrace-config-version.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cpptrace
)
include(cmake/InstallRules.cmake)
endif()
if(CPPTRACE_BUILD_TEST)
add_executable(test test/test.cpp)
target_link_libraries(test PRIVATE cpptrace)
# Clang has been fast to adopt dwarf 5, other tools (e.g. addr2line from binutils) have not
check_cxx_compiler_flag("-gdwarf-4" HAS_DWARF4)
if(HAS_DWARF4)
target_compile_options(test PRIVATE "$<$<CONFIG:Debug>:-gdwarf-4>")
endif()
if(CPPTRACE_BUILD_TEST_RDYNAMIC)
set_property(TARGET test PROPERTY ENABLE_EXPORTS ON)
# ================================================== Demo/test/tools ===================================================
if(CPPTRACE_BUILD_TESTING)
if(PROJECT_IS_TOP_LEVEL)
enable_testing()
endif()
add_subdirectory(test)
endif()
if(CPPTRACE_BUILD_DEMO)
add_executable(demo test/demo.cpp)
target_link_libraries(demo PRIVATE cpptrace)
# Clang has been fast to adopt dwarf 5, other tools (e.g. addr2line from binutils) have not
check_cxx_compiler_flag("-gdwarf-4" HAS_DWARF4)
if(HAS_DWARF4)
target_compile_options(demo PRIVATE "$<$<CONFIG:Debug>:-gdwarf-4>")
endif()
if(CPPTRACE_BUILD_TEST_RDYNAMIC)
set_property(TARGET demo PROPERTY ENABLE_EXPORTS ON)
endif()
if(CPPTRACE_BUILD_BENCHMARKING)
add_subdirectory(benchmarking)
endif()
if(CPPTRACE_BUILD_SPEEDTEST)
if(CPPTRACE_BUILD_SPEEDTEST_DWARF4)
check_cxx_compiler_flag("-gdwarf-4" HAS_DWARF4)
if(HAS_DWARF4)
add_compile_options("$<$<CONFIG:Debug>:-gdwarf-4>")
#target_compile_options(speedtest PRIVATE "$<$<CONFIG:Debug>:-gdwarf-4>")
#target_compile_options(googletest INTERFACE "$<$<CONFIG:Debug>:-gdwarf-4>")
endif()
endif()
if(CPPTRACE_BUILD_SPEEDTEST_DWARF5)
check_cxx_compiler_flag("-gdwarf-5" HAS_DWARF5)
if(HAS_DWARF5)
add_compile_options("$<$<CONFIG:Debug>:-gdwarf-5>")
#target_compile_options(speedtest PRIVATE "$<$<CONFIG:Debug>:-gdwarf-4>")
#target_compile_options(googletest INTERFACE "$<$<CONFIG:Debug>:-gdwarf-4>")
endif()
endif()
include(FetchContent)
FetchContent_Declare(
googletest
URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip
)
# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)
add_executable(speedtest test/speedtest.cpp)
target_link_libraries(
speedtest
PRIVATE
GTest::gtest_main
cpptrace
)
if(WIN32)
add_custom_command(
TARGET speedtest POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_RUNTIME_DLLS:speedtest> $<TARGET_FILE_DIR:speedtest>
COMMAND_EXPAND_LISTS
)
endif()
if(CPPTRACE_BUILD_TOOLS)
add_subdirectory(tools)
endif()

37
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,37 @@
# Contributing
Welcome, thank you for your interest in the project!
## Getting started
Contributions are always welcome. If you have not already, consider joining the community discord
(linked in the README). There is discussion about library development there as well as a development
roadmap. Github issues are also a good place to start.
I'm happy to merge fixes, improvements, and features as well as help with getting pull requests
(PRs) over the finish line. That being said, I can't merge stylistic changes,
premature-optimizations, or micro-optimizations.
When contributing, please try to match the current code style in the codebase. Style doesn't matter
too much ultimately but consistency within a codebase is important.
## Local development
The easiest way to get started with local development is running `make debug` (which automatically configures cmake and
builds). Note: This requires ninja at the moment.
For more control over how the library is built you can manually build with cmake:
`cmake ..` in a `build/` folder along with any cmake configurations you desire. Then run `make -j` or `ninja` or
`msbuild cpptrace.sln`.
Some useful configurations:
- `-DCMAKE_BUILD_TYPE=Debug|Release|RelWithDebInfo`: Build in debug / release / etc.
- `-DBUILD_SHARED_LIBS=On`: Build shared library
- `-DCPPTRACE_SANITIZER_BUILD=On`: Turn on sanitizers
- `-DCPPTRACE_BUILD_TESTING=On`: Build small test and demo program
## Testing
Unfortunately because this library is so platform-dependent the best way to test thoroughly is with
github's CI. This will happen automatically when you open a PR.

View File

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2023 Jeremy Rifkin
Copyright (c) 2023-2024 Jeremy Rifkin
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction,

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~"
]
]
}
}
}
}

63
Makefile Normal file
View File

@ -0,0 +1,63 @@
default: help
# The general philosophy and functionality of this makefile is shamelessly stolen from compiler explorer
help: # with thanks to Ben Rady
@grep -E '^[0-9a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
.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: configure-debug ## build in debug mode
cmake --build build
.PHONY: release
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 -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 -DCPPTRACE_BUILD_TOOLS=On
cmake --build build --config RelWithDebInfo
.PHONY: clean
clean: ## clean
rm -rf build
.PHONY: test
test: debug ## test
cd build && ninja test
.PHONY: test-release
test-release: release ## test-release
cd build && ninja test
# .PHONY: test-msvc
# test-msvc: debug-msvc ## test
# cmake --build build --target RUN_TESTS --config Debug
# .PHONY: test-msvc-release
# test-msvc-release: release-msvc ## test-release
# cmake --build build --target RUN_TESTS --config Release

1301
README.md

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@ We take security seriously and I'm grateful for reports of security vulnerabilit
If the vulnerability can be reported without revealing exploitable specifics, please open an issue.
If the vulnerability can't be reported publically without leaving an obvious exploit in the public eye please email me
If the vulnerability can't be reported publicly without leaving an obvious exploit in the public eye please email me
at jeremy@rifkin.dev or reach out to me on [discord](https://discord.gg/7kv5AuCndG).
I will do my best to get back to you within a day.

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,279 +0,0 @@
import argparse
import os
import platform
import shutil
import subprocess
import sys
from colorama import Fore, Back, Style
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")
os.mkdir("build")
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"-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:
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"-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++":
run_command("make", "-j")
else:
run_command("msbuild", "cpptrace.sln")
os.chdir("..")
print()
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")
os.mkdir("build")
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_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:
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']}"
]
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++":
run_command("make", "-j")
else:
run_command("msbuild", "cpptrace.sln")
os.chdir("..")
print()
def main():
parser = argparse.ArgumentParser(
prog="Build in all configs",
description="Try building the library in all possible configurations for the current host"
)
if platform.system() == "Linux":
matrix = {
"compiler": ["g++-10", "clang++-14"],
"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_ADDR2LINE",
"CPPTRACE_GET_SYMBOLS_WITH_NOTHING",
],
"demangle": [
"CPPTRACE_DEMANGLE_WITH_CXXABI",
"CPPTRACE_DEMANGLE_WITH_NOTHING",
],
}
exclude = []
run_matrix(matrix, exclude, build)
matrix = {
"compiler": ["g++-10", "clang++-14"],
"target": ["Debug"],
"std": ["11", "20"],
"config": ["-DCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE=On", ""]
}
exclude = []
run_matrix(matrix, exclude, build_full_or_auto)
if platform.system() == "Darwin":
matrix = {
"compiler": ["g++-13", "clang++"],
"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_ADDR2LINE",
"CPPTRACE_GET_SYMBOLS_WITH_NOTHING",
],
"demangle": [
"CPPTRACE_DEMANGLE_WITH_CXXABI",
"CPPTRACE_DEMANGLE_WITH_NOTHING",
]
}
exclude = []
run_matrix(matrix, exclude, build)
matrix = {
"compiler": ["g++-13", "clang++"],
"target": ["Debug"],
"std": ["11", "20"],
"config": [""]
}
exclude = []
run_matrix(matrix, exclude, build_full_or_auto)
if platform.system() == "Windows":
parser.add_argument(
"--clang-only",
action="store_true"
)
parser.add_argument(
"--msvc-only",
action="store_true"
)
parser.add_argument(
"--mingw-only",
action="store_true"
)
args = parser.parse_args()
compilers = ["cl", "clang++", "g++"]
if args.clang_only:
compilers = ["clang++"]
if args.msvc_only:
compilers = ["cl"]
if args.mingw_only:
compilers = ["g++"]
matrix = {
"compiler": compilers,
"target": ["Debug"],
"std": ["11", "20"],
"unwind": [
"CPPTRACE_UNWIND_WITH_WINAPI",
"CPPTRACE_UNWIND_WITH_UNWIND",
"CPPTRACE_UNWIND_WITH_NOTHING",
],
"symbols": [
"CPPTRACE_GET_SYMBOLS_WITH_DBGHELP",
"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_DBGHELP",
"compiler": "g++"
},
]
run_matrix(matrix, exclude, build)
matrix = {
"compiler": compilers,
"target": ["Debug"],
"std": ["11", "20"],
"config": [""]
}
exclude = [
{
"config": "-DCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE=On"
}
]
run_matrix(matrix, exclude, build_full_or_auto)
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

@ -0,0 +1,26 @@
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 checkout FETCH_HEAD
cd build/cmake
mkdir build
cd build
cmake .. -DZSTD_BUILD_SHARED=On -DZSTD_BUILD_SHARED=Off -DZSTD_LEGACY_SUPPORT=Off -DZSTD_BUILD_PROGRAMS=Off -DZSTD_BUILD_CONTRIB=Off -DZSTD_BUILD_TESTS=Off -G"Unix Makefiles"
make -j
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
git checkout FETCH_HEAD
mkdir build
cd build
cmake .. -DPIC_ALWAYS=TRUE -DBUILD_DWARFDUMP=FALSE -G"Unix Makefiles"
make -j
make install

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

25
ci/setup-prerequisites.sh Executable file
View File

@ -0,0 +1,25 @@
#!/bin/bash
sudo apt install libgtest-dev
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
git checkout FETCH_HEAD
mkdir build
cd build
cmake .. -DPIC_ALWAYS=TRUE -DBUILD_DWARFDUMP=FALSE
make -j
sudo make install

View File

@ -1,5 +1,6 @@
import sys
import re
import platform
def main():
output = sys.stdin.read()
@ -15,9 +16,14 @@ def main():
clang = any(["clang" in arg for arg in sys.argv[1:]])
# Somehow -gdwarf-4 clang is fast after a completely unrelated PR? Weird. Something to do with static linking...?
# https://github.com/jeremy-rifkin/cpptrace/pull/22
expect_slow = dwarf4 and not clang
expect_slow = False
threshold = 100 # ms
if platform.system() == "Windows":
# For some reason SymInitialize is super slow on github's windows runner and it alone takes 250ms. Nothing we
# can do about that.
threshold = 350 # ms
else:
threshold = 100 # ms
if expect_slow:
if time > threshold:

View File

@ -11,10 +11,11 @@ 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:
return compiler.replace("clang++", "clang").replace("g++", "gcc")
MAX_LINE_DIFF = 2
def similarity(name: str, target: List[str]) -> int:
@ -27,7 +28,7 @@ def similarity(name: str, target: List[str]) -> int:
return -1
return c
def output_matches(output: str, params: Tuple[str]):
def output_matches(raw_output: str, params: Tuple[str]):
target = []
if params[0].startswith("gcc") or params[0].startswith("g++"):
@ -69,264 +70,280 @@ def output_matches(output: str, params: Tuple[str]):
print(f"Reading from {file}")
with open(os.path.join(expected_dir, file), "r") as f:
expected = f.read()
raw_expected = f.read()
if output.strip() == "":
if raw_output.strip() == "":
print(f"Error: No output from test")
sys.exit(1)
return False
expected = [line.strip().split("||") for line in expected.split("\n")]
output = [line.strip().split("||") for line in output.split("\n")]
expected = [line.strip().split("||") for line in raw_expected.split("\n")]
output = [line.strip().split("||") for line in raw_output.split("\n")]
max_line_diff = MAX_LINE_DIFF
if "CPPTRACE_UNWIND_WITH_UNWIND" in params:
max_line_diff = 0
if "CPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE" in params:
max_line_diff = 0
if "CPPTRACE_FULL_TRACE_WITH_STACKTRACE" in params:
max_line_diff = 0
max_line_diff = 0
errored = False
for i, ((output_file, output_line, output_symbol), (expected_file, expected_line, expected_symbol)) in enumerate(zip(output, expected)):
if output_file != expected_file:
print(f"Error: File name mismatch on line {i + 1}, found \"{output_file}\" expected \"{expected_file}\"")
errored = True
if abs(int(output_line) - int(expected_line)) > max_line_diff:
print(f"Error: File line mismatch on line {i + 1}, found {output_line} expected {expected_line}")
errored = True
if output_symbol != expected_symbol:
print(f"Error: File symbol mismatch on line {i + 1}, found \"{output_symbol}\" expected \"{expected_symbol}\"")
errored = True
if expected_symbol == "main" or expected_symbol == "main()":
break
try:
for i, ((output_file, output_line, output_symbol), (expected_file, expected_line, expected_symbol)) in enumerate(zip(output, expected)):
if output_file != expected_file:
print(f"Error: File name mismatch on line {i + 1}, found \"{output_file}\" expected \"{expected_file}\"")
errored = True
if abs(int(output_line) - int(expected_line)) > max_line_diff:
print(f"Error: File line mismatch on line {i + 1}, found {output_line} expected {expected_line}")
errored = True
if output_symbol != expected_symbol:
print(f"Error: File symbol mismatch on line {i + 1}, found \"{output_symbol}\" expected \"{expected_symbol}\"")
errored = True
if expected_symbol == "main" or expected_symbol == "main()":
break
except ValueError:
print("ValueError during output checking")
errored = True
if errored:
print("Output:")
print(raw_output)
print("Expected:")
print(raw_expected)
return not errored
def run_command(*args: List[str]):
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}")
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("[🔴 Test command failed]")
print("stderr:")
print(test_stderr.decode("utf-8"), end="")
print("stdout:")
print(test_stdout.decode("utf-8"), end="")
failed = True
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}")
else:
print(f"{Fore.RED}{Style.BRIGHT}Test failed{Style.RESET_ALL}")
failed = True
def build(matrix):
def build(runner: MatrixRunner):
matrix = runner.current_config()
if platform.system() != "Windows":
args = [
"cmake",
"..",
f"-DCMAKE_BUILD_TYPE={matrix['target']}",
f"-DCMAKE_CXX_COMPILER={matrix['compiler']}",
f"-DCMAKE_C_COMPILER={get_c_compiler_counterpart(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",
"-DCPPTRACE_BUILD_TEST=On"
"-DCPPTRACE_BUILD_TESTING=On",
"-DCPPTRACE_SKIP_UNIT=On",
f"-DBUILD_SHARED_LIBS={matrix['shared']}"
]
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",
"..",
f"-DCMAKE_BUILD_TYPE={matrix['target']}",
f"-DCMAKE_CXX_COMPILER={matrix['compiler']}",
f"-DCMAKE_C_COMPILER={get_c_compiler_counterpart(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_BUILD_TEST=On"
"-DCPPTRACE_BUILD_TESTING=On",
"-DCPPTRACE_SKIP_UNIT=On",
f"-DBUILD_SHARED_LIBS={matrix['shared']}"
]
if matrix["compiler"] == "g++":
args.append("-GUnix Makefiles")
succeeded = run_command(*args)
succeeded = runner.run_command(*args)
if succeeded:
if matrix["compiler"] == "g++":
run_command("make", "-j")
return runner.run_command("make", "-j")
else:
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",
"..",
f"-DCMAKE_BUILD_TYPE={matrix['target']}",
f"-DCMAKE_CXX_COMPILER={matrix['compiler']}",
f"-DCMAKE_C_COMPILER={get_c_compiler_counterpart(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",
"-DCPPTRACE_BUILD_TEST=On"
"-DCPPTRACE_BUILD_TESTING=On",
"-DCPPTRACE_SKIP_UNIT=On",
f"-DBUILD_SHARED_LIBS={matrix['shared']}"
]
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",
"..",
f"-DCMAKE_BUILD_TYPE={matrix['target']}",
f"-DCMAKE_CXX_COMPILER={matrix['compiler']}",
f"-DCMAKE_C_COMPILER={get_c_compiler_counterpart(matrix['compiler'])}",
f"-DCMAKE_CXX_STANDARD={matrix['std']}",
"-DCPPTRACE_BUILD_TEST=On"
f"-DCPPTRACE_USE_EXTERNAL_LIBDWARF=On",
f"-DCPPTRACE_USE_EXTERNAL_ZSTD=On",
f"-DCPPTRACE_WERROR_BUILD=On",
"-DCPPTRACE_BUILD_TESTING=On",
"-DCPPTRACE_SKIP_UNIT=On",
f"-DBUILD_SHARED_LIBS={matrix['shared']}"
]
if matrix["config"] != "":
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++":
run_command("make", "-j")
return runner.run_command("make", "-j")
else:
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":
run_test(
"./test",
return run_test(
runner,
"./integration",
(matrix["compiler"], matrix["unwind"], matrix["symbols"], matrix["demangle"])
)
else:
if matrix["compiler"] == "g++":
run_test(
f".\\test.exe",
return run_test(
runner,
f".\\integration.exe",
(matrix["compiler"], matrix["unwind"], matrix["symbols"], matrix["demangle"])
)
else:
run_test(
f".\\{matrix['target']}\\test.exe",
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":
run_test(
"./test",
return run_test(
runner,
"./integration",
(matrix["compiler"],)
)
else:
if matrix["compiler"] == "g++":
run_test(
f".\\test.exe",
return run_test(
runner,
f".\\integration.exe",
(matrix["compiler"],)
)
else:
run_test(
f".\\{matrix['target']}\\test.exe",
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")
shutil.rmtree("build", ignore_errors=True)
os.mkdir("build")
if not os.path.exists("build"):
os.mkdir("build")
os.chdir("build")
if build(matrix):
test(matrix)
good = False
if build(runner):
good = test(runner)
os.chdir("..")
print()
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}")
return good
def build_and_test_full_or_auto(runner: MatrixRunner):
matrix = runner.current_config()
if os.path.exists("build"):
shutil.rmtree("build")
shutil.rmtree("build", ignore_errors=True)
os.mkdir("build")
if not os.path.exists("build"):
os.mkdir("build")
os.chdir("build")
if build_full_or_auto(matrix):
test_full_or_auto(matrix)
good = False
if build_full_or_auto(runner):
good = test_full_or_auto(runner)
os.chdir("..")
print()
def main():
parser = argparse.ArgumentParser(
prog="Build in all configs",
description="Try building the library in all possible configurations for the current host"
)
return good
if platform.system() == "Linux":
def run_linux_matrix(compilers: list, shared: bool):
MatrixRunner(
matrix = {
"compiler": ["g++-10", "clang++-14"],
"compiler": compilers,
"target": ["Debug"],
"std": ["11", "20"],
"unwind": [
"CPPTRACE_UNWIND_WITH_EXECINFO",
"CPPTRACE_UNWIND_WITH_UNWIND",
"CPPTRACE_UNWIND_WITH_LIBUNWIND",
#"CPPTRACE_UNWIND_WITH_NOTHING",
],
"symbols": [
"CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE",
# Disabled due to libbacktrace bug
# "CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE",
"CPPTRACE_GET_SYMBOLS_WITH_LIBDL",
"CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE",
"CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF",
#"CPPTRACE_GET_SYMBOLS_WITH_NOTHING",
],
"demangle": [
"CPPTRACE_DEMANGLE_WITH_CXXABI",
#"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": ["g++-10", "clang++-14"],
"compiler": compilers,
"target": ["Debug"],
"std": ["11", "20"],
"config": ["-DCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE=On", ""]
}
"config": [""],
"shared": ["On" if shared else "Off"]
},
exclude = []
run_matrix(matrix, exclude, build_and_test_full_or_auto)
if platform.system() == "Darwin":
).run(build_and_test_full_or_auto)
def run_macos_matrix(compilers: list, shared: bool):
MatrixRunner(
matrix = {
"compiler": ["g++-13", "clang++"],
"compiler": compilers,
"target": ["Debug"],
"std": ["11", "20"],
"unwind": [
@ -338,65 +355,54 @@ def main():
#"CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE",
"CPPTRACE_GET_SYMBOLS_WITH_LIBDL",
"CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE",
"CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF",
#"CPPTRACE_GET_SYMBOLS_WITH_NOTHING",
],
"demangle": [
"CPPTRACE_DEMANGLE_WITH_CXXABI",
#"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": ["g++-13", "clang++"],
"compiler": compilers,
"target": ["Debug"],
"std": ["11", "20"],
"config": [""]
}
"config": [""],
"shared": ["On" if shared else "Off"]
},
exclude = []
run_matrix(matrix, exclude, build_and_test_full_or_auto)
if platform.system() == "Windows":
parser.add_argument(
"--clang-only",
action="store_true"
)
parser.add_argument(
"--msvc-only",
action="store_true"
)
parser.add_argument(
"--mingw-only",
action="store_true"
)
args = parser.parse_args()
compilers = ["cl", "clang++", "g++"]
if args.clang_only:
compilers = ["clang++"]
if args.msvc_only:
compilers = ["cl"]
if args.mingw_only:
compilers = ["g++"]
).run(build_and_test_full_or_auto)
def run_windows_matrix(compilers: list, shared: bool):
MatrixRunner(
matrix = {
"compiler": compilers,
"target": ["Debug"],
"std": ["11", "20"],
"unwind": [
"CPPTRACE_UNWIND_WITH_WINAPI",
"CPPTRACE_UNWIND_WITH_UNWIND",
"CPPTRACE_UNWIND_WITH_DBGHELP",
"CPPTRACE_UNWIND_WITH_UNWIND", # Broken on github actions for some reason
#"CPPTRACE_UNWIND_WITH_NOTHING",
],
"symbols": [
"CPPTRACE_GET_SYMBOLS_WITH_DBGHELP",
"CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE",
"CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF",
#"CPPTRACE_GET_SYMBOLS_WITH_NOTHING",
],
"demangle": [
#"CPPTRACE_DEMANGLE_WITH_CXXABI",
"CPPTRACE_DEMANGLE_WITH_CXXABI",
"CPPTRACE_DEMANGLE_WITH_NOTHING",
]
}
],
"shared": ["On" if shared else "Off"]
},
exclude = [
{
"demangle": "CPPTRACE_DEMANGLE_WITH_CXXABI",
@ -418,28 +424,107 @@ def main():
"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++"
},
{
"symbols": "CPPTRACE_GET_SYMBOLS_WITH_DBGHELP",
"demangle": "CPPTRACE_DEMANGLE_WITH_CXXABI"
},
{
"symbols": "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF",
"demangle": "CPPTRACE_DEMANGLE_WITH_NOTHING"
},
{
"symbols": "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE",
"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": [""]
}
exclude = [
{
"config": "-DCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE=On"
}
]
run_matrix(matrix, exclude, build_and_test_full_or_auto)
"config": [""],
"shared": ["On" if shared else "Off"]
},
exclude = []
).run(build_and_test_full_or_auto)
global failed
if failed:
print("🔴 Some checks failed")
sys.exit(1)
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(
"--shared",
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, args.shared)
else:
run_linux_matrix(compilers, args.shared)
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, args.shared)
else:
run_macos_matrix(compilers, args.shared)
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, args.shared)
else:
run_windows_matrix(compilers, args.shared)
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,20 +1,159 @@
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
def do_exclude(matrix_config, exclude):
return all(map(lambda k: matrix_config[k] == exclude[k], exclude.keys()))
# https://stackoverflow.com/a/14693789/15675011
ansi_escape = re.compile(r'''
\x1B # ESC
(?: # 7-bit C1 Fe (except CSI)
[@-Z\\-_]
| # or [ for CSI, followed by a control sequence
\[
[0-?]* # Parameter bytes
[ -/]* # Intermediate bytes
[@-~] # Final byte
)
''', re.VERBOSE)
def run_matrix(matrix, exclude, fn):
#print(matrix.values())
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
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()
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:
fn(matrix_config)
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 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]):
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):
self.log("| {cell:{width}} ".format(cell=cell, width=column_widths[j] + self.adj_width(cell)), end="")
self.log("|")
def print_results(self):
self.log("Results:")
table = [self.keys]
for result in self.results:
table.append([
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)
])
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()

51
cmake/Findzstd.cmake Normal file
View File

@ -0,0 +1,51 @@
# Libdwarf needs zstd, cpptrace doesn't, and libdwarf has its own Findzstd but it doesn't define zstd::libzstd_static /
# zstd::libzstd_shared targets which leads to issues, necessitating a find_dependency(zstd) in cpptrace's cmake config
# and in order to support non-cmake-module installs we need to provide a Findzstd script.
# https://github.com/jeremy-rifkin/cpptrace/issues/112
# This will define
# zstd_FOUND
# zstd_INCLUDE_DIR
# zstd_LIBRARY
find_path(zstd_INCLUDE_DIR NAMES zstd.h)
find_library(zstd_LIBRARY_DEBUG NAMES zstdd zstd_staticd)
find_library(zstd_LIBRARY_RELEASE NAMES zstd zstd_static)
include(SelectLibraryConfigurations)
SELECT_LIBRARY_CONFIGURATIONS(zstd)
include(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(
zstd DEFAULT_MSG
zstd_LIBRARY zstd_INCLUDE_DIR
)
if(zstd_FOUND)
message(STATUS "Found Zstd: ${zstd_LIBRARY}")
endif()
mark_as_advanced(zstd_INCLUDE_DIR zstd_LIBRARY)
if(zstd_FOUND)
# just defining them the same... cmake will figure it out
if(NOT TARGET zstd::libzstd_static)
add_library(zstd::libzstd_static UNKNOWN IMPORTED)
set_target_properties(
zstd::libzstd_static
PROPERTIES
IMPORTED_LOCATION "${zstd_LIBRARIES}"
INTERFACE_INCLUDE_DIRECTORIES "${zstd_INCLUDE_DIR}"
)
endif()
if(NOT TARGET zstd::libzstd_shared)
add_library(zstd::libzstd_shared UNKNOWN IMPORTED)
set_target_properties(
zstd::libzstd_shared
PROPERTIES
IMPORTED_LOCATION "${zstd_LIBRARIES}"
INTERFACE_INCLUDE_DIRECTORIES "${zstd_INCLUDE_DIR}"
)
endif()
endif()

86
cmake/InstallRules.cmake Normal file
View File

@ -0,0 +1,86 @@
include(CMakePackageConfigHelpers)
# copy header files to CMAKE_INSTALL_INCLUDEDIR
# don't include third party header files
install(
DIRECTORY
"${PROJECT_SOURCE_DIR}/include/" # our header files
"${PROJECT_BINARY_DIR}/include/" # generated header files
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
COMPONENT ${package_name}_development
# PATTERN "**/third_party" EXCLUDE # skip third party directory
# PATTERN "**/third_party/**" EXCLUDE # skip third party files
)
# copy target build output artifacts to OS dependent locations
# (Except includes, that just sets a compiler flag with the path)
install(
TARGETS ${target_name}
EXPORT ${package_name}-targets
RUNTIME #
COMPONENT ${package_name}_runtime
LIBRARY #
COMPONENT ${package_name}_runtime
NAMELINK_COMPONENT ${package_name}_development
ARCHIVE #
COMPONENT ${package_name}_development
INCLUDES #
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
)
# create config file that points to targets file
configure_file(
"${PROJECT_SOURCE_DIR}/cmake/in/cpptrace-config-cmake.in"
"${PROJECT_BINARY_DIR}/cmake/${package_name}-config.cmake"
@ONLY
)
# copy config file for find_package to find
install(
FILES "${PROJECT_BINARY_DIR}/cmake/${package_name}-config.cmake"
DESTINATION "${CPPTRACE_INSTALL_CMAKEDIR}"
COMPONENT ${package_name}_development
)
# create version file for consumer to check version in CMake
write_basic_package_version_file(
"${package_name}-config-version.cmake"
COMPATIBILITY SameMajorVersion # a.k.a SemVer
)
# copy version file for find_package to find for version check
install(
FILES "${PROJECT_BINARY_DIR}/${package_name}-config-version.cmake"
DESTINATION "${CPPTRACE_INSTALL_CMAKEDIR}"
COMPONENT ${package_name}_development
)
# create targets file included by config file with targets for consumers
install(
EXPORT ${package_name}-targets
NAMESPACE cpptrace::
DESTINATION "${CPPTRACE_INSTALL_CMAKEDIR}"
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)
install(
FILES "${PROJECT_SOURCE_DIR}/cmake/Findzstd.cmake"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${package_name}"
)
endif()
# support packaging library
if(PROJECT_IS_TOP_LEVEL)
include(CPack)
endif()

208
cmake/OptionVariables.cmake Normal file
View File

@ -0,0 +1,208 @@
# Included further down to avoid interfering with our cache variables
# include(GNUInstallDirs)
# ---- Options Summary ----
# ---------------------------------------------------------------------------------------------------
# | Option | Availability | Default |
# |=================================|===============|===============================================|
# | BUILD_SHARED_LIBS | Top-Level | OFF |
# | BUILD_TESTING | Top-Level | OFF |
# |---------------------------------|---------------|-----------------------------------------------|
# | CPPTRACE_BUILD_SHARED | Always | ${BUILD_SHARED_LIBS} |
# | CPPTRACE_BUILD_TESTING | Always | ${BUILD_TESTING} AND ${PROJECT_IS_TOP_LEVEL} |
# | CPPTRACE_INCLUDES_WITH_SYSTEM | Not Top-Level | ON |
# | CPPTRACE_INSTALL_CMAKEDIR | Always | ${CMAKE_INSTALL_LIBDIR}/cmake/${package_name} |
# | CPPTRACE_USE_EXTERNAL_LIBDWARF | Always | OFF |
# | CPPTRACE_USE_EXTERNAL_ZSTD | Always | OFF |
# | ... | | |
# ---------------------------------------------------------------------------------------------------
# ---- Build Shared ----
# Sometimes it's useful to be able to single out a dependency to be built as
# static or shared, even if obtained from source
if(PROJECT_IS_TOP_LEVEL)
option(BUILD_SHARED_LIBS "Build shared libs" OFF)
endif()
option(
CPPTRACE_BUILD_SHARED
"Override BUILD_SHARED_LIBS for ${package_name} library"
${BUILD_SHARED_LIBS}
)
mark_as_advanced(CPPTRACE_BUILD_SHARED)
set(build_type STATIC)
if(CPPTRACE_BUILD_SHARED)
set(build_type SHARED)
endif()
# ---- Warning Guard ----
# target_include_directories with SYSTEM modifier will request the compiler to
# omit warnings from the provided paths, if the compiler supports that.
# This is to provide a user experience similar to find_package when
# add_subdirectory or FetchContent is used to consume this project.
set(warning_guard )
if(NOT PROJECT_IS_TOP_LEVEL)
option(
CPPTRACE_INCLUDES_WITH_SYSTEM
"Use SYSTEM modifier for ${package_name}'s includes, disabling warnings"
ON
)
mark_as_advanced(CPPTRACE_INCLUDES_WITH_SYSTEM)
if(CPPTRACE_INCLUDES_WITH_SYSTEM)
set(warning_guard SYSTEM)
endif()
endif()
# ---- Enable Testing ----
# By default tests aren't enabled even with BUILD_TESTING=ON unless the library
# is built as a top level project.
# This is in order to cut down on unnecessary compile times, since it's unlikely
# for users to want to run the tests of their dependencies.
if(PROJECT_IS_TOP_LEVEL)
option(BUILD_TESTING "Build tests" OFF)
endif()
if(PROJECT_IS_TOP_LEVEL AND BUILD_TESTING)
set(build_testing ON)
endif()
option(
CPPTRACE_BUILD_TESTING
"Override BUILD_TESTING for ${package_name} library"
${build_testing}
)
set(build_testing )
mark_as_advanced(CPPTRACE_BUILD_TESTING)
# ---- Install Include Directory ----
# Adds an extra directory to the include path by default, so that when you link
# against the target, you get `<prefix>/include/<package-X.Y.Z>` added to your
# include paths rather than `<prefix>/include`.
# This doesn't affect include paths used by consumers of this project, but helps
# prevent consumers having access to other projects in the same include
# directory (e.g. usr/include).
# The variable type is STRING rather than PATH, because otherwise passing
# -DCMAKE_INSTALL_INCLUDEDIR=include on the command line would expand to an
# absolute path with the base being the current CMake directory, leading to
# unexpected errors.
# if(PROJECT_IS_TOP_LEVEL)
# set(
# CMAKE_INSTALL_INCLUDEDIR "include/${package_name}-${PROJECT_VERSION}"
# CACHE STRING ""
# )
# # marked as advanced in GNUInstallDirs version, so we follow their lead
# mark_as_advanced(CMAKE_INSTALL_INCLUDEDIR)
# endif()
# do not include earlier or we can't set CMAKE_INSTALL_INCLUDEDIR above
# include required for CMAKE_INSTALL_LIBDIR below
include(GNUInstallDirs)
# ---- Install CMake Directory ----
# This allows package maintainers to freely override the installation path for
# the CMake configs.
# This doesn't affects include paths used by consumers of this project.
# The variable type is STRING rather than PATH, because otherwise passing
# -DCPPTRACE_INSTALL_CMAKEDIR=lib/cmake on the command line would expand to an
# absolute path with the base being the current CMake directory, leading to
# unexpected errors.
set(
CPPTRACE_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/${package_name}"
CACHE STRING "CMake package config location relative to the install prefix"
)
# depends on CMAKE_INSTALL_LIBDIR which is marked as advanced in GNUInstallDirs
mark_as_advanced(CPPTRACE_INSTALL_CMAKEDIR)
# ---- Symbol Options ----
option(CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE "" OFF)
option(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF "" OFF)
option(CPPTRACE_GET_SYMBOLS_WITH_LIBDL "" OFF)
option(CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE "" OFF)
option(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP "" OFF)
option(CPPTRACE_GET_SYMBOLS_WITH_NOTHING "" OFF)
# ---- Unwinding Options ----
option(CPPTRACE_UNWIND_WITH_UNWIND "" OFF)
option(CPPTRACE_UNWIND_WITH_LIBUNWIND "" OFF)
option(CPPTRACE_UNWIND_WITH_EXECINFO "" OFF)
option(CPPTRACE_UNWIND_WITH_WINAPI "" OFF)
option(CPPTRACE_UNWIND_WITH_DBGHELP "" OFF)
option(CPPTRACE_UNWIND_WITH_NOTHING "" OFF)
# ---- Demangling Options ----
option(CPPTRACE_DEMANGLE_WITH_CXXABI "" OFF)
option(CPPTRACE_DEMANGLE_WITH_WINAPI "" OFF)
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 400.")
set(CPPTRACE_ADDR2LINE_PATH "" CACHE STRING "Absolute path to the addr2line executable you want to use.")
option(CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH "" OFF)
# ---- Other configurations ----
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()
option(CPPTRACE_USE_EXTERNAL_LIBDWARF "" OFF)
option(CPPTRACE_FIND_LIBDWARF_WITH_PKGCONFIG "" OFF)
option(CPPTRACE_USE_EXTERNAL_ZSTD "" OFF)
option(CPPTRACE_CONAN "" OFF)
option(CPPTRACE_VCPKG "" OFF)
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
CPPTRACE_ADDR2LINE_PATH
CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH
CPPTRACE_SANITIZER_BUILD
CPPTRACE_WERROR_BUILD
CPPTRACE_CONAN
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,8 @@
# In-source build guard
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR)
message(
FATAL_ERROR
"In-source builds are not supported. "
"You may need to delete 'CMakeCache.txt' and 'CMakeFiles/' before rebuilding this project."
)
endif()

View File

@ -0,0 +1,6 @@
# This variable is set by project() in CMake 3.21+
string(
COMPARE EQUAL
"${CMAKE_SOURCE_DIR}" "${PROJECT_SOURCE_DIR}"
PROJECT_IS_TOP_LEVEL
)

View File

@ -1,3 +0,0 @@
@PACKAGE_INIT@
include(${CMAKE_CURRENT_LIST_DIR}/cpptrace_targets.cmake)

View File

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

View File

@ -0,0 +1,6 @@
#include <cxxabi.h>
int main() {
std::type_info* t = abi::__cxa_current_exception_type();
(void*) t;
}

View File

@ -0,0 +1,6 @@
#include <dlfcn.h>
int main() {
dl_find_object result;
_dl_find_object(reinterpret_cast<void*>(main), &result);
}

8
cmake/has_dladdr1.cpp Normal file
View File

@ -0,0 +1,8 @@
#include <dlfcn.h>
#include <link.h>
int main() {
Dl_info info;
link_map* link_map_info;
dladdr1(reinterpret_cast<void*>(&main), &info, reinterpret_cast<void**>(&link_map_info), RTLD_DL_LINKMAP);
}

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,8 +0,0 @@
#include <stacktrace>
int main() {
std::stacktrace trace = std::stacktrace::current();
for(const auto entry : trace) {
(void)entry;
}
}

102
cmake/has_stackwalk.cpp Normal file
View File

@ -0,0 +1,102 @@
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <dbghelp.h>
#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
int main() {
HANDLE proc = GetCurrentProcess();
HANDLE thread = GetCurrentThread();
// https://jpassing.com/2008/03/12/walking-the-stack-of-the-current-thread/
// Get current thread context
// GetThreadContext cannot be used on the current thread.
// RtlCaptureContext doesn't work on i386
CONTEXT context;
#if defined(_M_IX86) || defined(__i386__)
ZeroMemory(&context, sizeof(CONTEXT));
context.ContextFlags = CONTEXT_CONTROL;
#if IS_MSVC
__asm {
label:
mov [context.Ebp], ebp;
mov [context.Esp], esp;
mov eax, [label];
mov [context.Eip], eax;
}
#else
asm(
"label:\n\t"
"mov{l %%ebp, %[cEbp] | %[cEbp], ebp};\n\t"
"mov{l %%esp, %[cEsp] | %[cEsp], esp};\n\t"
"mov{l $label, %%eax | eax, OFFSET label};\n\t"
"mov{l %%eax, %[cEip] | %[cEip], eax};\n\t"
: [cEbp] "=r" (context.Ebp),
[cEsp] "=r" (context.Esp),
[cEip] "=r" (context.Eip)
);
#endif
#else
RtlCaptureContext(&context);
#endif
// Setup current frame
STACKFRAME64 frame;
ZeroMemory(&frame, sizeof(STACKFRAME64));
DWORD machine_type;
#if defined(_M_IX86) || defined(__i386__)
machine_type = IMAGE_FILE_MACHINE_I386;
frame.AddrPC.Offset = context.Eip;
frame.AddrPC.Mode = AddrModeFlat;
frame.AddrFrame.Offset = context.Ebp;
frame.AddrFrame.Mode = AddrModeFlat;
frame.AddrStack.Offset = context.Esp;
frame.AddrStack.Mode = AddrModeFlat;
#elif defined(_M_X64) || defined(__x86_64__)
machine_type = IMAGE_FILE_MACHINE_AMD64;
frame.AddrPC.Offset = context.Rip;
frame.AddrPC.Mode = AddrModeFlat;
frame.AddrFrame.Offset = context.Rsp;
frame.AddrFrame.Mode = AddrModeFlat;
frame.AddrStack.Offset = context.Rsp;
frame.AddrStack.Mode = AddrModeFlat;
#elif defined(_M_IA64) || defined(__aarch64__)
machine_type = IMAGE_FILE_MACHINE_IA64;
frame.AddrPC.Offset = context.StIIP;
frame.AddrPC.Mode = AddrModeFlat;
frame.AddrFrame.Offset = context.IntSp;
frame.AddrFrame.Mode = AddrModeFlat;
frame.AddrBStore.Offset= context.RsBSP;
frame.AddrBStore.Mode = AddrModeFlat;
frame.AddrStack.Offset = context.IntSp;
frame.AddrStack.Mode = AddrModeFlat;
#else
#error "Cpptrace: StackWalk64 not supported for this platform yet"
#endif
ZeroMemory(&context, sizeof(CONTEXT));
StackWalk64(
machine_type,
proc,
thread,
&frame,
machine_type == IMAGE_FILE_MACHINE_I386 ? NULL : &context,
NULL,
SymFunctionTableAccess64,
SymGetModuleBase64,
NULL
);
}

View File

@ -0,0 +1,32 @@
# Init @ variables before doing anything else
@PACKAGE_INIT@
# Dependencies
if(@CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF@)
include(CMakeFindDependencyMacro)
# we don't go the Findzstd.cmake route on vcpkg
if(@CPPTRACE_VCPKG@)
find_dependency(zstd CONFIG REQUIRED)
else()
set(CMAKE_MODULE_PATH_OLD "${CMAKE_MODULE_PATH}")
set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${CMAKE_CURRENT_LIST_DIR}")
find_dependency(zstd)
set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH_OLD}")
unset(CMAKE_MODULE_PATH_OLD)
endif()
if(NOT @CPPTRACE_FIND_LIBDWARF_WITH_PKGCONFIG@)
find_dependency(libdwarf REQUIRED)
endif()
endif()
# We cannot modify an existing IMPORT target
if(NOT TARGET cpptrace::cpptrace)
# import targets
include("${CMAKE_CURRENT_LIST_DIR}/@package_name@-targets.cmake")
endif()
if(@CPPTRACE_STATIC_DEFINE@)
target_compile_definitions(cpptrace::cpptrace INTERFACE CPPTRACE_STATIC_DEFINE)
endif()

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

173
docs/c-api.md Normal file
View File

@ -0,0 +1,173 @@
# ctrace <!-- omit in toc -->
Cpptrace provides a C API under the name ctrace, documented below.
## Table of Contents <!-- omit in toc -->
- [Documentation](#documentation)
- [Stack Traces](#stack-traces)
- [Object Traces](#object-traces)
- [Raw Traces](#raw-traces)
- [Utilities](#utilities)
- [Utility types](#utility-types)
- [Configuration](#configuration)
- [Signal-Safe Tracing](#signal-safe-tracing)
## Documentation
All ctrace declarations are in the `ctrace.h` header:
```c
#include <ctrace/ctrace.h>
```
### Stack Traces
Generate stack traces with `ctrace_generate_trace()`. Often `skip = 0` and `max_depth = SIZE_MAX` is what you want for
the parameters.
`ctrace_stacktrace_to_string` and `ctrace_print_stacktrace` can then be used for output.
`ctrace_free_stacktrace` must be called when you are done with the trace.
```c
typedef struct ctrace_stacktrace ctrace_stacktrace;
struct ctrace_stacktrace_frame {
ctrace_frame_ptr raw_address;
ctrace_frame_ptr object_address;
uint32_t line;
uint32_t column;
const char* filename;
const char* symbol;
ctrace_bool is_inline;
};
struct ctrace_stacktrace {
ctrace_stacktrace_frame* frames;
size_t count;
};
ctrace_stacktrace ctrace_generate_trace(size_t skip, size_t max_depth);
ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color);
void ctrace_print_stacktrace(const ctrace_stacktrace* trace, FILE* to, ctrace_bool use_color);
void ctrace_free_stacktrace(ctrace_stacktrace* trace);
// object_address is stored but if the object_path is needed this can be used
ctrace_object_frame ctrace_get_object_info(const ctrace_stacktrace_frame* frame);
```
### 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
is resolved), and the path to the object the instruction pointer is located in.
`ctrace_free_object_trace` must be called when you are done with the trace.
```c
typedef struct ctrace_object_trace ctrace_object_trace;
struct ctrace_object_frame {
ctrace_frame_ptr raw_address;
ctrace_frame_ptr obj_address;
const char* obj_path;
};
struct ctrace_object_trace {
ctrace_object_frame* frames;
size_t count;
};
ctrace_object_trace ctrace_generate_object_trace(size_t skip, size_t max_depth);
ctrace_stacktrace ctrace_resolve_object_trace(const ctrace_object_trace* trace);
void ctrace_free_object_trace(ctrace_object_trace* trace);
```
### Raw Traces
Raw traces are arrays of program counters. These are ideal for fast and cheap traces you want to resolve later.
Note it is important executables and shared libraries in memory aren't somehow unmapped otherwise libdl calls (and
`GetModuleFileName` in windows) will fail to figure out where the program counter corresponds to.
`ctrace_free_raw_trace` must be called when you are done with the trace.
```c
typedef struct ctrace_raw_trace ctrace_raw_trace;
ctrace_raw_trace ctrace_generate_raw_trace(size_t skip, size_t max_depth);
ctrace_stacktrace ctrace_resolve_raw_trace(const ctrace_raw_trace* trace);
void ctrace_free_raw_trace(ctrace_raw_trace* trace);
```
### Utilities
`ctrace_demangle`: Helper function for name demangling
`ctrace_stdin_fileno`, `ctrace_stderr_fileno`, `ctrace_stdout_fileno`: Returns the appropriate file descriptor for the
respective standard stream.
`ctrace_isatty`: Checks if a file descriptor corresponds to a tty device.
```c
ctrace_owning_string ctrace_demangle(const char* mangled);
int ctrace_stdin_fileno(void);
int ctrace_stderr_fileno(void);
int ctrace_stdout_fileno(void);
ctrace_bool ctrace_isatty(int fd);
```
### Utility types
For ABI reasons `ctrace_bool`s are used for bools. `ctrace_owning_string` is a wrapper type which indicates that a
string is owned and must be freed.
```c
typedef int8_t ctrace_bool;
typedef struct {
const char* data;
} ctrace_owning_string;
ctrace_owning_string ctrace_generate_owning_string(const char* raw_string);
void ctrace_free_owning_string(ctrace_owning_string* string);
```
### Configuration
`experimental::set_cache_mode`: Control time-memory tradeoffs within the library. By default speed is prioritized. If
using this function, set the cache mode at the very start of your program before any traces are performed. Note: This
API is not set in stone yet and could change in the future.
`ctrace_enable_inlined_call_resolution`: Configure whether the library will attempt to resolve inlined call information for
release builds. Default is true.
```c
typedef enum {
/* Only minimal lookup tables */
ctrace_prioritize_memory = 0,
/* Build lookup tables but don't keep them around between trace calls */
ctrace_hybrid = 1,
/* Build lookup tables as needed */
ctrace_prioritize_speed = 2
} ctrace_cache_mode;
void ctrace_set_cache_mode(ctrace_cache_mode mode);
void ctrace_enable_inlined_call_resolution(ctrace_bool enable);
```
### Signal-Safe Tracing
For more details on the signal-safe tracing interface please refer to the README and the
[signal-safe-tracing.md](signal-safe-tracing.md) guide.
```c
typedef struct ctrace_safe_object_frame ctrace_safe_object_frame;
struct ctrace_safe_object_frame {
ctrace_frame_ptr raw_address;
ctrace_frame_ptr relative_obj_address;
char object_path[CTRACE_PATH_MAX + 1];
};
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 ctrace_can_signal_safe_unwind();
ctrace_bool ctrace_can_get_safe_object_frame();
```

250
docs/signal-safe-tracing.md Normal file
View File

@ -0,0 +1,250 @@
# Signal-Safe Stack Tracing <!-- omit in toc -->
- [Overview](#overview)
- [Big-Picture](#big-picture)
- [API](#api)
- [Strategy](#strategy)
- [Technical Requirements](#technical-requirements)
- [Signal-Safe Tracing With `fork()` + `exec()`](#signal-safe-tracing-with-fork--exec)
- [In the main program](#in-the-main-program)
- [In the tracer program](#in-the-tracer-program)
# Overview
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.
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
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()` but this is what is needed to
really do this safely as far as I can tell.
FAQ: What's the worst that could happen if you call `cpptrace::generate_trace().print()` from a
signal handler? In many cases you might be able to get away with it but you risk deadlocking or
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.
# API
Cpptrace provides APIs for generating raw trace information safely and then also safely resolving
those raw pointers to the most minimal object information needed to resolve later.
```cpp
namespace cpptrace {
// signal-safe
std::size_t safe_generate_raw_trace(frame_ptr* buffer, std::size_t size, std::size_t skip = 0);
// signal-safe
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;
char object_path[CPPTRACE_PATH_MAX + 1];
object_frame resolve() const; // To be called outside a signal handler. Not signal safe.
};
// signal-safe
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();
}
```
`safe_generate_raw_trace` produces instruction pointers found while unwinding the stack. These addresses are sufficient
information to resolve a stack trace in the currently running process after a signal handler exits, but they aren't
sufficient for resolving outside of the currently running process, unless there is no position-independent code or
shared-library code.
To resolve outside the current process `safe_object_frame` information is needed. This contains the path to the
object where the address is located as well as the address before address randomization.
# Strategy
Signal-safe tracing can be done three ways:
- In a signal handler, call `safe_generate_raw_trace` and then outside a signal handler
construct a `cpptrace:raw_trace` and resolve.
- In a signal handler, call `safe_generate_raw_trace`, then write `cpptrace::safe_object_frame`
information to a file to be resolved later.
- In a signal handler, call `safe_generate_raw_trace`, `fork()` and `exec()` a process to handle the
resolution, pass `cpptrace::safe_object_frame` information to that child through a pipe, and
wait for the child to exit.
It's not as simple as calling `cpptrace::generate_trace().print()`, I know, but these are truly the
only ways to do this safely as far as I can tell.
# Technical Requirements
**Note:** Not all back-ends and platforms support these interfaces. If signal-safe unwinding isn't supported
`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.
# Signal-Safe Tracing With `fork()` + `exec()`
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`](../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
The main program handles most of the complexity for tracing from signal handlers:
- Collecting a raw trace
- Spawning a child process
- Resolving raw frame pointers to minimal object frames
- Sending that info to the other process
Also note: A warmup for the library is done in main.
A basic implementation is as follows:
```cpp
#include <sys/wait.h>
#include <cstring>
#include <signal.h>
#include <unistd.h>
#include <cpptrace/cpptrace.hpp>
// This is just a utility I like, it makes the pipe API more expressive.
struct pipe_t {
union {
struct {
int read_end;
int write_end;
};
int data[2];
};
};
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) {
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);
close(input_pipe.write_end);
execl("signal_tracer", "signal_tracer", nullptr);
const char* exec_failure_message = "exec(signal_tracer) failed: Make sure the signal_tracer executable is in "
"the current working directory and the binary's permissions are correct.\n";
write(STDERR_FILENO, exec_failure_message, strlen(exec_failure_message));
_exit(1);
}
// Resolve to safe_object_frames and write those to the pipe
for(std::size_t i = 0; i < count; i++) {
cpptrace::safe_object_frame frame;
cpptrace::get_safe_object_frame(buffer[i], &frame);
write(input_pipe.write_end, &frame, sizeof(frame));
}
close(input_pipe.read_end);
close(input_pipe.write_end);
// Wait for child
waitpid(pid, nullptr, 0);
}
void handler(int signo, siginfo_t* info, void* context) {
// Print basic message
const char* message = "SIGSEGV occurred:\n";
write(STDERR_FILENO, message, strlen(message));
// Generate trace
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, count);
// Up to you if you want to exit or continue or whatever
_exit(1);
}
void warmup_cpptrace() {
// This is done for any dynamic-loading shenanigans
cpptrace::frame_ptr buffer[10];
std::size_t count = cpptrace::safe_generate_raw_trace(buffer, 10);
cpptrace::safe_object_frame frame;
cpptrace::get_safe_object_frame(buffer[0], &frame);
}
int main() {
warmup_cpptrace();
// Setup signal handler
struct sigaction action = { 0 };
action.sa_flags = 0;
action.sa_sigaction = &handler;
if (sigaction(SIGSEGV, &action, NULL) == -1) {
perror("sigaction");
}
/// ...
}
```
## In the tracer program
The tracer program is quite simple. It just has to read `cpptrace::safe_object_frame`s from the pipe, resolve to
`cpptrace::object_frame`s, and resolve an `object_trace`.
```cpp
#include <cstdio>
#include <iostream>
#include <unistd.h>
#include <cpptrace/cpptrace.hpp>
int main() {
cpptrace::object_trace trace;
while(true) {
cpptrace::safe_object_frame frame;
// fread used over read because a read() from a pipe might not read the full frame
std::size_t res = fread(&frame, sizeof(frame), 1, stdin);
if(res == 0) {
break;
} else if(res != 1) {
std::cerr<<"Something went wrong while reading from the pipe"<<res<<" "<<std::endl;
break;
} else {
trace.frames.push_back(frame.resolve());
}
}
trace.resolve().print();
}
```

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,20 +1,9 @@
#ifndef CPPTRACE_HPP
#define CPPTRACE_HPP
#include <cstdint>
#include <string>
#include <vector>
namespace cpptrace {
struct stacktrace_frame {
uintptr_t address;
std::uint_least32_t line;
std::uint_least32_t col;
std::string filename;
std::string symbol;
};
std::vector<stacktrace_frame> generate_trace(std::uint32_t skip = 0);
void print_trace(std::uint32_t skip = 0);
}
#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

164
include/ctrace/ctrace.h Normal file
View File

@ -0,0 +1,164 @@
#ifndef CTRACE_H
#define CTRACE_H
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#ifdef _WIN32
#define CPPTRACE_EXPORT_ATTR __declspec(dllexport)
#define CPPTRACE_IMPORT_ATTR __declspec(dllimport)
#else
#define CPPTRACE_EXPORT_ATTR __attribute__((visibility("default")))
#define CPPTRACE_IMPORT_ATTR __attribute__((visibility("default")))
#endif
#ifdef CPPTRACE_STATIC_DEFINE
# define CPPTRACE_EXPORT
# define CPPTRACE_NO_EXPORT
#else
# ifndef CPPTRACE_EXPORT
# ifdef cpptrace_lib_EXPORTS
/* We are building this library */
# define CPPTRACE_EXPORT CPPTRACE_EXPORT_ATTR
# else
/* We are using this library */
# define CPPTRACE_EXPORT CPPTRACE_IMPORT_ATTR
# endif
# endif
#endif
#if defined(__cplusplus)
#define CTRACE_BEGIN_DEFINITIONS extern "C" {
#define CTRACE_END_DEFINITIONS }
#else
#define CTRACE_BEGIN_DEFINITIONS
#define CTRACE_END_DEFINITIONS
#endif
#ifdef _MSC_VER
#define CTRACE_FORCE_NO_INLINE __declspec(noinline)
#else
#define CTRACE_FORCE_NO_INLINE __attribute__((noinline))
#endif
#ifdef _MSC_VER
#define CTRACE_FORCE_INLINE __forceinline
#elif defined(__clang__) || defined(__GNUC__)
#define CTRACE_FORCE_INLINE __attribute__((always_inline)) inline
#else
#define CTRACE_FORCE_INLINE inline
#endif
/* See `CPPTRACE_PATH_MAX` for more info. */
#define CTRACE_PATH_MAX 4096
CTRACE_BEGIN_DEFINITIONS
typedef struct ctrace_raw_trace ctrace_raw_trace;
typedef struct ctrace_object_trace ctrace_object_trace;
typedef struct ctrace_stacktrace ctrace_stacktrace;
/* Represents a boolean value, ensures a consistent ABI. */
typedef int8_t ctrace_bool;
/* A type that can represent a pointer, alias for `uintptr_t`. */
typedef uintptr_t ctrace_frame_ptr;
typedef struct ctrace_object_frame ctrace_object_frame;
typedef struct ctrace_stacktrace_frame ctrace_stacktrace_frame;
typedef struct ctrace_safe_object_frame ctrace_safe_object_frame;
/* Type-safe null-terminated string wrapper */
typedef struct {
const char* data;
} ctrace_owning_string;
struct ctrace_object_frame {
ctrace_frame_ptr raw_address;
ctrace_frame_ptr obj_address;
const char* obj_path;
};
struct ctrace_stacktrace_frame {
ctrace_frame_ptr raw_address;
ctrace_frame_ptr object_address;
uint32_t line;
uint32_t column;
const char* filename;
const char* symbol;
ctrace_bool is_inline;
};
struct ctrace_safe_object_frame {
ctrace_frame_ptr raw_address;
ctrace_frame_ptr relative_obj_address;
char object_path[CTRACE_PATH_MAX + 1];
};
struct ctrace_raw_trace {
ctrace_frame_ptr* frames;
size_t count;
};
struct ctrace_object_trace {
ctrace_object_frame* frames;
size_t count;
};
struct ctrace_stacktrace {
ctrace_stacktrace_frame* frames;
size_t count;
};
/* ctrace::string: */
CPPTRACE_EXPORT ctrace_owning_string ctrace_generate_owning_string(const char* raw_string);
CPPTRACE_EXPORT void ctrace_free_owning_string(ctrace_owning_string* string);
/* ctrace::generation: */
CPPTRACE_EXPORT ctrace_raw_trace ctrace_generate_raw_trace(size_t skip, size_t max_depth);
CPPTRACE_EXPORT ctrace_object_trace ctrace_generate_object_trace(size_t skip, size_t max_depth);
CPPTRACE_EXPORT ctrace_stacktrace ctrace_generate_trace(size_t skip, size_t max_depth);
/* ctrace::freeing: */
CPPTRACE_EXPORT void ctrace_free_raw_trace(ctrace_raw_trace* trace);
CPPTRACE_EXPORT void ctrace_free_object_trace(ctrace_object_trace* trace);
CPPTRACE_EXPORT void ctrace_free_stacktrace(ctrace_stacktrace* trace);
/* ctrace::resolve: */
CPPTRACE_EXPORT ctrace_stacktrace ctrace_resolve_raw_trace(const ctrace_raw_trace* trace);
CPPTRACE_EXPORT ctrace_object_trace ctrace_resolve_raw_trace_to_object_trace(const ctrace_raw_trace* trace);
CPPTRACE_EXPORT ctrace_stacktrace ctrace_resolve_object_trace(const ctrace_object_trace* trace);
/* ctrace::safe: */
CPPTRACE_EXPORT size_t ctrace_safe_generate_raw_trace(ctrace_frame_ptr* buffer, size_t size, size_t skip, size_t max_depth);
CPPTRACE_EXPORT void ctrace_get_safe_object_frame(ctrace_frame_ptr address, ctrace_safe_object_frame* out);
CPPTRACE_EXPORT ctrace_bool ctrace_can_signal_safe_unwind(void);
CPPTRACE_EXPORT ctrace_bool ctrace_can_get_safe_object_frame(void);
/* ctrace::io: */
CPPTRACE_EXPORT ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color);
CPPTRACE_EXPORT void ctrace_print_stacktrace(const ctrace_stacktrace* trace, FILE* to, ctrace_bool use_color);
/* ctrace::utility: */
CPPTRACE_EXPORT ctrace_owning_string ctrace_demangle(const char* mangled);
CPPTRACE_EXPORT int ctrace_stdin_fileno(void);
CPPTRACE_EXPORT int ctrace_stderr_fileno(void);
CPPTRACE_EXPORT int ctrace_stdout_fileno(void);
CPPTRACE_EXPORT ctrace_bool ctrace_isatty(int fd);
CPPTRACE_EXPORT ctrace_object_frame ctrace_get_object_info(const ctrace_stacktrace_frame* frame);
/* ctrace::config: */
typedef enum {
/* Only minimal lookup tables */
ctrace_prioritize_memory = 0,
/* Build lookup tables but don't keep them around between trace calls */
ctrace_hybrid = 1,
/* Build lookup tables as needed */
ctrace_prioritize_speed = 2
} ctrace_cache_mode;
CPPTRACE_EXPORT void ctrace_set_cache_mode(ctrace_cache_mode mode);
CPPTRACE_EXPORT void ctrace_enable_inlined_call_resolution(ctrace_bool enable);
CTRACE_END_DEFINITIONS
#endif

14
lint.sh
View File

@ -1,14 +0,0 @@
#!/bin/bash
status=0
while read f
do
echo checking $f
flags="-DCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE -DCPPTRACE_FULL_TRACE_WITH_STACKTRACE -DCPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE -DCPPTRACE_GET_SYMBOLS_WITH_LIBDL -DCPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE -DCPPTRACE_GET_SYMBOLS_WITH_NOTHING -DCPPTRACE_UNWIND_WITH_EXECINFO -DCPPTRACE_UNWIND_WITH_UNWIND -DCPPTRACE_UNWIND_WITH_NOTHING -DCPPTRACE_DEMANGLE_WITH_CXXABI -DCPPTRACE_DEMANGLE_WITH_NOTHING -DCPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH"
clang-tidy $f -- -std=c++11 -Iinclude $@ $flags
ret=$?
if [ $ret -ne 0 ]; then
status=1
fi
done <<< $(find include src -name "*.hpp" -o -name "*.cpp" -not -path "test/speedtest.cpp" -not -path "src/full/full_trace_with_stacktrace.cpp")
exit $status

BIN
res/demo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

BIN
res/exception.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

BIN
res/from_current.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
res/inlining.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

BIN
res/snippets.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

8
sonar-project.properties Normal file
View File

@ -0,0 +1,8 @@
sonar.organization=jeremy-rifkin
sonar.projectKey=jeremy-rifkin_cpptrace
# relative paths to source directories. More details and properties are described
# in https://sonarcloud.io/documentation/project-administration/narrowing-the-focus/
sonar.sources=src, include
sonar.sourceEncoding=UTF-8
sonar.cfamily.compile-commands=build/compile_commands.json

437
src/binary/elf.cpp Normal file
View File

@ -0,0 +1,437 @@
#include "binary/elf.hpp"
#include "utils/optional.hpp"
#if IS_LINUX
#include <array>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <mutex>
#include <type_traits>
#include <unordered_map>
#include <elf.h>
namespace cpptrace {
namespace detail {
elf::elf(
file_wrapper file,
const std::string& object_path,
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<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);
}
// Initial checks/metadata
auto magic = load_bytes<std::array<char, 4>>(file, 0);
if(magic.is_error()) {
return std::move(magic).unwrap_error();
}
if(magic.unwrap_value() != (std::array<char, 4>{0x7F, 'E', 'L', 'F'})) {
return internal_error("File is not ELF " + object_path);
}
auto ei_class = load_bytes<std::uint8_t>(file, 4);
if(ei_class.is_error()) {
return std::move(ei_class).unwrap_error();
}
bool is_64 = ei_class.unwrap_value() == 2;
auto ei_data = load_bytes<std::uint8_t>(file, 5);
if(ei_data.is_error()) {
return std::move(ei_data).unwrap_error();
}
bool is_little_endian = ei_data.unwrap_value() == 1;
auto ei_version = load_bytes<std::uint8_t>(file, 6);
if(ei_version.is_error()) {
return std::move(ei_version).unwrap_error();
}
if(ei_version.unwrap_value() != 1) {
return internal_error("Unexpected ELF version " + 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 get_module_image_base_impl<64>();
} else {
return get_module_image_base_impl<32>();
}
}
template<std::size_t Bits>
Result<std::uintptr_t, internal_error> elf::get_module_image_base_impl() {
static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument");
using PHeader = typename std::conditional<Bits == 32, Elf32_Phdr, Elf64_Phdr>::type;
auto header = get_header_info();
if(header.is_error()) {
return std::move(header).unwrap_error();
}
const auto& header_info = header.unwrap_value();
// PT_PHDR will occur at most once
// Should be somewhat reliable https://stackoverflow.com/q/61568612/15675011
// It should occur at the beginning but may as well loop just in case
for(unsigned i = 0; i < header_info.e_phnum; i++) {
auto loaded_ph = 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); });
}
}
}
}
#endif

129
src/binary/elf.hpp Normal file
View File

@ -0,0 +1,129 @@
#ifndef ELF_HPP
#define ELF_HPP
#include "utils/common.hpp"
#include "utils/utils.hpp"
#if IS_LINUX
#include <cstdint>
#include <string>
#include <unordered_map>
namespace cpptrace {
namespace detail {
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);
}
}
#endif
#endif

699
src/binary/mach-o.cpp Normal file
View File

@ -0,0 +1,699 @@
#include "binary/mach-o.hpp"
#include "utils/common.hpp"
#include "utils/utils.hpp"
#if IS_APPLE
// A number of mach-o functions are deprecated as of macos 13
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#include <cstdio>
#include <cstring>
#include <mutex>
#include <string>
#include <type_traits>
#include <unordered_map>
#include <vector>
#include <iostream>
#include <iomanip>
#include <mach-o/loader.h>
#include <mach-o/swap.h>
#include <mach-o/fat.h>
#include <crt_externs.h>
#include <mach-o/nlist.h>
#include <mach-o/stab.h>
#include <mach-o/arch.h>
namespace cpptrace {
namespace detail {
bool is_mach_o(std::uint32_t magic) {
switch(magic) {
case FAT_MAGIC:
case FAT_CIGAM:
case MH_MAGIC:
case MH_CIGAM:
case MH_MAGIC_64:
case MH_CIGAM_64:
return true;
default:
return false;
}
}
bool file_is_mach_o(const std::string& object_path) noexcept {
auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter);
if(file == nullptr) {
return false;
}
auto magic = load_bytes<std::uint32_t>(file, 0);
if(magic) {
return is_mach_o(magic.unwrap_value());
} else {
return false;
}
}
bool is_fat_magic(std::uint32_t magic) {
return magic == FAT_MAGIC || magic == FAT_CIGAM;
}
// Based on https://github.com/AlexDenisov/segment_dumper/blob/master/main.c
// and https://lowlevelbits.org/parsing-mach-o-files/
bool is_magic_64(std::uint32_t magic) {
return magic == MH_MAGIC_64 || magic == MH_CIGAM_64;
}
bool should_swap_bytes(std::uint32_t magic) {
return magic == MH_CIGAM || magic == MH_CIGAM_64 || magic == FAT_CIGAM;
}
void swap_mach_header(mach_header_64& header) {
swap_mach_header_64(&header, NX_UnknownByteOrder);
}
void swap_mach_header(mach_header& header) {
swap_mach_header(&header, NX_UnknownByteOrder);
}
void swap_segment_command(segment_command_64& segment) {
swap_segment_command_64(&segment, NX_UnknownByteOrder);
}
void swap_segment_command(segment_command& segment) {
swap_segment_command(&segment, NX_UnknownByteOrder);
}
void swap_nlist(struct nlist& entry) {
swap_nlist(&entry, 1, NX_UnknownByteOrder);
}
void swap_nlist(struct nlist_64& entry) {
swap_nlist_64(&entry, 1, NX_UnknownByteOrder);
}
#ifdef __LP64__
#define LP(x) x##_64
#else
#define LP(x) x
#endif
Result<const char*, internal_error> mach_o::symtab_info_data::get_string(std::size_t index) const {
if(stringtab && index < symtab.strsize) {
return stringtab.unwrap().data() + index;
} else {
return internal_error("can't retrieve symbol from symtab");
}
}
Result<monostate, internal_error> mach_o::load() {
if(magic == FAT_MAGIC || magic == FAT_CIGAM) {
return load_fat_mach();
} else {
fat_index = 0;
if(is_magic_64(magic)) {
return load_mach<64>();
} else {
return load_mach<32>();
}
}
}
Result<mach_o, internal_error> mach_o::open_mach_o(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);
}
auto magic = load_bytes<std::uint32_t>(file, 0);
if(!magic) {
return magic.unwrap_error();
}
if(!is_mach_o(magic.unwrap_value())) {
return internal_error("File is not mach-o {}", object_path);
}
mach_o obj(std::move(file), object_path, magic.unwrap_value());
auto result = obj.load();
if(result.is_error()) {
return result.unwrap_error();
} else {
return obj;
}
}
Result<std::uintptr_t, internal_error> mach_o::get_text_vmaddr() {
for(const auto& command : load_commands) {
if(command.cmd == LC_SEGMENT_64 || command.cmd == LC_SEGMENT) {
auto segment = command.cmd == LC_SEGMENT_64
? load_segment_command<64>(command.file_offset)
: load_segment_command<32>(command.file_offset);
if(segment.is_error()) {
return std::move(segment).unwrap_error();
}
if(std::strcmp(segment.unwrap_value().segname, "__TEXT") == 0) {
return segment.unwrap_value().vmaddr;
}
}
}
// somehow no __TEXT section was found...
return internal_error("Couldn't find __TEXT section while parsing Mach-O object");
}
std::size_t mach_o::get_fat_index() const {
VERIFY(fat_index != std::numeric_limits<std::size_t>::max());
return fat_index;
}
void mach_o::print_segments() const {
int i = 0;
for(const auto& command : load_commands) {
if(command.cmd == LC_SEGMENT_64 || command.cmd == LC_SEGMENT) {
auto segment_load = command.cmd == LC_SEGMENT_64
? load_segment_command<64>(command.file_offset)
: load_segment_command<32>(command.file_offset);
fprintf(stderr, "Load command %d\n", i);
if(segment_load.is_error()) {
fprintf(stderr, " error\n");
segment_load.drop_error();
continue;
}
auto& segment = segment_load.unwrap_value();
fprintf(stderr, " cmd %u\n", segment.cmd);
fprintf(stderr, " cmdsize %u\n", segment.cmdsize);
fprintf(stderr, " segname %s\n", segment.segname);
fprintf(stderr, " vmaddr 0x%llx\n", segment.vmaddr);
fprintf(stderr, " vmsize 0x%llx\n", segment.vmsize);
fprintf(stderr, " off 0x%llx\n", segment.fileoff);
fprintf(stderr, " filesize %llu\n", segment.filesize);
fprintf(stderr, " nsects %u\n", segment.nsects);
}
i++;
}
}
Result<std::reference_wrapper<optional<mach_o::symtab_info_data>>, internal_error> mach_o::get_symtab_info() {
if(!symtab_info.has_value() && !tried_to_load_symtab) {
// don't try to load the symtab again if for some reason loading here fails
tried_to_load_symtab = true;
for(const auto& command : load_commands) {
if(command.cmd == LC_SYMTAB) {
symtab_info_data info;
auto symtab = load_symbol_table_command(command.file_offset);
if(!symtab) {
return std::move(symtab).unwrap_error();
}
info.symtab = symtab.unwrap_value();
auto string = load_string_table(info.symtab.stroff, info.symtab.strsize);
if(!string) {
return std::move(string).unwrap_error();
}
info.stringtab = std::move(string).unwrap_value();
symtab_info = std::move(info);
break;
}
}
}
return std::reference_wrapper<optional<symtab_info_data>>{symtab_info};
}
void mach_o::print_symbol_table_entry(
const nlist_64& entry,
const char* stringtab,
std::size_t stringsize,
std::size_t j
) const {
const char* type = "";
if(entry.n_type & N_STAB) {
switch(entry.n_type) {
case N_SO: type = "N_SO"; break;
case N_OSO: type = "N_OSO"; break;
case N_BNSYM: type = "N_BNSYM"; break;
case N_ENSYM: type = "N_ENSYM"; break;
case N_FUN: type = "N_FUN"; break;
}
} else if((entry.n_type & N_TYPE) == N_SECT) {
type = "N_SECT";
}
fprintf(
stderr,
"%5llu %8llx %2llx %7s %2llu %4llx %16llx %s\n",
to_ull(j),
to_ull(entry.n_un.n_strx),
to_ull(entry.n_type),
type,
to_ull(entry.n_sect),
to_ull(entry.n_desc),
to_ull(entry.n_value),
stringtab == nullptr
? "Stringtab error"
: entry.n_un.n_strx < stringsize
? stringtab + entry.n_un.n_strx
: "String index out of bounds"
);
}
void mach_o::print_symbol_table() {
int i = 0;
for(const auto& command : load_commands) {
if(command.cmd == LC_SYMTAB) {
auto symtab_load = load_symbol_table_command(command.file_offset);
fprintf(stderr, "Load command %d\n", i);
if(symtab_load.is_error()) {
fprintf(stderr, " error\n");
symtab_load.drop_error();
continue;
}
auto& symtab = symtab_load.unwrap_value();
fprintf(stderr, " cmd %llu\n", to_ull(symtab.cmd));
fprintf(stderr, " cmdsize %llu\n", to_ull(symtab.cmdsize));
fprintf(stderr, " symoff 0x%llu\n", to_ull(symtab.symoff));
fprintf(stderr, " nsyms %llu\n", to_ull(symtab.nsyms));
fprintf(stderr, " stroff 0x%llu\n", to_ull(symtab.stroff));
fprintf(stderr, " strsize %llu\n", to_ull(symtab.strsize));
auto stringtab = load_string_table(symtab.stroff, symtab.strsize);
if(!stringtab) {
stringtab.drop_error();
}
for(std::size_t j = 0; j < symtab.nsyms; j++) {
auto entry = bits == 32
? load_symtab_entry<32>(symtab.symoff, j)
: load_symtab_entry<64>(symtab.symoff, j);
if(!entry) {
fprintf(stderr, "error loading symtab entry\n");
entry.drop_error();
continue;
}
print_symbol_table_entry(
entry.unwrap_value(),
stringtab ? stringtab.unwrap_value().data() : nullptr,
symtab.strsize,
j
);
}
}
i++;
}
}
// produce information similar to dsymutil -dump-debug-map
Result<mach_o::debug_map, internal_error> mach_o::get_debug_map() {
// we have a bunch of symbols in our binary we need to pair up with symbols from various .o files
// first collect symbols and the objects they come from
debug_map debug_map;
auto symtab_info_res = get_symtab_info();
if(!symtab_info_res) {
return std::move(symtab_info_res).unwrap_error();
}
if(!symtab_info_res.unwrap_value().get()) {
return internal_error("No symtab info");
}
const auto& symtab_info = symtab_info_res.unwrap_value().get().unwrap();
const auto& symtab = symtab_info.symtab;
// TODO: Take timestamp into account?
std::string current_module;
optional<debug_map_entry> current_function;
for(std::size_t j = 0; j < symtab.nsyms; j++) {
auto load_entry = bits == 32
? load_symtab_entry<32>(symtab.symoff, j)
: load_symtab_entry<64>(symtab.symoff, j);
if(!load_entry) {
return std::move(load_entry).unwrap_error();
}
auto& entry = load_entry.unwrap_value();
// entry.n_type & N_STAB indicates symbolic debug info
if(!(entry.n_type & N_STAB)) {
continue;
}
switch(entry.n_type) {
case N_SO:
// pass - these encode path and filename for the module, if applicable
break;
case N_OSO:
{
// sets the module
auto str = symtab_info.get_string(entry.n_un.n_strx);
if(!str) {
return std::move(str).unwrap_error();
}
current_module = str.unwrap_value();
}
break;
case N_BNSYM: break; // pass
case N_ENSYM: break; // pass
case N_FUN:
{
auto str = symtab_info.get_string(entry.n_un.n_strx);
if(!str) {
return std::move(str).unwrap_error();
}
if(str.unwrap_value()[0] == 0) {
// end of function scope
if(!current_function) { /**/ }
current_function.unwrap().size = entry.n_value;
debug_map[current_module].push_back(std::move(current_function).unwrap());
} else {
current_function = debug_map_entry{};
current_function.unwrap().source_address = entry.n_value;
current_function.unwrap().name = str.unwrap_value();
}
}
break;
}
}
return debug_map;
}
Result<const std::vector<mach_o::symbol_entry>&, internal_error> mach_o::symbol_table() {
if(symbols) {
return symbols.unwrap();
}
if(tried_to_load_symbols) {
return internal_error("previous symbol table load failed");
}
tried_to_load_symbols = true;
std::vector<symbol_entry> symbol_table;
// we have a bunch of symbols in our binary we need to pair up with symbols from various .o files
// first collect symbols and the objects they come from
auto symtab_info_res = get_symtab_info();
if(!symtab_info_res) {
return std::move(symtab_info_res).unwrap_error();
}
if(!symtab_info_res.unwrap_value().get()) {
return internal_error("No symtab info");
}
const auto& symtab_info = symtab_info_res.unwrap_value().get().unwrap();
const auto& symtab = symtab_info.symtab;
// TODO: Take timestamp into account?
for(std::size_t j = 0; j < symtab.nsyms; j++) {
auto load_entry = bits == 32
? load_symtab_entry<32>(symtab.symoff, j)
: load_symtab_entry<64>(symtab.symoff, j);
if(!load_entry) {
return std::move(load_entry).unwrap_error();
}
auto& entry = load_entry.unwrap_value();
if(entry.n_type & N_STAB) {
continue;
}
if((entry.n_type & N_TYPE) == N_SECT) {
auto str = symtab_info.get_string(entry.n_un.n_strx);
if(!str) {
return std::move(str).unwrap_error();
}
symbol_table.push_back({
entry.n_value,
str.unwrap_value()
});
}
}
std::sort(
symbol_table.begin(),
symbol_table.end(),
[] (const symbol_entry& a, const symbol_entry& b) { return a.address < b.address; }
);
symbols = std::move(symbol_table);
return symbols.unwrap();
}
optional<std::string> mach_o::lookup_symbol(frame_ptr pc) {
auto symtab_ = symbol_table();
if(!symtab_) {
return nullopt;
}
const auto& symtab = symtab_.unwrap_value();;
auto it = first_less_than_or_equal(
symtab.begin(),
symtab.end(),
pc,
[] (frame_ptr pc, const symbol_entry& entry) {
return pc < entry.address;
}
);
if(it == symtab.end()) {
return nullopt;
}
ASSERT(pc >= it->address);
// TODO: We subtracted one from the address so name + diff won't show up in the objdump, decide if desirable
// to have an easier offset to lookup
return microfmt::format("{} + {}", it->name, pc - it->address);
}
// produce information similar to dsymutil -dump-debug-map
void mach_o::print_debug_map(const debug_map& debug_map) {
for(const auto& entry : debug_map) {
std::cout<<entry.first<<": "<< '\n';
for(const auto& symbol : entry.second) {
std::cerr
<< " "
<< symbol.name
<< " "
<< std::hex
<< symbol.source_address
<< " "
<< symbol.size
<< std::dec
<< '\n';
}
}
}
template<std::size_t Bits>
Result<monostate, internal_error> mach_o::load_mach() {
static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument");
bits = Bits;
using Mach_Header = typename std::conditional<Bits == 32, mach_header, mach_header_64>::type;
std::size_t header_size = sizeof(Mach_Header);
auto load_header = load_bytes<Mach_Header>(file, load_base);
if(!load_header) {
return load_header.unwrap_error();
}
Mach_Header& header = load_header.unwrap_value();
magic = header.magic;
if(should_swap()) {
swap_mach_header(header);
}
cputype = header.cputype;
cpusubtype = header.cpusubtype;
filetype = header.filetype;
n_load_commands = header.ncmds;
sizeof_load_commands = header.sizeofcmds;
flags = header.flags;
// handle load commands
std::uint32_t ncmds = header.ncmds;
std::uint32_t load_commands_offset = load_base + header_size;
// iterate load commands
std::uint32_t actual_offset = load_commands_offset;
for(std::uint32_t i = 0; i < ncmds; i++) {
auto load_cmd = load_bytes<load_command>(file, actual_offset);
if(!load_cmd) {
return load_cmd.unwrap_error();
}
load_command& cmd = load_cmd.unwrap_value();
if(should_swap()) {
swap_load_command(&cmd, NX_UnknownByteOrder);
}
load_commands.push_back({ actual_offset, cmd.cmd, cmd.cmdsize });
actual_offset += cmd.cmdsize;
}
return monostate{};
}
Result<monostate, internal_error> mach_o::load_fat_mach() {
std::size_t header_size = sizeof(fat_header);
std::size_t arch_size = sizeof(fat_arch);
auto load_header = load_bytes<fat_header>(file, 0);
if(!load_header) {
return load_header.unwrap_error();
}
fat_header& header = load_header.unwrap_value();
if(should_swap()) {
swap_fat_header(&header, NX_UnknownByteOrder);
}
// thread_local static struct LP(mach_header)* mhp = _NSGetMachExecuteHeader();
// off_t arch_offset = (off_t)header_size;
// for(std::size_t i = 0; i < header.nfat_arch; i++) {
// fat_arch arch = load_bytes<fat_arch>(file, arch_offset);
// if(should_swap()) {
// swap_fat_arch(&arch, 1, NX_UnknownByteOrder);
// }
// off_t mach_header_offset = (off_t)arch.offset;
// arch_offset += arch_size;
// std::uint32_t magic = load_bytes<std::uint32_t>(file, mach_header_offset);
// std::cerr<<"xxx: "<<arch.cputype<<" : "<<mhp->cputype<<std::endl;
// std::cerr<<" "<<arch.cpusubtype<<" : "<<static_cast<cpu_subtype_t>(mhp->cpusubtype & ~CPU_SUBTYPE_MASK)<<std::endl;
// if(
// arch.cputype == mhp->cputype &&
// static_cast<cpu_subtype_t>(mhp->cpusubtype & ~CPU_SUBTYPE_MASK) == arch.cpusubtype
// ) {
// load_base = mach_header_offset;
// fat_index = i;
// if(is_magic_64(magic)) {
// load_mach<64>(true);
// } else {
// load_mach<32>(true);
// }
// return;
// }
// }
std::vector<fat_arch> fat_arches;
fat_arches.reserve(header.nfat_arch);
off_t arch_offset = (off_t)header_size;
for(std::size_t i = 0; i < header.nfat_arch; i++) {
auto load_arch = load_bytes<fat_arch>(file, arch_offset);
if(!load_arch) {
return load_arch.unwrap_error();
}
fat_arch& arch = load_arch.unwrap_value();
if(should_swap()) {
swap_fat_arch(&arch, 1, NX_UnknownByteOrder);
}
fat_arches.push_back(arch);
arch_offset += arch_size;
}
thread_local static struct LP(mach_header)* mhp = _NSGetMachExecuteHeader();
fat_arch* best = NXFindBestFatArch(
mhp->cputype,
mhp->cpusubtype,
fat_arches.data(),
header.nfat_arch
);
if(best) {
off_t mach_header_offset = (off_t)best->offset;
auto magic = load_bytes<std::uint32_t>(file, mach_header_offset);
if(!magic) {
return magic.unwrap_error();
}
load_base = mach_header_offset;
fat_index = best - fat_arches.data();
if(is_magic_64(magic.unwrap_value())) {
load_mach<64>();
} else {
load_mach<32>();
}
return monostate{};
}
// If this is reached... something went wrong. The cpu we're on wasn't found.
return internal_error("Couldn't find appropriate architecture in fat Mach-O");
}
template<std::size_t Bits>
Result<segment_command_64, internal_error> mach_o::load_segment_command(std::uint32_t offset) const {
using Segment_Command = typename std::conditional<Bits == 32, segment_command, segment_command_64>::type;
auto load_segment = load_bytes<Segment_Command>(file, offset);
if(!load_segment) {
return load_segment.unwrap_error();
}
Segment_Command& segment = load_segment.unwrap_value();
ASSERT(segment.cmd == LC_SEGMENT_64 || segment.cmd == LC_SEGMENT);
if(should_swap()) {
swap_segment_command(segment);
}
// fields match just u64 instead of u32
segment_command_64 common;
common.cmd = segment.cmd;
common.cmdsize = segment.cmdsize;
static_assert(sizeof common.segname == 16 && sizeof segment.segname == 16, "xx");
memcpy(common.segname, segment.segname, 16);
common.vmaddr = segment.vmaddr;
common.vmsize = segment.vmsize;
common.fileoff = segment.fileoff;
common.filesize = segment.filesize;
common.maxprot = segment.maxprot;
common.initprot = segment.initprot;
common.nsects = segment.nsects;
common.flags = segment.flags;
return common;
}
Result<symtab_command, internal_error> mach_o::load_symbol_table_command(std::uint32_t offset) const {
auto load_symtab = load_bytes<symtab_command>(file, offset);
if(!load_symtab) {
return load_symtab.unwrap_error();
}
symtab_command& symtab = load_symtab.unwrap_value();
ASSERT(symtab.cmd == LC_SYMTAB);
if(should_swap()) {
swap_symtab_command(&symtab, NX_UnknownByteOrder);
}
return symtab;
}
template<std::size_t Bits>
Result<nlist_64, internal_error> mach_o::load_symtab_entry(std::uint32_t symbol_base, std::size_t index) const {
using Nlist = typename std::conditional<Bits == 32, struct nlist, struct nlist_64>::type;
uint32_t offset = load_base + symbol_base + index * sizeof(Nlist);
auto load_entry = load_bytes<Nlist>(file, offset);
if(!load_entry) {
return load_entry.unwrap_error();
}
Nlist& entry = load_entry.unwrap_value();
if(should_swap()) {
swap_nlist(entry);
}
// fields match just u64 instead of u32
nlist_64 common;
common.n_un.n_strx = entry.n_un.n_strx;
common.n_type = entry.n_type;
common.n_sect = entry.n_sect;
common.n_desc = entry.n_desc;
common.n_value = entry.n_value;
return common;
}
Result<std::vector<char>, internal_error> mach_o::load_string_table(std::uint32_t offset, std::uint32_t byte_count) const {
std::vector<char> buffer(byte_count + 1);
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.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
return buffer;
}
bool mach_o::should_swap() const {
return should_swap_bytes(magic);
}
Result<bool, internal_error> macho_is_fat(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);
}
auto magic = load_bytes<std::uint32_t>(file, 0);
if(!magic) {
return magic.unwrap_error();
} else {
return is_fat_magic(magic.unwrap_value());
}
}
Result<maybe_owned<mach_o>, internal_error> open_mach_o_cached(const std::string& object_path) {
if(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); });
}
}
}
}
#pragma GCC diagnostic pop
#endif

147
src/binary/mach-o.hpp Normal file
View File

@ -0,0 +1,147 @@
#ifndef MACHO_HPP
#define MACHO_HPP
#include "utils/common.hpp"
#include "utils/utils.hpp"
#if IS_APPLE
#include <cstdint>
#include <limits>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include <mach-o/arch.h>
#include <mach-o/loader.h>
#include <mach-o/nlist.h>
namespace cpptrace {
namespace detail {
bool file_is_mach_o(const std::string& object_path) noexcept;
struct load_command_entry {
std::uint32_t file_offset;
std::uint32_t cmd;
std::uint32_t cmdsize;
};
class mach_o {
public:
struct debug_map_entry {
uint64_t source_address;
uint64_t size;
std::string name;
};
struct symbol_entry {
uint64_t address;
std::string name;
};
// map from object file to a vector of symbols to resolve
using debug_map = std::unordered_map<std::string, std::vector<debug_map_entry>>;
private:
file_wrapper file;
std::string object_path;
std::uint32_t magic;
cpu_type_t cputype;
cpu_subtype_t cpusubtype;
std::uint32_t filetype;
std::uint32_t n_load_commands;
std::uint32_t sizeof_load_commands;
std::uint32_t flags;
std::size_t bits = 0; // 32 or 64 once load_mach is called
std::size_t load_base = 0;
std::size_t fat_index = std::numeric_limits<std::size_t>::max();
std::vector<load_command_entry> load_commands;
struct symtab_info_data {
symtab_command symtab;
optional<std::vector<char>> stringtab;
Result<const char*, internal_error> get_string(std::size_t index) const;
};
bool tried_to_load_symtab = false;
optional<symtab_info_data> symtab_info;
bool tried_to_load_symbols = false;
optional<std::vector<symbol_entry>> symbols;
mach_o(
file_wrapper file,
const std::string& object_path,
std::uint32_t magic
) :
file(std::move(file)),
object_path(object_path),
magic(magic) {}
Result<monostate, internal_error> load();
public:
static NODISCARD Result<mach_o, internal_error> open_mach_o(const std::string& object_path);
mach_o(mach_o&&) = default;
~mach_o() = default;
Result<std::uintptr_t, internal_error> get_text_vmaddr();
std::size_t get_fat_index() const;
void print_segments() const;
Result<std::reference_wrapper<optional<symtab_info_data>>, internal_error> get_symtab_info();
void print_symbol_table_entry(
const nlist_64& entry,
const char* stringtab,
std::size_t stringsize,
std::size_t j
) const;
void print_symbol_table();
// produce information similar to dsymutil -dump-debug-map
Result<debug_map, internal_error> get_debug_map();
Result<const std::vector<symbol_entry>&, internal_error> symbol_table();
optional<std::string> lookup_symbol(frame_ptr pc);
// produce information similar to dsymutil -dump-debug-map
static void print_debug_map(const debug_map& debug_map);
private:
template<std::size_t Bits>
Result<monostate, internal_error> load_mach();
Result<monostate, internal_error> load_fat_mach();
template<std::size_t Bits>
Result<segment_command_64, internal_error> load_segment_command(std::uint32_t offset) const;
Result<symtab_command, internal_error> load_symbol_table_command(std::uint32_t offset) const;
template<std::size_t Bits>
Result<nlist_64, internal_error> load_symtab_entry(std::uint32_t symbol_base, std::size_t index) const;
Result<std::vector<char>, internal_error> load_string_table(std::uint32_t offset, std::uint32_t byte_count) const;
bool should_swap() const;
};
Result<bool, internal_error> macho_is_fat(const std::string& object_path);
NODISCARD Result<maybe_owned<mach_o>, internal_error> open_mach_o_cached(const std::string& object_path);
}
}
#endif
#endif

View File

@ -0,0 +1,97 @@
#include "binary/module_base.hpp"
#include "platform/platform.hpp"
#include "utils/utils.hpp"
#include <string>
#include <mutex>
#include <unordered_map>
#if IS_LINUX || IS_APPLE
#include <unistd.h>
#include <dlfcn.h>
#if IS_APPLE
#include "binary/mach-o.hpp"
#else
#include "binary/elf.hpp"
#endif
#elif IS_WINDOWS
#include "binary/pe.hpp"
#endif
namespace cpptrace {
namespace detail {
#if IS_LINUX
Result<std::uintptr_t, internal_error> get_module_image_base(const std::string& object_path) {
static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex);
static std::unordered_map<std::string, std::uintptr_t> cache;
auto it = cache.find(object_path);
if(it == cache.end()) {
// arguably it'd be better to release the lock while computing this, but also arguably it's good to not
// have two threads try to do the same computation
auto elf_object = open_elf_cached(object_path);
// TODO: Cache the error
if(!elf_object) {
return elf_object.unwrap_error();
}
auto base = elf_object.unwrap_value()->get_module_image_base();
if(base.is_error()) {
return std::move(base).unwrap_error();
}
cache.insert(it, {object_path, base.unwrap_value()});
return base;
} else {
return it->second;
}
}
#elif IS_APPLE
Result<std::uintptr_t, internal_error> get_module_image_base(const std::string& object_path) {
// We have to parse the Mach-O to find the offset of the text section.....
// I don't know how addresses are handled if there is more than one __TEXT load command. I'm assuming for
// now that there is only one, and I'm using only the first section entry within that load command.
static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex);
static std::unordered_map<std::string, std::uintptr_t> cache;
auto it = cache.find(object_path);
if(it == cache.end()) {
// arguably it'd be better to release the lock while computing this, but also arguably it's good to not
// have two threads try to do the same computation
auto mach_o_object = open_mach_o_cached(object_path);
// TODO: Cache the error
if(!mach_o_object) {
return mach_o_object.unwrap_error();
}
auto base = mach_o_object.unwrap_value()->get_text_vmaddr();
if(!base) {
return std::move(base).unwrap_error();
}
cache.insert(it, {object_path, base.unwrap_value()});
return base;
} else {
return it->second;
}
}
#else // Windows
Result<std::uintptr_t, internal_error> get_module_image_base(const std::string& object_path) {
static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex);
static std::unordered_map<std::string, std::uintptr_t> cache;
auto it = cache.find(object_path);
if(it == cache.end()) {
// arguably it'd be better to release the lock while computing this, but also arguably it's good to not
// have two threads try to do the same computation
auto base = pe_get_module_image_base(object_path);
// TODO: Cache the error
if(!base) {
return std::move(base).unwrap_error();
}
cache.insert(it, {object_path, base.unwrap_value()});
return base;
} else {
return it->second;
}
}
#endif
}
}

View File

@ -0,0 +1,15 @@
#ifndef IMAGE_MODULE_BASE_HPP
#define IMAGE_MODULE_BASE_HPP
#include "utils/utils.hpp"
#include <cstdint>
#include <string>
namespace cpptrace {
namespace detail {
Result<std::uintptr_t, internal_error> get_module_image_base(const std::string& object_path);
}
}
#endif

189
src/binary/object.cpp Normal file
View File

@ -0,0 +1,189 @@
#include "binary/object.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>
#if IS_LINUX || IS_APPLE
#include <unistd.h>
#include <dlfcn.h>
#if IS_LINUX
#include <link.h> // needed for dladdr1's link_map info
#endif
#elif IS_WINDOWS
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#endif
namespace cpptrace {
namespace detail {
#if IS_LINUX || IS_APPLE
#if defined(CPPTRACE_HAS_DL_FIND_OBJECT) || defined(CPPTRACE_HAS_DLADDR1)
std::string resolve_l_name(const char* l_name) {
if(l_name != nullptr && l_name[0] != 0) {
return l_name;
} else {
// empty l_name, this means it's the currently running executable
// TODO: Caching and proper handling
char buffer[CPPTRACE_PATH_MAX + 1]{};
auto res = readlink("/proc/self/exe", buffer, CPPTRACE_PATH_MAX);
if(res == -1) {
return ""; // TODO
} else {
return buffer;
}
}
}
#endif
// dladdr queries are needed to get pre-ASLR addresses and targets to run symbol resolution on
// _dl_find_object is preferred if at all possible as it is much faster (added in glibc 2.35)
// dladdr1 is preferred if possible because it allows for a more accurate object path to be resolved (glibc 2.3.3)
#ifdef CPPTRACE_HAS_DL_FIND_OBJECT // we don't even check for this on apple
object_frame get_frame_object_info(frame_ptr address) {
// Use _dl_find_object when we can, it's orders of magnitude faster
object_frame frame;
frame.raw_address = address;
frame.object_address = 0;
dl_find_object result;
if(_dl_find_object(reinterpret_cast<void*>(address), &result) == 0) { // thread safe
frame.object_path = resolve_l_name(result.dlfo_link_map->l_name);
frame.object_address = address - to_frame_ptr(result.dlfo_link_map->l_addr);
}
return frame;
}
#elif defined(CPPTRACE_HAS_DLADDR1)
object_frame get_frame_object_info(frame_ptr address) {
// https://github.com/bminor/glibc/blob/91695ee4598b39d181ab8df579b888a8863c4cab/elf/dl-addr.c#L26
Dl_info info;
link_map* link_map_info;
object_frame frame;
frame.raw_address = address;
frame.object_address = 0;
if(
// thread safe
dladdr1(reinterpret_cast<void*>(address), &info, reinterpret_cast<void**>(&link_map_info), RTLD_DL_LINKMAP)
) {
frame.object_path = resolve_l_name(link_map_info->l_name);
auto base = get_module_image_base(frame.object_path);
if(base.has_value()) {
frame.object_address = address
- reinterpret_cast<std::uintptr_t>(info.dli_fbase)
+ base.unwrap_value();
} else {
if(!should_absorb_trace_exceptions()) {
base.drop_error();
}
}
}
return frame;
}
#else
// glibc dladdr may not return an accurate dli_fname as it uses argv[0] for addresses in the main executable
// https://github.com/bminor/glibc/blob/caed1f5c0b2e31b5f4e0f21fea4b2c9ecd3b5b30/elf/dl-addr.c#L33-L36
// macos doesn't have dladdr1 but its dli_fname behaves more sensibly, same with some other libc's like musl
object_frame get_frame_object_info(frame_ptr address) {
// reference: https://github.com/bminor/glibc/blob/master/debug/backtracesyms.c
Dl_info info;
object_frame frame;
frame.raw_address = address;
frame.object_address = 0;
if(dladdr(reinterpret_cast<void*>(address), &info)) { // thread safe
frame.object_path = info.dli_fname;
auto base = get_module_image_base(info.dli_fname);
if(base.has_value()) {
frame.object_address = address
- reinterpret_cast<std::uintptr_t>(info.dli_fbase)
+ base.unwrap_value();
} else {
if(!should_absorb_trace_exceptions()) {
base.drop_error();
}
}
}
return frame;
}
#endif
#else
std::string get_module_name(HMODULE handle) {
static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex);
static std::unordered_map<HMODULE, std::string> cache;
auto it = cache.find(handle);
if(it == cache.end()) {
char path[MAX_PATH];
if(GetModuleFileNameA(handle, path, sizeof(path))) {
cache.insert(it, {handle, path});
return path;
} else {
std::fprintf(stderr, "%s\n", std::system_error(GetLastError(), std::system_category()).what());
cache.insert(it, {handle, ""});
return "";
}
} else {
return it->second;
}
}
object_frame get_frame_object_info(frame_ptr address) {
object_frame frame;
frame.raw_address = address;
frame.object_address = 0;
HMODULE handle;
// Multithread safe as long as another thread doesn't come along and free the module
if(GetModuleHandleExA(
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
reinterpret_cast<const char*>(address),
&handle
)) {
frame.object_path = get_module_name(handle);
auto base = get_module_image_base(frame.object_path);
if(base.has_value()) {
frame.object_address = address
- reinterpret_cast<std::uintptr_t>(handle)
+ base.unwrap_value();
} else {
if(!should_absorb_trace_exceptions()) {
base.drop_error();
}
}
} else {
std::fprintf(stderr, "%s\n", std::system_error(GetLastError(), std::system_category()).what());
}
return frame;
}
#endif
std::vector<object_frame> get_frames_object_info(const std::vector<frame_ptr>& addresses) {
std::vector<object_frame> frames;
frames.reserve(addresses.size());
for(const frame_ptr address : addresses) {
frames.push_back(get_frame_object_info(address));
}
return frames;
}
object_frame resolve_safe_object_frame(const safe_object_frame& frame) {
std::string object_path = frame.object_path;
if(object_path.empty()) {
return {
frame.raw_address,
0,
""
};
}
return {
frame.raw_address,
frame.address_relative_to_object_start,
std::move(object_path)
};
}
}
}

20
src/binary/object.hpp Normal file
View File

@ -0,0 +1,20 @@
#ifndef OBJECT_HPP
#define OBJECT_HPP
#include <cpptrace/forward.hpp>
#include <vector>
#include <cstdint>
namespace cpptrace {
namespace detail {
object_frame get_frame_object_info(frame_ptr address);
std::vector<object_frame> get_frames_object_info(const std::vector<frame_ptr>& addresses);
object_frame resolve_safe_object_frame(const safe_object_frame& frame);
}
}
#endif

97
src/binary/pe.cpp Normal file
View File

@ -0,0 +1,97 @@
#include "binary/pe.hpp"
#include "platform/platform.hpp"
#include "utils/error.hpp"
#include "utils/utils.hpp"
#if IS_WINDOWS
#include <array>
#include <cstdio>
#include <cstring>
#include <string>
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
namespace cpptrace {
namespace detail {
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
T pe_byteswap_if_needed(T value) {
// PE header values are little endian, I think dos e_lfanew should be too
if(!is_little_endian()) {
return byteswap(value);
} else {
return value;
}
}
Result<std::uintptr_t, internal_error> pe_get_module_image_base(const std::string& object_path) {
// https://drive.google.com/file/d/0B3_wGJkuWLytbnIxY1J5WUs4MEk/view?pli=1&resourcekey=0-n5zZ2UW39xVTH8ZSu6C2aQ
// https://0xrick.github.io/win-internals/pe3/
// Endianness should always be little for dos and pe headers
std::FILE* file_ptr;
errno_t ret = fopen_s(&file_ptr, object_path.c_str(), "rb");
auto file = raii_wrap(std::move(file_ptr), file_deleter);
if(ret != 0 || file == nullptr) {
return internal_error("Unable to read object file {}", object_path);
}
auto magic = load_bytes<std::array<char, 2>>(file, 0);
if(!magic) {
return std::move(magic).unwrap_error();
}
if(std::memcmp(magic.unwrap_value().data(), "MZ", 2) != 0) {
return internal_error("File is not a PE file {}", object_path);
}
auto e_lfanew = load_bytes<DWORD>(file, 0x3c); // dos header + 0x3c
if(!e_lfanew) {
return std::move(e_lfanew).unwrap_error();
}
DWORD nt_header_offset = pe_byteswap_if_needed(e_lfanew.unwrap_value());
auto signature = load_bytes<std::array<char, 4>>(file, nt_header_offset); // nt header + 0
if(!signature) {
return std::move(signature).unwrap_error();
}
if(std::memcmp(signature.unwrap_value().data(), "PE\0\0", 4) != 0) {
return internal_error("File is not a PE file {}", object_path);
}
auto size_of_optional_header_raw = load_bytes<WORD>(file, nt_header_offset + 4 + 0x10); // file header + 0x10
if(!size_of_optional_header_raw) {
return std::move(size_of_optional_header_raw).unwrap_error();
}
WORD size_of_optional_header = pe_byteswap_if_needed(size_of_optional_header_raw.unwrap_value());
if(size_of_optional_header == 0) {
return internal_error("Unexpected optional header size for PE file");
}
auto optional_header_magic_raw = load_bytes<WORD>(file, nt_header_offset + 0x18); // optional header + 0x0
if(!optional_header_magic_raw) {
return std::move(optional_header_magic_raw).unwrap_error();
}
WORD optional_header_magic = pe_byteswap_if_needed(optional_header_magic_raw.unwrap_value());
VERIFY(
optional_header_magic == IMAGE_NT_OPTIONAL_HDR_MAGIC,
("PE file does not match expected bit-mode " + object_path).c_str()
);
// finally get image base
if(optional_header_magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
// 32 bit
auto bytes = load_bytes<DWORD>(file, nt_header_offset + 0x18 + 0x1c); // optional header + 0x1c
if(!bytes) {
return std::move(bytes).unwrap_error();
}
return to<std::uintptr_t>(pe_byteswap_if_needed(bytes.unwrap_value()));
} else {
// 64 bit
// I get an "error: 'QWORD' was not declared in this scope" for some reason when using QWORD
auto bytes = load_bytes<unsigned __int64>(file, nt_header_offset + 0x18 + 0x18); // optional header + 0x18
if(!bytes) {
return std::move(bytes).unwrap_error();
}
return to<std::uintptr_t>(pe_byteswap_if_needed(bytes.unwrap_value()));
}
}
}
}
#endif

19
src/binary/pe.hpp Normal file
View File

@ -0,0 +1,19 @@
#ifndef PE_HPP
#define PE_HPP
#include "platform/platform.hpp"
#include "utils/utils.hpp"
#if IS_WINDOWS
#include <cstdint>
#include <string>
namespace cpptrace {
namespace detail {
Result<std::uintptr_t, internal_error> pe_get_module_image_base(const std::string& object_path);
}
}
#endif
#endif

76
src/binary/safe_dl.cpp Normal file
View File

@ -0,0 +1,76 @@
#include "binary/safe_dl.hpp"
#include "utils/common.hpp"
#include "utils/utils.hpp"
#include "platform/program_name.hpp"
#include <string>
#include <vector>
#include <mutex>
#include <unordered_map>
#include <cstring>
#include <iostream>
#ifdef CPPTRACE_HAS_DL_FIND_OBJECT
#if IS_LINUX || IS_APPLE
#include <unistd.h>
#include <dlfcn.h>
#include <link.h>
#endif
namespace cpptrace {
namespace detail {
void get_safe_object_frame(frame_ptr address, safe_object_frame* out) {
out->raw_address = address;
dl_find_object result;
if(_dl_find_object(reinterpret_cast<void*>(address), &result) == 0) { // thread-safe, signal-safe
out->address_relative_to_object_start = address - to_frame_ptr(result.dlfo_link_map->l_addr);
if(result.dlfo_link_map->l_name != nullptr && result.dlfo_link_map->l_name[0] != 0) {
std::size_t path_length = std::strlen(result.dlfo_link_map->l_name);
std::memcpy(
out->object_path,
result.dlfo_link_map->l_name,
std::min(path_length + 1, std::size_t(CPPTRACE_PATH_MAX + 1))
);
} else {
// empty l_name, this means it's the currently running executable
memset(out->object_path, 0, CPPTRACE_PATH_MAX + 1);
// signal-safe
auto res = readlink("/proc/self/exe", out->object_path, CPPTRACE_PATH_MAX);
if(res == -1) {
// error handling?
}
// TODO: Special handling for /proc/pid/exe unlink edge case
}
} else {
// std::cout<<"error"<<std::endl;
out->address_relative_to_object_start = 0;
out->object_path[0] = 0;
}
// TODO: Handle this part of the documentation?
// The address can be a code address or data address. On architectures using function descriptors, no attempt is
// made to decode the function descriptor. Depending on how these descriptors are implemented, _dl_find_object
// may return the object that defines the function descriptor (and not the object that contains the code
// implementing the function), or fail to find any object at all.
}
bool has_get_safe_object_frame() {
return true;
}
}
}
#else
namespace cpptrace {
namespace detail {
void get_safe_object_frame(frame_ptr address, safe_object_frame* out) {
out->raw_address = address;
out->address_relative_to_object_start = 0;
out->object_path[0] = 0;
}
bool has_get_safe_object_frame() {
return false;
}
}
}
#endif

14
src/binary/safe_dl.hpp Normal file
View File

@ -0,0 +1,14 @@
#ifndef SAFE_DL_HPP
#define SAFE_DL_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();
}
}
#endif

View File

@ -1,103 +1,340 @@
#include <cpptrace/cpptrace.hpp>
#include <cpptrace/formatting.hpp>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <iomanip>
#include <iostream>
#if !(defined(CPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE) || defined(CPPTRACE_FULL_TRACE_WITH_STACKTRACE))
#include "cpptrace/basic.hpp"
#include "symbols/symbols.hpp"
#include "unwind/unwind.hpp"
#include "demangle/demangle.hpp"
#include "platform/common.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
std::vector<stacktrace_frame> generate_trace(std::uint32_t skip) {
std::vector<void*> frames = detail::capture_frames(skip + 1);
detail::symbolizer symbolizer;
std::vector<stacktrace_frame> trace = symbolizer.resolve_frames(frames);
for(auto& frame : trace) {
frame.symbol = detail::demangle(frame.symbol);
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{};
}
return trace;
}
}
#else
// full trace
#include "full/full_trace.hpp"
#include "demangle/demangle.hpp"
namespace cpptrace {
CPPTRACE_FORCE_NO_INLINE
std::vector<stacktrace_frame> generate_trace(std::uint32_t skip) {
auto trace = detail::generate_trace(skip + 1);
for(auto& entry : trace) {
entry.symbol = detail::demangle(entry.symbol);
}
return trace;
}
}
#endif
#define ESC "\033["
#define RESET ESC "0m"
#define RED ESC "31m"
#define GREEN ESC "32m"
#define YELLOW ESC "33m"
#define BLUE ESC "34m"
#define MAGENTA ESC "35m"
#define CYAN ESC "36m"
namespace cpptrace {
void print_trace(std::uint32_t skip) {
enable_virtual_terminal_processing_if_needed();
std::cerr<<"Stack trace (most recent call first):"<<std::endl;
std::size_t counter = 0;
const auto trace = generate_trace(skip + 1);
if(trace.empty()) {
std::cerr<<"<empty trace>"<<std::endl;
return;
}
const auto frame_number_width = n_digits(static_cast<int>(trace.size()) - 1);
for(const auto& frame : trace) {
std::cerr
<< '#'
<< std::setw(static_cast<int>(frame_number_width))
<< std::left
<< counter++
<< std::right
<< " "
<< std::hex
<< BLUE
<< "0x"
<< std::setw(2 * sizeof(uintptr_t))
<< std::setfill('0')
<< frame.address
<< std::dec
<< std::setfill(' ')
<< RESET
<< " in "
<< YELLOW
<< frame.symbol
<< RESET
<< " at "
<< GREEN
<< frame.filename
<< RESET
<< ":"
<< BLUE
<< frame.line
<< RESET
<< (frame.col > 0 ? ":" BLUE + std::to_string(frame.col) + RESET : "")
<< std::endl;
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 {
try {
return object_trace{detail::get_frames_object_info(frames)};
} catch(...) { // NOSONAR
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return object_trace{};
}
}
stacktrace raw_trace::resolve() const {
try {
std::vector<stacktrace_frame> trace = detail::resolve_frames(frames);
for(auto& frame : trace) {
frame.symbol = detail::demangle(frame.symbol, true);
}
return {std::move(trace)};
} catch(...) { // NOSONAR
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return stacktrace{};
}
}
void raw_trace::clear() {
frames.clear();
}
bool raw_trace::empty() const noexcept {
return frames.empty();
}
CPPTRACE_FORCE_NO_INLINE
object_trace object_trace::current(std::size_t skip) {
try { // try/catch can never be hit but it's needed to prevent TCO
return generate_object_trace(skip + 1);
} catch(...) {
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, true);
}
return {std::move(trace)};
} catch(...) { // NOSONAR
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return stacktrace();
}
}
void object_trace::clear() {
frames.clear();
}
bool object_trace::empty() const noexcept {
return frames.empty();
}
object_frame stacktrace_frame::get_object_info() const {
return detail::get_frame_object_info(raw_address);
}
std::string stacktrace_frame::to_string() const {
return to_string(false);
}
std::string stacktrace_frame::to_string(bool color) const {
return get_default_formatter().format(*this, color);
}
std::ostream& operator<<(std::ostream& stream, const stacktrace_frame& frame) {
return stream << frame.to_string();
}
CPPTRACE_FORCE_NO_INLINE
stacktrace stacktrace::current(std::size_t skip) {
try { // try/catch can never be hit but it's needed to prevent TCO
return generate_trace(skip + 1);
} catch(...) {
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 {
get_default_formatter().print(*this);
}
void stacktrace::print(std::ostream& stream) const {
get_default_formatter().print(stream, *this);
}
void stacktrace::print(std::ostream& stream, bool color) const {
get_default_formatter().print(stream, *this, color);
}
namespace detail {
const formatter& get_default_snippet_formatter() {
static formatter snippet_formatter = formatter{}.snippets(true);
return snippet_formatter;
}
}
void stacktrace::print_with_snippets() const {
detail::get_default_snippet_formatter().print(*this);
}
void stacktrace::print_with_snippets(std::ostream& stream) const {
detail::get_default_snippet_formatter().print(stream, *this);
}
void stacktrace::print_with_snippets(std::ostream& stream, bool color) const {
detail::get_default_snippet_formatter().print(stream, *this, color);
}
void stacktrace::clear() {
frames.clear();
}
bool stacktrace::empty() const noexcept {
return frames.empty();
}
std::string stacktrace::to_string(bool color) const {
return get_default_formatter().format(*this, color);
}
std::ostream& operator<<(std::ostream& stream, const stacktrace& trace) {
get_default_formatter().print(stream, trace);
return stream;
}
CPPTRACE_FORCE_NO_INLINE
raw_trace generate_raw_trace(std::size_t skip) {
try {
return raw_trace{detail::capture_frames(skip + 1, SIZE_MAX)};
} catch(...) { // NOSONAR
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return raw_trace{};
}
}
CPPTRACE_FORCE_NO_INLINE
raw_trace generate_raw_trace(std::size_t skip, std::size_t max_depth) {
try {
return raw_trace{detail::capture_frames(skip + 1, max_depth)};
} catch(...) { // NOSONAR
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return raw_trace{};
}
}
CPPTRACE_FORCE_NO_INLINE
std::size_t safe_generate_raw_trace(frame_ptr* buffer, std::size_t size, std::size_t skip) {
try { // try/catch can never be hit but it's needed to prevent TCO
return detail::safe_capture_frames(buffer, size, skip + 1, SIZE_MAX);
} catch(...) {
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return 0;
}
}
CPPTRACE_FORCE_NO_INLINE
std::size_t safe_generate_raw_trace(
frame_ptr* buffer,
std::size_t size,
std::size_t skip,
std::size_t max_depth
) {
try { // try/catch can never be hit but it's needed to prevent TCO
return detail::safe_capture_frames(buffer, size, skip + 1, max_depth);
} catch(...) {
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return 0;
}
}
CPPTRACE_FORCE_NO_INLINE
object_trace generate_object_trace(std::size_t skip) {
try {
return object_trace{detail::get_frames_object_info(detail::capture_frames(skip + 1, SIZE_MAX))};
} catch(...) { // NOSONAR
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return object_trace{};
}
}
CPPTRACE_FORCE_NO_INLINE
object_trace generate_object_trace(std::size_t skip, std::size_t max_depth) {
try {
return object_trace{detail::get_frames_object_info(detail::capture_frames(skip + 1, max_depth))};
} catch(...) { // NOSONAR
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return object_trace{};
}
}
CPPTRACE_FORCE_NO_INLINE
stacktrace generate_trace(std::size_t skip) {
try { // try/catch can never be hit but it's needed to prevent TCO
return generate_trace(skip + 1, SIZE_MAX);
} catch(...) {
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return stacktrace{};
}
}
CPPTRACE_FORCE_NO_INLINE
stacktrace generate_trace(std::size_t skip, std::size_t max_depth) {
try {
std::vector<frame_ptr> frames = detail::capture_frames(skip + 1, max_depth);
std::vector<stacktrace_frame> trace = detail::resolve_frames(frames);
for(auto& frame : trace) {
frame.symbol = detail::demangle(frame.symbol, true);
}
return {std::move(trace)};
} catch(...) { // NOSONAR
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return stacktrace();
}
}
object_frame safe_object_frame::resolve() const {
return detail::resolve_safe_object_frame(*this);
}
void get_safe_object_frame(frame_ptr address, safe_object_frame* out) {
detail::get_safe_object_frame(address, out);
}
bool can_signal_safe_unwind() {
return detail::has_safe_unwind();
}
bool can_get_safe_object_frame() {
return detail::has_get_safe_object_frame();
}
}

446
src/ctrace.cpp Normal file
View File

@ -0,0 +1,446 @@
#include <ctrace/ctrace.h>
#include <cpptrace/cpptrace.hpp>
#include <algorithm>
#include "symbols/symbols.hpp"
#include "unwind/unwind.hpp"
#include "demangle/demangle.hpp"
#include "platform/exception_type.hpp"
#include "utils/common.hpp"
#include "utils/utils.hpp"
#include "binary/object.hpp"
#include "binary/safe_dl.hpp"
#define ESC "\033["
#define RESET ESC "0m"
#define RED ESC "31m"
#define GREEN ESC "32m"
#define YELLOW ESC "33m"
#define BLUE ESC "34m"
#define MAGENTA ESC "35m"
#define CYAN ESC "36m"
#if defined(__GNUC__) && ((__GNUC__ > 2) || (__GNUC__ == 2 && __GNUC_MINOR__ >= 6))
# define CTRACE_GNU_FORMAT(...) __attribute__((format(__VA_ARGS__)))
#elif defined(__clang__)
// Probably requires llvm >3.5? Not exactly sure.
# define CTRACE_GNU_FORMAT(...) __attribute__((format(__VA_ARGS__)))
#else
# define CTRACE_GNU_FORMAT(...)
#endif
#if defined(__clang__)
# define CTRACE_FORMAT_PROLOGUE \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wformat-security\"")
# define CTRACE_FORMAT_EPILOGUE \
_Pragma("clang diagnostic pop")
#elif defined(__GNUC_MINOR__)
# define CTRACE_FORMAT_PROLOGUE \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wformat-security\"")
# define CTRACE_FORMAT_EPILOGUE \
_Pragma("GCC diagnostic pop")
#else
# define CTRACE_FORMAT_PROLOGUE
# define CTRACE_FORMAT_EPILOGUE
#endif
namespace ctrace {
static constexpr std::uint32_t invalid_pos = ~0U;
CTRACE_FORMAT_PROLOGUE
template <typename...Args>
CTRACE_GNU_FORMAT(printf, 2, 0)
static void ffprintf(std::FILE* f, const char fmt[], Args&&...args) {
(void)std::fprintf(f, fmt, args...);
(void)fflush(f);
}
CTRACE_FORMAT_EPILOGUE
static bool is_empty(std::uint32_t pos) noexcept {
return pos == invalid_pos;
}
static bool is_empty(const char* str) noexcept {
return !str || std::char_traits<char>::length(str) == 0;
}
static ctrace_owning_string generate_owning_string(const char* raw_string) noexcept {
// Returns length to the null terminator.
std::size_t count = std::char_traits<char>::length(raw_string);
char* new_string = new char[count + 1];
std::char_traits<char>::copy(new_string, raw_string, count);
new_string[count] = '\0';
return { new_string };
}
static ctrace_owning_string generate_owning_string(const std::string& std_string) {
return generate_owning_string(std_string.c_str());
}
static void free_owning_string(const char* owned_string) noexcept {
if(!owned_string) return; // Not necessary but eh
delete[] owned_string;
}
static void free_owning_string(ctrace_owning_string& owned_string) noexcept {
free_owning_string(owned_string.data);
}
static ctrace_object_frame convert_object_frame(const cpptrace::object_frame& frame) {
const char* new_path = generate_owning_string(frame.object_path).data;
return { frame.raw_address, frame.object_address, new_path };
}
static ctrace_object_trace c_convert(const std::vector<cpptrace::object_frame>& trace) {
std::size_t count = trace.size();
auto* frames = new ctrace_object_frame[count];
std::transform(trace.begin(), trace.end(), frames, convert_object_frame);
return { frames, count };
}
static ctrace_stacktrace_frame convert_stacktrace_frame(const cpptrace::stacktrace_frame& frame) {
ctrace_stacktrace_frame new_frame;
new_frame.raw_address = frame.raw_address;
new_frame.object_address = frame.object_address;
new_frame.line = frame.line.value_or(invalid_pos);
new_frame.column = frame.column.value_or(invalid_pos);
new_frame.filename = generate_owning_string(frame.filename).data;
new_frame.symbol = generate_owning_string(cpptrace::detail::demangle(frame.symbol, true)).data;
new_frame.is_inline = ctrace_bool(frame.is_inline);
return new_frame;
}
static cpptrace::stacktrace_frame convert_stacktrace_frame(const ctrace_stacktrace_frame& frame) {
using nullable_type = cpptrace::nullable<std::uint32_t>;
static constexpr auto null_v = nullable_type::null().raw_value;
cpptrace::stacktrace_frame new_frame;
new_frame.raw_address = frame.raw_address;
new_frame.object_address = frame.object_address;
new_frame.line = nullable_type{is_empty(frame.line) ? null_v : frame.line};
new_frame.column = nullable_type{is_empty(frame.column) ? null_v : frame.column};
new_frame.filename = frame.filename;
new_frame.symbol = frame.symbol;
new_frame.is_inline = bool(frame.is_inline);
return new_frame;
}
static ctrace_stacktrace c_convert(const std::vector<cpptrace::stacktrace_frame>& trace) {
std::size_t count = trace.size();
auto* frames = new ctrace_stacktrace_frame[count];
std::transform(
trace.begin(),
trace.end(), frames,
static_cast<ctrace_stacktrace_frame(*)(const cpptrace::stacktrace_frame&)>(convert_stacktrace_frame)
);
return { frames, count };
}
static cpptrace::stacktrace cpp_convert(const ctrace_stacktrace* ptrace) {
if(!ptrace || !ptrace->frames) {
return { };
}
std::vector<cpptrace::stacktrace_frame> new_frames;
new_frames.reserve(ptrace->count);
for(std::size_t i = 0; i < ptrace->count; ++i) {
new_frames.push_back(convert_stacktrace_frame(ptrace->frames[i]));
}
return cpptrace::stacktrace{std::move(new_frames)};
}
}
extern "C" {
// ctrace::string
ctrace_owning_string ctrace_generate_owning_string(const char* raw_string) {
return ctrace::generate_owning_string(raw_string);
}
void ctrace_free_owning_string(ctrace_owning_string* string) {
if(!string) {
return;
}
ctrace::free_owning_string(*string);
string->data = nullptr;
}
// ctrace::generation:
CTRACE_FORCE_NO_INLINE
ctrace_raw_trace ctrace_generate_raw_trace(size_t skip, size_t max_depth) {
try {
std::vector<cpptrace::frame_ptr> trace = cpptrace::detail::capture_frames(skip + 1, max_depth);
std::size_t count = trace.size();
auto* frames = new ctrace_frame_ptr[count];
std::copy(trace.data(), trace.data() + count, frames);
return { frames, count };
} catch(...) {
// Don't check rethrow condition, it's risky.
return { nullptr, 0 };
}
}
CTRACE_FORCE_NO_INLINE
ctrace_object_trace ctrace_generate_object_trace(size_t skip, size_t max_depth) {
try {
std::vector<cpptrace::object_frame> trace = cpptrace::detail::get_frames_object_info(
cpptrace::detail::capture_frames(skip + 1, max_depth)
);
return ctrace::c_convert(trace);
} catch(...) { // NOSONAR
// Don't check rethrow condition, it's risky.
return { nullptr, 0 };
}
}
CTRACE_FORCE_NO_INLINE
ctrace_stacktrace ctrace_generate_trace(size_t skip, size_t max_depth) {
try {
std::vector<cpptrace::frame_ptr> frames = cpptrace::detail::capture_frames(skip + 1, max_depth);
std::vector<cpptrace::stacktrace_frame> trace = cpptrace::detail::resolve_frames(frames);
return ctrace::c_convert(trace);
} catch(...) { // NOSONAR
// Don't check rethrow condition, it's risky.
return { nullptr, 0 };
}
}
// ctrace::freeing:
void ctrace_free_raw_trace(ctrace_raw_trace* trace) {
if(!trace) {
return;
}
ctrace_frame_ptr* frames = trace->frames;
delete[] frames;
trace->frames = nullptr;
trace->count = 0;
}
void ctrace_free_object_trace(ctrace_object_trace* trace) {
if(!trace || !trace->frames) {
return;
}
ctrace_object_frame* frames = trace->frames;
for(std::size_t i = 0; i < trace->count; ++i) {
const char* path = frames[i].obj_path;
ctrace::free_owning_string(path);
}
delete[] frames;
trace->frames = nullptr;
trace->count = 0;
}
void ctrace_free_stacktrace(ctrace_stacktrace* trace) {
if(!trace || !trace->frames) {
return;
}
ctrace_stacktrace_frame* frames = trace->frames;
for(std::size_t i = 0; i < trace->count; ++i) {
ctrace::free_owning_string(frames[i].filename);
ctrace::free_owning_string(frames[i].symbol);
}
delete[] frames;
trace->frames = nullptr;
trace->count = 0;
}
// ctrace::resolve:
ctrace_stacktrace ctrace_resolve_raw_trace(const ctrace_raw_trace* trace) {
if(!trace || !trace->frames) {
return { nullptr, 0 };
}
try {
std::vector<cpptrace::frame_ptr> frames(trace->count, 0);
std::copy(trace->frames, trace->frames + trace->count, frames.begin());
std::vector<cpptrace::stacktrace_frame> resolved = cpptrace::detail::resolve_frames(frames);
return ctrace::c_convert(resolved);
} catch(...) { // NOSONAR
// Don't check rethrow condition, it's risky.
return { nullptr, 0 };
}
}
ctrace_object_trace ctrace_resolve_raw_trace_to_object_trace(const ctrace_raw_trace* trace) {
if(!trace || !trace->frames) {
return { nullptr, 0 };
}
try {
std::vector<cpptrace::frame_ptr> frames(trace->count, 0);
std::copy(trace->frames, trace->frames + trace->count, frames.begin());
std::vector<cpptrace::object_frame> obj = cpptrace::detail::get_frames_object_info(frames);
return ctrace::c_convert(obj);
} catch(...) { // NOSONAR
// Don't check rethrow condition, it's risky.
return { nullptr, 0 };
}
}
ctrace_stacktrace ctrace_resolve_object_trace(const ctrace_object_trace* trace) {
if(!trace || !trace->frames) {
return { nullptr, 0 };
}
try {
std::vector<cpptrace::frame_ptr> frames(trace->count, 0);
std::transform(
trace->frames,
trace->frames + trace->count,
frames.begin(),
[] (const ctrace_object_frame& frame) -> cpptrace::frame_ptr {
return frame.raw_address;
}
);
std::vector<cpptrace::stacktrace_frame> resolved = cpptrace::detail::resolve_frames(frames);
return ctrace::c_convert(resolved);
} catch(...) { // NOSONAR
// Don't check rethrow condition, it's risky.
return { nullptr, 0 };
}
}
// ctrace::safe:
size_t ctrace_safe_generate_raw_trace(ctrace_frame_ptr* buffer, size_t size, size_t skip, size_t max_depth) {
return cpptrace::safe_generate_raw_trace(buffer, size, skip, max_depth);
}
void ctrace_get_safe_object_frame(ctrace_frame_ptr address, ctrace_safe_object_frame* out) {
// TODO: change this?
static_assert(sizeof(cpptrace::safe_object_frame) == sizeof(ctrace_safe_object_frame), "");
cpptrace::get_safe_object_frame(address, reinterpret_cast<cpptrace::safe_object_frame*>(out));
}
ctrace_bool ctrace_can_signal_safe_unwind() {
return cpptrace::can_signal_safe_unwind();
}
ctrace_bool ctrace_can_get_safe_object_frame(void) {
return cpptrace::can_get_safe_object_frame();
}
// ctrace::io:
ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color) {
if(!trace || !trace->frames) {
return ctrace::generate_owning_string("<empty trace>");
}
auto cpp_trace = ctrace::cpp_convert(trace);
std::string trace_string = cpp_trace.to_string(bool(use_color));
return ctrace::generate_owning_string(trace_string);
}
void ctrace_print_stacktrace(const ctrace_stacktrace* trace, FILE* to, ctrace_bool use_color) {
if(
use_color && (
(to == stdout && cpptrace::isatty(cpptrace::stdout_fileno)) ||
(to == stderr && cpptrace::isatty(cpptrace::stderr_fileno))
)
) {
cpptrace::detail::enable_virtual_terminal_processing_if_needed();
}
ctrace::ffprintf(to, "Stack trace (most recent call first):\n");
if(trace->count == 0 || !trace->frames) {
ctrace::ffprintf(to, "<empty trace>\n");
return;
}
const auto reset = use_color ? ESC "0m" : "";
const auto green = use_color ? ESC "32m" : "";
const auto yellow = use_color ? ESC "33m" : "";
const auto blue = use_color ? ESC "34m" : "";
const auto frame_number_width = cpptrace::detail::n_digits(unsigned(trace->count - 1));
ctrace_stacktrace_frame* frames = trace->frames;
for(std::size_t i = 0; i < trace->count; ++i) {
static constexpr auto ptr_len = 2 * sizeof(cpptrace::frame_ptr);
ctrace::ffprintf(to, "#%-*llu ", int(frame_number_width), i);
if(frames[i].is_inline) {
(void)std::fprintf(to, "%*s",
int(ptr_len + 2),
"(inlined)");
} else {
(void)std::fprintf(to, "%s0x%0*llx%s",
blue,
int(ptr_len),
cpptrace::detail::to_ull(frames[i].raw_address),
reset);
}
if(!ctrace::is_empty(frames[i].symbol)) {
(void)std::fprintf(to, " in %s%s%s",
yellow,
frames[i].symbol,
reset);
}
if(!ctrace::is_empty(frames[i].filename)) {
(void)std::fprintf(to, " at %s%s%s",
green,
frames[i].filename,
reset);
if(ctrace::is_empty(frames[i].line)) {
ctrace::ffprintf(to, "\n");
continue;
}
(void)std::fprintf(to, ":%s%llu%s",
blue,
cpptrace::detail::to_ull(frames[i].line),
reset);
if(ctrace::is_empty(frames[i].column)) {
ctrace::ffprintf(to, "\n");
continue;
}
(void)std::fprintf(to, ":%s%llu%s",
blue,
cpptrace::detail::to_ull(frames[i].column),
reset);
}
// always print newline at end :M
ctrace::ffprintf(to, "\n");
}
}
// utility::demangle:
ctrace_owning_string ctrace_demangle(const char* mangled) {
if(!mangled) {
return ctrace::generate_owning_string("");
}
std::string demangled = cpptrace::demangle(mangled);
return ctrace::generate_owning_string(demangled);
}
// utility::io
int ctrace_stdin_fileno(void) {
return cpptrace::stdin_fileno;
}
int ctrace_stderr_fileno(void) {
return cpptrace::stderr_fileno;
}
int ctrace_stdout_fileno(void) {
return cpptrace::stdout_fileno;
}
ctrace_bool ctrace_isatty(int fd) {
return cpptrace::isatty(fd);
}
// utility::cache:
void ctrace_set_cache_mode(ctrace_cache_mode mode) {
static constexpr auto cache_max = cpptrace::cache_mode::prioritize_speed;
if(mode > unsigned(cache_max)) {
return;
}
auto cache_mode = static_cast<cpptrace::cache_mode>(mode);
cpptrace::experimental::set_cache_mode(cache_mode);
}
void ctrace_enable_inlined_call_resolution(ctrace_bool enable) {
cpptrace::enable_inlined_call_resolution(enable);
}
ctrace_object_frame ctrace_get_object_info(const ctrace_stacktrace_frame* frame) {
try {
cpptrace::object_frame new_frame = cpptrace::detail::get_frame_object_info(frame->raw_address);
return ctrace::convert_object_frame(new_frame);
} catch(...) {
return {0, 0, nullptr};
}
}
}

View File

@ -4,9 +4,9 @@
#include <string>
namespace cpptrace {
namespace detail {
std::string demangle(const std::string&);
}
namespace detail {
std::string demangle(const std::string& name, bool check_prefix);
}
}
#endif

View File

@ -1,28 +1,61 @@
#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;
// presumably thread-safe
char* demangled = abi::__cxa_demangle(name.c_str(), nullptr, nullptr, &status);
if(demangled) {
std::string str = demangled;
// NOLINTNEXTLINE(cppcoreguidelines-no-malloc)
free(demangled);
return str;
} else {
return name;
namespace detail {
std::string demangle(const std::string& name, bool check_prefix) {
// https://itanium-cxx-abi.github.io/cxx-abi/abi.html#demangler
// Check both _Z and __Z, apple prefixes all symbols with an underscore
if(check_prefix && !(starts_with(name, "_Z") || starts_with(name, "__Z"))) {
return name;
}
// Apple clang demangles __Z just fine but gcc doesn't, so just offset the leading underscore
std::size_t offset = 0;
if(starts_with(name, "__Z")) {
offset = 1;
}
// Mangled names don't have spaces, we might add a space and some extra info somewhere but we still want it to
// be demanglable. Look for a space, if there is one swap it with a null terminator briefly.
auto end = name.find(' ');
std::string name_copy;
std::reference_wrapper<const std::string> to_demangle = name;
std::string rest;
if(end != std::string::npos) {
name_copy = name.substr(0, end);
rest = name.substr(end);
to_demangle = name_copy;
}
// presumably thread-safe
// it appears safe to pass nullptr for status however the docs don't explicitly say it's safe so I don't
// want to rely on it
int status;
auto demangled = raii_wrap(
abi::__cxa_demangle(to_demangle.get().c_str() + offset, nullptr, nullptr, &status),
[] (char* str) { std::free(str); }
);
// demangled will always be nullptr on non-zero status, and if __cxa_demangle ever fails for any reason
// we'll just quietly return the mangled name
if(demangled.get()) {
std::string str = demangled.get();
if(!rest.empty()) {
str += rest;
}
return str;
} else {
return name;
}
}
}
}
#endif

View File

@ -1,15 +1,15 @@
#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) {
return name;
}
namespace detail {
std::string demangle(const std::string& name, bool) {
return name;
}
}
}
#endif

View File

@ -0,0 +1,31 @@
#ifdef CPPTRACE_DEMANGLE_WITH_WINAPI
#include "demangle/demangle.hpp"
#include "platform/dbghelp_utils.hpp"
#include <string>
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <dbghelp.h>
namespace cpptrace {
namespace detail {
std::string demangle(const std::string& name, bool) {
// Dbghelp is is single-threaded, so acquire a lock.
auto lock = get_dbghelp_lock();
char buffer[500];
auto ret = UnDecorateSymbolName(name.c_str(), buffer, sizeof(buffer) - 1, 0);
if(ret == 0) {
return name;
} else {
buffer[ret] = 0; // just in case, ms' docs unclear if null terminator inserted
return buffer;
}
}
}
}
#endif

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();
}
}

View File

@ -1,17 +0,0 @@
#ifndef FULL_TRACE_HPP
#define FULL_TRACE_HPP
#include <cpptrace/cpptrace.hpp>
#include "../platform/common.hpp"
#include <cstddef>
#include <vector>
namespace cpptrace {
namespace detail {
CPPTRACE_FORCE_NO_INLINE
std::vector<stacktrace_frame> generate_trace(size_t skip);
}
}
#endif

View File

@ -1,93 +0,0 @@
#ifdef CPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE
#include <cpptrace/cpptrace.hpp>
#include "../platform/program_name.hpp"
#include "../platform/common.hpp"
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <mutex>
#include <vector>
#ifdef CPPTRACE_BACKTRACE_PATH
#include CPPTRACE_BACKTRACE_PATH
#else
#include <backtrace.h>
#endif
namespace cpptrace {
namespace detail {
struct trace_data {
// NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
std::vector<stacktrace_frame>& frames;
// NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
size_t& skip;
};
int full_callback(void* data_pointer, uintptr_t address, const char* file, int line, const char* symbol) {
trace_data& data = *reinterpret_cast<trace_data*>(data_pointer);
if(data.skip > 0) {
data.skip--;
} else if(address == uintptr_t(-1)) {
// sentinel for libbacktrace, stop tracing
return 1;
} else {
data.frames.push_back({
address,
static_cast<std::uint_least32_t>(line),
0,
file ? file : "",
symbol ? symbol : ""
});
}
return 0;
}
void syminfo_callback(void* data, uintptr_t, const char* symbol, uintptr_t, uintptr_t) {
stacktrace_frame& frame = *static_cast<stacktrace_frame*>(data);
frame.symbol = symbol ? symbol : "";
}
void error_callback(void*, const char* msg, int errnum) {
fprintf(stderr, "Libbacktrace error: %s, code %d\n", msg, errnum);
}
backtrace_state* get_backtrace_state() {
static std::mutex mutex;
const std::lock_guard<std::mutex> lock(mutex);
// backtrace_create_state must be called only one time per program
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
static backtrace_state* state = nullptr;
static bool called = false;
if(!called) {
state = backtrace_create_state(nullptr, true, error_callback, nullptr);
called = true;
}
return state;
}
CPPTRACE_FORCE_NO_INLINE
std::vector<stacktrace_frame> generate_trace(size_t skip) {
std::vector<stacktrace_frame> frames;
skip++; // add one for this call
trace_data data { frames, skip };
backtrace_full(get_backtrace_state(), 0, full_callback, error_callback, &data);
for(auto& frame : frames) {
if(frame.symbol.empty()) {
// fallback, try to at least recover the symbol name with backtrace_syminfo
backtrace_syminfo(
get_backtrace_state(),
frame.address,
syminfo_callback,
error_callback,
&frame
);
}
}
return frames;
}
}
}
#endif

View File

@ -1,30 +0,0 @@
#ifdef CPPTRACE_FULL_TRACE_WITH_STACKTRACE
#include <cpptrace/cpptrace.hpp>
#include "full_trace.hpp"
#include "../platform/common.hpp"
#include <vector>
#include <stacktrace>
namespace cpptrace {
namespace detail {
CPPTRACE_FORCE_NO_INLINE
std::vector<stacktrace_frame> generate_trace(size_t skip) {
std::vector<stacktrace_frame> frames;
std::stacktrace trace = std::stacktrace::current(skip + 1);
for(const auto entry : trace) {
frames.push_back({
entry.native_handle(),
entry.source_line(),
0,
entry.source_file(),
entry.description()
});
}
return frames;
}
}
}
#endif

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

@ -1,273 +0,0 @@
#ifndef COMMON_HPP
#define COMMON_HPP
#ifdef _MSC_VER
#define CPPTRACE_FORCE_NO_INLINE __declspec(noinline)
#define CPPTRACE_PFUNC __FUNCSIG__
#define CPPTRACE_MAYBE_UNUSED
#pragma warning(push)
#pragma warning(disable: 4505) // Unused local function
#else
#define CPPTRACE_FORCE_NO_INLINE __attribute__((noinline))
#define CPPTRACE_PFUNC __extension__ __PRETTY_FUNCTION__
#define CPPTRACE_MAYBE_UNUSED __attribute__((unused))
#endif
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <exception>
#include <ios>
#include <sstream>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#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
#if IS_WINDOWS
#include <windows.h>
#endif
// Lightweight std::source_location.
struct source_location {
// NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
const char* const file;
//const char* const function; // disabled for now due to static constexpr restrictions
// NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
const int line;
constexpr source_location(
//const char* _function /*= __builtin_FUNCTION()*/,
const char* _file = __builtin_FILE(),
int _line = __builtin_LINE()
) : file(_file), /*function(_function),*/ line(_line) {}
};
CPPTRACE_MAYBE_UNUSED
static void primitive_assert_impl(
bool condition,
bool verify,
const char* expression,
const char* signature,
source_location location,
const char* message = nullptr
) {
if(!condition) {
const char* action = verify ? "verification" : "assertion";
const char* name = verify ? "verify" : "assert";
if(message == nullptr) {
(void) fprintf(
stderr,
"Cpptrace %s failed at %s:%d: %s\n",
action, location.file, location.line, signature
);
} else {
(void) fprintf(
stderr,
"Cpptrace %s failed at %s:%d: %s: %s\n",
action, location.file, location.line, signature, message
);
}
(void) fprintf(stderr, " primitive_%s(%s);\n", name, expression);
std::abort();
}
}
template<typename T>
void nothing() {}
#define PHONY_USE(E) (nothing<decltype(E)>())
// Still present in release mode, nonfatal
#define internal_verify(c, ...) primitive_assert_impl(c, true, #c, CPPTRACE_PFUNC, {}, ##__VA_ARGS__)
#ifndef NDEBUG
#define CPPTRACE_PRIMITIVE_ASSERT(c, ...) \
primitive_assert_impl(c, false, #c, CPPTRACE_PFUNC, {}, ##__VA_ARGS__)
#else
#define CPPTRACE_PRIMITIVE_ASSERT(c, ...) PHONY_USE(c)
#endif
CPPTRACE_MAYBE_UNUSED
static std::vector<std::string> split(const std::string& str, const std::string& delims) {
std::vector<std::string> vec;
size_t old_pos = 0;
size_t pos = 0;
while((pos = str.find_first_of(delims, old_pos)) != std::string::npos) {
vec.emplace_back(str.substr(old_pos, pos - old_pos));
old_pos = pos + 1;
}
vec.emplace_back(str.substr(old_pos));
return vec;
}
template<typename C>
CPPTRACE_MAYBE_UNUSED
static std::string join(const C& container, const std::string& delim) {
auto iter = std::begin(container);
auto end = std::end(container);
std::string str;
if(std::distance(iter, end) > 0) {
str += *iter;
while(++iter != end) {
str += delim;
str += *iter;
}
}
return str;
}
constexpr const char* const whitespace = " \t\n\r\f\v";
CPPTRACE_MAYBE_UNUSED
static std::string trim(const std::string& str) {
if(str.empty()) {
return "";
}
const size_t left = str.find_first_not_of(whitespace);
const size_t right = str.find_last_not_of(whitespace) + 1;
return str.substr(left, right - left);
}
CPPTRACE_MAYBE_UNUSED
static std::string to_hex(uintptr_t addr) {
std::stringstream sstream;
sstream<<std::hex<<addr;
return std::move(sstream).str();
}
CPPTRACE_MAYBE_UNUSED
static bool is_little_endian() {
uint16_t num = 0x1;
auto* ptr = (uint8_t*)&num;
return ptr[0] == 1;
}
// Modified from
// https://stackoverflow.com/questions/105252/how-do-i-convert-between-big-endian-and-little-endian-values-in-c
template<typename T, size_t N>
struct byte_swapper;
template<typename T>
struct byte_swapper<T, 1> {
T operator()(T val) {
return val;
}
};
template<typename T>
struct byte_swapper<T, 2> {
T operator()(T val) {
return ((((val) >> 8) & 0xff) | (((val) & 0xff) << 8));
}
};
template<typename T>
struct byte_swapper<T, 4> {
T operator()(T val) {
return ((((val) & 0xff000000) >> 24) |
(((val) & 0x00ff0000) >> 8) |
(((val) & 0x0000ff00) << 8) |
(((val) & 0x000000ff) << 24));
}
};
template<typename T>
struct byte_swapper<T, 8> {
T operator()(T val) {
return ((((val) & 0xff00000000000000ull) >> 56) |
(((val) & 0x00ff000000000000ull) >> 40) |
(((val) & 0x0000ff0000000000ull) >> 24) |
(((val) & 0x000000ff00000000ull) >> 8 ) |
(((val) & 0x00000000ff000000ull) << 8 ) |
(((val) & 0x0000000000ff0000ull) << 24) |
(((val) & 0x000000000000ff00ull) << 40) |
(((val) & 0x00000000000000ffull) << 56));
}
};
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
T byteswap(T value) {
return byte_swapper<T, sizeof(T)>{}(value);
}
CPPTRACE_MAYBE_UNUSED
inline void enable_virtual_terminal_processing_if_needed() {
// enable colors / ansi processing if necessary
#if IS_WINDOWS
// https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example-of-enabling-virtual-terminal-processing
#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
constexpr DWORD ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4;
#endif
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD dwMode = 0;
if(hOut == INVALID_HANDLE_VALUE) return;
if(!GetConsoleMode(hOut, &dwMode)) return;
if(dwMode != (dwMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING))
if(!SetConsoleMode(hOut, dwMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) return;
#endif
}
CPPTRACE_MAYBE_UNUSED
// NOLINTNEXTLINE(misc-no-recursion)
inline constexpr unsigned n_digits(unsigned value) {
return value < 10 ? 1 : 1 + n_digits(value / 10);
}
static_assert(n_digits(1) == 1, "n_digits utility producing the wrong result");
static_assert(n_digits(9) == 1, "n_digits utility producing the wrong result");
static_assert(n_digits(10) == 2, "n_digits utility producing the wrong result");
static_assert(n_digits(11) == 2, "n_digits utility producing the wrong result");
static_assert(n_digits(1024) == 4, "n_digits utility producing the wrong result");
// TODO: Re-evaluate use of off_t
template<typename T, typename std::enable_if<std::is_pod<T>::value, int>::type = 0>
T load_bytes(FILE* obj_file, off_t offset) {
T object;
internal_verify(fseek(obj_file, offset, SEEK_SET) == 0, "fseek error");
internal_verify(fread(&object, sizeof(T), 1, obj_file) == 1, "fread error");
return object;
}
class file_error : std::exception {
const char* what() const noexcept override {
return "Unable to read file";
}
};
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#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

@ -1,93 +0,0 @@
#ifndef ELF_HPP
#define ELF_HPP
#include "common.hpp"
#if IS_LINUX
#include <array>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <elf.h>
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);
}
}
// TODO: Address code duplication here. Do we actually have to care about 32-bit if the library is compiled as 64-bit?
// I think probably not...
// TODO: Re-evaluate use of off_t
// I think we can rely on PT_PHDR https://stackoverflow.com/q/61568612/15675011...
static uintptr_t elf_get_module_image_base_from_program_table(
FILE* file,
bool is_64,
bool is_little_endian,
off_t e_phoff,
off_t e_phentsize,
int e_phnum
) {
for(int i = 0; i < e_phnum; i++) {
if(is_64) {
Elf64_Phdr program_header = load_bytes<Elf64_Phdr>(file, e_phoff + e_phentsize * i);
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);
}
} else {
Elf32_Phdr program_header = load_bytes<Elf32_Phdr>(file, e_phoff + e_phentsize * i);
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);
}
}
}
return 0;
}
static uintptr_t elf_get_module_image_base(const std::string& obj_path) {
FILE* file = fopen(obj_path.c_str(), "rb");
if(file == nullptr) {
throw file_error();
}
// Initial checks/metadata
auto magic = load_bytes<std::array<char, 4>>(file, 0);
internal_verify(magic == (std::array<char, 4>{0x7F, 'E', 'L', 'F'}));
bool is_64 = load_bytes<uint8_t>(file, 4) == 2;
bool is_little_endian = load_bytes<uint8_t>(file, 5) == 1;
internal_verify(load_bytes<uint8_t>(file, 6) == 1, "Unexpected ELF version");
//
if(is_64) {
Elf64_Ehdr file_header = load_bytes<Elf64_Ehdr>(file, 0);
internal_verify(file_header.e_ehsize == sizeof(Elf64_Ehdr));
return elf_get_module_image_base_from_program_table(
file,
is_64,
is_little_endian,
elf_byteswap_if_needed(file_header.e_phoff, is_little_endian),
elf_byteswap_if_needed(file_header.e_phentsize, is_little_endian),
elf_byteswap_if_needed(file_header.e_phnum, is_little_endian)
);
} else {
Elf32_Ehdr file_header = load_bytes<Elf32_Ehdr>(file, 0);
internal_verify(file_header.e_ehsize == sizeof(Elf32_Ehdr));
return elf_get_module_image_base_from_program_table(
file,
is_64,
is_little_endian,
elf_byteswap_if_needed(file_header.e_phoff, is_little_endian),
elf_byteswap_if_needed(file_header.e_phentsize, is_little_endian),
elf_byteswap_if_needed(file_header.e_phnum, is_little_endian)
);
}
}
#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

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