Compare commits

..

135 Commits

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
97 changed files with 5257 additions and 3605 deletions

View File

@ -1,145 +0,0 @@
name: build
on:
push:
pull_request:
jobs:
build-linux:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
compiler: [gcc, clang]
steps:
- uses: actions/checkout@v4
- name: dependencies
run: |
sudo apt install gcc-10 g++-10 libgcc-10-dev libunwind8-dev ninja-build
pip3 install colorama
- name: libdwarf
run: |
cd ..
cpptrace/ci/setup-prerequisites.sh
- name: build
run: |
python3 ci/build-in-all-configs.py --${{matrix.compiler}} --default-config
build-linux-bazel:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: dependencies
run: |
sudo apt install -y libtool libncurses5
- name: bazel build opt
run: |
bazel build //... -c opt
- name: bazel build dbg
run: |
bazel build //... -c dbg
build-macos:
runs-on: macos-14
strategy:
fail-fast: false
matrix:
compiler: [gcc, clang]
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-configs.py --${{matrix.compiler}} --default-config
build-windows:
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
compiler: [msvc, clang, gcc]
steps:
- uses: actions/checkout@v4
- name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@v1.13.0
- name: dependencies
run: |
pip3 install colorama
- name: libdwarf
run: |
if("${{matrix.compiler}}" -eq "gcc") {
cd ..
cpptrace/ci/setup-prerequisites-mingw.ps1
}
- name: build
run: |
python3 ci/build-in-all-configs.py --${{matrix.compiler}} --default-config
build-linux-all-configurations:
runs-on: ubuntu-22.04
needs: build-linux
strategy:
fail-fast: false
matrix:
compiler: [gcc, clang]
steps:
- uses: actions/checkout@v4
- name: dependencies
run: |
sudo apt install gcc-10 g++-10 libgcc-10-dev libunwind8-dev ninja-build
pip3 install colorama
- name: libdwarf
run: |
cd ..
cpptrace/ci/setup-prerequisites.sh
- name: build
run: |
python3 ci/build-in-all-configs.py --${{matrix.compiler}}
build-macos-all-configurations:
runs-on: macos-14
needs: build-macos
strategy:
fail-fast: false
matrix:
compiler: [gcc, clang]
steps:
- uses: actions/checkout@v4
- name: dependencies
run: |
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-configs.py --${{matrix.compiler}}
build-windows-all-configurations:
runs-on: windows-2022
needs: build-windows
strategy:
fail-fast: false
matrix:
compiler: [msvc, clang, gcc]
steps:
- uses: actions/checkout@v4
- name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@v1.13.0
- name: dependencies
run: |
pip3 install colorama
- name: libdwarf
run: |
if("${{matrix.compiler}}" -eq "gcc") {
cd ..
cpptrace/ci/setup-prerequisites-mingw.ps1
}
- name: build
run: |
python3 ci/build-in-all-configs.py --${{matrix.compiler}}

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

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

View File

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

View File

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

View File

@ -1,205 +0,0 @@
name: test
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-macos:
runs-on: macos-14
strategy:
fail-fast: false
matrix:
compiler: [gcc, clang]
shared: [--shared, ""]
steps:
- uses: actions/checkout@v4
- name: libdwarf
run: |
cd ..
cpptrace/ci/setup-prerequisites.sh
- name: dependencies
run: |
python3 -m venv env
env/bin/pip install colorama
- name: build and test
run: |
env/bin/python ci/test-all-configs.py --${{matrix.compiler}} --default-config
test-windows:
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
compiler: [msvc, clang, gcc]
shared: [--shared, ""]
steps:
- uses: actions/checkout@v4
- name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@v1.13.0
- name: dependencies
run: |
pip3 install colorama
- name: libdwarf
run: |
if("${{matrix.compiler}}" -eq "gcc") {
cd ..
cpptrace/ci/setup-prerequisites-mingw.ps1
}
- name: build and test
run: |
python3 ci/test-all-configs.py --${{matrix.compiler}} --default-config
test-linux-all-configurations:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
compiler: [gcc, clang]
shared: [--shared, ""]
needs: test-linux
steps:
- uses: actions/checkout@v4
- name: dependencies
run: |
sudo apt install gcc-10 g++-10 libgcc-10-dev libunwind8-dev
pip3 install colorama
- name: libdwarf
run: |
cd ..
cpptrace/ci/setup-prerequisites.sh
- name: build and test
run: |
python3 ci/test-all-configs.py --${{matrix.compiler}}
test-macos-all-configurations:
runs-on: macos-14
strategy:
fail-fast: false
matrix:
compiler: [gcc, clang]
shared: [--shared, ""]
needs: test-macos
steps:
- uses: actions/checkout@v4
- name: libdwarf
run: |
cd ..
cpptrace/ci/setup-prerequisites.sh
- name: dependencies
run: |
python3 -m venv env
env/bin/pip install colorama
- name: build and test
run: |
env/bin/python ci/test-all-configs.py --${{matrix.compiler}}
test-windows-all-configurations:
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
compiler: [msvc, clang, gcc]
shared: [--shared, ""]
needs: test-windows
steps:
- uses: actions/checkout@v4
- name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@v1.13.0
- name: dependencies
run: |
pip3 install colorama
- name: libdwarf
run: |
if("${{matrix.compiler}}" -eq "gcc") {
cd ..
cpptrace/ci/setup-prerequisites-mingw.ps1
}
- name: build and test
run: |
python3 ci/test-all-configs.py --${{matrix.compiler}}
unittest-linux:
runs-on: ubuntu-24.04
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
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-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,6 +1,11 @@
# 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)
@ -23,6 +28,84 @@
- [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:

View File

@ -9,7 +9,7 @@ set(package_name "cpptrace")
project(
cpptrace
VERSION 0.7.3
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
@ -70,6 +70,19 @@ include(cmake/Autoconfig.cmake)
# =================================================== Library Setup ====================================================
if(NOT CPPTRACE_BUILD_NO_SYMBOLS)
set(
debug
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-g>
$<$<CXX_COMPILER_ID:MSVC>:/DEBUG>
)
else()
add_compile_options($<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-g0>)
set(
debug
)
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")
@ -100,6 +113,7 @@ target_sources(
src/ctrace.cpp
src/exceptions.cpp
src/from_current.cpp
src/formatting.cpp
src/options.cpp
src/utils.cpp
src/demangle/demangle_with_cxxabi.cpp
@ -107,6 +121,7 @@ target_sources(
src/demangle/demangle_with_winapi.cpp
src/snippets/snippet.cpp
src/symbols/dwarf/debug_map_resolver.cpp
src/symbols/dwarf/dwarf_options.cpp
src/symbols/dwarf/dwarf_resolver.cpp
src/symbols/symbols_core.cpp
src/symbols/symbols_with_addr2line.cpp
@ -123,7 +138,7 @@ target_sources(
src/unwind/unwind_with_winapi.cpp
src/utils/microfmt.cpp
src/utils/utils.cpp
src/platform/dbghelp_syminit_manager.cpp
src/platform/dbghelp_utils.cpp
)
target_include_directories(
@ -161,6 +176,11 @@ target_compile_options(
${warning_options}
)
set(CPPTRACE_VERSION_MAJOR ${CMAKE_PROJECT_VERSION_MAJOR})
set(CPPTRACE_VERSION_MINOR ${CMAKE_PROJECT_VERSION_MINOR})
set(CPPTRACE_VERSION_PATCH ${CMAKE_PROJECT_VERSION_PATCH})
configure_file("${PROJECT_SOURCE_DIR}/cmake/in/version-hpp.in" "${PROJECT_BINARY_DIR}/include/cpptrace/version.hpp")
# ---- Generate Build Info Headers ----
if(build_type STREQUAL "STATIC")
@ -203,6 +223,10 @@ target_compile_features(
target_compile_definitions(${target_name} PRIVATE NOMINMAX)
if(HAS_ATTRIBUTE_PACKED)
target_compile_definitions(${target_name} PRIVATE HAS_ATTRIBUTE_PACKED)
endif()
if(NOT CPPTRACE_STD_FORMAT)
target_compile_definitions(${target_name} PUBLIC CPPTRACE_NO_STD_FORMAT)
endif()
@ -331,11 +355,14 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF)
endif()
endif()
if(CPPTRACE_CONAN)
set(dwarf_lib libdwarf::libdwarf)
target_link_libraries(${target_name} PRIVATE libdwarf::libdwarf)
elseif(CPPTRACE_VCPKG)
set(dwarf_lib libdwarf::dwarf)
target_link_libraries(${target_name} PRIVATE libdwarf::dwarf)
elseif(CPPTRACE_USE_EXTERNAL_LIBDWARF)
if(DEFINED LIBDWARF_LIBRARIES)
set(dwarf_lib ${LIBDWARF_LIBRARIES})
target_link_libraries(${target_name} PRIVATE ${LIBDWARF_LIBRARIES})
else()
# if LIBDWARF_LIBRARIES wasn't set by find_package, try looking for libdwarf::dwarf-static,
@ -353,6 +380,7 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF)
else()
message(FATAL_ERROR "Couldn't find libdwarf target name to link against")
endif()
set(dwarf_lib ${LIBDWARF_LIBRARIES})
target_link_libraries(${target_name} PRIVATE ${LIBDWARF_LIBRARIES})
endif()
# There seems to be no consistency at all about where libdwarf decides to place its headers........ Figure out if
@ -375,6 +403,7 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF)
message(FATAL_ERROR "Couldn't find libdwarf.h")
endif()
else()
set(dwarf_lib libdwarf::dwarf-static)
target_link_libraries(${target_name} PRIVATE libdwarf::dwarf-static)
endif()
if(UNIX)
@ -490,7 +519,7 @@ if(NOT CMAKE_SKIP_INSTALL_RULES)
include(cmake/InstallRules.cmake)
endif()
# ===================================================== Demo/test ======================================================
# ================================================== Demo/test/tools ===================================================
if(CPPTRACE_BUILD_TESTING)
if(PROJECT_IS_TOP_LEVEL)
@ -502,3 +531,7 @@ endif()
if(CPPTRACE_BUILD_BENCHMARKING)
add_subdirectory(benchmarking)
endif()
if(CPPTRACE_BUILD_TOOLS)
add_subdirectory(tools)
endif()

View File

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

224
README.md
View File

@ -1,7 +1,6 @@
# Cpptrace <!-- omit in toc -->
[![build](https://github.com/jeremy-rifkin/cpptrace/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/jeremy-rifkin/cpptrace/actions/workflows/build.yml)
[![test](https://github.com/jeremy-rifkin/cpptrace/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/jeremy-rifkin/cpptrace/actions/workflows/test.yml)
[![CI](https://github.com/jeremy-rifkin/cpptrace/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/jeremy-rifkin/cpptrace/actions/workflows/ci.yml)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=jeremy-rifkin_cpptrace&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=jeremy-rifkin_cpptrace)
<br/>
[![Community Discord Link](https://img.shields.io/badge/Chat%20on%20the%20(very%20small)-Community%20Discord-blue?labelColor=2C3239&color=7289DA&style=flat&logo=discord&logoColor=959DA5)](https://discord.gg/frjaAZvqUZ)
@ -17,9 +16,6 @@ Cpptrace also has a C API, docs [here](docs/c-api.md).
- [30-Second Overview](#30-second-overview)
- [CMake FetchContent Usage](#cmake-fetchcontent-usage)
- [FAQ](#faq)
- [What about C++23 `<stacktrace>`?](#what-about-c23-stacktrace)
- [What does cpptrace have over other C++ stacktrace libraries?](#what-does-cpptrace-have-over-other-c-stacktrace-libraries)
- [Prerequisites](#prerequisites)
- [Basic Usage](#basic-usage)
- [`namespace cpptrace`](#namespace-cpptrace)
@ -27,6 +23,7 @@ Cpptrace also has a C API, docs [here](docs/c-api.md).
- [Object Traces](#object-traces)
- [Raw Traces](#raw-traces)
- [Utilities](#utilities)
- [Formatting](#formatting)
- [Configuration](#configuration)
- [Traces From All Exceptions](#traces-from-all-exceptions)
- [Removing the `CPPTRACE_` prefix](#removing-the-cpptrace_-prefix)
@ -39,6 +36,7 @@ Cpptrace also has a C API, docs [here](docs/c-api.md).
- [Signal-Safe Tracing](#signal-safe-tracing)
- [Utility Types](#utility-types)
- [Headers](#headers)
- [Libdwarf Tuning](#libdwarf-tuning)
- [Supported Debug Formats](#supported-debug-formats)
- [How to Include The Library](#how-to-include-the-library)
- [CMake FetchContent](#cmake-fetchcontent)
@ -56,6 +54,10 @@ Cpptrace also has a C API, docs [here](docs/c-api.md).
- [Summary of Library Configurations](#summary-of-library-configurations)
- [Testing Methodology](#testing-methodology)
- [Notes About the Library](#notes-about-the-library)
- [FAQ](#faq)
- [What about C++23 `<stacktrace>`?](#what-about-c23-stacktrace)
- [What does cpptrace have over other C++ stacktrace libraries?](#what-does-cpptrace-have-over-other-c-stacktrace-libraries)
- [I'm getting undefined standard library symbols like `std::__1::basic_string` on MacOS](#im-getting-undefined-standard-library-symbols-like-std__1basic_string-on-macos)
- [Contributing](#contributing)
- [License](#license)
@ -126,6 +128,7 @@ Additional notable features:
- Utilities for catching `std::exception`s and wrapping them in traced exceptions
- Signal-safe stack tracing
- Source code snippets in traces
- Extensive configuration options for [trace formatting](#formatting)
![Snippets](res/snippets.png)
@ -136,7 +139,7 @@ include(FetchContent)
FetchContent_Declare(
cpptrace
GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git
GIT_TAG v0.7.3 # <HASH or TAG>
GIT_TAG v0.8.2 # <HASH or TAG>
)
FetchContent_MakeAvailable(cpptrace)
target_link_libraries(your_target cpptrace::cpptrace)
@ -161,33 +164,6 @@ On macOS it is recommended to generate a `.dSYM` file, see [Platform Logistics](
For other ways to use the library, such as through package managers, a system-wide installation, or on a platform
without internet access see [How to Include The Library](#how-to-include-the-library) below.
# FAQ
## What about C++23 `<stacktrace>`?
Some day C++23's `<stacktrace>` will be ubiquitous. And maybe one day the msvc implementation will be acceptable.
The original motivation for cpptrace was to support projects using older C++ standards and as the library has grown its
functionality has extended beyond the standard library's implementation.
Cpptrace provides functionality beyond what the standard library provides and what implementations provide, such as:
- Walking inlined function calls
- Providing a lightweight interface for "raw traces"
- Resolving function parameter types
- Providing traced exception objects
- Providing an API for signal-safe stacktrace generation
- Providing a way to retrieve stack traces from arbitrary exceptions, not just special cpptrace traced exception
objects. This is a feature coming to C++26, but cpptrace provides a solution for C++11.
## What does cpptrace have over other C++ stacktrace libraries?
Other C++ stacktrace libraries, such as boost stacktrace and backward-cpp, fall short when it comes to portability and
ease of use. In testing, I found neither to provide adaquate coverage of various environments. Even when they can be
made to work in an environment they require manual configuration from the end-user, possibly requiring manual
installation of third-party dependencies. This is a highly undesirable burden to impose on users, especially when it is
for a software package which just provides diagnostics as opposed to core functionality. Additionally, cpptrace provides
support for resolving inlined calls by default for DWARF symbols (boost does not do this, backward-cpp can do this but
only for some back-ends), better support for resolving full function signatures, and nicer API, among other features.
# Prerequisites
> [!IMPORTANT]
@ -343,6 +319,87 @@ namespace cpptrace {
}
```
## Formatting
Cpptrace provides a configurable formatter for stack trace printing which supports some common options. Formatters are
configured with a sort of builder pattern, e.g.:
```cpp
auto formatter = cpptrace::formatter{}
.header("Stack trace:")
.addresses(cpptrace::formatter::address_mode::object)
.snippets(true);
```
This API is available through the `<cpptrace/formatting.hpp>` header.
Synopsis:
```cpp
namespace cpptrace {
class formatter {
formatter& header(std::string);
enum class color_mode { always, none, automatic };
formatter& colors(color_mode);
enum class address_mode { raw, object, none };
formatter& addresses(address_mode);
enum class path_mode { full, basename };
formatter& paths(path_mode);
formatter& snippets(bool);
formatter& snippet_context(int);
formatter& columns(bool);
formatter& filtered_frame_placeholders(bool);
formatter& filter(std::function<bool(const stacktrace_frame&)>);
std::string format(const stacktrace_frame&) const;
std::string format(const stacktrace_frame&, bool color) const;
std::string format(const stacktrace&) const;
std::string format(const stacktrace&, bool color) const;
void print(const stacktrace_frame&) const;
void print(const stacktrace_frame&, bool color) const;
void print(std::ostream&, const stacktrace_frame&) const;
void print(std::ostream&, const stacktrace_frame&, bool color) const;
void print(std::FILE*, const stacktrace_frame&) const;
void print(std::FILE*, const stacktrace_frame&, bool color) const;
void print(const stacktrace&) const;
void print(const stacktrace&, bool color) const;
void print(std::ostream&, const stacktrace&) const;
void print(std::ostream&, const stacktrace&, bool color) const;
void print(std::FILE*, const stacktrace&) const;
void print(std::FILE*, const stacktrace&, bool color) const;
};
}
```
Options:
| Setting | Description | Default |
| ----------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------ |
| `header` | Header line printed before the trace | `Stack trace (most recent call first):` |
| `colors` | Default color mode for the trace | `automatic`, which attempts to detect if the target stream is a terminal |
| `addresses` | Raw addresses, object addresses, or no addresses | `raw` |
| `paths` | Full paths or just filenames | `full` |
| `snippets` | Whether to include source code snippets | `false` |
| `snippet_context` | How many lines of source context to show in a snippet | `2` |
| `columns` | Whether to include column numbers if present | `true` |
| `filtered_frame_placeholders` | Whether to still print filtered frames as just `#n (filtered)` | `true` |
| `filter` | A predicate to filter frames with | None |
The `automatic` color mode attempts to detect if a stream that may be attached to a terminal. As such, it will not use
colors for the `formatter::format` method and it may not be able to detect if some ostreams correspond to terminals or
not. For this reason, `formatter::format` and `formatter::print` methods have overloads taking a color parameter. This
color parameter will override configured color mode.
Recommended practice with formatters: It's generally preferable to create formatters objects that are long-lived rather
than to create them on the fly every time a trace needs to be formatted.
Cpptrace provides access to a formatter with default settings with `get_default_formatter`:
```cpp
namespace cpptrace {
const formatter& get_default_formatter();
}
```
## Configuration
`cpptrace::absorb_trace_exceptions`: Configure whether the library silently absorbs internal exceptions and continues.
@ -382,6 +439,8 @@ thrown exception object, with minimal or no overhead in the non-throwing path:
```cpp
#include <cpptrace/from_current.hpp>
#include <iostream>
void foo() {
throw std::runtime_error("foo failed");
}
@ -691,6 +750,7 @@ namespace cpptrace {
};
void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
bool can_signal_safe_unwind();
bool can_get_safe_object_frame();
}
```
@ -707,9 +767,9 @@ see the comprehensive overview and demo at [signal-safe-tracing.md](docs/signal-
> [!IMPORTANT]
> Currently signal-safe stack unwinding is only possible with `libunwind`, which must be
> [manually enabled](#library-back-ends). If signal-safe unwinding isn't supported, `safe_generate_raw_trace` will just
> produce an empty trace. `can_signal_safe_unwind` can be used to check for signal-safe unwinding support. If object
> information can't be resolved in a signal-safe way then `get_safe_object_frame` will not populate fields beyond the
> `raw_address`.
> produce an empty trace. `can_signal_safe_unwind` can be used to check for signal-safe unwinding support and
> `can_get_safe_object_frame` can be used to check `get_safe_object_frame` support. If object information can't be
> resolved in a signal-safe way then `get_safe_object_frame` will not populate fields beyond the `raw_address`.
> [!IMPORTANT]
> `_dl_find_object` is required for signal-safe stack tracing. This is a relatively recent addition to glibc, added in
@ -735,6 +795,7 @@ namespace cpptrace {
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
struct nullable {
T raw_value;
// all members are constexpr for c++17 and beyond, some are constexpr before c++17
nullable& operator=(T value)
bool has_value() const noexcept;
T& value() noexcept;
@ -744,6 +805,7 @@ namespace cpptrace {
void reset() noexcept;
bool operator==(const nullable& other) const noexcept;
bool operator!=(const nullable& other) const noexcept;
constexpr static T null_value() noexcept; // returns the raw null value
constexpr static nullable null() noexcept; // returns a null instance
};
@ -786,9 +848,38 @@ Cpptrace provides a handful of headers to make inclusion more minimal.
| `cpptrace/exceptions.hpp` | [Traced Exception Objects](#traced-exception-objects) and related utilities ([Wrapping std::exceptions](#wrapping-stdexceptions)) |
| `cpptrace/from_current.hpp` | [Traces From All Exceptions](#traces-from-all-exceptions) |
| `cpptrace/io.hpp` | `operator<<` overloads for `std::ostream` and `std::formatter`s |
| `cpptrace/formatting.hpp` | Configurable formatter API |
| `cpptrace/utils.hpp` | Utility functions, configuration functions, and terminate utilities ([Utilities](#utilities), [Configuration](#configuration), and [Terminate Handling](#terminate-handling)) |
| `cpptrace/version.hpp` | Library version macros |
The main cpptrace header is `cpptrace/cpptrace.hpp` which includes everything other than `from_current.hpp`.
The main cpptrace header is `cpptrace/cpptrace.hpp` which includes everything other than `from_current.hpp` and
`version.hpp`.
## Libdwarf Tuning
For extraordinarily large binaries (multiple gigabytes), cpptrace's internal caching can result in a lot of memory
usage. Cpptrace provides some options to reduce memory usage in exchange for performance in memory-constrained
applications.
Synopsis:
```cpp
namespace cpptrace {
namespace experimental {
void set_dwarf_resolver_line_table_cache_size(nullable<std::size_t> max_entries);
void set_dwarf_resolver_disable_aranges(bool disable);
}
}
```
Explanation:
- `set_dwarf_resolver_line_table_cache_size` can be used to set a limit to the cache size with evictions done LRU.
Cpptrace loads and caches line tables for dwarf compile units. These can take a lot of space for large binaries with
lots of debug info. Passing `nullable<std::size_t>::null()` will disable the cache size (which is the default
behavior).
- `set_dwarf_resolver_disable_aranges` can be used to disable use of dwarf `.debug_aranges`, an accelerated range lookup
table for compile units emitted by many compilers. Cpptrace uses these by default if they are present since they can
speed up resolution, however, they can also result in significant memory usage.
# Supported Debug Formats
@ -814,7 +905,7 @@ include(FetchContent)
FetchContent_Declare(
cpptrace
GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git
GIT_TAG v0.7.3 # <HASH or TAG>
GIT_TAG v0.8.2 # <HASH or TAG>
)
FetchContent_MakeAvailable(cpptrace)
target_link_libraries(your_target cpptrace::cpptrace)
@ -830,7 +921,7 @@ information.
```sh
git clone https://github.com/jeremy-rifkin/cpptrace.git
git checkout v0.7.3
git checkout v0.8.2
mkdir cpptrace/build
cd cpptrace/build
cmake .. -DCMAKE_BUILD_TYPE=Release
@ -873,7 +964,7 @@ you when installing new libraries.
```ps1
git clone https://github.com/jeremy-rifkin/cpptrace.git
git checkout v0.7.3
git checkout v0.8.2
mkdir cpptrace/build
cd cpptrace/build
cmake .. -DCMAKE_BUILD_TYPE=Release
@ -891,7 +982,7 @@ To install just for the local user (or any custom prefix):
```sh
git clone https://github.com/jeremy-rifkin/cpptrace.git
git checkout v0.7.3
git checkout v0.8.2
mkdir cpptrace/build
cd cpptrace/build
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$HOME/wherever
@ -964,7 +1055,7 @@ make install
cd ~/scratch/cpptrace-test
git clone https://github.com/jeremy-rifkin/libdwarf-lite.git
cd libdwarf-lite
git checkout 6dbcc23dba6ffd230063bda4b9d7298bf88d9d41
git checkout fe09ca800b988e2ff21225ac5e7468ceade2a30e
mkdir build
cd build
cmake .. -DPIC_ALWAYS=On -DBUILD_DWARFDUMP=Off -DCMAKE_PREFIX_PATH=~/scratch/cpptrace-test/resources -DCMAKE_INSTALL_PREFIX=~/scratch/cpptrace-test/resources
@ -974,7 +1065,7 @@ make install
cd ~/scratch/cpptrace-test
git clone https://github.com/jeremy-rifkin/cpptrace.git
cd cpptrace
git checkout v0.7.3
git checkout v0.8.2
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=On -DCPPTRACE_USE_EXTERNAL_LIBDWARF=On -DCMAKE_PREFIX_PATH=~/scratch/cpptrace-test/resources -DCMAKE_INSTALL_PREFIX=~/scratch/cpptrace-test/resources
@ -994,7 +1085,7 @@ cpptrace and its dependencies.
Cpptrace is available through conan at https://conan.io/center/recipes/cpptrace.
```
[requires]
cpptrace/0.7.3
cpptrace/0.8.2
[generators]
CMakeDeps
CMakeToolchain
@ -1203,6 +1294,51 @@ A couple things I'd like to improve in the future:
in dbghelp the library cannot accurately show const and volatile qualifiers or rvalue references (these appear as
pointers).
# FAQ
## What about C++23 `<stacktrace>`?
Some day C++23's `<stacktrace>` will be ubiquitous. And maybe one day the msvc implementation will be acceptable.
The original motivation for cpptrace was to support projects using older C++ standards and as the library has grown its
functionality has extended beyond the standard library's implementation.
Cpptrace provides functionality beyond what the standard library provides and what implementations provide, such as:
- Walking inlined function calls
- Providing a lightweight interface for "raw traces"
- Resolving function parameter types
- Providing traced exception objects
- Providing an API for signal-safe stacktrace generation
- Providing a way to retrieve stack traces from arbitrary exceptions, not just special cpptrace traced exception
objects. This is a feature coming to C++26, but cpptrace provides a solution for C++11.
## What does cpptrace have over other C++ stacktrace libraries?
Other C++ stacktrace libraries, such as boost stacktrace and backward-cpp, fall short when it comes to portability and
ease of use. In testing, I found neither to provide adaquate coverage of various environments. Even when they can be
made to work in an environment they require manual configuration from the end-user, possibly requiring manual
installation of third-party dependencies. This is a highly undesirable burden to impose on users, especially when it is
for a software package which just provides diagnostics as opposed to core functionality. Additionally, cpptrace provides
support for resolving inlined calls by default for DWARF symbols (boost does not do this, backward-cpp can do this but
only for some back-ends), better support for resolving full function signatures, and nicer API, among other features.
## I'm getting undefined standard library symbols like `std::__1::basic_string` on MacOS
If you see a linker error along the lines of the following on MacOS then it's highly likely you are mixing standard
library ABIs.
```
Undefined symbols for architecture arm64:
"std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::find(char, unsigned long) const", referenced from:
cpptrace::detail::demangle(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, bool) in libcpptrace.a(demangle_with_cxxabi.cpp.o)
cpptrace::detail::snippet_manager::build_line_table() in libcpptrace.a(snippet.cpp.o)
```
This can happen when using apple clang to compile cpptrace and gcc to compile your code, or vice versa. The reason is
that apple clang defaults to libc++ and gcc defaults to libstdc++ and these two standard library implementations are not
ABI-compatible. To resolve this, ensure you are compiling both cpptrace and your code with the same standard library by
either using the same compiler for both or using `-stdlib=libc++`/`-stdlib=libstdc++` to control which standard library
is used.
# Contributing
I'm grateful for the help I've received with this library and I welcome contributions! For information on contributing

View File

@ -7,12 +7,6 @@ set(
${warning_options} $<$<CXX_COMPILER_ID:GNU>:-Wno-infinite-recursion>
)
set(
debug
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-g>
$<$<CXX_COMPILER_ID:MSVC>:/DEBUG>
)
include(FetchContent)
set(BENCHMARK_ENABLE_TESTING OFF)
FetchContent_Declare(

View File

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

View File

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

View File

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

View File

@ -14,8 +14,8 @@ cd ..
mkdir libdwarf
cd libdwarf
git init
git remote add origin https://github.com/davea42/libdwarf-code.git
git fetch --depth 1 origin 285d9d34f3e9f56cc1c487d0055f6dc54a9c54a1 # 0.11.0
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

View File

@ -14,8 +14,8 @@ cd ..
mkdir libdwarf
cd libdwarf
git init
git remote add origin https://github.com/davea42/libdwarf-code.git
git fetch --depth 1 origin f4f6f782a06ab0618861cf0c4474989376c69c76 # 0.11.0 + trunk with some dwarf 5 fixes
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

View File

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

View File

@ -34,6 +34,7 @@ def build(runner: MatrixRunner):
"-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",
@ -61,6 +62,7 @@ def build(runner: MatrixRunner):
"-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",
@ -122,6 +124,7 @@ def run_linux_matrix():
"has_dl_find_object": ["OFF", "ON"],
"split_dwarf": ["OFF", "ON"],
"dwarf_version": ["4", "5"],
"symbols": ["On", "Off"],
},
exclude = [
{
@ -133,19 +136,6 @@ def run_linux_matrix():
"stdlib": "libc++",
"sanitizers": "ON",
},
{
# disabled until https://github.com/davea42/libdwarf-code/issues/259 is fixed
"compiler": "g++-10",
"build_type": "RelWithDebInfo",
"split_dwarf": "ON",
"dwarf_version": "5",
},
{
# disabled until https://github.com/davea42/libdwarf-code/issues/267 is fixed
"compiler": "clang++-18",
"split_dwarf": "ON",
"dwarf_version": "5",
},
]
).run(build_and_test)
@ -157,6 +147,7 @@ def run_macos_matrix():
"build_type": ["Debug", "RelWithDebInfo"],
"shared": ["OFF", "ON"],
"dSYM": [True, False],
"symbols": ["On", "Off"],
},
exclude = [
{

View File

@ -1,7 +1,7 @@
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
@ -23,6 +23,7 @@ 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
@ -32,6 +33,17 @@ class MatrixRunner:
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()
@ -78,6 +90,11 @@ class MatrixRunner:
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):
@ -87,7 +104,10 @@ class MatrixRunner:
def get_work(self):
work = []
for assignment in itertools.product(*self.matrix.values()):
if any(map(lambda ex: self.do_exclude(self.assignment_to_matrix_config(assignment), ex), self.exclude)):
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

View File

@ -4,6 +4,8 @@ function(check_support var source includes libraries definitions)
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)
@ -13,6 +15,10 @@ if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
check_support(HAS_CXXABI has_cxxabi.cpp "" "" "")
endif()
if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
check_support(HAS_ATTRIBUTE_PACKED has_attribute_packed.cpp "" "" "")
endif()
if(NOT WIN32)
check_support(HAS_UNWIND has_unwind.cpp "" "" "")
check_support(HAS_EXECINFO has_execinfo.cpp "" "" "")

View File

@ -5,8 +5,9 @@ include(CMakePackageConfigHelpers)
install(
DIRECTORY
"${PROJECT_SOURCE_DIR}/include/" # our header files
"${PROJECT_BINARY_DIR}/include/" # generated header files
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
COMPONENT ${package_name}-development
COMPONENT ${package_name}_development
# PATTERN "**/third_party" EXCLUDE # skip third party directory
# PATTERN "**/third_party/**" EXCLUDE # skip third party files
)
@ -17,12 +18,12 @@ install(
TARGETS ${target_name}
EXPORT ${package_name}-targets
RUNTIME #
COMPONENT ${package_name}-runtime
COMPONENT ${package_name}_runtime
LIBRARY #
COMPONENT ${package_name}-runtime
NAMELINK_COMPONENT ${package_name}-development
COMPONENT ${package_name}_runtime
NAMELINK_COMPONENT ${package_name}_development
ARCHIVE #
COMPONENT ${package_name}-development
COMPONENT ${package_name}_development
INCLUDES #
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
)
@ -38,7 +39,7 @@ configure_file(
install(
FILES "${PROJECT_BINARY_DIR}/cmake/${package_name}-config.cmake"
DESTINATION "${CPPTRACE_INSTALL_CMAKEDIR}"
COMPONENT ${package_name}-development
COMPONENT ${package_name}_development
)
# create version file for consumer to check version in CMake
@ -51,7 +52,7 @@ write_basic_package_version_file(
install(
FILES "${PROJECT_BINARY_DIR}/${package_name}-config-version.cmake"
DESTINATION "${CPPTRACE_INSTALL_CMAKEDIR}"
COMPONENT ${package_name}-development
COMPONENT ${package_name}_development
)
# create targets file included by config file with targets for consumers
@ -59,7 +60,7 @@ install(
EXPORT ${package_name}-targets
NAMESPACE cpptrace::
DESTINATION "${CPPTRACE_INSTALL_CMAKEDIR}"
COMPONENT ${package_name}-development
COMPONENT ${package_name}_development
)
if(CPPTRACE_PROVIDE_EXPORT_SET)

View File

@ -151,13 +151,17 @@ option(CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH "" OFF)
if(PROJECT_IS_TOP_LEVEL)
option(CPPTRACE_BUILD_TESTING "" OFF)
option(CPPTRACE_BUILD_TOOLS "" OFF)
option(CPPTRACE_BUILD_BENCHMARK "" OFF)
option(CPPTRACE_BUILD_NO_SYMBOLS "" OFF)
option(CPPTRACE_BUILD_TESTING_SPLIT_DWARF "" OFF)
set(CPPTRACE_BUILD_TESTING_DWARF_VERSION "0" CACHE STRING "")
option(CPPTRACE_BUILD_TEST_RDYNAMIC "" OFF)
mark_as_advanced(
CPPTRACE_BUILD_TESTING
CPPTRACE_BUILD_BENCHMARKING
CPPTRACE_BUILD_TOOLS
CPPTRACE_BUILD_BENCHMARK
CPPTRACE_BUILD_NO_SYMBOLS
CPPTRACE_BUILD_TESTING_SPLIT_DWARF
CPPTRACE_BUILD_TESTING_DWARF_VERSION
CPPTRACE_BUILD_TEST_RDYNAMIC
@ -176,12 +180,12 @@ option(CPPTRACE_SKIP_UNIT "" OFF)
option(CPPTRACE_STD_FORMAT "" ON)
option(CPPTRACE_UNPREFIXED_TRY_CATCH "" OFF)
option(CPPTRACE_USE_EXTERNAL_GTEST "" OFF)
set(CPPTRACE_ZSTD_URL "https://github.com/facebook/zstd/releases/download/v1.5.6/zstd-1.5.6.tar.gz" CACHE STRING "")
set(CPPTRACE_ZSTD_URL "https://github.com/facebook/zstd/releases/download/v1.5.7/zstd-1.5.7.tar.gz" CACHE STRING "")
set(CPPTRACE_LIBDWARF_REPO "https://github.com/jeremy-rifkin/libdwarf-lite.git" CACHE STRING "")
set(CPPTRACE_LIBDWARF_TAG "97fd68c6026c0237943106d6bc3e83f3661d39e8" CACHE STRING "") # v0.11.0
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 "" ON)
option(CPPTRACE_PROVIDE_EXPORT_SET_FOR_LIBDWARF "" OFF)
mark_as_advanced(
CPPTRACE_BACKTRACE_PATH

View File

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

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

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

View File

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

View File

@ -73,6 +73,7 @@ namespace cpptrace {
void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
// signal-safe
bool can_signal_safe_unwind();
bool can_get_safe_object_frame();
}
```
@ -104,6 +105,9 @@ only ways to do this safely as far as I can tell.
`safe_generate_raw_trace` will just produce an empty trace and if object information can't be resolved in a signal-safe
way then `get_safe_object_frame` will not populate fields beyond the `raw_address`.
`cpptrace::can_signal_safe_unwind` and `cpptrace::can_get_safe_object_frame` can be used to check for safe tracing
support.
Currently the only back-end that can unwind safely is libunwind. Currently, the only way I know to get `dladdr`'s
information in a signal-safe manner is `_dl_find_object`, which doesn't exist on macos (or windows of course). If anyone
knows ways to do these safely on other platforms, I'd be much appreciative.

View File

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

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

@ -43,6 +43,12 @@ namespace cpptrace {
namespace experimental {
CPPTRACE_EXPORT void set_cache_mode(cache_mode mode);
}
// dwarf options
namespace experimental {
CPPTRACE_EXPORT void set_dwarf_resolver_line_table_cache_size(nullable<std::size_t> max_entries);
CPPTRACE_EXPORT void set_dwarf_resolver_disable_aranges(bool disable);
}
}
#endif

View File

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

View File

@ -1,4 +1,5 @@
#include "binary/elf.hpp"
#include "utils/optional.hpp"
#if IS_LINUX
@ -6,60 +7,25 @@
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <mutex>
#include <type_traits>
#include <unordered_map>
#include <elf.h>
namespace cpptrace {
namespace detail {
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
T elf_byteswap_if_needed(T value, bool elf_is_little) {
if(is_little_endian() == elf_is_little) {
return value;
} else {
return byteswap(value);
}
}
template<std::size_t Bits>
static Result<std::uintptr_t, internal_error> elf_get_module_image_base_from_program_table(
elf::elf(
file_wrapper file,
const std::string& object_path,
std::FILE* file,
bool is_little_endian
) {
static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument");
using Header = typename std::conditional<Bits == 32, Elf32_Ehdr, Elf64_Ehdr>::type;
using PHeader = typename std::conditional<Bits == 32, Elf32_Phdr, Elf64_Phdr>::type;
auto loaded_header = load_bytes<Header>(file, 0);
if(loaded_header.is_error()) {
return std::move(loaded_header).unwrap_error();
}
const Header& file_header = loaded_header.unwrap_value();
if(file_header.e_ehsize != sizeof(Header)) {
return internal_error("ELF file header size mismatch" + object_path);
}
// PT_PHDR will occur at most once
// Should be somewhat reliable https://stackoverflow.com/q/61568612/15675011
// It should occur at the beginning but may as well loop just in case
for(int i = 0; i < file_header.e_phnum; i++) {
auto loaded_ph = load_bytes<PHeader>(file, file_header.e_phoff + file_header.e_phentsize * i);
if(loaded_ph.is_error()) {
return std::move(loaded_ph).unwrap_error();
}
const PHeader& program_header = loaded_ph.unwrap_value();
if(elf_byteswap_if_needed(program_header.p_type, is_little_endian) == PT_PHDR) {
return elf_byteswap_if_needed(program_header.p_vaddr, is_little_endian) -
elf_byteswap_if_needed(program_header.p_offset, is_little_endian);
}
}
// Apparently some objects like shared objects can end up missing this header. 0 as a base seems correct.
return 0;
}
bool is_little_endian,
bool is_64
) : file(std::move(file)), object_path(object_path), is_little_endian(is_little_endian), is_64(is_64) {}
Result<std::uintptr_t, internal_error> elf_get_module_image_base(const std::string& object_path) {
Result<elf, internal_error> elf::open_elf(const std::string& object_path) {
auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter);
if(file == nullptr) {
return internal_error("Unable to read object file " + object_path);
return internal_error("Unable to read object file {}", object_path);
}
// Initial checks/metadata
auto magic = load_bytes<std::array<char, 4>>(file, 0);
@ -86,14 +52,385 @@ namespace detail {
if(ei_version.unwrap_value() != 1) {
return internal_error("Unexpected ELF version " + object_path);
}
return elf(std::move(file), object_path, is_little_endian, is_64);
}
Result<std::uintptr_t, internal_error> elf::get_module_image_base() {
// get image base
if(is_64) {
return elf_get_module_image_base_from_program_table<64>(object_path, file, is_little_endian);
return get_module_image_base_impl<64>();
} else {
return elf_get_module_image_base_from_program_table<32>(object_path, file, is_little_endian);
return get_module_image_base_impl<32>();
}
}
template<std::size_t Bits>
Result<std::uintptr_t, internal_error> elf::get_module_image_base_impl() {
static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument");
using PHeader = typename std::conditional<Bits == 32, Elf32_Phdr, Elf64_Phdr>::type;
auto header = get_header_info();
if(header.is_error()) {
return std::move(header).unwrap_error();
}
const auto& header_info = header.unwrap_value();
// PT_PHDR will occur at most once
// Should be somewhat reliable https://stackoverflow.com/q/61568612/15675011
// It should occur at the beginning but may as well loop just in case
for(unsigned i = 0; i < header_info.e_phnum; i++) {
auto loaded_ph = load_bytes<PHeader>(file, header_info.e_phoff + header_info.e_phentsize * i);
if(loaded_ph.is_error()) {
return std::move(loaded_ph).unwrap_error();
}
const PHeader& program_header = loaded_ph.unwrap_value();
if(byteswap_if_needed(program_header.p_type) == PT_PHDR) {
return byteswap_if_needed(program_header.p_vaddr) -
byteswap_if_needed(program_header.p_offset);
}
}
// Apparently some objects like shared objects can end up missing this header. 0 as a base seems correct.
return 0;
}
optional<std::string> elf::lookup_symbol(frame_ptr pc) {
if(auto symtab = get_symtab()) {
if(auto symbol = lookup_symbol(pc, symtab.unwrap_value())) {
return symbol;
}
}
if(auto dynamic_symtab = get_dynamic_symtab()) {
if(auto symbol = lookup_symbol(pc, dynamic_symtab.unwrap_value())) {
return symbol;
}
}
return nullopt;
}
optional<std::string> elf::lookup_symbol(frame_ptr pc, const optional<symtab_info>& maybe_symtab) {
if(!maybe_symtab) {
return nullopt;
}
auto& symtab = maybe_symtab.unwrap();
if(symtab.strtab_link == SHN_UNDEF) {
return nullopt;
}
auto strtab_ = get_strtab(symtab.strtab_link);
if(strtab_.is_error()) {
return nullopt;
}
auto& strtab = strtab_.unwrap_value();
auto it = first_less_than_or_equal(
symtab.entries.begin(),
symtab.entries.end(),
pc,
[] (frame_ptr pc, const symtab_entry& entry) {
return pc < entry.st_value;
}
);
if(it == symtab.entries.end()) {
return nullopt;
}
if(pc <= it->st_value + it->st_size) {
return strtab.data() + it->st_name;
}
return nullopt;
}
Result<optional<std::vector<elf::symbol_entry>>, internal_error> elf::get_symtab_entries() {
return resolve_symtab_entries(get_symtab());
}
Result<optional<std::vector<elf::symbol_entry>>, internal_error> elf::get_dynamic_symtab_entries() {
return resolve_symtab_entries(get_dynamic_symtab());
}
Result<optional<std::vector<elf::symbol_entry>>, internal_error> elf::resolve_symtab_entries(
const Result<const optional<elf::symtab_info> &, internal_error>& symtab
) {
if(!symtab) {
return symtab.unwrap_error();
}
if(!symtab.unwrap_value()) {
return nullopt;
}
const auto& info = symtab.unwrap_value().unwrap();
optional<const std::vector<char>&> strtab;
if(info.strtab_link != SHN_UNDEF) {
auto strtab_ = get_strtab(info.strtab_link);
if(strtab_.is_error()) {
return strtab_.unwrap_error();
}
strtab = strtab_.unwrap_value();
}
std::vector<symbol_entry> res;
for(const auto& entry : info.entries) {
res.push_back({
strtab.has_value() ? strtab.unwrap().data() + entry.st_name : "<strtab error>",
entry.st_shndx,
entry.st_value,
entry.st_size
});
}
return res;
}
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type>
T elf::byteswap_if_needed(T value) {
if(cpptrace::detail::is_little_endian() == is_little_endian) {
return value;
} else {
return byteswap(value);
}
}
Result<const elf::header_info&, internal_error> elf::get_header_info() {
if(header) {
Result<const elf::header_info&, internal_error> r = header.unwrap();
return std::ref(header.unwrap());
}
if(tried_to_load_header) {
return internal_error("previous header load failed " + object_path);
}
tried_to_load_header = true;
if(is_64) {
return get_header_info_impl<64>();
} else {
return get_header_info_impl<32>();
}
}
template<std::size_t Bits>
Result<const elf::header_info&, internal_error> elf::get_header_info_impl() {
static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument");
using Header = typename std::conditional<Bits == 32, Elf32_Ehdr, Elf64_Ehdr>::type;
auto loaded_header = load_bytes<Header>(file, 0);
if(loaded_header.is_error()) {
return std::move(loaded_header).unwrap_error();
}
const Header& file_header = loaded_header.unwrap_value();
if(file_header.e_ehsize != sizeof(Header)) {
return internal_error("ELF file header size mismatch" + object_path);
}
header_info info;
info.e_phoff = byteswap_if_needed(file_header.e_phoff);
info.e_phnum = byteswap_if_needed(file_header.e_phnum);
info.e_phentsize = byteswap_if_needed(file_header.e_phentsize);
info.e_shoff = byteswap_if_needed(file_header.e_shoff);
info.e_shnum = byteswap_if_needed(file_header.e_shnum);
info.e_shentsize = byteswap_if_needed(file_header.e_shentsize);
header = info;
return header.unwrap();
}
Result<const std::vector<elf::section_info>&, internal_error> elf::get_sections() {
if(did_load_sections) {
return sections;
}
if(tried_to_load_sections) {
return internal_error("previous sections load failed " + object_path);
}
tried_to_load_sections = true;
if(is_64) {
return get_sections_impl<64>();
} else {
return get_sections_impl<32>();
}
}
template<std::size_t Bits>
Result<const std::vector<elf::section_info>&, internal_error> elf::get_sections_impl() {
static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument");
using SHeader = typename std::conditional<Bits == 32, Elf32_Shdr, Elf64_Shdr>::type;
auto header = get_header_info();
if(header.is_error()) {
return std::move(header).unwrap_error();
}
const auto& header_info = header.unwrap_value();
for(unsigned i = 0; i < header_info.e_shnum; i++) {
auto loaded_sh = load_bytes<SHeader>(file, header_info.e_shoff + header_info.e_shentsize * i);
if(loaded_sh.is_error()) {
return std::move(loaded_sh).unwrap_error();
}
const SHeader& section_header = loaded_sh.unwrap_value();
section_info info;
info.sh_type = byteswap_if_needed(section_header.sh_type);
info.sh_addr = byteswap_if_needed(section_header.sh_addr);
info.sh_offset = byteswap_if_needed(section_header.sh_offset);
info.sh_size = byteswap_if_needed(section_header.sh_size);
info.sh_entsize = byteswap_if_needed(section_header.sh_entsize);
info.sh_link = byteswap_if_needed(section_header.sh_link);
sections.push_back(info);
}
did_load_sections = true;
return sections;
}
Result<const std::vector<char>&, internal_error> elf::get_strtab(std::size_t index) {
auto res = strtab_entries.insert({index, {}});
auto it = res.first;
auto did_insert = res.second;
auto& entry = it->second;
if(!did_insert) {
if(entry.did_load_strtab) {
return entry.data;
}
if(entry.tried_to_load_strtab) {
return internal_error("previous strtab load failed {}", object_path);
}
}
entry.tried_to_load_strtab = true;
auto sections_ = get_sections();
if(sections_.is_error()) {
return std::move(sections_).unwrap_error();
}
const auto& sections = sections_.unwrap_value();
if(index >= sections.size()) {
return internal_error("requested strtab section index out of range");
}
const auto& section = sections[index];
if(section.sh_type != SHT_STRTAB) {
return internal_error("requested strtab section not a strtab (requested {} of {})", index, object_path);
}
entry.data.resize(section.sh_size + 1);
if(std::fseek(file, section.sh_offset, SEEK_SET) != 0) {
return internal_error("fseek error while loading elf string table");
}
if(std::fread(entry.data.data(), sizeof(char), section.sh_size, file) != section.sh_size) {
return internal_error("fread error while loading elf string table");
}
entry.data[section.sh_size] = 0; // just out of an abundance of caution
entry.did_load_strtab = true;
return entry.data;
}
Result<const optional<elf::symtab_info>&, internal_error> elf::get_symtab() {
if(did_load_symtab) {
return symtab;
}
if(tried_to_load_symtab) {
return internal_error("previous symtab load failed {}", object_path);
}
tried_to_load_symtab = true;
if(is_64) {
auto res = get_symtab_impl<64>(false);
if(res.has_value()) {
symtab = std::move(res).unwrap_value();
did_load_symtab = true;
return symtab;
} else {
return std::move(res).unwrap_error();
}
} else {
auto res = get_symtab_impl<32>(false);
if(res.has_value()) {
symtab = std::move(res).unwrap_value();
did_load_symtab = true;
return symtab;
} else {
return std::move(res).unwrap_error();
}
}
}
Result<const optional<elf::symtab_info>&, internal_error> elf::get_dynamic_symtab() {
if(did_load_dynamic_symtab) {
return dynamic_symtab;
}
if(tried_to_load_dynamic_symtab) {
return internal_error("previous dynamic symtab load failed {}", object_path);
}
tried_to_load_dynamic_symtab = true;
if(is_64) {
auto res = get_symtab_impl<64>(true);
if(res.has_value()) {
dynamic_symtab = std::move(res).unwrap_value();
did_load_dynamic_symtab = true;
return dynamic_symtab;
} else {
return std::move(res).unwrap_error();
}
} else {
auto res = get_symtab_impl<32>(true);
if(res.has_value()) {
dynamic_symtab = std::move(res).unwrap_value();
did_load_dynamic_symtab = true;
return dynamic_symtab;
} else {
return std::move(res).unwrap_error();
}
}
}
template<std::size_t Bits>
Result<optional<elf::symtab_info>, internal_error> elf::get_symtab_impl(bool dynamic) {
// https://refspecs.linuxfoundation.org/elf/elf.pdf
// page 66: only one sht_symtab and sht_dynsym section per file
// page 32: symtab spec
static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument");
using SymEntry = typename std::conditional<Bits == 32, Elf32_Sym, Elf64_Sym>::type;
auto sections_ = get_sections();
if(sections_.is_error()) {
return std::move(sections_).unwrap_error();
}
const auto& sections = sections_.unwrap_value();
optional<symtab_info> symbol_table;
for(const auto& section : sections) {
if(section.sh_type == (dynamic ? SHT_DYNSYM : SHT_SYMTAB)) {
if(section.sh_entsize != sizeof(SymEntry)) {
return internal_error("elf seems corrupted, sym entry mismatch {}", object_path);
}
if(section.sh_size % section.sh_entsize != 0) {
return internal_error("elf seems corrupted, sym entry vs section size mismatch {}", object_path);
}
std::vector<SymEntry> buffer(section.sh_size / section.sh_entsize);
if(std::fseek(file, section.sh_offset, SEEK_SET) != 0) {
return internal_error("fseek error while loading elf symbol table");
}
if(std::fread(buffer.data(), section.sh_entsize, buffer.size(), file) != buffer.size()) {
return internal_error("fread error while loading elf symbol table");
}
symbol_table = symtab_info{};
symbol_table.unwrap().entries.reserve(buffer.size());
for(const auto& entry : buffer) {
symtab_entry normalized;
normalized.st_name = byteswap_if_needed(entry.st_name);
normalized.st_info = byteswap_if_needed(entry.st_info);
normalized.st_other = byteswap_if_needed(entry.st_other);
normalized.st_shndx = byteswap_if_needed(entry.st_shndx);
normalized.st_value = byteswap_if_needed(entry.st_value);
normalized.st_size = byteswap_if_needed(entry.st_size);
symbol_table.unwrap().entries.push_back(normalized);
}
std::sort(
symbol_table.unwrap().entries.begin(),
symbol_table.unwrap().entries.end(),
[] (const symtab_entry& a, const symtab_entry& b) {
return a.st_value < b.st_value;
}
);
symbol_table.unwrap().strtab_link = section.sh_link;
break;
}
}
return symbol_table;
}
Result<maybe_owned<elf>, internal_error> open_elf_cached(const std::string& object_path) {
if(get_cache_mode() == cache_mode::prioritize_memory) {
return elf::open_elf(object_path)
.transform([](elf&& obj) { return maybe_owned<elf>{detail::make_unique<elf>(std::move(obj))}; });
} else {
std::mutex m;
std::unique_lock<std::mutex> lock{m};
// TODO: Re-evaluate storing the error
static std::unordered_map<std::string, Result<elf, internal_error>> cache;
auto it = cache.find(object_path);
if(it == cache.end()) {
auto res = cache.insert({ object_path, elf::open_elf(object_path) });
VERIFY(res.second);
it = res.first;
}
return it->second.transform([](elf& obj) { return maybe_owned<elf>(&obj); });
}
}
}
}

View File

@ -8,10 +8,119 @@
#include <cstdint>
#include <string>
#include <unordered_map>
namespace cpptrace {
namespace detail {
Result<std::uintptr_t, internal_error> elf_get_module_image_base(const std::string& object_path);
class elf {
file_wrapper file;
std::string object_path;
bool is_little_endian;
bool is_64;
struct header_info {
uint64_t e_phoff;
uint32_t e_phnum;
uint32_t e_phentsize;
uint64_t e_shoff;
uint32_t e_shnum;
uint32_t e_shentsize;
};
bool tried_to_load_header = false;
optional<header_info> header;
struct section_info {
uint32_t sh_type;
uint64_t sh_addr;
uint64_t sh_offset;
uint64_t sh_size;
uint64_t sh_entsize;
uint32_t sh_link;
};
bool tried_to_load_sections = false;
bool did_load_sections = false;
std::vector<section_info> sections;
struct strtab_entry {
bool tried_to_load_strtab = false;
bool did_load_strtab = false;
std::vector<char> data;
};
std::unordered_map<std::size_t, strtab_entry> strtab_entries;
struct symtab_entry {
uint32_t st_name;
unsigned char st_info;
unsigned char st_other;
uint16_t st_shndx;
uint64_t st_value;
uint64_t st_size;
};
struct symtab_info {
std::vector<symtab_entry> entries;
std::size_t strtab_link = 0;
};
bool tried_to_load_symtab = false;
bool did_load_symtab = false;
optional<symtab_info> symtab;
bool tried_to_load_dynamic_symtab = false;
bool did_load_dynamic_symtab = false;
optional<symtab_info> dynamic_symtab;
elf(file_wrapper file, const std::string& object_path, bool is_little_endian, bool is_64);
public:
static NODISCARD Result<elf, internal_error> open_elf(const std::string& object_path);
elf(elf&&) = default;
public:
Result<std::uintptr_t, internal_error> get_module_image_base();
private:
template<std::size_t Bits>
Result<std::uintptr_t, internal_error> get_module_image_base_impl();
public:
optional<std::string> lookup_symbol(frame_ptr pc);
private:
optional<std::string> lookup_symbol(frame_ptr pc, const optional<symtab_info>& maybe_symtab);
public:
struct symbol_entry {
std::string st_name;
uint16_t st_shndx;
uint64_t st_value;
uint64_t st_size;
};
Result<optional<std::vector<symbol_entry>>, internal_error> get_symtab_entries();
Result<optional<std::vector<symbol_entry>>, internal_error> get_dynamic_symtab_entries();
private:
Result<optional<std::vector<symbol_entry>>, internal_error> resolve_symtab_entries(
const Result<const optional<symtab_info> &, internal_error>&
);
private:
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
T byteswap_if_needed(T value);
Result<const header_info&, internal_error> get_header_info();
template<std::size_t Bits>
Result<const header_info&, internal_error> get_header_info_impl();
Result<const std::vector<section_info>&, internal_error> get_sections();
template<std::size_t Bits>
Result<const std::vector<section_info>&, internal_error> get_sections_impl();
Result<const std::vector<char>&, internal_error> get_strtab(std::size_t index);
Result<const optional<symtab_info>&, internal_error> get_symtab();
Result<const optional<symtab_info>&, internal_error> get_dynamic_symtab();
template<std::size_t Bits>
Result<optional<symtab_info>, internal_error> get_symtab_impl(bool dynamic);
};
NODISCARD Result<maybe_owned<elf>, internal_error> open_elf_cached(const std::string& object_path);
}
}

View File

@ -11,6 +11,7 @@
#include <cstdio>
#include <cstring>
#include <mutex>
#include <string>
#include <type_traits>
#include <unordered_map>
@ -102,7 +103,7 @@ namespace detail {
Result<const char*, internal_error> mach_o::symtab_info_data::get_string(std::size_t index) const {
if(stringtab && index < symtab.strsize) {
return stringtab.get() + index;
return stringtab.unwrap().data() + index;
} else {
return internal_error("can't retrieve symbol from symtab");
}
@ -219,7 +220,7 @@ namespace detail {
void mach_o::print_symbol_table_entry(
const nlist_64& entry,
const std::unique_ptr<char[]>& stringtab,
const char* stringtab,
std::size_t stringsize,
std::size_t j
) const {
@ -248,7 +249,7 @@ namespace detail {
stringtab == nullptr
? "Stringtab error"
: entry.n_un.n_strx < stringsize
? stringtab.get() + entry.n_un.n_strx
? stringtab + entry.n_un.n_strx
: "String index out of bounds"
);
}
@ -286,7 +287,7 @@ namespace detail {
}
print_symbol_table_entry(
entry.unwrap_value(),
std::move(stringtab).value_or(std::unique_ptr<char[]>(nullptr)),
stringtab ? stringtab.unwrap_value().data() : nullptr,
symtab.strsize,
j
);
@ -364,10 +365,17 @@ namespace detail {
return debug_map;
}
Result<std::vector<mach_o::symbol_entry>, internal_error> mach_o::symbol_table() {
Result<const std::vector<mach_o::symbol_entry>&, internal_error> mach_o::symbol_table() {
if(symbols) {
return symbols.unwrap();
}
if(tried_to_load_symbols) {
return internal_error("previous symbol table load failed");
}
tried_to_load_symbols = true;
std::vector<symbol_entry> symbol_table;
// we have a bunch of symbols in our binary we need to pair up with symbols from various .o files
// first collect symbols and the objects they come from
std::vector<symbol_entry> symbols;
auto symtab_info_res = get_symtab_info();
if(!symtab_info_res) {
return std::move(symtab_info_res).unwrap_error();
@ -394,13 +402,42 @@ namespace detail {
if(!str) {
return std::move(str).unwrap_error();
}
symbols.push_back({
symbol_table.push_back({
entry.n_value,
str.unwrap_value()
});
}
}
return symbols;
std::sort(
symbol_table.begin(),
symbol_table.end(),
[] (const symbol_entry& a, const symbol_entry& b) { return a.address < b.address; }
);
symbols = std::move(symbol_table);
return symbols.unwrap();
}
optional<std::string> mach_o::lookup_symbol(frame_ptr pc) {
auto symtab_ = symbol_table();
if(!symtab_) {
return nullopt;
}
const auto& symtab = symtab_.unwrap_value();;
auto it = first_less_than_or_equal(
symtab.begin(),
symtab.end(),
pc,
[] (frame_ptr pc, const symbol_entry& entry) {
return pc < entry.address;
}
);
if(it == symtab.end()) {
return nullopt;
}
ASSERT(pc >= it->address);
// TODO: We subtracted one from the address so name + diff won't show up in the objdump, decide if desirable
// to have an easier offset to lookup
return microfmt::format("{} + {}", it->name, pc - it->address);
}
// produce information similar to dsymutil -dump-debug-map
@ -605,12 +642,12 @@ namespace detail {
return common;
}
Result<std::unique_ptr<char[]>, internal_error> mach_o::load_string_table(std::uint32_t offset, std::uint32_t byte_count) const {
std::unique_ptr<char[]> buffer(new char[byte_count + 1]);
Result<std::vector<char>, internal_error> mach_o::load_string_table(std::uint32_t offset, std::uint32_t byte_count) const {
std::vector<char> buffer(byte_count + 1);
if(std::fseek(file, load_base + offset, SEEK_SET) != 0) {
return internal_error("fseek error while loading mach-o symbol table");
}
if(std::fread(buffer.get(), sizeof(char), byte_count, file) != byte_count) {
if(std::fread(buffer.data(), sizeof(char), byte_count, file) != byte_count) {
return internal_error("fread error while loading mach-o symbol table");
}
buffer[byte_count] = 0; // just out of an abundance of caution
@ -633,6 +670,27 @@ namespace detail {
return is_fat_magic(magic.unwrap_value());
}
}
Result<maybe_owned<mach_o>, internal_error> open_mach_o_cached(const std::string& object_path) {
if(get_cache_mode() == cache_mode::prioritize_memory) {
return mach_o::open_mach_o(object_path)
.transform([](mach_o&& obj) {
return maybe_owned<mach_o>{detail::make_unique<mach_o>(std::move(obj))};
});
} else {
std::mutex m;
std::unique_lock<std::mutex> lock{m};
// TODO: Re-evaluate storing the error
static std::unordered_map<std::string, Result<mach_o, internal_error>> cache;
auto it = cache.find(object_path);
if(it == cache.end()) {
auto res = cache.insert({ object_path, mach_o::open_mach_o(object_path) });
VERIFY(res.second);
it = res.first;
}
return it->second.transform([](mach_o& obj) { return maybe_owned<mach_o>(&obj); });
}
}
}
}

View File

@ -28,6 +28,23 @@ namespace detail {
};
class mach_o {
public:
struct debug_map_entry {
uint64_t source_address;
uint64_t size;
std::string name;
};
struct symbol_entry {
uint64_t address;
std::string name;
};
// map from object file to a vector of symbols to resolve
using debug_map = std::unordered_map<std::string, std::vector<debug_map_entry>>;
private:
file_wrapper file;
std::string object_path;
std::uint32_t magic;
@ -46,13 +63,16 @@ namespace detail {
struct symtab_info_data {
symtab_command symtab;
std::unique_ptr<char[]> stringtab;
optional<std::vector<char>> stringtab;
Result<const char*, internal_error> get_string(std::size_t index) const;
};
bool tried_to_load_symtab = false;
optional<symtab_info_data> symtab_info;
bool tried_to_load_symbols = false;
optional<std::vector<symbol_entry>> symbols;
mach_o(
file_wrapper file,
const std::string& object_path,
@ -80,31 +100,19 @@ namespace detail {
void print_symbol_table_entry(
const nlist_64& entry,
const std::unique_ptr<char[]>& stringtab,
const char* stringtab,
std::size_t stringsize,
std::size_t j
) const;
void print_symbol_table();
struct debug_map_entry {
uint64_t source_address;
uint64_t size;
std::string name;
};
struct symbol_entry {
uint64_t address;
std::string name;
};
// map from object file to a vector of symbols to resolve
using debug_map = std::unordered_map<std::string, std::vector<debug_map_entry>>;
// produce information similar to dsymutil -dump-debug-map
Result<debug_map, internal_error> get_debug_map();
Result<std::vector<symbol_entry>, internal_error> symbol_table();
Result<const std::vector<symbol_entry>&, internal_error> symbol_table();
optional<std::string> lookup_symbol(frame_ptr pc);
// produce information similar to dsymutil -dump-debug-map
static void print_debug_map(const debug_map& debug_map);
@ -123,12 +131,14 @@ namespace detail {
template<std::size_t Bits>
Result<nlist_64, internal_error> load_symtab_entry(std::uint32_t symbol_base, std::size_t index) const;
Result<std::unique_ptr<char[]>, internal_error> load_string_table(std::uint32_t offset, std::uint32_t byte_count) const;
Result<std::vector<char>, internal_error> load_string_table(std::uint32_t offset, std::uint32_t byte_count) const;
bool should_swap() const;
};
Result<bool, internal_error> macho_is_fat(const std::string& object_path);
NODISCARD Result<maybe_owned<mach_o>, internal_error> open_mach_o_cached(const std::string& object_path);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,5 @@
#include <cpptrace/cpptrace.hpp>
#include <cpptrace/formatting.hpp>
#include <cstddef>
#include <cstdint>
@ -9,6 +10,7 @@
#include <string>
#include <vector>
#include "cpptrace/basic.hpp"
#include "symbols/symbols.hpp"
#include "unwind/unwind.hpp"
#include "demangle/demangle.hpp"
@ -18,6 +20,7 @@
#include "binary/object.hpp"
#include "binary/safe_dl.hpp"
#include "snippets/snippet.hpp"
#include "options.hpp"
namespace cpptrace {
CPPTRACE_FORCE_NO_INLINE
@ -59,7 +62,7 @@ namespace cpptrace {
try {
std::vector<stacktrace_frame> trace = detail::resolve_frames(frames);
for(auto& frame : trace) {
frame.symbol = detail::demangle(frame.symbol);
frame.symbol = detail::demangle(frame.symbol, true);
}
return {std::move(trace)};
} catch(...) { // NOSONAR
@ -106,7 +109,7 @@ namespace cpptrace {
try {
std::vector<stacktrace_frame> trace = detail::resolve_frames(frames);
for(auto& frame : trace) {
frame.symbol = detail::demangle(frame.symbol);
frame.symbol = detail::demangle(frame.symbol, true);
}
return {std::move(trace)};
} catch(...) { // NOSONAR
@ -129,41 +132,12 @@ namespace cpptrace {
return detail::get_frame_object_info(raw_address);
}
static std::string frame_to_string(
bool color,
const stacktrace_frame& frame
) {
const auto reset = color ? RESET : "";
const auto green = color ? GREEN : "";
const auto yellow = color ? YELLOW : "";
const auto blue = color ? BLUE : "";
std::string str;
if(frame.is_inline) {
str += microfmt::format("{<{}}", 2 * sizeof(frame_ptr) + 2, "(inlined)");
} else {
str += microfmt::format("{}0x{>{}:0h}{}", blue, 2 * sizeof(frame_ptr), frame.raw_address, reset);
}
if(!frame.symbol.empty()) {
str += microfmt::format(" in {}{}{}", yellow, frame.symbol, reset);
}
if(!frame.filename.empty()) {
str += microfmt::format(" at {}{}{}", green, frame.filename, reset);
if(frame.line.has_value()) {
str += microfmt::format(":{}{}{}", blue, frame.line.value(), reset);
if(frame.column.has_value()) {
str += microfmt::format(":{}{}{}", blue, frame.column.value(), reset);
}
}
}
return str;
}
std::string stacktrace_frame::to_string() const {
return to_string(false);
}
std::string stacktrace_frame::to_string(bool color) const {
return frame_to_string(color, *this);
return get_default_formatter().format(*this, color);
}
std::ostream& operator<<(std::ostream& stream, const stacktrace_frame& frame) {
@ -195,89 +169,34 @@ namespace cpptrace {
}
void stacktrace::print() const {
print(std::cerr, true);
get_default_formatter().print(*this);
}
void stacktrace::print(std::ostream& stream) const {
print(stream, true);
get_default_formatter().print(stream, *this);
}
void stacktrace::print(std::ostream& stream, bool color) const {
print(stream, color, true, nullptr);
get_default_formatter().print(stream, *this, color);
}
static void print_frame(
std::ostream& stream,
bool color,
unsigned frame_number_width,
std::size_t counter,
const stacktrace_frame& frame
) {
std::string line = microfmt::format("#{<{}} {}", frame_number_width, counter, frame.to_string(color));
stream << line;
}
void stacktrace::print(std::ostream& stream, bool color, bool newline_at_end, const char* header) const {
if(
color && (
(&stream == &std::cout && isatty(stdout_fileno)) || (&stream == &std::cerr && isatty(stderr_fileno))
)
) {
detail::enable_virtual_terminal_processing_if_needed();
}
stream << (header ? header : "Stack trace (most recent call first):") << '\n';
std::size_t counter = 0;
if(frames.empty()) {
stream << "<empty trace>\n";
return;
}
const auto frame_number_width = detail::n_digits(static_cast<int>(frames.size()) - 1);
for(const auto& frame : frames) {
print_frame(stream, color, frame_number_width, counter, frame);
if(newline_at_end || &frame != &frames.back()) {
stream << '\n';
}
counter++;
namespace detail {
const formatter& get_default_snippet_formatter() {
static formatter snippet_formatter = formatter{}.snippets(true);
return snippet_formatter;
}
}
void stacktrace::print_with_snippets() const {
print_with_snippets(std::cerr, true);
detail::get_default_snippet_formatter().print(*this);
}
void stacktrace::print_with_snippets(std::ostream& stream) const {
print_with_snippets(stream, true);
detail::get_default_snippet_formatter().print(stream, *this);
}
void stacktrace::print_with_snippets(std::ostream& stream, bool color) const {
print_with_snippets(stream, color, true, nullptr);
}
void stacktrace::print_with_snippets(std::ostream& stream, bool color, bool newline_at_end, const char* header) const {
if(
color && (
(&stream == &std::cout && isatty(stdout_fileno)) || (&stream == &std::cerr && isatty(stderr_fileno))
)
) {
detail::enable_virtual_terminal_processing_if_needed();
}
stream << (header ? header : "Stack trace (most recent call first):") << '\n';
std::size_t counter = 0;
if(frames.empty()) {
stream << "<empty trace>" << '\n';
return;
}
const auto frame_number_width = detail::n_digits(static_cast<int>(frames.size()) - 1);
for(const auto& frame : frames) {
print_frame(stream, color, frame_number_width, counter, frame);
if(newline_at_end || &frame != &frames.back()) {
stream << '\n';
}
if(frame.line.has_value() && !frame.filename.empty()) {
stream << detail::get_snippet(frame.filename, frame.line.value(), 2, color);
}
counter++;
}
detail::get_default_snippet_formatter().print(stream, *this, color);
}
void stacktrace::clear() {
@ -289,13 +208,12 @@ namespace cpptrace {
}
std::string stacktrace::to_string(bool color) const {
std::ostringstream oss;
print(oss, color, false, nullptr);
return std::move(oss).str();
return get_default_formatter().format(*this, color);
}
std::ostream& operator<<(std::ostream& stream, const stacktrace& trace) {
return stream << trace.to_string();
get_default_formatter().print(stream, trace);
return stream;
}
CPPTRACE_FORCE_NO_INLINE
@ -393,7 +311,7 @@ namespace cpptrace {
std::vector<frame_ptr> frames = detail::capture_frames(skip + 1, max_depth);
std::vector<stacktrace_frame> trace = detail::resolve_frames(frames);
for(auto& frame : trace) {
frame.symbol = detail::demangle(frame.symbol);
frame.symbol = detail::demangle(frame.symbol, true);
}
return {std::move(trace)};
} catch(...) { // NOSONAR
@ -415,4 +333,8 @@ namespace cpptrace {
bool can_signal_safe_unwind() {
return detail::has_safe_unwind();
}
bool can_get_safe_object_frame() {
return detail::has_get_safe_object_frame();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

353
src/formatting.cpp Normal file
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;
}
}

View File

@ -5,7 +5,8 @@
namespace cpptrace {
namespace detail {
bool should_absorb_trace_exceptions();
// exported for test purposes
CPPTRACE_EXPORT bool should_absorb_trace_exceptions();
bool should_resolve_inlined_calls();
cache_mode get_cache_mode();
}

View File

@ -1,47 +0,0 @@
#include "platform/platform.hpp"
#if IS_WINDOWS
#include "platform/dbghelp_syminit_manager.hpp"
#include "utils/error.hpp"
#include "utils/microfmt.hpp"
#include <unordered_set>
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <dbghelp.h>
namespace cpptrace {
namespace detail {
dbghelp_syminit_manager::~dbghelp_syminit_manager() {
for(auto handle : set) {
if(!SymCleanup(handle)) {
ASSERT(false, microfmt::format("Cpptrace SymCleanup failed with code {}\n", GetLastError()).c_str());
}
}
}
void dbghelp_syminit_manager::init(HANDLE proc) {
if(set.count(proc) == 0) {
if(!SymInitialize(proc, NULL, TRUE)) {
throw internal_error("SymInitialize failed {}", GetLastError());
}
set.insert(proc);
}
}
// Thread-safety: Must only be called from symbols_with_dbghelp while the dbghelp_lock lock is held
dbghelp_syminit_manager& get_syminit_manager() {
static dbghelp_syminit_manager syminit_manager;
return syminit_manager;
}
}
}
#endif

View File

@ -1,22 +0,0 @@
#ifndef DBGHELP_SYMINIT_MANAGER_HPP
#define DBGHELP_SYMINIT_MANAGER_HPP
#include <unordered_set>
namespace cpptrace {
namespace detail {
struct dbghelp_syminit_manager {
// The set below contains Windows `HANDLE` objects, `void*` is used to avoid
// including the (expensive) Windows header here
std::unordered_set<void*> set;
~dbghelp_syminit_manager();
void init(void* proc);
};
// Thread-safety: Must only be called from symbols_with_dbghelp while the dbghelp_lock lock is held
dbghelp_syminit_manager& get_syminit_manager();
}
}
#endif

View File

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

@ -7,6 +7,7 @@
// libstdc++ and libc++
#if defined(CPPTRACE_HAS_CXX_EXCEPTION_TYPE) && (IS_LIBSTDCXX || IS_LIBCXX)
#include <typeinfo>
#include <cxxabi.h>
#include "demangle/demangle.hpp"
#endif
@ -16,7 +17,7 @@ namespace detail {
inline std::string exception_type_name() {
#if defined(CPPTRACE_HAS_CXX_EXCEPTION_TYPE) && (IS_LIBSTDCXX || IS_LIBCXX)
const std::type_info* t = abi::__cxa_current_exception_type();
return t ? detail::demangle(t->name()) : "<unknown>";
return t ? detail::demangle(t->name(), false) : "<unknown>";
#else
return "<unknown>";
#endif

View File

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

View File

@ -34,7 +34,7 @@ namespace libdwarf {
if(!resolver) {
// this seems silly but it's an attempt to not repeatedly try to initialize new dwarf_resolvers if
// exceptions are thrown, e.g. if the path doesn't exist
resolver = std::unique_ptr<null_resolver>(new null_resolver);
resolver = detail::make_unique<null_resolver>();
resolver = make_dwarf_resolver(object_path);
}
return resolver;
@ -46,11 +46,11 @@ namespace libdwarf {
// the path doesn't exist
std::unordered_map<std::string, uint64_t> symbols;
this->symbols = symbols;
auto obj = mach_o::open_mach_o(object_path);
if(!obj) {
auto mach_o_object = open_mach_o_cached(object_path);
if(!mach_o_object) {
return this->symbols.unwrap();
}
auto symbol_table = obj.unwrap_value().symbol_table();
const auto& symbol_table = mach_o_object.unwrap_value()->symbol_table();
if(!symbol_table) {
return this->symbols.unwrap();
}
@ -100,7 +100,7 @@ namespace libdwarf {
uint64_t size;
std::string name;
nullable<uint64_t> target_address; // T(-1) is used as a sentinel
std::size_t object_index;
std::size_t object_index; // index into target_objects
};
class debug_map_resolver : public symbol_resolver {
@ -110,11 +110,11 @@ namespace libdwarf {
debug_map_resolver(const std::string& source_object_path) {
// load mach-o
// TODO: Cache somehow?
auto obj = mach_o::open_mach_o(source_object_path);
if(!obj) {
auto mach_o_object = open_mach_o_cached(source_object_path);
if(!mach_o_object) {
return;
}
mach_o& source_mach = obj.unwrap_value();
mach_o& source_mach = *mach_o_object.unwrap_value();
auto source_debug_map = source_mach.get_debug_map();
if(!source_debug_map) {
return;
@ -137,6 +137,7 @@ namespace libdwarf {
}
}
// sort for binary lookup later
// TODO: Redundant?
std::sort(
symbols.begin(),
symbols.end(),
@ -197,7 +198,7 @@ namespace libdwarf {
};
std::unique_ptr<symbol_resolver> make_debug_map_resolver(const std::string& object_path) {
return std::unique_ptr<debug_map_resolver>(new debug_map_resolver(object_path));
return detail::make_unique<debug_map_resolver>(object_path);
}
#endif
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -83,6 +83,8 @@ namespace detail {
}
}
// TODO: Symbol resolution code should probably handle when object addresses are 0
std::vector<stacktrace_frame> resolve_frames(const std::vector<object_frame>& frames) {
#if defined(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) && defined(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP)
std::vector<stacktrace_frame> trace = libdwarf::resolve_frames(frames);

View File

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

View File

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

View File

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

View File

@ -6,6 +6,8 @@
#include "dwarf/resolver.hpp"
#include "utils/common.hpp"
#include "utils/utils.hpp"
#include "binary/elf.hpp"
#include "binary/mach-o.hpp"
#include <cstdint>
#include <cstdio>
@ -92,6 +94,12 @@ namespace libdwarf {
for(const auto& group : collate_frames(frames, trace)) {
try {
const auto& object_name = group.first;
// TODO PERF: Potentially a duplicate open and parse with module base stuff (and debug map resolver)
#if IS_LINUX
auto object = open_elf_cached(object_name);
#elif IS_APPLE
auto object = open_mach_o_cached(object_name);
#endif
auto resolver = get_resolver(object_name);
for(const auto& entry : group.second) {
const auto& dlframe = entry.first.get();
@ -106,6 +114,13 @@ namespace libdwarf {
throw;
}
}
#if IS_LINUX || IS_APPLE
if(frame.frame.symbol.empty() && object.has_value()) {
frame.frame.symbol = object
.unwrap_value()
->lookup_symbol(dlframe.object_address).value_or("");
}
#endif
}
} catch(...) { // NOSONAR
if(!should_absorb_trace_exceptions()) {

View File

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

View File

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

View File

@ -4,7 +4,6 @@
#include <cpptrace/basic.hpp>
#include "platform/platform.hpp"
#include "options.hpp"
#include <cstdint>
@ -25,6 +24,19 @@
#define NODISCARD
#endif
#if IS_MSVC
#define MSVC_CDECL __cdecl
#else
#define MSVC_CDECL
#endif
// support is pretty good https://godbolt.org/z/djTqv7WMY, checked in cmake during config
#ifdef HAS_ATTRIBUTE_PACKED
#define PACKED __attribute__((packed))
#else
#define PACKED
#endif
namespace cpptrace {
namespace detail {
static const stacktrace_frame null_frame {

108
src/utils/lru_cache.hpp Normal file
View File

@ -0,0 +1,108 @@
#ifndef LRU_CACHE_HPP
#define LRU_CACHE_HPP
#include "utils/error.hpp"
#include "utils/optional.hpp"
#include <list>
#include <unordered_map>
namespace cpptrace {
namespace detail {
template<typename K, typename V>
class lru_cache {
struct kvp {
K key;
V value;
};
using list_type = std::list<kvp>;
using list_iterator = typename list_type::iterator;
mutable list_type lru;
std::unordered_map<K, list_iterator> map;
optional<std::size_t> max_size;
public:
lru_cache() = default;
lru_cache(optional<std::size_t> max_size) : max_size(max_size) {
VERIFY(!max_size || max_size.unwrap() > 0);
}
void set_max_size(optional<std::size_t> size) {
VERIFY(!size || size.unwrap() > 0);
max_size = size;
maybe_trim();
}
void maybe_touch(const K& key) {
auto it = map.find(key);
if(it == map.end()) {
return;
}
auto list_it = it->second;
touch(list_it);
}
optional<V&> maybe_get(const K& key) {
auto it = map.find(key);
if(it == map.end()) {
return nullopt;
} else {
touch(it->second);
return it->second->value;
}
}
optional<const V&> maybe_get(const K& key) const {
auto it = map.find(key);
if(it == map.end()) {
return nullopt;
} else {
touch(it->second);
return it->second->value;
}
}
void set(const K& key, V value) {
auto it = map.find(key);
if(it == map.end()) {
insert(key, std::move(value));
} else {
touch(it->second);
it->second->value = std::move(value);
}
}
optional<V&> insert(const K& key, V value) {
auto pair = map.insert({key, lru.end()});
if(!pair.second) {
// didn't insert
return nullopt;
}
auto map_it = pair.first;
lru.push_front({key, std::move(value)});
map_it->second = lru.begin();
maybe_trim();
return lru.front().value;
}
std::size_t size() const {
return lru.size();
}
private:
void touch(list_iterator list_it) const {
lru.splice(lru.begin(), lru, list_it);
}
void maybe_trim() {
while(max_size && lru.size() > max_size.unwrap()) {
const auto& to_remove = lru.back();
map.erase(to_remove.key);
lru.pop_back();
}
}
};
}
}
#endif

View File

@ -2,10 +2,11 @@
#define MICROFMT_HPP
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstring>
#include <iosfwd>
#include <initializer_list>
#include <iostream>
#include <iterator>
#include <string>
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
#include <string_view>
@ -50,19 +51,18 @@ namespace microfmt {
char base = 'd';
};
template<typename It> void do_write(std::string& out, It begin, It end, const format_options& options) {
template<typename OutputIt, typename InputIt>
void do_write(OutputIt out, InputIt begin, InputIt end, const format_options& options) {
auto size = end - begin;
if(static_cast<std::size_t>(size) >= options.width) {
out.append(begin, end);
std::copy(begin, end, out);
} else {
auto out_size = out.size();
out.resize(out_size + options.width);
if(options.align == alignment::left) {
std::copy(begin, end, out.begin() + out_size);
std::fill(out.begin() + out_size + size, out.end(), options.fill);
std::copy(begin, end, out);
std::fill_n(out, options.width - size, options.fill);
} else {
std::fill(out.begin() + out_size, out.begin() + out_size + (options.width - size), options.fill);
std::copy(begin, end, out.begin() + out_size + (options.width - size));
std::fill_n(out, options.width - size, options.fill);
std::copy(begin, end, out);
}
}
}
@ -99,15 +99,18 @@ namespace microfmt {
}
}
struct string_view {
const char* data;
std::size_t size;
};
class format_value {
enum class value_type {
char_value,
int64_value,
uint64_value,
string_value,
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
string_view_value,
#endif
c_string_value,
};
union {
@ -115,9 +118,7 @@ namespace microfmt {
std::int64_t int64_value;
std::uint64_t uint64_value;
const std::string* string_value;
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
std::string_view string_view_value;
#endif
string_view string_view_value;
const char* c_string_value;
};
value_type value;
@ -135,7 +136,8 @@ namespace microfmt {
format_value(unsigned long long int_val) : uint64_value(int_val), value(value_type::uint64_value) {}
format_value(const std::string& string) : string_value(&string), value(value_type::string_value) {}
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
format_value(std::string_view sv) : string_view_value(sv), value(value_type::string_view_value) {}
format_value(std::string_view sv)
: string_view_value{sv.data(), sv.size()}, value(value_type::string_view_value) {}
#endif
format_value(const char* c_string) : c_string_value(c_string), value(value_type::c_string_value) {}
@ -148,7 +150,8 @@ namespace microfmt {
}
public:
void write(std::string& out, const format_options& options) const {
template<typename OutputIt>
void write(OutputIt out, const format_options& options) const {
switch(value) {
case value_type::char_value:
do_write(out, &char_value, &char_value + 1, options);
@ -174,11 +177,9 @@ namespace microfmt {
case value_type::string_value:
do_write(out, string_value->begin(), string_value->end(), options);
break;
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
case value_type::string_view_value:
do_write(out, string_view_value.begin(), string_view_value.end(), options);
do_write(out, string_view_value.data, string_view_value.data + string_view_value.size, options);
break;
#endif
case value_type::c_string_value:
do_write(out, c_string_value, c_string_value + std::strlen(c_string_value), options);
break;
@ -186,9 +187,10 @@ namespace microfmt {
}
};
template<std::size_t N, typename It>
std::string format(It fmt_begin, It fmt_end, std::array<format_value, N> args) {
std::string str;
// note: previously used std::array and there was a bug with std::array<T, 0> affecting old msvc
// https://godbolt.org/z/88T8hrzzq mre: https://godbolt.org/z/drd8echbP
template<typename OutputIt, typename InputIt>
void format(OutputIt out, InputIt fmt_begin, InputIt fmt_end, const std::initializer_list<format_value>& args) {
std::size_t arg_i = 0;
auto it = fmt_begin;
auto peek = [&] (std::size_t dist) -> char { // 0 on failure
@ -230,7 +232,7 @@ namespace microfmt {
return false;
}
it += 2;
options.width = arg_i < args.size() ? args[arg_i++].unwrap_int() : 0;
options.width = arg_i < args.size() ? args.begin()[arg_i++].unwrap_int() : 0;
}
// try to parse fill/base
if(it != fmt_end && *it == ':') {
@ -250,7 +252,7 @@ namespace microfmt {
return false;
}
if(arg_i < args.size()) {
args[arg_i++].write(str, options);
args.begin()[arg_i++].write(out, options);
}
return true;
};
@ -259,43 +261,40 @@ namespace microfmt {
}
it = saved_it; // go back
}
str += *it;
*out++ = *it;
}
return str;
}
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
template<typename OutputIt>
void format(OutputIt out, std::string_view fmt, const std::initializer_list<format_value>& args) {
return format(out, fmt.begin(), fmt.end(), args);
}
#endif
template<typename OutputIt>
void format(OutputIt out, const char* fmt, const std::initializer_list<format_value>& args) {
return format(out, fmt, fmt + std::strlen(fmt), args);
}
std::ostream& get_cout();
}
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
template<typename... Args>
std::string format(std::string_view fmt, Args&&... args) {
return detail::format<sizeof...(args)>(fmt.begin(), fmt.end(), {detail::format_value(args)...});
}
// working around an old msvc bug https://godbolt.org/z/88T8hrzzq mre: https://godbolt.org/z/drd8echbP
inline std::string format(std::string_view fmt) {
return detail::format<1>(fmt.begin(), fmt.end(), {detail::format_value(1)});
}
#endif
template<typename... Args>
std::string format(const char* fmt, Args&&... args) {
return detail::format<sizeof...(args)>(fmt, fmt + std::strlen(fmt), {detail::format_value(args)...});
}
inline std::string format(const char* fmt) {
return detail::format<1>(fmt, fmt + std::strlen(fmt), {detail::format_value(1)});
template<typename S, typename... Args>
std::string format(const S& fmt, Args&&... args) {
std::string str;
detail::format(std::back_inserter(str), fmt, {detail::format_value(args)...});
return str;
}
template<typename S, typename... Args>
void print(const S& fmt, Args&&... args) {
detail::get_cout()<<format(fmt, args...);
detail::format(std::ostream_iterator<char>(detail::get_cout()), fmt, {args...});
}
template<typename S, typename... Args>
void print(std::ostream& ostream, const S& fmt, Args&&... args) {
ostream<<format(fmt, args...);
detail::format(std::ostream_iterator<char>(ostream), fmt, {args...});
}
template<typename S, typename... Args>

168
src/utils/optional.hpp Normal file
View File

@ -0,0 +1,168 @@
#ifndef OPTIONAL_HPP
#define OPTIONAL_HPP
#include <functional>
#include <new>
#include <type_traits>
#include <utility>
#include "utils/common.hpp"
#include "utils/error.hpp"
namespace cpptrace {
namespace detail {
struct nullopt_t {};
static constexpr nullopt_t nullopt;
template<
typename T,
typename std::enable_if<
!std::is_same<typename std::decay<T>::type, void>::value && !std::is_rvalue_reference<T>::value,
int
>::type = 0
>
using well_behaved = typename std::conditional<
std::is_reference<T>::value, std::reference_wrapper<typename std::remove_reference<T>::type>, T
>::type;
template<
typename T,
typename std::enable_if<!std::is_same<typename std::decay<T>::type, void>::value, int>::type = 0
>
class optional {
using value_type = well_behaved<T>;
union {
char x;
value_type uvalue;
};
bool holds_value = false;
public:
optional() noexcept {}
optional(nullopt_t) noexcept {}
~optional() {
reset();
}
optional(const optional& other) : holds_value(other.holds_value) {
if(holds_value) {
new (static_cast<void*>(std::addressof(uvalue))) value_type(other.uvalue);
}
}
optional(optional&& other)
noexcept(std::is_nothrow_move_constructible<value_type>::value)
: holds_value(other.holds_value)
{
if(holds_value) {
new (static_cast<void*>(std::addressof(uvalue))) value_type(std::move(other.uvalue));
}
}
optional& operator=(const optional& other) {
optional copy(other);
swap(copy);
return *this;
}
optional& operator=(optional&& other)
noexcept(std::is_nothrow_move_assignable<value_type>::value && std::is_nothrow_move_constructible<value_type>::value)
{
reset();
if(other.holds_value) {
new (static_cast<void*>(std::addressof(uvalue))) value_type(std::move(other.uvalue));
holds_value = true;
}
return *this;
}
template<
typename U = T,
typename std::enable_if<!std::is_same<typename std::decay<U>::type, optional<T>>::value, int>::type = 0
>
optional(U&& value) : holds_value(true) {
new (static_cast<void*>(std::addressof(uvalue))) value_type(std::forward<U>(value));
}
template<
typename U = T,
typename std::enable_if<!std::is_same<typename std::decay<U>::type, optional<T>>::value, int>::type = 0
>
optional& operator=(U&& value) {
optional o(std::forward<U>(value));
swap(o);
return *this;
}
optional& operator=(nullopt_t) noexcept {
reset();
return *this;
}
void swap(optional& other) noexcept {
if(holds_value && other.holds_value) {
std::swap(uvalue, other.uvalue);
} else if(holds_value && !other.holds_value) {
new (&other.uvalue) value_type(std::move(uvalue));
uvalue.~value_type();
} else if(!holds_value && other.holds_value) {
new (static_cast<void*>(std::addressof(uvalue))) value_type(std::move(other.uvalue));
other.uvalue.~value_type();
}
std::swap(holds_value, other.holds_value);
}
bool has_value() const {
return holds_value;
}
explicit operator bool() const {
return holds_value;
}
void reset() {
if(holds_value) {
uvalue.~value_type();
}
holds_value = false;
}
NODISCARD T& unwrap() & {
ASSERT(holds_value, "Optional does not contain a value");
return uvalue;
}
NODISCARD const T& unwrap() const & {
ASSERT(holds_value, "Optional does not contain a value");
return uvalue;
}
NODISCARD T&& unwrap() && {
ASSERT(holds_value, "Optional does not contain a value");
return std::move(uvalue);
}
NODISCARD const T&& unwrap() const && {
ASSERT(holds_value, "Optional does not contain a value");
return std::move(uvalue);
}
template<typename U>
NODISCARD T value_or(U&& default_value) const & {
return holds_value ? static_cast<T>(uvalue) : static_cast<T>(std::forward<U>(default_value));
}
template<typename U>
NODISCARD T value_or(U&& default_value) && {
return holds_value ? static_cast<T>(std::move(uvalue)) : static_cast<T>(std::forward<U>(default_value));
}
};
}
}
#endif

157
src/utils/result.hpp Normal file
View File

@ -0,0 +1,157 @@
#ifndef RESULT_HPP
#define RESULT_HPP
#include <new>
#include <type_traits>
#include <utility>
#include "utils/common.hpp"
#include "utils/error.hpp"
#include "utils/optional.hpp"
#include "options.hpp"
namespace cpptrace {
namespace detail {
template<typename T, typename E, typename std::enable_if<!std::is_same<T, E>::value, int>::type = 0>
class Result {
using value_type = well_behaved<T>;
union {
value_type value_;
E error_;
};
enum class member { value, error };
member active;
public:
Result(value_type&& value) : value_(std::move(value)), active(member::value) {}
Result(E&& error) : error_(std::move(error)), active(member::error) {
if(!should_absorb_trace_exceptions()) {
std::fprintf(stderr, "%s\n", unwrap_error().what());
}
}
Result(const value_type& value) : value_(value_type(value)), active(member::value) {}
Result(const E& error) : error_(E(error)), active(member::error) {
if(!should_absorb_trace_exceptions()) {
std::fprintf(stderr, "%s\n", unwrap_error().what());
}
}
template<
typename U = T,
typename std::enable_if<
!std::is_same<typename std::decay<U>::type, Result<T, E>>::value &&
std::is_constructible<value_type, U>::value &&
!std::is_constructible<E, U>::value,
int
>::type = 0
>
Result(U&& u) : Result(value_type(std::forward<U>(u))) {}
Result(Result&& other) : active(other.active) {
if(other.active == member::value) {
new (&value_) value_type(std::move(other.value_));
} else {
new (&error_) E(std::move(other.error_));
}
}
~Result() {
if(active == member::value) {
value_.~value_type();
} else {
error_.~E();
}
}
bool has_value() const {
return active == member::value;
}
bool is_error() const {
return active == member::error;
}
explicit operator bool() const {
return has_value();
}
NODISCARD optional<T> value() const & {
return has_value() ? optional<T>(value_) : nullopt;
}
NODISCARD optional<E> error() const & {
return is_error() ? optional<E>(error_) : nullopt;
}
NODISCARD optional<T> value() && {
return has_value() ? optional<T>(std::move(value_)) : nullopt;
}
NODISCARD optional<E> error() && {
return is_error() ? optional<E>(std::move(error_)) : nullopt;
}
NODISCARD T& unwrap_value() & {
ASSERT(has_value(), "Result does not contain a value");
return value_;
}
NODISCARD const T& unwrap_value() const & {
ASSERT(has_value(), "Result does not contain a value");
return value_;
}
NODISCARD T unwrap_value() && {
ASSERT(has_value(), "Result does not contain a value");
return std::move(value_);
}
NODISCARD E& unwrap_error() & {
ASSERT(is_error(), "Result does not contain an error");
return error_;
}
NODISCARD const E& unwrap_error() const & {
ASSERT(is_error(), "Result does not contain an error");
return error_;
}
NODISCARD E unwrap_error() && {
ASSERT(is_error(), "Result does not contain an error");
return std::move(error_);
}
template<typename U>
NODISCARD T value_or(U&& default_value) const & {
return has_value() ? static_cast<T>(value_) : static_cast<T>(std::forward<U>(default_value));
}
template<typename U>
NODISCARD T value_or(U&& default_value) && {
return has_value() ? static_cast<T>(std::move(value_)) : static_cast<T>(std::forward<U>(default_value));
}
template<typename F>
NODISCARD auto transform(F&& f) & -> Result<decltype(f(std::declval<T&>())), E> {
if(has_value()) {
return f(unwrap_value());
} else {
return unwrap_error();
}
}
template<typename F>
NODISCARD auto transform(F&& f) && -> Result<decltype(f(std::declval<T&&>())), E> {
if(has_value()) {
return f(std::move(unwrap_value()));
} else {
return unwrap_error();
}
}
void drop_error() const {
if(is_error()) {
std::fprintf(stderr, "%s\n", unwrap_error().what());
}
}
};
}
}
#endif

View File

@ -2,13 +2,11 @@
#define UTILS_HPP
#include <algorithm>
#include <atomic>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <new>
#include <string>
#include <type_traits>
#include <utility>
@ -16,6 +14,8 @@
#include "utils/common.hpp"
#include "utils/error.hpp"
#include "utils/optional.hpp"
#include "utils/result.hpp"
namespace cpptrace {
namespace detail {
@ -35,7 +35,7 @@ namespace detail {
}
template<typename C>
inline std::string join(const C& container, const std::string& delim) {
std::string join(const C& container, const std::string& delim) {
auto iter = std::begin(container);
auto end = std::end(container);
std::string str;
@ -82,6 +82,10 @@ namespace detail {
return str.substr(left, right - left);
}
inline bool starts_with(const std::string& str, const std::string& prefix) {
return str.size() >= prefix.size() && str.compare(0, prefix.size(), prefix) == 0;
}
inline bool is_little_endian() {
std::uint16_t num = 0x1;
const auto* ptr = (std::uint8_t*)&num;
@ -141,265 +145,6 @@ namespace detail {
constexpr unsigned n_digits(unsigned value) noexcept {
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");
struct nullopt_t {};
static constexpr nullopt_t nullopt;
template<
typename T,
typename std::enable_if<!std::is_same<typename std::decay<T>::type, void>::value, int>::type = 0
>
class optional {
union {
char x;
T uvalue;
};
bool holds_value = false;
public:
optional() noexcept {}
optional(nullopt_t) noexcept {}
~optional() {
reset();
}
optional(const optional& other) : holds_value(other.holds_value) {
if(holds_value) {
new (static_cast<void*>(std::addressof(uvalue))) T(other.uvalue);
}
}
optional(optional&& other)
noexcept(std::is_nothrow_move_constructible<T>::value)
: holds_value(other.holds_value)
{
if(holds_value) {
new (static_cast<void*>(std::addressof(uvalue))) T(std::move(other.uvalue));
}
}
optional& operator=(const optional& other) {
optional copy(other);
swap(copy);
return *this;
}
optional& operator=(optional&& other)
noexcept(std::is_nothrow_move_assignable<T>::value && std::is_nothrow_move_constructible<T>::value)
{
reset();
if(other.holds_value) {
new (static_cast<void*>(std::addressof(uvalue))) T(std::move(other.uvalue));
holds_value = true;
}
return *this;
}
template<
typename U = T,
typename std::enable_if<!std::is_same<typename std::decay<U>::type, optional<T>>::value, int>::type = 0
>
optional(U&& value) : holds_value(true) {
new (static_cast<void*>(std::addressof(uvalue))) T(std::forward<U>(value));
}
template<
typename U = T,
typename std::enable_if<!std::is_same<typename std::decay<U>::type, optional<T>>::value, int>::type = 0
>
optional& operator=(U&& value) {
optional o(std::forward<U>(value));
swap(o);
return *this;
}
optional& operator=(nullopt_t) noexcept {
reset();
return *this;
}
void swap(optional& other) noexcept {
if(holds_value && other.holds_value) {
std::swap(uvalue, other.uvalue);
} else if(holds_value && !other.holds_value) {
new (&other.uvalue) T(std::move(uvalue));
uvalue.~T();
} else if(!holds_value && other.holds_value) {
new (static_cast<void*>(std::addressof(uvalue))) T(std::move(other.uvalue));
other.uvalue.~T();
}
std::swap(holds_value, other.holds_value);
}
bool has_value() const {
return holds_value;
}
explicit operator bool() const {
return holds_value;
}
void reset() {
if(holds_value) {
uvalue.~T();
}
holds_value = false;
}
NODISCARD T& unwrap() & {
ASSERT(holds_value, "Optional does not contain a value");
return uvalue;
}
NODISCARD const T& unwrap() const & {
ASSERT(holds_value, "Optional does not contain a value");
return uvalue;
}
NODISCARD T&& unwrap() && {
ASSERT(holds_value, "Optional does not contain a value");
return std::move(uvalue);
}
NODISCARD const T&& unwrap() const && {
ASSERT(holds_value, "Optional does not contain a value");
return std::move(uvalue);
}
template<typename U>
NODISCARD T value_or(U&& default_value) const & {
return holds_value ? uvalue : static_cast<T>(std::forward<U>(default_value));
}
template<typename U>
NODISCARD T value_or(U&& default_value) && {
return holds_value ? std::move(uvalue) : static_cast<T>(std::forward<U>(default_value));
}
};
extern std::atomic_bool absorb_trace_exceptions;
template<typename T, typename E, typename std::enable_if<!std::is_same<T, E>::value, int>::type = 0>
class Result {
union {
T value_;
E error_;
};
enum class member { value, error };
member active;
public:
Result(T&& value) : value_(std::move(value)), active(member::value) {}
Result(E&& error) : error_(std::move(error)), active(member::error) {
if(!absorb_trace_exceptions.load()) {
std::fprintf(stderr, "%s\n", unwrap_error().what());
}
}
Result(T& value) : value_(T(value)), active(member::value) {}
Result(E& error) : error_(E(error)), active(member::error) {
if(!absorb_trace_exceptions.load()) {
std::fprintf(stderr, "%s\n", unwrap_error().what());
}
}
Result(Result&& other) : active(other.active) {
if(other.active == member::value) {
new (&value_) T(std::move(other.value_));
} else {
new (&error_) E(std::move(other.error_));
}
}
~Result() {
if(active == member::value) {
value_.~T();
} else {
error_.~E();
}
}
bool has_value() const {
return active == member::value;
}
bool is_error() const {
return active == member::error;
}
explicit operator bool() const {
return has_value();
}
NODISCARD optional<T> value() const & {
return has_value() ? value_ : nullopt;
}
NODISCARD optional<E> error() const & {
return is_error() ? error_ : nullopt;
}
NODISCARD optional<T> value() && {
return has_value() ? std::move(value_) : nullopt;
}
NODISCARD optional<E> error() && {
return is_error() ? std::move(error_) : nullopt;
}
NODISCARD T& unwrap_value() & {
ASSERT(has_value(), "Result does not contain a value");
return value_;
}
NODISCARD const T& unwrap_value() const & {
ASSERT(has_value(), "Result does not contain a value");
return value_;
}
NODISCARD T unwrap_value() && {
ASSERT(has_value(), "Result does not contain a value");
return std::move(value_);
}
NODISCARD E& unwrap_error() & {
ASSERT(is_error(), "Result does not contain an error");
return error_;
}
NODISCARD const E& unwrap_error() const & {
ASSERT(is_error(), "Result does not contain an error");
return error_;
}
NODISCARD E unwrap_error() && {
ASSERT(is_error(), "Result does not contain an error");
return std::move(error_);
}
template<typename U>
NODISCARD T value_or(U&& default_value) const & {
return has_value() ? value_ : static_cast<T>(std::forward<U>(default_value));
}
template<typename U>
NODISCARD T value_or(U&& default_value) && {
return has_value() ? std::move(value_) : static_cast<T>(std::forward<U>(default_value));
}
void drop_error() const {
if(is_error()) {
std::fprintf(stderr, "%s\n", unwrap_error().what());
}
}
};
struct monostate {};
// TODO: Re-evaluate use of off_t
template<typename T, typename std::enable_if<std::is_trivial<T>::value, int>::type = 0>
@ -417,9 +162,9 @@ namespace detail {
// shamelessly stolen from stackoverflow
bool directory_exists(const std::string& path);
inline std::string basename(const std::string& path) {
inline std::string basename(const std::string& path, bool maybe_windows = false) {
// Assumes no trailing /'s
auto pos = path.rfind('/');
auto pos = path.find_last_of(maybe_windows ? "/\\" : "/");
if(pos == std::string::npos) {
return path;
} else {
@ -443,29 +188,32 @@ namespace detail {
return static_cast<U>(v);
}
template<typename T, typename U = T>
T exchange(T& obj, U&& value) {
T old = std::move(obj);
obj = std::forward<U>(value);
return old;
}
struct monostate {};
// TODO: Rework some stuff here. Not sure deleters should be optional or moved.
// Also allow file_wrapper file = std::fopen(object_path.c_str(), "rb");
template<
typename T,
typename D
// workaround for:
typename D,
// Note: Previously checked if D was invocable and returned void but this kept causing problems for MSVC
// == 19.38-specific msvc bug https://developercommunity.visualstudio.com/t/MSVC-1938331290-preview-fails-to-comp/10505565
// <= 19.23 msvc also appears to fail (but for a different reason https://godbolt.org/z/6Y5EvdWPK)
#if !defined(_MSC_VER) || !(_MSC_VER <= 1923 || _MSC_VER == 1938)
,
typename std::enable_if<
std::is_same<decltype(std::declval<D>()(std::declval<T>())), void>::value,
int
>::type = 0,
typename std::enable_if<
std::is_standard_layout<T>::value && std::is_trivial<T>::value,
int
>::type = 0,
typename std::enable_if<
std::is_nothrow_move_constructible<T>::value,
int
>::type = 0
#endif
// <= 19.39 msvc also has trouble with it for different reasons https://godbolt.org/z/aPPPT7z3z
typename std::enable_if<
std::is_standard_layout<T>::value && std::is_trivial<T>::value,
int
>::type = 0,
typename std::enable_if<
std::is_nothrow_move_constructible<T>::value,
int
>::type = 0
>
class raii_wrapper {
T obj;
@ -497,22 +245,7 @@ namespace detail {
}
};
template<
typename T,
typename D
// workaround a msvc bug https://developercommunity.visualstudio.com/t/MSVC-1938331290-preview-fails-to-comp/10505565
#if !defined(_MSC_VER) || _MSC_VER != 1938
,
typename std::enable_if<
std::is_same<decltype(std::declval<D>()(std::declval<T>())), void>::value,
int
>::type = 0,
typename std::enable_if<
std::is_standard_layout<T>::value && std::is_trivial<T>::value,
int
>::type = 0
#endif
>
template<typename T, typename D>
raii_wrapper<typename std::remove_reference<T>::type, D> raii_wrap(T obj, D deleter) {
return raii_wrapper<typename std::remove_reference<T>::type, D>(obj, deleter);
}
@ -525,6 +258,11 @@ namespace detail {
using file_wrapper = raii_wrapper<std::FILE*, void(*)(std::FILE*)>;
template<class T, class... Args>
auto make_unique(Args&&... args) -> typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::type {
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
template<typename T>
class maybe_owned {
std::unique_ptr<T> owned;
@ -535,7 +273,40 @@ namespace detail {
T* operator->() {
return ptr;
}
T& operator*() {
return *ptr;
}
};
template<typename F>
class scope_guard {
F f;
bool active;
public:
template<
typename G,
typename std::enable_if<!std::is_same<typename std::decay<G>::type, scope_guard<F>>::value, int>::type = 0
>
scope_guard(G&& f) : f(std::forward<F>(f)), active(true) {}
~scope_guard() {
if(active) {
f();
}
}
scope_guard(const scope_guard&) = delete;
scope_guard(scope_guard&& other) : f(std::move(other.f)), active(exchange(other.active, false)) {}
scope_guard& operator=(const scope_guard&) = delete;
scope_guard& operator=(scope_guard&& other) {
f = std::move(other.f);
active = exchange(other.active, false);
return *this;
}
};
template<typename F>
NODISCARD auto scope_exit(F&& f) -> scope_guard<F> {
return scope_guard<F>(std::forward<F>(f));
}
}
}

View File

@ -6,9 +6,23 @@ cc_test(
"@googletest//:gtest_main"
],
srcs = [
"unit/object_trace.cpp",
"unit/raw_trace.cpp",
"unit/stacktrace.cpp"
"unit/main.cpp",
"unit/tracing/common.hpp",
"unit/tracing/raw_trace.cpp",
"unit/tracing/object_trace.cpp",
"unit/tracing/stacktrace.cpp",
"unit/tracing/from_current.cpp",
"unit/tracing/from_current_z.cpp",
"unit/tracing/traced_exception.cpp",
"unit/internals/optional.cpp",
"unit/internals/result.cpp",
"unit/internals/string_utils.cpp",
"unit/internals/general.cpp",
"unit/lib/formatting.cpp",
"unit/lib/nullable.cpp"
],
local_defines = [
"CPPTRACE_NO_TEST_SNIPPETS"
],
linkstatic = 1,
)

View File

@ -7,12 +7,6 @@ set(
${warning_options} $<$<CXX_COMPILER_ID:GNU>:-Wno-infinite-recursion>
)
set(
debug
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-g>
$<$<CXX_COMPILER_ID:MSVC>:/DEBUG>
)
macro(add_test_dependencies exec_name)
target_compile_features(${exec_name} PRIVATE cxx_std_11)
target_link_libraries(${exec_name} PRIVATE ${target_name})
@ -25,9 +19,11 @@ macro(add_test_dependencies exec_name)
target_compile_options(${exec_name} PRIVATE -gdwarf-${CPPTRACE_BUILD_TESTING_DWARF_VERSION})
endif()
# 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(${exec_name} PRIVATE "$<$<CONFIG:Debug>:-gdwarf-4>")
if(NOT CPPTRACE_BUILD_NO_SYMBOLS)
check_cxx_compiler_flag("-gdwarf-4" HAS_DWARF4)
if(HAS_DWARF4)
target_compile_options(${exec_name} PRIVATE "$<$<CONFIG:Debug>:-gdwarf-4>")
endif()
endif()
# TODO: add debug info for mingw clang?
if(CPPTRACE_BUILD_TEST_RDYNAMIC)
@ -81,12 +77,19 @@ if(NOT CPPTRACE_SKIP_UNIT)
add_executable(
unittest
unit/main.cpp
unit/raw_trace.cpp
unit/object_trace.cpp
unit/stacktrace.cpp
unit/from_current.cpp
unit/from_current_z.cpp
unit/traced_exception.cpp
unit/tracing/raw_trace.cpp
unit/tracing/object_trace.cpp
unit/tracing/stacktrace.cpp
unit/tracing/from_current.cpp
unit/tracing/from_current_z.cpp
unit/tracing/traced_exception.cpp
unit/internals/optional.cpp
unit/internals/lru_cache.cpp
unit/internals/result.cpp
unit/internals/string_utils.cpp
unit/internals/general.cpp
unit/lib/formatting.cpp
unit/lib/nullable.cpp
)
target_compile_features(unittest PRIVATE cxx_std_20)
target_link_libraries(unittest PRIVATE ${target_name} GTest::gtest_main GTest::gmock_main)
@ -101,13 +104,9 @@ if(NOT CPPTRACE_SKIP_UNIT)
if(CPPTRACE_SANITIZER_BUILD)
target_compile_definitions(unittest PRIVATE CPPTRACE_SANITIZER_BUILD)
endif()
if(CPPTRACE_BUILD_NO_SYMBOLS)
target_compile_definitions(unittest PRIVATE CPPTRACE_BUILD_NO_SYMBOLS)
endif()
target_include_directories(unittest PRIVATE ../src)
add_test(NAME unittest COMMAND unittest)
endif()
find_package(LLVM REQUIRED CONFIG)
add_executable(jit_test jit/main.cpp)
add_test_dependencies(jit_test)
target_include_directories(jit_test PUBLIC ${LLVM_INCLUDE_DIRS})
target_compile_definitions(jit_test PUBLIC ${LLVM_DEFINITIONS})
llvm_map_components_to_libnames(llvm_libs support core orcjit native)
target_link_libraries(jit_test PUBLIC ${llvm_libs})

View File

@ -1,4 +1,5 @@
#include <cpptrace/cpptrace.hpp>
#include <cpptrace/version.hpp>
#include <algorithm>
#include <cctype>

View File

@ -1,101 +0,0 @@
//===- KaleidoscopeJIT.h - A simple JIT for Kaleidoscope --------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Contains a simple JIT definition for use in the kaleidoscope tutorials.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_EXECUTIONENGINE_ORC_KALEIDOSCOPEJIT_H
#define LLVM_EXECUTIONENGINE_ORC_KALEIDOSCOPEJIT_H
#include <llvm/ADT/StringRef.h>
#include <llvm/ExecutionEngine/Orc/CompileUtils.h>
#include <llvm/ExecutionEngine/Orc/Core.h>
#include <llvm/ExecutionEngine/Orc/ExecutionUtils.h>
#include <llvm/ExecutionEngine/Orc/ExecutorProcessControl.h>
#include <llvm/ExecutionEngine/Orc/IRCompileLayer.h>
#include <llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h>
#include <llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h>
#include <llvm/ExecutionEngine/Orc/Shared/ExecutorSymbolDef.h>
#include <llvm/ExecutionEngine/SectionMemoryManager.h>
#include <llvm/IR/DataLayout.h>
#include <llvm/IR/LLVMContext.h>
#include <memory>
namespace llvm {
namespace orc {
class KaleidoscopeJIT {
private:
std::unique_ptr<ExecutionSession> ES;
DataLayout DL;
MangleAndInterner Mangle;
RTDyldObjectLinkingLayer ObjectLayer;
IRCompileLayer CompileLayer;
JITDylib &MainJD;
public:
KaleidoscopeJIT(std::unique_ptr<ExecutionSession> ES,
JITTargetMachineBuilder JTMB, DataLayout DL)
: ES(std::move(ES)), DL(std::move(DL)), Mangle(*this->ES, this->DL),
ObjectLayer(*this->ES,
[]() { return std::make_unique<SectionMemoryManager>(); }),
CompileLayer(*this->ES, ObjectLayer,
std::make_unique<ConcurrentIRCompiler>(std::move(JTMB))),
MainJD(this->ES->createBareJITDylib("<main>")) {
MainJD.addGenerator(
cantFail(DynamicLibrarySearchGenerator::GetForCurrentProcess(
DL.getGlobalPrefix())));
}
~KaleidoscopeJIT() {
if (auto Err = ES->endSession())
ES->reportError(std::move(Err));
}
static Expected<std::unique_ptr<KaleidoscopeJIT>> Create() {
auto EPC = SelfExecutorProcessControl::Create();
if (!EPC)
return EPC.takeError();
auto ES = std::make_unique<ExecutionSession>(std::move(*EPC));
JITTargetMachineBuilder JTMB(
ES->getExecutorProcessControl().getTargetTriple());
auto DL = JTMB.getDefaultDataLayoutForTarget();
if (!DL)
return DL.takeError();
return std::make_unique<KaleidoscopeJIT>(std::move(ES), std::move(JTMB),
std::move(*DL));
}
const DataLayout &getDataLayout() const { return DL; }
JITDylib &getMainJITDylib() { return MainJD; }
Error addModule(ThreadSafeModule TSM, ResourceTrackerSP RT = nullptr) {
if (!RT)
RT = MainJD.getDefaultResourceTracker();
return CompileLayer.add(RT, std::move(TSM));
}
Expected<ExecutorSymbolDef> lookup(StringRef Name) {
return ES->lookup({&MainJD}, Mangle(Name.str()));
}
};
} // end namespace orc
} // end namespace llvm
#endif // LLVM_EXECUTIONENGINE_ORC_KALEIDOSCOPEJIT_H

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,219 @@
#include <gtest/gtest.h>
#include <gtest/gtest-matchers.h>
#include <gmock/gmock.h>
#include <gmock/gmock-matchers.h>
#include "utils/utils.hpp"
using cpptrace::detail::byteswap;
using cpptrace::detail::n_digits;
using cpptrace::detail::to;
using cpptrace::detail::raii_wrapper;
using cpptrace::detail::raii_wrap;
using cpptrace::detail::maybe_owned;
namespace {
TEST(ByteSwapTest, ByteSwapUint8) {
uint8_t input = 0x12;
uint8_t expected = 0x12;
uint8_t result = byteswap(input);
EXPECT_EQ(result, expected);
}
TEST(ByteSwapTest, ByteSwapInt8) {
int8_t input = 0x7F;
int8_t expected = 0x7F;
int8_t result = byteswap(input);
EXPECT_EQ(result, expected);
int8_t neg_input = to<int8_t>(0x80);
int8_t neg_expected = to<int8_t>(0x80);
int8_t neg_result = byteswap(neg_input);
EXPECT_EQ(neg_result, neg_expected);
}
TEST(ByteSwapTest, ByteSwapUint16)
{
uint16_t input = 0x1234;
uint16_t expected = 0x3412;
uint16_t result = byteswap(input);
EXPECT_EQ(result, expected);
EXPECT_EQ(byteswap(to<uint16_t>(0x0000)), to<uint16_t>(0x0000));
EXPECT_EQ(byteswap(to<uint16_t>(0xFFFF)), to<uint16_t>(0xFFFF));
}
TEST(ByteSwapTest, ByteSwapInt16) {
int16_t input = 0x1234;
int16_t expected = to<int16_t>(0x3412);
int16_t result = byteswap(input);
EXPECT_EQ(result, expected);
int16_t neg_input = to<uint16_t>(0xFEFF);
int16_t neg_expected = to<int16_t>(0xFFFE);
int16_t neg_result = byteswap(neg_input);
EXPECT_EQ(neg_result, neg_expected);
}
TEST(ByteSwapTest, ByteSwapUint32)
{
uint32_t input = 0x12345678;
uint32_t expected = 0x78563412;
uint32_t result = byteswap(input);
EXPECT_EQ(result, expected);
EXPECT_EQ(byteswap(to<uint32_t>(0x00000000)), to<uint32_t>(0x00000000));
EXPECT_EQ(byteswap(to<uint32_t>(0xFFFFFFFF)), to<uint32_t>(0xFFFFFFFF));
}
TEST(ByteSwapTest, ByteSwapInt32)
{
int32_t input = 0x12345678;
int32_t expected = to<int32_t>(0x78563412);
int32_t result = byteswap(input);
EXPECT_EQ(result, expected);
int32_t neg_input = to<uint32_t>(0xFF000000);
int32_t neg_expected = to<int32_t>(0x000000FF);
int32_t neg_result = byteswap(neg_input);
EXPECT_EQ(neg_result, neg_expected);
}
TEST(ByteSwapTest, ByteSwapUint64)
{
uint64_t input = 0x1122334455667788ULL;
uint64_t expected = 0x8877665544332211ULL;
uint64_t result = byteswap(input);
EXPECT_EQ(result, expected);
EXPECT_EQ(byteswap(to<uint64_t>(0x0000000000000000ULL)), to<uint64_t>(0x0000000000000000ULL));
EXPECT_EQ(byteswap(to<uint64_t>(0xFFFFFFFFFFFFFFFFULL)), to<uint64_t>(0xFFFFFFFFFFFFFFFFULL));
}
TEST(ByteSwapTest, ByteSwapInt64)
{
int64_t input = 0x1122334455667788LL;
int64_t expected = to<int64_t>(0x8877665544332211ULL);
int64_t result = byteswap(input);
EXPECT_EQ(result, expected);
int64_t neg_input = to<uint64_t>(0xFF00000000000000ULL);
int64_t neg_expected = to<int64_t>(0x00000000000000FFULL);
int64_t neg_result = byteswap(neg_input);
EXPECT_EQ(neg_result, neg_expected);
}
TEST(NDigitsTest, Basic)
{
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");
EXPECT_EQ(n_digits(1), 1);
EXPECT_EQ(n_digits(9), 1);
EXPECT_EQ(n_digits(10), 2);
EXPECT_EQ(n_digits(11), 2);
EXPECT_EQ(n_digits(1024), 4);
}
struct test_deleter {
static int call_count;
static int last_value;
void operator()(int value) {
call_count++;
last_value = value;
}
};
int test_deleter::call_count = 0;
int test_deleter::last_value = 0;
class RaiiWrapperTest : public ::testing::Test {
protected:
RaiiWrapperTest() {
test_deleter::call_count = 0;
test_deleter::last_value = 0;
}
};
TEST_F(RaiiWrapperTest, construct_and_destroy_calls_deleter) {
{
auto w = raii_wrap(42, test_deleter{});
EXPECT_EQ(test_deleter::call_count, 0);
}
EXPECT_EQ(test_deleter::call_count, 1);
EXPECT_EQ(test_deleter::last_value, 42);
}
TEST_F(RaiiWrapperTest, move_constructor_transfers_ownership) {
{
auto w1 = raii_wrap(123, test_deleter{});
auto w2 = std::move(w1);
EXPECT_EQ(test_deleter::call_count, 0);
EXPECT_EQ(static_cast<int>(w2), 123);
}
EXPECT_EQ(test_deleter::call_count, 1);
EXPECT_EQ(test_deleter::last_value, 123);
}
TEST_F(RaiiWrapperTest, operator_t_and_get) {
{
auto w = raii_wrap(999, test_deleter{});
int i = w;
EXPECT_EQ(i, 999);
w.get() = 1000;
EXPECT_EQ(static_cast<int>(w), 1000);
}
EXPECT_EQ(test_deleter::call_count, 1);
EXPECT_EQ(test_deleter::last_value, 1000);
}
class counting_helper {
public:
static int active;
int value;
counting_helper(int value) : value(value) {
++active;
}
~counting_helper() {
--active;
}
counting_helper(const counting_helper&) = delete;
counting_helper(counting_helper&&) = delete;
counting_helper& operator=(const counting_helper&) = delete;
counting_helper& operator=(counting_helper&&) = delete;
int foo() const {
return value;
}
};
int counting_helper::active = 0;
TEST(MaybeOwnedTest, NonOwningPointer) {
ASSERT_EQ(counting_helper::active, 0);
auto instance = std::make_unique<counting_helper>(42);
EXPECT_EQ(counting_helper::active, 1);
{
maybe_owned<counting_helper> non_owning(instance.get());
EXPECT_EQ(counting_helper::active, 1);
EXPECT_EQ(non_owning->foo(), 42);
}
EXPECT_EQ(counting_helper::active, 1);
instance.reset();
EXPECT_EQ(counting_helper::active, 0);
}
TEST(MaybeOwnedTest, OwningPointer) {
ASSERT_EQ(counting_helper::active, 0);
auto instance = std::make_unique<counting_helper>(42);
EXPECT_EQ(counting_helper::active, 1);
{
maybe_owned<counting_helper> non_owning(std::move(instance));
EXPECT_EQ(counting_helper::active, 1);
EXPECT_EQ(non_owning->foo(), 42);
}
EXPECT_EQ(counting_helper::active, 0);
instance.reset();
EXPECT_EQ(counting_helper::active, 0);
}
}

View File

@ -0,0 +1,104 @@
#include <gtest/gtest.h>
#include "utils/lru_cache.hpp"
using cpptrace::detail::lru_cache;
using cpptrace::detail::nullopt;
namespace {
TEST(LruCacheTest, DefaultConstructor) {
lru_cache<int, int> cache;
EXPECT_EQ(cache.size(), 0);
}
TEST(LruCacheTest, MaybeGet) {
lru_cache<int, int> cache;
auto result = cache.maybe_get(42);
EXPECT_FALSE(result.has_value());
}
TEST(LruCacheTest, InsertAndGet) {
lru_cache<int, int> cache;
cache.insert(42, 50);
auto result = cache.maybe_get(42);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result.unwrap(), 50);
}
TEST(LruCacheTest, ConstGet) {
lru_cache<int, int> cache;
cache.insert(42, 50);
const lru_cache<int, int>& cache_ref = cache;
auto result = cache_ref.maybe_get(42);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result.unwrap(), 50);
}
TEST(LruCacheTest, Set) {
lru_cache<int, int> cache;
cache.set(42, 50);
auto result = cache.maybe_get(42);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result.unwrap(), 50);
cache.set(42, 60);
auto result2 = cache.maybe_get(42);
ASSERT_TRUE(result2.has_value());
EXPECT_EQ(result2.unwrap(), 60);
}
TEST(LruCacheTest, NoMaxSize) {
lru_cache<int, int> cache;
for(int i = 0; i < 1000; i++) {
cache.insert(i, i + 50);
}
EXPECT_EQ(cache.size(), 1000);
for(int i = 0; i < 1000; i++) {
EXPECT_EQ(cache.maybe_get(i).unwrap(), i + 50);
}
}
TEST(LruCacheTest, MaxSize) {
lru_cache<int, int> cache(20);
for(int i = 0; i < 1000; i++) {
cache.insert(i, i + 50);
}
EXPECT_EQ(cache.size(), 20);
for(int i = 0; i < 1000 - 20; i++) {
EXPECT_FALSE(cache.maybe_get(i).has_value());
}
for(int i = 1000 - 20; i < 1000; i++) {
EXPECT_EQ(cache.maybe_get(i).unwrap(), i + 50);
}
}
TEST(LruCacheTest, SizeAfterInserts) {
lru_cache<int, int> cache;
for(int i = 0; i < 1000; i++) {
cache.insert(i, i + 50);
}
EXPECT_EQ(cache.size(), 1000);
cache.set_max_size(20);
EXPECT_EQ(cache.size(), 20);
for(int i = 0; i < 1000 - 20; i++) {
EXPECT_FALSE(cache.maybe_get(i).has_value());
}
for(int i = 1000 - 20; i < 1000; i++) {
EXPECT_EQ(cache.maybe_get(i).unwrap(), i + 50);
}
}
TEST(LruCacheTest, Touch) {
lru_cache<int, int> cache(20);
for(int i = 0; i < 1000; i++) {
cache.maybe_touch(0);
cache.insert(i, i + 50);
}
EXPECT_EQ(cache.size(), 20);
for(int i = 1000 - 19; i < 1000; i++) {
EXPECT_EQ(cache.maybe_get(i).unwrap(), i + 50);
}
EXPECT_EQ(cache.maybe_get(0).unwrap(), 50);
}
}

View File

@ -0,0 +1,219 @@
#include <gtest/gtest.h>
#include "utils/optional.hpp"
using cpptrace::detail::optional;
using cpptrace::detail::nullopt;
namespace {
TEST(OptionalTest, DefaultConstructor) {
optional<int> o;
EXPECT_FALSE(o.has_value());
EXPECT_FALSE(static_cast<bool>(o));
optional<int&> o1;
EXPECT_FALSE(o1.has_value());
EXPECT_FALSE(static_cast<bool>(o1));
}
TEST(OptionalTest, ConstructWithNullopt) {
optional<int> o(nullopt);
EXPECT_FALSE(o.has_value());
optional<int&> o1(nullopt);
EXPECT_FALSE(o1.has_value());
EXPECT_FALSE(static_cast<bool>(o1));
}
TEST(OptionalTest, ValueConstructor) {
optional<int> o(42);
EXPECT_TRUE(o.has_value());
EXPECT_EQ(o.unwrap(), 42);
int x = 100;
optional<int> o2(x);
EXPECT_TRUE(o2.has_value());
EXPECT_EQ(o2.unwrap(), 100);
int y = 100;
optional<int&> o3(y);
EXPECT_TRUE(o3.has_value());
EXPECT_EQ(o3.unwrap(), 100);
y = 200;
EXPECT_EQ(o3.unwrap(), 200);
}
TEST(OptionalTest, CopyConstructor) {
optional<int> o1(42);
optional<int> o2(o1);
EXPECT_TRUE(o2.has_value());
EXPECT_EQ(o2.unwrap(), 42);
optional<int> o3(nullopt);
optional<int> o4(o3);
EXPECT_FALSE(o4.has_value());
int y = 100;
optional<int&> o5(y);
optional<int&> o6(o5);
EXPECT_TRUE(o5.has_value());
EXPECT_EQ(o5.unwrap(), 100);
EXPECT_TRUE(o6.has_value());
EXPECT_EQ(o6.unwrap(), 100);
y = 200;
EXPECT_EQ(o5.unwrap(), 200);
EXPECT_EQ(o6.unwrap(), 200);
}
TEST(OptionalTest, MoveConstructor) {
optional<int> o1(42);
optional<int> o2(std::move(o1));
EXPECT_TRUE(o2.has_value());
EXPECT_EQ(o2.unwrap(), 42);
optional<int> o3(nullopt);
optional<int> o4(std::move(o3));
EXPECT_FALSE(o4.has_value());
int y = 100;
optional<int&> o5(y);
optional<int&> o6(std::move(o5));
EXPECT_TRUE(o6.has_value());
EXPECT_EQ(o6.unwrap(), 100);
y = 200;
EXPECT_EQ(o6.unwrap(), 200);
}
TEST(OptionalTest, CopyAssignmentOperator) {
optional<int> o1(42);
optional<int> o2;
o2 = o1;
EXPECT_TRUE(o2.has_value());
EXPECT_EQ(o2.unwrap(), 42);
optional<int> o3(nullopt);
optional<int> o4(100);
o4 = o3;
EXPECT_FALSE(o4.has_value());
int y = 100;
optional<int&> o5(y);
optional<int&> o6;
o6 = o5;
EXPECT_TRUE(o5.has_value());
EXPECT_EQ(o5.unwrap(), 100);
EXPECT_TRUE(o6.has_value());
EXPECT_EQ(o6.unwrap(), 100);
y = 200;
EXPECT_EQ(o5.unwrap(), 200);
EXPECT_EQ(o6.unwrap(), 200);
}
TEST(OptionalTest, MoveAssignmentOperator) {
optional<int> o1(42);
optional<int> o2;
o2 = std::move(o1);
EXPECT_TRUE(o2.has_value());
EXPECT_EQ(o2.unwrap(), 42);
optional<int> o3(nullopt);
optional<int> o4(99);
o4 = std::move(o3);
EXPECT_FALSE(o4.has_value());
int y = 100;
optional<int&> o5(y);
optional<int&> o6;
o6 = std::move(o5);
EXPECT_TRUE(o6.has_value());
EXPECT_EQ(o6.unwrap(), 100);
y = 200;
EXPECT_EQ(o6.unwrap(), 200);
}
TEST(OptionalTest, AssignmentFromValue) {
optional<int> o;
o = 123;
EXPECT_TRUE(o.has_value());
EXPECT_EQ(o.unwrap(), 123);
o = nullopt;
EXPECT_FALSE(o.has_value());
optional<int&> o1;
int x = 100;
o1 = x;
EXPECT_TRUE(o1.has_value());
EXPECT_EQ(o1.unwrap(), x);
EXPECT_EQ(&o1.unwrap(), &x);
o1 = nullopt;
EXPECT_FALSE(o1.has_value());
}
TEST(OptionalTest, Reset) {
optional<int> o(42);
EXPECT_TRUE(o.has_value());
EXPECT_EQ(o.unwrap(), 42);
o.reset();
EXPECT_FALSE(o.has_value());
int x = 44;
optional<int&> o1(x);
EXPECT_TRUE(o1.has_value());
EXPECT_EQ(o1.unwrap(), 44);
o1.reset();
EXPECT_FALSE(o1.has_value());
}
TEST(OptionalTest, Swap) {
optional<int> o1(42);
optional<int> o2(100);
o1.swap(o2);
EXPECT_TRUE(o1.has_value());
EXPECT_TRUE(o2.has_value());
EXPECT_EQ(o1.unwrap(), 100);
EXPECT_EQ(o2.unwrap(), 42);
// Swap a value-holding optional with an empty optional
optional<int> o3(7);
optional<int> o4(nullopt);
o3.swap(o4);
EXPECT_FALSE(o3.has_value());
EXPECT_TRUE(o4.has_value());
EXPECT_EQ(o4.unwrap(), 7);
int x = 20;
int y = 40;
optional<int&> o5 = x;
optional<int&> o6 = y;
EXPECT_EQ(o5.unwrap(), 20);
EXPECT_EQ(o6.unwrap(), 40);
o5.swap(o6);
EXPECT_EQ(o5.unwrap(), 40);
EXPECT_EQ(o6.unwrap(), 20);
EXPECT_EQ(x, 20);
EXPECT_EQ(y, 40);
}
TEST(OptionalTest, ValueOr) {
optional<int> o1(42);
EXPECT_EQ(o1.value_or(100), 42);
optional<int> o2(nullopt);
EXPECT_EQ(o2.value_or(100), 100);
int x = 20;
int y = 100;
optional<int&> o3(x);
EXPECT_EQ(o3.value_or(y), 20);
o3.reset();
EXPECT_EQ(o3.value_or(y), 100);
EXPECT_EQ(&o3.value_or(y), &y);
EXPECT_EQ(x, 20);
EXPECT_EQ(y, 100);
}
}

View File

@ -0,0 +1,129 @@
#include <gtest/gtest.h>
#include "utils/result.hpp"
using cpptrace::detail::Result;
namespace {
// A simple custom error type that behaves like a standard exception.
struct error {
int x;
const char* what() const {
return "error...";
}
};
class ResultFixture : public testing::Test {
public:
ResultFixture() {
cpptrace::absorb_trace_exceptions(true);
}
~ResultFixture() override {
cpptrace::absorb_trace_exceptions(false);
}
};
TEST_F(ResultFixture, ConstructWithValueRValue) {
cpptrace::detail::Result<std::string, error> result("test");
EXPECT_TRUE(result.has_value());
EXPECT_FALSE(result.is_error());
EXPECT_TRUE(static_cast<bool>(result));
EXPECT_EQ(result.unwrap_value(), "test");
EXPECT_FALSE(result.error().has_value());
}
TEST_F(ResultFixture, ConstructWithValueLValue) {
std::string s = "test";
cpptrace::detail::Result<std::string, error> result(s);
EXPECT_TRUE(result.has_value());
EXPECT_FALSE(result.is_error());
EXPECT_EQ(result.unwrap_value(), "test");
s = "x";
EXPECT_EQ(result.unwrap_value(), "test");
cpptrace::detail::Result<std::string&, error> r2(s);
EXPECT_EQ(r2.unwrap_value(), "x");
s = "y";
EXPECT_EQ(r2.unwrap_value(), "y");
}
TEST_F(ResultFixture, ConstructWithErrorRValue) {
cpptrace::detail::Result<std::string, error> result(error{1});
EXPECT_FALSE(result.has_value());
EXPECT_TRUE(result.is_error());
EXPECT_FALSE(static_cast<bool>(result));
EXPECT_EQ(result.unwrap_error().x, 1);
// Check that value() returns nullopt in this scenario
EXPECT_FALSE(result.value().has_value());
}
TEST_F(ResultFixture, ConstructWithErrorLValue) {
error e{1};
cpptrace::detail::Result<std::string, error> result(e);
EXPECT_FALSE(result.has_value());
EXPECT_TRUE(result.is_error());
EXPECT_EQ(result.unwrap_error().x, 1);
}
TEST_F(ResultFixture, MoveConstructorValue) {
cpptrace::detail::Result<std::string, error> original(std::string("move"));
cpptrace::detail::Result<std::string, error> moved(std::move(original));
EXPECT_TRUE(moved.has_value());
EXPECT_EQ(moved.unwrap_value(), "move");
EXPECT_TRUE(original.has_value());
std::string s = "test";
cpptrace::detail::Result<std::string&, error> r1(s);
cpptrace::detail::Result<std::string&, error> r2(std::move(r1));
EXPECT_TRUE(r2.has_value());
EXPECT_EQ(r2.unwrap_value(), "test");
s = "foo";
EXPECT_EQ(r2.unwrap_value(), "foo");
EXPECT_TRUE(r2.has_value());
}
TEST_F(ResultFixture, MoveConstructorError) {
cpptrace::detail::Result<std::string, error> original(error{1});
cpptrace::detail::Result<std::string, error> moved(std::move(original));
EXPECT_TRUE(moved.is_error());
EXPECT_EQ(moved.unwrap_error().x, 1);
EXPECT_TRUE(original.is_error());
}
TEST_F(ResultFixture, ValueOr) {
{
cpptrace::detail::Result<int, error> res_with_value(42);
EXPECT_EQ(res_with_value.value_or(-1), 42);
EXPECT_EQ(std::move(res_with_value).value_or(-1), 42);
}
{
cpptrace::detail::Result<int, error> res_with_error(error{});
EXPECT_EQ(res_with_error.value_or(-1), -1);
EXPECT_EQ(std::move(res_with_error).value_or(-1), -1);
}
{
int x = 2;
int y = 3;
cpptrace::detail::Result<int&, error> res_with_value(x);
EXPECT_EQ(res_with_value.value_or(y), 2);
EXPECT_EQ(std::move(res_with_value).value_or(y), 2);
}
{
int x = 2;
cpptrace::detail::Result<int&, error> res_with_error(error{});
EXPECT_EQ(res_with_error.value_or(x), 2);
EXPECT_EQ(&res_with_error.value_or(x), &x);
EXPECT_EQ(std::move(res_with_error).value_or(x), 2);
}
}
}

View File

@ -0,0 +1,92 @@
#include <gtest/gtest.h>
#include <gtest/gtest-matchers.h>
#include <gmock/gmock.h>
#include <gmock/gmock-matchers.h>
#include "utils/utils.hpp"
using testing::ElementsAre;
using cpptrace::detail::split;
using cpptrace::detail::join;
using cpptrace::detail::trim;
using cpptrace::detail::starts_with;
namespace {
TEST(SplitTest, SplitBySingleDelimiter) {
std::string input = "hello,world";
auto tokens = split(input, ",");
EXPECT_THAT(tokens, ElementsAre("hello", "world"));
}
TEST(SplitTest, SplitByMultipleDelimiters) {
std::string input = "hello,world;test";
auto tokens = split(input, ",;");
EXPECT_THAT(tokens, ElementsAre("hello", "world", "test"));
}
TEST(SplitTest, HandlesNoDelimiterFound) {
std::string input = "nodellimitershere";
auto tokens = split(input, ", ");
EXPECT_THAT(tokens, ElementsAre("nodellimitershere"));
}
TEST(SplitTest, HandlesEmptyString) {
std::string input = "";
auto tokens = split(input, ",");
EXPECT_THAT(tokens, ElementsAre(""));
}
TEST(SplitTest, HandlesConsecutiveDelimiters) {
std::string input = "one,,two,,,three";
auto tokens = split(input, ",");
EXPECT_THAT(tokens, ElementsAre("one", "", "two", "", "", "three"));
}
TEST(SplitTest, HandlesTrailingDelimiter) {
std::string input = "abc,";
auto tokens = split(input, ",");
EXPECT_THAT(tokens, ElementsAre("abc", ""));
}
TEST(SplitTest, HandlesLeadingDelimiter) {
std::string input = ",abc";
auto tokens = split(input, ",");
EXPECT_THAT(tokens, ElementsAre("", "abc"));
}
TEST(JoinTest, EmptyContainer) {
std::vector<std::string> vec;
EXPECT_EQ(join(vec, ","), "");
}
TEST(JoinTest, SingleElements) {
std::vector<std::string> vec = {"one"};
EXPECT_EQ(join(vec, ","), "one");
}
TEST(JoinTest, MultipleElements) {
std::vector<std::string> vec = {"one", "two", "three"};
EXPECT_EQ(join(vec, ","), "one,two,three");
}
TEST(TrimTest, Basic) {
EXPECT_EQ(trim(""), "");
EXPECT_EQ(trim("test"), "test");
EXPECT_EQ(trim(" test "), "test");
EXPECT_EQ(trim(" test\n "), "test");
EXPECT_EQ(trim("\t test\n "), "test");
}
TEST(StartsWith, Basic) {
EXPECT_TRUE(starts_with("", ""));
EXPECT_TRUE(starts_with("abc", ""));
EXPECT_FALSE(starts_with("", "abc"));
EXPECT_FALSE(starts_with("ab", "abc"));
EXPECT_TRUE(starts_with("test", "test"));
EXPECT_TRUE(starts_with("hello_world", "hello"));
EXPECT_FALSE(starts_with("hello_world", "world"));
EXPECT_FALSE(starts_with("abcd", "abce"));
}
}

View File

@ -0,0 +1,318 @@
#include <cpptrace/formatting.hpp>
#include <gtest/gtest.h>
#include <gtest/gtest-matchers.h>
#include <gmock/gmock.h>
#include <gmock/gmock-matchers.h>
#include "utils/microfmt.hpp"
#include "utils/utils.hpp"
using cpptrace::detail::split;
using testing::ElementsAre;
namespace {
cpptrace::stacktrace make_test_stacktrace() {
cpptrace::stacktrace trace;
trace.frames.push_back({0x1, 0x1001, {20}, {30}, "foo.cpp", "foo()", false});
trace.frames.push_back({0x2, 0x1002, {30}, {40}, "bar.cpp", "bar()", false});
trace.frames.push_back({0x3, 0x1003, {40}, {25}, "foo.cpp", "main", false});
return trace;
}
TEST(FormatterTest, Basic) {
auto res = split(cpptrace::get_default_formatter().format(make_test_stacktrace()), "\n");
EXPECT_THAT(
res,
ElementsAre(
"Stack trace (most recent call first):",
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
"#1 0x0000000000000002 in bar() at bar.cpp:30:40",
"#2 0x0000000000000003 in main at foo.cpp:40:25"
)
);
}
TEST(FormatterTest, Inlines) {
auto trace = make_test_stacktrace();
trace.frames[1].is_inline = true;
trace.frames[1].raw_address = 0;
trace.frames[1].object_address = 0;
auto res = split(cpptrace::get_default_formatter().format(trace), "\n");
EXPECT_THAT(
res,
ElementsAre(
"Stack trace (most recent call first):",
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
"#1 (inlined) in bar() at bar.cpp:30:40",
"#2 0x0000000000000003 in main at foo.cpp:40:25"
)
);
}
TEST(FormatterTest, Header) {
auto formatter = cpptrace::formatter{}
.header("Stack trace:");
auto res = split(formatter.format(make_test_stacktrace()), "\n");
EXPECT_THAT(
res,
ElementsAre(
"Stack trace:",
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
"#1 0x0000000000000002 in bar() at bar.cpp:30:40",
"#2 0x0000000000000003 in main at foo.cpp:40:25"
)
);
}
TEST(FormatterTest, NoColumn) {
auto formatter = cpptrace::formatter{}
.columns(false);
auto res = split(formatter.format(make_test_stacktrace()), "\n");
EXPECT_THAT(
res,
ElementsAre(
"Stack trace (most recent call first):",
"#0 0x0000000000000001 in foo() at foo.cpp:20",
"#1 0x0000000000000002 in bar() at bar.cpp:30",
"#2 0x0000000000000003 in main at foo.cpp:40"
)
);
}
TEST(FormatterTest, ObjectAddresses) {
auto formatter = cpptrace::formatter{}
.addresses(cpptrace::formatter::address_mode::object);
auto res = split(formatter.format(make_test_stacktrace()), "\n");
EXPECT_THAT(
res,
ElementsAre(
"Stack trace (most recent call first):",
"#0 0x0000000000001001 in foo() at foo.cpp:20:30",
"#1 0x0000000000001002 in bar() at bar.cpp:30:40",
"#2 0x0000000000001003 in main at foo.cpp:40:25"
)
);
}
TEST(FormatterTest, NoAddresses) {
auto formatter = cpptrace::formatter{}
.addresses(cpptrace::formatter::address_mode::none);
auto res = split(formatter.format(make_test_stacktrace()), "\n");
EXPECT_THAT(
res,
ElementsAre(
"Stack trace (most recent call first):",
"#0 in foo() at foo.cpp:20:30",
"#1 in bar() at bar.cpp:30:40",
"#2 in main at foo.cpp:40:25"
)
);
}
TEST(FormatterTest, PathShortening) {
cpptrace::stacktrace trace;
trace.frames.push_back({0x1, 0x1001, {20}, {30}, "/home/foo/foo.cpp", "foo()", false});
trace.frames.push_back({0x2, 0x1002, {30}, {40}, "/bar.cpp", "bar()", false});
trace.frames.push_back({0x3, 0x1003, {40}, {25}, "baz/foo.cpp", "main", false});
trace.frames.push_back({0x3, 0x1003, {50}, {25}, "C:\\foo\\bar\\baz.cpp", "main", false});
auto formatter = cpptrace::formatter{}
.paths(cpptrace::formatter::path_mode::basename);
auto res = split(formatter.format(trace), "\n");
EXPECT_THAT(
res,
ElementsAre(
"Stack trace (most recent call first):",
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
"#1 0x0000000000000002 in bar() at bar.cpp:30:40",
"#2 0x0000000000000003 in main at foo.cpp:40:25",
"#3 0x0000000000000003 in main at baz.cpp:50:25"
)
);
}
#ifndef CPPTRACE_NO_TEST_SNIPPETS
TEST(FormatterTest, Snippets) {
cpptrace::stacktrace trace;
unsigned line = __LINE__ + 1;
trace.frames.push_back({0x1, 0x1001, {line}, {20}, __FILE__, "foo()", false});
trace.frames.push_back({0x2, 0x1002, {line + 1}, {20}, __FILE__, "foo()", false});
auto formatter = cpptrace::formatter{}
.snippets(true);
auto res = split(formatter.format(trace), "\n");
EXPECT_THAT(
res,
ElementsAre(
"Stack trace (most recent call first):",
// frame 1
cpptrace::microfmt::format("#0 0x0000000000000001 in foo() at {}:{}:20", __FILE__, line),
cpptrace::microfmt::format(" {}: cpptrace::stacktrace trace;", line - 2),
cpptrace::microfmt::format(" {}: unsigned line = __LINE__ + 1;", line - 1),
cpptrace::microfmt::format(
" {}: trace.frames.push_back({0x1, 0x1001, {line}, {{20}}, __FILE__, \"foo()\", false});",
line
),
cpptrace::microfmt::format(
" {}: trace.frames.push_back({0x2, 0x1002, {line + 1}, {{20}}, __FILE__, \"foo()\", false});",
line + 1
),
cpptrace::microfmt::format(" {}: auto formatter = cpptrace::formatter{{}}", line + 2),
// frame 2
cpptrace::microfmt::format("#1 0x0000000000000002 in foo() at {}:{}:20", __FILE__, line + 1),
cpptrace::microfmt::format(" {}: unsigned line = __LINE__ + 1;", line - 1),
cpptrace::microfmt::format(
" {}: trace.frames.push_back({0x1, 0x1001, {line}, {{20}}, __FILE__, \"foo()\", false});",
line
),
cpptrace::microfmt::format(
" {}: trace.frames.push_back({0x2, 0x1002, {line + 1}, {{20}}, __FILE__, \"foo()\", false});",
line + 1
),
cpptrace::microfmt::format(" {}: auto formatter = cpptrace::formatter{{}}", line + 2),
cpptrace::microfmt::format(" {}: .snippets(true);", line + 3)
)
);
formatter.snippet_context(1);
res = split(formatter.format(trace), "\n");
EXPECT_THAT(
res,
ElementsAre(
"Stack trace (most recent call first):",
// frame 1
cpptrace::microfmt::format("#0 0x0000000000000001 in foo() at {}:{}:20", __FILE__, line),
cpptrace::microfmt::format(" {}: unsigned line = __LINE__ + 1;", line - 1),
cpptrace::microfmt::format(
" {}: trace.frames.push_back({0x1, 0x1001, {line}, {{20}}, __FILE__, \"foo()\", false});",
line
),
cpptrace::microfmt::format(
" {}: trace.frames.push_back({0x2, 0x1002, {line + 1}, {{20}}, __FILE__, \"foo()\", false});",
line + 1
),
// frame 2
cpptrace::microfmt::format("#1 0x0000000000000002 in foo() at {}:{}:20", __FILE__, line + 1),
cpptrace::microfmt::format(
" {}: trace.frames.push_back({0x1, 0x1001, {line}, {{20}}, __FILE__, \"foo()\", false});",
line
),
cpptrace::microfmt::format(
" {}: trace.frames.push_back({0x2, 0x1002, {line + 1}, {{20}}, __FILE__, \"foo()\", false});",
line + 1
),
cpptrace::microfmt::format(" {}: auto formatter = cpptrace::formatter{{}}", line + 2)
)
);
}
#endif
TEST(FormatterTest, Colors) {
auto formatter = cpptrace::formatter{}
.colors(cpptrace::formatter::color_mode::always);
auto res = split(formatter.format(make_test_stacktrace()), "\n");
EXPECT_THAT(
res,
ElementsAre(
"Stack trace (most recent call first):",
"#0 \x1B[34m0x0000000000000001\x1B[0m in \x1B[33mfoo()\x1B[0m at \x1B[32mfoo.cpp\x1B[0m:\x1B[34m20\x1B[0m:\x1B[34m30\x1B[0m",
"#1 \x1B[34m0x0000000000000002\x1B[0m in \x1B[33mbar()\x1B[0m at \x1B[32mbar.cpp\x1B[0m:\x1B[34m30\x1B[0m:\x1B[34m40\x1B[0m",
"#2 \x1B[34m0x0000000000000003\x1B[0m in \x1B[33mmain\x1B[0m at \x1B[32mfoo.cpp\x1B[0m:\x1B[34m40\x1B[0m:\x1B[34m25\x1B[0m"
)
);
}
TEST(FormatterTest, Filtering) {
auto formatter = cpptrace::formatter{}
.filter([] (const cpptrace::stacktrace_frame& frame) -> bool {
return frame.filename.find("foo.cpp") != std::string::npos;
});
auto res = split(formatter.format(make_test_stacktrace()), "\n");
EXPECT_THAT(
res,
ElementsAre(
"Stack trace (most recent call first):",
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
"#1 (filtered)",
"#2 0x0000000000000003 in main at foo.cpp:40:25"
)
);
}
TEST(FormatterTest, DontShowFilteredFrames) {
auto formatter = cpptrace::formatter{}
.filter([] (const cpptrace::stacktrace_frame& frame) -> bool {
return frame.filename.find("foo.cpp") != std::string::npos;
})
.filtered_frame_placeholders(false);
auto res = split(formatter.format(make_test_stacktrace()), "\n");
EXPECT_THAT(
res,
ElementsAre(
"Stack trace (most recent call first):",
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
"#2 0x0000000000000003 in main at foo.cpp:40:25"
)
);
}
TEST(FormatterTest, MoveSemantics) {
auto formatter = cpptrace::formatter{}
.filter([] (const cpptrace::stacktrace_frame& frame) -> bool {
return frame.filename.find("foo.cpp") != std::string::npos;
});
auto formatter2 = std::move(formatter);
auto res = split(formatter2.format(make_test_stacktrace()), "\n");
EXPECT_THAT(
res,
ElementsAre(
"Stack trace (most recent call first):",
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
"#1 (filtered)",
"#2 0x0000000000000003 in main at foo.cpp:40:25"
)
);
cpptrace::formatter formatter3;
formatter3 = std::move(formatter);
auto res2 = split(formatter2.format(make_test_stacktrace()), "\n");
EXPECT_THAT(
res2,
ElementsAre(
"Stack trace (most recent call first):",
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
"#1 (filtered)",
"#2 0x0000000000000003 in main at foo.cpp:40:25"
)
);
}
TEST(FormatterTest, CopySemantics) {
auto formatter = cpptrace::formatter{}
.filter([] (const cpptrace::stacktrace_frame& frame) -> bool {
return frame.filename.find("foo.cpp") != std::string::npos;
});
auto formatter2 = formatter;
auto res = split(formatter2.format(make_test_stacktrace()), "\n");
EXPECT_THAT(
res,
ElementsAre(
"Stack trace (most recent call first):",
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
"#1 (filtered)",
"#2 0x0000000000000003 in main at foo.cpp:40:25"
)
);
cpptrace::formatter formatter3;
formatter3 = formatter;
auto res2 = split(formatter2.format(make_test_stacktrace()), "\n");
EXPECT_THAT(
res2,
ElementsAre(
"Stack trace (most recent call first):",
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
"#1 (filtered)",
"#2 0x0000000000000003 in main at foo.cpp:40:25"
)
);
}
}

View File

@ -0,0 +1,66 @@
#include <cpptrace/basic.hpp>
#include <gtest/gtest.h>
#include <gtest/gtest-matchers.h>
#include <gmock/gmock.h>
#include <gmock/gmock-matchers.h>
using cpptrace::nullable;
namespace {
TEST(NullableTest, Basic) {
nullable<std::uint32_t> a{12};
EXPECT_EQ(a.value(), 12);
EXPECT_EQ(a.raw_value, 12);
nullable<std::uint32_t> b = 20;
EXPECT_EQ(b.value(), 20);
}
TEST(NullableTest, Null) {
auto a = nullable<std::uint32_t>::null();
EXPECT_FALSE(a.has_value());
EXPECT_EQ(a.raw_value, (std::numeric_limits<std::uint32_t>::max)());
nullable<std::uint32_t> b;
EXPECT_FALSE(b.has_value());
EXPECT_EQ(b.raw_value, nullable<std::uint32_t>::null_value());
}
TEST(NullableTest, Assignment) {
nullable<std::uint32_t> a;
a = 12;
EXPECT_EQ(a.value(), 12);
nullable<std::uint32_t> b = 20;
a = b;
EXPECT_EQ(a.value(), 20);
}
TEST(NullableTest, Reset) {
nullable<std::uint32_t> a{12};
a.reset();
EXPECT_FALSE(a.has_value());
}
TEST(NullableTest, ValueOr) {
auto a = nullable<std::uint32_t>::null();
EXPECT_EQ(a.value_or(20), 20);
}
TEST(NullableTest, Comparison) {
EXPECT_EQ(nullable<std::uint32_t>{12}, nullable<std::uint32_t>{12});
EXPECT_NE(nullable<std::uint32_t>{12}, nullable<std::uint32_t>{20});
EXPECT_NE(nullable<std::uint32_t>{12}, nullable<std::uint32_t>::null());
EXPECT_EQ(nullable<std::uint32_t>::null(), nullable<std::uint32_t>::null());
}
TEST(NullableTest, Swap) {
auto a = nullable<std::uint32_t>::null();
nullable<std::uint32_t> b = 12;
EXPECT_FALSE(a.has_value());
EXPECT_EQ(b.value(), 12);
a.swap(b);
EXPECT_FALSE(b.has_value());
EXPECT_EQ(a.value(), 12);
}
}

View File

@ -0,0 +1,14 @@
#ifndef TRACING_COMMON_HPP
#define TRACING_COMMON_HPP
template<typename...> using void_t = void;
#ifndef CPPTRACE_BUILD_NO_SYMBOLS
#define EXPECT_FILE(A, B) EXPECT_THAT((A), testing::EndsWith(B))
#define EXPECT_LINE(A, B) EXPECT_EQ((A), (B))
#else
#define EXPECT_FILE(A, B) (void_t<decltype(A), decltype(B)>)0
#define EXPECT_LINE(A, B) (void_t<decltype(A), decltype(B)>)0
#endif
#endif

View File

@ -10,6 +10,8 @@
#include <cpptrace/cpptrace.hpp>
#include <cpptrace/from_current.hpp>
#include "common.hpp"
using namespace std::literals;
@ -52,8 +54,7 @@ TEST(FromCurrent, Basic) {
trace.frames.begin(),
trace.frames.end(),
[](const cpptrace::stacktrace_frame& frame) {
return frame.filename.find("from_current.cpp") != std::string::npos
&& frame.symbol.find("lambda") == std::string::npos; // due to msvc
return frame.symbol.find("stacktrace_from_current_3") != std::string::npos;
}
);
ASSERT_NE(it, trace.frames.end());
@ -61,29 +62,29 @@ TEST(FromCurrent, Basic) {
int j = 0;
ASSERT_LT(i, trace.frames.size());
ASSERT_LT(j, line_numbers.size());
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("from_current.cpp"));
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]);
EXPECT_FILE(trace.frames[i].filename, "from_current.cpp");
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[j]);
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_from_current_3"));
i++;
j++;
ASSERT_LT(i, trace.frames.size());
ASSERT_LT(j, line_numbers.size());
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("from_current.cpp"));
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]);
EXPECT_FILE(trace.frames[i].filename, "from_current.cpp");
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[j]);
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_from_current_2"));
i++;
j++;
ASSERT_LT(i, trace.frames.size());
ASSERT_LT(j, line_numbers.size());
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("from_current.cpp"));
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]);
EXPECT_FILE(trace.frames[i].filename, "from_current.cpp");
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[j]);
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_from_current_1"));
i++;
j++;
ASSERT_LT(i, trace.frames.size());
ASSERT_LT(j, line_numbers.size());
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("from_current.cpp"));
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]);
EXPECT_FILE(trace.frames[i].filename, "from_current.cpp");
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[j]);
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("FromCurrent_Basic_Test::TestBody"));
}
}
@ -104,8 +105,7 @@ TEST(FromCurrent, CorrectHandler) {
trace.frames.begin(),
trace.frames.end(),
[](const cpptrace::stacktrace_frame& frame) {
return frame.filename.find("from_current.cpp") != std::string::npos
&& frame.symbol.find("lambda") == std::string::npos;
return frame.symbol.find("stacktrace_from_current_3") != std::string::npos;
}
);
EXPECT_NE(it, trace.frames.end());
@ -134,8 +134,7 @@ TEST(FromCurrent, RawTrace) {
trace.frames.begin(),
trace.frames.end(),
[](const cpptrace::stacktrace_frame& frame) {
return frame.filename.find("from_current.cpp") != std::string::npos
&& frame.symbol.find("lambda") == std::string::npos;
return frame.symbol.find("stacktrace_from_current_3") != std::string::npos;
}
);
EXPECT_NE(it, trace.frames.end());

View File

@ -1,3 +1,4 @@
#include "common.hpp"
#include <algorithm>
#include <string_view>
#include <string>
@ -10,6 +11,8 @@
#include <cpptrace/cpptrace.hpp>
#include <cpptrace/from_current.hpp>
#include "common.hpp"
using namespace std::literals;
@ -52,8 +55,7 @@ TEST(FromCurrentZ, Basic) {
trace.frames.begin(),
trace.frames.end(),
[](const cpptrace::stacktrace_frame& frame) {
return frame.filename.find("from_current_z.cpp") != std::string::npos
&& frame.symbol.find("lambda") == std::string::npos; // due to msvc
return frame.symbol.find("stacktrace_from_current_z_3") != std::string::npos;
}
);
ASSERT_NE(it, trace.frames.end());
@ -61,29 +63,29 @@ TEST(FromCurrentZ, Basic) {
int j = 0;
ASSERT_LT(i, trace.frames.size());
ASSERT_LT(j, line_numbers.size());
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("from_current_z.cpp"));
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]);
EXPECT_FILE(trace.frames[i].filename, "from_current_z.cpp");
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[j]);
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_from_current_z_3"));
i++;
j++;
ASSERT_LT(i, trace.frames.size());
ASSERT_LT(j, line_numbers.size());
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("from_current_z.cpp"));
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]);
EXPECT_FILE(trace.frames[i].filename, "from_current_z.cpp");
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[j]);
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_from_current_z_2"));
i++;
j++;
ASSERT_LT(i, trace.frames.size());
ASSERT_LT(j, line_numbers.size());
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("from_current_z.cpp"));
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]);
EXPECT_FILE(trace.frames[i].filename, "from_current_z.cpp");
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[j]);
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_from_current_z_1"));
i++;
j++;
ASSERT_LT(i, trace.frames.size());
ASSERT_LT(j, line_numbers.size());
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("from_current_z.cpp"));
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]);
EXPECT_FILE(trace.frames[i].filename, "from_current_z.cpp");
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[j]);
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("FromCurrentZ_Basic_Test::TestBody"));
}
}
@ -104,8 +106,7 @@ TEST(FromCurrentZ, CorrectHandler) {
trace.frames.begin(),
trace.frames.end(),
[](const cpptrace::stacktrace_frame& frame) {
return frame.filename.find("from_current_z.cpp") != std::string::npos
&& frame.symbol.find("lambda") == std::string::npos;
return frame.symbol.find("stacktrace_from_current_z_3") != std::string::npos;
}
);
EXPECT_NE(it, trace.frames.end());
@ -133,8 +134,7 @@ TEST(FromCurrentZ, RawTrace) {
trace.frames.begin(),
trace.frames.end(),
[](const cpptrace::stacktrace_frame& frame) {
return frame.filename.find("from_current_z.cpp") != std::string::npos
&& frame.symbol.find("lambda") == std::string::npos;
return frame.symbol.find("stacktrace_from_current_z_3") != std::string::npos;
}
);
EXPECT_NE(it, trace.frames.end());

View File

@ -5,8 +5,11 @@
#include <gtest/gtest-matchers.h>
#include <gmock/gmock.h>
#include <gmock/gmock-matchers.h>
#include <cpptrace/cpptrace.hpp>
#include "common.hpp"
using namespace std::literals;
@ -39,7 +42,7 @@ CPPTRACE_FORCE_NO_INLINE void object_basic_resolution() {
auto line = __LINE__ + 1;
auto trace = cpptrace::generate_object_trace().resolve();
ASSERT_GE(trace.frames.size(), 1);
EXPECT_THAT(trace.frames[0].filename, testing::EndsWith("object_trace.cpp"));
EXPECT_FILE(trace.frames[0].filename, "object_trace.cpp");
EXPECT_EQ(trace.frames[0].line.value(), line);
EXPECT_THAT(trace.frames[0].symbol, testing::HasSubstr("object_basic_resolution"));
}
@ -75,20 +78,20 @@ CPPTRACE_FORCE_NO_INLINE int object_resolve_3(std::vector<int>& line_numbers) {
return 2;
}
int i = 0;
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("object_trace.cpp"));
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_FILE(trace.frames[i].filename, "object_trace.cpp");
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("object_resolve_3"));
i++;
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("object_trace.cpp"));
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_FILE(trace.frames[i].filename, "object_trace.cpp");
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("object_resolve_2"));
i++;
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("object_trace.cpp"));
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_FILE(trace.frames[i].filename, "object_trace.cpp");
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("object_resolve_1"));
i++;
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("object_trace.cpp"));
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_FILE(trace.frames[i].filename, "object_trace.cpp");
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("ObjectTrace_Resolution_Test::TestBody"));
return 2;
}

View File

@ -4,14 +4,17 @@
#include <gtest/gtest-matchers.h>
#include <gmock/gmock.h>
#include <gmock/gmock-matchers.h>
#include <cpptrace/cpptrace.hpp>
#include "common.hpp"
using namespace std::literals;
#ifdef _MSC_VER
#define CPPTRACE_FORCE_INLINE [[msvc::flatten]]
#else
#define CPPTRACE_FORCE_INLINE [[gnu::always_inline]]
#define CPPTRACE_FORCE_INLINE [[gnu::always_inline]] static
#endif
@ -29,8 +32,8 @@ CPPTRACE_FORCE_NO_INLINE void stacktrace_basic() {
auto line = __LINE__ + 1;
auto trace = cpptrace::generate_trace();
ASSERT_GE(trace.frames.size(), 1);
EXPECT_THAT(trace.frames[0].filename, testing::EndsWith("stacktrace.cpp"));
EXPECT_EQ(trace.frames[0].line.value(), line);
EXPECT_FILE(trace.frames[0].filename, "stacktrace.cpp");
EXPECT_LINE(trace.frames[0].line.value(), line);
EXPECT_THAT(trace.frames[0].symbol, testing::HasSubstr("stacktrace_basic"));
}
@ -51,20 +54,20 @@ CPPTRACE_FORCE_NO_INLINE int stacktrace_multi_3(std::vector<int>& line_numbers)
return 2;
}
int i = 0;
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("stacktrace.cpp"));
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_FILE(trace.frames[i].filename, "stacktrace.cpp");
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_multi_3"));
i++;
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("stacktrace.cpp"));
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_FILE(trace.frames[i].filename, "stacktrace.cpp");
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_multi_2"));
i++;
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("stacktrace.cpp"));
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_FILE(trace.frames[i].filename, "stacktrace.cpp");
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_multi_1"));
i++;
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("stacktrace.cpp"));
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_FILE(trace.frames[i].filename, "stacktrace.cpp");
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("Stacktrace_MultipleFrames_Test::TestBody"));
return 2;
}
@ -114,25 +117,25 @@ TEST(Stacktrace, RawTraceResolution) {
auto trace = raw.resolve();
ASSERT_GE(trace.frames.size(), 4);
int i = 0;
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("stacktrace.cpp"));
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_FILE(trace.frames[i].filename, "stacktrace.cpp");
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_raw_resolve_3"));
i++;
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("stacktrace.cpp"));
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_FILE(trace.frames[i].filename, "stacktrace.cpp");
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_raw_resolve_2"));
i++;
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("stacktrace.cpp"));
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_FILE(trace.frames[i].filename, "stacktrace.cpp");
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_raw_resolve_1"));
i++;
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("stacktrace.cpp"));
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_FILE(trace.frames[i].filename, "stacktrace.cpp");
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("Stacktrace_RawTraceResolution_Test::TestBody"));
}
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF
#if defined(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) && !defined(CPPTRACE_BUILD_NO_SYMBOLS)
CPPTRACE_FORCE_NO_INLINE int stacktrace_inline_resolution_3(std::vector<int>& line_numbers) {
static volatile int lto_guard; lto_guard = lto_guard + 1;
line_numbers.insert(line_numbers.begin(), __LINE__ + 1);
@ -142,29 +145,29 @@ CPPTRACE_FORCE_NO_INLINE int stacktrace_inline_resolution_3(std::vector<int>& li
return 2;
}
int i = 0;
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("stacktrace.cpp"));
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_FILE(trace.frames[i].filename, "stacktrace.cpp");
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_inline_resolution_3"));
EXPECT_FALSE(trace.frames[i].is_inline);
EXPECT_NE(trace.frames[i].raw_address, 0);
EXPECT_NE(trace.frames[i].object_address, 0);
i++;
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("stacktrace.cpp"));
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_FILE(trace.frames[i].filename, "stacktrace.cpp");
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_inline_resolution_2"));
EXPECT_TRUE(trace.frames[i].is_inline);
EXPECT_EQ(trace.frames[i].raw_address, 0);
EXPECT_EQ(trace.frames[i].object_address, 0);
i++;
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("stacktrace.cpp"));
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_FILE(trace.frames[i].filename, "stacktrace.cpp");
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_inline_resolution_1"));
EXPECT_FALSE(trace.frames[i].is_inline);
EXPECT_NE(trace.frames[i].raw_address, 0);
EXPECT_NE(trace.frames[i].object_address, 0);
i++;
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("stacktrace.cpp"));
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_FILE(trace.frames[i].filename, "stacktrace.cpp");
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("Stacktrace_InlineResolution_Test::TestBody"));
EXPECT_FALSE(trace.frames[i].is_inline);
EXPECT_NE(trace.frames[i].raw_address, 0);

View File

@ -8,6 +8,8 @@
#include <cpptrace/cpptrace.hpp>
#include "common.hpp"
using namespace std::literals;
@ -48,26 +50,26 @@ TEST(TracedException, Basic) {
size_t i = 0;
ASSERT_LT(i, trace.frames.size());
ASSERT_LT(i, line_numbers.size());
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("traced_exception.cpp"));
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_FILE(trace.frames[i].filename, "traced_exception.cpp");
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_traced_object_3"));
i++;
ASSERT_LT(i, trace.frames.size());
ASSERT_LT(i, line_numbers.size());
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("traced_exception.cpp"));
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_FILE(trace.frames[i].filename, "traced_exception.cpp");
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_traced_object_2"));
i++;
ASSERT_LT(i, trace.frames.size());
ASSERT_LT(i, line_numbers.size());
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("traced_exception.cpp"));
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_FILE(trace.frames[i].filename, "traced_exception.cpp");
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_traced_object_1"));
i++;
ASSERT_LT(i, trace.frames.size());
ASSERT_LT(i, line_numbers.size());
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("traced_exception.cpp"));
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_FILE(trace.frames[i].filename, "traced_exception.cpp");
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("TracedException_Basic_Test::TestBody"));
}
}

48
tools/CMakeLists.txt Normal file
View File

@ -0,0 +1,48 @@
include(FetchContent)
FetchContent_Declare(
lyra
GIT_SHALLOW TRUE
GIT_REPOSITORY "https://github.com/bfgroup/Lyra.git"
GIT_TAG "ee3c076fa6b9d64c9d249a21f5b9b5a8dae92cd8"
)
FetchContent_MakeAvailable(lyra)
FetchContent_Declare(
fmt
GIT_SHALLOW TRUE
GIT_REPOSITORY "https://github.com/fmtlib/fmt.git"
GIT_TAG "e69e5f977d458f2650bb346dadf2ad30c5320281" # v10.2.1
)
FetchContent_MakeAvailable(fmt)
set(
COMMON_LIBS
cpptrace::cpptrace
bfg::lyra
fmt::fmt
)
function(binary TARGET)
cmake_parse_arguments(BINARY "" "" "SOURCES;LIBS;FLAGS;DEFS" ${ARGN})
add_executable(${TARGET} main.cpp)
if(BINARY_SOURCES)
add_library(${TARGET}_OBJ OBJECT ${BINARY_SOURCES})
target_link_libraries(${TARGET}_OBJ PUBLIC ${COMMON_LIBS})
endif()
target_link_libraries(${TARGET} PUBLIC ${COMMON_LIBS})
target_link_libraries(${TARGET} PUBLIC ${BINARY_LIBS})
target_compile_definitions(${TARGET} PUBLIC ${BINARY_DEFS})
target_compile_options(${TARGET} PUBLIC ${BINARY_FLAGS} ${debug} ${warning_options})
target_include_directories(${TARGET} PUBLIC "${PROJECT_SOURCE_DIR}/src")
target_compile_features(${TARGET} PRIVATE cxx_std_20)
set_target_properties(
${TARGET}
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)
endfunction()
add_subdirectory(dwarfdump)
add_subdirectory(symbol_tables)
add_subdirectory(resolver)

View File

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

179
tools/dwarfdump/main.cpp Normal file
View File

@ -0,0 +1,179 @@
#include <lyra/lyra.hpp>
#include <fmt/format.h>
#include <fmt/std.h>
#include <fmt/ostream.h>
#include <cpptrace/cpptrace.hpp>
#include <cpptrace/from_current.hpp>
#include <filesystem>
#include "symbols/dwarf/dwarf.hpp"
using namespace std::literals;
using namespace cpptrace::detail::libdwarf;
template<> struct fmt::formatter<lyra::cli> : ostream_formatter {};
class DwarfDumper {
std::string object_path;
Dwarf_Debug dbg = nullptr;
// Error handling helper
// For some reason R (*f)(Args..., void*)-style deduction isn't possible, seems like a bug in all compilers
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56190
// TODO: Duplicate
template<
typename... Args,
typename... Args2,
typename std::enable_if<
std::is_same<
decltype(
(void)std::declval<int(Args...)>()(std::forward<Args2>(std::declval<Args2>())..., nullptr)
),
void
>::value,
int
>::type = 0
>
int wrap(int (*f)(Args...), Args2&&... args) const {
Dwarf_Error error = nullptr;
int ret = f(std::forward<Args2>(args)..., &error);
if(ret == DW_DLV_ERROR) {
handle_dwarf_error(dbg, error);
}
return ret;
}
// TODO: Duplicate
// walk all CU's in a dbg, callback is called on each die and should return true to
// continue traversal
void walk_compilation_units(const std::function<bool(const die_object&)>& fn) {
// libdwarf keeps track of where it is in the file, dwarf_next_cu_header_d is statefull
Dwarf_Unsigned next_cu_header;
Dwarf_Half header_cu_type;
while(true) {
int ret = wrap(
dwarf_next_cu_header_d,
dbg,
true,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
&next_cu_header,
&header_cu_type
);
if(ret == DW_DLV_NO_ENTRY) {
fmt::println("End walk_dbg");
return;
}
if(ret != DW_DLV_OK) {
PANIC("Unexpected return code from dwarf_next_cu_header_d");
return;
}
// 0 passed as the die to the first call of dwarf_siblingof_b immediately after dwarf_next_cu_header_d
// to fetch the cu die
die_object cu_die(dbg, nullptr);
cu_die = cu_die.get_sibling();
if(!cu_die) {
break;
}
if(!walk_die_list(cu_die, fn)) {
break;
}
}
fmt::println("End walk_compilation_units");
}
void dump_die_tree(const die_object& die, int depth) {
walk_die_list(
die,
[this, depth] (const die_object& die) {
fmt::println("{:016x}{: <{}} {}", die.get_global_offset(), "", depth * 2, die.get_tag_name());
fmt::println("{: <16}{: <{}} name: {}", "", "", depth * 2, die.get_name());
fmt::println("");
auto child = die.get_child();
if(child) {
dump_die_tree(child, depth + 1);
}
return true;
}
);
}
void dump_cu(const die_object& cu_die) {
Dwarf_Half offset_size = 0;
Dwarf_Half dwversion = 0;
dwarf_get_version_of_die(cu_die.get(), &dwversion, &offset_size);
fmt::println("{:016x} Compile Unit: version = {}, unit type = {}", cu_die.get_global_offset(), dwversion, cu_die.get_tag_name());
dump_die_tree(cu_die, 0);
}
public:
DwarfDumper() = default;
~DwarfDumper() {
dwarf_finish(dbg);
}
void dump(std::filesystem::path path) {
object_path = path;
auto ret = wrap(
dwarf_init_path_a,
object_path.c_str(),
nullptr,
0,
DW_GROUPNUMBER_ANY,
0,
nullptr,
nullptr,
&dbg
);
if(ret == DW_DLV_OK) {
// ok
} else if(ret == DW_DLV_NO_ENTRY) {
// fail, no debug info
fmt::println(stderr, "No debug info");
std::exit(1);
} else {
fmt::println(stderr, "Error: Unknown return code from dwarf_init_path {}", ret);
std::exit(1);
}
walk_compilation_units([this] (const die_object& cu_die) {
dump_cu(cu_die);
return true;
});
}
};
int main(int argc, char** argv) CPPTRACE_TRY {
bool show_help = false;
std::filesystem::path path;
auto cli = lyra::cli()
| lyra::help(show_help)
| lyra::arg(path, "binary path")("binary to dwarfdump").required();
if(auto result = cli.parse({ argc, argv }); !result) {
fmt::println(stderr, "Error in command line: {}", result.message());
fmt::println("{}", cli);
return 1;
}
if(show_help) {
fmt::println("{}", cli);
return 0;
}
if(!std::filesystem::exists(path)) {
fmt::println(stderr, "Error: Path doesn't exist {}", path);
return 1;
}
if(!std::filesystem::is_regular_file(path)) {
fmt::println(stderr, "Error: Path isn't a regular file {}", path);
return 1;
}
DwarfDumper{}.dump(path);
} CPPTRACE_CATCH(const std::exception& e) {
fmt::println(stderr, "Caught exception {}: {}", cpptrace::demangle(typeid(e).name()), e.what());
cpptrace::from_current_exception().print();
}

View File

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

109
tools/resolver/main.cpp Normal file
View File

@ -0,0 +1,109 @@
#include "cpptrace/basic.hpp"
#include "cpptrace/formatting.hpp"
#include "cpptrace/forward.hpp"
#include <chrono>
#include <lyra/lyra.hpp>
#include <fmt/format.h>
#include <fmt/std.h>
#include <fmt/ostream.h>
#include <fmt/chrono.h>
#include <cpptrace/cpptrace.hpp>
#include <cpptrace/from_current.hpp>
#include <filesystem>
#include <stdexcept>
#include <string>
#include <thread>
#include "symbols/symbols.hpp"
#include "demangle/demangle.hpp"
using namespace std::literals;
using namespace cpptrace::detail;
template<> struct fmt::formatter<lyra::cli> : ostream_formatter {};
auto formatter = cpptrace::formatter{}.addresses(cpptrace::formatter::address_mode::object);
struct options {
bool show_help = false;
std::filesystem::path path;
std::vector<std::string> address_strings;
bool from_stdin = false;
bool keepalive = false;
bool timing = false;
bool disable_aranges = false;
cpptrace::nullable<std::size_t> line_table_cache_size;
};
void resolve(const options& opts, cpptrace::frame_ptr address) {
cpptrace::object_frame obj_frame{0, address, opts.path.string()};
auto start = std::chrono::high_resolution_clock::now();
std::vector<cpptrace::stacktrace_frame> trace = cpptrace::detail::resolve_frames({obj_frame});
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
if(trace.size() != 1) {
throw std::runtime_error("Something went wrong, trace vector size didn't match");
}
trace[0].symbol = cpptrace::demangle(trace[0].symbol);
formatter.print(trace[0]);
std::cout<<std::endl;
if(opts.timing) {
fmt::println("resolve time: {}", duration);
}
}
int main(int argc, char** argv) CPPTRACE_TRY {
options opts;
auto cli = lyra::cli()
| lyra::help(opts.show_help)
| lyra::opt(opts.from_stdin)["--stdin"]("read addresses from stdin")
| lyra::opt(opts.keepalive)["--keepalive"]("keep the program alive after resolution finishes (useful for debugging)")
| lyra::opt(opts.timing)["--timing"]("provide timing stats")
| lyra::opt(opts.disable_aranges)["--disable-aranges"]("don't use the .debug_aranges accelerated address lookup table")
| lyra::opt(opts.line_table_cache_size.raw_value, "line table cache size")["--line-table-cache-size"]("limit the size of cpptrace's line table cache")
| lyra::arg(opts.path, "binary path")("binary to look in").required()
| lyra::arg(opts.address_strings, "addresses")("addresses");
if(auto result = cli.parse({ argc, argv }); !result) {
fmt::println(stderr, "Error in command line: {}", result.message());
fmt::println("{}", cli);
return 1;
}
if(opts.show_help) {
fmt::println("{}", cli);
return 0;
}
if(!std::filesystem::exists(opts.path)) {
fmt::println(stderr, "Error: Path doesn't exist {}", opts.path);
return 1;
}
if(!std::filesystem::is_regular_file(opts.path)) {
fmt::println(stderr, "Error: Path isn't a regular file {}", opts.path);
return 1;
}
if(opts.disable_aranges) {
cpptrace::experimental::set_dwarf_resolver_disable_aranges(true);
}
if(opts.line_table_cache_size.has_value()) {
cpptrace::experimental::set_dwarf_resolver_line_table_cache_size(opts.line_table_cache_size);
}
for(const auto& address : opts.address_strings) {
resolve(opts, std::stoi(address, nullptr, 16));
}
if(opts.from_stdin) {
std::string word;
while(std::cin >> word) {
resolve(opts, std::stoi(word, nullptr, 16));
}
}
if(opts.keepalive) {
fmt::println("Done");
while(true) {
std::this_thread::sleep_for(std::chrono::seconds(60));
}
}
return 0;
} CPPTRACE_CATCH(const std::exception& e) {
fmt::println(stderr, "Caught exception {}: {}", cpptrace::demangle(typeid(e).name()), e.what());
cpptrace::from_current_exception().print();
}

View File

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

View File

@ -0,0 +1,83 @@
#include <lyra/lyra.hpp>
#include <fmt/format.h>
#include <fmt/std.h>
#include <fmt/ostream.h>
#include <cpptrace/cpptrace.hpp>
#include <cpptrace/from_current.hpp>
#include <filesystem>
#include "binary/elf.hpp"
#include "binary/mach-o.hpp"
using namespace std::literals;
using namespace cpptrace::detail;
template<> struct fmt::formatter<lyra::cli> : ostream_formatter {};
#if IS_LINUX
void dump_symtab_result(const Result<optional<std::vector<elf::symbol_entry>>, internal_error>& res) {
if(!res) {
fmt::println(stderr, "Error loading: {}", res.unwrap_error().what());
}
const auto& entries_ = res.unwrap_value();
if(!entries_) {
fmt::println("Empty symbol table");
}
const auto& entries = entries_.unwrap();
fmt::println("{:16} {:16} {:4} {}", "value", "size", "shdx", "symbol");
for(const auto& entry : entries) {
fmt::println("{:016x} {:016x} {:04x} {}", entry.st_value, entry.st_size, entry.st_shndx, entry.st_name);
}
}
void dump_symbols(const std::filesystem::path& path) {
auto elf_ = elf::open_elf(path);
if(!elf_) {
fmt::println(stderr, "Error reading file: {}", elf_.unwrap_error().what());
}
auto& elf = elf_.unwrap_value();
fmt::println("Symtab:");
dump_symtab_result(elf.get_symtab_entries());
fmt::println("Dynamic symtab:");
dump_symtab_result(elf.get_dynamic_symtab_entries());
}
#elif IS_APPLE
void dump_symbols(const std::filesystem::path& path) {
fmt::println("Not implemented yet (TODO)");
}
#else
void dump_symbols(const std::filesystem::path&) {
fmt::println("Unable to dump symbol table on this platform");
}
#endif
int main(int argc, char** argv) CPPTRACE_TRY {
bool show_help = false;
std::filesystem::path path;
auto cli = lyra::cli()
| lyra::help(show_help)
| lyra::arg(path, "binary path")("binary to dump symbol tables for").required();
if(auto result = cli.parse({ argc, argv }); !result) {
fmt::println(stderr, "Error in command line: {}", result.message());
fmt::println("{}", cli);
return 1;
}
if(show_help) {
fmt::println("{}", cli);
return 0;
}
if(!std::filesystem::exists(path)) {
fmt::println(stderr, "Error: Path doesn't exist {}", path);
return 1;
}
if(!std::filesystem::is_regular_file(path)) {
fmt::println(stderr, "Error: Path isn't a regular file {}", path);
return 1;
}
dump_symbols(path);
return 0;
} CPPTRACE_CATCH(const std::exception& e) {
fmt::println(stderr, "Caught exception {}: {}", cpptrace::demangle(typeid(e).name()), e.what());
cpptrace::from_current_exception().print();
}