Compare commits
1 Commits
main
...
jr/jit-dwa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73690b7a4e |
145
.github/workflows/build.yml
vendored
Normal file
145
.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
name: build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-linux:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
compiler: [gcc, clang]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt install gcc-10 g++-10 libgcc-10-dev libunwind8-dev ninja-build
|
||||||
|
pip3 install colorama
|
||||||
|
- name: libdwarf
|
||||||
|
run: |
|
||||||
|
cd ..
|
||||||
|
cpptrace/ci/setup-prerequisites.sh
|
||||||
|
- name: build
|
||||||
|
run: |
|
||||||
|
python3 ci/build-in-all-configs.py --${{matrix.compiler}} --default-config
|
||||||
|
build-linux-bazel:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt install -y libtool libncurses5
|
||||||
|
- name: bazel build opt
|
||||||
|
run: |
|
||||||
|
bazel build //... -c opt
|
||||||
|
- name: bazel build dbg
|
||||||
|
run: |
|
||||||
|
bazel build //... -c dbg
|
||||||
|
build-macos:
|
||||||
|
runs-on: macos-14
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
compiler: [gcc, clang]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: dependencies
|
||||||
|
run: |
|
||||||
|
brew install ninja
|
||||||
|
python3 -m venv env
|
||||||
|
env/bin/pip install colorama
|
||||||
|
- name: libdwarf
|
||||||
|
run: |
|
||||||
|
cd ..
|
||||||
|
cpptrace/ci/setup-prerequisites.sh
|
||||||
|
- name: build
|
||||||
|
run: |
|
||||||
|
env/bin/python ci/build-in-all-configs.py --${{matrix.compiler}} --default-config
|
||||||
|
build-windows:
|
||||||
|
runs-on: windows-2022
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
compiler: [msvc, clang, gcc]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Enable Developer Command Prompt
|
||||||
|
uses: ilammy/msvc-dev-cmd@v1.13.0
|
||||||
|
- name: dependencies
|
||||||
|
run: |
|
||||||
|
pip3 install colorama
|
||||||
|
- name: libdwarf
|
||||||
|
run: |
|
||||||
|
if("${{matrix.compiler}}" -eq "gcc") {
|
||||||
|
cd ..
|
||||||
|
cpptrace/ci/setup-prerequisites-mingw.ps1
|
||||||
|
}
|
||||||
|
- name: build
|
||||||
|
run: |
|
||||||
|
python3 ci/build-in-all-configs.py --${{matrix.compiler}} --default-config
|
||||||
|
build-linux-all-configurations:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
needs: build-linux
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
compiler: [gcc, clang]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt install gcc-10 g++-10 libgcc-10-dev libunwind8-dev ninja-build
|
||||||
|
pip3 install colorama
|
||||||
|
- name: libdwarf
|
||||||
|
run: |
|
||||||
|
cd ..
|
||||||
|
cpptrace/ci/setup-prerequisites.sh
|
||||||
|
- name: build
|
||||||
|
run: |
|
||||||
|
python3 ci/build-in-all-configs.py --${{matrix.compiler}}
|
||||||
|
build-macos-all-configurations:
|
||||||
|
runs-on: macos-14
|
||||||
|
needs: build-macos
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
compiler: [gcc, clang]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: dependencies
|
||||||
|
run: |
|
||||||
|
brew install ninja
|
||||||
|
python3 -m venv env
|
||||||
|
env/bin/pip install colorama
|
||||||
|
- name: libdwarf
|
||||||
|
run: |
|
||||||
|
cd ..
|
||||||
|
cpptrace/ci/setup-prerequisites.sh
|
||||||
|
- name: build
|
||||||
|
run: |
|
||||||
|
env/bin/python ci/build-in-all-configs.py --${{matrix.compiler}}
|
||||||
|
build-windows-all-configurations:
|
||||||
|
runs-on: windows-2022
|
||||||
|
needs: build-windows
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
compiler: [msvc, clang, gcc]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Enable Developer Command Prompt
|
||||||
|
uses: ilammy/msvc-dev-cmd@v1.13.0
|
||||||
|
- name: dependencies
|
||||||
|
run: |
|
||||||
|
pip3 install colorama
|
||||||
|
- name: libdwarf
|
||||||
|
run: |
|
||||||
|
if("${{matrix.compiler}}" -eq "gcc") {
|
||||||
|
cd ..
|
||||||
|
cpptrace/ci/setup-prerequisites-mingw.ps1
|
||||||
|
}
|
||||||
|
- name: build
|
||||||
|
run: |
|
||||||
|
python3 ci/build-in-all-configs.py --${{matrix.compiler}}
|
||||||
652
.github/workflows/ci.yml
vendored
652
.github/workflows/ci.yml
vendored
@ -1,652 +0,0 @@
|
|||||||
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
Normal file
255
.github/workflows/cmake-integration.yml
vendored
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
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
Normal file
68
.github/workflows/performance-tests.yml
vendored
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
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}}
|
||||||
205
.github/workflows/test.yml
vendored
Normal file
205
.github/workflows/test.yml
vendored
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
name: test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test-linux:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
compiler: [gcc, clang]
|
||||||
|
shared: [--shared, ""]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt install gcc-10 g++-10 libgcc-10-dev libunwind8-dev
|
||||||
|
pip3 install colorama
|
||||||
|
- name: libdwarf
|
||||||
|
run: |
|
||||||
|
cd ..
|
||||||
|
cpptrace/ci/setup-prerequisites.sh
|
||||||
|
- name: build and test
|
||||||
|
run: |
|
||||||
|
python3 ci/test-all-configs.py --${{matrix.compiler}} --default-config
|
||||||
|
test-macos:
|
||||||
|
runs-on: macos-14
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
compiler: [gcc, clang]
|
||||||
|
shared: [--shared, ""]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: libdwarf
|
||||||
|
run: |
|
||||||
|
cd ..
|
||||||
|
cpptrace/ci/setup-prerequisites.sh
|
||||||
|
- name: dependencies
|
||||||
|
run: |
|
||||||
|
python3 -m venv env
|
||||||
|
env/bin/pip install colorama
|
||||||
|
- name: build and test
|
||||||
|
run: |
|
||||||
|
env/bin/python ci/test-all-configs.py --${{matrix.compiler}} --default-config
|
||||||
|
test-windows:
|
||||||
|
runs-on: windows-2022
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
compiler: [msvc, clang, gcc]
|
||||||
|
shared: [--shared, ""]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Enable Developer Command Prompt
|
||||||
|
uses: ilammy/msvc-dev-cmd@v1.13.0
|
||||||
|
- name: dependencies
|
||||||
|
run: |
|
||||||
|
pip3 install colorama
|
||||||
|
- name: libdwarf
|
||||||
|
run: |
|
||||||
|
if("${{matrix.compiler}}" -eq "gcc") {
|
||||||
|
cd ..
|
||||||
|
cpptrace/ci/setup-prerequisites-mingw.ps1
|
||||||
|
}
|
||||||
|
- name: build and test
|
||||||
|
run: |
|
||||||
|
python3 ci/test-all-configs.py --${{matrix.compiler}} --default-config
|
||||||
|
test-linux-all-configurations:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
compiler: [gcc, clang]
|
||||||
|
shared: [--shared, ""]
|
||||||
|
needs: test-linux
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt install gcc-10 g++-10 libgcc-10-dev libunwind8-dev
|
||||||
|
pip3 install colorama
|
||||||
|
- name: libdwarf
|
||||||
|
run: |
|
||||||
|
cd ..
|
||||||
|
cpptrace/ci/setup-prerequisites.sh
|
||||||
|
- name: build and test
|
||||||
|
run: |
|
||||||
|
python3 ci/test-all-configs.py --${{matrix.compiler}}
|
||||||
|
test-macos-all-configurations:
|
||||||
|
runs-on: macos-14
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
compiler: [gcc, clang]
|
||||||
|
shared: [--shared, ""]
|
||||||
|
needs: test-macos
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: libdwarf
|
||||||
|
run: |
|
||||||
|
cd ..
|
||||||
|
cpptrace/ci/setup-prerequisites.sh
|
||||||
|
- name: dependencies
|
||||||
|
run: |
|
||||||
|
python3 -m venv env
|
||||||
|
env/bin/pip install colorama
|
||||||
|
- name: build and test
|
||||||
|
run: |
|
||||||
|
env/bin/python ci/test-all-configs.py --${{matrix.compiler}}
|
||||||
|
test-windows-all-configurations:
|
||||||
|
runs-on: windows-2022
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
compiler: [msvc, clang, gcc]
|
||||||
|
shared: [--shared, ""]
|
||||||
|
needs: test-windows
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Enable Developer Command Prompt
|
||||||
|
uses: ilammy/msvc-dev-cmd@v1.13.0
|
||||||
|
- name: dependencies
|
||||||
|
run: |
|
||||||
|
pip3 install colorama
|
||||||
|
- name: libdwarf
|
||||||
|
run: |
|
||||||
|
if("${{matrix.compiler}}" -eq "gcc") {
|
||||||
|
cd ..
|
||||||
|
cpptrace/ci/setup-prerequisites-mingw.ps1
|
||||||
|
}
|
||||||
|
- name: build and test
|
||||||
|
run: |
|
||||||
|
python3 ci/test-all-configs.py --${{matrix.compiler}}
|
||||||
|
|
||||||
|
|
||||||
|
unittest-linux:
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt install gcc-10 g++-10 libgcc-10-dev ninja-build libc++-dev
|
||||||
|
cd ..
|
||||||
|
cpptrace/ci/setup-prerequisites-unittest.sh
|
||||||
|
- name: build and test
|
||||||
|
run: |
|
||||||
|
python3 ci/unittest.py
|
||||||
|
unittest-linux-bazel:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt install -y libtool libncurses5
|
||||||
|
- name: test dbg
|
||||||
|
run: |
|
||||||
|
bazel test //... -c dbg
|
||||||
|
- name: test opt
|
||||||
|
run: |
|
||||||
|
bazel test //... -c opt
|
||||||
|
unittest-macos:
|
||||||
|
runs-on: macos-14
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: maxim-lobanov/setup-xcode@v1
|
||||||
|
with:
|
||||||
|
xcode-version: "15.4"
|
||||||
|
- name: dependencies
|
||||||
|
run: |
|
||||||
|
brew install ninja
|
||||||
|
python3 -m venv env
|
||||||
|
env/bin/pip install colorama
|
||||||
|
cd ..
|
||||||
|
cpptrace/ci/setup-prerequisites-unittest-macos.sh
|
||||||
|
- name: build and test
|
||||||
|
run: |
|
||||||
|
env/bin/python ci/unittest.py
|
||||||
|
unittest-windows:
|
||||||
|
runs-on: windows-2022
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
compiler: [cl, clang++]
|
||||||
|
shared: [OFF] # TODO: Re-enable shared
|
||||||
|
build_type: [Debug, RelWithDebInfo]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Enable Developer Command Prompt
|
||||||
|
uses: ilammy/msvc-dev-cmd@v1.13.0
|
||||||
|
- name: build and test
|
||||||
|
run: |
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
cmake .. `
|
||||||
|
-DCMAKE_CXX_COMPILER=${{matrix.compiler}} `
|
||||||
|
-DCMAKE_C_COMPILER=${{matrix.compiler == 'clang++' && 'clang' || matrix.compiler}} `
|
||||||
|
-DBUILD_SHARED_LIBS=${{matrix.shared}} `
|
||||||
|
-DCPPTRACE_WERROR_BUILD=On `
|
||||||
|
-DCPPTRACE_BUILD_TESTING=On
|
||||||
|
cmake --build . --config ${{matrix.build_type}}
|
||||||
|
./${{matrix.build_type}}/unittest
|
||||||
|
# TODO: Macos, mingw
|
||||||
83
CHANGELOG.md
83
CHANGELOG.md
@ -1,11 +1,6 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
- [Changelog](#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.3](#v073)
|
||||||
- [v0.7.2](#v072)
|
- [v0.7.2](#v072)
|
||||||
- [v0.7.1](#v071)
|
- [v0.7.1](#v071)
|
||||||
@ -28,84 +23,6 @@
|
|||||||
- [v0.1.1](#v011)
|
- [v0.1.1](#v011)
|
||||||
- [v0.1](#v01)
|
- [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
|
# v0.7.3
|
||||||
|
|
||||||
Fixed:
|
Fixed:
|
||||||
|
|||||||
@ -9,7 +9,7 @@ set(package_name "cpptrace")
|
|||||||
|
|
||||||
project(
|
project(
|
||||||
cpptrace
|
cpptrace
|
||||||
VERSION 0.8.2
|
VERSION 0.7.3
|
||||||
DESCRIPTION "Simple, portable, and self-contained stacktrace library for C++11 and newer "
|
DESCRIPTION "Simple, portable, and self-contained stacktrace library for C++11 and newer "
|
||||||
HOMEPAGE_URL "https://github.com/jeremy-rifkin/cpptrace"
|
HOMEPAGE_URL "https://github.com/jeremy-rifkin/cpptrace"
|
||||||
LANGUAGES C CXX
|
LANGUAGES C CXX
|
||||||
@ -70,19 +70,6 @@ include(cmake/Autoconfig.cmake)
|
|||||||
|
|
||||||
# =================================================== Library Setup ====================================================
|
# =================================================== Library Setup ====================================================
|
||||||
|
|
||||||
if(NOT CPPTRACE_BUILD_NO_SYMBOLS)
|
|
||||||
set(
|
|
||||||
debug
|
|
||||||
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-g>
|
|
||||||
$<$<CXX_COMPILER_ID:MSVC>:/DEBUG>
|
|
||||||
)
|
|
||||||
else()
|
|
||||||
add_compile_options($<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-g0>)
|
|
||||||
set(
|
|
||||||
debug
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Target that we can modify (can't modify ALIAS targets)
|
# Target that we can modify (can't modify ALIAS targets)
|
||||||
# Target name should not be the same as ${PROJECT_NAME}, causes add_subdirectory issues
|
# Target name should not be the same as ${PROJECT_NAME}, causes add_subdirectory issues
|
||||||
set(target_name "cpptrace-lib")
|
set(target_name "cpptrace-lib")
|
||||||
@ -113,7 +100,6 @@ target_sources(
|
|||||||
src/ctrace.cpp
|
src/ctrace.cpp
|
||||||
src/exceptions.cpp
|
src/exceptions.cpp
|
||||||
src/from_current.cpp
|
src/from_current.cpp
|
||||||
src/formatting.cpp
|
|
||||||
src/options.cpp
|
src/options.cpp
|
||||||
src/utils.cpp
|
src/utils.cpp
|
||||||
src/demangle/demangle_with_cxxabi.cpp
|
src/demangle/demangle_with_cxxabi.cpp
|
||||||
@ -121,7 +107,6 @@ target_sources(
|
|||||||
src/demangle/demangle_with_winapi.cpp
|
src/demangle/demangle_with_winapi.cpp
|
||||||
src/snippets/snippet.cpp
|
src/snippets/snippet.cpp
|
||||||
src/symbols/dwarf/debug_map_resolver.cpp
|
src/symbols/dwarf/debug_map_resolver.cpp
|
||||||
src/symbols/dwarf/dwarf_options.cpp
|
|
||||||
src/symbols/dwarf/dwarf_resolver.cpp
|
src/symbols/dwarf/dwarf_resolver.cpp
|
||||||
src/symbols/symbols_core.cpp
|
src/symbols/symbols_core.cpp
|
||||||
src/symbols/symbols_with_addr2line.cpp
|
src/symbols/symbols_with_addr2line.cpp
|
||||||
@ -138,7 +123,7 @@ target_sources(
|
|||||||
src/unwind/unwind_with_winapi.cpp
|
src/unwind/unwind_with_winapi.cpp
|
||||||
src/utils/microfmt.cpp
|
src/utils/microfmt.cpp
|
||||||
src/utils/utils.cpp
|
src/utils/utils.cpp
|
||||||
src/platform/dbghelp_utils.cpp
|
src/platform/dbghelp_syminit_manager.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(
|
target_include_directories(
|
||||||
@ -176,11 +161,6 @@ target_compile_options(
|
|||||||
${warning_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 ----
|
# ---- Generate Build Info Headers ----
|
||||||
|
|
||||||
if(build_type STREQUAL "STATIC")
|
if(build_type STREQUAL "STATIC")
|
||||||
@ -223,10 +203,6 @@ target_compile_features(
|
|||||||
|
|
||||||
target_compile_definitions(${target_name} PRIVATE NOMINMAX)
|
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)
|
if(NOT CPPTRACE_STD_FORMAT)
|
||||||
target_compile_definitions(${target_name} PUBLIC CPPTRACE_NO_STD_FORMAT)
|
target_compile_definitions(${target_name} PUBLIC CPPTRACE_NO_STD_FORMAT)
|
||||||
endif()
|
endif()
|
||||||
@ -355,14 +331,11 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF)
|
|||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
if(CPPTRACE_CONAN)
|
if(CPPTRACE_CONAN)
|
||||||
set(dwarf_lib libdwarf::libdwarf)
|
|
||||||
target_link_libraries(${target_name} PRIVATE libdwarf::libdwarf)
|
target_link_libraries(${target_name} PRIVATE libdwarf::libdwarf)
|
||||||
elseif(CPPTRACE_VCPKG)
|
elseif(CPPTRACE_VCPKG)
|
||||||
set(dwarf_lib libdwarf::dwarf)
|
|
||||||
target_link_libraries(${target_name} PRIVATE libdwarf::dwarf)
|
target_link_libraries(${target_name} PRIVATE libdwarf::dwarf)
|
||||||
elseif(CPPTRACE_USE_EXTERNAL_LIBDWARF)
|
elseif(CPPTRACE_USE_EXTERNAL_LIBDWARF)
|
||||||
if(DEFINED LIBDWARF_LIBRARIES)
|
if(DEFINED LIBDWARF_LIBRARIES)
|
||||||
set(dwarf_lib ${LIBDWARF_LIBRARIES})
|
|
||||||
target_link_libraries(${target_name} PRIVATE ${LIBDWARF_LIBRARIES})
|
target_link_libraries(${target_name} PRIVATE ${LIBDWARF_LIBRARIES})
|
||||||
else()
|
else()
|
||||||
# if LIBDWARF_LIBRARIES wasn't set by find_package, try looking for libdwarf::dwarf-static,
|
# if LIBDWARF_LIBRARIES wasn't set by find_package, try looking for libdwarf::dwarf-static,
|
||||||
@ -380,7 +353,6 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF)
|
|||||||
else()
|
else()
|
||||||
message(FATAL_ERROR "Couldn't find libdwarf target name to link against")
|
message(FATAL_ERROR "Couldn't find libdwarf target name to link against")
|
||||||
endif()
|
endif()
|
||||||
set(dwarf_lib ${LIBDWARF_LIBRARIES})
|
|
||||||
target_link_libraries(${target_name} PRIVATE ${LIBDWARF_LIBRARIES})
|
target_link_libraries(${target_name} PRIVATE ${LIBDWARF_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
# There seems to be no consistency at all about where libdwarf decides to place its headers........ Figure out if
|
# There seems to be no consistency at all about where libdwarf decides to place its headers........ Figure out if
|
||||||
@ -403,7 +375,6 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF)
|
|||||||
message(FATAL_ERROR "Couldn't find libdwarf.h")
|
message(FATAL_ERROR "Couldn't find libdwarf.h")
|
||||||
endif()
|
endif()
|
||||||
else()
|
else()
|
||||||
set(dwarf_lib libdwarf::dwarf-static)
|
|
||||||
target_link_libraries(${target_name} PRIVATE libdwarf::dwarf-static)
|
target_link_libraries(${target_name} PRIVATE libdwarf::dwarf-static)
|
||||||
endif()
|
endif()
|
||||||
if(UNIX)
|
if(UNIX)
|
||||||
@ -519,7 +490,7 @@ if(NOT CMAKE_SKIP_INSTALL_RULES)
|
|||||||
include(cmake/InstallRules.cmake)
|
include(cmake/InstallRules.cmake)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# ================================================== Demo/test/tools ===================================================
|
# ===================================================== Demo/test ======================================================
|
||||||
|
|
||||||
if(CPPTRACE_BUILD_TESTING)
|
if(CPPTRACE_BUILD_TESTING)
|
||||||
if(PROJECT_IS_TOP_LEVEL)
|
if(PROJECT_IS_TOP_LEVEL)
|
||||||
@ -531,7 +502,3 @@ endif()
|
|||||||
if(CPPTRACE_BUILD_BENCHMARKING)
|
if(CPPTRACE_BUILD_BENCHMARKING)
|
||||||
add_subdirectory(benchmarking)
|
add_subdirectory(benchmarking)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(CPPTRACE_BUILD_TOOLS)
|
|
||||||
add_subdirectory(tools)
|
|
||||||
endif()
|
|
||||||
|
|||||||
8
Makefile
8
Makefile
@ -9,12 +9,12 @@ help: # with thanks to Ben Rady
|
|||||||
build: debug ## build in debug mode
|
build: debug ## build in debug mode
|
||||||
|
|
||||||
build/configured-debug:
|
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
|
cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On -DCPPTRACE_BUILD_SHARED=On -DCMAKE_PREFIX_PATH=~/thirdparty/llvm-project/build/foo
|
||||||
rm -f build/configured-release
|
rm -f build/configured-release
|
||||||
touch build/configured-debug
|
touch build/configured-debug
|
||||||
|
|
||||||
build/configured-release:
|
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
|
cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On
|
||||||
rm -f build/configured-debug
|
rm -f build/configured-debug
|
||||||
touch build/configured-release
|
touch build/configured-release
|
||||||
|
|
||||||
@ -34,12 +34,12 @@ release: configure-release ## build in release mode (with debug info)
|
|||||||
|
|
||||||
.PHONY: debug-msvc
|
.PHONY: debug-msvc
|
||||||
debug-msvc: ## build in debug mode
|
debug-msvc: ## build in debug mode
|
||||||
cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On -DCPPTRACE_BUILD_TOOLS=On
|
cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On
|
||||||
cmake --build build --config Debug
|
cmake --build build --config Debug
|
||||||
|
|
||||||
.PHONY: release-msvc
|
.PHONY: release-msvc
|
||||||
release-msvc: ## build in release mode (with debug info)
|
release-msvc: ## build in release mode (with debug info)
|
||||||
cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On -DCPPTRACE_BUILD_TOOLS=On
|
cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On
|
||||||
cmake --build build --config RelWithDebInfo
|
cmake --build build --config RelWithDebInfo
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
|
|||||||
224
README.md
224
README.md
@ -1,6 +1,7 @@
|
|||||||
# Cpptrace <!-- omit in toc -->
|
# Cpptrace <!-- omit in toc -->
|
||||||
|
|
||||||
[](https://github.com/jeremy-rifkin/cpptrace/actions/workflows/ci.yml)
|
[](https://github.com/jeremy-rifkin/cpptrace/actions/workflows/build.yml)
|
||||||
|
[](https://github.com/jeremy-rifkin/cpptrace/actions/workflows/test.yml)
|
||||||
[](https://sonarcloud.io/summary/new_code?id=jeremy-rifkin_cpptrace)
|
[](https://sonarcloud.io/summary/new_code?id=jeremy-rifkin_cpptrace)
|
||||||
<br/>
|
<br/>
|
||||||
[-Community%20Discord-blue?labelColor=2C3239&color=7289DA&style=flat&logo=discord&logoColor=959DA5)](https://discord.gg/frjaAZvqUZ)
|
[-Community%20Discord-blue?labelColor=2C3239&color=7289DA&style=flat&logo=discord&logoColor=959DA5)](https://discord.gg/frjaAZvqUZ)
|
||||||
@ -16,6 +17,9 @@ Cpptrace also has a C API, docs [here](docs/c-api.md).
|
|||||||
|
|
||||||
- [30-Second Overview](#30-second-overview)
|
- [30-Second Overview](#30-second-overview)
|
||||||
- [CMake FetchContent Usage](#cmake-fetchcontent-usage)
|
- [CMake FetchContent Usage](#cmake-fetchcontent-usage)
|
||||||
|
- [FAQ](#faq)
|
||||||
|
- [What about C++23 `<stacktrace>`?](#what-about-c23-stacktrace)
|
||||||
|
- [What does cpptrace have over other C++ stacktrace libraries?](#what-does-cpptrace-have-over-other-c-stacktrace-libraries)
|
||||||
- [Prerequisites](#prerequisites)
|
- [Prerequisites](#prerequisites)
|
||||||
- [Basic Usage](#basic-usage)
|
- [Basic Usage](#basic-usage)
|
||||||
- [`namespace cpptrace`](#namespace-cpptrace)
|
- [`namespace cpptrace`](#namespace-cpptrace)
|
||||||
@ -23,7 +27,6 @@ Cpptrace also has a C API, docs [here](docs/c-api.md).
|
|||||||
- [Object Traces](#object-traces)
|
- [Object Traces](#object-traces)
|
||||||
- [Raw Traces](#raw-traces)
|
- [Raw Traces](#raw-traces)
|
||||||
- [Utilities](#utilities)
|
- [Utilities](#utilities)
|
||||||
- [Formatting](#formatting)
|
|
||||||
- [Configuration](#configuration)
|
- [Configuration](#configuration)
|
||||||
- [Traces From All Exceptions](#traces-from-all-exceptions)
|
- [Traces From All Exceptions](#traces-from-all-exceptions)
|
||||||
- [Removing the `CPPTRACE_` prefix](#removing-the-cpptrace_-prefix)
|
- [Removing the `CPPTRACE_` prefix](#removing-the-cpptrace_-prefix)
|
||||||
@ -36,7 +39,6 @@ Cpptrace also has a C API, docs [here](docs/c-api.md).
|
|||||||
- [Signal-Safe Tracing](#signal-safe-tracing)
|
- [Signal-Safe Tracing](#signal-safe-tracing)
|
||||||
- [Utility Types](#utility-types)
|
- [Utility Types](#utility-types)
|
||||||
- [Headers](#headers)
|
- [Headers](#headers)
|
||||||
- [Libdwarf Tuning](#libdwarf-tuning)
|
|
||||||
- [Supported Debug Formats](#supported-debug-formats)
|
- [Supported Debug Formats](#supported-debug-formats)
|
||||||
- [How to Include The Library](#how-to-include-the-library)
|
- [How to Include The Library](#how-to-include-the-library)
|
||||||
- [CMake FetchContent](#cmake-fetchcontent)
|
- [CMake FetchContent](#cmake-fetchcontent)
|
||||||
@ -54,10 +56,6 @@ Cpptrace also has a C API, docs [here](docs/c-api.md).
|
|||||||
- [Summary of Library Configurations](#summary-of-library-configurations)
|
- [Summary of Library Configurations](#summary-of-library-configurations)
|
||||||
- [Testing Methodology](#testing-methodology)
|
- [Testing Methodology](#testing-methodology)
|
||||||
- [Notes About the Library](#notes-about-the-library)
|
- [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)
|
- [Contributing](#contributing)
|
||||||
- [License](#license)
|
- [License](#license)
|
||||||
|
|
||||||
@ -128,7 +126,6 @@ Additional notable features:
|
|||||||
- Utilities for catching `std::exception`s and wrapping them in traced exceptions
|
- Utilities for catching `std::exception`s and wrapping them in traced exceptions
|
||||||
- Signal-safe stack tracing
|
- Signal-safe stack tracing
|
||||||
- Source code snippets in traces
|
- Source code snippets in traces
|
||||||
- Extensive configuration options for [trace formatting](#formatting)
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@ -139,7 +136,7 @@ include(FetchContent)
|
|||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
cpptrace
|
cpptrace
|
||||||
GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git
|
GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git
|
||||||
GIT_TAG v0.8.2 # <HASH or TAG>
|
GIT_TAG v0.7.3 # <HASH or TAG>
|
||||||
)
|
)
|
||||||
FetchContent_MakeAvailable(cpptrace)
|
FetchContent_MakeAvailable(cpptrace)
|
||||||
target_link_libraries(your_target cpptrace::cpptrace)
|
target_link_libraries(your_target cpptrace::cpptrace)
|
||||||
@ -164,6 +161,33 @@ On macOS it is recommended to generate a `.dSYM` file, see [Platform Logistics](
|
|||||||
For other ways to use the library, such as through package managers, a system-wide installation, or on a platform
|
For other ways to use the library, such as through package managers, a system-wide installation, or on a platform
|
||||||
without internet access see [How to Include The Library](#how-to-include-the-library) below.
|
without internet access see [How to Include The Library](#how-to-include-the-library) below.
|
||||||
|
|
||||||
|
# FAQ
|
||||||
|
|
||||||
|
## What about C++23 `<stacktrace>`?
|
||||||
|
|
||||||
|
Some day C++23's `<stacktrace>` will be ubiquitous. And maybe one day the msvc implementation will be acceptable.
|
||||||
|
The original motivation for cpptrace was to support projects using older C++ standards and as the library has grown its
|
||||||
|
functionality has extended beyond the standard library's implementation.
|
||||||
|
|
||||||
|
Cpptrace provides functionality beyond what the standard library provides and what implementations provide, such as:
|
||||||
|
- Walking inlined function calls
|
||||||
|
- Providing a lightweight interface for "raw traces"
|
||||||
|
- Resolving function parameter types
|
||||||
|
- Providing traced exception objects
|
||||||
|
- Providing an API for signal-safe stacktrace generation
|
||||||
|
- Providing a way to retrieve stack traces from arbitrary exceptions, not just special cpptrace traced exception
|
||||||
|
objects. This is a feature coming to C++26, but cpptrace provides a solution for C++11.
|
||||||
|
|
||||||
|
## What does cpptrace have over other C++ stacktrace libraries?
|
||||||
|
|
||||||
|
Other C++ stacktrace libraries, such as boost stacktrace and backward-cpp, fall short when it comes to portability and
|
||||||
|
ease of use. In testing, I found neither to provide adaquate coverage of various environments. Even when they can be
|
||||||
|
made to work in an environment they require manual configuration from the end-user, possibly requiring manual
|
||||||
|
installation of third-party dependencies. This is a highly undesirable burden to impose on users, especially when it is
|
||||||
|
for a software package which just provides diagnostics as opposed to core functionality. Additionally, cpptrace provides
|
||||||
|
support for resolving inlined calls by default for DWARF symbols (boost does not do this, backward-cpp can do this but
|
||||||
|
only for some back-ends), better support for resolving full function signatures, and nicer API, among other features.
|
||||||
|
|
||||||
# Prerequisites
|
# Prerequisites
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
@ -319,87 +343,6 @@ namespace cpptrace {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Formatting
|
|
||||||
|
|
||||||
Cpptrace provides a configurable formatter for stack trace printing which supports some common options. Formatters are
|
|
||||||
configured with a sort of builder pattern, e.g.:
|
|
||||||
```cpp
|
|
||||||
auto formatter = cpptrace::formatter{}
|
|
||||||
.header("Stack trace:")
|
|
||||||
.addresses(cpptrace::formatter::address_mode::object)
|
|
||||||
.snippets(true);
|
|
||||||
```
|
|
||||||
|
|
||||||
This API is available through the `<cpptrace/formatting.hpp>` header.
|
|
||||||
|
|
||||||
Synopsis:
|
|
||||||
```cpp
|
|
||||||
namespace cpptrace {
|
|
||||||
class formatter {
|
|
||||||
formatter& header(std::string);
|
|
||||||
enum class color_mode { always, none, automatic };
|
|
||||||
formatter& colors(color_mode);
|
|
||||||
enum class address_mode { raw, object, none };
|
|
||||||
formatter& addresses(address_mode);
|
|
||||||
enum class path_mode { full, basename };
|
|
||||||
formatter& paths(path_mode);
|
|
||||||
formatter& snippets(bool);
|
|
||||||
formatter& snippet_context(int);
|
|
||||||
formatter& columns(bool);
|
|
||||||
formatter& filtered_frame_placeholders(bool);
|
|
||||||
formatter& filter(std::function<bool(const stacktrace_frame&)>);
|
|
||||||
|
|
||||||
std::string format(const stacktrace_frame&) const;
|
|
||||||
std::string format(const stacktrace_frame&, bool color) const;
|
|
||||||
|
|
||||||
std::string format(const stacktrace&) const;
|
|
||||||
std::string format(const stacktrace&, bool color) const;
|
|
||||||
|
|
||||||
void print(const stacktrace_frame&) const;
|
|
||||||
void print(const stacktrace_frame&, bool color) const;
|
|
||||||
void print(std::ostream&, const stacktrace_frame&) const;
|
|
||||||
void print(std::ostream&, const stacktrace_frame&, bool color) const;
|
|
||||||
void print(std::FILE*, const stacktrace_frame&) const;
|
|
||||||
void print(std::FILE*, const stacktrace_frame&, bool color) const;
|
|
||||||
|
|
||||||
void print(const stacktrace&) const;
|
|
||||||
void print(const stacktrace&, bool color) const;
|
|
||||||
void print(std::ostream&, const stacktrace&) const;
|
|
||||||
void print(std::ostream&, const stacktrace&, bool color) const;
|
|
||||||
void print(std::FILE*, const stacktrace&) const;
|
|
||||||
void print(std::FILE*, const stacktrace&, bool color) const;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Options:
|
|
||||||
| Setting | Description | Default |
|
|
||||||
| ----------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------ |
|
|
||||||
| `header` | Header line printed before the trace | `Stack trace (most recent call first):` |
|
|
||||||
| `colors` | Default color mode for the trace | `automatic`, which attempts to detect if the target stream is a terminal |
|
|
||||||
| `addresses` | Raw addresses, object addresses, or no addresses | `raw` |
|
|
||||||
| `paths` | Full paths or just filenames | `full` |
|
|
||||||
| `snippets` | Whether to include source code snippets | `false` |
|
|
||||||
| `snippet_context` | How many lines of source context to show in a snippet | `2` |
|
|
||||||
| `columns` | Whether to include column numbers if present | `true` |
|
|
||||||
| `filtered_frame_placeholders` | Whether to still print filtered frames as just `#n (filtered)` | `true` |
|
|
||||||
| `filter` | A predicate to filter frames with | None |
|
|
||||||
|
|
||||||
The `automatic` color mode attempts to detect if a stream that may be attached to a terminal. As such, it will not use
|
|
||||||
colors for the `formatter::format` method and it may not be able to detect if some ostreams correspond to terminals or
|
|
||||||
not. For this reason, `formatter::format` and `formatter::print` methods have overloads taking a color parameter. This
|
|
||||||
color parameter will override configured color mode.
|
|
||||||
|
|
||||||
Recommended practice with formatters: It's generally preferable to create formatters objects that are long-lived rather
|
|
||||||
than to create them on the fly every time a trace needs to be formatted.
|
|
||||||
|
|
||||||
Cpptrace provides access to a formatter with default settings with `get_default_formatter`:
|
|
||||||
```cpp
|
|
||||||
namespace cpptrace {
|
|
||||||
const formatter& get_default_formatter();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
`cpptrace::absorb_trace_exceptions`: Configure whether the library silently absorbs internal exceptions and continues.
|
`cpptrace::absorb_trace_exceptions`: Configure whether the library silently absorbs internal exceptions and continues.
|
||||||
@ -439,8 +382,6 @@ thrown exception object, with minimal or no overhead in the non-throwing path:
|
|||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
#include <cpptrace/from_current.hpp>
|
#include <cpptrace/from_current.hpp>
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
void foo() {
|
void foo() {
|
||||||
throw std::runtime_error("foo failed");
|
throw std::runtime_error("foo failed");
|
||||||
}
|
}
|
||||||
@ -750,7 +691,6 @@ namespace cpptrace {
|
|||||||
};
|
};
|
||||||
void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
|
void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
|
||||||
bool can_signal_safe_unwind();
|
bool can_signal_safe_unwind();
|
||||||
bool can_get_safe_object_frame();
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -767,9 +707,9 @@ see the comprehensive overview and demo at [signal-safe-tracing.md](docs/signal-
|
|||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> Currently signal-safe stack unwinding is only possible with `libunwind`, which must be
|
> 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
|
> [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 and
|
> produce an empty trace. `can_signal_safe_unwind` can be used to check for signal-safe unwinding support. If object
|
||||||
> `can_get_safe_object_frame` can be used to check `get_safe_object_frame` support. If object information can't be
|
> information can't be resolved in a signal-safe way then `get_safe_object_frame` will not populate fields beyond the
|
||||||
> resolved in a signal-safe way then `get_safe_object_frame` will not populate fields beyond the `raw_address`.
|
> `raw_address`.
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> `_dl_find_object` is required for signal-safe stack tracing. This is a relatively recent addition to glibc, added in
|
> `_dl_find_object` is required for signal-safe stack tracing. This is a relatively recent addition to glibc, added in
|
||||||
@ -795,7 +735,6 @@ namespace cpptrace {
|
|||||||
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
|
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
|
||||||
struct nullable {
|
struct nullable {
|
||||||
T raw_value;
|
T raw_value;
|
||||||
// all members are constexpr for c++17 and beyond, some are constexpr before c++17
|
|
||||||
nullable& operator=(T value)
|
nullable& operator=(T value)
|
||||||
bool has_value() const noexcept;
|
bool has_value() const noexcept;
|
||||||
T& value() noexcept;
|
T& value() noexcept;
|
||||||
@ -805,7 +744,6 @@ namespace cpptrace {
|
|||||||
void reset() noexcept;
|
void reset() noexcept;
|
||||||
bool operator==(const nullable& other) const noexcept;
|
bool operator==(const nullable& other) const 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
|
constexpr static nullable null() noexcept; // returns a null instance
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -848,38 +786,9 @@ Cpptrace provides a handful of headers to make inclusion more minimal.
|
|||||||
| `cpptrace/exceptions.hpp` | [Traced Exception Objects](#traced-exception-objects) and related utilities ([Wrapping std::exceptions](#wrapping-stdexceptions)) |
|
| `cpptrace/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/from_current.hpp` | [Traces From All Exceptions](#traces-from-all-exceptions) |
|
||||||
| `cpptrace/io.hpp` | `operator<<` overloads for `std::ostream` and `std::formatter`s |
|
| `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/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
|
The main cpptrace header is `cpptrace/cpptrace.hpp` which includes everything other than `from_current.hpp`.
|
||||||
`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
|
# Supported Debug Formats
|
||||||
|
|
||||||
@ -905,7 +814,7 @@ include(FetchContent)
|
|||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
cpptrace
|
cpptrace
|
||||||
GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git
|
GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git
|
||||||
GIT_TAG v0.8.2 # <HASH or TAG>
|
GIT_TAG v0.7.3 # <HASH or TAG>
|
||||||
)
|
)
|
||||||
FetchContent_MakeAvailable(cpptrace)
|
FetchContent_MakeAvailable(cpptrace)
|
||||||
target_link_libraries(your_target cpptrace::cpptrace)
|
target_link_libraries(your_target cpptrace::cpptrace)
|
||||||
@ -921,7 +830,7 @@ information.
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/jeremy-rifkin/cpptrace.git
|
git clone https://github.com/jeremy-rifkin/cpptrace.git
|
||||||
git checkout v0.8.2
|
git checkout v0.7.3
|
||||||
mkdir cpptrace/build
|
mkdir cpptrace/build
|
||||||
cd cpptrace/build
|
cd cpptrace/build
|
||||||
cmake .. -DCMAKE_BUILD_TYPE=Release
|
cmake .. -DCMAKE_BUILD_TYPE=Release
|
||||||
@ -964,7 +873,7 @@ you when installing new libraries.
|
|||||||
|
|
||||||
```ps1
|
```ps1
|
||||||
git clone https://github.com/jeremy-rifkin/cpptrace.git
|
git clone https://github.com/jeremy-rifkin/cpptrace.git
|
||||||
git checkout v0.8.2
|
git checkout v0.7.3
|
||||||
mkdir cpptrace/build
|
mkdir cpptrace/build
|
||||||
cd cpptrace/build
|
cd cpptrace/build
|
||||||
cmake .. -DCMAKE_BUILD_TYPE=Release
|
cmake .. -DCMAKE_BUILD_TYPE=Release
|
||||||
@ -982,7 +891,7 @@ To install just for the local user (or any custom prefix):
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/jeremy-rifkin/cpptrace.git
|
git clone https://github.com/jeremy-rifkin/cpptrace.git
|
||||||
git checkout v0.8.2
|
git checkout v0.7.3
|
||||||
mkdir cpptrace/build
|
mkdir cpptrace/build
|
||||||
cd cpptrace/build
|
cd cpptrace/build
|
||||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$HOME/wherever
|
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$HOME/wherever
|
||||||
@ -1055,7 +964,7 @@ make install
|
|||||||
cd ~/scratch/cpptrace-test
|
cd ~/scratch/cpptrace-test
|
||||||
git clone https://github.com/jeremy-rifkin/libdwarf-lite.git
|
git clone https://github.com/jeremy-rifkin/libdwarf-lite.git
|
||||||
cd libdwarf-lite
|
cd libdwarf-lite
|
||||||
git checkout fe09ca800b988e2ff21225ac5e7468ceade2a30e
|
git checkout 6dbcc23dba6ffd230063bda4b9d7298bf88d9d41
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
cmake .. -DPIC_ALWAYS=On -DBUILD_DWARFDUMP=Off -DCMAKE_PREFIX_PATH=~/scratch/cpptrace-test/resources -DCMAKE_INSTALL_PREFIX=~/scratch/cpptrace-test/resources
|
cmake .. -DPIC_ALWAYS=On -DBUILD_DWARFDUMP=Off -DCMAKE_PREFIX_PATH=~/scratch/cpptrace-test/resources -DCMAKE_INSTALL_PREFIX=~/scratch/cpptrace-test/resources
|
||||||
@ -1065,7 +974,7 @@ make install
|
|||||||
cd ~/scratch/cpptrace-test
|
cd ~/scratch/cpptrace-test
|
||||||
git clone https://github.com/jeremy-rifkin/cpptrace.git
|
git clone https://github.com/jeremy-rifkin/cpptrace.git
|
||||||
cd cpptrace
|
cd cpptrace
|
||||||
git checkout v0.8.2
|
git checkout v0.7.3
|
||||||
mkdir build
|
mkdir build
|
||||||
cd 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
|
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
|
||||||
@ -1085,7 +994,7 @@ cpptrace and its dependencies.
|
|||||||
Cpptrace is available through conan at https://conan.io/center/recipes/cpptrace.
|
Cpptrace is available through conan at https://conan.io/center/recipes/cpptrace.
|
||||||
```
|
```
|
||||||
[requires]
|
[requires]
|
||||||
cpptrace/0.8.2
|
cpptrace/0.7.3
|
||||||
[generators]
|
[generators]
|
||||||
CMakeDeps
|
CMakeDeps
|
||||||
CMakeToolchain
|
CMakeToolchain
|
||||||
@ -1294,51 +1203,6 @@ 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
|
in dbghelp the library cannot accurately show const and volatile qualifiers or rvalue references (these appear as
|
||||||
pointers).
|
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
|
# Contributing
|
||||||
|
|
||||||
I'm grateful for the help I've received with this library and I welcome contributions! For information on contributing
|
I'm grateful for the help I've received with this library and I welcome contributions! For information on contributing
|
||||||
|
|||||||
@ -7,6 +7,12 @@ set(
|
|||||||
${warning_options} $<$<CXX_COMPILER_ID:GNU>:-Wno-infinite-recursion>
|
${warning_options} $<$<CXX_COMPILER_ID:GNU>:-Wno-infinite-recursion>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set(
|
||||||
|
debug
|
||||||
|
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-g>
|
||||||
|
$<$<CXX_COMPILER_ID:MSVC>:/DEBUG>
|
||||||
|
)
|
||||||
|
|
||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
set(BENCHMARK_ENABLE_TESTING OFF)
|
set(BENCHMARK_ENABLE_TESTING OFF)
|
||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
|
|||||||
328
ci/build-in-all-configs.py
Normal file
328
ci/build-in-all-configs.py
Normal file
@ -0,0 +1,328 @@
|
|||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from colorama import Fore, Back, Style
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from util import *
|
||||||
|
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8') # for windows gh runner
|
||||||
|
|
||||||
|
def build(runner: MatrixRunner):
|
||||||
|
matrix = runner.current_config()
|
||||||
|
|
||||||
|
if os.path.exists("build"):
|
||||||
|
shutil.rmtree("build", ignore_errors=True)
|
||||||
|
|
||||||
|
os.makedirs("build", exist_ok=True)
|
||||||
|
os.chdir("build")
|
||||||
|
|
||||||
|
if platform.system() != "Windows":
|
||||||
|
succeeded = runner.run_command(
|
||||||
|
"cmake",
|
||||||
|
"..",
|
||||||
|
"-GNinja",
|
||||||
|
f"-DCMAKE_BUILD_TYPE={matrix['target']}",
|
||||||
|
f"-DCMAKE_CXX_COMPILER={matrix['compiler']}",
|
||||||
|
f"-DCMAKE_CXX_STANDARD={matrix['std']}",
|
||||||
|
f"-DCPPTRACE_USE_EXTERNAL_LIBDWARF=On",
|
||||||
|
f"-DCPPTRACE_USE_EXTERNAL_ZSTD=On",
|
||||||
|
f"-DCPPTRACE_WERROR_BUILD=On",
|
||||||
|
f"-D{matrix['unwind']}=On",
|
||||||
|
f"-D{matrix['symbols']}=On",
|
||||||
|
f"-D{matrix['demangle']}=On",
|
||||||
|
"-DCPPTRACE_BACKTRACE_PATH=/usr/lib/gcc/x86_64-linux-gnu/10/include/backtrace.h",
|
||||||
|
)
|
||||||
|
if succeeded:
|
||||||
|
succeeded = runner.run_command("ninja")
|
||||||
|
else:
|
||||||
|
args = [
|
||||||
|
"cmake",
|
||||||
|
"..",
|
||||||
|
f"-DCMAKE_BUILD_TYPE={matrix['target']}",
|
||||||
|
f"-DCMAKE_CXX_COMPILER={matrix['compiler']}",
|
||||||
|
f"-DCMAKE_CXX_STANDARD={matrix['std']}",
|
||||||
|
f"-DCPPTRACE_USE_EXTERNAL_LIBDWARF=On",
|
||||||
|
f"-DCPPTRACE_USE_EXTERNAL_ZSTD=On",
|
||||||
|
f"-DCPPTRACE_WERROR_BUILD=On",
|
||||||
|
f"-D{matrix['unwind']}=On",
|
||||||
|
f"-D{matrix['symbols']}=On",
|
||||||
|
f"-D{matrix['demangle']}=On",
|
||||||
|
]
|
||||||
|
if matrix["compiler"] == "g++":
|
||||||
|
args.append("-GUnix Makefiles")
|
||||||
|
succeeded = runner.run_command(*args)
|
||||||
|
if succeeded:
|
||||||
|
if matrix["compiler"] == "g++":
|
||||||
|
succeeded = runner.run_command("make", "-j", "VERBOSE=1")
|
||||||
|
else:
|
||||||
|
succeeded = runner.run_command("msbuild", "cpptrace.sln")
|
||||||
|
|
||||||
|
os.chdir("..")
|
||||||
|
print()
|
||||||
|
|
||||||
|
return succeeded
|
||||||
|
|
||||||
|
def build_full_or_auto(runner: MatrixRunner):
|
||||||
|
matrix = runner.current_config()
|
||||||
|
|
||||||
|
if os.path.exists("build"):
|
||||||
|
shutil.rmtree("build", ignore_errors=True)
|
||||||
|
|
||||||
|
os.makedirs("build", exist_ok=True)
|
||||||
|
os.chdir("build")
|
||||||
|
|
||||||
|
if platform.system() != "Windows":
|
||||||
|
args = [
|
||||||
|
"cmake",
|
||||||
|
"..",
|
||||||
|
"-GNinja",
|
||||||
|
f"-DCMAKE_BUILD_TYPE={matrix['target']}",
|
||||||
|
f"-DCMAKE_CXX_COMPILER={matrix['compiler']}",
|
||||||
|
f"-DCMAKE_CXX_STANDARD={matrix['std']}",
|
||||||
|
f"-DCPPTRACE_USE_EXTERNAL_LIBDWARF=On",
|
||||||
|
f"-DCPPTRACE_USE_EXTERNAL_ZSTD=On",
|
||||||
|
f"-DCPPTRACE_WERROR_BUILD=On",
|
||||||
|
f"-DCPPTRACE_BACKTRACE_PATH=/usr/lib/gcc/x86_64-linux-gnu/10/include/backtrace.h",
|
||||||
|
]
|
||||||
|
if matrix["config"] != "":
|
||||||
|
args.append(f"{matrix['config']}")
|
||||||
|
succeeded = runner.run_command(*args)
|
||||||
|
if succeeded:
|
||||||
|
succeeded = runner.run_command("ninja")
|
||||||
|
else:
|
||||||
|
args = [
|
||||||
|
"cmake",
|
||||||
|
"..",
|
||||||
|
f"-DCMAKE_BUILD_TYPE={matrix['target']}",
|
||||||
|
f"-DCMAKE_CXX_COMPILER={matrix['compiler']}",
|
||||||
|
f"-DCMAKE_CXX_STANDARD={matrix['std']}",
|
||||||
|
f"-DCPPTRACE_USE_EXTERNAL_LIBDWARF=On",
|
||||||
|
f"-DCPPTRACE_USE_EXTERNAL_ZSTD=On",
|
||||||
|
f"-DCPPTRACE_WERROR_BUILD=On",
|
||||||
|
]
|
||||||
|
if matrix["config"] != "":
|
||||||
|
args.append(f"{matrix['config']}")
|
||||||
|
if matrix["compiler"] == "g++":
|
||||||
|
args.append("-GUnix Makefiles")
|
||||||
|
succeeded = runner.run_command(*args)
|
||||||
|
if succeeded:
|
||||||
|
if matrix["compiler"] == "g++":
|
||||||
|
succeeded = runner.run_command("make", "-j")
|
||||||
|
else:
|
||||||
|
succeeded = runner.run_command("msbuild", "cpptrace.sln")
|
||||||
|
|
||||||
|
os.chdir("..")
|
||||||
|
print()
|
||||||
|
|
||||||
|
return succeeded
|
||||||
|
|
||||||
|
def run_linux_matrix(compilers: list):
|
||||||
|
MatrixRunner(
|
||||||
|
matrix = {
|
||||||
|
"compiler": compilers,
|
||||||
|
"target": ["Debug"],
|
||||||
|
"std": ["11", "20"],
|
||||||
|
"unwind": [
|
||||||
|
"CPPTRACE_UNWIND_WITH_UNWIND",
|
||||||
|
"CPPTRACE_UNWIND_WITH_EXECINFO",
|
||||||
|
"CPPTRACE_UNWIND_WITH_LIBUNWIND",
|
||||||
|
"CPPTRACE_UNWIND_WITH_NOTHING",
|
||||||
|
],
|
||||||
|
"symbols": [
|
||||||
|
"CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE",
|
||||||
|
"CPPTRACE_GET_SYMBOLS_WITH_LIBDL",
|
||||||
|
"CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF",
|
||||||
|
"CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE",
|
||||||
|
"CPPTRACE_GET_SYMBOLS_WITH_NOTHING",
|
||||||
|
],
|
||||||
|
"demangle": [
|
||||||
|
"CPPTRACE_DEMANGLE_WITH_CXXABI",
|
||||||
|
"CPPTRACE_DEMANGLE_WITH_NOTHING",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
exclude = []
|
||||||
|
).run(build)
|
||||||
|
|
||||||
|
def run_linux_default(compilers: list):
|
||||||
|
MatrixRunner(
|
||||||
|
matrix = {
|
||||||
|
"compiler": compilers,
|
||||||
|
"target": ["Debug"],
|
||||||
|
"std": ["11", "20"],
|
||||||
|
"config": [""]
|
||||||
|
},
|
||||||
|
exclude = []
|
||||||
|
).run(build_full_or_auto)
|
||||||
|
|
||||||
|
def run_macos_matrix(compilers: list):
|
||||||
|
MatrixRunner(
|
||||||
|
matrix = {
|
||||||
|
"compiler": compilers,
|
||||||
|
"target": ["Debug"],
|
||||||
|
"std": ["11", "20"],
|
||||||
|
"unwind": [
|
||||||
|
"CPPTRACE_UNWIND_WITH_UNWIND",
|
||||||
|
"CPPTRACE_UNWIND_WITH_EXECINFO",
|
||||||
|
"CPPTRACE_UNWIND_WITH_NOTHING",
|
||||||
|
],
|
||||||
|
"symbols": [
|
||||||
|
#"CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE",
|
||||||
|
"CPPTRACE_GET_SYMBOLS_WITH_LIBDL",
|
||||||
|
"CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF",
|
||||||
|
"CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE",
|
||||||
|
"CPPTRACE_GET_SYMBOLS_WITH_NOTHING",
|
||||||
|
],
|
||||||
|
"demangle": [
|
||||||
|
"CPPTRACE_DEMANGLE_WITH_CXXABI",
|
||||||
|
"CPPTRACE_DEMANGLE_WITH_NOTHING",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
exclude = []
|
||||||
|
).run(build)
|
||||||
|
|
||||||
|
def run_macos_default(compilers: list):
|
||||||
|
MatrixRunner(
|
||||||
|
matrix = {
|
||||||
|
"compiler": compilers,
|
||||||
|
"target": ["Debug"],
|
||||||
|
"std": ["11", "20"],
|
||||||
|
"config": [""]
|
||||||
|
},
|
||||||
|
exclude = []
|
||||||
|
).run(build_full_or_auto)
|
||||||
|
|
||||||
|
def run_windows_matrix(compilers: list):
|
||||||
|
MatrixRunner(
|
||||||
|
matrix = {
|
||||||
|
"compiler": compilers,
|
||||||
|
"target": ["Debug"],
|
||||||
|
"std": ["11", "20"],
|
||||||
|
"unwind": [
|
||||||
|
"CPPTRACE_UNWIND_WITH_WINAPI",
|
||||||
|
"CPPTRACE_UNWIND_WITH_DBGHELP",
|
||||||
|
"CPPTRACE_UNWIND_WITH_UNWIND",
|
||||||
|
"CPPTRACE_UNWIND_WITH_NOTHING",
|
||||||
|
],
|
||||||
|
"symbols": [
|
||||||
|
"CPPTRACE_GET_SYMBOLS_WITH_DBGHELP",
|
||||||
|
"CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF",
|
||||||
|
"CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE",
|
||||||
|
"CPPTRACE_GET_SYMBOLS_WITH_NOTHING",
|
||||||
|
],
|
||||||
|
"demangle": [
|
||||||
|
#"CPPTRACE_DEMANGLE_WITH_CXXABI",
|
||||||
|
"CPPTRACE_DEMANGLE_WITH_NOTHING",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
exclude = [
|
||||||
|
{
|
||||||
|
"demangle": "CPPTRACE_DEMANGLE_WITH_CXXABI",
|
||||||
|
"compiler": "cl"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"unwind": "CPPTRACE_UNWIND_WITH_UNWIND",
|
||||||
|
"compiler": "cl"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"unwind": "CPPTRACE_UNWIND_WITH_UNWIND",
|
||||||
|
"compiler": "clang++"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"symbols": "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE",
|
||||||
|
"compiler": "cl"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"symbols": "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE",
|
||||||
|
"compiler": "clang++"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"symbols": "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF",
|
||||||
|
"compiler": "cl"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"symbols": "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF",
|
||||||
|
"compiler": "clang++"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"symbols": "CPPTRACE_GET_SYMBOLS_WITH_DBGHELP",
|
||||||
|
"compiler": "g++"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
).run(build)
|
||||||
|
|
||||||
|
def run_windows_default(compilers: list):
|
||||||
|
MatrixRunner(
|
||||||
|
matrix = {
|
||||||
|
"compiler": compilers,
|
||||||
|
"target": ["Debug"],
|
||||||
|
"std": ["11", "20"],
|
||||||
|
"config": [""]
|
||||||
|
},
|
||||||
|
exclude = []
|
||||||
|
).run(build_full_or_auto)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog="Build in all configs",
|
||||||
|
description="Try building the library in all possible configurations for the current host"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--clang",
|
||||||
|
action="store_true"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--gcc",
|
||||||
|
action="store_true"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--msvc",
|
||||||
|
action="store_true"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--all",
|
||||||
|
action="store_true"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--default-config",
|
||||||
|
action="store_true"
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if platform.system() == "Linux":
|
||||||
|
compilers = []
|
||||||
|
if args.clang or args.all:
|
||||||
|
compilers.append("clang++-14")
|
||||||
|
if args.gcc or args.all:
|
||||||
|
compilers.append("g++-10")
|
||||||
|
if args.default_config:
|
||||||
|
run_linux_default(compilers)
|
||||||
|
else:
|
||||||
|
run_linux_matrix(compilers)
|
||||||
|
if platform.system() == "Darwin":
|
||||||
|
compilers = []
|
||||||
|
if args.clang or args.all:
|
||||||
|
compilers.append("clang++")
|
||||||
|
if args.gcc or args.all:
|
||||||
|
compilers.append("g++-12")
|
||||||
|
if args.default_config:
|
||||||
|
run_macos_default(compilers)
|
||||||
|
else:
|
||||||
|
run_macos_matrix(compilers)
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
compilers = []
|
||||||
|
if args.clang or args.all:
|
||||||
|
compilers.append("clang++")
|
||||||
|
if args.msvc or args.all:
|
||||||
|
compilers.append("cl")
|
||||||
|
if args.gcc or args.all:
|
||||||
|
compilers.append("g++")
|
||||||
|
if args.default_config:
|
||||||
|
run_windows_default(compilers)
|
||||||
|
else:
|
||||||
|
run_windows_matrix(compilers)
|
||||||
|
|
||||||
|
main()
|
||||||
@ -1,195 +0,0 @@
|
|||||||
import argparse
|
|
||||||
import os
|
|
||||||
import platform
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
from colorama import Fore, Back, Style
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from util import *
|
|
||||||
|
|
||||||
sys.stdout.reconfigure(encoding='utf-8') # for windows gh runner
|
|
||||||
|
|
||||||
def build(runner: MatrixRunner):
|
|
||||||
matrix = runner.current_config()
|
|
||||||
|
|
||||||
if os.path.exists("build"):
|
|
||||||
shutil.rmtree("build", ignore_errors=True)
|
|
||||||
|
|
||||||
os.makedirs("build", exist_ok=True)
|
|
||||||
os.chdir("build")
|
|
||||||
|
|
||||||
if platform.system() != "Windows":
|
|
||||||
succeeded = runner.run_command(
|
|
||||||
"cmake",
|
|
||||||
"..",
|
|
||||||
"-GNinja",
|
|
||||||
f"-DCMAKE_BUILD_TYPE={matrix['target']}",
|
|
||||||
f"-DCMAKE_CXX_COMPILER={matrix['compiler']}",
|
|
||||||
f"-DCMAKE_CXX_STANDARD={matrix['std']}",
|
|
||||||
f"-DCPPTRACE_USE_EXTERNAL_LIBDWARF=On",
|
|
||||||
f"-DCPPTRACE_USE_EXTERNAL_ZSTD=On",
|
|
||||||
f"-DCPPTRACE_WERROR_BUILD=On",
|
|
||||||
f"-D{matrix['unwind']}=On",
|
|
||||||
f"-D{matrix['symbols']}=On",
|
|
||||||
f"-D{matrix['demangle']}=On",
|
|
||||||
"-DCPPTRACE_BACKTRACE_PATH=/usr/lib/gcc/x86_64-linux-gnu/10/include/backtrace.h",
|
|
||||||
)
|
|
||||||
if succeeded:
|
|
||||||
succeeded = runner.run_command("ninja")
|
|
||||||
else:
|
|
||||||
args = [
|
|
||||||
"cmake",
|
|
||||||
"..",
|
|
||||||
f"-DCMAKE_BUILD_TYPE={matrix['target']}",
|
|
||||||
f"-DCMAKE_CXX_COMPILER={matrix['compiler']}",
|
|
||||||
f"-DCMAKE_CXX_STANDARD={matrix['std']}",
|
|
||||||
f"-DCPPTRACE_USE_EXTERNAL_LIBDWARF=On",
|
|
||||||
f"-DCPPTRACE_USE_EXTERNAL_ZSTD=On",
|
|
||||||
f"-DCPPTRACE_WERROR_BUILD=On",
|
|
||||||
f"-D{matrix['unwind']}=On",
|
|
||||||
f"-D{matrix['symbols']}=On",
|
|
||||||
f"-D{matrix['demangle']}=On",
|
|
||||||
]
|
|
||||||
if matrix["compiler"] == "g++":
|
|
||||||
args.append("-GUnix Makefiles")
|
|
||||||
succeeded = runner.run_command(*args)
|
|
||||||
if succeeded:
|
|
||||||
if matrix["compiler"] == "g++":
|
|
||||||
succeeded = runner.run_command("make", "-j", "VERBOSE=1")
|
|
||||||
else:
|
|
||||||
succeeded = runner.run_command("msbuild", "cpptrace.sln")
|
|
||||||
|
|
||||||
os.chdir("..")
|
|
||||||
print()
|
|
||||||
|
|
||||||
return succeeded
|
|
||||||
|
|
||||||
def 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
|
cd libdwarf
|
||||||
git init
|
git init
|
||||||
git remote add origin https://github.com/jeremy-rifkin/libdwarf-lite.git
|
git remote add origin https://github.com/jeremy-rifkin/libdwarf-lite.git
|
||||||
git fetch --depth 1 origin fe09ca800b988e2ff21225ac5e7468ceade2a30e
|
git fetch --depth 1 origin 6dbcc23dba6ffd230063bda4b9d7298bf88d9d41
|
||||||
git checkout FETCH_HEAD
|
git checkout FETCH_HEAD
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
|
|||||||
@ -14,8 +14,8 @@ cd ..
|
|||||||
mkdir libdwarf
|
mkdir libdwarf
|
||||||
cd libdwarf
|
cd libdwarf
|
||||||
git init
|
git init
|
||||||
git remote add origin https://github.com/jeremy-rifkin/libdwarf-lite.git
|
git remote add origin https://github.com/davea42/libdwarf-code.git
|
||||||
git fetch --depth 1 origin fe09ca800b988e2ff21225ac5e7468ceade2a30e # 0.11.1
|
git fetch --depth 1 origin 285d9d34f3e9f56cc1c487d0055f6dc54a9c54a1 # 0.11.0
|
||||||
git checkout FETCH_HEAD
|
git checkout FETCH_HEAD
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
|
|||||||
@ -14,8 +14,8 @@ cd ..
|
|||||||
mkdir libdwarf
|
mkdir libdwarf
|
||||||
cd libdwarf
|
cd libdwarf
|
||||||
git init
|
git init
|
||||||
git remote add origin https://github.com/jeremy-rifkin/libdwarf-lite.git
|
git remote add origin https://github.com/davea42/libdwarf-code.git
|
||||||
git fetch --depth 1 origin fe09ca800b988e2ff21225ac5e7468ceade2a30e # 0.11.1
|
git fetch --depth 1 origin f4f6f782a06ab0618861cf0c4474989376c69c76 # 0.11.0 + trunk with some dwarf 5 fixes
|
||||||
git checkout FETCH_HEAD
|
git checkout FETCH_HEAD
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
|
|||||||
@ -16,7 +16,7 @@ mkdir libdwarf
|
|||||||
cd libdwarf
|
cd libdwarf
|
||||||
git init
|
git init
|
||||||
git remote add origin https://github.com/jeremy-rifkin/libdwarf-lite.git
|
git remote add origin https://github.com/jeremy-rifkin/libdwarf-lite.git
|
||||||
git fetch --depth 1 origin fe09ca800b988e2ff21225ac5e7468ceade2a30e
|
git fetch --depth 1 origin 97fd68c6026c0237943106d6bc3e83f3661d39e8
|
||||||
git checkout FETCH_HEAD
|
git checkout FETCH_HEAD
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
|
|||||||
@ -34,7 +34,6 @@ def build(runner: MatrixRunner):
|
|||||||
"-DCPPTRACE_STD_FORMAT=Off",
|
"-DCPPTRACE_STD_FORMAT=Off",
|
||||||
"-DCPPTRACE_BUILD_TESTING=On",
|
"-DCPPTRACE_BUILD_TESTING=On",
|
||||||
f"-DCPPTRACE_SANITIZER_BUILD={matrix['sanitizers']}",
|
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['split_dwarf']}",
|
||||||
f"-DCPPTRACE_BUILD_TESTING_DWARF_VERSION={matrix['dwarf_version']}",
|
f"-DCPPTRACE_BUILD_TESTING_DWARF_VERSION={matrix['dwarf_version']}",
|
||||||
f"-DCPPTRACE_USE_EXTERNAL_LIBDWARF=On",
|
f"-DCPPTRACE_USE_EXTERNAL_LIBDWARF=On",
|
||||||
@ -62,7 +61,6 @@ def build(runner: MatrixRunner):
|
|||||||
"-DCPPTRACE_STD_FORMAT=Off",
|
"-DCPPTRACE_STD_FORMAT=Off",
|
||||||
"-DCPPTRACE_BUILD_TESTING=On",
|
"-DCPPTRACE_BUILD_TESTING=On",
|
||||||
f"-DCPPTRACE_SANITIZER_BUILD={matrix['sanitizers']}",
|
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['split_dwarf']}",
|
||||||
# f"-DCPPTRACE_BUILD_TESTING_SPLIT_DWARF={matrix['dwarf_version']}",
|
# f"-DCPPTRACE_BUILD_TESTING_SPLIT_DWARF={matrix['dwarf_version']}",
|
||||||
f"-DCPPTRACE_USE_EXTERNAL_LIBDWARF=On",
|
f"-DCPPTRACE_USE_EXTERNAL_LIBDWARF=On",
|
||||||
@ -124,7 +122,6 @@ def run_linux_matrix():
|
|||||||
"has_dl_find_object": ["OFF", "ON"],
|
"has_dl_find_object": ["OFF", "ON"],
|
||||||
"split_dwarf": ["OFF", "ON"],
|
"split_dwarf": ["OFF", "ON"],
|
||||||
"dwarf_version": ["4", "5"],
|
"dwarf_version": ["4", "5"],
|
||||||
"symbols": ["On", "Off"],
|
|
||||||
},
|
},
|
||||||
exclude = [
|
exclude = [
|
||||||
{
|
{
|
||||||
@ -136,6 +133,19 @@ def run_linux_matrix():
|
|||||||
"stdlib": "libc++",
|
"stdlib": "libc++",
|
||||||
"sanitizers": "ON",
|
"sanitizers": "ON",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
# disabled until https://github.com/davea42/libdwarf-code/issues/259 is fixed
|
||||||
|
"compiler": "g++-10",
|
||||||
|
"build_type": "RelWithDebInfo",
|
||||||
|
"split_dwarf": "ON",
|
||||||
|
"dwarf_version": "5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
# disabled until https://github.com/davea42/libdwarf-code/issues/267 is fixed
|
||||||
|
"compiler": "clang++-18",
|
||||||
|
"split_dwarf": "ON",
|
||||||
|
"dwarf_version": "5",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
).run(build_and_test)
|
).run(build_and_test)
|
||||||
|
|
||||||
@ -147,7 +157,6 @@ def run_macos_matrix():
|
|||||||
"build_type": ["Debug", "RelWithDebInfo"],
|
"build_type": ["Debug", "RelWithDebInfo"],
|
||||||
"shared": ["OFF", "ON"],
|
"shared": ["OFF", "ON"],
|
||||||
"dSYM": [True, False],
|
"dSYM": [True, False],
|
||||||
"symbols": ["On", "Off"],
|
|
||||||
},
|
},
|
||||||
exclude = [
|
exclude = [
|
||||||
{
|
{
|
||||||
|
|||||||
24
ci/util.py
24
ci/util.py
@ -1,7 +1,7 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import itertools
|
import itertools
|
||||||
from typing import List, Dict
|
from typing import List
|
||||||
from colorama import Fore, Back, Style
|
from colorama import Fore, Back, Style
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
@ -23,7 +23,6 @@ class MatrixRunner:
|
|||||||
def __init__(self, matrix, exclude):
|
def __init__(self, matrix, exclude):
|
||||||
self.matrix = matrix
|
self.matrix = matrix
|
||||||
self.exclude = exclude
|
self.exclude = exclude
|
||||||
self.include = self.parse_includes()
|
|
||||||
self.keys = [*matrix.keys()]
|
self.keys = [*matrix.keys()]
|
||||||
self.values = [*matrix.values()]
|
self.values = [*matrix.values()]
|
||||||
self.results = {} # insertion-ordered
|
self.results = {} # insertion-ordered
|
||||||
@ -33,17 +32,6 @@ class MatrixRunner:
|
|||||||
self.last_matrix_config = None
|
self.last_matrix_config = None
|
||||||
self.current_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:
|
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}")
|
self.log(f"{Fore.CYAN}{Style.BRIGHT}Running Command \"{' '.join(args)}\"{Style.RESET_ALL}")
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
@ -90,11 +78,6 @@ class MatrixRunner:
|
|||||||
def do_exclude(self, matrix_config, exclude):
|
def do_exclude(self, matrix_config, exclude):
|
||||||
return all(map(lambda k: matrix_config[k] == exclude[k], exclude.keys()))
|
return all(map(lambda k: matrix_config[k] == exclude[k], exclude.keys()))
|
||||||
|
|
||||||
def do_include(self, matrix_config, include):
|
|
||||||
if len(include) == 0:
|
|
||||||
return True
|
|
||||||
return all(map(lambda k: matrix_config[k] in include[k], include.keys()))
|
|
||||||
|
|
||||||
def assignment_to_matrix_config(self, assignment):
|
def assignment_to_matrix_config(self, assignment):
|
||||||
matrix_config = {}
|
matrix_config = {}
|
||||||
for k, v in zip(self.matrix.keys(), assignment):
|
for k, v in zip(self.matrix.keys(), assignment):
|
||||||
@ -104,10 +87,7 @@ class MatrixRunner:
|
|||||||
def get_work(self):
|
def get_work(self):
|
||||||
work = []
|
work = []
|
||||||
for assignment in itertools.product(*self.matrix.values()):
|
for assignment in itertools.product(*self.matrix.values()):
|
||||||
config = self.assignment_to_matrix_config(assignment)
|
if any(map(lambda ex: self.do_exclude(self.assignment_to_matrix_config(assignment), ex), self.exclude)):
|
||||||
if any(map(lambda ex: self.do_exclude(config, ex), self.exclude)):
|
|
||||||
continue
|
|
||||||
if not self.do_include(config, self.include):
|
|
||||||
continue
|
continue
|
||||||
work.append(assignment)
|
work.append(assignment)
|
||||||
return work
|
return work
|
||||||
|
|||||||
@ -4,8 +4,6 @@ function(check_support var source includes libraries definitions)
|
|||||||
list(APPEND CMAKE_REQUIRED_INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
list(APPEND CMAKE_REQUIRED_INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||||
set(CMAKE_REQUIRED_LIBRARIES "${libraries}")
|
set(CMAKE_REQUIRED_LIBRARIES "${libraries}")
|
||||||
set(CMAKE_REQUIRED_DEFINITIONS "${definitions}")
|
set(CMAKE_REQUIRED_DEFINITIONS "${definitions}")
|
||||||
set(CMAKE_CXX_STANDARD 11)
|
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
||||||
string(CONCAT full_source "#include \"${source}\"" ${nonce})
|
string(CONCAT full_source "#include \"${source}\"" ${nonce})
|
||||||
check_cxx_source_compiles(${full_source} ${var})
|
check_cxx_source_compiles(${full_source} ${var})
|
||||||
set(${var} ${${var}} PARENT_SCOPE)
|
set(${var} ${${var}} PARENT_SCOPE)
|
||||||
@ -15,10 +13,6 @@ if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
|||||||
check_support(HAS_CXXABI has_cxxabi.cpp "" "" "")
|
check_support(HAS_CXXABI has_cxxabi.cpp "" "" "")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
|
||||||
check_support(HAS_ATTRIBUTE_PACKED has_attribute_packed.cpp "" "" "")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(NOT WIN32)
|
if(NOT WIN32)
|
||||||
check_support(HAS_UNWIND has_unwind.cpp "" "" "")
|
check_support(HAS_UNWIND has_unwind.cpp "" "" "")
|
||||||
check_support(HAS_EXECINFO has_execinfo.cpp "" "" "")
|
check_support(HAS_EXECINFO has_execinfo.cpp "" "" "")
|
||||||
|
|||||||
@ -5,9 +5,8 @@ include(CMakePackageConfigHelpers)
|
|||||||
install(
|
install(
|
||||||
DIRECTORY
|
DIRECTORY
|
||||||
"${PROJECT_SOURCE_DIR}/include/" # our header files
|
"${PROJECT_SOURCE_DIR}/include/" # our header files
|
||||||
"${PROJECT_BINARY_DIR}/include/" # generated header files
|
|
||||||
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
|
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 directory
|
||||||
# PATTERN "**/third_party/**" EXCLUDE # skip third party files
|
# PATTERN "**/third_party/**" EXCLUDE # skip third party files
|
||||||
)
|
)
|
||||||
@ -18,12 +17,12 @@ install(
|
|||||||
TARGETS ${target_name}
|
TARGETS ${target_name}
|
||||||
EXPORT ${package_name}-targets
|
EXPORT ${package_name}-targets
|
||||||
RUNTIME #
|
RUNTIME #
|
||||||
COMPONENT ${package_name}_runtime
|
COMPONENT ${package_name}-runtime
|
||||||
LIBRARY #
|
LIBRARY #
|
||||||
COMPONENT ${package_name}_runtime
|
COMPONENT ${package_name}-runtime
|
||||||
NAMELINK_COMPONENT ${package_name}_development
|
NAMELINK_COMPONENT ${package_name}-development
|
||||||
ARCHIVE #
|
ARCHIVE #
|
||||||
COMPONENT ${package_name}_development
|
COMPONENT ${package_name}-development
|
||||||
INCLUDES #
|
INCLUDES #
|
||||||
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
|
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
|
||||||
)
|
)
|
||||||
@ -39,7 +38,7 @@ configure_file(
|
|||||||
install(
|
install(
|
||||||
FILES "${PROJECT_BINARY_DIR}/cmake/${package_name}-config.cmake"
|
FILES "${PROJECT_BINARY_DIR}/cmake/${package_name}-config.cmake"
|
||||||
DESTINATION "${CPPTRACE_INSTALL_CMAKEDIR}"
|
DESTINATION "${CPPTRACE_INSTALL_CMAKEDIR}"
|
||||||
COMPONENT ${package_name}_development
|
COMPONENT ${package_name}-development
|
||||||
)
|
)
|
||||||
|
|
||||||
# create version file for consumer to check version in CMake
|
# create version file for consumer to check version in CMake
|
||||||
@ -52,7 +51,7 @@ write_basic_package_version_file(
|
|||||||
install(
|
install(
|
||||||
FILES "${PROJECT_BINARY_DIR}/${package_name}-config-version.cmake"
|
FILES "${PROJECT_BINARY_DIR}/${package_name}-config-version.cmake"
|
||||||
DESTINATION "${CPPTRACE_INSTALL_CMAKEDIR}"
|
DESTINATION "${CPPTRACE_INSTALL_CMAKEDIR}"
|
||||||
COMPONENT ${package_name}_development
|
COMPONENT ${package_name}-development
|
||||||
)
|
)
|
||||||
|
|
||||||
# create targets file included by config file with targets for consumers
|
# create targets file included by config file with targets for consumers
|
||||||
@ -60,7 +59,7 @@ install(
|
|||||||
EXPORT ${package_name}-targets
|
EXPORT ${package_name}-targets
|
||||||
NAMESPACE cpptrace::
|
NAMESPACE cpptrace::
|
||||||
DESTINATION "${CPPTRACE_INSTALL_CMAKEDIR}"
|
DESTINATION "${CPPTRACE_INSTALL_CMAKEDIR}"
|
||||||
COMPONENT ${package_name}_development
|
COMPONENT ${package_name}-development
|
||||||
)
|
)
|
||||||
|
|
||||||
if(CPPTRACE_PROVIDE_EXPORT_SET)
|
if(CPPTRACE_PROVIDE_EXPORT_SET)
|
||||||
|
|||||||
@ -151,17 +151,13 @@ option(CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH "" OFF)
|
|||||||
|
|
||||||
if(PROJECT_IS_TOP_LEVEL)
|
if(PROJECT_IS_TOP_LEVEL)
|
||||||
option(CPPTRACE_BUILD_TESTING "" OFF)
|
option(CPPTRACE_BUILD_TESTING "" OFF)
|
||||||
option(CPPTRACE_BUILD_TOOLS "" OFF)
|
|
||||||
option(CPPTRACE_BUILD_BENCHMARK "" OFF)
|
option(CPPTRACE_BUILD_BENCHMARK "" OFF)
|
||||||
option(CPPTRACE_BUILD_NO_SYMBOLS "" OFF)
|
|
||||||
option(CPPTRACE_BUILD_TESTING_SPLIT_DWARF "" OFF)
|
option(CPPTRACE_BUILD_TESTING_SPLIT_DWARF "" OFF)
|
||||||
set(CPPTRACE_BUILD_TESTING_DWARF_VERSION "0" CACHE STRING "")
|
set(CPPTRACE_BUILD_TESTING_DWARF_VERSION "0" CACHE STRING "")
|
||||||
option(CPPTRACE_BUILD_TEST_RDYNAMIC "" OFF)
|
option(CPPTRACE_BUILD_TEST_RDYNAMIC "" OFF)
|
||||||
mark_as_advanced(
|
mark_as_advanced(
|
||||||
CPPTRACE_BUILD_TESTING
|
CPPTRACE_BUILD_TESTING
|
||||||
CPPTRACE_BUILD_TOOLS
|
CPPTRACE_BUILD_BENCHMARKING
|
||||||
CPPTRACE_BUILD_BENCHMARK
|
|
||||||
CPPTRACE_BUILD_NO_SYMBOLS
|
|
||||||
CPPTRACE_BUILD_TESTING_SPLIT_DWARF
|
CPPTRACE_BUILD_TESTING_SPLIT_DWARF
|
||||||
CPPTRACE_BUILD_TESTING_DWARF_VERSION
|
CPPTRACE_BUILD_TESTING_DWARF_VERSION
|
||||||
CPPTRACE_BUILD_TEST_RDYNAMIC
|
CPPTRACE_BUILD_TEST_RDYNAMIC
|
||||||
@ -180,12 +176,12 @@ option(CPPTRACE_SKIP_UNIT "" OFF)
|
|||||||
option(CPPTRACE_STD_FORMAT "" ON)
|
option(CPPTRACE_STD_FORMAT "" ON)
|
||||||
option(CPPTRACE_UNPREFIXED_TRY_CATCH "" OFF)
|
option(CPPTRACE_UNPREFIXED_TRY_CATCH "" OFF)
|
||||||
option(CPPTRACE_USE_EXTERNAL_GTEST "" 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_ZSTD_URL "https://github.com/facebook/zstd/releases/download/v1.5.6/zstd-1.5.6.tar.gz" CACHE STRING "")
|
||||||
set(CPPTRACE_LIBDWARF_REPO "https://github.com/jeremy-rifkin/libdwarf-lite.git" 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_TAG "97fd68c6026c0237943106d6bc3e83f3661d39e8" CACHE STRING "") # v0.11.0
|
||||||
set(CPPTRACE_LIBDWARF_SHALLOW "1" CACHE STRING "")
|
set(CPPTRACE_LIBDWARF_SHALLOW "1" CACHE STRING "")
|
||||||
option(CPPTRACE_PROVIDE_EXPORT_SET "" ON)
|
option(CPPTRACE_PROVIDE_EXPORT_SET "" ON)
|
||||||
option(CPPTRACE_PROVIDE_EXPORT_SET_FOR_LIBDWARF "" OFF)
|
option(CPPTRACE_PROVIDE_EXPORT_SET_FOR_LIBDWARF "" ON)
|
||||||
|
|
||||||
mark_as_advanced(
|
mark_as_advanced(
|
||||||
CPPTRACE_BACKTRACE_PATH
|
CPPTRACE_BACKTRACE_PATH
|
||||||
|
|||||||
@ -1,6 +0,0 @@
|
|||||||
struct __attribute__((packed)) foo {
|
|
||||||
int i;
|
|
||||||
double d;
|
|
||||||
};
|
|
||||||
|
|
||||||
int main() {}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
#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,6 +168,5 @@ 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);
|
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);
|
void ctrace_get_safe_object_frame(ctrace_frame_ptr address, ctrace_safe_object_frame* out);
|
||||||
ctrace_bool ctrace_can_signal_safe_unwind();
|
ctrace_bool can_signal_safe_unwind();
|
||||||
ctrace_bool ctrace_can_get_safe_object_frame();
|
|
||||||
```
|
```
|
||||||
|
|||||||
@ -73,7 +73,6 @@ namespace cpptrace {
|
|||||||
void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
|
void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
|
||||||
// signal-safe
|
// signal-safe
|
||||||
bool can_signal_safe_unwind();
|
bool can_signal_safe_unwind();
|
||||||
bool can_get_safe_object_frame();
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -105,9 +104,6 @@ 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
|
`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`.
|
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
|
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
|
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.
|
knows ways to do these safely on other platforms, I'd be much appreciative.
|
||||||
|
|||||||
@ -31,12 +31,6 @@
|
|||||||
# endif
|
# endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if __cplusplus >= 201703L
|
|
||||||
#define CONSTEXPR_SINCE_CPP17 constexpr
|
|
||||||
#else
|
|
||||||
#define CONSTEXPR_SINCE_CPP17
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
#define CPPTRACE_FORCE_NO_INLINE __declspec(noinline)
|
#define CPPTRACE_FORCE_NO_INLINE __declspec(noinline)
|
||||||
#else
|
#else
|
||||||
@ -101,42 +95,37 @@ namespace cpptrace {
|
|||||||
// use.
|
// use.
|
||||||
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
|
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
|
||||||
struct nullable {
|
struct nullable {
|
||||||
T raw_value = null_value();
|
T raw_value;
|
||||||
constexpr nullable() noexcept = default;
|
nullable& operator=(T value) {
|
||||||
constexpr nullable(T value) noexcept : raw_value(value) {}
|
|
||||||
CONSTEXPR_SINCE_CPP17 nullable& operator=(T value) noexcept {
|
|
||||||
raw_value = value;
|
raw_value = value;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
constexpr bool has_value() const noexcept {
|
bool has_value() const noexcept {
|
||||||
return raw_value != null_value();
|
return raw_value != (std::numeric_limits<T>::max)();
|
||||||
}
|
}
|
||||||
CONSTEXPR_SINCE_CPP17 T& value() noexcept {
|
T& value() noexcept {
|
||||||
return raw_value;
|
return raw_value;
|
||||||
}
|
}
|
||||||
constexpr const T& value() const noexcept {
|
const T& value() const noexcept {
|
||||||
return raw_value;
|
return raw_value;
|
||||||
}
|
}
|
||||||
constexpr T value_or(T alternative) const noexcept {
|
T value_or(T alternative) const noexcept {
|
||||||
return has_value() ? raw_value : alternative;
|
return has_value() ? raw_value : alternative;
|
||||||
}
|
}
|
||||||
CONSTEXPR_SINCE_CPP17 void swap(nullable& other) noexcept {
|
void swap(nullable& other) noexcept {
|
||||||
std::swap(raw_value, other.raw_value);
|
std::swap(raw_value, other.raw_value);
|
||||||
}
|
}
|
||||||
CONSTEXPR_SINCE_CPP17 void reset() noexcept {
|
void reset() noexcept {
|
||||||
raw_value = (std::numeric_limits<T>::max)();
|
raw_value = (std::numeric_limits<T>::max)();
|
||||||
}
|
}
|
||||||
constexpr bool operator==(const nullable& other) const noexcept {
|
bool operator==(const nullable& other) const noexcept {
|
||||||
return raw_value == other.raw_value;
|
return raw_value == other.raw_value;
|
||||||
}
|
}
|
||||||
constexpr bool operator!=(const nullable& other) const noexcept {
|
bool operator!=(const nullable& other) const noexcept {
|
||||||
return raw_value != other.raw_value;
|
return raw_value != other.raw_value;
|
||||||
}
|
}
|
||||||
constexpr static T null_value() noexcept {
|
|
||||||
return (std::numeric_limits<T>::max)();
|
|
||||||
}
|
|
||||||
constexpr static nullable null() noexcept {
|
constexpr static nullable null() noexcept {
|
||||||
return { null_value() };
|
return { (std::numeric_limits<T>::max)() };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -193,6 +182,8 @@ namespace cpptrace {
|
|||||||
inline const_iterator cbegin() const noexcept { return frames.cbegin(); }
|
inline const_iterator cbegin() const noexcept { return frames.cbegin(); }
|
||||||
inline const_iterator cend() const noexcept { return frames.cend(); }
|
inline const_iterator cend() const noexcept { return frames.cend(); }
|
||||||
private:
|
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();
|
friend void print_terminate_trace();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -235,7 +226,6 @@ namespace cpptrace {
|
|||||||
// signal-safe
|
// signal-safe
|
||||||
CPPTRACE_EXPORT void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
|
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_signal_safe_unwind();
|
||||||
CPPTRACE_EXPORT bool can_get_safe_object_frame();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
|
|||||||
@ -1,74 +0,0 @@
|
|||||||
#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
|
|
||||||
@ -43,12 +43,6 @@ namespace cpptrace {
|
|||||||
namespace experimental {
|
namespace experimental {
|
||||||
CPPTRACE_EXPORT void set_cache_mode(cache_mode mode);
|
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
|
#endif
|
||||||
|
|||||||
@ -131,8 +131,7 @@ CTRACE_BEGIN_DEFINITIONS
|
|||||||
/* ctrace::safe: */
|
/* 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 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 void ctrace_get_safe_object_frame(ctrace_frame_ptr address, ctrace_safe_object_frame* out);
|
||||||
CPPTRACE_EXPORT ctrace_bool ctrace_can_signal_safe_unwind(void);
|
CPPTRACE_EXPORT ctrace_bool can_signal_safe_unwind(void);
|
||||||
CPPTRACE_EXPORT ctrace_bool ctrace_can_get_safe_object_frame(void);
|
|
||||||
|
|
||||||
/* ctrace::io: */
|
/* ctrace::io: */
|
||||||
CPPTRACE_EXPORT ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color);
|
CPPTRACE_EXPORT ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color);
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
#include "binary/elf.hpp"
|
#include "binary/elf.hpp"
|
||||||
#include "utils/optional.hpp"
|
|
||||||
|
|
||||||
#if IS_LINUX
|
#if IS_LINUX
|
||||||
|
|
||||||
@ -7,25 +6,60 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <mutex>
|
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
#include <elf.h>
|
#include <elf.h>
|
||||||
|
|
||||||
namespace cpptrace {
|
namespace cpptrace {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
elf::elf(
|
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
|
||||||
file_wrapper file,
|
T elf_byteswap_if_needed(T value, bool elf_is_little) {
|
||||||
const std::string& object_path,
|
if(is_little_endian() == elf_is_little) {
|
||||||
bool is_little_endian,
|
return value;
|
||||||
bool is_64
|
} else {
|
||||||
) : file(std::move(file)), object_path(object_path), is_little_endian(is_little_endian), is_64(is_64) {}
|
return byteswap(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Result<elf, internal_error> elf::open_elf(const std::string& object_path) {
|
template<std::size_t Bits>
|
||||||
|
static Result<std::uintptr_t, internal_error> elf_get_module_image_base_from_program_table(
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<std::uintptr_t, internal_error> elf_get_module_image_base(const std::string& object_path) {
|
||||||
auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter);
|
auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter);
|
||||||
if(file == nullptr) {
|
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
|
// Initial checks/metadata
|
||||||
auto magic = load_bytes<std::array<char, 4>>(file, 0);
|
auto magic = load_bytes<std::array<char, 4>>(file, 0);
|
||||||
@ -52,385 +86,14 @@ namespace detail {
|
|||||||
if(ei_version.unwrap_value() != 1) {
|
if(ei_version.unwrap_value() != 1) {
|
||||||
return internal_error("Unexpected ELF version " + object_path);
|
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
|
// get image base
|
||||||
if(is_64) {
|
if(is_64) {
|
||||||
return get_module_image_base_impl<64>();
|
return elf_get_module_image_base_from_program_table<64>(object_path, file, is_little_endian);
|
||||||
} else {
|
} else {
|
||||||
return get_module_image_base_impl<32>();
|
return elf_get_module_image_base_from_program_table<32>(object_path, file, is_little_endian);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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); });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,119 +8,10 @@
|
|||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
namespace cpptrace {
|
namespace cpptrace {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
class elf {
|
Result<std::uintptr_t, internal_error> elf_get_module_image_base(const std::string& object_path);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,6 @@
|
|||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <mutex>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
@ -103,7 +102,7 @@ namespace detail {
|
|||||||
|
|
||||||
Result<const char*, internal_error> mach_o::symtab_info_data::get_string(std::size_t index) const {
|
Result<const char*, internal_error> mach_o::symtab_info_data::get_string(std::size_t index) const {
|
||||||
if(stringtab && index < symtab.strsize) {
|
if(stringtab && index < symtab.strsize) {
|
||||||
return stringtab.unwrap().data() + index;
|
return stringtab.get() + index;
|
||||||
} else {
|
} else {
|
||||||
return internal_error("can't retrieve symbol from symtab");
|
return internal_error("can't retrieve symbol from symtab");
|
||||||
}
|
}
|
||||||
@ -220,7 +219,7 @@ namespace detail {
|
|||||||
|
|
||||||
void mach_o::print_symbol_table_entry(
|
void mach_o::print_symbol_table_entry(
|
||||||
const nlist_64& entry,
|
const nlist_64& entry,
|
||||||
const char* stringtab,
|
const std::unique_ptr<char[]>& stringtab,
|
||||||
std::size_t stringsize,
|
std::size_t stringsize,
|
||||||
std::size_t j
|
std::size_t j
|
||||||
) const {
|
) const {
|
||||||
@ -249,7 +248,7 @@ namespace detail {
|
|||||||
stringtab == nullptr
|
stringtab == nullptr
|
||||||
? "Stringtab error"
|
? "Stringtab error"
|
||||||
: entry.n_un.n_strx < stringsize
|
: entry.n_un.n_strx < stringsize
|
||||||
? stringtab + entry.n_un.n_strx
|
? stringtab.get() + entry.n_un.n_strx
|
||||||
: "String index out of bounds"
|
: "String index out of bounds"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -287,7 +286,7 @@ namespace detail {
|
|||||||
}
|
}
|
||||||
print_symbol_table_entry(
|
print_symbol_table_entry(
|
||||||
entry.unwrap_value(),
|
entry.unwrap_value(),
|
||||||
stringtab ? stringtab.unwrap_value().data() : nullptr,
|
std::move(stringtab).value_or(std::unique_ptr<char[]>(nullptr)),
|
||||||
symtab.strsize,
|
symtab.strsize,
|
||||||
j
|
j
|
||||||
);
|
);
|
||||||
@ -365,17 +364,10 @@ namespace detail {
|
|||||||
return debug_map;
|
return debug_map;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result<const std::vector<mach_o::symbol_entry>&, internal_error> mach_o::symbol_table() {
|
Result<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
|
// 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
|
// first collect symbols and the objects they come from
|
||||||
|
std::vector<symbol_entry> symbols;
|
||||||
auto symtab_info_res = get_symtab_info();
|
auto symtab_info_res = get_symtab_info();
|
||||||
if(!symtab_info_res) {
|
if(!symtab_info_res) {
|
||||||
return std::move(symtab_info_res).unwrap_error();
|
return std::move(symtab_info_res).unwrap_error();
|
||||||
@ -402,42 +394,13 @@ namespace detail {
|
|||||||
if(!str) {
|
if(!str) {
|
||||||
return std::move(str).unwrap_error();
|
return std::move(str).unwrap_error();
|
||||||
}
|
}
|
||||||
symbol_table.push_back({
|
symbols.push_back({
|
||||||
entry.n_value,
|
entry.n_value,
|
||||||
str.unwrap_value()
|
str.unwrap_value()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::sort(
|
return symbols;
|
||||||
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
|
// produce information similar to dsymutil -dump-debug-map
|
||||||
@ -642,12 +605,12 @@ namespace detail {
|
|||||||
return common;
|
return common;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result<std::vector<char>, internal_error> mach_o::load_string_table(std::uint32_t offset, std::uint32_t byte_count) const {
|
Result<std::unique_ptr<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);
|
std::unique_ptr<char[]> buffer(new char[byte_count + 1]);
|
||||||
if(std::fseek(file, load_base + offset, SEEK_SET) != 0) {
|
if(std::fseek(file, load_base + offset, SEEK_SET) != 0) {
|
||||||
return internal_error("fseek error while loading mach-o symbol table");
|
return internal_error("fseek error while loading mach-o symbol table");
|
||||||
}
|
}
|
||||||
if(std::fread(buffer.data(), sizeof(char), byte_count, file) != byte_count) {
|
if(std::fread(buffer.get(), sizeof(char), byte_count, file) != byte_count) {
|
||||||
return internal_error("fread error while loading mach-o symbol table");
|
return internal_error("fread error while loading mach-o symbol table");
|
||||||
}
|
}
|
||||||
buffer[byte_count] = 0; // just out of an abundance of caution
|
buffer[byte_count] = 0; // just out of an abundance of caution
|
||||||
@ -670,27 +633,6 @@ namespace detail {
|
|||||||
return is_fat_magic(magic.unwrap_value());
|
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); });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -28,23 +28,6 @@ namespace detail {
|
|||||||
};
|
};
|
||||||
|
|
||||||
class mach_o {
|
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;
|
file_wrapper file;
|
||||||
std::string object_path;
|
std::string object_path;
|
||||||
std::uint32_t magic;
|
std::uint32_t magic;
|
||||||
@ -63,16 +46,13 @@ namespace detail {
|
|||||||
|
|
||||||
struct symtab_info_data {
|
struct symtab_info_data {
|
||||||
symtab_command symtab;
|
symtab_command symtab;
|
||||||
optional<std::vector<char>> stringtab;
|
std::unique_ptr<char[]> stringtab;
|
||||||
Result<const char*, internal_error> get_string(std::size_t index) const;
|
Result<const char*, internal_error> get_string(std::size_t index) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool tried_to_load_symtab = false;
|
bool tried_to_load_symtab = false;
|
||||||
optional<symtab_info_data> symtab_info;
|
optional<symtab_info_data> symtab_info;
|
||||||
|
|
||||||
bool tried_to_load_symbols = false;
|
|
||||||
optional<std::vector<symbol_entry>> symbols;
|
|
||||||
|
|
||||||
mach_o(
|
mach_o(
|
||||||
file_wrapper file,
|
file_wrapper file,
|
||||||
const std::string& object_path,
|
const std::string& object_path,
|
||||||
@ -100,19 +80,31 @@ namespace detail {
|
|||||||
|
|
||||||
void print_symbol_table_entry(
|
void print_symbol_table_entry(
|
||||||
const nlist_64& entry,
|
const nlist_64& entry,
|
||||||
const char* stringtab,
|
const std::unique_ptr<char[]>& stringtab,
|
||||||
std::size_t stringsize,
|
std::size_t stringsize,
|
||||||
std::size_t j
|
std::size_t j
|
||||||
) const;
|
) const;
|
||||||
|
|
||||||
void print_symbol_table();
|
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
|
// produce information similar to dsymutil -dump-debug-map
|
||||||
Result<debug_map, internal_error> get_debug_map();
|
Result<debug_map, internal_error> get_debug_map();
|
||||||
|
|
||||||
Result<const std::vector<symbol_entry>&, internal_error> symbol_table();
|
Result<std::vector<symbol_entry>, internal_error> symbol_table();
|
||||||
|
|
||||||
optional<std::string> lookup_symbol(frame_ptr pc);
|
|
||||||
|
|
||||||
// produce information similar to dsymutil -dump-debug-map
|
// produce information similar to dsymutil -dump-debug-map
|
||||||
static void print_debug_map(const debug_map& debug_map);
|
static void print_debug_map(const debug_map& debug_map);
|
||||||
@ -131,14 +123,12 @@ namespace detail {
|
|||||||
template<std::size_t Bits>
|
template<std::size_t Bits>
|
||||||
Result<nlist_64, internal_error> load_symtab_entry(std::uint32_t symbol_base, std::size_t index) const;
|
Result<nlist_64, internal_error> load_symtab_entry(std::uint32_t symbol_base, std::size_t index) const;
|
||||||
|
|
||||||
Result<std::vector<char>, internal_error> load_string_table(std::uint32_t offset, std::uint32_t byte_count) const;
|
Result<std::unique_ptr<char[]>, internal_error> load_string_table(std::uint32_t offset, std::uint32_t byte_count) const;
|
||||||
|
|
||||||
bool should_swap() const;
|
bool should_swap() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
Result<bool, internal_error> macho_is_fat(const std::string& object_path);
|
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -30,12 +30,8 @@ namespace detail {
|
|||||||
if(it == cache.end()) {
|
if(it == cache.end()) {
|
||||||
// arguably it'd be better to release the lock while computing this, but also arguably it's good to not
|
// 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
|
// have two threads try to do the same computation
|
||||||
auto elf_object = open_elf_cached(object_path);
|
auto base = elf_get_module_image_base(object_path);
|
||||||
// TODO: Cache the error
|
// 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()) {
|
if(base.is_error()) {
|
||||||
return std::move(base).unwrap_error();
|
return std::move(base).unwrap_error();
|
||||||
}
|
}
|
||||||
@ -57,12 +53,12 @@ namespace detail {
|
|||||||
if(it == cache.end()) {
|
if(it == cache.end()) {
|
||||||
// arguably it'd be better to release the lock while computing this, but also arguably it's good to not
|
// 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
|
// have two threads try to do the same computation
|
||||||
auto mach_o_object = open_mach_o_cached(object_path);
|
auto obj = mach_o::open_mach_o(object_path);
|
||||||
// TODO: Cache the error
|
// TODO: Cache the error
|
||||||
if(!mach_o_object) {
|
if(!obj) {
|
||||||
return mach_o_object.unwrap_error();
|
return obj.unwrap_error();
|
||||||
}
|
}
|
||||||
auto base = mach_o_object.unwrap_value()->get_text_vmaddr();
|
auto base = obj.unwrap_value().get_text_vmaddr();
|
||||||
if(!base) {
|
if(!base) {
|
||||||
return std::move(base).unwrap_error();
|
return std::move(base).unwrap_error();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -78,9 +78,7 @@ namespace detail {
|
|||||||
- reinterpret_cast<std::uintptr_t>(info.dli_fbase)
|
- reinterpret_cast<std::uintptr_t>(info.dli_fbase)
|
||||||
+ base.unwrap_value();
|
+ base.unwrap_value();
|
||||||
} else {
|
} else {
|
||||||
if(!should_absorb_trace_exceptions()) {
|
base.drop_error();
|
||||||
base.drop_error();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return frame;
|
return frame;
|
||||||
@ -103,9 +101,7 @@ namespace detail {
|
|||||||
- reinterpret_cast<std::uintptr_t>(info.dli_fbase)
|
- reinterpret_cast<std::uintptr_t>(info.dli_fbase)
|
||||||
+ base.unwrap_value();
|
+ base.unwrap_value();
|
||||||
} else {
|
} else {
|
||||||
if(!should_absorb_trace_exceptions()) {
|
base.drop_error();
|
||||||
base.drop_error();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return frame;
|
return frame;
|
||||||
@ -150,9 +146,7 @@ namespace detail {
|
|||||||
- reinterpret_cast<std::uintptr_t>(handle)
|
- reinterpret_cast<std::uintptr_t>(handle)
|
||||||
+ base.unwrap_value();
|
+ base.unwrap_value();
|
||||||
} else {
|
} else {
|
||||||
if(!should_absorb_trace_exceptions()) {
|
base.drop_error();
|
||||||
base.drop_error();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
std::fprintf(stderr, "%s\n", std::system_error(GetLastError(), std::system_category()).what());
|
std::fprintf(stderr, "%s\n", std::system_error(GetLastError(), std::system_category()).what());
|
||||||
|
|||||||
@ -53,10 +53,6 @@ namespace detail {
|
|||||||
// may return the object that defines the function descriptor (and not the object that contains the code
|
// 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.
|
// implementing the function), or fail to find any object at all.
|
||||||
}
|
}
|
||||||
|
|
||||||
bool has_get_safe_object_frame() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
@ -67,10 +63,6 @@ namespace detail {
|
|||||||
out->address_relative_to_object_start = 0;
|
out->address_relative_to_object_start = 0;
|
||||||
out->object_path[0] = 0;
|
out->object_path[0] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool has_get_safe_object_frame() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -6,8 +6,6 @@
|
|||||||
namespace cpptrace {
|
namespace cpptrace {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
|
void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
|
||||||
|
|
||||||
bool has_get_safe_object_frame();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
126
src/cpptrace.cpp
126
src/cpptrace.cpp
@ -1,5 +1,4 @@
|
|||||||
#include <cpptrace/cpptrace.hpp>
|
#include <cpptrace/cpptrace.hpp>
|
||||||
#include <cpptrace/formatting.hpp>
|
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@ -10,7 +9,6 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "cpptrace/basic.hpp"
|
|
||||||
#include "symbols/symbols.hpp"
|
#include "symbols/symbols.hpp"
|
||||||
#include "unwind/unwind.hpp"
|
#include "unwind/unwind.hpp"
|
||||||
#include "demangle/demangle.hpp"
|
#include "demangle/demangle.hpp"
|
||||||
@ -20,7 +18,6 @@
|
|||||||
#include "binary/object.hpp"
|
#include "binary/object.hpp"
|
||||||
#include "binary/safe_dl.hpp"
|
#include "binary/safe_dl.hpp"
|
||||||
#include "snippets/snippet.hpp"
|
#include "snippets/snippet.hpp"
|
||||||
#include "options.hpp"
|
|
||||||
|
|
||||||
namespace cpptrace {
|
namespace cpptrace {
|
||||||
CPPTRACE_FORCE_NO_INLINE
|
CPPTRACE_FORCE_NO_INLINE
|
||||||
@ -62,7 +59,7 @@ namespace cpptrace {
|
|||||||
try {
|
try {
|
||||||
std::vector<stacktrace_frame> trace = detail::resolve_frames(frames);
|
std::vector<stacktrace_frame> trace = detail::resolve_frames(frames);
|
||||||
for(auto& frame : trace) {
|
for(auto& frame : trace) {
|
||||||
frame.symbol = detail::demangle(frame.symbol, true);
|
frame.symbol = detail::demangle(frame.symbol);
|
||||||
}
|
}
|
||||||
return {std::move(trace)};
|
return {std::move(trace)};
|
||||||
} catch(...) { // NOSONAR
|
} catch(...) { // NOSONAR
|
||||||
@ -109,7 +106,7 @@ namespace cpptrace {
|
|||||||
try {
|
try {
|
||||||
std::vector<stacktrace_frame> trace = detail::resolve_frames(frames);
|
std::vector<stacktrace_frame> trace = detail::resolve_frames(frames);
|
||||||
for(auto& frame : trace) {
|
for(auto& frame : trace) {
|
||||||
frame.symbol = detail::demangle(frame.symbol, true);
|
frame.symbol = detail::demangle(frame.symbol);
|
||||||
}
|
}
|
||||||
return {std::move(trace)};
|
return {std::move(trace)};
|
||||||
} catch(...) { // NOSONAR
|
} catch(...) { // NOSONAR
|
||||||
@ -132,12 +129,41 @@ namespace cpptrace {
|
|||||||
return detail::get_frame_object_info(raw_address);
|
return detail::get_frame_object_info(raw_address);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::string frame_to_string(
|
||||||
|
bool color,
|
||||||
|
const stacktrace_frame& frame
|
||||||
|
) {
|
||||||
|
const auto reset = color ? RESET : "";
|
||||||
|
const auto green = color ? GREEN : "";
|
||||||
|
const auto yellow = color ? YELLOW : "";
|
||||||
|
const auto blue = color ? BLUE : "";
|
||||||
|
std::string str;
|
||||||
|
if(frame.is_inline) {
|
||||||
|
str += microfmt::format("{<{}}", 2 * sizeof(frame_ptr) + 2, "(inlined)");
|
||||||
|
} else {
|
||||||
|
str += microfmt::format("{}0x{>{}:0h}{}", blue, 2 * sizeof(frame_ptr), frame.raw_address, reset);
|
||||||
|
}
|
||||||
|
if(!frame.symbol.empty()) {
|
||||||
|
str += microfmt::format(" in {}{}{}", yellow, frame.symbol, reset);
|
||||||
|
}
|
||||||
|
if(!frame.filename.empty()) {
|
||||||
|
str += microfmt::format(" at {}{}{}", green, frame.filename, reset);
|
||||||
|
if(frame.line.has_value()) {
|
||||||
|
str += microfmt::format(":{}{}{}", blue, frame.line.value(), reset);
|
||||||
|
if(frame.column.has_value()) {
|
||||||
|
str += microfmt::format(":{}{}{}", blue, frame.column.value(), reset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
std::string stacktrace_frame::to_string() const {
|
std::string stacktrace_frame::to_string() const {
|
||||||
return to_string(false);
|
return to_string(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string stacktrace_frame::to_string(bool color) const {
|
std::string stacktrace_frame::to_string(bool color) const {
|
||||||
return get_default_formatter().format(*this, color);
|
return frame_to_string(color, *this);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ostream& operator<<(std::ostream& stream, const stacktrace_frame& frame) {
|
std::ostream& operator<<(std::ostream& stream, const stacktrace_frame& frame) {
|
||||||
@ -169,34 +195,89 @@ namespace cpptrace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void stacktrace::print() const {
|
void stacktrace::print() const {
|
||||||
get_default_formatter().print(*this);
|
print(std::cerr, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void stacktrace::print(std::ostream& stream) const {
|
void stacktrace::print(std::ostream& stream) const {
|
||||||
get_default_formatter().print(stream, *this);
|
print(stream, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void stacktrace::print(std::ostream& stream, bool color) const {
|
void stacktrace::print(std::ostream& stream, bool color) const {
|
||||||
get_default_formatter().print(stream, *this, color);
|
print(stream, color, true, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace detail {
|
static void print_frame(
|
||||||
const formatter& get_default_snippet_formatter() {
|
std::ostream& stream,
|
||||||
static formatter snippet_formatter = formatter{}.snippets(true);
|
bool color,
|
||||||
return snippet_formatter;
|
unsigned frame_number_width,
|
||||||
|
std::size_t counter,
|
||||||
|
const stacktrace_frame& frame
|
||||||
|
) {
|
||||||
|
std::string line = microfmt::format("#{<{}} {}", frame_number_width, counter, frame.to_string(color));
|
||||||
|
stream << line;
|
||||||
|
}
|
||||||
|
|
||||||
|
void stacktrace::print(std::ostream& stream, bool color, bool newline_at_end, const char* header) const {
|
||||||
|
if(
|
||||||
|
color && (
|
||||||
|
(&stream == &std::cout && isatty(stdout_fileno)) || (&stream == &std::cerr && isatty(stderr_fileno))
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
detail::enable_virtual_terminal_processing_if_needed();
|
||||||
|
}
|
||||||
|
stream << (header ? header : "Stack trace (most recent call first):") << '\n';
|
||||||
|
std::size_t counter = 0;
|
||||||
|
if(frames.empty()) {
|
||||||
|
stream << "<empty trace>\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto frame_number_width = detail::n_digits(static_cast<int>(frames.size()) - 1);
|
||||||
|
for(const auto& frame : frames) {
|
||||||
|
print_frame(stream, color, frame_number_width, counter, frame);
|
||||||
|
if(newline_at_end || &frame != &frames.back()) {
|
||||||
|
stream << '\n';
|
||||||
|
}
|
||||||
|
counter++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void stacktrace::print_with_snippets() const {
|
void stacktrace::print_with_snippets() const {
|
||||||
detail::get_default_snippet_formatter().print(*this);
|
print_with_snippets(std::cerr, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void stacktrace::print_with_snippets(std::ostream& stream) const {
|
void stacktrace::print_with_snippets(std::ostream& stream) const {
|
||||||
detail::get_default_snippet_formatter().print(stream, *this);
|
print_with_snippets(stream, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void stacktrace::print_with_snippets(std::ostream& stream, bool color) const {
|
void stacktrace::print_with_snippets(std::ostream& stream, bool color) const {
|
||||||
detail::get_default_snippet_formatter().print(stream, *this, color);
|
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++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void stacktrace::clear() {
|
void stacktrace::clear() {
|
||||||
@ -208,12 +289,13 @@ namespace cpptrace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string stacktrace::to_string(bool color) const {
|
std::string stacktrace::to_string(bool color) const {
|
||||||
return get_default_formatter().format(*this, color);
|
std::ostringstream oss;
|
||||||
|
print(oss, color, false, nullptr);
|
||||||
|
return std::move(oss).str();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ostream& operator<<(std::ostream& stream, const stacktrace& trace) {
|
std::ostream& operator<<(std::ostream& stream, const stacktrace& trace) {
|
||||||
get_default_formatter().print(stream, trace);
|
return stream << trace.to_string();
|
||||||
return stream;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CPPTRACE_FORCE_NO_INLINE
|
CPPTRACE_FORCE_NO_INLINE
|
||||||
@ -311,7 +393,7 @@ namespace cpptrace {
|
|||||||
std::vector<frame_ptr> frames = detail::capture_frames(skip + 1, max_depth);
|
std::vector<frame_ptr> frames = detail::capture_frames(skip + 1, max_depth);
|
||||||
std::vector<stacktrace_frame> trace = detail::resolve_frames(frames);
|
std::vector<stacktrace_frame> trace = detail::resolve_frames(frames);
|
||||||
for(auto& frame : trace) {
|
for(auto& frame : trace) {
|
||||||
frame.symbol = detail::demangle(frame.symbol, true);
|
frame.symbol = detail::demangle(frame.symbol);
|
||||||
}
|
}
|
||||||
return {std::move(trace)};
|
return {std::move(trace)};
|
||||||
} catch(...) { // NOSONAR
|
} catch(...) { // NOSONAR
|
||||||
@ -333,8 +415,4 @@ namespace cpptrace {
|
|||||||
bool can_signal_safe_unwind() {
|
bool can_signal_safe_unwind() {
|
||||||
return detail::has_safe_unwind();
|
return detail::has_safe_unwind();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool can_get_safe_object_frame() {
|
|
||||||
return detail::has_get_safe_object_frame();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -107,7 +107,7 @@ CTRACE_FORMAT_EPILOGUE
|
|||||||
new_frame.line = frame.line.value_or(invalid_pos);
|
new_frame.line = frame.line.value_or(invalid_pos);
|
||||||
new_frame.column = frame.column.value_or(invalid_pos);
|
new_frame.column = frame.column.value_or(invalid_pos);
|
||||||
new_frame.filename = generate_owning_string(frame.filename).data;
|
new_frame.filename = generate_owning_string(frame.filename).data;
|
||||||
new_frame.symbol = generate_owning_string(cpptrace::detail::demangle(frame.symbol, true)).data;
|
new_frame.symbol = generate_owning_string(cpptrace::detail::demangle(frame.symbol)).data;
|
||||||
new_frame.is_inline = ctrace_bool(frame.is_inline);
|
new_frame.is_inline = ctrace_bool(frame.is_inline);
|
||||||
return new_frame;
|
return new_frame;
|
||||||
}
|
}
|
||||||
@ -310,14 +310,10 @@ extern "C" {
|
|||||||
cpptrace::get_safe_object_frame(address, reinterpret_cast<cpptrace::safe_object_frame*>(out));
|
cpptrace::get_safe_object_frame(address, reinterpret_cast<cpptrace::safe_object_frame*>(out));
|
||||||
}
|
}
|
||||||
|
|
||||||
ctrace_bool ctrace_can_signal_safe_unwind() {
|
ctrace_bool can_signal_safe_unwind() {
|
||||||
return cpptrace::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::io:
|
||||||
ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color) {
|
ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color) {
|
||||||
if(!trace || !trace->frames) {
|
if(!trace || !trace->frames) {
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
namespace cpptrace {
|
namespace cpptrace {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
std::string demangle(const std::string& name, bool check_prefix);
|
std::string demangle(const std::string&);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,55 +1,25 @@
|
|||||||
#include "utils/microfmt.hpp"
|
|
||||||
#ifdef CPPTRACE_DEMANGLE_WITH_CXXABI
|
#ifdef CPPTRACE_DEMANGLE_WITH_CXXABI
|
||||||
|
|
||||||
#include "demangle/demangle.hpp"
|
#include "demangle/demangle.hpp"
|
||||||
|
|
||||||
#include "utils/utils.hpp"
|
|
||||||
|
|
||||||
#include <cxxabi.h>
|
#include <cxxabi.h>
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <functional>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace cpptrace {
|
namespace cpptrace {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
std::string demangle(const std::string& name, bool check_prefix) {
|
std::string demangle(const std::string& name) {
|
||||||
// https://itanium-cxx-abi.github.io/cxx-abi/abi.html#demangler
|
int status;
|
||||||
// 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
|
// 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
|
// 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
|
// want to rely on it
|
||||||
int status;
|
char* const demangled = abi::__cxa_demangle(name.c_str(), nullptr, nullptr, &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
|
// 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
|
// we'll just quietly return the mangled name
|
||||||
if(demangled.get()) {
|
if(demangled) {
|
||||||
std::string str = demangled.get();
|
std::string str = demangled;
|
||||||
if(!rest.empty()) {
|
std::free(demangled);
|
||||||
str += rest;
|
|
||||||
}
|
|
||||||
return str;
|
return str;
|
||||||
} else {
|
} else {
|
||||||
return name;
|
return name;
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
namespace cpptrace {
|
namespace cpptrace {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
std::string demangle(const std::string& name, bool) {
|
std::string demangle(const std::string& name) {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
#ifdef CPPTRACE_DEMANGLE_WITH_WINAPI
|
#ifdef CPPTRACE_DEMANGLE_WITH_WINAPI
|
||||||
|
|
||||||
#include "demangle/demangle.hpp"
|
#include "demangle/demangle.hpp"
|
||||||
#include "platform/dbghelp_utils.hpp"
|
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@ -13,9 +12,7 @@
|
|||||||
|
|
||||||
namespace cpptrace {
|
namespace cpptrace {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
std::string demangle(const std::string& name, bool) {
|
std::string demangle(const std::string& name) {
|
||||||
// Dbghelp is is single-threaded, so acquire a lock.
|
|
||||||
auto lock = get_dbghelp_lock();
|
|
||||||
char buffer[500];
|
char buffer[500];
|
||||||
auto ret = UnDecorateSymbolName(name.c_str(), buffer, sizeof(buffer) - 1, 0);
|
auto ret = UnDecorateSymbolName(name.c_str(), buffer, sizeof(buffer) - 1, 0);
|
||||||
if(ret == 0) {
|
if(ret == 0) {
|
||||||
|
|||||||
@ -10,7 +10,6 @@
|
|||||||
|
|
||||||
#include "platform/exception_type.hpp"
|
#include "platform/exception_type.hpp"
|
||||||
#include "utils/common.hpp"
|
#include "utils/common.hpp"
|
||||||
#include "options.hpp"
|
|
||||||
|
|
||||||
namespace cpptrace {
|
namespace cpptrace {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|||||||
@ -1,353 +0,0 @@
|
|||||||
#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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -5,8 +5,7 @@
|
|||||||
|
|
||||||
namespace cpptrace {
|
namespace cpptrace {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
// exported for test purposes
|
bool should_absorb_trace_exceptions();
|
||||||
CPPTRACE_EXPORT bool should_absorb_trace_exceptions();
|
|
||||||
bool should_resolve_inlined_calls();
|
bool should_resolve_inlined_calls();
|
||||||
cache_mode get_cache_mode();
|
cache_mode get_cache_mode();
|
||||||
}
|
}
|
||||||
|
|||||||
47
src/platform/dbghelp_syminit_manager.cpp
Normal file
47
src/platform/dbghelp_syminit_manager.cpp
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
#include "platform/platform.hpp"
|
||||||
|
|
||||||
|
#if IS_WINDOWS
|
||||||
|
|
||||||
|
#include "platform/dbghelp_syminit_manager.hpp"
|
||||||
|
|
||||||
|
#include "utils/error.hpp"
|
||||||
|
#include "utils/microfmt.hpp"
|
||||||
|
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#endif
|
||||||
|
#include <windows.h>
|
||||||
|
#include <dbghelp.h>
|
||||||
|
|
||||||
|
namespace cpptrace {
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
dbghelp_syminit_manager::~dbghelp_syminit_manager() {
|
||||||
|
for(auto handle : set) {
|
||||||
|
if(!SymCleanup(handle)) {
|
||||||
|
ASSERT(false, microfmt::format("Cpptrace SymCleanup failed with code {}\n", GetLastError()).c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void dbghelp_syminit_manager::init(HANDLE proc) {
|
||||||
|
if(set.count(proc) == 0) {
|
||||||
|
if(!SymInitialize(proc, NULL, TRUE)) {
|
||||||
|
throw internal_error("SymInitialize failed {}", GetLastError());
|
||||||
|
}
|
||||||
|
set.insert(proc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thread-safety: Must only be called from symbols_with_dbghelp while the dbghelp_lock lock is held
|
||||||
|
dbghelp_syminit_manager& get_syminit_manager() {
|
||||||
|
static dbghelp_syminit_manager syminit_manager;
|
||||||
|
return syminit_manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
22
src/platform/dbghelp_syminit_manager.hpp
Normal file
22
src/platform/dbghelp_syminit_manager.hpp
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#ifndef DBGHELP_SYMINIT_MANAGER_HPP
|
||||||
|
#define DBGHELP_SYMINIT_MANAGER_HPP
|
||||||
|
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
namespace cpptrace {
|
||||||
|
namespace detail {
|
||||||
|
struct dbghelp_syminit_manager {
|
||||||
|
// The set below contains Windows `HANDLE` objects, `void*` is used to avoid
|
||||||
|
// including the (expensive) Windows header here
|
||||||
|
std::unordered_set<void*> set;
|
||||||
|
|
||||||
|
~dbghelp_syminit_manager();
|
||||||
|
void init(void* proc);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Thread-safety: Must only be called from symbols_with_dbghelp while the dbghelp_lock lock is held
|
||||||
|
dbghelp_syminit_manager& get_syminit_manager();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -1,149 +0,0 @@
|
|||||||
#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
|
|
||||||
@ -1,50 +0,0 @@
|
|||||||
#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
|
|
||||||
@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
// libstdc++ and libc++
|
// libstdc++ and libc++
|
||||||
#if defined(CPPTRACE_HAS_CXX_EXCEPTION_TYPE) && (IS_LIBSTDCXX || IS_LIBCXX)
|
#if defined(CPPTRACE_HAS_CXX_EXCEPTION_TYPE) && (IS_LIBSTDCXX || IS_LIBCXX)
|
||||||
#include <typeinfo>
|
|
||||||
#include <cxxabi.h>
|
#include <cxxabi.h>
|
||||||
#include "demangle/demangle.hpp"
|
#include "demangle/demangle.hpp"
|
||||||
#endif
|
#endif
|
||||||
@ -17,7 +16,7 @@ namespace detail {
|
|||||||
inline std::string exception_type_name() {
|
inline std::string exception_type_name() {
|
||||||
#if defined(CPPTRACE_HAS_CXX_EXCEPTION_TYPE) && (IS_LIBSTDCXX || IS_LIBCXX)
|
#if defined(CPPTRACE_HAS_CXX_EXCEPTION_TYPE) && (IS_LIBSTDCXX || IS_LIBCXX)
|
||||||
const std::type_info* t = abi::__cxa_current_exception_type();
|
const std::type_info* t = abi::__cxa_current_exception_type();
|
||||||
return t ? detail::demangle(t->name(), false) : "<unknown>";
|
return t ? detail::demangle(t->name()) : "<unknown>";
|
||||||
#else
|
#else
|
||||||
return "<unknown>";
|
return "<unknown>";
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -135,10 +135,7 @@ namespace detail {
|
|||||||
if(color && line == target_line) {
|
if(color && line == target_line) {
|
||||||
snippet += RESET;
|
snippet += RESET;
|
||||||
}
|
}
|
||||||
snippet += lines[line - original_begin];
|
snippet += lines[line - original_begin] + "\n";
|
||||||
if(line != end) {
|
|
||||||
snippet += '\n';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return snippet;
|
return snippet;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,7 +34,7 @@ namespace libdwarf {
|
|||||||
if(!resolver) {
|
if(!resolver) {
|
||||||
// this seems silly but it's an attempt to not repeatedly try to initialize new dwarf_resolvers if
|
// 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
|
// exceptions are thrown, e.g. if the path doesn't exist
|
||||||
resolver = detail::make_unique<null_resolver>();
|
resolver = std::unique_ptr<null_resolver>(new null_resolver);
|
||||||
resolver = make_dwarf_resolver(object_path);
|
resolver = make_dwarf_resolver(object_path);
|
||||||
}
|
}
|
||||||
return resolver;
|
return resolver;
|
||||||
@ -46,11 +46,11 @@ namespace libdwarf {
|
|||||||
// the path doesn't exist
|
// the path doesn't exist
|
||||||
std::unordered_map<std::string, uint64_t> symbols;
|
std::unordered_map<std::string, uint64_t> symbols;
|
||||||
this->symbols = symbols;
|
this->symbols = symbols;
|
||||||
auto mach_o_object = open_mach_o_cached(object_path);
|
auto obj = mach_o::open_mach_o(object_path);
|
||||||
if(!mach_o_object) {
|
if(!obj) {
|
||||||
return this->symbols.unwrap();
|
return this->symbols.unwrap();
|
||||||
}
|
}
|
||||||
const auto& symbol_table = mach_o_object.unwrap_value()->symbol_table();
|
auto symbol_table = obj.unwrap_value().symbol_table();
|
||||||
if(!symbol_table) {
|
if(!symbol_table) {
|
||||||
return this->symbols.unwrap();
|
return this->symbols.unwrap();
|
||||||
}
|
}
|
||||||
@ -100,7 +100,7 @@ namespace libdwarf {
|
|||||||
uint64_t size;
|
uint64_t size;
|
||||||
std::string name;
|
std::string name;
|
||||||
nullable<uint64_t> target_address; // T(-1) is used as a sentinel
|
nullable<uint64_t> target_address; // T(-1) is used as a sentinel
|
||||||
std::size_t object_index; // index into target_objects
|
std::size_t object_index;
|
||||||
};
|
};
|
||||||
|
|
||||||
class debug_map_resolver : public symbol_resolver {
|
class debug_map_resolver : public symbol_resolver {
|
||||||
@ -110,11 +110,11 @@ namespace libdwarf {
|
|||||||
debug_map_resolver(const std::string& source_object_path) {
|
debug_map_resolver(const std::string& source_object_path) {
|
||||||
// load mach-o
|
// load mach-o
|
||||||
// TODO: Cache somehow?
|
// TODO: Cache somehow?
|
||||||
auto mach_o_object = open_mach_o_cached(source_object_path);
|
auto obj = mach_o::open_mach_o(source_object_path);
|
||||||
if(!mach_o_object) {
|
if(!obj) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mach_o& source_mach = *mach_o_object.unwrap_value();
|
mach_o& source_mach = obj.unwrap_value();
|
||||||
auto source_debug_map = source_mach.get_debug_map();
|
auto source_debug_map = source_mach.get_debug_map();
|
||||||
if(!source_debug_map) {
|
if(!source_debug_map) {
|
||||||
return;
|
return;
|
||||||
@ -137,7 +137,6 @@ namespace libdwarf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// sort for binary lookup later
|
// sort for binary lookup later
|
||||||
// TODO: Redundant?
|
|
||||||
std::sort(
|
std::sort(
|
||||||
symbols.begin(),
|
symbols.begin(),
|
||||||
symbols.end(),
|
symbols.end(),
|
||||||
@ -198,7 +197,7 @@ namespace libdwarf {
|
|||||||
};
|
};
|
||||||
|
|
||||||
std::unique_ptr<symbol_resolver> make_debug_map_resolver(const std::string& object_path) {
|
std::unique_ptr<symbol_resolver> make_debug_map_resolver(const std::string& object_path) {
|
||||||
return detail::make_unique<debug_map_resolver>(object_path);
|
return std::unique_ptr<debug_map_resolver>(new debug_map_resolver(object_path));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,9 +27,10 @@ namespace libdwarf {
|
|||||||
|
|
||||||
[[noreturn]] inline void handle_dwarf_error(Dwarf_Debug dbg, Dwarf_Error error) {
|
[[noreturn]] inline void handle_dwarf_error(Dwarf_Debug dbg, Dwarf_Error error) {
|
||||||
Dwarf_Unsigned ev = dwarf_errno(error);
|
Dwarf_Unsigned ev = dwarf_errno(error);
|
||||||
// dwarf_dealloc_error deallocates the message, attaching to msg is convenient
|
char* msg = dwarf_errmsg(error);
|
||||||
auto msg = raii_wrap(dwarf_errmsg(error), [dbg, error] (char*) { dwarf_dealloc_error(dbg, error); });
|
(void)dbg;
|
||||||
throw internal_error(microfmt::format("dwarf error {} {}", ev, msg.get()));
|
// dwarf_dealloc_error(dbg, error);
|
||||||
|
throw internal_error("dwarf error {} {}", ev, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct die_object {
|
struct die_object {
|
||||||
@ -66,12 +67,8 @@ namespace libdwarf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
~die_object() {
|
~die_object() {
|
||||||
release();
|
|
||||||
}
|
|
||||||
|
|
||||||
void release() {
|
|
||||||
if(die) {
|
if(die) {
|
||||||
dwarf_dealloc_die(exchange(die, nullptr));
|
dwarf_dealloc_die(die);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,15 +76,16 @@ namespace libdwarf {
|
|||||||
|
|
||||||
die_object& operator=(const die_object&) = delete;
|
die_object& operator=(const die_object&) = delete;
|
||||||
|
|
||||||
// dbg doesn't strictly have to be st to null but it helps ensure attempts to use the die_object after this to
|
die_object(die_object&& other) noexcept : dbg(other.dbg), die(other.die) {
|
||||||
// segfault. A valid use otherwise would be moved_from.get_sibling() which would get the next CU.
|
// done for finding mistakes, attempts to use the die_object after this should segfault
|
||||||
die_object(die_object&& other) noexcept
|
// a valid use otherwise would be moved_from.get_sibling() which would get the next CU
|
||||||
: dbg(exchange(other.dbg, nullptr)), die(exchange(other.die, nullptr)) {}
|
other.dbg = nullptr;
|
||||||
|
other.die = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
die_object& operator=(die_object&& other) noexcept {
|
die_object& operator=(die_object&& other) noexcept {
|
||||||
release();
|
std::swap(dbg, other.dbg);
|
||||||
dbg = exchange(other.dbg, nullptr);
|
std::swap(die, other.die);
|
||||||
die = exchange(other.die, nullptr);
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,31 +0,0 @@
|
|||||||
#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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
#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
|
|
||||||
@ -4,13 +4,10 @@
|
|||||||
|
|
||||||
#include <cpptrace/basic.hpp>
|
#include <cpptrace/basic.hpp>
|
||||||
#include "symbols/dwarf/dwarf.hpp" // has dwarf #includes
|
#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 "symbols/symbols.hpp"
|
||||||
#include "utils/common.hpp"
|
#include "utils/common.hpp"
|
||||||
#include "utils/error.hpp"
|
#include "utils/error.hpp"
|
||||||
#include "utils/utils.hpp"
|
#include "utils/utils.hpp"
|
||||||
#include "utils/lru_cache.hpp"
|
|
||||||
#include "platform/path.hpp"
|
#include "platform/path.hpp"
|
||||||
#include "platform/program_name.hpp" // For CPPTRACE_MAX_PATH
|
#include "platform/program_name.hpp" // For CPPTRACE_MAX_PATH
|
||||||
|
|
||||||
@ -22,7 +19,6 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <limits>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
@ -41,6 +37,38 @@ namespace libdwarf {
|
|||||||
constexpr bool dump_dwarf = false;
|
constexpr bool dump_dwarf = false;
|
||||||
constexpr bool trace_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;
|
class dwarf_resolver;
|
||||||
|
|
||||||
// used to describe data from an upstream binary to a resolver for the .dwo
|
// used to describe data from an upstream binary to a resolver for the .dwo
|
||||||
@ -52,24 +80,20 @@ namespace libdwarf {
|
|||||||
|
|
||||||
class dwarf_resolver : public symbol_resolver {
|
class dwarf_resolver : public symbol_resolver {
|
||||||
std::string object_path;
|
std::string object_path;
|
||||||
// dwarf_finish needs to be called after all other dwarf stuff is cleaned up, e.g. `srcfiles` and aranges etc
|
Dwarf_Debug dbg = nullptr;
|
||||||
// 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;
|
bool ok = false;
|
||||||
// .debug_aranges cache
|
// .debug_aranges cache
|
||||||
Dwarf_Arange* aranges = nullptr;
|
Dwarf_Arange* aranges = nullptr;
|
||||||
Dwarf_Signed arange_count = 0;
|
Dwarf_Signed arange_count = 0;
|
||||||
// Map from CU -> Line context
|
// Map from CU -> Line context
|
||||||
lru_cache<Dwarf_Off, line_table_info> line_tables{get_dwarf_resolver_line_table_cache_size()};
|
std::unordered_map<Dwarf_Off, line_table_info> line_tables;
|
||||||
// Map from CU -> Sorted subprograms vector
|
// Map from CU -> Sorted subprograms vector
|
||||||
std::unordered_map<Dwarf_Off, die_cache<monostate>> subprograms_cache;
|
std::unordered_map<Dwarf_Off, std::vector<subprogram_entry>> subprograms_cache;
|
||||||
// Vector of ranges and their corresponding CU offsets
|
// Vector of ranges and their corresponding CU offsets
|
||||||
// data stored for each cache entry is a Dwarf_Half dwversion
|
std::vector<cu_entry> cu_cache;
|
||||||
die_cache<Dwarf_Half> cu_cache;
|
|
||||||
bool generated_cu_cache = false;
|
bool generated_cu_cache = false;
|
||||||
// Map from CU -> {srcfiles, count}
|
// Map from CU -> {srcfiles, count}
|
||||||
std::unordered_map<Dwarf_Off, srcfiles> srcfiles_cache;
|
std::unordered_map<Dwarf_Off, std::pair<char**, Dwarf_Signed>> srcfiles_cache;
|
||||||
// Map from CU -> split full cu resolver
|
// Map from CU -> split full cu resolver
|
||||||
std::unordered_map<Dwarf_Off, std::unique_ptr<dwarf_resolver>> split_full_cu_resolvers;
|
std::unordered_map<Dwarf_Off, std::unique_ptr<dwarf_resolver>> split_full_cu_resolvers;
|
||||||
// info for resolving a dwo object
|
// info for resolving a dwo object
|
||||||
@ -127,12 +151,12 @@ namespace libdwarf {
|
|||||||
if(result.is_error()) {
|
if(result.is_error()) {
|
||||||
result.drop_error();
|
result.drop_error();
|
||||||
} else if(result.unwrap_value()) {
|
} else if(result.unwrap_value()) {
|
||||||
auto mach_o_object = open_mach_o_cached(object_path);
|
auto obj = mach_o::open_mach_o(object_path);
|
||||||
if(!mach_o_object) {
|
if(!obj) {
|
||||||
ok = false;
|
ok = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
universal_number = mach_o_object.unwrap_value()->get_fat_index();
|
universal_number = obj.unwrap_value().get_fat_index();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -142,7 +166,6 @@ namespace libdwarf {
|
|||||||
if(use_buffer) {
|
if(use_buffer) {
|
||||||
buffer = std::unique_ptr<char[]>(new char[CPPTRACE_MAX_PATH]);
|
buffer = std::unique_ptr<char[]>(new char[CPPTRACE_MAX_PATH]);
|
||||||
}
|
}
|
||||||
dwarf_set_de_alloc_flag(0);
|
|
||||||
auto ret = wrap(
|
auto ret = wrap(
|
||||||
dwarf_init_path_a,
|
dwarf_init_path_a,
|
||||||
object_path.c_str(),
|
object_path.c_str(),
|
||||||
@ -152,7 +175,7 @@ namespace libdwarf {
|
|||||||
universal_number,
|
universal_number,
|
||||||
nullptr,
|
nullptr,
|
||||||
nullptr,
|
nullptr,
|
||||||
&dbg.get()
|
&dbg
|
||||||
);
|
);
|
||||||
if(ret == DW_DLV_OK) {
|
if(ret == DW_DLV_OK) {
|
||||||
ok = true;
|
ok = true;
|
||||||
@ -168,7 +191,7 @@ namespace libdwarf {
|
|||||||
VERIFY(wrap(dwarf_set_tied_dbg, dbg, skeleton.unwrap().resolver.dbg) == DW_DLV_OK);
|
VERIFY(wrap(dwarf_set_tied_dbg, dbg, skeleton.unwrap().resolver.dbg) == DW_DLV_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(ok && !get_dwarf_resolver_disable_aranges()) {
|
if(ok) {
|
||||||
// Check for .debug_aranges for fast lookup
|
// Check for .debug_aranges for fast lookup
|
||||||
wrap(dwarf_get_aranges, dbg, &aranges, &arange_count);
|
wrap(dwarf_get_aranges, dbg, &aranges, &arange_count);
|
||||||
}
|
}
|
||||||
@ -176,13 +199,23 @@ namespace libdwarf {
|
|||||||
|
|
||||||
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
|
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
|
||||||
~dwarf_resolver() override {
|
~dwarf_resolver() override {
|
||||||
|
// TODO: Maybe redundant since dwarf_finish(dbg); will clean up the line stuff anyway but may as well just
|
||||||
|
// for thoroughness
|
||||||
|
for(auto& entry : line_tables) {
|
||||||
|
dwarf_srclines_dealloc_b(entry.second.line_context);
|
||||||
|
}
|
||||||
|
for(auto& entry : srcfiles_cache) {
|
||||||
|
dwarf_dealloc(dbg, entry.second.first, DW_DLA_LIST);
|
||||||
|
}
|
||||||
|
// subprograms_cache needs to be destroyed before dbg otherwise there will be another use after free
|
||||||
|
subprograms_cache.clear();
|
||||||
|
split_full_cu_resolvers.clear();
|
||||||
|
skeleton.reset();
|
||||||
if(aranges) {
|
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);
|
dwarf_dealloc(dbg, aranges, DW_DLA_LIST);
|
||||||
}
|
}
|
||||||
|
cu_cache.clear();
|
||||||
|
dwarf_finish(dbg);
|
||||||
}
|
}
|
||||||
|
|
||||||
dwarf_resolver(const dwarf_resolver&) = delete;
|
dwarf_resolver(const dwarf_resolver&) = delete;
|
||||||
@ -244,32 +277,30 @@ namespace libdwarf {
|
|||||||
walk_compilation_units([this] (const die_object& cu_die) {
|
walk_compilation_units([this] (const die_object& cu_die) {
|
||||||
Dwarf_Half offset_size = 0;
|
Dwarf_Half offset_size = 0;
|
||||||
Dwarf_Half dwversion = 0;
|
Dwarf_Half dwversion = 0;
|
||||||
VERIFY(dwarf_get_version_of_die(cu_die.get(), &dwversion, &offset_size) == DW_DLV_OK);
|
dwarf_get_version_of_die(cu_die.get(), &dwversion, &offset_size);
|
||||||
if(skeleton) {
|
if(skeleton) {
|
||||||
// NOTE: If we have a corresponding skeleton, we assume we have one CU matching the skeleton CU
|
// 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
|
// Precedence for this assumption is https://dwarfstd.org/doc/DWARF5.pdf#subsection.3.1.3
|
||||||
// TODO: Also assuming same dwversion
|
// TODO: Also assuming same dwversion
|
||||||
const auto& skeleton_cu = skeleton.unwrap().cu_die;
|
const auto& skeleton_cu = skeleton.unwrap().cu_die;
|
||||||
auto ranges_vec = skeleton_cu.get_rangelist_entries(skeleton_cu, dwversion);
|
auto ranges_vec = skeleton_cu.get_rangelist_entries(skeleton_cu, dwversion);
|
||||||
if(!ranges_vec.empty()) {
|
for(auto range : ranges_vec) {
|
||||||
auto cu_die_handle = cu_cache.add_die(cu_die.clone());
|
// TODO: Reduce cloning here
|
||||||
for(auto range : ranges_vec) {
|
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;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
auto ranges_vec = cu_die.get_rangelist_entries(cu_die, dwversion);
|
auto ranges_vec = cu_die.get_rangelist_entries(cu_die, dwversion);
|
||||||
if(!ranges_vec.empty()) {
|
for(auto range : ranges_vec) {
|
||||||
auto cu_die_handle = cu_cache.add_die(cu_die.clone());
|
// TODO: Reduce cloning here
|
||||||
for(auto range : ranges_vec) {
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
cu_cache.finalize();
|
std::sort(cu_cache.begin(), cu_cache.end(), [] (const cu_entry& a, const cu_entry& b) {
|
||||||
|
return a.low < b.low;
|
||||||
|
});
|
||||||
generated_cu_cache = true;
|
generated_cu_cache = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -312,11 +343,11 @@ namespace libdwarf {
|
|||||||
char** dw_srcfiles;
|
char** dw_srcfiles;
|
||||||
Dwarf_Signed dw_filecount;
|
Dwarf_Signed dw_filecount;
|
||||||
VERIFY(wrap(dwarf_srcfiles, cu_die.get(), &dw_srcfiles, &dw_filecount) == DW_DLV_OK);
|
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) {
|
if(Dwarf_Signed(file_i) < dw_filecount) {
|
||||||
// dwarf is using 1-indexing
|
// dwarf is using 1-indexing
|
||||||
filename = srcfiles.get(file_i);
|
filename = dw_srcfiles[file_i];
|
||||||
}
|
}
|
||||||
|
dwarf_dealloc(cu_die.dbg, dw_srcfiles, DW_DLA_LIST);
|
||||||
} else {
|
} else {
|
||||||
auto off = cu_die.get_global_offset();
|
auto off = cu_die.get_global_offset();
|
||||||
auto it = srcfiles_cache.find(off);
|
auto it = srcfiles_cache.find(off);
|
||||||
@ -324,11 +355,13 @@ namespace libdwarf {
|
|||||||
char** dw_srcfiles;
|
char** dw_srcfiles;
|
||||||
Dwarf_Signed dw_filecount;
|
Dwarf_Signed dw_filecount;
|
||||||
VERIFY(wrap(dwarf_srcfiles, cu_die.get(), &dw_srcfiles, &dw_filecount) == DW_DLV_OK);
|
VERIFY(wrap(dwarf_srcfiles, cu_die.get(), &dw_srcfiles, &dw_filecount) == DW_DLV_OK);
|
||||||
it = srcfiles_cache.insert(it, {off, srcfiles{cu_die.dbg, dw_srcfiles, dw_filecount}});
|
it = srcfiles_cache.insert(it, {off, {dw_srcfiles, dw_filecount}});
|
||||||
}
|
}
|
||||||
if(file_i < it->second.count()) {
|
char** dw_srcfiles = it->second.first;
|
||||||
|
Dwarf_Signed dw_filecount = it->second.second;
|
||||||
|
if(Dwarf_Signed(file_i) < dw_filecount) {
|
||||||
// dwarf is using 1-indexing
|
// dwarf is using 1-indexing
|
||||||
filename = it->second.get(file_i);
|
filename = dw_srcfiles[file_i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return filename;
|
return filename;
|
||||||
@ -364,7 +397,7 @@ namespace libdwarf {
|
|||||||
if(file_i) {
|
if(file_i) {
|
||||||
// for dwarf 2, 3, 4, and experimental line table version 0xfe06 1-indexing is used
|
// for dwarf 2, 3, 4, and experimental line table version 0xfe06 1-indexing is used
|
||||||
// for dwarf 5 0-indexing is used
|
// for dwarf 5 0-indexing is used
|
||||||
optional<line_table_info&> line_table_opt;
|
optional<std::reference_wrapper<line_table_info>> line_table_opt;
|
||||||
if(skeleton) {
|
if(skeleton) {
|
||||||
line_table_opt = skeleton.unwrap().resolver.get_line_table(
|
line_table_opt = skeleton.unwrap().resolver.get_line_table(
|
||||||
skeleton.unwrap().cu_die
|
skeleton.unwrap().cu_die
|
||||||
@ -373,7 +406,7 @@ namespace libdwarf {
|
|||||||
line_table_opt = get_line_table(cu_die);
|
line_table_opt = get_line_table(cu_die);
|
||||||
}
|
}
|
||||||
if(line_table_opt) {
|
if(line_table_opt) {
|
||||||
auto& line_table = line_table_opt.unwrap();
|
auto& line_table = line_table_opt.unwrap().get();
|
||||||
if(line_table.version != 5) {
|
if(line_table.version != 5) {
|
||||||
if(file_i.unwrap() == 0) {
|
if(file_i.unwrap() == 0) {
|
||||||
file_i.reset(); // 0 means no name to be found
|
file_i.reset(); // 0 means no name to be found
|
||||||
@ -498,28 +531,26 @@ namespace libdwarf {
|
|||||||
const die_object& cu_die,
|
const die_object& cu_die,
|
||||||
const die_object& die,
|
const die_object& die,
|
||||||
Dwarf_Half dwversion,
|
Dwarf_Half dwversion,
|
||||||
die_cache<monostate>& subprogram_cache
|
std::vector<subprogram_entry>& vec
|
||||||
) {
|
) {
|
||||||
walk_die_list(
|
walk_die_list(
|
||||||
die,
|
die,
|
||||||
[this, &cu_die, dwversion, &subprogram_cache] (const die_object& die) {
|
[this, &cu_die, dwversion, &vec] (const die_object& die) {
|
||||||
switch(die.get_tag()) {
|
switch(die.get_tag()) {
|
||||||
case DW_TAG_subprogram:
|
case DW_TAG_subprogram:
|
||||||
{
|
{
|
||||||
auto ranges_vec = die.get_rangelist_entries(cu_die, dwversion);
|
auto ranges_vec = die.get_rangelist_entries(cu_die, dwversion);
|
||||||
// TODO: Feels super inefficient and some day should maybe use an interval tree.
|
// TODO: Feels super inefficient and some day should maybe use an interval tree.
|
||||||
if(!ranges_vec.empty()) {
|
for(auto range : ranges_vec) {
|
||||||
auto die_handle = subprogram_cache.add_die(die.clone());
|
// TODO: Reduce cloning here
|
||||||
for(auto range : ranges_vec) {
|
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
|
// Walk children to get things like lambdas
|
||||||
// TODO: Somehow find a way to get better names here? For gcc it's just "operator()"
|
// TODO: Somehow find a way to get better names here? For gcc it's just "operator()"
|
||||||
// On clang it's better
|
// On clang it's better
|
||||||
auto child = die.get_child();
|
auto child = die.get_child();
|
||||||
if(child) {
|
if(child) {
|
||||||
preprocess_subprograms(cu_die, child, dwversion, subprogram_cache);
|
preprocess_subprograms(cu_die, child, dwversion, vec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -532,7 +563,7 @@ namespace libdwarf {
|
|||||||
{
|
{
|
||||||
auto child = die.get_child();
|
auto child = die.get_child();
|
||||||
if(child) {
|
if(child) {
|
||||||
preprocess_subprograms(cu_die, child, dwversion, subprogram_cache);
|
preprocess_subprograms(cu_die, child, dwversion, vec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -562,32 +593,41 @@ namespace libdwarf {
|
|||||||
auto it = subprograms_cache.find(off);
|
auto it = subprograms_cache.find(off);
|
||||||
if(it == subprograms_cache.end()) {
|
if(it == subprograms_cache.end()) {
|
||||||
// TODO: Refactor. Do the sort in the preprocess function and return the vec directly.
|
// TODO: Refactor. Do the sort in the preprocess function and return the vec directly.
|
||||||
die_cache<monostate> subprogram_cache;
|
std::vector<subprogram_entry> vec;
|
||||||
preprocess_subprograms(cu_die, cu_die, dwversion, subprogram_cache);
|
preprocess_subprograms(cu_die, cu_die, dwversion, vec);
|
||||||
subprogram_cache.finalize();
|
std::sort(vec.begin(), vec.end(), [] (const subprogram_entry& a, const subprogram_entry& b) {
|
||||||
subprograms_cache.emplace(off, std::move(subprogram_cache));
|
return a.low < b.low;
|
||||||
|
});
|
||||||
|
subprograms_cache.emplace(off, std::move(vec));
|
||||||
it = subprograms_cache.find(off);
|
it = subprograms_cache.find(off);
|
||||||
}
|
}
|
||||||
const auto& subprogram_cache = it->second;
|
auto& vec = it->second;
|
||||||
auto maybe_die = subprogram_cache.lookup(pc);
|
auto vec_it = first_less_than_or_equal(
|
||||||
|
vec.begin(),
|
||||||
|
vec.end(),
|
||||||
|
pc,
|
||||||
|
[] (Dwarf_Addr pc, const subprogram_entry& entry) {
|
||||||
|
return pc < entry.low;
|
||||||
|
}
|
||||||
|
);
|
||||||
// If the vector has been empty this can happen
|
// If the vector has been empty this can happen
|
||||||
if(maybe_die.has_value()) {
|
if(vec_it != vec.end()) {
|
||||||
if(maybe_die.unwrap().pc_in_die(cu_die, dwversion, pc)) {
|
if(vec_it->die.pc_in_die(cu_die, dwversion, pc)) {
|
||||||
frame.symbol = retrieve_symbol_for_subprogram(cu_die, maybe_die.unwrap(), pc, dwversion, inlines);
|
frame.symbol = retrieve_symbol_for_subprogram(cu_die, vec_it->die, pc, dwversion, inlines);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ASSERT(subprogram_cache.ranges_count() == 0, "subprogram_cache.ranges_count() should be 0?");
|
ASSERT(vec.size() == 0, "Vec should be empty?");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns a reference to a CU's line table, may be invalidated if the line_tables map is modified
|
// 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
|
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
|
||||||
optional<line_table_info&> get_line_table(const die_object& cu_die) {
|
optional<std::reference_wrapper<line_table_info>> get_line_table(const die_object& cu_die) {
|
||||||
auto off = cu_die.get_global_offset();
|
auto off = cu_die.get_global_offset();
|
||||||
auto res = line_tables.maybe_get(off);
|
auto it = line_tables.find(off);
|
||||||
if(res) {
|
if(it != line_tables.end()) {
|
||||||
return res;
|
return it->second;
|
||||||
} else {
|
} else {
|
||||||
Dwarf_Unsigned version;
|
Dwarf_Unsigned version;
|
||||||
Dwarf_Small table_count;
|
Dwarf_Small table_count;
|
||||||
@ -642,6 +682,24 @@ namespace libdwarf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
line = line_buffer[j - 1];
|
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({
|
line_entries.push_back({
|
||||||
low_addr,
|
low_addr,
|
||||||
line
|
line
|
||||||
@ -654,7 +712,8 @@ namespace libdwarf {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return line_tables.insert(off, line_table_info{version, line_context, std::move(line_entries)});
|
it = line_tables.insert({off, {version, line_context, std::move(line_entries)}}).first;
|
||||||
|
return it->second;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -672,7 +731,7 @@ namespace libdwarf {
|
|||||||
if(!table_info_opt) {
|
if(!table_info_opt) {
|
||||||
return; // failing silently for now
|
return; // failing silently for now
|
||||||
}
|
}
|
||||||
auto& table_info = table_info_opt.unwrap();
|
auto& table_info = table_info_opt.unwrap().get();
|
||||||
if(get_cache_mode() == cache_mode::prioritize_speed) {
|
if(get_cache_mode() == cache_mode::prioritize_speed) {
|
||||||
// Lookup in the table
|
// Lookup in the table
|
||||||
auto& line_entries = table_info.line_entries;
|
auto& line_entries = table_info.line_entries;
|
||||||
@ -857,13 +916,17 @@ namespace libdwarf {
|
|||||||
} else {
|
} else {
|
||||||
lazy_generate_cu_cache();
|
lazy_generate_cu_cache();
|
||||||
// look up the cu
|
// look up the cu
|
||||||
auto res = cu_cache.lookup(pc);
|
auto vec_it = first_less_than_or_equal(
|
||||||
// res can be nullopt if the cu_cache vector is empty
|
cu_cache.begin(),
|
||||||
// It can also happen for something like _start, where there is a cached CU for the object but
|
cu_cache.end(),
|
||||||
// _start is outside of the CU's PC range
|
pc,
|
||||||
if(res) {
|
[] (Dwarf_Addr pc, const cu_entry& entry) {
|
||||||
const auto& die = res.unwrap().die;
|
return pc < entry.low;
|
||||||
const auto dwversion = res.unwrap().data;
|
}
|
||||||
|
);
|
||||||
|
// 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()) {
|
||||||
// TODO: Cache the range list?
|
// TODO: Cache the range list?
|
||||||
// NOTE: If we have a corresponding skeleton, we assume we have one CU matching the skeleton CU
|
// NOTE: If we have a corresponding skeleton, we assume we have one CU matching the skeleton CU
|
||||||
if(
|
if(
|
||||||
@ -874,10 +937,14 @@ namespace libdwarf {
|
|||||||
skeleton.unwrap().dwversion,
|
skeleton.unwrap().dwversion,
|
||||||
pc
|
pc
|
||||||
)
|
)
|
||||||
) || die.pc_in_die(die, dwversion, pc)
|
) || vec_it->die.pc_in_die(vec_it->die, vec_it->dwversion, pc)
|
||||||
) {
|
) {
|
||||||
return cu_info{maybe_owned_die_object::ref(die), dwversion};
|
return cu_info{maybe_owned_die_object::ref(vec_it->die), vec_it->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;
|
return nullopt;
|
||||||
}
|
}
|
||||||
@ -932,7 +999,12 @@ namespace libdwarf {
|
|||||||
if(it == split_full_cu_resolvers.end()) {
|
if(it == split_full_cu_resolvers.end()) {
|
||||||
it = split_full_cu_resolvers.emplace(
|
it = split_full_cu_resolvers.emplace(
|
||||||
off,
|
off,
|
||||||
detail::make_unique<dwarf_resolver>(path, skeleton_info{cu_die.clone(), dwversion, *this})
|
std::unique_ptr<dwarf_resolver>(
|
||||||
|
new dwarf_resolver(
|
||||||
|
path,
|
||||||
|
skeleton_info{cu_die.clone(), dwversion, *this}
|
||||||
|
)
|
||||||
|
)
|
||||||
).first;
|
).first;
|
||||||
}
|
}
|
||||||
res = it->second->resolve_frame(object_frame_info);
|
res = it->second->resolve_frame(object_frame_info);
|
||||||
@ -1010,7 +1082,7 @@ namespace libdwarf {
|
|||||||
};
|
};
|
||||||
|
|
||||||
std::unique_ptr<symbol_resolver> make_dwarf_resolver(const std::string& object_path) {
|
std::unique_ptr<symbol_resolver> make_dwarf_resolver(const std::string& object_path) {
|
||||||
return detail::make_unique<dwarf_resolver>(object_path);
|
return std::unique_ptr<dwarf_resolver>(new dwarf_resolver(object_path));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,208 +0,0 @@
|
|||||||
#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
|
|
||||||
@ -83,8 +83,6 @@ 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) {
|
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)
|
#if defined(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) && defined(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP)
|
||||||
std::vector<stacktrace_frame> trace = libdwarf::resolve_frames(frames);
|
std::vector<stacktrace_frame> trace = libdwarf::resolve_frames(frames);
|
||||||
|
|||||||
@ -22,7 +22,6 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "binary/object.hpp"
|
#include "binary/object.hpp"
|
||||||
#include "options.hpp"
|
|
||||||
|
|
||||||
namespace cpptrace {
|
namespace cpptrace {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|||||||
@ -2,13 +2,12 @@
|
|||||||
|
|
||||||
#include <cpptrace/basic.hpp>
|
#include <cpptrace/basic.hpp>
|
||||||
#include "symbols/symbols.hpp"
|
#include "symbols/symbols.hpp"
|
||||||
#include "platform/dbghelp_utils.hpp"
|
#include "platform/dbghelp_syminit_manager.hpp"
|
||||||
#include "binary/object.hpp"
|
#include "binary/object.hpp"
|
||||||
#include "utils/common.hpp"
|
#include "utils/common.hpp"
|
||||||
#include "utils/error.hpp"
|
#include "utils/error.hpp"
|
||||||
#include "utils/utils.hpp"
|
|
||||||
#include "options.hpp"
|
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
#include <regex>
|
#include <regex>
|
||||||
#include <system_error>
|
#include <system_error>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@ -240,9 +239,6 @@ namespace dbghelp {
|
|||||||
std::size_t sz = sizeof(TI_FINDCHILDREN_PARAMS) +
|
std::size_t sz = sizeof(TI_FINDCHILDREN_PARAMS) +
|
||||||
(n_children) * sizeof(TI_FINDCHILDREN_PARAMS::ChildId[0]);
|
(n_children) * sizeof(TI_FINDCHILDREN_PARAMS::ChildId[0]);
|
||||||
TI_FINDCHILDREN_PARAMS* children = (TI_FINDCHILDREN_PARAMS*) new char[sz];
|
TI_FINDCHILDREN_PARAMS* children = (TI_FINDCHILDREN_PARAMS*) new char[sz];
|
||||||
auto guard = scope_exit([&] {
|
|
||||||
delete[] (char*) children;
|
|
||||||
});
|
|
||||||
children->Start = 0;
|
children->Start = 0;
|
||||||
children->Count = n_children;
|
children->Count = n_children;
|
||||||
if(
|
if(
|
||||||
@ -268,6 +264,7 @@ namespace dbghelp {
|
|||||||
extent += (i == 0 ? "" : ", ") + resolve_type(children->ChildId[i], proc, modbase);
|
extent += (i == 0 ? "" : ", ") + resolve_type(children->ChildId[i], proc, modbase);
|
||||||
}
|
}
|
||||||
extent += ")";
|
extent += ")";
|
||||||
|
delete[] (char*) children;
|
||||||
return {return_type.base, extent + return_type.extent};
|
return {return_type.base, extent + return_type.extent};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -327,6 +324,8 @@ namespace dbghelp {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::recursive_mutex dbghelp_lock;
|
||||||
|
|
||||||
// TODO: Handle backtrace_pcinfo calling the callback multiple times on inlined functions
|
// TODO: Handle backtrace_pcinfo calling the callback multiple times on inlined functions
|
||||||
stacktrace_frame resolve_frame(HANDLE proc, frame_ptr addr) {
|
stacktrace_frame resolve_frame(HANDLE proc, frame_ptr addr) {
|
||||||
// The get_frame_object_info() ends up being inexpensive, at on my machine
|
// The get_frame_object_info() ends up being inexpensive, at on my machine
|
||||||
@ -336,8 +335,7 @@ namespace dbghelp {
|
|||||||
// get_frame_object_info() 0.001-0.002 ms 0.0003-0.0006 ms
|
// get_frame_object_info() 0.001-0.002 ms 0.0003-0.0006 ms
|
||||||
// At some point it might make sense to make an option to control this.
|
// At some point it might make sense to make an option to control this.
|
||||||
auto object_frame = get_frame_object_info(addr);
|
auto object_frame = get_frame_object_info(addr);
|
||||||
// Dbghelp is is single-threaded, so acquire a lock.
|
const std::lock_guard<std::recursive_mutex> lock(dbghelp_lock); // all dbghelp functions are not thread safe
|
||||||
auto lock = get_dbghelp_lock();
|
|
||||||
alignas(SYMBOL_INFO) char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
|
alignas(SYMBOL_INFO) char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
|
||||||
SYMBOL_INFO* symbol = (SYMBOL_INFO*)buffer;
|
SYMBOL_INFO* symbol = (SYMBOL_INFO*)buffer;
|
||||||
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
|
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
|
||||||
@ -422,18 +420,23 @@ namespace dbghelp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<frame_ptr>& frames) {
|
std::vector<stacktrace_frame> resolve_frames(const std::vector<frame_ptr>& frames) {
|
||||||
// Dbghelp is is single-threaded, so acquire a lock.
|
const std::lock_guard<std::recursive_mutex> lock(dbghelp_lock); // all dbghelp functions are not thread safe
|
||||||
auto lock = get_dbghelp_lock();
|
|
||||||
std::vector<stacktrace_frame> trace;
|
std::vector<stacktrace_frame> trace;
|
||||||
trace.reserve(frames.size());
|
trace.reserve(frames.size());
|
||||||
|
|
||||||
// TODO: When does this need to be called? Can it be moved to the symbolizer?
|
// TODO: When does this need to be called? Can it be moved to the symbolizer?
|
||||||
SymSetOptions(SYMOPT_ALLOW_ABSOLUTE_SYMBOLS);
|
SymSetOptions(SYMOPT_ALLOW_ABSOLUTE_SYMBOLS);
|
||||||
|
HANDLE proc = GetCurrentProcess();
|
||||||
auto syminit_info = ensure_syminit();
|
if(get_cache_mode() == cache_mode::prioritize_speed) {
|
||||||
|
get_syminit_manager().init(proc);
|
||||||
|
} else {
|
||||||
|
if(!SymInitialize(proc, NULL, TRUE)) {
|
||||||
|
throw internal_error("SymInitialize failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
for(const auto frame : frames) {
|
for(const auto frame : frames) {
|
||||||
try {
|
try {
|
||||||
trace.push_back(resolve_frame(syminit_info.get_process_handle() , frame));
|
trace.push_back(resolve_frame(proc, frame));
|
||||||
} catch(...) { // NOSONAR
|
} catch(...) { // NOSONAR
|
||||||
if(!detail::should_absorb_trace_exceptions()) {
|
if(!detail::should_absorb_trace_exceptions()) {
|
||||||
throw;
|
throw;
|
||||||
@ -443,6 +446,11 @@ namespace dbghelp {
|
|||||||
trace.push_back(entry);
|
trace.push_back(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(get_cache_mode() != cache_mode::prioritize_speed) {
|
||||||
|
if(!SymCleanup(proc)) {
|
||||||
|
throw internal_error("SymCleanup failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
return trace;
|
return trace;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,6 @@
|
|||||||
#include "platform/program_name.hpp"
|
#include "platform/program_name.hpp"
|
||||||
#include "utils/error.hpp"
|
#include "utils/error.hpp"
|
||||||
#include "utils/common.hpp"
|
#include "utils/common.hpp"
|
||||||
#include "options.hpp"
|
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
|||||||
@ -6,8 +6,6 @@
|
|||||||
#include "dwarf/resolver.hpp"
|
#include "dwarf/resolver.hpp"
|
||||||
#include "utils/common.hpp"
|
#include "utils/common.hpp"
|
||||||
#include "utils/utils.hpp"
|
#include "utils/utils.hpp"
|
||||||
#include "binary/elf.hpp"
|
|
||||||
#include "binary/mach-o.hpp"
|
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
@ -94,12 +92,6 @@ namespace libdwarf {
|
|||||||
for(const auto& group : collate_frames(frames, trace)) {
|
for(const auto& group : collate_frames(frames, trace)) {
|
||||||
try {
|
try {
|
||||||
const auto& object_name = group.first;
|
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);
|
auto resolver = get_resolver(object_name);
|
||||||
for(const auto& entry : group.second) {
|
for(const auto& entry : group.second) {
|
||||||
const auto& dlframe = entry.first.get();
|
const auto& dlframe = entry.first.get();
|
||||||
@ -114,13 +106,6 @@ namespace libdwarf {
|
|||||||
throw;
|
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
|
} catch(...) { // NOSONAR
|
||||||
if(!should_absorb_trace_exceptions()) {
|
if(!should_absorb_trace_exceptions()) {
|
||||||
|
|||||||
@ -4,9 +4,10 @@
|
|||||||
#include "unwind/unwind.hpp"
|
#include "unwind/unwind.hpp"
|
||||||
#include "utils/common.hpp"
|
#include "utils/common.hpp"
|
||||||
#include "utils/utils.hpp"
|
#include "utils/utils.hpp"
|
||||||
#include "platform/dbghelp_utils.hpp"
|
#include "platform/dbghelp_syminit_manager.hpp"
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <mutex>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
@ -95,19 +96,27 @@ namespace detail {
|
|||||||
std::vector<frame_ptr> trace;
|
std::vector<frame_ptr> trace;
|
||||||
|
|
||||||
// Dbghelp is is single-threaded, so acquire a lock.
|
// Dbghelp is is single-threaded, so acquire a lock.
|
||||||
auto lock = get_dbghelp_lock();
|
static std::mutex mutex;
|
||||||
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
// For some reason SymInitialize must be called before StackWalk64
|
// For some reason SymInitialize must be called before StackWalk64
|
||||||
// Note that the code assumes that
|
// Note that the code assumes that
|
||||||
// SymInitialize( GetCurrentProcess(), NULL, TRUE ) has
|
// SymInitialize( GetCurrentProcess(), NULL, TRUE ) has
|
||||||
// already been called.
|
// already been called.
|
||||||
//
|
//
|
||||||
auto syminit_info = ensure_syminit();
|
HANDLE proc = GetCurrentProcess();
|
||||||
HANDLE thread = GetCurrentThread();
|
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) {
|
while(trace.size() < max_depth) {
|
||||||
if(
|
if(
|
||||||
!StackWalk64(
|
!StackWalk64(
|
||||||
machine_type,
|
machine_type,
|
||||||
syminit_info.get_process_handle(),
|
proc,
|
||||||
thread,
|
thread,
|
||||||
&frame,
|
&frame,
|
||||||
machine_type == IMAGE_FILE_MACHINE_I386 ? NULL : &context,
|
machine_type == IMAGE_FILE_MACHINE_I386 ? NULL : &context,
|
||||||
@ -135,6 +144,11 @@ namespace detail {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(get_cache_mode() != cache_mode::prioritize_speed) {
|
||||||
|
if(!SymCleanup(proc)) {
|
||||||
|
throw internal_error("SymCleanup failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
return trace;
|
return trace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
#include <cpptrace/utils.hpp>
|
#include <cpptrace/utils.hpp>
|
||||||
#include <cpptrace/exceptions.hpp>
|
#include <cpptrace/exceptions.hpp>
|
||||||
#include <cpptrace/formatting.hpp>
|
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
@ -8,11 +7,10 @@
|
|||||||
#include "snippets/snippet.hpp"
|
#include "snippets/snippet.hpp"
|
||||||
#include "utils/utils.hpp"
|
#include "utils/utils.hpp"
|
||||||
#include "platform/exception_type.hpp"
|
#include "platform/exception_type.hpp"
|
||||||
#include "options.hpp"
|
|
||||||
|
|
||||||
namespace cpptrace {
|
namespace cpptrace {
|
||||||
std::string demangle(const std::string& name) {
|
std::string demangle(const std::string& name) {
|
||||||
return detail::demangle(name, false);
|
return detail::demangle(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string get_snippet(const std::string& path, std::size_t line, std::size_t context_size, bool color) {
|
std::string get_snippet(const std::string& path, std::size_t line, std::size_t context_size, bool color) {
|
||||||
@ -27,17 +25,14 @@ namespace cpptrace {
|
|||||||
extern const int stdout_fileno = detail::fileno(stdout);
|
extern const int stdout_fileno = detail::fileno(stdout);
|
||||||
extern const int stderr_fileno = detail::fileno(stderr);
|
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() {
|
CPPTRACE_FORCE_NO_INLINE void print_terminate_trace() {
|
||||||
try { // try/catch can never be hit but it's needed to prevent TCO
|
try { // try/catch can never be hit but it's needed to prevent TCO
|
||||||
detail::get_terminate_formatter().print(std::cerr, generate_trace(1));
|
generate_trace(1).print(
|
||||||
|
std::cerr,
|
||||||
|
isatty(stderr_fileno),
|
||||||
|
true,
|
||||||
|
"Stack trace to reach terminate handler (most recent call first):"
|
||||||
|
);
|
||||||
} catch(...) {
|
} catch(...) {
|
||||||
if(!detail::should_absorb_trace_exceptions()) {
|
if(!detail::should_absorb_trace_exceptions()) {
|
||||||
throw;
|
throw;
|
||||||
@ -45,7 +40,7 @@ namespace cpptrace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]] void MSVC_CDECL terminate_handler() {
|
[[noreturn]] void terminate_handler() {
|
||||||
// TODO: Support std::nested_exception?
|
// TODO: Support std::nested_exception?
|
||||||
try {
|
try {
|
||||||
auto ptr = std::current_exception();
|
auto ptr = std::current_exception();
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
#include <cpptrace/basic.hpp>
|
#include <cpptrace/basic.hpp>
|
||||||
|
|
||||||
#include "platform/platform.hpp"
|
#include "platform/platform.hpp"
|
||||||
|
#include "options.hpp"
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
@ -24,19 +25,6 @@
|
|||||||
#define NODISCARD
|
#define NODISCARD
|
||||||
#endif
|
#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 cpptrace {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
static const stacktrace_frame null_frame {
|
static const stacktrace_frame null_frame {
|
||||||
|
|||||||
@ -1,108 +0,0 @@
|
|||||||
#ifndef LRU_CACHE_HPP
|
|
||||||
#define LRU_CACHE_HPP
|
|
||||||
|
|
||||||
#include "utils/error.hpp"
|
|
||||||
#include "utils/optional.hpp"
|
|
||||||
|
|
||||||
#include <list>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
namespace cpptrace {
|
|
||||||
namespace detail {
|
|
||||||
template<typename K, typename V>
|
|
||||||
class lru_cache {
|
|
||||||
struct kvp {
|
|
||||||
K key;
|
|
||||||
V value;
|
|
||||||
};
|
|
||||||
using list_type = std::list<kvp>;
|
|
||||||
using list_iterator = typename list_type::iterator;
|
|
||||||
mutable list_type lru;
|
|
||||||
std::unordered_map<K, list_iterator> map;
|
|
||||||
optional<std::size_t> max_size;
|
|
||||||
|
|
||||||
public:
|
|
||||||
lru_cache() = default;
|
|
||||||
lru_cache(optional<std::size_t> max_size) : max_size(max_size) {
|
|
||||||
VERIFY(!max_size || max_size.unwrap() > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_max_size(optional<std::size_t> size) {
|
|
||||||
VERIFY(!size || size.unwrap() > 0);
|
|
||||||
max_size = size;
|
|
||||||
maybe_trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
void maybe_touch(const K& key) {
|
|
||||||
auto it = map.find(key);
|
|
||||||
if(it == map.end()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto list_it = it->second;
|
|
||||||
touch(list_it);
|
|
||||||
}
|
|
||||||
|
|
||||||
optional<V&> maybe_get(const K& key) {
|
|
||||||
auto it = map.find(key);
|
|
||||||
if(it == map.end()) {
|
|
||||||
return nullopt;
|
|
||||||
} else {
|
|
||||||
touch(it->second);
|
|
||||||
return it->second->value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
optional<const V&> maybe_get(const K& key) const {
|
|
||||||
auto it = map.find(key);
|
|
||||||
if(it == map.end()) {
|
|
||||||
return nullopt;
|
|
||||||
} else {
|
|
||||||
touch(it->second);
|
|
||||||
return it->second->value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void set(const K& key, V value) {
|
|
||||||
auto it = map.find(key);
|
|
||||||
if(it == map.end()) {
|
|
||||||
insert(key, std::move(value));
|
|
||||||
} else {
|
|
||||||
touch(it->second);
|
|
||||||
it->second->value = std::move(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
optional<V&> insert(const K& key, V value) {
|
|
||||||
auto pair = map.insert({key, lru.end()});
|
|
||||||
if(!pair.second) {
|
|
||||||
// didn't insert
|
|
||||||
return nullopt;
|
|
||||||
}
|
|
||||||
auto map_it = pair.first;
|
|
||||||
lru.push_front({key, std::move(value)});
|
|
||||||
map_it->second = lru.begin();
|
|
||||||
maybe_trim();
|
|
||||||
return lru.front().value;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t size() const {
|
|
||||||
return lru.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
void touch(list_iterator list_it) const {
|
|
||||||
lru.splice(lru.begin(), lru, list_it);
|
|
||||||
}
|
|
||||||
|
|
||||||
void maybe_trim() {
|
|
||||||
while(max_size && lru.size() > max_size.unwrap()) {
|
|
||||||
const auto& to_remove = lru.back();
|
|
||||||
map.erase(to_remove.key);
|
|
||||||
lru.pop_back();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -2,11 +2,10 @@
|
|||||||
#define MICROFMT_HPP
|
#define MICROFMT_HPP
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <initializer_list>
|
#include <iosfwd>
|
||||||
#include <iostream>
|
|
||||||
#include <iterator>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
|
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
@ -51,18 +50,19 @@ namespace microfmt {
|
|||||||
char base = 'd';
|
char base = 'd';
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename OutputIt, typename InputIt>
|
template<typename It> void do_write(std::string& out, It begin, It end, const format_options& options) {
|
||||||
void do_write(OutputIt out, InputIt begin, InputIt end, const format_options& options) {
|
|
||||||
auto size = end - begin;
|
auto size = end - begin;
|
||||||
if(static_cast<std::size_t>(size) >= options.width) {
|
if(static_cast<std::size_t>(size) >= options.width) {
|
||||||
std::copy(begin, end, out);
|
out.append(begin, end);
|
||||||
} else {
|
} else {
|
||||||
|
auto out_size = out.size();
|
||||||
|
out.resize(out_size + options.width);
|
||||||
if(options.align == alignment::left) {
|
if(options.align == alignment::left) {
|
||||||
std::copy(begin, end, out);
|
std::copy(begin, end, out.begin() + out_size);
|
||||||
std::fill_n(out, options.width - size, options.fill);
|
std::fill(out.begin() + out_size + size, out.end(), options.fill);
|
||||||
} else {
|
} else {
|
||||||
std::fill_n(out, options.width - size, options.fill);
|
std::fill(out.begin() + out_size, out.begin() + out_size + (options.width - size), options.fill);
|
||||||
std::copy(begin, end, out);
|
std::copy(begin, end, out.begin() + out_size + (options.width - size));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -99,18 +99,15 @@ namespace microfmt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct string_view {
|
|
||||||
const char* data;
|
|
||||||
std::size_t size;
|
|
||||||
};
|
|
||||||
|
|
||||||
class format_value {
|
class format_value {
|
||||||
enum class value_type {
|
enum class value_type {
|
||||||
char_value,
|
char_value,
|
||||||
int64_value,
|
int64_value,
|
||||||
uint64_value,
|
uint64_value,
|
||||||
string_value,
|
string_value,
|
||||||
|
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
|
||||||
string_view_value,
|
string_view_value,
|
||||||
|
#endif
|
||||||
c_string_value,
|
c_string_value,
|
||||||
};
|
};
|
||||||
union {
|
union {
|
||||||
@ -118,7 +115,9 @@ namespace microfmt {
|
|||||||
std::int64_t int64_value;
|
std::int64_t int64_value;
|
||||||
std::uint64_t uint64_value;
|
std::uint64_t uint64_value;
|
||||||
const std::string* string_value;
|
const std::string* string_value;
|
||||||
string_view string_view_value;
|
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
|
||||||
|
std::string_view string_view_value;
|
||||||
|
#endif
|
||||||
const char* c_string_value;
|
const char* c_string_value;
|
||||||
};
|
};
|
||||||
value_type value;
|
value_type value;
|
||||||
@ -136,8 +135,7 @@ namespace microfmt {
|
|||||||
format_value(unsigned long long int_val) : uint64_value(int_val), value(value_type::uint64_value) {}
|
format_value(unsigned long long int_val) : uint64_value(int_val), value(value_type::uint64_value) {}
|
||||||
format_value(const std::string& string) : string_value(&string), value(value_type::string_value) {}
|
format_value(const std::string& string) : string_value(&string), value(value_type::string_value) {}
|
||||||
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
|
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
|
||||||
format_value(std::string_view sv)
|
format_value(std::string_view sv) : string_view_value(sv), value(value_type::string_view_value) {}
|
||||||
: string_view_value{sv.data(), sv.size()}, value(value_type::string_view_value) {}
|
|
||||||
#endif
|
#endif
|
||||||
format_value(const char* c_string) : c_string_value(c_string), value(value_type::c_string_value) {}
|
format_value(const char* c_string) : c_string_value(c_string), value(value_type::c_string_value) {}
|
||||||
|
|
||||||
@ -150,8 +148,7 @@ namespace microfmt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
template<typename OutputIt>
|
void write(std::string& out, const format_options& options) const {
|
||||||
void write(OutputIt out, const format_options& options) const {
|
|
||||||
switch(value) {
|
switch(value) {
|
||||||
case value_type::char_value:
|
case value_type::char_value:
|
||||||
do_write(out, &char_value, &char_value + 1, options);
|
do_write(out, &char_value, &char_value + 1, options);
|
||||||
@ -177,9 +174,11 @@ namespace microfmt {
|
|||||||
case value_type::string_value:
|
case value_type::string_value:
|
||||||
do_write(out, string_value->begin(), string_value->end(), options);
|
do_write(out, string_value->begin(), string_value->end(), options);
|
||||||
break;
|
break;
|
||||||
|
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
|
||||||
case value_type::string_view_value:
|
case value_type::string_view_value:
|
||||||
do_write(out, string_view_value.data, string_view_value.data + string_view_value.size, options);
|
do_write(out, string_view_value.begin(), string_view_value.end(), options);
|
||||||
break;
|
break;
|
||||||
|
#endif
|
||||||
case value_type::c_string_value:
|
case value_type::c_string_value:
|
||||||
do_write(out, c_string_value, c_string_value + std::strlen(c_string_value), options);
|
do_write(out, c_string_value, c_string_value + std::strlen(c_string_value), options);
|
||||||
break;
|
break;
|
||||||
@ -187,10 +186,9 @@ namespace microfmt {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// note: previously used std::array and there was a bug with std::array<T, 0> affecting old msvc
|
template<std::size_t N, typename It>
|
||||||
// https://godbolt.org/z/88T8hrzzq mre: https://godbolt.org/z/drd8echbP
|
std::string format(It fmt_begin, It fmt_end, std::array<format_value, N> args) {
|
||||||
template<typename OutputIt, typename InputIt>
|
std::string str;
|
||||||
void format(OutputIt out, InputIt fmt_begin, InputIt fmt_end, const std::initializer_list<format_value>& args) {
|
|
||||||
std::size_t arg_i = 0;
|
std::size_t arg_i = 0;
|
||||||
auto it = fmt_begin;
|
auto it = fmt_begin;
|
||||||
auto peek = [&] (std::size_t dist) -> char { // 0 on failure
|
auto peek = [&] (std::size_t dist) -> char { // 0 on failure
|
||||||
@ -232,7 +230,7 @@ namespace microfmt {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
it += 2;
|
it += 2;
|
||||||
options.width = arg_i < args.size() ? args.begin()[arg_i++].unwrap_int() : 0;
|
options.width = arg_i < args.size() ? args[arg_i++].unwrap_int() : 0;
|
||||||
}
|
}
|
||||||
// try to parse fill/base
|
// try to parse fill/base
|
||||||
if(it != fmt_end && *it == ':') {
|
if(it != fmt_end && *it == ':') {
|
||||||
@ -252,7 +250,7 @@ namespace microfmt {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if(arg_i < args.size()) {
|
if(arg_i < args.size()) {
|
||||||
args.begin()[arg_i++].write(out, options);
|
args[arg_i++].write(str, options);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
@ -261,40 +259,43 @@ namespace microfmt {
|
|||||||
}
|
}
|
||||||
it = saved_it; // go back
|
it = saved_it; // go back
|
||||||
}
|
}
|
||||||
*out++ = *it;
|
str += *it;
|
||||||
}
|
}
|
||||||
}
|
return str;
|
||||||
|
|
||||||
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
|
|
||||||
template<typename OutputIt>
|
|
||||||
void format(OutputIt out, std::string_view fmt, const std::initializer_list<format_value>& args) {
|
|
||||||
return format(out, fmt.begin(), fmt.end(), args);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
template<typename OutputIt>
|
|
||||||
void format(OutputIt out, const char* fmt, const std::initializer_list<format_value>& args) {
|
|
||||||
return format(out, fmt, fmt + std::strlen(fmt), args);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ostream& get_cout();
|
std::ostream& get_cout();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename S, typename... Args>
|
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
|
||||||
std::string format(const S& fmt, Args&&... args) {
|
template<typename... Args>
|
||||||
std::string str;
|
std::string format(std::string_view fmt, Args&&... args) {
|
||||||
detail::format(std::back_inserter(str), fmt, {detail::format_value(args)...});
|
return detail::format<sizeof...(args)>(fmt.begin(), fmt.end(), {detail::format_value(args)...});
|
||||||
return str;
|
}
|
||||||
|
|
||||||
|
// working around an old msvc bug https://godbolt.org/z/88T8hrzzq mre: https://godbolt.org/z/drd8echbP
|
||||||
|
inline std::string format(std::string_view fmt) {
|
||||||
|
return detail::format<1>(fmt.begin(), fmt.end(), {detail::format_value(1)});
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
std::string format(const char* fmt, Args&&... args) {
|
||||||
|
return detail::format<sizeof...(args)>(fmt, fmt + std::strlen(fmt), {detail::format_value(args)...});
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string format(const char* fmt) {
|
||||||
|
return detail::format<1>(fmt, fmt + std::strlen(fmt), {detail::format_value(1)});
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename S, typename... Args>
|
template<typename S, typename... Args>
|
||||||
void print(const S& fmt, Args&&... args) {
|
void print(const S& fmt, Args&&... args) {
|
||||||
detail::format(std::ostream_iterator<char>(detail::get_cout()), fmt, {args...});
|
detail::get_cout()<<format(fmt, args...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename S, typename... Args>
|
template<typename S, typename... Args>
|
||||||
void print(std::ostream& ostream, const S& fmt, Args&&... args) {
|
void print(std::ostream& ostream, const S& fmt, Args&&... args) {
|
||||||
detail::format(std::ostream_iterator<char>(ostream), fmt, {args...});
|
ostream<<format(fmt, args...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename S, typename... Args>
|
template<typename S, typename... Args>
|
||||||
|
|||||||
@ -1,168 +0,0 @@
|
|||||||
#ifndef OPTIONAL_HPP
|
|
||||||
#define OPTIONAL_HPP
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <new>
|
|
||||||
#include <type_traits>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#include "utils/common.hpp"
|
|
||||||
#include "utils/error.hpp"
|
|
||||||
|
|
||||||
namespace cpptrace {
|
|
||||||
namespace detail {
|
|
||||||
struct nullopt_t {};
|
|
||||||
|
|
||||||
static constexpr nullopt_t nullopt;
|
|
||||||
|
|
||||||
template<
|
|
||||||
typename T,
|
|
||||||
typename std::enable_if<
|
|
||||||
!std::is_same<typename std::decay<T>::type, void>::value && !std::is_rvalue_reference<T>::value,
|
|
||||||
int
|
|
||||||
>::type = 0
|
|
||||||
>
|
|
||||||
using well_behaved = typename std::conditional<
|
|
||||||
std::is_reference<T>::value, std::reference_wrapper<typename std::remove_reference<T>::type>, T
|
|
||||||
>::type;
|
|
||||||
|
|
||||||
template<
|
|
||||||
typename T,
|
|
||||||
typename std::enable_if<!std::is_same<typename std::decay<T>::type, void>::value, int>::type = 0
|
|
||||||
>
|
|
||||||
class optional {
|
|
||||||
using value_type = well_behaved<T>;
|
|
||||||
|
|
||||||
union {
|
|
||||||
char x;
|
|
||||||
value_type uvalue;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool holds_value = false;
|
|
||||||
|
|
||||||
public:
|
|
||||||
optional() noexcept {}
|
|
||||||
|
|
||||||
optional(nullopt_t) noexcept {}
|
|
||||||
|
|
||||||
~optional() {
|
|
||||||
reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
optional(const optional& other) : holds_value(other.holds_value) {
|
|
||||||
if(holds_value) {
|
|
||||||
new (static_cast<void*>(std::addressof(uvalue))) value_type(other.uvalue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
optional(optional&& other)
|
|
||||||
noexcept(std::is_nothrow_move_constructible<value_type>::value)
|
|
||||||
: holds_value(other.holds_value)
|
|
||||||
{
|
|
||||||
if(holds_value) {
|
|
||||||
new (static_cast<void*>(std::addressof(uvalue))) value_type(std::move(other.uvalue));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
optional& operator=(const optional& other) {
|
|
||||||
optional copy(other);
|
|
||||||
swap(copy);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
optional& operator=(optional&& other)
|
|
||||||
noexcept(std::is_nothrow_move_assignable<value_type>::value && std::is_nothrow_move_constructible<value_type>::value)
|
|
||||||
{
|
|
||||||
reset();
|
|
||||||
if(other.holds_value) {
|
|
||||||
new (static_cast<void*>(std::addressof(uvalue))) value_type(std::move(other.uvalue));
|
|
||||||
holds_value = true;
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<
|
|
||||||
typename U = T,
|
|
||||||
typename std::enable_if<!std::is_same<typename std::decay<U>::type, optional<T>>::value, int>::type = 0
|
|
||||||
>
|
|
||||||
optional(U&& value) : holds_value(true) {
|
|
||||||
new (static_cast<void*>(std::addressof(uvalue))) value_type(std::forward<U>(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
template<
|
|
||||||
typename U = T,
|
|
||||||
typename std::enable_if<!std::is_same<typename std::decay<U>::type, optional<T>>::value, int>::type = 0
|
|
||||||
>
|
|
||||||
optional& operator=(U&& value) {
|
|
||||||
optional o(std::forward<U>(value));
|
|
||||||
swap(o);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
optional& operator=(nullopt_t) noexcept {
|
|
||||||
reset();
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void swap(optional& other) noexcept {
|
|
||||||
if(holds_value && other.holds_value) {
|
|
||||||
std::swap(uvalue, other.uvalue);
|
|
||||||
} else if(holds_value && !other.holds_value) {
|
|
||||||
new (&other.uvalue) value_type(std::move(uvalue));
|
|
||||||
uvalue.~value_type();
|
|
||||||
} else if(!holds_value && other.holds_value) {
|
|
||||||
new (static_cast<void*>(std::addressof(uvalue))) value_type(std::move(other.uvalue));
|
|
||||||
other.uvalue.~value_type();
|
|
||||||
}
|
|
||||||
std::swap(holds_value, other.holds_value);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool has_value() const {
|
|
||||||
return holds_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
explicit operator bool() const {
|
|
||||||
return holds_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
void reset() {
|
|
||||||
if(holds_value) {
|
|
||||||
uvalue.~value_type();
|
|
||||||
}
|
|
||||||
holds_value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
NODISCARD T& unwrap() & {
|
|
||||||
ASSERT(holds_value, "Optional does not contain a value");
|
|
||||||
return uvalue;
|
|
||||||
}
|
|
||||||
|
|
||||||
NODISCARD const T& unwrap() const & {
|
|
||||||
ASSERT(holds_value, "Optional does not contain a value");
|
|
||||||
return uvalue;
|
|
||||||
}
|
|
||||||
|
|
||||||
NODISCARD T&& unwrap() && {
|
|
||||||
ASSERT(holds_value, "Optional does not contain a value");
|
|
||||||
return std::move(uvalue);
|
|
||||||
}
|
|
||||||
|
|
||||||
NODISCARD const T&& unwrap() const && {
|
|
||||||
ASSERT(holds_value, "Optional does not contain a value");
|
|
||||||
return std::move(uvalue);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename U>
|
|
||||||
NODISCARD T value_or(U&& default_value) const & {
|
|
||||||
return holds_value ? static_cast<T>(uvalue) : static_cast<T>(std::forward<U>(default_value));
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename U>
|
|
||||||
NODISCARD T value_or(U&& default_value) && {
|
|
||||||
return holds_value ? static_cast<T>(std::move(uvalue)) : static_cast<T>(std::forward<U>(default_value));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -1,157 +0,0 @@
|
|||||||
#ifndef RESULT_HPP
|
|
||||||
#define RESULT_HPP
|
|
||||||
|
|
||||||
#include <new>
|
|
||||||
#include <type_traits>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#include "utils/common.hpp"
|
|
||||||
#include "utils/error.hpp"
|
|
||||||
#include "utils/optional.hpp"
|
|
||||||
#include "options.hpp"
|
|
||||||
|
|
||||||
namespace cpptrace {
|
|
||||||
namespace detail {
|
|
||||||
template<typename T, typename E, typename std::enable_if<!std::is_same<T, E>::value, int>::type = 0>
|
|
||||||
class Result {
|
|
||||||
using value_type = well_behaved<T>;
|
|
||||||
union {
|
|
||||||
value_type value_;
|
|
||||||
E error_;
|
|
||||||
};
|
|
||||||
enum class member { value, error };
|
|
||||||
member active;
|
|
||||||
public:
|
|
||||||
Result(value_type&& value) : value_(std::move(value)), active(member::value) {}
|
|
||||||
Result(E&& error) : error_(std::move(error)), active(member::error) {
|
|
||||||
if(!should_absorb_trace_exceptions()) {
|
|
||||||
std::fprintf(stderr, "%s\n", unwrap_error().what());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Result(const value_type& value) : value_(value_type(value)), active(member::value) {}
|
|
||||||
Result(const E& error) : error_(E(error)), active(member::error) {
|
|
||||||
if(!should_absorb_trace_exceptions()) {
|
|
||||||
std::fprintf(stderr, "%s\n", unwrap_error().what());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
template<
|
|
||||||
typename U = T,
|
|
||||||
typename std::enable_if<
|
|
||||||
!std::is_same<typename std::decay<U>::type, Result<T, E>>::value &&
|
|
||||||
std::is_constructible<value_type, U>::value &&
|
|
||||||
!std::is_constructible<E, U>::value,
|
|
||||||
int
|
|
||||||
>::type = 0
|
|
||||||
>
|
|
||||||
Result(U&& u) : Result(value_type(std::forward<U>(u))) {}
|
|
||||||
Result(Result&& other) : active(other.active) {
|
|
||||||
if(other.active == member::value) {
|
|
||||||
new (&value_) value_type(std::move(other.value_));
|
|
||||||
} else {
|
|
||||||
new (&error_) E(std::move(other.error_));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
~Result() {
|
|
||||||
if(active == member::value) {
|
|
||||||
value_.~value_type();
|
|
||||||
} else {
|
|
||||||
error_.~E();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool has_value() const {
|
|
||||||
return active == member::value;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_error() const {
|
|
||||||
return active == member::error;
|
|
||||||
}
|
|
||||||
|
|
||||||
explicit operator bool() const {
|
|
||||||
return has_value();
|
|
||||||
}
|
|
||||||
|
|
||||||
NODISCARD optional<T> value() const & {
|
|
||||||
return has_value() ? optional<T>(value_) : nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
NODISCARD optional<E> error() const & {
|
|
||||||
return is_error() ? optional<E>(error_) : nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
NODISCARD optional<T> value() && {
|
|
||||||
return has_value() ? optional<T>(std::move(value_)) : nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
NODISCARD optional<E> error() && {
|
|
||||||
return is_error() ? optional<E>(std::move(error_)) : nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
NODISCARD T& unwrap_value() & {
|
|
||||||
ASSERT(has_value(), "Result does not contain a value");
|
|
||||||
return value_;
|
|
||||||
}
|
|
||||||
|
|
||||||
NODISCARD const T& unwrap_value() const & {
|
|
||||||
ASSERT(has_value(), "Result does not contain a value");
|
|
||||||
return value_;
|
|
||||||
}
|
|
||||||
|
|
||||||
NODISCARD T unwrap_value() && {
|
|
||||||
ASSERT(has_value(), "Result does not contain a value");
|
|
||||||
return std::move(value_);
|
|
||||||
}
|
|
||||||
|
|
||||||
NODISCARD E& unwrap_error() & {
|
|
||||||
ASSERT(is_error(), "Result does not contain an error");
|
|
||||||
return error_;
|
|
||||||
}
|
|
||||||
|
|
||||||
NODISCARD const E& unwrap_error() const & {
|
|
||||||
ASSERT(is_error(), "Result does not contain an error");
|
|
||||||
return error_;
|
|
||||||
}
|
|
||||||
|
|
||||||
NODISCARD E unwrap_error() && {
|
|
||||||
ASSERT(is_error(), "Result does not contain an error");
|
|
||||||
return std::move(error_);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename U>
|
|
||||||
NODISCARD T value_or(U&& default_value) const & {
|
|
||||||
return has_value() ? static_cast<T>(value_) : static_cast<T>(std::forward<U>(default_value));
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename U>
|
|
||||||
NODISCARD T value_or(U&& default_value) && {
|
|
||||||
return has_value() ? static_cast<T>(std::move(value_)) : static_cast<T>(std::forward<U>(default_value));
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename F>
|
|
||||||
NODISCARD auto transform(F&& f) & -> Result<decltype(f(std::declval<T&>())), E> {
|
|
||||||
if(has_value()) {
|
|
||||||
return f(unwrap_value());
|
|
||||||
} else {
|
|
||||||
return unwrap_error();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename F>
|
|
||||||
NODISCARD auto transform(F&& f) && -> Result<decltype(f(std::declval<T&&>())), E> {
|
|
||||||
if(has_value()) {
|
|
||||||
return f(std::move(unwrap_value()));
|
|
||||||
} else {
|
|
||||||
return unwrap_error();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void drop_error() const {
|
|
||||||
if(is_error()) {
|
|
||||||
std::fprintf(stderr, "%s\n", unwrap_error().what());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -2,11 +2,13 @@
|
|||||||
#define UTILS_HPP
|
#define UTILS_HPP
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <atomic>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <new>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@ -14,8 +16,6 @@
|
|||||||
|
|
||||||
#include "utils/common.hpp"
|
#include "utils/common.hpp"
|
||||||
#include "utils/error.hpp"
|
#include "utils/error.hpp"
|
||||||
#include "utils/optional.hpp"
|
|
||||||
#include "utils/result.hpp"
|
|
||||||
|
|
||||||
namespace cpptrace {
|
namespace cpptrace {
|
||||||
namespace detail {
|
namespace detail {
|
||||||
@ -35,7 +35,7 @@ namespace detail {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<typename C>
|
template<typename C>
|
||||||
std::string join(const C& container, const std::string& delim) {
|
inline std::string join(const C& container, const std::string& delim) {
|
||||||
auto iter = std::begin(container);
|
auto iter = std::begin(container);
|
||||||
auto end = std::end(container);
|
auto end = std::end(container);
|
||||||
std::string str;
|
std::string str;
|
||||||
@ -82,10 +82,6 @@ namespace detail {
|
|||||||
return str.substr(left, right - left);
|
return str.substr(left, right - left);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool starts_with(const std::string& str, const std::string& prefix) {
|
|
||||||
return str.size() >= prefix.size() && str.compare(0, prefix.size(), prefix) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool is_little_endian() {
|
inline bool is_little_endian() {
|
||||||
std::uint16_t num = 0x1;
|
std::uint16_t num = 0x1;
|
||||||
const auto* ptr = (std::uint8_t*)#
|
const auto* ptr = (std::uint8_t*)#
|
||||||
@ -145,6 +141,265 @@ namespace detail {
|
|||||||
constexpr unsigned n_digits(unsigned value) noexcept {
|
constexpr unsigned n_digits(unsigned value) noexcept {
|
||||||
return value < 10 ? 1 : 1 + n_digits(value / 10);
|
return value < 10 ? 1 : 1 + n_digits(value / 10);
|
||||||
}
|
}
|
||||||
|
static_assert(n_digits(1) == 1, "n_digits utility producing the wrong result");
|
||||||
|
static_assert(n_digits(9) == 1, "n_digits utility producing the wrong result");
|
||||||
|
static_assert(n_digits(10) == 2, "n_digits utility producing the wrong result");
|
||||||
|
static_assert(n_digits(11) == 2, "n_digits utility producing the wrong result");
|
||||||
|
static_assert(n_digits(1024) == 4, "n_digits utility producing the wrong result");
|
||||||
|
|
||||||
|
struct nullopt_t {};
|
||||||
|
|
||||||
|
static constexpr nullopt_t nullopt;
|
||||||
|
|
||||||
|
template<
|
||||||
|
typename T,
|
||||||
|
typename std::enable_if<!std::is_same<typename std::decay<T>::type, void>::value, int>::type = 0
|
||||||
|
>
|
||||||
|
class optional {
|
||||||
|
union {
|
||||||
|
char x;
|
||||||
|
T uvalue;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool holds_value = false;
|
||||||
|
|
||||||
|
public:
|
||||||
|
optional() noexcept {}
|
||||||
|
|
||||||
|
optional(nullopt_t) noexcept {}
|
||||||
|
|
||||||
|
~optional() {
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
optional(const optional& other) : holds_value(other.holds_value) {
|
||||||
|
if(holds_value) {
|
||||||
|
new (static_cast<void*>(std::addressof(uvalue))) T(other.uvalue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
optional(optional&& other)
|
||||||
|
noexcept(std::is_nothrow_move_constructible<T>::value)
|
||||||
|
: holds_value(other.holds_value)
|
||||||
|
{
|
||||||
|
if(holds_value) {
|
||||||
|
new (static_cast<void*>(std::addressof(uvalue))) T(std::move(other.uvalue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
optional& operator=(const optional& other) {
|
||||||
|
optional copy(other);
|
||||||
|
swap(copy);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
optional& operator=(optional&& other)
|
||||||
|
noexcept(std::is_nothrow_move_assignable<T>::value && std::is_nothrow_move_constructible<T>::value)
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
if(other.holds_value) {
|
||||||
|
new (static_cast<void*>(std::addressof(uvalue))) T(std::move(other.uvalue));
|
||||||
|
holds_value = true;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<
|
||||||
|
typename U = T,
|
||||||
|
typename std::enable_if<!std::is_same<typename std::decay<U>::type, optional<T>>::value, int>::type = 0
|
||||||
|
>
|
||||||
|
optional(U&& value) : holds_value(true) {
|
||||||
|
new (static_cast<void*>(std::addressof(uvalue))) T(std::forward<U>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<
|
||||||
|
typename U = T,
|
||||||
|
typename std::enable_if<!std::is_same<typename std::decay<U>::type, optional<T>>::value, int>::type = 0
|
||||||
|
>
|
||||||
|
optional& operator=(U&& value) {
|
||||||
|
optional o(std::forward<U>(value));
|
||||||
|
swap(o);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
optional& operator=(nullopt_t) noexcept {
|
||||||
|
reset();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void swap(optional& other) noexcept {
|
||||||
|
if(holds_value && other.holds_value) {
|
||||||
|
std::swap(uvalue, other.uvalue);
|
||||||
|
} else if(holds_value && !other.holds_value) {
|
||||||
|
new (&other.uvalue) T(std::move(uvalue));
|
||||||
|
uvalue.~T();
|
||||||
|
} else if(!holds_value && other.holds_value) {
|
||||||
|
new (static_cast<void*>(std::addressof(uvalue))) T(std::move(other.uvalue));
|
||||||
|
other.uvalue.~T();
|
||||||
|
}
|
||||||
|
std::swap(holds_value, other.holds_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool has_value() const {
|
||||||
|
return holds_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit operator bool() const {
|
||||||
|
return holds_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
if(holds_value) {
|
||||||
|
uvalue.~T();
|
||||||
|
}
|
||||||
|
holds_value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
NODISCARD T& unwrap() & {
|
||||||
|
ASSERT(holds_value, "Optional does not contain a value");
|
||||||
|
return uvalue;
|
||||||
|
}
|
||||||
|
|
||||||
|
NODISCARD const T& unwrap() const & {
|
||||||
|
ASSERT(holds_value, "Optional does not contain a value");
|
||||||
|
return uvalue;
|
||||||
|
}
|
||||||
|
|
||||||
|
NODISCARD T&& unwrap() && {
|
||||||
|
ASSERT(holds_value, "Optional does not contain a value");
|
||||||
|
return std::move(uvalue);
|
||||||
|
}
|
||||||
|
|
||||||
|
NODISCARD const T&& unwrap() const && {
|
||||||
|
ASSERT(holds_value, "Optional does not contain a value");
|
||||||
|
return std::move(uvalue);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename U>
|
||||||
|
NODISCARD T value_or(U&& default_value) const & {
|
||||||
|
return holds_value ? uvalue : static_cast<T>(std::forward<U>(default_value));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename U>
|
||||||
|
NODISCARD T value_or(U&& default_value) && {
|
||||||
|
return holds_value ? std::move(uvalue) : static_cast<T>(std::forward<U>(default_value));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
extern std::atomic_bool absorb_trace_exceptions;
|
||||||
|
|
||||||
|
template<typename T, typename E, typename std::enable_if<!std::is_same<T, E>::value, int>::type = 0>
|
||||||
|
class Result {
|
||||||
|
union {
|
||||||
|
T value_;
|
||||||
|
E error_;
|
||||||
|
};
|
||||||
|
enum class member { value, error };
|
||||||
|
member active;
|
||||||
|
public:
|
||||||
|
Result(T&& value) : value_(std::move(value)), active(member::value) {}
|
||||||
|
Result(E&& error) : error_(std::move(error)), active(member::error) {
|
||||||
|
if(!absorb_trace_exceptions.load()) {
|
||||||
|
std::fprintf(stderr, "%s\n", unwrap_error().what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Result(T& value) : value_(T(value)), active(member::value) {}
|
||||||
|
Result(E& error) : error_(E(error)), active(member::error) {
|
||||||
|
if(!absorb_trace_exceptions.load()) {
|
||||||
|
std::fprintf(stderr, "%s\n", unwrap_error().what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Result(Result&& other) : active(other.active) {
|
||||||
|
if(other.active == member::value) {
|
||||||
|
new (&value_) T(std::move(other.value_));
|
||||||
|
} else {
|
||||||
|
new (&error_) E(std::move(other.error_));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
~Result() {
|
||||||
|
if(active == member::value) {
|
||||||
|
value_.~T();
|
||||||
|
} else {
|
||||||
|
error_.~E();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool has_value() const {
|
||||||
|
return active == member::value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_error() const {
|
||||||
|
return active == member::error;
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit operator bool() const {
|
||||||
|
return has_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
NODISCARD optional<T> value() const & {
|
||||||
|
return has_value() ? value_ : nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
NODISCARD optional<E> error() const & {
|
||||||
|
return is_error() ? error_ : nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
NODISCARD optional<T> value() && {
|
||||||
|
return has_value() ? std::move(value_) : nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
NODISCARD optional<E> error() && {
|
||||||
|
return is_error() ? std::move(error_) : nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
NODISCARD T& unwrap_value() & {
|
||||||
|
ASSERT(has_value(), "Result does not contain a value");
|
||||||
|
return value_;
|
||||||
|
}
|
||||||
|
|
||||||
|
NODISCARD const T& unwrap_value() const & {
|
||||||
|
ASSERT(has_value(), "Result does not contain a value");
|
||||||
|
return value_;
|
||||||
|
}
|
||||||
|
|
||||||
|
NODISCARD T unwrap_value() && {
|
||||||
|
ASSERT(has_value(), "Result does not contain a value");
|
||||||
|
return std::move(value_);
|
||||||
|
}
|
||||||
|
|
||||||
|
NODISCARD E& unwrap_error() & {
|
||||||
|
ASSERT(is_error(), "Result does not contain an error");
|
||||||
|
return error_;
|
||||||
|
}
|
||||||
|
|
||||||
|
NODISCARD const E& unwrap_error() const & {
|
||||||
|
ASSERT(is_error(), "Result does not contain an error");
|
||||||
|
return error_;
|
||||||
|
}
|
||||||
|
|
||||||
|
NODISCARD E unwrap_error() && {
|
||||||
|
ASSERT(is_error(), "Result does not contain an error");
|
||||||
|
return std::move(error_);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename U>
|
||||||
|
NODISCARD T value_or(U&& default_value) const & {
|
||||||
|
return has_value() ? value_ : static_cast<T>(std::forward<U>(default_value));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename U>
|
||||||
|
NODISCARD T value_or(U&& default_value) && {
|
||||||
|
return has_value() ? std::move(value_) : static_cast<T>(std::forward<U>(default_value));
|
||||||
|
}
|
||||||
|
|
||||||
|
void drop_error() const {
|
||||||
|
if(is_error()) {
|
||||||
|
std::fprintf(stderr, "%s\n", unwrap_error().what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct monostate {};
|
||||||
|
|
||||||
// TODO: Re-evaluate use of off_t
|
// TODO: Re-evaluate use of off_t
|
||||||
template<typename T, typename std::enable_if<std::is_trivial<T>::value, int>::type = 0>
|
template<typename T, typename std::enable_if<std::is_trivial<T>::value, int>::type = 0>
|
||||||
@ -162,9 +417,9 @@ namespace detail {
|
|||||||
// shamelessly stolen from stackoverflow
|
// shamelessly stolen from stackoverflow
|
||||||
bool directory_exists(const std::string& path);
|
bool directory_exists(const std::string& path);
|
||||||
|
|
||||||
inline std::string basename(const std::string& path, bool maybe_windows = false) {
|
inline std::string basename(const std::string& path) {
|
||||||
// Assumes no trailing /'s
|
// Assumes no trailing /'s
|
||||||
auto pos = path.find_last_of(maybe_windows ? "/\\" : "/");
|
auto pos = path.rfind('/');
|
||||||
if(pos == std::string::npos) {
|
if(pos == std::string::npos) {
|
||||||
return path;
|
return path;
|
||||||
} else {
|
} else {
|
||||||
@ -188,32 +443,29 @@ namespace detail {
|
|||||||
return static_cast<U>(v);
|
return static_cast<U>(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T, typename U = T>
|
|
||||||
T exchange(T& obj, U&& value) {
|
|
||||||
T old = std::move(obj);
|
|
||||||
obj = std::forward<U>(value);
|
|
||||||
return old;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct monostate {};
|
|
||||||
|
|
||||||
// TODO: Rework some stuff here. Not sure deleters should be optional or moved.
|
// TODO: Rework some stuff here. Not sure deleters should be optional or moved.
|
||||||
// Also allow file_wrapper file = std::fopen(object_path.c_str(), "rb");
|
// Also allow file_wrapper file = std::fopen(object_path.c_str(), "rb");
|
||||||
template<
|
template<
|
||||||
typename T,
|
typename T,
|
||||||
typename D,
|
typename D
|
||||||
// Note: Previously checked if D was invocable and returned void but this kept causing problems for MSVC
|
// workaround for:
|
||||||
// == 19.38-specific msvc bug https://developercommunity.visualstudio.com/t/MSVC-1938331290-preview-fails-to-comp/10505565
|
// == 19.38-specific msvc bug https://developercommunity.visualstudio.com/t/MSVC-1938331290-preview-fails-to-comp/10505565
|
||||||
// <= 19.23 msvc also appears to fail (but for a different reason https://godbolt.org/z/6Y5EvdWPK)
|
// <= 19.23 msvc also appears to fail (but for a different reason https://godbolt.org/z/6Y5EvdWPK)
|
||||||
// <= 19.39 msvc also has trouble with it for different reasons https://godbolt.org/z/aPPPT7z3z
|
#if !defined(_MSC_VER) || !(_MSC_VER <= 1923 || _MSC_VER == 1938)
|
||||||
typename std::enable_if<
|
,
|
||||||
std::is_standard_layout<T>::value && std::is_trivial<T>::value,
|
typename std::enable_if<
|
||||||
int
|
std::is_same<decltype(std::declval<D>()(std::declval<T>())), void>::value,
|
||||||
>::type = 0,
|
int
|
||||||
typename std::enable_if<
|
>::type = 0,
|
||||||
std::is_nothrow_move_constructible<T>::value,
|
typename std::enable_if<
|
||||||
int
|
std::is_standard_layout<T>::value && std::is_trivial<T>::value,
|
||||||
>::type = 0
|
int
|
||||||
|
>::type = 0,
|
||||||
|
typename std::enable_if<
|
||||||
|
std::is_nothrow_move_constructible<T>::value,
|
||||||
|
int
|
||||||
|
>::type = 0
|
||||||
|
#endif
|
||||||
>
|
>
|
||||||
class raii_wrapper {
|
class raii_wrapper {
|
||||||
T obj;
|
T obj;
|
||||||
@ -245,7 +497,22 @@ namespace detail {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename T, typename D>
|
template<
|
||||||
|
typename T,
|
||||||
|
typename D
|
||||||
|
// workaround a msvc bug https://developercommunity.visualstudio.com/t/MSVC-1938331290-preview-fails-to-comp/10505565
|
||||||
|
#if !defined(_MSC_VER) || _MSC_VER != 1938
|
||||||
|
,
|
||||||
|
typename std::enable_if<
|
||||||
|
std::is_same<decltype(std::declval<D>()(std::declval<T>())), void>::value,
|
||||||
|
int
|
||||||
|
>::type = 0,
|
||||||
|
typename std::enable_if<
|
||||||
|
std::is_standard_layout<T>::value && std::is_trivial<T>::value,
|
||||||
|
int
|
||||||
|
>::type = 0
|
||||||
|
#endif
|
||||||
|
>
|
||||||
raii_wrapper<typename std::remove_reference<T>::type, D> raii_wrap(T obj, D deleter) {
|
raii_wrapper<typename std::remove_reference<T>::type, D> raii_wrap(T obj, D deleter) {
|
||||||
return raii_wrapper<typename std::remove_reference<T>::type, D>(obj, deleter);
|
return raii_wrapper<typename std::remove_reference<T>::type, D>(obj, deleter);
|
||||||
}
|
}
|
||||||
@ -258,11 +525,6 @@ namespace detail {
|
|||||||
|
|
||||||
using file_wrapper = raii_wrapper<std::FILE*, void(*)(std::FILE*)>;
|
using file_wrapper = raii_wrapper<std::FILE*, void(*)(std::FILE*)>;
|
||||||
|
|
||||||
template<class T, class... Args>
|
|
||||||
auto make_unique(Args&&... args) -> typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::type {
|
|
||||||
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
class maybe_owned {
|
class maybe_owned {
|
||||||
std::unique_ptr<T> owned;
|
std::unique_ptr<T> owned;
|
||||||
@ -273,40 +535,7 @@ namespace detail {
|
|||||||
T* operator->() {
|
T* operator->() {
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
T& operator*() {
|
|
||||||
return *ptr;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename F>
|
|
||||||
class scope_guard {
|
|
||||||
F f;
|
|
||||||
bool active;
|
|
||||||
public:
|
|
||||||
template<
|
|
||||||
typename G,
|
|
||||||
typename std::enable_if<!std::is_same<typename std::decay<G>::type, scope_guard<F>>::value, int>::type = 0
|
|
||||||
>
|
|
||||||
scope_guard(G&& f) : f(std::forward<F>(f)), active(true) {}
|
|
||||||
~scope_guard() {
|
|
||||||
if(active) {
|
|
||||||
f();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
scope_guard(const scope_guard&) = delete;
|
|
||||||
scope_guard(scope_guard&& other) : f(std::move(other.f)), active(exchange(other.active, false)) {}
|
|
||||||
scope_guard& operator=(const scope_guard&) = delete;
|
|
||||||
scope_guard& operator=(scope_guard&& other) {
|
|
||||||
f = std::move(other.f);
|
|
||||||
active = exchange(other.active, false);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename F>
|
|
||||||
NODISCARD auto scope_exit(F&& f) -> scope_guard<F> {
|
|
||||||
return scope_guard<F>(std::forward<F>(f));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,23 +6,9 @@ cc_test(
|
|||||||
"@googletest//:gtest_main"
|
"@googletest//:gtest_main"
|
||||||
],
|
],
|
||||||
srcs = [
|
srcs = [
|
||||||
"unit/main.cpp",
|
"unit/object_trace.cpp",
|
||||||
"unit/tracing/common.hpp",
|
"unit/raw_trace.cpp",
|
||||||
"unit/tracing/raw_trace.cpp",
|
"unit/stacktrace.cpp"
|
||||||
"unit/tracing/object_trace.cpp",
|
|
||||||
"unit/tracing/stacktrace.cpp",
|
|
||||||
"unit/tracing/from_current.cpp",
|
|
||||||
"unit/tracing/from_current_z.cpp",
|
|
||||||
"unit/tracing/traced_exception.cpp",
|
|
||||||
"unit/internals/optional.cpp",
|
|
||||||
"unit/internals/result.cpp",
|
|
||||||
"unit/internals/string_utils.cpp",
|
|
||||||
"unit/internals/general.cpp",
|
|
||||||
"unit/lib/formatting.cpp",
|
|
||||||
"unit/lib/nullable.cpp"
|
|
||||||
],
|
|
||||||
local_defines = [
|
|
||||||
"CPPTRACE_NO_TEST_SNIPPETS"
|
|
||||||
],
|
],
|
||||||
linkstatic = 1,
|
linkstatic = 1,
|
||||||
)
|
)
|
||||||
@ -7,6 +7,12 @@ set(
|
|||||||
${warning_options} $<$<CXX_COMPILER_ID:GNU>:-Wno-infinite-recursion>
|
${warning_options} $<$<CXX_COMPILER_ID:GNU>:-Wno-infinite-recursion>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set(
|
||||||
|
debug
|
||||||
|
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-g>
|
||||||
|
$<$<CXX_COMPILER_ID:MSVC>:/DEBUG>
|
||||||
|
)
|
||||||
|
|
||||||
macro(add_test_dependencies exec_name)
|
macro(add_test_dependencies exec_name)
|
||||||
target_compile_features(${exec_name} PRIVATE cxx_std_11)
|
target_compile_features(${exec_name} PRIVATE cxx_std_11)
|
||||||
target_link_libraries(${exec_name} PRIVATE ${target_name})
|
target_link_libraries(${exec_name} PRIVATE ${target_name})
|
||||||
@ -19,11 +25,9 @@ macro(add_test_dependencies exec_name)
|
|||||||
target_compile_options(${exec_name} PRIVATE -gdwarf-${CPPTRACE_BUILD_TESTING_DWARF_VERSION})
|
target_compile_options(${exec_name} PRIVATE -gdwarf-${CPPTRACE_BUILD_TESTING_DWARF_VERSION})
|
||||||
endif()
|
endif()
|
||||||
# Clang has been fast to adopt dwarf 5, other tools (e.g. addr2line from binutils) have not
|
# Clang has been fast to adopt dwarf 5, other tools (e.g. addr2line from binutils) have not
|
||||||
if(NOT CPPTRACE_BUILD_NO_SYMBOLS)
|
check_cxx_compiler_flag("-gdwarf-4" HAS_DWARF4)
|
||||||
check_cxx_compiler_flag("-gdwarf-4" HAS_DWARF4)
|
if(HAS_DWARF4)
|
||||||
if(HAS_DWARF4)
|
target_compile_options(${exec_name} PRIVATE "$<$<CONFIG:Debug>:-gdwarf-4>")
|
||||||
target_compile_options(${exec_name} PRIVATE "$<$<CONFIG:Debug>:-gdwarf-4>")
|
|
||||||
endif()
|
|
||||||
endif()
|
endif()
|
||||||
# TODO: add debug info for mingw clang?
|
# TODO: add debug info for mingw clang?
|
||||||
if(CPPTRACE_BUILD_TEST_RDYNAMIC)
|
if(CPPTRACE_BUILD_TEST_RDYNAMIC)
|
||||||
@ -77,19 +81,12 @@ if(NOT CPPTRACE_SKIP_UNIT)
|
|||||||
add_executable(
|
add_executable(
|
||||||
unittest
|
unittest
|
||||||
unit/main.cpp
|
unit/main.cpp
|
||||||
unit/tracing/raw_trace.cpp
|
unit/raw_trace.cpp
|
||||||
unit/tracing/object_trace.cpp
|
unit/object_trace.cpp
|
||||||
unit/tracing/stacktrace.cpp
|
unit/stacktrace.cpp
|
||||||
unit/tracing/from_current.cpp
|
unit/from_current.cpp
|
||||||
unit/tracing/from_current_z.cpp
|
unit/from_current_z.cpp
|
||||||
unit/tracing/traced_exception.cpp
|
unit/traced_exception.cpp
|
||||||
unit/internals/optional.cpp
|
|
||||||
unit/internals/lru_cache.cpp
|
|
||||||
unit/internals/result.cpp
|
|
||||||
unit/internals/string_utils.cpp
|
|
||||||
unit/internals/general.cpp
|
|
||||||
unit/lib/formatting.cpp
|
|
||||||
unit/lib/nullable.cpp
|
|
||||||
)
|
)
|
||||||
target_compile_features(unittest PRIVATE cxx_std_20)
|
target_compile_features(unittest PRIVATE cxx_std_20)
|
||||||
target_link_libraries(unittest PRIVATE ${target_name} GTest::gtest_main GTest::gmock_main)
|
target_link_libraries(unittest PRIVATE ${target_name} GTest::gtest_main GTest::gmock_main)
|
||||||
@ -104,9 +101,13 @@ if(NOT CPPTRACE_SKIP_UNIT)
|
|||||||
if(CPPTRACE_SANITIZER_BUILD)
|
if(CPPTRACE_SANITIZER_BUILD)
|
||||||
target_compile_definitions(unittest PRIVATE CPPTRACE_SANITIZER_BUILD)
|
target_compile_definitions(unittest PRIVATE CPPTRACE_SANITIZER_BUILD)
|
||||||
endif()
|
endif()
|
||||||
if(CPPTRACE_BUILD_NO_SYMBOLS)
|
|
||||||
target_compile_definitions(unittest PRIVATE CPPTRACE_BUILD_NO_SYMBOLS)
|
|
||||||
endif()
|
|
||||||
target_include_directories(unittest PRIVATE ../src)
|
|
||||||
add_test(NAME unittest COMMAND unittest)
|
add_test(NAME unittest COMMAND unittest)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
find_package(LLVM REQUIRED CONFIG)
|
||||||
|
add_executable(jit_test jit/main.cpp)
|
||||||
|
add_test_dependencies(jit_test)
|
||||||
|
target_include_directories(jit_test PUBLIC ${LLVM_INCLUDE_DIRS})
|
||||||
|
target_compile_definitions(jit_test PUBLIC ${LLVM_DEFINITIONS})
|
||||||
|
llvm_map_components_to_libnames(llvm_libs support core orcjit native)
|
||||||
|
target_link_libraries(jit_test PUBLIC ${llvm_libs})
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
#include <cpptrace/cpptrace.hpp>
|
#include <cpptrace/cpptrace.hpp>
|
||||||
#include <cpptrace/version.hpp>
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
|
|||||||
101
test/jit/KaleidoscopeJIT.hpp
Normal file
101
test/jit/KaleidoscopeJIT.hpp
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
//===- KaleidoscopeJIT.h - A simple JIT for Kaleidoscope --------*- C++ -*-===//
|
||||||
|
//
|
||||||
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||||
|
// See https://llvm.org/LICENSE.txt for license information.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||||
|
//
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
//
|
||||||
|
// Contains a simple JIT definition for use in the kaleidoscope tutorials.
|
||||||
|
//
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
|
#ifndef LLVM_EXECUTIONENGINE_ORC_KALEIDOSCOPEJIT_H
|
||||||
|
#define LLVM_EXECUTIONENGINE_ORC_KALEIDOSCOPEJIT_H
|
||||||
|
|
||||||
|
#include <llvm/ADT/StringRef.h>
|
||||||
|
#include <llvm/ExecutionEngine/Orc/CompileUtils.h>
|
||||||
|
#include <llvm/ExecutionEngine/Orc/Core.h>
|
||||||
|
#include <llvm/ExecutionEngine/Orc/ExecutionUtils.h>
|
||||||
|
#include <llvm/ExecutionEngine/Orc/ExecutorProcessControl.h>
|
||||||
|
#include <llvm/ExecutionEngine/Orc/IRCompileLayer.h>
|
||||||
|
#include <llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h>
|
||||||
|
#include <llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h>
|
||||||
|
#include <llvm/ExecutionEngine/Orc/Shared/ExecutorSymbolDef.h>
|
||||||
|
#include <llvm/ExecutionEngine/SectionMemoryManager.h>
|
||||||
|
#include <llvm/IR/DataLayout.h>
|
||||||
|
#include <llvm/IR/LLVMContext.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace llvm {
|
||||||
|
namespace orc {
|
||||||
|
|
||||||
|
class KaleidoscopeJIT {
|
||||||
|
private:
|
||||||
|
std::unique_ptr<ExecutionSession> ES;
|
||||||
|
|
||||||
|
DataLayout DL;
|
||||||
|
MangleAndInterner Mangle;
|
||||||
|
|
||||||
|
RTDyldObjectLinkingLayer ObjectLayer;
|
||||||
|
IRCompileLayer CompileLayer;
|
||||||
|
|
||||||
|
JITDylib &MainJD;
|
||||||
|
|
||||||
|
public:
|
||||||
|
KaleidoscopeJIT(std::unique_ptr<ExecutionSession> ES,
|
||||||
|
JITTargetMachineBuilder JTMB, DataLayout DL)
|
||||||
|
: ES(std::move(ES)), DL(std::move(DL)), Mangle(*this->ES, this->DL),
|
||||||
|
ObjectLayer(*this->ES,
|
||||||
|
[]() { return std::make_unique<SectionMemoryManager>(); }),
|
||||||
|
CompileLayer(*this->ES, ObjectLayer,
|
||||||
|
std::make_unique<ConcurrentIRCompiler>(std::move(JTMB))),
|
||||||
|
MainJD(this->ES->createBareJITDylib("<main>")) {
|
||||||
|
MainJD.addGenerator(
|
||||||
|
cantFail(DynamicLibrarySearchGenerator::GetForCurrentProcess(
|
||||||
|
DL.getGlobalPrefix())));
|
||||||
|
}
|
||||||
|
|
||||||
|
~KaleidoscopeJIT() {
|
||||||
|
if (auto Err = ES->endSession())
|
||||||
|
ES->reportError(std::move(Err));
|
||||||
|
}
|
||||||
|
|
||||||
|
static Expected<std::unique_ptr<KaleidoscopeJIT>> Create() {
|
||||||
|
auto EPC = SelfExecutorProcessControl::Create();
|
||||||
|
if (!EPC)
|
||||||
|
return EPC.takeError();
|
||||||
|
|
||||||
|
auto ES = std::make_unique<ExecutionSession>(std::move(*EPC));
|
||||||
|
|
||||||
|
JITTargetMachineBuilder JTMB(
|
||||||
|
ES->getExecutorProcessControl().getTargetTriple());
|
||||||
|
|
||||||
|
auto DL = JTMB.getDefaultDataLayoutForTarget();
|
||||||
|
if (!DL)
|
||||||
|
return DL.takeError();
|
||||||
|
|
||||||
|
return std::make_unique<KaleidoscopeJIT>(std::move(ES), std::move(JTMB),
|
||||||
|
std::move(*DL));
|
||||||
|
}
|
||||||
|
|
||||||
|
const DataLayout &getDataLayout() const { return DL; }
|
||||||
|
|
||||||
|
JITDylib &getMainJITDylib() { return MainJD; }
|
||||||
|
|
||||||
|
Error addModule(ThreadSafeModule TSM, ResourceTrackerSP RT = nullptr) {
|
||||||
|
if (!RT)
|
||||||
|
RT = MainJD.getDefaultResourceTracker();
|
||||||
|
return CompileLayer.add(RT, std::move(TSM));
|
||||||
|
}
|
||||||
|
|
||||||
|
Expected<ExecutorSymbolDef> lookup(StringRef Name) {
|
||||||
|
return ES->lookup({&MainJD}, Mangle(Name.str()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // end namespace orc
|
||||||
|
} // end namespace llvm
|
||||||
|
|
||||||
|
#endif // LLVM_EXECUTIONENGINE_ORC_KALEIDOSCOPEJIT_H
|
||||||
1464
test/jit/main.cpp
Normal file
1464
test/jit/main.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@ -10,8 +10,6 @@
|
|||||||
#include <cpptrace/cpptrace.hpp>
|
#include <cpptrace/cpptrace.hpp>
|
||||||
#include <cpptrace/from_current.hpp>
|
#include <cpptrace/from_current.hpp>
|
||||||
|
|
||||||
#include "common.hpp"
|
|
||||||
|
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
|
|
||||||
|
|
||||||
@ -54,7 +52,8 @@ TEST(FromCurrent, Basic) {
|
|||||||
trace.frames.begin(),
|
trace.frames.begin(),
|
||||||
trace.frames.end(),
|
trace.frames.end(),
|
||||||
[](const cpptrace::stacktrace_frame& frame) {
|
[](const cpptrace::stacktrace_frame& frame) {
|
||||||
return frame.symbol.find("stacktrace_from_current_3") != std::string::npos;
|
return frame.filename.find("from_current.cpp") != std::string::npos
|
||||||
|
&& frame.symbol.find("lambda") == std::string::npos; // due to msvc
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
ASSERT_NE(it, trace.frames.end());
|
ASSERT_NE(it, trace.frames.end());
|
||||||
@ -62,29 +61,29 @@ TEST(FromCurrent, Basic) {
|
|||||||
int j = 0;
|
int j = 0;
|
||||||
ASSERT_LT(i, trace.frames.size());
|
ASSERT_LT(i, trace.frames.size());
|
||||||
ASSERT_LT(j, line_numbers.size());
|
ASSERT_LT(j, line_numbers.size());
|
||||||
EXPECT_FILE(trace.frames[i].filename, "from_current.cpp");
|
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("from_current.cpp"));
|
||||||
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[j]);
|
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]);
|
||||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_from_current_3"));
|
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_from_current_3"));
|
||||||
i++;
|
i++;
|
||||||
j++;
|
j++;
|
||||||
ASSERT_LT(i, trace.frames.size());
|
ASSERT_LT(i, trace.frames.size());
|
||||||
ASSERT_LT(j, line_numbers.size());
|
ASSERT_LT(j, line_numbers.size());
|
||||||
EXPECT_FILE(trace.frames[i].filename, "from_current.cpp");
|
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("from_current.cpp"));
|
||||||
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[j]);
|
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]);
|
||||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_from_current_2"));
|
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_from_current_2"));
|
||||||
i++;
|
i++;
|
||||||
j++;
|
j++;
|
||||||
ASSERT_LT(i, trace.frames.size());
|
ASSERT_LT(i, trace.frames.size());
|
||||||
ASSERT_LT(j, line_numbers.size());
|
ASSERT_LT(j, line_numbers.size());
|
||||||
EXPECT_FILE(trace.frames[i].filename, "from_current.cpp");
|
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("from_current.cpp"));
|
||||||
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[j]);
|
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]);
|
||||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_from_current_1"));
|
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_from_current_1"));
|
||||||
i++;
|
i++;
|
||||||
j++;
|
j++;
|
||||||
ASSERT_LT(i, trace.frames.size());
|
ASSERT_LT(i, trace.frames.size());
|
||||||
ASSERT_LT(j, line_numbers.size());
|
ASSERT_LT(j, line_numbers.size());
|
||||||
EXPECT_FILE(trace.frames[i].filename, "from_current.cpp");
|
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("from_current.cpp"));
|
||||||
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[j]);
|
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]);
|
||||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("FromCurrent_Basic_Test::TestBody"));
|
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("FromCurrent_Basic_Test::TestBody"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -105,7 +104,8 @@ TEST(FromCurrent, CorrectHandler) {
|
|||||||
trace.frames.begin(),
|
trace.frames.begin(),
|
||||||
trace.frames.end(),
|
trace.frames.end(),
|
||||||
[](const cpptrace::stacktrace_frame& frame) {
|
[](const cpptrace::stacktrace_frame& frame) {
|
||||||
return frame.symbol.find("stacktrace_from_current_3") != std::string::npos;
|
return frame.filename.find("from_current.cpp") != std::string::npos
|
||||||
|
&& frame.symbol.find("lambda") == std::string::npos;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
EXPECT_NE(it, trace.frames.end());
|
EXPECT_NE(it, trace.frames.end());
|
||||||
@ -134,7 +134,8 @@ TEST(FromCurrent, RawTrace) {
|
|||||||
trace.frames.begin(),
|
trace.frames.begin(),
|
||||||
trace.frames.end(),
|
trace.frames.end(),
|
||||||
[](const cpptrace::stacktrace_frame& frame) {
|
[](const cpptrace::stacktrace_frame& frame) {
|
||||||
return frame.symbol.find("stacktrace_from_current_3") != std::string::npos;
|
return frame.filename.find("from_current.cpp") != std::string::npos
|
||||||
|
&& frame.symbol.find("lambda") == std::string::npos;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
EXPECT_NE(it, trace.frames.end());
|
EXPECT_NE(it, trace.frames.end());
|
||||||
@ -1,4 +1,3 @@
|
|||||||
#include "common.hpp"
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <string>
|
#include <string>
|
||||||
@ -11,8 +10,6 @@
|
|||||||
#include <cpptrace/cpptrace.hpp>
|
#include <cpptrace/cpptrace.hpp>
|
||||||
#include <cpptrace/from_current.hpp>
|
#include <cpptrace/from_current.hpp>
|
||||||
|
|
||||||
#include "common.hpp"
|
|
||||||
|
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
|
|
||||||
|
|
||||||
@ -55,7 +52,8 @@ TEST(FromCurrentZ, Basic) {
|
|||||||
trace.frames.begin(),
|
trace.frames.begin(),
|
||||||
trace.frames.end(),
|
trace.frames.end(),
|
||||||
[](const cpptrace::stacktrace_frame& frame) {
|
[](const cpptrace::stacktrace_frame& frame) {
|
||||||
return frame.symbol.find("stacktrace_from_current_z_3") != std::string::npos;
|
return frame.filename.find("from_current_z.cpp") != std::string::npos
|
||||||
|
&& frame.symbol.find("lambda") == std::string::npos; // due to msvc
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
ASSERT_NE(it, trace.frames.end());
|
ASSERT_NE(it, trace.frames.end());
|
||||||
@ -63,29 +61,29 @@ TEST(FromCurrentZ, Basic) {
|
|||||||
int j = 0;
|
int j = 0;
|
||||||
ASSERT_LT(i, trace.frames.size());
|
ASSERT_LT(i, trace.frames.size());
|
||||||
ASSERT_LT(j, line_numbers.size());
|
ASSERT_LT(j, line_numbers.size());
|
||||||
EXPECT_FILE(trace.frames[i].filename, "from_current_z.cpp");
|
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("from_current_z.cpp"));
|
||||||
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[j]);
|
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]);
|
||||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_from_current_z_3"));
|
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_from_current_z_3"));
|
||||||
i++;
|
i++;
|
||||||
j++;
|
j++;
|
||||||
ASSERT_LT(i, trace.frames.size());
|
ASSERT_LT(i, trace.frames.size());
|
||||||
ASSERT_LT(j, line_numbers.size());
|
ASSERT_LT(j, line_numbers.size());
|
||||||
EXPECT_FILE(trace.frames[i].filename, "from_current_z.cpp");
|
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("from_current_z.cpp"));
|
||||||
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[j]);
|
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]);
|
||||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_from_current_z_2"));
|
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_from_current_z_2"));
|
||||||
i++;
|
i++;
|
||||||
j++;
|
j++;
|
||||||
ASSERT_LT(i, trace.frames.size());
|
ASSERT_LT(i, trace.frames.size());
|
||||||
ASSERT_LT(j, line_numbers.size());
|
ASSERT_LT(j, line_numbers.size());
|
||||||
EXPECT_FILE(trace.frames[i].filename, "from_current_z.cpp");
|
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("from_current_z.cpp"));
|
||||||
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[j]);
|
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]);
|
||||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_from_current_z_1"));
|
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_from_current_z_1"));
|
||||||
i++;
|
i++;
|
||||||
j++;
|
j++;
|
||||||
ASSERT_LT(i, trace.frames.size());
|
ASSERT_LT(i, trace.frames.size());
|
||||||
ASSERT_LT(j, line_numbers.size());
|
ASSERT_LT(j, line_numbers.size());
|
||||||
EXPECT_FILE(trace.frames[i].filename, "from_current_z.cpp");
|
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("from_current_z.cpp"));
|
||||||
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[j]);
|
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]);
|
||||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("FromCurrentZ_Basic_Test::TestBody"));
|
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("FromCurrentZ_Basic_Test::TestBody"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -106,7 +104,8 @@ TEST(FromCurrentZ, CorrectHandler) {
|
|||||||
trace.frames.begin(),
|
trace.frames.begin(),
|
||||||
trace.frames.end(),
|
trace.frames.end(),
|
||||||
[](const cpptrace::stacktrace_frame& frame) {
|
[](const cpptrace::stacktrace_frame& frame) {
|
||||||
return frame.symbol.find("stacktrace_from_current_z_3") != std::string::npos;
|
return frame.filename.find("from_current_z.cpp") != std::string::npos
|
||||||
|
&& frame.symbol.find("lambda") == std::string::npos;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
EXPECT_NE(it, trace.frames.end());
|
EXPECT_NE(it, trace.frames.end());
|
||||||
@ -134,7 +133,8 @@ TEST(FromCurrentZ, RawTrace) {
|
|||||||
trace.frames.begin(),
|
trace.frames.begin(),
|
||||||
trace.frames.end(),
|
trace.frames.end(),
|
||||||
[](const cpptrace::stacktrace_frame& frame) {
|
[](const cpptrace::stacktrace_frame& frame) {
|
||||||
return frame.symbol.find("stacktrace_from_current_z_3") != std::string::npos;
|
return frame.filename.find("from_current_z.cpp") != std::string::npos
|
||||||
|
&& frame.symbol.find("lambda") == std::string::npos;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
EXPECT_NE(it, trace.frames.end());
|
EXPECT_NE(it, trace.frames.end());
|
||||||
@ -1,219 +0,0 @@
|
|||||||
#include <gtest/gtest.h>
|
|
||||||
#include <gtest/gtest-matchers.h>
|
|
||||||
#include <gmock/gmock.h>
|
|
||||||
#include <gmock/gmock-matchers.h>
|
|
||||||
|
|
||||||
#include "utils/utils.hpp"
|
|
||||||
|
|
||||||
using cpptrace::detail::byteswap;
|
|
||||||
using cpptrace::detail::n_digits;
|
|
||||||
using cpptrace::detail::to;
|
|
||||||
using cpptrace::detail::raii_wrapper;
|
|
||||||
using cpptrace::detail::raii_wrap;
|
|
||||||
using cpptrace::detail::maybe_owned;
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
TEST(ByteSwapTest, ByteSwapUint8) {
|
|
||||||
uint8_t input = 0x12;
|
|
||||||
uint8_t expected = 0x12;
|
|
||||||
uint8_t result = byteswap(input);
|
|
||||||
EXPECT_EQ(result, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(ByteSwapTest, ByteSwapInt8) {
|
|
||||||
int8_t input = 0x7F;
|
|
||||||
int8_t expected = 0x7F;
|
|
||||||
int8_t result = byteswap(input);
|
|
||||||
EXPECT_EQ(result, expected);
|
|
||||||
|
|
||||||
int8_t neg_input = to<int8_t>(0x80);
|
|
||||||
int8_t neg_expected = to<int8_t>(0x80);
|
|
||||||
int8_t neg_result = byteswap(neg_input);
|
|
||||||
EXPECT_EQ(neg_result, neg_expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(ByteSwapTest, ByteSwapUint16)
|
|
||||||
{
|
|
||||||
uint16_t input = 0x1234;
|
|
||||||
uint16_t expected = 0x3412;
|
|
||||||
uint16_t result = byteswap(input);
|
|
||||||
EXPECT_EQ(result, expected);
|
|
||||||
EXPECT_EQ(byteswap(to<uint16_t>(0x0000)), to<uint16_t>(0x0000));
|
|
||||||
EXPECT_EQ(byteswap(to<uint16_t>(0xFFFF)), to<uint16_t>(0xFFFF));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(ByteSwapTest, ByteSwapInt16) {
|
|
||||||
int16_t input = 0x1234;
|
|
||||||
int16_t expected = to<int16_t>(0x3412);
|
|
||||||
int16_t result = byteswap(input);
|
|
||||||
EXPECT_EQ(result, expected);
|
|
||||||
|
|
||||||
int16_t neg_input = to<uint16_t>(0xFEFF);
|
|
||||||
int16_t neg_expected = to<int16_t>(0xFFFE);
|
|
||||||
int16_t neg_result = byteswap(neg_input);
|
|
||||||
EXPECT_EQ(neg_result, neg_expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(ByteSwapTest, ByteSwapUint32)
|
|
||||||
{
|
|
||||||
uint32_t input = 0x12345678;
|
|
||||||
uint32_t expected = 0x78563412;
|
|
||||||
uint32_t result = byteswap(input);
|
|
||||||
EXPECT_EQ(result, expected);
|
|
||||||
EXPECT_EQ(byteswap(to<uint32_t>(0x00000000)), to<uint32_t>(0x00000000));
|
|
||||||
EXPECT_EQ(byteswap(to<uint32_t>(0xFFFFFFFF)), to<uint32_t>(0xFFFFFFFF));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(ByteSwapTest, ByteSwapInt32)
|
|
||||||
{
|
|
||||||
int32_t input = 0x12345678;
|
|
||||||
int32_t expected = to<int32_t>(0x78563412);
|
|
||||||
int32_t result = byteswap(input);
|
|
||||||
EXPECT_EQ(result, expected);
|
|
||||||
|
|
||||||
int32_t neg_input = to<uint32_t>(0xFF000000);
|
|
||||||
int32_t neg_expected = to<int32_t>(0x000000FF);
|
|
||||||
int32_t neg_result = byteswap(neg_input);
|
|
||||||
EXPECT_EQ(neg_result, neg_expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(ByteSwapTest, ByteSwapUint64)
|
|
||||||
{
|
|
||||||
uint64_t input = 0x1122334455667788ULL;
|
|
||||||
uint64_t expected = 0x8877665544332211ULL;
|
|
||||||
uint64_t result = byteswap(input);
|
|
||||||
EXPECT_EQ(result, expected);
|
|
||||||
EXPECT_EQ(byteswap(to<uint64_t>(0x0000000000000000ULL)), to<uint64_t>(0x0000000000000000ULL));
|
|
||||||
EXPECT_EQ(byteswap(to<uint64_t>(0xFFFFFFFFFFFFFFFFULL)), to<uint64_t>(0xFFFFFFFFFFFFFFFFULL));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(ByteSwapTest, ByteSwapInt64)
|
|
||||||
{
|
|
||||||
int64_t input = 0x1122334455667788LL;
|
|
||||||
int64_t expected = to<int64_t>(0x8877665544332211ULL);
|
|
||||||
int64_t result = byteswap(input);
|
|
||||||
EXPECT_EQ(result, expected);
|
|
||||||
|
|
||||||
int64_t neg_input = to<uint64_t>(0xFF00000000000000ULL);
|
|
||||||
int64_t neg_expected = to<int64_t>(0x00000000000000FFULL);
|
|
||||||
int64_t neg_result = byteswap(neg_input);
|
|
||||||
EXPECT_EQ(neg_result, neg_expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(NDigitsTest, Basic)
|
|
||||||
{
|
|
||||||
static_assert(n_digits(1) == 1, "n_digits utility producing the wrong result");
|
|
||||||
static_assert(n_digits(9) == 1, "n_digits utility producing the wrong result");
|
|
||||||
static_assert(n_digits(10) == 2, "n_digits utility producing the wrong result");
|
|
||||||
static_assert(n_digits(11) == 2, "n_digits utility producing the wrong result");
|
|
||||||
static_assert(n_digits(1024) == 4, "n_digits utility producing the wrong result");
|
|
||||||
|
|
||||||
EXPECT_EQ(n_digits(1), 1);
|
|
||||||
EXPECT_EQ(n_digits(9), 1);
|
|
||||||
EXPECT_EQ(n_digits(10), 2);
|
|
||||||
EXPECT_EQ(n_digits(11), 2);
|
|
||||||
EXPECT_EQ(n_digits(1024), 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct test_deleter {
|
|
||||||
static int call_count;
|
|
||||||
static int last_value;
|
|
||||||
|
|
||||||
void operator()(int value) {
|
|
||||||
call_count++;
|
|
||||||
last_value = value;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
int test_deleter::call_count = 0;
|
|
||||||
int test_deleter::last_value = 0;
|
|
||||||
|
|
||||||
class RaiiWrapperTest : public ::testing::Test {
|
|
||||||
protected:
|
|
||||||
RaiiWrapperTest() {
|
|
||||||
test_deleter::call_count = 0;
|
|
||||||
test_deleter::last_value = 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
TEST_F(RaiiWrapperTest, construct_and_destroy_calls_deleter) {
|
|
||||||
{
|
|
||||||
auto w = raii_wrap(42, test_deleter{});
|
|
||||||
EXPECT_EQ(test_deleter::call_count, 0);
|
|
||||||
}
|
|
||||||
EXPECT_EQ(test_deleter::call_count, 1);
|
|
||||||
EXPECT_EQ(test_deleter::last_value, 42);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(RaiiWrapperTest, move_constructor_transfers_ownership) {
|
|
||||||
{
|
|
||||||
auto w1 = raii_wrap(123, test_deleter{});
|
|
||||||
auto w2 = std::move(w1);
|
|
||||||
EXPECT_EQ(test_deleter::call_count, 0);
|
|
||||||
EXPECT_EQ(static_cast<int>(w2), 123);
|
|
||||||
}
|
|
||||||
EXPECT_EQ(test_deleter::call_count, 1);
|
|
||||||
EXPECT_EQ(test_deleter::last_value, 123);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(RaiiWrapperTest, operator_t_and_get) {
|
|
||||||
{
|
|
||||||
auto w = raii_wrap(999, test_deleter{});
|
|
||||||
int i = w;
|
|
||||||
EXPECT_EQ(i, 999);
|
|
||||||
w.get() = 1000;
|
|
||||||
EXPECT_EQ(static_cast<int>(w), 1000);
|
|
||||||
}
|
|
||||||
EXPECT_EQ(test_deleter::call_count, 1);
|
|
||||||
EXPECT_EQ(test_deleter::last_value, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
class counting_helper {
|
|
||||||
public:
|
|
||||||
static int active;
|
|
||||||
int value;
|
|
||||||
counting_helper(int value) : value(value) {
|
|
||||||
++active;
|
|
||||||
}
|
|
||||||
~counting_helper() {
|
|
||||||
--active;
|
|
||||||
}
|
|
||||||
counting_helper(const counting_helper&) = delete;
|
|
||||||
counting_helper(counting_helper&&) = delete;
|
|
||||||
counting_helper& operator=(const counting_helper&) = delete;
|
|
||||||
counting_helper& operator=(counting_helper&&) = delete;
|
|
||||||
int foo() const {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
int counting_helper::active = 0;
|
|
||||||
|
|
||||||
TEST(MaybeOwnedTest, NonOwningPointer) {
|
|
||||||
ASSERT_EQ(counting_helper::active, 0);
|
|
||||||
auto instance = std::make_unique<counting_helper>(42);
|
|
||||||
EXPECT_EQ(counting_helper::active, 1);
|
|
||||||
{
|
|
||||||
maybe_owned<counting_helper> non_owning(instance.get());
|
|
||||||
EXPECT_EQ(counting_helper::active, 1);
|
|
||||||
EXPECT_EQ(non_owning->foo(), 42);
|
|
||||||
}
|
|
||||||
EXPECT_EQ(counting_helper::active, 1);
|
|
||||||
instance.reset();
|
|
||||||
EXPECT_EQ(counting_helper::active, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(MaybeOwnedTest, OwningPointer) {
|
|
||||||
ASSERT_EQ(counting_helper::active, 0);
|
|
||||||
auto instance = std::make_unique<counting_helper>(42);
|
|
||||||
EXPECT_EQ(counting_helper::active, 1);
|
|
||||||
{
|
|
||||||
maybe_owned<counting_helper> non_owning(std::move(instance));
|
|
||||||
EXPECT_EQ(counting_helper::active, 1);
|
|
||||||
EXPECT_EQ(non_owning->foo(), 42);
|
|
||||||
}
|
|
||||||
EXPECT_EQ(counting_helper::active, 0);
|
|
||||||
instance.reset();
|
|
||||||
EXPECT_EQ(counting_helper::active, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,104 +0,0 @@
|
|||||||
#include <gtest/gtest.h>
|
|
||||||
|
|
||||||
#include "utils/lru_cache.hpp"
|
|
||||||
|
|
||||||
using cpptrace::detail::lru_cache;
|
|
||||||
using cpptrace::detail::nullopt;
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
TEST(LruCacheTest, DefaultConstructor) {
|
|
||||||
lru_cache<int, int> cache;
|
|
||||||
EXPECT_EQ(cache.size(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(LruCacheTest, MaybeGet) {
|
|
||||||
lru_cache<int, int> cache;
|
|
||||||
auto result = cache.maybe_get(42);
|
|
||||||
EXPECT_FALSE(result.has_value());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(LruCacheTest, InsertAndGet) {
|
|
||||||
lru_cache<int, int> cache;
|
|
||||||
cache.insert(42, 50);
|
|
||||||
auto result = cache.maybe_get(42);
|
|
||||||
ASSERT_TRUE(result.has_value());
|
|
||||||
EXPECT_EQ(result.unwrap(), 50);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(LruCacheTest, ConstGet) {
|
|
||||||
lru_cache<int, int> cache;
|
|
||||||
cache.insert(42, 50);
|
|
||||||
const lru_cache<int, int>& cache_ref = cache;
|
|
||||||
auto result = cache_ref.maybe_get(42);
|
|
||||||
ASSERT_TRUE(result.has_value());
|
|
||||||
EXPECT_EQ(result.unwrap(), 50);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(LruCacheTest, Set) {
|
|
||||||
lru_cache<int, int> cache;
|
|
||||||
cache.set(42, 50);
|
|
||||||
auto result = cache.maybe_get(42);
|
|
||||||
ASSERT_TRUE(result.has_value());
|
|
||||||
EXPECT_EQ(result.unwrap(), 50);
|
|
||||||
cache.set(42, 60);
|
|
||||||
auto result2 = cache.maybe_get(42);
|
|
||||||
ASSERT_TRUE(result2.has_value());
|
|
||||||
EXPECT_EQ(result2.unwrap(), 60);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(LruCacheTest, NoMaxSize) {
|
|
||||||
lru_cache<int, int> cache;
|
|
||||||
for(int i = 0; i < 1000; i++) {
|
|
||||||
cache.insert(i, i + 50);
|
|
||||||
}
|
|
||||||
EXPECT_EQ(cache.size(), 1000);
|
|
||||||
for(int i = 0; i < 1000; i++) {
|
|
||||||
EXPECT_EQ(cache.maybe_get(i).unwrap(), i + 50);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(LruCacheTest, MaxSize) {
|
|
||||||
lru_cache<int, int> cache(20);
|
|
||||||
for(int i = 0; i < 1000; i++) {
|
|
||||||
cache.insert(i, i + 50);
|
|
||||||
}
|
|
||||||
EXPECT_EQ(cache.size(), 20);
|
|
||||||
for(int i = 0; i < 1000 - 20; i++) {
|
|
||||||
EXPECT_FALSE(cache.maybe_get(i).has_value());
|
|
||||||
}
|
|
||||||
for(int i = 1000 - 20; i < 1000; i++) {
|
|
||||||
EXPECT_EQ(cache.maybe_get(i).unwrap(), i + 50);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(LruCacheTest, SizeAfterInserts) {
|
|
||||||
lru_cache<int, int> cache;
|
|
||||||
for(int i = 0; i < 1000; i++) {
|
|
||||||
cache.insert(i, i + 50);
|
|
||||||
}
|
|
||||||
EXPECT_EQ(cache.size(), 1000);
|
|
||||||
cache.set_max_size(20);
|
|
||||||
EXPECT_EQ(cache.size(), 20);
|
|
||||||
for(int i = 0; i < 1000 - 20; i++) {
|
|
||||||
EXPECT_FALSE(cache.maybe_get(i).has_value());
|
|
||||||
}
|
|
||||||
for(int i = 1000 - 20; i < 1000; i++) {
|
|
||||||
EXPECT_EQ(cache.maybe_get(i).unwrap(), i + 50);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(LruCacheTest, Touch) {
|
|
||||||
lru_cache<int, int> cache(20);
|
|
||||||
for(int i = 0; i < 1000; i++) {
|
|
||||||
cache.maybe_touch(0);
|
|
||||||
cache.insert(i, i + 50);
|
|
||||||
}
|
|
||||||
EXPECT_EQ(cache.size(), 20);
|
|
||||||
for(int i = 1000 - 19; i < 1000; i++) {
|
|
||||||
EXPECT_EQ(cache.maybe_get(i).unwrap(), i + 50);
|
|
||||||
}
|
|
||||||
EXPECT_EQ(cache.maybe_get(0).unwrap(), 50);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,219 +0,0 @@
|
|||||||
#include <gtest/gtest.h>
|
|
||||||
|
|
||||||
#include "utils/optional.hpp"
|
|
||||||
|
|
||||||
using cpptrace::detail::optional;
|
|
||||||
using cpptrace::detail::nullopt;
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
TEST(OptionalTest, DefaultConstructor) {
|
|
||||||
optional<int> o;
|
|
||||||
EXPECT_FALSE(o.has_value());
|
|
||||||
EXPECT_FALSE(static_cast<bool>(o));
|
|
||||||
|
|
||||||
optional<int&> o1;
|
|
||||||
EXPECT_FALSE(o1.has_value());
|
|
||||||
EXPECT_FALSE(static_cast<bool>(o1));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(OptionalTest, ConstructWithNullopt) {
|
|
||||||
optional<int> o(nullopt);
|
|
||||||
EXPECT_FALSE(o.has_value());
|
|
||||||
|
|
||||||
optional<int&> o1(nullopt);
|
|
||||||
EXPECT_FALSE(o1.has_value());
|
|
||||||
EXPECT_FALSE(static_cast<bool>(o1));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(OptionalTest, ValueConstructor) {
|
|
||||||
optional<int> o(42);
|
|
||||||
EXPECT_TRUE(o.has_value());
|
|
||||||
EXPECT_EQ(o.unwrap(), 42);
|
|
||||||
|
|
||||||
int x = 100;
|
|
||||||
optional<int> o2(x);
|
|
||||||
EXPECT_TRUE(o2.has_value());
|
|
||||||
EXPECT_EQ(o2.unwrap(), 100);
|
|
||||||
|
|
||||||
int y = 100;
|
|
||||||
optional<int&> o3(y);
|
|
||||||
EXPECT_TRUE(o3.has_value());
|
|
||||||
EXPECT_EQ(o3.unwrap(), 100);
|
|
||||||
y = 200;
|
|
||||||
EXPECT_EQ(o3.unwrap(), 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(OptionalTest, CopyConstructor) {
|
|
||||||
optional<int> o1(42);
|
|
||||||
optional<int> o2(o1);
|
|
||||||
EXPECT_TRUE(o2.has_value());
|
|
||||||
EXPECT_EQ(o2.unwrap(), 42);
|
|
||||||
|
|
||||||
optional<int> o3(nullopt);
|
|
||||||
optional<int> o4(o3);
|
|
||||||
EXPECT_FALSE(o4.has_value());
|
|
||||||
|
|
||||||
int y = 100;
|
|
||||||
optional<int&> o5(y);
|
|
||||||
optional<int&> o6(o5);
|
|
||||||
EXPECT_TRUE(o5.has_value());
|
|
||||||
EXPECT_EQ(o5.unwrap(), 100);
|
|
||||||
EXPECT_TRUE(o6.has_value());
|
|
||||||
EXPECT_EQ(o6.unwrap(), 100);
|
|
||||||
y = 200;
|
|
||||||
EXPECT_EQ(o5.unwrap(), 200);
|
|
||||||
EXPECT_EQ(o6.unwrap(), 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(OptionalTest, MoveConstructor) {
|
|
||||||
optional<int> o1(42);
|
|
||||||
optional<int> o2(std::move(o1));
|
|
||||||
EXPECT_TRUE(o2.has_value());
|
|
||||||
EXPECT_EQ(o2.unwrap(), 42);
|
|
||||||
|
|
||||||
optional<int> o3(nullopt);
|
|
||||||
optional<int> o4(std::move(o3));
|
|
||||||
EXPECT_FALSE(o4.has_value());
|
|
||||||
|
|
||||||
int y = 100;
|
|
||||||
optional<int&> o5(y);
|
|
||||||
optional<int&> o6(std::move(o5));
|
|
||||||
EXPECT_TRUE(o6.has_value());
|
|
||||||
EXPECT_EQ(o6.unwrap(), 100);
|
|
||||||
y = 200;
|
|
||||||
EXPECT_EQ(o6.unwrap(), 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(OptionalTest, CopyAssignmentOperator) {
|
|
||||||
optional<int> o1(42);
|
|
||||||
optional<int> o2;
|
|
||||||
o2 = o1;
|
|
||||||
EXPECT_TRUE(o2.has_value());
|
|
||||||
EXPECT_EQ(o2.unwrap(), 42);
|
|
||||||
|
|
||||||
optional<int> o3(nullopt);
|
|
||||||
optional<int> o4(100);
|
|
||||||
o4 = o3;
|
|
||||||
EXPECT_FALSE(o4.has_value());
|
|
||||||
|
|
||||||
int y = 100;
|
|
||||||
optional<int&> o5(y);
|
|
||||||
optional<int&> o6;
|
|
||||||
o6 = o5;
|
|
||||||
EXPECT_TRUE(o5.has_value());
|
|
||||||
EXPECT_EQ(o5.unwrap(), 100);
|
|
||||||
EXPECT_TRUE(o6.has_value());
|
|
||||||
EXPECT_EQ(o6.unwrap(), 100);
|
|
||||||
y = 200;
|
|
||||||
EXPECT_EQ(o5.unwrap(), 200);
|
|
||||||
EXPECT_EQ(o6.unwrap(), 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(OptionalTest, MoveAssignmentOperator) {
|
|
||||||
optional<int> o1(42);
|
|
||||||
optional<int> o2;
|
|
||||||
o2 = std::move(o1);
|
|
||||||
EXPECT_TRUE(o2.has_value());
|
|
||||||
EXPECT_EQ(o2.unwrap(), 42);
|
|
||||||
|
|
||||||
optional<int> o3(nullopt);
|
|
||||||
optional<int> o4(99);
|
|
||||||
o4 = std::move(o3);
|
|
||||||
EXPECT_FALSE(o4.has_value());
|
|
||||||
|
|
||||||
int y = 100;
|
|
||||||
optional<int&> o5(y);
|
|
||||||
optional<int&> o6;
|
|
||||||
o6 = std::move(o5);
|
|
||||||
EXPECT_TRUE(o6.has_value());
|
|
||||||
EXPECT_EQ(o6.unwrap(), 100);
|
|
||||||
y = 200;
|
|
||||||
EXPECT_EQ(o6.unwrap(), 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(OptionalTest, AssignmentFromValue) {
|
|
||||||
optional<int> o;
|
|
||||||
o = 123;
|
|
||||||
EXPECT_TRUE(o.has_value());
|
|
||||||
EXPECT_EQ(o.unwrap(), 123);
|
|
||||||
|
|
||||||
o = nullopt;
|
|
||||||
EXPECT_FALSE(o.has_value());
|
|
||||||
|
|
||||||
optional<int&> o1;
|
|
||||||
int x = 100;
|
|
||||||
o1 = x;
|
|
||||||
EXPECT_TRUE(o1.has_value());
|
|
||||||
EXPECT_EQ(o1.unwrap(), x);
|
|
||||||
EXPECT_EQ(&o1.unwrap(), &x);
|
|
||||||
|
|
||||||
o1 = nullopt;
|
|
||||||
EXPECT_FALSE(o1.has_value());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(OptionalTest, Reset) {
|
|
||||||
optional<int> o(42);
|
|
||||||
EXPECT_TRUE(o.has_value());
|
|
||||||
EXPECT_EQ(o.unwrap(), 42);
|
|
||||||
o.reset();
|
|
||||||
EXPECT_FALSE(o.has_value());
|
|
||||||
|
|
||||||
int x = 44;
|
|
||||||
optional<int&> o1(x);
|
|
||||||
EXPECT_TRUE(o1.has_value());
|
|
||||||
EXPECT_EQ(o1.unwrap(), 44);
|
|
||||||
o1.reset();
|
|
||||||
EXPECT_FALSE(o1.has_value());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(OptionalTest, Swap) {
|
|
||||||
optional<int> o1(42);
|
|
||||||
optional<int> o2(100);
|
|
||||||
|
|
||||||
o1.swap(o2);
|
|
||||||
EXPECT_TRUE(o1.has_value());
|
|
||||||
EXPECT_TRUE(o2.has_value());
|
|
||||||
EXPECT_EQ(o1.unwrap(), 100);
|
|
||||||
EXPECT_EQ(o2.unwrap(), 42);
|
|
||||||
|
|
||||||
// Swap a value-holding optional with an empty optional
|
|
||||||
optional<int> o3(7);
|
|
||||||
optional<int> o4(nullopt);
|
|
||||||
o3.swap(o4);
|
|
||||||
EXPECT_FALSE(o3.has_value());
|
|
||||||
EXPECT_TRUE(o4.has_value());
|
|
||||||
EXPECT_EQ(o4.unwrap(), 7);
|
|
||||||
|
|
||||||
int x = 20;
|
|
||||||
int y = 40;
|
|
||||||
optional<int&> o5 = x;
|
|
||||||
optional<int&> o6 = y;
|
|
||||||
EXPECT_EQ(o5.unwrap(), 20);
|
|
||||||
EXPECT_EQ(o6.unwrap(), 40);
|
|
||||||
o5.swap(o6);
|
|
||||||
EXPECT_EQ(o5.unwrap(), 40);
|
|
||||||
EXPECT_EQ(o6.unwrap(), 20);
|
|
||||||
EXPECT_EQ(x, 20);
|
|
||||||
EXPECT_EQ(y, 40);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(OptionalTest, ValueOr) {
|
|
||||||
optional<int> o1(42);
|
|
||||||
EXPECT_EQ(o1.value_or(100), 42);
|
|
||||||
|
|
||||||
optional<int> o2(nullopt);
|
|
||||||
EXPECT_EQ(o2.value_or(100), 100);
|
|
||||||
|
|
||||||
int x = 20;
|
|
||||||
int y = 100;
|
|
||||||
optional<int&> o3(x);
|
|
||||||
EXPECT_EQ(o3.value_or(y), 20);
|
|
||||||
o3.reset();
|
|
||||||
EXPECT_EQ(o3.value_or(y), 100);
|
|
||||||
EXPECT_EQ(&o3.value_or(y), &y);
|
|
||||||
EXPECT_EQ(x, 20);
|
|
||||||
EXPECT_EQ(y, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,129 +0,0 @@
|
|||||||
#include <gtest/gtest.h>
|
|
||||||
|
|
||||||
#include "utils/result.hpp"
|
|
||||||
|
|
||||||
using cpptrace::detail::Result;
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
// A simple custom error type that behaves like a standard exception.
|
|
||||||
struct error {
|
|
||||||
int x;
|
|
||||||
const char* what() const {
|
|
||||||
return "error...";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class ResultFixture : public testing::Test {
|
|
||||||
public:
|
|
||||||
ResultFixture() {
|
|
||||||
cpptrace::absorb_trace_exceptions(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
~ResultFixture() override {
|
|
||||||
cpptrace::absorb_trace_exceptions(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
TEST_F(ResultFixture, ConstructWithValueRValue) {
|
|
||||||
cpptrace::detail::Result<std::string, error> result("test");
|
|
||||||
EXPECT_TRUE(result.has_value());
|
|
||||||
EXPECT_FALSE(result.is_error());
|
|
||||||
EXPECT_TRUE(static_cast<bool>(result));
|
|
||||||
|
|
||||||
EXPECT_EQ(result.unwrap_value(), "test");
|
|
||||||
EXPECT_FALSE(result.error().has_value());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(ResultFixture, ConstructWithValueLValue) {
|
|
||||||
std::string s = "test";
|
|
||||||
cpptrace::detail::Result<std::string, error> result(s);
|
|
||||||
|
|
||||||
EXPECT_TRUE(result.has_value());
|
|
||||||
EXPECT_FALSE(result.is_error());
|
|
||||||
EXPECT_EQ(result.unwrap_value(), "test");
|
|
||||||
|
|
||||||
s = "x";
|
|
||||||
EXPECT_EQ(result.unwrap_value(), "test");
|
|
||||||
|
|
||||||
cpptrace::detail::Result<std::string&, error> r2(s);
|
|
||||||
EXPECT_EQ(r2.unwrap_value(), "x");
|
|
||||||
s = "y";
|
|
||||||
EXPECT_EQ(r2.unwrap_value(), "y");
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(ResultFixture, ConstructWithErrorRValue) {
|
|
||||||
cpptrace::detail::Result<std::string, error> result(error{1});
|
|
||||||
EXPECT_FALSE(result.has_value());
|
|
||||||
EXPECT_TRUE(result.is_error());
|
|
||||||
EXPECT_FALSE(static_cast<bool>(result));
|
|
||||||
|
|
||||||
EXPECT_EQ(result.unwrap_error().x, 1);
|
|
||||||
|
|
||||||
// Check that value() returns nullopt in this scenario
|
|
||||||
EXPECT_FALSE(result.value().has_value());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(ResultFixture, ConstructWithErrorLValue) {
|
|
||||||
error e{1};
|
|
||||||
cpptrace::detail::Result<std::string, error> result(e);
|
|
||||||
|
|
||||||
EXPECT_FALSE(result.has_value());
|
|
||||||
EXPECT_TRUE(result.is_error());
|
|
||||||
EXPECT_EQ(result.unwrap_error().x, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(ResultFixture, MoveConstructorValue) {
|
|
||||||
cpptrace::detail::Result<std::string, error> original(std::string("move"));
|
|
||||||
cpptrace::detail::Result<std::string, error> moved(std::move(original));
|
|
||||||
EXPECT_TRUE(moved.has_value());
|
|
||||||
EXPECT_EQ(moved.unwrap_value(), "move");
|
|
||||||
EXPECT_TRUE(original.has_value());
|
|
||||||
|
|
||||||
std::string s = "test";
|
|
||||||
cpptrace::detail::Result<std::string&, error> r1(s);
|
|
||||||
cpptrace::detail::Result<std::string&, error> r2(std::move(r1));
|
|
||||||
EXPECT_TRUE(r2.has_value());
|
|
||||||
EXPECT_EQ(r2.unwrap_value(), "test");
|
|
||||||
s = "foo";
|
|
||||||
EXPECT_EQ(r2.unwrap_value(), "foo");
|
|
||||||
EXPECT_TRUE(r2.has_value());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(ResultFixture, MoveConstructorError) {
|
|
||||||
cpptrace::detail::Result<std::string, error> original(error{1});
|
|
||||||
cpptrace::detail::Result<std::string, error> moved(std::move(original));
|
|
||||||
|
|
||||||
EXPECT_TRUE(moved.is_error());
|
|
||||||
EXPECT_EQ(moved.unwrap_error().x, 1);
|
|
||||||
EXPECT_TRUE(original.is_error());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(ResultFixture, ValueOr) {
|
|
||||||
{
|
|
||||||
cpptrace::detail::Result<int, error> res_with_value(42);
|
|
||||||
EXPECT_EQ(res_with_value.value_or(-1), 42);
|
|
||||||
EXPECT_EQ(std::move(res_with_value).value_or(-1), 42);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
cpptrace::detail::Result<int, error> res_with_error(error{});
|
|
||||||
EXPECT_EQ(res_with_error.value_or(-1), -1);
|
|
||||||
EXPECT_EQ(std::move(res_with_error).value_or(-1), -1);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
int x = 2;
|
|
||||||
int y = 3;
|
|
||||||
cpptrace::detail::Result<int&, error> res_with_value(x);
|
|
||||||
EXPECT_EQ(res_with_value.value_or(y), 2);
|
|
||||||
EXPECT_EQ(std::move(res_with_value).value_or(y), 2);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
int x = 2;
|
|
||||||
cpptrace::detail::Result<int&, error> res_with_error(error{});
|
|
||||||
EXPECT_EQ(res_with_error.value_or(x), 2);
|
|
||||||
EXPECT_EQ(&res_with_error.value_or(x), &x);
|
|
||||||
EXPECT_EQ(std::move(res_with_error).value_or(x), 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,92 +0,0 @@
|
|||||||
#include <gtest/gtest.h>
|
|
||||||
#include <gtest/gtest-matchers.h>
|
|
||||||
#include <gmock/gmock.h>
|
|
||||||
#include <gmock/gmock-matchers.h>
|
|
||||||
|
|
||||||
#include "utils/utils.hpp"
|
|
||||||
|
|
||||||
using testing::ElementsAre;
|
|
||||||
using cpptrace::detail::split;
|
|
||||||
using cpptrace::detail::join;
|
|
||||||
using cpptrace::detail::trim;
|
|
||||||
using cpptrace::detail::starts_with;
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
TEST(SplitTest, SplitBySingleDelimiter) {
|
|
||||||
std::string input = "hello,world";
|
|
||||||
auto tokens = split(input, ",");
|
|
||||||
EXPECT_THAT(tokens, ElementsAre("hello", "world"));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(SplitTest, SplitByMultipleDelimiters) {
|
|
||||||
std::string input = "hello,world;test";
|
|
||||||
auto tokens = split(input, ",;");
|
|
||||||
EXPECT_THAT(tokens, ElementsAre("hello", "world", "test"));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(SplitTest, HandlesNoDelimiterFound) {
|
|
||||||
std::string input = "nodellimitershere";
|
|
||||||
auto tokens = split(input, ", ");
|
|
||||||
EXPECT_THAT(tokens, ElementsAre("nodellimitershere"));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(SplitTest, HandlesEmptyString) {
|
|
||||||
std::string input = "";
|
|
||||||
auto tokens = split(input, ",");
|
|
||||||
EXPECT_THAT(tokens, ElementsAre(""));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(SplitTest, HandlesConsecutiveDelimiters) {
|
|
||||||
std::string input = "one,,two,,,three";
|
|
||||||
auto tokens = split(input, ",");
|
|
||||||
EXPECT_THAT(tokens, ElementsAre("one", "", "two", "", "", "three"));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(SplitTest, HandlesTrailingDelimiter) {
|
|
||||||
std::string input = "abc,";
|
|
||||||
auto tokens = split(input, ",");
|
|
||||||
EXPECT_THAT(tokens, ElementsAre("abc", ""));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(SplitTest, HandlesLeadingDelimiter) {
|
|
||||||
std::string input = ",abc";
|
|
||||||
auto tokens = split(input, ",");
|
|
||||||
EXPECT_THAT(tokens, ElementsAre("", "abc"));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(JoinTest, EmptyContainer) {
|
|
||||||
std::vector<std::string> vec;
|
|
||||||
EXPECT_EQ(join(vec, ","), "");
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(JoinTest, SingleElements) {
|
|
||||||
std::vector<std::string> vec = {"one"};
|
|
||||||
EXPECT_EQ(join(vec, ","), "one");
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(JoinTest, MultipleElements) {
|
|
||||||
std::vector<std::string> vec = {"one", "two", "three"};
|
|
||||||
EXPECT_EQ(join(vec, ","), "one,two,three");
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(TrimTest, Basic) {
|
|
||||||
EXPECT_EQ(trim(""), "");
|
|
||||||
EXPECT_EQ(trim("test"), "test");
|
|
||||||
EXPECT_EQ(trim(" test "), "test");
|
|
||||||
EXPECT_EQ(trim(" test\n "), "test");
|
|
||||||
EXPECT_EQ(trim("\t test\n "), "test");
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(StartsWith, Basic) {
|
|
||||||
EXPECT_TRUE(starts_with("", ""));
|
|
||||||
EXPECT_TRUE(starts_with("abc", ""));
|
|
||||||
EXPECT_FALSE(starts_with("", "abc"));
|
|
||||||
EXPECT_FALSE(starts_with("ab", "abc"));
|
|
||||||
EXPECT_TRUE(starts_with("test", "test"));
|
|
||||||
EXPECT_TRUE(starts_with("hello_world", "hello"));
|
|
||||||
EXPECT_FALSE(starts_with("hello_world", "world"));
|
|
||||||
EXPECT_FALSE(starts_with("abcd", "abce"));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,318 +0,0 @@
|
|||||||
#include <cpptrace/formatting.hpp>
|
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
#include <gtest/gtest-matchers.h>
|
|
||||||
#include <gmock/gmock.h>
|
|
||||||
#include <gmock/gmock-matchers.h>
|
|
||||||
|
|
||||||
#include "utils/microfmt.hpp"
|
|
||||||
#include "utils/utils.hpp"
|
|
||||||
|
|
||||||
using cpptrace::detail::split;
|
|
||||||
using testing::ElementsAre;
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
cpptrace::stacktrace make_test_stacktrace() {
|
|
||||||
cpptrace::stacktrace trace;
|
|
||||||
trace.frames.push_back({0x1, 0x1001, {20}, {30}, "foo.cpp", "foo()", false});
|
|
||||||
trace.frames.push_back({0x2, 0x1002, {30}, {40}, "bar.cpp", "bar()", false});
|
|
||||||
trace.frames.push_back({0x3, 0x1003, {40}, {25}, "foo.cpp", "main", false});
|
|
||||||
return trace;
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(FormatterTest, Basic) {
|
|
||||||
auto res = split(cpptrace::get_default_formatter().format(make_test_stacktrace()), "\n");
|
|
||||||
EXPECT_THAT(
|
|
||||||
res,
|
|
||||||
ElementsAre(
|
|
||||||
"Stack trace (most recent call first):",
|
|
||||||
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
|
|
||||||
"#1 0x0000000000000002 in bar() at bar.cpp:30:40",
|
|
||||||
"#2 0x0000000000000003 in main at foo.cpp:40:25"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(FormatterTest, Inlines) {
|
|
||||||
auto trace = make_test_stacktrace();
|
|
||||||
trace.frames[1].is_inline = true;
|
|
||||||
trace.frames[1].raw_address = 0;
|
|
||||||
trace.frames[1].object_address = 0;
|
|
||||||
auto res = split(cpptrace::get_default_formatter().format(trace), "\n");
|
|
||||||
EXPECT_THAT(
|
|
||||||
res,
|
|
||||||
ElementsAre(
|
|
||||||
"Stack trace (most recent call first):",
|
|
||||||
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
|
|
||||||
"#1 (inlined) in bar() at bar.cpp:30:40",
|
|
||||||
"#2 0x0000000000000003 in main at foo.cpp:40:25"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(FormatterTest, Header) {
|
|
||||||
auto formatter = cpptrace::formatter{}
|
|
||||||
.header("Stack trace:");
|
|
||||||
auto res = split(formatter.format(make_test_stacktrace()), "\n");
|
|
||||||
EXPECT_THAT(
|
|
||||||
res,
|
|
||||||
ElementsAre(
|
|
||||||
"Stack trace:",
|
|
||||||
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
|
|
||||||
"#1 0x0000000000000002 in bar() at bar.cpp:30:40",
|
|
||||||
"#2 0x0000000000000003 in main at foo.cpp:40:25"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(FormatterTest, NoColumn) {
|
|
||||||
auto formatter = cpptrace::formatter{}
|
|
||||||
.columns(false);
|
|
||||||
auto res = split(formatter.format(make_test_stacktrace()), "\n");
|
|
||||||
EXPECT_THAT(
|
|
||||||
res,
|
|
||||||
ElementsAre(
|
|
||||||
"Stack trace (most recent call first):",
|
|
||||||
"#0 0x0000000000000001 in foo() at foo.cpp:20",
|
|
||||||
"#1 0x0000000000000002 in bar() at bar.cpp:30",
|
|
||||||
"#2 0x0000000000000003 in main at foo.cpp:40"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(FormatterTest, ObjectAddresses) {
|
|
||||||
auto formatter = cpptrace::formatter{}
|
|
||||||
.addresses(cpptrace::formatter::address_mode::object);
|
|
||||||
auto res = split(formatter.format(make_test_stacktrace()), "\n");
|
|
||||||
EXPECT_THAT(
|
|
||||||
res,
|
|
||||||
ElementsAre(
|
|
||||||
"Stack trace (most recent call first):",
|
|
||||||
"#0 0x0000000000001001 in foo() at foo.cpp:20:30",
|
|
||||||
"#1 0x0000000000001002 in bar() at bar.cpp:30:40",
|
|
||||||
"#2 0x0000000000001003 in main at foo.cpp:40:25"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(FormatterTest, NoAddresses) {
|
|
||||||
auto formatter = cpptrace::formatter{}
|
|
||||||
.addresses(cpptrace::formatter::address_mode::none);
|
|
||||||
auto res = split(formatter.format(make_test_stacktrace()), "\n");
|
|
||||||
EXPECT_THAT(
|
|
||||||
res,
|
|
||||||
ElementsAre(
|
|
||||||
"Stack trace (most recent call first):",
|
|
||||||
"#0 in foo() at foo.cpp:20:30",
|
|
||||||
"#1 in bar() at bar.cpp:30:40",
|
|
||||||
"#2 in main at foo.cpp:40:25"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(FormatterTest, PathShortening) {
|
|
||||||
cpptrace::stacktrace trace;
|
|
||||||
trace.frames.push_back({0x1, 0x1001, {20}, {30}, "/home/foo/foo.cpp", "foo()", false});
|
|
||||||
trace.frames.push_back({0x2, 0x1002, {30}, {40}, "/bar.cpp", "bar()", false});
|
|
||||||
trace.frames.push_back({0x3, 0x1003, {40}, {25}, "baz/foo.cpp", "main", false});
|
|
||||||
trace.frames.push_back({0x3, 0x1003, {50}, {25}, "C:\\foo\\bar\\baz.cpp", "main", false});
|
|
||||||
auto formatter = cpptrace::formatter{}
|
|
||||||
.paths(cpptrace::formatter::path_mode::basename);
|
|
||||||
auto res = split(formatter.format(trace), "\n");
|
|
||||||
EXPECT_THAT(
|
|
||||||
res,
|
|
||||||
ElementsAre(
|
|
||||||
"Stack trace (most recent call first):",
|
|
||||||
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
|
|
||||||
"#1 0x0000000000000002 in bar() at bar.cpp:30:40",
|
|
||||||
"#2 0x0000000000000003 in main at foo.cpp:40:25",
|
|
||||||
"#3 0x0000000000000003 in main at baz.cpp:50:25"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef CPPTRACE_NO_TEST_SNIPPETS
|
|
||||||
TEST(FormatterTest, Snippets) {
|
|
||||||
cpptrace::stacktrace trace;
|
|
||||||
unsigned line = __LINE__ + 1;
|
|
||||||
trace.frames.push_back({0x1, 0x1001, {line}, {20}, __FILE__, "foo()", false});
|
|
||||||
trace.frames.push_back({0x2, 0x1002, {line + 1}, {20}, __FILE__, "foo()", false});
|
|
||||||
auto formatter = cpptrace::formatter{}
|
|
||||||
.snippets(true);
|
|
||||||
auto res = split(formatter.format(trace), "\n");
|
|
||||||
EXPECT_THAT(
|
|
||||||
res,
|
|
||||||
ElementsAre(
|
|
||||||
"Stack trace (most recent call first):",
|
|
||||||
// frame 1
|
|
||||||
cpptrace::microfmt::format("#0 0x0000000000000001 in foo() at {}:{}:20", __FILE__, line),
|
|
||||||
cpptrace::microfmt::format(" {}: cpptrace::stacktrace trace;", line - 2),
|
|
||||||
cpptrace::microfmt::format(" {}: unsigned line = __LINE__ + 1;", line - 1),
|
|
||||||
cpptrace::microfmt::format(
|
|
||||||
" {}: trace.frames.push_back({0x1, 0x1001, {line}, {{20}}, __FILE__, \"foo()\", false});",
|
|
||||||
line
|
|
||||||
),
|
|
||||||
cpptrace::microfmt::format(
|
|
||||||
" {}: trace.frames.push_back({0x2, 0x1002, {line + 1}, {{20}}, __FILE__, \"foo()\", false});",
|
|
||||||
line + 1
|
|
||||||
),
|
|
||||||
cpptrace::microfmt::format(" {}: auto formatter = cpptrace::formatter{{}}", line + 2),
|
|
||||||
// frame 2
|
|
||||||
cpptrace::microfmt::format("#1 0x0000000000000002 in foo() at {}:{}:20", __FILE__, line + 1),
|
|
||||||
cpptrace::microfmt::format(" {}: unsigned line = __LINE__ + 1;", line - 1),
|
|
||||||
cpptrace::microfmt::format(
|
|
||||||
" {}: trace.frames.push_back({0x1, 0x1001, {line}, {{20}}, __FILE__, \"foo()\", false});",
|
|
||||||
line
|
|
||||||
),
|
|
||||||
cpptrace::microfmt::format(
|
|
||||||
" {}: trace.frames.push_back({0x2, 0x1002, {line + 1}, {{20}}, __FILE__, \"foo()\", false});",
|
|
||||||
line + 1
|
|
||||||
),
|
|
||||||
cpptrace::microfmt::format(" {}: auto formatter = cpptrace::formatter{{}}", line + 2),
|
|
||||||
cpptrace::microfmt::format(" {}: .snippets(true);", line + 3)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
formatter.snippet_context(1);
|
|
||||||
res = split(formatter.format(trace), "\n");
|
|
||||||
EXPECT_THAT(
|
|
||||||
res,
|
|
||||||
ElementsAre(
|
|
||||||
"Stack trace (most recent call first):",
|
|
||||||
// frame 1
|
|
||||||
cpptrace::microfmt::format("#0 0x0000000000000001 in foo() at {}:{}:20", __FILE__, line),
|
|
||||||
cpptrace::microfmt::format(" {}: unsigned line = __LINE__ + 1;", line - 1),
|
|
||||||
cpptrace::microfmt::format(
|
|
||||||
" {}: trace.frames.push_back({0x1, 0x1001, {line}, {{20}}, __FILE__, \"foo()\", false});",
|
|
||||||
line
|
|
||||||
),
|
|
||||||
cpptrace::microfmt::format(
|
|
||||||
" {}: trace.frames.push_back({0x2, 0x1002, {line + 1}, {{20}}, __FILE__, \"foo()\", false});",
|
|
||||||
line + 1
|
|
||||||
),
|
|
||||||
// frame 2
|
|
||||||
cpptrace::microfmt::format("#1 0x0000000000000002 in foo() at {}:{}:20", __FILE__, line + 1),
|
|
||||||
cpptrace::microfmt::format(
|
|
||||||
" {}: trace.frames.push_back({0x1, 0x1001, {line}, {{20}}, __FILE__, \"foo()\", false});",
|
|
||||||
line
|
|
||||||
),
|
|
||||||
cpptrace::microfmt::format(
|
|
||||||
" {}: trace.frames.push_back({0x2, 0x1002, {line + 1}, {{20}}, __FILE__, \"foo()\", false});",
|
|
||||||
line + 1
|
|
||||||
),
|
|
||||||
cpptrace::microfmt::format(" {}: auto formatter = cpptrace::formatter{{}}", line + 2)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
TEST(FormatterTest, Colors) {
|
|
||||||
auto formatter = cpptrace::formatter{}
|
|
||||||
.colors(cpptrace::formatter::color_mode::always);
|
|
||||||
auto res = split(formatter.format(make_test_stacktrace()), "\n");
|
|
||||||
EXPECT_THAT(
|
|
||||||
res,
|
|
||||||
ElementsAre(
|
|
||||||
"Stack trace (most recent call first):",
|
|
||||||
"#0 \x1B[34m0x0000000000000001\x1B[0m in \x1B[33mfoo()\x1B[0m at \x1B[32mfoo.cpp\x1B[0m:\x1B[34m20\x1B[0m:\x1B[34m30\x1B[0m",
|
|
||||||
"#1 \x1B[34m0x0000000000000002\x1B[0m in \x1B[33mbar()\x1B[0m at \x1B[32mbar.cpp\x1B[0m:\x1B[34m30\x1B[0m:\x1B[34m40\x1B[0m",
|
|
||||||
"#2 \x1B[34m0x0000000000000003\x1B[0m in \x1B[33mmain\x1B[0m at \x1B[32mfoo.cpp\x1B[0m:\x1B[34m40\x1B[0m:\x1B[34m25\x1B[0m"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(FormatterTest, Filtering) {
|
|
||||||
auto formatter = cpptrace::formatter{}
|
|
||||||
.filter([] (const cpptrace::stacktrace_frame& frame) -> bool {
|
|
||||||
return frame.filename.find("foo.cpp") != std::string::npos;
|
|
||||||
});
|
|
||||||
auto res = split(formatter.format(make_test_stacktrace()), "\n");
|
|
||||||
EXPECT_THAT(
|
|
||||||
res,
|
|
||||||
ElementsAre(
|
|
||||||
"Stack trace (most recent call first):",
|
|
||||||
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
|
|
||||||
"#1 (filtered)",
|
|
||||||
"#2 0x0000000000000003 in main at foo.cpp:40:25"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(FormatterTest, DontShowFilteredFrames) {
|
|
||||||
auto formatter = cpptrace::formatter{}
|
|
||||||
.filter([] (const cpptrace::stacktrace_frame& frame) -> bool {
|
|
||||||
return frame.filename.find("foo.cpp") != std::string::npos;
|
|
||||||
})
|
|
||||||
.filtered_frame_placeholders(false);
|
|
||||||
auto res = split(formatter.format(make_test_stacktrace()), "\n");
|
|
||||||
EXPECT_THAT(
|
|
||||||
res,
|
|
||||||
ElementsAre(
|
|
||||||
"Stack trace (most recent call first):",
|
|
||||||
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
|
|
||||||
"#2 0x0000000000000003 in main at foo.cpp:40:25"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(FormatterTest, MoveSemantics) {
|
|
||||||
auto formatter = cpptrace::formatter{}
|
|
||||||
.filter([] (const cpptrace::stacktrace_frame& frame) -> bool {
|
|
||||||
return frame.filename.find("foo.cpp") != std::string::npos;
|
|
||||||
});
|
|
||||||
auto formatter2 = std::move(formatter);
|
|
||||||
auto res = split(formatter2.format(make_test_stacktrace()), "\n");
|
|
||||||
EXPECT_THAT(
|
|
||||||
res,
|
|
||||||
ElementsAre(
|
|
||||||
"Stack trace (most recent call first):",
|
|
||||||
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
|
|
||||||
"#1 (filtered)",
|
|
||||||
"#2 0x0000000000000003 in main at foo.cpp:40:25"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
cpptrace::formatter formatter3;
|
|
||||||
formatter3 = std::move(formatter);
|
|
||||||
auto res2 = split(formatter2.format(make_test_stacktrace()), "\n");
|
|
||||||
EXPECT_THAT(
|
|
||||||
res2,
|
|
||||||
ElementsAre(
|
|
||||||
"Stack trace (most recent call first):",
|
|
||||||
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
|
|
||||||
"#1 (filtered)",
|
|
||||||
"#2 0x0000000000000003 in main at foo.cpp:40:25"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(FormatterTest, CopySemantics) {
|
|
||||||
auto formatter = cpptrace::formatter{}
|
|
||||||
.filter([] (const cpptrace::stacktrace_frame& frame) -> bool {
|
|
||||||
return frame.filename.find("foo.cpp") != std::string::npos;
|
|
||||||
});
|
|
||||||
auto formatter2 = formatter;
|
|
||||||
auto res = split(formatter2.format(make_test_stacktrace()), "\n");
|
|
||||||
EXPECT_THAT(
|
|
||||||
res,
|
|
||||||
ElementsAre(
|
|
||||||
"Stack trace (most recent call first):",
|
|
||||||
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
|
|
||||||
"#1 (filtered)",
|
|
||||||
"#2 0x0000000000000003 in main at foo.cpp:40:25"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
cpptrace::formatter formatter3;
|
|
||||||
formatter3 = formatter;
|
|
||||||
auto res2 = split(formatter2.format(make_test_stacktrace()), "\n");
|
|
||||||
EXPECT_THAT(
|
|
||||||
res2,
|
|
||||||
ElementsAre(
|
|
||||||
"Stack trace (most recent call first):",
|
|
||||||
"#0 0x0000000000000001 in foo() at foo.cpp:20:30",
|
|
||||||
"#1 (filtered)",
|
|
||||||
"#2 0x0000000000000003 in main at foo.cpp:40:25"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
#include <cpptrace/basic.hpp>
|
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
#include <gtest/gtest-matchers.h>
|
|
||||||
#include <gmock/gmock.h>
|
|
||||||
#include <gmock/gmock-matchers.h>
|
|
||||||
|
|
||||||
using cpptrace::nullable;
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
TEST(NullableTest, Basic) {
|
|
||||||
nullable<std::uint32_t> a{12};
|
|
||||||
EXPECT_EQ(a.value(), 12);
|
|
||||||
EXPECT_EQ(a.raw_value, 12);
|
|
||||||
nullable<std::uint32_t> b = 20;
|
|
||||||
EXPECT_EQ(b.value(), 20);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(NullableTest, Null) {
|
|
||||||
auto a = nullable<std::uint32_t>::null();
|
|
||||||
EXPECT_FALSE(a.has_value());
|
|
||||||
EXPECT_EQ(a.raw_value, (std::numeric_limits<std::uint32_t>::max)());
|
|
||||||
nullable<std::uint32_t> b;
|
|
||||||
EXPECT_FALSE(b.has_value());
|
|
||||||
EXPECT_EQ(b.raw_value, nullable<std::uint32_t>::null_value());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(NullableTest, Assignment) {
|
|
||||||
nullable<std::uint32_t> a;
|
|
||||||
a = 12;
|
|
||||||
EXPECT_EQ(a.value(), 12);
|
|
||||||
nullable<std::uint32_t> b = 20;
|
|
||||||
a = b;
|
|
||||||
EXPECT_EQ(a.value(), 20);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(NullableTest, Reset) {
|
|
||||||
nullable<std::uint32_t> a{12};
|
|
||||||
a.reset();
|
|
||||||
EXPECT_FALSE(a.has_value());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(NullableTest, ValueOr) {
|
|
||||||
auto a = nullable<std::uint32_t>::null();
|
|
||||||
EXPECT_EQ(a.value_or(20), 20);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(NullableTest, Comparison) {
|
|
||||||
EXPECT_EQ(nullable<std::uint32_t>{12}, nullable<std::uint32_t>{12});
|
|
||||||
EXPECT_NE(nullable<std::uint32_t>{12}, nullable<std::uint32_t>{20});
|
|
||||||
EXPECT_NE(nullable<std::uint32_t>{12}, nullable<std::uint32_t>::null());
|
|
||||||
EXPECT_EQ(nullable<std::uint32_t>::null(), nullable<std::uint32_t>::null());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(NullableTest, Swap) {
|
|
||||||
auto a = nullable<std::uint32_t>::null();
|
|
||||||
nullable<std::uint32_t> b = 12;
|
|
||||||
EXPECT_FALSE(a.has_value());
|
|
||||||
EXPECT_EQ(b.value(), 12);
|
|
||||||
a.swap(b);
|
|
||||||
EXPECT_FALSE(b.has_value());
|
|
||||||
EXPECT_EQ(a.value(), 12);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -5,11 +5,8 @@
|
|||||||
#include <gtest/gtest-matchers.h>
|
#include <gtest/gtest-matchers.h>
|
||||||
#include <gmock/gmock.h>
|
#include <gmock/gmock.h>
|
||||||
#include <gmock/gmock-matchers.h>
|
#include <gmock/gmock-matchers.h>
|
||||||
|
|
||||||
#include <cpptrace/cpptrace.hpp>
|
#include <cpptrace/cpptrace.hpp>
|
||||||
|
|
||||||
#include "common.hpp"
|
|
||||||
|
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
|
|
||||||
|
|
||||||
@ -42,7 +39,7 @@ CPPTRACE_FORCE_NO_INLINE void object_basic_resolution() {
|
|||||||
auto line = __LINE__ + 1;
|
auto line = __LINE__ + 1;
|
||||||
auto trace = cpptrace::generate_object_trace().resolve();
|
auto trace = cpptrace::generate_object_trace().resolve();
|
||||||
ASSERT_GE(trace.frames.size(), 1);
|
ASSERT_GE(trace.frames.size(), 1);
|
||||||
EXPECT_FILE(trace.frames[0].filename, "object_trace.cpp");
|
EXPECT_THAT(trace.frames[0].filename, testing::EndsWith("object_trace.cpp"));
|
||||||
EXPECT_EQ(trace.frames[0].line.value(), line);
|
EXPECT_EQ(trace.frames[0].line.value(), line);
|
||||||
EXPECT_THAT(trace.frames[0].symbol, testing::HasSubstr("object_basic_resolution"));
|
EXPECT_THAT(trace.frames[0].symbol, testing::HasSubstr("object_basic_resolution"));
|
||||||
}
|
}
|
||||||
@ -78,20 +75,20 @@ CPPTRACE_FORCE_NO_INLINE int object_resolve_3(std::vector<int>& line_numbers) {
|
|||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
int i = 0;
|
int i = 0;
|
||||||
EXPECT_FILE(trace.frames[i].filename, "object_trace.cpp");
|
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("object_trace.cpp"));
|
||||||
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
|
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
|
||||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("object_resolve_3"));
|
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("object_resolve_3"));
|
||||||
i++;
|
i++;
|
||||||
EXPECT_FILE(trace.frames[i].filename, "object_trace.cpp");
|
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("object_trace.cpp"));
|
||||||
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
|
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
|
||||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("object_resolve_2"));
|
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("object_resolve_2"));
|
||||||
i++;
|
i++;
|
||||||
EXPECT_FILE(trace.frames[i].filename, "object_trace.cpp");
|
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("object_trace.cpp"));
|
||||||
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
|
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
|
||||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("object_resolve_1"));
|
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("object_resolve_1"));
|
||||||
i++;
|
i++;
|
||||||
EXPECT_FILE(trace.frames[i].filename, "object_trace.cpp");
|
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("object_trace.cpp"));
|
||||||
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
|
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
|
||||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("ObjectTrace_Resolution_Test::TestBody"));
|
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("ObjectTrace_Resolution_Test::TestBody"));
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
@ -4,17 +4,14 @@
|
|||||||
#include <gtest/gtest-matchers.h>
|
#include <gtest/gtest-matchers.h>
|
||||||
#include <gmock/gmock.h>
|
#include <gmock/gmock.h>
|
||||||
#include <gmock/gmock-matchers.h>
|
#include <gmock/gmock-matchers.h>
|
||||||
|
|
||||||
#include <cpptrace/cpptrace.hpp>
|
#include <cpptrace/cpptrace.hpp>
|
||||||
|
|
||||||
#include "common.hpp"
|
|
||||||
|
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
#define CPPTRACE_FORCE_INLINE [[msvc::flatten]]
|
#define CPPTRACE_FORCE_INLINE [[msvc::flatten]]
|
||||||
#else
|
#else
|
||||||
#define CPPTRACE_FORCE_INLINE [[gnu::always_inline]] static
|
#define CPPTRACE_FORCE_INLINE [[gnu::always_inline]]
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
@ -32,8 +29,8 @@ CPPTRACE_FORCE_NO_INLINE void stacktrace_basic() {
|
|||||||
auto line = __LINE__ + 1;
|
auto line = __LINE__ + 1;
|
||||||
auto trace = cpptrace::generate_trace();
|
auto trace = cpptrace::generate_trace();
|
||||||
ASSERT_GE(trace.frames.size(), 1);
|
ASSERT_GE(trace.frames.size(), 1);
|
||||||
EXPECT_FILE(trace.frames[0].filename, "stacktrace.cpp");
|
EXPECT_THAT(trace.frames[0].filename, testing::EndsWith("stacktrace.cpp"));
|
||||||
EXPECT_LINE(trace.frames[0].line.value(), line);
|
EXPECT_EQ(trace.frames[0].line.value(), line);
|
||||||
EXPECT_THAT(trace.frames[0].symbol, testing::HasSubstr("stacktrace_basic"));
|
EXPECT_THAT(trace.frames[0].symbol, testing::HasSubstr("stacktrace_basic"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,20 +51,20 @@ CPPTRACE_FORCE_NO_INLINE int stacktrace_multi_3(std::vector<int>& line_numbers)
|
|||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
int i = 0;
|
int i = 0;
|
||||||
EXPECT_FILE(trace.frames[i].filename, "stacktrace.cpp");
|
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("stacktrace.cpp"));
|
||||||
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
|
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
|
||||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_multi_3"));
|
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_multi_3"));
|
||||||
i++;
|
i++;
|
||||||
EXPECT_FILE(trace.frames[i].filename, "stacktrace.cpp");
|
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("stacktrace.cpp"));
|
||||||
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
|
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
|
||||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_multi_2"));
|
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_multi_2"));
|
||||||
i++;
|
i++;
|
||||||
EXPECT_FILE(trace.frames[i].filename, "stacktrace.cpp");
|
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("stacktrace.cpp"));
|
||||||
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
|
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
|
||||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_multi_1"));
|
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_multi_1"));
|
||||||
i++;
|
i++;
|
||||||
EXPECT_FILE(trace.frames[i].filename, "stacktrace.cpp");
|
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("stacktrace.cpp"));
|
||||||
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
|
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
|
||||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("Stacktrace_MultipleFrames_Test::TestBody"));
|
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("Stacktrace_MultipleFrames_Test::TestBody"));
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
@ -117,25 +114,25 @@ TEST(Stacktrace, RawTraceResolution) {
|
|||||||
auto trace = raw.resolve();
|
auto trace = raw.resolve();
|
||||||
ASSERT_GE(trace.frames.size(), 4);
|
ASSERT_GE(trace.frames.size(), 4);
|
||||||
int i = 0;
|
int i = 0;
|
||||||
EXPECT_FILE(trace.frames[i].filename, "stacktrace.cpp");
|
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("stacktrace.cpp"));
|
||||||
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
|
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
|
||||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_raw_resolve_3"));
|
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_raw_resolve_3"));
|
||||||
i++;
|
i++;
|
||||||
EXPECT_FILE(trace.frames[i].filename, "stacktrace.cpp");
|
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("stacktrace.cpp"));
|
||||||
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
|
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
|
||||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_raw_resolve_2"));
|
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_raw_resolve_2"));
|
||||||
i++;
|
i++;
|
||||||
EXPECT_FILE(trace.frames[i].filename, "stacktrace.cpp");
|
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("stacktrace.cpp"));
|
||||||
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
|
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
|
||||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_raw_resolve_1"));
|
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_raw_resolve_1"));
|
||||||
i++;
|
i++;
|
||||||
EXPECT_FILE(trace.frames[i].filename, "stacktrace.cpp");
|
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("stacktrace.cpp"));
|
||||||
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
|
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
|
||||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("Stacktrace_RawTraceResolution_Test::TestBody"));
|
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("Stacktrace_RawTraceResolution_Test::TestBody"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#if defined(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) && !defined(CPPTRACE_BUILD_NO_SYMBOLS)
|
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF
|
||||||
CPPTRACE_FORCE_NO_INLINE int stacktrace_inline_resolution_3(std::vector<int>& line_numbers) {
|
CPPTRACE_FORCE_NO_INLINE int stacktrace_inline_resolution_3(std::vector<int>& line_numbers) {
|
||||||
static volatile int lto_guard; lto_guard = lto_guard + 1;
|
static volatile int lto_guard; lto_guard = lto_guard + 1;
|
||||||
line_numbers.insert(line_numbers.begin(), __LINE__ + 1);
|
line_numbers.insert(line_numbers.begin(), __LINE__ + 1);
|
||||||
@ -145,29 +142,29 @@ CPPTRACE_FORCE_NO_INLINE int stacktrace_inline_resolution_3(std::vector<int>& li
|
|||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
int i = 0;
|
int i = 0;
|
||||||
EXPECT_FILE(trace.frames[i].filename, "stacktrace.cpp");
|
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("stacktrace.cpp"));
|
||||||
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
|
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
|
||||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_inline_resolution_3"));
|
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_inline_resolution_3"));
|
||||||
EXPECT_FALSE(trace.frames[i].is_inline);
|
EXPECT_FALSE(trace.frames[i].is_inline);
|
||||||
EXPECT_NE(trace.frames[i].raw_address, 0);
|
EXPECT_NE(trace.frames[i].raw_address, 0);
|
||||||
EXPECT_NE(trace.frames[i].object_address, 0);
|
EXPECT_NE(trace.frames[i].object_address, 0);
|
||||||
i++;
|
i++;
|
||||||
EXPECT_FILE(trace.frames[i].filename, "stacktrace.cpp");
|
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("stacktrace.cpp"));
|
||||||
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
|
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
|
||||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_inline_resolution_2"));
|
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_inline_resolution_2"));
|
||||||
EXPECT_TRUE(trace.frames[i].is_inline);
|
EXPECT_TRUE(trace.frames[i].is_inline);
|
||||||
EXPECT_EQ(trace.frames[i].raw_address, 0);
|
EXPECT_EQ(trace.frames[i].raw_address, 0);
|
||||||
EXPECT_EQ(trace.frames[i].object_address, 0);
|
EXPECT_EQ(trace.frames[i].object_address, 0);
|
||||||
i++;
|
i++;
|
||||||
EXPECT_FILE(trace.frames[i].filename, "stacktrace.cpp");
|
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("stacktrace.cpp"));
|
||||||
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
|
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
|
||||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_inline_resolution_1"));
|
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_inline_resolution_1"));
|
||||||
EXPECT_FALSE(trace.frames[i].is_inline);
|
EXPECT_FALSE(trace.frames[i].is_inline);
|
||||||
EXPECT_NE(trace.frames[i].raw_address, 0);
|
EXPECT_NE(trace.frames[i].raw_address, 0);
|
||||||
EXPECT_NE(trace.frames[i].object_address, 0);
|
EXPECT_NE(trace.frames[i].object_address, 0);
|
||||||
i++;
|
i++;
|
||||||
EXPECT_FILE(trace.frames[i].filename, "stacktrace.cpp");
|
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("stacktrace.cpp"));
|
||||||
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
|
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
|
||||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("Stacktrace_InlineResolution_Test::TestBody"));
|
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("Stacktrace_InlineResolution_Test::TestBody"));
|
||||||
EXPECT_FALSE(trace.frames[i].is_inline);
|
EXPECT_FALSE(trace.frames[i].is_inline);
|
||||||
EXPECT_NE(trace.frames[i].raw_address, 0);
|
EXPECT_NE(trace.frames[i].raw_address, 0);
|
||||||
@ -8,8 +8,6 @@
|
|||||||
|
|
||||||
#include <cpptrace/cpptrace.hpp>
|
#include <cpptrace/cpptrace.hpp>
|
||||||
|
|
||||||
#include "common.hpp"
|
|
||||||
|
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
|
|
||||||
|
|
||||||
@ -50,26 +48,26 @@ TEST(TracedException, Basic) {
|
|||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
ASSERT_LT(i, trace.frames.size());
|
ASSERT_LT(i, trace.frames.size());
|
||||||
ASSERT_LT(i, line_numbers.size());
|
ASSERT_LT(i, line_numbers.size());
|
||||||
EXPECT_FILE(trace.frames[i].filename, "traced_exception.cpp");
|
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("traced_exception.cpp"));
|
||||||
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
|
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
|
||||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_traced_object_3"));
|
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_traced_object_3"));
|
||||||
i++;
|
i++;
|
||||||
ASSERT_LT(i, trace.frames.size());
|
ASSERT_LT(i, trace.frames.size());
|
||||||
ASSERT_LT(i, line_numbers.size());
|
ASSERT_LT(i, line_numbers.size());
|
||||||
EXPECT_FILE(trace.frames[i].filename, "traced_exception.cpp");
|
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("traced_exception.cpp"));
|
||||||
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
|
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
|
||||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_traced_object_2"));
|
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_traced_object_2"));
|
||||||
i++;
|
i++;
|
||||||
ASSERT_LT(i, trace.frames.size());
|
ASSERT_LT(i, trace.frames.size());
|
||||||
ASSERT_LT(i, line_numbers.size());
|
ASSERT_LT(i, line_numbers.size());
|
||||||
EXPECT_FILE(trace.frames[i].filename, "traced_exception.cpp");
|
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("traced_exception.cpp"));
|
||||||
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
|
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
|
||||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_traced_object_1"));
|
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_traced_object_1"));
|
||||||
i++;
|
i++;
|
||||||
ASSERT_LT(i, trace.frames.size());
|
ASSERT_LT(i, trace.frames.size());
|
||||||
ASSERT_LT(i, line_numbers.size());
|
ASSERT_LT(i, line_numbers.size());
|
||||||
EXPECT_FILE(trace.frames[i].filename, "traced_exception.cpp");
|
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("traced_exception.cpp"));
|
||||||
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[i]);
|
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[i]);
|
||||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("TracedException_Basic_Test::TestBody"));
|
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("TracedException_Basic_Test::TestBody"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,14 +0,0 @@
|
|||||||
#ifndef TRACING_COMMON_HPP
|
|
||||||
#define TRACING_COMMON_HPP
|
|
||||||
|
|
||||||
template<typename...> using void_t = void;
|
|
||||||
|
|
||||||
#ifndef CPPTRACE_BUILD_NO_SYMBOLS
|
|
||||||
#define EXPECT_FILE(A, B) EXPECT_THAT((A), testing::EndsWith(B))
|
|
||||||
#define EXPECT_LINE(A, B) EXPECT_EQ((A), (B))
|
|
||||||
#else
|
|
||||||
#define EXPECT_FILE(A, B) (void_t<decltype(A), decltype(B)>)0
|
|
||||||
#define EXPECT_LINE(A, B) (void_t<decltype(A), decltype(B)>)0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
include(FetchContent)
|
|
||||||
|
|
||||||
FetchContent_Declare(
|
|
||||||
lyra
|
|
||||||
GIT_SHALLOW TRUE
|
|
||||||
GIT_REPOSITORY "https://github.com/bfgroup/Lyra.git"
|
|
||||||
GIT_TAG "ee3c076fa6b9d64c9d249a21f5b9b5a8dae92cd8"
|
|
||||||
)
|
|
||||||
FetchContent_MakeAvailable(lyra)
|
|
||||||
|
|
||||||
FetchContent_Declare(
|
|
||||||
fmt
|
|
||||||
GIT_SHALLOW TRUE
|
|
||||||
GIT_REPOSITORY "https://github.com/fmtlib/fmt.git"
|
|
||||||
GIT_TAG "e69e5f977d458f2650bb346dadf2ad30c5320281" # v10.2.1
|
|
||||||
)
|
|
||||||
FetchContent_MakeAvailable(fmt)
|
|
||||||
|
|
||||||
set(
|
|
||||||
COMMON_LIBS
|
|
||||||
cpptrace::cpptrace
|
|
||||||
bfg::lyra
|
|
||||||
fmt::fmt
|
|
||||||
)
|
|
||||||
|
|
||||||
function(binary TARGET)
|
|
||||||
cmake_parse_arguments(BINARY "" "" "SOURCES;LIBS;FLAGS;DEFS" ${ARGN})
|
|
||||||
add_executable(${TARGET} main.cpp)
|
|
||||||
if(BINARY_SOURCES)
|
|
||||||
add_library(${TARGET}_OBJ OBJECT ${BINARY_SOURCES})
|
|
||||||
target_link_libraries(${TARGET}_OBJ PUBLIC ${COMMON_LIBS})
|
|
||||||
endif()
|
|
||||||
target_link_libraries(${TARGET} PUBLIC ${COMMON_LIBS})
|
|
||||||
target_link_libraries(${TARGET} PUBLIC ${BINARY_LIBS})
|
|
||||||
target_compile_definitions(${TARGET} PUBLIC ${BINARY_DEFS})
|
|
||||||
target_compile_options(${TARGET} PUBLIC ${BINARY_FLAGS} ${debug} ${warning_options})
|
|
||||||
target_include_directories(${TARGET} PUBLIC "${PROJECT_SOURCE_DIR}/src")
|
|
||||||
target_compile_features(${TARGET} PRIVATE cxx_std_20)
|
|
||||||
set_target_properties(
|
|
||||||
${TARGET}
|
|
||||||
PROPERTIES
|
|
||||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
|
|
||||||
)
|
|
||||||
endfunction()
|
|
||||||
|
|
||||||
add_subdirectory(dwarfdump)
|
|
||||||
add_subdirectory(symbol_tables)
|
|
||||||
add_subdirectory(resolver)
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF)
|
|
||||||
binary(dwarfdump LIBS ${dwarf_lib})
|
|
||||||
endif()
|
|
||||||
@ -1,179 +0,0 @@
|
|||||||
#include <lyra/lyra.hpp>
|
|
||||||
#include <fmt/format.h>
|
|
||||||
#include <fmt/std.h>
|
|
||||||
#include <fmt/ostream.h>
|
|
||||||
#include <cpptrace/cpptrace.hpp>
|
|
||||||
#include <cpptrace/from_current.hpp>
|
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
|
|
||||||
#include "symbols/dwarf/dwarf.hpp"
|
|
||||||
|
|
||||||
using namespace std::literals;
|
|
||||||
using namespace cpptrace::detail::libdwarf;
|
|
||||||
|
|
||||||
template<> struct fmt::formatter<lyra::cli> : ostream_formatter {};
|
|
||||||
|
|
||||||
class DwarfDumper {
|
|
||||||
std::string object_path;
|
|
||||||
Dwarf_Debug dbg = nullptr;
|
|
||||||
|
|
||||||
// Error handling helper
|
|
||||||
// For some reason R (*f)(Args..., void*)-style deduction isn't possible, seems like a bug in all compilers
|
|
||||||
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56190
|
|
||||||
// TODO: Duplicate
|
|
||||||
template<
|
|
||||||
typename... Args,
|
|
||||||
typename... Args2,
|
|
||||||
typename std::enable_if<
|
|
||||||
std::is_same<
|
|
||||||
decltype(
|
|
||||||
(void)std::declval<int(Args...)>()(std::forward<Args2>(std::declval<Args2>())..., nullptr)
|
|
||||||
),
|
|
||||||
void
|
|
||||||
>::value,
|
|
||||||
int
|
|
||||||
>::type = 0
|
|
||||||
>
|
|
||||||
int wrap(int (*f)(Args...), Args2&&... args) const {
|
|
||||||
Dwarf_Error error = nullptr;
|
|
||||||
int ret = f(std::forward<Args2>(args)..., &error);
|
|
||||||
if(ret == DW_DLV_ERROR) {
|
|
||||||
handle_dwarf_error(dbg, error);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Duplicate
|
|
||||||
// walk all CU's in a dbg, callback is called on each die and should return true to
|
|
||||||
// continue traversal
|
|
||||||
void walk_compilation_units(const std::function<bool(const die_object&)>& fn) {
|
|
||||||
// libdwarf keeps track of where it is in the file, dwarf_next_cu_header_d is statefull
|
|
||||||
Dwarf_Unsigned next_cu_header;
|
|
||||||
Dwarf_Half header_cu_type;
|
|
||||||
while(true) {
|
|
||||||
int ret = wrap(
|
|
||||||
dwarf_next_cu_header_d,
|
|
||||||
dbg,
|
|
||||||
true,
|
|
||||||
nullptr,
|
|
||||||
nullptr,
|
|
||||||
nullptr,
|
|
||||||
nullptr,
|
|
||||||
nullptr,
|
|
||||||
nullptr,
|
|
||||||
nullptr,
|
|
||||||
nullptr,
|
|
||||||
&next_cu_header,
|
|
||||||
&header_cu_type
|
|
||||||
);
|
|
||||||
if(ret == DW_DLV_NO_ENTRY) {
|
|
||||||
fmt::println("End walk_dbg");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(ret != DW_DLV_OK) {
|
|
||||||
PANIC("Unexpected return code from dwarf_next_cu_header_d");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 0 passed as the die to the first call of dwarf_siblingof_b immediately after dwarf_next_cu_header_d
|
|
||||||
// to fetch the cu die
|
|
||||||
die_object cu_die(dbg, nullptr);
|
|
||||||
cu_die = cu_die.get_sibling();
|
|
||||||
if(!cu_die) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if(!walk_die_list(cu_die, fn)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt::println("End walk_compilation_units");
|
|
||||||
}
|
|
||||||
|
|
||||||
void dump_die_tree(const die_object& die, int depth) {
|
|
||||||
walk_die_list(
|
|
||||||
die,
|
|
||||||
[this, depth] (const die_object& die) {
|
|
||||||
fmt::println("{:016x}{: <{}} {}", die.get_global_offset(), "", depth * 2, die.get_tag_name());
|
|
||||||
fmt::println("{: <16}{: <{}} name: {}", "", "", depth * 2, die.get_name());
|
|
||||||
fmt::println("");
|
|
||||||
auto child = die.get_child();
|
|
||||||
if(child) {
|
|
||||||
dump_die_tree(child, depth + 1);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void dump_cu(const die_object& cu_die) {
|
|
||||||
Dwarf_Half offset_size = 0;
|
|
||||||
Dwarf_Half dwversion = 0;
|
|
||||||
dwarf_get_version_of_die(cu_die.get(), &dwversion, &offset_size);
|
|
||||||
fmt::println("{:016x} Compile Unit: version = {}, unit type = {}", cu_die.get_global_offset(), dwversion, cu_die.get_tag_name());
|
|
||||||
dump_die_tree(cu_die, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
DwarfDumper() = default;
|
|
||||||
~DwarfDumper() {
|
|
||||||
dwarf_finish(dbg);
|
|
||||||
}
|
|
||||||
|
|
||||||
void dump(std::filesystem::path path) {
|
|
||||||
object_path = path;
|
|
||||||
auto ret = wrap(
|
|
||||||
dwarf_init_path_a,
|
|
||||||
object_path.c_str(),
|
|
||||||
nullptr,
|
|
||||||
0,
|
|
||||||
DW_GROUPNUMBER_ANY,
|
|
||||||
0,
|
|
||||||
nullptr,
|
|
||||||
nullptr,
|
|
||||||
&dbg
|
|
||||||
);
|
|
||||||
if(ret == DW_DLV_OK) {
|
|
||||||
// ok
|
|
||||||
} else if(ret == DW_DLV_NO_ENTRY) {
|
|
||||||
// fail, no debug info
|
|
||||||
fmt::println(stderr, "No debug info");
|
|
||||||
std::exit(1);
|
|
||||||
} else {
|
|
||||||
fmt::println(stderr, "Error: Unknown return code from dwarf_init_path {}", ret);
|
|
||||||
std::exit(1);
|
|
||||||
}
|
|
||||||
walk_compilation_units([this] (const die_object& cu_die) {
|
|
||||||
dump_cu(cu_die);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
int main(int argc, char** argv) CPPTRACE_TRY {
|
|
||||||
bool show_help = false;
|
|
||||||
std::filesystem::path path;
|
|
||||||
auto cli = lyra::cli()
|
|
||||||
| lyra::help(show_help)
|
|
||||||
| lyra::arg(path, "binary path")("binary to dwarfdump").required();
|
|
||||||
if(auto result = cli.parse({ argc, argv }); !result) {
|
|
||||||
fmt::println(stderr, "Error in command line: {}", result.message());
|
|
||||||
fmt::println("{}", cli);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if(show_help) {
|
|
||||||
fmt::println("{}", cli);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if(!std::filesystem::exists(path)) {
|
|
||||||
fmt::println(stderr, "Error: Path doesn't exist {}", path);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if(!std::filesystem::is_regular_file(path)) {
|
|
||||||
fmt::println(stderr, "Error: Path isn't a regular file {}", path);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
DwarfDumper{}.dump(path);
|
|
||||||
} CPPTRACE_CATCH(const std::exception& e) {
|
|
||||||
fmt::println(stderr, "Caught exception {}: {}", cpptrace::demangle(typeid(e).name()), e.what());
|
|
||||||
cpptrace::from_current_exception().print();
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
binary(resolver)
|
|
||||||
@ -1,109 +0,0 @@
|
|||||||
#include "cpptrace/basic.hpp"
|
|
||||||
#include "cpptrace/formatting.hpp"
|
|
||||||
#include "cpptrace/forward.hpp"
|
|
||||||
#include <chrono>
|
|
||||||
#include <lyra/lyra.hpp>
|
|
||||||
#include <fmt/format.h>
|
|
||||||
#include <fmt/std.h>
|
|
||||||
#include <fmt/ostream.h>
|
|
||||||
#include <fmt/chrono.h>
|
|
||||||
#include <cpptrace/cpptrace.hpp>
|
|
||||||
#include <cpptrace/from_current.hpp>
|
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <string>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
#include "symbols/symbols.hpp"
|
|
||||||
#include "demangle/demangle.hpp"
|
|
||||||
|
|
||||||
using namespace std::literals;
|
|
||||||
using namespace cpptrace::detail;
|
|
||||||
|
|
||||||
template<> struct fmt::formatter<lyra::cli> : ostream_formatter {};
|
|
||||||
|
|
||||||
auto formatter = cpptrace::formatter{}.addresses(cpptrace::formatter::address_mode::object);
|
|
||||||
|
|
||||||
struct options {
|
|
||||||
bool show_help = false;
|
|
||||||
std::filesystem::path path;
|
|
||||||
std::vector<std::string> address_strings;
|
|
||||||
bool from_stdin = false;
|
|
||||||
bool keepalive = false;
|
|
||||||
bool timing = false;
|
|
||||||
bool disable_aranges = false;
|
|
||||||
cpptrace::nullable<std::size_t> line_table_cache_size;
|
|
||||||
};
|
|
||||||
|
|
||||||
void resolve(const options& opts, cpptrace::frame_ptr address) {
|
|
||||||
cpptrace::object_frame obj_frame{0, address, opts.path.string()};
|
|
||||||
auto start = std::chrono::high_resolution_clock::now();
|
|
||||||
std::vector<cpptrace::stacktrace_frame> trace = cpptrace::detail::resolve_frames({obj_frame});
|
|
||||||
auto end = std::chrono::high_resolution_clock::now();
|
|
||||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
|
||||||
if(trace.size() != 1) {
|
|
||||||
throw std::runtime_error("Something went wrong, trace vector size didn't match");
|
|
||||||
}
|
|
||||||
trace[0].symbol = cpptrace::demangle(trace[0].symbol);
|
|
||||||
formatter.print(trace[0]);
|
|
||||||
std::cout<<std::endl;
|
|
||||||
if(opts.timing) {
|
|
||||||
fmt::println("resolve time: {}", duration);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char** argv) CPPTRACE_TRY {
|
|
||||||
options opts;
|
|
||||||
auto cli = lyra::cli()
|
|
||||||
| lyra::help(opts.show_help)
|
|
||||||
| lyra::opt(opts.from_stdin)["--stdin"]("read addresses from stdin")
|
|
||||||
| lyra::opt(opts.keepalive)["--keepalive"]("keep the program alive after resolution finishes (useful for debugging)")
|
|
||||||
| lyra::opt(opts.timing)["--timing"]("provide timing stats")
|
|
||||||
| lyra::opt(opts.disable_aranges)["--disable-aranges"]("don't use the .debug_aranges accelerated address lookup table")
|
|
||||||
| lyra::opt(opts.line_table_cache_size.raw_value, "line table cache size")["--line-table-cache-size"]("limit the size of cpptrace's line table cache")
|
|
||||||
| lyra::arg(opts.path, "binary path")("binary to look in").required()
|
|
||||||
| lyra::arg(opts.address_strings, "addresses")("addresses");
|
|
||||||
if(auto result = cli.parse({ argc, argv }); !result) {
|
|
||||||
fmt::println(stderr, "Error in command line: {}", result.message());
|
|
||||||
fmt::println("{}", cli);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if(opts.show_help) {
|
|
||||||
fmt::println("{}", cli);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if(!std::filesystem::exists(opts.path)) {
|
|
||||||
fmt::println(stderr, "Error: Path doesn't exist {}", opts.path);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if(!std::filesystem::is_regular_file(opts.path)) {
|
|
||||||
fmt::println(stderr, "Error: Path isn't a regular file {}", opts.path);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if(opts.disable_aranges) {
|
|
||||||
cpptrace::experimental::set_dwarf_resolver_disable_aranges(true);
|
|
||||||
}
|
|
||||||
if(opts.line_table_cache_size.has_value()) {
|
|
||||||
cpptrace::experimental::set_dwarf_resolver_line_table_cache_size(opts.line_table_cache_size);
|
|
||||||
}
|
|
||||||
for(const auto& address : opts.address_strings) {
|
|
||||||
resolve(opts, std::stoi(address, nullptr, 16));
|
|
||||||
}
|
|
||||||
if(opts.from_stdin) {
|
|
||||||
std::string word;
|
|
||||||
while(std::cin >> word) {
|
|
||||||
resolve(opts, std::stoi(word, nullptr, 16));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(opts.keepalive) {
|
|
||||||
fmt::println("Done");
|
|
||||||
while(true) {
|
|
||||||
std::this_thread::sleep_for(std::chrono::seconds(60));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
} CPPTRACE_CATCH(const std::exception& e) {
|
|
||||||
fmt::println(stderr, "Caught exception {}: {}", cpptrace::demangle(typeid(e).name()), e.what());
|
|
||||||
cpptrace::from_current_exception().print();
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
binary(symbol_tables)
|
|
||||||
@ -1,83 +0,0 @@
|
|||||||
#include <lyra/lyra.hpp>
|
|
||||||
#include <fmt/format.h>
|
|
||||||
#include <fmt/std.h>
|
|
||||||
#include <fmt/ostream.h>
|
|
||||||
#include <cpptrace/cpptrace.hpp>
|
|
||||||
#include <cpptrace/from_current.hpp>
|
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
|
|
||||||
#include "binary/elf.hpp"
|
|
||||||
#include "binary/mach-o.hpp"
|
|
||||||
|
|
||||||
using namespace std::literals;
|
|
||||||
using namespace cpptrace::detail;
|
|
||||||
|
|
||||||
template<> struct fmt::formatter<lyra::cli> : ostream_formatter {};
|
|
||||||
|
|
||||||
#if IS_LINUX
|
|
||||||
void dump_symtab_result(const Result<optional<std::vector<elf::symbol_entry>>, internal_error>& res) {
|
|
||||||
if(!res) {
|
|
||||||
fmt::println(stderr, "Error loading: {}", res.unwrap_error().what());
|
|
||||||
}
|
|
||||||
const auto& entries_ = res.unwrap_value();
|
|
||||||
if(!entries_) {
|
|
||||||
fmt::println("Empty symbol table");
|
|
||||||
}
|
|
||||||
const auto& entries = entries_.unwrap();
|
|
||||||
fmt::println("{:16} {:16} {:4} {}", "value", "size", "shdx", "symbol");
|
|
||||||
for(const auto& entry : entries) {
|
|
||||||
fmt::println("{:016x} {:016x} {:04x} {}", entry.st_value, entry.st_size, entry.st_shndx, entry.st_name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void dump_symbols(const std::filesystem::path& path) {
|
|
||||||
auto elf_ = elf::open_elf(path);
|
|
||||||
if(!elf_) {
|
|
||||||
fmt::println(stderr, "Error reading file: {}", elf_.unwrap_error().what());
|
|
||||||
}
|
|
||||||
auto& elf = elf_.unwrap_value();
|
|
||||||
fmt::println("Symtab:");
|
|
||||||
dump_symtab_result(elf.get_symtab_entries());
|
|
||||||
fmt::println("Dynamic symtab:");
|
|
||||||
dump_symtab_result(elf.get_dynamic_symtab_entries());
|
|
||||||
}
|
|
||||||
#elif IS_APPLE
|
|
||||||
void dump_symbols(const std::filesystem::path& path) {
|
|
||||||
fmt::println("Not implemented yet (TODO)");
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
void dump_symbols(const std::filesystem::path&) {
|
|
||||||
fmt::println("Unable to dump symbol table on this platform");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int main(int argc, char** argv) CPPTRACE_TRY {
|
|
||||||
bool show_help = false;
|
|
||||||
std::filesystem::path path;
|
|
||||||
auto cli = lyra::cli()
|
|
||||||
| lyra::help(show_help)
|
|
||||||
| lyra::arg(path, "binary path")("binary to dump symbol tables for").required();
|
|
||||||
if(auto result = cli.parse({ argc, argv }); !result) {
|
|
||||||
fmt::println(stderr, "Error in command line: {}", result.message());
|
|
||||||
fmt::println("{}", cli);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if(show_help) {
|
|
||||||
fmt::println("{}", cli);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if(!std::filesystem::exists(path)) {
|
|
||||||
fmt::println(stderr, "Error: Path doesn't exist {}", path);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if(!std::filesystem::is_regular_file(path)) {
|
|
||||||
fmt::println(stderr, "Error: Path isn't a regular file {}", path);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
dump_symbols(path);
|
|
||||||
return 0;
|
|
||||||
} CPPTRACE_CATCH(const std::exception& e) {
|
|
||||||
fmt::println(stderr, "Caught exception {}: {}", cpptrace::demangle(typeid(e).name()), e.what());
|
|
||||||
cpptrace::from_current_exception().print();
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user