Compare commits

..

1 Commits

Author SHA1 Message Date
Jeremy Rifkin
73690b7a4e
simple llvm kaleidoscope jit setup for testing 2024-11-21 00:00:03 -06:00
97 changed files with 3606 additions and 5258 deletions

145
.github/workflows/build.yml vendored Normal file
View 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}}

View File

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

View File

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

View File

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

View File

@ -9,12 +9,12 @@ help: # with thanks to Ben Rady
build: debug ## build in debug mode
build/configured-debug:
cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On -DCPPTRACE_BUILD_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
touch build/configured-debug
build/configured-release:
cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On -DCPPTRACE_BUILD_TOOLS=On
cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On
rm -f build/configured-debug
touch build/configured-release
@ -34,12 +34,12 @@ release: configure-release ## build in release mode (with debug info)
.PHONY: debug-msvc
debug-msvc: ## build in debug mode
cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On -DCPPTRACE_BUILD_TOOLS=On
cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On
cmake --build build --config Debug
.PHONY: release-msvc
release-msvc: ## build in release mode (with debug info)
cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On -DCPPTRACE_BUILD_TOOLS=On
cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCPPTRACE_BUILD_TESTING=On
cmake --build build --config RelWithDebInfo
.PHONY: clean

224
README.md
View File

@ -1,6 +1,7 @@
# Cpptrace <!-- omit in toc -->
[![CI](https://github.com/jeremy-rifkin/cpptrace/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/jeremy-rifkin/cpptrace/actions/workflows/ci.yml)
[![build](https://github.com/jeremy-rifkin/cpptrace/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/jeremy-rifkin/cpptrace/actions/workflows/build.yml)
[![test](https://github.com/jeremy-rifkin/cpptrace/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/jeremy-rifkin/cpptrace/actions/workflows/test.yml)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=jeremy-rifkin_cpptrace&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=jeremy-rifkin_cpptrace)
<br/>
[![Community Discord Link](https://img.shields.io/badge/Chat%20on%20the%20(very%20small)-Community%20Discord-blue?labelColor=2C3239&color=7289DA&style=flat&logo=discord&logoColor=959DA5)](https://discord.gg/frjaAZvqUZ)
@ -16,6 +17,9 @@ Cpptrace also has a C API, docs [here](docs/c-api.md).
- [30-Second Overview](#30-second-overview)
- [CMake FetchContent Usage](#cmake-fetchcontent-usage)
- [FAQ](#faq)
- [What about C++23 `<stacktrace>`?](#what-about-c23-stacktrace)
- [What does cpptrace have over other C++ stacktrace libraries?](#what-does-cpptrace-have-over-other-c-stacktrace-libraries)
- [Prerequisites](#prerequisites)
- [Basic Usage](#basic-usage)
- [`namespace cpptrace`](#namespace-cpptrace)
@ -23,7 +27,6 @@ Cpptrace also has a C API, docs [here](docs/c-api.md).
- [Object Traces](#object-traces)
- [Raw Traces](#raw-traces)
- [Utilities](#utilities)
- [Formatting](#formatting)
- [Configuration](#configuration)
- [Traces From All Exceptions](#traces-from-all-exceptions)
- [Removing the `CPPTRACE_` prefix](#removing-the-cpptrace_-prefix)
@ -36,7 +39,6 @@ Cpptrace also has a C API, docs [here](docs/c-api.md).
- [Signal-Safe Tracing](#signal-safe-tracing)
- [Utility Types](#utility-types)
- [Headers](#headers)
- [Libdwarf Tuning](#libdwarf-tuning)
- [Supported Debug Formats](#supported-debug-formats)
- [How to Include The Library](#how-to-include-the-library)
- [CMake FetchContent](#cmake-fetchcontent)
@ -54,10 +56,6 @@ Cpptrace also has a C API, docs [here](docs/c-api.md).
- [Summary of Library Configurations](#summary-of-library-configurations)
- [Testing Methodology](#testing-methodology)
- [Notes About the Library](#notes-about-the-library)
- [FAQ](#faq)
- [What about C++23 `<stacktrace>`?](#what-about-c23-stacktrace)
- [What does cpptrace have over other C++ stacktrace libraries?](#what-does-cpptrace-have-over-other-c-stacktrace-libraries)
- [I'm getting undefined standard library symbols like `std::__1::basic_string` on MacOS](#im-getting-undefined-standard-library-symbols-like-std__1basic_string-on-macos)
- [Contributing](#contributing)
- [License](#license)
@ -128,7 +126,6 @@ Additional notable features:
- Utilities for catching `std::exception`s and wrapping them in traced exceptions
- Signal-safe stack tracing
- Source code snippets in traces
- Extensive configuration options for [trace formatting](#formatting)
![Snippets](res/snippets.png)
@ -139,7 +136,7 @@ include(FetchContent)
FetchContent_Declare(
cpptrace
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)
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
without internet access see [How to Include The Library](#how-to-include-the-library) below.
# FAQ
## What about C++23 `<stacktrace>`?
Some day C++23's `<stacktrace>` will be ubiquitous. And maybe one day the msvc implementation will be acceptable.
The original motivation for cpptrace was to support projects using older C++ standards and as the library has grown its
functionality has extended beyond the standard library's implementation.
Cpptrace provides functionality beyond what the standard library provides and what implementations provide, such as:
- Walking inlined function calls
- Providing a lightweight interface for "raw traces"
- Resolving function parameter types
- Providing traced exception objects
- Providing an API for signal-safe stacktrace generation
- Providing a way to retrieve stack traces from arbitrary exceptions, not just special cpptrace traced exception
objects. This is a feature coming to C++26, but cpptrace provides a solution for C++11.
## What does cpptrace have over other C++ stacktrace libraries?
Other C++ stacktrace libraries, such as boost stacktrace and backward-cpp, fall short when it comes to portability and
ease of use. In testing, I found neither to provide adaquate coverage of various environments. Even when they can be
made to work in an environment they require manual configuration from the end-user, possibly requiring manual
installation of third-party dependencies. This is a highly undesirable burden to impose on users, especially when it is
for a software package which just provides diagnostics as opposed to core functionality. Additionally, cpptrace provides
support for resolving inlined calls by default for DWARF symbols (boost does not do this, backward-cpp can do this but
only for some back-ends), better support for resolving full function signatures, and nicer API, among other features.
# Prerequisites
> [!IMPORTANT]
@ -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
`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
#include <cpptrace/from_current.hpp>
#include <iostream>
void foo() {
throw std::runtime_error("foo failed");
}
@ -750,7 +691,6 @@ namespace cpptrace {
};
void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
bool can_signal_safe_unwind();
bool can_get_safe_object_frame();
}
```
@ -767,9 +707,9 @@ see the comprehensive overview and demo at [signal-safe-tracing.md](docs/signal-
> [!IMPORTANT]
> Currently signal-safe stack unwinding is only possible with `libunwind`, which must be
> [manually enabled](#library-back-ends). If signal-safe unwinding isn't supported, `safe_generate_raw_trace` will just
> produce an empty trace. `can_signal_safe_unwind` can be used to check for signal-safe unwinding support and
> `can_get_safe_object_frame` can be used to check `get_safe_object_frame` support. If object information can't be
> resolved in a signal-safe way then `get_safe_object_frame` will not populate fields beyond the `raw_address`.
> produce an empty trace. `can_signal_safe_unwind` can be used to check for signal-safe unwinding support. If object
> information can't be resolved in a signal-safe way then `get_safe_object_frame` will not populate fields beyond the
> `raw_address`.
> [!IMPORTANT]
> `_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>
struct nullable {
T raw_value;
// all members are constexpr for c++17 and beyond, some are constexpr before c++17
nullable& operator=(T value)
bool has_value() const noexcept;
T& value() noexcept;
@ -805,7 +744,6 @@ namespace cpptrace {
void reset() noexcept;
bool operator==(const nullable& other) const noexcept;
bool operator!=(const nullable& other) const noexcept;
constexpr static T null_value() noexcept; // returns the raw null value
constexpr static nullable null() noexcept; // returns a null instance
};
@ -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/from_current.hpp` | [Traces From All Exceptions](#traces-from-all-exceptions) |
| `cpptrace/io.hpp` | `operator<<` overloads for `std::ostream` and `std::formatter`s |
| `cpptrace/formatting.hpp` | Configurable formatter API |
| `cpptrace/utils.hpp` | Utility functions, configuration functions, and terminate utilities ([Utilities](#utilities), [Configuration](#configuration), and [Terminate Handling](#terminate-handling)) |
| `cpptrace/version.hpp` | Library version macros |
The main cpptrace header is `cpptrace/cpptrace.hpp` which includes everything other than `from_current.hpp` and
`version.hpp`.
## Libdwarf Tuning
For extraordinarily large binaries (multiple gigabytes), cpptrace's internal caching can result in a lot of memory
usage. Cpptrace provides some options to reduce memory usage in exchange for performance in memory-constrained
applications.
Synopsis:
```cpp
namespace cpptrace {
namespace experimental {
void set_dwarf_resolver_line_table_cache_size(nullable<std::size_t> max_entries);
void set_dwarf_resolver_disable_aranges(bool disable);
}
}
```
Explanation:
- `set_dwarf_resolver_line_table_cache_size` can be used to set a limit to the cache size with evictions done LRU.
Cpptrace loads and caches line tables for dwarf compile units. These can take a lot of space for large binaries with
lots of debug info. Passing `nullable<std::size_t>::null()` will disable the cache size (which is the default
behavior).
- `set_dwarf_resolver_disable_aranges` can be used to disable use of dwarf `.debug_aranges`, an accelerated range lookup
table for compile units emitted by many compilers. Cpptrace uses these by default if they are present since they can
speed up resolution, however, they can also result in significant memory usage.
The main cpptrace header is `cpptrace/cpptrace.hpp` which includes everything other than `from_current.hpp`.
# Supported Debug Formats
@ -905,7 +814,7 @@ include(FetchContent)
FetchContent_Declare(
cpptrace
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)
target_link_libraries(your_target cpptrace::cpptrace)
@ -921,7 +830,7 @@ information.
```sh
git clone https://github.com/jeremy-rifkin/cpptrace.git
git checkout v0.8.2
git checkout v0.7.3
mkdir cpptrace/build
cd cpptrace/build
cmake .. -DCMAKE_BUILD_TYPE=Release
@ -964,7 +873,7 @@ you when installing new libraries.
```ps1
git clone https://github.com/jeremy-rifkin/cpptrace.git
git checkout v0.8.2
git checkout v0.7.3
mkdir cpptrace/build
cd cpptrace/build
cmake .. -DCMAKE_BUILD_TYPE=Release
@ -982,7 +891,7 @@ To install just for the local user (or any custom prefix):
```sh
git clone https://github.com/jeremy-rifkin/cpptrace.git
git checkout v0.8.2
git checkout v0.7.3
mkdir cpptrace/build
cd cpptrace/build
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$HOME/wherever
@ -1055,7 +964,7 @@ make install
cd ~/scratch/cpptrace-test
git clone https://github.com/jeremy-rifkin/libdwarf-lite.git
cd libdwarf-lite
git checkout fe09ca800b988e2ff21225ac5e7468ceade2a30e
git checkout 6dbcc23dba6ffd230063bda4b9d7298bf88d9d41
mkdir build
cd build
cmake .. -DPIC_ALWAYS=On -DBUILD_DWARFDUMP=Off -DCMAKE_PREFIX_PATH=~/scratch/cpptrace-test/resources -DCMAKE_INSTALL_PREFIX=~/scratch/cpptrace-test/resources
@ -1065,7 +974,7 @@ make install
cd ~/scratch/cpptrace-test
git clone https://github.com/jeremy-rifkin/cpptrace.git
cd cpptrace
git checkout v0.8.2
git checkout v0.7.3
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=On -DCPPTRACE_USE_EXTERNAL_LIBDWARF=On -DCMAKE_PREFIX_PATH=~/scratch/cpptrace-test/resources -DCMAKE_INSTALL_PREFIX=~/scratch/cpptrace-test/resources
@ -1085,7 +994,7 @@ cpptrace and its dependencies.
Cpptrace is available through conan at https://conan.io/center/recipes/cpptrace.
```
[requires]
cpptrace/0.8.2
cpptrace/0.7.3
[generators]
CMakeDeps
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
pointers).
# FAQ
## What about C++23 `<stacktrace>`?
Some day C++23's `<stacktrace>` will be ubiquitous. And maybe one day the msvc implementation will be acceptable.
The original motivation for cpptrace was to support projects using older C++ standards and as the library has grown its
functionality has extended beyond the standard library's implementation.
Cpptrace provides functionality beyond what the standard library provides and what implementations provide, such as:
- Walking inlined function calls
- Providing a lightweight interface for "raw traces"
- Resolving function parameter types
- Providing traced exception objects
- Providing an API for signal-safe stacktrace generation
- Providing a way to retrieve stack traces from arbitrary exceptions, not just special cpptrace traced exception
objects. This is a feature coming to C++26, but cpptrace provides a solution for C++11.
## What does cpptrace have over other C++ stacktrace libraries?
Other C++ stacktrace libraries, such as boost stacktrace and backward-cpp, fall short when it comes to portability and
ease of use. In testing, I found neither to provide adaquate coverage of various environments. Even when they can be
made to work in an environment they require manual configuration from the end-user, possibly requiring manual
installation of third-party dependencies. This is a highly undesirable burden to impose on users, especially when it is
for a software package which just provides diagnostics as opposed to core functionality. Additionally, cpptrace provides
support for resolving inlined calls by default for DWARF symbols (boost does not do this, backward-cpp can do this but
only for some back-ends), better support for resolving full function signatures, and nicer API, among other features.
## I'm getting undefined standard library symbols like `std::__1::basic_string` on MacOS
If you see a linker error along the lines of the following on MacOS then it's highly likely you are mixing standard
library ABIs.
```
Undefined symbols for architecture arm64:
"std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::find(char, unsigned long) const", referenced from:
cpptrace::detail::demangle(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, bool) in libcpptrace.a(demangle_with_cxxabi.cpp.o)
cpptrace::detail::snippet_manager::build_line_table() in libcpptrace.a(snippet.cpp.o)
```
This can happen when using apple clang to compile cpptrace and gcc to compile your code, or vice versa. The reason is
that apple clang defaults to libc++ and gcc defaults to libstdc++ and these two standard library implementations are not
ABI-compatible. To resolve this, ensure you are compiling both cpptrace and your code with the same standard library by
either using the same compiler for both or using `-stdlib=libc++`/`-stdlib=libstdc++` to control which standard library
is used.
# Contributing
I'm grateful for the help I've received with this library and I welcome contributions! For information on contributing

View File

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

328
ci/build-in-all-configs.py Normal file
View 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()

View File

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

View File

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

View File

@ -14,8 +14,8 @@ cd ..
mkdir libdwarf
cd libdwarf
git init
git remote add origin https://github.com/jeremy-rifkin/libdwarf-lite.git
git fetch --depth 1 origin fe09ca800b988e2ff21225ac5e7468ceade2a30e # 0.11.1
git remote add origin https://github.com/davea42/libdwarf-code.git
git fetch --depth 1 origin 285d9d34f3e9f56cc1c487d0055f6dc54a9c54a1 # 0.11.0
git checkout FETCH_HEAD
mkdir build
cd build

View File

@ -14,8 +14,8 @@ cd ..
mkdir libdwarf
cd libdwarf
git init
git remote add origin https://github.com/jeremy-rifkin/libdwarf-lite.git
git fetch --depth 1 origin fe09ca800b988e2ff21225ac5e7468ceade2a30e # 0.11.1
git remote add origin https://github.com/davea42/libdwarf-code.git
git fetch --depth 1 origin f4f6f782a06ab0618861cf0c4474989376c69c76 # 0.11.0 + trunk with some dwarf 5 fixes
git checkout FETCH_HEAD
mkdir build
cd build

View File

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

View File

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

View File

@ -1,7 +1,7 @@
import subprocess
import sys
import itertools
from typing import List, Dict
from typing import List
from colorama import Fore, Back, Style
import re
import time
@ -23,7 +23,6 @@ class MatrixRunner:
def __init__(self, matrix, exclude):
self.matrix = matrix
self.exclude = exclude
self.include = self.parse_includes()
self.keys = [*matrix.keys()]
self.values = [*matrix.values()]
self.results = {} # insertion-ordered
@ -33,17 +32,6 @@ class MatrixRunner:
self.last_matrix_config = None
self.current_matrix_config = None
def parse_includes(self) -> Dict[str, List[str]]:
includes: Dict[str, List[str]] = dict()
for arg in sys.argv:
if arg.startswith("--slice="):
rest = arg[len("--slice="):]
key, value = rest.split(":")
if key not in includes:
includes[key] = []
includes[key].append(value)
return includes
def run_command(self, *args: List[str], always_output=False, output_matcher=None) -> bool:
self.log(f"{Fore.CYAN}{Style.BRIGHT}Running Command \"{' '.join(args)}\"{Style.RESET_ALL}")
start_time = time.time()
@ -90,11 +78,6 @@ class MatrixRunner:
def do_exclude(self, matrix_config, exclude):
return all(map(lambda k: matrix_config[k] == exclude[k], exclude.keys()))
def do_include(self, matrix_config, include):
if len(include) == 0:
return True
return all(map(lambda k: matrix_config[k] in include[k], include.keys()))
def assignment_to_matrix_config(self, assignment):
matrix_config = {}
for k, v in zip(self.matrix.keys(), assignment):
@ -104,10 +87,7 @@ class MatrixRunner:
def get_work(self):
work = []
for assignment in itertools.product(*self.matrix.values()):
config = self.assignment_to_matrix_config(assignment)
if any(map(lambda ex: self.do_exclude(config, ex), self.exclude)):
continue
if not self.do_include(config, self.include):
if any(map(lambda ex: self.do_exclude(self.assignment_to_matrix_config(assignment), ex), self.exclude)):
continue
work.append(assignment)
return work

View File

@ -4,8 +4,6 @@ function(check_support var source includes libraries definitions)
list(APPEND CMAKE_REQUIRED_INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
set(CMAKE_REQUIRED_LIBRARIES "${libraries}")
set(CMAKE_REQUIRED_DEFINITIONS "${definitions}")
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
string(CONCAT full_source "#include \"${source}\"" ${nonce})
check_cxx_source_compiles(${full_source} ${var})
set(${var} ${${var}} PARENT_SCOPE)
@ -15,10 +13,6 @@ if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
check_support(HAS_CXXABI has_cxxabi.cpp "" "" "")
endif()
if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
check_support(HAS_ATTRIBUTE_PACKED has_attribute_packed.cpp "" "" "")
endif()
if(NOT WIN32)
check_support(HAS_UNWIND has_unwind.cpp "" "" "")
check_support(HAS_EXECINFO has_execinfo.cpp "" "" "")

View File

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

View File

@ -151,17 +151,13 @@ option(CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH "" OFF)
if(PROJECT_IS_TOP_LEVEL)
option(CPPTRACE_BUILD_TESTING "" OFF)
option(CPPTRACE_BUILD_TOOLS "" OFF)
option(CPPTRACE_BUILD_BENCHMARK "" OFF)
option(CPPTRACE_BUILD_NO_SYMBOLS "" OFF)
option(CPPTRACE_BUILD_TESTING_SPLIT_DWARF "" OFF)
set(CPPTRACE_BUILD_TESTING_DWARF_VERSION "0" CACHE STRING "")
option(CPPTRACE_BUILD_TEST_RDYNAMIC "" OFF)
mark_as_advanced(
CPPTRACE_BUILD_TESTING
CPPTRACE_BUILD_TOOLS
CPPTRACE_BUILD_BENCHMARK
CPPTRACE_BUILD_NO_SYMBOLS
CPPTRACE_BUILD_BENCHMARKING
CPPTRACE_BUILD_TESTING_SPLIT_DWARF
CPPTRACE_BUILD_TESTING_DWARF_VERSION
CPPTRACE_BUILD_TEST_RDYNAMIC
@ -180,12 +176,12 @@ option(CPPTRACE_SKIP_UNIT "" OFF)
option(CPPTRACE_STD_FORMAT "" ON)
option(CPPTRACE_UNPREFIXED_TRY_CATCH "" OFF)
option(CPPTRACE_USE_EXTERNAL_GTEST "" OFF)
set(CPPTRACE_ZSTD_URL "https://github.com/facebook/zstd/releases/download/v1.5.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_TAG "fe09ca800b988e2ff21225ac5e7468ceade2a30e" CACHE STRING "") # v0.11.1
set(CPPTRACE_LIBDWARF_TAG "97fd68c6026c0237943106d6bc3e83f3661d39e8" CACHE STRING "") # v0.11.0
set(CPPTRACE_LIBDWARF_SHALLOW "1" CACHE STRING "")
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(
CPPTRACE_BACKTRACE_PATH

View File

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

View File

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

View File

@ -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);
void ctrace_get_safe_object_frame(ctrace_frame_ptr address, ctrace_safe_object_frame* out);
ctrace_bool ctrace_can_signal_safe_unwind();
ctrace_bool ctrace_can_get_safe_object_frame();
ctrace_bool can_signal_safe_unwind();
```

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,4 @@
#include "binary/elf.hpp"
#include "utils/optional.hpp"
#if IS_LINUX
@ -7,25 +6,60 @@
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <mutex>
#include <type_traits>
#include <unordered_map>
#include <elf.h>
namespace cpptrace {
namespace detail {
elf::elf(
file_wrapper file,
const std::string& object_path,
bool is_little_endian,
bool is_64
) : file(std::move(file)), object_path(object_path), is_little_endian(is_little_endian), is_64(is_64) {}
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
T elf_byteswap_if_needed(T value, bool elf_is_little) {
if(is_little_endian() == elf_is_little) {
return value;
} else {
return byteswap(value);
}
}
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);
if(file == nullptr) {
return internal_error("Unable to read object file {}", object_path);
return internal_error("Unable to read object file " + object_path);
}
// Initial checks/metadata
auto magic = load_bytes<std::array<char, 4>>(file, 0);
@ -52,385 +86,14 @@ namespace detail {
if(ei_version.unwrap_value() != 1) {
return internal_error("Unexpected ELF version " + object_path);
}
return elf(std::move(file), object_path, is_little_endian, is_64);
}
Result<std::uintptr_t, internal_error> elf::get_module_image_base() {
// get image base
if(is_64) {
return get_module_image_base_impl<64>();
return elf_get_module_image_base_from_program_table<64>(object_path, file, is_little_endian);
} 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); });
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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) {
#if defined(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) && defined(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP)
std::vector<stacktrace_frame> trace = libdwarf::resolve_frames(frames);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,11 +2,13 @@
#define UTILS_HPP
#include <algorithm>
#include <atomic>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <new>
#include <string>
#include <type_traits>
#include <utility>
@ -14,8 +16,6 @@
#include "utils/common.hpp"
#include "utils/error.hpp"
#include "utils/optional.hpp"
#include "utils/result.hpp"
namespace cpptrace {
namespace detail {
@ -35,7 +35,7 @@ namespace detail {
}
template<typename C>
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 end = std::end(container);
std::string str;
@ -82,10 +82,6 @@ namespace detail {
return str.substr(left, right - left);
}
inline bool starts_with(const std::string& str, const std::string& prefix) {
return str.size() >= prefix.size() && str.compare(0, prefix.size(), prefix) == 0;
}
inline bool is_little_endian() {
std::uint16_t num = 0x1;
const auto* ptr = (std::uint8_t*)&num;
@ -145,6 +141,265 @@ namespace detail {
constexpr unsigned n_digits(unsigned value) noexcept {
return value < 10 ? 1 : 1 + n_digits(value / 10);
}
static_assert(n_digits(1) == 1, "n_digits utility producing the wrong result");
static_assert(n_digits(9) == 1, "n_digits utility producing the wrong result");
static_assert(n_digits(10) == 2, "n_digits utility producing the wrong result");
static_assert(n_digits(11) == 2, "n_digits utility producing the wrong result");
static_assert(n_digits(1024) == 4, "n_digits utility producing the wrong result");
struct nullopt_t {};
static constexpr nullopt_t nullopt;
template<
typename T,
typename std::enable_if<!std::is_same<typename std::decay<T>::type, void>::value, int>::type = 0
>
class optional {
union {
char x;
T uvalue;
};
bool holds_value = false;
public:
optional() noexcept {}
optional(nullopt_t) noexcept {}
~optional() {
reset();
}
optional(const optional& other) : holds_value(other.holds_value) {
if(holds_value) {
new (static_cast<void*>(std::addressof(uvalue))) T(other.uvalue);
}
}
optional(optional&& other)
noexcept(std::is_nothrow_move_constructible<T>::value)
: holds_value(other.holds_value)
{
if(holds_value) {
new (static_cast<void*>(std::addressof(uvalue))) T(std::move(other.uvalue));
}
}
optional& operator=(const optional& other) {
optional copy(other);
swap(copy);
return *this;
}
optional& operator=(optional&& other)
noexcept(std::is_nothrow_move_assignable<T>::value && std::is_nothrow_move_constructible<T>::value)
{
reset();
if(other.holds_value) {
new (static_cast<void*>(std::addressof(uvalue))) T(std::move(other.uvalue));
holds_value = true;
}
return *this;
}
template<
typename U = T,
typename std::enable_if<!std::is_same<typename std::decay<U>::type, optional<T>>::value, int>::type = 0
>
optional(U&& value) : holds_value(true) {
new (static_cast<void*>(std::addressof(uvalue))) T(std::forward<U>(value));
}
template<
typename U = T,
typename std::enable_if<!std::is_same<typename std::decay<U>::type, optional<T>>::value, int>::type = 0
>
optional& operator=(U&& value) {
optional o(std::forward<U>(value));
swap(o);
return *this;
}
optional& operator=(nullopt_t) noexcept {
reset();
return *this;
}
void swap(optional& other) noexcept {
if(holds_value && other.holds_value) {
std::swap(uvalue, other.uvalue);
} else if(holds_value && !other.holds_value) {
new (&other.uvalue) T(std::move(uvalue));
uvalue.~T();
} else if(!holds_value && other.holds_value) {
new (static_cast<void*>(std::addressof(uvalue))) T(std::move(other.uvalue));
other.uvalue.~T();
}
std::swap(holds_value, other.holds_value);
}
bool has_value() const {
return holds_value;
}
explicit operator bool() const {
return holds_value;
}
void reset() {
if(holds_value) {
uvalue.~T();
}
holds_value = false;
}
NODISCARD T& unwrap() & {
ASSERT(holds_value, "Optional does not contain a value");
return uvalue;
}
NODISCARD const T& unwrap() const & {
ASSERT(holds_value, "Optional does not contain a value");
return uvalue;
}
NODISCARD T&& unwrap() && {
ASSERT(holds_value, "Optional does not contain a value");
return std::move(uvalue);
}
NODISCARD const T&& unwrap() const && {
ASSERT(holds_value, "Optional does not contain a value");
return std::move(uvalue);
}
template<typename U>
NODISCARD T value_or(U&& default_value) const & {
return holds_value ? uvalue : static_cast<T>(std::forward<U>(default_value));
}
template<typename U>
NODISCARD T value_or(U&& default_value) && {
return holds_value ? std::move(uvalue) : static_cast<T>(std::forward<U>(default_value));
}
};
extern std::atomic_bool absorb_trace_exceptions;
template<typename T, typename E, typename std::enable_if<!std::is_same<T, E>::value, int>::type = 0>
class Result {
union {
T value_;
E error_;
};
enum class member { value, error };
member active;
public:
Result(T&& value) : value_(std::move(value)), active(member::value) {}
Result(E&& error) : error_(std::move(error)), active(member::error) {
if(!absorb_trace_exceptions.load()) {
std::fprintf(stderr, "%s\n", unwrap_error().what());
}
}
Result(T& value) : value_(T(value)), active(member::value) {}
Result(E& error) : error_(E(error)), active(member::error) {
if(!absorb_trace_exceptions.load()) {
std::fprintf(stderr, "%s\n", unwrap_error().what());
}
}
Result(Result&& other) : active(other.active) {
if(other.active == member::value) {
new (&value_) T(std::move(other.value_));
} else {
new (&error_) E(std::move(other.error_));
}
}
~Result() {
if(active == member::value) {
value_.~T();
} else {
error_.~E();
}
}
bool has_value() const {
return active == member::value;
}
bool is_error() const {
return active == member::error;
}
explicit operator bool() const {
return has_value();
}
NODISCARD optional<T> value() const & {
return has_value() ? value_ : nullopt;
}
NODISCARD optional<E> error() const & {
return is_error() ? error_ : nullopt;
}
NODISCARD optional<T> value() && {
return has_value() ? std::move(value_) : nullopt;
}
NODISCARD optional<E> error() && {
return is_error() ? std::move(error_) : nullopt;
}
NODISCARD T& unwrap_value() & {
ASSERT(has_value(), "Result does not contain a value");
return value_;
}
NODISCARD const T& unwrap_value() const & {
ASSERT(has_value(), "Result does not contain a value");
return value_;
}
NODISCARD T unwrap_value() && {
ASSERT(has_value(), "Result does not contain a value");
return std::move(value_);
}
NODISCARD E& unwrap_error() & {
ASSERT(is_error(), "Result does not contain an error");
return error_;
}
NODISCARD const E& unwrap_error() const & {
ASSERT(is_error(), "Result does not contain an error");
return error_;
}
NODISCARD E unwrap_error() && {
ASSERT(is_error(), "Result does not contain an error");
return std::move(error_);
}
template<typename U>
NODISCARD T value_or(U&& default_value) const & {
return has_value() ? value_ : static_cast<T>(std::forward<U>(default_value));
}
template<typename U>
NODISCARD T value_or(U&& default_value) && {
return has_value() ? std::move(value_) : static_cast<T>(std::forward<U>(default_value));
}
void drop_error() const {
if(is_error()) {
std::fprintf(stderr, "%s\n", unwrap_error().what());
}
}
};
struct monostate {};
// TODO: Re-evaluate use of off_t
template<typename T, typename std::enable_if<std::is_trivial<T>::value, int>::type = 0>
@ -162,9 +417,9 @@ namespace detail {
// shamelessly stolen from stackoverflow
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
auto pos = path.find_last_of(maybe_windows ? "/\\" : "/");
auto pos = path.rfind('/');
if(pos == std::string::npos) {
return path;
} else {
@ -188,24 +443,20 @@ namespace detail {
return static_cast<U>(v);
}
template<typename T, typename U = T>
T exchange(T& obj, U&& value) {
T old = std::move(obj);
obj = std::forward<U>(value);
return old;
}
struct monostate {};
// TODO: Rework some stuff here. Not sure deleters should be optional or moved.
// Also allow file_wrapper file = std::fopen(object_path.c_str(), "rb");
template<
typename T,
typename D,
// Note: Previously checked if D was invocable and returned void but this kept causing problems for MSVC
typename D
// workaround for:
// == 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.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_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
@ -214,6 +465,7 @@ namespace detail {
std::is_nothrow_move_constructible<T>::value,
int
>::type = 0
#endif
>
class raii_wrapper {
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) {
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*)>;
template<class T, class... Args>
auto make_unique(Args&&... args) -> typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::type {
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
template<typename T>
class maybe_owned {
std::unique_ptr<T> owned;
@ -273,40 +535,7 @@ namespace detail {
T* operator->() {
return ptr;
}
T& operator*() {
return *ptr;
}
};
template<typename F>
class scope_guard {
F f;
bool active;
public:
template<
typename G,
typename std::enable_if<!std::is_same<typename std::decay<G>::type, scope_guard<F>>::value, int>::type = 0
>
scope_guard(G&& f) : f(std::forward<F>(f)), active(true) {}
~scope_guard() {
if(active) {
f();
}
}
scope_guard(const scope_guard&) = delete;
scope_guard(scope_guard&& other) : f(std::move(other.f)), active(exchange(other.active, false)) {}
scope_guard& operator=(const scope_guard&) = delete;
scope_guard& operator=(scope_guard&& other) {
f = std::move(other.f);
active = exchange(other.active, false);
return *this;
}
};
template<typename F>
NODISCARD auto scope_exit(F&& f) -> scope_guard<F> {
return scope_guard<F>(std::forward<F>(f));
}
}
}

View File

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

View File

@ -7,6 +7,12 @@ set(
${warning_options} $<$<CXX_COMPILER_ID:GNU>:-Wno-infinite-recursion>
)
set(
debug
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-g>
$<$<CXX_COMPILER_ID:MSVC>:/DEBUG>
)
macro(add_test_dependencies exec_name)
target_compile_features(${exec_name} PRIVATE cxx_std_11)
target_link_libraries(${exec_name} PRIVATE ${target_name})
@ -19,12 +25,10 @@ macro(add_test_dependencies exec_name)
target_compile_options(${exec_name} PRIVATE -gdwarf-${CPPTRACE_BUILD_TESTING_DWARF_VERSION})
endif()
# Clang has been fast to adopt dwarf 5, other tools (e.g. addr2line from binutils) have not
if(NOT CPPTRACE_BUILD_NO_SYMBOLS)
check_cxx_compiler_flag("-gdwarf-4" HAS_DWARF4)
if(HAS_DWARF4)
target_compile_options(${exec_name} PRIVATE "$<$<CONFIG:Debug>:-gdwarf-4>")
endif()
endif()
# TODO: add debug info for mingw clang?
if(CPPTRACE_BUILD_TEST_RDYNAMIC)
set_property(TARGET ${exec_name} PROPERTY ENABLE_EXPORTS ON)
@ -77,19 +81,12 @@ if(NOT CPPTRACE_SKIP_UNIT)
add_executable(
unittest
unit/main.cpp
unit/tracing/raw_trace.cpp
unit/tracing/object_trace.cpp
unit/tracing/stacktrace.cpp
unit/tracing/from_current.cpp
unit/tracing/from_current_z.cpp
unit/tracing/traced_exception.cpp
unit/internals/optional.cpp
unit/internals/lru_cache.cpp
unit/internals/result.cpp
unit/internals/string_utils.cpp
unit/internals/general.cpp
unit/lib/formatting.cpp
unit/lib/nullable.cpp
unit/raw_trace.cpp
unit/object_trace.cpp
unit/stacktrace.cpp
unit/from_current.cpp
unit/from_current_z.cpp
unit/traced_exception.cpp
)
target_compile_features(unittest PRIVATE cxx_std_20)
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)
target_compile_definitions(unittest PRIVATE CPPTRACE_SANITIZER_BUILD)
endif()
if(CPPTRACE_BUILD_NO_SYMBOLS)
target_compile_definitions(unittest PRIVATE CPPTRACE_BUILD_NO_SYMBOLS)
endif()
target_include_directories(unittest PRIVATE ../src)
add_test(NAME unittest COMMAND unittest)
endif()
find_package(LLVM REQUIRED CONFIG)
add_executable(jit_test jit/main.cpp)
add_test_dependencies(jit_test)
target_include_directories(jit_test PUBLIC ${LLVM_INCLUDE_DIRS})
target_compile_definitions(jit_test PUBLIC ${LLVM_DEFINITIONS})
llvm_map_components_to_libnames(llvm_libs support core orcjit native)
target_link_libraries(jit_test PUBLIC ${llvm_libs})

View File

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

View 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

File diff suppressed because it is too large Load Diff

View File

@ -10,8 +10,6 @@
#include <cpptrace/cpptrace.hpp>
#include <cpptrace/from_current.hpp>
#include "common.hpp"
using namespace std::literals;
@ -54,7 +52,8 @@ TEST(FromCurrent, Basic) {
trace.frames.begin(),
trace.frames.end(),
[](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());
@ -62,29 +61,29 @@ TEST(FromCurrent, Basic) {
int j = 0;
ASSERT_LT(i, trace.frames.size());
ASSERT_LT(j, line_numbers.size());
EXPECT_FILE(trace.frames[i].filename, "from_current.cpp");
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[j]);
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("from_current.cpp"));
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]);
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_from_current_3"));
i++;
j++;
ASSERT_LT(i, trace.frames.size());
ASSERT_LT(j, line_numbers.size());
EXPECT_FILE(trace.frames[i].filename, "from_current.cpp");
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[j]);
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("from_current.cpp"));
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]);
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_from_current_2"));
i++;
j++;
ASSERT_LT(i, trace.frames.size());
ASSERT_LT(j, line_numbers.size());
EXPECT_FILE(trace.frames[i].filename, "from_current.cpp");
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[j]);
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("from_current.cpp"));
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]);
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_from_current_1"));
i++;
j++;
ASSERT_LT(i, trace.frames.size());
ASSERT_LT(j, line_numbers.size());
EXPECT_FILE(trace.frames[i].filename, "from_current.cpp");
EXPECT_LINE(trace.frames[i].line.value(), line_numbers[j]);
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("from_current.cpp"));
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]);
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("FromCurrent_Basic_Test::TestBody"));
}
}
@ -105,7 +104,8 @@ TEST(FromCurrent, CorrectHandler) {
trace.frames.begin(),
trace.frames.end(),
[](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());
@ -134,7 +134,8 @@ TEST(FromCurrent, RawTrace) {
trace.frames.begin(),
trace.frames.end(),
[](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());

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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"));
}
}

View File

@ -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"
)
);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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