Merge branch 'dev' into main

This commit is contained in:
Jeremy 2024-02-11 12:09:13 -06:00
commit 203dbb524e
No known key found for this signature in database
GPG Key ID: 19AA8270105E8EB4
29 changed files with 2284 additions and 514 deletions

View File

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

View File

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

View File

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

@ -4,3 +4,4 @@ build*/
repro*/
__pycache__
scratch
.vscode

View File

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

View File

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

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

View File

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

View File

@ -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']}"
]

View File

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

View File

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

View File

@ -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
View 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);
```

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,7 +12,7 @@
#include <unistd.h>
void trace() {
*(char*)0 = 2;
*(volatile char*)0 = 2;
}
void bar() {

View File

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