Compare commits
235 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fac4d08fd0 | ||
|
|
c0354799c7 | ||
|
|
c37b5ed736 | ||
|
|
a32e22aa44 | ||
|
|
6cec10601e | ||
|
|
c9dc51aa61 | ||
|
|
e6d55b5e7d | ||
|
|
1940dc607a | ||
|
|
477aecec01 | ||
|
|
03b292c20b | ||
|
|
5bfcf280a5 | ||
|
|
9b02fc6f74 | ||
|
|
3a4da8ccf0 | ||
|
|
9a2ae3c96f | ||
|
|
6877782d96 | ||
|
|
98ea78445c | ||
|
|
3aa080d536 | ||
|
|
34ea9572b8 | ||
|
|
525ce871d5 | ||
|
|
daed105fef | ||
|
|
69875cde19 | ||
|
|
b59a08634d | ||
|
|
2bb29f71bc | ||
|
|
d6fff9022e | ||
|
|
e86f4eba8f | ||
|
|
ca8416ea1e | ||
|
|
6d62d01496 | ||
|
|
74b5ac6e07 | ||
|
|
dff5b8f18e | ||
|
|
c375a72efc | ||
|
|
d8a0097c43 | ||
|
|
350382bb93 | ||
|
|
f7675eac91 | ||
|
|
832c3014b0 | ||
|
|
b0d12daf22 | ||
|
|
cebca81aa9 | ||
|
|
c2b3b7e0a1 | ||
|
|
27107556f8 | ||
|
|
26ef617c25 | ||
|
|
9c0d0db884 | ||
|
|
5e4ea9a88f | ||
|
|
99814905be | ||
|
|
6180399996 | ||
|
|
e7f8521936 | ||
|
|
e77f16031b | ||
|
|
261ca9d554 | ||
|
|
5073cc218a | ||
|
|
34be9f2f19 | ||
|
|
6d41ea0135 | ||
|
|
27924487dc | ||
|
|
728cefab55 | ||
|
|
aed47df73e | ||
|
|
e22300b36d | ||
|
|
a4faef7f1e | ||
|
|
83527947a2 | ||
|
|
1d79dbcf42 | ||
|
|
8c7b1dc6aa | ||
|
|
111f8e6aec | ||
|
|
ead3f128aa | ||
|
|
8963639639 | ||
|
|
b762ee7ec6 | ||
|
|
87f2fd4c43 | ||
|
|
87b14c87f8 | ||
|
|
62548497a8 | ||
|
|
0f990f05a1 | ||
|
|
4ab78f7a69 | ||
|
|
bc0164224e | ||
|
|
d18d6ee77d | ||
|
|
8f2193f35a | ||
|
|
457bc4b8a1 | ||
|
|
ce97e0004d | ||
|
|
000168b93c | ||
|
|
156ede9aab | ||
|
|
2b7d47d627 | ||
|
|
b724d1328c | ||
|
|
8e7b4a953f | ||
|
|
b2180ae797 | ||
|
|
f0a9e12e88 | ||
|
|
eb83ee2a1c | ||
|
|
d1ce9c8896 | ||
|
|
00c7feb2e0 | ||
|
|
32f0d5d273 | ||
|
|
85be7c32a4 | ||
|
|
248ad447b1 | ||
|
|
eb9ebc31a7 | ||
|
|
e2b1252438 | ||
|
|
3557d7b885 | ||
|
|
9077430b6a | ||
|
|
06c9c14995 | ||
|
|
7f6945c7d9 | ||
|
|
73ee7aa3a1 | ||
|
|
18046688f6 | ||
|
|
1429cfc429 | ||
|
|
82ef2287dd | ||
|
|
25eaa832d1 | ||
|
|
f16c36e0b6 | ||
|
|
b498dead92 | ||
|
|
6971f6a5ca | ||
|
|
247474389f | ||
|
|
dc0c683804 | ||
|
|
c5c785db89 | ||
|
|
a28cc3a3a0 | ||
|
|
08306c12a5 | ||
|
|
68c9d33e94 | ||
|
|
8edb419342 | ||
|
|
dc118dcb6d | ||
|
|
7a1eb4d5bf | ||
|
|
6a9d2c5bbd | ||
|
|
c9826616b5 | ||
|
|
293f4d1593 | ||
|
|
01ea4e9ea7 | ||
|
|
06732add17 | ||
|
|
78086d0267 | ||
|
|
906784284f | ||
|
|
b705afba69 | ||
|
|
76a21d266a | ||
|
|
1bcfcff021 | ||
|
|
485d9a6f21 | ||
|
|
d063784abc | ||
|
|
6689d14c20 | ||
|
|
f42ed3500d | ||
|
|
f35ef65ed6 | ||
|
|
0c40839009 | ||
|
|
d5a2043fd3 | ||
|
|
b127cfb176 | ||
|
|
b23bc42df4 | ||
|
|
5b3f2fb0b1 | ||
|
|
43b0ed1b21 | ||
|
|
27bbe75bd3 | ||
|
|
fe97f8f0f0 | ||
|
|
cc497fb62b | ||
|
|
acddc383a4 | ||
|
|
61d06d56d6 | ||
|
|
4227fc4abe | ||
|
|
ddec65195d | ||
|
|
379a0fa594 | ||
|
|
4354eb21ea | ||
|
|
6945c2167e | ||
|
|
d43318aa92 | ||
|
|
e10dbf431e | ||
|
|
8866d70a32 | ||
|
|
5a9a4f314d | ||
|
|
88df27aa32 | ||
|
|
bbae57bd22 | ||
|
|
5c9f5150fb | ||
|
|
addaf02387 | ||
|
|
855210e624 | ||
|
|
e41f89ee82 | ||
|
|
4cb425fb35 | ||
|
|
90da0563f9 | ||
|
|
04b85a0dfa | ||
|
|
f152788abd | ||
|
|
9269a72c54 | ||
|
|
124dba5254 | ||
|
|
557a4a6fab | ||
|
|
7a0c6ec78d | ||
|
|
81d4776a22 | ||
|
|
0d53defcd9 | ||
|
|
a1fa0a1b81 | ||
|
|
e89eb61a5f | ||
|
|
2d5842164b | ||
|
|
0d2cb217bf | ||
|
|
daa2c53042 | ||
|
|
757c0f87fe | ||
|
|
4c59e73a01 | ||
|
|
c95ab97a48 | ||
|
|
ae86a79f16 | ||
|
|
7b2a994559 | ||
|
|
0ddbbf43cb | ||
|
|
ce8214bb16 | ||
|
|
54a3e6fdf7 | ||
|
|
06eb15bda6 | ||
|
|
3890a70cb4 | ||
|
|
4ed90c1585 | ||
|
|
142e0b9ea4 | ||
|
|
d09378c8e8 | ||
|
|
f9ab949a9e | ||
|
|
4e9f0da95f | ||
|
|
4f94f20d41 | ||
|
|
499bea182c | ||
|
|
0d89be4fbe | ||
|
|
7fdbbfdf67 | ||
|
|
26093d5791 | ||
|
|
f671819510 | ||
|
|
231960d472 | ||
|
|
cf08f2578c | ||
|
|
c7ff8cbf79 | ||
|
|
9a7bd8f95e | ||
|
|
0742b42dad | ||
|
|
31ebb3ca23 | ||
|
|
4dcfdf5281 | ||
|
|
3b0eb54797 | ||
|
|
0249b50698 | ||
|
|
fddbe72a66 | ||
|
|
88d681d986 | ||
|
|
3e131ce8c6 | ||
|
|
5e30d2ae60 | ||
|
|
b87be87c66 | ||
|
|
b364d37f78 | ||
|
|
a4d75a3894 | ||
|
|
d64a935fb4 | ||
|
|
64e0210449 | ||
|
|
0e2c3a130d | ||
|
|
973309cb22 | ||
|
|
164cc75681 | ||
|
|
0bb5aa2318 | ||
|
|
3f68171d0b | ||
|
|
7621f2b277 | ||
|
|
5558210cbe | ||
|
|
26e009c688 | ||
|
|
d5eed55dcd | ||
|
|
df1d78d1eb | ||
|
|
51551ab741 | ||
|
|
70e78e3c8a | ||
|
|
767ddeb10c | ||
|
|
90de25f1df | ||
|
|
2ae193256d | ||
|
|
a2f1812896 | ||
|
|
95a233958c | ||
|
|
59f74320fc | ||
|
|
0b95a6afb9 | ||
|
|
cc2b3fce40 | ||
|
|
dcf89743f6 | ||
|
|
c35ad6887a | ||
|
|
f8fee797cf | ||
|
|
e8fce3fad1 | ||
|
|
15b6d9a2cd | ||
|
|
aae6091395 | ||
|
|
dbeb12b71d | ||
|
|
7497bf2a6f | ||
|
|
e1fb3527a4 | ||
|
|
611660de5b | ||
|
|
b93028921b | ||
|
|
060272be5a | ||
|
|
bf0551b781 |
1
.bazeliskrc
Normal file
1
.bazeliskrc
Normal file
@ -0,0 +1 @@
|
||||
USE_BAZEL_VERSION=7.2.1
|
||||
3
.bazelrc
Normal file
3
.bazelrc
Normal file
@ -0,0 +1,3 @@
|
||||
test --strip=never
|
||||
test --test_output=all
|
||||
test --copt=-g
|
||||
130
.github/workflows/build.yml
vendored
130
.github/workflows/build.yml
vendored
@ -1,130 +0,0 @@
|
||||
name: build
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build-linux:
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
compiler: [gcc, clang]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: dependencies
|
||||
run: |
|
||||
sudo apt install gcc-10 g++-10 libgcc-10-dev libunwind8-dev
|
||||
pip3 install colorama
|
||||
- name: libdwarf
|
||||
run: |
|
||||
cd ..
|
||||
cpptrace/ci/setup-prerequisites.sh
|
||||
- name: build
|
||||
run: |
|
||||
python3 ci/build-in-all-configs.py --${{matrix.compiler}} --default-config
|
||||
build-macos:
|
||||
runs-on: macos-14
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
compiler: [gcc, clang]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: dependencies
|
||||
run: |
|
||||
python3 -m venv env
|
||||
env/bin/pip install colorama
|
||||
- name: libdwarf
|
||||
run: |
|
||||
cd ..
|
||||
cpptrace/ci/setup-prerequisites.sh
|
||||
- name: build
|
||||
run: |
|
||||
env/bin/python ci/build-in-all-configs.py --${{matrix.compiler}} --default-config
|
||||
build-windows:
|
||||
runs-on: windows-2022
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
compiler: [msvc, clang, gcc]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Enable Developer Command Prompt
|
||||
uses: ilammy/msvc-dev-cmd@v1.13.0
|
||||
- name: dependencies
|
||||
run: |
|
||||
pip3 install colorama
|
||||
- name: libdwarf
|
||||
run: |
|
||||
if("${{matrix.compiler}}" -eq "gcc") {
|
||||
cd ..
|
||||
cpptrace/ci/setup-prerequisites-mingw.ps1
|
||||
}
|
||||
- name: build
|
||||
run: |
|
||||
python3 ci/build-in-all-configs.py --${{matrix.compiler}} --default-config
|
||||
build-linux-all-configurations:
|
||||
runs-on: ubuntu-22.04
|
||||
needs: build-linux
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
compiler: [gcc, clang]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: dependencies
|
||||
run: |
|
||||
sudo apt install gcc-10 g++-10 libgcc-10-dev libunwind8-dev
|
||||
pip3 install colorama
|
||||
- name: libdwarf
|
||||
run: |
|
||||
cd ..
|
||||
cpptrace/ci/setup-prerequisites.sh
|
||||
- name: build
|
||||
run: |
|
||||
python3 ci/build-in-all-configs.py --${{matrix.compiler}}
|
||||
build-macos-all-configurations:
|
||||
runs-on: macos-14
|
||||
needs: build-macos
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
compiler: [gcc, clang]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: dependencies
|
||||
run: |
|
||||
python3 -m venv env
|
||||
env/bin/pip install colorama
|
||||
- name: libdwarf
|
||||
run: |
|
||||
cd ..
|
||||
cpptrace/ci/setup-prerequisites.sh
|
||||
- name: build
|
||||
run: |
|
||||
env/bin/python ci/build-in-all-configs.py --${{matrix.compiler}}
|
||||
build-windows-all-configurations:
|
||||
runs-on: windows-2022
|
||||
needs: build-windows
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
compiler: [msvc, clang, gcc]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Enable Developer Command Prompt
|
||||
uses: ilammy/msvc-dev-cmd@v1.13.0
|
||||
- name: dependencies
|
||||
run: |
|
||||
pip3 install colorama
|
||||
- name: libdwarf
|
||||
run: |
|
||||
if("${{matrix.compiler}}" -eq "gcc") {
|
||||
cd ..
|
||||
cpptrace/ci/setup-prerequisites-mingw.ps1
|
||||
}
|
||||
- name: build
|
||||
run: |
|
||||
python3 ci/build-in-all-configs.py --${{matrix.compiler}}
|
||||
652
.github/workflows/ci.yml
vendored
Normal file
652
.github/workflows/ci.yml
vendored
Normal 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
|
||||
255
.github/workflows/cmake-integration.yml
vendored
255
.github/workflows/cmake-integration.yml
vendored
@ -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
|
||||
68
.github/workflows/performance-tests.yml
vendored
68
.github/workflows/performance-tests.yml
vendored
@ -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}}
|
||||
195
.github/workflows/test.yml
vendored
195
.github/workflows/test.yml
vendored
@ -1,195 +0,0 @@
|
||||
name: test
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
# TODO: Test statically linked
|
||||
|
||||
jobs:
|
||||
test-linux:
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
compiler: [gcc, clang]
|
||||
shared: [--shared, ""]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: dependencies
|
||||
run: |
|
||||
sudo apt install gcc-10 g++-10 libgcc-10-dev libunwind8-dev
|
||||
pip3 install colorama
|
||||
- name: libdwarf
|
||||
run: |
|
||||
cd ..
|
||||
cpptrace/ci/setup-prerequisites.sh
|
||||
- name: build and test
|
||||
run: |
|
||||
python3 ci/test-all-configs.py --${{matrix.compiler}} --default-config
|
||||
test-macos:
|
||||
runs-on: macos-14
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
compiler: [gcc, clang]
|
||||
shared: [--shared, ""]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: libdwarf
|
||||
run: |
|
||||
cd ..
|
||||
cpptrace/ci/setup-prerequisites.sh
|
||||
- name: dependencies
|
||||
run: |
|
||||
python3 -m venv env
|
||||
env/bin/pip install colorama
|
||||
- name: build and test
|
||||
run: |
|
||||
env/bin/python ci/test-all-configs.py --${{matrix.compiler}} --default-config
|
||||
test-windows:
|
||||
runs-on: windows-2022
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
compiler: [msvc, clang, gcc]
|
||||
shared: [--shared, ""]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Enable Developer Command Prompt
|
||||
uses: ilammy/msvc-dev-cmd@v1.13.0
|
||||
- name: dependencies
|
||||
run: |
|
||||
pip3 install colorama
|
||||
- name: libdwarf
|
||||
run: |
|
||||
if("${{matrix.compiler}}" -eq "gcc") {
|
||||
cd ..
|
||||
cpptrace/ci/setup-prerequisites-mingw.ps1
|
||||
}
|
||||
- name: build and test
|
||||
run: |
|
||||
python3 ci/test-all-configs.py --${{matrix.compiler}} --default-config
|
||||
test-linux-all-configurations:
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
compiler: [gcc, clang]
|
||||
shared: [--shared, ""]
|
||||
needs: test-linux
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: dependencies
|
||||
run: |
|
||||
sudo apt install gcc-10 g++-10 libgcc-10-dev libunwind8-dev
|
||||
pip3 install colorama
|
||||
- name: libdwarf
|
||||
run: |
|
||||
cd ..
|
||||
cpptrace/ci/setup-prerequisites.sh
|
||||
- name: build and test
|
||||
run: |
|
||||
python3 ci/test-all-configs.py --${{matrix.compiler}}
|
||||
test-macos-all-configurations:
|
||||
runs-on: macos-14
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
compiler: [gcc, clang]
|
||||
shared: [--shared, ""]
|
||||
needs: test-macos
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: libdwarf
|
||||
run: |
|
||||
cd ..
|
||||
cpptrace/ci/setup-prerequisites.sh
|
||||
- name: dependencies
|
||||
run: |
|
||||
python3 -m venv env
|
||||
env/bin/pip install colorama
|
||||
- name: build and test
|
||||
run: |
|
||||
env/bin/python ci/test-all-configs.py --${{matrix.compiler}}
|
||||
test-windows-all-configurations:
|
||||
runs-on: windows-2022
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
compiler: [msvc, clang, gcc]
|
||||
shared: [--shared, ""]
|
||||
needs: test-windows
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Enable Developer Command Prompt
|
||||
uses: ilammy/msvc-dev-cmd@v1.13.0
|
||||
- name: dependencies
|
||||
run: |
|
||||
pip3 install colorama
|
||||
- name: libdwarf
|
||||
run: |
|
||||
if("${{matrix.compiler}}" -eq "gcc") {
|
||||
cd ..
|
||||
cpptrace/ci/setup-prerequisites-mingw.ps1
|
||||
}
|
||||
- name: build and test
|
||||
run: |
|
||||
python3 ci/test-all-configs.py --${{matrix.compiler}}
|
||||
|
||||
|
||||
unittest-linux:
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
compiler: [g++-10, clang++-14]
|
||||
shared: [OFF, ON]
|
||||
build_type: [Debug, RelWithDebInfo]
|
||||
has_dl_find_object: [OFF, ON]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: dependencies
|
||||
run: |
|
||||
sudo apt install gcc-10 g++-10 libgcc-10-dev ninja-build
|
||||
- name: build and test
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. \
|
||||
-GNinja \
|
||||
-DCMAKE_CXX_COMPILER=${{matrix.compiler}} \
|
||||
-DCMAKE_C_COMPILER=${{matrix.compiler == 'g++-10' && 'gcc-10' || 'clang-14'}} \
|
||||
-DCMAKE_BUILD_TYPE=${{matrix.build_type}} \
|
||||
-DBUILD_SHARED_LIBS=${{matrix.shared}} \
|
||||
-DHAS_DL_FIND_OBJECT=${{matrix.has_dl_find_object}} \
|
||||
-DCPPTRACE_WERROR_BUILD=On \
|
||||
-DCPPTRACE_BUILD_TESTING=On
|
||||
ninja
|
||||
./unittest
|
||||
bash -c "exec -a u ./unittest"
|
||||
unittest-windows:
|
||||
runs-on: windows-2022
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
compiler: [cl, clang++]
|
||||
shared: [OFF] # TODO: Re-enable shared
|
||||
build_type: [Debug, RelWithDebInfo]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Enable Developer Command Prompt
|
||||
uses: ilammy/msvc-dev-cmd@v1.13.0
|
||||
- name: build and test
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. `
|
||||
-DCMAKE_CXX_COMPILER=${{matrix.compiler}} `
|
||||
-DCMAKE_C_COMPILER=${{matrix.compiler == 'clang++' && 'clang' || matrix.compiler}} `
|
||||
-DBUILD_SHARED_LIBS=${{matrix.shared}} `
|
||||
-DCPPTRACE_WERROR_BUILD=On `
|
||||
-DCPPTRACE_BUILD_TESTING=On
|
||||
cmake --build . --config ${{matrix.build_type}}
|
||||
./${{matrix.build_type}}/unittest
|
||||
# TODO: Macos, mingw
|
||||
11
.gitignore
vendored
11
.gitignore
vendored
@ -1,8 +1,11 @@
|
||||
.vscode
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
a.out
|
||||
build*/
|
||||
repro*/
|
||||
__pycache__
|
||||
__pycache__/
|
||||
scratch
|
||||
.vscode
|
||||
tmp
|
||||
tmp/
|
||||
bazel-*/
|
||||
cmake-build-*/
|
||||
|
||||
34
BUILD.bazel
Normal file
34
BUILD.bazel
Normal file
@ -0,0 +1,34 @@
|
||||
cc_library(
|
||||
name = "cpptrace",
|
||||
srcs = glob([
|
||||
"src/**/*.hpp",
|
||||
"src/**/*.cpp",
|
||||
]),
|
||||
local_defines = [
|
||||
"CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF",
|
||||
"CPPTRACE_DEMANGLE_WITH_CXXABI",
|
||||
"CPPTRACE_UNWIND_WITH_LIBUNWIND"
|
||||
],
|
||||
hdrs = glob([
|
||||
"include/cpptrace/*.hpp",
|
||||
"include/ctrace/*.h",
|
||||
]),
|
||||
includes = [
|
||||
"include",
|
||||
"src"
|
||||
],
|
||||
deps = [
|
||||
"@libdwarf//:libdwarf",
|
||||
"@libunwind//:libunwind"
|
||||
],
|
||||
copts = [
|
||||
"-Wall",
|
||||
"-Wextra",
|
||||
"-Werror=return-type",
|
||||
"-Wundef",
|
||||
"-Wuninitialized",
|
||||
"-fPIC",
|
||||
"-std=c++11"
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
169
CHANGELOG.md
169
CHANGELOG.md
@ -1,6 +1,16 @@
|
||||
# Changelog
|
||||
|
||||
- [Changelog](#changelog)
|
||||
- [v0.8.2](#v082)
|
||||
- [v0.8.1](#v081)
|
||||
- [v0.8.0](#v080)
|
||||
- [v0.7.5](#v075)
|
||||
- [v0.7.4](#v074)
|
||||
- [v0.7.3](#v073)
|
||||
- [v0.7.2](#v072)
|
||||
- [v0.7.1](#v071)
|
||||
- [v0.7.0](#v070)
|
||||
- [v0.6.3](#v063)
|
||||
- [v0.6.2](#v062)
|
||||
- [v0.6.1](#v061)
|
||||
- [v0.6.0](#v060)
|
||||
@ -18,6 +28,165 @@
|
||||
- [v0.1.1](#v011)
|
||||
- [v0.1](#v01)
|
||||
|
||||
# v0.8.2
|
||||
|
||||
Fixed:
|
||||
- Fixed printing of internal error messages when an object file can't be loaded, mainly affecting MacOS https://github.com/jeremy-rifkin/cpptrace/issues/217
|
||||
|
||||
Other:
|
||||
- Bumped zstd via FetchContent to 1.5.7
|
||||
|
||||
# v0.8.1
|
||||
|
||||
Fixed:
|
||||
- Fixed compile error on msvc https://github.com/jeremy-rifkin/cpptrace/issues/215
|
||||
|
||||
Added:
|
||||
- Added `cpptrace::can_get_safe_object_frame()`
|
||||
|
||||
Breaking changes:
|
||||
- Renamed ctrace's `can_signal_safe_unwind` to `ctrace_can_signal_safe_unwind`. This was an oversight. Apologies for
|
||||
including a breaking change in a patch release. Github code search suggests this API isn't used in public code, at
|
||||
least.
|
||||
|
||||
Other:
|
||||
- Added CI workflow to test on old msvc
|
||||
- Made some internal improvements on robustness and cleanliness
|
||||
|
||||
# v0.8.0
|
||||
|
||||
Added:
|
||||
- Added support for resolving symbols from elf and mach-o symbol tables, allowing function names to be resolved even in
|
||||
a build that doesn't include debug information https://github.com/jeremy-rifkin/cpptrace/issues/201
|
||||
- Added a configurable stack trace formatter https://github.com/jeremy-rifkin/cpptrace/issues/164
|
||||
- Added configuration options for the libdwarf back-end that can be used to lower memory usage on memory-constrained
|
||||
systems https://github.com/jeremy-rifkin/cpptrace/issues/193
|
||||
- Added `cpptrace::nullable<T>::null_value`
|
||||
- Made `cpptrace::nullable<T>` member functions conditionally `constexpr` where possible
|
||||
|
||||
Fixed:
|
||||
- Fixed handling of `SymInitialize` when other code has already called `SymInitialize`. `SymInitialize` must only be
|
||||
called once per handle and cpptrace now attempts to duplicate the current process handle to avoid conflicts.
|
||||
https://github.com/jeremy-rifkin/cpptrace/issues/204
|
||||
- Fixed a couple of locking edge cases surrounding dbghelp functions
|
||||
- Fixed improper deallocation of `dwarf_errmsg` in the libdwarf back-end
|
||||
|
||||
Breaking changes:
|
||||
- `cpptrace::get_snippet` previously included a newline at the end but it now does not. This also affects the behavior
|
||||
of trace formatting with snippets enabled.
|
||||
|
||||
Other:
|
||||
- Significantly improved memory usage and performance of the libdwarf back-end
|
||||
- Improved implementation and organization of internal utility types, such as `optional` and `Result`
|
||||
- Improved trace printing and formatting implementation
|
||||
- Added unit tests for library internal utilities
|
||||
- Added logic to the cxxabi demangler to ensure external names begin with `_Z` or `__Z` before attempting to demangle
|
||||
- Added various internal tools and abstractions to improve maintainability and clarity
|
||||
- Various internal improvements for robustness
|
||||
- Added a small handful of utility tool programs that are useful for continued development, maintenance, and debugging
|
||||
- Improved library CI setup
|
||||
- Marked the `CPPTRACE_BUILD_BENCHMARK` option as advanced
|
||||
|
||||
# v0.7.5
|
||||
|
||||
Fixed:
|
||||
- Fixed missing `<typeinfo>` include https://github.com/jeremy-rifkin/cpptrace/pull/202
|
||||
- Added `__cdecl` to a terminate handler to appease MSVC under some configurations https://github.com/jeremy-rifkin/cpptrace/issues/197
|
||||
- Set C++ standard for cmake support checks https://github.com/jeremy-rifkin/cpptrace/issues/200
|
||||
- Changed hyphens to underscores for cmake component names due to cpack issue https://github.com/jeremy-rifkin/cpptrace/issues/203
|
||||
|
||||
# v0.7.4
|
||||
|
||||
Added:
|
||||
- Added `<cpptrace/version.hpp>` header with version macros
|
||||
|
||||
Fixes:
|
||||
- Bumped libdwarf to 0.11.0 which fixes a number of dwarf 5 debug fission issues
|
||||
|
||||
Other:
|
||||
- Various improvements to internal testing setup
|
||||
|
||||
# v0.7.3
|
||||
|
||||
Fixed:
|
||||
- Fixed missing include affecting macos https://github.com/jeremy-rifkin/cpptrace/pull/183
|
||||
- Fixed issue with cmake not using the ccache program found by `find_program` https://github.com/jeremy-rifkin/cpptrace/pull/184
|
||||
- Fixed missing include and warnings affecting mingw https://github.com/jeremy-rifkin/cpptrace/pull/186
|
||||
- Fixed issue with identifying inlined call frames when the `DW_TAG_inlined_subroutine` is under a `DW_TAG_lexical_block`
|
||||
- Fixed a typo in the README
|
||||
- Improved unittest support on various configurations
|
||||
- Improved unittest robustness under LTO
|
||||
- Fixed bug signal_demo in the event `fork()` fails
|
||||
|
||||
Added:
|
||||
- Added color overload for `stacktrace_frame::to_string`
|
||||
- Added CMake `export()` definition for cpptrace as well as a definition for libdwarf which currently doesn't provide one
|
||||
|
||||
Changed:
|
||||
- Updated documentation surrounding the signal safe API
|
||||
|
||||
# v0.7.2
|
||||
|
||||
Changes:
|
||||
- Better support for older CMake with using `FetchContent_Declare` from a URL https://github.com/jeremy-rifkin/cpptrace/pull/176
|
||||
- Better portability for page size detection https://github.com/jeremy-rifkin/cpptrace/pull/177
|
||||
- Improved compile times https://github.com/jeremy-rifkin/cpptrace/pull/172
|
||||
- Split up `cpptrace.hpp` into finer-grained headers for lower compile time impact
|
||||
- Some minor readme restructuring
|
||||
|
||||
# v0.7.1
|
||||
|
||||
Added
|
||||
- Better support for finding libunwind on macos https://github.com/jeremy-rifkin/cpptrace/pull/162
|
||||
- Support for libbacktrace under mingw https://github.com/jeremy-rifkin/cpptrace/pull/166
|
||||
|
||||
Fixed
|
||||
- Computation of object address for safe object frames https://github.com/jeremy-rifkin/cpptrace/issues/169
|
||||
- Nested microfmt in cpptrace's namespace due to an ODR problem with libassert https://github.com/jeremy-rifkin/libassert/issues/103
|
||||
- Compilation on iOS https://github.com/jeremy-rifkin/cpptrace/pull/167
|
||||
- Compilation on old MSVC https://github.com/jeremy-rifkin/cpptrace/pull/165
|
||||
- Dbghelp use on 32 bit https://github.com/jeremy-rifkin/cpptrace/issues/170
|
||||
- Warning in brand new cmake due to `FetchContent_Populate` being deprecated https://github.com/jeremy-rifkin/cpptrace/issues/171
|
||||
|
||||
Other changes
|
||||
- Bumped the buffer size for execinfo and CaptureStackBackTrace to 400 frames
|
||||
- Switched to execinfo.h for unwinding on clang/apple clang on macos due to `_Unwind` not working with `-fno-exceptions` https://github.com/jeremy-rifkin/cpptrace/issues/161
|
||||
|
||||
# v0.7.0
|
||||
|
||||
Added
|
||||
- Added `cpptrace::from_current_exception()` and associated exception handler macros to allow tracing of all exceptions,
|
||||
even without cpptrace traced exception objects.
|
||||
|
||||
Fixes:
|
||||
- Fixed issue with using `resolve_safe_object_frame` on `safe_object_frame`s with empty paths
|
||||
- Fixed handling of dwarf 4 rangelist base addresses when a `DW_AT_low_pc` is not present
|
||||
- Fixed use of `-g` with MSVC
|
||||
|
||||
Other changes:
|
||||
- Bazel is now supported on linux (https://github.com/jeremy-rifkin/cpptrace/pull/153)
|
||||
- More work on testing
|
||||
- Some internal refactoring
|
||||
|
||||
# v0.6.3
|
||||
|
||||
Added:
|
||||
- Added a flag to disable inclusion of `<format>` by cpptrace.hpp and the definition of formatter specializations
|
||||
|
||||
Fixes:
|
||||
- Fixed use after free during cleanup of split dwarf information https://github.com/jeremy-rifkin/cpptrace/issues/141
|
||||
- Fixed an issue with TCO by clang on arm interfering with unwinding skip counts for internal methods
|
||||
- Fixed issue with incorrect object addresses being reported on macos when debug maps are used
|
||||
- Fixed issue with handling of split dwarf emitted by clang under dwarf4 mode
|
||||
|
||||
Other changes:
|
||||
- Added note about signal-safe tracing requiring `_dl_find_object` to documentation and fixed errors in the signal-safe
|
||||
tracing docs
|
||||
- Added more configurations to unittest ci setup
|
||||
- Optimized unittest ci matrix setup
|
||||
- Added options for zstd and libdwarf sources if FetchContent is being used to bring the dependencies in
|
||||
- Optimized includes in cpptrace.hpp
|
||||
|
||||
# v0.6.2
|
||||
|
||||
Fixes:
|
||||
|
||||
250
CMakeLists.txt
250
CMakeLists.txt
@ -9,7 +9,7 @@ set(package_name "cpptrace")
|
||||
|
||||
project(
|
||||
cpptrace
|
||||
VERSION 0.6.2
|
||||
VERSION 0.8.2
|
||||
DESCRIPTION "Simple, portable, and self-contained stacktrace library for C++11 and newer "
|
||||
HOMEPAGE_URL "https://github.com/jeremy-rifkin/cpptrace"
|
||||
LANGUAGES C CXX
|
||||
@ -24,9 +24,10 @@ include(CheckCXXSourceCompiles)
|
||||
include(CheckCXXCompilerFlag)
|
||||
|
||||
if(PROJECT_IS_TOP_LEVEL)
|
||||
find_program(CCACHE_FOUND ccache)
|
||||
if(CCACHE_FOUND)
|
||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
|
||||
find_program(CCACHE_PROGRAM ccache)
|
||||
if(CCACHE_PROGRAM)
|
||||
set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE_PROGRAM})
|
||||
set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE_PROGRAM})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@ -64,127 +65,23 @@ else()
|
||||
set(CPPTRACE_BACKTRACE_PATH_DEFINITION "")
|
||||
endif()
|
||||
|
||||
# =============================================== Platform Support ===============================================
|
||||
function(check_support var source includes libraries definitions)
|
||||
set(CMAKE_REQUIRED_INCLUDES "${includes}")
|
||||
list(APPEND CMAKE_REQUIRED_INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
set(CMAKE_REQUIRED_LIBRARIES "${libraries}")
|
||||
set(CMAKE_REQUIRED_DEFINITIONS "${definitions}")
|
||||
string(CONCAT full_source "#include \"${source}\"" ${nonce})
|
||||
check_cxx_source_compiles(${full_source} ${var})
|
||||
set(${var} ${${var}} PARENT_SCOPE)
|
||||
endfunction()
|
||||
# ========================================== Platform Support and Auto-config ==========================================
|
||||
include(cmake/Autoconfig.cmake)
|
||||
|
||||
if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||
check_support(HAS_CXXABI has_cxxabi.cpp "" "" "")
|
||||
endif()
|
||||
# =================================================== Library Setup ====================================================
|
||||
|
||||
if(NOT WIN32)
|
||||
check_support(HAS_UNWIND has_unwind.cpp "" "" "")
|
||||
check_support(HAS_EXECINFO has_execinfo.cpp "" "" "")
|
||||
check_support(HAS_BACKTRACE has_backtrace.cpp "" "backtrace" "${CPPTRACE_BACKTRACE_PATH_DEFINITION}")
|
||||
set(STACKTRACE_LINK_LIB "stdc++_libbacktrace")
|
||||
else()
|
||||
check_support(HAS_STACKWALK has_stackwalk.cpp "" "dbghelp" "")
|
||||
endif()
|
||||
|
||||
if(NOT WIN32 OR MINGW)
|
||||
check_support(HAS_CXX_EXCEPTION_TYPE has_cxx_exception_type.cpp "" "" "")
|
||||
endif()
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
check_support(HAS_DL_FIND_OBJECT has_dl_find_object.cpp "" "dl" "")
|
||||
if(NOT HAS_DL_FIND_OBJECT)
|
||||
check_support(HAS_DLADDR1 has_dladdr1.cpp "" "dl" "")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# =============================================== Autoconfig unwinding ===============================================
|
||||
# Unwind back-ends
|
||||
if(
|
||||
NOT (
|
||||
CPPTRACE_UNWIND_WITH_UNWIND OR
|
||||
CPPTRACE_UNWIND_WITH_LIBUNWIND OR
|
||||
CPPTRACE_UNWIND_WITH_EXECINFO OR
|
||||
CPPTRACE_UNWIND_WITH_WINAPI OR
|
||||
CPPTRACE_UNWIND_WITH_DBGHELP OR
|
||||
CPPTRACE_UNWIND_WITH_NOTHING
|
||||
if(NOT CPPTRACE_BUILD_NO_SYMBOLS)
|
||||
set(
|
||||
debug
|
||||
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-g>
|
||||
$<$<CXX_COMPILER_ID:MSVC>:/DEBUG>
|
||||
)
|
||||
else()
|
||||
add_compile_options($<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-g0>)
|
||||
set(
|
||||
debug
|
||||
)
|
||||
# Attempt to auto-config
|
||||
if(UNIX)
|
||||
if(HAS_UNWIND)
|
||||
set(CPPTRACE_UNWIND_WITH_UNWIND On)
|
||||
message(STATUS "Cpptrace auto config: Using libgcc unwind for unwinding")
|
||||
elseif(HAS_EXECINFO)
|
||||
set(CPPTRACE_UNWIND_WITH_EXECINFO On)
|
||||
message(STATUS "Cpptrace auto config: Using execinfo.h for unwinding")
|
||||
else()
|
||||
set(CPPTRACE_UNWIND_WITH_NOTHING On)
|
||||
message(FATAL_ERROR "Cpptrace auto config: No unwinding back-end seems to be supported, stack tracing will not work. To compile anyway set CPPTRACE_UNWIND_WITH_NOTHING.")
|
||||
endif()
|
||||
elseif(MINGW OR WIN32)
|
||||
if(HAS_STACKWALK)
|
||||
set(CPPTRACE_UNWIND_WITH_DBGHELP On)
|
||||
message(STATUS "Cpptrace auto config: Using dbghelp for unwinding")
|
||||
else()
|
||||
set(CPPTRACE_UNWIND_WITH_WINAPI On)
|
||||
message(STATUS "Cpptrace auto config: Using winapi for unwinding")
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
#message(STATUS "MANUAL CONFIG SPECIFIED")
|
||||
endif()
|
||||
|
||||
# =============================================== Autoconfig symbols ===============================================
|
||||
if(
|
||||
NOT (
|
||||
CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE OR
|
||||
CPPTRACE_GET_SYMBOLS_WITH_LIBDL OR
|
||||
CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE OR
|
||||
CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF OR
|
||||
CPPTRACE_GET_SYMBOLS_WITH_DBGHELP OR
|
||||
CPPTRACE_GET_SYMBOLS_WITH_NOTHING
|
||||
)
|
||||
)
|
||||
if(UNIX)
|
||||
message(STATUS "Cpptrace auto config: Using libdwarf for symbols")
|
||||
set(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF On)
|
||||
elseif(MINGW)
|
||||
message(STATUS "Cpptrace auto config: Using libdwarf + dbghelp for symbols")
|
||||
# Use both dbghelp and libdwarf under mingw: Some files may use pdb symbols, e.g. system dlls like KERNEL32.dll and
|
||||
# ntdll.dll at the very least, but also other libraries linked with may have pdb symbols.
|
||||
set(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF On)
|
||||
set(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP On)
|
||||
else()
|
||||
message(STATUS "Cpptrace auto config: Using dbghelp for symbols")
|
||||
set(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP On)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# =============================================== Autoconfig demangling ===============================================
|
||||
# Handle demangle configuration
|
||||
if(
|
||||
NOT (
|
||||
CPPTRACE_DEMANGLE_WITH_CXXABI OR
|
||||
CPPTRACE_DEMANGLE_WITH_WINAPI OR
|
||||
CPPTRACE_DEMANGLE_WITH_NOTHING
|
||||
)
|
||||
)
|
||||
if(HAS_CXXABI)
|
||||
message(STATUS "Cpptrace auto config: Using cxxabi for demangling")
|
||||
set(CPPTRACE_DEMANGLE_WITH_CXXABI On)
|
||||
elseif(WIN32 AND NOT MINGW)
|
||||
message(STATUS "Cpptrace auto config: Using dbghelp for demangling")
|
||||
set(CPPTRACE_DEMANGLE_WITH_WINAPI On)
|
||||
else()
|
||||
set(CPPTRACE_DEMANGLE_WITH_NOTHING On)
|
||||
endif()
|
||||
else()
|
||||
#message(STATUS "Manual demangling back-end specified")
|
||||
endif()
|
||||
|
||||
# =============================================== Now define the library ===============================================
|
||||
|
||||
# Target that we can modify (can't modify ALIAS targets)
|
||||
# Target name should not be the same as ${PROJECT_NAME}, causes add_subdirectory issues
|
||||
@ -214,11 +111,17 @@ target_sources(
|
||||
src/binary/safe_dl.cpp
|
||||
src/cpptrace.cpp
|
||||
src/ctrace.cpp
|
||||
src/exceptions.cpp
|
||||
src/from_current.cpp
|
||||
src/formatting.cpp
|
||||
src/options.cpp
|
||||
src/utils.cpp
|
||||
src/demangle/demangle_with_cxxabi.cpp
|
||||
src/demangle/demangle_with_nothing.cpp
|
||||
src/demangle/demangle_with_winapi.cpp
|
||||
src/snippets/snippet.cpp
|
||||
src/symbols/dwarf/debug_map_resolver.cpp
|
||||
src/symbols/dwarf/dwarf_options.cpp
|
||||
src/symbols/dwarf/dwarf_resolver.cpp
|
||||
src/symbols/symbols_core.cpp
|
||||
src/symbols/symbols_with_addr2line.cpp
|
||||
@ -233,6 +136,9 @@ target_sources(
|
||||
src/unwind/unwind_with_nothing.cpp
|
||||
src/unwind/unwind_with_unwind.cpp
|
||||
src/unwind/unwind_with_winapi.cpp
|
||||
src/utils/microfmt.cpp
|
||||
src/utils/utils.cpp
|
||||
src/platform/dbghelp_utils.cpp
|
||||
)
|
||||
|
||||
target_include_directories(
|
||||
@ -242,6 +148,12 @@ target_include_directories(
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/cpptrace/>
|
||||
)
|
||||
|
||||
target_include_directories(
|
||||
${target_name}
|
||||
PRIVATE
|
||||
src
|
||||
)
|
||||
|
||||
set(
|
||||
warning_options
|
||||
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wall -Wextra -Werror=return-type -Wundef>
|
||||
@ -264,6 +176,11 @@ target_compile_options(
|
||||
${warning_options}
|
||||
)
|
||||
|
||||
set(CPPTRACE_VERSION_MAJOR ${CMAKE_PROJECT_VERSION_MAJOR})
|
||||
set(CPPTRACE_VERSION_MINOR ${CMAKE_PROJECT_VERSION_MINOR})
|
||||
set(CPPTRACE_VERSION_PATCH ${CMAKE_PROJECT_VERSION_PATCH})
|
||||
configure_file("${PROJECT_SOURCE_DIR}/cmake/in/version-hpp.in" "${PROJECT_BINARY_DIR}/include/cpptrace/version.hpp")
|
||||
|
||||
# ---- Generate Build Info Headers ----
|
||||
|
||||
if(build_type STREQUAL "STATIC")
|
||||
@ -306,12 +223,24 @@ target_compile_features(
|
||||
|
||||
target_compile_definitions(${target_name} PRIVATE NOMINMAX)
|
||||
|
||||
if(HAS_ATTRIBUTE_PACKED)
|
||||
target_compile_definitions(${target_name} PRIVATE HAS_ATTRIBUTE_PACKED)
|
||||
endif()
|
||||
|
||||
if(NOT CPPTRACE_STD_FORMAT)
|
||||
target_compile_definitions(${target_name} PUBLIC CPPTRACE_NO_STD_FORMAT)
|
||||
endif()
|
||||
|
||||
if(CPPTRACE_UNPREFIXED_TRY_CATCH)
|
||||
target_compile_definitions(${target_name} PUBLIC CPPTRACE_UNPREFIXED_TRY_CATCH)
|
||||
endif()
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
|
||||
SET(CMAKE_C_ARCHIVE_FINISH "<CMAKE_RANLIB> -no_warning_for_no_symbols -c <TARGET>")
|
||||
SET(CMAKE_CXX_ARCHIVE_FINISH "<CMAKE_RANLIB> -no_warning_for_no_symbols -c <TARGET>")
|
||||
endif()
|
||||
|
||||
# =============================================== Apply options to build ===============================================
|
||||
# =================================================== Back-end setup ===================================================
|
||||
|
||||
if(HAS_CXX_EXCEPTION_TYPE)
|
||||
target_compile_definitions(${target_name} PUBLIC CPPTRACE_HAS_CXX_EXCEPTION_TYPE)
|
||||
@ -325,6 +254,10 @@ if(HAS_DLADDR1)
|
||||
target_compile_definitions(${target_name} PUBLIC CPPTRACE_HAS_DLADDR1)
|
||||
endif()
|
||||
|
||||
if(HAS_MACH_VM)
|
||||
target_compile_definitions(${target_name} PUBLIC HAS_MACH_VM)
|
||||
endif()
|
||||
|
||||
# Symbols
|
||||
if(CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE)
|
||||
if(NOT HAS_BACKTRACE)
|
||||
@ -383,66 +316,53 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF)
|
||||
find_package(zstd)
|
||||
else()
|
||||
cmake_policy(SET CMP0074 NEW)
|
||||
FetchContent_Declare(
|
||||
zstd
|
||||
GIT_REPOSITORY https://github.com/facebook/zstd.git
|
||||
GIT_TAG 63779c798237346c2b245c546c40b72a5a5913fe # v1.5.5
|
||||
GIT_SHALLOW 1
|
||||
SOURCE_SUBDIR build/cmake
|
||||
)
|
||||
# FetchContent_MakeAvailable(zstd)
|
||||
FetchContent_GetProperties(zstd)
|
||||
if(NOT zstd_POPULATED)
|
||||
FetchContent_Populate(zstd)
|
||||
set(ZSTD_BUILD_PROGRAMS OFF)
|
||||
set(ZSTD_BUILD_CONTRIB OFF)
|
||||
set(ZSTD_BUILD_TESTS OFF)
|
||||
set(ZSTD_BUILD_STATIC ON)
|
||||
set(ZSTD_BUILD_SHARED OFF)
|
||||
set(ZSTD_LEGACY_SUPPORT OFF)
|
||||
add_subdirectory("${zstd_SOURCE_DIR}/build/cmake" "${zstd_BINARY_DIR}")
|
||||
endif()
|
||||
FetchContent_Declare(
|
||||
zstd
|
||||
SOURCE_SUBDIR build/cmake
|
||||
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||
URL "${CPPTRACE_ZSTD_URL}"
|
||||
)
|
||||
FetchContent_MakeAvailable(zstd)
|
||||
endif()
|
||||
# Libdwarf itself
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
|
||||
# set(PIC_ALWAYS TRUE)
|
||||
# set(BUILD_DWARFDUMP FALSE)
|
||||
FetchContent_Declare(
|
||||
libdwarf
|
||||
# GIT_REPOSITORY https://github.com/davea42/libdwarf-code.git
|
||||
# GIT_TAG 6216e185863f41d6f19ab850caabfff7326020d7 # v0.8.0
|
||||
# GIT_TAG 8b0bd09d8c77d45a68cb1bb00a54186a92b683d9 # v0.9.0
|
||||
# GIT_TAG 8cdcc531f310d1c5ae61da469d8056bdd36b77e7 # v0.9.1 + some cmake changes
|
||||
# Using a lightweight mirror that's optimized for clone + configure speed
|
||||
# GIT_TAG ee53f0b6c99fc8cdaa3ae77af0196fb20e16177a # main 5.10.24
|
||||
GIT_REPOSITORY https://github.com/jeremy-rifkin/libdwarf-lite.git
|
||||
# GIT_TAG c78e984f3abbd20f6e01d6f51819e826b1691f65 # v0.8.0
|
||||
# GIT_TAG 71090c680b4c943448ba87a0f1f864f174e4edda # v0.9.0
|
||||
# GIT_TAG 5c0cb251f94b27e90184e6b2d9a0c9c62593babc # v0.9.1 + some cmake changes
|
||||
GIT_TAG 87401f22cd05628d23059cb29ee6448a55c3a88a # v0.9.2
|
||||
GIT_SHALLOW 1
|
||||
)
|
||||
# FetchContent_MakeAvailable(libdwarf)
|
||||
FetchContent_GetProperties(libdwarf)
|
||||
if(NOT libdwarf_POPULATED)
|
||||
set(PIC_ALWAYS TRUE)
|
||||
set(BUILD_DWARFDUMP FALSE)
|
||||
# set(ENABLE_DECOMPRESSION FALSE)
|
||||
FetchContent_Populate(libdwarf)
|
||||
add_subdirectory("${libdwarf_SOURCE_DIR}" "${libdwarf_BINARY_DIR}")
|
||||
FetchContent_Declare(
|
||||
libdwarf
|
||||
GIT_REPOSITORY ${CPPTRACE_LIBDWARF_REPO}
|
||||
GIT_TAG ${CPPTRACE_LIBDWARF_TAG}
|
||||
GIT_SHALLOW ${CPPTRACE_LIBDWARF_SHALLOW}
|
||||
)
|
||||
FetchContent_MakeAvailable(libdwarf)
|
||||
target_include_directories(
|
||||
dwarf
|
||||
PRIVATE
|
||||
${zstd_SOURCE_DIR}/lib
|
||||
)
|
||||
if(CPPTRACE_PROVIDE_EXPORT_SET_FOR_LIBDWARF)
|
||||
export(
|
||||
TARGETS dwarf
|
||||
NAMESPACE libdwarf::
|
||||
FILE "${PROJECT_BINARY_DIR}/libdwarf-targets.cmake"
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
if(CPPTRACE_CONAN)
|
||||
set(dwarf_lib libdwarf::libdwarf)
|
||||
target_link_libraries(${target_name} PRIVATE libdwarf::libdwarf)
|
||||
elseif(CPPTRACE_VCPKG)
|
||||
set(dwarf_lib libdwarf::dwarf)
|
||||
target_link_libraries(${target_name} PRIVATE libdwarf::dwarf)
|
||||
elseif(CPPTRACE_USE_EXTERNAL_LIBDWARF)
|
||||
if(DEFINED LIBDWARF_LIBRARIES)
|
||||
set(dwarf_lib ${LIBDWARF_LIBRARIES})
|
||||
target_link_libraries(${target_name} PRIVATE ${LIBDWARF_LIBRARIES})
|
||||
else()
|
||||
# if LIBDWARF_LIBRARIES wasn't set by find_package, try looking for libdwarf::dwarf-static,
|
||||
@ -460,6 +380,7 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF)
|
||||
else()
|
||||
message(FATAL_ERROR "Couldn't find libdwarf target name to link against")
|
||||
endif()
|
||||
set(dwarf_lib ${LIBDWARF_LIBRARIES})
|
||||
target_link_libraries(${target_name} PRIVATE ${LIBDWARF_LIBRARIES})
|
||||
endif()
|
||||
# There seems to be no consistency at all about where libdwarf decides to place its headers........ Figure out if
|
||||
@ -482,6 +403,7 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF)
|
||||
message(FATAL_ERROR "Couldn't find libdwarf.h")
|
||||
endif()
|
||||
else()
|
||||
set(dwarf_lib libdwarf::dwarf-static)
|
||||
target_link_libraries(${target_name} PRIVATE libdwarf::dwarf-static)
|
||||
endif()
|
||||
if(UNIX)
|
||||
@ -517,6 +439,7 @@ if(CPPTRACE_UNWIND_WITH_LIBUNWIND)
|
||||
endif()
|
||||
endif()
|
||||
if(NOT libunwind_FOUND)
|
||||
if (NOT APPLE)
|
||||
# set_property(GLOBAL PROPERTY FIND_LIBRARY_USE_LIB64_PATHS ON)
|
||||
# set_property(GLOBAL PROPERTY FIND_LIBRARY_USE_LIB32_PATHS ON)
|
||||
find_path(LIBUNWIND_INCLUDE_DIRS NAMES "libunwind.h")
|
||||
@ -537,6 +460,7 @@ if(CPPTRACE_UNWIND_WITH_LIBUNWIND)
|
||||
target_compile_options(${target_name} PRIVATE ${LIBUNWIND_CFLAGS_OTHER})
|
||||
target_include_directories(${target_name} PRIVATE ${LIBUNWIND_INCLUDE_DIRS})
|
||||
target_link_libraries(${target_name} PRIVATE ${LIBUNWIND_LDFLAGS})
|
||||
endif()
|
||||
target_compile_definitions(${target_name} PUBLIC CPPTRACE_UNWIND_WITH_LIBUNWIND UNW_LOCAL_ONLY)
|
||||
endif()
|
||||
endif()
|
||||
@ -589,13 +513,13 @@ if(NOT "${CPPTRACE_HARD_MAX_FRAMES}" STREQUAL "")
|
||||
target_compile_definitions(${target_name} PUBLIC CPPTRACE_HARD_MAX_FRAMES=${CPPTRACE_HARD_MAX_FRAMES})
|
||||
endif()
|
||||
|
||||
# =============================================== Install ===============================================
|
||||
# ====================================================== Install =======================================================
|
||||
|
||||
if(NOT CMAKE_SKIP_INSTALL_RULES)
|
||||
include(cmake/InstallRules.cmake)
|
||||
endif()
|
||||
|
||||
# =============================================== Demo/test ===============================================
|
||||
# ================================================== Demo/test/tools ===================================================
|
||||
|
||||
if(CPPTRACE_BUILD_TESTING)
|
||||
if(PROJECT_IS_TOP_LEVEL)
|
||||
@ -603,3 +527,11 @@ if(CPPTRACE_BUILD_TESTING)
|
||||
endif()
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
|
||||
if(CPPTRACE_BUILD_BENCHMARKING)
|
||||
add_subdirectory(benchmarking)
|
||||
endif()
|
||||
|
||||
if(CPPTRACE_BUILD_TOOLS)
|
||||
add_subdirectory(tools)
|
||||
endif()
|
||||
|
||||
104
MODULE.bazel
Normal file
104
MODULE.bazel
Normal file
@ -0,0 +1,104 @@
|
||||
module(
|
||||
name = "cpptrace",
|
||||
)
|
||||
|
||||
bazel_dep(name = "googletest", version = "1.14.0")
|
||||
bazel_dep(name = "bazel_skylib", version = "1.7.1")
|
||||
bazel_dep(name = "rules_foreign_cc", version = "0.11.1")
|
||||
bazel_dep(name = "zstd", version = "1.5.6")
|
||||
bazel_dep(name = "zlib", version = "1.3.1")
|
||||
bazel_dep(name = "xz", version = "5.4.5.bcr.2")
|
||||
bazel_dep(name = "toolchains_llvm", version = "1.1.2")
|
||||
|
||||
# Configure and register the toolchain.
|
||||
llvm = use_extension("@toolchains_llvm//toolchain/extensions:llvm.bzl", "llvm", dev_dependency = True)
|
||||
|
||||
llvm.toolchain(
|
||||
llvm_versions = {
|
||||
"": "18.1.8",
|
||||
},
|
||||
sha256 = {
|
||||
"": "54ec30358afcc9fb8aa74307db3046f5187f9fb89fb37064cdde906e062ebf36",
|
||||
},
|
||||
strip_prefix = {
|
||||
"": "clang+llvm-18.1.8-x86_64-linux-gnu-ubuntu-18.04",
|
||||
},
|
||||
urls = {
|
||||
"": ["https://github.com/llvm/llvm-project/releases/download/llvmorg-18.1.8/clang+llvm-18.1.8-x86_64-linux-gnu-ubuntu-18.04.tar.xz"],
|
||||
},
|
||||
)
|
||||
|
||||
use_repo(llvm, "llvm_toolchain")
|
||||
|
||||
register_toolchains("@llvm_toolchain//:all", dev_dependency = True)
|
||||
|
||||
http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
||||
|
||||
http_archive(
|
||||
name = "libdwarf",
|
||||
build_file_content =
|
||||
"""
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
load("@rules_foreign_cc//foreign_cc:defs.bzl", "cmake")
|
||||
|
||||
filegroup(
|
||||
name = "sources",
|
||||
srcs = glob(["**/*"]),
|
||||
)
|
||||
cmake(
|
||||
name = "libdwarf",
|
||||
build_args = ["-j12"],
|
||||
lib_source = ":sources",
|
||||
out_static_libs = ["libdwarf.a"],
|
||||
copts = ["-Wall", "-Werror"],
|
||||
deps = [
|
||||
"@zstd",
|
||||
"@zlib"
|
||||
]
|
||||
)
|
||||
""",
|
||||
sha256 = "4ab8ae7b4b7aa42453725054b348f4fdb2460d5ba644199a1305311c718ff416",
|
||||
strip_prefix = "libdwarf-code-0.10.1",
|
||||
url = "https://github.com/davea42/libdwarf-code/archive/refs/tags/v0.10.1.tar.gz",
|
||||
)
|
||||
|
||||
|
||||
|
||||
http_archive(
|
||||
name = "libunwind",
|
||||
build_file_content =
|
||||
"""
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
load("@rules_foreign_cc//foreign_cc:defs.bzl", "configure_make")
|
||||
|
||||
filegroup(
|
||||
name = "sources",
|
||||
srcs = glob(["**/*"]),
|
||||
)
|
||||
configure_make(
|
||||
name = "libunwind",
|
||||
args = ["-j12"],
|
||||
autoreconf = True,
|
||||
configure_in_place = True,
|
||||
autoreconf_options = [
|
||||
"-i",
|
||||
],
|
||||
lib_source = ":sources",
|
||||
out_static_libs = [
|
||||
"libunwind.a",
|
||||
"libunwind-coredump.a",
|
||||
"libunwind-ptrace.a",
|
||||
"libunwind-x86_64.a",
|
||||
"libunwind-generic.a",
|
||||
"libunwind-setjmp.a"
|
||||
],
|
||||
deps = [
|
||||
"@xz//:lzma"
|
||||
]
|
||||
)
|
||||
""",
|
||||
sha256 = "38833b7b1582db7d76485a62a213706c9252b3dab7380069fea5824e823d8e41",
|
||||
strip_prefix = "libunwind-1.8.1",
|
||||
url = "https://github.com/libunwind/libunwind/archive/refs/tags/v1.8.1.tar.gz",
|
||||
)
|
||||
|
||||
851
MODULE.bazel.lock
generated
Normal file
851
MODULE.bazel.lock
generated
Normal file
@ -0,0 +1,851 @@
|
||||
{
|
||||
"lockFileVersion": 11,
|
||||
"registryFileHashes": {
|
||||
"https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497",
|
||||
"https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2",
|
||||
"https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589",
|
||||
"https://bcr.bazel.build/modules/abseil-cpp/20230125.1/MODULE.bazel": "89047429cb0207707b2dface14ba7f8df85273d484c2572755be4bab7ce9c3a0",
|
||||
"https://bcr.bazel.build/modules/abseil-cpp/20230125.1/source.json": "06cc0842d241da0c5edc755edb3c7d0d008d304330e57ecf2d6449fb0b633a82",
|
||||
"https://bcr.bazel.build/modules/apple_support/1.5.0/MODULE.bazel": "50341a62efbc483e8a2a6aec30994a58749bd7b885e18dd96aa8c33031e558ef",
|
||||
"https://bcr.bazel.build/modules/apple_support/1.5.0/source.json": "eb98a7627c0bc486b57f598ad8da50f6625d974c8f723e9ea71bd39f709c9862",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.11.0/source.json": "c9320aa53cd1c441d24bd6b716da087ad7e4ff0d9742a9884587596edfe53015",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.4.1/MODULE.bazel": "a0dcb779424be33100dcae821e9e27e4f2901d9dfd5333efe5ac6a8d7ab75e1d",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.5.0/MODULE.bazel": "32880f5e2945ce6a03d1fbd588e9198c0a959bb42297b2cfaf1685b7bc32e138",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.7.1/source.json": "f121b43eeefc7c29efbd51b83d08631e2347297c95aac9764a701f2a6a2bb953",
|
||||
"https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84",
|
||||
"https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8",
|
||||
"https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4",
|
||||
"https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f",
|
||||
"https://bcr.bazel.build/modules/googletest/1.14.0/source.json": "2478949479000fdd7de9a3d0107ba2c85bb5f961c3ecb1aa448f52549ce310b5",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.9/MODULE.bazel": "4a87a60c927b56ddd67db50c89acaa62f4ce2a1d2149ccb63ffd871d5ce29ebc",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.9/source.json": "cd74d854bf16a9e002fb2ca7b1a421f4403cda29f824a765acd3a8c56f8d43e6",
|
||||
"https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7",
|
||||
"https://bcr.bazel.build/modules/protobuf/21.7/source.json": "bbe500720421e582ff2d18b0802464205138c06056f443184de39fbb8187b09b",
|
||||
"https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0",
|
||||
"https://bcr.bazel.build/modules/protobuf/3.19.6/MODULE.bazel": "9233edc5e1f2ee276a60de3eaa47ac4132302ef9643238f23128fea53ea12858",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.9/source.json": "1f1ba6fea244b616de4a554a0f4983c91a9301640c8fe0dd1d410254115c8430",
|
||||
"https://bcr.bazel.build/modules/rules_foreign_cc/0.11.1/MODULE.bazel": "beeb0dd8d488d3cff57fa12ab3378051a7299aa9de2476d61c1d46f664d6398d",
|
||||
"https://bcr.bazel.build/modules/rules_foreign_cc/0.11.1/source.json": "be2106be697115c10c03c6505a07bd4e259719c6608f08a61d600a560b8cf172",
|
||||
"https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74",
|
||||
"https://bcr.bazel.build/modules/rules_java/7.6.5/MODULE.bazel": "481164be5e02e4cab6e77a36927683263be56b7e36fef918b458d7a8a1ebadb1",
|
||||
"https://bcr.bazel.build/modules/rules_java/7.6.5/source.json": "a805b889531d1690e3c72a7a7e47a870d00323186a9904b36af83aa3d053ee8d",
|
||||
"https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7",
|
||||
"https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/source.json": "a075731e1b46bc8425098512d038d416e966ab19684a10a34f4741295642fc35",
|
||||
"https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0",
|
||||
"https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d",
|
||||
"https://bcr.bazel.build/modules/rules_license/0.0.7/source.json": "355cc5737a0f294e560d52b1b7a6492d4fff2caf0bef1a315df5a298fca2d34a",
|
||||
"https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc",
|
||||
"https://bcr.bazel.build/modules/rules_pkg/0.7.0/source.json": "c2557066e0c0342223ba592510ad3d812d4963b9024831f7f66fd0584dd8c66c",
|
||||
"https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06",
|
||||
"https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7",
|
||||
"https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/source.json": "d57902c052424dfda0e71646cb12668d39c4620ee0544294d9d941e7d12bc3a9",
|
||||
"https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f",
|
||||
"https://bcr.bazel.build/modules/rules_python/0.22.1/MODULE.bazel": "26114f0c0b5e93018c0c066d6673f1a2c3737c7e90af95eff30cfee38d0bbac7",
|
||||
"https://bcr.bazel.build/modules/rules_python/0.23.1/MODULE.bazel": "49ffccf0511cb8414de28321f5fcf2a31312b47c40cc21577144b7447f2bf300",
|
||||
"https://bcr.bazel.build/modules/rules_python/0.23.1/source.json": "a6d9965700e3bd75df4e19140c0e651851bb720d8b9eb280ecd1ee44b92d7646",
|
||||
"https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c",
|
||||
"https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8",
|
||||
"https://bcr.bazel.build/modules/stardoc/0.5.1/source.json": "a96f95e02123320aa015b956f29c00cb818fa891ef823d55148e1a362caacf29",
|
||||
"https://bcr.bazel.build/modules/toolchains_llvm/1.1.2/MODULE.bazel": "402101d6f73115ec49a3a765a3361c1dd90ba3959fa688ccdcd465c36dbbbc52",
|
||||
"https://bcr.bazel.build/modules/toolchains_llvm/1.1.2/source.json": "27f3cf531bc654c719b50411cac94613b7676d63e60962243d485af63e13b9ff",
|
||||
"https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43",
|
||||
"https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/source.json": "f1ef7d3f9e0e26d4b23d1c39b5f5de71f584dd7d1b4ef83d9bbba6ec7a6a6459",
|
||||
"https://bcr.bazel.build/modules/xz/5.4.5.bcr.2/MODULE.bazel": "463976fb85f578a2535421ba4c38fe90657ab348e4b5d5404b75c061602705d0",
|
||||
"https://bcr.bazel.build/modules/xz/5.4.5.bcr.2/source.json": "e735da8a3f396bf200ed06c585f670f7667e08c4e1ed2849bae7c2691bcb10cf",
|
||||
"https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0",
|
||||
"https://bcr.bazel.build/modules/zlib/1.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27",
|
||||
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/MODULE.bazel": "af322bc08976524477c79d1e45e241b6efbeb918c497e8840b8ab116802dda79",
|
||||
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/source.json": "2be409ac3c7601245958cd4fcdff4288be79ed23bd690b4b951f500d54ee6e7d",
|
||||
"https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198",
|
||||
"https://bcr.bazel.build/modules/zstd/1.5.6/MODULE.bazel": "471ebe7d3cdd8c6469390fcf623eb4779ff55fbee0a87f1dc57a1def468b96d4",
|
||||
"https://bcr.bazel.build/modules/zstd/1.5.6/source.json": "02010c3333fc89b44fe861db049968decb6e688411f7f9d4f6791d74f9adfb51"
|
||||
},
|
||||
"selectedYankedVersions": {},
|
||||
"moduleExtensions": {
|
||||
"@@apple_support~//crosstool:setup.bzl%apple_cc_configure_extension": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "PjIds3feoYE8SGbbIq2SFTZy3zmxeO2tQevJZNDo7iY=",
|
||||
"usagesDigest": "aLmqbvowmHkkBPve05yyDNGN7oh7QE9kBADr3QIZTZs=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {
|
||||
"local_config_apple_cc": {
|
||||
"bzlFile": "@@apple_support~//crosstool:setup.bzl",
|
||||
"ruleClassName": "_apple_cc_autoconf",
|
||||
"attributes": {}
|
||||
},
|
||||
"local_config_apple_cc_toolchains": {
|
||||
"bzlFile": "@@apple_support~//crosstool:setup.bzl",
|
||||
"ruleClassName": "_apple_cc_autoconf_toolchains",
|
||||
"attributes": {}
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": [
|
||||
[
|
||||
"apple_support~",
|
||||
"bazel_tools",
|
||||
"bazel_tools"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"@@platforms//host:extension.bzl%host_platform": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "xelQcPZH8+tmuOHVjL9vDxMnnQNMlwj0SlvgoqBkm4U=",
|
||||
"usagesDigest": "meSzxn3DUCcYEhq4HQwExWkWtU4EjriRBQLsZN+Q0SU=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {
|
||||
"host_platform": {
|
||||
"bzlFile": "@@platforms//host:extension.bzl",
|
||||
"ruleClassName": "host_platform_repo",
|
||||
"attributes": {}
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": []
|
||||
}
|
||||
},
|
||||
"@@rules_foreign_cc~//foreign_cc:extensions.bzl%tools": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "5Xt39wqg6Xufojy5gN4ke9V2Mv5ANvdeLlAL1hp6+ic=",
|
||||
"usagesDigest": "WnYOMrYXMz7KcDu0mMpDP5Eue8sHuFMA1dmDT6I124Q=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {
|
||||
"cmake-3.23.2-linux-aarch64": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/Kitware/CMake/releases/download/v3.23.2/cmake-3.23.2-linux-aarch64.tar.gz"
|
||||
],
|
||||
"sha256": "f2654bf780b53f170bbbec44d8ac67d401d24788e590faa53036a89476efa91e",
|
||||
"strip_prefix": "cmake-3.23.2-linux-aarch64",
|
||||
"build_file_content": "load(\"@rules_foreign_cc//toolchains/native_tools:native_tools_toolchain.bzl\", \"native_tool_toolchain\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nfilegroup(\n name = \"cmake_bin\",\n srcs = [\"bin/cmake\"],\n)\n\nfilegroup(\n name = \"cmake_data\",\n srcs = glob(\n [\n \"**\",\n ],\n exclude = [\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"**/* *\",\n ],\n ),\n)\n\nnative_tool_toolchain(\n name = \"cmake_tool\",\n path = \"bin/cmake\",\n target = \":cmake_data\",\n env = {\"CMAKE\": \"$(execpath :cmake_bin)\"},\n tools = [\":cmake_bin\"],\n)\n"
|
||||
}
|
||||
},
|
||||
"rules_foreign_cc_framework_toolchain_macos": {
|
||||
"bzlFile": "@@rules_foreign_cc~//foreign_cc/private/framework:toolchain.bzl",
|
||||
"ruleClassName": "framework_toolchain_repository",
|
||||
"attributes": {
|
||||
"commands_src": "@rules_foreign_cc//foreign_cc/private/framework/toolchains:macos_commands.bzl",
|
||||
"exec_compatible_with": [
|
||||
"@platforms//os:macos"
|
||||
]
|
||||
}
|
||||
},
|
||||
"ninja_1.12.0_mac": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/ninja-build/ninja/releases/download/v1.12.0/ninja-mac.zip"
|
||||
],
|
||||
"sha256": "19806019c9623a062c3d9fa0d5f45b633a3d150f88e73fbd6c0ff6ea5534df10",
|
||||
"strip_prefix": "",
|
||||
"build_file_content": "load(\"@rules_foreign_cc//toolchains/native_tools:native_tools_toolchain.bzl\", \"native_tool_toolchain\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nfilegroup(\n name = \"ninja_bin\",\n srcs = [\"ninja\"],\n)\n\nnative_tool_toolchain(\n name = \"ninja_tool\",\n env = {\"NINJA\": \"$(execpath :ninja_bin)\"},\n path = \"$(execpath :ninja_bin)\",\n target = \":ninja_bin\",\n)\n"
|
||||
}
|
||||
},
|
||||
"ninja_1.12.0_mac_aarch64": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/ninja-build/ninja/releases/download/v1.12.0/ninja-mac.zip"
|
||||
],
|
||||
"sha256": "19806019c9623a062c3d9fa0d5f45b633a3d150f88e73fbd6c0ff6ea5534df10",
|
||||
"strip_prefix": "",
|
||||
"build_file_content": "load(\"@rules_foreign_cc//toolchains/native_tools:native_tools_toolchain.bzl\", \"native_tool_toolchain\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nfilegroup(\n name = \"ninja_bin\",\n srcs = [\"ninja\"],\n)\n\nnative_tool_toolchain(\n name = \"ninja_tool\",\n env = {\"NINJA\": \"$(execpath :ninja_bin)\"},\n path = \"$(execpath :ninja_bin)\",\n target = \":ninja_bin\",\n)\n"
|
||||
}
|
||||
},
|
||||
"gnumake_src": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"build_file_content": "filegroup(\n name = \"all_srcs\",\n srcs = glob([\"**\"]),\n visibility = [\"//visibility:public\"],\n)\n",
|
||||
"sha256": "dd16fb1d67bfab79a72f5e8390735c49e3e8e70b4945a15ab1f81ddb78658fb3",
|
||||
"strip_prefix": "make-4.4.1",
|
||||
"urls": [
|
||||
"https://mirror.bazel.build/ftpmirror.gnu.org/gnu/make/make-4.4.1.tar.gz",
|
||||
"http://ftpmirror.gnu.org/gnu/make/make-4.4.1.tar.gz"
|
||||
]
|
||||
}
|
||||
},
|
||||
"gettext_runtime": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"build_file_content": "\ncc_import(\n name = \"gettext_runtime\",\n shared_library = \"bin/libintl-8.dll\",\n visibility = [\"//visibility:public\"],\n)\n ",
|
||||
"sha256": "1f4269c0e021076d60a54e98da6f978a3195013f6de21674ba0edbc339c5b079",
|
||||
"urls": [
|
||||
"https://mirror.bazel.build/download.gnome.org/binaries/win64/dependencies/gettext-runtime_0.18.1.1-2_win64.zip",
|
||||
"https://download.gnome.org/binaries/win64/dependencies/gettext-runtime_0.18.1.1-2_win64.zip"
|
||||
]
|
||||
}
|
||||
},
|
||||
"cmake_src": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"build_file_content": "filegroup(\n name = \"all_srcs\",\n srcs = glob([\"**\"]),\n visibility = [\"//visibility:public\"],\n)\n",
|
||||
"sha256": "f316b40053466f9a416adf981efda41b160ca859e97f6a484b447ea299ff26aa",
|
||||
"strip_prefix": "cmake-3.23.2",
|
||||
"urls": [
|
||||
"https://github.com/Kitware/CMake/releases/download/v3.23.2/cmake-3.23.2.tar.gz"
|
||||
],
|
||||
"patches": [
|
||||
"@@rules_foreign_cc~//toolchains:cmake-c++11.patch"
|
||||
]
|
||||
}
|
||||
},
|
||||
"bazel_skylib": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.2.1/bazel-skylib-1.2.1.tar.gz",
|
||||
"https://github.com/bazelbuild/bazel-skylib/releases/download/1.2.1/bazel-skylib-1.2.1.tar.gz"
|
||||
],
|
||||
"sha256": "f7be3474d42aae265405a592bb7da8e171919d74c16f082a5457840f06054728"
|
||||
}
|
||||
},
|
||||
"cmake-3.23.2-macos-universal": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/Kitware/CMake/releases/download/v3.23.2/cmake-3.23.2-macos-universal.tar.gz"
|
||||
],
|
||||
"sha256": "853a0f9af148c5ef47282ffffee06c4c9f257be2635936755f39ca13c3286c88",
|
||||
"strip_prefix": "cmake-3.23.2-macos-universal/CMake.app/Contents",
|
||||
"build_file_content": "load(\"@rules_foreign_cc//toolchains/native_tools:native_tools_toolchain.bzl\", \"native_tool_toolchain\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nfilegroup(\n name = \"cmake_bin\",\n srcs = [\"bin/cmake\"],\n)\n\nfilegroup(\n name = \"cmake_data\",\n srcs = glob(\n [\n \"**\",\n ],\n exclude = [\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"**/* *\",\n ],\n ),\n)\n\nnative_tool_toolchain(\n name = \"cmake_tool\",\n path = \"bin/cmake\",\n target = \":cmake_data\",\n env = {\"CMAKE\": \"$(execpath :cmake_bin)\"},\n tools = [\":cmake_bin\"],\n)\n"
|
||||
}
|
||||
},
|
||||
"ninja_1.12.0_win": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/ninja-build/ninja/releases/download/v1.12.0/ninja-win.zip"
|
||||
],
|
||||
"sha256": "51d99be9ceea8835edf536d52d47fa4c316aa332e57f71a08df5bd059da11417",
|
||||
"strip_prefix": "",
|
||||
"build_file_content": "load(\"@rules_foreign_cc//toolchains/native_tools:native_tools_toolchain.bzl\", \"native_tool_toolchain\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nfilegroup(\n name = \"ninja_bin\",\n srcs = [\"ninja.exe\"],\n)\n\nnative_tool_toolchain(\n name = \"ninja_tool\",\n env = {\"NINJA\": \"$(execpath :ninja_bin)\"},\n path = \"$(execpath :ninja_bin)\",\n target = \":ninja_bin\",\n)\n"
|
||||
}
|
||||
},
|
||||
"meson_src": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"build_file_content": "exports_files([\"meson.py\"])\n\nfilegroup(\n name = \"runtime\",\n srcs = glob([\"mesonbuild/**\"]),\n visibility = [\"//visibility:public\"],\n)\n",
|
||||
"sha256": "d04b541f97ca439fb82fab7d0d480988be4bd4e62563a5ca35fadb5400727b1c",
|
||||
"strip_prefix": "meson-1.1.1",
|
||||
"urls": [
|
||||
"https://mirror.bazel.build/github.com/mesonbuild/meson/releases/download/1.1.1/meson-1.1.1.tar.gz",
|
||||
"https://github.com/mesonbuild/meson/releases/download/1.1.1/meson-1.1.1.tar.gz"
|
||||
]
|
||||
}
|
||||
},
|
||||
"rules_foreign_cc_framework_toolchain_freebsd": {
|
||||
"bzlFile": "@@rules_foreign_cc~//foreign_cc/private/framework:toolchain.bzl",
|
||||
"ruleClassName": "framework_toolchain_repository",
|
||||
"attributes": {
|
||||
"commands_src": "@rules_foreign_cc//foreign_cc/private/framework/toolchains:freebsd_commands.bzl",
|
||||
"exec_compatible_with": [
|
||||
"@platforms//os:freebsd"
|
||||
]
|
||||
}
|
||||
},
|
||||
"rules_foreign_cc_framework_toolchain_linux": {
|
||||
"bzlFile": "@@rules_foreign_cc~//foreign_cc/private/framework:toolchain.bzl",
|
||||
"ruleClassName": "framework_toolchain_repository",
|
||||
"attributes": {
|
||||
"commands_src": "@rules_foreign_cc//foreign_cc/private/framework/toolchains:linux_commands.bzl",
|
||||
"exec_compatible_with": [
|
||||
"@platforms//os:linux"
|
||||
]
|
||||
}
|
||||
},
|
||||
"rules_python": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"sha256": "84aec9e21cc56fbc7f1335035a71c850d1b9b5cc6ff497306f84cced9a769841",
|
||||
"strip_prefix": "rules_python-0.23.1",
|
||||
"url": "https://github.com/bazelbuild/rules_python/archive/refs/tags/0.23.1.tar.gz"
|
||||
}
|
||||
},
|
||||
"pkgconfig_src": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"build_file_content": "filegroup(\n name = \"all_srcs\",\n srcs = glob([\"**\"]),\n visibility = [\"//visibility:public\"],\n)\n",
|
||||
"sha256": "6fc69c01688c9458a57eb9a1664c9aba372ccda420a02bf4429fe610e7e7d591",
|
||||
"strip_prefix": "pkg-config-0.29.2",
|
||||
"patches": [
|
||||
"@@rules_foreign_cc~//toolchains:pkgconfig-detectenv.patch",
|
||||
"@@rules_foreign_cc~//toolchains:pkgconfig-makefile-vc.patch",
|
||||
"@@rules_foreign_cc~//toolchains:pkgconfig-builtin-glib-int-conversion.patch"
|
||||
],
|
||||
"urls": [
|
||||
"https://pkgconfig.freedesktop.org/releases/pkg-config-0.29.2.tar.gz",
|
||||
"https://mirror.bazel.build/pkgconfig.freedesktop.org/releases/pkg-config-0.29.2.tar.gz"
|
||||
]
|
||||
}
|
||||
},
|
||||
"ninja_build_src": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"build_file_content": "filegroup(\n name = \"all_srcs\",\n srcs = glob([\"**\"]),\n visibility = [\"//visibility:public\"],\n)\n",
|
||||
"integrity": "sha256-iyyGzUg9x/y3l1xexzKRNdIQCZqJvH2wWQoHsLv+SaU=",
|
||||
"strip_prefix": "ninja-1.12.0",
|
||||
"urls": [
|
||||
"https://mirror.bazel.build/github.com/ninja-build/ninja/archive/v1.12.0.tar.gz",
|
||||
"https://github.com/ninja-build/ninja/archive/v1.12.0.tar.gz"
|
||||
]
|
||||
}
|
||||
},
|
||||
"glib_src": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"build_file_content": "\ncc_import(\n name = \"msvc_hdr\",\n hdrs = [\"msvc_recommended_pragmas.h\"],\n visibility = [\"//visibility:public\"],\n)\n ",
|
||||
"sha256": "bc96f63112823b7d6c9f06572d2ad626ddac7eb452c04d762592197f6e07898e",
|
||||
"strip_prefix": "glib-2.26.1",
|
||||
"urls": [
|
||||
"https://mirror.bazel.build/download.gnome.org/sources/glib/2.26/glib-2.26.1.tar.gz",
|
||||
"https://download.gnome.org/sources/glib/2.26/glib-2.26.1.tar.gz"
|
||||
]
|
||||
}
|
||||
},
|
||||
"cmake-3.23.2-windows-x86_64": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/Kitware/CMake/releases/download/v3.23.2/cmake-3.23.2-windows-x86_64.zip"
|
||||
],
|
||||
"sha256": "2329387f3166b84c25091c86389fb891193967740c9bcf01e7f6d3306f7ffda0",
|
||||
"strip_prefix": "cmake-3.23.2-windows-x86_64",
|
||||
"build_file_content": "load(\"@rules_foreign_cc//toolchains/native_tools:native_tools_toolchain.bzl\", \"native_tool_toolchain\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nfilegroup(\n name = \"cmake_bin\",\n srcs = [\"bin/cmake.exe\"],\n)\n\nfilegroup(\n name = \"cmake_data\",\n srcs = glob(\n [\n \"**\",\n ],\n exclude = [\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"**/* *\",\n ],\n ),\n)\n\nnative_tool_toolchain(\n name = \"cmake_tool\",\n path = \"bin/cmake.exe\",\n target = \":cmake_data\",\n env = {\"CMAKE\": \"$(execpath :cmake_bin)\"},\n tools = [\":cmake_bin\"],\n)\n"
|
||||
}
|
||||
},
|
||||
"glib_runtime": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"build_file_content": "\nexports_files(\n [\n \"bin/libgio-2.0-0.dll\",\n \"bin/libglib-2.0-0.dll\",\n \"bin/libgmodule-2.0-0.dll\",\n \"bin/libgobject-2.0-0.dll\",\n \"bin/libgthread-2.0-0.dll\",\n ],\n visibility = [\"//visibility:public\"],\n)\n ",
|
||||
"sha256": "88d857087e86f16a9be651ee7021880b3f7ba050d34a1ed9f06113b8799cb973",
|
||||
"urls": [
|
||||
"https://mirror.bazel.build/download.gnome.org/binaries/win64/glib/2.26/glib_2.26.1-1_win64.zip",
|
||||
"https://download.gnome.org/binaries/win64/glib/2.26/glib_2.26.1-1_win64.zip"
|
||||
]
|
||||
}
|
||||
},
|
||||
"rules_foreign_cc_framework_toolchains": {
|
||||
"bzlFile": "@@rules_foreign_cc~//foreign_cc/private/framework:toolchain.bzl",
|
||||
"ruleClassName": "framework_toolchain_repository_hub",
|
||||
"attributes": {}
|
||||
},
|
||||
"glib_dev": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"build_file_content": "\nload(\"@rules_cc//cc:defs.bzl\", \"cc_library\")\n\ncc_import(\n name = \"glib_dev\",\n hdrs = glob([\"include/**\"]),\n shared_library = \"@glib_runtime//:bin/libglib-2.0-0.dll\",\n visibility = [\"//visibility:public\"],\n)\n ",
|
||||
"sha256": "bdf18506df304d38be98a4b3f18055b8b8cca81beabecad0eece6ce95319c369",
|
||||
"urls": [
|
||||
"https://mirror.bazel.build/download.gnome.org/binaries/win64/glib/2.26/glib-dev_2.26.1-1_win64.zip",
|
||||
"https://download.gnome.org/binaries/win64/glib/2.26/glib-dev_2.26.1-1_win64.zip"
|
||||
]
|
||||
}
|
||||
},
|
||||
"cmake_3.23.2_toolchains": {
|
||||
"bzlFile": "@@rules_foreign_cc~//toolchains:prebuilt_toolchains_repository.bzl",
|
||||
"ruleClassName": "prebuilt_toolchains_repository",
|
||||
"attributes": {
|
||||
"repos": {
|
||||
"cmake-3.23.2-linux-aarch64": [
|
||||
"@platforms//cpu:aarch64",
|
||||
"@platforms//os:linux"
|
||||
],
|
||||
"cmake-3.23.2-linux-x86_64": [
|
||||
"@platforms//cpu:x86_64",
|
||||
"@platforms//os:linux"
|
||||
],
|
||||
"cmake-3.23.2-macos-universal": [
|
||||
"@platforms//os:macos"
|
||||
],
|
||||
"cmake-3.23.2-windows-i386": [
|
||||
"@platforms//cpu:x86_32",
|
||||
"@platforms//os:windows"
|
||||
],
|
||||
"cmake-3.23.2-windows-x86_64": [
|
||||
"@platforms//cpu:x86_64",
|
||||
"@platforms//os:windows"
|
||||
]
|
||||
},
|
||||
"tool": "cmake"
|
||||
}
|
||||
},
|
||||
"ninja_1.12.0_linux-aarch64": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/ninja-build/ninja/releases/download/v1.12.0/ninja-linux-aarch64.zip"
|
||||
],
|
||||
"sha256": "375a49c79095334c88338ff15f90730e08a4d03997ef660f48f11ee7e450db7a",
|
||||
"strip_prefix": "",
|
||||
"build_file_content": "load(\"@rules_foreign_cc//toolchains/native_tools:native_tools_toolchain.bzl\", \"native_tool_toolchain\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nfilegroup(\n name = \"ninja_bin\",\n srcs = [\"ninja\"],\n)\n\nnative_tool_toolchain(\n name = \"ninja_tool\",\n env = {\"NINJA\": \"$(execpath :ninja_bin)\"},\n path = \"$(execpath :ninja_bin)\",\n target = \":ninja_bin\",\n)\n"
|
||||
}
|
||||
},
|
||||
"cmake-3.23.2-windows-i386": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/Kitware/CMake/releases/download/v3.23.2/cmake-3.23.2-windows-i386.zip"
|
||||
],
|
||||
"sha256": "6a4fcd6a2315b93cb23c93507efccacc30c449c2bf98f14d6032bb226c582e07",
|
||||
"strip_prefix": "cmake-3.23.2-windows-i386",
|
||||
"build_file_content": "load(\"@rules_foreign_cc//toolchains/native_tools:native_tools_toolchain.bzl\", \"native_tool_toolchain\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nfilegroup(\n name = \"cmake_bin\",\n srcs = [\"bin/cmake.exe\"],\n)\n\nfilegroup(\n name = \"cmake_data\",\n srcs = glob(\n [\n \"**\",\n ],\n exclude = [\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"**/* *\",\n ],\n ),\n)\n\nnative_tool_toolchain(\n name = \"cmake_tool\",\n path = \"bin/cmake.exe\",\n target = \":cmake_data\",\n env = {\"CMAKE\": \"$(execpath :cmake_bin)\"},\n tools = [\":cmake_bin\"],\n)\n"
|
||||
}
|
||||
},
|
||||
"ninja_1.12.0_linux": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/ninja-build/ninja/releases/download/v1.12.0/ninja-linux.zip"
|
||||
],
|
||||
"sha256": "ddc96efa3c7c9d41de733d15e2eda07a8a212555cb43f35d727e080d2ca687ab",
|
||||
"strip_prefix": "",
|
||||
"build_file_content": "load(\"@rules_foreign_cc//toolchains/native_tools:native_tools_toolchain.bzl\", \"native_tool_toolchain\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nfilegroup(\n name = \"ninja_bin\",\n srcs = [\"ninja\"],\n)\n\nnative_tool_toolchain(\n name = \"ninja_tool\",\n env = {\"NINJA\": \"$(execpath :ninja_bin)\"},\n path = \"$(execpath :ninja_bin)\",\n target = \":ninja_bin\",\n)\n"
|
||||
}
|
||||
},
|
||||
"cmake-3.23.2-linux-x86_64": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/Kitware/CMake/releases/download/v3.23.2/cmake-3.23.2-linux-x86_64.tar.gz"
|
||||
],
|
||||
"sha256": "aaced6f745b86ce853661a595bdac6c5314a60f8181b6912a0a4920acfa32708",
|
||||
"strip_prefix": "cmake-3.23.2-linux-x86_64",
|
||||
"build_file_content": "load(\"@rules_foreign_cc//toolchains/native_tools:native_tools_toolchain.bzl\", \"native_tool_toolchain\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nfilegroup(\n name = \"cmake_bin\",\n srcs = [\"bin/cmake\"],\n)\n\nfilegroup(\n name = \"cmake_data\",\n srcs = glob(\n [\n \"**\",\n ],\n exclude = [\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"**/* *\",\n ],\n ),\n)\n\nnative_tool_toolchain(\n name = \"cmake_tool\",\n path = \"bin/cmake\",\n target = \":cmake_data\",\n env = {\"CMAKE\": \"$(execpath :cmake_bin)\"},\n tools = [\":cmake_bin\"],\n)\n"
|
||||
}
|
||||
},
|
||||
"ninja_1.12.0_toolchains": {
|
||||
"bzlFile": "@@rules_foreign_cc~//toolchains:prebuilt_toolchains_repository.bzl",
|
||||
"ruleClassName": "prebuilt_toolchains_repository",
|
||||
"attributes": {
|
||||
"repos": {
|
||||
"ninja_1.12.0_linux": [
|
||||
"@platforms//cpu:x86_64",
|
||||
"@platforms//os:linux"
|
||||
],
|
||||
"ninja_1.12.0_linux-aarch64": [
|
||||
"@platforms//cpu:aarch64",
|
||||
"@platforms//os:linux"
|
||||
],
|
||||
"ninja_1.12.0_mac": [
|
||||
"@platforms//cpu:x86_64",
|
||||
"@platforms//os:macos"
|
||||
],
|
||||
"ninja_1.12.0_mac_aarch64": [
|
||||
"@platforms//cpu:aarch64",
|
||||
"@platforms//os:macos"
|
||||
],
|
||||
"ninja_1.12.0_win": [
|
||||
"@platforms//cpu:x86_64",
|
||||
"@platforms//os:windows"
|
||||
]
|
||||
},
|
||||
"tool": "ninja"
|
||||
}
|
||||
},
|
||||
"rules_foreign_cc_framework_toolchain_windows": {
|
||||
"bzlFile": "@@rules_foreign_cc~//foreign_cc/private/framework:toolchain.bzl",
|
||||
"ruleClassName": "framework_toolchain_repository",
|
||||
"attributes": {
|
||||
"commands_src": "@rules_foreign_cc//foreign_cc/private/framework/toolchains:windows_commands.bzl",
|
||||
"exec_compatible_with": [
|
||||
"@platforms//os:windows"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": [
|
||||
[
|
||||
"rules_foreign_cc~",
|
||||
"bazel_tools",
|
||||
"bazel_tools"
|
||||
],
|
||||
[
|
||||
"rules_foreign_cc~",
|
||||
"rules_foreign_cc",
|
||||
"rules_foreign_cc~"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"@@rules_python~//python/extensions:python.bzl%python": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "XaaZIw4dO4l6naftU5IBdrfCE1mOmelaT/Sq9uyBnhs=",
|
||||
"usagesDigest": "XRXGQ1YSlgZzzO0pux+3DEHfP/c70L/kznvRIwakvlw=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {
|
||||
"python_3_11_aarch64-unknown-linux-gnu": {
|
||||
"bzlFile": "@@rules_python~//python:repositories.bzl",
|
||||
"ruleClassName": "python_repository",
|
||||
"attributes": {
|
||||
"sha256": "debf15783bdcb5530504f533d33fda75a7b905cec5361ae8f33da5ba6599f8b4",
|
||||
"patches": [],
|
||||
"platform": "aarch64-unknown-linux-gnu",
|
||||
"python_version": "3.11.1",
|
||||
"release_filename": "20230116/cpython-3.11.1+20230116-aarch64-unknown-linux-gnu-install_only.tar.gz",
|
||||
"urls": [
|
||||
"https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1+20230116-aarch64-unknown-linux-gnu-install_only.tar.gz"
|
||||
],
|
||||
"distutils_content": "",
|
||||
"strip_prefix": "python",
|
||||
"ignore_root_user_error": false
|
||||
}
|
||||
},
|
||||
"python_3_9": {
|
||||
"bzlFile": "@@rules_python~//python/private:toolchains_repo.bzl",
|
||||
"ruleClassName": "toolchain_aliases",
|
||||
"attributes": {
|
||||
"python_version": "3.9.16",
|
||||
"user_repository_name": "python_3_9"
|
||||
}
|
||||
},
|
||||
"python_3_11_aarch64-apple-darwin": {
|
||||
"bzlFile": "@@rules_python~//python:repositories.bzl",
|
||||
"ruleClassName": "python_repository",
|
||||
"attributes": {
|
||||
"sha256": "4918cdf1cab742a90f85318f88b8122aeaa2d04705803c7b6e78e81a3dd40f80",
|
||||
"patches": [],
|
||||
"platform": "aarch64-apple-darwin",
|
||||
"python_version": "3.11.1",
|
||||
"release_filename": "20230116/cpython-3.11.1+20230116-aarch64-apple-darwin-install_only.tar.gz",
|
||||
"urls": [
|
||||
"https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1+20230116-aarch64-apple-darwin-install_only.tar.gz"
|
||||
],
|
||||
"distutils_content": "",
|
||||
"strip_prefix": "python",
|
||||
"ignore_root_user_error": false
|
||||
}
|
||||
},
|
||||
"pythons_hub": {
|
||||
"bzlFile": "@@rules_python~//python/extensions/private:pythons_hub.bzl",
|
||||
"ruleClassName": "hub_repo",
|
||||
"attributes": {
|
||||
"toolchain_prefixes": [
|
||||
"_0000_python_3_9_",
|
||||
"_0001_python_3_11_"
|
||||
],
|
||||
"toolchain_python_versions": [
|
||||
"3.9",
|
||||
"3.11"
|
||||
],
|
||||
"toolchain_set_python_version_constraints": [
|
||||
"True",
|
||||
"False"
|
||||
],
|
||||
"toolchain_user_repository_names": [
|
||||
"python_3_9",
|
||||
"python_3_11"
|
||||
]
|
||||
}
|
||||
},
|
||||
"python_3_11_x86_64-pc-windows-msvc": {
|
||||
"bzlFile": "@@rules_python~//python:repositories.bzl",
|
||||
"ruleClassName": "python_repository",
|
||||
"attributes": {
|
||||
"sha256": "edc08979cb0666a597466176511529c049a6f0bba8adf70df441708f766de5bf",
|
||||
"patches": [],
|
||||
"platform": "x86_64-pc-windows-msvc",
|
||||
"python_version": "3.11.1",
|
||||
"release_filename": "20230116/cpython-3.11.1+20230116-x86_64-pc-windows-msvc-shared-install_only.tar.gz",
|
||||
"urls": [
|
||||
"https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1+20230116-x86_64-pc-windows-msvc-shared-install_only.tar.gz"
|
||||
],
|
||||
"distutils_content": "",
|
||||
"strip_prefix": "python",
|
||||
"ignore_root_user_error": false
|
||||
}
|
||||
},
|
||||
"python_3_9_aarch64-apple-darwin": {
|
||||
"bzlFile": "@@rules_python~//python:repositories.bzl",
|
||||
"ruleClassName": "python_repository",
|
||||
"attributes": {
|
||||
"sha256": "c1de1d854717a6245f45262ef1bb17b09e2c587590e7e3f406593c143ff875bd",
|
||||
"patches": [],
|
||||
"platform": "aarch64-apple-darwin",
|
||||
"python_version": "3.9.16",
|
||||
"release_filename": "20230507/cpython-3.9.16+20230507-aarch64-apple-darwin-install_only.tar.gz",
|
||||
"urls": [
|
||||
"https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16+20230507-aarch64-apple-darwin-install_only.tar.gz"
|
||||
],
|
||||
"distutils_content": "",
|
||||
"strip_prefix": "python",
|
||||
"ignore_root_user_error": false
|
||||
}
|
||||
},
|
||||
"python_3_9_x86_64-pc-windows-msvc": {
|
||||
"bzlFile": "@@rules_python~//python:repositories.bzl",
|
||||
"ruleClassName": "python_repository",
|
||||
"attributes": {
|
||||
"sha256": "cdabb47204e96ce7ea31fbd0b5ed586114dd7d8f8eddf60a509a7f70b48a1c5e",
|
||||
"patches": [],
|
||||
"platform": "x86_64-pc-windows-msvc",
|
||||
"python_version": "3.9.16",
|
||||
"release_filename": "20230507/cpython-3.9.16+20230507-x86_64-pc-windows-msvc-shared-install_only.tar.gz",
|
||||
"urls": [
|
||||
"https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16+20230507-x86_64-pc-windows-msvc-shared-install_only.tar.gz"
|
||||
],
|
||||
"distutils_content": "",
|
||||
"strip_prefix": "python",
|
||||
"ignore_root_user_error": false
|
||||
}
|
||||
},
|
||||
"python_3_9_ppc64le-unknown-linux-gnu": {
|
||||
"bzlFile": "@@rules_python~//python:repositories.bzl",
|
||||
"ruleClassName": "python_repository",
|
||||
"attributes": {
|
||||
"sha256": "ff3ac35c58f67839aff9b5185a976abd3d1abbe61af02089f7105e876c1fe284",
|
||||
"patches": [],
|
||||
"platform": "ppc64le-unknown-linux-gnu",
|
||||
"python_version": "3.9.16",
|
||||
"release_filename": "20230507/cpython-3.9.16+20230507-ppc64le-unknown-linux-gnu-install_only.tar.gz",
|
||||
"urls": [
|
||||
"https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16+20230507-ppc64le-unknown-linux-gnu-install_only.tar.gz"
|
||||
],
|
||||
"distutils_content": "",
|
||||
"strip_prefix": "python",
|
||||
"ignore_root_user_error": false
|
||||
}
|
||||
},
|
||||
"python_3_9_aarch64-unknown-linux-gnu": {
|
||||
"bzlFile": "@@rules_python~//python:repositories.bzl",
|
||||
"ruleClassName": "python_repository",
|
||||
"attributes": {
|
||||
"sha256": "f629b75ebfcafe9ceee2e796b7e4df5cf8dbd14f3c021afca078d159ab797acf",
|
||||
"patches": [],
|
||||
"platform": "aarch64-unknown-linux-gnu",
|
||||
"python_version": "3.9.16",
|
||||
"release_filename": "20230507/cpython-3.9.16+20230507-aarch64-unknown-linux-gnu-install_only.tar.gz",
|
||||
"urls": [
|
||||
"https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16+20230507-aarch64-unknown-linux-gnu-install_only.tar.gz"
|
||||
],
|
||||
"distutils_content": "",
|
||||
"strip_prefix": "python",
|
||||
"ignore_root_user_error": false
|
||||
}
|
||||
},
|
||||
"python_aliases": {
|
||||
"bzlFile": "@@rules_python~//python/private:toolchains_repo.bzl",
|
||||
"ruleClassName": "multi_toolchain_aliases",
|
||||
"attributes": {
|
||||
"python_versions": {
|
||||
"3.9": "python_3_9",
|
||||
"3.11": "python_3_11"
|
||||
}
|
||||
}
|
||||
},
|
||||
"python_3_11": {
|
||||
"bzlFile": "@@rules_python~//python/private:toolchains_repo.bzl",
|
||||
"ruleClassName": "toolchain_aliases",
|
||||
"attributes": {
|
||||
"python_version": "3.11.1",
|
||||
"user_repository_name": "python_3_11"
|
||||
}
|
||||
},
|
||||
"python_3_11_x86_64-apple-darwin": {
|
||||
"bzlFile": "@@rules_python~//python:repositories.bzl",
|
||||
"ruleClassName": "python_repository",
|
||||
"attributes": {
|
||||
"sha256": "20a4203d069dc9b710f70b09e7da2ce6f473d6b1110f9535fb6f4c469ed54733",
|
||||
"patches": [],
|
||||
"platform": "x86_64-apple-darwin",
|
||||
"python_version": "3.11.1",
|
||||
"release_filename": "20230116/cpython-3.11.1+20230116-x86_64-apple-darwin-install_only.tar.gz",
|
||||
"urls": [
|
||||
"https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1+20230116-x86_64-apple-darwin-install_only.tar.gz"
|
||||
],
|
||||
"distutils_content": "",
|
||||
"strip_prefix": "python",
|
||||
"ignore_root_user_error": false
|
||||
}
|
||||
},
|
||||
"python_3_9_x86_64-apple-darwin": {
|
||||
"bzlFile": "@@rules_python~//python:repositories.bzl",
|
||||
"ruleClassName": "python_repository",
|
||||
"attributes": {
|
||||
"sha256": "3abc4d5fbbc80f5f848f280927ac5d13de8dc03aabb6ae65d8247cbb68e6f6bf",
|
||||
"patches": [],
|
||||
"platform": "x86_64-apple-darwin",
|
||||
"python_version": "3.9.16",
|
||||
"release_filename": "20230507/cpython-3.9.16+20230507-x86_64-apple-darwin-install_only.tar.gz",
|
||||
"urls": [
|
||||
"https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16+20230507-x86_64-apple-darwin-install_only.tar.gz"
|
||||
],
|
||||
"distutils_content": "",
|
||||
"strip_prefix": "python",
|
||||
"ignore_root_user_error": false
|
||||
}
|
||||
},
|
||||
"python_3_9_x86_64-unknown-linux-gnu": {
|
||||
"bzlFile": "@@rules_python~//python:repositories.bzl",
|
||||
"ruleClassName": "python_repository",
|
||||
"attributes": {
|
||||
"sha256": "2b6e146234a4ef2a8946081fc3fbfffe0765b80b690425a49ebe40b47c33445b",
|
||||
"patches": [],
|
||||
"platform": "x86_64-unknown-linux-gnu",
|
||||
"python_version": "3.9.16",
|
||||
"release_filename": "20230507/cpython-3.9.16+20230507-x86_64-unknown-linux-gnu-install_only.tar.gz",
|
||||
"urls": [
|
||||
"https://github.com/indygreg/python-build-standalone/releases/download/20230507/cpython-3.9.16+20230507-x86_64-unknown-linux-gnu-install_only.tar.gz"
|
||||
],
|
||||
"distutils_content": "",
|
||||
"strip_prefix": "python",
|
||||
"ignore_root_user_error": false
|
||||
}
|
||||
},
|
||||
"python_3_11_x86_64-unknown-linux-gnu": {
|
||||
"bzlFile": "@@rules_python~//python:repositories.bzl",
|
||||
"ruleClassName": "python_repository",
|
||||
"attributes": {
|
||||
"sha256": "02a551fefab3750effd0e156c25446547c238688a32fabde2995c941c03a6423",
|
||||
"patches": [],
|
||||
"platform": "x86_64-unknown-linux-gnu",
|
||||
"python_version": "3.11.1",
|
||||
"release_filename": "20230116/cpython-3.11.1+20230116-x86_64-unknown-linux-gnu-install_only.tar.gz",
|
||||
"urls": [
|
||||
"https://github.com/indygreg/python-build-standalone/releases/download/20230116/cpython-3.11.1+20230116-x86_64-unknown-linux-gnu-install_only.tar.gz"
|
||||
],
|
||||
"distutils_content": "",
|
||||
"strip_prefix": "python",
|
||||
"ignore_root_user_error": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": [
|
||||
[
|
||||
"rules_python~",
|
||||
"bazel_tools",
|
||||
"bazel_tools"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"@@toolchains_llvm~//toolchain/extensions:llvm.bzl%llvm": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "y9h5L2NtWbogyWSOJgqnUaU50MTPWAW+waelXSirMVg=",
|
||||
"usagesDigest": "cWIs+RUBBwv1tE2cNVULmSLCA/wGvHWaQzdrujSEO74=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {
|
||||
"llvm_toolchain": {
|
||||
"bzlFile": "@@toolchains_llvm~//toolchain:rules.bzl",
|
||||
"ruleClassName": "toolchain",
|
||||
"attributes": {
|
||||
"absolute_paths": false,
|
||||
"archive_flags": {},
|
||||
"compile_flags": {},
|
||||
"coverage_compile_flags": {},
|
||||
"coverage_link_flags": {},
|
||||
"cxx_builtin_include_directories": {},
|
||||
"cxx_flags": {},
|
||||
"cxx_standard": {},
|
||||
"dbg_compile_flags": {},
|
||||
"exec_arch": "",
|
||||
"exec_os": "",
|
||||
"link_flags": {},
|
||||
"link_libs": {},
|
||||
"llvm_versions": {
|
||||
"": "18.1.8"
|
||||
},
|
||||
"opt_compile_flags": {},
|
||||
"opt_link_flags": {},
|
||||
"stdlib": {},
|
||||
"target_settings": {},
|
||||
"unfiltered_compile_flags": {},
|
||||
"toolchain_roots": {},
|
||||
"sysroot": {}
|
||||
}
|
||||
},
|
||||
"llvm_toolchain_llvm": {
|
||||
"bzlFile": "@@toolchains_llvm~//toolchain:rules.bzl",
|
||||
"ruleClassName": "llvm",
|
||||
"attributes": {
|
||||
"alternative_llvm_sources": [],
|
||||
"auth_patterns": {},
|
||||
"distribution": "auto",
|
||||
"exec_arch": "",
|
||||
"exec_os": "",
|
||||
"llvm_mirror": "",
|
||||
"llvm_version": "",
|
||||
"llvm_versions": {
|
||||
"": "18.1.8"
|
||||
},
|
||||
"netrc": "",
|
||||
"sha256": {
|
||||
"": "54ec30358afcc9fb8aa74307db3046f5187f9fb89fb37064cdde906e062ebf36"
|
||||
},
|
||||
"strip_prefix": {
|
||||
"": "clang+llvm-18.1.8-x86_64-linux-gnu-ubuntu-18.04"
|
||||
},
|
||||
"urls": {
|
||||
"": [
|
||||
"https://github.com/llvm/llvm-project/releases/download/llvmorg-18.1.8/clang+llvm-18.1.8-x86_64-linux-gnu-ubuntu-18.04.tar.xz"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": [
|
||||
[
|
||||
"toolchains_llvm~",
|
||||
"bazel_skylib",
|
||||
"bazel_skylib~"
|
||||
],
|
||||
[
|
||||
"toolchains_llvm~",
|
||||
"bazel_tools",
|
||||
"bazel_tools"
|
||||
],
|
||||
[
|
||||
"toolchains_llvm~",
|
||||
"toolchains_llvm",
|
||||
"toolchains_llvm~"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
26
Makefile
26
Makefile
@ -8,24 +8,38 @@ help: # with thanks to Ben Rady
|
||||
.PHONY: build
|
||||
build: debug ## build in debug mode
|
||||
|
||||
build/configured-debug:
|
||||
cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On -DCPPTRACE_BUILD_TOOLS=On
|
||||
rm -f build/configured-release
|
||||
touch build/configured-debug
|
||||
|
||||
build/configured-release:
|
||||
cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On -DCPPTRACE_BUILD_TOOLS=On
|
||||
rm -f build/configured-debug
|
||||
touch build/configured-release
|
||||
|
||||
.PHONY: configure-debug
|
||||
configure-debug: build/configured-debug
|
||||
|
||||
.PHONY: configure-release
|
||||
configure-release: build/configured-release
|
||||
|
||||
.PHONY: debug
|
||||
debug: ## build in debug mode
|
||||
cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On
|
||||
debug: configure-debug ## build in debug mode
|
||||
cmake --build build
|
||||
|
||||
.PHONY: release
|
||||
release: ## build in release mode (with debug info)
|
||||
cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On
|
||||
release: configure-release ## build in release mode (with debug info)
|
||||
cmake --build build
|
||||
|
||||
.PHONY: debug-msvc
|
||||
debug-msvc: ## build in debug mode
|
||||
cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On
|
||||
cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On -DCPPTRACE_BUILD_TOOLS=On
|
||||
cmake --build build --config Debug
|
||||
|
||||
.PHONY: release-msvc
|
||||
release-msvc: ## build in release mode (with debug info)
|
||||
cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On
|
||||
cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On -DCPPTRACE_BUILD_TOOLS=On
|
||||
cmake --build build --config RelWithDebInfo
|
||||
|
||||
.PHONY: clean
|
||||
|
||||
554
README.md
554
README.md
@ -1,14 +1,13 @@
|
||||
# Cpptrace <!-- omit in toc -->
|
||||
|
||||
[](https://github.com/jeremy-rifkin/cpptrace/actions/workflows/build.yml)
|
||||
[](https://github.com/jeremy-rifkin/cpptrace/actions/workflows/test.yml)
|
||||
[](https://github.com/jeremy-rifkin/cpptrace/actions/workflows/ci.yml)
|
||||
[](https://sonarcloud.io/summary/new_code?id=jeremy-rifkin_cpptrace)
|
||||
<br/>
|
||||
[-Community%20Discord-blue?labelColor=2C3239&color=7289DA&style=flat&logo=discord&logoColor=959DA5)](https://discord.gg/frjaAZvqUZ)
|
||||
<br/>
|
||||
[](https://godbolt.org/z/c6TqTzqcf)
|
||||
|
||||
Cpptrace is a simple, portable, and self-contained C++ stacktrace library supporting C++11 and greater on Linux, macOS,
|
||||
Cpptrace is a simple and portable C++ stacktrace library supporting C++11 and greater on Linux, macOS,
|
||||
and Windows including MinGW and Cygwin environments. The goal: Make stack traces simple for once.
|
||||
|
||||
Cpptrace also has a C API, docs [here](docs/c-api.md).
|
||||
@ -17,24 +16,29 @@ Cpptrace also has a C API, docs [here](docs/c-api.md).
|
||||
|
||||
- [30-Second Overview](#30-second-overview)
|
||||
- [CMake FetchContent Usage](#cmake-fetchcontent-usage)
|
||||
- [FAQ](#faq)
|
||||
- [What about C++23 `<stacktrace>`?](#what-about-c23-stacktrace)
|
||||
- [What does cpptrace have over other C++ stacktrace libraries?](#what-does-cpptrace-have-over-other-c-stacktrace-libraries)
|
||||
- [In-Depth Documentation](#in-depth-documentation)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Basic Usage](#basic-usage)
|
||||
- [`namespace cpptrace`](#namespace-cpptrace)
|
||||
- [Stack Traces](#stack-traces)
|
||||
- [Object Traces](#object-traces)
|
||||
- [Raw Traces](#raw-traces)
|
||||
- [Utilities](#utilities)
|
||||
- [Formatting](#formatting)
|
||||
- [Configuration](#configuration)
|
||||
- [Traced Exceptions](#traced-exceptions)
|
||||
- [Traces From All Exceptions](#traces-from-all-exceptions)
|
||||
- [Removing the `CPPTRACE_` prefix](#removing-the-cpptrace_-prefix)
|
||||
- [How it works](#how-it-works)
|
||||
- [Performance](#performance)
|
||||
- [Traced Exception Objects](#traced-exception-objects)
|
||||
- [Wrapping std::exceptions](#wrapping-stdexceptions)
|
||||
- [Exception handling with cpptrace](#exception-handling-with-cpptrace)
|
||||
- [Exception handling with cpptrace exception objects](#exception-handling-with-cpptrace-exception-objects)
|
||||
- [Terminate Handling](#terminate-handling)
|
||||
- [Signal-Safe Tracing](#signal-safe-tracing)
|
||||
- [Utility Types](#utility-types)
|
||||
- [Headers](#headers)
|
||||
- [Libdwarf Tuning](#libdwarf-tuning)
|
||||
- [Supported Debug Formats](#supported-debug-formats)
|
||||
- [Usage](#usage)
|
||||
- [How to Include The Library](#how-to-include-the-library)
|
||||
- [CMake FetchContent](#cmake-fetchcontent)
|
||||
- [System-Wide Installation](#system-wide-installation)
|
||||
- [Local User Installation](#local-user-installation)
|
||||
@ -50,6 +54,10 @@ Cpptrace also has a C API, docs [here](docs/c-api.md).
|
||||
- [Summary of Library Configurations](#summary-of-library-configurations)
|
||||
- [Testing Methodology](#testing-methodology)
|
||||
- [Notes About the Library](#notes-about-the-library)
|
||||
- [FAQ](#faq)
|
||||
- [What about C++23 `<stacktrace>`?](#what-about-c23-stacktrace)
|
||||
- [What does cpptrace have over other C++ stacktrace libraries?](#what-does-cpptrace-have-over-other-c-stacktrace-libraries)
|
||||
- [I'm getting undefined standard library symbols like `std::__1::basic_string` on MacOS](#im-getting-undefined-standard-library-symbols-like-std__1basic_string-on-macos)
|
||||
- [Contributing](#contributing)
|
||||
- [License](#license)
|
||||
|
||||
@ -80,7 +88,30 @@ const auto raw_trace = cpptrace::generate_raw_trace();
|
||||
raw_trace.resolve().print();
|
||||
```
|
||||
|
||||
Cpptrace also provides exception types that store stack traces:
|
||||
Cpptrace provides a way to produce stack traces on arbitrary exceptions. More information on this system
|
||||
[below](#traces-from-all-exceptions).
|
||||
```cpp
|
||||
#include <cpptrace/from_current.hpp>
|
||||
void foo() {
|
||||
throw std::runtime_error("foo failed");
|
||||
}
|
||||
int main() {
|
||||
CPPTRACE_TRY {
|
||||
foo();
|
||||
} CPPTRACE_CATCH(const std::exception& e) {
|
||||
std::cerr<<"Exception: "<<e.what()<<std::endl;
|
||||
cpptrace::from_current_exception().print();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
There are a few extraneous frames at the top of the stack corresponding to internals of exception handling in the
|
||||
standard library. These are a small price to pay for stack traces on all exceptions.
|
||||
|
||||
Cpptrace also provides a handful of traced exception objects that store stack traces when thrown. This is useful when
|
||||
the exceptions might not be caught by `CPPTRACE_CATCH`:
|
||||
```cpp
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
|
||||
@ -89,7 +120,7 @@ void trace() {
|
||||
}
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
Additional notable features:
|
||||
|
||||
@ -97,6 +128,7 @@ Additional notable features:
|
||||
- Utilities for catching `std::exception`s and wrapping them in traced exceptions
|
||||
- Signal-safe stack tracing
|
||||
- Source code snippets in traces
|
||||
- Extensive configuration options for [trace formatting](#formatting)
|
||||
|
||||

|
||||
|
||||
@ -107,12 +139,13 @@ include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
cpptrace
|
||||
GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git
|
||||
GIT_TAG v0.6.2 # <HASH or TAG>
|
||||
GIT_TAG v0.8.2 # <HASH or TAG>
|
||||
)
|
||||
FetchContent_MakeAvailable(cpptrace)
|
||||
target_link_libraries(your_target cpptrace::cpptrace)
|
||||
|
||||
# On windows copy cpptrace.dll to the same directory as the executable for your_target
|
||||
# Needed for shared library builds on windows: copy cpptrace.dll to the same directory as the
|
||||
# executable for your_target
|
||||
if(WIN32)
|
||||
add_custom_command(
|
||||
TARGET your_target POST_BUILD
|
||||
@ -129,50 +162,26 @@ information.
|
||||
On macOS it is recommended to generate a `.dSYM` file, see [Platform Logistics](#platform-logistics) below.
|
||||
|
||||
For other ways to use the library, such as through package managers, a system-wide installation, or on a platform
|
||||
without internet access see [Usage](#usage) below.
|
||||
without internet access see [How to Include The Library](#how-to-include-the-library) below.
|
||||
|
||||
# FAQ
|
||||
|
||||
## What about C++23 `<stacktrace>`?
|
||||
|
||||
Some day C++23's `<stacktrace>` will be ubiquitous. And maybe one day the msvc implementation will be acceptable.
|
||||
The original motivation for cpptrace was to support projects using older C++ standards and as the library has grown its
|
||||
functionality has extended beyond the standard library's implementation.
|
||||
|
||||
Cpptrace provides functionality beyond what the standard library provides and what implementations provide, such as:
|
||||
- Walking inlined function calls
|
||||
- Providing a lightweight interface for "raw traces"
|
||||
- Resolving function parameter types
|
||||
- Providing traced exception objects
|
||||
- Providing an API for signal-safe stacktrace generation
|
||||
|
||||
## What does cpptrace have over other C++ stacktrace libraries?
|
||||
|
||||
Other C++ stacktrace libraries, such as boost stacktrace and backward-cpp, fall short when it comes to portability and
|
||||
ease of use. In testing, I found neither to provide adaquate coverage of various environments. Even when they can be
|
||||
made to work in an environment they require manual configuration from the end-user, possibly requiring manual
|
||||
installation of third-party dependencies. This is a highly undesirable burden to impose on users, especially when it is
|
||||
for a software package which just provides diagnostics as opposed to core functionality. Additionally, cpptrace provides
|
||||
support for resolving inlined calls by default for DWARF symbols (boost does not do this, backward-cpp can do this but
|
||||
only for some back-ends), better support for resolving full function signatures, and nicer API, among other features.
|
||||
|
||||
# In-Depth Documentation
|
||||
|
||||
## Prerequisites
|
||||
# Prerequisites
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Debug info (`-g`/`/Z7`/`/Zi`/`/DEBUG`/`-DBUILD_TYPE=Debug`/`-DBUILD_TYPE=RelWithDebInfo`) is required for complete
|
||||
> trace information.
|
||||
|
||||
## `namespace cpptrace`
|
||||
# Basic Usage
|
||||
|
||||
`cpptrace::generate_trace()` can be used to generate a stacktrace object at the current call site. Resolved frames can
|
||||
be accessed from this object with `.frames` and also the trace can be printed with `.print()`. Cpptrace also provides a
|
||||
method to get lightweight raw traces, which are just vectors of program counters, which can be resolved at a later time.
|
||||
`cpptrace::generate_trace()` can be used to generate a `stacktrace` object at the current call site. Resolved frames can
|
||||
be accessed from this object with `.frames` and the trace can be printed with `.print()`. Cpptrace also provides a
|
||||
method to get light-weight raw traces with `cpptrace::generate_raw_trace()`, which are just vectors of program counters,
|
||||
which can be resolved at a later time.
|
||||
|
||||
# `namespace cpptrace`
|
||||
|
||||
All functions are thread-safe unless otherwise noted.
|
||||
|
||||
### Stack Traces
|
||||
## Stack Traces
|
||||
|
||||
The core resolved stack trace object. Generate a trace with `cpptrace::generate_trace()` or
|
||||
`cpptrace::stacktrace::current()`. On top of a set of helper functions `struct stacktrace` allows
|
||||
@ -224,7 +233,7 @@ namespace cpptrace {
|
||||
}
|
||||
```
|
||||
|
||||
### Object Traces
|
||||
## Object Traces
|
||||
|
||||
Object traces contain the most basic information needed to construct a stack trace outside the currently running
|
||||
executable. It contains the raw address, the address in the binary (ASLR and the object file's memory space and whatnot
|
||||
@ -253,7 +262,7 @@ namespace cpptrace {
|
||||
}
|
||||
```
|
||||
|
||||
### Raw Traces
|
||||
## Raw Traces
|
||||
|
||||
Raw trace access: A vector of program counters. These are ideal for fast and cheap traces you want to resolve later.
|
||||
|
||||
@ -278,7 +287,7 @@ namespace cpptrace {
|
||||
}
|
||||
```
|
||||
|
||||
### Utilities
|
||||
## Utilities
|
||||
|
||||
`cpptrace::demangle` provides a helper function for name demangling, since it has to implement that helper internally
|
||||
anyways.
|
||||
@ -310,12 +319,93 @@ namespace cpptrace {
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration
|
||||
## Formatting
|
||||
|
||||
Cpptrace provides a configurable formatter for stack trace printing which supports some common options. Formatters are
|
||||
configured with a sort of builder pattern, e.g.:
|
||||
```cpp
|
||||
auto formatter = cpptrace::formatter{}
|
||||
.header("Stack trace:")
|
||||
.addresses(cpptrace::formatter::address_mode::object)
|
||||
.snippets(true);
|
||||
```
|
||||
|
||||
This API is available through the `<cpptrace/formatting.hpp>` header.
|
||||
|
||||
Synopsis:
|
||||
```cpp
|
||||
namespace cpptrace {
|
||||
class formatter {
|
||||
formatter& header(std::string);
|
||||
enum class color_mode { always, none, automatic };
|
||||
formatter& colors(color_mode);
|
||||
enum class address_mode { raw, object, none };
|
||||
formatter& addresses(address_mode);
|
||||
enum class path_mode { full, basename };
|
||||
formatter& paths(path_mode);
|
||||
formatter& snippets(bool);
|
||||
formatter& snippet_context(int);
|
||||
formatter& columns(bool);
|
||||
formatter& filtered_frame_placeholders(bool);
|
||||
formatter& filter(std::function<bool(const stacktrace_frame&)>);
|
||||
|
||||
std::string format(const stacktrace_frame&) const;
|
||||
std::string format(const stacktrace_frame&, bool color) const;
|
||||
|
||||
std::string format(const stacktrace&) const;
|
||||
std::string format(const stacktrace&, bool color) const;
|
||||
|
||||
void print(const stacktrace_frame&) const;
|
||||
void print(const stacktrace_frame&, bool color) const;
|
||||
void print(std::ostream&, const stacktrace_frame&) const;
|
||||
void print(std::ostream&, const stacktrace_frame&, bool color) const;
|
||||
void print(std::FILE*, const stacktrace_frame&) const;
|
||||
void print(std::FILE*, const stacktrace_frame&, bool color) const;
|
||||
|
||||
void print(const stacktrace&) const;
|
||||
void print(const stacktrace&, bool color) const;
|
||||
void print(std::ostream&, const stacktrace&) const;
|
||||
void print(std::ostream&, const stacktrace&, bool color) const;
|
||||
void print(std::FILE*, const stacktrace&) const;
|
||||
void print(std::FILE*, const stacktrace&, bool color) const;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Options:
|
||||
| Setting | Description | Default |
|
||||
| ----------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------ |
|
||||
| `header` | Header line printed before the trace | `Stack trace (most recent call first):` |
|
||||
| `colors` | Default color mode for the trace | `automatic`, which attempts to detect if the target stream is a terminal |
|
||||
| `addresses` | Raw addresses, object addresses, or no addresses | `raw` |
|
||||
| `paths` | Full paths or just filenames | `full` |
|
||||
| `snippets` | Whether to include source code snippets | `false` |
|
||||
| `snippet_context` | How many lines of source context to show in a snippet | `2` |
|
||||
| `columns` | Whether to include column numbers if present | `true` |
|
||||
| `filtered_frame_placeholders` | Whether to still print filtered frames as just `#n (filtered)` | `true` |
|
||||
| `filter` | A predicate to filter frames with | None |
|
||||
|
||||
The `automatic` color mode attempts to detect if a stream that may be attached to a terminal. As such, it will not use
|
||||
colors for the `formatter::format` method and it may not be able to detect if some ostreams correspond to terminals or
|
||||
not. For this reason, `formatter::format` and `formatter::print` methods have overloads taking a color parameter. This
|
||||
color parameter will override configured color mode.
|
||||
|
||||
Recommended practice with formatters: It's generally preferable to create formatters objects that are long-lived rather
|
||||
than to create them on the fly every time a trace needs to be formatted.
|
||||
|
||||
Cpptrace provides access to a formatter with default settings with `get_default_formatter`:
|
||||
```cpp
|
||||
namespace cpptrace {
|
||||
const formatter& get_default_formatter();
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
`cpptrace::absorb_trace_exceptions`: Configure whether the library silently absorbs internal exceptions and continues.
|
||||
Default is true.
|
||||
|
||||
`cpptrace::ctrace_enable_inlined_call_resolution`: Configure whether the library will attempt to resolve inlined call
|
||||
`cpptrace::enable_inlined_call_resolution`: Configure whether the library will attempt to resolve inlined call
|
||||
information for release builds. Default is true.
|
||||
|
||||
`cpptrace::experimental::set_cache_mode`: Control time-memory tradeoffs within the library. By default speed is
|
||||
@ -325,7 +415,7 @@ performed.
|
||||
```cpp
|
||||
namespace cpptrace {
|
||||
void absorb_trace_exceptions(bool absorb);
|
||||
void ctrace_enable_inlined_call_resolution(bool enable);
|
||||
void enable_inlined_call_resolution(bool enable);
|
||||
|
||||
enum class cache_mode {
|
||||
// Only minimal lookup tables
|
||||
@ -342,11 +432,167 @@ namespace cpptrace {
|
||||
}
|
||||
```
|
||||
|
||||
### Traced Exceptions
|
||||
## Traces From All Exceptions
|
||||
|
||||
Cpptrace provides an interface for a traced exceptions, `cpptrace::exception`, as well as a set of exception classes
|
||||
that that generate stack traces when thrown. These exceptions generate relatively lightweight raw traces and resolve
|
||||
symbols and line numbers lazily if and when requested.
|
||||
Cpptrace provides `CPPTRACE_TRY` and `CPPTRACE_CATCH` macros that allow a stack trace to be collected from the current
|
||||
thrown exception object, with minimal or no overhead in the non-throwing path:
|
||||
|
||||
```cpp
|
||||
#include <cpptrace/from_current.hpp>
|
||||
#include <iostream>
|
||||
|
||||
void foo() {
|
||||
throw std::runtime_error("foo failed");
|
||||
}
|
||||
int main() {
|
||||
CPPTRACE_TRY {
|
||||
foo();
|
||||
} CPPTRACE_CATCH(const std::exception& e) {
|
||||
std::cerr<<"Exception: "<<e.what()<<std::endl;
|
||||
cpptrace::from_current_exception().print();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This functionality is entirely opt-in, to access this use `#include <cpptrace/from_current.hpp>`.
|
||||
|
||||
Any declarator `catch` accepts works with `CPPTRACE_CATCH`, including `...`. This works with any thrown object, not just
|
||||
`std::exceptions`, it even works with `throw 0;`
|
||||
|
||||

|
||||
|
||||
There are a few extraneous frames at the top of the stack corresponding to standard library exception handling
|
||||
internals. These are a small price to pay for stack traces on all exceptions.
|
||||
|
||||
API functions:
|
||||
- `cpptrace::raw_trace_from_current_exception`: Returns `const raw_trace&` from the current exception.
|
||||
- `cpptrace::from_current_exception`: Returns a resolved `const stacktrace&` from the current exception. Invalidates
|
||||
references to traces returned by `cpptrace::raw_trace_from_current_exception`.
|
||||
|
||||
There is a performance tradeoff with this functionality: Either the try-block can be zero overhead in the
|
||||
non-throwing path with potential expense in the throwing path, or the try-block can have very minimal overhead
|
||||
in the non-throwing path due to bookkeeping with guarantees about the expense of the throwing path. More details on
|
||||
this tradeoff [below](#performance). Cpptrace provides macros for both sides of this tradeoff:
|
||||
- `CPPTRACE_TRY`/`CPPTRACE_CATCH`: Minimal overhead in the non-throwing path (one `mov` on x86, and this may be
|
||||
optimized out if the compiler is able)
|
||||
- `CPPTRACE_TRYZ`/`CPPTRACE_CATCHZ`: Zero overhead in the non-throwing path, potential extra cost in the throwing path
|
||||
|
||||
Note: It's important to not mix the `Z` variants with the non-`Z` variants.
|
||||
|
||||
Unfortunately the try/catch macros are needed to insert some magic to perform a trace during the unwinding search phase.
|
||||
In order to have multiple catch alternatives, either `CPPTRACE_CATCH_ALT` or a normal `catch` must be used:
|
||||
```cpp
|
||||
CPPTRACE_TRY {
|
||||
foo();
|
||||
} CPPTRACE_CATCH(const std::exception&) { // <- First catch must be CPPTRACE_CATCH
|
||||
// ...
|
||||
} CPPTRACE_CATCH_ALT(const std::exception&) { // <- Ok
|
||||
// ...
|
||||
} catch(const std::exception&) { // <- Also Ok
|
||||
// ...
|
||||
} CPPTRACE_CATCH(const std::exception&) { // <- Not Ok
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Note: The current exception is the exception most recently seen by a cpptrace try-catch macro block.
|
||||
|
||||
```cpp
|
||||
CPPTRACE_TRY {
|
||||
throw std::runtime_error("foo");
|
||||
} CPPTRACE_CATCH(const std::exception& e) {
|
||||
cpptrace::from_current_exception().print(); // the trace for std::runtime_error("foo")
|
||||
CPPTRACE_TRY {
|
||||
throw std::runtime_error("bar");
|
||||
} CPPTRACE_CATCH(const std::exception& e) {
|
||||
cpptrace::from_current_exception().print(); // the trace for std::runtime_error("bar")
|
||||
}
|
||||
cpptrace::from_current_exception().print(); // the trace for std::runtime_error("bar"), again
|
||||
}
|
||||
```
|
||||
|
||||
### Removing the `CPPTRACE_` prefix
|
||||
|
||||
`CPPTRACE_TRY` is a little cumbersome to type. To remove the `CPPTRACE_` prefix you can use the
|
||||
`CPPTRACE_UNPREFIXED_TRY_CATCH` cmake option or the `CPPTRACE_UNPREFIXED_TRY_CATCH` preprocessor definition:
|
||||
|
||||
```cpp
|
||||
TRY {
|
||||
foo();
|
||||
} CATCH(const std::exception& e) {
|
||||
std::cerr<<"Exception: "<<e.what()<<std::endl;
|
||||
cpptrace::from_current_exception().print();
|
||||
}
|
||||
```
|
||||
|
||||
This is not done by default for macro safety/hygiene reasons. If you do not want `TRY`/`CATCH` macros defined, as they
|
||||
are common macro names, you can easily modify the following snippet to provide your own aliases:
|
||||
|
||||
```cpp
|
||||
#define TRY CPPTRACE_TRY
|
||||
#define CATCH(param) CPPTRACE_CATCH(param)
|
||||
#define TRYZ CPPTRACE_TRYZ
|
||||
#define CATCHZ(param) CPPTRACE_CATCHZ(param)
|
||||
#define CATCH_ALT(param) CPPTRACE_CATCH_ALT(param)
|
||||
```
|
||||
|
||||
### How it works
|
||||
|
||||
C++ does not provide any language support for collecting stack traces when exceptions are thrown, however, exception
|
||||
handling under both the Itanium ABI and by SEH (used to implement C++ exceptions on windows) involves unwinding the
|
||||
stack twice. The first unwind searches for an appropriate `catch` handler, the second actually unwinds the stack and
|
||||
calls destructors. Since the stack remains intact during the search phase it's possible to collect a stack trace with
|
||||
little to no overhead when the `catch` is considered for matching the exception. The try/catch macros for cpptrace set
|
||||
up a special try/catch system that can collect a stack trace when considered during a search phase.
|
||||
|
||||
N.b.: This mechanism is also discussed in [P2490R3][P2490R3].
|
||||
|
||||
### Performance
|
||||
|
||||
The fundamental mechanism for this functionality is generating a trace when a catch block is considered during an
|
||||
exception handler search phase. Internally a lightweight raw trace is generated upon consideration, which is quite
|
||||
fast. This raw trace is only resolved when `cpptrace::from_current_exception` is called, or when the user manually
|
||||
resolves a trace from `cpptrace::raw_trace_from_current_exception`.
|
||||
|
||||
It's tricky, however, from the library's standpoint to check if the catch will end up matching. The library could simply
|
||||
generate a trace every time a `CPPTRACE_CATCH` is considered, however, in a deep nesting of catch's, e.g. as a result of
|
||||
recusion, where a matching handler is not found quickly this could introduce a non-trivial cost in the throwing pat due
|
||||
to tracing the stack multiple times. Thus, there is a performance tradeoff between a little book keeping to prevent
|
||||
duplicate tracing or biting the bullet, so to speak, in the throwing path and unwinding multiple times.
|
||||
|
||||
> [!TIP]
|
||||
> The choice between the `Z` and non-`Z` (zero-overhead and non-zero-overhead) variants of the exception handlers should
|
||||
> not matter 99% of the time, however, both are provided in the rare case that it does.
|
||||
>
|
||||
> `CPPTRACE_TRY`/`CPPTRACE_CATCH` could only hurt performance if used in a hot loop where the compiler can't optimize
|
||||
> away the internal bookkeeping, otherwise the bookkeeping should be completely negligible.
|
||||
>
|
||||
> `CPPTRACE_TRYZ`/`CPPTRACE_CATCHZ` could only hurt performance when there is an exceptionally deep nesting of exception
|
||||
> handlers in a call stack before a matching handler.
|
||||
|
||||
More information on performance considerations with the zero-overhead variant:
|
||||
|
||||
Tracing the stack multiple times in throwing paths should not matter for the vast majority applications given that:
|
||||
1. Performance very rarely is critical in throwing paths and exceptions should be exceptionally rare
|
||||
2. Exception handling is not usually used in such a way that you could have a deep nesting of handlers before finding a
|
||||
matching handler
|
||||
3. Most call stacks are fairly shallow
|
||||
|
||||
To put the scale of this performance consideration into perspective: In my benchmarking I have found generation of raw
|
||||
traces to take on the order of `100ns` per frame. Thus, even if there were 100 non-matching handlers before a matching
|
||||
handler in a 100-deep call stack the total time would stil be on the order of one millisecond.
|
||||
|
||||
Nonetheless, I chose a default bookkeeping behavior for `CPPTRACE_TRY`/`CPPTRACE_CATCH` since it is safer with better
|
||||
performance guarantees for the most general possible set of users.
|
||||
|
||||
## Traced Exception Objects
|
||||
|
||||
Cpptrace provides a handful of traced exception classes which automatically collect stack traces when thrown. These
|
||||
are useful when throwing exceptions that may not be caught by `CPPTRACE_CATCH`.
|
||||
|
||||
The base traced exception class is `cpptrace::exception` and cpptrace provides a handful of helper classes for working
|
||||
with traced exceptions. These exceptions generate relatively lightweight raw traces and resolve symbols and line numbers
|
||||
lazily if and when requested.
|
||||
|
||||
These are provided both as a useful utility and as a reference implementation for traced exceptions.
|
||||
|
||||
@ -427,7 +673,11 @@ namespace cpptrace {
|
||||
}
|
||||
```
|
||||
|
||||
## Wrapping std::exceptions
|
||||
### Wrapping std::exceptions
|
||||
|
||||
> [!NOTE]
|
||||
> This section is largely obsolete now that cpptrace provides a better mechanism for collecting
|
||||
> [traces from exceptions](#traces-from-exceptions)
|
||||
|
||||
Cpptrace exceptions can provide great information for user-controlled exceptions. For non-cpptrace::exceptions that may
|
||||
originate outside of code you control, e.g. the standard library, cpptrace provides some wrapper utilities that can
|
||||
@ -444,7 +694,11 @@ CPPTRACE_WRAP_BLOCK(
|
||||
std::cout<<CPPTRACE_WRAP(foo.at(12))<<std::endl;
|
||||
```
|
||||
|
||||
## Exception handling with cpptrace
|
||||
### Exception handling with cpptrace exception objects
|
||||
|
||||
> [!NOTE]
|
||||
> This section pertains to cpptrace traced exception objects and not the mechanism for collecting
|
||||
> [traces from arbitrary exceptions](#traces-from-exceptions)
|
||||
|
||||
Working with cpptrace exceptions in your code:
|
||||
```cpp
|
||||
@ -460,10 +714,12 @@ try {
|
||||
}
|
||||
```
|
||||
|
||||
Additionally cpptrace provides a custom `std::terminate` handler that prints a stack trace from a cpptrace exception and otherwise behaves like the normal terminate handler and prints the stack trace involved in reaching `std::terminate`.
|
||||
The stack trace to `std::terminate` may be helpful or it may not, it depends on the implementation, but often if an
|
||||
implementation can't find an appropriate `catch` while unwinding it will jump directly to `std::terminate` giving
|
||||
good information.
|
||||
## Terminate Handling
|
||||
|
||||
Cpptrace provides a custom `std::terminate` handler that prints stacktraces while otherwise behaving like the normal
|
||||
`std::terminate` handler. If a cpptrace exception object reaches `std::terminate` the trace from that exception is
|
||||
printed, otherwise a stack trace is generated at the point of the terminate handler. Often `std::terminate` is called
|
||||
directly without unwinding so the trace is preserved.
|
||||
|
||||
To register this custom handler:
|
||||
|
||||
@ -473,17 +729,14 @@ cpptrace::register_terminate_handler();
|
||||
|
||||
## Signal-Safe Tracing
|
||||
|
||||
Signal-safe stack tracing is very useful for debugging application crashes, e.g. SIGSEGVs or
|
||||
SIGTRAPs, but it's very difficult to do correctly and most implementations I see online do this
|
||||
incorrectly.
|
||||
Stack traces from signal handlers can provide very helpful information for debugging application crashes, e.g. from
|
||||
SIGSEGV or SIGTRAP handlers. Signal handlers are really restrictive environments as your application could be
|
||||
interrupted by a signal at any point, including in the middle of malloc or buffered IO or while holding a lock.
|
||||
Doing a stack trace in a signal handler is possible but it requires a lot of care. This is difficult to do correctly
|
||||
and most examples online do this incorrectly.
|
||||
|
||||
In order to do this full process safely the way to go is collecting basic information in the signal
|
||||
handler and then either resolving later or handing that information to another process to resolve.
|
||||
|
||||
It's not as simple as calling `cpptrace::generate_trace().print()`, though you might be able to get
|
||||
away with that, but this is what is needed to really do this safely.
|
||||
|
||||
The safe API is as follows:
|
||||
Cpptrace offers an API to walk the stack in a signal handler and produce a raw trace safely. The library also provides
|
||||
an interface for producing a object frame safely:
|
||||
|
||||
```cpp
|
||||
namespace cpptrace {
|
||||
@ -491,29 +744,41 @@ namespace cpptrace {
|
||||
std::size_t safe_generate_raw_trace(frame_ptr* buffer, std::size_t size, std::size_t skip, std::size_t max_depth);
|
||||
struct safe_object_frame {
|
||||
frame_ptr raw_address;
|
||||
frame_ptr address_relative_to_object_start; // object base address must yet be added
|
||||
frame_ptr address_relative_to_object_start;
|
||||
char object_path[CPPTRACE_PATH_MAX + 1];
|
||||
object_frame resolve() const; // To be called outside a signal handler. Not signal safe.
|
||||
};
|
||||
void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
|
||||
bool can_signal_safe_unwind();
|
||||
bool can_get_safe_object_frame();
|
||||
}
|
||||
```
|
||||
|
||||
It is not possible to resolve debug symbols safely in the process from a signal handler without heroic effort. In order
|
||||
to produce a full trace there are three options:
|
||||
1. Carefully save the object trace information to be resolved at a later time outside the signal handler
|
||||
2. Write the object trace information to a file to be resolved later
|
||||
3. Spawn a new process, communicate object trace information to that process, and have that process do the trace
|
||||
resolution
|
||||
|
||||
For traces on segfaults, e.g., only options 2 and 3 are viable. For more information an implementation of approach 3,
|
||||
see the comprehensive overview and demo at [signal-safe-tracing.md](docs/signal-safe-tracing.md).
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Currently signal-safe stack unwinding is only possible with `libunwind`, which must be
|
||||
> [manually enabled](#library-back-ends). If signal-safe unwinding isn't supported, `safe_generate_raw_trace` will just
|
||||
> produce an empty trace. `can_signal_safe_unwind` can be used to check for signal-safe unwinding support. If object
|
||||
> information can't be resolved in a signal-safe way then `get_safe_object_frame` will not populate fields beyond the
|
||||
> `raw_address`.
|
||||
> produce an empty trace. `can_signal_safe_unwind` can be used to check for signal-safe unwinding support and
|
||||
> `can_get_safe_object_frame` can be used to check `get_safe_object_frame` support. If object information can't be
|
||||
> resolved in a signal-safe way then `get_safe_object_frame` will not populate fields beyond the `raw_address`.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> `_dl_find_object` is required for signal-safe stack tracing. This is a relatively recent addition to glibc, added in
|
||||
> glibc 2.35.
|
||||
|
||||
> [!CAUTION]
|
||||
> Calls to shared objects can be lazy-loaded where the first call to the shared object invokes non-signal-safe functions
|
||||
> such as `malloc()`. To avoid this, call these routines in `main()` ahead of a signal handler to "warm up" the library.
|
||||
|
||||
Because signal-safe tracing is an involved process, I have written up a comprehensive overview of
|
||||
what is involved at [signal-safe-tracing.md](docs/signal-safe-tracing.md).
|
||||
|
||||
## Utility Types
|
||||
|
||||
A couple utility types are used to provide the library with a good interface.
|
||||
@ -530,6 +795,7 @@ namespace cpptrace {
|
||||
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
|
||||
struct nullable {
|
||||
T raw_value;
|
||||
// all members are constexpr for c++17 and beyond, some are constexpr before c++17
|
||||
nullable& operator=(T value)
|
||||
bool has_value() const noexcept;
|
||||
T& value() noexcept;
|
||||
@ -539,6 +805,7 @@ namespace cpptrace {
|
||||
void reset() noexcept;
|
||||
bool operator==(const nullable& other) const noexcept;
|
||||
bool operator!=(const nullable& other) const noexcept;
|
||||
constexpr static T null_value() noexcept; // returns the raw null value
|
||||
constexpr static nullable null() noexcept; // returns a null instance
|
||||
};
|
||||
|
||||
@ -561,6 +828,7 @@ namespace cpptrace {
|
||||
lazy_trace_holder& operator=(lazy_trace_holder&& other) noexcept;
|
||||
~lazy_trace_holder();
|
||||
// access
|
||||
const raw_trace& get_raw_trace() const;
|
||||
stacktrace& get_resolved_trace();
|
||||
const stacktrace& get_resolved_trace() const; // throws if not already resolved
|
||||
private:
|
||||
@ -570,22 +838,63 @@ namespace cpptrace {
|
||||
}
|
||||
```
|
||||
|
||||
## Headers
|
||||
|
||||
Cpptrace provides a handful of headers to make inclusion more minimal.
|
||||
| Header | Contents |
|
||||
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `cpptrace/forward.hpp` | `cpptrace::frame_ptr` and a few trace class forward declarations |
|
||||
| `cpptrace/basic.hpp` | Definitions for trace classes and the basic tracing APIs ([Stack Traces](#stack-traces), [Object Traces](#object-traces), [Raw Traces](#raw-traces), and [Signal-Safe Tracing](#signal-safe-tracing)) |
|
||||
| `cpptrace/exceptions.hpp` | [Traced Exception Objects](#traced-exception-objects) and related utilities ([Wrapping std::exceptions](#wrapping-stdexceptions)) |
|
||||
| `cpptrace/from_current.hpp` | [Traces From All Exceptions](#traces-from-all-exceptions) |
|
||||
| `cpptrace/io.hpp` | `operator<<` overloads for `std::ostream` and `std::formatter`s |
|
||||
| `cpptrace/formatting.hpp` | Configurable formatter API |
|
||||
| `cpptrace/utils.hpp` | Utility functions, configuration functions, and terminate utilities ([Utilities](#utilities), [Configuration](#configuration), and [Terminate Handling](#terminate-handling)) |
|
||||
| `cpptrace/version.hpp` | Library version macros |
|
||||
|
||||
The main cpptrace header is `cpptrace/cpptrace.hpp` which includes everything other than `from_current.hpp` and
|
||||
`version.hpp`.
|
||||
|
||||
## Libdwarf Tuning
|
||||
|
||||
For extraordinarily large binaries (multiple gigabytes), cpptrace's internal caching can result in a lot of memory
|
||||
usage. Cpptrace provides some options to reduce memory usage in exchange for performance in memory-constrained
|
||||
applications.
|
||||
|
||||
Synopsis:
|
||||
|
||||
```cpp
|
||||
namespace cpptrace {
|
||||
namespace experimental {
|
||||
void set_dwarf_resolver_line_table_cache_size(nullable<std::size_t> max_entries);
|
||||
void set_dwarf_resolver_disable_aranges(bool disable);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Explanation:
|
||||
- `set_dwarf_resolver_line_table_cache_size` can be used to set a limit to the cache size with evictions done LRU.
|
||||
Cpptrace loads and caches line tables for dwarf compile units. These can take a lot of space for large binaries with
|
||||
lots of debug info. Passing `nullable<std::size_t>::null()` will disable the cache size (which is the default
|
||||
behavior).
|
||||
- `set_dwarf_resolver_disable_aranges` can be used to disable use of dwarf `.debug_aranges`, an accelerated range lookup
|
||||
table for compile units emitted by many compilers. Cpptrace uses these by default if they are present since they can
|
||||
speed up resolution, however, they can also result in significant memory usage.
|
||||
|
||||
# Supported Debug Formats
|
||||
|
||||
| Format | Supported |
|
||||
| --------------------------------- | --------- |
|
||||
| ---------------------------- | --------- |
|
||||
| DWARF in binary | ✔️ |
|
||||
| GNU debug link | ️️✔️ |
|
||||
| Split dwarf (debug fission) | ✔️* |
|
||||
| Split dwarf (debug fission) | ✔️ |
|
||||
| DWARF in dSYM | ✔️ |
|
||||
| DWARF via Mach-O debug map | ✔️ |
|
||||
| Windows debug symbols in PDB | ✔️ |
|
||||
|
||||
*There seem to be a couple issues upstream with libdwarf however they will hopefully be resolved soon.
|
||||
|
||||
DWARF5 added DWARF package files. As far as I can tell no compiler implements these yet.
|
||||
|
||||
# Usage
|
||||
# How to Include The Library
|
||||
|
||||
## CMake FetchContent
|
||||
|
||||
@ -596,7 +905,7 @@ include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
cpptrace
|
||||
GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git
|
||||
GIT_TAG v0.6.2 # <HASH or TAG>
|
||||
GIT_TAG v0.8.2 # <HASH or TAG>
|
||||
)
|
||||
FetchContent_MakeAvailable(cpptrace)
|
||||
target_link_libraries(your_target cpptrace::cpptrace)
|
||||
@ -612,7 +921,7 @@ information.
|
||||
|
||||
```sh
|
||||
git clone https://github.com/jeremy-rifkin/cpptrace.git
|
||||
git checkout v0.6.2
|
||||
git checkout v0.8.2
|
||||
mkdir cpptrace/build
|
||||
cd cpptrace/build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release
|
||||
@ -655,7 +964,7 @@ you when installing new libraries.
|
||||
|
||||
```ps1
|
||||
git clone https://github.com/jeremy-rifkin/cpptrace.git
|
||||
git checkout v0.6.2
|
||||
git checkout v0.8.2
|
||||
mkdir cpptrace/build
|
||||
cd cpptrace/build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release
|
||||
@ -673,12 +982,12 @@ To install just for the local user (or any custom prefix):
|
||||
|
||||
```sh
|
||||
git clone https://github.com/jeremy-rifkin/cpptrace.git
|
||||
git checkout v0.6.2
|
||||
git checkout v0.8.2
|
||||
mkdir cpptrace/build
|
||||
cd cpptrace/build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$HOME/wherever
|
||||
make -j
|
||||
sudo make install
|
||||
make install
|
||||
```
|
||||
|
||||
Using through cmake:
|
||||
@ -746,7 +1055,7 @@ make install
|
||||
cd ~/scratch/cpptrace-test
|
||||
git clone https://github.com/jeremy-rifkin/libdwarf-lite.git
|
||||
cd libdwarf-lite
|
||||
git checkout 5c0cb251f94b27e90184e6b2d9a0c9c62593babc
|
||||
git checkout fe09ca800b988e2ff21225ac5e7468ceade2a30e
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DPIC_ALWAYS=On -DBUILD_DWARFDUMP=Off -DCMAKE_PREFIX_PATH=~/scratch/cpptrace-test/resources -DCMAKE_INSTALL_PREFIX=~/scratch/cpptrace-test/resources
|
||||
@ -756,7 +1065,7 @@ make install
|
||||
cd ~/scratch/cpptrace-test
|
||||
git clone https://github.com/jeremy-rifkin/cpptrace.git
|
||||
cd cpptrace
|
||||
git checkout v0.6.2
|
||||
git checkout v0.8.2
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=On -DCPPTRACE_USE_EXTERNAL_LIBDWARF=On -DCMAKE_PREFIX_PATH=~/scratch/cpptrace-test/resources -DCMAKE_INSTALL_PREFIX=~/scratch/cpptrace-test/resources
|
||||
@ -776,7 +1085,7 @@ cpptrace and its dependencies.
|
||||
Cpptrace is available through conan at https://conan.io/center/recipes/cpptrace.
|
||||
```
|
||||
[requires]
|
||||
cpptrace/0.6.2
|
||||
cpptrace/0.8.2
|
||||
[generators]
|
||||
CMakeDeps
|
||||
CMakeToolchain
|
||||
@ -855,9 +1164,9 @@ The library's CMake automatically configures itself for what your system support
|
||||
follows:
|
||||
|
||||
| Platform | Unwinding | Symbols | Demangling |
|
||||
| -------- | ------------- | ------------------ | -------------------- |
|
||||
| -------- | ------------------------------------------------------- | ------------------ | -------------------- |
|
||||
| Linux | `_Unwind` | libdwarf | cxxabi.h |
|
||||
| MacOS | `_Unwind` | libdwarf | cxxabi.h |
|
||||
| MacOS | `_Unwind` for gcc, execinfo.h for clang and apple clang | libdwarf | cxxabi.h |
|
||||
| Windows | `StackWalk64` | dbghelp | No demangling needed |
|
||||
| MinGW | `StackWalk64` | libdwarf + dbghelp | cxxabi.h |
|
||||
|
||||
@ -876,7 +1185,7 @@ back-end such as addr2line, for example, you can configure the library to do so.
|
||||
| N/A | `CPPTRACE_UNWIND_WITH_NOTHING` | all | Unwinding is not done, stack traces will be empty. |
|
||||
|
||||
Some back-ends (execinfo and `CaptureStackBackTrace`) require a fixed buffer has to be created to read addresses into
|
||||
while unwinding. By default the buffer can hold addresses for 200 frames (beyond the `skip` frames). This is
|
||||
while unwinding. By default the buffer can hold addresses for 400 frames (beyond the `skip` frames). This is
|
||||
configurable with `CPPTRACE_HARD_MAX_FRAMES`.
|
||||
|
||||
**Symbol resolution**
|
||||
@ -938,7 +1247,7 @@ Back-ends:
|
||||
Back-end configuration:
|
||||
- `CPPTRACE_BACKTRACE_PATH=<string>`: Path to libbacktrace backtrace.h, needed when compiling with clang/
|
||||
- `CPPTRACE_HARD_MAX_FRAMES=<number>`: Some back-ends write to a fixed-size buffer. This is the size of that buffer.
|
||||
Default is `200`.
|
||||
Default is `400`.
|
||||
- `CPPTRACE_ADDR2LINE_PATH=<string>`: Specify the absolute path to the addr2line binary for cpptrace to invoke. By
|
||||
default the config script will search for a binary and use that absolute path (this is to prevent against path
|
||||
injection).
|
||||
@ -952,6 +1261,8 @@ Other useful configurations:
|
||||
- `CPPTRACE_INSTALL_CMAKEDIR`: Override for the installation path for the cmake configs.
|
||||
- `CPPTRACE_USE_EXTERNAL_LIBDWARF=On/Off`: Get libdwarf from `find_package` rather than `FetchContent`.
|
||||
- `CPPTRACE_POSITION_INDEPENDENT_CODE=On/Off`: Compile the library as a position independent code (PIE). Defaults to On.
|
||||
- `CPPTRACE_STD_FORMAT=On/Off`: Control inclusion of `<format>` and provision of `std::formatter` specializations by
|
||||
cpptrace.hpp. This can also be controlled with the macro `CPPTRACE_NO_STD_FORMAT`.
|
||||
|
||||
Testing:
|
||||
- `CPPTRACE_BUILD_TESTING` Build small demo and test program
|
||||
@ -983,6 +1294,51 @@ A couple things I'd like to improve in the future:
|
||||
in dbghelp the library cannot accurately show const and volatile qualifiers or rvalue references (these appear as
|
||||
pointers).
|
||||
|
||||
# FAQ
|
||||
|
||||
## What about C++23 `<stacktrace>`?
|
||||
|
||||
Some day C++23's `<stacktrace>` will be ubiquitous. And maybe one day the msvc implementation will be acceptable.
|
||||
The original motivation for cpptrace was to support projects using older C++ standards and as the library has grown its
|
||||
functionality has extended beyond the standard library's implementation.
|
||||
|
||||
Cpptrace provides functionality beyond what the standard library provides and what implementations provide, such as:
|
||||
- Walking inlined function calls
|
||||
- Providing a lightweight interface for "raw traces"
|
||||
- Resolving function parameter types
|
||||
- Providing traced exception objects
|
||||
- Providing an API for signal-safe stacktrace generation
|
||||
- Providing a way to retrieve stack traces from arbitrary exceptions, not just special cpptrace traced exception
|
||||
objects. This is a feature coming to C++26, but cpptrace provides a solution for C++11.
|
||||
|
||||
## What does cpptrace have over other C++ stacktrace libraries?
|
||||
|
||||
Other C++ stacktrace libraries, such as boost stacktrace and backward-cpp, fall short when it comes to portability and
|
||||
ease of use. In testing, I found neither to provide adaquate coverage of various environments. Even when they can be
|
||||
made to work in an environment they require manual configuration from the end-user, possibly requiring manual
|
||||
installation of third-party dependencies. This is a highly undesirable burden to impose on users, especially when it is
|
||||
for a software package which just provides diagnostics as opposed to core functionality. Additionally, cpptrace provides
|
||||
support for resolving inlined calls by default for DWARF symbols (boost does not do this, backward-cpp can do this but
|
||||
only for some back-ends), better support for resolving full function signatures, and nicer API, among other features.
|
||||
|
||||
## I'm getting undefined standard library symbols like `std::__1::basic_string` on MacOS
|
||||
|
||||
If you see a linker error along the lines of the following on MacOS then it's highly likely you are mixing standard
|
||||
library ABIs.
|
||||
|
||||
```
|
||||
Undefined symbols for architecture arm64:
|
||||
"std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::find(char, unsigned long) const", referenced from:
|
||||
cpptrace::detail::demangle(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, bool) in libcpptrace.a(demangle_with_cxxabi.cpp.o)
|
||||
cpptrace::detail::snippet_manager::build_line_table() in libcpptrace.a(snippet.cpp.o)
|
||||
```
|
||||
|
||||
This can happen when using apple clang to compile cpptrace and gcc to compile your code, or vice versa. The reason is
|
||||
that apple clang defaults to libc++ and gcc defaults to libstdc++ and these two standard library implementations are not
|
||||
ABI-compatible. To resolve this, ensure you are compiling both cpptrace and your code with the same standard library by
|
||||
either using the same compiler for both or using `-stdlib=libc++`/`-stdlib=libstdc++` to control which standard library
|
||||
is used.
|
||||
|
||||
# Contributing
|
||||
|
||||
I'm grateful for the help I've received with this library and I welcome contributions! For information on contributing
|
||||
@ -994,3 +1350,5 @@ This library is under the MIT license.
|
||||
|
||||
Cpptrace uses libdwarf on linux, macos, and mingw/cygwin unless configured to use something else. If this library is
|
||||
statically linked with libdwarf then the library's binary will itself be LGPL.
|
||||
|
||||
[P2490R3]: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2490r3.html
|
||||
|
||||
21
benchmarking/CMakeLists.txt
Normal file
21
benchmarking/CMakeLists.txt
Normal file
@ -0,0 +1,21 @@
|
||||
include(CTest)
|
||||
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
|
||||
set(
|
||||
warning_options
|
||||
${warning_options} $<$<CXX_COMPILER_ID:GNU>:-Wno-infinite-recursion>
|
||||
)
|
||||
|
||||
include(FetchContent)
|
||||
set(BENCHMARK_ENABLE_TESTING OFF)
|
||||
FetchContent_Declare(
|
||||
googlebench
|
||||
GIT_REPOSITORY "https://github.com/google/benchmark.git"
|
||||
GIT_TAG 12235e24652fc7f809373e7c11a5f73c5763fc4c # v1.9.0
|
||||
)
|
||||
FetchContent_MakeAvailable(googlebench)
|
||||
|
||||
add_executable(benchmark_unwinding unwinding.cpp)
|
||||
target_compile_features(benchmark_unwinding PRIVATE cxx_std_20)
|
||||
target_link_libraries(benchmark_unwinding PRIVATE ${target_name} benchmark::benchmark)
|
||||
55
benchmarking/unwinding.cpp
Normal file
55
benchmarking/unwinding.cpp
Normal file
@ -0,0 +1,55 @@
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
|
||||
#include <benchmark/benchmark.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
struct unwind_benchmark_info {
|
||||
benchmark::State& state;
|
||||
size_t& stack_depth;
|
||||
};
|
||||
|
||||
void unwind_loop(unwind_benchmark_info info) {
|
||||
auto& [state, depth] = info;
|
||||
depth = cpptrace::generate_raw_trace().frames.size();
|
||||
for(auto _ : state) {
|
||||
benchmark::DoNotOptimize(cpptrace::generate_raw_trace());
|
||||
}
|
||||
}
|
||||
|
||||
void foo(unwind_benchmark_info info, int n) {
|
||||
if(n == 0) {
|
||||
unwind_loop(info);
|
||||
} else {
|
||||
foo(info, n - 1);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
void foo(unwind_benchmark_info info, int, Args... args) {
|
||||
foo(info, args...);
|
||||
}
|
||||
|
||||
void function_two(unwind_benchmark_info info, int, float) {
|
||||
foo(info, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
|
||||
}
|
||||
|
||||
void function_one(unwind_benchmark_info info, int) {
|
||||
function_two(info, 0, 0);
|
||||
}
|
||||
|
||||
static void unwinding(benchmark::State& state) {
|
||||
size_t stack_depth = 0;
|
||||
function_one({state, stack_depth}, 0);
|
||||
static bool did_print = false;
|
||||
if(!did_print) {
|
||||
did_print = true;
|
||||
std::cerr<<"[info] Unwinding benchmark stack depth: "<<stack_depth<<std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// Register the function as a benchmark
|
||||
BENCHMARK(unwinding);
|
||||
|
||||
// Run the benchmark
|
||||
BENCHMARK_MAIN();
|
||||
@ -1,345 +0,0 @@
|
||||
import argparse
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from colorama import Fore, Back, Style
|
||||
from pathlib import Path
|
||||
|
||||
from util import *
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8') # for windows gh runner
|
||||
|
||||
failed = False
|
||||
|
||||
def run_command(*args: List[str]):
|
||||
print(f"{Fore.CYAN}{Style.BRIGHT}Running Command \"{' '.join(args)}\"{Style.RESET_ALL}")
|
||||
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
stdout, stderr = p.communicate()
|
||||
print(Style.RESET_ALL, end="") # makefile in parallel sometimes messes up colors
|
||||
if p.returncode != 0:
|
||||
print(f"{Fore.RED}{Style.BRIGHT}Command failed{Style.RESET_ALL}")
|
||||
print("stdout:")
|
||||
print(stdout.decode("utf-8"), end="")
|
||||
print("stderr:")
|
||||
print(stderr.decode("utf-8"), end="")
|
||||
global failed
|
||||
failed = True
|
||||
return False
|
||||
else:
|
||||
print(f"{Fore.GREEN}{Style.BRIGHT}Command succeeded{Style.RESET_ALL}")
|
||||
return True
|
||||
|
||||
def build(matrix):
|
||||
print(f"{Fore.BLUE}{Style.BRIGHT}{'=' * 10} Running build with config {', '.join(matrix.values())} {'=' * 10}{Style.RESET_ALL}")
|
||||
|
||||
if os.path.exists("build"):
|
||||
shutil.rmtree("build", ignore_errors=True)
|
||||
|
||||
os.makedirs("build", exist_ok=True)
|
||||
os.chdir("build")
|
||||
|
||||
if platform.system() != "Windows":
|
||||
succeeded = run_command(
|
||||
"cmake",
|
||||
"..",
|
||||
f"-DCMAKE_BUILD_TYPE={matrix['target']}",
|
||||
f"-DCMAKE_CXX_COMPILER={matrix['compiler']}",
|
||||
f"-DCMAKE_CXX_STANDARD={matrix['std']}",
|
||||
f"-DCPPTRACE_USE_EXTERNAL_LIBDWARF=On",
|
||||
f"-DCPPTRACE_USE_EXTERNAL_ZSTD=On",
|
||||
f"-DCPPTRACE_WERROR_BUILD=On",
|
||||
f"-D{matrix['unwind']}=On",
|
||||
f"-D{matrix['symbols']}=On",
|
||||
f"-D{matrix['demangle']}=On",
|
||||
"-DCPPTRACE_BACKTRACE_PATH=/usr/lib/gcc/x86_64-linux-gnu/10/include/backtrace.h",
|
||||
)
|
||||
if succeeded:
|
||||
succeeded = run_command("make", "-j", "VERBOSE=1")
|
||||
else:
|
||||
args = [
|
||||
"cmake",
|
||||
"..",
|
||||
f"-DCMAKE_BUILD_TYPE={matrix['target']}",
|
||||
f"-DCMAKE_CXX_COMPILER={matrix['compiler']}",
|
||||
f"-DCMAKE_CXX_STANDARD={matrix['std']}",
|
||||
f"-DCPPTRACE_USE_EXTERNAL_LIBDWARF=On",
|
||||
f"-DCPPTRACE_USE_EXTERNAL_ZSTD=On",
|
||||
f"-DCPPTRACE_WERROR_BUILD=On",
|
||||
f"-D{matrix['unwind']}=On",
|
||||
f"-D{matrix['symbols']}=On",
|
||||
f"-D{matrix['demangle']}=On",
|
||||
]
|
||||
if matrix["compiler"] == "g++":
|
||||
args.append("-GUnix Makefiles")
|
||||
succeeded = run_command(*args)
|
||||
if succeeded:
|
||||
if matrix["compiler"] == "g++":
|
||||
succeeded = run_command("make", "-j", "VERBOSE=1")
|
||||
else:
|
||||
succeeded = run_command("msbuild", "cpptrace.sln")
|
||||
|
||||
os.chdir("..")
|
||||
print()
|
||||
|
||||
return succeeded
|
||||
|
||||
def build_full_or_auto(matrix):
|
||||
print(f"{Fore.BLUE}{Style.BRIGHT}{'=' * 10} Running build with config {'<auto>' if matrix['config'] == '' else ', '.join(matrix.values())} {'=' * 10}{Style.RESET_ALL}")
|
||||
|
||||
if os.path.exists("build"):
|
||||
shutil.rmtree("build", ignore_errors=True)
|
||||
|
||||
os.makedirs("build", exist_ok=True)
|
||||
os.chdir("build")
|
||||
|
||||
if platform.system() != "Windows":
|
||||
args = [
|
||||
"cmake",
|
||||
"..",
|
||||
f"-DCMAKE_BUILD_TYPE={matrix['target']}",
|
||||
f"-DCMAKE_CXX_COMPILER={matrix['compiler']}",
|
||||
f"-DCMAKE_CXX_STANDARD={matrix['std']}",
|
||||
f"-DCPPTRACE_USE_EXTERNAL_LIBDWARF=On",
|
||||
f"-DCPPTRACE_USE_EXTERNAL_ZSTD=On",
|
||||
f"-DCPPTRACE_WERROR_BUILD=On",
|
||||
f"-DCPPTRACE_BACKTRACE_PATH=/usr/lib/gcc/x86_64-linux-gnu/10/include/backtrace.h",
|
||||
]
|
||||
if matrix["config"] != "":
|
||||
args.append(f"{matrix['config']}")
|
||||
succeeded = run_command(*args)
|
||||
if succeeded:
|
||||
succeeded = run_command("make", "-j")
|
||||
else:
|
||||
args = [
|
||||
"cmake",
|
||||
"..",
|
||||
f"-DCMAKE_BUILD_TYPE={matrix['target']}",
|
||||
f"-DCMAKE_CXX_COMPILER={matrix['compiler']}",
|
||||
f"-DCMAKE_CXX_STANDARD={matrix['std']}",
|
||||
f"-DCPPTRACE_USE_EXTERNAL_LIBDWARF=On",
|
||||
f"-DCPPTRACE_USE_EXTERNAL_ZSTD=On",
|
||||
f"-DCPPTRACE_WERROR_BUILD=On",
|
||||
]
|
||||
if matrix["config"] != "":
|
||||
args.append(f"{matrix['config']}")
|
||||
if matrix["compiler"] == "g++":
|
||||
args.append("-GUnix Makefiles")
|
||||
succeeded = run_command(*args)
|
||||
if succeeded:
|
||||
if matrix["compiler"] == "g++":
|
||||
succeeded = run_command("make", "-j")
|
||||
else:
|
||||
succeeded = run_command("msbuild", "cpptrace.sln")
|
||||
|
||||
os.chdir("..")
|
||||
print()
|
||||
|
||||
return succeeded
|
||||
|
||||
def run_linux_matrix(compilers: list):
|
||||
matrix = {
|
||||
"compiler": compilers,
|
||||
"target": ["Debug"],
|
||||
"std": ["11", "20"],
|
||||
"unwind": [
|
||||
"CPPTRACE_UNWIND_WITH_UNWIND",
|
||||
"CPPTRACE_UNWIND_WITH_EXECINFO",
|
||||
"CPPTRACE_UNWIND_WITH_LIBUNWIND",
|
||||
"CPPTRACE_UNWIND_WITH_NOTHING",
|
||||
],
|
||||
"symbols": [
|
||||
"CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE",
|
||||
"CPPTRACE_GET_SYMBOLS_WITH_LIBDL",
|
||||
"CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF",
|
||||
"CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE",
|
||||
"CPPTRACE_GET_SYMBOLS_WITH_NOTHING",
|
||||
],
|
||||
"demangle": [
|
||||
"CPPTRACE_DEMANGLE_WITH_CXXABI",
|
||||
"CPPTRACE_DEMANGLE_WITH_NOTHING",
|
||||
],
|
||||
}
|
||||
exclude = []
|
||||
run_matrix(matrix, exclude, build)
|
||||
|
||||
def run_linux_default(compilers: list):
|
||||
matrix = {
|
||||
"compiler": compilers,
|
||||
"target": ["Debug"],
|
||||
"std": ["11", "20"],
|
||||
"config": [""]
|
||||
}
|
||||
exclude = []
|
||||
run_matrix(matrix, exclude, build_full_or_auto)
|
||||
|
||||
def run_macos_matrix(compilers: list):
|
||||
matrix = {
|
||||
"compiler": compilers,
|
||||
"target": ["Debug"],
|
||||
"std": ["11", "20"],
|
||||
"unwind": [
|
||||
"CPPTRACE_UNWIND_WITH_UNWIND",
|
||||
"CPPTRACE_UNWIND_WITH_EXECINFO",
|
||||
"CPPTRACE_UNWIND_WITH_NOTHING",
|
||||
],
|
||||
"symbols": [
|
||||
#"CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE",
|
||||
"CPPTRACE_GET_SYMBOLS_WITH_LIBDL",
|
||||
"CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF",
|
||||
"CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE",
|
||||
"CPPTRACE_GET_SYMBOLS_WITH_NOTHING",
|
||||
],
|
||||
"demangle": [
|
||||
"CPPTRACE_DEMANGLE_WITH_CXXABI",
|
||||
"CPPTRACE_DEMANGLE_WITH_NOTHING",
|
||||
]
|
||||
}
|
||||
exclude = []
|
||||
run_matrix(matrix, exclude, build)
|
||||
|
||||
def run_macos_default(compilers: list):
|
||||
matrix = {
|
||||
"compiler": compilers,
|
||||
"target": ["Debug"],
|
||||
"std": ["11", "20"],
|
||||
"config": [""]
|
||||
}
|
||||
exclude = []
|
||||
run_matrix(matrix, exclude, build_full_or_auto)
|
||||
|
||||
def run_windows_matrix(compilers: list):
|
||||
matrix = {
|
||||
"compiler": compilers,
|
||||
"target": ["Debug"],
|
||||
"std": ["11", "20"],
|
||||
"unwind": [
|
||||
"CPPTRACE_UNWIND_WITH_WINAPI",
|
||||
"CPPTRACE_UNWIND_WITH_DBGHELP",
|
||||
"CPPTRACE_UNWIND_WITH_UNWIND",
|
||||
"CPPTRACE_UNWIND_WITH_NOTHING",
|
||||
],
|
||||
"symbols": [
|
||||
"CPPTRACE_GET_SYMBOLS_WITH_DBGHELP",
|
||||
"CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF",
|
||||
"CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE",
|
||||
"CPPTRACE_GET_SYMBOLS_WITH_NOTHING",
|
||||
],
|
||||
"demangle": [
|
||||
#"CPPTRACE_DEMANGLE_WITH_CXXABI",
|
||||
"CPPTRACE_DEMANGLE_WITH_NOTHING",
|
||||
]
|
||||
}
|
||||
exclude = [
|
||||
{
|
||||
"demangle": "CPPTRACE_DEMANGLE_WITH_CXXABI",
|
||||
"compiler": "cl"
|
||||
},
|
||||
{
|
||||
"unwind": "CPPTRACE_UNWIND_WITH_UNWIND",
|
||||
"compiler": "cl"
|
||||
},
|
||||
{
|
||||
"unwind": "CPPTRACE_UNWIND_WITH_UNWIND",
|
||||
"compiler": "clang++"
|
||||
},
|
||||
{
|
||||
"symbols": "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE",
|
||||
"compiler": "cl"
|
||||
},
|
||||
{
|
||||
"symbols": "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE",
|
||||
"compiler": "clang++"
|
||||
},
|
||||
{
|
||||
"symbols": "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF",
|
||||
"compiler": "cl"
|
||||
},
|
||||
{
|
||||
"symbols": "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF",
|
||||
"compiler": "clang++"
|
||||
},
|
||||
{
|
||||
"symbols": "CPPTRACE_GET_SYMBOLS_WITH_DBGHELP",
|
||||
"compiler": "g++"
|
||||
},
|
||||
]
|
||||
run_matrix(matrix, exclude, build)
|
||||
|
||||
def run_windows_default(compilers: list):
|
||||
matrix = {
|
||||
"compiler": compilers,
|
||||
"target": ["Debug"],
|
||||
"std": ["11", "20"],
|
||||
"config": [""]
|
||||
}
|
||||
exclude = []
|
||||
run_matrix(matrix, exclude, build_full_or_auto)
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="Build in all configs",
|
||||
description="Try building the library in all possible configurations for the current host"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--clang",
|
||||
action="store_true"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--gcc",
|
||||
action="store_true"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--msvc",
|
||||
action="store_true"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--all",
|
||||
action="store_true"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--default-config",
|
||||
action="store_true"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
if platform.system() == "Linux":
|
||||
compilers = []
|
||||
if args.clang or args.all:
|
||||
compilers.append("clang++-14")
|
||||
if args.gcc or args.all:
|
||||
compilers.append("g++-10")
|
||||
if args.default_config:
|
||||
run_linux_default(compilers)
|
||||
else:
|
||||
run_linux_matrix(compilers)
|
||||
if platform.system() == "Darwin":
|
||||
compilers = []
|
||||
if args.clang or args.all:
|
||||
compilers.append("clang++")
|
||||
if args.gcc or args.all:
|
||||
compilers.append("g++-12")
|
||||
if args.default_config:
|
||||
run_macos_default(compilers)
|
||||
else:
|
||||
run_macos_matrix(compilers)
|
||||
if platform.system() == "Windows":
|
||||
compilers = []
|
||||
if args.clang or args.all:
|
||||
compilers.append("clang++")
|
||||
if args.msvc or args.all:
|
||||
compilers.append("cl")
|
||||
if args.gcc or args.all:
|
||||
compilers.append("g++")
|
||||
if args.default_config:
|
||||
run_windows_default(compilers)
|
||||
else:
|
||||
run_windows_matrix(compilers)
|
||||
|
||||
global failed
|
||||
if failed:
|
||||
print("🔴 Some checks failed")
|
||||
sys.exit(1)
|
||||
|
||||
main()
|
||||
195
ci/build-in-all-remaining-configs.py
Normal file
195
ci/build-in-all-remaining-configs.py
Normal 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()
|
||||
@ -17,7 +17,7 @@ mkdir libdwarf
|
||||
cd libdwarf
|
||||
git init
|
||||
git remote add origin https://github.com/jeremy-rifkin/libdwarf-lite.git
|
||||
git fetch --depth 1 origin 5c0cb251f94b27e90184e6b2d9a0c9c62593babc
|
||||
git fetch --depth 1 origin fe09ca800b988e2ff21225ac5e7468ceade2a30e
|
||||
git checkout FETCH_HEAD
|
||||
mkdir build
|
||||
cd build
|
||||
|
||||
44
ci/setup-prerequisites-unittest-macos.sh
Executable file
44
ci/setup-prerequisites-unittest-macos.sh
Executable file
@ -0,0 +1,44 @@
|
||||
#!/bin/bash
|
||||
|
||||
mkdir zstd
|
||||
cd zstd
|
||||
git init
|
||||
git remote add origin https://github.com/facebook/zstd.git
|
||||
git fetch --depth 1 origin 794ea1b0afca0f020f4e57b6732332231fb23c70 # 1.5.6
|
||||
git checkout FETCH_HEAD
|
||||
make -j
|
||||
sudo make install
|
||||
|
||||
cd ..
|
||||
|
||||
mkdir libdwarf
|
||||
cd libdwarf
|
||||
git init
|
||||
git remote add origin https://github.com/jeremy-rifkin/libdwarf-lite.git
|
||||
git fetch --depth 1 origin fe09ca800b988e2ff21225ac5e7468ceade2a30e # 0.11.1
|
||||
git checkout FETCH_HEAD
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -GNinja -DPIC_ALWAYS=TRUE -DBUILD_DWARFDUMP=FALSE
|
||||
sudo ninja install
|
||||
|
||||
cd ../..
|
||||
|
||||
mkdir googletest
|
||||
cd googletest
|
||||
git init
|
||||
git remote add origin https://github.com/google/googletest.git
|
||||
git fetch --depth 1 origin f8d7d77c06936315286eb55f8de22cd23c188571
|
||||
git checkout FETCH_HEAD
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -GNinja -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_INSTALL_PREFIX=/tmp/gtest_install
|
||||
sudo ninja install
|
||||
rm -rf *
|
||||
# There's a false-positive container-overflow for apple clang/relwithdebinfo/sanitizers=on if gtest isn't built with
|
||||
# sanitizers. https://github.com/google/sanitizers/wiki/AddressSanitizerContainerOverflow#false-positives
|
||||
cmake .. -GNinja -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_INSTALL_PREFIX=/tmp/gtest_asan_install -DCMAKE_CXX_FLAGS=-fsanitize=address
|
||||
sudo ninja install
|
||||
rm -rf *
|
||||
cmake .. -GNinja -DCMAKE_CXX_COMPILER=g++-12 -DCMAKE_INSTALL_PREFIX=/tmp/gtest_install_gcc
|
||||
sudo ninja install
|
||||
39
ci/setup-prerequisites-unittest.sh
Executable file
39
ci/setup-prerequisites-unittest.sh
Executable file
@ -0,0 +1,39 @@
|
||||
#!/bin/bash
|
||||
|
||||
mkdir zstd
|
||||
cd zstd
|
||||
git init
|
||||
git remote add origin https://github.com/facebook/zstd.git
|
||||
git fetch --depth 1 origin 794ea1b0afca0f020f4e57b6732332231fb23c70 # 1.5.6
|
||||
git checkout FETCH_HEAD
|
||||
make -j
|
||||
sudo make install
|
||||
|
||||
cd ..
|
||||
|
||||
mkdir libdwarf
|
||||
cd libdwarf
|
||||
git init
|
||||
git remote add origin https://github.com/jeremy-rifkin/libdwarf-lite.git
|
||||
git fetch --depth 1 origin fe09ca800b988e2ff21225ac5e7468ceade2a30e # 0.11.1
|
||||
git checkout FETCH_HEAD
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -GNinja -DPIC_ALWAYS=TRUE -DBUILD_DWARFDUMP=FALSE
|
||||
sudo ninja install
|
||||
|
||||
cd ../..
|
||||
|
||||
mkdir googletest
|
||||
cd googletest
|
||||
git init
|
||||
git remote add origin https://github.com/google/googletest.git
|
||||
git fetch --depth 1 origin f8d7d77c06936315286eb55f8de22cd23c188571
|
||||
git checkout FETCH_HEAD
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -GNinja -DCMAKE_INSTALL_PREFIX=/tmp/gtest_install
|
||||
sudo ninja install
|
||||
rm -rf *
|
||||
cmake .. -GNinja -DCMAKE_CXX_COMPILER=clang++-18 -DCMAKE_CXX_FLAGS=-stdlib=libc++ -DCMAKE_INSTALL_PREFIX=/tmp/gtest_install_libcxx
|
||||
sudo ninja install
|
||||
@ -5,7 +5,7 @@ mkdir zstd
|
||||
cd zstd
|
||||
git init
|
||||
git remote add origin https://github.com/facebook/zstd.git
|
||||
git fetch --depth 1 origin 63779c798237346c2b245c546c40b72a5a5913fe # 1.5.5
|
||||
git fetch --depth 1 origin 794ea1b0afca0f020f4e57b6732332231fb23c70 # 1.5.6
|
||||
git checkout FETCH_HEAD
|
||||
make -j
|
||||
sudo make install
|
||||
@ -16,7 +16,7 @@ mkdir libdwarf
|
||||
cd libdwarf
|
||||
git init
|
||||
git remote add origin https://github.com/jeremy-rifkin/libdwarf-lite.git
|
||||
git fetch --depth 1 origin 5c0cb251f94b27e90184e6b2d9a0c9c62593babc
|
||||
git fetch --depth 1 origin fe09ca800b988e2ff21225ac5e7468ceade2a30e
|
||||
git checkout FETCH_HEAD
|
||||
mkdir build
|
||||
cd build
|
||||
|
||||
@ -11,8 +11,6 @@ from util import *
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8') # for windows gh runner
|
||||
|
||||
failed = False
|
||||
|
||||
expected_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../test/expected/")
|
||||
|
||||
def get_c_compiler_counterpart(compiler: str) -> str:
|
||||
@ -110,57 +108,13 @@ def output_matches(raw_output: str, params: Tuple[str]):
|
||||
|
||||
return not errored
|
||||
|
||||
def run_command(*args: List[str], always_output=False):
|
||||
global failed
|
||||
print(f"{Fore.CYAN}{Style.BRIGHT}Running Command \"{' '.join(args)}\"{Style.RESET_ALL}")
|
||||
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
stdout, stderr = p.communicate()
|
||||
print(Style.RESET_ALL, end="") # makefile in parallel sometimes messes up colors
|
||||
if p.returncode != 0:
|
||||
print(f"{Fore.RED}{Style.BRIGHT}Command failed{Style.RESET_ALL}")
|
||||
print("stdout:")
|
||||
print(stdout.decode("utf-8"), end="")
|
||||
print("stderr:")
|
||||
print(stderr.decode("utf-8"), end="")
|
||||
failed = True
|
||||
return False
|
||||
else:
|
||||
print(f"{Fore.GREEN}{Style.BRIGHT}Command succeeded{Style.RESET_ALL}")
|
||||
if always_output:
|
||||
print("stdout:")
|
||||
print(stdout.decode("utf-8"), end="")
|
||||
print("stderr:")
|
||||
print(stderr.decode("utf-8"), end="")
|
||||
return True
|
||||
def run_test(runner: MatrixRunner, test_binary, params: Tuple[str]):
|
||||
def output_matcher(output: str):
|
||||
return output_matches(output, params)
|
||||
return runner.run_command(test_binary, output_matcher=output_matcher)
|
||||
|
||||
def run_test(test_binary, params: Tuple[str]):
|
||||
global failed
|
||||
print(f"{Fore.CYAN}{Style.BRIGHT}Running test{Style.RESET_ALL}")
|
||||
test = subprocess.Popen([test_binary], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
test_stdout, test_stderr = test.communicate()
|
||||
print(Style.RESET_ALL, end="") # makefile in parallel sometimes messes up colors
|
||||
|
||||
if test.returncode != 0:
|
||||
print(f"[🔴 Test command failed with code {test.returncode}]")
|
||||
print("stderr:")
|
||||
print(test_stderr.decode("utf-8"), end="")
|
||||
print("stdout:")
|
||||
print(test_stdout.decode("utf-8"), end="")
|
||||
failed = True
|
||||
return False
|
||||
else:
|
||||
if len(test_stderr) != 0:
|
||||
print("stderr:")
|
||||
print(test_stderr.decode("utf-8"), end="")
|
||||
if output_matches(test_stdout.decode("utf-8"), params):
|
||||
print(f"{Fore.GREEN}{Style.BRIGHT}Test succeeded{Style.RESET_ALL}")
|
||||
return True
|
||||
else:
|
||||
print(f"{Fore.RED}{Style.BRIGHT}Test failed{Style.RESET_ALL}")
|
||||
failed = True
|
||||
return False
|
||||
|
||||
def build(matrix):
|
||||
def build(runner: MatrixRunner):
|
||||
matrix = runner.current_config()
|
||||
if platform.system() != "Windows":
|
||||
args = [
|
||||
"cmake",
|
||||
@ -182,9 +136,9 @@ def build(matrix):
|
||||
]
|
||||
if matrix['symbols'] == "CPPTRACE_GET_SYMBOLS_WITH_LIBDL":
|
||||
args.append("-DCPPTRACE_BUILD_TEST_RDYNAMIC=On")
|
||||
succeeded = run_command(*args)
|
||||
succeeded = runner.run_command(*args)
|
||||
if succeeded:
|
||||
return run_command("make", "-j")
|
||||
return runner.run_command("make", "-j")
|
||||
else:
|
||||
args = [
|
||||
"cmake",
|
||||
@ -205,15 +159,16 @@ def build(matrix):
|
||||
]
|
||||
if matrix["compiler"] == "g++":
|
||||
args.append("-GUnix Makefiles")
|
||||
succeeded = run_command(*args)
|
||||
succeeded = runner.run_command(*args)
|
||||
if succeeded:
|
||||
if matrix["compiler"] == "g++":
|
||||
return run_command("make", "-j")
|
||||
return runner.run_command("make", "-j")
|
||||
else:
|
||||
return run_command("msbuild", "cpptrace.sln")
|
||||
return runner.run_command("msbuild", "cpptrace.sln")
|
||||
return False
|
||||
|
||||
def build_full_or_auto(matrix):
|
||||
def build_full_or_auto(runner: MatrixRunner):
|
||||
matrix = runner.current_config()
|
||||
if platform.system() != "Windows":
|
||||
args = [
|
||||
"cmake",
|
||||
@ -232,9 +187,9 @@ def build_full_or_auto(matrix):
|
||||
]
|
||||
if matrix["config"] != "":
|
||||
args.append(f"{matrix['config']}")
|
||||
succeeded = run_command(*args)
|
||||
succeeded = runner.run_command(*args)
|
||||
if succeeded:
|
||||
return run_command("make", "-j")
|
||||
return runner.run_command("make", "-j")
|
||||
else:
|
||||
args = [
|
||||
"cmake",
|
||||
@ -254,52 +209,60 @@ def build_full_or_auto(matrix):
|
||||
args.append(f"{matrix['config']}")
|
||||
if matrix["compiler"] == "g++":
|
||||
args.append("-GUnix Makefiles")
|
||||
succeeded = run_command(*args)
|
||||
succeeded = runner.run_command(*args)
|
||||
if succeeded:
|
||||
if matrix["compiler"] == "g++":
|
||||
return run_command("make", "-j")
|
||||
return runner.run_command("make", "-j")
|
||||
else:
|
||||
return run_command("msbuild", "cpptrace.sln")
|
||||
return runner.run_command("msbuild", "cpptrace.sln")
|
||||
return False
|
||||
|
||||
def test(matrix):
|
||||
def test(runner: MatrixRunner):
|
||||
matrix = runner.current_config()
|
||||
if platform.system() != "Windows":
|
||||
return run_test(
|
||||
runner,
|
||||
"./integration",
|
||||
(matrix["compiler"], matrix["unwind"], matrix["symbols"], matrix["demangle"])
|
||||
)
|
||||
else:
|
||||
if matrix["compiler"] == "g++":
|
||||
return run_test(
|
||||
runner,
|
||||
f".\\integration.exe",
|
||||
(matrix["compiler"], matrix["unwind"], matrix["symbols"], matrix["demangle"])
|
||||
)
|
||||
else:
|
||||
return run_test(
|
||||
runner,
|
||||
f".\\{matrix['target']}\\integration.exe",
|
||||
(matrix["compiler"], matrix["unwind"], matrix["symbols"], matrix["demangle"])
|
||||
)
|
||||
|
||||
def test_full_or_auto(matrix):
|
||||
def test_full_or_auto(runner: MatrixRunner):
|
||||
matrix = runner.current_config()
|
||||
if platform.system() != "Windows":
|
||||
return run_test(
|
||||
runner,
|
||||
"./integration",
|
||||
(matrix["compiler"],)
|
||||
)
|
||||
else:
|
||||
if matrix["compiler"] == "g++":
|
||||
return run_test(
|
||||
runner,
|
||||
f".\\integration.exe",
|
||||
(matrix["compiler"],)
|
||||
)
|
||||
else:
|
||||
return run_test(
|
||||
runner,
|
||||
f".\\{matrix['target']}\\integration.exe",
|
||||
(matrix["compiler"],)
|
||||
)
|
||||
|
||||
def build_and_test(matrix):
|
||||
print(f"{Fore.BLUE}{Style.BRIGHT}{'=' * 10} Running build and test with config {', '.join(matrix.values())} {'=' * 10}{Style.RESET_ALL}")
|
||||
def build_and_test(runner: MatrixRunner):
|
||||
matrix = runner.current_config()
|
||||
|
||||
if os.path.exists("build"):
|
||||
shutil.rmtree("build", ignore_errors=True)
|
||||
@ -309,16 +272,16 @@ def build_and_test(matrix):
|
||||
os.chdir("build")
|
||||
|
||||
good = False
|
||||
if build(matrix):
|
||||
good = test(matrix)
|
||||
if build(runner):
|
||||
good = test(runner)
|
||||
|
||||
os.chdir("..")
|
||||
print()
|
||||
|
||||
return good
|
||||
|
||||
def build_and_test_full_or_auto(matrix):
|
||||
print(f"{Fore.BLUE}{Style.BRIGHT}{'=' * 10} Running build and test with config {'<auto>' if matrix['config'] == '' else ', '.join(matrix.values())} {'=' * 10}{Style.RESET_ALL}")
|
||||
def build_and_test_full_or_auto(runner: MatrixRunner):
|
||||
matrix = runner.current_config()
|
||||
|
||||
if os.path.exists("build"):
|
||||
shutil.rmtree("build", ignore_errors=True)
|
||||
@ -328,8 +291,8 @@ def build_and_test_full_or_auto(matrix):
|
||||
os.chdir("build")
|
||||
|
||||
good = False
|
||||
if build_full_or_auto(matrix):
|
||||
good = test_full_or_auto(matrix)
|
||||
if build_full_or_auto(runner):
|
||||
good = test_full_or_auto(runner)
|
||||
|
||||
os.chdir("..")
|
||||
print()
|
||||
@ -337,6 +300,7 @@ def build_and_test_full_or_auto(matrix):
|
||||
return good
|
||||
|
||||
def run_linux_matrix(compilers: list, shared: bool):
|
||||
MatrixRunner(
|
||||
matrix = {
|
||||
"compiler": compilers,
|
||||
"target": ["Debug"],
|
||||
@ -360,22 +324,24 @@ def run_linux_matrix(compilers: list, shared: bool):
|
||||
#"CPPTRACE_DEMANGLE_WITH_NOTHING",
|
||||
],
|
||||
"shared": ["On" if shared else "Off"]
|
||||
}
|
||||
},
|
||||
exclude = []
|
||||
run_matrix(matrix, exclude, build_and_test)
|
||||
).run(build_and_test)
|
||||
|
||||
def run_linux_default(compilers: list, shared: bool):
|
||||
MatrixRunner(
|
||||
matrix = {
|
||||
"compiler": compilers,
|
||||
"target": ["Debug"],
|
||||
"std": ["11", "20"],
|
||||
"config": [""],
|
||||
"shared": ["On" if shared else "Off"]
|
||||
}
|
||||
},
|
||||
exclude = []
|
||||
run_matrix(matrix, exclude, build_and_test_full_or_auto)
|
||||
).run(build_and_test_full_or_auto)
|
||||
|
||||
def run_macos_matrix(compilers: list, shared: bool):
|
||||
MatrixRunner(
|
||||
matrix = {
|
||||
"compiler": compilers,
|
||||
"target": ["Debug"],
|
||||
@ -397,22 +363,24 @@ def run_macos_matrix(compilers: list, shared: bool):
|
||||
#"CPPTRACE_DEMANGLE_WITH_NOTHING",
|
||||
],
|
||||
"shared": ["On" if shared else "Off"]
|
||||
}
|
||||
},
|
||||
exclude = []
|
||||
run_matrix(matrix, exclude, build_and_test)
|
||||
).run(build_and_test)
|
||||
|
||||
def run_macos_default(compilers: list, shared: bool):
|
||||
MatrixRunner(
|
||||
matrix = {
|
||||
"compiler": compilers,
|
||||
"target": ["Debug"],
|
||||
"std": ["11", "20"],
|
||||
"config": [""],
|
||||
"shared": ["On" if shared else "Off"]
|
||||
}
|
||||
},
|
||||
exclude = []
|
||||
run_matrix(matrix, exclude, build_and_test_full_or_auto)
|
||||
).run(build_and_test_full_or_auto)
|
||||
|
||||
def run_windows_matrix(compilers: list, shared: bool):
|
||||
MatrixRunner(
|
||||
matrix = {
|
||||
"compiler": compilers,
|
||||
"target": ["Debug"],
|
||||
@ -434,7 +402,7 @@ def run_windows_matrix(compilers: list, shared: bool):
|
||||
"CPPTRACE_DEMANGLE_WITH_NOTHING",
|
||||
],
|
||||
"shared": ["On" if shared else "Off"]
|
||||
}
|
||||
},
|
||||
exclude = [
|
||||
{
|
||||
"demangle": "CPPTRACE_DEMANGLE_WITH_CXXABI",
|
||||
@ -481,18 +449,19 @@ def run_windows_matrix(compilers: list, shared: bool):
|
||||
"demangle": "CPPTRACE_DEMANGLE_WITH_NOTHING"
|
||||
}
|
||||
]
|
||||
run_matrix(matrix, exclude, build_and_test)
|
||||
).run(build_and_test)
|
||||
|
||||
def run_windows_default(compilers: list, shared: bool):
|
||||
MatrixRunner(
|
||||
matrix = {
|
||||
"compiler": compilers,
|
||||
"target": ["Debug"],
|
||||
"std": ["11", "20"],
|
||||
"config": [""],
|
||||
"shared": ["On" if shared else "Off"]
|
||||
}
|
||||
},
|
||||
exclude = []
|
||||
run_matrix(matrix, exclude, build_and_test_full_or_auto)
|
||||
).run(build_and_test_full_or_auto)
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
@ -558,9 +527,4 @@ def main():
|
||||
else:
|
||||
run_windows_matrix(compilers, args.shared)
|
||||
|
||||
global failed
|
||||
if failed:
|
||||
print("🔴 Some checks failed")
|
||||
sys.exit(1)
|
||||
|
||||
main()
|
||||
|
||||
168
ci/unittest.py
Normal file
168
ci/unittest.py
Normal file
@ -0,0 +1,168 @@
|
||||
import argparse
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Tuple
|
||||
from colorama import Fore, Back, Style
|
||||
|
||||
from util import *
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8') # for windows gh runner
|
||||
|
||||
def get_c_compiler_counterpart(compiler: str) -> str:
|
||||
return compiler.replace("clang++", "clang").replace("g++", "gcc")
|
||||
|
||||
def build(runner: MatrixRunner):
|
||||
if platform.system() == "Linux":
|
||||
matrix = runner.current_config()
|
||||
if "stdlib" in matrix and matrix["stdlib"] == "libc++":
|
||||
gtest_path = "/tmp/gtest_install_libcxx"
|
||||
else:
|
||||
gtest_path = "/tmp/gtest_install"
|
||||
args = [
|
||||
"cmake",
|
||||
"..",
|
||||
"-GNinja",
|
||||
f"-DCMAKE_CXX_COMPILER={matrix['compiler']}",
|
||||
f"-DCMAKE_C_COMPILER={get_c_compiler_counterpart(matrix['compiler'])}",
|
||||
f"-DCMAKE_BUILD_TYPE={matrix['build_type']}",
|
||||
f"-DCPPTRACE_BUILD_SHARED={matrix['shared']}",
|
||||
f"-DHAS_DL_FIND_OBJECT={matrix['has_dl_find_object']}",
|
||||
"-DCPPTRACE_WERROR_BUILD=On",
|
||||
"-DCPPTRACE_STD_FORMAT=Off",
|
||||
"-DCPPTRACE_BUILD_TESTING=On",
|
||||
f"-DCPPTRACE_SANITIZER_BUILD={matrix['sanitizers']}",
|
||||
f"-DCPPTRACE_BUILD_NO_SYMBOLS={matrix['symbols']}",
|
||||
f"-DCPPTRACE_BUILD_TESTING_SPLIT_DWARF={matrix['split_dwarf']}",
|
||||
f"-DCPPTRACE_BUILD_TESTING_DWARF_VERSION={matrix['dwarf_version']}",
|
||||
f"-DCPPTRACE_USE_EXTERNAL_LIBDWARF=On",
|
||||
f"-DCPPTRACE_USE_EXTERNAL_ZSTD=On",
|
||||
f"-DCPPTRACE_USE_EXTERNAL_GTEST=On",
|
||||
f"-DCMAKE_PREFIX_PATH={gtest_path}",
|
||||
*(["-DCMAKE_CXX_FLAGS=-stdlib=libc++"] if "stdlib" in matrix and matrix["stdlib"] == "libc++" else [])
|
||||
]
|
||||
return runner.run_command(*args) and runner.run_command("ninja")
|
||||
elif platform.system() == "Darwin":
|
||||
matrix = runner.current_config()
|
||||
if "clang++" in matrix["compiler"]:
|
||||
gtest_path = "/tmp/gtest_asan_install" if matrix['sanitizers'] == "ON" else "/tmp/gtest_install"
|
||||
else:
|
||||
gtest_path = "/tmp/gtest_install_gcc"
|
||||
args = [
|
||||
"cmake",
|
||||
"..",
|
||||
"-GNinja",
|
||||
f"-DCMAKE_CXX_COMPILER={matrix['compiler']}",
|
||||
f"-DCMAKE_C_COMPILER={get_c_compiler_counterpart(matrix['compiler'])}",
|
||||
f"-DCMAKE_BUILD_TYPE={matrix['build_type']}",
|
||||
f"-DCPPTRACE_BUILD_SHARED={matrix['shared']}",
|
||||
"-DCPPTRACE_WERROR_BUILD=On",
|
||||
"-DCPPTRACE_STD_FORMAT=Off",
|
||||
"-DCPPTRACE_BUILD_TESTING=On",
|
||||
f"-DCPPTRACE_SANITIZER_BUILD={matrix['sanitizers']}",
|
||||
f"-DCPPTRACE_BUILD_NO_SYMBOLS={matrix['symbols']}",
|
||||
# f"-DCPPTRACE_BUILD_TESTING_SPLIT_DWARF={matrix['split_dwarf']}",
|
||||
# f"-DCPPTRACE_BUILD_TESTING_SPLIT_DWARF={matrix['dwarf_version']}",
|
||||
f"-DCPPTRACE_USE_EXTERNAL_LIBDWARF=On",
|
||||
f"-DCPPTRACE_USE_EXTERNAL_ZSTD=On",
|
||||
f"-DCPPTRACE_USE_EXTERNAL_GTEST=On",
|
||||
f"-DCMAKE_PREFIX_PATH={gtest_path}",
|
||||
]
|
||||
return runner.run_command(*args) and runner.run_command("ninja")
|
||||
else:
|
||||
raise ValueError()
|
||||
|
||||
def test(runner: MatrixRunner):
|
||||
if platform.system() == "Linux":
|
||||
return runner.run_command("./unittest") and runner.run_command("bash", "-c", "exec -a u ./unittest")
|
||||
elif platform.system() == "Darwin":
|
||||
if runner.current_config()["dSYM"]:
|
||||
if not runner.run_command("dsymutil", "unittest"):
|
||||
return False
|
||||
good = runner.run_command("./unittest") and runner.run_command("bash", "-c", "exec -a u ./unittest")
|
||||
if runner.current_config()["dSYM"]:
|
||||
shutil.rmtree("unittest.dSYM")
|
||||
return good
|
||||
else:
|
||||
raise ValueError()
|
||||
|
||||
def build_and_test(runner: MatrixRunner):
|
||||
# the build directory has to be purged on compiler or shared change
|
||||
last = runner.last_config()
|
||||
current = runner.current_config()
|
||||
if (
|
||||
last is None
|
||||
or last["compiler"] != current["compiler"]
|
||||
or ("stdlib" in current and last["stdlib"] != current["stdlib"])
|
||||
or (platform.system() == "Darwin" and last["sanitizers"] != current["sanitizers"])
|
||||
) and os.path.exists("build"):
|
||||
shutil.rmtree("build", ignore_errors=True)
|
||||
|
||||
if not os.path.exists("build"):
|
||||
os.mkdir("build")
|
||||
os.chdir("build")
|
||||
|
||||
good = False
|
||||
if build(runner):
|
||||
good = test(runner)
|
||||
|
||||
os.chdir("..")
|
||||
print(flush=True)
|
||||
|
||||
return good
|
||||
|
||||
def run_linux_matrix():
|
||||
MatrixRunner(
|
||||
matrix = {
|
||||
"compiler": ["g++-10", "clang++-18"],
|
||||
"stdlib": ["libstdc++", "libc++"],
|
||||
"sanitizers": ["OFF", "ON"],
|
||||
"build_type": ["Debug", "RelWithDebInfo"],
|
||||
"shared": ["OFF", "ON"],
|
||||
"has_dl_find_object": ["OFF", "ON"],
|
||||
"split_dwarf": ["OFF", "ON"],
|
||||
"dwarf_version": ["4", "5"],
|
||||
"symbols": ["On", "Off"],
|
||||
},
|
||||
exclude = [
|
||||
{
|
||||
"compiler": "g++-10",
|
||||
"stdlib": "libc++",
|
||||
},
|
||||
{
|
||||
# need to workaround https://github.com/llvm/llvm-project/issues/59432 later
|
||||
"stdlib": "libc++",
|
||||
"sanitizers": "ON",
|
||||
},
|
||||
]
|
||||
).run(build_and_test)
|
||||
|
||||
def run_macos_matrix():
|
||||
MatrixRunner(
|
||||
matrix = {
|
||||
"compiler": ["g++-12", "clang++"],
|
||||
"sanitizers": ["OFF", "ON"],
|
||||
"build_type": ["Debug", "RelWithDebInfo"],
|
||||
"shared": ["OFF", "ON"],
|
||||
"dSYM": [True, False],
|
||||
"symbols": ["On", "Off"],
|
||||
},
|
||||
exclude = [
|
||||
{
|
||||
"compiler": "g++-12",
|
||||
"sanitizers": "ON",
|
||||
},
|
||||
]
|
||||
).run(build_and_test)
|
||||
|
||||
def main():
|
||||
if platform.system() == "Linux":
|
||||
run_linux_matrix()
|
||||
if platform.system() == "Darwin":
|
||||
run_macos_matrix()
|
||||
if platform.system() == "Windows":
|
||||
raise ValueError() # run_windows_matrix()
|
||||
|
||||
main()
|
||||
203
ci/util.py
203
ci/util.py
@ -1,9 +1,10 @@
|
||||
import subprocess
|
||||
import sys
|
||||
import itertools
|
||||
from typing import List
|
||||
from typing import List, Dict
|
||||
from colorama import Fore, Back, Style
|
||||
import re
|
||||
import time
|
||||
|
||||
# https://stackoverflow.com/a/14693789/15675011
|
||||
ansi_escape = re.compile(r'''
|
||||
@ -18,95 +19,141 @@ ansi_escape = re.compile(r'''
|
||||
)
|
||||
''', re.VERBOSE)
|
||||
|
||||
def adj_width(text):
|
||||
return len(text) - len(ansi_escape.sub("", text))
|
||||
class MatrixRunner:
|
||||
def __init__(self, matrix, exclude):
|
||||
self.matrix = matrix
|
||||
self.exclude = exclude
|
||||
self.include = self.parse_includes()
|
||||
self.keys = [*matrix.keys()]
|
||||
self.values = [*matrix.values()]
|
||||
self.results = {} # insertion-ordered
|
||||
self.failed = False
|
||||
self.work = self.get_work()
|
||||
|
||||
def do_exclude(matrix_config, exclude):
|
||||
self.last_matrix_config = None
|
||||
self.current_matrix_config = None
|
||||
|
||||
def parse_includes(self) -> Dict[str, List[str]]:
|
||||
includes: Dict[str, List[str]] = dict()
|
||||
for arg in sys.argv:
|
||||
if arg.startswith("--slice="):
|
||||
rest = arg[len("--slice="):]
|
||||
key, value = rest.split(":")
|
||||
if key not in includes:
|
||||
includes[key] = []
|
||||
includes[key].append(value)
|
||||
return includes
|
||||
|
||||
def run_command(self, *args: List[str], always_output=False, output_matcher=None) -> bool:
|
||||
self.log(f"{Fore.CYAN}{Style.BRIGHT}Running Command \"{' '.join(args)}\"{Style.RESET_ALL}")
|
||||
start_time = time.time()
|
||||
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
stdout, stderr = p.communicate()
|
||||
runtime = time.time() - start_time
|
||||
self.log(Style.RESET_ALL, end="") # makefile in parallel sometimes messes up colors
|
||||
if p.returncode != 0:
|
||||
self.log(f"{Fore.RED}{Style.BRIGHT}Command failed{Style.RESET_ALL} {Fore.MAGENTA}(time: {runtime:.2f}s){Style.RESET_ALL}")
|
||||
self.log("stdout:")
|
||||
self.log(stdout.decode("utf-8"), end="")
|
||||
self.log("stderr:")
|
||||
self.log(stderr.decode("utf-8"), end="")
|
||||
self.failed = True
|
||||
return False
|
||||
else:
|
||||
self.log(f"{Fore.GREEN}{Style.BRIGHT}Command succeeded{Style.RESET_ALL} {Fore.MAGENTA}(time: {runtime:.2f}s){Style.RESET_ALL}")
|
||||
if always_output:
|
||||
self.log("stdout:")
|
||||
self.log(stdout.decode("utf-8"), end="")
|
||||
self.log("stderr:")
|
||||
self.log(stderr.decode("utf-8"), end="")
|
||||
elif len(stderr) != 0:
|
||||
self.log("stderr:")
|
||||
self.log(stderr.decode("utf-8"), end="")
|
||||
if output_matcher is not None:
|
||||
if not output_matcher(stdout.decode("utf-8")):
|
||||
self.failed = True
|
||||
return False
|
||||
return True
|
||||
|
||||
def set_fail(self):
|
||||
self.failed = True
|
||||
|
||||
def current_config(self):
|
||||
return self.current_matrix_config
|
||||
|
||||
def last_config(self):
|
||||
return self.last_matrix_config
|
||||
|
||||
def log(self, *args, **kwargs):
|
||||
print(*args, **kwargs, flush=True)
|
||||
|
||||
def do_exclude(self, matrix_config, exclude):
|
||||
return all(map(lambda k: matrix_config[k] == exclude[k], exclude.keys()))
|
||||
|
||||
def print_table(table):
|
||||
def do_include(self, matrix_config, include):
|
||||
if len(include) == 0:
|
||||
return True
|
||||
return all(map(lambda k: matrix_config[k] in include[k], include.keys()))
|
||||
|
||||
def assignment_to_matrix_config(self, assignment):
|
||||
matrix_config = {}
|
||||
for k, v in zip(self.matrix.keys(), assignment):
|
||||
matrix_config[k] = v
|
||||
return matrix_config
|
||||
|
||||
def get_work(self):
|
||||
work = []
|
||||
for assignment in itertools.product(*self.matrix.values()):
|
||||
config = self.assignment_to_matrix_config(assignment)
|
||||
if any(map(lambda ex: self.do_exclude(config, ex), self.exclude)):
|
||||
continue
|
||||
if not self.do_include(config, self.include):
|
||||
continue
|
||||
work.append(assignment)
|
||||
return work
|
||||
|
||||
def run(self, fn):
|
||||
for i, assignment in enumerate(self.work):
|
||||
matrix_config = self.assignment_to_matrix_config(assignment)
|
||||
config_tuple = tuple(self.values[i].index(p) for i, p in enumerate(assignment))
|
||||
config_str = ', '.join(map(lambda v: str(v), matrix_config.values()))
|
||||
if config_str == "":
|
||||
self.log(f"{Fore.BLUE}{Style.BRIGHT}{'=' * 10} [{i + 1}/{len(self.work)}] Running with blank config {'=' * 10}{Style.RESET_ALL}")
|
||||
else:
|
||||
self.log(f"{Fore.BLUE}{Style.BRIGHT}{'=' * 10} [{i + 1}/{len(self.work)}] Running with config {config_str} {'=' * 10}{Style.RESET_ALL}")
|
||||
self.last_matrix_config = self.current_matrix_config
|
||||
self.current_matrix_config = matrix_config
|
||||
self.results[config_tuple] = fn(self)
|
||||
self.print_results()
|
||||
if self.failed:
|
||||
self.log("🔴 Some checks failed")
|
||||
sys.exit(1)
|
||||
else:
|
||||
self.log("🟢 All checks passed")
|
||||
|
||||
def adj_width(self, text):
|
||||
return len(text) - len(ansi_escape.sub("", text))
|
||||
|
||||
def print_table(self, table):
|
||||
columns = len(table[0])
|
||||
column_widths = [1 for _ in range(columns)]
|
||||
for row in table:
|
||||
for i, cell in enumerate(row):
|
||||
column_widths[i] = max(column_widths[i], len(ansi_escape.sub("", cell)))
|
||||
for j, cell in enumerate(table[0]):
|
||||
print("| {cell:{width}} ".format(cell=cell, width=column_widths[j] + adj_width(cell)), end="")
|
||||
print("|")
|
||||
self.log("| {cell:{width}} ".format(cell=cell, width=column_widths[j] + self.adj_width(cell)), end="")
|
||||
self.log("|")
|
||||
for i, row in enumerate(table[1:]):
|
||||
for j, cell in enumerate(row):
|
||||
print("| {cell:{width}} ".format(cell=cell, width=column_widths[j] + adj_width(cell)), end="")
|
||||
print("|")
|
||||
self.log("| {cell:{width}} ".format(cell=cell, width=column_widths[j] + self.adj_width(cell)), end="")
|
||||
self.log("|")
|
||||
|
||||
def run_matrix(matrix, exclude, fn):
|
||||
keys = [*matrix.keys()]
|
||||
values = [*matrix.values()]
|
||||
#print("Values:", values)
|
||||
results = {} # insertion-ordered
|
||||
for config in itertools.product(*matrix.values()):
|
||||
#print(config)
|
||||
matrix_config = {}
|
||||
for k, v in zip(matrix.keys(), config):
|
||||
matrix_config[k] = v
|
||||
#print(matrix_config)
|
||||
if any(map(lambda ex: do_exclude(matrix_config, ex), exclude)):
|
||||
continue
|
||||
else:
|
||||
config_tuple = tuple(values[i].index(p) for i, p in enumerate(config))
|
||||
results[config_tuple] = fn(matrix_config)
|
||||
# Fudged data for testing
|
||||
#print(config_tuple)
|
||||
#if "symbols" not in matrix_config:
|
||||
# results[config_tuple] = matrix_config["compiler"] != "g++-10"
|
||||
#else:
|
||||
# results[config_tuple] = not (matrix_config["compiler"] == "clang++-14" and matrix_config["symbols"] == "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE")
|
||||
# I had an idea for printing 2d slices of the n-dimensional matrix, but it didn't pan out as much as I'd hoped
|
||||
dimensions = len(values)
|
||||
# # Output diagnostic tables
|
||||
# print("Results:", results)
|
||||
# if dimensions >= 2:
|
||||
# for iteraxes in itertools.combinations(range(dimensions), dimensions - 2):
|
||||
# # iteraxes are the axes we iterate over to slice, these fixed axes are the axes of the table
|
||||
# # just the complement of axes, these are the two fixed axes
|
||||
# fixed = [x for x in range(dimensions) if x not in iteraxes]
|
||||
# assert(len(fixed) == 2)
|
||||
# if any([len(values[i]) == 1 for i in fixed]):
|
||||
# continue
|
||||
# print("Fixed:", fixed)
|
||||
# for iteraxesvalues in itertools.product(
|
||||
# *[range(len(values[i])) if i in iteraxes else [-1] for i in range(dimensions)]
|
||||
# ):
|
||||
# print(">>", iteraxesvalues)
|
||||
# # Now that we have our iteraxes values we have a unique plane
|
||||
# table = [
|
||||
# ["", *[value for value in values[fixed[0]]]]
|
||||
# ]
|
||||
# #print(values[fixed[1]])
|
||||
# for row_i, row_value in enumerate(values[fixed[1]]):
|
||||
# row = [row_value]
|
||||
# for col_i in range(len(values[fixed[0]])):
|
||||
# iteraxesvaluescopy = [x for x in iteraxesvalues]
|
||||
# iteraxesvaluescopy[fixed[1]] = row_i
|
||||
# iteraxesvaluescopy[fixed[0]] = col_i
|
||||
# #print("----->", iteraxesvaluescopy)
|
||||
# row.append(
|
||||
# f"{Fore.GREEN}{Style.BRIGHT}Good{Style.RESET_ALL}"
|
||||
# if results[tuple(iteraxesvaluescopy)]
|
||||
# else f"{Fore.RED}{Style.BRIGHT}Bad{Style.RESET_ALL}"
|
||||
# if tuple(iteraxesvaluescopy) in results else ""
|
||||
# )
|
||||
# table.append(row)
|
||||
# print_table(table)
|
||||
|
||||
# Better idea would be looking for m<n tuples that are consistently failing and reporting on those
|
||||
#for fixed_axes in itertools.product(range(dimensions), 2):
|
||||
# pass
|
||||
|
||||
print("Results:")
|
||||
table = [keys]
|
||||
for result in results:
|
||||
def print_results(self):
|
||||
self.log("Results:")
|
||||
table = [self.keys]
|
||||
for result in self.results:
|
||||
table.append([
|
||||
f"{Fore.GREEN if results[result] else Fore.RED}{Style.BRIGHT}{values[i][v]}{Style.RESET_ALL}"
|
||||
f"{Fore.GREEN if self.results[result] else Fore.RED}{Style.BRIGHT}{self.values[i][v]}{Style.RESET_ALL}"
|
||||
for i, v in enumerate(result)
|
||||
])
|
||||
print_table(table)
|
||||
self.print_table(table)
|
||||
|
||||
137
cmake/Autoconfig.cmake
Normal file
137
cmake/Autoconfig.cmake
Normal file
@ -0,0 +1,137 @@
|
||||
# ================================================== Platform Support ==================================================
|
||||
function(check_support var source includes libraries definitions)
|
||||
set(CMAKE_REQUIRED_INCLUDES "${includes}")
|
||||
list(APPEND CMAKE_REQUIRED_INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
set(CMAKE_REQUIRED_LIBRARIES "${libraries}")
|
||||
set(CMAKE_REQUIRED_DEFINITIONS "${definitions}")
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
string(CONCAT full_source "#include \"${source}\"" ${nonce})
|
||||
check_cxx_source_compiles(${full_source} ${var})
|
||||
set(${var} ${${var}} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||
check_support(HAS_CXXABI has_cxxabi.cpp "" "" "")
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||
check_support(HAS_ATTRIBUTE_PACKED has_attribute_packed.cpp "" "" "")
|
||||
endif()
|
||||
|
||||
if(NOT WIN32)
|
||||
check_support(HAS_UNWIND has_unwind.cpp "" "" "")
|
||||
check_support(HAS_EXECINFO has_execinfo.cpp "" "" "")
|
||||
else()
|
||||
check_support(HAS_STACKWALK has_stackwalk.cpp "" "dbghelp" "")
|
||||
endif()
|
||||
|
||||
if(NOT WIN32 OR MINGW)
|
||||
check_support(HAS_BACKTRACE has_backtrace.cpp "" "backtrace" "${CPPTRACE_BACKTRACE_PATH_DEFINITION}")
|
||||
set(STACKTRACE_LINK_LIB "stdc++_libbacktrace")
|
||||
check_support(HAS_CXX_EXCEPTION_TYPE has_cxx_exception_type.cpp "" "" "")
|
||||
endif()
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
check_support(HAS_DL_FIND_OBJECT has_dl_find_object.cpp "" "dl" "")
|
||||
if(NOT HAS_DL_FIND_OBJECT)
|
||||
check_support(HAS_DLADDR1 has_dladdr1.cpp "" "dl" "")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
check_support(HAS_MACH_VM has_mach_vm.cpp "" "" "")
|
||||
endif()
|
||||
|
||||
# ================================================ Autoconfig unwinding ================================================
|
||||
# Unwind back-ends
|
||||
if(
|
||||
NOT (
|
||||
CPPTRACE_UNWIND_WITH_UNWIND OR
|
||||
CPPTRACE_UNWIND_WITH_LIBUNWIND OR
|
||||
CPPTRACE_UNWIND_WITH_EXECINFO OR
|
||||
CPPTRACE_UNWIND_WITH_WINAPI OR
|
||||
CPPTRACE_UNWIND_WITH_DBGHELP OR
|
||||
CPPTRACE_UNWIND_WITH_NOTHING
|
||||
)
|
||||
)
|
||||
# Attempt to auto-config
|
||||
if(APPLE AND ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang"))
|
||||
if(HAS_EXECINFO)
|
||||
set(CPPTRACE_UNWIND_WITH_EXECINFO On)
|
||||
message(STATUS "Cpptrace auto config: Using execinfo.h for unwinding")
|
||||
else()
|
||||
set(CPPTRACE_UNWIND_WITH_NOTHING On)
|
||||
message(FATAL_ERROR "Cpptrace auto config: No unwinding back-end seems to be supported, stack tracing will not work. To compile anyway set CPPTRACE_UNWIND_WITH_NOTHING.")
|
||||
endif()
|
||||
elseif(UNIX)
|
||||
if(HAS_UNWIND)
|
||||
set(CPPTRACE_UNWIND_WITH_UNWIND On)
|
||||
message(STATUS "Cpptrace auto config: Using libgcc unwind for unwinding")
|
||||
elseif(HAS_EXECINFO)
|
||||
set(CPPTRACE_UNWIND_WITH_EXECINFO On)
|
||||
message(STATUS "Cpptrace auto config: Using execinfo.h for unwinding")
|
||||
else()
|
||||
set(CPPTRACE_UNWIND_WITH_NOTHING On)
|
||||
message(FATAL_ERROR "Cpptrace auto config: No unwinding back-end seems to be supported, stack tracing will not work. To compile anyway set CPPTRACE_UNWIND_WITH_NOTHING.")
|
||||
endif()
|
||||
elseif(MINGW OR WIN32)
|
||||
if(HAS_STACKWALK)
|
||||
set(CPPTRACE_UNWIND_WITH_DBGHELP On)
|
||||
message(STATUS "Cpptrace auto config: Using dbghelp for unwinding")
|
||||
else()
|
||||
set(CPPTRACE_UNWIND_WITH_WINAPI On)
|
||||
message(STATUS "Cpptrace auto config: Using winapi for unwinding")
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
#message(STATUS "MANUAL CONFIG SPECIFIED")
|
||||
endif()
|
||||
|
||||
# ================================================= Autoconfig symbols =================================================
|
||||
if(
|
||||
NOT (
|
||||
CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE OR
|
||||
CPPTRACE_GET_SYMBOLS_WITH_LIBDL OR
|
||||
CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE OR
|
||||
CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF OR
|
||||
CPPTRACE_GET_SYMBOLS_WITH_DBGHELP OR
|
||||
CPPTRACE_GET_SYMBOLS_WITH_NOTHING
|
||||
)
|
||||
)
|
||||
if(UNIX)
|
||||
message(STATUS "Cpptrace auto config: Using libdwarf for symbols")
|
||||
set(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF On)
|
||||
elseif(MINGW)
|
||||
message(STATUS "Cpptrace auto config: Using libdwarf + dbghelp for symbols")
|
||||
# Use both dbghelp and libdwarf under mingw: Some files may use pdb symbols, e.g. system dlls like KERNEL32.dll and
|
||||
# ntdll.dll at the very least, but also other libraries linked with may have pdb symbols.
|
||||
set(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF On)
|
||||
set(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP On)
|
||||
else()
|
||||
message(STATUS "Cpptrace auto config: Using dbghelp for symbols")
|
||||
set(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP On)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# =============================================== Autoconfig demangling ================================================
|
||||
# Handle demangle configuration
|
||||
if(
|
||||
NOT (
|
||||
CPPTRACE_DEMANGLE_WITH_CXXABI OR
|
||||
CPPTRACE_DEMANGLE_WITH_WINAPI OR
|
||||
CPPTRACE_DEMANGLE_WITH_NOTHING
|
||||
)
|
||||
)
|
||||
if(HAS_CXXABI)
|
||||
message(STATUS "Cpptrace auto config: Using cxxabi for demangling")
|
||||
set(CPPTRACE_DEMANGLE_WITH_CXXABI On)
|
||||
elseif(WIN32 AND NOT MINGW)
|
||||
message(STATUS "Cpptrace auto config: Using dbghelp for demangling")
|
||||
set(CPPTRACE_DEMANGLE_WITH_WINAPI On)
|
||||
else()
|
||||
set(CPPTRACE_DEMANGLE_WITH_NOTHING On)
|
||||
endif()
|
||||
else()
|
||||
#message(STATUS "Manual demangling back-end specified")
|
||||
endif()
|
||||
@ -5,8 +5,9 @@ include(CMakePackageConfigHelpers)
|
||||
install(
|
||||
DIRECTORY
|
||||
"${PROJECT_SOURCE_DIR}/include/" # our header files
|
||||
"${PROJECT_BINARY_DIR}/include/" # generated header files
|
||||
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
|
||||
COMPONENT ${package_name}-development
|
||||
COMPONENT ${package_name}_development
|
||||
# PATTERN "**/third_party" EXCLUDE # skip third party directory
|
||||
# PATTERN "**/third_party/**" EXCLUDE # skip third party files
|
||||
)
|
||||
@ -17,12 +18,12 @@ install(
|
||||
TARGETS ${target_name}
|
||||
EXPORT ${package_name}-targets
|
||||
RUNTIME #
|
||||
COMPONENT ${package_name}-runtime
|
||||
COMPONENT ${package_name}_runtime
|
||||
LIBRARY #
|
||||
COMPONENT ${package_name}-runtime
|
||||
NAMELINK_COMPONENT ${package_name}-development
|
||||
COMPONENT ${package_name}_runtime
|
||||
NAMELINK_COMPONENT ${package_name}_development
|
||||
ARCHIVE #
|
||||
COMPONENT ${package_name}-development
|
||||
COMPONENT ${package_name}_development
|
||||
INCLUDES #
|
||||
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
|
||||
)
|
||||
@ -38,7 +39,7 @@ configure_file(
|
||||
install(
|
||||
FILES "${PROJECT_BINARY_DIR}/cmake/${package_name}-config.cmake"
|
||||
DESTINATION "${CPPTRACE_INSTALL_CMAKEDIR}"
|
||||
COMPONENT ${package_name}-development
|
||||
COMPONENT ${package_name}_development
|
||||
)
|
||||
|
||||
# create version file for consumer to check version in CMake
|
||||
@ -51,7 +52,7 @@ write_basic_package_version_file(
|
||||
install(
|
||||
FILES "${PROJECT_BINARY_DIR}/${package_name}-config-version.cmake"
|
||||
DESTINATION "${CPPTRACE_INSTALL_CMAKEDIR}"
|
||||
COMPONENT ${package_name}-development
|
||||
COMPONENT ${package_name}_development
|
||||
)
|
||||
|
||||
# create targets file included by config file with targets for consumers
|
||||
@ -59,9 +60,17 @@ install(
|
||||
EXPORT ${package_name}-targets
|
||||
NAMESPACE cpptrace::
|
||||
DESTINATION "${CPPTRACE_INSTALL_CMAKEDIR}"
|
||||
COMPONENT ${package_name}-development
|
||||
COMPONENT ${package_name}_development
|
||||
)
|
||||
|
||||
if(CPPTRACE_PROVIDE_EXPORT_SET)
|
||||
export(
|
||||
TARGETS ${target_name}
|
||||
NAMESPACE cpptrace::
|
||||
FILE "${PROJECT_BINARY_DIR}/${package_name}-targets.cmake"
|
||||
)
|
||||
endif()
|
||||
|
||||
# Findzstd.cmake
|
||||
# vcpkg doesn't like anything being put in share/, which is where this goes apparently on their setup
|
||||
if(NOT CPPTRACE_VCPKG)
|
||||
|
||||
@ -143,7 +143,7 @@ option(CPPTRACE_DEMANGLE_WITH_NOTHING "" OFF)
|
||||
# ---- Back-end configurations ----
|
||||
|
||||
set(CPPTRACE_BACKTRACE_PATH "" CACHE STRING "Path to backtrace.h, if the compiler doesn't already know it. Check /usr/lib/gcc/x86_64-linux-gnu/*/include.")
|
||||
set(CPPTRACE_HARD_MAX_FRAMES "" CACHE STRING "Hard limit on unwinding depth. Default is 200.")
|
||||
set(CPPTRACE_HARD_MAX_FRAMES "" CACHE STRING "Hard limit on unwinding depth. Default is 400.")
|
||||
set(CPPTRACE_ADDR2LINE_PATH "" CACHE STRING "Absolute path to the addr2line executable you want to use.")
|
||||
option(CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH "" OFF)
|
||||
|
||||
@ -151,9 +151,19 @@ option(CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH "" OFF)
|
||||
|
||||
if(PROJECT_IS_TOP_LEVEL)
|
||||
option(CPPTRACE_BUILD_TESTING "" OFF)
|
||||
option(CPPTRACE_BUILD_TOOLS "" OFF)
|
||||
option(CPPTRACE_BUILD_BENCHMARK "" OFF)
|
||||
option(CPPTRACE_BUILD_NO_SYMBOLS "" OFF)
|
||||
option(CPPTRACE_BUILD_TESTING_SPLIT_DWARF "" OFF)
|
||||
set(CPPTRACE_BUILD_TESTING_DWARF_VERSION "0" CACHE STRING "")
|
||||
option(CPPTRACE_BUILD_TEST_RDYNAMIC "" OFF)
|
||||
mark_as_advanced(
|
||||
CPPTRACE_BUILD_TESTING
|
||||
CPPTRACE_BUILD_TOOLS
|
||||
CPPTRACE_BUILD_BENCHMARK
|
||||
CPPTRACE_BUILD_NO_SYMBOLS
|
||||
CPPTRACE_BUILD_TESTING_SPLIT_DWARF
|
||||
CPPTRACE_BUILD_TESTING_DWARF_VERSION
|
||||
CPPTRACE_BUILD_TEST_RDYNAMIC
|
||||
)
|
||||
endif()
|
||||
@ -167,7 +177,15 @@ option(CPPTRACE_SANITIZER_BUILD "" OFF)
|
||||
option(CPPTRACE_WERROR_BUILD "" OFF)
|
||||
option(CPPTRACE_POSITION_INDEPENDENT_CODE "" ON)
|
||||
option(CPPTRACE_SKIP_UNIT "" OFF)
|
||||
option(CPPTRACE_STD_FORMAT "" ON)
|
||||
option(CPPTRACE_UNPREFIXED_TRY_CATCH "" OFF)
|
||||
option(CPPTRACE_USE_EXTERNAL_GTEST "" OFF)
|
||||
set(CPPTRACE_ZSTD_URL "https://github.com/facebook/zstd/releases/download/v1.5.7/zstd-1.5.7.tar.gz" CACHE STRING "")
|
||||
set(CPPTRACE_LIBDWARF_REPO "https://github.com/jeremy-rifkin/libdwarf-lite.git" CACHE STRING "")
|
||||
set(CPPTRACE_LIBDWARF_TAG "fe09ca800b988e2ff21225ac5e7468ceade2a30e" CACHE STRING "") # v0.11.1
|
||||
set(CPPTRACE_LIBDWARF_SHALLOW "1" CACHE STRING "")
|
||||
option(CPPTRACE_PROVIDE_EXPORT_SET "" ON)
|
||||
option(CPPTRACE_PROVIDE_EXPORT_SET_FOR_LIBDWARF "" OFF)
|
||||
|
||||
mark_as_advanced(
|
||||
CPPTRACE_BACKTRACE_PATH
|
||||
@ -179,4 +197,12 @@ mark_as_advanced(
|
||||
CPPTRACE_VCPKG
|
||||
CPPTRACE_SKIP_UNIT
|
||||
CPPTRACE_USE_EXTERNAL_GTEST
|
||||
CPPTRACE_ZSTD_REPO
|
||||
CPPTRACE_ZSTD_TAG
|
||||
CPPTRACE_ZSTD_SHALLOW
|
||||
CPPTRACE_LIBDWARF_REPO
|
||||
CPPTRACE_LIBDWARF_TAG
|
||||
CPPTRACE_LIBDWARF_SHALLOW
|
||||
CPPTRACE_PROVIDE_EXPORT_SET
|
||||
CPPTRACE_PROVIDE_EXPORT_SET_FOR_LIBDWARF
|
||||
)
|
||||
|
||||
6
cmake/has_attribute_packed.cpp
Normal file
6
cmake/has_attribute_packed.cpp
Normal file
@ -0,0 +1,6 @@
|
||||
struct __attribute__((packed)) foo {
|
||||
int i;
|
||||
double d;
|
||||
};
|
||||
|
||||
int main() {}
|
||||
23
cmake/has_mach_vm.cpp
Normal file
23
cmake/has_mach_vm.cpp
Normal file
@ -0,0 +1,23 @@
|
||||
#include <mach/mach.h>
|
||||
#include <mach/mach_vm.h>
|
||||
#include <cstdint>
|
||||
|
||||
int main() {
|
||||
mach_vm_size_t vmsize;
|
||||
uintptr_t addr = reinterpret_cast<uintptr_t>(&vmsize);
|
||||
uintptr_t page_addr = addr & ~(4096 - 1);
|
||||
mach_vm_address_t address = (mach_vm_address_t)page_addr;
|
||||
vm_region_basic_info_data_t info;
|
||||
mach_msg_type_number_t info_count =
|
||||
sizeof(size_t) == 8 ? VM_REGION_BASIC_INFO_COUNT_64 : VM_REGION_BASIC_INFO_COUNT;
|
||||
memory_object_name_t object;
|
||||
mach_vm_region(
|
||||
mach_task_self(),
|
||||
&address,
|
||||
&vmsize,
|
||||
VM_REGION_BASIC_INFO,
|
||||
(vm_region_info_t)&info,
|
||||
&info_count,
|
||||
&object
|
||||
);
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <dbghelp.h>
|
||||
|
||||
|
||||
11
cmake/in/version-hpp.in
Normal file
11
cmake/in/version-hpp.in
Normal 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
|
||||
@ -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();
|
||||
```
|
||||
|
||||
@ -11,14 +11,21 @@
|
||||
|
||||
# Overview
|
||||
|
||||
Signal-safe stack tracing is very useful for debugging application crashes, e.g. SIGSEGVs or
|
||||
SIGTRAPs, but it's very difficult to do correctly and most implementations I see online do this
|
||||
incorrectly.
|
||||
Stack traces from signal handlers can provide very helpful information for debugging application crashes, e.g. from
|
||||
SIGSEGV or SIGTRAP handlers. Signal handlers are really restrictive environments as your application could be
|
||||
interrupted by a signal at any point, including in the middle of malloc or buffered IO or while holding a lock.
|
||||
Doing a stack trace in a signal handler is possible but it requires a lot of care. This is difficult to do correctly
|
||||
and most examples online do this incorrectly.
|
||||
|
||||
Signal-safe tracing is difficult because most methods for unwinding are not signal-safe, figuring
|
||||
out what shared objects addresses are in is tricky to do in a signal-safe manner (`dladdr` isn't
|
||||
safe), and then the symbol/line resolution process is pretty much impossible to do safely (parsing
|
||||
dwarf will not be safe).
|
||||
It is not possible to resolve debug symbols safely in the process from a signal handler without heroic effort. In order
|
||||
to produce a full trace there are three options:
|
||||
1. Carefully save the object trace information to be resolved at a later time outside the signal handler
|
||||
2. Write the object trace information to a file to be resolved later
|
||||
3. Spawn a new process, communicate object trace information to that process, and have that process do the trace
|
||||
resolution
|
||||
|
||||
For traces on segfaults, e.g., only options 2 and 3 are viable. The this guide will go over approach 3 and the cpptrace
|
||||
safe API.
|
||||
|
||||
# Big-Picture
|
||||
|
||||
@ -35,6 +42,10 @@ memory corruption.
|
||||
> [!IMPORTANT]
|
||||
> Currently signal-safe stack unwinding is only possible with `libunwind`, more details later.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> `_dl_find_object` is required for signal-safe stack tracing. This is a relatively recent addition to glibc, added in
|
||||
> glibc 2.35.
|
||||
|
||||
> [!CAUTION]
|
||||
> Calls to shared objects can be lazy-loaded where the first call to the shared object invokes non-signal-safe functions
|
||||
> such as `malloc()`. Because of this, the signal safe api must be "warmed up" ahead of a signal handler.
|
||||
@ -53,7 +64,7 @@ namespace cpptrace {
|
||||
|
||||
struct safe_object_frame {
|
||||
frame_ptr raw_address;
|
||||
frame_ptr address_relative_to_object_start; // object base address must yet be added
|
||||
frame_ptr address_relative_to_object_start;
|
||||
char object_path[CPPTRACE_PATH_MAX + 1];
|
||||
object_frame resolve() const; // To be called outside a signal handler. Not signal safe.
|
||||
};
|
||||
@ -62,6 +73,7 @@ namespace cpptrace {
|
||||
void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
|
||||
// signal-safe
|
||||
bool can_signal_safe_unwind();
|
||||
bool can_get_safe_object_frame();
|
||||
}
|
||||
```
|
||||
|
||||
@ -93,6 +105,9 @@ only ways to do this safely as far as I can tell.
|
||||
`safe_generate_raw_trace` will just produce an empty trace and if object information can't be resolved in a signal-safe
|
||||
way then `get_safe_object_frame` will not populate fields beyond the `raw_address`.
|
||||
|
||||
`cpptrace::can_signal_safe_unwind` and `cpptrace::can_get_safe_object_frame` can be used to check for safe tracing
|
||||
support.
|
||||
|
||||
Currently the only back-end that can unwind safely is libunwind. Currently, the only way I know to get `dladdr`'s
|
||||
information in a signal-safe manner is `_dl_find_object`, which doesn't exist on macos (or windows of course). If anyone
|
||||
knows ways to do these safely on other platforms, I'd be much appreciative.
|
||||
@ -102,8 +117,8 @@ knows ways to do these safely on other platforms, I'd be much appreciative.
|
||||
Of the three strategies, `fork()` + `exec()`, is the most technically involved and the only way to resolve while the
|
||||
signal handler is running. I think it's worthwhile to do a deep-dive into how to do this.
|
||||
|
||||
In the source code, [`signal_demo.cpp`](signal_demo.cpp) and [`signal_tracer.cpp`](signal_tracer.cpp) provide a working
|
||||
example for what is described here.
|
||||
In the source code, [`signal_demo.cpp`](../test/signal_demo.cpp) and [`signal_tracer.cpp`](../test/signal_tracer.cpp)
|
||||
provide a working example for what is described here.
|
||||
|
||||
## In the main program
|
||||
|
||||
@ -136,12 +151,16 @@ struct pipe_t {
|
||||
};
|
||||
};
|
||||
|
||||
void do_signal_safe_trace(cpptrace::frame_ptr* buffer, std::size_t size) {
|
||||
void do_signal_safe_trace(cpptrace::frame_ptr* buffer, std::size_t count) {
|
||||
// Setup pipe and spawn child
|
||||
pipe_t input_pipe;
|
||||
pipe(input_pipe.data);
|
||||
const pid_t pid = fork();
|
||||
if(pid == -1) { return; /* Some error occurred */ }
|
||||
if(pid == -1) {
|
||||
const char* fork_failure_message = "fork() failed\n";
|
||||
write(STDERR_FILENO, fork_failure_message, strlen(fork_failure_message));
|
||||
return;
|
||||
}
|
||||
if(pid == 0) { // child
|
||||
dup2(input_pipe.read_end, STDIN_FILENO);
|
||||
close(input_pipe.read_end);
|
||||
@ -172,7 +191,7 @@ void handler(int signo, siginfo_t* info, void* context) {
|
||||
constexpr std::size_t N = 100;
|
||||
cpptrace::frame_ptr buffer[N];
|
||||
std::size_t count = cpptrace::safe_generate_raw_trace(buffer, N);
|
||||
do_signal_safe_trace(buffer, N);
|
||||
do_signal_safe_trace(buffer, count);
|
||||
// Up to you if you want to exit or continue or whatever
|
||||
_exit(1);
|
||||
}
|
||||
@ -219,9 +238,6 @@ int main() {
|
||||
std::size_t res = fread(&frame, sizeof(frame), 1, stdin);
|
||||
if(res == 0) {
|
||||
break;
|
||||
} else if(res == -1) {
|
||||
perror("Something went wrong while reading from the pipe");
|
||||
break;
|
||||
} else if(res != 1) {
|
||||
std::cerr<<"Something went wrong while reading from the pipe"<<res<<" "<<std::endl;
|
||||
break;
|
||||
|
||||
245
include/cpptrace/basic.hpp
Normal file
245
include/cpptrace/basic.hpp
Normal file
@ -0,0 +1,245 @@
|
||||
#ifndef CPPTRACE_BASIC_HPP
|
||||
#define CPPTRACE_BASIC_HPP
|
||||
|
||||
#include <cpptrace/forward.hpp>
|
||||
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <iosfwd>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define CPPTRACE_EXPORT_ATTR __declspec(dllexport)
|
||||
#define CPPTRACE_IMPORT_ATTR __declspec(dllimport)
|
||||
#else
|
||||
#define CPPTRACE_EXPORT_ATTR __attribute__((visibility("default")))
|
||||
#define CPPTRACE_IMPORT_ATTR __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
#ifdef CPPTRACE_STATIC_DEFINE
|
||||
# define CPPTRACE_EXPORT
|
||||
# define CPPTRACE_NO_EXPORT
|
||||
#else
|
||||
# ifndef CPPTRACE_EXPORT
|
||||
# ifdef cpptrace_lib_EXPORTS
|
||||
/* We are building this library */
|
||||
# define CPPTRACE_EXPORT CPPTRACE_EXPORT_ATTR
|
||||
# else
|
||||
/* We are using this library */
|
||||
# define CPPTRACE_EXPORT CPPTRACE_IMPORT_ATTR
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if __cplusplus >= 201703L
|
||||
#define CONSTEXPR_SINCE_CPP17 constexpr
|
||||
#else
|
||||
#define CONSTEXPR_SINCE_CPP17
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define CPPTRACE_FORCE_NO_INLINE __declspec(noinline)
|
||||
#else
|
||||
#define CPPTRACE_FORCE_NO_INLINE __attribute__((noinline))
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
// warning C4251: using non-dll-exported type in dll-exported type, firing on std::vector<frame_ptr> and others for some
|
||||
// reason
|
||||
// 4275 is the same thing but for base classes
|
||||
#pragma warning(disable: 4251; disable: 4275)
|
||||
#endif
|
||||
|
||||
namespace cpptrace {
|
||||
struct CPPTRACE_EXPORT raw_trace {
|
||||
std::vector<frame_ptr> frames;
|
||||
static raw_trace current(std::size_t skip = 0);
|
||||
static raw_trace current(std::size_t skip, std::size_t max_depth);
|
||||
object_trace resolve_object_trace() const;
|
||||
stacktrace resolve() const;
|
||||
void clear();
|
||||
bool empty() const noexcept;
|
||||
|
||||
using iterator = std::vector<frame_ptr>::iterator;
|
||||
using const_iterator = std::vector<frame_ptr>::const_iterator;
|
||||
inline iterator begin() noexcept { return frames.begin(); }
|
||||
inline iterator end() noexcept { return frames.end(); }
|
||||
inline const_iterator begin() const noexcept { return frames.begin(); }
|
||||
inline const_iterator end() const noexcept { return frames.end(); }
|
||||
inline const_iterator cbegin() const noexcept { return frames.cbegin(); }
|
||||
inline const_iterator cend() const noexcept { return frames.cend(); }
|
||||
};
|
||||
|
||||
struct CPPTRACE_EXPORT object_frame {
|
||||
frame_ptr raw_address;
|
||||
frame_ptr object_address;
|
||||
std::string object_path;
|
||||
};
|
||||
|
||||
struct CPPTRACE_EXPORT object_trace {
|
||||
std::vector<object_frame> frames;
|
||||
static object_trace current(std::size_t skip = 0);
|
||||
static object_trace current(std::size_t skip, std::size_t max_depth);
|
||||
stacktrace resolve() const;
|
||||
void clear();
|
||||
bool empty() const noexcept;
|
||||
|
||||
using iterator = std::vector<object_frame>::iterator;
|
||||
using const_iterator = std::vector<object_frame>::const_iterator;
|
||||
inline iterator begin() noexcept { return frames.begin(); }
|
||||
inline iterator end() noexcept { return frames.end(); }
|
||||
inline const_iterator begin() const noexcept { return frames.begin(); }
|
||||
inline const_iterator end() const noexcept { return frames.end(); }
|
||||
inline const_iterator cbegin() const noexcept { return frames.cbegin(); }
|
||||
inline const_iterator cend() const noexcept { return frames.cend(); }
|
||||
};
|
||||
|
||||
// This represents a nullable integer type
|
||||
// The max value of the type is used as a sentinel
|
||||
// This is used over std::optional because the library is C++11 and also std::optional is a bit heavy-duty for this
|
||||
// use.
|
||||
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
|
||||
struct nullable {
|
||||
T raw_value = null_value();
|
||||
constexpr nullable() noexcept = default;
|
||||
constexpr nullable(T value) noexcept : raw_value(value) {}
|
||||
CONSTEXPR_SINCE_CPP17 nullable& operator=(T value) noexcept {
|
||||
raw_value = value;
|
||||
return *this;
|
||||
}
|
||||
constexpr bool has_value() const noexcept {
|
||||
return raw_value != null_value();
|
||||
}
|
||||
CONSTEXPR_SINCE_CPP17 T& value() noexcept {
|
||||
return raw_value;
|
||||
}
|
||||
constexpr const T& value() const noexcept {
|
||||
return raw_value;
|
||||
}
|
||||
constexpr T value_or(T alternative) const noexcept {
|
||||
return has_value() ? raw_value : alternative;
|
||||
}
|
||||
CONSTEXPR_SINCE_CPP17 void swap(nullable& other) noexcept {
|
||||
std::swap(raw_value, other.raw_value);
|
||||
}
|
||||
CONSTEXPR_SINCE_CPP17 void reset() noexcept {
|
||||
raw_value = (std::numeric_limits<T>::max)();
|
||||
}
|
||||
constexpr bool operator==(const nullable& other) const noexcept {
|
||||
return raw_value == other.raw_value;
|
||||
}
|
||||
constexpr bool operator!=(const nullable& other) const noexcept {
|
||||
return raw_value != other.raw_value;
|
||||
}
|
||||
constexpr static T null_value() noexcept {
|
||||
return (std::numeric_limits<T>::max)();
|
||||
}
|
||||
constexpr static nullable null() noexcept {
|
||||
return { null_value() };
|
||||
}
|
||||
};
|
||||
|
||||
struct CPPTRACE_EXPORT stacktrace_frame {
|
||||
frame_ptr raw_address;
|
||||
frame_ptr object_address;
|
||||
nullable<std::uint32_t> line;
|
||||
nullable<std::uint32_t> column;
|
||||
std::string filename;
|
||||
std::string symbol;
|
||||
bool is_inline;
|
||||
|
||||
bool operator==(const stacktrace_frame& other) const {
|
||||
return raw_address == other.raw_address
|
||||
&& object_address == other.object_address
|
||||
&& line == other.line
|
||||
&& column == other.column
|
||||
&& filename == other.filename
|
||||
&& symbol == other.symbol;
|
||||
}
|
||||
|
||||
bool operator!=(const stacktrace_frame& other) const {
|
||||
return !operator==(other);
|
||||
}
|
||||
|
||||
object_frame get_object_info() const;
|
||||
|
||||
std::string to_string() const;
|
||||
std::string to_string(bool color) const;
|
||||
friend std::ostream& operator<<(std::ostream& stream, const stacktrace_frame& frame);
|
||||
};
|
||||
|
||||
struct CPPTRACE_EXPORT stacktrace {
|
||||
std::vector<stacktrace_frame> frames;
|
||||
static stacktrace current(std::size_t skip = 0);
|
||||
static stacktrace current(std::size_t skip, std::size_t max_depth);
|
||||
void print() const;
|
||||
void print(std::ostream& stream) const;
|
||||
void print(std::ostream& stream, bool color) const;
|
||||
void print_with_snippets() const;
|
||||
void print_with_snippets(std::ostream& stream) const;
|
||||
void print_with_snippets(std::ostream& stream, bool color) const;
|
||||
void clear();
|
||||
bool empty() const noexcept;
|
||||
std::string to_string(bool color = false) const;
|
||||
friend std::ostream& operator<<(std::ostream& stream, const stacktrace& trace);
|
||||
|
||||
using iterator = std::vector<stacktrace_frame>::iterator;
|
||||
using const_iterator = std::vector<stacktrace_frame>::const_iterator;
|
||||
inline iterator begin() noexcept { return frames.begin(); }
|
||||
inline iterator end() noexcept { return frames.end(); }
|
||||
inline const_iterator begin() const noexcept { return frames.begin(); }
|
||||
inline const_iterator end() const noexcept { return frames.end(); }
|
||||
inline const_iterator cbegin() const noexcept { return frames.cbegin(); }
|
||||
inline const_iterator cend() const noexcept { return frames.cend(); }
|
||||
private:
|
||||
friend void print_terminate_trace();
|
||||
};
|
||||
|
||||
CPPTRACE_EXPORT raw_trace generate_raw_trace(std::size_t skip = 0);
|
||||
CPPTRACE_EXPORT raw_trace generate_raw_trace(std::size_t skip, std::size_t max_depth);
|
||||
CPPTRACE_EXPORT object_trace generate_object_trace(std::size_t skip = 0);
|
||||
CPPTRACE_EXPORT object_trace generate_object_trace(std::size_t skip, std::size_t max_depth);
|
||||
CPPTRACE_EXPORT stacktrace generate_trace(std::size_t skip = 0);
|
||||
CPPTRACE_EXPORT stacktrace generate_trace(std::size_t skip, std::size_t max_depth);
|
||||
|
||||
// Path max isn't so simple, so I'm choosing 4096 which seems to encompass what all major OS's expect and should be
|
||||
// fine in all reasonable cases.
|
||||
// https://eklitzke.org/path-max-is-tricky
|
||||
// https://insanecoding.blogspot.com/2007/11/pathmax-simply-isnt.html
|
||||
#define CPPTRACE_PATH_MAX 4096
|
||||
|
||||
// safe tracing interface
|
||||
// signal-safe
|
||||
CPPTRACE_EXPORT std::size_t safe_generate_raw_trace(
|
||||
frame_ptr* buffer,
|
||||
std::size_t size,
|
||||
std::size_t skip = 0
|
||||
);
|
||||
// signal-safe
|
||||
CPPTRACE_EXPORT std::size_t safe_generate_raw_trace(
|
||||
frame_ptr* buffer,
|
||||
std::size_t size,
|
||||
std::size_t skip,
|
||||
std::size_t max_depth
|
||||
);
|
||||
struct CPPTRACE_EXPORT safe_object_frame {
|
||||
frame_ptr raw_address;
|
||||
// This ends up being the real object address. It was named at a time when I thought the object base address
|
||||
// still needed to be added in
|
||||
frame_ptr address_relative_to_object_start;
|
||||
char object_path[CPPTRACE_PATH_MAX + 1];
|
||||
// To be called outside a signal handler. Not signal safe.
|
||||
object_frame resolve() const;
|
||||
};
|
||||
// signal-safe
|
||||
CPPTRACE_EXPORT void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
|
||||
CPPTRACE_EXPORT bool can_signal_safe_unwind();
|
||||
CPPTRACE_EXPORT bool can_get_safe_object_frame();
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@ -1,493 +1,9 @@
|
||||
#ifndef CPPTRACE_HPP
|
||||
#define CPPTRACE_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <limits>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define CPPTRACE_EXPORT_ATTR __declspec(dllexport)
|
||||
#define CPPTRACE_IMPORT_ATTR __declspec(dllimport)
|
||||
#else
|
||||
#define CPPTRACE_EXPORT_ATTR __attribute__((visibility("default")))
|
||||
#define CPPTRACE_IMPORT_ATTR __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
#ifdef CPPTRACE_STATIC_DEFINE
|
||||
# define CPPTRACE_EXPORT
|
||||
# define CPPTRACE_NO_EXPORT
|
||||
#else
|
||||
# ifndef CPPTRACE_EXPORT
|
||||
# ifdef cpptrace_lib_EXPORTS
|
||||
/* We are building this library */
|
||||
# define CPPTRACE_EXPORT CPPTRACE_EXPORT_ATTR
|
||||
# else
|
||||
/* We are using this library */
|
||||
# define CPPTRACE_EXPORT CPPTRACE_IMPORT_ATTR
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if __cplusplus >= 202002L
|
||||
#ifdef __has_include
|
||||
#if __has_include(<format>)
|
||||
#define CPPTRACE_STD_FORMAT
|
||||
#include <format>
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define CPPTRACE_FORCE_NO_INLINE __declspec(noinline)
|
||||
#else
|
||||
#define CPPTRACE_FORCE_NO_INLINE __attribute__((noinline))
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
// warning C4251: using non-dll-exported type in dll-exported type, firing on std::vector<frame_ptr> and others for some
|
||||
// reason
|
||||
// 4275 is the same thing but for base classes
|
||||
#pragma warning(disable: 4251; disable: 4275)
|
||||
#endif
|
||||
|
||||
namespace cpptrace {
|
||||
struct object_trace;
|
||||
struct stacktrace;
|
||||
|
||||
// Some type sufficient for an instruction pointer, currently always an alias to std::uintptr_t
|
||||
using frame_ptr = std::uintptr_t;
|
||||
|
||||
struct CPPTRACE_EXPORT raw_trace {
|
||||
std::vector<frame_ptr> frames;
|
||||
static raw_trace current(std::size_t skip = 0);
|
||||
static raw_trace current(std::size_t skip, std::size_t max_depth);
|
||||
object_trace resolve_object_trace() const;
|
||||
stacktrace resolve() const;
|
||||
void clear();
|
||||
bool empty() const noexcept;
|
||||
|
||||
using iterator = std::vector<frame_ptr>::iterator;
|
||||
using const_iterator = std::vector<frame_ptr>::const_iterator;
|
||||
inline iterator begin() noexcept { return frames.begin(); }
|
||||
inline iterator end() noexcept { return frames.end(); }
|
||||
inline const_iterator begin() const noexcept { return frames.begin(); }
|
||||
inline const_iterator end() const noexcept { return frames.end(); }
|
||||
inline const_iterator cbegin() const noexcept { return frames.cbegin(); }
|
||||
inline const_iterator cend() const noexcept { return frames.cend(); }
|
||||
};
|
||||
|
||||
struct CPPTRACE_EXPORT object_frame {
|
||||
frame_ptr raw_address;
|
||||
frame_ptr object_address;
|
||||
std::string object_path;
|
||||
};
|
||||
|
||||
struct CPPTRACE_EXPORT object_trace {
|
||||
std::vector<object_frame> frames;
|
||||
static object_trace current(std::size_t skip = 0);
|
||||
static object_trace current(std::size_t skip, std::size_t max_depth);
|
||||
stacktrace resolve() const;
|
||||
void clear();
|
||||
bool empty() const noexcept;
|
||||
|
||||
using iterator = std::vector<object_frame>::iterator;
|
||||
using const_iterator = std::vector<object_frame>::const_iterator;
|
||||
inline iterator begin() noexcept { return frames.begin(); }
|
||||
inline iterator end() noexcept { return frames.end(); }
|
||||
inline const_iterator begin() const noexcept { return frames.begin(); }
|
||||
inline const_iterator end() const noexcept { return frames.end(); }
|
||||
inline const_iterator cbegin() const noexcept { return frames.cbegin(); }
|
||||
inline const_iterator cend() const noexcept { return frames.cend(); }
|
||||
};
|
||||
|
||||
// This represents a nullable integer type
|
||||
// The max value of the type is used as a sentinel
|
||||
// This is used over std::optional because the library is C++11 and also std::optional is a bit heavy-duty for this
|
||||
// use.
|
||||
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
|
||||
struct nullable {
|
||||
T raw_value;
|
||||
nullable& operator=(T value) {
|
||||
raw_value = value;
|
||||
return *this;
|
||||
}
|
||||
bool has_value() const noexcept {
|
||||
return raw_value != (std::numeric_limits<T>::max)();
|
||||
}
|
||||
T& value() noexcept {
|
||||
return raw_value;
|
||||
}
|
||||
const T& value() const noexcept {
|
||||
return raw_value;
|
||||
}
|
||||
T value_or(T alternative) const noexcept {
|
||||
return has_value() ? raw_value : alternative;
|
||||
}
|
||||
void swap(nullable& other) noexcept {
|
||||
std::swap(raw_value, other.raw_value);
|
||||
}
|
||||
void reset() noexcept {
|
||||
raw_value = (std::numeric_limits<T>::max)();
|
||||
}
|
||||
bool operator==(const nullable& other) const noexcept {
|
||||
return raw_value == other.raw_value;
|
||||
}
|
||||
bool operator!=(const nullable& other) const noexcept {
|
||||
return raw_value != other.raw_value;
|
||||
}
|
||||
constexpr static nullable null() noexcept {
|
||||
return { (std::numeric_limits<T>::max)() };
|
||||
}
|
||||
};
|
||||
|
||||
struct CPPTRACE_EXPORT stacktrace_frame {
|
||||
frame_ptr raw_address;
|
||||
frame_ptr object_address;
|
||||
nullable<std::uint32_t> line;
|
||||
nullable<std::uint32_t> column;
|
||||
std::string filename;
|
||||
std::string symbol;
|
||||
bool is_inline;
|
||||
|
||||
bool operator==(const stacktrace_frame& other) const {
|
||||
return raw_address == other.raw_address
|
||||
&& object_address == other.object_address
|
||||
&& line == other.line
|
||||
&& column == other.column
|
||||
&& filename == other.filename
|
||||
&& symbol == other.symbol;
|
||||
}
|
||||
|
||||
bool operator!=(const stacktrace_frame& other) const {
|
||||
return !operator==(other);
|
||||
}
|
||||
|
||||
object_frame get_object_info() const;
|
||||
|
||||
std::string to_string() const;
|
||||
friend std::ostream& operator<<(std::ostream& stream, const stacktrace_frame& frame);
|
||||
};
|
||||
|
||||
struct CPPTRACE_EXPORT stacktrace {
|
||||
std::vector<stacktrace_frame> frames;
|
||||
static stacktrace current(std::size_t skip = 0);
|
||||
static stacktrace current(std::size_t skip, std::size_t max_depth);
|
||||
void print() const;
|
||||
void print(std::ostream& stream) const;
|
||||
void print(std::ostream& stream, bool color) const;
|
||||
void print_with_snippets() const;
|
||||
void print_with_snippets(std::ostream& stream) const;
|
||||
void print_with_snippets(std::ostream& stream, bool color) const;
|
||||
void clear();
|
||||
bool empty() const noexcept;
|
||||
std::string to_string(bool color = false) const;
|
||||
friend std::ostream& operator<<(std::ostream& stream, const stacktrace& trace);
|
||||
|
||||
using iterator = std::vector<stacktrace_frame>::iterator;
|
||||
using const_iterator = std::vector<stacktrace_frame>::const_iterator;
|
||||
inline iterator begin() noexcept { return frames.begin(); }
|
||||
inline iterator end() noexcept { return frames.end(); }
|
||||
inline const_iterator begin() const noexcept { return frames.begin(); }
|
||||
inline const_iterator end() const noexcept { return frames.end(); }
|
||||
inline const_iterator cbegin() const noexcept { return frames.cbegin(); }
|
||||
inline const_iterator cend() const noexcept { return frames.cend(); }
|
||||
private:
|
||||
void print(std::ostream& stream, bool color, bool newline_at_end, const char* header) const;
|
||||
void print_with_snippets(std::ostream& stream, bool color, bool newline_at_end, const char* header) const;
|
||||
friend void print_terminate_trace();
|
||||
};
|
||||
|
||||
CPPTRACE_EXPORT raw_trace generate_raw_trace(std::size_t skip = 0);
|
||||
CPPTRACE_EXPORT raw_trace generate_raw_trace(std::size_t skip, std::size_t max_depth);
|
||||
CPPTRACE_EXPORT object_trace generate_object_trace(std::size_t skip = 0);
|
||||
CPPTRACE_EXPORT object_trace generate_object_trace(std::size_t skip, std::size_t max_depth);
|
||||
CPPTRACE_EXPORT stacktrace generate_trace(std::size_t skip = 0);
|
||||
CPPTRACE_EXPORT stacktrace generate_trace(std::size_t skip, std::size_t max_depth);
|
||||
|
||||
// Path max isn't so simple, so I'm choosing 4096 which seems to encompass what all major OS's expect and should be
|
||||
// fine in all reasonable cases.
|
||||
// https://eklitzke.org/path-max-is-tricky
|
||||
// https://insanecoding.blogspot.com/2007/11/pathmax-simply-isnt.html
|
||||
#define CPPTRACE_PATH_MAX 4096
|
||||
|
||||
// safe tracing interface
|
||||
// signal-safe
|
||||
CPPTRACE_EXPORT std::size_t safe_generate_raw_trace(
|
||||
frame_ptr* buffer,
|
||||
std::size_t size,
|
||||
std::size_t skip = 0
|
||||
);
|
||||
// signal-safe
|
||||
CPPTRACE_EXPORT std::size_t safe_generate_raw_trace(
|
||||
frame_ptr* buffer,
|
||||
std::size_t size,
|
||||
std::size_t skip,
|
||||
std::size_t max_depth
|
||||
);
|
||||
struct CPPTRACE_EXPORT safe_object_frame {
|
||||
frame_ptr raw_address;
|
||||
frame_ptr address_relative_to_object_start; // base must still be added
|
||||
char object_path[CPPTRACE_PATH_MAX + 1];
|
||||
// To be called outside a signal handler. Not signal safe.
|
||||
object_frame resolve() const;
|
||||
};
|
||||
// signal-safe
|
||||
CPPTRACE_EXPORT void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
|
||||
CPPTRACE_EXPORT bool can_signal_safe_unwind();
|
||||
|
||||
// utilities:
|
||||
CPPTRACE_EXPORT std::string demangle(const std::string& name);
|
||||
CPPTRACE_EXPORT std::string get_snippet(
|
||||
const std::string& path,
|
||||
std::size_t line,
|
||||
std::size_t context_size,
|
||||
bool color = false
|
||||
);
|
||||
CPPTRACE_EXPORT bool isatty(int fd);
|
||||
|
||||
CPPTRACE_EXPORT extern const int stdin_fileno;
|
||||
CPPTRACE_EXPORT extern const int stderr_fileno;
|
||||
CPPTRACE_EXPORT extern const int stdout_fileno;
|
||||
|
||||
CPPTRACE_EXPORT void register_terminate_handler();
|
||||
|
||||
// configuration:
|
||||
CPPTRACE_EXPORT void absorb_trace_exceptions(bool absorb);
|
||||
CPPTRACE_EXPORT void enable_inlined_call_resolution(bool enable);
|
||||
|
||||
enum class cache_mode {
|
||||
// Only minimal lookup tables
|
||||
prioritize_memory = 0,
|
||||
// Build lookup tables but don't keep them around between trace calls
|
||||
hybrid = 1,
|
||||
// Build lookup tables as needed
|
||||
prioritize_speed = 2
|
||||
};
|
||||
|
||||
namespace experimental {
|
||||
CPPTRACE_EXPORT void set_cache_mode(cache_mode mode);
|
||||
}
|
||||
|
||||
// tracing exceptions:
|
||||
namespace detail {
|
||||
// This is a helper utility, if the library weren't C++11 an std::variant would be used
|
||||
class CPPTRACE_EXPORT lazy_trace_holder {
|
||||
bool resolved;
|
||||
union {
|
||||
raw_trace trace;
|
||||
stacktrace resolved_trace;
|
||||
};
|
||||
public:
|
||||
// constructors
|
||||
lazy_trace_holder() : resolved(false), trace() {}
|
||||
explicit lazy_trace_holder(raw_trace&& _trace) : resolved(false), trace(std::move(_trace)) {}
|
||||
explicit lazy_trace_holder(stacktrace&& _resolved_trace) : resolved(true), resolved_trace(std::move(_resolved_trace)) {}
|
||||
// logistics
|
||||
lazy_trace_holder(const lazy_trace_holder& other);
|
||||
lazy_trace_holder(lazy_trace_holder&& other) noexcept;
|
||||
lazy_trace_holder& operator=(const lazy_trace_holder& other);
|
||||
lazy_trace_holder& operator=(lazy_trace_holder&& other) noexcept;
|
||||
~lazy_trace_holder();
|
||||
// access
|
||||
stacktrace& get_resolved_trace();
|
||||
const stacktrace& get_resolved_trace() const;
|
||||
private:
|
||||
void clear();
|
||||
};
|
||||
|
||||
CPPTRACE_EXPORT raw_trace get_raw_trace_and_absorb(std::size_t skip, std::size_t max_depth);
|
||||
CPPTRACE_EXPORT raw_trace get_raw_trace_and_absorb(std::size_t skip = 0);
|
||||
}
|
||||
|
||||
// Interface for a traced exception object
|
||||
class CPPTRACE_EXPORT exception : public std::exception {
|
||||
public:
|
||||
const char* what() const noexcept override = 0;
|
||||
virtual const char* message() const noexcept = 0;
|
||||
virtual const stacktrace& trace() const noexcept = 0;
|
||||
};
|
||||
|
||||
// Cpptrace traced exception object
|
||||
// I hate to have to expose anything about implementation detail but the idea here is that
|
||||
class CPPTRACE_EXPORT lazy_exception : public exception {
|
||||
mutable detail::lazy_trace_holder trace_holder;
|
||||
mutable std::string what_string;
|
||||
|
||||
public:
|
||||
explicit lazy_exception(
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) : trace_holder(std::move(trace)) {}
|
||||
// std::exception
|
||||
const char* what() const noexcept override;
|
||||
// cpptrace::exception
|
||||
const char* message() const noexcept override;
|
||||
const stacktrace& trace() const noexcept override;
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT exception_with_message : public lazy_exception {
|
||||
mutable std::string user_message;
|
||||
|
||||
public:
|
||||
explicit exception_with_message(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept : lazy_exception(std::move(trace)), user_message(std::move(message_arg)) {}
|
||||
|
||||
const char* message() const noexcept override;
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT logic_error : public exception_with_message {
|
||||
public:
|
||||
explicit logic_error(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT domain_error : public exception_with_message {
|
||||
public:
|
||||
explicit domain_error(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT invalid_argument : public exception_with_message {
|
||||
public:
|
||||
explicit invalid_argument(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT length_error : public exception_with_message {
|
||||
public:
|
||||
explicit length_error(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT out_of_range : public exception_with_message {
|
||||
public:
|
||||
explicit out_of_range(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT runtime_error : public exception_with_message {
|
||||
public:
|
||||
explicit runtime_error(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT range_error : public exception_with_message {
|
||||
public:
|
||||
explicit range_error(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT overflow_error : public exception_with_message {
|
||||
public:
|
||||
explicit overflow_error(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT underflow_error : public exception_with_message {
|
||||
public:
|
||||
explicit underflow_error(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT nested_exception : public lazy_exception {
|
||||
std::exception_ptr ptr;
|
||||
mutable std::string message_value;
|
||||
public:
|
||||
explicit nested_exception(
|
||||
const std::exception_ptr& exception_ptr,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: lazy_exception(std::move(trace)), ptr(exception_ptr) {}
|
||||
|
||||
const char* message() const noexcept override;
|
||||
std::exception_ptr nested_ptr() const noexcept;
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT system_error : public runtime_error {
|
||||
std::error_code ec;
|
||||
public:
|
||||
explicit system_error(
|
||||
int error_code,
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept;
|
||||
const std::error_code& code() const noexcept;
|
||||
};
|
||||
|
||||
// [[noreturn]] must come first due to old clang
|
||||
[[noreturn]] CPPTRACE_EXPORT void rethrow_and_wrap_if_needed(std::size_t skip = 0);
|
||||
}
|
||||
|
||||
#if defined(CPPTRACE_STD_FORMAT) && defined(__cpp_lib_format)
|
||||
template <>
|
||||
struct std::formatter<cpptrace::stacktrace_frame> : std::formatter<std::string> {
|
||||
auto format(cpptrace::stacktrace_frame frame, format_context& ctx) const {
|
||||
return formatter<string>::format(frame.to_string(), ctx);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct std::formatter<cpptrace::stacktrace> : std::formatter<std::string> {
|
||||
auto format(cpptrace::stacktrace trace, format_context& ctx) const {
|
||||
return formatter<string>::format(trace.to_string(), ctx);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
// Exception wrapper utilities
|
||||
#define CPPTRACE_WRAP_BLOCK(statements) do { \
|
||||
try { \
|
||||
statements \
|
||||
} catch(...) { \
|
||||
::cpptrace::rethrow_and_wrap_if_needed(); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define CPPTRACE_WRAP(expression) [&] () -> decltype((expression)) { \
|
||||
try { \
|
||||
return expression; \
|
||||
} catch(...) { \
|
||||
::cpptrace::rethrow_and_wrap_if_needed(1); \
|
||||
} \
|
||||
} ()
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
#include <cpptrace/basic.hpp>
|
||||
#include <cpptrace/utils.hpp>
|
||||
#include <cpptrace/exceptions.hpp>
|
||||
#include <cpptrace/io.hpp>
|
||||
|
||||
#endif
|
||||
|
||||
218
include/cpptrace/exceptions.hpp
Normal file
218
include/cpptrace/exceptions.hpp
Normal file
@ -0,0 +1,218 @@
|
||||
#ifndef CPPTRACE_EXCEPTIONS_HPP
|
||||
#define CPPTRACE_EXCEPTIONS_HPP
|
||||
|
||||
#include <cpptrace/basic.hpp>
|
||||
|
||||
#include <exception>
|
||||
#include <system_error>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
// warning C4251: using non-dll-exported type in dll-exported type, firing on std::vector<frame_ptr> and others for some
|
||||
// reason
|
||||
// 4275 is the same thing but for base classes
|
||||
#pragma warning(disable: 4251; disable: 4275)
|
||||
#endif
|
||||
|
||||
namespace cpptrace {
|
||||
// tracing exceptions:
|
||||
namespace detail {
|
||||
// This is a helper utility, if the library weren't C++11 an std::variant would be used
|
||||
class CPPTRACE_EXPORT lazy_trace_holder {
|
||||
bool resolved;
|
||||
union {
|
||||
raw_trace trace;
|
||||
stacktrace resolved_trace;
|
||||
};
|
||||
public:
|
||||
// constructors
|
||||
lazy_trace_holder() : resolved(false), trace() {}
|
||||
explicit lazy_trace_holder(raw_trace&& _trace) : resolved(false), trace(std::move(_trace)) {}
|
||||
explicit lazy_trace_holder(stacktrace&& _resolved_trace) : resolved(true), resolved_trace(std::move(_resolved_trace)) {}
|
||||
// logistics
|
||||
lazy_trace_holder(const lazy_trace_holder& other);
|
||||
lazy_trace_holder(lazy_trace_holder&& other) noexcept;
|
||||
lazy_trace_holder& operator=(const lazy_trace_holder& other);
|
||||
lazy_trace_holder& operator=(lazy_trace_holder&& other) noexcept;
|
||||
~lazy_trace_holder();
|
||||
// access
|
||||
const raw_trace& get_raw_trace() const;
|
||||
stacktrace& get_resolved_trace();
|
||||
const stacktrace& get_resolved_trace() const;
|
||||
private:
|
||||
void clear();
|
||||
};
|
||||
|
||||
CPPTRACE_EXPORT raw_trace get_raw_trace_and_absorb(std::size_t skip, std::size_t max_depth);
|
||||
CPPTRACE_EXPORT raw_trace get_raw_trace_and_absorb(std::size_t skip = 0);
|
||||
}
|
||||
|
||||
// Interface for a traced exception object
|
||||
class CPPTRACE_EXPORT exception : public std::exception {
|
||||
public:
|
||||
const char* what() const noexcept override = 0;
|
||||
virtual const char* message() const noexcept = 0;
|
||||
virtual const stacktrace& trace() const noexcept = 0;
|
||||
};
|
||||
|
||||
// Cpptrace traced exception object
|
||||
// I hate to have to expose anything about implementation detail but the idea here is that
|
||||
class CPPTRACE_EXPORT lazy_exception : public exception {
|
||||
mutable detail::lazy_trace_holder trace_holder;
|
||||
mutable std::string what_string;
|
||||
|
||||
public:
|
||||
explicit lazy_exception(
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) : trace_holder(std::move(trace)) {}
|
||||
// std::exception
|
||||
const char* what() const noexcept override;
|
||||
// cpptrace::exception
|
||||
const char* message() const noexcept override;
|
||||
const stacktrace& trace() const noexcept override;
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT exception_with_message : public lazy_exception {
|
||||
mutable std::string user_message;
|
||||
|
||||
public:
|
||||
explicit exception_with_message(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept : lazy_exception(std::move(trace)), user_message(std::move(message_arg)) {}
|
||||
|
||||
const char* message() const noexcept override;
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT logic_error : public exception_with_message {
|
||||
public:
|
||||
explicit logic_error(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT domain_error : public exception_with_message {
|
||||
public:
|
||||
explicit domain_error(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT invalid_argument : public exception_with_message {
|
||||
public:
|
||||
explicit invalid_argument(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT length_error : public exception_with_message {
|
||||
public:
|
||||
explicit length_error(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT out_of_range : public exception_with_message {
|
||||
public:
|
||||
explicit out_of_range(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT runtime_error : public exception_with_message {
|
||||
public:
|
||||
explicit runtime_error(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT range_error : public exception_with_message {
|
||||
public:
|
||||
explicit range_error(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT overflow_error : public exception_with_message {
|
||||
public:
|
||||
explicit overflow_error(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT underflow_error : public exception_with_message {
|
||||
public:
|
||||
explicit underflow_error(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT nested_exception : public lazy_exception {
|
||||
std::exception_ptr ptr;
|
||||
mutable std::string message_value;
|
||||
public:
|
||||
explicit nested_exception(
|
||||
const std::exception_ptr& exception_ptr,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: lazy_exception(std::move(trace)), ptr(exception_ptr) {}
|
||||
|
||||
const char* message() const noexcept override;
|
||||
std::exception_ptr nested_ptr() const noexcept;
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT system_error : public runtime_error {
|
||||
std::error_code ec;
|
||||
public:
|
||||
explicit system_error(
|
||||
int error_code,
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept;
|
||||
const std::error_code& code() const noexcept;
|
||||
};
|
||||
|
||||
// [[noreturn]] must come first due to old clang
|
||||
[[noreturn]] CPPTRACE_EXPORT void rethrow_and_wrap_if_needed(std::size_t skip = 0);
|
||||
}
|
||||
|
||||
// Exception wrapper utilities
|
||||
#define CPPTRACE_WRAP_BLOCK(statements) do { \
|
||||
try { \
|
||||
statements \
|
||||
} catch(...) { \
|
||||
::cpptrace::rethrow_and_wrap_if_needed(); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define CPPTRACE_WRAP(expression) [&] () -> decltype((expression)) { \
|
||||
try { \
|
||||
return expression; \
|
||||
} catch(...) { \
|
||||
::cpptrace::rethrow_and_wrap_if_needed(1); \
|
||||
} \
|
||||
} ()
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
74
include/cpptrace/formatting.hpp
Normal file
74
include/cpptrace/formatting.hpp
Normal 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
|
||||
19
include/cpptrace/forward.hpp
Normal file
19
include/cpptrace/forward.hpp
Normal file
@ -0,0 +1,19 @@
|
||||
#ifndef CPPTRACE_FORWARD_HPP
|
||||
#define CPPTRACE_FORWARD_HPP
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace cpptrace {
|
||||
// Some type sufficient for an instruction pointer, currently always an alias to std::uintptr_t
|
||||
using frame_ptr = std::uintptr_t;
|
||||
|
||||
struct raw_trace;
|
||||
struct object_trace;
|
||||
struct stacktrace;
|
||||
|
||||
struct object_frame;
|
||||
struct stacktrace_frame;
|
||||
struct safe_object_frame;
|
||||
}
|
||||
|
||||
#endif
|
||||
131
include/cpptrace/from_current.hpp
Normal file
131
include/cpptrace/from_current.hpp
Normal file
@ -0,0 +1,131 @@
|
||||
#ifndef CPPTRACE_FROM_CURRENT_HPP
|
||||
#define CPPTRACE_FROM_CURRENT_HPP
|
||||
|
||||
#include <cpptrace/basic.hpp>
|
||||
|
||||
namespace cpptrace {
|
||||
CPPTRACE_EXPORT const raw_trace& raw_trace_from_current_exception();
|
||||
CPPTRACE_EXPORT const stacktrace& from_current_exception();
|
||||
|
||||
namespace detail {
|
||||
// Trace switch is to prevent multiple tracing of stacks on call stacks with multiple catches that don't
|
||||
// immediately match
|
||||
inline bool& get_trace_switch() {
|
||||
static thread_local bool trace_switch = true;
|
||||
return trace_switch;
|
||||
}
|
||||
|
||||
class CPPTRACE_EXPORT try_canary {
|
||||
public:
|
||||
~try_canary() {
|
||||
// Fires when we exit a try block, either via normal means or during unwinding.
|
||||
// Either way: Flip the switch.
|
||||
get_trace_switch() = true;
|
||||
}
|
||||
};
|
||||
|
||||
CPPTRACE_EXPORT CPPTRACE_FORCE_NO_INLINE void collect_current_trace(std::size_t skip);
|
||||
|
||||
// this function can be void, however, a char return is used to prevent TCO of the collect_current_trace
|
||||
CPPTRACE_FORCE_NO_INLINE inline char exception_unwind_interceptor(std::size_t skip) {
|
||||
if(get_trace_switch()) {
|
||||
// Done during a search phase. Flip the switch off, no more traces until an unwind happens
|
||||
get_trace_switch() = false;
|
||||
collect_current_trace(skip + 1);
|
||||
}
|
||||
return 42;
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
CPPTRACE_FORCE_NO_INLINE inline int exception_filter() {
|
||||
exception_unwind_interceptor(1);
|
||||
return 0; // EXCEPTION_CONTINUE_SEARCH
|
||||
}
|
||||
CPPTRACE_FORCE_NO_INLINE inline int unconditional_exception_filter() {
|
||||
collect_current_trace(1);
|
||||
return 0; // EXCEPTION_CONTINUE_SEARCH
|
||||
}
|
||||
#else
|
||||
class CPPTRACE_EXPORT unwind_interceptor {
|
||||
public:
|
||||
virtual ~unwind_interceptor();
|
||||
};
|
||||
class CPPTRACE_EXPORT unconditional_unwind_interceptor {
|
||||
public:
|
||||
virtual ~unconditional_unwind_interceptor();
|
||||
};
|
||||
|
||||
CPPTRACE_EXPORT void do_prepare_unwind_interceptor(char(*)(std::size_t));
|
||||
|
||||
#ifndef CPPTRACE_DONT_PREPARE_UNWIND_INTERCEPTOR_ON
|
||||
__attribute__((constructor)) inline void prepare_unwind_interceptor() {
|
||||
// __attribute__((constructor)) inline functions can be called for every source file they're #included in
|
||||
// there is still only one copy of the inline function in the final executable, though
|
||||
// LTO can make the redundant constructs fire only once
|
||||
// do_prepare_unwind_interceptor prevents against multiple preparations however it makes sense to guard
|
||||
// against it here too as a fast path, not that this should matter for performance
|
||||
static bool did_prepare = false;
|
||||
if(!did_prepare) {
|
||||
do_prepare_unwind_interceptor(exception_unwind_interceptor);
|
||||
did_prepare = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
// this awful double-IILE is due to C2713 "You can't use structured exception handling (__try/__except) and C++
|
||||
// exception handling (try/catch) in the same function."
|
||||
#define CPPTRACE_TRY \
|
||||
try { \
|
||||
::cpptrace::detail::try_canary cpptrace_try_canary; \
|
||||
[&]() { \
|
||||
__try { \
|
||||
[&]() {
|
||||
#define CPPTRACE_CATCH(param) \
|
||||
}(); \
|
||||
} __except(::cpptrace::detail::exception_filter()) {} \
|
||||
}(); \
|
||||
} catch(param)
|
||||
#define CPPTRACE_TRYZ \
|
||||
try { \
|
||||
[&]() { \
|
||||
__try { \
|
||||
[&]() {
|
||||
#define CPPTRACE_CATCHZ(param) \
|
||||
}(); \
|
||||
} __except(::cpptrace::detail::unconditional_exception_filter()) {} \
|
||||
}(); \
|
||||
} catch(param)
|
||||
#else
|
||||
#define CPPTRACE_TRY \
|
||||
try { \
|
||||
_Pragma("GCC diagnostic push") \
|
||||
_Pragma("GCC diagnostic ignored \"-Wshadow\"") \
|
||||
::cpptrace::detail::try_canary cpptrace_try_canary; \
|
||||
_Pragma("GCC diagnostic pop") \
|
||||
try {
|
||||
#define CPPTRACE_CATCH(param) \
|
||||
} catch(::cpptrace::detail::unwind_interceptor&) {} \
|
||||
} catch(param)
|
||||
#define CPPTRACE_TRYZ \
|
||||
try { \
|
||||
try {
|
||||
#define CPPTRACE_CATCHZ(param) \
|
||||
} catch(::cpptrace::detail::unconditional_unwind_interceptor&) {} \
|
||||
} catch(param)
|
||||
#endif
|
||||
|
||||
#define CPPTRACE_CATCH_ALT(param) catch(param)
|
||||
|
||||
#ifdef CPPTRACE_UNPREFIXED_TRY_CATCH
|
||||
#define TRY CPPTRACE_TRY
|
||||
#define CATCH(param) CPPTRACE_CATCH(param)
|
||||
#define TRYZ CPPTRACE_TRYZ
|
||||
#define CATCHZ(param) CPPTRACE_CATCHZ(param)
|
||||
#define CATCH_ALT(param) CPPTRACE_CATCH_ALT(param)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
52
include/cpptrace/io.hpp
Normal file
52
include/cpptrace/io.hpp
Normal file
@ -0,0 +1,52 @@
|
||||
#ifndef CPPTRACE_IO_HPP
|
||||
#define CPPTRACE_IO_HPP
|
||||
|
||||
#include <cpptrace/basic.hpp>
|
||||
|
||||
#include <iosfwd>
|
||||
|
||||
#ifndef CPPTRACE_NO_STD_FORMAT
|
||||
#if __cplusplus >= 202002L
|
||||
#ifdef __has_include
|
||||
#if __has_include(<format>)
|
||||
#define CPPTRACE_STD_FORMAT
|
||||
#include <format>
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
// warning C4251: using non-dll-exported type in dll-exported type, firing on std::vector<frame_ptr> and others for some
|
||||
// reason
|
||||
// 4275 is the same thing but for base classes
|
||||
#pragma warning(disable: 4251; disable: 4275)
|
||||
#endif
|
||||
|
||||
namespace cpptrace {
|
||||
std::ostream& operator<<(std::ostream& stream, const stacktrace_frame& frame);
|
||||
std::ostream& operator<<(std::ostream& stream, const stacktrace& trace);
|
||||
}
|
||||
|
||||
#if defined(CPPTRACE_STD_FORMAT) && defined(__cpp_lib_format)
|
||||
template <>
|
||||
struct std::formatter<cpptrace::stacktrace_frame> : std::formatter<std::string> {
|
||||
auto format(cpptrace::stacktrace_frame frame, format_context& ctx) const {
|
||||
return formatter<string>::format(frame.to_string(), ctx);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct std::formatter<cpptrace::stacktrace> : std::formatter<std::string> {
|
||||
auto format(cpptrace::stacktrace trace, format_context& ctx) const {
|
||||
return formatter<string>::format(trace.to_string(), ctx);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
54
include/cpptrace/utils.hpp
Normal file
54
include/cpptrace/utils.hpp
Normal file
@ -0,0 +1,54 @@
|
||||
#ifndef CPPTRACE_UTILS_HPP
|
||||
#define CPPTRACE_UTILS_HPP
|
||||
|
||||
#include <cpptrace/basic.hpp>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
// warning C4251: using non-dll-exported type in dll-exported type, firing on std::vector<frame_ptr> and others for some
|
||||
// reason
|
||||
// 4275 is the same thing but for base classes
|
||||
#pragma warning(disable: 4251; disable: 4275)
|
||||
#endif
|
||||
|
||||
namespace cpptrace {
|
||||
CPPTRACE_EXPORT std::string demangle(const std::string& name);
|
||||
CPPTRACE_EXPORT std::string get_snippet(
|
||||
const std::string& path,
|
||||
std::size_t line,
|
||||
std::size_t context_size,
|
||||
bool color = false
|
||||
);
|
||||
CPPTRACE_EXPORT bool isatty(int fd);
|
||||
|
||||
CPPTRACE_EXPORT extern const int stdin_fileno;
|
||||
CPPTRACE_EXPORT extern const int stderr_fileno;
|
||||
CPPTRACE_EXPORT extern const int stdout_fileno;
|
||||
|
||||
CPPTRACE_EXPORT void register_terminate_handler();
|
||||
|
||||
// options:
|
||||
CPPTRACE_EXPORT void absorb_trace_exceptions(bool absorb);
|
||||
CPPTRACE_EXPORT void enable_inlined_call_resolution(bool enable);
|
||||
|
||||
enum class cache_mode {
|
||||
// Only minimal lookup tables
|
||||
prioritize_memory = 0,
|
||||
// Build lookup tables but don't keep them around between trace calls
|
||||
hybrid = 1,
|
||||
// Build lookup tables as needed
|
||||
prioritize_speed = 2
|
||||
};
|
||||
|
||||
namespace experimental {
|
||||
CPPTRACE_EXPORT void set_cache_mode(cache_mode mode);
|
||||
}
|
||||
|
||||
// dwarf options
|
||||
namespace experimental {
|
||||
CPPTRACE_EXPORT void set_dwarf_resolver_line_table_cache_size(nullable<std::size_t> max_entries);
|
||||
CPPTRACE_EXPORT void set_dwarf_resolver_disable_aranges(bool disable);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -131,7 +131,8 @@ CTRACE_BEGIN_DEFINITIONS
|
||||
/* ctrace::safe: */
|
||||
CPPTRACE_EXPORT size_t ctrace_safe_generate_raw_trace(ctrace_frame_ptr* buffer, size_t size, size_t skip, size_t max_depth);
|
||||
CPPTRACE_EXPORT void ctrace_get_safe_object_frame(ctrace_frame_ptr address, ctrace_safe_object_frame* out);
|
||||
CPPTRACE_EXPORT ctrace_bool can_signal_safe_unwind(void);
|
||||
CPPTRACE_EXPORT ctrace_bool ctrace_can_signal_safe_unwind(void);
|
||||
CPPTRACE_EXPORT ctrace_bool ctrace_can_get_safe_object_frame(void);
|
||||
|
||||
/* ctrace::io: */
|
||||
CPPTRACE_EXPORT ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color);
|
||||
|
||||
BIN
res/from_current.png
Normal file
BIN
res/from_current.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
@ -1,4 +1,5 @@
|
||||
#include "elf.hpp"
|
||||
#include "binary/elf.hpp"
|
||||
#include "utils/optional.hpp"
|
||||
|
||||
#if IS_LINUX
|
||||
|
||||
@ -6,60 +7,25 @@
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <elf.h>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
|
||||
T elf_byteswap_if_needed(T value, bool elf_is_little) {
|
||||
if(is_little_endian() == elf_is_little) {
|
||||
return value;
|
||||
} else {
|
||||
return byteswap(value);
|
||||
}
|
||||
}
|
||||
|
||||
template<std::size_t Bits>
|
||||
static Result<std::uintptr_t, internal_error> elf_get_module_image_base_from_program_table(
|
||||
elf::elf(
|
||||
file_wrapper file,
|
||||
const std::string& object_path,
|
||||
std::FILE* file,
|
||||
bool is_little_endian
|
||||
) {
|
||||
static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument");
|
||||
using Header = typename std::conditional<Bits == 32, Elf32_Ehdr, Elf64_Ehdr>::type;
|
||||
using PHeader = typename std::conditional<Bits == 32, Elf32_Phdr, Elf64_Phdr>::type;
|
||||
auto loaded_header = load_bytes<Header>(file, 0);
|
||||
if(loaded_header.is_error()) {
|
||||
return std::move(loaded_header).unwrap_error();
|
||||
}
|
||||
const Header& file_header = loaded_header.unwrap_value();
|
||||
if(file_header.e_ehsize != sizeof(Header)) {
|
||||
return internal_error("ELF file header size mismatch" + object_path);
|
||||
}
|
||||
// PT_PHDR will occur at most once
|
||||
// Should be somewhat reliable https://stackoverflow.com/q/61568612/15675011
|
||||
// It should occur at the beginning but may as well loop just in case
|
||||
for(int i = 0; i < file_header.e_phnum; i++) {
|
||||
auto loaded_ph = load_bytes<PHeader>(file, file_header.e_phoff + file_header.e_phentsize * i);
|
||||
if(loaded_ph.is_error()) {
|
||||
return std::move(loaded_ph).unwrap_error();
|
||||
}
|
||||
const PHeader& program_header = loaded_ph.unwrap_value();
|
||||
if(elf_byteswap_if_needed(program_header.p_type, is_little_endian) == PT_PHDR) {
|
||||
return elf_byteswap_if_needed(program_header.p_vaddr, is_little_endian) -
|
||||
elf_byteswap_if_needed(program_header.p_offset, is_little_endian);
|
||||
}
|
||||
}
|
||||
// Apparently some objects like shared objects can end up missing this header. 0 as a base seems correct.
|
||||
return 0;
|
||||
}
|
||||
bool is_little_endian,
|
||||
bool is_64
|
||||
) : file(std::move(file)), object_path(object_path), is_little_endian(is_little_endian), is_64(is_64) {}
|
||||
|
||||
Result<std::uintptr_t, internal_error> elf_get_module_image_base(const std::string& object_path) {
|
||||
Result<elf, internal_error> elf::open_elf(const std::string& object_path) {
|
||||
auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter);
|
||||
if(file == nullptr) {
|
||||
return internal_error("Unable to read object file " + object_path);
|
||||
return internal_error("Unable to read object file {}", object_path);
|
||||
}
|
||||
// Initial checks/metadata
|
||||
auto magic = load_bytes<std::array<char, 4>>(file, 0);
|
||||
@ -86,14 +52,385 @@ namespace detail {
|
||||
if(ei_version.unwrap_value() != 1) {
|
||||
return internal_error("Unexpected ELF version " + object_path);
|
||||
}
|
||||
return elf(std::move(file), object_path, is_little_endian, is_64);
|
||||
}
|
||||
|
||||
Result<std::uintptr_t, internal_error> elf::get_module_image_base() {
|
||||
// get image base
|
||||
if(is_64) {
|
||||
return elf_get_module_image_base_from_program_table<64>(object_path, file, is_little_endian);
|
||||
return get_module_image_base_impl<64>();
|
||||
} else {
|
||||
return elf_get_module_image_base_from_program_table<32>(object_path, file, is_little_endian);
|
||||
return get_module_image_base_impl<32>();
|
||||
}
|
||||
}
|
||||
|
||||
template<std::size_t Bits>
|
||||
Result<std::uintptr_t, internal_error> elf::get_module_image_base_impl() {
|
||||
static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument");
|
||||
using PHeader = typename std::conditional<Bits == 32, Elf32_Phdr, Elf64_Phdr>::type;
|
||||
auto header = get_header_info();
|
||||
if(header.is_error()) {
|
||||
return std::move(header).unwrap_error();
|
||||
}
|
||||
const auto& header_info = header.unwrap_value();
|
||||
// PT_PHDR will occur at most once
|
||||
// Should be somewhat reliable https://stackoverflow.com/q/61568612/15675011
|
||||
// It should occur at the beginning but may as well loop just in case
|
||||
for(unsigned i = 0; i < header_info.e_phnum; i++) {
|
||||
auto loaded_ph = load_bytes<PHeader>(file, header_info.e_phoff + header_info.e_phentsize * i);
|
||||
if(loaded_ph.is_error()) {
|
||||
return std::move(loaded_ph).unwrap_error();
|
||||
}
|
||||
const PHeader& program_header = loaded_ph.unwrap_value();
|
||||
if(byteswap_if_needed(program_header.p_type) == PT_PHDR) {
|
||||
return byteswap_if_needed(program_header.p_vaddr) -
|
||||
byteswap_if_needed(program_header.p_offset);
|
||||
}
|
||||
}
|
||||
// Apparently some objects like shared objects can end up missing this header. 0 as a base seems correct.
|
||||
return 0;
|
||||
}
|
||||
|
||||
optional<std::string> elf::lookup_symbol(frame_ptr pc) {
|
||||
if(auto symtab = get_symtab()) {
|
||||
if(auto symbol = lookup_symbol(pc, symtab.unwrap_value())) {
|
||||
return symbol;
|
||||
}
|
||||
}
|
||||
if(auto dynamic_symtab = get_dynamic_symtab()) {
|
||||
if(auto symbol = lookup_symbol(pc, dynamic_symtab.unwrap_value())) {
|
||||
return symbol;
|
||||
}
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
optional<std::string> elf::lookup_symbol(frame_ptr pc, const optional<symtab_info>& maybe_symtab) {
|
||||
if(!maybe_symtab) {
|
||||
return nullopt;
|
||||
}
|
||||
auto& symtab = maybe_symtab.unwrap();
|
||||
if(symtab.strtab_link == SHN_UNDEF) {
|
||||
return nullopt;
|
||||
}
|
||||
auto strtab_ = get_strtab(symtab.strtab_link);
|
||||
if(strtab_.is_error()) {
|
||||
return nullopt;
|
||||
}
|
||||
auto& strtab = strtab_.unwrap_value();
|
||||
auto it = first_less_than_or_equal(
|
||||
symtab.entries.begin(),
|
||||
symtab.entries.end(),
|
||||
pc,
|
||||
[] (frame_ptr pc, const symtab_entry& entry) {
|
||||
return pc < entry.st_value;
|
||||
}
|
||||
);
|
||||
if(it == symtab.entries.end()) {
|
||||
return nullopt;
|
||||
}
|
||||
if(pc <= it->st_value + it->st_size) {
|
||||
return strtab.data() + it->st_name;
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
Result<optional<std::vector<elf::symbol_entry>>, internal_error> elf::get_symtab_entries() {
|
||||
return resolve_symtab_entries(get_symtab());
|
||||
}
|
||||
Result<optional<std::vector<elf::symbol_entry>>, internal_error> elf::get_dynamic_symtab_entries() {
|
||||
return resolve_symtab_entries(get_dynamic_symtab());
|
||||
}
|
||||
|
||||
Result<optional<std::vector<elf::symbol_entry>>, internal_error> elf::resolve_symtab_entries(
|
||||
const Result<const optional<elf::symtab_info> &, internal_error>& symtab
|
||||
) {
|
||||
if(!symtab) {
|
||||
return symtab.unwrap_error();
|
||||
}
|
||||
if(!symtab.unwrap_value()) {
|
||||
return nullopt;
|
||||
}
|
||||
const auto& info = symtab.unwrap_value().unwrap();
|
||||
optional<const std::vector<char>&> strtab;
|
||||
if(info.strtab_link != SHN_UNDEF) {
|
||||
auto strtab_ = get_strtab(info.strtab_link);
|
||||
if(strtab_.is_error()) {
|
||||
return strtab_.unwrap_error();
|
||||
}
|
||||
strtab = strtab_.unwrap_value();
|
||||
}
|
||||
std::vector<symbol_entry> res;
|
||||
for(const auto& entry : info.entries) {
|
||||
res.push_back({
|
||||
strtab.has_value() ? strtab.unwrap().data() + entry.st_name : "<strtab error>",
|
||||
entry.st_shndx,
|
||||
entry.st_value,
|
||||
entry.st_size
|
||||
});
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type>
|
||||
T elf::byteswap_if_needed(T value) {
|
||||
if(cpptrace::detail::is_little_endian() == is_little_endian) {
|
||||
return value;
|
||||
} else {
|
||||
return byteswap(value);
|
||||
}
|
||||
}
|
||||
|
||||
Result<const elf::header_info&, internal_error> elf::get_header_info() {
|
||||
if(header) {
|
||||
Result<const elf::header_info&, internal_error> r = header.unwrap();
|
||||
return std::ref(header.unwrap());
|
||||
}
|
||||
if(tried_to_load_header) {
|
||||
return internal_error("previous header load failed " + object_path);
|
||||
}
|
||||
tried_to_load_header = true;
|
||||
if(is_64) {
|
||||
return get_header_info_impl<64>();
|
||||
} else {
|
||||
return get_header_info_impl<32>();
|
||||
}
|
||||
}
|
||||
|
||||
template<std::size_t Bits>
|
||||
Result<const elf::header_info&, internal_error> elf::get_header_info_impl() {
|
||||
static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument");
|
||||
using Header = typename std::conditional<Bits == 32, Elf32_Ehdr, Elf64_Ehdr>::type;
|
||||
auto loaded_header = load_bytes<Header>(file, 0);
|
||||
if(loaded_header.is_error()) {
|
||||
return std::move(loaded_header).unwrap_error();
|
||||
}
|
||||
const Header& file_header = loaded_header.unwrap_value();
|
||||
if(file_header.e_ehsize != sizeof(Header)) {
|
||||
return internal_error("ELF file header size mismatch" + object_path);
|
||||
}
|
||||
header_info info;
|
||||
info.e_phoff = byteswap_if_needed(file_header.e_phoff);
|
||||
info.e_phnum = byteswap_if_needed(file_header.e_phnum);
|
||||
info.e_phentsize = byteswap_if_needed(file_header.e_phentsize);
|
||||
info.e_shoff = byteswap_if_needed(file_header.e_shoff);
|
||||
info.e_shnum = byteswap_if_needed(file_header.e_shnum);
|
||||
info.e_shentsize = byteswap_if_needed(file_header.e_shentsize);
|
||||
header = info;
|
||||
return header.unwrap();
|
||||
}
|
||||
|
||||
Result<const std::vector<elf::section_info>&, internal_error> elf::get_sections() {
|
||||
if(did_load_sections) {
|
||||
return sections;
|
||||
}
|
||||
if(tried_to_load_sections) {
|
||||
return internal_error("previous sections load failed " + object_path);
|
||||
}
|
||||
tried_to_load_sections = true;
|
||||
if(is_64) {
|
||||
return get_sections_impl<64>();
|
||||
} else {
|
||||
return get_sections_impl<32>();
|
||||
}
|
||||
}
|
||||
|
||||
template<std::size_t Bits>
|
||||
Result<const std::vector<elf::section_info>&, internal_error> elf::get_sections_impl() {
|
||||
static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument");
|
||||
using SHeader = typename std::conditional<Bits == 32, Elf32_Shdr, Elf64_Shdr>::type;
|
||||
auto header = get_header_info();
|
||||
if(header.is_error()) {
|
||||
return std::move(header).unwrap_error();
|
||||
}
|
||||
const auto& header_info = header.unwrap_value();
|
||||
for(unsigned i = 0; i < header_info.e_shnum; i++) {
|
||||
auto loaded_sh = load_bytes<SHeader>(file, header_info.e_shoff + header_info.e_shentsize * i);
|
||||
if(loaded_sh.is_error()) {
|
||||
return std::move(loaded_sh).unwrap_error();
|
||||
}
|
||||
const SHeader& section_header = loaded_sh.unwrap_value();
|
||||
section_info info;
|
||||
info.sh_type = byteswap_if_needed(section_header.sh_type);
|
||||
info.sh_addr = byteswap_if_needed(section_header.sh_addr);
|
||||
info.sh_offset = byteswap_if_needed(section_header.sh_offset);
|
||||
info.sh_size = byteswap_if_needed(section_header.sh_size);
|
||||
info.sh_entsize = byteswap_if_needed(section_header.sh_entsize);
|
||||
info.sh_link = byteswap_if_needed(section_header.sh_link);
|
||||
sections.push_back(info);
|
||||
}
|
||||
did_load_sections = true;
|
||||
return sections;
|
||||
}
|
||||
|
||||
Result<const std::vector<char>&, internal_error> elf::get_strtab(std::size_t index) {
|
||||
auto res = strtab_entries.insert({index, {}});
|
||||
auto it = res.first;
|
||||
auto did_insert = res.second;
|
||||
auto& entry = it->second;
|
||||
if(!did_insert) {
|
||||
if(entry.did_load_strtab) {
|
||||
return entry.data;
|
||||
}
|
||||
if(entry.tried_to_load_strtab) {
|
||||
return internal_error("previous strtab load failed {}", object_path);
|
||||
}
|
||||
}
|
||||
entry.tried_to_load_strtab = true;
|
||||
auto sections_ = get_sections();
|
||||
if(sections_.is_error()) {
|
||||
return std::move(sections_).unwrap_error();
|
||||
}
|
||||
const auto& sections = sections_.unwrap_value();
|
||||
if(index >= sections.size()) {
|
||||
return internal_error("requested strtab section index out of range");
|
||||
}
|
||||
const auto& section = sections[index];
|
||||
if(section.sh_type != SHT_STRTAB) {
|
||||
return internal_error("requested strtab section not a strtab (requested {} of {})", index, object_path);
|
||||
}
|
||||
entry.data.resize(section.sh_size + 1);
|
||||
if(std::fseek(file, section.sh_offset, SEEK_SET) != 0) {
|
||||
return internal_error("fseek error while loading elf string table");
|
||||
}
|
||||
if(std::fread(entry.data.data(), sizeof(char), section.sh_size, file) != section.sh_size) {
|
||||
return internal_error("fread error while loading elf string table");
|
||||
}
|
||||
entry.data[section.sh_size] = 0; // just out of an abundance of caution
|
||||
entry.did_load_strtab = true;
|
||||
return entry.data;
|
||||
}
|
||||
|
||||
Result<const optional<elf::symtab_info>&, internal_error> elf::get_symtab() {
|
||||
if(did_load_symtab) {
|
||||
return symtab;
|
||||
}
|
||||
if(tried_to_load_symtab) {
|
||||
return internal_error("previous symtab load failed {}", object_path);
|
||||
}
|
||||
tried_to_load_symtab = true;
|
||||
if(is_64) {
|
||||
auto res = get_symtab_impl<64>(false);
|
||||
if(res.has_value()) {
|
||||
symtab = std::move(res).unwrap_value();
|
||||
did_load_symtab = true;
|
||||
return symtab;
|
||||
} else {
|
||||
return std::move(res).unwrap_error();
|
||||
}
|
||||
} else {
|
||||
auto res = get_symtab_impl<32>(false);
|
||||
if(res.has_value()) {
|
||||
symtab = std::move(res).unwrap_value();
|
||||
did_load_symtab = true;
|
||||
return symtab;
|
||||
} else {
|
||||
return std::move(res).unwrap_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result<const optional<elf::symtab_info>&, internal_error> elf::get_dynamic_symtab() {
|
||||
if(did_load_dynamic_symtab) {
|
||||
return dynamic_symtab;
|
||||
}
|
||||
if(tried_to_load_dynamic_symtab) {
|
||||
return internal_error("previous dynamic symtab load failed {}", object_path);
|
||||
}
|
||||
tried_to_load_dynamic_symtab = true;
|
||||
if(is_64) {
|
||||
auto res = get_symtab_impl<64>(true);
|
||||
if(res.has_value()) {
|
||||
dynamic_symtab = std::move(res).unwrap_value();
|
||||
did_load_dynamic_symtab = true;
|
||||
return dynamic_symtab;
|
||||
} else {
|
||||
return std::move(res).unwrap_error();
|
||||
}
|
||||
} else {
|
||||
auto res = get_symtab_impl<32>(true);
|
||||
if(res.has_value()) {
|
||||
dynamic_symtab = std::move(res).unwrap_value();
|
||||
did_load_dynamic_symtab = true;
|
||||
return dynamic_symtab;
|
||||
} else {
|
||||
return std::move(res).unwrap_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<std::size_t Bits>
|
||||
Result<optional<elf::symtab_info>, internal_error> elf::get_symtab_impl(bool dynamic) {
|
||||
// https://refspecs.linuxfoundation.org/elf/elf.pdf
|
||||
// page 66: only one sht_symtab and sht_dynsym section per file
|
||||
// page 32: symtab spec
|
||||
static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument");
|
||||
using SymEntry = typename std::conditional<Bits == 32, Elf32_Sym, Elf64_Sym>::type;
|
||||
auto sections_ = get_sections();
|
||||
if(sections_.is_error()) {
|
||||
return std::move(sections_).unwrap_error();
|
||||
}
|
||||
const auto& sections = sections_.unwrap_value();
|
||||
optional<symtab_info> symbol_table;
|
||||
for(const auto& section : sections) {
|
||||
if(section.sh_type == (dynamic ? SHT_DYNSYM : SHT_SYMTAB)) {
|
||||
if(section.sh_entsize != sizeof(SymEntry)) {
|
||||
return internal_error("elf seems corrupted, sym entry mismatch {}", object_path);
|
||||
}
|
||||
if(section.sh_size % section.sh_entsize != 0) {
|
||||
return internal_error("elf seems corrupted, sym entry vs section size mismatch {}", object_path);
|
||||
}
|
||||
std::vector<SymEntry> buffer(section.sh_size / section.sh_entsize);
|
||||
if(std::fseek(file, section.sh_offset, SEEK_SET) != 0) {
|
||||
return internal_error("fseek error while loading elf symbol table");
|
||||
}
|
||||
if(std::fread(buffer.data(), section.sh_entsize, buffer.size(), file) != buffer.size()) {
|
||||
return internal_error("fread error while loading elf symbol table");
|
||||
}
|
||||
symbol_table = symtab_info{};
|
||||
symbol_table.unwrap().entries.reserve(buffer.size());
|
||||
for(const auto& entry : buffer) {
|
||||
symtab_entry normalized;
|
||||
normalized.st_name = byteswap_if_needed(entry.st_name);
|
||||
normalized.st_info = byteswap_if_needed(entry.st_info);
|
||||
normalized.st_other = byteswap_if_needed(entry.st_other);
|
||||
normalized.st_shndx = byteswap_if_needed(entry.st_shndx);
|
||||
normalized.st_value = byteswap_if_needed(entry.st_value);
|
||||
normalized.st_size = byteswap_if_needed(entry.st_size);
|
||||
symbol_table.unwrap().entries.push_back(normalized);
|
||||
}
|
||||
std::sort(
|
||||
symbol_table.unwrap().entries.begin(),
|
||||
symbol_table.unwrap().entries.end(),
|
||||
[] (const symtab_entry& a, const symtab_entry& b) {
|
||||
return a.st_value < b.st_value;
|
||||
}
|
||||
);
|
||||
symbol_table.unwrap().strtab_link = section.sh_link;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return symbol_table;
|
||||
}
|
||||
|
||||
Result<maybe_owned<elf>, internal_error> open_elf_cached(const std::string& object_path) {
|
||||
if(get_cache_mode() == cache_mode::prioritize_memory) {
|
||||
return elf::open_elf(object_path)
|
||||
.transform([](elf&& obj) { return maybe_owned<elf>{detail::make_unique<elf>(std::move(obj))}; });
|
||||
} else {
|
||||
std::mutex m;
|
||||
std::unique_lock<std::mutex> lock{m};
|
||||
// TODO: Re-evaluate storing the error
|
||||
static std::unordered_map<std::string, Result<elf, internal_error>> cache;
|
||||
auto it = cache.find(object_path);
|
||||
if(it == cache.end()) {
|
||||
auto res = cache.insert({ object_path, elf::open_elf(object_path) });
|
||||
VERIFY(res.second);
|
||||
it = res.first;
|
||||
}
|
||||
return it->second.transform([](elf& obj) { return maybe_owned<elf>(&obj); });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,17 +1,126 @@
|
||||
#ifndef ELF_HPP
|
||||
#define ELF_HPP
|
||||
|
||||
#include "../utils/common.hpp"
|
||||
#include "../utils/utils.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#if IS_LINUX
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
Result<std::uintptr_t, internal_error> elf_get_module_image_base(const std::string& object_path);
|
||||
class elf {
|
||||
file_wrapper file;
|
||||
std::string object_path;
|
||||
bool is_little_endian;
|
||||
bool is_64;
|
||||
|
||||
struct header_info {
|
||||
uint64_t e_phoff;
|
||||
uint32_t e_phnum;
|
||||
uint32_t e_phentsize;
|
||||
uint64_t e_shoff;
|
||||
uint32_t e_shnum;
|
||||
uint32_t e_shentsize;
|
||||
};
|
||||
bool tried_to_load_header = false;
|
||||
optional<header_info> header;
|
||||
|
||||
struct section_info {
|
||||
uint32_t sh_type;
|
||||
uint64_t sh_addr;
|
||||
uint64_t sh_offset;
|
||||
uint64_t sh_size;
|
||||
uint64_t sh_entsize;
|
||||
uint32_t sh_link;
|
||||
};
|
||||
bool tried_to_load_sections = false;
|
||||
bool did_load_sections = false;
|
||||
std::vector<section_info> sections;
|
||||
|
||||
struct strtab_entry {
|
||||
bool tried_to_load_strtab = false;
|
||||
bool did_load_strtab = false;
|
||||
std::vector<char> data;
|
||||
};
|
||||
std::unordered_map<std::size_t, strtab_entry> strtab_entries;
|
||||
|
||||
struct symtab_entry {
|
||||
uint32_t st_name;
|
||||
unsigned char st_info;
|
||||
unsigned char st_other;
|
||||
uint16_t st_shndx;
|
||||
uint64_t st_value;
|
||||
uint64_t st_size;
|
||||
};
|
||||
struct symtab_info {
|
||||
std::vector<symtab_entry> entries;
|
||||
std::size_t strtab_link = 0;
|
||||
};
|
||||
bool tried_to_load_symtab = false;
|
||||
bool did_load_symtab = false;
|
||||
optional<symtab_info> symtab;
|
||||
|
||||
bool tried_to_load_dynamic_symtab = false;
|
||||
bool did_load_dynamic_symtab = false;
|
||||
optional<symtab_info> dynamic_symtab;
|
||||
|
||||
elf(file_wrapper file, const std::string& object_path, bool is_little_endian, bool is_64);
|
||||
|
||||
public:
|
||||
static NODISCARD Result<elf, internal_error> open_elf(const std::string& object_path);
|
||||
|
||||
elf(elf&&) = default;
|
||||
|
||||
public:
|
||||
Result<std::uintptr_t, internal_error> get_module_image_base();
|
||||
private:
|
||||
template<std::size_t Bits>
|
||||
Result<std::uintptr_t, internal_error> get_module_image_base_impl();
|
||||
|
||||
public:
|
||||
optional<std::string> lookup_symbol(frame_ptr pc);
|
||||
private:
|
||||
optional<std::string> lookup_symbol(frame_ptr pc, const optional<symtab_info>& maybe_symtab);
|
||||
|
||||
public:
|
||||
struct symbol_entry {
|
||||
std::string st_name;
|
||||
uint16_t st_shndx;
|
||||
uint64_t st_value;
|
||||
uint64_t st_size;
|
||||
};
|
||||
Result<optional<std::vector<symbol_entry>>, internal_error> get_symtab_entries();
|
||||
Result<optional<std::vector<symbol_entry>>, internal_error> get_dynamic_symtab_entries();
|
||||
private:
|
||||
Result<optional<std::vector<symbol_entry>>, internal_error> resolve_symtab_entries(
|
||||
const Result<const optional<symtab_info> &, internal_error>&
|
||||
);
|
||||
|
||||
private:
|
||||
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
|
||||
T byteswap_if_needed(T value);
|
||||
|
||||
Result<const header_info&, internal_error> get_header_info();
|
||||
template<std::size_t Bits>
|
||||
Result<const header_info&, internal_error> get_header_info_impl();
|
||||
|
||||
Result<const std::vector<section_info>&, internal_error> get_sections();
|
||||
template<std::size_t Bits>
|
||||
Result<const std::vector<section_info>&, internal_error> get_sections_impl();
|
||||
|
||||
Result<const std::vector<char>&, internal_error> get_strtab(std::size_t index);
|
||||
|
||||
Result<const optional<symtab_info>&, internal_error> get_symtab();
|
||||
Result<const optional<symtab_info>&, internal_error> get_dynamic_symtab();
|
||||
template<std::size_t Bits>
|
||||
Result<optional<symtab_info>, internal_error> get_symtab_impl(bool dynamic);
|
||||
};
|
||||
|
||||
NODISCARD Result<maybe_owned<elf>, internal_error> open_elf_cached(const std::string& object_path);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
#include "mach-o.hpp"
|
||||
#include "binary/mach-o.hpp"
|
||||
|
||||
#include "../utils/common.hpp"
|
||||
#include "../utils/utils.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#if IS_APPLE
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
@ -102,7 +103,7 @@ namespace detail {
|
||||
|
||||
Result<const char*, internal_error> mach_o::symtab_info_data::get_string(std::size_t index) const {
|
||||
if(stringtab && index < symtab.strsize) {
|
||||
return stringtab.get() + index;
|
||||
return stringtab.unwrap().data() + index;
|
||||
} else {
|
||||
return internal_error("can't retrieve symbol from symtab");
|
||||
}
|
||||
@ -219,7 +220,7 @@ namespace detail {
|
||||
|
||||
void mach_o::print_symbol_table_entry(
|
||||
const nlist_64& entry,
|
||||
const std::unique_ptr<char[]>& stringtab,
|
||||
const char* stringtab,
|
||||
std::size_t stringsize,
|
||||
std::size_t j
|
||||
) const {
|
||||
@ -248,7 +249,7 @@ namespace detail {
|
||||
stringtab == nullptr
|
||||
? "Stringtab error"
|
||||
: entry.n_un.n_strx < stringsize
|
||||
? stringtab.get() + entry.n_un.n_strx
|
||||
? stringtab + entry.n_un.n_strx
|
||||
: "String index out of bounds"
|
||||
);
|
||||
}
|
||||
@ -286,7 +287,7 @@ namespace detail {
|
||||
}
|
||||
print_symbol_table_entry(
|
||||
entry.unwrap_value(),
|
||||
std::move(stringtab).value_or(std::unique_ptr<char[]>(nullptr)),
|
||||
stringtab ? stringtab.unwrap_value().data() : nullptr,
|
||||
symtab.strsize,
|
||||
j
|
||||
);
|
||||
@ -364,10 +365,17 @@ namespace detail {
|
||||
return debug_map;
|
||||
}
|
||||
|
||||
Result<std::vector<mach_o::symbol_entry>, internal_error> mach_o::symbol_table() {
|
||||
Result<const std::vector<mach_o::symbol_entry>&, internal_error> mach_o::symbol_table() {
|
||||
if(symbols) {
|
||||
return symbols.unwrap();
|
||||
}
|
||||
if(tried_to_load_symbols) {
|
||||
return internal_error("previous symbol table load failed");
|
||||
}
|
||||
tried_to_load_symbols = true;
|
||||
std::vector<symbol_entry> symbol_table;
|
||||
// we have a bunch of symbols in our binary we need to pair up with symbols from various .o files
|
||||
// first collect symbols and the objects they come from
|
||||
std::vector<symbol_entry> symbols;
|
||||
auto symtab_info_res = get_symtab_info();
|
||||
if(!symtab_info_res) {
|
||||
return std::move(symtab_info_res).unwrap_error();
|
||||
@ -394,13 +402,42 @@ namespace detail {
|
||||
if(!str) {
|
||||
return std::move(str).unwrap_error();
|
||||
}
|
||||
symbols.push_back({
|
||||
symbol_table.push_back({
|
||||
entry.n_value,
|
||||
str.unwrap_value()
|
||||
});
|
||||
}
|
||||
}
|
||||
return symbols;
|
||||
std::sort(
|
||||
symbol_table.begin(),
|
||||
symbol_table.end(),
|
||||
[] (const symbol_entry& a, const symbol_entry& b) { return a.address < b.address; }
|
||||
);
|
||||
symbols = std::move(symbol_table);
|
||||
return symbols.unwrap();
|
||||
}
|
||||
|
||||
optional<std::string> mach_o::lookup_symbol(frame_ptr pc) {
|
||||
auto symtab_ = symbol_table();
|
||||
if(!symtab_) {
|
||||
return nullopt;
|
||||
}
|
||||
const auto& symtab = symtab_.unwrap_value();;
|
||||
auto it = first_less_than_or_equal(
|
||||
symtab.begin(),
|
||||
symtab.end(),
|
||||
pc,
|
||||
[] (frame_ptr pc, const symbol_entry& entry) {
|
||||
return pc < entry.address;
|
||||
}
|
||||
);
|
||||
if(it == symtab.end()) {
|
||||
return nullopt;
|
||||
}
|
||||
ASSERT(pc >= it->address);
|
||||
// TODO: We subtracted one from the address so name + diff won't show up in the objdump, decide if desirable
|
||||
// to have an easier offset to lookup
|
||||
return microfmt::format("{} + {}", it->name, pc - it->address);
|
||||
}
|
||||
|
||||
// produce information similar to dsymutil -dump-debug-map
|
||||
@ -605,12 +642,12 @@ namespace detail {
|
||||
return common;
|
||||
}
|
||||
|
||||
Result<std::unique_ptr<char[]>, internal_error> mach_o::load_string_table(std::uint32_t offset, std::uint32_t byte_count) const {
|
||||
std::unique_ptr<char[]> buffer(new char[byte_count + 1]);
|
||||
Result<std::vector<char>, internal_error> mach_o::load_string_table(std::uint32_t offset, std::uint32_t byte_count) const {
|
||||
std::vector<char> buffer(byte_count + 1);
|
||||
if(std::fseek(file, load_base + offset, SEEK_SET) != 0) {
|
||||
return internal_error("fseek error while loading mach-o symbol table");
|
||||
}
|
||||
if(std::fread(buffer.get(), sizeof(char), byte_count, file) != byte_count) {
|
||||
if(std::fread(buffer.data(), sizeof(char), byte_count, file) != byte_count) {
|
||||
return internal_error("fread error while loading mach-o symbol table");
|
||||
}
|
||||
buffer[byte_count] = 0; // just out of an abundance of caution
|
||||
@ -633,6 +670,27 @@ namespace detail {
|
||||
return is_fat_magic(magic.unwrap_value());
|
||||
}
|
||||
}
|
||||
|
||||
Result<maybe_owned<mach_o>, internal_error> open_mach_o_cached(const std::string& object_path) {
|
||||
if(get_cache_mode() == cache_mode::prioritize_memory) {
|
||||
return mach_o::open_mach_o(object_path)
|
||||
.transform([](mach_o&& obj) {
|
||||
return maybe_owned<mach_o>{detail::make_unique<mach_o>(std::move(obj))};
|
||||
});
|
||||
} else {
|
||||
std::mutex m;
|
||||
std::unique_lock<std::mutex> lock{m};
|
||||
// TODO: Re-evaluate storing the error
|
||||
static std::unordered_map<std::string, Result<mach_o, internal_error>> cache;
|
||||
auto it = cache.find(object_path);
|
||||
if(it == cache.end()) {
|
||||
auto res = cache.insert({ object_path, mach_o::open_mach_o(object_path) });
|
||||
VERIFY(res.second);
|
||||
it = res.first;
|
||||
}
|
||||
return it->second.transform([](mach_o& obj) { return maybe_owned<mach_o>(&obj); });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
#ifndef MACHO_HPP
|
||||
#define MACHO_HPP
|
||||
|
||||
#include "../utils/common.hpp"
|
||||
#include "../utils/utils.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#if IS_APPLE
|
||||
|
||||
@ -28,6 +28,23 @@ namespace detail {
|
||||
};
|
||||
|
||||
class mach_o {
|
||||
public:
|
||||
struct debug_map_entry {
|
||||
uint64_t source_address;
|
||||
uint64_t size;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
struct symbol_entry {
|
||||
uint64_t address;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
// map from object file to a vector of symbols to resolve
|
||||
using debug_map = std::unordered_map<std::string, std::vector<debug_map_entry>>;
|
||||
|
||||
private:
|
||||
|
||||
file_wrapper file;
|
||||
std::string object_path;
|
||||
std::uint32_t magic;
|
||||
@ -46,13 +63,16 @@ namespace detail {
|
||||
|
||||
struct symtab_info_data {
|
||||
symtab_command symtab;
|
||||
std::unique_ptr<char[]> stringtab;
|
||||
optional<std::vector<char>> stringtab;
|
||||
Result<const char*, internal_error> get_string(std::size_t index) const;
|
||||
};
|
||||
|
||||
bool tried_to_load_symtab = false;
|
||||
optional<symtab_info_data> symtab_info;
|
||||
|
||||
bool tried_to_load_symbols = false;
|
||||
optional<std::vector<symbol_entry>> symbols;
|
||||
|
||||
mach_o(
|
||||
file_wrapper file,
|
||||
const std::string& object_path,
|
||||
@ -80,31 +100,19 @@ namespace detail {
|
||||
|
||||
void print_symbol_table_entry(
|
||||
const nlist_64& entry,
|
||||
const std::unique_ptr<char[]>& stringtab,
|
||||
const char* stringtab,
|
||||
std::size_t stringsize,
|
||||
std::size_t j
|
||||
) const;
|
||||
|
||||
void print_symbol_table();
|
||||
|
||||
struct debug_map_entry {
|
||||
uint64_t source_address;
|
||||
uint64_t size;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
struct symbol_entry {
|
||||
uint64_t address;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
// map from object file to a vector of symbols to resolve
|
||||
using debug_map = std::unordered_map<std::string, std::vector<debug_map_entry>>;
|
||||
|
||||
// produce information similar to dsymutil -dump-debug-map
|
||||
Result<debug_map, internal_error> get_debug_map();
|
||||
|
||||
Result<std::vector<symbol_entry>, internal_error> symbol_table();
|
||||
Result<const std::vector<symbol_entry>&, internal_error> symbol_table();
|
||||
|
||||
optional<std::string> lookup_symbol(frame_ptr pc);
|
||||
|
||||
// produce information similar to dsymutil -dump-debug-map
|
||||
static void print_debug_map(const debug_map& debug_map);
|
||||
@ -123,12 +131,14 @@ namespace detail {
|
||||
template<std::size_t Bits>
|
||||
Result<nlist_64, internal_error> load_symtab_entry(std::uint32_t symbol_base, std::size_t index) const;
|
||||
|
||||
Result<std::unique_ptr<char[]>, internal_error> load_string_table(std::uint32_t offset, std::uint32_t byte_count) const;
|
||||
Result<std::vector<char>, internal_error> load_string_table(std::uint32_t offset, std::uint32_t byte_count) const;
|
||||
|
||||
bool should_swap() const;
|
||||
};
|
||||
|
||||
Result<bool, internal_error> macho_is_fat(const std::string& object_path);
|
||||
|
||||
NODISCARD Result<maybe_owned<mach_o>, internal_error> open_mach_o_cached(const std::string& object_path);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
#include "module_base.hpp"
|
||||
#include "binary/module_base.hpp"
|
||||
|
||||
#include "../utils/common.hpp"
|
||||
#include "../utils/utils.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
@ -12,13 +11,12 @@
|
||||
#include <unistd.h>
|
||||
#include <dlfcn.h>
|
||||
#if IS_APPLE
|
||||
#include "mach-o.hpp"
|
||||
#include "binary/mach-o.hpp"
|
||||
#else
|
||||
#include "elf.hpp"
|
||||
#include "binary/elf.hpp"
|
||||
#endif
|
||||
#elif IS_WINDOWS
|
||||
#include <windows.h>
|
||||
#include "pe.hpp"
|
||||
#include "binary/pe.hpp"
|
||||
#endif
|
||||
|
||||
namespace cpptrace {
|
||||
@ -32,8 +30,12 @@ namespace detail {
|
||||
if(it == cache.end()) {
|
||||
// arguably it'd be better to release the lock while computing this, but also arguably it's good to not
|
||||
// have two threads try to do the same computation
|
||||
auto base = elf_get_module_image_base(object_path);
|
||||
auto elf_object = open_elf_cached(object_path);
|
||||
// TODO: Cache the error
|
||||
if(!elf_object) {
|
||||
return elf_object.unwrap_error();
|
||||
}
|
||||
auto base = elf_object.unwrap_value()->get_module_image_base();
|
||||
if(base.is_error()) {
|
||||
return std::move(base).unwrap_error();
|
||||
}
|
||||
@ -55,12 +57,12 @@ namespace detail {
|
||||
if(it == cache.end()) {
|
||||
// arguably it'd be better to release the lock while computing this, but also arguably it's good to not
|
||||
// have two threads try to do the same computation
|
||||
auto obj = mach_o::open_mach_o(object_path);
|
||||
auto mach_o_object = open_mach_o_cached(object_path);
|
||||
// TODO: Cache the error
|
||||
if(!obj) {
|
||||
return obj.unwrap_error();
|
||||
if(!mach_o_object) {
|
||||
return mach_o_object.unwrap_error();
|
||||
}
|
||||
auto base = obj.unwrap_value().get_text_vmaddr();
|
||||
auto base = mach_o_object.unwrap_value()->get_text_vmaddr();
|
||||
if(!base) {
|
||||
return std::move(base).unwrap_error();
|
||||
}
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
#ifndef IMAGE_MODULE_BASE_HPP
|
||||
#define IMAGE_MODULE_BASE_HPP
|
||||
|
||||
#include "../utils/common.hpp"
|
||||
#include "../utils/utils.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
#include "object.hpp"
|
||||
#include "binary/object.hpp"
|
||||
|
||||
#include "../utils/common.hpp"
|
||||
#include "../utils/utils.hpp"
|
||||
#include "module_base.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "binary/module_base.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
@ -16,6 +17,9 @@
|
||||
#include <link.h> // needed for dladdr1's link_map info
|
||||
#endif
|
||||
#elif IS_WINDOWS
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
@ -74,9 +78,11 @@ namespace detail {
|
||||
- reinterpret_cast<std::uintptr_t>(info.dli_fbase)
|
||||
+ base.unwrap_value();
|
||||
} else {
|
||||
if(!should_absorb_trace_exceptions()) {
|
||||
base.drop_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
#else
|
||||
@ -97,9 +103,11 @@ namespace detail {
|
||||
- reinterpret_cast<std::uintptr_t>(info.dli_fbase)
|
||||
+ base.unwrap_value();
|
||||
} else {
|
||||
if(!should_absorb_trace_exceptions()) {
|
||||
base.drop_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
#endif
|
||||
@ -142,8 +150,10 @@ namespace detail {
|
||||
- reinterpret_cast<std::uintptr_t>(handle)
|
||||
+ base.unwrap_value();
|
||||
} else {
|
||||
if(!should_absorb_trace_exceptions()) {
|
||||
base.drop_error();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::fprintf(stderr, "%s\n", std::system_error(GetLastError(), std::system_category()).what());
|
||||
}
|
||||
@ -161,14 +171,18 @@ namespace detail {
|
||||
}
|
||||
|
||||
object_frame resolve_safe_object_frame(const safe_object_frame& frame) {
|
||||
auto base = get_module_image_base(frame.object_path);
|
||||
if(base.is_error()) {
|
||||
throw base.unwrap_error(); // This throw is intentional
|
||||
std::string object_path = frame.object_path;
|
||||
if(object_path.empty()) {
|
||||
return {
|
||||
frame.raw_address,
|
||||
0,
|
||||
""
|
||||
};
|
||||
}
|
||||
return {
|
||||
frame.raw_address,
|
||||
frame.address_relative_to_object_start + base.unwrap_value(),
|
||||
frame.object_path
|
||||
frame.address_relative_to_object_start,
|
||||
std::move(object_path)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,14 +1,13 @@
|
||||
#ifndef OBJECT_HPP
|
||||
#define OBJECT_HPP
|
||||
|
||||
#include "../utils/common.hpp"
|
||||
#include "../utils/utils.hpp"
|
||||
#include "module_base.hpp"
|
||||
#include <cpptrace/forward.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
namespace cpptrace {
|
||||
|
||||
namespace detail {
|
||||
object_frame get_frame_object_info(frame_ptr address);
|
||||
|
||||
|
||||
@ -1,16 +1,18 @@
|
||||
#include "pe.hpp"
|
||||
#include "binary/pe.hpp"
|
||||
|
||||
#include "../utils/common.hpp"
|
||||
#include "../utils/error.hpp"
|
||||
#include "../utils/utils.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#if IS_WINDOWS
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
|
||||
namespace cpptrace {
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
#ifndef PE_HPP
|
||||
#define PE_HPP
|
||||
|
||||
#include "../utils/common.hpp"
|
||||
#include "../utils/utils.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#if IS_WINDOWS
|
||||
#include <cstdint>
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
#include "safe_dl.hpp"
|
||||
#include "binary/safe_dl.hpp"
|
||||
|
||||
#include "../utils/common.hpp"
|
||||
#include "../utils/utils.hpp"
|
||||
#include "../utils/program_name.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "platform/program_name.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@ -53,6 +53,10 @@ namespace detail {
|
||||
// may return the object that defines the function descriptor (and not the object that contains the code
|
||||
// implementing the function), or fail to find any object at all.
|
||||
}
|
||||
|
||||
bool has_get_safe_object_frame() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
@ -63,6 +67,10 @@ namespace detail {
|
||||
out->address_relative_to_object_start = 0;
|
||||
out->object_path[0] = 0;
|
||||
}
|
||||
|
||||
bool has_get_safe_object_frame() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
#ifndef SAFE_DL_HPP
|
||||
#define SAFE_DL_HPP
|
||||
|
||||
#include "../utils/common.hpp"
|
||||
#include "utils/common.hpp"
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
|
||||
|
||||
bool has_get_safe_object_frame();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
454
src/cpptrace.cpp
454
src/cpptrace.cpp
@ -1,35 +1,50 @@
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
#include <cpptrace/formatting.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "cpptrace/basic.hpp"
|
||||
#include "symbols/symbols.hpp"
|
||||
#include "unwind/unwind.hpp"
|
||||
#include "demangle/demangle.hpp"
|
||||
#include "utils/exception_type.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/microfmt.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "binary/object.hpp"
|
||||
#include "binary/safe_dl.hpp"
|
||||
#include "snippets/snippet.hpp"
|
||||
#include "options.hpp"
|
||||
|
||||
namespace cpptrace {
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
raw_trace raw_trace::current(std::size_t skip) {
|
||||
try { // try/catch can never be hit but it's needed to prevent TCO
|
||||
return generate_raw_trace(skip + 1);
|
||||
} catch(...) {
|
||||
if(!detail::should_absorb_trace_exceptions()) {
|
||||
throw;
|
||||
}
|
||||
return raw_trace{};
|
||||
}
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
raw_trace raw_trace::current(std::size_t skip, std::size_t max_depth) {
|
||||
try { // try/catch can never be hit but it's needed to prevent TCO
|
||||
return generate_raw_trace(skip + 1, max_depth);
|
||||
} catch(...) {
|
||||
if(!detail::should_absorb_trace_exceptions()) {
|
||||
throw;
|
||||
}
|
||||
return raw_trace{};
|
||||
}
|
||||
}
|
||||
|
||||
object_trace raw_trace::resolve_object_trace() const {
|
||||
@ -47,7 +62,7 @@ namespace cpptrace {
|
||||
try {
|
||||
std::vector<stacktrace_frame> trace = detail::resolve_frames(frames);
|
||||
for(auto& frame : trace) {
|
||||
frame.symbol = detail::demangle(frame.symbol);
|
||||
frame.symbol = detail::demangle(frame.symbol, true);
|
||||
}
|
||||
return {std::move(trace)};
|
||||
} catch(...) { // NOSONAR
|
||||
@ -68,19 +83,33 @@ namespace cpptrace {
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
object_trace object_trace::current(std::size_t skip) {
|
||||
try { // try/catch can never be hit but it's needed to prevent TCO
|
||||
return generate_object_trace(skip + 1);
|
||||
} catch(...) {
|
||||
if(!detail::should_absorb_trace_exceptions()) {
|
||||
throw;
|
||||
}
|
||||
return object_trace{};
|
||||
}
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
object_trace object_trace::current(std::size_t skip, std::size_t max_depth) {
|
||||
try { // try/catch can never be hit but it's needed to prevent TCO
|
||||
return generate_object_trace(skip + 1, max_depth);
|
||||
} catch(...) {
|
||||
if(!detail::should_absorb_trace_exceptions()) {
|
||||
throw;
|
||||
}
|
||||
return object_trace{};
|
||||
}
|
||||
}
|
||||
|
||||
stacktrace object_trace::resolve() const {
|
||||
try {
|
||||
std::vector<stacktrace_frame> trace = detail::resolve_frames(frames);
|
||||
for(auto& frame : trace) {
|
||||
frame.symbol = detail::demangle(frame.symbol);
|
||||
frame.symbol = detail::demangle(frame.symbol, true);
|
||||
}
|
||||
return {std::move(trace)};
|
||||
} catch(...) { // NOSONAR
|
||||
@ -104,25 +133,11 @@ namespace cpptrace {
|
||||
}
|
||||
|
||||
std::string stacktrace_frame::to_string() const {
|
||||
std::string str;
|
||||
if(is_inline) {
|
||||
str += microfmt::format("{<{}}", 2 * sizeof(frame_ptr) + 2, "(inlined)");
|
||||
} else {
|
||||
str += microfmt::format("0x{>{}:0h}", 2 * sizeof(frame_ptr), raw_address);
|
||||
return to_string(false);
|
||||
}
|
||||
if(!symbol.empty()) {
|
||||
str += microfmt::format(" in {}", symbol);
|
||||
}
|
||||
if(!filename.empty()) {
|
||||
str += microfmt::format(" at {}", filename);
|
||||
if(line.has_value()) {
|
||||
str += microfmt::format(":{}", line.value());
|
||||
if(column.has_value()) {
|
||||
str += microfmt::format(":{}", column.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
return str;
|
||||
|
||||
std::string stacktrace_frame::to_string(bool color) const {
|
||||
return get_default_formatter().format(*this, color);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const stacktrace_frame& frame) {
|
||||
@ -131,119 +146,57 @@ namespace cpptrace {
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
stacktrace stacktrace::current(std::size_t skip) {
|
||||
try { // try/catch can never be hit but it's needed to prevent TCO
|
||||
return generate_trace(skip + 1);
|
||||
} catch(...) {
|
||||
if(!detail::should_absorb_trace_exceptions()) {
|
||||
throw;
|
||||
}
|
||||
return stacktrace{};
|
||||
}
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
stacktrace stacktrace::current(std::size_t skip, std::size_t max_depth) {
|
||||
try { // try/catch can never be hit but it's needed to prevent TCO
|
||||
return generate_trace(skip + 1, max_depth);
|
||||
} catch(...) {
|
||||
if(!detail::should_absorb_trace_exceptions()) {
|
||||
throw;
|
||||
}
|
||||
return stacktrace{};
|
||||
}
|
||||
}
|
||||
|
||||
void stacktrace::print() const {
|
||||
print(std::cerr, true);
|
||||
get_default_formatter().print(*this);
|
||||
}
|
||||
|
||||
void stacktrace::print(std::ostream& stream) const {
|
||||
print(stream, true);
|
||||
get_default_formatter().print(stream, *this);
|
||||
}
|
||||
|
||||
void stacktrace::print(std::ostream& stream, bool color) const {
|
||||
print(stream, color, true, nullptr);
|
||||
get_default_formatter().print(stream, *this, color);
|
||||
}
|
||||
|
||||
void print_frame(
|
||||
std::ostream& stream,
|
||||
bool color,
|
||||
unsigned frame_number_width,
|
||||
std::size_t counter,
|
||||
const stacktrace_frame& frame
|
||||
) {
|
||||
const auto reset = color ? RESET : "";
|
||||
const auto green = color ? GREEN : "";
|
||||
const auto yellow = color ? YELLOW : "";
|
||||
const auto blue = color ? BLUE : "";
|
||||
std::string line = microfmt::format("#{<{}} ", frame_number_width, counter);
|
||||
if(frame.is_inline) {
|
||||
line += microfmt::format("{<{}}", 2 * sizeof(frame_ptr) + 2, "(inlined)");
|
||||
} else {
|
||||
line += microfmt::format("{}0x{>{}:0h}{}", blue, 2 * sizeof(frame_ptr), frame.raw_address, reset);
|
||||
}
|
||||
if(!frame.symbol.empty()) {
|
||||
line += microfmt::format(" in {}{}{}", yellow, frame.symbol, reset);
|
||||
}
|
||||
if(!frame.filename.empty()) {
|
||||
line += microfmt::format(" at {}{}{}", green, frame.filename, reset);
|
||||
if(frame.line.has_value()) {
|
||||
line += microfmt::format(":{}{}{}", blue, frame.line.value(), reset);
|
||||
if(frame.column.has_value()) {
|
||||
line += microfmt::format(":{}{}{}", blue, frame.column.value(), reset);
|
||||
}
|
||||
}
|
||||
}
|
||||
stream << line;
|
||||
}
|
||||
|
||||
void stacktrace::print(std::ostream& stream, bool color, bool newline_at_end, const char* header) const {
|
||||
if(
|
||||
color && (
|
||||
(&stream == &std::cout && isatty(stdout_fileno)) || (&stream == &std::cerr && isatty(stderr_fileno))
|
||||
)
|
||||
) {
|
||||
detail::enable_virtual_terminal_processing_if_needed();
|
||||
}
|
||||
stream << (header ? header : "Stack trace (most recent call first):") << '\n';
|
||||
std::size_t counter = 0;
|
||||
if(frames.empty()) {
|
||||
stream << "<empty trace>\n";
|
||||
return;
|
||||
}
|
||||
const auto frame_number_width = detail::n_digits(static_cast<int>(frames.size()) - 1);
|
||||
for(const auto& frame : frames) {
|
||||
print_frame(stream, color, frame_number_width, counter, frame);
|
||||
if(newline_at_end || &frame != &frames.back()) {
|
||||
stream << '\n';
|
||||
}
|
||||
counter++;
|
||||
namespace detail {
|
||||
const formatter& get_default_snippet_formatter() {
|
||||
static formatter snippet_formatter = formatter{}.snippets(true);
|
||||
return snippet_formatter;
|
||||
}
|
||||
}
|
||||
|
||||
void stacktrace::print_with_snippets() const {
|
||||
print_with_snippets(std::cerr, true);
|
||||
detail::get_default_snippet_formatter().print(*this);
|
||||
}
|
||||
|
||||
void stacktrace::print_with_snippets(std::ostream& stream) const {
|
||||
print_with_snippets(stream, true);
|
||||
detail::get_default_snippet_formatter().print(stream, *this);
|
||||
}
|
||||
|
||||
void stacktrace::print_with_snippets(std::ostream& stream, bool color) const {
|
||||
print_with_snippets(stream, color, true, nullptr);
|
||||
}
|
||||
|
||||
void stacktrace::print_with_snippets(std::ostream& stream, bool color, bool newline_at_end, const char* header) const {
|
||||
if(
|
||||
color && (
|
||||
(&stream == &std::cout && isatty(stdout_fileno)) || (&stream == &std::cerr && isatty(stderr_fileno))
|
||||
)
|
||||
) {
|
||||
detail::enable_virtual_terminal_processing_if_needed();
|
||||
}
|
||||
stream << (header ? header : "Stack trace (most recent call first):") << '\n';
|
||||
std::size_t counter = 0;
|
||||
if(frames.empty()) {
|
||||
stream << "<empty trace>" << '\n';
|
||||
return;
|
||||
}
|
||||
const auto frame_number_width = detail::n_digits(static_cast<int>(frames.size()) - 1);
|
||||
for(const auto& frame : frames) {
|
||||
print_frame(stream, color, frame_number_width, counter, frame);
|
||||
if(newline_at_end || &frame != &frames.back()) {
|
||||
stream << '\n';
|
||||
}
|
||||
if(frame.line.has_value() && !frame.filename.empty()) {
|
||||
stream << detail::get_snippet(frame.filename, frame.line.value(), 2, color);
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
detail::get_default_snippet_formatter().print(stream, *this, color);
|
||||
}
|
||||
|
||||
void stacktrace::clear() {
|
||||
@ -255,13 +208,12 @@ namespace cpptrace {
|
||||
}
|
||||
|
||||
std::string stacktrace::to_string(bool color) const {
|
||||
std::ostringstream oss;
|
||||
print(oss, color, false, nullptr);
|
||||
return std::move(oss).str();
|
||||
return get_default_formatter().format(*this, color);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& stream, const stacktrace& trace) {
|
||||
return stream << trace.to_string();
|
||||
get_default_formatter().print(stream, trace);
|
||||
return stream;
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
@ -290,7 +242,14 @@ namespace cpptrace {
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
std::size_t safe_generate_raw_trace(frame_ptr* buffer, std::size_t size, std::size_t skip) {
|
||||
try { // try/catch can never be hit but it's needed to prevent TCO
|
||||
return detail::safe_capture_frames(buffer, size, skip + 1, SIZE_MAX);
|
||||
} catch(...) {
|
||||
if(!detail::should_absorb_trace_exceptions()) {
|
||||
throw;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
@ -300,7 +259,14 @@ namespace cpptrace {
|
||||
std::size_t skip,
|
||||
std::size_t max_depth
|
||||
) {
|
||||
try { // try/catch can never be hit but it's needed to prevent TCO
|
||||
return detail::safe_capture_frames(buffer, size, skip + 1, max_depth);
|
||||
} catch(...) {
|
||||
if(!detail::should_absorb_trace_exceptions()) {
|
||||
throw;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
@ -329,7 +295,14 @@ namespace cpptrace {
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
stacktrace generate_trace(std::size_t skip) {
|
||||
try { // try/catch can never be hit but it's needed to prevent TCO
|
||||
return generate_trace(skip + 1, SIZE_MAX);
|
||||
} catch(...) {
|
||||
if(!detail::should_absorb_trace_exceptions()) {
|
||||
throw;
|
||||
}
|
||||
return stacktrace{};
|
||||
}
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
@ -338,7 +311,7 @@ namespace cpptrace {
|
||||
std::vector<frame_ptr> frames = detail::capture_frames(skip + 1, max_depth);
|
||||
std::vector<stacktrace_frame> trace = detail::resolve_frames(frames);
|
||||
for(auto& frame : trace) {
|
||||
frame.symbol = detail::demangle(frame.symbol);
|
||||
frame.symbol = detail::demangle(frame.symbol, true);
|
||||
}
|
||||
return {std::move(trace)};
|
||||
} catch(...) { // NOSONAR
|
||||
@ -361,254 +334,7 @@ namespace cpptrace {
|
||||
return detail::has_safe_unwind();
|
||||
}
|
||||
|
||||
std::string demangle(const std::string& name) {
|
||||
return detail::demangle(name);
|
||||
}
|
||||
|
||||
std::string get_snippet(const std::string& path, std::size_t line, std::size_t context_size, bool color) {
|
||||
return detail::get_snippet(path, line, context_size, color);
|
||||
}
|
||||
|
||||
bool isatty(int fd) {
|
||||
return detail::isatty(fd);
|
||||
}
|
||||
|
||||
extern const int stdin_fileno = detail::fileno(stdin);
|
||||
extern const int stdout_fileno = detail::fileno(stdout);
|
||||
extern const int stderr_fileno = detail::fileno(stderr);
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE void print_terminate_trace() {
|
||||
generate_trace(1).print(
|
||||
std::cerr,
|
||||
isatty(stderr_fileno),
|
||||
true,
|
||||
"Stack trace to reach terminate handler (most recent call first):"
|
||||
);
|
||||
}
|
||||
|
||||
[[noreturn]] void terminate_handler() {
|
||||
// TODO: Support std::nested_exception?
|
||||
try {
|
||||
auto ptr = std::current_exception();
|
||||
if(ptr == nullptr) {
|
||||
fputs("terminate called without an active exception", stderr);
|
||||
print_terminate_trace();
|
||||
} else {
|
||||
std::rethrow_exception(ptr);
|
||||
}
|
||||
} catch(cpptrace::exception& e) {
|
||||
microfmt::print(
|
||||
stderr,
|
||||
"Terminate called after throwing an instance of {}: {}\n",
|
||||
demangle(typeid(e).name()),
|
||||
e.message()
|
||||
);
|
||||
e.trace().print(std::cerr, isatty(stderr_fileno));
|
||||
} catch(std::exception& e) {
|
||||
microfmt::print(
|
||||
stderr, "Terminate called after throwing an instance of {}: {}\n", demangle(typeid(e).name()), e.what()
|
||||
);
|
||||
print_terminate_trace();
|
||||
} catch(...) {
|
||||
microfmt::print(
|
||||
stderr, "Terminate called after throwing an instance of {}\n", detail::exception_type_name()
|
||||
);
|
||||
print_terminate_trace();
|
||||
}
|
||||
std::flush(std::cerr);
|
||||
abort();
|
||||
}
|
||||
|
||||
void register_terminate_handler() {
|
||||
std::set_terminate(terminate_handler);
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
std::atomic_bool absorb_trace_exceptions(true); // NOSONAR
|
||||
std::atomic_bool resolve_inlined_calls(true); // NOSONAR
|
||||
std::atomic<enum cache_mode> cache_mode(cache_mode::prioritize_speed); // NOSONAR
|
||||
}
|
||||
|
||||
void absorb_trace_exceptions(bool absorb) {
|
||||
detail::absorb_trace_exceptions = absorb;
|
||||
}
|
||||
|
||||
void enable_inlined_call_resolution(bool enable) {
|
||||
detail::resolve_inlined_calls = enable;
|
||||
}
|
||||
|
||||
namespace experimental {
|
||||
void set_cache_mode(cache_mode mode) {
|
||||
detail::cache_mode = mode;
|
||||
}
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
bool should_absorb_trace_exceptions() {
|
||||
return absorb_trace_exceptions;
|
||||
}
|
||||
|
||||
bool should_resolve_inlined_calls() {
|
||||
return resolve_inlined_calls;
|
||||
}
|
||||
|
||||
enum cache_mode get_cache_mode() {
|
||||
return cache_mode;
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
raw_trace get_raw_trace_and_absorb(std::size_t skip, std::size_t max_depth) {
|
||||
try {
|
||||
return generate_raw_trace(skip + 1, max_depth);
|
||||
} catch(const std::exception& e) {
|
||||
if(!detail::should_absorb_trace_exceptions()) {
|
||||
// TODO: Append to message somehow
|
||||
std::fprintf(
|
||||
stderr,
|
||||
"Cpptrace: Exception occurred while resolving trace in cpptrace::exception object:\n%s\n",
|
||||
e.what()
|
||||
);
|
||||
}
|
||||
return raw_trace{};
|
||||
}
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
raw_trace get_raw_trace_and_absorb(std::size_t skip) {
|
||||
return get_raw_trace_and_absorb(skip + 1, SIZE_MAX);
|
||||
}
|
||||
|
||||
lazy_trace_holder::lazy_trace_holder(const lazy_trace_holder& other) : resolved(other.resolved) {
|
||||
if(other.resolved) {
|
||||
new (&resolved_trace) stacktrace(other.resolved_trace);
|
||||
} else {
|
||||
new (&trace) raw_trace(other.trace);
|
||||
}
|
||||
}
|
||||
lazy_trace_holder::lazy_trace_holder(lazy_trace_holder&& other) noexcept : resolved(other.resolved) {
|
||||
if(other.resolved) {
|
||||
new (&resolved_trace) stacktrace(std::move(other.resolved_trace));
|
||||
} else {
|
||||
new (&trace) raw_trace(std::move(other.trace));
|
||||
}
|
||||
}
|
||||
lazy_trace_holder& lazy_trace_holder::operator=(const lazy_trace_holder& other) {
|
||||
clear();
|
||||
resolved = other.resolved;
|
||||
if(other.resolved) {
|
||||
new (&resolved_trace) stacktrace(other.resolved_trace);
|
||||
} else {
|
||||
new (&trace) raw_trace(other.trace);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
lazy_trace_holder& lazy_trace_holder::operator=(lazy_trace_holder&& other) noexcept {
|
||||
clear();
|
||||
resolved = other.resolved;
|
||||
if(other.resolved) {
|
||||
new (&resolved_trace) stacktrace(std::move(other.resolved_trace));
|
||||
} else {
|
||||
new (&trace) raw_trace(std::move(other.trace));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
lazy_trace_holder::~lazy_trace_holder() {
|
||||
clear();
|
||||
}
|
||||
// access
|
||||
stacktrace& lazy_trace_holder::get_resolved_trace() {
|
||||
if(!resolved) {
|
||||
raw_trace old_trace = std::move(trace);
|
||||
*this = lazy_trace_holder(stacktrace{});
|
||||
try {
|
||||
if(!old_trace.empty()) {
|
||||
resolved_trace = old_trace.resolve();
|
||||
}
|
||||
} catch(const std::exception& e) {
|
||||
if(!detail::should_absorb_trace_exceptions()) {
|
||||
// TODO: Append to message somehow?
|
||||
std::fprintf(
|
||||
stderr,
|
||||
"Exception occurred while resolving trace in cpptrace::exception object:\n%s\n",
|
||||
e.what()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return resolved_trace;
|
||||
}
|
||||
const stacktrace& lazy_trace_holder::get_resolved_trace() const {
|
||||
if(!resolved) {
|
||||
throw std::logic_error(
|
||||
"cpptrace::detaillazy_trace_holder::get_resolved_trace called on unresolved const object"
|
||||
);
|
||||
}
|
||||
return resolved_trace;
|
||||
}
|
||||
void lazy_trace_holder::clear() {
|
||||
if(resolved) {
|
||||
resolved_trace.~stacktrace();
|
||||
} else {
|
||||
trace.~raw_trace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char* lazy_exception::what() const noexcept {
|
||||
if(what_string.empty()) {
|
||||
what_string = message() + std::string(":\n") + trace_holder.get_resolved_trace().to_string();
|
||||
}
|
||||
return what_string.c_str();
|
||||
}
|
||||
|
||||
const char* lazy_exception::message() const noexcept {
|
||||
return "cpptrace::lazy_exception";
|
||||
}
|
||||
|
||||
const stacktrace& lazy_exception::trace() const noexcept {
|
||||
return trace_holder.get_resolved_trace();
|
||||
}
|
||||
|
||||
const char* exception_with_message::message() const noexcept {
|
||||
return user_message.c_str();
|
||||
}
|
||||
|
||||
system_error::system_error(int error_code, std::string&& message_arg, raw_trace&& trace) noexcept
|
||||
: runtime_error(
|
||||
message_arg + ": " + std::error_code(error_code, std::generic_category()).message(),
|
||||
std::move(trace)
|
||||
),
|
||||
ec(std::error_code(error_code, std::generic_category())) {}
|
||||
|
||||
const std::error_code& system_error::code() const noexcept {
|
||||
return ec;
|
||||
}
|
||||
|
||||
const char* nested_exception::message() const noexcept {
|
||||
if(message_value.empty()) {
|
||||
try {
|
||||
std::rethrow_exception(ptr);
|
||||
} catch(std::exception& e) {
|
||||
message_value = std::string("Nested exception: ") + e.what();
|
||||
} catch(...) {
|
||||
message_value = "Nested exception holding instance of " + detail::exception_type_name();
|
||||
}
|
||||
}
|
||||
return message_value.c_str();
|
||||
}
|
||||
|
||||
std::exception_ptr nested_exception::nested_ptr() const noexcept {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
void rethrow_and_wrap_if_needed(std::size_t skip) {
|
||||
try {
|
||||
std::rethrow_exception(std::current_exception());
|
||||
} catch(cpptrace::exception&) {
|
||||
throw; // already a cpptrace::exception
|
||||
} catch(...) {
|
||||
throw nested_exception(std::current_exception(), detail::get_raw_trace_and_absorb(skip + 1));
|
||||
}
|
||||
bool can_get_safe_object_frame() {
|
||||
return detail::has_get_safe_object_frame();
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
#include "symbols/symbols.hpp"
|
||||
#include "unwind/unwind.hpp"
|
||||
#include "demangle/demangle.hpp"
|
||||
#include "utils/exception_type.hpp"
|
||||
#include "platform/exception_type.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "binary/object.hpp"
|
||||
@ -107,7 +107,7 @@ CTRACE_FORMAT_EPILOGUE
|
||||
new_frame.line = frame.line.value_or(invalid_pos);
|
||||
new_frame.column = frame.column.value_or(invalid_pos);
|
||||
new_frame.filename = generate_owning_string(frame.filename).data;
|
||||
new_frame.symbol = generate_owning_string(cpptrace::detail::demangle(frame.symbol)).data;
|
||||
new_frame.symbol = generate_owning_string(cpptrace::detail::demangle(frame.symbol, true)).data;
|
||||
new_frame.is_inline = ctrace_bool(frame.is_inline);
|
||||
return new_frame;
|
||||
}
|
||||
@ -310,10 +310,14 @@ extern "C" {
|
||||
cpptrace::get_safe_object_frame(address, reinterpret_cast<cpptrace::safe_object_frame*>(out));
|
||||
}
|
||||
|
||||
ctrace_bool can_signal_safe_unwind() {
|
||||
ctrace_bool ctrace_can_signal_safe_unwind() {
|
||||
return cpptrace::can_signal_safe_unwind();
|
||||
}
|
||||
|
||||
ctrace_bool ctrace_can_get_safe_object_frame(void) {
|
||||
return cpptrace::can_get_safe_object_frame();
|
||||
}
|
||||
|
||||
// ctrace::io:
|
||||
ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color) {
|
||||
if(!trace || !trace->frames) {
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
std::string demangle(const std::string&);
|
||||
std::string demangle(const std::string& name, bool check_prefix);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,25 +1,55 @@
|
||||
#include "utils/microfmt.hpp"
|
||||
#ifdef CPPTRACE_DEMANGLE_WITH_CXXABI
|
||||
|
||||
#include "demangle.hpp"
|
||||
#include "demangle/demangle.hpp"
|
||||
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#include <cxxabi.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
std::string demangle(const std::string& name) {
|
||||
int status;
|
||||
std::string demangle(const std::string& name, bool check_prefix) {
|
||||
// https://itanium-cxx-abi.github.io/cxx-abi/abi.html#demangler
|
||||
// Check both _Z and __Z, apple prefixes all symbols with an underscore
|
||||
if(check_prefix && !(starts_with(name, "_Z") || starts_with(name, "__Z"))) {
|
||||
return name;
|
||||
}
|
||||
// Apple clang demangles __Z just fine but gcc doesn't, so just offset the leading underscore
|
||||
std::size_t offset = 0;
|
||||
if(starts_with(name, "__Z")) {
|
||||
offset = 1;
|
||||
}
|
||||
// Mangled names don't have spaces, we might add a space and some extra info somewhere but we still want it to
|
||||
// be demanglable. Look for a space, if there is one swap it with a null terminator briefly.
|
||||
auto end = name.find(' ');
|
||||
std::string name_copy;
|
||||
std::reference_wrapper<const std::string> to_demangle = name;
|
||||
std::string rest;
|
||||
if(end != std::string::npos) {
|
||||
name_copy = name.substr(0, end);
|
||||
rest = name.substr(end);
|
||||
to_demangle = name_copy;
|
||||
}
|
||||
// presumably thread-safe
|
||||
// it appears safe to pass nullptr for status however the docs don't explicitly say it's safe so I don't
|
||||
// want to rely on it
|
||||
char* const demangled = abi::__cxa_demangle(name.c_str(), nullptr, nullptr, &status);
|
||||
int status;
|
||||
auto demangled = raii_wrap(
|
||||
abi::__cxa_demangle(to_demangle.get().c_str() + offset, nullptr, nullptr, &status),
|
||||
[] (char* str) { std::free(str); }
|
||||
);
|
||||
// demangled will always be nullptr on non-zero status, and if __cxa_demangle ever fails for any reason
|
||||
// we'll just quietly return the mangled name
|
||||
if(demangled) {
|
||||
std::string str = demangled;
|
||||
std::free(demangled);
|
||||
if(demangled.get()) {
|
||||
std::string str = demangled.get();
|
||||
if(!rest.empty()) {
|
||||
str += rest;
|
||||
}
|
||||
return str;
|
||||
} else {
|
||||
return name;
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
#ifdef CPPTRACE_DEMANGLE_WITH_NOTHING
|
||||
|
||||
#include "demangle.hpp"
|
||||
#include "demangle/demangle.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
std::string demangle(const std::string& name) {
|
||||
std::string demangle(const std::string& name, bool) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,15 +1,21 @@
|
||||
#ifdef CPPTRACE_DEMANGLE_WITH_WINAPI
|
||||
|
||||
#include "demangle.hpp"
|
||||
#include "demangle/demangle.hpp"
|
||||
#include "platform/dbghelp_utils.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <dbghelp.h>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
std::string demangle(const std::string& name) {
|
||||
std::string demangle(const std::string& name, bool) {
|
||||
// Dbghelp is is single-threaded, so acquire a lock.
|
||||
auto lock = get_dbghelp_lock();
|
||||
char buffer[500];
|
||||
auto ret = UnDecorateSymbolName(name.c_str(), buffer, sizeof(buffer) - 1, 0);
|
||||
if(ret == 0) {
|
||||
|
||||
187
src/exceptions.cpp
Normal file
187
src/exceptions.cpp
Normal file
@ -0,0 +1,187 @@
|
||||
#include <cpptrace/exceptions.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include "platform/exception_type.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "options.hpp"
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
lazy_trace_holder::lazy_trace_holder(const lazy_trace_holder& other) : resolved(other.resolved) {
|
||||
if(other.resolved) {
|
||||
new (&resolved_trace) stacktrace(other.resolved_trace);
|
||||
} else {
|
||||
new (&trace) raw_trace(other.trace);
|
||||
}
|
||||
}
|
||||
lazy_trace_holder::lazy_trace_holder(lazy_trace_holder&& other) noexcept : resolved(other.resolved) {
|
||||
if(other.resolved) {
|
||||
new (&resolved_trace) stacktrace(std::move(other.resolved_trace));
|
||||
} else {
|
||||
new (&trace) raw_trace(std::move(other.trace));
|
||||
}
|
||||
}
|
||||
lazy_trace_holder& lazy_trace_holder::operator=(const lazy_trace_holder& other) {
|
||||
clear();
|
||||
resolved = other.resolved;
|
||||
if(other.resolved) {
|
||||
new (&resolved_trace) stacktrace(other.resolved_trace);
|
||||
} else {
|
||||
new (&trace) raw_trace(other.trace);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
lazy_trace_holder& lazy_trace_holder::operator=(lazy_trace_holder&& other) noexcept {
|
||||
clear();
|
||||
resolved = other.resolved;
|
||||
if(other.resolved) {
|
||||
new (&resolved_trace) stacktrace(std::move(other.resolved_trace));
|
||||
} else {
|
||||
new (&trace) raw_trace(std::move(other.trace));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
lazy_trace_holder::~lazy_trace_holder() {
|
||||
clear();
|
||||
}
|
||||
// access
|
||||
const raw_trace& lazy_trace_holder::get_raw_trace() const {
|
||||
if(resolved) {
|
||||
throw std::logic_error(
|
||||
"cpptrace::detail::lazy_trace_holder::get_resolved_trace called on resolved holder"
|
||||
);
|
||||
}
|
||||
return trace;
|
||||
}
|
||||
stacktrace& lazy_trace_holder::get_resolved_trace() {
|
||||
if(!resolved) {
|
||||
raw_trace old_trace = std::move(trace);
|
||||
*this = lazy_trace_holder(stacktrace{});
|
||||
try {
|
||||
if(!old_trace.empty()) {
|
||||
resolved_trace = old_trace.resolve();
|
||||
}
|
||||
} catch(const std::exception& e) {
|
||||
if(!detail::should_absorb_trace_exceptions()) {
|
||||
// TODO: Append to message somehow?
|
||||
std::fprintf(
|
||||
stderr,
|
||||
"Exception occurred while resolving trace in cpptrace::detail::lazy_trace_holder:\n%s\n",
|
||||
e.what()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return resolved_trace;
|
||||
}
|
||||
const stacktrace& lazy_trace_holder::get_resolved_trace() const {
|
||||
if(!resolved) {
|
||||
throw std::logic_error(
|
||||
"cpptrace::detail::lazy_trace_holder::get_resolved_trace called on unresolved const holder"
|
||||
);
|
||||
}
|
||||
return resolved_trace;
|
||||
}
|
||||
void lazy_trace_holder::clear() {
|
||||
if(resolved) {
|
||||
resolved_trace.~stacktrace();
|
||||
} else {
|
||||
trace.~raw_trace();
|
||||
}
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
raw_trace get_raw_trace_and_absorb(std::size_t skip, std::size_t max_depth) {
|
||||
try {
|
||||
return generate_raw_trace(skip + 1, max_depth);
|
||||
} catch(const std::exception& e) {
|
||||
if(!detail::should_absorb_trace_exceptions()) {
|
||||
// TODO: Append to message somehow
|
||||
std::fprintf(
|
||||
stderr,
|
||||
"Cpptrace: Exception occurred while resolving trace in cpptrace::exception object:\n%s\n",
|
||||
e.what()
|
||||
);
|
||||
}
|
||||
return raw_trace{};
|
||||
}
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
raw_trace get_raw_trace_and_absorb(std::size_t skip) {
|
||||
try { // try/catch can never be hit but it's needed to prevent TCO
|
||||
return get_raw_trace_and_absorb(skip + 1, SIZE_MAX);
|
||||
} catch(...) {
|
||||
if(!detail::should_absorb_trace_exceptions()) {
|
||||
throw;
|
||||
}
|
||||
return raw_trace{};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char* lazy_exception::what() const noexcept {
|
||||
if(what_string.empty()) {
|
||||
what_string = message() + std::string(":\n") + trace_holder.get_resolved_trace().to_string();
|
||||
}
|
||||
return what_string.c_str();
|
||||
}
|
||||
|
||||
const char* lazy_exception::message() const noexcept {
|
||||
return "cpptrace::lazy_exception";
|
||||
}
|
||||
|
||||
const stacktrace& lazy_exception::trace() const noexcept {
|
||||
return trace_holder.get_resolved_trace();
|
||||
}
|
||||
|
||||
const char* exception_with_message::message() const noexcept {
|
||||
return user_message.c_str();
|
||||
}
|
||||
|
||||
system_error::system_error(int error_code, std::string&& message_arg, raw_trace&& trace) noexcept
|
||||
: runtime_error(
|
||||
message_arg + ": " + std::error_code(error_code, std::generic_category()).message(),
|
||||
std::move(trace)
|
||||
),
|
||||
ec(std::error_code(error_code, std::generic_category())) {}
|
||||
|
||||
const std::error_code& system_error::code() const noexcept {
|
||||
return ec;
|
||||
}
|
||||
|
||||
const char* nested_exception::message() const noexcept {
|
||||
if(message_value.empty()) {
|
||||
try {
|
||||
std::rethrow_exception(ptr);
|
||||
} catch(std::exception& e) {
|
||||
message_value = std::string("Nested exception: ") + e.what();
|
||||
} catch(...) {
|
||||
message_value = "Nested exception holding instance of " + detail::exception_type_name();
|
||||
}
|
||||
}
|
||||
return message_value.c_str();
|
||||
}
|
||||
|
||||
std::exception_ptr nested_exception::nested_ptr() const noexcept {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
void rethrow_and_wrap_if_needed(std::size_t skip) {
|
||||
try {
|
||||
std::rethrow_exception(std::current_exception());
|
||||
} catch(cpptrace::exception&) {
|
||||
throw; // already a cpptrace::exception
|
||||
} catch(...) {
|
||||
throw nested_exception(std::current_exception(), detail::get_raw_trace_and_absorb(skip + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
353
src/formatting.cpp
Normal file
353
src/formatting.cpp
Normal file
@ -0,0 +1,353 @@
|
||||
#include <cpptrace/formatting.hpp>
|
||||
#include <cpptrace/utils.hpp>
|
||||
|
||||
#include "utils/optional.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "snippets/snippet.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
namespace cpptrace {
|
||||
class formatter::impl {
|
||||
struct {
|
||||
std::string header = "Stack trace (most recent call first):";
|
||||
color_mode color = color_mode::automatic;
|
||||
address_mode addresses = address_mode::raw;
|
||||
path_mode paths = path_mode::full;
|
||||
bool snippets = false;
|
||||
int context_lines = 2;
|
||||
bool columns = true;
|
||||
bool show_filtered_frames = true;
|
||||
std::function<bool(const stacktrace_frame&)> filter;
|
||||
} options;
|
||||
|
||||
public:
|
||||
void header(std::string header) {
|
||||
options.header = std::move(header);
|
||||
}
|
||||
void colors(formatter::color_mode mode) {
|
||||
options.color = mode;
|
||||
}
|
||||
void addresses(formatter::address_mode mode) {
|
||||
options.addresses = mode;
|
||||
}
|
||||
void paths(path_mode mode) {
|
||||
options.paths = mode;
|
||||
}
|
||||
void snippets(bool snippets) {
|
||||
options.snippets = snippets;
|
||||
}
|
||||
void snippet_context(int lines) {
|
||||
options.context_lines = lines;
|
||||
}
|
||||
void columns(bool columns) {
|
||||
options.columns = columns;
|
||||
}
|
||||
void filtered_frame_placeholders(bool show) {
|
||||
options.show_filtered_frames = show;
|
||||
}
|
||||
void filter(std::function<bool(const stacktrace_frame&)> filter) {
|
||||
options.filter = filter;
|
||||
}
|
||||
|
||||
std::string format(const stacktrace_frame& frame, detail::optional<bool> color_override = detail::nullopt) const {
|
||||
std::ostringstream oss;
|
||||
print_frame_inner(oss, frame, color_override.value_or(options.color == color_mode::always));
|
||||
return std::move(oss).str();
|
||||
}
|
||||
|
||||
std::string format(const stacktrace& trace, detail::optional<bool> color_override = detail::nullopt) const {
|
||||
std::ostringstream oss;
|
||||
print_internal(oss, trace, false, color_override);
|
||||
return std::move(oss).str();
|
||||
}
|
||||
|
||||
void print(const stacktrace_frame& frame, detail::optional<bool> color_override = detail::nullopt) const {
|
||||
print(std::cout, frame, color_override);
|
||||
}
|
||||
void print(
|
||||
std::ostream& stream,
|
||||
const stacktrace_frame& frame,
|
||||
detail::optional<bool> color_override = detail::nullopt
|
||||
) const {
|
||||
print_frame_internal(stream, frame, color_override);
|
||||
}
|
||||
void print(
|
||||
std::FILE* file,
|
||||
const stacktrace_frame& frame,
|
||||
detail::optional<bool> color_override = detail::nullopt
|
||||
) const {
|
||||
auto str = format(frame, color_override);
|
||||
std::fwrite(str.data(), 1, str.size(), file);
|
||||
}
|
||||
|
||||
void print(const stacktrace& trace, detail::optional<bool> color_override = detail::nullopt) const {
|
||||
print(std::cout, trace, color_override);
|
||||
}
|
||||
void print(
|
||||
std::ostream& stream,
|
||||
const stacktrace& trace,
|
||||
detail::optional<bool> color_override = detail::nullopt
|
||||
) const {
|
||||
print_internal(stream, trace, true, color_override);
|
||||
}
|
||||
void print(
|
||||
std::FILE* file,
|
||||
const stacktrace& trace,
|
||||
detail::optional<bool> color_override = detail::nullopt
|
||||
) const {
|
||||
auto str = format(trace, color_override);
|
||||
std::fwrite(str.data(), 1, str.size(), file);
|
||||
}
|
||||
|
||||
private:
|
||||
bool stream_is_tty(std::ostream& stream) const {
|
||||
// not great, but it'll have to do
|
||||
return (&stream == &std::cout && isatty(stdout_fileno))
|
||||
|| (&stream == &std::cerr && isatty(stderr_fileno));
|
||||
}
|
||||
|
||||
void maybe_ensure_virtual_terminal_processing(std::ostream& stream, bool color) const {
|
||||
if(color && stream_is_tty(stream)) {
|
||||
detail::enable_virtual_terminal_processing_if_needed();
|
||||
}
|
||||
}
|
||||
|
||||
bool should_do_color(std::ostream& stream, detail::optional<bool> color_override) const {
|
||||
bool do_color = options.color == color_mode::always || color_override.value_or(false);
|
||||
if(
|
||||
(options.color == color_mode::automatic || options.color == color_mode::always) &&
|
||||
(!color_override || color_override.unwrap() != false) &&
|
||||
stream_is_tty(stream)
|
||||
) {
|
||||
detail::enable_virtual_terminal_processing_if_needed();
|
||||
do_color = true;
|
||||
}
|
||||
return do_color;
|
||||
}
|
||||
|
||||
void print_internal(std::ostream& stream, const stacktrace& trace, bool newline_at_end, detail::optional<bool> color_override) const {
|
||||
bool do_color = should_do_color(stream, color_override);
|
||||
maybe_ensure_virtual_terminal_processing(stream, do_color);
|
||||
print_internal(stream, trace, newline_at_end, do_color);
|
||||
}
|
||||
|
||||
void print_internal(std::ostream& stream, const stacktrace& trace, bool newline_at_end, bool color) const {
|
||||
if(!options.header.empty()) {
|
||||
stream << options.header << '\n';
|
||||
}
|
||||
std::size_t counter = 0;
|
||||
const auto& frames = trace.frames;
|
||||
if(frames.empty()) {
|
||||
stream << "<empty trace>\n";
|
||||
return;
|
||||
}
|
||||
const auto frame_number_width = detail::n_digits(static_cast<int>(frames.size()) - 1);
|
||||
for(const auto& frame : frames) {
|
||||
if(options.filter && !options.filter(frame)) {
|
||||
if(!options.show_filtered_frames) {
|
||||
counter++;
|
||||
continue;
|
||||
}
|
||||
print_placeholder_frame(stream, frame_number_width, counter);
|
||||
} else {
|
||||
print_frame_internal(stream, frame, color, frame_number_width, counter);
|
||||
if(frame.line.has_value() && !frame.filename.empty() && options.snippets) {
|
||||
auto snippet = detail::get_snippet(
|
||||
frame.filename,
|
||||
frame.line.value(),
|
||||
options.context_lines,
|
||||
color
|
||||
);
|
||||
if(!snippet.empty()) {
|
||||
stream << '\n';
|
||||
stream << snippet;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(newline_at_end || &frame != &frames.back()) {
|
||||
stream << '\n';
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
|
||||
void print_frame_internal(
|
||||
std::ostream& stream,
|
||||
const stacktrace_frame& frame,
|
||||
bool color,
|
||||
unsigned frame_number_width,
|
||||
std::size_t counter
|
||||
) const {
|
||||
microfmt::print(stream, "#{<{}} ", frame_number_width, counter);
|
||||
print_frame_inner(stream, frame, color);
|
||||
}
|
||||
|
||||
void print_placeholder_frame(std::ostream& stream, unsigned frame_number_width, std::size_t counter) const {
|
||||
microfmt::print(stream, "#{<{}} (filtered)", frame_number_width, counter);
|
||||
}
|
||||
|
||||
void print_frame_internal(
|
||||
std::ostream& stream,
|
||||
const stacktrace_frame& frame,
|
||||
detail::optional<bool> color_override
|
||||
) const {
|
||||
bool do_color = should_do_color(stream, color_override);
|
||||
maybe_ensure_virtual_terminal_processing(stream, do_color);
|
||||
print_frame_inner(stream, frame, do_color);
|
||||
}
|
||||
|
||||
void print_frame_inner(std::ostream& stream, const stacktrace_frame& frame, bool color) const {
|
||||
const auto reset = color ? RESET : "";
|
||||
const auto green = color ? GREEN : "";
|
||||
const auto yellow = color ? YELLOW : "";
|
||||
const auto blue = color ? BLUE : "";
|
||||
if(frame.is_inline) {
|
||||
microfmt::print(stream, "{<{}} ", 2 * sizeof(frame_ptr) + 2, "(inlined)");
|
||||
} else if(options.addresses != address_mode::none) {
|
||||
auto address = options.addresses == address_mode::raw ? frame.raw_address : frame.object_address;
|
||||
microfmt::print(stream, "{}0x{>{}:0h}{} ", blue, 2 * sizeof(frame_ptr), address, reset);
|
||||
}
|
||||
if(!frame.symbol.empty()) {
|
||||
microfmt::print(stream, "in {}{}{}", yellow, frame.symbol, reset);
|
||||
}
|
||||
if(!frame.filename.empty()) {
|
||||
microfmt::print(
|
||||
stream,
|
||||
"{}at {}{}{}",
|
||||
frame.symbol.empty() ? "" : " ",
|
||||
green,
|
||||
options.paths == path_mode::full ? frame.filename : detail::basename(frame.filename, true),
|
||||
reset
|
||||
);
|
||||
if(frame.line.has_value()) {
|
||||
microfmt::print(stream, ":{}{}{}", blue, frame.line.value(), reset);
|
||||
if(frame.column.has_value() && options.columns) {
|
||||
microfmt::print(stream, ":{}{}{}", blue, frame.column.value(), reset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
formatter::formatter() : pimpl(new impl) {}
|
||||
formatter::~formatter() {
|
||||
delete pimpl;
|
||||
}
|
||||
|
||||
formatter::formatter(formatter&& other) : pimpl(detail::exchange(other.pimpl, nullptr)) {}
|
||||
formatter::formatter(const formatter& other) : pimpl(new impl(*other.pimpl)) {}
|
||||
formatter& formatter::operator=(formatter&& other) {
|
||||
if(pimpl) {
|
||||
delete pimpl;
|
||||
}
|
||||
pimpl = detail::exchange(other.pimpl, nullptr);
|
||||
return *this;
|
||||
}
|
||||
formatter& formatter::operator=(const formatter& other) {
|
||||
if(pimpl) {
|
||||
delete pimpl;
|
||||
}
|
||||
pimpl = new impl(*other.pimpl);
|
||||
return *this;
|
||||
}
|
||||
|
||||
formatter& formatter::header(std::string header) {
|
||||
pimpl->header(std::move(header));
|
||||
return *this;
|
||||
}
|
||||
formatter& formatter::colors(color_mode mode) {
|
||||
pimpl->colors(mode);
|
||||
return *this;
|
||||
}
|
||||
formatter& formatter::addresses(address_mode mode) {
|
||||
pimpl->addresses(mode);
|
||||
return *this;
|
||||
}
|
||||
formatter& formatter::paths(path_mode mode) {
|
||||
pimpl->paths(mode);
|
||||
return *this;
|
||||
}
|
||||
formatter& formatter::snippets(bool snippets) {
|
||||
pimpl->snippets(snippets);
|
||||
return *this;
|
||||
}
|
||||
formatter& formatter::snippet_context(int lines) {
|
||||
pimpl->snippet_context(lines);
|
||||
return *this;
|
||||
}
|
||||
formatter& formatter::columns(bool columns) {
|
||||
pimpl->columns(columns);
|
||||
return *this;
|
||||
}
|
||||
formatter& formatter::filtered_frame_placeholders(bool show) {
|
||||
pimpl->filtered_frame_placeholders(show);
|
||||
return *this;
|
||||
}
|
||||
formatter& formatter::filter(std::function<bool(const stacktrace_frame&)> filter) {
|
||||
pimpl->filter(std::move(filter));
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::string formatter::format(const stacktrace_frame& frame) const {
|
||||
return pimpl->format(frame);
|
||||
}
|
||||
std::string formatter::format(const stacktrace_frame& frame, bool color) const {
|
||||
return pimpl->format(frame, color);
|
||||
}
|
||||
|
||||
std::string formatter::format(const stacktrace& trace) const {
|
||||
return pimpl->format(trace);
|
||||
}
|
||||
std::string formatter::format(const stacktrace& trace, bool color) const {
|
||||
return pimpl->format(trace, color);
|
||||
}
|
||||
|
||||
void formatter::print(const stacktrace& trace) const {
|
||||
pimpl->print(trace);
|
||||
}
|
||||
void formatter::print(const stacktrace& trace, bool color) const {
|
||||
pimpl->print(trace, color);
|
||||
}
|
||||
void formatter::print(std::ostream& stream, const stacktrace& trace) const {
|
||||
pimpl->print(stream, trace);
|
||||
}
|
||||
void formatter::print(std::ostream& stream, const stacktrace& trace, bool color) const {
|
||||
pimpl->print(stream, trace, color);
|
||||
}
|
||||
void formatter::print(std::FILE* file, const stacktrace& trace) const {
|
||||
pimpl->print(file, trace);
|
||||
}
|
||||
void formatter::print(std::FILE* file, const stacktrace& trace, bool color) const {
|
||||
pimpl->print(file, trace, color);
|
||||
}
|
||||
|
||||
void formatter::print(const stacktrace_frame& frame) const {
|
||||
pimpl->print(frame);
|
||||
}
|
||||
void formatter::print(const stacktrace_frame& frame, bool color) const {
|
||||
pimpl->print(frame, color);
|
||||
}
|
||||
void formatter::print(std::ostream& stream, const stacktrace_frame& frame) const {
|
||||
pimpl->print(stream, frame);
|
||||
}
|
||||
void formatter::print(std::ostream& stream, const stacktrace_frame& frame, bool color) const {
|
||||
pimpl->print(stream, frame, color);
|
||||
}
|
||||
void formatter::print(std::FILE* file, const stacktrace_frame& frame) const {
|
||||
pimpl->print(file, frame);
|
||||
}
|
||||
void formatter::print(std::FILE* file, const stacktrace_frame& frame, bool color) const {
|
||||
pimpl->print(file, frame, color);
|
||||
}
|
||||
|
||||
const formatter& get_default_formatter() {
|
||||
static formatter formatter;
|
||||
return formatter;
|
||||
}
|
||||
}
|
||||
327
src/from_current.cpp
Normal file
327
src/from_current.cpp
Normal file
@ -0,0 +1,327 @@
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
#define CPPTRACE_DONT_PREPARE_UNWIND_INTERCEPTOR_ON
|
||||
#include <cpptrace/from_current.hpp>
|
||||
|
||||
#include <system_error>
|
||||
#include <typeinfo>
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
#include "utils/microfmt.hpp"
|
||||
|
||||
#ifndef _MSC_VER
|
||||
#include <string.h>
|
||||
#if IS_WINDOWS
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#if IS_APPLE
|
||||
#include <mach/mach.h>
|
||||
#ifdef HAS_MACH_VM
|
||||
#include <mach/mach_vm.h>
|
||||
#endif
|
||||
#else
|
||||
#include <fstream>
|
||||
#include <ios>
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
thread_local lazy_trace_holder current_exception_trace;
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE void collect_current_trace(std::size_t skip) {
|
||||
current_exception_trace = lazy_trace_holder(cpptrace::generate_raw_trace(skip + 1));
|
||||
}
|
||||
|
||||
#ifndef _MSC_VER
|
||||
// set only once by do_prepare_unwind_interceptor
|
||||
char (*intercept_unwind_handler)(std::size_t) = nullptr;
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
bool intercept_unwind(const std::type_info*, const std::type_info*, void**, unsigned) {
|
||||
if(intercept_unwind_handler) {
|
||||
intercept_unwind_handler(1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
bool unconditional_exception_unwind_interceptor(const std::type_info*, const std::type_info*, void**, unsigned) {
|
||||
collect_current_trace(1);
|
||||
return false;
|
||||
}
|
||||
|
||||
using do_catch_fn = decltype(intercept_unwind);
|
||||
|
||||
unwind_interceptor::~unwind_interceptor() = default;
|
||||
unconditional_unwind_interceptor::~unconditional_unwind_interceptor() = default;
|
||||
|
||||
#if IS_LIBSTDCXX
|
||||
constexpr size_t vtable_size = 11;
|
||||
#elif IS_LIBCXX
|
||||
constexpr size_t vtable_size = 10;
|
||||
#else
|
||||
#warning "Cpptrace from_current: Unrecognized C++ standard library, from_current() won't be supported"
|
||||
constexpr size_t vtable_size = 0;
|
||||
#endif
|
||||
|
||||
#if IS_WINDOWS
|
||||
int get_page_size() {
|
||||
SYSTEM_INFO info;
|
||||
GetSystemInfo(&info);
|
||||
return info.dwPageSize;
|
||||
}
|
||||
constexpr auto memory_readonly = PAGE_READONLY;
|
||||
constexpr auto memory_readwrite = PAGE_READWRITE;
|
||||
int mprotect_page_and_return_old_protections(void* page, int page_size, int protections) {
|
||||
DWORD old_protections;
|
||||
if(!VirtualProtect(page, page_size, protections, &old_protections)) {
|
||||
throw std::runtime_error(
|
||||
microfmt::format(
|
||||
"VirtualProtect call failed: {}",
|
||||
std::system_error(GetLastError(), std::system_category()).what()
|
||||
)
|
||||
);
|
||||
}
|
||||
return old_protections;
|
||||
}
|
||||
void mprotect_page(void* page, int page_size, int protections) {
|
||||
mprotect_page_and_return_old_protections(page, page_size, protections);
|
||||
}
|
||||
void* allocate_page(int page_size) {
|
||||
auto page = VirtualAlloc(nullptr, page_size, MEM_COMMIT | MEM_RESERVE, memory_readwrite);
|
||||
if(!page) {
|
||||
throw std::runtime_error(
|
||||
microfmt::format(
|
||||
"VirtualAlloc call failed: {}",
|
||||
std::system_error(GetLastError(), std::system_category()).what()
|
||||
)
|
||||
);
|
||||
}
|
||||
return page;
|
||||
}
|
||||
#else
|
||||
int get_page_size() {
|
||||
#if defined(_SC_PAGESIZE)
|
||||
return sysconf(_SC_PAGESIZE);
|
||||
#else
|
||||
return getpagesize();
|
||||
#endif
|
||||
}
|
||||
constexpr auto memory_readonly = PROT_READ;
|
||||
constexpr auto memory_readwrite = PROT_READ | PROT_WRITE;
|
||||
#if IS_APPLE
|
||||
int get_page_protections(void* page) {
|
||||
// https://stackoverflow.com/a/12627784/15675011
|
||||
#ifdef HAS_MACH_VM
|
||||
mach_vm_size_t vmsize;
|
||||
mach_vm_address_t address = (mach_vm_address_t)page;
|
||||
#else
|
||||
vm_size_t vmsize;
|
||||
vm_address_t address = (vm_address_t)page;
|
||||
#endif
|
||||
vm_region_basic_info_data_t info;
|
||||
mach_msg_type_number_t info_count =
|
||||
sizeof(size_t) == 8 ? VM_REGION_BASIC_INFO_COUNT_64 : VM_REGION_BASIC_INFO_COUNT;
|
||||
memory_object_name_t object;
|
||||
kern_return_t status =
|
||||
#ifdef HAS_MACH_VM
|
||||
mach_vm_region
|
||||
#else
|
||||
vm_region_64
|
||||
#endif
|
||||
(
|
||||
mach_task_self(),
|
||||
&address,
|
||||
&vmsize,
|
||||
VM_REGION_BASIC_INFO,
|
||||
(vm_region_info_t)&info,
|
||||
&info_count,
|
||||
&object
|
||||
);
|
||||
if(status == KERN_INVALID_ADDRESS) {
|
||||
throw std::runtime_error("vm_region failed with KERN_INVALID_ADDRESS");
|
||||
}
|
||||
int perms = 0;
|
||||
if(info.protection & VM_PROT_READ) {
|
||||
perms |= PROT_READ;
|
||||
}
|
||||
if(info.protection & VM_PROT_WRITE) {
|
||||
perms |= PROT_WRITE;
|
||||
}
|
||||
if(info.protection & VM_PROT_EXECUTE) {
|
||||
perms |= PROT_EXEC;
|
||||
}
|
||||
return perms;
|
||||
}
|
||||
#else
|
||||
int get_page_protections(void* page) {
|
||||
auto page_addr = reinterpret_cast<uintptr_t>(page);
|
||||
std::ifstream stream("/proc/self/maps");
|
||||
stream>>std::hex;
|
||||
while(!stream.eof()) {
|
||||
uintptr_t start;
|
||||
uintptr_t stop;
|
||||
stream>>start;
|
||||
stream.ignore(1); // dash
|
||||
stream>>stop;
|
||||
if(stream.eof()) {
|
||||
break;
|
||||
}
|
||||
if(stream.fail()) {
|
||||
throw std::runtime_error("Failure reading /proc/self/maps");
|
||||
}
|
||||
if(page_addr >= start && page_addr < stop) {
|
||||
stream.ignore(1); // space
|
||||
char r, w, x; // there's a private/shared flag after these but we don't need it
|
||||
stream>>r>>w>>x;
|
||||
if(stream.fail() || stream.eof()) {
|
||||
throw std::runtime_error("Failure reading /proc/self/maps");
|
||||
}
|
||||
int perms = 0;
|
||||
if(r == 'r') {
|
||||
perms |= PROT_READ;
|
||||
}
|
||||
if(w == 'w') {
|
||||
perms |= PROT_WRITE;
|
||||
}
|
||||
if(x == 'x') {
|
||||
perms |= PROT_EXEC;
|
||||
}
|
||||
// std::cerr<<"--parsed: "<<std::hex<<start<<" "<<stop<<" "<<r<<w<<x<<std::endl;
|
||||
return perms;
|
||||
}
|
||||
stream.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
|
||||
}
|
||||
throw std::runtime_error("Failed to find mapping with page in /proc/self/maps");
|
||||
}
|
||||
#endif
|
||||
void mprotect_page(void* page, int page_size, int protections) {
|
||||
if(mprotect(page, page_size, protections) != 0) {
|
||||
throw std::runtime_error(microfmt::format("mprotect call failed: {}", strerror(errno)));
|
||||
}
|
||||
}
|
||||
int mprotect_page_and_return_old_protections(void* page, int page_size, int protections) {
|
||||
auto old_protections = get_page_protections(page);
|
||||
mprotect_page(page, page_size, protections);
|
||||
return old_protections;
|
||||
}
|
||||
void* allocate_page(int page_size) {
|
||||
auto page = mmap(nullptr, page_size, memory_readwrite, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
|
||||
if(page == MAP_FAILED) {
|
||||
throw std::runtime_error(microfmt::format("mmap call failed: {}", strerror(errno)));
|
||||
}
|
||||
return page;
|
||||
}
|
||||
#endif
|
||||
|
||||
void perform_typeinfo_surgery(const std::type_info& info, do_catch_fn* do_catch_function) {
|
||||
if(vtable_size == 0) { // set to zero if we don't know what standard library we're working with
|
||||
return;
|
||||
}
|
||||
void* type_info_pointer = const_cast<void*>(static_cast<const void*>(&info));
|
||||
void* type_info_vtable_pointer = *static_cast<void**>(type_info_pointer);
|
||||
// the type info vtable pointer points to two pointers inside the vtable, adjust it back
|
||||
type_info_vtable_pointer = static_cast<void*>(static_cast<void**>(type_info_vtable_pointer) - 2);
|
||||
|
||||
// for libstdc++ the class type info vtable looks like
|
||||
// 0x7ffff7f89d18 <_ZTVN10__cxxabiv117__class_type_infoE>: 0x0000000000000000 0x00007ffff7f89d00
|
||||
// [offset ][typeinfo pointer ]
|
||||
// 0x7ffff7f89d28 <_ZTVN10__cxxabiv117__class_type_infoE+16>: 0x00007ffff7dd65a0 0x00007ffff7dd65c0
|
||||
// [base destructor ][deleting dtor ]
|
||||
// 0x7ffff7f89d38 <_ZTVN10__cxxabiv117__class_type_infoE+32>: 0x00007ffff7dd8f10 0x00007ffff7dd8f10
|
||||
// [__is_pointer_p ][__is_function_p ]
|
||||
// 0x7ffff7f89d48 <_ZTVN10__cxxabiv117__class_type_infoE+48>: 0x00007ffff7dd6640 0x00007ffff7dd6500
|
||||
// [__do_catch ][__do_upcast ]
|
||||
// 0x7ffff7f89d58 <_ZTVN10__cxxabiv117__class_type_infoE+64>: 0x00007ffff7dd65e0 0x00007ffff7dd66d0
|
||||
// [__do_upcast ][__do_dyncast ]
|
||||
// 0x7ffff7f89d68 <_ZTVN10__cxxabiv117__class_type_infoE+80>: 0x00007ffff7dd6580 0x00007ffff7f8abe8
|
||||
// [__do_find_public_src][other ]
|
||||
// In libc++ the layout is
|
||||
// [offset ][typeinfo pointer ]
|
||||
// [base destructor ][deleting dtor ]
|
||||
// [noop1 ][noop2 ]
|
||||
// [can_catch ][search_above_dst ]
|
||||
// [search_below_dst ][has_unambiguous_public_base]
|
||||
// Relevant documentation/implementation:
|
||||
// https://itanium-cxx-abi.github.io/cxx-abi/abi.html
|
||||
// libstdc++
|
||||
// https://github.com/gcc-mirror/gcc/blob/b13e34699c7d27e561fcfe1b66ced1e50e69976f/libstdc%252B%252B-v3/libsupc%252B%252B/typeinfo
|
||||
// https://github.com/gcc-mirror/gcc/blob/b13e34699c7d27e561fcfe1b66ced1e50e69976f/libstdc%252B%252B-v3/libsupc%252B%252B/class_type_info.cc
|
||||
// libc++
|
||||
// https://github.com/llvm/llvm-project/blob/648f4d0658ab00cf1e95330c8811aaea9481a274/libcxx/include/typeinfo
|
||||
// https://github.com/llvm/llvm-project/blob/648f4d0658ab00cf1e95330c8811aaea9481a274/libcxxabi/src/private_typeinfo.h
|
||||
|
||||
// shouldn't be anything other than 4096 but out of an abundance of caution
|
||||
auto page_size = get_page_size();
|
||||
if(page_size <= 0 && (page_size & (page_size - 1)) != 0) {
|
||||
throw std::runtime_error(
|
||||
microfmt::format("getpagesize() is not a power of 2 greater than zero (was {})", page_size)
|
||||
);
|
||||
}
|
||||
|
||||
// allocate a page for the new vtable so it can be made read-only later
|
||||
// the OS cleans this up, no cleanup done here for it
|
||||
void* new_vtable_page = allocate_page(page_size);
|
||||
// make our own copy of the vtable
|
||||
memcpy(new_vtable_page, type_info_vtable_pointer, vtable_size * sizeof(void*));
|
||||
// ninja in the custom __do_catch interceptor
|
||||
auto new_vtable = static_cast<void**>(new_vtable_page);
|
||||
new_vtable[6] = reinterpret_cast<void*>(do_catch_function);
|
||||
// make the page read-only
|
||||
mprotect_page(new_vtable_page, page_size, memory_readonly);
|
||||
|
||||
// make the vtable pointer for unwind_interceptor's type_info point to the new vtable
|
||||
auto type_info_addr = reinterpret_cast<uintptr_t>(type_info_pointer);
|
||||
auto page_addr = type_info_addr & ~(page_size - 1);
|
||||
// make sure the memory we're going to set is within the page
|
||||
if(type_info_addr - page_addr + sizeof(void*) > static_cast<unsigned>(page_size)) {
|
||||
throw std::runtime_error("pointer crosses page boundaries");
|
||||
}
|
||||
auto old_protections = mprotect_page_and_return_old_protections(
|
||||
reinterpret_cast<void*>(page_addr),
|
||||
page_size,
|
||||
memory_readwrite
|
||||
);
|
||||
*static_cast<void**>(type_info_pointer) = static_cast<void*>(new_vtable + 2);
|
||||
mprotect_page(reinterpret_cast<void*>(page_addr), page_size, old_protections);
|
||||
}
|
||||
|
||||
void do_prepare_unwind_interceptor(char(*intercept_unwind_handler)(std::size_t)) {
|
||||
static bool did_prepare = false;
|
||||
if(!did_prepare) {
|
||||
cpptrace::detail::intercept_unwind_handler = intercept_unwind_handler;
|
||||
try {
|
||||
perform_typeinfo_surgery(typeid(cpptrace::detail::unwind_interceptor), intercept_unwind);
|
||||
perform_typeinfo_surgery(
|
||||
typeid(cpptrace::detail::unconditional_unwind_interceptor),
|
||||
unconditional_exception_unwind_interceptor
|
||||
);
|
||||
} catch(std::exception& e) {
|
||||
std::fprintf(
|
||||
stderr,
|
||||
"Cpptrace: Exception occurred while preparing from_current support: %s\n",
|
||||
e.what()
|
||||
);
|
||||
} catch(...) {
|
||||
std::fprintf(stderr, "Cpptrace: Unknown exception occurred while preparing from_current support\n");
|
||||
}
|
||||
did_prepare = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
const raw_trace& raw_trace_from_current_exception() {
|
||||
return detail::current_exception_trace.get_raw_trace();
|
||||
}
|
||||
|
||||
const stacktrace& from_current_exception() {
|
||||
return detail::current_exception_trace.get_resolved_trace();
|
||||
}
|
||||
}
|
||||
41
src/options.cpp
Normal file
41
src/options.cpp
Normal file
@ -0,0 +1,41 @@
|
||||
#include <cpptrace/basic.hpp>
|
||||
|
||||
#include "options.hpp"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
std::atomic_bool absorb_trace_exceptions(true); // NOSONAR
|
||||
std::atomic_bool resolve_inlined_calls(true); // NOSONAR
|
||||
std::atomic<cache_mode> current_cache_mode(cache_mode::prioritize_speed); // NOSONAR
|
||||
}
|
||||
|
||||
void absorb_trace_exceptions(bool absorb) {
|
||||
detail::absorb_trace_exceptions = absorb;
|
||||
}
|
||||
|
||||
void enable_inlined_call_resolution(bool enable) {
|
||||
detail::resolve_inlined_calls = enable;
|
||||
}
|
||||
|
||||
namespace experimental {
|
||||
void set_cache_mode(cache_mode mode) {
|
||||
detail::current_cache_mode = mode;
|
||||
}
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
bool should_absorb_trace_exceptions() {
|
||||
return absorb_trace_exceptions;
|
||||
}
|
||||
|
||||
bool should_resolve_inlined_calls() {
|
||||
return resolve_inlined_calls;
|
||||
}
|
||||
|
||||
cache_mode get_cache_mode() {
|
||||
return current_cache_mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/options.hpp
Normal file
15
src/options.hpp
Normal file
@ -0,0 +1,15 @@
|
||||
#ifndef OPTIONS_HPP
|
||||
#define OPTIONS_HPP
|
||||
|
||||
#include <cpptrace/utils.hpp>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
// exported for test purposes
|
||||
CPPTRACE_EXPORT bool should_absorb_trace_exceptions();
|
||||
bool should_resolve_inlined_calls();
|
||||
cache_mode get_cache_mode();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
149
src/platform/dbghelp_utils.cpp
Normal file
149
src/platform/dbghelp_utils.cpp
Normal 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
|
||||
50
src/platform/dbghelp_utils.hpp
Normal file
50
src/platform/dbghelp_utils.hpp
Normal 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
|
||||
28
src/platform/exception_type.hpp
Normal file
28
src/platform/exception_type.hpp
Normal file
@ -0,0 +1,28 @@
|
||||
#ifndef EXCEPTION_TYPE_HPP
|
||||
#define EXCEPTION_TYPE_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
// libstdc++ and libc++
|
||||
#if defined(CPPTRACE_HAS_CXX_EXCEPTION_TYPE) && (IS_LIBSTDCXX || IS_LIBCXX)
|
||||
#include <typeinfo>
|
||||
#include <cxxabi.h>
|
||||
#include "demangle/demangle.hpp"
|
||||
#endif
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
inline std::string exception_type_name() {
|
||||
#if defined(CPPTRACE_HAS_CXX_EXCEPTION_TYPE) && (IS_LIBSTDCXX || IS_LIBCXX)
|
||||
const std::type_info* t = abi::__cxa_current_exception_type();
|
||||
return t ? detail::demangle(t->name(), false) : "<unknown>";
|
||||
#else
|
||||
return "<unknown>";
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -1,9 +1,15 @@
|
||||
#ifndef PATH_HPP
|
||||
#define PATH_HPP
|
||||
|
||||
#include "common.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <cctype>
|
||||
|
||||
#if IS_WINDOWS
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
48
src/platform/platform.hpp
Normal file
48
src/platform/platform.hpp
Normal file
@ -0,0 +1,48 @@
|
||||
#ifndef PLATFORM_HPP
|
||||
#define PLATFORM_HPP
|
||||
|
||||
#define IS_WINDOWS 0
|
||||
#define IS_LINUX 0
|
||||
#define IS_APPLE 0
|
||||
|
||||
#if defined(_WIN32)
|
||||
#undef IS_WINDOWS
|
||||
#define IS_WINDOWS 1
|
||||
#elif defined(__linux)
|
||||
#undef IS_LINUX
|
||||
#define IS_LINUX 1
|
||||
#elif defined(__APPLE__)
|
||||
#undef IS_APPLE
|
||||
#define IS_APPLE 1
|
||||
#else
|
||||
#error "Unexpected platform"
|
||||
#endif
|
||||
|
||||
#define IS_CLANG 0
|
||||
#define IS_GCC 0
|
||||
#define IS_MSVC 0
|
||||
|
||||
#if defined(__clang__)
|
||||
#undef IS_CLANG
|
||||
#define IS_CLANG 1
|
||||
#elif defined(__GNUC__) || defined(__GNUG__)
|
||||
#undef IS_GCC
|
||||
#define IS_GCC 1
|
||||
#elif defined(_MSC_VER)
|
||||
#undef IS_MSVC
|
||||
#define IS_MSVC 1
|
||||
#else
|
||||
#error "Unsupported compiler"
|
||||
#endif
|
||||
|
||||
#define IS_LIBSTDCXX 0
|
||||
#define IS_LIBCXX 0
|
||||
#if defined(__GLIBCXX__) || defined(__GLIBCPP__)
|
||||
#undef IS_LIBSTDCXX
|
||||
#define IS_LIBSTDCXX 1
|
||||
#elif defined(_LIBCPP_VERSION)
|
||||
#undef IS_LIBCXX
|
||||
#define IS_LIBCXX 1
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@ -4,14 +4,19 @@
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#if IS_WINDOWS
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
|
||||
#define CPPTRACE_MAX_PATH MAX_PATH
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
inline std::string program_name() {
|
||||
inline const char* program_name() {
|
||||
static std::mutex mutex;
|
||||
const std::lock_guard<std::mutex> lock(mutex);
|
||||
static std::string name;
|
||||
@ -31,7 +36,7 @@ namespace detail {
|
||||
}
|
||||
}
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
#elif IS_APPLE
|
||||
|
||||
#include <cstdint>
|
||||
#include <mach-o/dyld.h>
|
||||
@ -61,7 +66,7 @@ namespace detail {
|
||||
}
|
||||
}
|
||||
|
||||
#elif defined(__linux__)
|
||||
#elif IS_LINUX
|
||||
|
||||
#include <linux/limits.h>
|
||||
#include <sys/types.h>
|
||||
@ -1,4 +1,4 @@
|
||||
#include "snippet.hpp"
|
||||
#include "snippets/snippet.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
@ -8,8 +8,9 @@
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include "../utils/common.hpp"
|
||||
#include "../utils/utils.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/microfmt.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
@ -134,7 +135,10 @@ namespace detail {
|
||||
if(color && line == target_line) {
|
||||
snippet += RESET;
|
||||
}
|
||||
snippet += lines[line - original_begin] + "\n";
|
||||
snippet += lines[line - original_begin];
|
||||
if(line != end) {
|
||||
snippet += '\n';
|
||||
}
|
||||
}
|
||||
return snippet;
|
||||
}
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF
|
||||
|
||||
#include "resolver.hpp"
|
||||
#include "symbols/dwarf/resolver.hpp"
|
||||
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
#include "../symbols.hpp"
|
||||
#include "../../utils/common.hpp"
|
||||
#include "../../utils/error.hpp"
|
||||
#include "../../binary/object.hpp"
|
||||
#include "../../binary/mach-o.hpp"
|
||||
#include "../../utils/utils.hpp"
|
||||
#include <cpptrace/basic.hpp>
|
||||
#include "symbols/symbols.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "binary/object.hpp"
|
||||
#include "binary/mach-o.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
@ -34,7 +34,7 @@ namespace libdwarf {
|
||||
if(!resolver) {
|
||||
// this seems silly but it's an attempt to not repeatedly try to initialize new dwarf_resolvers if
|
||||
// exceptions are thrown, e.g. if the path doesn't exist
|
||||
resolver = std::unique_ptr<null_resolver>(new null_resolver);
|
||||
resolver = detail::make_unique<null_resolver>();
|
||||
resolver = make_dwarf_resolver(object_path);
|
||||
}
|
||||
return resolver;
|
||||
@ -46,11 +46,11 @@ namespace libdwarf {
|
||||
// the path doesn't exist
|
||||
std::unordered_map<std::string, uint64_t> symbols;
|
||||
this->symbols = symbols;
|
||||
auto obj = mach_o::open_mach_o(object_path);
|
||||
if(!obj) {
|
||||
auto mach_o_object = open_mach_o_cached(object_path);
|
||||
if(!mach_o_object) {
|
||||
return this->symbols.unwrap();
|
||||
}
|
||||
auto symbol_table = obj.unwrap_value().symbol_table();
|
||||
const auto& symbol_table = mach_o_object.unwrap_value()->symbol_table();
|
||||
if(!symbol_table) {
|
||||
return this->symbols.unwrap();
|
||||
}
|
||||
@ -72,8 +72,12 @@ namespace libdwarf {
|
||||
auto it = symbol_table.find(symbol_name);
|
||||
if(it != symbol_table.end()) {
|
||||
auto frame = frame_info;
|
||||
// substitute a translated address object for the target file in
|
||||
frame.object_address = it->second + offset;
|
||||
return get_resolver()->resolve_frame(frame);
|
||||
auto res = get_resolver()->resolve_frame(frame);
|
||||
// replace the translated address with the object address in the binary
|
||||
res.frame.object_address = frame_info.object_address;
|
||||
return res;
|
||||
} else {
|
||||
return {
|
||||
{
|
||||
@ -96,7 +100,7 @@ namespace libdwarf {
|
||||
uint64_t size;
|
||||
std::string name;
|
||||
nullable<uint64_t> target_address; // T(-1) is used as a sentinel
|
||||
std::size_t object_index;
|
||||
std::size_t object_index; // index into target_objects
|
||||
};
|
||||
|
||||
class debug_map_resolver : public symbol_resolver {
|
||||
@ -106,11 +110,11 @@ namespace libdwarf {
|
||||
debug_map_resolver(const std::string& source_object_path) {
|
||||
// load mach-o
|
||||
// TODO: Cache somehow?
|
||||
auto obj = mach_o::open_mach_o(source_object_path);
|
||||
if(!obj) {
|
||||
auto mach_o_object = open_mach_o_cached(source_object_path);
|
||||
if(!mach_o_object) {
|
||||
return;
|
||||
}
|
||||
mach_o& source_mach = obj.unwrap_value();
|
||||
mach_o& source_mach = *mach_o_object.unwrap_value();
|
||||
auto source_debug_map = source_mach.get_debug_map();
|
||||
if(!source_debug_map) {
|
||||
return;
|
||||
@ -133,6 +137,7 @@ namespace libdwarf {
|
||||
}
|
||||
}
|
||||
// sort for binary lookup later
|
||||
// TODO: Redundant?
|
||||
std::sort(
|
||||
symbols.begin(),
|
||||
symbols.end(),
|
||||
@ -167,7 +172,7 @@ namespace libdwarf {
|
||||
frame_info.raw_address,
|
||||
// the resolver doesn't care about the object address here, only the offset from the start
|
||||
// of the symbol and it'll lookup the symbol's base-address
|
||||
0,
|
||||
frame_info.object_address,
|
||||
frame_info.object_path
|
||||
},
|
||||
closest_symbol_it->name,
|
||||
@ -193,7 +198,7 @@ namespace libdwarf {
|
||||
};
|
||||
|
||||
std::unique_ptr<symbol_resolver> make_debug_map_resolver(const std::string& object_path) {
|
||||
return std::unique_ptr<debug_map_resolver>(new debug_map_resolver(object_path));
|
||||
return detail::make_unique<debug_map_resolver>(object_path);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
#ifndef DWARF_HPP
|
||||
#define DWARF_HPP
|
||||
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
#include "../utils/error.hpp"
|
||||
#include "../utils/utils.hpp"
|
||||
#include <cpptrace/basic.hpp>
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/microfmt.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
|
||||
#ifdef CPPTRACE_USE_NESTED_LIBDWARF_HEADER_PATH
|
||||
@ -27,10 +27,9 @@ namespace libdwarf {
|
||||
|
||||
[[noreturn]] inline void handle_dwarf_error(Dwarf_Debug dbg, Dwarf_Error error) {
|
||||
Dwarf_Unsigned ev = dwarf_errno(error);
|
||||
char* msg = dwarf_errmsg(error);
|
||||
(void)dbg;
|
||||
// dwarf_dealloc_error(dbg, error);
|
||||
throw internal_error("dwarf error {} {}", ev, msg);
|
||||
// dwarf_dealloc_error deallocates the message, attaching to msg is convenient
|
||||
auto msg = raii_wrap(dwarf_errmsg(error), [dbg, error] (char*) { dwarf_dealloc_error(dbg, error); });
|
||||
throw internal_error(microfmt::format("dwarf error {} {}", ev, msg.get()));
|
||||
}
|
||||
|
||||
struct die_object {
|
||||
@ -67,8 +66,12 @@ namespace libdwarf {
|
||||
}
|
||||
|
||||
~die_object() {
|
||||
release();
|
||||
}
|
||||
|
||||
void release() {
|
||||
if(die) {
|
||||
dwarf_dealloc_die(die);
|
||||
dwarf_dealloc_die(exchange(die, nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,16 +79,15 @@ namespace libdwarf {
|
||||
|
||||
die_object& operator=(const die_object&) = delete;
|
||||
|
||||
die_object(die_object&& other) noexcept : dbg(other.dbg), die(other.die) {
|
||||
// done for finding mistakes, attempts to use the die_object after this should segfault
|
||||
// a valid use otherwise would be moved_from.get_sibling() which would get the next CU
|
||||
other.dbg = nullptr;
|
||||
other.die = nullptr;
|
||||
}
|
||||
// dbg doesn't strictly have to be st to null but it helps ensure attempts to use the die_object after this to
|
||||
// segfault. A valid use otherwise would be moved_from.get_sibling() which would get the next CU.
|
||||
die_object(die_object&& other) noexcept
|
||||
: dbg(exchange(other.dbg, nullptr)), die(exchange(other.die, nullptr)) {}
|
||||
|
||||
die_object& operator=(die_object&& other) noexcept {
|
||||
std::swap(dbg, other.dbg);
|
||||
std::swap(die, other.die);
|
||||
release();
|
||||
dbg = exchange(other.dbg, nullptr);
|
||||
die = exchange(other.die, nullptr);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -241,6 +243,30 @@ namespace libdwarf {
|
||||
}
|
||||
}
|
||||
|
||||
Dwarf_Unsigned get_ranges_base_address(const die_object& cu_die) const {
|
||||
// After libdwarf v0.11.0 this can use dwarf_get_ranges_baseaddress, however, in the interest of not
|
||||
// requiring v0.11.0 just yet the logic is implemented here too.
|
||||
// The base address is:
|
||||
// - If the die has a rangelist, use the low_pc for that die
|
||||
// - Otherwise use the low_pc from the CU if present
|
||||
// - Otherwise 0
|
||||
if(has_attr(DW_AT_ranges)) {
|
||||
if(has_attr(DW_AT_low_pc)) {
|
||||
Dwarf_Addr lowpc;
|
||||
if(wrap(dwarf_lowpc, die, &lowpc) == DW_DLV_OK) {
|
||||
return lowpc;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(cu_die.has_attr(DW_AT_low_pc)) {
|
||||
Dwarf_Addr lowpc;
|
||||
if(wrap(dwarf_lowpc, cu_die.get(), &lowpc) == DW_DLV_OK) {
|
||||
return lowpc;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Dwarf_Unsigned get_ranges_offset(Dwarf_Attribute attr) const {
|
||||
Dwarf_Unsigned off = 0;
|
||||
Dwarf_Half form = 0;
|
||||
@ -334,7 +360,7 @@ namespace libdwarf {
|
||||
|
||||
template<typename F>
|
||||
// callback should return true to keep going
|
||||
void dwarf4_ranges(Dwarf_Addr lowpc, F callback) const {
|
||||
void dwarf4_ranges(Dwarf_Addr baseaddr, F callback) const {
|
||||
Dwarf_Attribute attr = nullptr;
|
||||
if(wrap(dwarf_attr, die, DW_AT_ranges, &attr) != DW_DLV_OK) {
|
||||
return;
|
||||
@ -344,10 +370,7 @@ namespace libdwarf {
|
||||
if(wrap(dwarf_global_formref, attr, &offset) != DW_DLV_OK) {
|
||||
return;
|
||||
}
|
||||
Dwarf_Addr baseaddr = 0;
|
||||
if(lowpc != (std::numeric_limits<Dwarf_Addr>::max)()) {
|
||||
baseaddr = lowpc;
|
||||
}
|
||||
Dwarf_Addr baseaddr_original = baseaddr;
|
||||
Dwarf_Ranges* ranges = nullptr;
|
||||
Dwarf_Signed count = 0;
|
||||
VERIFY(
|
||||
@ -375,15 +398,15 @@ namespace libdwarf {
|
||||
baseaddr = ranges[i].dwr_addr2;
|
||||
} else {
|
||||
ASSERT(ranges[i].dwr_type == DW_RANGES_END);
|
||||
baseaddr = lowpc;
|
||||
baseaddr = baseaddr_original;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
// callback should return true to keep going
|
||||
void dwarf_ranges(int version, F callback) const {
|
||||
Dwarf_Addr lowpc = (std::numeric_limits<Dwarf_Addr>::max)();
|
||||
void dwarf_ranges(const die_object& cu_die, int version, F callback) const {
|
||||
Dwarf_Addr lowpc;
|
||||
if(wrap(dwarf_lowpc, die, &lowpc) == DW_DLV_OK) {
|
||||
Dwarf_Addr highpc = 0;
|
||||
enum Dwarf_Form_Class return_class;
|
||||
@ -399,13 +422,13 @@ namespace libdwarf {
|
||||
if(version >= 5) {
|
||||
dwarf5_ranges(callback);
|
||||
} else {
|
||||
dwarf4_ranges(lowpc, callback);
|
||||
dwarf4_ranges(get_ranges_base_address(cu_die), callback);
|
||||
}
|
||||
}
|
||||
|
||||
rangelist_entries get_rangelist_entries(int version) const {
|
||||
rangelist_entries get_rangelist_entries(const die_object& cu_die, int version) const {
|
||||
rangelist_entries vec;
|
||||
dwarf_ranges(version, [&vec] (Dwarf_Addr low, Dwarf_Addr high) {
|
||||
dwarf_ranges(cu_die, version, [&vec] (Dwarf_Addr low, Dwarf_Addr high) {
|
||||
// Simple coalescing optimization:
|
||||
// Sometimes the range list entries are really continuous: [100, 200), [200, 300)
|
||||
// Other times there's just one byte of separation [300, 399), [400, 500)
|
||||
@ -422,9 +445,9 @@ namespace libdwarf {
|
||||
return vec;
|
||||
}
|
||||
|
||||
Dwarf_Bool pc_in_die(int version, Dwarf_Addr pc) const {
|
||||
Dwarf_Bool pc_in_die(const die_object& cu_die, int version, Dwarf_Addr pc) const {
|
||||
bool found = false;
|
||||
dwarf_ranges(version, [&found, pc] (Dwarf_Addr low, Dwarf_Addr high) {
|
||||
dwarf_ranges(cu_die, version, [&found, pc] (Dwarf_Addr low, Dwarf_Addr high) {
|
||||
if(pc >= low && pc < high) {
|
||||
found = true;
|
||||
return false;
|
||||
31
src/symbols/dwarf/dwarf_options.cpp
Normal file
31
src/symbols/dwarf/dwarf_options.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/symbols/dwarf/dwarf_options.hpp
Normal file
15
src/symbols/dwarf/dwarf_options.hpp
Normal 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
|
||||
@ -1,24 +1,29 @@
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF
|
||||
|
||||
#include "resolver.hpp"
|
||||
#include "symbols/dwarf/resolver.hpp"
|
||||
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
#include "../symbols.hpp"
|
||||
#include "../../utils/common.hpp"
|
||||
#include "../../utils/dwarf.hpp" // has dwarf #includes
|
||||
#include "../../utils/error.hpp"
|
||||
#include "../../utils/utils.hpp"
|
||||
#include "../../utils/path.hpp"
|
||||
#include "../../utils/program_name.hpp" // For CPPTRACE_MAX_PATH
|
||||
#include "../../binary/mach-o.hpp"
|
||||
#include <cpptrace/basic.hpp>
|
||||
#include "symbols/dwarf/dwarf.hpp" // has dwarf #includes
|
||||
#include "symbols/dwarf/dwarf_utils.hpp"
|
||||
#include "symbols/dwarf/dwarf_options.hpp"
|
||||
#include "symbols/symbols.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "utils/lru_cache.hpp"
|
||||
#include "platform/path.hpp"
|
||||
#include "platform/program_name.hpp" // For CPPTRACE_MAX_PATH
|
||||
|
||||
#if IS_APPLE
|
||||
#include "binary/mach-o.hpp"
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
@ -36,38 +41,6 @@ namespace libdwarf {
|
||||
constexpr bool dump_dwarf = false;
|
||||
constexpr bool trace_dwarf = false;
|
||||
|
||||
struct subprogram_entry {
|
||||
die_object die;
|
||||
Dwarf_Addr low;
|
||||
Dwarf_Addr high;
|
||||
};
|
||||
|
||||
struct cu_entry {
|
||||
die_object die;
|
||||
Dwarf_Half dwversion;
|
||||
Dwarf_Addr low;
|
||||
Dwarf_Addr high;
|
||||
};
|
||||
|
||||
struct line_entry {
|
||||
Dwarf_Addr low;
|
||||
// Dwarf_Addr high;
|
||||
// int i;
|
||||
Dwarf_Line line;
|
||||
optional<std::string> path;
|
||||
optional<std::uint32_t> line_number;
|
||||
optional<std::uint32_t> column_number;
|
||||
line_entry(Dwarf_Addr low, Dwarf_Line line) : low(low), line(line) {}
|
||||
};
|
||||
|
||||
struct line_table_info {
|
||||
Dwarf_Unsigned version;
|
||||
Dwarf_Line_Context line_context;
|
||||
// sorted by low_addr
|
||||
// TODO: Make this optional at some point, it may not be generated if cache mode switches during program exec...
|
||||
std::vector<line_entry> line_entries;
|
||||
};
|
||||
|
||||
class dwarf_resolver;
|
||||
|
||||
// used to describe data from an upstream binary to a resolver for the .dwo
|
||||
@ -79,20 +52,24 @@ namespace libdwarf {
|
||||
|
||||
class dwarf_resolver : public symbol_resolver {
|
||||
std::string object_path;
|
||||
Dwarf_Debug dbg = nullptr;
|
||||
// dwarf_finish needs to be called after all other dwarf stuff is cleaned up, e.g. `srcfiles` and aranges etc
|
||||
// raii_wrapping ensures this is the last thing done after the destructor logic and all other data members are
|
||||
// cleaned up
|
||||
raii_wrapper<Dwarf_Debug, void(*)(Dwarf_Debug)> dbg{nullptr, [](Dwarf_Debug dbg) { dwarf_finish(dbg); }};
|
||||
bool ok = false;
|
||||
// .debug_aranges cache
|
||||
Dwarf_Arange* aranges = nullptr;
|
||||
Dwarf_Signed arange_count = 0;
|
||||
// Map from CU -> Line context
|
||||
std::unordered_map<Dwarf_Off, line_table_info> line_tables;
|
||||
lru_cache<Dwarf_Off, line_table_info> line_tables{get_dwarf_resolver_line_table_cache_size()};
|
||||
// Map from CU -> Sorted subprograms vector
|
||||
std::unordered_map<Dwarf_Off, std::vector<subprogram_entry>> subprograms_cache;
|
||||
std::unordered_map<Dwarf_Off, die_cache<monostate>> subprograms_cache;
|
||||
// Vector of ranges and their corresponding CU offsets
|
||||
std::vector<cu_entry> cu_cache;
|
||||
// data stored for each cache entry is a Dwarf_Half dwversion
|
||||
die_cache<Dwarf_Half> cu_cache;
|
||||
bool generated_cu_cache = false;
|
||||
// Map from CU -> {srcfiles, count}
|
||||
std::unordered_map<Dwarf_Off, std::pair<char**, Dwarf_Signed>> srcfiles_cache;
|
||||
std::unordered_map<Dwarf_Off, srcfiles> srcfiles_cache;
|
||||
// Map from CU -> split full cu resolver
|
||||
std::unordered_map<Dwarf_Off, std::unique_ptr<dwarf_resolver>> split_full_cu_resolvers;
|
||||
// info for resolving a dwo object
|
||||
@ -150,12 +127,12 @@ namespace libdwarf {
|
||||
if(result.is_error()) {
|
||||
result.drop_error();
|
||||
} else if(result.unwrap_value()) {
|
||||
auto obj = mach_o::open_mach_o(object_path);
|
||||
if(!obj) {
|
||||
auto mach_o_object = open_mach_o_cached(object_path);
|
||||
if(!mach_o_object) {
|
||||
ok = false;
|
||||
return;
|
||||
}
|
||||
universal_number = obj.unwrap_value().get_fat_index();
|
||||
universal_number = mach_o_object.unwrap_value()->get_fat_index();
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -165,6 +142,7 @@ namespace libdwarf {
|
||||
if(use_buffer) {
|
||||
buffer = std::unique_ptr<char[]>(new char[CPPTRACE_MAX_PATH]);
|
||||
}
|
||||
dwarf_set_de_alloc_flag(0);
|
||||
auto ret = wrap(
|
||||
dwarf_init_path_a,
|
||||
object_path.c_str(),
|
||||
@ -174,7 +152,7 @@ namespace libdwarf {
|
||||
universal_number,
|
||||
nullptr,
|
||||
nullptr,
|
||||
&dbg
|
||||
&dbg.get()
|
||||
);
|
||||
if(ret == DW_DLV_OK) {
|
||||
ok = true;
|
||||
@ -190,7 +168,7 @@ namespace libdwarf {
|
||||
VERIFY(wrap(dwarf_set_tied_dbg, dbg, skeleton.unwrap().resolver.dbg) == DW_DLV_OK);
|
||||
}
|
||||
|
||||
if(ok) {
|
||||
if(ok && !get_dwarf_resolver_disable_aranges()) {
|
||||
// Check for .debug_aranges for fast lookup
|
||||
wrap(dwarf_get_aranges, dbg, &aranges, &arange_count);
|
||||
}
|
||||
@ -198,21 +176,13 @@ namespace libdwarf {
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
|
||||
~dwarf_resolver() override {
|
||||
// TODO: Maybe redundant since dwarf_finish(dbg); will clean up the line stuff anyway but may as well just
|
||||
// for thoroughness
|
||||
for(auto& entry : line_tables) {
|
||||
dwarf_srclines_dealloc_b(entry.second.line_context);
|
||||
}
|
||||
for(auto& entry : srcfiles_cache) {
|
||||
dwarf_dealloc(dbg, entry.second.first, DW_DLA_LIST);
|
||||
}
|
||||
// subprograms_cache needs to be destroyed before dbg otherwise there will be another use after free
|
||||
subprograms_cache.clear();
|
||||
if(aranges) {
|
||||
for(int i = 0; i < arange_count; i++) {
|
||||
dwarf_dealloc(dbg, aranges[i], DW_DLA_ARANGE);
|
||||
aranges[i] = nullptr;
|
||||
}
|
||||
dwarf_dealloc(dbg, aranges, DW_DLA_LIST);
|
||||
}
|
||||
cu_cache.clear();
|
||||
dwarf_finish(dbg);
|
||||
}
|
||||
|
||||
dwarf_resolver(const dwarf_resolver&) = delete;
|
||||
@ -274,29 +244,32 @@ namespace libdwarf {
|
||||
walk_compilation_units([this] (const die_object& cu_die) {
|
||||
Dwarf_Half offset_size = 0;
|
||||
Dwarf_Half dwversion = 0;
|
||||
dwarf_get_version_of_die(cu_die.get(), &dwversion, &offset_size);
|
||||
VERIFY(dwarf_get_version_of_die(cu_die.get(), &dwversion, &offset_size) == DW_DLV_OK);
|
||||
if(skeleton) {
|
||||
// NOTE: If we have a corresponding skeleton, we assume we have one CU matching the skeleton CU
|
||||
// Precedence for this assumption is https://dwarfstd.org/doc/DWARF5.pdf#subsection.3.1.3
|
||||
// TODO: Also assuming same dwversion
|
||||
auto ranges_vec = skeleton.unwrap().cu_die.get_rangelist_entries(dwversion);
|
||||
const auto& skeleton_cu = skeleton.unwrap().cu_die;
|
||||
auto ranges_vec = skeleton_cu.get_rangelist_entries(skeleton_cu, dwversion);
|
||||
if(!ranges_vec.empty()) {
|
||||
auto cu_die_handle = cu_cache.add_die(cu_die.clone());
|
||||
for(auto range : ranges_vec) {
|
||||
// TODO: Reduce cloning here
|
||||
cu_cache.push_back({ cu_die.clone(), dwversion, range.first, range.second });
|
||||
cu_cache.insert(cu_die_handle, range.first, range.second, dwversion);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
auto ranges_vec = cu_die.get_rangelist_entries(dwversion);
|
||||
auto ranges_vec = cu_die.get_rangelist_entries(cu_die, dwversion);
|
||||
if(!ranges_vec.empty()) {
|
||||
auto cu_die_handle = cu_cache.add_die(cu_die.clone());
|
||||
for(auto range : ranges_vec) {
|
||||
// TODO: Reduce cloning here
|
||||
cu_cache.push_back({ cu_die.clone(), dwversion, range.first, range.second });
|
||||
cu_cache.insert(cu_die_handle, range.first, range.second, dwversion);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
std::sort(cu_cache.begin(), cu_cache.end(), [] (const cu_entry& a, const cu_entry& b) {
|
||||
return a.low < b.low;
|
||||
});
|
||||
cu_cache.finalize();
|
||||
generated_cu_cache = true;
|
||||
}
|
||||
}
|
||||
@ -339,11 +312,11 @@ namespace libdwarf {
|
||||
char** dw_srcfiles;
|
||||
Dwarf_Signed dw_filecount;
|
||||
VERIFY(wrap(dwarf_srcfiles, cu_die.get(), &dw_srcfiles, &dw_filecount) == DW_DLV_OK);
|
||||
srcfiles srcfiles(cu_die.dbg, dw_srcfiles, dw_filecount);
|
||||
if(Dwarf_Signed(file_i) < dw_filecount) {
|
||||
// dwarf is using 1-indexing
|
||||
filename = dw_srcfiles[file_i];
|
||||
filename = srcfiles.get(file_i);
|
||||
}
|
||||
dwarf_dealloc(cu_die.dbg, dw_srcfiles, DW_DLA_LIST);
|
||||
} else {
|
||||
auto off = cu_die.get_global_offset();
|
||||
auto it = srcfiles_cache.find(off);
|
||||
@ -351,13 +324,11 @@ namespace libdwarf {
|
||||
char** dw_srcfiles;
|
||||
Dwarf_Signed dw_filecount;
|
||||
VERIFY(wrap(dwarf_srcfiles, cu_die.get(), &dw_srcfiles, &dw_filecount) == DW_DLV_OK);
|
||||
it = srcfiles_cache.insert(it, {off, {dw_srcfiles, dw_filecount}});
|
||||
it = srcfiles_cache.insert(it, {off, srcfiles{cu_die.dbg, dw_srcfiles, dw_filecount}});
|
||||
}
|
||||
char** dw_srcfiles = it->second.first;
|
||||
Dwarf_Signed dw_filecount = it->second.second;
|
||||
if(Dwarf_Signed(file_i) < dw_filecount) {
|
||||
if(file_i < it->second.count()) {
|
||||
// dwarf is using 1-indexing
|
||||
filename = dw_srcfiles[file_i];
|
||||
filename = it->second.get(file_i);
|
||||
}
|
||||
}
|
||||
return filename;
|
||||
@ -386,14 +357,14 @@ namespace libdwarf {
|
||||
walk_die_list(
|
||||
child,
|
||||
[this, &cu_die, pc, dwversion, &inlines, &target_die, ¤t_obj_holder] (const die_object& die) {
|
||||
if(die.get_tag() == DW_TAG_inlined_subroutine && die.pc_in_die(dwversion, pc)) {
|
||||
if(die.get_tag() == DW_TAG_inlined_subroutine && die.pc_in_die(cu_die, dwversion, pc)) {
|
||||
const auto name = subprogram_symbol(die, dwversion);
|
||||
auto file_i = die.get_unsigned_attribute(DW_AT_call_file);
|
||||
// TODO: Refactor.... Probably put logic in resolve_filename.
|
||||
if(file_i) {
|
||||
// for dwarf 2, 3, 4, and experimental line table version 0xfe06 1-indexing is used
|
||||
// for dwarf 5 0-indexing is used
|
||||
optional<std::reference_wrapper<line_table_info>> line_table_opt;
|
||||
optional<line_table_info&> line_table_opt;
|
||||
if(skeleton) {
|
||||
line_table_opt = skeleton.unwrap().resolver.get_line_table(
|
||||
skeleton.unwrap().cu_die
|
||||
@ -402,7 +373,7 @@ namespace libdwarf {
|
||||
line_table_opt = get_line_table(cu_die);
|
||||
}
|
||||
if(line_table_opt) {
|
||||
auto& line_table = line_table_opt.unwrap().get();
|
||||
auto& line_table = line_table_opt.unwrap();
|
||||
if(line_table.version != 5) {
|
||||
if(file_i.unwrap() == 0) {
|
||||
file_i.reset(); // 0 means no name to be found
|
||||
@ -430,6 +401,10 @@ namespace libdwarf {
|
||||
current_obj_holder = die.clone();
|
||||
target_die = current_obj_holder;
|
||||
return false;
|
||||
} else if(die.get_tag() == DW_TAG_lexical_block && die.pc_in_die(cu_die, dwversion, pc)) {
|
||||
current_obj_holder = die.clone();
|
||||
target_die = current_obj_holder;
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
@ -478,7 +453,7 @@ namespace libdwarf {
|
||||
die.get_name().c_str()
|
||||
);
|
||||
}
|
||||
if(!(die.get_tag() == DW_TAG_namespace || die.pc_in_die(dwversion, pc))) {
|
||||
if(!(die.get_tag() == DW_TAG_namespace || die.pc_in_die(cu_die, dwversion, pc))) {
|
||||
if(dump_dwarf) {
|
||||
std::fprintf(stderr, "pc not in die\n");
|
||||
}
|
||||
@ -520,28 +495,31 @@ namespace libdwarf {
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
|
||||
void preprocess_subprograms(
|
||||
const die_object& cu_die,
|
||||
const die_object& die,
|
||||
Dwarf_Half dwversion,
|
||||
std::vector<subprogram_entry>& vec
|
||||
die_cache<monostate>& subprogram_cache
|
||||
) {
|
||||
walk_die_list(
|
||||
die,
|
||||
[this, dwversion, &vec] (const die_object& die) {
|
||||
[this, &cu_die, dwversion, &subprogram_cache] (const die_object& die) {
|
||||
switch(die.get_tag()) {
|
||||
case DW_TAG_subprogram:
|
||||
{
|
||||
auto ranges_vec = die.get_rangelist_entries(dwversion);
|
||||
auto ranges_vec = die.get_rangelist_entries(cu_die, dwversion);
|
||||
// TODO: Feels super inefficient and some day should maybe use an interval tree.
|
||||
if(!ranges_vec.empty()) {
|
||||
auto die_handle = subprogram_cache.add_die(die.clone());
|
||||
for(auto range : ranges_vec) {
|
||||
// TODO: Reduce cloning here
|
||||
vec.push_back({ die.clone(), range.first, range.second });
|
||||
subprogram_cache.insert(die_handle, range.first, range.second);
|
||||
}
|
||||
}
|
||||
// Walk children to get things like lambdas
|
||||
// TODO: Somehow find a way to get better names here? For gcc it's just "operator()"
|
||||
// On clang it's better
|
||||
auto child = die.get_child();
|
||||
if(child) {
|
||||
preprocess_subprograms(child, dwversion, vec);
|
||||
preprocess_subprograms(cu_die, child, dwversion, subprogram_cache);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -554,7 +532,7 @@ namespace libdwarf {
|
||||
{
|
||||
auto child = die.get_child();
|
||||
if(child) {
|
||||
preprocess_subprograms(child, dwversion, vec);
|
||||
preprocess_subprograms(cu_die, child, dwversion, subprogram_cache);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -584,41 +562,32 @@ namespace libdwarf {
|
||||
auto it = subprograms_cache.find(off);
|
||||
if(it == subprograms_cache.end()) {
|
||||
// TODO: Refactor. Do the sort in the preprocess function and return the vec directly.
|
||||
std::vector<subprogram_entry> vec;
|
||||
preprocess_subprograms(cu_die, dwversion, vec);
|
||||
std::sort(vec.begin(), vec.end(), [] (const subprogram_entry& a, const subprogram_entry& b) {
|
||||
return a.low < b.low;
|
||||
});
|
||||
subprograms_cache.emplace(off, std::move(vec));
|
||||
die_cache<monostate> subprogram_cache;
|
||||
preprocess_subprograms(cu_die, cu_die, dwversion, subprogram_cache);
|
||||
subprogram_cache.finalize();
|
||||
subprograms_cache.emplace(off, std::move(subprogram_cache));
|
||||
it = subprograms_cache.find(off);
|
||||
}
|
||||
auto& vec = it->second;
|
||||
auto vec_it = first_less_than_or_equal(
|
||||
vec.begin(),
|
||||
vec.end(),
|
||||
pc,
|
||||
[] (Dwarf_Addr pc, const subprogram_entry& entry) {
|
||||
return pc < entry.low;
|
||||
}
|
||||
);
|
||||
const auto& subprogram_cache = it->second;
|
||||
auto maybe_die = subprogram_cache.lookup(pc);
|
||||
// If the vector has been empty this can happen
|
||||
if(vec_it != vec.end()) {
|
||||
if(vec_it->die.pc_in_die(dwversion, pc)) {
|
||||
frame.symbol = retrieve_symbol_for_subprogram(cu_die, vec_it->die, pc, dwversion, inlines);
|
||||
if(maybe_die.has_value()) {
|
||||
if(maybe_die.unwrap().pc_in_die(cu_die, dwversion, pc)) {
|
||||
frame.symbol = retrieve_symbol_for_subprogram(cu_die, maybe_die.unwrap(), pc, dwversion, inlines);
|
||||
}
|
||||
} else {
|
||||
ASSERT(vec.size() == 0, "Vec should be empty?");
|
||||
ASSERT(subprogram_cache.ranges_count() == 0, "subprogram_cache.ranges_count() should be 0?");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// returns a reference to a CU's line table, may be invalidated if the line_tables map is modified
|
||||
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
|
||||
optional<std::reference_wrapper<line_table_info>> get_line_table(const die_object& cu_die) {
|
||||
optional<line_table_info&> get_line_table(const die_object& cu_die) {
|
||||
auto off = cu_die.get_global_offset();
|
||||
auto it = line_tables.find(off);
|
||||
if(it != line_tables.end()) {
|
||||
return it->second;
|
||||
auto res = line_tables.maybe_get(off);
|
||||
if(res) {
|
||||
return res;
|
||||
} else {
|
||||
Dwarf_Unsigned version;
|
||||
Dwarf_Small table_count;
|
||||
@ -673,24 +642,6 @@ namespace libdwarf {
|
||||
}
|
||||
}
|
||||
line = line_buffer[j - 1];
|
||||
// {
|
||||
// Dwarf_Unsigned line_number = 0;
|
||||
// VERIFY(wrap(dwarf_lineno, line, &line_number) == DW_DLV_OK);
|
||||
// frame.line = static_cast<std::uint32_t>(line_number);
|
||||
// char* filename = nullptr;
|
||||
// VERIFY(wrap(dwarf_linesrc, line, &filename) == DW_DLV_OK);
|
||||
// auto wrapper = raii_wrap(
|
||||
// filename,
|
||||
// [this] (char* str) { if(str) dwarf_dealloc(dbg, str, DW_DLA_STRING); }
|
||||
// );
|
||||
// frame.filename = filename;
|
||||
// printf("%s : %d\n", filename, line_number);
|
||||
// Dwarf_Bool is_line_end;
|
||||
// VERIFY(wrap(dwarf_lineendsequence, line, &is_line_end) == DW_DLV_OK);
|
||||
// if(is_line_end) {
|
||||
// puts("Line end");
|
||||
// }
|
||||
// }
|
||||
line_entries.push_back({
|
||||
low_addr,
|
||||
line
|
||||
@ -703,8 +654,7 @@ namespace libdwarf {
|
||||
});
|
||||
}
|
||||
|
||||
it = line_tables.insert({off, {version, line_context, std::move(line_entries)}}).first;
|
||||
return it->second;
|
||||
return line_tables.insert(off, line_table_info{version, line_context, std::move(line_entries)});
|
||||
}
|
||||
}
|
||||
|
||||
@ -722,7 +672,7 @@ namespace libdwarf {
|
||||
if(!table_info_opt) {
|
||||
return; // failing silently for now
|
||||
}
|
||||
auto& table_info = table_info_opt.unwrap().get();
|
||||
auto& table_info = table_info_opt.unwrap();
|
||||
if(get_cache_mode() == cache_mode::prioritize_speed) {
|
||||
// Lookup in the table
|
||||
auto& line_entries = table_info.line_entries;
|
||||
@ -880,8 +830,14 @@ namespace libdwarf {
|
||||
}
|
||||
// NOTE: If we have a corresponding skeleton, we assume we have one CU matching the skeleton CU
|
||||
if(
|
||||
(skeleton && skeleton.unwrap().cu_die.pc_in_die(skeleton.unwrap().dwversion, pc))
|
||||
|| cu_die.pc_in_die(dwversion, pc)
|
||||
(
|
||||
skeleton
|
||||
&& skeleton.unwrap().cu_die.pc_in_die(
|
||||
skeleton.unwrap().cu_die,
|
||||
skeleton.unwrap().dwversion,
|
||||
pc
|
||||
)
|
||||
) || cu_die.pc_in_die(cu_die, dwversion, pc)
|
||||
) {
|
||||
if(trace_dwarf) {
|
||||
std::fprintf(
|
||||
@ -901,29 +857,27 @@ namespace libdwarf {
|
||||
} else {
|
||||
lazy_generate_cu_cache();
|
||||
// look up the cu
|
||||
auto vec_it = first_less_than_or_equal(
|
||||
cu_cache.begin(),
|
||||
cu_cache.end(),
|
||||
pc,
|
||||
[] (Dwarf_Addr pc, const cu_entry& entry) {
|
||||
return pc < entry.low;
|
||||
}
|
||||
);
|
||||
// TODO: Vec-it is already range-based, this range check is redundant
|
||||
// If the vector has been empty this can happen
|
||||
if(vec_it != cu_cache.end()) {
|
||||
auto res = cu_cache.lookup(pc);
|
||||
// res can be nullopt if the cu_cache vector is empty
|
||||
// It can also happen for something like _start, where there is a cached CU for the object but
|
||||
// _start is outside of the CU's PC range
|
||||
if(res) {
|
||||
const auto& die = res.unwrap().die;
|
||||
const auto dwversion = res.unwrap().data;
|
||||
// TODO: Cache the range list?
|
||||
// NOTE: If we have a corresponding skeleton, we assume we have one CU matching the skeleton CU
|
||||
if(
|
||||
(skeleton && skeleton.unwrap().cu_die.pc_in_die(skeleton.unwrap().dwversion, pc))
|
||||
|| vec_it->die.pc_in_die(vec_it->dwversion, pc)
|
||||
(
|
||||
skeleton
|
||||
&& skeleton.unwrap().cu_die.pc_in_die(
|
||||
skeleton.unwrap().cu_die,
|
||||
skeleton.unwrap().dwversion,
|
||||
pc
|
||||
)
|
||||
) || die.pc_in_die(die, dwversion, pc)
|
||||
) {
|
||||
return cu_info{maybe_owned_die_object::ref(vec_it->die), vec_it->dwversion};
|
||||
return cu_info{maybe_owned_die_object::ref(die), dwversion};
|
||||
}
|
||||
} else {
|
||||
// I've had this happen for _start, where there is a cached CU for the object but _start is outside
|
||||
// of the CU's PC range
|
||||
// ASSERT(cu_cache.size() == 0, "Vec should be empty?");
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
@ -978,12 +932,7 @@ namespace libdwarf {
|
||||
if(it == split_full_cu_resolvers.end()) {
|
||||
it = split_full_cu_resolvers.emplace(
|
||||
off,
|
||||
std::unique_ptr<dwarf_resolver>(
|
||||
new dwarf_resolver(
|
||||
path,
|
||||
skeleton_info{cu_die.clone(), dwversion, *this}
|
||||
)
|
||||
)
|
||||
detail::make_unique<dwarf_resolver>(path, skeleton_info{cu_die.clone(), dwversion, *this})
|
||||
).first;
|
||||
}
|
||||
res = it->second->resolve_frame(object_frame_info);
|
||||
@ -1008,8 +957,11 @@ namespace libdwarf {
|
||||
if(cu) {
|
||||
const auto& cu_die = cu.unwrap().cu_die.get();
|
||||
// gnu non-standard debug-fission may create non-skeleton CU DIEs and just add dwo attributes
|
||||
// clang emits dwo names in the split CUs, so guard against going down the dwarf fission path (which
|
||||
// doesn't infinitely recurse because it's not emitted as an absolute path and there's no comp dir but
|
||||
// it's good to guard against the infinite recursion anyway)
|
||||
auto dwo_name = get_dwo_name(cu_die);
|
||||
if(cu_die.get_tag() == DW_TAG_skeleton_unit || dwo_name) {
|
||||
if(cu_die.get_tag() == DW_TAG_skeleton_unit || (dwo_name && !skeleton)) {
|
||||
perform_dwarf_fission_resolution(cu_die, dwo_name, object_frame_info, frame, inlines);
|
||||
} else {
|
||||
retrieve_line_info(cu_die, pc, frame);
|
||||
@ -1058,7 +1010,7 @@ namespace libdwarf {
|
||||
};
|
||||
|
||||
std::unique_ptr<symbol_resolver> make_dwarf_resolver(const std::string& object_path) {
|
||||
return std::unique_ptr<dwarf_resolver>(new dwarf_resolver(object_path));
|
||||
return detail::make_unique<dwarf_resolver>(object_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
208
src/symbols/dwarf/dwarf_utils.hpp
Normal file
208
src/symbols/dwarf/dwarf_utils.hpp
Normal 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
|
||||
@ -1,9 +1,9 @@
|
||||
#ifndef SYMBOL_RESOLVER_HPP
|
||||
#define SYMBOL_RESOLVER_HPP
|
||||
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
#include "../symbols.hpp"
|
||||
#include "../../utils/common.hpp"
|
||||
#include <cpptrace/basic.hpp>
|
||||
#include "symbols/symbols.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
#ifndef SYMBOLS_HPP
|
||||
#define SYMBOLS_HPP
|
||||
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
#include <cpptrace/basic.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "../binary/object.hpp"
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
#include "symbols.hpp"
|
||||
#include <cpptrace/basic.hpp>
|
||||
|
||||
#include "symbols/symbols.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "../utils/common.hpp"
|
||||
#include "../binary/object.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "binary/object.hpp"
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
@ -81,6 +83,8 @@ namespace detail {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Symbol resolution code should probably handle when object addresses are 0
|
||||
|
||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<object_frame>& frames) {
|
||||
#if defined(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) && defined(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP)
|
||||
std::vector<stacktrace_frame> trace = libdwarf::resolve_frames(frames);
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE
|
||||
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
#include "symbols.hpp"
|
||||
#include "../utils/common.hpp"
|
||||
#include "../utils/utils.hpp"
|
||||
#include <cpptrace/basic.hpp>
|
||||
#include "symbols/symbols.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/microfmt.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
@ -20,7 +21,8 @@
|
||||
#include <sys/wait.h>
|
||||
#endif
|
||||
|
||||
#include "../binary/object.hpp"
|
||||
#include "binary/object.hpp"
|
||||
#include "options.hpp"
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
|
||||
@ -1,16 +1,21 @@
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_DBGHELP
|
||||
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
#include "symbols.hpp"
|
||||
#include "../utils/dbghelp_syminit_manager.hpp"
|
||||
#include <cpptrace/basic.hpp>
|
||||
#include "symbols/symbols.hpp"
|
||||
#include "platform/dbghelp_utils.hpp"
|
||||
#include "binary/object.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "options.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <regex>
|
||||
#include <stdexcept>
|
||||
#include <system_error>
|
||||
#include <vector>
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <dbghelp.h>
|
||||
|
||||
@ -235,6 +240,9 @@ namespace dbghelp {
|
||||
std::size_t sz = sizeof(TI_FINDCHILDREN_PARAMS) +
|
||||
(n_children) * sizeof(TI_FINDCHILDREN_PARAMS::ChildId[0]);
|
||||
TI_FINDCHILDREN_PARAMS* children = (TI_FINDCHILDREN_PARAMS*) new char[sz];
|
||||
auto guard = scope_exit([&] {
|
||||
delete[] (char*) children;
|
||||
});
|
||||
children->Start = 0;
|
||||
children->Count = n_children;
|
||||
if(
|
||||
@ -260,7 +268,6 @@ namespace dbghelp {
|
||||
extent += (i == 0 ? "" : ", ") + resolve_type(children->ChildId[i], proc, modbase);
|
||||
}
|
||||
extent += ")";
|
||||
delete[] (char*) children;
|
||||
return {return_type.base, extent + return_type.extent};
|
||||
}
|
||||
}
|
||||
@ -320,8 +327,6 @@ namespace dbghelp {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::recursive_mutex dbghelp_lock;
|
||||
|
||||
// TODO: Handle backtrace_pcinfo calling the callback multiple times on inlined functions
|
||||
stacktrace_frame resolve_frame(HANDLE proc, frame_ptr addr) {
|
||||
// The get_frame_object_info() ends up being inexpensive, at on my machine
|
||||
@ -331,14 +336,15 @@ namespace dbghelp {
|
||||
// get_frame_object_info() 0.001-0.002 ms 0.0003-0.0006 ms
|
||||
// At some point it might make sense to make an option to control this.
|
||||
auto object_frame = get_frame_object_info(addr);
|
||||
const std::lock_guard<std::recursive_mutex> lock(dbghelp_lock); // all dbghelp functions are not thread safe
|
||||
// Dbghelp is is single-threaded, so acquire a lock.
|
||||
auto lock = get_dbghelp_lock();
|
||||
alignas(SYMBOL_INFO) char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
|
||||
SYMBOL_INFO* symbol = (SYMBOL_INFO*)buffer;
|
||||
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
|
||||
symbol->MaxNameLen = MAX_SYM_NAME;
|
||||
union { DWORD64 a; DWORD b; } displacement;
|
||||
IMAGEHLP_LINE64 line;
|
||||
bool got_line = SymGetLineFromAddr64(proc, addr, &displacement.b, &line);
|
||||
IMAGEHLP_LINE line;
|
||||
bool got_line = SymGetLineFromAddr(proc, addr, &displacement.b, &line);
|
||||
if(SymFromAddr(proc, addr, &displacement.a, symbol)) {
|
||||
if(got_line) {
|
||||
IMAGEHLP_STACK_FRAME frame;
|
||||
@ -416,23 +422,18 @@ namespace dbghelp {
|
||||
}
|
||||
|
||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<frame_ptr>& frames) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(dbghelp_lock); // all dbghelp functions are not thread safe
|
||||
// Dbghelp is is single-threaded, so acquire a lock.
|
||||
auto lock = get_dbghelp_lock();
|
||||
std::vector<stacktrace_frame> trace;
|
||||
trace.reserve(frames.size());
|
||||
|
||||
// TODO: When does this need to be called? Can it be moved to the symbolizer?
|
||||
SymSetOptions(SYMOPT_ALLOW_ABSOLUTE_SYMBOLS);
|
||||
HANDLE proc = GetCurrentProcess();
|
||||
if(get_cache_mode() == cache_mode::prioritize_speed) {
|
||||
get_syminit_manager().init(proc);
|
||||
} else {
|
||||
if(!SymInitialize(proc, NULL, TRUE)) {
|
||||
throw internal_error("SymInitialize failed");
|
||||
}
|
||||
}
|
||||
|
||||
auto syminit_info = ensure_syminit();
|
||||
for(const auto frame : frames) {
|
||||
try {
|
||||
trace.push_back(resolve_frame(proc, frame));
|
||||
trace.push_back(resolve_frame(syminit_info.get_process_handle() , frame));
|
||||
} catch(...) { // NOSONAR
|
||||
if(!detail::should_absorb_trace_exceptions()) {
|
||||
throw;
|
||||
@ -442,11 +443,6 @@ namespace dbghelp {
|
||||
trace.push_back(entry);
|
||||
}
|
||||
}
|
||||
if(get_cache_mode() != cache_mode::prioritize_speed) {
|
||||
if(!SymCleanup(proc)) {
|
||||
throw internal_error("SymCleanup failed");
|
||||
}
|
||||
}
|
||||
return trace;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDL
|
||||
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
#include "symbols.hpp"
|
||||
#include <cpptrace/basic.hpp>
|
||||
#include "symbols/symbols.hpp"
|
||||
#include "binary/module_base.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE
|
||||
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
#include "symbols.hpp"
|
||||
#include "../utils/program_name.hpp"
|
||||
#include <cpptrace/basic.hpp>
|
||||
#include "symbols/symbols.hpp"
|
||||
#include "platform/program_name.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "options.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF
|
||||
|
||||
#include "symbols.hpp"
|
||||
#include "symbols/symbols.hpp"
|
||||
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
#include <cpptrace/basic.hpp>
|
||||
#include "dwarf/resolver.hpp"
|
||||
#include "../utils/common.hpp"
|
||||
#include "../utils/error.hpp"
|
||||
#include "../utils/utils.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "binary/elf.hpp"
|
||||
#include "binary/mach-o.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
@ -15,8 +16,6 @@
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
@ -95,6 +94,12 @@ namespace libdwarf {
|
||||
for(const auto& group : collate_frames(frames, trace)) {
|
||||
try {
|
||||
const auto& object_name = group.first;
|
||||
// TODO PERF: Potentially a duplicate open and parse with module base stuff (and debug map resolver)
|
||||
#if IS_LINUX
|
||||
auto object = open_elf_cached(object_name);
|
||||
#elif IS_APPLE
|
||||
auto object = open_mach_o_cached(object_name);
|
||||
#endif
|
||||
auto resolver = get_resolver(object_name);
|
||||
for(const auto& entry : group.second) {
|
||||
const auto& dlframe = entry.first.get();
|
||||
@ -109,6 +114,13 @@ namespace libdwarf {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
#if IS_LINUX || IS_APPLE
|
||||
if(frame.frame.symbol.empty() && object.has_value()) {
|
||||
frame.frame.symbol = object
|
||||
.unwrap_value()
|
||||
->lookup_symbol(dlframe.object_address).value_or("");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
} catch(...) { // NOSONAR
|
||||
if(!should_absorb_trace_exceptions()) {
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
#ifdef CPPTRACE_GET_SYMBOLS_WITH_NOTHING
|
||||
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
#include "symbols.hpp"
|
||||
#include <cpptrace/basic.hpp>
|
||||
#include "symbols/symbols.hpp"
|
||||
#include "utils/common.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
#ifndef UNWIND_HPP
|
||||
#define UNWIND_HPP
|
||||
|
||||
#include "../utils/common.hpp"
|
||||
#include "../utils/utils.hpp"
|
||||
#include <cpptrace/basic.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
@ -12,7 +11,7 @@ namespace detail {
|
||||
#ifdef CPPTRACE_HARD_MAX_FRAMES
|
||||
constexpr std::size_t hard_max_frames = CPPTRACE_HARD_MAX_FRAMES;
|
||||
#else
|
||||
constexpr std::size_t hard_max_frames = 200;
|
||||
constexpr std::size_t hard_max_frames = 400;
|
||||
#endif
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
|
||||
@ -1,15 +1,13 @@
|
||||
#ifdef CPPTRACE_UNWIND_WITH_DBGHELP
|
||||
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
#include "unwind.hpp"
|
||||
#include "../utils/common.hpp"
|
||||
#include "../utils/utils.hpp"
|
||||
#include "../utils/dbghelp_syminit_manager.hpp"
|
||||
#include <cpptrace/basic.hpp>
|
||||
#include "unwind/unwind.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "platform/dbghelp_utils.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <cstddef>
|
||||
|
||||
#include <windows.h>
|
||||
#include <dbghelp.h>
|
||||
@ -97,27 +95,19 @@ namespace detail {
|
||||
std::vector<frame_ptr> trace;
|
||||
|
||||
// Dbghelp is is single-threaded, so acquire a lock.
|
||||
static std::mutex mutex;
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
auto lock = get_dbghelp_lock();
|
||||
// For some reason SymInitialize must be called before StackWalk64
|
||||
// Note that the code assumes that
|
||||
// SymInitialize( GetCurrentProcess(), NULL, TRUE ) has
|
||||
// already been called.
|
||||
//
|
||||
HANDLE proc = GetCurrentProcess();
|
||||
auto syminit_info = ensure_syminit();
|
||||
HANDLE thread = GetCurrentThread();
|
||||
if(get_cache_mode() == cache_mode::prioritize_speed) {
|
||||
get_syminit_manager().init(proc);
|
||||
} else {
|
||||
if(!SymInitialize(proc, NULL, TRUE)) {
|
||||
throw internal_error("SymInitialize failed");
|
||||
}
|
||||
}
|
||||
while(trace.size() < max_depth) {
|
||||
if(
|
||||
!StackWalk64(
|
||||
machine_type,
|
||||
proc,
|
||||
syminit_info.get_process_handle(),
|
||||
thread,
|
||||
&frame,
|
||||
machine_type == IMAGE_FILE_MACHINE_I386 ? NULL : &context,
|
||||
@ -145,11 +135,6 @@ namespace detail {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(get_cache_mode() != cache_mode::prioritize_speed) {
|
||||
if(!SymCleanup(proc)) {
|
||||
throw internal_error("SymCleanup failed");
|
||||
}
|
||||
}
|
||||
return trace;
|
||||
}
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
#ifdef CPPTRACE_UNWIND_WITH_EXECINFO
|
||||
|
||||
#include "unwind.hpp"
|
||||
#include "../utils/common.hpp"
|
||||
#include "../utils/utils.hpp"
|
||||
#include "unwind/unwind.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
#ifdef CPPTRACE_UNWIND_WITH_LIBUNWIND
|
||||
|
||||
#include "unwind.hpp"
|
||||
#include "../utils/common.hpp"
|
||||
#include "../utils/error.hpp"
|
||||
#include "../utils/utils.hpp"
|
||||
#include "unwind/unwind.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
#ifdef CPPTRACE_UNWIND_WITH_NOTHING
|
||||
|
||||
#include "unwind.hpp"
|
||||
#include "unwind/unwind.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
#ifdef CPPTRACE_UNWIND_WITH_UNWIND
|
||||
|
||||
#include "unwind.hpp"
|
||||
#include "../utils/common.hpp"
|
||||
#include "../utils/error.hpp"
|
||||
#include "../utils/utils.hpp"
|
||||
#include "unwind/unwind.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
|
||||
@ -1,14 +1,17 @@
|
||||
#ifdef CPPTRACE_UNWIND_WITH_WINAPI
|
||||
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
#include "unwind.hpp"
|
||||
#include "../utils/common.hpp"
|
||||
#include "../utils/utils.hpp"
|
||||
#include <cpptrace/basic.hpp>
|
||||
#include "unwind/unwind.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
|
||||
// Fucking windows headers
|
||||
|
||||
84
src/utils.cpp
Normal file
84
src/utils.cpp
Normal file
@ -0,0 +1,84 @@
|
||||
#include <cpptrace/utils.hpp>
|
||||
#include <cpptrace/exceptions.hpp>
|
||||
#include <cpptrace/formatting.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "demangle/demangle.hpp"
|
||||
#include "snippets/snippet.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "platform/exception_type.hpp"
|
||||
#include "options.hpp"
|
||||
|
||||
namespace cpptrace {
|
||||
std::string demangle(const std::string& name) {
|
||||
return detail::demangle(name, false);
|
||||
}
|
||||
|
||||
std::string get_snippet(const std::string& path, std::size_t line, std::size_t context_size, bool color) {
|
||||
return detail::get_snippet(path, line, context_size, color);
|
||||
}
|
||||
|
||||
bool isatty(int fd) {
|
||||
return detail::isatty(fd);
|
||||
}
|
||||
|
||||
extern const int stdin_fileno = detail::fileno(stdin);
|
||||
extern const int stdout_fileno = detail::fileno(stdout);
|
||||
extern const int stderr_fileno = detail::fileno(stderr);
|
||||
|
||||
namespace detail {
|
||||
const formatter& get_terminate_formatter() {
|
||||
static formatter the_formatter = formatter{}
|
||||
.header("Stack trace to reach terminate handler (most recent call first):");
|
||||
return the_formatter;
|
||||
}
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE void print_terminate_trace() {
|
||||
try { // try/catch can never be hit but it's needed to prevent TCO
|
||||
detail::get_terminate_formatter().print(std::cerr, generate_trace(1));
|
||||
} catch(...) {
|
||||
if(!detail::should_absorb_trace_exceptions()) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[noreturn]] void MSVC_CDECL terminate_handler() {
|
||||
// TODO: Support std::nested_exception?
|
||||
try {
|
||||
auto ptr = std::current_exception();
|
||||
if(ptr == nullptr) {
|
||||
fputs("terminate called without an active exception", stderr);
|
||||
print_terminate_trace();
|
||||
} else {
|
||||
std::rethrow_exception(ptr);
|
||||
}
|
||||
} catch(cpptrace::exception& e) {
|
||||
microfmt::print(
|
||||
stderr,
|
||||
"Terminate called after throwing an instance of {}: {}\n",
|
||||
demangle(typeid(e).name()),
|
||||
e.message()
|
||||
);
|
||||
e.trace().print(std::cerr, isatty(stderr_fileno));
|
||||
} catch(std::exception& e) {
|
||||
microfmt::print(
|
||||
stderr, "Terminate called after throwing an instance of {}: {}\n", demangle(typeid(e).name()), e.what()
|
||||
);
|
||||
print_terminate_trace();
|
||||
} catch(...) {
|
||||
microfmt::print(
|
||||
stderr, "Terminate called after throwing an instance of {}\n", detail::exception_type_name()
|
||||
);
|
||||
print_terminate_trace();
|
||||
}
|
||||
std::flush(std::cerr);
|
||||
abort();
|
||||
}
|
||||
|
||||
void register_terminate_handler() {
|
||||
std::set_terminate(terminate_handler);
|
||||
}
|
||||
}
|
||||
@ -1,45 +1,11 @@
|
||||
#ifndef COMMON_HPP
|
||||
#define COMMON_HPP
|
||||
|
||||
#define IS_WINDOWS 0
|
||||
#define IS_LINUX 0
|
||||
#define IS_APPLE 0
|
||||
#include <cpptrace/basic.hpp>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#undef IS_WINDOWS
|
||||
#define IS_WINDOWS 1
|
||||
#elif defined(__linux)
|
||||
#undef IS_LINUX
|
||||
#define IS_LINUX 1
|
||||
#elif defined(__APPLE__)
|
||||
#undef IS_APPLE
|
||||
#define IS_APPLE 1
|
||||
#else
|
||||
#error "Unexpected platform"
|
||||
#endif
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#define IS_CLANG 0
|
||||
#define IS_GCC 0
|
||||
#define IS_MSVC 0
|
||||
|
||||
#if defined(__clang__)
|
||||
#undef IS_CLANG
|
||||
#define IS_CLANG 1
|
||||
#elif defined(__GNUC__) || defined(__GNUG__)
|
||||
#undef IS_GCC
|
||||
#define IS_GCC 1
|
||||
#elif defined(_MSC_VER)
|
||||
#undef IS_MSVC
|
||||
#define IS_MSVC 1
|
||||
#else
|
||||
#error "Unsupported compiler"
|
||||
#endif
|
||||
|
||||
#include <cstdio>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
#include <cstdint>
|
||||
|
||||
#define ESC "\033["
|
||||
#define RESET ESC "0m"
|
||||
@ -58,21 +24,30 @@
|
||||
#define NODISCARD
|
||||
#endif
|
||||
|
||||
#if IS_MSVC
|
||||
#define MSVC_CDECL __cdecl
|
||||
#else
|
||||
#define MSVC_CDECL
|
||||
#endif
|
||||
|
||||
// support is pretty good https://godbolt.org/z/djTqv7WMY, checked in cmake during config
|
||||
#ifdef HAS_ATTRIBUTE_PACKED
|
||||
#define PACKED __attribute__((packed))
|
||||
#else
|
||||
#define PACKED
|
||||
#endif
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
static const stacktrace_frame null_frame {
|
||||
0,
|
||||
0,
|
||||
nullable<uint32_t>::null(),
|
||||
nullable<uint32_t>::null(),
|
||||
nullable<std::uint32_t>::null(),
|
||||
nullable<std::uint32_t>::null(),
|
||||
"",
|
||||
"",
|
||||
false
|
||||
};
|
||||
|
||||
bool should_absorb_trace_exceptions();
|
||||
bool should_resolve_inlined_calls();
|
||||
enum cache_mode get_cache_mode();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user