Merge branch 'dev' into main
This commit is contained in:
commit
203dbb524e
52
.github/workflows/build.yml
vendored
52
.github/workflows/build.yml
vendored
@ -19,12 +19,21 @@ jobs:
|
||||
pip3 install colorama
|
||||
- name: libdwarf
|
||||
run: |
|
||||
cd ..
|
||||
mkdir zstd
|
||||
cd zstd
|
||||
git init
|
||||
git remote add origin https://github.com/facebook/zstd.git
|
||||
git fetch --depth 1 origin 63779c798237346c2b245c546c40b72a5a5913fe # 1.5.5
|
||||
git checkout FETCH_HEAD
|
||||
make -j
|
||||
sudo make install
|
||||
cd ..
|
||||
mkdir libdwarf
|
||||
cd libdwarf
|
||||
git init
|
||||
git remote add origin https://github.com/jeremy-rifkin/libdwarf-code.git
|
||||
git fetch --depth 1 origin 6216e185863f41d6f19ab850caabfff7326020d7
|
||||
git remote add origin https://github.com/jeremy-rifkin/libdwarf-lite.git
|
||||
git fetch --depth 1 origin 5c0cb251f94b27e90184e6b2d9a0c9c62593babc
|
||||
git checkout FETCH_HEAD
|
||||
mkdir build
|
||||
cd build
|
||||
@ -36,7 +45,7 @@ jobs:
|
||||
run: |
|
||||
python3 ci/build-in-all-configs.py --${{matrix.compiler}}
|
||||
build-macos:
|
||||
runs-on: macos-13
|
||||
runs-on: macos-14
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@ -48,12 +57,21 @@ jobs:
|
||||
pip3 install colorama
|
||||
- name: libdwarf
|
||||
run: |
|
||||
cd ..
|
||||
mkdir zstd
|
||||
cd zstd
|
||||
git init
|
||||
git remote add origin https://github.com/facebook/zstd.git
|
||||
git fetch --depth 1 origin 63779c798237346c2b245c546c40b72a5a5913fe # 1.5.5
|
||||
git checkout FETCH_HEAD
|
||||
make -j
|
||||
sudo make install
|
||||
cd ..
|
||||
mkdir libdwarf
|
||||
cd libdwarf
|
||||
git init
|
||||
git remote add origin https://github.com/jeremy-rifkin/libdwarf-code.git
|
||||
git fetch --depth 1 origin 6216e185863f41d6f19ab850caabfff7326020d7
|
||||
git remote add origin https://github.com/jeremy-rifkin/libdwarf-lite.git
|
||||
git fetch --depth 1 origin 5c0cb251f94b27e90184e6b2d9a0c9c62593babc
|
||||
git checkout FETCH_HEAD
|
||||
mkdir build
|
||||
cd build
|
||||
@ -79,24 +97,34 @@ jobs:
|
||||
pip3 install colorama
|
||||
- name: libdwarf
|
||||
run: |
|
||||
if("${{matrix.compiler}}" -eq "gcc") {
|
||||
cd ..
|
||||
mkdir zstd
|
||||
cd zstd
|
||||
git init
|
||||
git remote add origin https://github.com/facebook/zstd.git
|
||||
git fetch --depth 1 origin 63779c798237346c2b245c546c40b72a5a5913fe # 1.5.5
|
||||
git checkout FETCH_HEAD
|
||||
cd build/cmake
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DZSTD_BUILD_SHARED=On -DZSTD_BUILD_SHARED=Off -DZSTD_LEGACY_SUPPORT=Off -DZSTD_BUILD_PROGRAMS=Off -DZSTD_BUILD_CONTRIB=Off -DZSTD_BUILD_TESTS=Off -G"Unix Makefiles"
|
||||
make -j
|
||||
make install
|
||||
cd ../../../..
|
||||
mkdir libdwarf
|
||||
cd libdwarf
|
||||
git init
|
||||
git remote add origin https://github.com/jeremy-rifkin/libdwarf-code.git
|
||||
git fetch --depth 1 origin 6216e185863f41d6f19ab850caabfff7326020d7
|
||||
git remote add origin https://github.com/jeremy-rifkin/libdwarf-lite.git
|
||||
git fetch --depth 1 origin 5c0cb251f94b27e90184e6b2d9a0c9c62593babc
|
||||
git checkout FETCH_HEAD
|
||||
mkdir build
|
||||
cd build
|
||||
if("${{matrix.compiler}}" -eq "gcc") {
|
||||
cmake .. -DPIC_ALWAYS=TRUE -DBUILD_DWARFDUMP=FALSE -G"Unix Makefiles"
|
||||
make -j
|
||||
make install
|
||||
} else {
|
||||
cmake .. -DPIC_ALWAYS=TRUE -DBUILD_DWARFDUMP=FALSE
|
||||
msbuild INSTALL.vcxproj
|
||||
}
|
||||
cd ../../cpptrace
|
||||
}
|
||||
- name: build
|
||||
run: |
|
||||
python3 ci/build-in-all-configs.py --${{matrix.compiler}}
|
||||
|
||||
121
.github/workflows/cmake-integration.yml
vendored
121
.github/workflows/cmake-integration.yml
vendored
@ -7,6 +7,10 @@ on:
|
||||
jobs:
|
||||
test-linux-fetchcontent:
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shared: [On, Off]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: test
|
||||
@ -17,11 +21,15 @@ jobs:
|
||||
cp -rv cpptrace/test/fetchcontent-integration .
|
||||
mkdir fetchcontent-integration/build
|
||||
cd fetchcontent-integration/build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Debug -DCPPTRACE_TAG=$tag
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Debug -DCPPTRACE_TAG=$tag -DBUILD_SHARED_LIBS=${{matrix.shared}}
|
||||
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
|
||||
@ -29,7 +37,7 @@ jobs:
|
||||
tag=$(git rev-parse --abbrev-ref HEAD)
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Debug
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=${{matrix.shared}}
|
||||
sudo make -j install
|
||||
cd ../..
|
||||
cp -rv cpptrace/test/findpackage-integration .
|
||||
@ -40,6 +48,10 @@ jobs:
|
||||
./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
|
||||
@ -49,12 +61,16 @@ jobs:
|
||||
cp -rv cpptrace add_subdirectory-integration
|
||||
mkdir add_subdirectory-integration/build
|
||||
cd add_subdirectory-integration/build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Debug
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=${{matrix.shared}}
|
||||
make
|
||||
./main
|
||||
|
||||
test-macos-fetchcontent:
|
||||
runs-on: macos-13
|
||||
runs-on: macos-14
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shared: [On, Off]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: test
|
||||
@ -65,11 +81,15 @@ jobs:
|
||||
cp -rv cpptrace/test/fetchcontent-integration .
|
||||
mkdir fetchcontent-integration/build
|
||||
cd fetchcontent-integration/build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Debug -DCPPTRACE_TAG=$tag
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Debug -DCPPTRACE_TAG=$tag -DBUILD_SHARED_LIBS=${{matrix.shared}}
|
||||
make
|
||||
./main
|
||||
test-macos-findpackage:
|
||||
runs-on: macos-13
|
||||
runs-on: macos-14
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shared: [On, Off]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: test
|
||||
@ -78,7 +98,7 @@ jobs:
|
||||
echo $tag
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Debug
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=${{matrix.shared}}
|
||||
sudo make -j install
|
||||
cd ../..
|
||||
cp -rv cpptrace/test/findpackage-integration .
|
||||
@ -88,7 +108,11 @@ jobs:
|
||||
make
|
||||
./main
|
||||
test-macos-add_subdirectory:
|
||||
runs-on: macos-13
|
||||
runs-on: macos-14
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shared: [On, Off]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: test
|
||||
@ -98,12 +122,16 @@ jobs:
|
||||
cp -rv cpptrace add_subdirectory-integration
|
||||
mkdir add_subdirectory-integration/build
|
||||
cd add_subdirectory-integration/build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Debug
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=${{matrix.shared}}
|
||||
make
|
||||
./main
|
||||
|
||||
test-mingw-fetchcontent:
|
||||
runs-on: windows-2022
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shared: [On, Off]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: test
|
||||
@ -114,11 +142,41 @@ jobs:
|
||||
cp -Recurse cpptrace/test/fetchcontent-integration .
|
||||
mkdir fetchcontent-integration/build
|
||||
cd fetchcontent-integration/build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Debug -DCPPTRACE_TAG="$tag" -DCMAKE_BUILD_TYPE=g++ "-GUnix Makefiles"
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Debug -DCPPTRACE_TAG="$tag" "-GUnix Makefiles" -DBUILD_SHARED_LIBS=${{matrix.shared}}
|
||||
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
|
||||
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
|
||||
if("${{matrix.shared}}" -eq "On") {
|
||||
cp C:/foo/bin/libcpptrace.dll .
|
||||
}
|
||||
./main
|
||||
test-mingw-add_subdirectory:
|
||||
runs-on: windows-2022
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shared: [On, Off]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: test
|
||||
@ -128,11 +186,15 @@ jobs:
|
||||
cp -Recurse cpptrace add_subdirectory-integration
|
||||
mkdir add_subdirectory-integration/build
|
||||
cd add_subdirectory-integration/build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_BUILD_TYPE=g++ "-GUnix Makefiles"
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Debug "-GUnix Makefiles" -DBUILD_SHARED_LIBS=${{matrix.shared}}
|
||||
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
|
||||
@ -145,11 +207,44 @@ jobs:
|
||||
cp -Recurse cpptrace/test/fetchcontent-integration .
|
||||
mkdir fetchcontent-integration/build
|
||||
cd fetchcontent-integration/build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Debug -DCPPTRACE_TAG="$tag"
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Debug -DCPPTRACE_TAG="$tag" -DBUILD_SHARED_LIBS=${{matrix.shared}}
|
||||
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.10.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
|
||||
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
|
||||
if("${{matrix.shared}}" -eq "On") {
|
||||
cp C:/foo/bin/cpptrace.dll .
|
||||
}
|
||||
.\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
|
||||
@ -161,6 +256,6 @@ jobs:
|
||||
cp -Recurse cpptrace add_subdirectory-integration
|
||||
mkdir add_subdirectory-integration/build
|
||||
cd add_subdirectory-integration/build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Debug
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=${{matrix.shared}}
|
||||
msbuild demo_project.sln
|
||||
.\Debug\main.exe
|
||||
|
||||
63
.github/workflows/test.yml
vendored
63
.github/workflows/test.yml
vendored
@ -22,12 +22,21 @@ jobs:
|
||||
pip3 install colorama
|
||||
- name: libdwarf
|
||||
run: |
|
||||
cd ..
|
||||
mkdir zstd
|
||||
cd zstd
|
||||
git init
|
||||
git remote add origin https://github.com/facebook/zstd.git
|
||||
git fetch --depth 1 origin 63779c798237346c2b245c546c40b72a5a5913fe # 1.5.5
|
||||
git checkout FETCH_HEAD
|
||||
make -j
|
||||
sudo make install
|
||||
cd ..
|
||||
mkdir libdwarf
|
||||
cd libdwarf
|
||||
git init
|
||||
git remote add origin https://github.com/jeremy-rifkin/libdwarf-code.git
|
||||
git fetch --depth 1 origin 6216e185863f41d6f19ab850caabfff7326020d7
|
||||
git remote add origin https://github.com/jeremy-rifkin/libdwarf-lite.git
|
||||
git fetch --depth 1 origin 5c0cb251f94b27e90184e6b2d9a0c9c62593babc
|
||||
git checkout FETCH_HEAD
|
||||
mkdir build
|
||||
cd build
|
||||
@ -39,7 +48,7 @@ jobs:
|
||||
run: |
|
||||
python3 ci/test-all-configs.py --${{matrix.compiler}}
|
||||
test-macos:
|
||||
runs-on: macos-13
|
||||
runs-on: macos-14
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@ -49,12 +58,21 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- name: libdwarf
|
||||
run: |
|
||||
cd ..
|
||||
mkdir zstd
|
||||
cd zstd
|
||||
git init
|
||||
git remote add origin https://github.com/facebook/zstd.git
|
||||
git fetch --depth 1 origin 63779c798237346c2b245c546c40b72a5a5913fe # 1.5.5
|
||||
git checkout FETCH_HEAD
|
||||
make -j
|
||||
sudo make install
|
||||
cd ..
|
||||
mkdir libdwarf
|
||||
cd libdwarf
|
||||
git init
|
||||
git remote add origin https://github.com/jeremy-rifkin/libdwarf-code.git
|
||||
git fetch --depth 1 origin 6216e185863f41d6f19ab850caabfff7326020d7
|
||||
git remote add origin https://github.com/jeremy-rifkin/libdwarf-lite.git
|
||||
git fetch --depth 1 origin 5c0cb251f94b27e90184e6b2d9a0c9c62593babc
|
||||
git checkout FETCH_HEAD
|
||||
mkdir build
|
||||
cd build
|
||||
@ -68,6 +86,17 @@ jobs:
|
||||
- name: build and test
|
||||
run: |
|
||||
python3 ci/test-all-configs.py --${{matrix.compiler}}
|
||||
# - name: bundle artifacts
|
||||
# if: always()
|
||||
# run: |
|
||||
# tar czfH bundle.tar.gz build
|
||||
# - name: upload artifacts
|
||||
# uses: actions/upload-artifact@v4
|
||||
# if: always()
|
||||
# with:
|
||||
# name: build-macos-${{matrix.compiler}}${{matrix.shared}}
|
||||
# path: bundle.tar.gz
|
||||
# retention-days: 2
|
||||
test-windows:
|
||||
runs-on: windows-2022
|
||||
strategy:
|
||||
@ -84,24 +113,34 @@ jobs:
|
||||
pip3 install colorama
|
||||
- name: libdwarf
|
||||
run: |
|
||||
if("${{matrix.compiler}}" -eq "gcc") {
|
||||
cd ..
|
||||
mkdir zstd
|
||||
cd zstd
|
||||
git init
|
||||
git remote add origin https://github.com/facebook/zstd.git
|
||||
git fetch --depth 1 origin 63779c798237346c2b245c546c40b72a5a5913fe # 1.5.5
|
||||
git checkout FETCH_HEAD
|
||||
cd build/cmake
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DZSTD_BUILD_SHARED=On -DZSTD_BUILD_SHARED=Off -DZSTD_LEGACY_SUPPORT=Off -DZSTD_BUILD_PROGRAMS=Off -DZSTD_BUILD_CONTRIB=Off -DZSTD_BUILD_TESTS=Off -G"Unix Makefiles"
|
||||
make -j
|
||||
make install
|
||||
cd ../../../..
|
||||
mkdir libdwarf
|
||||
cd libdwarf
|
||||
git init
|
||||
git remote add origin https://github.com/jeremy-rifkin/libdwarf-code.git
|
||||
git fetch --depth 1 origin 6216e185863f41d6f19ab850caabfff7326020d7
|
||||
git remote add origin https://github.com/jeremy-rifkin/libdwarf-lite.git
|
||||
git fetch --depth 1 origin 5c0cb251f94b27e90184e6b2d9a0c9c62593babc
|
||||
git checkout FETCH_HEAD
|
||||
mkdir build
|
||||
cd build
|
||||
if("${{matrix.compiler}}" -eq "gcc") {
|
||||
cmake .. -DPIC_ALWAYS=TRUE -DBUILD_DWARFDUMP=FALSE -G"Unix Makefiles"
|
||||
make -j
|
||||
make install
|
||||
} else {
|
||||
cmake .. -DPIC_ALWAYS=TRUE -DBUILD_DWARFDUMP=FALSE
|
||||
msbuild INSTALL.vcxproj
|
||||
}
|
||||
cd ../../cpptrace
|
||||
}
|
||||
- name: build and test
|
||||
run: |
|
||||
python3 ci/test-all-configs.py --${{matrix.compiler}}
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -4,3 +4,4 @@ build*/
|
||||
repro*/
|
||||
__pycache__
|
||||
scratch
|
||||
.vscode
|
||||
|
||||
177
CMakeLists.txt
177
CMakeLists.txt
@ -189,6 +189,7 @@ add_library(cpptrace::cpptrace ALIAS ${target_name})
|
||||
target_sources(
|
||||
${target_name} PRIVATE
|
||||
include/cpptrace/cpptrace.hpp
|
||||
include/ctrace/ctrace.h
|
||||
)
|
||||
|
||||
# add /src files to target
|
||||
@ -196,6 +197,7 @@ target_sources(
|
||||
${target_name} PRIVATE
|
||||
# src
|
||||
src/cpptrace.cpp
|
||||
src/ctrace.cpp
|
||||
src/demangle/demangle_with_cxxabi.cpp
|
||||
src/demangle/demangle_with_winapi.cpp
|
||||
src/demangle/demangle_with_nothing.cpp
|
||||
@ -229,20 +231,21 @@ target_compile_options(
|
||||
$<$<CXX_COMPILER_ID:MSVC>:/W4 /WX /permissive->
|
||||
)
|
||||
|
||||
# ---- Generate Build Info Headers ----
|
||||
|
||||
# used in export header generated below
|
||||
if(NOT CPPTRACE_BUILD_SHARED)
|
||||
target_compile_definitions(${target_name} PUBLIC CPPTRACE_STATIC_DEFINE)
|
||||
if(CPPTRACE_WERROR_BUILD)
|
||||
target_compile_options(
|
||||
${target_name}
|
||||
PRIVATE
|
||||
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Werror>
|
||||
$<$<CXX_COMPILER_ID:MSVC>:/WX>
|
||||
)
|
||||
endif()
|
||||
|
||||
# generate header file with export macros prefixed with BASE_NAME
|
||||
include(GenerateExportHeader)
|
||||
generate_export_header(
|
||||
${target_name}
|
||||
BASE_NAME cpptrace
|
||||
EXPORT_FILE_NAME include/cpptrace/cpptrace_export.hpp
|
||||
)
|
||||
# ---- Generate Build Info Headers ----
|
||||
|
||||
if(build_type STREQUAL "STATIC")
|
||||
target_compile_definitions(${target_name} PUBLIC CPPTRACE_STATIC_DEFINE)
|
||||
set(CPPTRACE_STATIC_DEFINE TRUE)
|
||||
endif()
|
||||
|
||||
# ---- Library Properties ----
|
||||
|
||||
@ -335,29 +338,111 @@ endif()
|
||||
|
||||
if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF)
|
||||
target_compile_definitions(${target_name} PUBLIC CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF)
|
||||
# First, dependencies: Zstd and zlib (currently relying on system zlib)
|
||||
if(CPPTRACE_USE_EXTERNAL_ZSTD)
|
||||
find_package(zstd)
|
||||
else()
|
||||
include(FetchContent)
|
||||
cmake_policy(SET CMP0074 NEW)
|
||||
FetchContent_Declare(
|
||||
zstd
|
||||
GIT_REPOSITORY https://github.com/facebook/zstd.git
|
||||
GIT_TAG 63779c798237346c2b245c546c40b72a5a5913fe # v1.5.5
|
||||
GIT_SHALLOW 1
|
||||
SOURCE_SUBDIR build/cmake
|
||||
)
|
||||
# FetchContent_MakeAvailable(zstd)
|
||||
FetchContent_GetProperties(zstd)
|
||||
if(NOT zstd_POPULATED)
|
||||
FetchContent_Populate(zstd)
|
||||
set(ZSTD_BUILD_PROGRAMS OFF)
|
||||
set(ZSTD_BUILD_CONTRIB OFF)
|
||||
set(ZSTD_BUILD_TESTS OFF)
|
||||
set(ZSTD_BUILD_STATIC ON)
|
||||
set(ZSTD_BUILD_SHARED OFF)
|
||||
set(ZSTD_LEGACY_SUPPORT OFF)
|
||||
add_subdirectory("${zstd_SOURCE_DIR}/build/cmake" "${zstd_BINARY_DIR}")
|
||||
endif()
|
||||
endif()
|
||||
# Libdwarf itself
|
||||
if(CPPTRACE_USE_EXTERNAL_LIBDWARF)
|
||||
find_package(libdwarf REQUIRED)
|
||||
target_compile_definitions(${target_name} PRIVATE CPPTRACE_USE_EXTERNAL_LIBDWARF)
|
||||
else()
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
|
||||
set(PIC_ALWAYS TRUE)
|
||||
set(BUILD_DWARFDUMP FALSE)
|
||||
# set(PIC_ALWAYS TRUE)
|
||||
# set(BUILD_DWARFDUMP FALSE)
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
libdwarf
|
||||
# GIT_REPOSITORY https://github.com/jeremy-rifkin/libdwarf-code.git
|
||||
# GIT_REPOSITORY https://github.com/davea42/libdwarf-code.git
|
||||
# GIT_TAG 6216e185863f41d6f19ab850caabfff7326020d7 # v0.8.0
|
||||
# GIT_TAG 8b0bd09d8c77d45a68cb1bb00a54186a92b683d9 # v0.9.0
|
||||
# GIT_TAG 8cdcc531f310d1c5ae61da469d8056bdd36b77e7 # v0.9.1 + some cmake changes
|
||||
# Using a lightweight mirror that's optimized for clone + configure speed
|
||||
GIT_REPOSITORY https://github.com/jeremy-rifkin/libdwarf-lite.git
|
||||
GIT_TAG c78e984f3abbd20f6e01d6f51819e826b1691f65
|
||||
# GIT_TAG c78e984f3abbd20f6e01d6f51819e826b1691f65 # v0.8.0
|
||||
# GIT_TAG 71090c680b4c943448ba87a0f1f864f174e4edda # v0.9.0
|
||||
GIT_TAG 5c0cb251f94b27e90184e6b2d9a0c9c62593babc # v0.9.1 + some cmake changes
|
||||
# GIT_REPOSITORY https://github.com/jeremy-rifkin/libdwarf-code.git
|
||||
# GIT_TAG 308b55331b564d4fdbe3bc6856712270e5b2395b
|
||||
GIT_SHALLOW 1
|
||||
)
|
||||
FetchContent_MakeAvailable(libdwarf)
|
||||
# FetchContent_MakeAvailable(libdwarf)
|
||||
FetchContent_GetProperties(libdwarf)
|
||||
if(NOT libdwarf_POPULATED)
|
||||
set(PIC_ALWAYS TRUE)
|
||||
set(BUILD_DWARFDUMP FALSE)
|
||||
# set(ENABLE_DECOMPRESSION FALSE)
|
||||
FetchContent_Populate(libdwarf)
|
||||
add_subdirectory("${libdwarf_SOURCE_DIR}" "${libdwarf_BINARY_DIR}")
|
||||
target_include_directories(
|
||||
dwarf
|
||||
PRIVATE
|
||||
${zstd_SOURCE_DIR}/lib
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
if(CPPTRACE_CONAN)
|
||||
target_link_libraries(${target_name} PRIVATE libdwarf::libdwarf)
|
||||
target_compile_definitions(${target_name} PRIVATE CPPTRACE_USE_NESTED_LIBDWARF_HEADER_PATH)
|
||||
elseif(CPPTRACE_VCPKG)
|
||||
target_link_libraries(${target_name} PRIVATE $<IF:$<TARGET_EXISTS:libdwarf::dwarf-static>,libdwarf::dwarf-static,libdwarf::dwarf-shared>)
|
||||
target_link_libraries(${target_name} PRIVATE libdwarf::dwarf)
|
||||
elseif(CPPTRACE_USE_EXTERNAL_LIBDWARF)
|
||||
if(DEFINED LIBDWARF_LIBRARIES)
|
||||
target_link_libraries(${target_name} PRIVATE ${LIBDWARF_LIBRARIES})
|
||||
else()
|
||||
# if LIBDWARF_LIBRARIES wasn't set by find_package, try looking for libdwarf::dwarf-static,
|
||||
# libdwarf::dwarf-shared, libdwarf::dwarf, then libdwarf
|
||||
# libdwarf v0.8.0 installs with the target libdwarf::dwarf somehow, despite creating libdwarf::dwarf-static or
|
||||
# libdwarf::dwarf-shared under fetchcontent
|
||||
if(TARGET libdwarf::dwarf-static)
|
||||
set(LIBDWARF_LIBRARIES libdwarf::dwarf-static)
|
||||
elseif(TARGET libdwarf::dwarf-shared)
|
||||
set(LIBDWARF_LIBRARIES libdwarf::dwarf-shared)
|
||||
elseif(TARGET libdwarf::dwarf)
|
||||
set(LIBDWARF_LIBRARIES libdwarf::dwarf)
|
||||
elseif(TARGET libdwarf)
|
||||
set(LIBDWARF_LIBRARIES libdwarf)
|
||||
else()
|
||||
message(FATAL_ERROR "Couldn't find libdwarf target name to link against")
|
||||
endif()
|
||||
target_link_libraries(${target_name} PRIVATE ${LIBDWARF_LIBRARIES})
|
||||
endif()
|
||||
# There seems to be no consistency at all about where libdwarf decides to place its headers........ Figure out if
|
||||
# it's libdwarf/libdwarf.h and libdwarf/dwarf.h or just libdwarf.h and dwarf.h
|
||||
include(CheckIncludeFileCXX)
|
||||
# libdwarf's cmake doesn't properly set variables to indicate where its libraries live
|
||||
get_target_property(LIBDWARF_INTERFACE_INCLUDE_DIRECTORIES ${LIBDWARF_LIBRARIES} INTERFACE_INCLUDE_DIRECTORIES)
|
||||
set(CMAKE_REQUIRED_INCLUDES ${LIBDWARF_INTERFACE_INCLUDE_DIRECTORIES})
|
||||
CHECK_INCLUDE_FILE_CXX("libdwarf/libdwarf.h" LIBDWARF_IS_NESTED)
|
||||
CHECK_INCLUDE_FILE_CXX("libdwarf.h" LIBDWARF_IS_NOT_NESTED)
|
||||
# check_include_file("libdwarf/libdwarf.h" LIBDWARF_IS_NESTED)
|
||||
# check_support(LIBDWARF_IS_NESTED nested_libdwarf_include.cpp "" "" "")
|
||||
if(${LIBDWARF_IS_NESTED})
|
||||
target_compile_definitions(${target_name} PRIVATE CPPTRACE_USE_NESTED_LIBDWARF_HEADER_PATH)
|
||||
elseif(NOT LIBDWARF_IS_NOT_NESTED)
|
||||
message(FATAL_ERROR "Couldn't find libdwarf.h")
|
||||
endif()
|
||||
else()
|
||||
target_link_libraries(${target_name} PRIVATE libdwarf::dwarf-static)
|
||||
endif()
|
||||
@ -471,44 +556,28 @@ endif()
|
||||
|
||||
# =============================================== Demo/test ===============================================
|
||||
|
||||
macro(add_test_dependencies exec_name)
|
||||
target_compile_features(${exec_name} PRIVATE cxx_std_11)
|
||||
target_link_libraries(${exec_name} PRIVATE ${target_name})
|
||||
# Clang has been fast to adopt dwarf 5, other tools (e.g. addr2line from binutils) have not
|
||||
check_cxx_compiler_flag("-gdwarf-4" HAS_DWARF4)
|
||||
if(HAS_DWARF4)
|
||||
target_compile_options(${exec_name} PRIVATE "$<$<CONFIG:Debug>:-gdwarf-4>")
|
||||
endif()
|
||||
# TODO: add debug info for mingw clang?
|
||||
if(CPPTRACE_BUILD_TEST_RDYNAMIC)
|
||||
set_property(TARGET ${exec_name} PROPERTY ENABLE_EXPORTS ON)
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
if(CPPTRACE_BUILD_TESTING)
|
||||
add_executable(test test/test.cpp)
|
||||
target_compile_features(test PRIVATE cxx_std_11)
|
||||
target_link_libraries(test PRIVATE ${target_name})
|
||||
# Clang has been fast to adopt dwarf 5, other tools (e.g. addr2line from binutils) have not
|
||||
check_cxx_compiler_flag("-gdwarf-4" HAS_DWARF4)
|
||||
if(HAS_DWARF4)
|
||||
target_compile_options(test PRIVATE "$<$<CONFIG:Debug>:-gdwarf-4>")
|
||||
endif()
|
||||
if(CPPTRACE_BUILD_TEST_RDYNAMIC)
|
||||
set_property(TARGET test PROPERTY ENABLE_EXPORTS ON)
|
||||
endif()
|
||||
if(APPLE) # TODO: Temporary
|
||||
add_custom_command(
|
||||
TARGET test
|
||||
POST_BUILD
|
||||
COMMAND dsymutil $<TARGET_FILE:test>
|
||||
)
|
||||
endif()
|
||||
|
||||
add_executable(demo test/demo.cpp)
|
||||
target_compile_features(demo PRIVATE cxx_std_11)
|
||||
target_link_libraries(demo PRIVATE ${target_name})
|
||||
# Clang has been fast to adopt dwarf 5, other tools (e.g. addr2line from binutils) have not
|
||||
check_cxx_compiler_flag("-gdwarf-4" HAS_DWARF4)
|
||||
if(HAS_DWARF4)
|
||||
target_compile_options(demo PRIVATE "$<$<CONFIG:Debug>:-gdwarf-4>")
|
||||
endif()
|
||||
if(CPPTRACE_BUILD_TEST_RDYNAMIC)
|
||||
set_property(TARGET demo PROPERTY ENABLE_EXPORTS ON)
|
||||
endif()
|
||||
if(APPLE) # TODO: Temporary
|
||||
add_custom_command(
|
||||
TARGET demo
|
||||
POST_BUILD
|
||||
COMMAND dsymutil $<TARGET_FILE:demo>
|
||||
)
|
||||
endif()
|
||||
add_executable(c_demo test/ctrace_demo.c)
|
||||
|
||||
add_test_dependencies(test)
|
||||
add_test_dependencies(demo)
|
||||
add_test_dependencies(c_demo)
|
||||
|
||||
if(UNIX)
|
||||
add_executable(signal_demo test/signal_demo.cpp)
|
||||
|
||||
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2023 Jeremy Rifkin
|
||||
Copyright (c) 2023-2024 Jeremy Rifkin
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
|
||||
151
README.md
151
README.md
@ -11,6 +11,8 @@
|
||||
Cpptrace is a simple, portable, and self-contained C++ stacktrace library supporting C++11 and greater on Linux, macOS,
|
||||
and Windows including MinGW and Cygwin environments. The goal: Make stack traces simple for once.
|
||||
|
||||
Cpptrace also has a C API, docs [here](docs/c-api.md).
|
||||
|
||||
## Table of Contents <!-- omit in toc -->
|
||||
|
||||
- [30-Second Overview](#30-second-overview)
|
||||
@ -23,6 +25,7 @@ and Windows including MinGW and Cygwin environments. The goal: Make stack traces
|
||||
- [Object Traces](#object-traces)
|
||||
- [Raw Traces](#raw-traces)
|
||||
- [Utilities](#utilities)
|
||||
- [Configuration](#configuration)
|
||||
- [Traced Exceptions](#traced-exceptions)
|
||||
- [Wrapping std::exceptions](#wrapping-stdexceptions)
|
||||
- [Exception handling with cpptrace](#exception-handling-with-cpptrace)
|
||||
@ -33,6 +36,8 @@ and Windows including MinGW and Cygwin environments. The goal: Make stack traces
|
||||
- [CMake FetchContent](#cmake-fetchcontent)
|
||||
- [System-Wide Installation](#system-wide-installation)
|
||||
- [Local User Installation](#local-user-installation)
|
||||
- [Use Without CMake](#use-without-cmake)
|
||||
- [Installation Without Package Managers or FetchContent](#installation-without-package-managers-or-fetchcontent)
|
||||
- [Package Managers](#package-managers)
|
||||
- [Conan](#conan)
|
||||
- [Vcpkg](#vcpkg)
|
||||
@ -113,7 +118,7 @@ endif()
|
||||
Be sure to configure with `-DCMAKE_BUILD_TYPE=Debug` or `-DDCMAKE_BUILD_TYPE=RelWithDebInfo` for symbols and line
|
||||
information.
|
||||
|
||||
On macos a little extra work to generate a .dSYM file is required, see [Platform Logistics](#platform-logistics) below.
|
||||
On macOS it is recommended to generate a .dSYM file, see [Platform Logistics](#platform-logistics) below.
|
||||
|
||||
For other ways to use the library, such as through package managers, a system-wide installation, or on a platform
|
||||
without internet access see [Usage](#usage) below.
|
||||
@ -126,7 +131,9 @@ Some day C++23's `<stacktrace>` will be ubiquitous. And maybe one day the msvc i
|
||||
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 also provides additional functionality including being able to
|
||||
Cpptrace also provides additional functionality including showing inlined function calls, allowing generation of
|
||||
lightweight "raw traces" that can be resolved later, offering exception objects that embed a lightweight trace when
|
||||
thrown, and providing an API for safe tracing from signal handlers.
|
||||
|
||||
# In-Depth Documentation
|
||||
|
||||
@ -138,9 +145,6 @@ method to get lightweight raw traces, which are just vectors of program counters
|
||||
|
||||
**Note:** Debug info (`-g`/`/Z7`/`/Zi`/`/DEBUG`) is generally required for good trace information.
|
||||
|
||||
**Note:** Currently on Mac .dSYM files are required, which can be generated with `dsymutil yourbinary`. A cmake snippet
|
||||
for generating these is provided in [Platform Logistics](#platform-logistics) below.
|
||||
|
||||
All functions are thread-safe unless otherwise noted.
|
||||
|
||||
### Stack Traces
|
||||
@ -218,7 +222,7 @@ namespace cpptrace {
|
||||
|
||||
### Raw Traces
|
||||
|
||||
Raw trace access: A vector of program counters. These are ideal for traces you want to resolve later.
|
||||
Raw trace access: A vector of program counters. These are ideal for fast and cheap traces you want to resolve later.
|
||||
|
||||
Note it is important executables and shared libraries in memory aren't somehow unmapped otherwise libdl calls (and
|
||||
`GetModuleFileName` in windows) will fail to figure out where the program counter corresponds to.
|
||||
@ -246,14 +250,6 @@ namespace cpptrace {
|
||||
`cpptrace::demangle` provides a helper function for name demangling, since it has to implement that helper internally
|
||||
anyways.
|
||||
|
||||
The library makes an attempt to fail silently and continue during trace generation if any errors are encountered.
|
||||
`cpptrace::absorb_trace_exceptions` can be used to configure whether these exceptions are absorbed silently internally
|
||||
or wether they're rethrown to the caller.
|
||||
|
||||
`cpptrace::experimental::set_cache_mode` can be used to control time-memory tradeoffs within the library. By default
|
||||
speed is prioritized. If using this function, set the cache mode at the very start of your program before any traces are
|
||||
performed.
|
||||
|
||||
`cpptrace::isatty` and the fileno definitions are useful for deciding whether to use color when printing stack taces.
|
||||
|
||||
`cpptrace::register_terminate_handler()` is a helper function to set a custom `std::terminate` handler that prints a
|
||||
@ -262,7 +258,6 @@ stack trace from a cpptrace exception (more info below) and otherwise behaves li
|
||||
```cpp
|
||||
namespace cpptrace {
|
||||
std::string demangle(const std::string& name);
|
||||
void absorb_trace_exceptions(bool absorb);
|
||||
bool isatty(int fd);
|
||||
|
||||
extern const int stdin_fileno;
|
||||
@ -270,6 +265,25 @@ namespace cpptrace {
|
||||
extern const int stdout_fileno;
|
||||
|
||||
void register_terminate_handler();
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
`cpptrace::absorb_trace_exceptions`: Configure whether the library silently absorbs internal exceptions and continues.
|
||||
Default is true.
|
||||
|
||||
`cpptrace::ctrace_enable_inlined_call_resolution`: Configure whether the library will attempt to resolve inlined call
|
||||
information for release builds. Default is true.
|
||||
|
||||
`cpptrace::experimental::set_cache_mode`: Control time-memory tradeoffs within the library. By default speed is
|
||||
prioritized. If using this function, set the cache mode at the very start of your program before any traces are
|
||||
performed.
|
||||
|
||||
```cpp
|
||||
namespace cpptrace {
|
||||
void absorb_trace_exceptions(bool absorb);
|
||||
void ctrace_enable_inlined_call_resolution(bool enable);
|
||||
|
||||
enum class cache_mode {
|
||||
// Only minimal lookup tables
|
||||
@ -311,13 +325,13 @@ interface or type system but this seems to be the best way to do this.
|
||||
```cpp
|
||||
namespace cpptrace {
|
||||
class lazy_exception : public exception {
|
||||
mutable detail::lazy_trace_holder trace_holder; // basically std::variant<raw_trace, stacktrace>, more docs later
|
||||
// lazy_trace_holder is basically a std::variant<raw_trace, stacktrace>, more docs later
|
||||
mutable detail::lazy_trace_holder trace_holder;
|
||||
mutable std::string what_string;
|
||||
protected:
|
||||
explicit lazy_exception(std::size_t skip, std::size_t max_depth) noexcept;
|
||||
explicit lazy_exception(std::size_t skip) noexcept;
|
||||
public:
|
||||
explicit lazy_exception() noexcept : lazy_exception(1) {}
|
||||
explicit lazy_exception(
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept : trace_holder(std::move(trace)) {}
|
||||
const char* what() const noexcept override;
|
||||
const char* message() const noexcept override;
|
||||
const stacktrace& trace() const noexcept override;
|
||||
@ -332,19 +346,22 @@ well as a number of traced exception classes resembling `<stdexcept>`:
|
||||
|
||||
```cpp
|
||||
namespace cpptrace {
|
||||
class CPPTRACE_EXPORT exception_with_message : public lazy_exception {
|
||||
class exception_with_message : public lazy_exception {
|
||||
mutable std::string user_message;
|
||||
protected:
|
||||
explicit exception_with_message(std::string&& message_arg, std::size_t skip) noexcept;
|
||||
explicit exception_with_message(std::string&& message_arg, std::size_t skip, std::size_t max_depth) noexcept;
|
||||
public:
|
||||
explicit exception_with_message(std::string&& message_arg) noexcept
|
||||
: exception_with_message(std::move(message_arg), 1) {}
|
||||
|
||||
explicit exception_with_message(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept : lazy_exception(std::move(trace)), user_message(std::move(message_arg)) {}
|
||||
const char* message() const noexcept override;
|
||||
};
|
||||
|
||||
// All stdexcept errors have analogs here. Same constructor as exception_with_message.
|
||||
// All stdexcept errors have analogs here. All have the constructor:
|
||||
// explicit the_error(
|
||||
// std::string&& message_arg,
|
||||
// raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
// ) noexcept
|
||||
// : exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
class logic_error : public exception_with_message { ... };
|
||||
class domain_error : public exception_with_message { ... };
|
||||
class invalid_argument : public exception_with_message { ... };
|
||||
@ -429,8 +446,8 @@ namespace cpptrace {
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** Not all back-ends and platforms support these interfaces. If signal-safe unwinding isn't supported
|
||||
`safe_generate_raw_trace` will just produce an empty trace and if object information can't be resolved in a signal-safe
|
||||
**Note:** Not all back-ends and platforms support these interfaces. If signal-safe unwinding isn't supported,
|
||||
`safe_generate_raw_trace` will just produce an empty trace, and if object information can't be resolved in a signal-safe
|
||||
way then `get_safe_object_frame` will not populate fields beyond the `raw_address`.
|
||||
|
||||
**Another big note:** Calls to shared objects can be lazy-loaded where the first call to the shared object invokes
|
||||
@ -504,7 +521,7 @@ namespace cpptrace {
|
||||
| DWARF in separate binary (binary gnu debug link) | ️️✔️ |
|
||||
| DWARF in separate binary (split dwarf) | ✔️ |
|
||||
| DWARF in dSYM | ✔️ |
|
||||
| DWARF in via Mach-O debug map | Soon |
|
||||
| DWARF in via Mach-O debug map | ✔️ |
|
||||
| Windows debug symbols in PDB | ✔️ |
|
||||
|
||||
DWARF5 added DWARF package files. As far as I can tell no compiler implements these yet.
|
||||
@ -609,6 +626,75 @@ Using manually:
|
||||
g++ main.cpp -o main -g -Wall -I$HOME/wherever/include -L$HOME/wherever/lib -lcpptrace
|
||||
```
|
||||
|
||||
## Use Without CMake
|
||||
|
||||
To use the library without cmake first follow the installation instructions at
|
||||
[System-Wide Installation](#system-wide-installation), [Local User Installation](#local-user-installation),
|
||||
or [Package Managers](#package-managers).
|
||||
|
||||
In addition to any include or library paths you'll need to specify to tell the compiler where cpptrace was installed the
|
||||
typical dependencies for cpptrace are:
|
||||
|
||||
| Compiler | Platform | Dependencies |
|
||||
| ----------------------- | ---------------- | ---------------------------------- |
|
||||
| gcc, clang, intel, etc. | Linux/macos/unix | `-lcpptrace -ldwarf -lz -ldl` |
|
||||
| gcc | Windows | `-lcpptrace -ldbghelp -ldwarf -lz` |
|
||||
| msvc | Windows | `cpptrace.lib dbghelp.lib` |
|
||||
| clang | Windows | `-lcpptrace -ldbghelp` |
|
||||
|
||||
Dependencies may differ if different back-ends are manually selected.
|
||||
|
||||
## Installation Without Package Managers or FetchContent
|
||||
|
||||
Some users may prefer, or need to, to install cpptrace without package managers or fetchcontent (e.g. if their system
|
||||
does not have internet access). Below are instructions for how to install libdwarf and cpptrace.
|
||||
|
||||
<details>
|
||||
<summary>Installation Without Package Managers or FetchContent</summary>
|
||||
|
||||
Here is an example for how to build cpptrace and libdwarf. `~/scratch/cpptrace-test` is used as a working directory and
|
||||
the libraries are installed to `~/scratch/cpptrace-test/resources`.
|
||||
|
||||
```sh
|
||||
mkdir -p ~/scratch/cpptrace-test/resources
|
||||
|
||||
cd ~/scratch/cpptrace-test
|
||||
git clone https://github.com/facebook/zstd.git
|
||||
cd zstd
|
||||
git checkout 63779c798237346c2b245c546c40b72a5a5913fe
|
||||
cd build/cmake
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_INSTALL_PREFIX=~/scratch/cpptrace-test/resources -DZSTD_BUILD_PROGRAMS=On -DZSTD_BUILD_CONTRIB=On -DZSTD_BUILD_TESTS=On -DZSTD_BUILD_STATIC=On -DZSTD_BUILD_SHARED=On -DZSTD_LEGACY_SUPPORT=On
|
||||
make -j
|
||||
make install
|
||||
|
||||
cd ~/scratch/cpptrace-test
|
||||
git clone https://github.com/jeremy-rifkin/libdwarf-lite.git
|
||||
cd libdwarf-lite
|
||||
git checkout 5c0cb251f94b27e90184e6b2d9a0c9c62593babc
|
||||
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
|
||||
make -j
|
||||
make install
|
||||
|
||||
cd ~/scratch/cpptrace-test
|
||||
git clone https://github.com/jeremy-rifkin/cpptrace.git
|
||||
cd cpptrace
|
||||
git checkout v0.3.1
|
||||
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
|
||||
make -j
|
||||
make install
|
||||
```
|
||||
|
||||
The `~/scratch/cpptrace-test/resources` directory also serves as a bundle you can ship with all the installed files for
|
||||
cpptrace and its dependencies.
|
||||
|
||||
</details>
|
||||
|
||||
## Package Managers
|
||||
|
||||
### Conan
|
||||
@ -659,7 +745,7 @@ if(WIN32)
|
||||
endif()
|
||||
```
|
||||
|
||||
Generating a .dSYM file on macos:
|
||||
On macOS it's recommended to generate a dSYM file containing debug information for your program:
|
||||
|
||||
In xcode cmake this can be done with
|
||||
|
||||
@ -812,7 +898,6 @@ and time-memory tradeoffs. If you find the current implementation is either slow
|
||||
to explore some of these options.
|
||||
|
||||
A couple things I'd like to improve in the future:
|
||||
- On MacOS .dSYM files are required
|
||||
- On Windows when collecting symbols with dbghelp (msvc/clang) parameter types are almost perfect but due to limitations
|
||||
in dbghelp the library cannot accurately show const and volatile qualifiers or rvalue references (these appear as
|
||||
pointers).
|
||||
|
||||
@ -48,6 +48,8 @@ def build(matrix):
|
||||
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",
|
||||
@ -63,6 +65,8 @@ def build(matrix):
|
||||
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",
|
||||
@ -98,6 +102,8 @@ def build_full_or_auto(matrix):
|
||||
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"] != "":
|
||||
@ -113,6 +119,8 @@ def build_full_or_auto(matrix):
|
||||
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']}")
|
||||
|
||||
@ -30,7 +30,7 @@ def similarity(name: str, target: List[str]) -> int:
|
||||
return -1
|
||||
return c
|
||||
|
||||
def output_matches(output: str, params: Tuple[str]):
|
||||
def output_matches(raw_output: str, params: Tuple[str]):
|
||||
target = []
|
||||
|
||||
if params[0].startswith("gcc") or params[0].startswith("g++"):
|
||||
@ -72,19 +72,20 @@ def output_matches(output: str, params: Tuple[str]):
|
||||
print(f"Reading from {file}")
|
||||
|
||||
with open(os.path.join(expected_dir, file), "r") as f:
|
||||
expected = f.read()
|
||||
raw_expected = f.read()
|
||||
|
||||
if output.strip() == "":
|
||||
if raw_output.strip() == "":
|
||||
print(f"Error: No output from test")
|
||||
return False
|
||||
|
||||
expected = [line.strip().split("||") for line in expected.split("\n")]
|
||||
output = [line.strip().split("||") for line in output.split("\n")]
|
||||
expected = [line.strip().split("||") for line in raw_expected.split("\n")]
|
||||
output = [line.strip().split("||") for line in raw_output.split("\n")]
|
||||
|
||||
max_line_diff = 0
|
||||
|
||||
errored = False
|
||||
|
||||
try:
|
||||
for i, ((output_file, output_line, output_symbol), (expected_file, expected_line, expected_symbol)) in enumerate(zip(output, expected)):
|
||||
if output_file != expected_file:
|
||||
print(f"Error: File name mismatch on line {i + 1}, found \"{output_file}\" expected \"{expected_file}\"")
|
||||
@ -97,10 +98,19 @@ def output_matches(output: str, params: Tuple[str]):
|
||||
errored = True
|
||||
if expected_symbol == "main" or expected_symbol == "main()":
|
||||
break
|
||||
except ValueError:
|
||||
print("ValueError during output checking")
|
||||
errored = True
|
||||
|
||||
if errored:
|
||||
print("Output:")
|
||||
print(raw_output)
|
||||
print("Expected:")
|
||||
print(raw_expected)
|
||||
|
||||
return not errored
|
||||
|
||||
def run_command(*args: List[str]):
|
||||
def run_command(*args: List[str], always_output=False):
|
||||
global failed
|
||||
print(f"{Fore.CYAN}{Style.BRIGHT}Running Command \"{' '.join(args)}\"{Style.RESET_ALL}")
|
||||
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
@ -116,6 +126,11 @@ def run_command(*args: List[str]):
|
||||
return False
|
||||
else:
|
||||
print(f"{Fore.GREEN}{Style.BRIGHT}Command succeeded{Style.RESET_ALL}")
|
||||
if always_output:
|
||||
print("stdout:")
|
||||
print(stdout.decode("utf-8"), end="")
|
||||
print("stderr:")
|
||||
print(stderr.decode("utf-8"), end="")
|
||||
return True
|
||||
|
||||
def run_test(test_binary, params: Tuple[str]):
|
||||
@ -126,7 +141,7 @@ def run_test(test_binary, params: Tuple[str]):
|
||||
print(Style.RESET_ALL, end="") # makefile in parallel sometimes messes up colors
|
||||
|
||||
if test.returncode != 0:
|
||||
print("[🔴 Test command failed]")
|
||||
print(f"[🔴 Test command failed with code {test.returncode}]")
|
||||
print("stderr:")
|
||||
print(test_stderr.decode("utf-8"), end="")
|
||||
print("stdout:")
|
||||
@ -155,6 +170,8 @@ def build(matrix):
|
||||
f"-DCMAKE_C_COMPILER={get_c_compiler_counterpart(matrix['compiler'])}",
|
||||
f"-DCMAKE_CXX_STANDARD={matrix['std']}",
|
||||
f"-DCPPTRACE_USE_EXTERNAL_LIBDWARF=On",
|
||||
f"-DCPPTRACE_USE_EXTERNAL_ZSTD=On",
|
||||
f"-DCPPTRACE_WERROR_BUILD=On",
|
||||
f"-D{matrix['unwind']}=On",
|
||||
f"-D{matrix['symbols']}=On",
|
||||
f"-D{matrix['demangle']}=On",
|
||||
@ -176,6 +193,8 @@ def build(matrix):
|
||||
f"-DCMAKE_C_COMPILER={get_c_compiler_counterpart(matrix['compiler'])}",
|
||||
f"-DCMAKE_CXX_STANDARD={matrix['std']}",
|
||||
f"-DCPPTRACE_USE_EXTERNAL_LIBDWARF=On",
|
||||
f"-DCPPTRACE_USE_EXTERNAL_ZSTD=On",
|
||||
f"-DCPPTRACE_WERROR_BUILD=On",
|
||||
f"-D{matrix['unwind']}=On",
|
||||
f"-D{matrix['symbols']}=On",
|
||||
f"-D{matrix['demangle']}=On",
|
||||
@ -202,6 +221,8 @@ def build_full_or_auto(matrix):
|
||||
f"-DCMAKE_C_COMPILER={get_c_compiler_counterpart(matrix['compiler'])}",
|
||||
f"-DCMAKE_CXX_STANDARD={matrix['std']}",
|
||||
f"-DCPPTRACE_USE_EXTERNAL_LIBDWARF=On",
|
||||
f"-DCPPTRACE_USE_EXTERNAL_ZSTD=On",
|
||||
f"-DCPPTRACE_WERROR_BUILD=On",
|
||||
f"-DCPPTRACE_BACKTRACE_PATH=/usr/lib/gcc/x86_64-linux-gnu/10/include/backtrace.h",
|
||||
"-DCPPTRACE_BUILD_TESTING=On",
|
||||
f"-DBUILD_SHARED_LIBS={matrix['shared']}"
|
||||
@ -220,6 +241,8 @@ def build_full_or_auto(matrix):
|
||||
f"-DCMAKE_C_COMPILER={get_c_compiler_counterpart(matrix['compiler'])}",
|
||||
f"-DCMAKE_CXX_STANDARD={matrix['std']}",
|
||||
f"-DCPPTRACE_USE_EXTERNAL_LIBDWARF=On",
|
||||
f"-DCPPTRACE_USE_EXTERNAL_ZSTD=On",
|
||||
f"-DCPPTRACE_WERROR_BUILD=On",
|
||||
"-DCPPTRACE_BUILD_TESTING=On",
|
||||
f"-DBUILD_SHARED_LIBS={matrix['shared']}"
|
||||
]
|
||||
|
||||
@ -5,7 +5,6 @@ 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
|
||||
# PATTERN "**/third_party" EXCLUDE # skip third party directory
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
# | CPPTRACE_INCLUDES_WITH_SYSTEM | Not Top-Level | ON |
|
||||
# | CPPTRACE_INSTALL_CMAKEDIR | Always | ${CMAKE_INSTALL_LIBDIR}/cmake/${package_name} |
|
||||
# | CPPTRACE_USE_EXTERNAL_LIBDWARF | Always | OFF |
|
||||
# | CPPTRACE_USE_EXTERNAL_ZSTD | Always | OFF |
|
||||
# | ... | | |
|
||||
# ---------------------------------------------------------------------------------------------------
|
||||
|
||||
@ -158,15 +159,18 @@ if(PROJECT_IS_TOP_LEVEL)
|
||||
endif()
|
||||
|
||||
option(CPPTRACE_USE_EXTERNAL_LIBDWARF "" OFF)
|
||||
option(CPPTRACE_USE_EXTERNAL_ZSTD "" OFF)
|
||||
option(CPPTRACE_CONAN "" OFF)
|
||||
option(CPPTRACE_VCPKG "" OFF)
|
||||
option(CPPTRACE_SANITIZER_BUILD "" OFF)
|
||||
option(CPPTRACE_WERROR_BUILD "" OFF)
|
||||
|
||||
mark_as_advanced(
|
||||
CPPTRACE_BACKTRACE_PATH
|
||||
CPPTRACE_ADDR2LINE_PATH
|
||||
CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH
|
||||
CPPTRACE_SANITIZER_BUILD
|
||||
CPPTRACE_WERROR_BUILD
|
||||
CPPTRACE_CONAN
|
||||
CPPTRACE_VCPKG
|
||||
)
|
||||
|
||||
@ -4,13 +4,18 @@
|
||||
# Dependencies
|
||||
if(@CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF@)
|
||||
include(CMakeFindDependencyMacro)
|
||||
find_dependency(zstd REQUIRED)
|
||||
find_dependency(libdwarf REQUIRED)
|
||||
endif()
|
||||
|
||||
# We cannot modify an existing IMPORT target
|
||||
if(NOT TARGET assert::assert)
|
||||
if(NOT TARGET cpptrace::cpptrace)
|
||||
|
||||
# import targets
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/@package_name@-targets.cmake")
|
||||
|
||||
endif()
|
||||
|
||||
if(@CPPTRACE_STATIC_DEFINE@)
|
||||
target_compile_definitions(cpptrace::cpptrace INTERFACE CPPTRACE_STATIC_DEFINE)
|
||||
endif()
|
||||
|
||||
167
docs/c-api.md
Normal file
167
docs/c-api.md
Normal file
@ -0,0 +1,167 @@
|
||||
# ctrace <!-- omit in toc -->
|
||||
|
||||
Cpptrace provides a C API under the name ctrace, documented below.
|
||||
|
||||
## Table of Contents <!-- omit in toc -->
|
||||
|
||||
- [Documentation](#documentation)
|
||||
- [Stack Traces](#stack-traces)
|
||||
- [Object Traces](#object-traces)
|
||||
- [Raw Traces](#raw-traces)
|
||||
- [Utilities](#utilities)
|
||||
- [Utility types](#utility-types)
|
||||
- [Configuration](#configuration)
|
||||
- [Signal-Safe Tracing](#signal-safe-tracing)
|
||||
|
||||
## Documentation
|
||||
|
||||
All ctrace declarations are in the `ctrace.h` header:
|
||||
|
||||
```c
|
||||
#include <ctrace/ctrace.h>
|
||||
```
|
||||
|
||||
### Stack Traces
|
||||
|
||||
Generate stack traces with `ctrace_generate_trace()`. Often `skip = 0` and `max_depth = SIZE_MAX` is what you want for
|
||||
the parameters.
|
||||
|
||||
`ctrace_stacktrace_to_string` and `ctrace_print_stacktrace` can then be used for output.
|
||||
|
||||
`ctrace_free_stacktrace` must be called when you are done with the trace.
|
||||
|
||||
```c
|
||||
typedef struct ctrace_stacktrace ctrace_stacktrace;
|
||||
|
||||
struct ctrace_stacktrace_frame {
|
||||
ctrace_frame_ptr address;
|
||||
uint32_t line;
|
||||
uint32_t column;
|
||||
const char* filename;
|
||||
const char* symbol;
|
||||
ctrace_bool is_inline;
|
||||
};
|
||||
|
||||
struct ctrace_stacktrace {
|
||||
ctrace_stacktrace_frame* frames;
|
||||
size_t count;
|
||||
};
|
||||
|
||||
ctrace_stacktrace ctrace_generate_trace(size_t skip, size_t max_depth);
|
||||
ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color);
|
||||
void ctrace_print_stacktrace(const ctrace_stacktrace* trace, FILE* to, ctrace_bool use_color);
|
||||
void ctrace_free_stacktrace(ctrace_stacktrace* trace);
|
||||
```
|
||||
|
||||
### Object Traces
|
||||
|
||||
Object traces contain the most basic information needed to construct a stack trace outside the currently running
|
||||
executable. It contains the raw address, the address in the binary (ASLR and the object file's memory space and whatnot
|
||||
is resolved), and the path to the object the instruction pointer is located in.
|
||||
|
||||
`ctrace_free_object_trace` must be called when you are done with the trace.
|
||||
|
||||
```c
|
||||
typedef struct ctrace_object_trace ctrace_object_trace;
|
||||
|
||||
struct ctrace_object_frame {
|
||||
ctrace_frame_ptr raw_address;
|
||||
ctrace_frame_ptr obj_address;
|
||||
const char* obj_path;
|
||||
};
|
||||
|
||||
struct ctrace_object_trace {
|
||||
ctrace_object_frame* frames;
|
||||
size_t count;
|
||||
};
|
||||
|
||||
ctrace_object_trace ctrace_generate_object_trace(size_t skip, size_t max_depth);
|
||||
ctrace_stacktrace ctrace_resolve_object_trace(const ctrace_object_trace* trace);
|
||||
void ctrace_free_object_trace(ctrace_object_trace* trace);
|
||||
```
|
||||
|
||||
### Raw Traces
|
||||
|
||||
Raw traces are arrays of program counters. These are ideal for fast and cheap traces you want to resolve later.
|
||||
|
||||
Note it is important executables and shared libraries in memory aren't somehow unmapped otherwise libdl calls (and
|
||||
`GetModuleFileName` in windows) will fail to figure out where the program counter corresponds to.
|
||||
|
||||
`ctrace_free_raw_trace` must be called when you are done with the trace.
|
||||
|
||||
```c
|
||||
typedef struct ctrace_raw_trace ctrace_raw_trace;
|
||||
|
||||
ctrace_raw_trace ctrace_generate_raw_trace(size_t skip, size_t max_depth);
|
||||
ctrace_stacktrace ctrace_resolve_raw_trace(const ctrace_raw_trace* trace);
|
||||
void ctrace_free_raw_trace(ctrace_raw_trace* trace);
|
||||
```
|
||||
|
||||
### Utilities
|
||||
|
||||
`ctrace_demangle`: Helper function for name demangling
|
||||
|
||||
`ctrace_stdin_fileno`, `ctrace_stderr_fileno`, `ctrace_stdout_fileno`: Returns the appropriate file descriptor for the
|
||||
respective standard stream.
|
||||
|
||||
`ctrace_isatty`: Checks if a file descriptor corresponds to a tty device.
|
||||
|
||||
```c
|
||||
ctrace_owning_string ctrace_demangle(const char* mangled);
|
||||
int ctrace_stdin_fileno(void);
|
||||
int ctrace_stderr_fileno(void);
|
||||
int ctrace_stdout_fileno(void);
|
||||
ctrace_bool ctrace_isatty(int fd);
|
||||
```
|
||||
|
||||
### Utility types
|
||||
|
||||
For ABI reasons `ctrace_bool`s are used for bools. `ctrace_owning_string` is a wrapper type which indicates that a
|
||||
string is owned and must be freed.
|
||||
|
||||
```c
|
||||
typedef int8_t ctrace_bool;
|
||||
typedef struct {
|
||||
const char* data;
|
||||
} ctrace_owning_string;
|
||||
ctrace_owning_string ctrace_generate_owning_string(const char* raw_string);
|
||||
void ctrace_free_owning_string(ctrace_owning_string* string);
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
`experimental::set_cache_mode`: Control time-memory tradeoffs within the library. By default speed is prioritized. If
|
||||
using this function, set the cache mode at the very start of your program before any traces are performed. Note: This
|
||||
API is not set in stone yet and could change in the future.
|
||||
|
||||
`ctrace_enable_inlined_call_resolution`: Configure whether the library will attempt to resolve inlined call information for
|
||||
release builds. Default is true.
|
||||
|
||||
```c
|
||||
typedef enum {
|
||||
/* Only minimal lookup tables */
|
||||
ctrace_prioritize_memory = 0,
|
||||
/* Build lookup tables but don't keep them around between trace calls */
|
||||
ctrace_hybrid = 1,
|
||||
/* Build lookup tables as needed */
|
||||
ctrace_prioritize_speed = 2
|
||||
} ctrace_cache_mode;
|
||||
void ctrace_set_cache_mode(ctrace_cache_mode mode);
|
||||
void ctrace_enable_inlined_call_resolution(ctrace_bool enable);
|
||||
```
|
||||
|
||||
### Signal-Safe Tracing
|
||||
|
||||
For more details on the signal-safe tracing interface please refer to the README and the
|
||||
[signal-safe-tracing.md](signal-safe-tracing.md) guide.
|
||||
|
||||
```c
|
||||
typedef struct ctrace_safe_object_frame ctrace_safe_object_frame;
|
||||
struct ctrace_safe_object_frame {
|
||||
ctrace_frame_ptr raw_address;
|
||||
ctrace_frame_ptr relative_obj_address;
|
||||
char object_path[CTRACE_PATH_MAX + 1];
|
||||
};
|
||||
size_t ctrace_safe_generate_raw_trace(ctrace_frame_ptr* buffer, size_t size, size_t skip, size_t max_depth);
|
||||
void ctrace_get_safe_object_frame(ctrace_frame_ptr address, ctrace_safe_object_frame* out);
|
||||
```
|
||||
@ -10,7 +10,28 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <cpptrace/cpptrace_export.hpp>
|
||||
#ifdef _WIN32
|
||||
#define CPPTRACE_EXPORT_ATTR __declspec(dllexport)
|
||||
#define CPPTRACE_IMPORT_ATTR __declspec(dllimport)
|
||||
#else
|
||||
#define CPPTRACE_EXPORT_ATTR __attribute__((visibility("default")))
|
||||
#define CPPTRACE_IMPORT_ATTR __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
#ifdef CPPTRACE_STATIC_DEFINE
|
||||
# define CPPTRACE_EXPORT
|
||||
# define CPPTRACE_NO_EXPORT
|
||||
#else
|
||||
# ifndef CPPTRACE_EXPORT
|
||||
# ifdef cpptrace_lib_EXPORTS
|
||||
/* We are building this library */
|
||||
# define CPPTRACE_EXPORT CPPTRACE_EXPORT_ATTR
|
||||
# else
|
||||
/* We are using this library */
|
||||
# define CPPTRACE_EXPORT CPPTRACE_IMPORT_ATTR
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if __cplusplus >= 202002L
|
||||
#ifdef __has_include
|
||||
@ -213,7 +234,6 @@ namespace cpptrace {
|
||||
|
||||
// utilities:
|
||||
CPPTRACE_EXPORT std::string demangle(const std::string& name);
|
||||
CPPTRACE_EXPORT void absorb_trace_exceptions(bool absorb);
|
||||
CPPTRACE_EXPORT bool isatty(int fd);
|
||||
|
||||
CPPTRACE_EXPORT extern const int stdin_fileno;
|
||||
@ -222,19 +242,24 @@ namespace cpptrace {
|
||||
|
||||
CPPTRACE_EXPORT void register_terminate_handler();
|
||||
|
||||
// configuration:
|
||||
CPPTRACE_EXPORT void absorb_trace_exceptions(bool absorb);
|
||||
CPPTRACE_EXPORT void enable_inlined_call_resolution(bool enable);
|
||||
|
||||
enum class cache_mode {
|
||||
// Only minimal lookup tables
|
||||
prioritize_memory,
|
||||
prioritize_memory = 0,
|
||||
// Build lookup tables but don't keep them around between trace calls
|
||||
hybrid,
|
||||
hybrid = 1,
|
||||
// Build lookup tables as needed
|
||||
prioritize_speed
|
||||
prioritize_speed = 2
|
||||
};
|
||||
|
||||
namespace experimental {
|
||||
CPPTRACE_EXPORT void set_cache_mode(cache_mode mode);
|
||||
}
|
||||
|
||||
// tracing exceptions:
|
||||
namespace detail {
|
||||
// This is a helper utility, if the library weren't C++11 an std::variant would be used
|
||||
class CPPTRACE_EXPORT lazy_trace_holder {
|
||||
@ -260,6 +285,9 @@ namespace cpptrace {
|
||||
private:
|
||||
void clear();
|
||||
};
|
||||
|
||||
CPPTRACE_EXPORT raw_trace get_raw_trace_and_absorb(std::size_t skip, std::size_t max_depth) noexcept;
|
||||
CPPTRACE_EXPORT raw_trace get_raw_trace_and_absorb(std::size_t skip = 0) noexcept;
|
||||
}
|
||||
|
||||
// Interface for a traced exception object
|
||||
@ -272,17 +300,14 @@ namespace cpptrace {
|
||||
|
||||
// Cpptrace traced exception object
|
||||
// I hate to have to expose anything about implementation detail but the idea here is that
|
||||
// TODO: CPPTRACE_FORCE_NO_INLINE annotations
|
||||
class CPPTRACE_EXPORT lazy_exception : public exception {
|
||||
mutable detail::lazy_trace_holder trace_holder;
|
||||
mutable std::string what_string;
|
||||
|
||||
protected:
|
||||
explicit lazy_exception(std::size_t skip, std::size_t max_depth) noexcept;
|
||||
explicit lazy_exception(std::size_t skip) noexcept : lazy_exception(skip + 1, SIZE_MAX) {}
|
||||
|
||||
public:
|
||||
explicit lazy_exception() noexcept : lazy_exception(1) {}
|
||||
explicit lazy_exception(
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept : trace_holder(std::move(trace)) {}
|
||||
// std::exception
|
||||
const char* what() const noexcept override;
|
||||
// cpptrace::exception
|
||||
@ -293,87 +318,105 @@ namespace cpptrace {
|
||||
class CPPTRACE_EXPORT exception_with_message : public lazy_exception {
|
||||
mutable std::string user_message;
|
||||
|
||||
protected:
|
||||
explicit exception_with_message(
|
||||
std::string&& message_arg,
|
||||
std::size_t skip
|
||||
) noexcept : lazy_exception(skip + 1), user_message(std::move(message_arg)) {}
|
||||
|
||||
explicit exception_with_message(
|
||||
std::string&& message_arg,
|
||||
std::size_t skip,
|
||||
std::size_t max_depth
|
||||
) noexcept : lazy_exception(skip + 1, max_depth), user_message(std::move(message_arg)) {}
|
||||
|
||||
public:
|
||||
explicit exception_with_message(std::string&& message_arg) noexcept
|
||||
: exception_with_message(std::move(message_arg), 1) {}
|
||||
explicit exception_with_message(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept : lazy_exception(std::move(trace)), user_message(std::move(message_arg)) {}
|
||||
|
||||
const char* message() const noexcept override;
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT logic_error : public exception_with_message {
|
||||
public:
|
||||
explicit logic_error(std::string&& message_arg) noexcept
|
||||
: exception_with_message(std::move(message_arg), 1) {}
|
||||
explicit logic_error(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT domain_error : public exception_with_message {
|
||||
public:
|
||||
explicit domain_error(std::string&& message_arg) noexcept
|
||||
: exception_with_message(std::move(message_arg), 1) {}
|
||||
explicit domain_error(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT invalid_argument : public exception_with_message {
|
||||
public:
|
||||
explicit invalid_argument(std::string&& message_arg) noexcept
|
||||
: exception_with_message(std::move(message_arg), 1) {}
|
||||
explicit invalid_argument(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT length_error : public exception_with_message {
|
||||
public:
|
||||
explicit length_error(std::string&& message_arg) noexcept
|
||||
: exception_with_message(std::move(message_arg), 1) {}
|
||||
explicit length_error(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT out_of_range : public exception_with_message {
|
||||
public:
|
||||
explicit out_of_range(std::string&& message_arg) noexcept
|
||||
: exception_with_message(std::move(message_arg), 1) {}
|
||||
explicit out_of_range(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT runtime_error : public exception_with_message {
|
||||
public:
|
||||
explicit runtime_error(std::string&& message_arg) noexcept
|
||||
: exception_with_message(std::move(message_arg), 1) {}
|
||||
explicit runtime_error(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT range_error : public exception_with_message {
|
||||
public:
|
||||
explicit range_error(std::string&& message_arg) noexcept
|
||||
: exception_with_message(std::move(message_arg), 1) {}
|
||||
explicit range_error(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT overflow_error : public exception_with_message {
|
||||
public:
|
||||
explicit overflow_error(std::string&& message_arg) noexcept
|
||||
: exception_with_message(std::move(message_arg), 1) {}
|
||||
explicit overflow_error(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT underflow_error : public exception_with_message {
|
||||
public:
|
||||
explicit underflow_error(std::string&& message_arg) noexcept
|
||||
: exception_with_message(std::move(message_arg), 1) {}
|
||||
explicit underflow_error(
|
||||
std::string&& message_arg,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: exception_with_message(std::move(message_arg), std::move(trace)) {}
|
||||
};
|
||||
|
||||
class CPPTRACE_EXPORT nested_exception : public lazy_exception {
|
||||
std::exception_ptr ptr;
|
||||
mutable std::string message_value;
|
||||
public:
|
||||
explicit nested_exception(std::exception_ptr exception_ptr) noexcept
|
||||
: lazy_exception(1), ptr(exception_ptr) {}
|
||||
explicit nested_exception(std::exception_ptr exception_ptr, std::size_t skip) noexcept
|
||||
: lazy_exception(skip + 1), ptr(exception_ptr) {}
|
||||
explicit nested_exception(
|
||||
std::exception_ptr exception_ptr,
|
||||
raw_trace&& trace = detail::get_raw_trace_and_absorb()
|
||||
) noexcept
|
||||
: lazy_exception(std::move(trace)), ptr(exception_ptr) {}
|
||||
|
||||
const char* message() const noexcept override;
|
||||
std::exception_ptr nested_ptr() const noexcept;
|
||||
|
||||
159
include/ctrace/ctrace.h
Normal file
159
include/ctrace/ctrace.h
Normal file
@ -0,0 +1,159 @@
|
||||
#ifndef CTRACE_H
|
||||
#define CTRACE_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define CPPTRACE_EXPORT_ATTR __declspec(dllexport)
|
||||
#define CPPTRACE_IMPORT_ATTR __declspec(dllimport)
|
||||
#else
|
||||
#define CPPTRACE_EXPORT_ATTR __attribute__((visibility("default")))
|
||||
#define CPPTRACE_IMPORT_ATTR __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
#ifdef CPPTRACE_STATIC_DEFINE
|
||||
# define CPPTRACE_EXPORT
|
||||
# define CPPTRACE_NO_EXPORT
|
||||
#else
|
||||
# ifndef CPPTRACE_EXPORT
|
||||
# ifdef cpptrace_lib_EXPORTS
|
||||
/* We are building this library */
|
||||
# define CPPTRACE_EXPORT CPPTRACE_EXPORT_ATTR
|
||||
# else
|
||||
/* We are using this library */
|
||||
# define CPPTRACE_EXPORT CPPTRACE_IMPORT_ATTR
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if defined(__cplusplus)
|
||||
#define CTRACE_BEGIN_DEFINITIONS extern "C" {
|
||||
#define CTRACE_END_DEFINITIONS }
|
||||
#else
|
||||
#define CTRACE_BEGIN_DEFINITIONS
|
||||
#define CTRACE_END_DEFINITIONS
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define CTRACE_FORCE_NO_INLINE __declspec(noinline)
|
||||
#else
|
||||
#define CTRACE_FORCE_NO_INLINE __attribute__((noinline))
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define CTRACE_FORCE_INLINE __forceinline
|
||||
#elif defined(__clang__) || defined(__GNUC__)
|
||||
#define CTRACE_FORCE_INLINE __attribute__((always_inline)) inline
|
||||
#else
|
||||
#define CTRACE_FORCE_INLINE inline
|
||||
#endif
|
||||
|
||||
/* See `CPPTRACE_PATH_MAX` for more info. */
|
||||
#define CTRACE_PATH_MAX 4096
|
||||
|
||||
CTRACE_BEGIN_DEFINITIONS
|
||||
|
||||
typedef struct ctrace_raw_trace ctrace_raw_trace;
|
||||
typedef struct ctrace_object_trace ctrace_object_trace;
|
||||
typedef struct ctrace_stacktrace ctrace_stacktrace;
|
||||
|
||||
/* Represents a boolean value, ensures a consistent ABI. */
|
||||
typedef int8_t ctrace_bool;
|
||||
/* A type that can represent a pointer, alias for `uintptr_t`. */
|
||||
typedef uintptr_t ctrace_frame_ptr;
|
||||
typedef struct ctrace_object_frame ctrace_object_frame;
|
||||
typedef struct ctrace_stacktrace_frame ctrace_stacktrace_frame;
|
||||
typedef struct ctrace_safe_object_frame ctrace_safe_object_frame;
|
||||
|
||||
/* Type-safe null-terminated string wrapper */
|
||||
typedef struct {
|
||||
const char* data;
|
||||
} ctrace_owning_string;
|
||||
|
||||
struct ctrace_object_frame {
|
||||
ctrace_frame_ptr raw_address;
|
||||
ctrace_frame_ptr obj_address;
|
||||
const char* obj_path;
|
||||
};
|
||||
|
||||
struct ctrace_stacktrace_frame {
|
||||
ctrace_frame_ptr address;
|
||||
uint32_t line;
|
||||
uint32_t column;
|
||||
const char* filename;
|
||||
const char* symbol;
|
||||
ctrace_bool is_inline;
|
||||
};
|
||||
|
||||
struct ctrace_safe_object_frame {
|
||||
ctrace_frame_ptr raw_address;
|
||||
ctrace_frame_ptr relative_obj_address;
|
||||
char object_path[CTRACE_PATH_MAX + 1];
|
||||
};
|
||||
|
||||
struct ctrace_raw_trace {
|
||||
ctrace_frame_ptr* frames;
|
||||
size_t count;
|
||||
};
|
||||
|
||||
struct ctrace_object_trace {
|
||||
ctrace_object_frame* frames;
|
||||
size_t count;
|
||||
};
|
||||
|
||||
struct ctrace_stacktrace {
|
||||
ctrace_stacktrace_frame* frames;
|
||||
size_t count;
|
||||
};
|
||||
|
||||
/* ctrace::string: */
|
||||
CPPTRACE_EXPORT ctrace_owning_string ctrace_generate_owning_string(const char* raw_string);
|
||||
CPPTRACE_EXPORT void ctrace_free_owning_string(ctrace_owning_string* string);
|
||||
|
||||
/* ctrace::generation: */
|
||||
CPPTRACE_EXPORT ctrace_raw_trace ctrace_generate_raw_trace(size_t skip, size_t max_depth);
|
||||
CPPTRACE_EXPORT ctrace_object_trace ctrace_generate_object_trace(size_t skip, size_t max_depth);
|
||||
CPPTRACE_EXPORT ctrace_stacktrace ctrace_generate_trace(size_t skip, size_t max_depth);
|
||||
|
||||
/* ctrace::freeing: */
|
||||
CPPTRACE_EXPORT void ctrace_free_raw_trace(ctrace_raw_trace* trace);
|
||||
CPPTRACE_EXPORT void ctrace_free_object_trace(ctrace_object_trace* trace);
|
||||
CPPTRACE_EXPORT void ctrace_free_stacktrace(ctrace_stacktrace* trace);
|
||||
|
||||
/* ctrace::resolve: */
|
||||
CPPTRACE_EXPORT ctrace_stacktrace ctrace_resolve_raw_trace(const ctrace_raw_trace* trace);
|
||||
CPPTRACE_EXPORT ctrace_object_trace ctrace_resolve_raw_trace_to_object_trace(const ctrace_raw_trace* trace);
|
||||
CPPTRACE_EXPORT ctrace_stacktrace ctrace_resolve_object_trace(const ctrace_object_trace* trace);
|
||||
|
||||
/* ctrace::safe: */
|
||||
CPPTRACE_EXPORT size_t ctrace_safe_generate_raw_trace(ctrace_frame_ptr* buffer, size_t size, size_t skip, size_t max_depth);
|
||||
CPPTRACE_EXPORT void ctrace_get_safe_object_frame(ctrace_frame_ptr address, ctrace_safe_object_frame* out);
|
||||
|
||||
/* ctrace::io: */
|
||||
CPPTRACE_EXPORT ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color);
|
||||
CPPTRACE_EXPORT void ctrace_print_stacktrace(const ctrace_stacktrace* trace, FILE* to, ctrace_bool use_color);
|
||||
|
||||
/* ctrace::utility: */
|
||||
CPPTRACE_EXPORT ctrace_owning_string ctrace_demangle(const char* mangled);
|
||||
CPPTRACE_EXPORT int ctrace_stdin_fileno(void);
|
||||
CPPTRACE_EXPORT int ctrace_stderr_fileno(void);
|
||||
CPPTRACE_EXPORT int ctrace_stdout_fileno(void);
|
||||
CPPTRACE_EXPORT ctrace_bool ctrace_isatty(int fd);
|
||||
|
||||
/* ctrace::config: */
|
||||
typedef enum {
|
||||
/* Only minimal lookup tables */
|
||||
ctrace_prioritize_memory = 0,
|
||||
/* Build lookup tables but don't keep them around between trace calls */
|
||||
ctrace_hybrid = 1,
|
||||
/* Build lookup tables as needed */
|
||||
ctrace_prioritize_speed = 2
|
||||
} ctrace_cache_mode;
|
||||
CPPTRACE_EXPORT void ctrace_set_cache_mode(ctrace_cache_mode mode);
|
||||
CPPTRACE_EXPORT void ctrace_enable_inlined_call_resolution(ctrace_bool enable);
|
||||
|
||||
CTRACE_END_DEFINITIONS
|
||||
|
||||
#endif
|
||||
@ -12,7 +12,13 @@
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
|
||||
#include <mach-o/loader.h>
|
||||
#include <mach-o/swap.h>
|
||||
@ -20,10 +26,11 @@
|
||||
#include <crt_externs.h>
|
||||
#include <mach-o/nlist.h>
|
||||
#include <mach-o/stab.h>
|
||||
#include <mach-o/arch.h>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
static bool is_mach_o(std::uint32_t magic) {
|
||||
inline bool is_mach_o(std::uint32_t magic) {
|
||||
switch(magic) {
|
||||
case FAT_MAGIC:
|
||||
case FAT_CIGAM:
|
||||
@ -37,171 +44,515 @@ namespace detail {
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_fat_magic(std::uint32_t magic) {
|
||||
inline bool file_is_mach_o(const std::string& object_path) noexcept {
|
||||
try {
|
||||
FILE* file = std::fopen(object_path.c_str(), "rb");
|
||||
if(file == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto magic = load_bytes<std::uint32_t>(file, 0);
|
||||
return is_mach_o(magic);
|
||||
} catch(...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool is_fat_magic(std::uint32_t magic) {
|
||||
return magic == FAT_MAGIC || magic == FAT_CIGAM;
|
||||
}
|
||||
|
||||
// Based on https://github.com/AlexDenisov/segment_dumper/blob/master/main.c
|
||||
// and https://lowlevelbits.org/parsing-mach-o-files/
|
||||
static bool is_magic_64(std::uint32_t magic) {
|
||||
inline bool is_magic_64(std::uint32_t magic) {
|
||||
return magic == MH_MAGIC_64 || magic == MH_CIGAM_64;
|
||||
}
|
||||
|
||||
static bool should_swap_bytes(std::uint32_t magic) {
|
||||
inline bool should_swap_bytes(std::uint32_t magic) {
|
||||
return magic == MH_CIGAM || magic == MH_CIGAM_64 || magic == FAT_CIGAM;
|
||||
}
|
||||
|
||||
static void swap_mach_header(mach_header_64& header) {
|
||||
inline void swap_mach_header(mach_header_64& header) {
|
||||
swap_mach_header_64(&header, NX_UnknownByteOrder);
|
||||
}
|
||||
|
||||
static void swap_mach_header(mach_header& header) {
|
||||
inline void swap_mach_header(mach_header& header) {
|
||||
swap_mach_header(&header, NX_UnknownByteOrder);
|
||||
}
|
||||
|
||||
static void swap_segment_command(segment_command_64& segment) {
|
||||
inline void swap_segment_command(segment_command_64& segment) {
|
||||
swap_segment_command_64(&segment, NX_UnknownByteOrder);
|
||||
}
|
||||
|
||||
static void swap_segment_command(segment_command& segment) {
|
||||
inline void swap_segment_command(segment_command& segment) {
|
||||
swap_segment_command(&segment, NX_UnknownByteOrder);
|
||||
}
|
||||
|
||||
inline void swap_nlist(struct nlist& entry) {
|
||||
swap_nlist(&entry, 1, NX_UnknownByteOrder);
|
||||
}
|
||||
|
||||
inline void swap_nlist(struct nlist_64& entry) {
|
||||
swap_nlist_64(&entry, 1, NX_UnknownByteOrder);
|
||||
}
|
||||
|
||||
#ifdef __LP64__
|
||||
#define LP(x) x##_64
|
||||
#else
|
||||
#define LP(x) x
|
||||
#endif
|
||||
|
||||
template<std::size_t Bits>
|
||||
static optional<std::uintptr_t> macho_get_text_vmaddr_mach(
|
||||
std::FILE* object_file,
|
||||
const std::string& object_path,
|
||||
off_t offset,
|
||||
bool should_swap,
|
||||
bool allow_arch_mismatch
|
||||
) {
|
||||
static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument");
|
||||
using Mach_Header = typename std::conditional<Bits == 32, mach_header, mach_header_64>::type;
|
||||
using Segment_Command = typename std::conditional<Bits == 32, segment_command, segment_command_64>::type;
|
||||
std::uint32_t ncmds;
|
||||
off_t load_commands_offset = offset;
|
||||
std::size_t header_size = sizeof(Mach_Header);
|
||||
Mach_Header header = load_bytes<Mach_Header>(object_file, offset);
|
||||
if(should_swap) {
|
||||
swap_mach_header(header);
|
||||
}
|
||||
thread_local static struct LP(mach_header)* mhp = _NSGetMachExecuteHeader();
|
||||
//std::fprintf(
|
||||
// stderr,
|
||||
// "----> %d %d; %d %d\n",
|
||||
// header.cputype,
|
||||
// mhp->cputype,
|
||||
// static_cast<cpu_subtype_t>(mhp->cpusubtype & ~CPU_SUBTYPE_MASK),
|
||||
// header.cpusubtype
|
||||
//);
|
||||
if(
|
||||
header.cputype != mhp->cputype ||
|
||||
static_cast<cpu_subtype_t>(mhp->cpusubtype & ~CPU_SUBTYPE_MASK) != header.cpusubtype
|
||||
) {
|
||||
if(allow_arch_mismatch) {
|
||||
return nullopt;
|
||||
} else {
|
||||
PANIC("Mach-O file cpu type and subtype do not match current machine " + object_path);
|
||||
}
|
||||
}
|
||||
ncmds = header.ncmds;
|
||||
load_commands_offset += header_size;
|
||||
// iterate load commands
|
||||
off_t actual_offset = load_commands_offset;
|
||||
for(std::uint32_t i = 0; i < ncmds; i++) {
|
||||
load_command cmd = load_bytes<load_command>(object_file, actual_offset);
|
||||
if(should_swap) {
|
||||
swap_load_command(&cmd, NX_UnknownByteOrder);
|
||||
}
|
||||
// TODO: This is a mistake? Need to check cmd.cmd == LC_SEGMENT_64 / cmd.cmd == LC_SEGMENT
|
||||
Segment_Command segment = load_bytes<Segment_Command>(object_file, actual_offset);
|
||||
if(should_swap) {
|
||||
swap_segment_command(segment);
|
||||
}
|
||||
if(std::strcmp(segment.segname, "__TEXT") == 0) {
|
||||
return segment.vmaddr;
|
||||
}
|
||||
actual_offset += cmd.cmdsize;
|
||||
}
|
||||
// somehow no __TEXT section was found...
|
||||
PANIC("Couldn't find __TEXT section while parsing Mach-O object");
|
||||
return 0;
|
||||
}
|
||||
struct load_command_entry {
|
||||
std::uint32_t file_offset;
|
||||
std::uint32_t cmd;
|
||||
std::uint32_t cmdsize;
|
||||
};
|
||||
|
||||
static std::uintptr_t macho_get_text_vmaddr_fat(
|
||||
std::FILE* object_file,
|
||||
const std::string& object_path,
|
||||
bool should_swap
|
||||
) {
|
||||
std::size_t header_size = sizeof(fat_header);
|
||||
std::size_t arch_size = sizeof(fat_arch);
|
||||
fat_header header = load_bytes<fat_header>(object_file, 0);
|
||||
if(should_swap) {
|
||||
swap_fat_header(&header, NX_UnknownByteOrder);
|
||||
}
|
||||
off_t arch_offset = (off_t)header_size;
|
||||
optional<std::uintptr_t> text_vmaddr;
|
||||
for(std::uint32_t i = 0; i < header.nfat_arch; i++) {
|
||||
fat_arch arch = load_bytes<fat_arch>(object_file, arch_offset);
|
||||
if(should_swap) {
|
||||
swap_fat_arch(&arch, 1, NX_UnknownByteOrder);
|
||||
}
|
||||
off_t mach_header_offset = (off_t)arch.offset;
|
||||
arch_offset += arch_size;
|
||||
std::uint32_t magic = load_bytes<std::uint32_t>(object_file, mach_header_offset);
|
||||
if(is_magic_64(magic)) {
|
||||
text_vmaddr = macho_get_text_vmaddr_mach<64>(
|
||||
object_file,
|
||||
object_path,
|
||||
mach_header_offset,
|
||||
should_swap_bytes(magic),
|
||||
true
|
||||
);
|
||||
} else {
|
||||
text_vmaddr = macho_get_text_vmaddr_mach<32>(
|
||||
object_file,
|
||||
object_path,
|
||||
mach_header_offset,
|
||||
should_swap_bytes(magic),
|
||||
true
|
||||
);
|
||||
}
|
||||
if(text_vmaddr.has_value()) {
|
||||
return text_vmaddr.unwrap();
|
||||
}
|
||||
}
|
||||
// If this is reached... something went wrong. The cpu we're on wasn't found.
|
||||
PANIC("Couldn't find appropriate architecture in fat Mach-O");
|
||||
return 0;
|
||||
}
|
||||
class mach_o {
|
||||
std::FILE* file = nullptr;
|
||||
std::string object_path;
|
||||
std::uint32_t magic;
|
||||
cpu_type_t cputype;
|
||||
cpu_subtype_t cpusubtype;
|
||||
std::uint32_t filetype;
|
||||
std::uint32_t n_load_commands;
|
||||
std::uint32_t sizeof_load_commands;
|
||||
std::uint32_t flags;
|
||||
std::size_t bits = 0; // 32 or 64 once load_mach is called
|
||||
|
||||
static std::uintptr_t macho_get_text_vmaddr(const std::string& object_path) {
|
||||
//std::fprintf(stderr, "--%s--\n", object_path.c_str());
|
||||
auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter);
|
||||
std::size_t load_base = 0;
|
||||
std::size_t fat_index = std::numeric_limits<std::size_t>::max();
|
||||
|
||||
std::vector<load_command_entry> load_commands;
|
||||
|
||||
struct symtab_info_data {
|
||||
symtab_command symtab;
|
||||
std::unique_ptr<char[]> stringtab;
|
||||
const char* get_string(std::size_t index) const {
|
||||
if(stringtab && index < symtab.strsize) {
|
||||
return stringtab.get() + index;
|
||||
} else {
|
||||
throw std::runtime_error("can't retrieve symbol from symtab");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
bool tried_to_load_symtab = false;
|
||||
optional<symtab_info_data> symtab_info;
|
||||
|
||||
public:
|
||||
mach_o(const std::string& object_path) : object_path(object_path) {
|
||||
file = std::fopen(object_path.c_str(), "rb");
|
||||
if(file == nullptr) {
|
||||
throw file_error("Unable to read object file " + object_path);
|
||||
}
|
||||
std::uint32_t magic = load_bytes<std::uint32_t>(file, 0);
|
||||
magic = load_bytes<std::uint32_t>(file, 0);
|
||||
VERIFY(is_mach_o(magic), "File is not Mach-O " + object_path);
|
||||
bool is_64 = is_magic_64(magic);
|
||||
bool should_swap = should_swap_bytes(magic);
|
||||
if(magic == FAT_MAGIC || magic == FAT_CIGAM) {
|
||||
return macho_get_text_vmaddr_fat(file, object_path, should_swap);
|
||||
load_fat_mach();
|
||||
} else {
|
||||
if(is_64) {
|
||||
return macho_get_text_vmaddr_mach<64>(file, object_path, 0, should_swap, false).unwrap();
|
||||
fat_index = 0;
|
||||
if(is_magic_64(magic)) {
|
||||
load_mach<64>();
|
||||
} else {
|
||||
return macho_get_text_vmaddr_mach<32>(file, object_path, 0, should_swap, false).unwrap();
|
||||
load_mach<32>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~mach_o() {
|
||||
if(file) {
|
||||
std::fclose(file);
|
||||
}
|
||||
}
|
||||
|
||||
std::uintptr_t get_text_vmaddr() {
|
||||
for(const auto& command : load_commands) {
|
||||
if(command.cmd == LC_SEGMENT_64 || command.cmd == LC_SEGMENT) {
|
||||
auto segment = command.cmd == LC_SEGMENT_64
|
||||
? load_segment_command<64>(command.file_offset)
|
||||
: load_segment_command<32>(command.file_offset);
|
||||
if(std::strcmp(segment.segname, "__TEXT") == 0) {
|
||||
return segment.vmaddr;
|
||||
}
|
||||
}
|
||||
}
|
||||
// somehow no __TEXT section was found...
|
||||
throw std::runtime_error("Couldn't find __TEXT section while parsing Mach-O object");
|
||||
}
|
||||
|
||||
std::size_t get_fat_index() const {
|
||||
VERIFY(fat_index != std::numeric_limits<std::size_t>::max());
|
||||
return fat_index;
|
||||
}
|
||||
|
||||
void print_segments() const {
|
||||
int i = 0;
|
||||
for(const auto& command : load_commands) {
|
||||
if(command.cmd == LC_SEGMENT_64 || command.cmd == LC_SEGMENT) {
|
||||
auto segment = command.cmd == LC_SEGMENT_64
|
||||
? load_segment_command<64>(command.file_offset)
|
||||
: load_segment_command<32>(command.file_offset);
|
||||
fprintf(stderr, "Load command %d\n", i);
|
||||
fprintf(stderr, " cmd %u\n", segment.cmd);
|
||||
fprintf(stderr, " cmdsize %u\n", segment.cmdsize);
|
||||
fprintf(stderr, " segname %s\n", segment.segname);
|
||||
fprintf(stderr, " vmaddr 0x%llx\n", segment.vmaddr);
|
||||
fprintf(stderr, " vmsize 0x%llx\n", segment.vmsize);
|
||||
fprintf(stderr, " off 0x%llx\n", segment.fileoff);
|
||||
fprintf(stderr, " filesize %llu\n", segment.filesize);
|
||||
fprintf(stderr, " nsects %u\n", segment.nsects);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
optional<symtab_info_data>& get_symtab_info() {
|
||||
if(!symtab_info.has_value() && !tried_to_load_symtab) {
|
||||
// don't try to load the symtab again if for some reason loading here fails
|
||||
tried_to_load_symtab = true;
|
||||
for(const auto& command : load_commands) {
|
||||
if(command.cmd == LC_SYMTAB) {
|
||||
symtab_info_data info;
|
||||
info.symtab = load_symbol_table_command(command.file_offset);
|
||||
info.stringtab = load_string_table(info.symtab.stroff, info.symtab.strsize);
|
||||
symtab_info = std::move(info);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return symtab_info;
|
||||
}
|
||||
|
||||
void print_symbol_table_entry(
|
||||
const nlist_64& entry,
|
||||
const std::unique_ptr<char[]>& stringtab,
|
||||
std::size_t stringsize,
|
||||
std::size_t j
|
||||
) const {
|
||||
const char* type = "";
|
||||
if(entry.n_type & N_STAB) {
|
||||
switch(entry.n_type) {
|
||||
case N_SO: type = "N_SO"; break;
|
||||
case N_OSO: type = "N_OSO"; break;
|
||||
case N_BNSYM: type = "N_BNSYM"; break;
|
||||
case N_ENSYM: type = "N_ENSYM"; break;
|
||||
case N_FUN: type = "N_FUN"; break;
|
||||
}
|
||||
} else if((entry.n_type & N_TYPE) == N_SECT) {
|
||||
type = "N_SECT";
|
||||
}
|
||||
fprintf(
|
||||
stderr,
|
||||
"%5llu %8llx %2llx %7s %2llu %4llx %16llx %s\n",
|
||||
to_ull(j),
|
||||
to_ull(entry.n_un.n_strx),
|
||||
to_ull(entry.n_type),
|
||||
type,
|
||||
to_ull(entry.n_sect),
|
||||
to_ull(entry.n_desc),
|
||||
to_ull(entry.n_value),
|
||||
stringtab == nullptr
|
||||
? "Stringtab error"
|
||||
: entry.n_un.n_strx < stringsize
|
||||
? stringtab.get() + entry.n_un.n_strx
|
||||
: "String index out of bounds"
|
||||
);
|
||||
}
|
||||
|
||||
void print_symbol_table() {
|
||||
int i = 0;
|
||||
for(const auto& command : load_commands) {
|
||||
if(command.cmd == LC_SYMTAB) {
|
||||
auto symtab = load_symbol_table_command(command.file_offset);
|
||||
fprintf(stderr, "Load command %d\n", i);
|
||||
fprintf(stderr, " cmd %llu\n", to_ull(symtab.cmd));
|
||||
fprintf(stderr, " cmdsize %llu\n", to_ull(symtab.cmdsize));
|
||||
fprintf(stderr, " symoff 0x%llu\n", to_ull(symtab.symoff));
|
||||
fprintf(stderr, " nsyms %llu\n", to_ull(symtab.nsyms));
|
||||
fprintf(stderr, " stroff 0x%llu\n", to_ull(symtab.stroff));
|
||||
fprintf(stderr, " strsize %llu\n", to_ull(symtab.strsize));
|
||||
auto stringtab = load_string_table(symtab.stroff, symtab.strsize);
|
||||
for(std::size_t j = 0; j < symtab.nsyms; j++) {
|
||||
nlist_64 entry = bits == 32
|
||||
? load_symtab_entry<32>(symtab.symoff, j)
|
||||
: load_symtab_entry<64>(symtab.symoff, j);
|
||||
print_symbol_table_entry(entry, stringtab, symtab.strsize, j);
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
debug_map get_debug_map() {
|
||||
// we have a bunch of symbols in our binary we need to pair up with symbols from various .o files
|
||||
// first collect symbols and the objects they come from
|
||||
debug_map debug_map;
|
||||
const auto& symtab_info = get_symtab_info().unwrap();
|
||||
const auto& symtab = symtab_info.symtab;
|
||||
// TODO: Take timestamp into account?
|
||||
std::string current_module;
|
||||
optional<debug_map_entry> current_function;
|
||||
for(std::size_t j = 0; j < symtab.nsyms; j++) {
|
||||
nlist_64 entry = bits == 32
|
||||
? load_symtab_entry<32>(symtab.symoff, j)
|
||||
: load_symtab_entry<64>(symtab.symoff, j);
|
||||
// entry.n_type & N_STAB indicates symbolic debug info
|
||||
if(!(entry.n_type & N_STAB)) {
|
||||
continue;
|
||||
}
|
||||
switch(entry.n_type) {
|
||||
case N_SO:
|
||||
// pass - these encode path and filename for the module, if applicable
|
||||
break;
|
||||
case N_OSO:
|
||||
// sets the module
|
||||
current_module = symtab_info.get_string(entry.n_un.n_strx);
|
||||
break;
|
||||
case N_BNSYM: break; // pass
|
||||
case N_ENSYM: break; // pass
|
||||
case N_FUN:
|
||||
{
|
||||
const char* str = symtab_info.get_string(entry.n_un.n_strx);
|
||||
if(str[0] == 0) {
|
||||
// end of function scope
|
||||
if(!current_function) { /**/ }
|
||||
current_function.unwrap().size = entry.n_value;
|
||||
debug_map[current_module].push_back(std::move(current_function).unwrap());
|
||||
} else {
|
||||
current_function = debug_map_entry{};
|
||||
current_function.unwrap().source_address = entry.n_value;
|
||||
current_function.unwrap().name = str;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return debug_map;
|
||||
}
|
||||
|
||||
std::vector<symbol_entry> symbol_table() {
|
||||
// we have a bunch of symbols in our binary we need to pair up with symbols from various .o files
|
||||
// first collect symbols and the objects they come from
|
||||
std::vector<symbol_entry> symbols;
|
||||
const auto& symtab_info = get_symtab_info().unwrap();
|
||||
const auto& symtab = symtab_info.symtab;
|
||||
// TODO: Take timestamp into account?
|
||||
for(std::size_t j = 0; j < symtab.nsyms; j++) {
|
||||
nlist_64 entry = bits == 32
|
||||
? load_symtab_entry<32>(symtab.symoff, j)
|
||||
: load_symtab_entry<64>(symtab.symoff, j);
|
||||
if(entry.n_type & N_STAB) {
|
||||
continue;
|
||||
}
|
||||
if((entry.n_type & N_TYPE) == N_SECT) {
|
||||
symbols.push_back({
|
||||
entry.n_value,
|
||||
symtab_info.get_string(entry.n_un.n_strx)
|
||||
});
|
||||
}
|
||||
}
|
||||
return symbols;
|
||||
}
|
||||
|
||||
// produce information similar to dsymutil -dump-debug-map
|
||||
static void print_debug_map(const debug_map& debug_map) {
|
||||
for(const auto& entry : debug_map) {
|
||||
std::cout<<entry.first<<": "<<std::endl;
|
||||
for(const auto& symbol : entry.second) {
|
||||
std::cerr
|
||||
<< " "
|
||||
<< symbol.name
|
||||
<< " "
|
||||
<< std::hex
|
||||
<< symbol.source_address
|
||||
<< " "
|
||||
<< symbol.size
|
||||
<< std::dec
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
template<std::size_t Bits>
|
||||
void load_mach() {
|
||||
static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument");
|
||||
bits = Bits;
|
||||
using Mach_Header = typename std::conditional<Bits == 32, mach_header, mach_header_64>::type;
|
||||
std::size_t header_size = sizeof(Mach_Header);
|
||||
Mach_Header header = load_bytes<Mach_Header>(file, load_base);
|
||||
magic = header.magic;
|
||||
if(should_swap()) {
|
||||
swap_mach_header(header);
|
||||
}
|
||||
cputype = header.cputype;
|
||||
cpusubtype = header.cpusubtype;
|
||||
filetype = header.filetype;
|
||||
n_load_commands = header.ncmds;
|
||||
sizeof_load_commands = header.sizeofcmds;
|
||||
flags = header.flags;
|
||||
// handle load commands
|
||||
std::uint32_t ncmds = header.ncmds;
|
||||
std::uint32_t load_commands_offset = load_base + header_size;
|
||||
// iterate load commands
|
||||
std::uint32_t actual_offset = load_commands_offset;
|
||||
for(std::uint32_t i = 0; i < ncmds; i++) {
|
||||
load_command cmd = load_bytes<load_command>(file, actual_offset);
|
||||
if(should_swap()) {
|
||||
swap_load_command(&cmd, NX_UnknownByteOrder);
|
||||
}
|
||||
load_commands.push_back({ actual_offset, cmd.cmd, cmd.cmdsize });
|
||||
actual_offset += cmd.cmdsize;
|
||||
}
|
||||
}
|
||||
|
||||
void load_fat_mach() {
|
||||
std::size_t header_size = sizeof(fat_header);
|
||||
std::size_t arch_size = sizeof(fat_arch);
|
||||
fat_header header = load_bytes<fat_header>(file, 0);
|
||||
if(should_swap()) {
|
||||
swap_fat_header(&header, NX_UnknownByteOrder);
|
||||
}
|
||||
// thread_local static struct LP(mach_header)* mhp = _NSGetMachExecuteHeader();
|
||||
// off_t arch_offset = (off_t)header_size;
|
||||
// for(std::size_t i = 0; i < header.nfat_arch; i++) {
|
||||
// fat_arch arch = load_bytes<fat_arch>(file, arch_offset);
|
||||
// if(should_swap()) {
|
||||
// swap_fat_arch(&arch, 1, NX_UnknownByteOrder);
|
||||
// }
|
||||
// off_t mach_header_offset = (off_t)arch.offset;
|
||||
// arch_offset += arch_size;
|
||||
// std::uint32_t magic = load_bytes<std::uint32_t>(file, mach_header_offset);
|
||||
// std::cerr<<"xxx: "<<arch.cputype<<" : "<<mhp->cputype<<std::endl;
|
||||
// std::cerr<<" "<<arch.cpusubtype<<" : "<<static_cast<cpu_subtype_t>(mhp->cpusubtype & ~CPU_SUBTYPE_MASK)<<std::endl;
|
||||
// if(
|
||||
// arch.cputype == mhp->cputype &&
|
||||
// static_cast<cpu_subtype_t>(mhp->cpusubtype & ~CPU_SUBTYPE_MASK) == arch.cpusubtype
|
||||
// ) {
|
||||
// load_base = mach_header_offset;
|
||||
// fat_index = i;
|
||||
// if(is_magic_64(magic)) {
|
||||
// load_mach<64>(true);
|
||||
// } else {
|
||||
// load_mach<32>(true);
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
std::vector<fat_arch> fat_arches;
|
||||
fat_arches.reserve(header.nfat_arch);
|
||||
off_t arch_offset = (off_t)header_size;
|
||||
for(std::size_t i = 0; i < header.nfat_arch; i++) {
|
||||
fat_arch arch = load_bytes<fat_arch>(file, arch_offset);
|
||||
if(should_swap()) {
|
||||
swap_fat_arch(&arch, 1, NX_UnknownByteOrder);
|
||||
}
|
||||
fat_arches.push_back(arch);
|
||||
arch_offset += arch_size;
|
||||
}
|
||||
thread_local static struct LP(mach_header)* mhp = _NSGetMachExecuteHeader();
|
||||
fat_arch* best = NXFindBestFatArch(
|
||||
mhp->cputype,
|
||||
mhp->cpusubtype,
|
||||
fat_arches.data(),
|
||||
header.nfat_arch
|
||||
);
|
||||
if(best) {
|
||||
off_t mach_header_offset = (off_t)best->offset;
|
||||
std::uint32_t magic = load_bytes<std::uint32_t>(file, mach_header_offset);
|
||||
load_base = mach_header_offset;
|
||||
fat_index = best - fat_arches.data();
|
||||
if(is_magic_64(magic)) {
|
||||
load_mach<64>();
|
||||
} else {
|
||||
load_mach<32>();
|
||||
}
|
||||
return;
|
||||
}
|
||||
// If this is reached... something went wrong. The cpu we're on wasn't found.
|
||||
throw std::runtime_error("Couldn't find appropriate architecture in fat Mach-O");
|
||||
}
|
||||
|
||||
template<std::size_t Bits>
|
||||
segment_command_64 load_segment_command(std::uint32_t offset) const {
|
||||
using Segment_Command = typename std::conditional<Bits == 32, segment_command, segment_command_64>::type;
|
||||
Segment_Command segment = load_bytes<Segment_Command>(file, offset);
|
||||
ASSERT(segment.cmd == LC_SEGMENT_64 || segment.cmd == LC_SEGMENT);
|
||||
if(should_swap()) {
|
||||
swap_segment_command(segment);
|
||||
}
|
||||
// fields match just u64 instead of u32
|
||||
segment_command_64 common;
|
||||
common.cmd = segment.cmd;
|
||||
common.cmdsize = segment.cmdsize;
|
||||
static_assert(sizeof common.segname == 16 && sizeof segment.segname == 16, "xx");
|
||||
memcpy(common.segname, segment.segname, 16);
|
||||
common.vmaddr = segment.vmaddr;
|
||||
common.vmsize = segment.vmsize;
|
||||
common.fileoff = segment.fileoff;
|
||||
common.filesize = segment.filesize;
|
||||
common.maxprot = segment.maxprot;
|
||||
common.initprot = segment.initprot;
|
||||
common.nsects = segment.nsects;
|
||||
common.flags = segment.flags;
|
||||
return common;
|
||||
}
|
||||
|
||||
symtab_command load_symbol_table_command(std::uint32_t offset) const {
|
||||
symtab_command symtab = load_bytes<symtab_command>(file, offset);
|
||||
ASSERT(symtab.cmd == LC_SYMTAB);
|
||||
if(should_swap()) {
|
||||
swap_symtab_command(&symtab, NX_UnknownByteOrder);
|
||||
}
|
||||
return symtab;
|
||||
}
|
||||
|
||||
template<std::size_t Bits>
|
||||
nlist_64 load_symtab_entry(std::uint32_t symbol_base, std::size_t index) const {
|
||||
using Nlist = typename std::conditional<Bits == 32, struct nlist, struct nlist_64>::type;
|
||||
uint32_t offset = load_base + symbol_base + index * sizeof(Nlist);
|
||||
Nlist entry = load_bytes<Nlist>(file, offset);
|
||||
if(should_swap()) {
|
||||
swap_nlist(entry);
|
||||
}
|
||||
// fields match just u64 instead of u32
|
||||
nlist_64 common;
|
||||
common.n_un.n_strx = entry.n_un.n_strx;
|
||||
common.n_type = entry.n_type;
|
||||
common.n_sect = entry.n_sect;
|
||||
common.n_desc = entry.n_desc;
|
||||
common.n_value = entry.n_value;
|
||||
return common;
|
||||
}
|
||||
|
||||
std::unique_ptr<char[]> load_string_table(std::uint32_t offset, std::uint32_t byte_count) const {
|
||||
std::unique_ptr<char[]> buffer(new char[byte_count + 1]);
|
||||
VERIFY(std::fseek(file, load_base + offset, SEEK_SET) == 0, "fseek error");
|
||||
VERIFY(std::fread(buffer.get(), sizeof(char), byte_count, file) == byte_count, "fread error");
|
||||
buffer[byte_count] = 0; // just out of an abundance of caution
|
||||
return buffer;
|
||||
}
|
||||
|
||||
bool should_swap() const {
|
||||
return should_swap_bytes(magic);
|
||||
}
|
||||
};
|
||||
|
||||
inline bool macho_is_fat(const std::string& object_path) {
|
||||
auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter);
|
||||
if(file == nullptr) {
|
||||
@ -210,41 +561,6 @@ namespace detail {
|
||||
std::uint32_t magic = load_bytes<std::uint32_t>(file, 0);
|
||||
return is_fat_magic(magic);
|
||||
}
|
||||
|
||||
// returns index of the appropriate mach-o binary in the universal binary
|
||||
// TODO: Code duplication with macho_get_text_vmaddr_fat
|
||||
inline unsigned get_fat_macho_index(const std::string& object_path) {
|
||||
auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter);
|
||||
if(file == nullptr) {
|
||||
throw file_error("Unable to read object file " + object_path);
|
||||
}
|
||||
std::uint32_t magic = load_bytes<std::uint32_t>(file, 0);
|
||||
VERIFY(is_fat_magic(magic));
|
||||
bool should_swap = should_swap_bytes(magic);
|
||||
std::size_t header_size = sizeof(fat_header);
|
||||
std::size_t arch_size = sizeof(fat_arch);
|
||||
fat_header header = load_bytes<fat_header>(file, 0);
|
||||
if(should_swap) {
|
||||
swap_fat_header(&header, NX_UnknownByteOrder);
|
||||
}
|
||||
off_t arch_offset = (off_t)header_size;
|
||||
thread_local static struct LP(mach_header)* mhp = _NSGetMachExecuteHeader();
|
||||
for(std::uint32_t i = 0; i < header.nfat_arch; i++) {
|
||||
fat_arch arch = load_bytes<fat_arch>(file, arch_offset);
|
||||
if(should_swap) {
|
||||
swap_fat_arch(&arch, 1, NX_UnknownByteOrder);
|
||||
}
|
||||
arch_offset += arch_size;
|
||||
if(
|
||||
arch.cputype == mhp->cputype &&
|
||||
static_cast<cpu_subtype_t>(mhp->cpusubtype & ~CPU_SUBTYPE_MASK) == arch.cpusubtype
|
||||
) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
// If this is reached... something went wrong. The cpu we're on wasn't found.
|
||||
PANIC("Couldn't find appropriate architecture in fat Mach-O");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -56,7 +56,7 @@ namespace detail {
|
||||
if(it == cache.end()) {
|
||||
// arguably it'd be better to release the lock while computing this, but also arguably it's good to not
|
||||
// have two threads try to do the same computation
|
||||
auto base = macho_get_text_vmaddr(object_path);
|
||||
auto base = mach_o(object_path).get_text_vmaddr();
|
||||
cache.insert(it, {object_path, base});
|
||||
return base;
|
||||
} else {
|
||||
|
||||
@ -58,7 +58,7 @@ namespace cpptrace {
|
||||
for(auto& frame : trace) {
|
||||
frame.symbol = detail::demangle(frame.symbol);
|
||||
}
|
||||
return stacktrace{std::move(trace)};
|
||||
return {std::move(trace)};
|
||||
} catch(...) { // NOSONAR
|
||||
if(!detail::should_absorb_trace_exceptions()) {
|
||||
throw;
|
||||
@ -91,7 +91,7 @@ namespace cpptrace {
|
||||
for(auto& frame : trace) {
|
||||
frame.symbol = detail::demangle(frame.symbol);
|
||||
}
|
||||
return stacktrace{std::move(trace)};
|
||||
return {std::move(trace)};
|
||||
} catch(...) { // NOSONAR
|
||||
if(!detail::should_absorb_trace_exceptions()) {
|
||||
throw;
|
||||
@ -167,7 +167,11 @@ namespace cpptrace {
|
||||
}
|
||||
|
||||
void stacktrace::print(std::ostream& stream, bool color, bool newline_at_end, const char* header) const {
|
||||
if(color) {
|
||||
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):")<<std::endl;
|
||||
@ -333,7 +337,7 @@ namespace cpptrace {
|
||||
for(auto& frame : trace) {
|
||||
frame.symbol = detail::demangle(frame.symbol);
|
||||
}
|
||||
return stacktrace{std::move(trace)};
|
||||
return {std::move(trace)};
|
||||
} catch(...) { // NOSONAR
|
||||
if(!detail::should_absorb_trace_exceptions()) {
|
||||
throw;
|
||||
@ -411,6 +415,7 @@ namespace cpptrace {
|
||||
|
||||
namespace detail {
|
||||
std::atomic_bool absorb_trace_exceptions(true); // NOSONAR
|
||||
std::atomic_bool resolve_inlined_calls(true); // NOSONAR
|
||||
std::atomic<enum cache_mode> cache_mode(cache_mode::prioritize_speed); // NOSONAR
|
||||
}
|
||||
|
||||
@ -418,6 +423,10 @@ namespace cpptrace {
|
||||
detail::absorb_trace_exceptions = absorb;
|
||||
}
|
||||
|
||||
void enable_inlined_call_resolution(bool enable) {
|
||||
detail::resolve_inlined_calls = enable;
|
||||
}
|
||||
|
||||
namespace experimental {
|
||||
void set_cache_mode(cache_mode mode) {
|
||||
detail::cache_mode = mode;
|
||||
@ -429,6 +438,10 @@ namespace cpptrace {
|
||||
return absorb_trace_exceptions;
|
||||
}
|
||||
|
||||
bool should_resolve_inlined_calls() {
|
||||
return resolve_inlined_calls;
|
||||
}
|
||||
|
||||
enum cache_mode get_cache_mode() {
|
||||
return cache_mode;
|
||||
}
|
||||
@ -450,6 +463,11 @@ namespace cpptrace {
|
||||
}
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
raw_trace get_raw_trace_and_absorb(std::size_t skip) noexcept {
|
||||
return get_raw_trace_and_absorb(skip + 1, SIZE_MAX);
|
||||
}
|
||||
|
||||
lazy_trace_holder::lazy_trace_holder(const lazy_trace_holder& other) : resolved(other.resolved) {
|
||||
if(other.resolved) {
|
||||
new (&resolved_trace) stacktrace(other.resolved_trace);
|
||||
@ -530,9 +548,6 @@ namespace cpptrace {
|
||||
}
|
||||
}
|
||||
|
||||
lazy_exception::lazy_exception(std::size_t skip, std::size_t max_depth) noexcept
|
||||
: trace_holder(detail::get_raw_trace_and_absorb(skip + 1, max_depth)) {}
|
||||
|
||||
const char* lazy_exception::what() const noexcept {
|
||||
if(what_string.empty()) {
|
||||
what_string = message() + std::string(":\n") + trace_holder.get_resolved_trace().to_string();
|
||||
@ -576,7 +591,7 @@ namespace cpptrace {
|
||||
} catch(cpptrace::exception&) {
|
||||
throw; // already a cpptrace::exception
|
||||
} catch(...) {
|
||||
throw nested_exception(std::current_exception(), skip + 1);
|
||||
throw nested_exception(std::current_exception(), detail::get_raw_trace_and_absorb(skip + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
426
src/ctrace.cpp
Normal file
426
src/ctrace.cpp
Normal file
@ -0,0 +1,426 @@
|
||||
#include <ctrace/ctrace.h>
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
#include <algorithm>
|
||||
|
||||
#include "symbols/symbols.hpp"
|
||||
#include "unwind/unwind.hpp"
|
||||
#include "demangle/demangle.hpp"
|
||||
#include "utils/exception_type.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "binary/object.hpp"
|
||||
#include "binary/safe_dl.hpp"
|
||||
|
||||
#define ESC "\033["
|
||||
#define RESET ESC "0m"
|
||||
#define RED ESC "31m"
|
||||
#define GREEN ESC "32m"
|
||||
#define YELLOW ESC "33m"
|
||||
#define BLUE ESC "34m"
|
||||
#define MAGENTA ESC "35m"
|
||||
#define CYAN ESC "36m"
|
||||
|
||||
#if defined(__GNUC__) && ((__GNUC__ > 2) || (__GNUC__ == 2 && __GNUC_MINOR__ >= 6))
|
||||
# define CTRACE_GNU_FORMAT(...) __attribute__((format(__VA_ARGS__)))
|
||||
#elif defined(__clang__)
|
||||
// Probably requires llvm >3.5? Not exactly sure.
|
||||
# define CTRACE_GNU_FORMAT(...) __attribute__((format(__VA_ARGS__)))
|
||||
#else
|
||||
# define CTRACE_GNU_FORMAT(...)
|
||||
#endif
|
||||
|
||||
#if defined(__clang__)
|
||||
# define CTRACE_FORMAT_PROLOGUE \
|
||||
_Pragma("clang diagnostic push") \
|
||||
_Pragma("clang diagnostic ignored \"-Wformat-security\"")
|
||||
# define CTRACE_FORMAT_EPILOGUE \
|
||||
_Pragma("clang diagnostic pop")
|
||||
#elif defined(__GNUC_MINOR__)
|
||||
# define CTRACE_FORMAT_PROLOGUE \
|
||||
_Pragma("GCC diagnostic push") \
|
||||
_Pragma("GCC diagnostic ignored \"-Wformat-security\"")
|
||||
# define CTRACE_FORMAT_EPILOGUE \
|
||||
_Pragma("GCC diagnostic pop")
|
||||
#else
|
||||
# define CTRACE_FORMAT_PROLOGUE
|
||||
# define CTRACE_FORMAT_EPILOGUE
|
||||
#endif
|
||||
|
||||
namespace ctrace {
|
||||
static constexpr std::uint32_t invalid_pos = ~0U;
|
||||
|
||||
CTRACE_FORMAT_PROLOGUE
|
||||
template <typename...Args>
|
||||
CTRACE_GNU_FORMAT(printf, 2, 0)
|
||||
static void ffprintf(std::FILE* f, const char fmt[], Args&&...args) {
|
||||
(void)std::fprintf(f, fmt, args...);
|
||||
(void)fflush(f);
|
||||
}
|
||||
CTRACE_FORMAT_EPILOGUE
|
||||
|
||||
static bool is_empty(std::uint32_t pos) noexcept {
|
||||
return pos == invalid_pos;
|
||||
}
|
||||
|
||||
static bool is_empty(const char* str) noexcept {
|
||||
return !str || std::char_traits<char>::length(str) == 0;
|
||||
}
|
||||
|
||||
static ctrace_owning_string generate_owning_string(const char* raw_string) noexcept {
|
||||
// Returns length to the null terminator.
|
||||
std::size_t count = std::char_traits<char>::length(raw_string);
|
||||
char* new_string = new char[count + 1];
|
||||
std::char_traits<char>::copy(new_string, raw_string, count);
|
||||
new_string[count] = '\0';
|
||||
return { new_string };
|
||||
}
|
||||
|
||||
static ctrace_owning_string generate_owning_string(const std::string& std_string) {
|
||||
return generate_owning_string(std_string.c_str());
|
||||
}
|
||||
|
||||
static void free_owning_string(const char* owned_string) noexcept {
|
||||
if(!owned_string) return; // Not necessary but eh
|
||||
delete[] owned_string;
|
||||
}
|
||||
|
||||
static void free_owning_string(ctrace_owning_string& owned_string) noexcept {
|
||||
free_owning_string(owned_string.data);
|
||||
}
|
||||
|
||||
static ctrace_object_trace c_convert(const std::vector<cpptrace::object_frame>& trace) {
|
||||
std::size_t count = trace.size();
|
||||
auto* frames = new ctrace_object_frame[count];
|
||||
std::transform(
|
||||
trace.begin(),
|
||||
trace.end(),
|
||||
frames,
|
||||
[] (const cpptrace::object_frame& frame) -> ctrace_object_frame {
|
||||
const char* new_path = generate_owning_string(frame.object_path).data;
|
||||
return { frame.raw_address, frame.object_address, new_path };
|
||||
}
|
||||
);
|
||||
return { frames, count };
|
||||
}
|
||||
|
||||
static ctrace_stacktrace c_convert(const std::vector<cpptrace::stacktrace_frame>& trace) {
|
||||
std::size_t count = trace.size();
|
||||
auto* frames = new ctrace_stacktrace_frame[count];
|
||||
std::transform(
|
||||
trace.begin(),
|
||||
trace.end(),
|
||||
frames,
|
||||
[] (const cpptrace::stacktrace_frame& frame) -> ctrace_stacktrace_frame {
|
||||
ctrace_stacktrace_frame new_frame;
|
||||
new_frame.address = frame.address;
|
||||
new_frame.line = frame.line.value_or(invalid_pos);
|
||||
new_frame.column = frame.column.value_or(invalid_pos);
|
||||
new_frame.filename = generate_owning_string(frame.filename).data;
|
||||
new_frame.symbol = generate_owning_string(cpptrace::detail::demangle(frame.symbol)).data;
|
||||
new_frame.is_inline = ctrace_bool(frame.is_inline);
|
||||
return new_frame;
|
||||
}
|
||||
);
|
||||
return { frames, count };
|
||||
}
|
||||
|
||||
static cpptrace::stacktrace cpp_convert(const ctrace_stacktrace* ptrace) {
|
||||
if(!ptrace || !ptrace->frames) {
|
||||
return { };
|
||||
}
|
||||
std::vector<cpptrace::stacktrace_frame> new_frames;
|
||||
new_frames.reserve(ptrace->count);
|
||||
for(std::size_t i = 0; i < ptrace->count; ++i) {
|
||||
using nullable_type = cpptrace::nullable<std::uint32_t>;
|
||||
static constexpr auto null_v = nullable_type::null().raw_value;
|
||||
const ctrace_stacktrace_frame& old_frame = ptrace->frames[i];
|
||||
cpptrace::stacktrace_frame new_frame;
|
||||
new_frame.address = old_frame.address;
|
||||
new_frame.line = nullable_type{is_empty(old_frame.line) ? null_v : old_frame.line};
|
||||
new_frame.column = nullable_type{is_empty(old_frame.column) ? null_v : old_frame.column};
|
||||
new_frame.filename = old_frame.filename;
|
||||
new_frame.symbol = old_frame.symbol;
|
||||
new_frame.is_inline = bool(old_frame.is_inline);
|
||||
new_frames.push_back(std::move(new_frame));
|
||||
}
|
||||
return cpptrace::stacktrace{std::move(new_frames)};
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
// ctrace::string
|
||||
ctrace_owning_string ctrace_generate_owning_string(const char* raw_string) {
|
||||
return ctrace::generate_owning_string(raw_string);
|
||||
}
|
||||
|
||||
void ctrace_free_owning_string(ctrace_owning_string* string) {
|
||||
if(!string) {
|
||||
return;
|
||||
}
|
||||
ctrace::free_owning_string(*string);
|
||||
string->data = nullptr;
|
||||
}
|
||||
|
||||
// ctrace::generation:
|
||||
CTRACE_FORCE_NO_INLINE
|
||||
ctrace_raw_trace ctrace_generate_raw_trace(size_t skip, size_t max_depth) {
|
||||
try {
|
||||
std::vector<cpptrace::frame_ptr> trace = cpptrace::detail::capture_frames(skip + 1, max_depth);
|
||||
std::size_t count = trace.size();
|
||||
auto* frames = new ctrace_frame_ptr[count];
|
||||
std::copy(trace.data(), trace.data() + count, frames);
|
||||
return { frames, count };
|
||||
} catch(...) {
|
||||
// Don't check rethrow condition, it's risky.
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
CTRACE_FORCE_NO_INLINE
|
||||
ctrace_object_trace ctrace_generate_object_trace(size_t skip, size_t max_depth) {
|
||||
try {
|
||||
std::vector<cpptrace::object_frame> trace = cpptrace::detail::get_frames_object_info(
|
||||
cpptrace::detail::capture_frames(skip + 1, max_depth)
|
||||
);
|
||||
return ctrace::c_convert(trace);
|
||||
} catch(...) { // NOSONAR
|
||||
// Don't check rethrow condition, it's risky.
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
CTRACE_FORCE_NO_INLINE
|
||||
ctrace_stacktrace ctrace_generate_trace(size_t skip, size_t max_depth) {
|
||||
try {
|
||||
std::vector<cpptrace::frame_ptr> frames = cpptrace::detail::capture_frames(skip + 1, max_depth);
|
||||
std::vector<cpptrace::stacktrace_frame> trace = cpptrace::detail::resolve_frames(frames);
|
||||
return ctrace::c_convert(trace);
|
||||
} catch(...) { // NOSONAR
|
||||
// Don't check rethrow condition, it's risky.
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ctrace::freeing:
|
||||
void ctrace_free_raw_trace(ctrace_raw_trace* trace) {
|
||||
if(!trace) {
|
||||
return;
|
||||
}
|
||||
ctrace_frame_ptr* frames = trace->frames;
|
||||
delete[] frames;
|
||||
trace->frames = nullptr;
|
||||
trace->count = 0;
|
||||
}
|
||||
|
||||
void ctrace_free_object_trace(ctrace_object_trace* trace) {
|
||||
if(!trace || !trace->frames) {
|
||||
return;
|
||||
}
|
||||
ctrace_object_frame* frames = trace->frames;
|
||||
for(std::size_t i = 0; i < trace->count; ++i) {
|
||||
const char* path = frames[i].obj_path;
|
||||
ctrace::free_owning_string(path);
|
||||
}
|
||||
|
||||
delete[] frames;
|
||||
trace->frames = nullptr;
|
||||
trace->count = 0;
|
||||
}
|
||||
|
||||
void ctrace_free_stacktrace(ctrace_stacktrace* trace) {
|
||||
if(!trace || !trace->frames) {
|
||||
return;
|
||||
}
|
||||
ctrace_stacktrace_frame* frames = trace->frames;
|
||||
for(std::size_t i = 0; i < trace->count; ++i) {
|
||||
ctrace::free_owning_string(frames[i].filename);
|
||||
ctrace::free_owning_string(frames[i].symbol);
|
||||
}
|
||||
|
||||
delete[] frames;
|
||||
trace->frames = nullptr;
|
||||
trace->count = 0;
|
||||
}
|
||||
|
||||
// ctrace::resolve:
|
||||
ctrace_stacktrace ctrace_resolve_raw_trace(const ctrace_raw_trace* trace) {
|
||||
if(!trace || !trace->frames) {
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
try {
|
||||
std::vector<cpptrace::frame_ptr> frames(trace->count, 0);
|
||||
std::copy(trace->frames, trace->frames + trace->count, frames.begin());
|
||||
std::vector<cpptrace::stacktrace_frame> resolved = cpptrace::detail::resolve_frames(frames);
|
||||
return ctrace::c_convert(resolved);
|
||||
} catch(...) { // NOSONAR
|
||||
// Don't check rethrow condition, it's risky.
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
ctrace_object_trace ctrace_resolve_raw_trace_to_object_trace(const ctrace_raw_trace* trace) {
|
||||
if(!trace || !trace->frames) {
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
try {
|
||||
std::vector<cpptrace::frame_ptr> frames(trace->count, 0);
|
||||
std::copy(trace->frames, trace->frames + trace->count, frames.begin());
|
||||
std::vector<cpptrace::object_frame> obj = cpptrace::detail::get_frames_object_info(frames);
|
||||
return ctrace::c_convert(obj);
|
||||
} catch(...) { // NOSONAR
|
||||
// Don't check rethrow condition, it's risky.
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
ctrace_stacktrace ctrace_resolve_object_trace(const ctrace_object_trace* trace) {
|
||||
if(!trace || !trace->frames) {
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
try {
|
||||
std::vector<cpptrace::frame_ptr> frames(trace->count, 0);
|
||||
std::transform(
|
||||
trace->frames,
|
||||
trace->frames + trace->count,
|
||||
frames.begin(),
|
||||
[] (const ctrace_object_frame& frame) -> cpptrace::frame_ptr {
|
||||
return frame.raw_address;
|
||||
}
|
||||
);
|
||||
std::vector<cpptrace::stacktrace_frame> resolved = cpptrace::detail::resolve_frames(frames);
|
||||
return ctrace::c_convert(resolved);
|
||||
} catch(...) { // NOSONAR
|
||||
// Don't check rethrow condition, it's risky.
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
// ctrace::safe:
|
||||
size_t ctrace_safe_generate_raw_trace(ctrace_frame_ptr* buffer, size_t size, size_t skip, size_t max_depth) {
|
||||
return cpptrace::safe_generate_raw_trace(buffer, size, skip, max_depth);
|
||||
}
|
||||
|
||||
void ctrace_get_safe_object_frame(ctrace_frame_ptr address, ctrace_safe_object_frame* out) {
|
||||
// TODO: change this?
|
||||
static_assert(sizeof(cpptrace::safe_object_frame) == sizeof(ctrace_safe_object_frame), "");
|
||||
cpptrace::get_safe_object_frame(address, reinterpret_cast<cpptrace::safe_object_frame*>(out));
|
||||
}
|
||||
|
||||
// ctrace::io:
|
||||
ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color) {
|
||||
if(!trace || !trace->frames) {
|
||||
return ctrace::generate_owning_string("<empty trace>");
|
||||
}
|
||||
auto cpp_trace = ctrace::cpp_convert(trace);
|
||||
std::string trace_string = cpp_trace.to_string(bool(use_color));
|
||||
return ctrace::generate_owning_string(trace_string);
|
||||
}
|
||||
|
||||
void ctrace_print_stacktrace(const ctrace_stacktrace* trace, FILE* to, ctrace_bool use_color) {
|
||||
if(
|
||||
use_color && (
|
||||
(to == stdout && cpptrace::isatty(cpptrace::stdout_fileno)) ||
|
||||
(to == stderr && cpptrace::isatty(cpptrace::stderr_fileno))
|
||||
)
|
||||
) {
|
||||
cpptrace::detail::enable_virtual_terminal_processing_if_needed();
|
||||
}
|
||||
ctrace::ffprintf(to, "Stack trace (most recent call first):\n");
|
||||
if(trace->count == 0 || !trace->frames) {
|
||||
ctrace::ffprintf(to, "<empty trace>\n");
|
||||
return;
|
||||
}
|
||||
const auto reset = use_color ? ESC "0m" : "";
|
||||
const auto green = use_color ? ESC "32m" : "";
|
||||
const auto yellow = use_color ? ESC "33m" : "";
|
||||
const auto blue = use_color ? ESC "34m" : "";
|
||||
const auto frame_number_width = cpptrace::detail::n_digits(unsigned(trace->count - 1));
|
||||
ctrace_stacktrace_frame* frames = trace->frames;
|
||||
for(std::size_t i = 0; i < trace->count; ++i) {
|
||||
static constexpr auto ptr_len = 2 * sizeof(cpptrace::frame_ptr);
|
||||
ctrace::ffprintf(to, "#%-*llu ", int(frame_number_width), i);
|
||||
if(frames[i].is_inline) {
|
||||
(void)std::fprintf(to, "%*s",
|
||||
int(ptr_len + 2),
|
||||
"(inlined)");
|
||||
} else {
|
||||
(void)std::fprintf(to, "%s0x%0*llx%s",
|
||||
blue,
|
||||
int(ptr_len),
|
||||
cpptrace::detail::to_ull(frames[i].address),
|
||||
reset);
|
||||
}
|
||||
if(!ctrace::is_empty(frames[i].symbol)) {
|
||||
(void)std::fprintf(to, " in %s%s%s",
|
||||
yellow,
|
||||
frames[i].symbol,
|
||||
reset);
|
||||
}
|
||||
if(!ctrace::is_empty(frames[i].filename)) {
|
||||
(void)std::fprintf(to, " at %s%s%s",
|
||||
green,
|
||||
frames[i].filename,
|
||||
reset);
|
||||
if(ctrace::is_empty(frames[i].line)) {
|
||||
ctrace::ffprintf(to, "\n");
|
||||
continue;
|
||||
}
|
||||
(void)std::fprintf(to, ":%s%llu%s",
|
||||
blue,
|
||||
cpptrace::detail::to_ull(frames[i].line),
|
||||
reset);
|
||||
if(ctrace::is_empty(frames[i].column)) {
|
||||
ctrace::ffprintf(to, "\n");
|
||||
continue;
|
||||
}
|
||||
(void)std::fprintf(to, ":%s%llu%s",
|
||||
blue,
|
||||
cpptrace::detail::to_ull(frames[i].column),
|
||||
reset);
|
||||
}
|
||||
// always print newline at end :M
|
||||
ctrace::ffprintf(to, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
// utility::demangle:
|
||||
ctrace_owning_string ctrace_demangle(const char* mangled) {
|
||||
if(!mangled) {
|
||||
return ctrace::generate_owning_string("");
|
||||
}
|
||||
std::string demangled = cpptrace::demangle(mangled);
|
||||
return ctrace::generate_owning_string(demangled);
|
||||
}
|
||||
|
||||
// utility::io
|
||||
int ctrace_stdin_fileno(void) {
|
||||
return cpptrace::stdin_fileno;
|
||||
}
|
||||
|
||||
int ctrace_stderr_fileno(void) {
|
||||
return cpptrace::stderr_fileno;
|
||||
}
|
||||
|
||||
int ctrace_stdout_fileno(void) {
|
||||
return cpptrace::stdout_fileno;
|
||||
}
|
||||
|
||||
ctrace_bool ctrace_isatty(int fd) {
|
||||
return cpptrace::isatty(fd);
|
||||
}
|
||||
|
||||
// utility::cache:
|
||||
void ctrace_set_cache_mode(ctrace_cache_mode mode) {
|
||||
static constexpr auto cache_max = cpptrace::cache_mode::prioritize_speed;
|
||||
if(mode > unsigned(cache_max)) {
|
||||
return;
|
||||
}
|
||||
auto cache_mode = static_cast<cpptrace::cache_mode>(mode);
|
||||
cpptrace::experimental::set_cache_mode(cache_mode);
|
||||
}
|
||||
|
||||
void ctrace_enable_inlined_call_resolution(ctrace_bool enable) {
|
||||
cpptrace::enable_inlined_call_resolution(enable);
|
||||
}
|
||||
}
|
||||
@ -345,7 +345,7 @@ namespace dbghelp {
|
||||
std::fprintf(stderr, "Stack trace: Internal error while calling SymSetContext\n");
|
||||
return {
|
||||
addr,
|
||||
static_cast<std::uint32_t>(line.LineNumber),
|
||||
{ static_cast<std::uint32_t>(line.LineNumber) },
|
||||
nullable<std::uint32_t>::null(),
|
||||
line.FileName,
|
||||
symbol->Name,
|
||||
@ -377,7 +377,7 @@ namespace dbghelp {
|
||||
signature = std::regex_replace(signature, comma_re, ", ");
|
||||
return {
|
||||
addr,
|
||||
static_cast<std::uint32_t>(line.LineNumber),
|
||||
{ static_cast<std::uint32_t>(line.LineNumber) },
|
||||
nullable<std::uint32_t>::null(),
|
||||
line.FileName,
|
||||
signature,
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
#include "symbols.hpp"
|
||||
#include "../utils/common.hpp"
|
||||
#include "../utils/dwarf.hpp"
|
||||
#include "../utils/dwarf.hpp" // has dwarf #includes
|
||||
#include "../utils/error.hpp"
|
||||
#include "../binary/object.hpp"
|
||||
#include "../utils/utils.hpp"
|
||||
@ -20,13 +20,8 @@
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#ifdef CPPTRACE_USE_EXTERNAL_LIBDWARF
|
||||
#include <libdwarf/libdwarf.h>
|
||||
#include <libdwarf/dwarf.h>
|
||||
#else
|
||||
#include <libdwarf.h>
|
||||
#include <dwarf.h>
|
||||
#endif
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
|
||||
// It's been tricky to piece together how to handle all this dwarf stuff. Some resources I've used are
|
||||
// https://www.prevanders.net/libdwarf.pdf
|
||||
@ -82,7 +77,14 @@ namespace libdwarf {
|
||||
std::vector<line_entry> line_entries;
|
||||
};
|
||||
|
||||
struct dwarf_resolver {
|
||||
class symbol_resolver {
|
||||
public:
|
||||
virtual ~symbol_resolver() = default;
|
||||
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
|
||||
virtual frame_with_inlines resolve_frame(const object_frame& frame_info) = 0;
|
||||
};
|
||||
|
||||
class dwarf_resolver : public symbol_resolver {
|
||||
std::string object_path;
|
||||
Dwarf_Debug dbg = nullptr;
|
||||
bool ok = false;
|
||||
@ -95,9 +97,11 @@ namespace libdwarf {
|
||||
std::unordered_map<Dwarf_Off, std::vector<subprogram_entry>> subprograms_cache;
|
||||
// Vector of ranges and their corresponding CU offsets
|
||||
std::vector<cu_entry> cu_cache;
|
||||
bool generated_cu_cache = false;
|
||||
// Map from CU -> {srcfiles, count}
|
||||
std::unordered_map<Dwarf_Off, std::pair<char**, Dwarf_Signed>> srcfiles_cache;
|
||||
|
||||
private:
|
||||
// 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
|
||||
@ -123,23 +127,37 @@ namespace libdwarf {
|
||||
return ret;
|
||||
}
|
||||
|
||||
public:
|
||||
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
|
||||
dwarf_resolver(const std::string& _object_path) {
|
||||
object_path = _object_path;
|
||||
// use a buffer when invoking dwarf_init_path, which allows it to automatically find debuglink or dSYM
|
||||
// sources
|
||||
bool use_buffer = true;
|
||||
// for universal / fat mach-o files
|
||||
unsigned universal_number = 0;
|
||||
#if IS_APPLE
|
||||
if(directory_exists(object_path + ".dSYM")) {
|
||||
object_path += ".dSYM/Contents/Resources/DWARF/" + basename(object_path);
|
||||
// Possibly depends on the build system but a obj.cpp.o.dSYM/Contents/Resources/DWARF/obj.cpp.o can be
|
||||
// created alongside .o files. These are text files containing directives, as opposed to something we
|
||||
// can actually use
|
||||
std::string dsym_resource = object_path + ".dSYM/Contents/Resources/DWARF/" + basename(object_path);
|
||||
if(file_is_mach_o(dsym_resource)) {
|
||||
object_path = std::move(dsym_resource);
|
||||
}
|
||||
use_buffer = false; // we resolved dSYM above as appropriate
|
||||
}
|
||||
if(macho_is_fat(object_path)) {
|
||||
universal_number = get_fat_macho_index(object_path);
|
||||
universal_number = mach_o(object_path).get_fat_index();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Giving libdwarf a buffer for a true output path is needed for its automatic resolution of debuglink and
|
||||
// dSYM files. We don't utilize the dSYM logic here, we just care about debuglink.
|
||||
std::unique_ptr<char[]> buffer(new char[CPPTRACE_MAX_PATH]);
|
||||
std::unique_ptr<char[]> buffer;
|
||||
if(use_buffer) {
|
||||
buffer = std::unique_ptr<char[]>(new char[CPPTRACE_MAX_PATH]);
|
||||
}
|
||||
auto ret = wrap(
|
||||
dwarf_init_path_a,
|
||||
object_path.c_str(),
|
||||
@ -165,21 +183,6 @@ namespace libdwarf {
|
||||
// Check for .debug_aranges for fast lookup
|
||||
wrap(dwarf_get_aranges, dbg, &aranges, &arange_count);
|
||||
}
|
||||
if(ok && !aranges && get_cache_mode() != cache_mode::prioritize_memory) {
|
||||
walk_compilation_units([this] (const die_object& cu_die) {
|
||||
Dwarf_Half offset_size = 0;
|
||||
Dwarf_Half dwversion = 0;
|
||||
dwarf_get_version_of_die(cu_die.get(), &dwversion, &offset_size);
|
||||
auto ranges_vec = cu_die.get_rangelist_entries(dwversion);
|
||||
for(auto range : ranges_vec) {
|
||||
cu_cache.push_back({ cu_die.clone(), dwversion, range.first, range.second });
|
||||
}
|
||||
return true;
|
||||
});
|
||||
std::sort(cu_cache.begin(), cu_cache.end(), [] (const cu_entry& a, const cu_entry& b) {
|
||||
return a.low < b.low;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
|
||||
@ -213,6 +216,7 @@ namespace libdwarf {
|
||||
line_contexts(std::move(other.line_contexts)),
|
||||
subprograms_cache(std::move(other.subprograms_cache)),
|
||||
cu_cache(std::move(other.cu_cache)),
|
||||
generated_cu_cache(other.generated_cu_cache),
|
||||
srcfiles_cache(std::move(other.srcfiles_cache))
|
||||
{
|
||||
other.dbg = nullptr;
|
||||
@ -228,12 +232,14 @@ namespace libdwarf {
|
||||
line_contexts = std::move(other.line_contexts);
|
||||
subprograms_cache = std::move(other.subprograms_cache);
|
||||
cu_cache = std::move(other.cu_cache);
|
||||
generated_cu_cache = other.generated_cu_cache;
|
||||
srcfiles_cache = std::move(other.srcfiles_cache);
|
||||
other.dbg = nullptr;
|
||||
other.aranges = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
// 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) {
|
||||
@ -282,6 +288,25 @@ namespace libdwarf {
|
||||
}
|
||||
}
|
||||
|
||||
void lazy_generate_cu_cache() {
|
||||
if(!generated_cu_cache) {
|
||||
walk_compilation_units([this] (const die_object& cu_die) {
|
||||
Dwarf_Half offset_size = 0;
|
||||
Dwarf_Half dwversion = 0;
|
||||
dwarf_get_version_of_die(cu_die.get(), &dwversion, &offset_size);
|
||||
auto ranges_vec = cu_die.get_rangelist_entries(dwversion);
|
||||
for(auto range : ranges_vec) {
|
||||
cu_cache.push_back({ cu_die.clone(), dwversion, range.first, range.second });
|
||||
}
|
||||
return true;
|
||||
});
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
std::string subprogram_symbol(
|
||||
const die_object& die,
|
||||
Dwarf_Half dwversion
|
||||
@ -381,7 +406,9 @@ namespace libdwarf {
|
||||
) {
|
||||
ASSERT(die.get_tag() == DW_TAG_subprogram);
|
||||
const auto name = subprogram_symbol(die, dwversion);
|
||||
if(detail::should_resolve_inlined_calls()) {
|
||||
get_inlines_info(cu_die, die, pc, dwversion, inlines);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
@ -522,19 +549,14 @@ namespace libdwarf {
|
||||
it = subprograms_cache.find(off);
|
||||
}
|
||||
auto& vec = it->second;
|
||||
auto vec_it = std::lower_bound(
|
||||
auto vec_it = first_less_than_or_equal(
|
||||
vec.begin(),
|
||||
vec.end(),
|
||||
pc,
|
||||
[] (const subprogram_entry& entry, Dwarf_Addr pc) {
|
||||
return entry.low < pc;
|
||||
[] (Dwarf_Addr pc, const subprogram_entry& entry) {
|
||||
return pc < entry.low;
|
||||
}
|
||||
);
|
||||
// vec_it is first >= pc
|
||||
// we want first <= pc
|
||||
if(vec_it != vec.begin()) {
|
||||
vec_it--;
|
||||
}
|
||||
// If the vector has been empty this can happen
|
||||
if(vec_it != vec.end()) {
|
||||
//vec_it->die.print();
|
||||
@ -649,19 +671,14 @@ namespace libdwarf {
|
||||
if(get_cache_mode() == cache_mode::prioritize_speed) {
|
||||
// Lookup in the table
|
||||
auto& line_entries = table_info.line_entries;
|
||||
auto table_it = std::lower_bound(
|
||||
auto table_it = first_less_than_or_equal(
|
||||
line_entries.begin(),
|
||||
line_entries.end(),
|
||||
pc,
|
||||
[] (const line_entry& entry, Dwarf_Addr pc) {
|
||||
return entry.low < pc;
|
||||
[] (Dwarf_Addr pc, const line_entry& entry) {
|
||||
return pc < entry.low;
|
||||
}
|
||||
);
|
||||
// vec_it is first >= pc
|
||||
// we want first <= pc
|
||||
if(table_it != line_entries.begin()) {
|
||||
table_it--;
|
||||
}
|
||||
// If the vector has been empty this can happen
|
||||
if(table_it != line_entries.end()) {
|
||||
Dwarf_Line line = table_it->line;
|
||||
@ -787,8 +804,13 @@ namespace libdwarf {
|
||||
}
|
||||
retrieve_line_info(cu_die, pc, frame); // no offset for line info
|
||||
retrieve_symbol(cu_die, pc, dwversion, frame, inlines);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
}
|
||||
// otherwise, or if not in aranges
|
||||
// one reason to fallback here is if the compilation has dwarf generated from different compilers and only
|
||||
// some of them generate aranges (e.g. static linking with cpptrace after specifying clang++ as the c++
|
||||
// compiler while the C compiler defaults to an older gcc)
|
||||
if(get_cache_mode() == cache_mode::prioritize_memory) {
|
||||
// walk for the cu and go from there
|
||||
walk_compilation_units([this, pc, &frame, &inlines] (const die_object& cu_die) {
|
||||
@ -818,20 +840,16 @@ namespace libdwarf {
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
lazy_generate_cu_cache();
|
||||
// look up the cu
|
||||
auto vec_it = std::lower_bound(
|
||||
auto vec_it = first_less_than_or_equal(
|
||||
cu_cache.begin(),
|
||||
cu_cache.end(),
|
||||
pc,
|
||||
[] (const cu_entry& entry, Dwarf_Addr pc) {
|
||||
return entry.low < pc;
|
||||
[] (Dwarf_Addr pc, const cu_entry& entry) {
|
||||
return pc < entry.low;
|
||||
}
|
||||
);
|
||||
// vec_it is first >= pc
|
||||
// we want first <= pc
|
||||
if(vec_it != cu_cache.begin()) {
|
||||
vec_it--;
|
||||
}
|
||||
// If the vector has been empty this can happen
|
||||
if(vec_it != cu_cache.end()) {
|
||||
//vec_it->die.print();
|
||||
@ -844,10 +862,23 @@ namespace libdwarf {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
|
||||
frame_with_inlines resolve_frame(const object_frame& frame_info) {
|
||||
frame_with_inlines resolve_frame(const object_frame& frame_info) override {
|
||||
if(!ok) {
|
||||
return {
|
||||
{
|
||||
frame_info.raw_address,
|
||||
nullable<std::uint32_t>::null(),
|
||||
nullable<std::uint32_t>::null(),
|
||||
frame_info.object_path,
|
||||
"",
|
||||
false
|
||||
},
|
||||
{}
|
||||
};
|
||||
}
|
||||
stacktrace_frame frame = null_frame;
|
||||
frame.filename = frame_info.object_path;
|
||||
frame.address = frame_info.raw_address;
|
||||
@ -869,28 +900,214 @@ namespace libdwarf {
|
||||
}
|
||||
};
|
||||
|
||||
class null_resolver : public symbol_resolver {
|
||||
public:
|
||||
null_resolver() = default;
|
||||
null_resolver(const std::string&) {}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
|
||||
frame_with_inlines resolve_frame(const object_frame& frame_info) override {
|
||||
return {
|
||||
{
|
||||
frame_info.raw_address,
|
||||
nullable<std::uint32_t>::null(),
|
||||
nullable<std::uint32_t>::null(),
|
||||
frame_info.object_path,
|
||||
"",
|
||||
false
|
||||
},
|
||||
{}
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
#if IS_APPLE
|
||||
struct target_object {
|
||||
std::string object_path;
|
||||
bool path_ok = true;
|
||||
optional<std::unordered_map<std::string, uint64_t>> symbols;
|
||||
std::unique_ptr<symbol_resolver> resolver;
|
||||
|
||||
target_object(std::string object_path) : object_path(object_path) {}
|
||||
|
||||
std::unique_ptr<symbol_resolver>& get_resolver() {
|
||||
if(!resolver) {
|
||||
// this seems silly but it's an attempt to not repeatedly try to initialize new dwarf_resolvers if
|
||||
// exceptions are thrown, e.g. if the path doesn't exist
|
||||
resolver = std::unique_ptr<null_resolver>(new null_resolver);
|
||||
resolver = std::unique_ptr<dwarf_resolver>(new dwarf_resolver(object_path));
|
||||
}
|
||||
return resolver;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, uint64_t>& get_symbols() {
|
||||
if(!symbols) {
|
||||
// this is an attempt to not repeatedly try to reprocess mach-o files if exceptions are thrown, e.g. if
|
||||
// the path doesn't exist
|
||||
std::unordered_map<std::string, uint64_t> symbols;
|
||||
this->symbols = symbols;
|
||||
auto symbol_table = mach_o(object_path).symbol_table();
|
||||
for(const auto& symbol : symbol_table) {
|
||||
symbols[symbol.name] = symbol.address;
|
||||
}
|
||||
this->symbols = std::move(symbols);
|
||||
}
|
||||
return symbols.unwrap();
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
|
||||
frame_with_inlines resolve_frame(
|
||||
const object_frame& frame_info,
|
||||
const std::string& symbol_name,
|
||||
std::size_t offset
|
||||
) {
|
||||
const auto& symbol_table = get_symbols();
|
||||
auto it = symbol_table.find(symbol_name);
|
||||
if(it != symbol_table.end()) {
|
||||
auto frame = frame_info;
|
||||
frame.object_address = it->second + offset;
|
||||
return get_resolver()->resolve_frame(frame);
|
||||
} else {
|
||||
return {
|
||||
{
|
||||
frame_info.raw_address,
|
||||
nullable<std::uint32_t>::null(),
|
||||
nullable<std::uint32_t>::null(),
|
||||
frame_info.object_path,
|
||||
symbol_name,
|
||||
false
|
||||
},
|
||||
{}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct debug_map_symbol_info {
|
||||
uint64_t source_address;
|
||||
uint64_t size;
|
||||
std::string name;
|
||||
nullable<uint64_t> target_address; // T(-1) is used as a sentinel
|
||||
std::size_t object_index;
|
||||
};
|
||||
|
||||
class debug_map_resolver : public symbol_resolver {
|
||||
std::vector<target_object> target_objects;
|
||||
std::vector<debug_map_symbol_info> symbols;
|
||||
public:
|
||||
debug_map_resolver(const std::string& source_object_path) {
|
||||
// load mach-o
|
||||
// TODO: Cache somehow?
|
||||
mach_o source_mach(source_object_path);
|
||||
auto source_debug_map = source_mach.get_debug_map();
|
||||
// get symbol entries from debug map, as well as the various object files used to make this binary
|
||||
for(auto& entry : source_debug_map) {
|
||||
// object it came from
|
||||
target_objects.push_back({std::move(entry.first)});
|
||||
// push the symbols
|
||||
auto& map_entry_symbols = entry.second;
|
||||
symbols.reserve(symbols.size() + map_entry_symbols.size());
|
||||
for(auto& symbol : map_entry_symbols) {
|
||||
symbols.push_back({
|
||||
symbol.source_address,
|
||||
symbol.size,
|
||||
std::move(symbol.name),
|
||||
nullable<uint64_t>::null(),
|
||||
target_objects.size() - 1
|
||||
});
|
||||
}
|
||||
}
|
||||
// sort for binary lookup later
|
||||
std::sort(
|
||||
symbols.begin(),
|
||||
symbols.end(),
|
||||
[] (
|
||||
const debug_map_symbol_info& a,
|
||||
const debug_map_symbol_info& b
|
||||
) {
|
||||
return a.source_address < b.source_address;
|
||||
}
|
||||
);
|
||||
}
|
||||
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
|
||||
frame_with_inlines resolve_frame(const object_frame& frame_info) override {
|
||||
// resolve object frame:
|
||||
// find the symbol in this executable corresponding to the object address
|
||||
// resolve the symbol in the object it came from, based on the symbol name
|
||||
auto closest_symbol_it = first_less_than_or_equal(
|
||||
symbols.begin(),
|
||||
symbols.end(),
|
||||
frame_info.object_address,
|
||||
[] (
|
||||
Dwarf_Addr pc,
|
||||
const debug_map_symbol_info& symbol
|
||||
) {
|
||||
return pc < symbol.source_address;
|
||||
}
|
||||
);
|
||||
if(closest_symbol_it != symbols.end()) {
|
||||
if(frame_info.object_address <= closest_symbol_it->source_address + closest_symbol_it->size) {
|
||||
return target_objects[closest_symbol_it->object_index].resolve_frame(
|
||||
{
|
||||
frame_info.raw_address,
|
||||
// the resolver doesn't care about the object address here, only the offset from the start
|
||||
// of the symbol and it'll lookup the symbol's base-address
|
||||
0,
|
||||
frame_info.object_path
|
||||
},
|
||||
closest_symbol_it->name,
|
||||
frame_info.object_address - closest_symbol_it->source_address
|
||||
);
|
||||
}
|
||||
}
|
||||
// There was either no closest symbol or the closest symbol didn't end up containing the address we're
|
||||
// looking for, so just return a blank frame
|
||||
return {
|
||||
{
|
||||
frame_info.raw_address,
|
||||
nullable<std::uint32_t>::null(),
|
||||
nullable<std::uint32_t>::null(),
|
||||
frame_info.object_path,
|
||||
"",
|
||||
false
|
||||
},
|
||||
{}
|
||||
};
|
||||
};
|
||||
};
|
||||
#endif
|
||||
|
||||
std::unique_ptr<symbol_resolver> get_resolver_for_object(const std::string& object_path) {
|
||||
#if IS_APPLE
|
||||
// Check if dSYM exist, if not fallback to debug map
|
||||
if(!directory_exists(object_path + ".dSYM")) {
|
||||
return std::unique_ptr<debug_map_resolver>(new debug_map_resolver(object_path));
|
||||
}
|
||||
#endif
|
||||
return std::unique_ptr<dwarf_resolver>(new dwarf_resolver(object_path));
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
|
||||
std::vector<stacktrace_frame> resolve_frames(const std::vector<object_frame>& frames) {
|
||||
std::vector<frame_with_inlines> trace(frames.size(), {null_frame, {}});
|
||||
static std::mutex mutex;
|
||||
// cache resolvers since objects are likely to be traced more than once
|
||||
static std::unordered_map<std::string, dwarf_resolver> resolver_map;
|
||||
static std::unordered_map<std::string, std::unique_ptr<symbol_resolver>> resolver_map;
|
||||
// Locking around all libdwarf interaction per https://github.com/davea42/libdwarf-code/discussions/184
|
||||
// And also interactions with the above static map
|
||||
const std::lock_guard<std::mutex> lock(mutex);
|
||||
for(const auto& object_entry : collate_frames(frames, trace)) {
|
||||
try {
|
||||
const auto& object_name = object_entry.first;
|
||||
optional<dwarf_resolver> resolver_object = nullopt;
|
||||
dwarf_resolver* resolver = nullptr;
|
||||
std::unique_ptr<symbol_resolver> resolver_object;
|
||||
symbol_resolver* resolver = nullptr;
|
||||
auto it = resolver_map.find(object_name);
|
||||
if(it != resolver_map.end()) {
|
||||
resolver = &it->second;
|
||||
resolver = it->second.get();
|
||||
} else {
|
||||
resolver_object = dwarf_resolver(object_name);
|
||||
resolver = &resolver_object.unwrap();
|
||||
resolver_object = get_resolver_for_object(object_name);
|
||||
resolver = resolver_object.get();
|
||||
}
|
||||
// If there's no debug information it'll mark itself as not ok
|
||||
if(resolver->ok) {
|
||||
for(const auto& entry : object_entry.second) {
|
||||
try {
|
||||
const auto& dlframe = entry.first.get();
|
||||
@ -902,22 +1119,29 @@ namespace libdwarf {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// at least copy the addresses
|
||||
for(const auto& entry : object_entry.second) {
|
||||
const auto& dlframe = entry.first.get();
|
||||
auto& frame = entry.second.get();
|
||||
frame.frame.address = dlframe.raw_address;
|
||||
}
|
||||
}
|
||||
if(resolver_object.has_value() && get_cache_mode() == cache_mode::prioritize_speed) {
|
||||
if(resolver_object && get_cache_mode() == cache_mode::prioritize_speed) {
|
||||
// .emplace needed, for some reason .insert tries to copy <= gcc 7.2
|
||||
resolver_map.emplace(object_name, std::move(resolver_object).unwrap());
|
||||
resolver_map.emplace(object_name, std::move(resolver_object));
|
||||
}
|
||||
} catch(...) { // NOSONAR
|
||||
if(!should_absorb_trace_exceptions()) {
|
||||
throw;
|
||||
}
|
||||
for(const auto& entry : object_entry.second) {
|
||||
const auto& dlframe = entry.first.get();
|
||||
auto& frame = entry.second.get();
|
||||
frame = {
|
||||
{
|
||||
dlframe.raw_address,
|
||||
nullable<std::uint32_t>::null(),
|
||||
nullable<std::uint32_t>::null(),
|
||||
dlframe.object_path,
|
||||
"",
|
||||
false
|
||||
},
|
||||
{}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
// flatten trace with inlines
|
||||
|
||||
@ -21,7 +21,7 @@ namespace detail {
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
std::vector<frame_ptr> capture_frames(std::size_t skip, std::size_t max_depth) {
|
||||
std::vector<void*> addrs(skip + std::min(hard_max_frames, max_depth), nullptr);
|
||||
int n_frames = CaptureStackBackTrace(
|
||||
std::size_t n_frames = CaptureStackBackTrace(
|
||||
static_cast<ULONG>(skip + 1),
|
||||
static_cast<ULONG>(addrs.size()),
|
||||
addrs.data(),
|
||||
|
||||
@ -58,6 +58,7 @@ namespace detail {
|
||||
static const stacktrace_frame null_frame {0, nullable<uint32_t>::null(), nullable<uint32_t>::null(), "", "", false};
|
||||
|
||||
bool should_absorb_trace_exceptions();
|
||||
bool should_resolve_inlined_calls();
|
||||
enum cache_mode get_cache_mode();
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
|
||||
#ifdef CPPTRACE_USE_EXTERNAL_LIBDWARF
|
||||
#ifdef CPPTRACE_USE_NESTED_LIBDWARF_HEADER_PATH
|
||||
#include <libdwarf/libdwarf.h>
|
||||
#include <libdwarf/dwarf.h>
|
||||
#else
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#ifndef UTILS_HPP
|
||||
#define UTILS_HPP
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
@ -72,6 +73,28 @@ namespace detail {
|
||||
return str;
|
||||
}
|
||||
|
||||
// first value in a sorted range such that *it <= value
|
||||
template<typename ForwardIt, typename T>
|
||||
ForwardIt first_less_than_or_equal(ForwardIt begin, ForwardIt end, const T& value) {
|
||||
auto it = std::upper_bound(begin, end, value);
|
||||
// it is first > value, we want first <= value
|
||||
if(it != begin) {
|
||||
return --it;
|
||||
}
|
||||
return end;
|
||||
}
|
||||
|
||||
// first value in a sorted range such that *it <= value
|
||||
template<typename ForwardIt, typename T, typename Compare>
|
||||
ForwardIt first_less_than_or_equal(ForwardIt begin, ForwardIt end, const T& value, Compare compare) {
|
||||
auto it = std::upper_bound(begin, end, value, compare);
|
||||
// it is first > value, we want first <= value
|
||||
if(it != begin) {
|
||||
return --it;
|
||||
}
|
||||
return end;
|
||||
}
|
||||
|
||||
constexpr const char* const whitespace = " \t\n\r\f\v";
|
||||
|
||||
inline std::string trim(const std::string& str) {
|
||||
|
||||
39
test/ctrace_demo.c
Normal file
39
test/ctrace_demo.c
Normal file
@ -0,0 +1,39 @@
|
||||
#include <ctrace/ctrace.h>
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
|
||||
void trace() {
|
||||
ctrace_raw_trace raw_trace = ctrace_generate_raw_trace(1, INT_MAX);
|
||||
ctrace_object_trace obj_trace = ctrace_resolve_raw_trace_to_object_trace(&raw_trace);
|
||||
ctrace_stacktrace trace = ctrace_resolve_object_trace(&obj_trace);
|
||||
ctrace_print_stacktrace(&trace, stdout, 1);
|
||||
ctrace_free_stacktrace(&trace);
|
||||
ctrace_free_object_trace(&obj_trace);
|
||||
ctrace_free_raw_trace(&raw_trace);
|
||||
assert(raw_trace.frames == NULL && obj_trace.count == 0);
|
||||
}
|
||||
|
||||
void bar(int n) {
|
||||
if(n == 0) {
|
||||
trace();
|
||||
} else {
|
||||
bar(n - 1);
|
||||
}
|
||||
}
|
||||
|
||||
void foo(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j) {
|
||||
bar(1);
|
||||
}
|
||||
|
||||
void function_two(int a, float b) {
|
||||
foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
|
||||
}
|
||||
|
||||
void function_one(int a) {
|
||||
function_two(0, 0);
|
||||
}
|
||||
|
||||
int main() {
|
||||
function_one(0);
|
||||
}
|
||||
@ -12,7 +12,7 @@
|
||||
#include <unistd.h>
|
||||
|
||||
void trace() {
|
||||
*(char*)0 = 2;
|
||||
*(volatile char*)0 = 2;
|
||||
}
|
||||
|
||||
void bar() {
|
||||
|
||||
@ -36,6 +36,7 @@ FetchContent_MakeAvailable(googletest)
|
||||
|
||||
set(cpptrace_DIR "../../build/foo/lib/cmake/cpptrace")
|
||||
set(libdwarf_DIR "../../build/foo/lib/cmake/libdwarf")
|
||||
set(zstd_DIR "../../build/foo/lib/cmake/zstd")
|
||||
find_package(cpptrace REQUIRED)
|
||||
|
||||
add_executable(speedtest speedtest.cpp)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user