From aa5315769e346adeb8d5d54a929904e5d2e995fe Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Wed, 6 Dec 2023 00:16:24 -0500 Subject: [PATCH 01/63] Handle color arguments better if C and C++ compiler families differ --- CMakeLists.txt | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 418afae..02c56b5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,11 +23,18 @@ include(GNUInstallDirs) include(CheckCXXSourceCompiles) include(CheckCXXCompilerFlag) -if(CMAKE_GENERATOR STREQUAL "Ninja") - if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") - add_compile_options(-fdiagnostics-color=always) - elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - add_compile_options(-fcolor-diagnostics) +if(PROJECT_IS_TOP_LEVEL) + if(CMAKE_GENERATOR STREQUAL "Ninja") + if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color=always") + elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcolor-diagnostics") + endif() + if("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fdiagnostics-color=always") + elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fcolor-diagnostics") + endif() endif() endif() From e8fd01bbe12f827cb34f9baa8260670554f93294 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Wed, 6 Dec 2023 00:17:38 -0500 Subject: [PATCH 02/63] Fallback to the cu cache or walking cu's if aranges lookup fails --- src/symbols/symbols_with_libdwarf.cpp | 144 ++++++++++++++------------ 1 file changed, 78 insertions(+), 66 deletions(-) diff --git a/src/symbols/symbols_with_libdwarf.cpp b/src/symbols/symbols_with_libdwarf.cpp index a13c1bd..fb3b66e 100644 --- a/src/symbols/symbols_with_libdwarf.cpp +++ b/src/symbols/symbols_with_libdwarf.cpp @@ -95,6 +95,7 @@ namespace libdwarf { std::unordered_map> subprograms_cache; // Vector of ranges and their corresponding CU offsets std::vector cu_cache; + bool generated_cu_cache = false; // Map from CU -> {srcfiles, count} std::unordered_map> srcfiles_cache; @@ -165,21 +166,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 +199,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,6 +215,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; other.aranges = nullptr; @@ -282,6 +270,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 @@ -787,61 +794,66 @@ namespace libdwarf { } retrieve_line_info(cu_die, pc, frame); // no offset for line info retrieve_symbol(cu_die, pc, dwversion, frame, inlines); + return; } - } else { - 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) { - Dwarf_Half offset_size = 0; - Dwarf_Half dwversion = 0; - dwarf_get_version_of_die(cu_die.get(), &dwversion, &offset_size); - //auto p = cu_die.get_pc_range(dwversion); - //cu_die.print(); - //fprintf(stderr, " %llx, %llx\n", p.first, p.second); + } + // 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) { + Dwarf_Half offset_size = 0; + Dwarf_Half dwversion = 0; + dwarf_get_version_of_die(cu_die.get(), &dwversion, &offset_size); + //auto p = cu_die.get_pc_range(dwversion); + //cu_die.print(); + //fprintf(stderr, " %llx, %llx\n", p.first, p.second); + if(trace_dwarf) { + std::fprintf(stderr, "CU: %d %s\n", dwversion, cu_die.get_name().c_str()); + } + if(cu_die.pc_in_die(dwversion, pc)) { if(trace_dwarf) { - std::fprintf(stderr, "CU: %d %s\n", dwversion, cu_die.get_name().c_str()); + std::fprintf( + stderr, + "pc in die %08llx %s (now searching for %08llx)\n", + to_ull(cu_die.get_global_offset()), + cu_die.get_tag_name(), + to_ull(pc) + ); } - if(cu_die.pc_in_die(dwversion, pc)) { - if(trace_dwarf) { - std::fprintf( - stderr, - "pc in die %08llx %s (now searching for %08llx)\n", - to_ull(cu_die.get_global_offset()), - cu_die.get_tag_name(), - to_ull(pc) - ); - } - retrieve_line_info(cu_die, pc, frame); // no offset for line info - retrieve_symbol(cu_die, pc, dwversion, frame, inlines); - return false; - } - return true; - }); + retrieve_line_info(cu_die, pc, frame); // no offset for line info + retrieve_symbol(cu_die, pc, dwversion, frame, inlines); + return false; + } + return true; + }); + } else { + lazy_generate_cu_cache(); + // look up the cu + auto vec_it = std::lower_bound( + cu_cache.begin(), + cu_cache.end(), + pc, + [] (const cu_entry& entry, Dwarf_Addr pc) { + return entry.low < pc; + } + ); + // 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(); + if(vec_it->die.pc_in_die(vec_it->dwversion, pc)) { + retrieve_line_info(vec_it->die, pc, frame); // no offset for line info + retrieve_symbol(vec_it->die, pc, vec_it->dwversion, frame, inlines); + } } else { - // look up the cu - auto vec_it = std::lower_bound( - cu_cache.begin(), - cu_cache.end(), - pc, - [] (const cu_entry& entry, Dwarf_Addr pc) { - return entry.low < pc; - } - ); - // 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(); - if(vec_it->die.pc_in_die(vec_it->dwversion, pc)) { - retrieve_line_info(vec_it->die, pc, frame); // no offset for line info - retrieve_symbol(vec_it->die, pc, vec_it->dwversion, frame, inlines); - } - } else { - ASSERT(cu_cache.size() == 0, "Vec should be empty?"); - } + ASSERT(cu_cache.size() == 0, "Vec should be empty?"); } } } From ca76080968bcd7ace0185487354a6b486f77015c Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sat, 13 Jan 2024 01:31:10 -0600 Subject: [PATCH 03/63] Updates to better support external libdwarf, because nothing about how libdwarf's cmake is setup is consistent --- CMakeLists.txt | 16 +++++++++++++++- cmake/OptionVariables.cmake | 2 ++ src/symbols/symbols_with_libdwarf.cpp | 2 +- src/utils/dwarf.hpp | 2 +- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 02c56b5..5da1101 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -337,7 +337,6 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) target_compile_definitions(${target_name} PUBLIC CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) 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) @@ -356,14 +355,29 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) 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 $,libdwarf::dwarf-static,libdwarf::dwarf-shared>) + target_compile_definitions(${target_name} PRIVATE CPPTRACE_USE_NESTED_LIBDWARF_HEADER_PATH) + 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, then libdwarf::dwarf + # libdwarf v0.8.0 installs with the target libdwarf::dwarf somehow, despite creating libdwarf::dwarf-static or + # libdwarf::dwarf-shared under fetchcontent + target_link_libraries(${target_name} PRIVATE $,libdwarf::dwarf-static,$,libdwarf::dwarf-shared,libdwarf::dwarf>>) + endif() else() target_link_libraries(${target_name} PRIVATE libdwarf::dwarf-static) endif() if(UNIX) target_link_libraries(${target_name} PRIVATE ${CMAKE_DL_LIBS}) endif() + if(CPPTRACE_USE_NESTED_LIBDWARF_HEADER_PATH) # user override + target_compile_definitions(${target_name} PRIVATE CPPTRACE_USE_NESTED_LIBDWARF_HEADER_PATH) + endif() endif() if(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP) diff --git a/cmake/OptionVariables.cmake b/cmake/OptionVariables.cmake index 377290d..abdc269 100644 --- a/cmake/OptionVariables.cmake +++ b/cmake/OptionVariables.cmake @@ -158,6 +158,7 @@ if(PROJECT_IS_TOP_LEVEL) endif() option(CPPTRACE_USE_EXTERNAL_LIBDWARF "" OFF) +option(CPPTRACE_USE_NESTED_LIBDWARF_HEADER_PATH "" OFF) option(CPPTRACE_CONAN "" OFF) option(CPPTRACE_VCPKG "" OFF) option(CPPTRACE_SANITIZER_BUILD "" OFF) @@ -167,6 +168,7 @@ mark_as_advanced( CPPTRACE_ADDR2LINE_PATH CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH CPPTRACE_SANITIZER_BUILD + CPPTRACE_USE_NESTED_LIBDWARF_HEADER_PATH CPPTRACE_CONAN CPPTRACE_VCPKG ) diff --git a/src/symbols/symbols_with_libdwarf.cpp b/src/symbols/symbols_with_libdwarf.cpp index fb3b66e..f3cc878 100644 --- a/src/symbols/symbols_with_libdwarf.cpp +++ b/src/symbols/symbols_with_libdwarf.cpp @@ -20,7 +20,7 @@ #include #include -#ifdef CPPTRACE_USE_EXTERNAL_LIBDWARF +#ifdef CPPTRACE_USE_NESTED_LIBDWARF_HEADER_PATH #include #include #else diff --git a/src/utils/dwarf.hpp b/src/utils/dwarf.hpp index 61a8ea5..39fbb1a 100644 --- a/src/utils/dwarf.hpp +++ b/src/utils/dwarf.hpp @@ -9,7 +9,7 @@ #include #include -#ifdef CPPTRACE_USE_EXTERNAL_LIBDWARF +#ifdef CPPTRACE_USE_NESTED_LIBDWARF_HEADER_PATH #include #include #else From 7720df748a20fbbe4c5144b33d621294e6a78698 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 14 Jan 2024 02:53:35 -0600 Subject: [PATCH 04/63] Updates to urls and tags for libdwarf --- .github/workflows/build.yml | 6 +++--- .github/workflows/test.yml | 6 +++--- CMakeLists.txt | 6 ++++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ab45134..04031bc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,7 +23,7 @@ jobs: mkdir libdwarf cd libdwarf git init - git remote add origin https://github.com/jeremy-rifkin/libdwarf-code.git + git remote add origin https://github.com/davea42/libdwarf-code.git git fetch --depth 1 origin 6216e185863f41d6f19ab850caabfff7326020d7 git checkout FETCH_HEAD mkdir build @@ -52,7 +52,7 @@ jobs: mkdir libdwarf cd libdwarf git init - git remote add origin https://github.com/jeremy-rifkin/libdwarf-code.git + git remote add origin https://github.com/davea42/libdwarf-code.git git fetch --depth 1 origin 6216e185863f41d6f19ab850caabfff7326020d7 git checkout FETCH_HEAD mkdir build @@ -83,7 +83,7 @@ jobs: mkdir libdwarf cd libdwarf git init - git remote add origin https://github.com/jeremy-rifkin/libdwarf-code.git + git remote add origin https://github.com/davea42/libdwarf-code.git git fetch --depth 1 origin 6216e185863f41d6f19ab850caabfff7326020d7 git checkout FETCH_HEAD mkdir build diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5ec85a1..624ec53 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,7 +26,7 @@ jobs: mkdir libdwarf cd libdwarf git init - git remote add origin https://github.com/jeremy-rifkin/libdwarf-code.git + git remote add origin https://github.com/davea42/libdwarf-code.git git fetch --depth 1 origin 6216e185863f41d6f19ab850caabfff7326020d7 git checkout FETCH_HEAD mkdir build @@ -53,7 +53,7 @@ jobs: mkdir libdwarf cd libdwarf git init - git remote add origin https://github.com/jeremy-rifkin/libdwarf-code.git + git remote add origin https://github.com/davea42/libdwarf-code.git git fetch --depth 1 origin 6216e185863f41d6f19ab850caabfff7326020d7 git checkout FETCH_HEAD mkdir build @@ -88,7 +88,7 @@ jobs: mkdir libdwarf cd libdwarf git init - git remote add origin https://github.com/jeremy-rifkin/libdwarf-code.git + git remote add origin https://github.com/davea42/libdwarf-code.git git fetch --depth 1 origin 6216e185863f41d6f19ab850caabfff7326020d7 git checkout FETCH_HEAD mkdir build diff --git a/CMakeLists.txt b/CMakeLists.txt index 5da1101..5816768 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -344,11 +344,13 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) 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 # 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_SHALLOW 1 ) FetchContent_MakeAvailable(libdwarf) From 5d64a98d08f5ed7d9e581412e04d8a0d66e0ba49 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 14 Jan 2024 03:11:33 -0600 Subject: [PATCH 05/63] Handle libdwarf's sporadic header placement better --- CMakeLists.txt | 10 +++++++--- cmake/OptionVariables.cmake | 2 -- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a48ff10..5b0f3f3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -371,15 +371,19 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) # libdwarf::dwarf-shared under fetchcontent target_link_libraries(${target_name} PRIVATE $,libdwarf::dwarf-static,$,libdwarf::dwarf-shared,libdwarf::dwarf>>) endif() + include(CheckIncludeFileCXX) + # 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 + CHECK_INCLUDE_FILE_CXX("libdwarf/libdwarf.h" LIBDWARF_IS_NESTED) + if(${LIBDWARF_IS_NESTED}) + target_compile_definitions(${target_name} PRIVATE CPPTRACE_USE_NESTED_LIBDWARF_HEADER_PATH) + endif() else() target_link_libraries(${target_name} PRIVATE libdwarf::dwarf-static) endif() if(UNIX) target_link_libraries(${target_name} PRIVATE ${CMAKE_DL_LIBS}) endif() - if(CPPTRACE_USE_NESTED_LIBDWARF_HEADER_PATH) # user override - target_compile_definitions(${target_name} PRIVATE CPPTRACE_USE_NESTED_LIBDWARF_HEADER_PATH) - endif() endif() if(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP) diff --git a/cmake/OptionVariables.cmake b/cmake/OptionVariables.cmake index abdc269..377290d 100644 --- a/cmake/OptionVariables.cmake +++ b/cmake/OptionVariables.cmake @@ -158,7 +158,6 @@ if(PROJECT_IS_TOP_LEVEL) endif() option(CPPTRACE_USE_EXTERNAL_LIBDWARF "" OFF) -option(CPPTRACE_USE_NESTED_LIBDWARF_HEADER_PATH "" OFF) option(CPPTRACE_CONAN "" OFF) option(CPPTRACE_VCPKG "" OFF) option(CPPTRACE_SANITIZER_BUILD "" OFF) @@ -168,7 +167,6 @@ mark_as_advanced( CPPTRACE_ADDR2LINE_PATH CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH CPPTRACE_SANITIZER_BUILD - CPPTRACE_USE_NESTED_LIBDWARF_HEADER_PATH CPPTRACE_CONAN CPPTRACE_VCPKG ) From 399e52f4601914aefc84fe6f0ff5f4bba760219f Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 14 Jan 2024 15:23:02 -0600 Subject: [PATCH 06/63] Try again to fix libdwarf #includes --- CMakeLists.txt | 23 ++++++++++++++++------- src/symbols/symbols_with_libdwarf.cpp | 10 +--------- src/utils/dwarf.hpp | 18 ++++++++++++++---- 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b0f3f3..4a8b0ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -371,13 +371,22 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) # libdwarf::dwarf-shared under fetchcontent target_link_libraries(${target_name} PRIVATE $,libdwarf::dwarf-static,$,libdwarf::dwarf-shared,libdwarf::dwarf>>) endif() - include(CheckIncludeFileCXX) - # 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 - CHECK_INCLUDE_FILE_CXX("libdwarf/libdwarf.h" LIBDWARF_IS_NESTED) - if(${LIBDWARF_IS_NESTED}) - target_compile_definitions(${target_name} PRIVATE CPPTRACE_USE_NESTED_LIBDWARF_HEADER_PATH) - endif() + # include(CheckIncludeFileCXX) + # # include(CheckIncludeFile) + # # 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 + # get_cmake_property(_variableNames VARIABLES) + # list (SORT _variableNames) + # foreach (_variableName ${_variableNames}) + # message(STATUS "${_variableName}=${${_variableName}}") + # endforeach() + # CHECK_INCLUDE_FILE_CXX("libdwarf/libdwarf.h" LIBDWARF_IS_NESTED CMAKE_REQUIRED_LIBRARIES ${LIBDWARF_LIBRARIES}) + # CHECK_INCLUDE_FILE_CXX("libdwarf.h" LIBDWARF_IS_NOT_NESTED CMAKE_REQUIRED_LIBRARIES ${LIBDWARF_LIBRARIES}) + # # 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) + # endif() else() target_link_libraries(${target_name} PRIVATE libdwarf::dwarf-static) endif() diff --git a/src/symbols/symbols_with_libdwarf.cpp b/src/symbols/symbols_with_libdwarf.cpp index f3cc878..29cf417 100644 --- a/src/symbols/symbols_with_libdwarf.cpp +++ b/src/symbols/symbols_with_libdwarf.cpp @@ -3,7 +3,7 @@ #include #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,14 +20,6 @@ #include #include -#ifdef CPPTRACE_USE_NESTED_LIBDWARF_HEADER_PATH -#include -#include -#else -#include -#include -#endif - // 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 // https://github.com/davea42/libdwarf-addr2line diff --git a/src/utils/dwarf.hpp b/src/utils/dwarf.hpp index 39fbb1a..d682418 100644 --- a/src/utils/dwarf.hpp +++ b/src/utils/dwarf.hpp @@ -10,11 +10,21 @@ #include #ifdef CPPTRACE_USE_NESTED_LIBDWARF_HEADER_PATH -#include -#include + #include + #include #else -#include -#include + #ifdef __has_include + #ifdef __has_include() + #include + #include + #else + #include + #include + #endif + #else + #include + #include + #endif #endif namespace cpptrace { From 3e46d2a570ffeb20e7fb8c6ebf7724a0a0be3726 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 14 Jan 2024 18:05:31 -0600 Subject: [PATCH 07/63] Fix typo --- src/utils/dwarf.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/dwarf.hpp b/src/utils/dwarf.hpp index d682418..8b2b038 100644 --- a/src/utils/dwarf.hpp +++ b/src/utils/dwarf.hpp @@ -14,7 +14,7 @@ #include #else #ifdef __has_include - #ifdef __has_include() + #if __has_include() #include #include #else From 2100723312aecb1df58bf6b62926212fc9dddebe Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 14 Jan 2024 19:56:11 -0600 Subject: [PATCH 08/63] Another attempt to resolve libdwarf header issues --- CMakeLists.txt | 46 +++++++++++++++++++++++++++------------------ src/utils/dwarf.hpp | 14 ++------------ 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a8b0ac..2d098d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -366,27 +366,37 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) 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, then libdwarf::dwarf + # 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 - target_link_libraries(${target_name} PRIVATE $,libdwarf::dwarf-static,$,libdwarf::dwarf-shared,libdwarf::dwarf>>) + 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() - # include(CheckIncludeFileCXX) - # # include(CheckIncludeFile) - # # 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 - # get_cmake_property(_variableNames VARIABLES) - # list (SORT _variableNames) - # foreach (_variableName ${_variableNames}) - # message(STATUS "${_variableName}=${${_variableName}}") - # endforeach() - # CHECK_INCLUDE_FILE_CXX("libdwarf/libdwarf.h" LIBDWARF_IS_NESTED CMAKE_REQUIRED_LIBRARIES ${LIBDWARF_LIBRARIES}) - # CHECK_INCLUDE_FILE_CXX("libdwarf.h" LIBDWARF_IS_NOT_NESTED CMAKE_REQUIRED_LIBRARIES ${LIBDWARF_LIBRARIES}) - # # 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) - # endif() else() target_link_libraries(${target_name} PRIVATE libdwarf::dwarf-static) endif() diff --git a/src/utils/dwarf.hpp b/src/utils/dwarf.hpp index 8b2b038..39feb34 100644 --- a/src/utils/dwarf.hpp +++ b/src/utils/dwarf.hpp @@ -13,18 +13,8 @@ #include #include #else - #ifdef __has_include - #if __has_include() - #include - #include - #else - #include - #include - #endif - #else - #include - #include - #endif + #include + #include #endif namespace cpptrace { From f4faf00a8bce578ab4b746c70388b30a1c03a320 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 14 Jan 2024 20:08:48 -0600 Subject: [PATCH 09/63] Add some more instructions for using the library --- README.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/README.md b/README.md index b080054..ad3189d 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,9 @@ 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) + - [Creating a Bundle](#creating-a-bundle) - [Package Managers](#package-managers) - [Conan](#conan) - [Vcpkg](#vcpkg) @@ -609,6 +612,49 @@ Using manually: g++ main.cpp -o main -g -Wall -I$HOME/wherever/include -L$HOME/wherever/lib -lcpptrace ``` +## Use Without CMake + +If you want to use the library without cmake, follow either the [System-Wide Installation](#system-wide-installation), +[Local User Installation](#local-user-installation), or [Package Managers](#package-managers) instructions. + +### 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. + +
+ Installation Without Package Managers or FetchContent + +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/davea42/libdwarf-code.git +cd libdwarf-code +git checkout 6216e185863f41d6f19ab850caabfff7326020d7 # looks like some cmake stuff changed upstream, checking out the commit cpptrace currently uses +mkdir build +cd build +cmake .. -DPIC_ALWAYS=On -DBUILD_DWARFDUMP=Off -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 +``` +
+ +### Creating a Bundle + ## Package Managers ### Conan From e8c857d6c2db1800becc0b13856fa5d7f246d6f7 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 14 Jan 2024 22:27:25 -0600 Subject: [PATCH 10/63] Small tweak for a couple aggregate initializations --- src/cpptrace.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cpptrace.cpp b/src/cpptrace.cpp index 564b660..91972f9 100644 --- a/src/cpptrace.cpp +++ b/src/cpptrace.cpp @@ -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; @@ -333,7 +333,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; From 7f6e91e0ff49f7518c6e70b9e91121c88e0190c3 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 14 Jan 2024 22:28:01 -0600 Subject: [PATCH 11/63] Update to installation and usage instructions --- README.md | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index ad3189d..e4dd4c1 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,7 @@ and Windows including MinGW and Cygwin environments. The goal: Make stack traces - [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) - - [Creating a Bundle](#creating-a-bundle) + - [Installation Without Package Managers or FetchContent](#installation-without-package-managers-or-fetchcontent) - [Package Managers](#package-managers) - [Conan](#conan) - [Vcpkg](#vcpkg) @@ -614,10 +613,23 @@ g++ main.cpp -o main -g -Wall -I$HOME/wherever/include -L$HOME/wherever/lib -lcp ## Use Without CMake -If you want to use the library without cmake, follow either the [System-Wide Installation](#system-wide-installation), -[Local User Installation](#local-user-installation), or [Package Managers](#package-managers) instructions. +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). -### Installation Without Package Managers or FetchContent +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. @@ -651,9 +663,11 @@ cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=On -DCPPTRACE_USE_EXTERN make -j make install ``` - -### Creating a Bundle +The `~/scratch/cpptrace-test/resources` directory also serves as a bundle you can ship with all the installed files for +cpptrace and its dependencies. + + ## Package Managers From a654f2082e1d43c42cb0d05e3c3683fd8903d0d9 Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 14 Jan 2024 23:36:30 -0600 Subject: [PATCH 12/63] Mach-o refactoring (#77) This is the first step towards a more comprehensive mach-o system. Next step will be to add support for symbol table parsing. --- src/binary/mach-o.hpp | 344 ++++++++++++++------------ src/binary/object.hpp | 2 +- src/symbols/symbols_with_libdwarf.cpp | 2 +- 3 files changed, 192 insertions(+), 156 deletions(-) diff --git a/src/binary/mach-o.hpp b/src/binary/mach-o.hpp index 6821e3a..23153f5 100644 --- a/src/binary/mach-o.hpp +++ b/src/binary/mach-o.hpp @@ -73,134 +73,205 @@ namespace detail { #define LP(x) x #endif - template - static optional 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::type; - using Segment_Command = typename std::conditional::type; - std::uint32_t ncmds; - off_t load_commands_offset = offset; - std::size_t header_size = sizeof(Mach_Header); - Mach_Header header = load_bytes(object_file, offset); - if(should_swap) { - swap_mach_header(header); + struct load_command_entry { + std::uint32_t file_offset; + std::uint32_t cmd; + std::uint32_t cmdsize; + }; + + 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 load_base = 0; + std::size_t fat_index = std::numeric_limits::max(); + + std::vector load_commands; + + 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); + } + magic = load_bytes(file, 0); + VERIFY(is_mach_o(magic), "File is not Mach-O " + object_path); + if(magic == FAT_MAGIC || magic == FAT_CIGAM) { + load_fat_mach(); + } else { + fat_index = 0; + if(is_magic_64(magic)) { + load_mach<64>(false); + } else { + load_mach<32>(false); + } + } } - thread_local static struct LP(mach_header)* mhp = _NSGetMachExecuteHeader(); - //std::fprintf( - // stderr, - // "----> %d %d; %d %d\n", - // header.cputype, - // mhp->cputype, - // static_cast(mhp->cpusubtype & ~CPU_SUBTYPE_MASK), - // header.cpusubtype - //); - if( - header.cputype != mhp->cputype || - static_cast(mhp->cpusubtype & ~CPU_SUBTYPE_MASK) != header.cpusubtype + + ~mach_o() { + if(file) { + 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... + PANIC("Couldn't find __TEXT section while parsing Mach-O object"); + return 0; + } + + std::size_t get_fat_index() const { + VERIFY(fat_index != std::numeric_limits::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++; + } + } + + private: + template + void load_mach( + bool allow_arch_mismatch ) { - if(allow_arch_mismatch) { - return nullopt; - } else { - PANIC("Mach-O file cpu type and subtype do not match current machine " + object_path); + static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument"); + using Mach_Header = typename std::conditional::type; + std::size_t header_size = sizeof(Mach_Header); + Mach_Header header = load_bytes(file, load_base); + magic = header.magic; + if(should_swap()) { + swap_mach_header(header); + } + thread_local static struct LP(mach_header)* mhp = _NSGetMachExecuteHeader(); + if( + header.cputype != mhp->cputype || + static_cast(mhp->cpusubtype & ~CPU_SUBTYPE_MASK) != header.cpusubtype + ) { + if(allow_arch_mismatch) { + return; + } else { + PANIC("Mach-O file cpu type and subtype do not match current machine " + object_path); + } + } + 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(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; } } - 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(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(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; - } - 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(object_file, 0); - if(should_swap) { - swap_fat_header(&header, NX_UnknownByteOrder); + 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(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(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(file, mach_header_offset); + if( + arch.cputype == mhp->cputype && + static_cast(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; + } + } + // 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"); } - off_t arch_offset = (off_t)header_size; - optional text_vmaddr; - for(std::uint32_t i = 0; i < header.nfat_arch; i++) { - fat_arch arch = load_bytes(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(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; - } - 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); - if(file == nullptr) { - throw file_error("Unable to read object file " + object_path); - } - std::uint32_t magic = load_bytes(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); - } else { - if(is_64) { - return macho_get_text_vmaddr_mach<64>(file, object_path, 0, should_swap, false).unwrap(); - } else { - return macho_get_text_vmaddr_mach<32>(file, object_path, 0, should_swap, false).unwrap(); + template + segment_command_64 load_segment_command(std::uint32_t offset) const { + using Segment_Command = typename std::conditional::type; + Segment_Command segment = load_bytes(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; } - } + + 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); @@ -210,41 +281,6 @@ namespace detail { std::uint32_t magic = load_bytes(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(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(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(file, arch_offset); - if(should_swap) { - swap_fat_arch(&arch, 1, NX_UnknownByteOrder); - } - arch_offset += arch_size; - if( - arch.cputype == mhp->cputype && - static_cast(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"); - } } } diff --git a/src/binary/object.hpp b/src/binary/object.hpp index 1af4f21..efb3ae0 100644 --- a/src/binary/object.hpp +++ b/src/binary/object.hpp @@ -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 { diff --git a/src/symbols/symbols_with_libdwarf.cpp b/src/symbols/symbols_with_libdwarf.cpp index 29cf417..a260cc4 100644 --- a/src/symbols/symbols_with_libdwarf.cpp +++ b/src/symbols/symbols_with_libdwarf.cpp @@ -126,7 +126,7 @@ namespace libdwarf { object_path += ".dSYM/Contents/Resources/DWARF/" + basename(object_path); } if(macho_is_fat(object_path)) { - universal_number = get_fat_macho_index(object_path); + universal_number = mach_o(object_path).get_fat_index(); } #endif From 3d74da8df1a691c392efeda58843df587ac2341e Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 14 Jan 2024 23:42:15 -0600 Subject: [PATCH 13/63] Turn on -Werror for CI pipelines --- CMakeLists.txt | 9 +++++++++ ci/build-in-all-configs.py | 4 ++++ ci/test-all-configs.py | 4 ++++ cmake/OptionVariables.cmake | 2 ++ 4 files changed, 19 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d098d8..09158a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -229,6 +229,15 @@ target_compile_options( $<$:/W4 /WX /permissive-> ) +if(CPPTRACE_WERROR_BUILD) + target_compile_options( + ${target_name} + PRIVATE + $<$>:-Werror> + $<$:/WX> + ) +endif() + # ---- Generate Build Info Headers ---- # used in export header generated below diff --git a/ci/build-in-all-configs.py b/ci/build-in-all-configs.py index e5dc2e3..913f4aa 100644 --- a/ci/build-in-all-configs.py +++ b/ci/build-in-all-configs.py @@ -48,6 +48,7 @@ def build(matrix): f"-DCMAKE_CXX_COMPILER={matrix['compiler']}", f"-DCMAKE_CXX_STANDARD={matrix['std']}", f"-DCPPTRACE_USE_EXTERNAL_LIBDWARF=On", + f"-DCPPTRACE_WERROR_BUILD=On", f"-D{matrix['unwind']}=On", f"-D{matrix['symbols']}=On", f"-D{matrix['demangle']}=On", @@ -63,6 +64,7 @@ def build(matrix): f"-DCMAKE_CXX_COMPILER={matrix['compiler']}", f"-DCMAKE_CXX_STANDARD={matrix['std']}", f"-DCPPTRACE_USE_EXTERNAL_LIBDWARF=On", + f"-DCPPTRACE_WERROR_BUILD=On", f"-D{matrix['unwind']}=On", f"-D{matrix['symbols']}=On", f"-D{matrix['demangle']}=On", @@ -98,6 +100,7 @@ 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_WERROR_BUILD=On", f"-DCPPTRACE_BACKTRACE_PATH=/usr/lib/gcc/x86_64-linux-gnu/10/include/backtrace.h", ] if matrix["config"] != "": @@ -113,6 +116,7 @@ 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_WERROR_BUILD=On", ] if matrix["config"] != "": args.append(f"{matrix['config']}") diff --git a/ci/test-all-configs.py b/ci/test-all-configs.py index 1cfd0d5..abdd7c0 100644 --- a/ci/test-all-configs.py +++ b/ci/test-all-configs.py @@ -155,6 +155,7 @@ 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_WERROR_BUILD=On", f"-D{matrix['unwind']}=On", f"-D{matrix['symbols']}=On", f"-D{matrix['demangle']}=On", @@ -176,6 +177,7 @@ 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_WERROR_BUILD=On", f"-D{matrix['unwind']}=On", f"-D{matrix['symbols']}=On", f"-D{matrix['demangle']}=On", @@ -202,6 +204,7 @@ 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_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 +223,7 @@ 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_WERROR_BUILD=On", "-DCPPTRACE_BUILD_TESTING=On", f"-DBUILD_SHARED_LIBS={matrix['shared']}" ] diff --git a/cmake/OptionVariables.cmake b/cmake/OptionVariables.cmake index 377290d..6a4a3c6 100644 --- a/cmake/OptionVariables.cmake +++ b/cmake/OptionVariables.cmake @@ -161,12 +161,14 @@ option(CPPTRACE_USE_EXTERNAL_LIBDWARF "" 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 ) From cfdd311e156b5064e25fd938a61ccf779173f17a Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 14 Jan 2024 23:51:41 -0600 Subject: [PATCH 14/63] Fix warning for gcc on windows --- src/unwind/unwind_with_winapi.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/unwind/unwind_with_winapi.cpp b/src/unwind/unwind_with_winapi.cpp index d10f6d9..a499357 100644 --- a/src/unwind/unwind_with_winapi.cpp +++ b/src/unwind/unwind_with_winapi.cpp @@ -21,7 +21,7 @@ namespace detail { CPPTRACE_FORCE_NO_INLINE std::vector capture_frames(std::size_t skip, std::size_t max_depth) { std::vector addrs(skip + std::min(hard_max_frames, max_depth), nullptr); - int n_frames = CaptureStackBackTrace( + std::size_t n_frames = CaptureStackBackTrace( static_cast(skip + 1), static_cast(addrs.size()), addrs.data(), From 218957dfb0a957debfdf8e497c50e8c542bf236c Mon Sep 17 00:00:00 2001 From: eightfold <97198162+8ightfold@users.noreply.github.com> Date: Tue, 23 Jan 2024 01:16:20 -0500 Subject: [PATCH 15/63] New C api (#80) Updated C API following the requested scheme. May implement "exceptions" later... Closes #38 --------- Co-authored-by: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> --- .gitignore | 1 + CMakeLists.txt | 63 ++--- README.md | 4 +- include/cpptrace/cpptrace.hpp | 8 +- include/ctrace/ctrace.h | 141 ++++++++++ src/ctrace.cpp | 398 +++++++++++++++++++++++++++ src/symbols/symbols_with_dbghelp.cpp | 4 +- test/ctrace_demo.cpp | 58 ++++ 8 files changed, 634 insertions(+), 43 deletions(-) create mode 100644 include/ctrace/ctrace.h create mode 100644 src/ctrace.cpp create mode 100644 test/ctrace_demo.cpp diff --git a/.gitignore b/.gitignore index 252257b..fbd07ba 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ build*/ repro*/ __pycache__ scratch +.vscode diff --git a/CMakeLists.txt b/CMakeLists.txt index 09158a1..c42206f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -189,6 +189,7 @@ add_library(cpptrace::cpptrace ALIAS ${target_name}) target_sources( ${target_name} PRIVATE include/cpptrace/cpptrace.hpp + include/ctrace/ctrace.h ) # add /src files to target @@ -196,6 +197,7 @@ target_sources( ${target_name} PRIVATE # src src/cpptrace.cpp + src/ctrace.cpp src/demangle/demangle_with_cxxabi.cpp src/demangle/demangle_with_winapi.cpp src/demangle/demangle_with_nothing.cpp @@ -519,44 +521,35 @@ 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 "$<$:-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() + if(APPLE) # TODO: Temporary + add_custom_command( + TARGET ${exec_name} + POST_BUILD + COMMAND dsymutil $ + ) + 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 "$<$:-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 $ - ) - 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 "$<$:-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 $ - ) - endif() + add_executable(c_demo test/ctrace_demo.cpp) + + add_test_dependencies(test) + add_test_dependencies(demo) + add_test_dependencies(c_demo) if(UNIX) add_executable(signal_demo test/signal_demo.cpp) diff --git a/README.md b/README.md index e4dd4c1..17d81bd 100644 --- a/README.md +++ b/README.md @@ -431,8 +431,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 diff --git a/include/cpptrace/cpptrace.hpp b/include/cpptrace/cpptrace.hpp index 156e348..10f7af3 100644 --- a/include/cpptrace/cpptrace.hpp +++ b/include/cpptrace/cpptrace.hpp @@ -9,7 +9,7 @@ #include #include #include - +// Generated by the build system. #include #if __cplusplus >= 202002L @@ -224,11 +224,11 @@ namespace cpptrace { 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 { diff --git a/include/ctrace/ctrace.h b/include/ctrace/ctrace.h new file mode 100644 index 0000000..3aa1467 --- /dev/null +++ b/include/ctrace/ctrace.h @@ -0,0 +1,141 @@ +#ifndef CTRACE_H +#define CTRACE_H + +#include +#include +#include + +#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 + +// TODO: Add exports + +CTRACE_BEGIN_DEFINITIONS + typedef struct raw_trace ctrace_raw_trace; + typedef struct object_trace ctrace_object_trace; + typedef struct 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 object_frame ctrace_object_frame; + typedef struct stacktrace_frame ctrace_stacktrace_frame; + typedef struct safe_object_frame ctrace_safe_object_frame; + + // Type-safe null-terminated string wrapper + typedef struct { + const char* data; + } ctrace_owning_string; + + struct object_frame { + ctrace_frame_ptr raw_address; + ctrace_frame_ptr obj_address; + const char* obj_path; + // const char* symbol; + }; + + struct stacktrace_frame { + ctrace_frame_ptr address; + uint32_t line; + uint32_t column; + const char* filename; + const char* symbol; + ctrace_bool is_inline; + }; + + struct safe_object_frame { + ctrace_frame_ptr raw_address; + ctrace_frame_ptr relative_obj_address; + char object_path[CTRACE_PATH_MAX + 1]; + }; + + struct raw_trace { + ctrace_frame_ptr* frames; + size_t count; + }; + + struct object_trace { + ctrace_object_frame* frames; + size_t count; + }; + + struct stacktrace { + ctrace_stacktrace_frame* frames; + size_t count; + }; + + 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; + + // ctrace::string: + ctrace_owning_string ctrace_generate_owning_string(const char* raw_string); + void ctrace_free_owning_string(ctrace_owning_string* string); + + // ctrace::generation: + ctrace_raw_trace ctrace_generate_raw_trace(size_t skip, size_t max_depth); + ctrace_object_trace ctrace_generate_object_trace(size_t skip, size_t max_depth); + ctrace_stacktrace ctrace_generate_trace(size_t skip, size_t max_depth); + + // ctrace::freeing: + void ctrace_free_raw_trace(ctrace_raw_trace* trace); + void ctrace_free_object_trace(ctrace_object_trace* trace); + void ctrace_free_stacktrace(ctrace_stacktrace* trace); + + // ctrace::resolve: + ctrace_stacktrace ctrace_raw_trace_resolve(const ctrace_raw_trace* trace); + ctrace_object_trace ctrace_raw_trace_resolve_object_trace(const ctrace_raw_trace* trace); + ctrace_stacktrace ctrace_object_trace_resolve(const ctrace_object_trace* trace); + + // ctrace::safe: + size_t ctrace_safe_generate_raw_trace(ctrace_frame_ptr* buffer, size_t size, size_t skip, size_t max_depth); + void ctrace_get_safe_object_frame(ctrace_frame_ptr address, ctrace_safe_object_frame* out); + + // ctrace::io: + ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color); + void ctrace_stacktrace_print(const ctrace_stacktrace* trace, FILE* to, ctrace_bool use_color); + + // utility::demangle: + ctrace_owning_string ctrace_demangle(const char* mangled); + + // utility::io: + int ctrace_stdin_fileno(void); + int ctrace_stderr_fileno(void); + int ctrace_stdout_fileno(void); + ctrace_bool ctrace_isatty(int fd); + + // utility::cache: + void ctrace_set_cache_mode(ctrace_cache_mode mode); + ctrace_cache_mode ctrace_get_cache_mode(void); + +CTRACE_END_DEFINITIONS + +#endif diff --git a/src/ctrace.cpp b/src/ctrace.cpp new file mode 100644 index 0000000..7cc3a49 --- /dev/null +++ b/src/ctrace.cpp @@ -0,0 +1,398 @@ +#include +#include +#include + +#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 + 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::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::length(raw_string); + char* new_string = new char[count + 1]; + std::char_traits::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& 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& 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 new_frames; + new_frames.reserve(ptrace->count); + for(std::size_t i = 0; i < ptrace->count; ++i) { + using nullable_type = cpptrace::nullable; + 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 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 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 frames = cpptrace::detail::capture_frames(skip + 1, max_depth); + std::vector 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_raw_trace_resolve(const ctrace_raw_trace* trace) { + if(!trace || !trace->frames) return { nullptr, 0 }; + try { + std::vector frames(trace->count, 0); + std::copy(trace->frames, trace->frames + trace->count, frames.begin()); + std::vector 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_raw_trace_resolve_object_trace(const ctrace_raw_trace* trace) { + if(!trace || !trace->frames) return { nullptr, 0 }; + try { + std::vector frames(trace->count, 0); + std::copy(trace->frames, trace->frames + trace->count, frames.begin()); + std::vector 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_object_trace_resolve(const ctrace_object_trace* trace) { + if(!trace || !trace->frames) return { nullptr, 0 }; + try { + std::vector 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 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(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(""); + 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_stacktrace_print(const ctrace_stacktrace* trace, FILE* to, ctrace_bool use_color) { + if(use_color) 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, "\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(mode); + cpptrace::experimental::set_cache_mode(cache_mode); + } + + ctrace_cache_mode ctrace_get_cache_mode(void) { + auto cache_mode = cpptrace::detail::get_cache_mode(); + return static_cast(cache_mode); + } +} diff --git a/src/symbols/symbols_with_dbghelp.cpp b/src/symbols/symbols_with_dbghelp.cpp index 82d2fd5..929419e 100644 --- a/src/symbols/symbols_with_dbghelp.cpp +++ b/src/symbols/symbols_with_dbghelp.cpp @@ -345,7 +345,7 @@ namespace dbghelp { std::fprintf(stderr, "Stack trace: Internal error while calling SymSetContext\n"); return { addr, - static_cast(line.LineNumber), + { static_cast(line.LineNumber) }, nullable::null(), line.FileName, symbol->Name, @@ -377,7 +377,7 @@ namespace dbghelp { signature = std::regex_replace(signature, comma_re, ", "); return { addr, - static_cast(line.LineNumber), + { static_cast(line.LineNumber) }, nullable::null(), line.FileName, signature, diff --git a/test/ctrace_demo.cpp b/test/ctrace_demo.cpp new file mode 100644 index 0000000..955de0a --- /dev/null +++ b/test/ctrace_demo.cpp @@ -0,0 +1,58 @@ +#include +#include +#include +#include + +void test_linker() { + /* Owning String */ { + auto str = ctrace_generate_owning_string("Hello C!"); + std::printf("%s\n", str.data); + ctrace_free_owning_string(&str); + assert(str.data == nullptr); + } /* Trace */ { + ctrace_stacktrace trace = ctrace_generate_trace(0, INT_MAX); + ctrace_owning_string str = ctrace_stacktrace_to_string(&trace, 0); + ctrace_free_stacktrace(&trace); + assert(trace.count == 0); + std::printf("%s\n", str.data); + ctrace_free_owning_string(&str); + } +} + +void trace() { + ctrace_raw_trace raw_trace = ctrace_generate_raw_trace(1, INT_MAX); + ctrace_object_trace obj_trace = ctrace_raw_trace_resolve_object_trace(&raw_trace); + ctrace_stacktrace trace = ctrace_object_trace_resolve(&obj_trace); + ctrace_stacktrace_print(&trace, stdout, 1); + ctrace_free_stacktrace(&trace); + ctrace_free_object_trace(&obj_trace); + ctrace_free_raw_trace(&raw_trace); + assert(raw_trace.frames == nullptr && obj_trace.count == 0); +} + +void foo(int n) { + if(n == 0) { + trace(); + } else { + foo(n - 1); + } +} + +template +void foo(int x, Args... args) { + foo(args...); +} + +void function_two(int, float) { + foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); +} + +CTRACE_FORCE_INLINE +void function_one(int) { + function_two(0, 0); +} + +int main() { + test_linker(); + function_one(0); +} From ac89001bad1b7c7e1f841ad10bf63addd2edf5dd Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Tue, 23 Jan 2024 01:09:46 -0600 Subject: [PATCH 16/63] Update copyright --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 152a555..299bf1f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2023 Jeremy Rifkin +Copyright (c) 2023-2024 Jeremy Rifkin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, From ea30c99f35565945a2a83cbc7c4870f96314e77c Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Tue, 23 Jan 2024 01:10:00 -0600 Subject: [PATCH 17/63] Fix formatting of a table --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 17d81bd..800469a 100644 --- a/README.md +++ b/README.md @@ -620,12 +620,12 @@ 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` | +| 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` | +| msvc | Windows | `cpptrace.lib dbghelp.lib` | +| clang | Windows | `-lcpptrace -ldbghelp` | Dependencies may differ if different back-ends are manually selected. From 79931c88238eb188476f29a2e18585ba9c84538f Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Mon, 29 Jan 2024 22:12:59 -0600 Subject: [PATCH 18/63] Parsing of mach-o symbol tables, generation of debug maps, and resolution through object files (#82) --- .github/workflows/build.yml | 8 +- .github/workflows/test.yml | 19 +- CMakeLists.txt | 20 +- README.md | 10 +- ci/test-all-configs.py | 46 ++-- src/binary/mach-o.hpp | 279 +++++++++++++++++++++- src/symbols/symbols_with_libdwarf.cpp | 323 +++++++++++++++++++++----- src/utils/utils.hpp | 23 ++ 8 files changed, 622 insertions(+), 106 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 04031bc..399e889 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,8 +23,8 @@ jobs: mkdir libdwarf cd libdwarf git init - git remote add origin https://github.com/davea42/libdwarf-code.git - git fetch --depth 1 origin 6216e185863f41d6f19ab850caabfff7326020d7 + git remote add origin https://github.com/flagarde/libdwarf-code.git + git fetch --depth 1 origin d1a559b7af0840194dfa51f7e3013e0f80614032 git checkout FETCH_HEAD mkdir build cd build @@ -52,8 +52,8 @@ jobs: mkdir libdwarf cd libdwarf git init - git remote add origin https://github.com/davea42/libdwarf-code.git - git fetch --depth 1 origin 6216e185863f41d6f19ab850caabfff7326020d7 + git remote add origin https://github.com/flagarde/libdwarf-code.git + git fetch --depth 1 origin d1a559b7af0840194dfa51f7e3013e0f80614032 git checkout FETCH_HEAD mkdir build cd build diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 624ec53..c7cb37d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,8 +26,8 @@ jobs: mkdir libdwarf cd libdwarf git init - git remote add origin https://github.com/davea42/libdwarf-code.git - git fetch --depth 1 origin 6216e185863f41d6f19ab850caabfff7326020d7 + git remote add origin https://github.com/flagarde/libdwarf-code.git + git fetch --depth 1 origin d1a559b7af0840194dfa51f7e3013e0f80614032 git checkout FETCH_HEAD mkdir build cd build @@ -53,8 +53,8 @@ jobs: mkdir libdwarf cd libdwarf git init - git remote add origin https://github.com/davea42/libdwarf-code.git - git fetch --depth 1 origin 6216e185863f41d6f19ab850caabfff7326020d7 + git remote add origin https://github.com/flagarde/libdwarf-code.git + git fetch --depth 1 origin d1a559b7af0840194dfa51f7e3013e0f80614032 git checkout FETCH_HEAD mkdir build cd build @@ -68,6 +68,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: diff --git a/CMakeLists.txt b/CMakeLists.txt index c42206f..eaa8341 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,12 +27,12 @@ if(PROJECT_IS_TOP_LEVEL) if(CMAKE_GENERATOR STREQUAL "Ninja") if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color=always") - elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcolor-diagnostics") endif() if("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fdiagnostics-color=always") - elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") + elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fcolor-diagnostics") endif() endif() @@ -358,10 +358,13 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) # GIT_REPOSITORY https://github.com/davea42/libdwarf-code.git # GIT_TAG 6216e185863f41d6f19ab850caabfff7326020d7 # v0.8.0 # GIT_TAG 8b0bd09d8c77d45a68cb1bb00a54186a92b683d9 # v0.9.0 + # GIT_TAG c0cfba34ec80996426b5be2523f6447a2c9b7b39 # v0.9.0 + mach-o 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 # v0.8.0 + # GIT_REPOSITORY https://github.com/jeremy-rifkin/libdwarf-lite.git + # GIT_TAG c78e984f3abbd20f6e01d6f51819e826b1691f65 # v0.8.0 # GIT_TAG 71090c680b4c943448ba87a0f1f864f174e4edda # v0.9.0 + GIT_REPOSITORY https://github.com/flagarde/libdwarf-code.git + GIT_TAG d1a559b7af0840194dfa51f7e3013e0f80614032 GIT_SHALLOW 1 ) FetchContent_MakeAvailable(libdwarf) @@ -533,20 +536,13 @@ macro(add_test_dependencies exec_name) if(CPPTRACE_BUILD_TEST_RDYNAMIC) set_property(TARGET ${exec_name} PROPERTY ENABLE_EXPORTS ON) endif() - if(APPLE) # TODO: Temporary - add_custom_command( - TARGET ${exec_name} - POST_BUILD - COMMAND dsymutil $ - ) - endif() endmacro() if(CPPTRACE_BUILD_TESTING) add_executable(test test/test.cpp) add_executable(demo test/demo.cpp) add_executable(c_demo test/ctrace_demo.cpp) - + add_test_dependencies(test) add_test_dependencies(demo) add_test_dependencies(c_demo) diff --git a/README.md b/README.md index 800469a..d454897 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,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. @@ -140,9 +140,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 @@ -506,7 +503,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. @@ -719,7 +716,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 @@ -872,7 +869,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). diff --git a/ci/test-all-configs.py b/ci/test-all-configs.py index abdd7c0..76a5bca 100644 --- a/ci/test-all-configs.py +++ b/ci/test-all-configs.py @@ -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,31 +72,41 @@ 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 - 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}\"") - errored = True - if abs(int(output_line) - int(expected_line)) > max_line_diff: - print(f"Error: File line mismatch on line {i + 1}, found {output_line} expected {expected_line}") - errored = True - if output_symbol != expected_symbol: - print(f"Error: File symbol mismatch on line {i + 1}, found \"{output_symbol}\" expected \"{expected_symbol}\"") - errored = True - if expected_symbol == "main" or expected_symbol == "main()": - break + 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}\"") + errored = True + if abs(int(output_line) - int(expected_line)) > max_line_diff: + print(f"Error: File line mismatch on line {i + 1}, found {output_line} expected {expected_line}") + errored = True + if output_symbol != expected_symbol: + print(f"Error: File symbol mismatch on line {i + 1}, found \"{output_symbol}\" expected \"{expected_symbol}\"") + 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 @@ -126,7 +136,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:") diff --git a/src/binary/mach-o.hpp b/src/binary/mach-o.hpp index 23153f5..02fd776 100644 --- a/src/binary/mach-o.hpp +++ b/src/binary/mach-o.hpp @@ -12,7 +12,13 @@ #include #include +#include #include +#include +#include + +#include +#include #include #include @@ -23,7 +29,7 @@ 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,36 +43,57 @@ 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(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 @@ -89,12 +116,28 @@ namespace detail { 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 std::size_t load_base = 0; std::size_t fat_index = std::numeric_limits::max(); std::vector load_commands; + struct symtab_info_data { + symtab_command symtab; + std::unique_ptr 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; + public: mach_o(const std::string& object_path) : object_path(object_path) { file = std::fopen(object_path.c_str(), "rb"); @@ -117,7 +160,7 @@ namespace detail { ~mach_o() { if(file) { - fclose(file); + std::fclose(file); } } @@ -163,12 +206,195 @@ namespace detail { } } + optional& 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& 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>; + + // 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 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_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 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< void load_mach( bool allow_arch_mismatch ) { static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument"); + bits = Bits; using Mach_Header = typename std::conditional::type; std::size_t header_size = sizeof(Mach_Header); Mach_Header header = load_bytes(file, load_base); @@ -268,6 +494,41 @@ namespace detail { return common; } + symtab_command load_symbol_table_command(std::uint32_t offset) const { + symtab_command symtab = load_bytes(file, offset); + ASSERT(symtab.cmd == LC_SYMTAB); + if(should_swap()) { + swap_symtab_command(&symtab, NX_UnknownByteOrder); + } + return symtab; + } + + template + nlist_64 load_symtab_entry(std::uint32_t symbol_base, std::size_t index) const { + using Nlist = typename std::conditional::type; + uint32_t offset = load_base + symbol_base + index * sizeof(Nlist); + Nlist entry = load_bytes(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 load_string_table(std::uint32_t offset, std::uint32_t byte_count) const { + std::unique_ptr 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); } diff --git a/src/symbols/symbols_with_libdwarf.cpp b/src/symbols/symbols_with_libdwarf.cpp index a260cc4..5e8e9ad 100644 --- a/src/symbols/symbols_with_libdwarf.cpp +++ b/src/symbols/symbols_with_libdwarf.cpp @@ -20,6 +20,9 @@ #include #include +#include +#include + // 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 // https://github.com/davea42/libdwarf-addr2line @@ -74,7 +77,14 @@ namespace libdwarf { std::vector 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; @@ -91,6 +101,7 @@ namespace libdwarf { // Map from CU -> {srcfiles, count} std::unordered_map> 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 @@ -116,14 +127,25 @@ 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 = mach_o(object_path).get_fat_index(); @@ -132,7 +154,10 @@ namespace libdwarf { // 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 buffer(new char[CPPTRACE_MAX_PATH]); + std::unique_ptr buffer; + if(use_buffer) { + buffer = std::unique_ptr(new char[CPPTRACE_MAX_PATH]); + } auto ret = wrap( dwarf_init_path_a, object_path.c_str(), @@ -214,6 +239,7 @@ namespace libdwarf { 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& fn) { @@ -521,19 +547,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(); @@ -648,19 +669,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; @@ -824,19 +840,14 @@ namespace libdwarf { } 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(); @@ -850,8 +861,22 @@ 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::null(), + nullable::null(), + frame_info.object_path, + "", + false + }, + {} + }; + } stacktrace_frame frame = null_frame; frame.filename = frame_info.object_path; frame.address = frame_info.raw_address; @@ -873,55 +898,249 @@ 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::null(), + nullable::null(), + frame_info.object_path, + "", + false + }, + {} + }; + }; + }; + + #if IS_APPLE + struct target_object { + std::string object_path; + bool path_ok = true; + optional> symbols; + std::unique_ptr resolver; + + target_object(std::string object_path) : object_path(object_path) {} + + std::unique_ptr& 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(new null_resolver); + resolver = std::unique_ptr(new dwarf_resolver(object_path)); + } + return resolver; + } + + std::unordered_map& 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 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::null(), + nullable::null(), + frame_info.object_path, + symbol_name, + false + }, + {} + }; + } + } + }; + + struct debug_map_symbol_info { + uint64_t source_address; + uint64_t size; + std::string name; + nullable target_address; // T(-1) is used as a sentinel + std::size_t object_index; + }; + + class debug_map_resolver : public symbol_resolver { + std::vector target_objects; + std::vector 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::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::null(), + nullable::null(), + frame_info.object_path, + "", + false + }, + {} + }; + }; + }; + #endif + + std::unique_ptr 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(new debug_map_resolver(object_path)); + } + #endif + return std::unique_ptr(new dwarf_resolver(object_path)); + } + CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING std::vector resolve_frames(const std::vector& frames) { std::vector 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 resolver_map; + static std::unordered_map> 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 lock(mutex); for(const auto& object_entry : collate_frames(frames, trace)) { try { const auto& object_name = object_entry.first; - optional resolver_object = nullopt; - dwarf_resolver* resolver = nullptr; + std::unique_ptr 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(); - auto& frame = entry.second.get(); - frame = resolver->resolve_frame(dlframe); - } catch(...) { - if(!should_absorb_trace_exceptions()) { - throw; - } - } - } - } else { - // at least copy the addresses - for(const auto& entry : object_entry.second) { + for(const auto& entry : object_entry.second) { + try { const auto& dlframe = entry.first.get(); auto& frame = entry.second.get(); - frame.frame.address = dlframe.raw_address; + frame = resolver->resolve_frame(dlframe); + } catch(...) { + if(!should_absorb_trace_exceptions()) { + throw; + } } } - 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::null(), + nullable::null(), + dlframe.object_path, + "", + false + }, + {} + }; + } } } // flatten trace with inlines diff --git a/src/utils/utils.hpp b/src/utils/utils.hpp index ea0c53f..c51a2a2 100644 --- a/src/utils/utils.hpp +++ b/src/utils/utils.hpp @@ -1,6 +1,7 @@ #ifndef UTILS_HPP #define UTILS_HPP +#include #include #include #include @@ -72,6 +73,28 @@ namespace detail { return str; } + // first value in a sorted range such that *it <= value + template + 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 + 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) { From 58837d70695c3ad7e6051b9ec9aba7f6f1f38913 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Mon, 29 Jan 2024 22:18:15 -0600 Subject: [PATCH 19/63] Fix cpptrace-config-cmake issue --- cmake/in/cpptrace-config-cmake.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/in/cpptrace-config-cmake.in b/cmake/in/cpptrace-config-cmake.in index eea7703..91483c6 100644 --- a/cmake/in/cpptrace-config-cmake.in +++ b/cmake/in/cpptrace-config-cmake.in @@ -8,7 +8,7 @@ if(@CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF@) 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") From b8c66cd855bb1167df086cf71d6fe1be1e1bf74c Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Mon, 29 Jan 2024 22:29:35 -0600 Subject: [PATCH 20/63] Remove old comment --- src/symbols/symbols_with_libdwarf.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/symbols/symbols_with_libdwarf.cpp b/src/symbols/symbols_with_libdwarf.cpp index 5e8e9ad..ca423d6 100644 --- a/src/symbols/symbols_with_libdwarf.cpp +++ b/src/symbols/symbols_with_libdwarf.cpp @@ -1106,7 +1106,6 @@ namespace libdwarf { 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 for(const auto& entry : object_entry.second) { try { const auto& dlframe = entry.first.get(); From b1ff59b59c25e4951f72f498b2621779787b32a8 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Mon, 29 Jan 2024 23:17:59 -0600 Subject: [PATCH 21/63] Update ctrace demo to be a .c --- CMakeLists.txt | 2 +- test/ctrace_demo.c | 39 +++++++++++++++++++++++++++++ test/ctrace_demo.cpp | 58 -------------------------------------------- 3 files changed, 40 insertions(+), 59 deletions(-) create mode 100644 test/ctrace_demo.c delete mode 100644 test/ctrace_demo.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index eaa8341..127e288 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -541,7 +541,7 @@ endmacro() if(CPPTRACE_BUILD_TESTING) add_executable(test test/test.cpp) add_executable(demo test/demo.cpp) - add_executable(c_demo test/ctrace_demo.cpp) + add_executable(c_demo test/ctrace_demo.c) add_test_dependencies(test) add_test_dependencies(demo) diff --git a/test/ctrace_demo.c b/test/ctrace_demo.c new file mode 100644 index 0000000..b91ef1d --- /dev/null +++ b/test/ctrace_demo.c @@ -0,0 +1,39 @@ +#include +#include +#include +#include + +void trace() { + ctrace_raw_trace raw_trace = ctrace_generate_raw_trace(1, INT_MAX); + ctrace_object_trace obj_trace = ctrace_raw_trace_resolve_object_trace(&raw_trace); + ctrace_stacktrace trace = ctrace_object_trace_resolve(&obj_trace); + ctrace_stacktrace_print(&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); +} diff --git a/test/ctrace_demo.cpp b/test/ctrace_demo.cpp deleted file mode 100644 index 955de0a..0000000 --- a/test/ctrace_demo.cpp +++ /dev/null @@ -1,58 +0,0 @@ -#include -#include -#include -#include - -void test_linker() { - /* Owning String */ { - auto str = ctrace_generate_owning_string("Hello C!"); - std::printf("%s\n", str.data); - ctrace_free_owning_string(&str); - assert(str.data == nullptr); - } /* Trace */ { - ctrace_stacktrace trace = ctrace_generate_trace(0, INT_MAX); - ctrace_owning_string str = ctrace_stacktrace_to_string(&trace, 0); - ctrace_free_stacktrace(&trace); - assert(trace.count == 0); - std::printf("%s\n", str.data); - ctrace_free_owning_string(&str); - } -} - -void trace() { - ctrace_raw_trace raw_trace = ctrace_generate_raw_trace(1, INT_MAX); - ctrace_object_trace obj_trace = ctrace_raw_trace_resolve_object_trace(&raw_trace); - ctrace_stacktrace trace = ctrace_object_trace_resolve(&obj_trace); - ctrace_stacktrace_print(&trace, stdout, 1); - ctrace_free_stacktrace(&trace); - ctrace_free_object_trace(&obj_trace); - ctrace_free_raw_trace(&raw_trace); - assert(raw_trace.frames == nullptr && obj_trace.count == 0); -} - -void foo(int n) { - if(n == 0) { - trace(); - } else { - foo(n - 1); - } -} - -template -void foo(int x, Args... args) { - foo(args...); -} - -void function_two(int, float) { - foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); -} - -CTRACE_FORCE_INLINE -void function_one(int) { - function_two(0, 0); -} - -int main() { - test_linker(); - function_one(0); -} From 5fde4081dc74826d8043bfa7442be0b1ed0b68fe Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Mon, 29 Jan 2024 23:26:18 -0600 Subject: [PATCH 22/63] Replace the cmake generated export header --- CMakeLists.txt | 8 -------- include/cpptrace/cpptrace.hpp | 29 +++++++++++++++++++++++++++-- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 127e288..a4d9140 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -247,14 +247,6 @@ if(NOT CPPTRACE_BUILD_SHARED) target_compile_definitions(${target_name} PUBLIC CPPTRACE_STATIC_DEFINE) 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 -) - # ---- Library Properties ---- # Hide all symbols by default diff --git a/include/cpptrace/cpptrace.hpp b/include/cpptrace/cpptrace.hpp index 10f7af3..de84225 100644 --- a/include/cpptrace/cpptrace.hpp +++ b/include/cpptrace/cpptrace.hpp @@ -9,8 +9,33 @@ #include #include #include -// Generated by the build system. -#include + +#ifdef _WIN32 +#define CPPTRACE_EXPORT_ATTR __declspec(dllexport) +#define CPPTRACE_IMPORT_ATTR __declspec(dllimport) +#define CPPTRACE_NO_EXPORT_ATTR +#define CPPTRACE_DEPRECATED_ATTR __declspec(deprecated) +#else +#define CPPTRACE_EXPORT_ATTR __attribute__((visibility("default"))) +#define CPPTRACE_IMPORT_ATTR __attribute__((visibility("default"))) +#define CPPTRACE_NO_EXPORT_ATTR __attribute__((visibility("hidden"))) +#define CPPTRACE_DEPRECATE_ATTR __attribute__((__deprecated__)) +#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 From 6b87927acf9f167872d35dfc0b07ad07c3dba285 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Mon, 29 Jan 2024 23:31:40 -0600 Subject: [PATCH 23/63] No longer try to install build/include --- cmake/InstallRules.cmake | 1 - 1 file changed, 1 deletion(-) diff --git a/cmake/InstallRules.cmake b/cmake/InstallRules.cmake index 4fc7002..b35c4d7 100644 --- a/cmake/InstallRules.cmake +++ b/cmake/InstallRules.cmake @@ -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 From a65aed275239d698f479600ddd47feb898972946 Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Thu, 1 Feb 2024 00:14:48 -0600 Subject: [PATCH 24/63] Aquire zstd with fetchcontent for libdwarf 9 (#85) --- .github/workflows/build.yml | 62 ++++++++++++++++++++++--------- .github/workflows/test.yml | 62 ++++++++++++++++++++++--------- CMakeLists.txt | 55 ++++++++++++++++++++++++--- cmake/in/cpptrace-config-cmake.in | 1 + test/speedtest/CMakeLists.txt | 1 + 5 files changed, 142 insertions(+), 39 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 399e889..6805510 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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/flagarde/libdwarf-code.git - git fetch --depth 1 origin d1a559b7af0840194dfa51f7e3013e0f80614032 + git remote add origin https://github.com/jeremy-rifkin/libdwarf-code.git + git fetch --depth 1 origin b8cf3548112fd4cb68e8d4b89c6ce4348bc8745c git checkout FETCH_HEAD mkdir build cd build @@ -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/flagarde/libdwarf-code.git - git fetch --depth 1 origin d1a559b7af0840194dfa51f7e3013e0f80614032 + git remote add origin https://github.com/jeremy-rifkin/libdwarf-code.git + git fetch --depth 1 origin b8cf3548112fd4cb68e8d4b89c6ce4348bc8745c git checkout FETCH_HEAD mkdir build cd build @@ -79,24 +97,34 @@ jobs: pip3 install colorama - name: libdwarf run: | - cd .. - mkdir libdwarf - cd libdwarf - git init - git remote add origin https://github.com/davea42/libdwarf-code.git - git fetch --depth 1 origin 6216e185863f41d6f19ab850caabfff7326020d7 - git checkout FETCH_HEAD - mkdir build - cd build 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 b8cf3548112fd4cb68e8d4b89c6ce4348bc8745c + git checkout FETCH_HEAD + mkdir build + cd build 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 } - cd ../../cpptrace - name: build run: | python3 ci/build-in-all-configs.py --${{matrix.compiler}} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c7cb37d..afbb080 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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/flagarde/libdwarf-code.git - git fetch --depth 1 origin d1a559b7af0840194dfa51f7e3013e0f80614032 + git remote add origin https://github.com/jeremy-rifkin/libdwarf-code.git + git fetch --depth 1 origin b8cf3548112fd4cb68e8d4b89c6ce4348bc8745c git checkout FETCH_HEAD mkdir build cd build @@ -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/flagarde/libdwarf-code.git - git fetch --depth 1 origin d1a559b7af0840194dfa51f7e3013e0f80614032 + git remote add origin https://github.com/jeremy-rifkin/libdwarf-code.git + git fetch --depth 1 origin b8cf3548112fd4cb68e8d4b89c6ce4348bc8745c git checkout FETCH_HEAD mkdir build cd build @@ -95,24 +113,34 @@ jobs: pip3 install colorama - name: libdwarf run: | - cd .. - mkdir libdwarf - cd libdwarf - git init - git remote add origin https://github.com/davea42/libdwarf-code.git - git fetch --depth 1 origin 6216e185863f41d6f19ab850caabfff7326020d7 - git checkout FETCH_HEAD - mkdir build - cd build 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 b8cf3548112fd4cb68e8d4b89c6ce4348bc8745c + git checkout FETCH_HEAD + mkdir build + cd build 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 } - cd ../../cpptrace - name: build and test run: | python3 ci/test-all-configs.py --${{matrix.compiler}} diff --git a/CMakeLists.txt b/CMakeLists.txt index a4d9140..0bb00ab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -339,11 +339,43 @@ endif() if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) target_compile_definitions(${target_name} PUBLIC CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) if(CPPTRACE_USE_EXTERNAL_LIBDWARF) + find_package(zstd) + if(TARGET libzstd_static) + add_library(ZSTD::ZSTD ALIAS libzstd_static) + set(ZSTD_FOUND TRUE) + endif() find_package(libdwarf REQUIRED) 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() + + if(TARGET libzstd_static) + add_library(ZSTD::ZSTD ALIAS libzstd_static) + set(ZSTD_FOUND TRUE) + endif() + 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 @@ -355,11 +387,24 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) # GIT_REPOSITORY https://github.com/jeremy-rifkin/libdwarf-lite.git # GIT_TAG c78e984f3abbd20f6e01d6f51819e826b1691f65 # v0.8.0 # GIT_TAG 71090c680b4c943448ba87a0f1f864f174e4edda # v0.9.0 - GIT_REPOSITORY https://github.com/flagarde/libdwarf-code.git - GIT_TAG d1a559b7af0840194dfa51f7e3013e0f80614032 + GIT_REPOSITORY https://github.com/jeremy-rifkin/libdwarf-code.git + GIT_TAG b8cf3548112fd4cb68e8d4b89c6ce4348bc8745c 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) diff --git a/cmake/in/cpptrace-config-cmake.in b/cmake/in/cpptrace-config-cmake.in index 91483c6..a196307 100644 --- a/cmake/in/cpptrace-config-cmake.in +++ b/cmake/in/cpptrace-config-cmake.in @@ -4,6 +4,7 @@ # Dependencies if(@CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF@) include(CMakeFindDependencyMacro) + find_dependency(zstd REQUIRED) find_dependency(libdwarf REQUIRED) endif() diff --git a/test/speedtest/CMakeLists.txt b/test/speedtest/CMakeLists.txt index 6cfd3ee..a6eb38d 100644 --- a/test/speedtest/CMakeLists.txt +++ b/test/speedtest/CMakeLists.txt @@ -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) From ab9a832a30051afed024c7fc188c5e8f2e129246 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 4 Feb 2024 00:00:12 -0600 Subject: [PATCH 25/63] Try to get some updated libdwarf/zstd handling --- .github/workflows/build.yml | 6 +++--- .github/workflows/test.yml | 6 +++--- CMakeLists.txt | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6805510..77140c5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,7 +33,7 @@ jobs: cd libdwarf git init git remote add origin https://github.com/jeremy-rifkin/libdwarf-code.git - git fetch --depth 1 origin b8cf3548112fd4cb68e8d4b89c6ce4348bc8745c + git fetch --depth 1 origin 2d8b0a15349ca634b212a11f2b2a97b9a85890a3 git checkout FETCH_HEAD mkdir build cd build @@ -71,7 +71,7 @@ jobs: cd libdwarf git init git remote add origin https://github.com/jeremy-rifkin/libdwarf-code.git - git fetch --depth 1 origin b8cf3548112fd4cb68e8d4b89c6ce4348bc8745c + git fetch --depth 1 origin 2d8b0a15349ca634b212a11f2b2a97b9a85890a3 git checkout FETCH_HEAD mkdir build cd build @@ -116,7 +116,7 @@ jobs: cd libdwarf git init git remote add origin https://github.com/jeremy-rifkin/libdwarf-code.git - git fetch --depth 1 origin b8cf3548112fd4cb68e8d4b89c6ce4348bc8745c + git fetch --depth 1 origin 2d8b0a15349ca634b212a11f2b2a97b9a85890a3 git checkout FETCH_HEAD mkdir build cd build diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index afbb080..f590624 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -36,7 +36,7 @@ jobs: cd libdwarf git init git remote add origin https://github.com/jeremy-rifkin/libdwarf-code.git - git fetch --depth 1 origin b8cf3548112fd4cb68e8d4b89c6ce4348bc8745c + git fetch --depth 1 origin 2d8b0a15349ca634b212a11f2b2a97b9a85890a3 git checkout FETCH_HEAD mkdir build cd build @@ -72,7 +72,7 @@ jobs: cd libdwarf git init git remote add origin https://github.com/jeremy-rifkin/libdwarf-code.git - git fetch --depth 1 origin b8cf3548112fd4cb68e8d4b89c6ce4348bc8745c + git fetch --depth 1 origin 2d8b0a15349ca634b212a11f2b2a97b9a85890a3 git checkout FETCH_HEAD mkdir build cd build @@ -132,7 +132,7 @@ jobs: cd libdwarf git init git remote add origin https://github.com/jeremy-rifkin/libdwarf-code.git - git fetch --depth 1 origin b8cf3548112fd4cb68e8d4b89c6ce4348bc8745c + git fetch --depth 1 origin 2d8b0a15349ca634b212a11f2b2a97b9a85890a3 git checkout FETCH_HEAD mkdir build cd build diff --git a/CMakeLists.txt b/CMakeLists.txt index 0bb00ab..6203e9a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -368,10 +368,10 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) add_subdirectory("${zstd_SOURCE_DIR}/build/cmake" "${zstd_BINARY_DIR}") endif() - if(TARGET libzstd_static) - add_library(ZSTD::ZSTD ALIAS libzstd_static) - set(ZSTD_FOUND TRUE) - endif() + # if(TARGET libzstd_static) + # add_library(ZSTD::ZSTD ALIAS libzstd_static) + # set(ZSTD_FOUND TRUE) + # endif() set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) # set(PIC_ALWAYS TRUE) @@ -388,7 +388,7 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) # GIT_TAG c78e984f3abbd20f6e01d6f51819e826b1691f65 # v0.8.0 # GIT_TAG 71090c680b4c943448ba87a0f1f864f174e4edda # v0.9.0 GIT_REPOSITORY https://github.com/jeremy-rifkin/libdwarf-code.git - GIT_TAG b8cf3548112fd4cb68e8d4b89c6ce4348bc8745c + GIT_TAG 2d8b0a15349ca634b212a11f2b2a97b9a85890a3 GIT_SHALLOW 1 ) # FetchContent_MakeAvailable(libdwarf) From c161293e89a0ef4428a298eea3ad125fffdc8ef5 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 4 Feb 2024 00:17:25 -0600 Subject: [PATCH 26/63] Update for zstd target handling again --- .github/workflows/build.yml | 6 +++--- .github/workflows/test.yml | 6 +++--- CMakeLists.txt | 11 +---------- 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 77140c5..cd32829 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,7 +33,7 @@ jobs: cd libdwarf git init git remote add origin https://github.com/jeremy-rifkin/libdwarf-code.git - git fetch --depth 1 origin 2d8b0a15349ca634b212a11f2b2a97b9a85890a3 + git fetch --depth 1 origin 308b55331b564d4fdbe3bc6856712270e5b2395b git checkout FETCH_HEAD mkdir build cd build @@ -71,7 +71,7 @@ jobs: cd libdwarf git init git remote add origin https://github.com/jeremy-rifkin/libdwarf-code.git - git fetch --depth 1 origin 2d8b0a15349ca634b212a11f2b2a97b9a85890a3 + git fetch --depth 1 origin 308b55331b564d4fdbe3bc6856712270e5b2395b git checkout FETCH_HEAD mkdir build cd build @@ -116,7 +116,7 @@ jobs: cd libdwarf git init git remote add origin https://github.com/jeremy-rifkin/libdwarf-code.git - git fetch --depth 1 origin 2d8b0a15349ca634b212a11f2b2a97b9a85890a3 + git fetch --depth 1 origin 308b55331b564d4fdbe3bc6856712270e5b2395b git checkout FETCH_HEAD mkdir build cd build diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f590624..553a20d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -36,7 +36,7 @@ jobs: cd libdwarf git init git remote add origin https://github.com/jeremy-rifkin/libdwarf-code.git - git fetch --depth 1 origin 2d8b0a15349ca634b212a11f2b2a97b9a85890a3 + git fetch --depth 1 origin 308b55331b564d4fdbe3bc6856712270e5b2395b git checkout FETCH_HEAD mkdir build cd build @@ -72,7 +72,7 @@ jobs: cd libdwarf git init git remote add origin https://github.com/jeremy-rifkin/libdwarf-code.git - git fetch --depth 1 origin 2d8b0a15349ca634b212a11f2b2a97b9a85890a3 + git fetch --depth 1 origin 308b55331b564d4fdbe3bc6856712270e5b2395b git checkout FETCH_HEAD mkdir build cd build @@ -132,7 +132,7 @@ jobs: cd libdwarf git init git remote add origin https://github.com/jeremy-rifkin/libdwarf-code.git - git fetch --depth 1 origin 2d8b0a15349ca634b212a11f2b2a97b9a85890a3 + git fetch --depth 1 origin 308b55331b564d4fdbe3bc6856712270e5b2395b git checkout FETCH_HEAD mkdir build cd build diff --git a/CMakeLists.txt b/CMakeLists.txt index 6203e9a..619f33b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -340,10 +340,6 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) target_compile_definitions(${target_name} PUBLIC CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) if(CPPTRACE_USE_EXTERNAL_LIBDWARF) find_package(zstd) - if(TARGET libzstd_static) - add_library(ZSTD::ZSTD ALIAS libzstd_static) - set(ZSTD_FOUND TRUE) - endif() find_package(libdwarf REQUIRED) else() include(FetchContent) @@ -368,11 +364,6 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) add_subdirectory("${zstd_SOURCE_DIR}/build/cmake" "${zstd_BINARY_DIR}") endif() - # if(TARGET libzstd_static) - # add_library(ZSTD::ZSTD ALIAS libzstd_static) - # set(ZSTD_FOUND TRUE) - # endif() - set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) # set(PIC_ALWAYS TRUE) # set(BUILD_DWARFDUMP FALSE) @@ -388,7 +379,7 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) # GIT_TAG c78e984f3abbd20f6e01d6f51819e826b1691f65 # v0.8.0 # GIT_TAG 71090c680b4c943448ba87a0f1f864f174e4edda # v0.9.0 GIT_REPOSITORY https://github.com/jeremy-rifkin/libdwarf-code.git - GIT_TAG 2d8b0a15349ca634b212a11f2b2a97b9a85890a3 + GIT_TAG 308b55331b564d4fdbe3bc6856712270e5b2395b GIT_SHALLOW 1 ) # FetchContent_MakeAvailable(libdwarf) From 589d87063a3fccb231359fabad3a595cc96a9958 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 4 Feb 2024 10:48:55 -0600 Subject: [PATCH 27/63] Small header refactor --- include/cpptrace/cpptrace.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/cpptrace/cpptrace.hpp b/include/cpptrace/cpptrace.hpp index de84225..c8caed9 100644 --- a/include/cpptrace/cpptrace.hpp +++ b/include/cpptrace/cpptrace.hpp @@ -238,7 +238,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; @@ -247,6 +246,9 @@ namespace cpptrace { CPPTRACE_EXPORT void register_terminate_handler(); + // configuration: + CPPTRACE_EXPORT void absorb_trace_exceptions(bool absorb); + enum class cache_mode { // Only minimal lookup tables prioritize_memory = 0, @@ -260,6 +262,7 @@ namespace cpptrace { 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 { From d7aac52f8b8d7a2e745e8f93f6d9c87c387266f8 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 4 Feb 2024 11:03:03 -0600 Subject: [PATCH 28/63] Add configuration to control resolution of inlined calls --- README.md | 28 ++++++++++++++++++--------- include/cpptrace/cpptrace.hpp | 1 + src/cpptrace.cpp | 17 ++++++++++++---- src/symbols/symbols_with_libdwarf.cpp | 4 +++- src/utils/common.hpp | 1 + 5 files changed, 37 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index d454897..e79a366 100644 --- a/README.md +++ b/README.md @@ -245,14 +245,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 @@ -261,7 +253,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; @@ -269,6 +260,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::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 enable_inlined_call_resolution(bool enable); enum class cache_mode { // Only minimal lookup tables diff --git a/include/cpptrace/cpptrace.hpp b/include/cpptrace/cpptrace.hpp index c8caed9..8cf1b5c 100644 --- a/include/cpptrace/cpptrace.hpp +++ b/include/cpptrace/cpptrace.hpp @@ -248,6 +248,7 @@ namespace cpptrace { // 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 diff --git a/src/cpptrace.cpp b/src/cpptrace.cpp index 91972f9..c632df3 100644 --- a/src/cpptrace.cpp +++ b/src/cpptrace.cpp @@ -411,25 +411,34 @@ namespace cpptrace { namespace detail { std::atomic_bool absorb_trace_exceptions(true); // NOSONAR + std::atomic_bool resolve_inlined_calls(true); // NOSONAR std::atomic cache_mode(cache_mode::prioritize_speed); // NOSONAR } - void absorb_trace_exceptions(bool absorb) { + void absorb_trace_exceptions(bool absorb) { detail::absorb_trace_exceptions = absorb; } + void enable_inlined_call_resolution(bool enable) { + detail::resolve_inlined_calls = enable; + } + namespace experimental { - void set_cache_mode(cache_mode mode) { + void set_cache_mode(cache_mode mode) { detail::cache_mode = mode; } } namespace detail { - bool should_absorb_trace_exceptions() { + bool should_absorb_trace_exceptions() { return absorb_trace_exceptions; } - enum cache_mode get_cache_mode() { + bool should_resolve_inlined_calls() { + return resolve_inlined_calls; + } + + enum cache_mode get_cache_mode() { return cache_mode; } diff --git a/src/symbols/symbols_with_libdwarf.cpp b/src/symbols/symbols_with_libdwarf.cpp index ca423d6..20d691b 100644 --- a/src/symbols/symbols_with_libdwarf.cpp +++ b/src/symbols/symbols_with_libdwarf.cpp @@ -406,7 +406,9 @@ namespace libdwarf { ) { ASSERT(die.get_tag() == DW_TAG_subprogram); const auto name = subprogram_symbol(die, dwversion); - get_inlines_info(cu_die, die, pc, dwversion, inlines); + if(detail::should_resolve_inlined_calls()) { + get_inlines_info(cu_die, die, pc, dwversion, inlines); + } return name; } diff --git a/src/utils/common.hpp b/src/utils/common.hpp index ccc7461..5f79f59 100644 --- a/src/utils/common.hpp +++ b/src/utils/common.hpp @@ -58,6 +58,7 @@ namespace detail { static const stacktrace_frame null_frame {0, nullable::null(), nullable::null(), "", "", false}; bool should_absorb_trace_exceptions(); + bool should_resolve_inlined_calls(); enum cache_mode get_cache_mode(); } } From 36d1dbf9e6b46e189c34103dbef9c5c4658de947 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 4 Feb 2024 11:08:18 -0600 Subject: [PATCH 29/63] Add ctrace export annotations --- include/ctrace/ctrace.h | 73 ++++++++++++++++++++++++++++------------- 1 file changed, 50 insertions(+), 23 deletions(-) diff --git a/include/ctrace/ctrace.h b/include/ctrace/ctrace.h index 3aa1467..2d866c1 100644 --- a/include/ctrace/ctrace.h +++ b/include/ctrace/ctrace.h @@ -5,6 +5,33 @@ #include #include +#ifdef _WIN32 +#define CPPTRACE_EXPORT_ATTR __declspec(dllexport) +#define CPPTRACE_IMPORT_ATTR __declspec(dllimport) +#define CPPTRACE_NO_EXPORT_ATTR +#define CPPTRACE_DEPRECATED_ATTR __declspec(deprecated) +#else +#define CPPTRACE_EXPORT_ATTR __attribute__((visibility("default"))) +#define CPPTRACE_IMPORT_ATTR __attribute__((visibility("default"))) +#define CPPTRACE_NO_EXPORT_ATTR __attribute__((visibility("hidden"))) +#define CPPTRACE_DEPRECATE_ATTR __attribute__((__deprecated__)) +#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 } @@ -97,44 +124,44 @@ CTRACE_BEGIN_DEFINITIONS } ctrace_cache_mode; // ctrace::string: - ctrace_owning_string ctrace_generate_owning_string(const char* raw_string); - void ctrace_free_owning_string(ctrace_owning_string* 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: - ctrace_raw_trace ctrace_generate_raw_trace(size_t skip, size_t max_depth); - ctrace_object_trace ctrace_generate_object_trace(size_t skip, size_t max_depth); - ctrace_stacktrace ctrace_generate_trace(size_t skip, size_t max_depth); + 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: - void ctrace_free_raw_trace(ctrace_raw_trace* trace); - void ctrace_free_object_trace(ctrace_object_trace* trace); - void ctrace_free_stacktrace(ctrace_stacktrace* trace); + 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: - ctrace_stacktrace ctrace_raw_trace_resolve(const ctrace_raw_trace* trace); - ctrace_object_trace ctrace_raw_trace_resolve_object_trace(const ctrace_raw_trace* trace); - ctrace_stacktrace ctrace_object_trace_resolve(const ctrace_object_trace* trace); + CPPTRACE_EXPORT ctrace_stacktrace ctrace_raw_trace_resolve(const ctrace_raw_trace* trace); + CPPTRACE_EXPORT ctrace_object_trace ctrace_raw_trace_resolve_object_trace(const ctrace_raw_trace* trace); + CPPTRACE_EXPORT ctrace_stacktrace ctrace_object_trace_resolve(const ctrace_object_trace* trace); // ctrace::safe: - 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); + 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: - ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color); - void ctrace_stacktrace_print(const ctrace_stacktrace* trace, FILE* to, ctrace_bool use_color); + CPPTRACE_EXPORT ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color); + CPPTRACE_EXPORT void ctrace_stacktrace_print(const ctrace_stacktrace* trace, FILE* to, ctrace_bool use_color); // utility::demangle: - ctrace_owning_string ctrace_demangle(const char* mangled); + CPPTRACE_EXPORT ctrace_owning_string ctrace_demangle(const char* mangled); // utility::io: - int ctrace_stdin_fileno(void); - int ctrace_stderr_fileno(void); - int ctrace_stdout_fileno(void); - ctrace_bool ctrace_isatty(int fd); - + 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); + // utility::cache: - void ctrace_set_cache_mode(ctrace_cache_mode mode); - ctrace_cache_mode ctrace_get_cache_mode(void); + CPPTRACE_EXPORT void ctrace_set_cache_mode(ctrace_cache_mode mode); + CPPTRACE_EXPORT ctrace_cache_mode ctrace_get_cache_mode(void); CTRACE_END_DEFINITIONS From 2f7f5107a53169ee5fffda539c500ed64c7f0c16 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 4 Feb 2024 11:10:24 -0600 Subject: [PATCH 30/63] Add ctrace interface for enable_inlined_call_resolution --- include/ctrace/ctrace.h | 8 ++++---- src/ctrace.cpp | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/include/ctrace/ctrace.h b/include/ctrace/ctrace.h index 2d866c1..a1c7c79 100644 --- a/include/ctrace/ctrace.h +++ b/include/ctrace/ctrace.h @@ -150,19 +150,19 @@ CTRACE_BEGIN_DEFINITIONS CPPTRACE_EXPORT ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color); CPPTRACE_EXPORT void ctrace_stacktrace_print(const ctrace_stacktrace* trace, FILE* to, ctrace_bool use_color); - // utility::demangle: + // ctrace::utility: CPPTRACE_EXPORT ctrace_owning_string ctrace_demangle(const char* mangled); - - // utility::io: 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); - // utility::cache: + // ctrace::config: CPPTRACE_EXPORT void ctrace_set_cache_mode(ctrace_cache_mode mode); CPPTRACE_EXPORT ctrace_cache_mode ctrace_get_cache_mode(void); + CPPTRACE_EXPORT void enable_inlined_call_resolution(ctrace_bool enable); + CTRACE_END_DEFINITIONS #endif diff --git a/src/ctrace.cpp b/src/ctrace.cpp index 7cc3a49..bdd5132 100644 --- a/src/ctrace.cpp +++ b/src/ctrace.cpp @@ -395,4 +395,8 @@ extern "C" { auto cache_mode = cpptrace::detail::get_cache_mode(); return static_cast(cache_mode); } + + void enable_inlined_call_resolution(ctrace_bool enable) { + cpptrace::enable_inlined_call_resolution(enable); + } } From ab2d440a00ea9d7b02464a6947445f6bc34afbe0 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 4 Feb 2024 13:40:12 -0600 Subject: [PATCH 31/63] Update cpptrace exceptions to defer trace generation to the callsite with a default argument --- README.md | 29 +++++----- include/cpptrace/cpptrace.hpp | 102 ++++++++++++++++++++-------------- src/cpptrace.cpp | 10 ++-- 3 files changed, 82 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index e79a366..ad32522 100644 --- a/README.md +++ b/README.md @@ -320,13 +320,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, more docs later + // lazy_trace_holder is basically a std::variant, 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; @@ -341,19 +341,22 @@ well as a number of traced exception classes resembling ``: ```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 { ... }; diff --git a/include/cpptrace/cpptrace.hpp b/include/cpptrace/cpptrace.hpp index 8cf1b5c..38aa7c6 100644 --- a/include/cpptrace/cpptrace.hpp +++ b/include/cpptrace/cpptrace.hpp @@ -289,6 +289,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 @@ -301,17 +304,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 @@ -322,87 +322,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; diff --git a/src/cpptrace.cpp b/src/cpptrace.cpp index c632df3..acd7fc3 100644 --- a/src/cpptrace.cpp +++ b/src/cpptrace.cpp @@ -459,6 +459,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); @@ -539,9 +544,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(); @@ -585,7 +587,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)); } } } From a3e3916daa1a56fec093020e22cb1f9e2293ed71 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 4 Feb 2024 13:49:10 -0600 Subject: [PATCH 32/63] Fix issue with trying to call detail::enable_virtual_terminal_processing_if_needed unconditionally on print --- src/cpptrace.cpp | 6 +++++- src/ctrace.cpp | 7 ++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/cpptrace.cpp b/src/cpptrace.cpp index acd7fc3..d69a2c2 100644 --- a/src/cpptrace.cpp +++ b/src/cpptrace.cpp @@ -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):")<count == 0 || !trace->frames) { ctrace::ffprintf(to, "\n"); From 8f9e8c5c1b41ea1c1e28639d0df2b8d0d04c46ae Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 4 Feb 2024 13:58:57 -0600 Subject: [PATCH 33/63] Some formatting consistency tweaks --- src/ctrace.cpp | 48 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/src/ctrace.cpp b/src/ctrace.cpp index 63e3d4a..1ee8905 100644 --- a/src/ctrace.cpp +++ b/src/ctrace.cpp @@ -125,7 +125,9 @@ CTRACE_FORMAT_EPILOGUE } static cpptrace::stacktrace cpp_convert(const ctrace_stacktrace* ptrace) { - if(!ptrace || !ptrace->frames) return { }; + if(!ptrace || !ptrace->frames) { + return { }; + } std::vector new_frames; new_frames.reserve(ptrace->count); for(std::size_t i = 0; i < ptrace->count; ++i) { @@ -152,7 +154,9 @@ extern "C" { } void ctrace_free_owning_string(ctrace_owning_string* string) { - if(!string) return; + if(!string) { + return; + } ctrace::free_owning_string(*string); string->data = nullptr; } @@ -200,7 +204,9 @@ extern "C" { // ctrace::freeing: void ctrace_free_raw_trace(ctrace_raw_trace* trace) { - if(!trace) return; + if(!trace) { + return; + } ctrace_frame_ptr* frames = trace->frames; delete[] frames; trace->frames = nullptr; @@ -208,7 +214,9 @@ extern "C" { } void ctrace_free_object_trace(ctrace_object_trace* trace) { - if(!trace || !trace->frames) return; + 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; @@ -221,7 +229,9 @@ extern "C" { } void ctrace_free_stacktrace(ctrace_stacktrace* trace) { - if(!trace || !trace->frames) return; + 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); @@ -235,7 +245,9 @@ extern "C" { // ctrace::resolve: ctrace_stacktrace ctrace_raw_trace_resolve(const ctrace_raw_trace* trace) { - if(!trace || !trace->frames) return { nullptr, 0 }; + if(!trace || !trace->frames) { + return { nullptr, 0 }; + } try { std::vector frames(trace->count, 0); std::copy(trace->frames, trace->frames + trace->count, frames.begin()); @@ -248,7 +260,9 @@ extern "C" { } ctrace_object_trace ctrace_raw_trace_resolve_object_trace(const ctrace_raw_trace* trace) { - if(!trace || !trace->frames) return { nullptr, 0 }; + if(!trace || !trace->frames) { + return { nullptr, 0 }; + } try { std::vector frames(trace->count, 0); std::copy(trace->frames, trace->frames + trace->count, frames.begin()); @@ -261,7 +275,9 @@ extern "C" { } ctrace_stacktrace ctrace_object_trace_resolve(const ctrace_object_trace* trace) { - if(!trace || !trace->frames) return { nullptr, 0 }; + if(!trace || !trace->frames) { + return { nullptr, 0 }; + } try { std::vector frames(trace->count, 0); std::transform( @@ -293,7 +309,9 @@ extern "C" { // 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(""); + if(!trace || !trace->frames) { + return ctrace::generate_owning_string(""); + } 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); @@ -305,7 +323,9 @@ extern "C" { (to == stdout && cpptrace::isatty(cpptrace::stdout_fileno)) || (to == stderr && cpptrace::isatty(cpptrace::stderr_fileno)) ) - ) cpptrace::detail::enable_virtual_terminal_processing_if_needed(); + ) { + 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, "\n"); @@ -366,7 +386,9 @@ extern "C" { // utility::demangle: ctrace_owning_string ctrace_demangle(const char* mangled) { - if(!mangled) return ctrace::generate_owning_string(""); + if(!mangled) { + return ctrace::generate_owning_string(""); + } std::string demangled = cpptrace::demangle(mangled); return ctrace::generate_owning_string(demangled); } @@ -391,7 +413,9 @@ extern "C" { // 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; + if(mode > unsigned(cache_max)) { + return; + } auto cache_mode = static_cast(mode); cpptrace::experimental::set_cache_mode(cache_mode); } From 46069760c66de562db7f67988a9c6a0c098324e2 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 4 Feb 2024 14:18:13 -0600 Subject: [PATCH 34/63] Fix a readme editing error, somehow I cropped a paragraph --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ad32522..d6eee71 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,9 @@ Some day C++23's `` 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 From eea0fcd11815f5ac2a0bd64310d126508a38f878 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 4 Feb 2024 14:20:04 -0600 Subject: [PATCH 35/63] Update table of contents --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d6eee71..6d92e0e 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,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) From 5e65ccecc0fa61efe9ab244d337d297d85a667e3 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 4 Feb 2024 14:35:49 -0600 Subject: [PATCH 36/63] Add external zstd option --- CMakeLists.txt | 10 +++++++--- cmake/OptionVariables.cmake | 1 + 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 619f33b..8096487 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -338,9 +338,9 @@ endif() if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) target_compile_definitions(${target_name} PUBLIC CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) - if(CPPTRACE_USE_EXTERNAL_LIBDWARF) + # First, dependencies: Zstd and zlib (currently relying on system zlib) + if(CPPTRACE_USE_EXTERNAL_ZSTD) find_package(zstd) - find_package(libdwarf REQUIRED) else() include(FetchContent) cmake_policy(SET CMP0074 NEW) @@ -363,7 +363,11 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) 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) + else() set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) # set(PIC_ALWAYS TRUE) # set(BUILD_DWARFDUMP FALSE) diff --git a/cmake/OptionVariables.cmake b/cmake/OptionVariables.cmake index 6a4a3c6..4e2ea6f 100644 --- a/cmake/OptionVariables.cmake +++ b/cmake/OptionVariables.cmake @@ -158,6 +158,7 @@ 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) From 5232bb04fd15cb28ba24d60193b085812f67b730 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 4 Feb 2024 14:42:05 -0600 Subject: [PATCH 37/63] Use CPPTRACE_USE_EXTERNAL_ZSTD in CI --- ci/build-in-all-configs.py | 4 ++++ ci/test-all-configs.py | 4 ++++ cmake/OptionVariables.cmake | 1 + 3 files changed, 9 insertions(+) diff --git a/ci/build-in-all-configs.py b/ci/build-in-all-configs.py index 913f4aa..7754bd4 100644 --- a/ci/build-in-all-configs.py +++ b/ci/build-in-all-configs.py @@ -48,6 +48,7 @@ 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", @@ -64,6 +65,7 @@ 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", @@ -100,6 +102,7 @@ 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", ] @@ -116,6 +119,7 @@ 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"] != "": diff --git a/ci/test-all-configs.py b/ci/test-all-configs.py index 76a5bca..1930bfb 100644 --- a/ci/test-all-configs.py +++ b/ci/test-all-configs.py @@ -165,6 +165,7 @@ 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", @@ -187,6 +188,7 @@ 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", @@ -214,6 +216,7 @@ 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", @@ -233,6 +236,7 @@ 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']}" diff --git a/cmake/OptionVariables.cmake b/cmake/OptionVariables.cmake index 4e2ea6f..022bfd8 100644 --- a/cmake/OptionVariables.cmake +++ b/cmake/OptionVariables.cmake @@ -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 | # | ... | | | # --------------------------------------------------------------------------------------------------- From f13e2a0d7b28f4cb3a62c211d6bcdd85a949b262 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Mon, 5 Feb 2024 00:03:22 -0600 Subject: [PATCH 38/63] Test cmake integration with shared too --- .github/workflows/cmake-integration.yml | 60 ++++++++++++++++++++----- 1 file changed, 50 insertions(+), 10 deletions(-) diff --git a/.github/workflows/cmake-integration.yml b/.github/workflows/cmake-integration.yml index 12121a8..69771f6 100644 --- a/.github/workflows/cmake-integration.yml +++ b/.github/workflows/cmake-integration.yml @@ -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 + 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 + 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 . @@ -89,6 +109,10 @@ jobs: ./main test-macos-add_subdirectory: runs-on: macos-13 + 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,15 @@ 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" -DCMAKE_BUILD_TYPE=g++ "-GUnix Makefiles" -DBUILD_SHARED_LIBS=${{matrix.shared}} make .\main.exe 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 +160,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 -DCMAKE_BUILD_TYPE=g++ "-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 +181,15 @@ 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-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 +201,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 From 7c9b3ed635bc0798c31812a096799e78b5ac48d2 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Mon, 5 Feb 2024 23:34:09 -0600 Subject: [PATCH 39/63] Try to test mingw findpackage integration --- .github/workflows/cmake-integration.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/.github/workflows/cmake-integration.yml b/.github/workflows/cmake-integration.yml index 69771f6..8643eb8 100644 --- a/.github/workflows/cmake-integration.yml +++ b/.github/workflows/cmake-integration.yml @@ -145,6 +145,29 @@ jobs: cmake .. -DCMAKE_BUILD_TYPE=Debug -DCPPTRACE_TAG="$tag" -DCMAKE_BUILD_TYPE=g++ "-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}} -DCMAKE_INSTALL_PREFIX=C:/foo + sudo make -j install + cd ../.. + cp -rv cpptrace/test/findpackage-integration . + mkdir findpackage-integration/build + cd findpackage-integration/build + cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_PREFIX_PATH=C:/foo + make + ./main test-mingw-add_subdirectory: runs-on: windows-2022 strategy: From 74ed63f3fffe63515b072424a7328c1a079278e4 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Mon, 5 Feb 2024 23:37:33 -0600 Subject: [PATCH 40/63] Fix some funny business --- .github/workflows/cmake-integration.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cmake-integration.yml b/.github/workflows/cmake-integration.yml index 8643eb8..9fa1d41 100644 --- a/.github/workflows/cmake-integration.yml +++ b/.github/workflows/cmake-integration.yml @@ -142,7 +142,7 @@ 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" -DBUILD_SHARED_LIBS=${{matrix.shared}} + cmake .. -DCMAKE_BUILD_TYPE=Debug -DCPPTRACE_TAG="$tag" "-GUnix Makefiles" -DBUILD_SHARED_LIBS=${{matrix.shared}} make .\main.exe test-mingw-findpackage: @@ -159,8 +159,8 @@ jobs: echo $tag mkdir build cd build - cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=${{matrix.shared}} -DCMAKE_INSTALL_PREFIX=C:/foo - sudo make -j install + cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=${{matrix.shared}} "-GUnix Makefiles" -DCMAKE_INSTALL_PREFIX=C:/foo + make -j install cd ../.. cp -rv cpptrace/test/findpackage-integration . mkdir findpackage-integration/build @@ -183,7 +183,7 @@ 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" -DBUILD_SHARED_LIBS=${{matrix.shared}} + cmake .. -DCMAKE_BUILD_TYPE=Debug "-GUnix Makefiles" -DBUILD_SHARED_LIBS=${{matrix.shared}} make .\main.exe test-windows-fetchcontent: From 6525b125ef6b2831424b23015dec80d24a6e6bc5 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Mon, 5 Feb 2024 23:40:08 -0600 Subject: [PATCH 41/63] Two small fixes --- .github/workflows/cmake-integration.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cmake-integration.yml b/.github/workflows/cmake-integration.yml index 9fa1d41..d67a272 100644 --- a/.github/workflows/cmake-integration.yml +++ b/.github/workflows/cmake-integration.yml @@ -162,10 +162,10 @@ jobs: cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=${{matrix.shared}} "-GUnix Makefiles" -DCMAKE_INSTALL_PREFIX=C:/foo make -j install cd ../.. - cp -rv cpptrace/test/findpackage-integration . + mv cpptrace/test/findpackage-integration . mkdir findpackage-integration/build cd findpackage-integration/build - cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_PREFIX_PATH=C:/foo + cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_PREFIX_PATH=C:/foo "-GUnix Makefiles" make ./main test-mingw-add_subdirectory: From f972c4342063e71c7163a14abb874510b5a97937 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Mon, 5 Feb 2024 23:47:07 -0600 Subject: [PATCH 42/63] Copy the dll --- .github/workflows/cmake-integration.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/cmake-integration.yml b/.github/workflows/cmake-integration.yml index d67a272..a5d0bfb 100644 --- a/.github/workflows/cmake-integration.yml +++ b/.github/workflows/cmake-integration.yml @@ -167,6 +167,9 @@ jobs: 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 From 8b8bd1b5df523f9b61e1fdd914c8fe4b8f30c416 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Mon, 5 Feb 2024 23:55:23 -0600 Subject: [PATCH 43/63] Test findpackage for msvc too --- .github/workflows/cmake-integration.yml | 29 +++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/.github/workflows/cmake-integration.yml b/.github/workflows/cmake-integration.yml index a5d0bfb..dc6a025 100644 --- a/.github/workflows/cmake-integration.yml +++ b/.github/workflows/cmake-integration.yml @@ -210,6 +210,35 @@ jobs: 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 . + } + ./main test-windows-add_subdirectory: runs-on: windows-2022 strategy: From ead847ad705090ca2475bc0a192b0faac825be35 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Tue, 6 Feb 2024 09:08:22 -0600 Subject: [PATCH 44/63] Fix main invocation --- .github/workflows/cmake-integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake-integration.yml b/.github/workflows/cmake-integration.yml index dc6a025..8274c73 100644 --- a/.github/workflows/cmake-integration.yml +++ b/.github/workflows/cmake-integration.yml @@ -238,7 +238,7 @@ jobs: if("${{matrix.shared}}" -eq "On") { cp C:/foo/bin/cpptrace.dll . } - ./main + .\Debug\main.exe test-windows-add_subdirectory: runs-on: windows-2022 strategy: From 78dd053f212fea30e3760f695e168fdf43ef86cf Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Tue, 6 Feb 2024 23:10:02 -0600 Subject: [PATCH 45/63] Light ctrace refactoring --- include/ctrace/ctrace.h | 77 +++++++++++++++++++---------------------- src/ctrace.cpp | 11 ++---- test/ctrace_demo.c | 4 +-- 3 files changed, 41 insertions(+), 51 deletions(-) diff --git a/include/ctrace/ctrace.h b/include/ctrace/ctrace.h index a1c7c79..469fd78 100644 --- a/include/ctrace/ctrace.h +++ b/include/ctrace/ctrace.h @@ -54,37 +54,35 @@ #define CTRACE_FORCE_INLINE inline #endif -// See `CPPTRACE_PATH_MAX` for more info. +/* See `CPPTRACE_PATH_MAX` for more info. */ #define CTRACE_PATH_MAX 4096 -// TODO: Add exports - CTRACE_BEGIN_DEFINITIONS - typedef struct raw_trace ctrace_raw_trace; - typedef struct object_trace ctrace_object_trace; - typedef struct stacktrace ctrace_stacktrace; - // Represents a boolean value, ensures a consistent ABI. + 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`. + /* A type that can represent a pointer, alias for `uintptr_t`. */ typedef uintptr_t ctrace_frame_ptr; - typedef struct object_frame ctrace_object_frame; - typedef struct stacktrace_frame ctrace_stacktrace_frame; - typedef struct safe_object_frame ctrace_safe_object_frame; + 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 + /* Type-safe null-terminated string wrapper */ typedef struct { const char* data; } ctrace_owning_string; - struct object_frame { + struct ctrace_object_frame { ctrace_frame_ptr raw_address; ctrace_frame_ptr obj_address; const char* obj_path; - // const char* symbol; }; - struct stacktrace_frame { + struct ctrace_stacktrace_frame { ctrace_frame_ptr address; uint32_t line; uint32_t column; @@ -93,74 +91,71 @@ CTRACE_BEGIN_DEFINITIONS ctrace_bool is_inline; }; - struct 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]; }; - struct raw_trace { + struct ctrace_raw_trace { ctrace_frame_ptr* frames; size_t count; }; - struct object_trace { + struct ctrace_object_trace { ctrace_object_frame* frames; size_t count; }; - struct stacktrace { + struct ctrace_stacktrace { ctrace_stacktrace_frame* frames; size_t count; }; - 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; - - // ctrace::string: + /* 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: + /* 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: + /* 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_raw_trace_resolve(const ctrace_raw_trace* trace); - CPPTRACE_EXPORT ctrace_object_trace ctrace_raw_trace_resolve_object_trace(const ctrace_raw_trace* trace); - CPPTRACE_EXPORT ctrace_stacktrace ctrace_object_trace_resolve(const ctrace_object_trace* 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: + /* 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: + /* ctrace::io: */ CPPTRACE_EXPORT ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color); CPPTRACE_EXPORT void ctrace_stacktrace_print(const ctrace_stacktrace* trace, FILE* to, ctrace_bool use_color); - // ctrace::utility: + /* 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: + /* 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 ctrace_cache_mode ctrace_get_cache_mode(void); - CPPTRACE_EXPORT void enable_inlined_call_resolution(ctrace_bool enable); CTRACE_END_DEFINITIONS diff --git a/src/ctrace.cpp b/src/ctrace.cpp index 1ee8905..4246810 100644 --- a/src/ctrace.cpp +++ b/src/ctrace.cpp @@ -244,7 +244,7 @@ extern "C" { } // ctrace::resolve: - ctrace_stacktrace ctrace_raw_trace_resolve(const ctrace_raw_trace* trace) { + ctrace_stacktrace ctrace_resolve_raw_trace(const ctrace_raw_trace* trace) { if(!trace || !trace->frames) { return { nullptr, 0 }; } @@ -259,7 +259,7 @@ extern "C" { } } - ctrace_object_trace ctrace_raw_trace_resolve_object_trace(const ctrace_raw_trace* trace) { + ctrace_object_trace ctrace_resolve_raw_trace_to_object_trace(const ctrace_raw_trace* trace) { if(!trace || !trace->frames) { return { nullptr, 0 }; } @@ -274,7 +274,7 @@ extern "C" { } } - ctrace_stacktrace ctrace_object_trace_resolve(const ctrace_object_trace* trace) { + ctrace_stacktrace ctrace_resolve_object_trace(const ctrace_object_trace* trace) { if(!trace || !trace->frames) { return { nullptr, 0 }; } @@ -420,11 +420,6 @@ extern "C" { cpptrace::experimental::set_cache_mode(cache_mode); } - ctrace_cache_mode ctrace_get_cache_mode(void) { - auto cache_mode = cpptrace::detail::get_cache_mode(); - return static_cast(cache_mode); - } - void enable_inlined_call_resolution(ctrace_bool enable) { cpptrace::enable_inlined_call_resolution(enable); } diff --git a/test/ctrace_demo.c b/test/ctrace_demo.c index b91ef1d..1bebf4e 100644 --- a/test/ctrace_demo.c +++ b/test/ctrace_demo.c @@ -5,8 +5,8 @@ void trace() { ctrace_raw_trace raw_trace = ctrace_generate_raw_trace(1, INT_MAX); - ctrace_object_trace obj_trace = ctrace_raw_trace_resolve_object_trace(&raw_trace); - ctrace_stacktrace trace = ctrace_object_trace_resolve(&obj_trace); + ctrace_object_trace obj_trace = ctrace_resolve_raw_trace_to_object_trace(&raw_trace); + ctrace_stacktrace trace = ctrace_resolve_object_trace(&obj_trace); ctrace_stacktrace_print(&trace, stdout, 1); ctrace_free_stacktrace(&trace); ctrace_free_object_trace(&obj_trace); From 68f919f292ff4b4db5114f44465ed3f58c081017 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Wed, 7 Feb 2024 00:11:48 -0600 Subject: [PATCH 46/63] Add ctrace documentation --- README.md | 4 +- docs/c-api.md | 167 ++++++++++++++++++ .../signal-safe-tracing.md | 0 include/ctrace/ctrace.h | 4 +- src/ctrace.cpp | 4 +- test/ctrace_demo.c | 2 +- 6 files changed, 175 insertions(+), 6 deletions(-) create mode 100644 docs/c-api.md rename signal-safe-tracing.md => docs/signal-safe-tracing.md (100%) diff --git a/README.md b/README.md index 6d92e0e..4e33883 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ Cpptrace is a simple, portable, and self-contained C++ stacktrace library supporting C++11 and greater on Linux, macOS, and Windows including MinGW and Cygwin environments. The goal: Make stack traces simple for once. +Cpptrace also has a C API, docs [here](docs/c-api.md). + ## Table of Contents - [30-Second Overview](#30-second-overview) @@ -220,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. diff --git a/docs/c-api.md b/docs/c-api.md new file mode 100644 index 0000000..2d8086d --- /dev/null +++ b/docs/c-api.md @@ -0,0 +1,167 @@ +# ctrace + +Cpptrace provides a C API under the name ctrace, documented below. + +## Table of Contents + +- [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 +``` + +### 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); +``` diff --git a/signal-safe-tracing.md b/docs/signal-safe-tracing.md similarity index 100% rename from signal-safe-tracing.md rename to docs/signal-safe-tracing.md diff --git a/include/ctrace/ctrace.h b/include/ctrace/ctrace.h index 469fd78..186dfb9 100644 --- a/include/ctrace/ctrace.h +++ b/include/ctrace/ctrace.h @@ -137,7 +137,7 @@ CTRACE_BEGIN_DEFINITIONS /* ctrace::io: */ CPPTRACE_EXPORT ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color); - CPPTRACE_EXPORT void ctrace_stacktrace_print(const ctrace_stacktrace* trace, FILE* to, 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); @@ -156,7 +156,7 @@ CTRACE_BEGIN_DEFINITIONS ctrace_prioritize_speed = 2 } ctrace_cache_mode; CPPTRACE_EXPORT void ctrace_set_cache_mode(ctrace_cache_mode mode); - CPPTRACE_EXPORT void enable_inlined_call_resolution(ctrace_bool enable); + CPPTRACE_EXPORT void ctrace_enable_inlined_call_resolution(ctrace_bool enable); CTRACE_END_DEFINITIONS diff --git a/src/ctrace.cpp b/src/ctrace.cpp index 4246810..ecf775e 100644 --- a/src/ctrace.cpp +++ b/src/ctrace.cpp @@ -317,7 +317,7 @@ extern "C" { return ctrace::generate_owning_string(trace_string); } - void ctrace_stacktrace_print(const ctrace_stacktrace* trace, FILE* to, ctrace_bool use_color) { + void ctrace_print_stacktrace(const ctrace_stacktrace* trace, FILE* to, ctrace_bool use_color) { if( use_color && ( (to == stdout && cpptrace::isatty(cpptrace::stdout_fileno)) || @@ -420,7 +420,7 @@ extern "C" { cpptrace::experimental::set_cache_mode(cache_mode); } - void enable_inlined_call_resolution(ctrace_bool enable) { + void ctrace_enable_inlined_call_resolution(ctrace_bool enable) { cpptrace::enable_inlined_call_resolution(enable); } } diff --git a/test/ctrace_demo.c b/test/ctrace_demo.c index 1bebf4e..4dc85df 100644 --- a/test/ctrace_demo.c +++ b/test/ctrace_demo.c @@ -7,7 +7,7 @@ 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_stacktrace_print(&trace, stdout, 1); + ctrace_print_stacktrace(&trace, stdout, 1); ctrace_free_stacktrace(&trace); ctrace_free_object_trace(&obj_trace); ctrace_free_raw_trace(&raw_trace); From 683c046f878b68e7c2714335cd669c1f5a9759e5 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Wed, 7 Feb 2024 21:26:03 -0600 Subject: [PATCH 47/63] Forgot to commit readme update --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4e33883..6adfbe7 100644 --- a/README.md +++ b/README.md @@ -273,7 +273,7 @@ namespace cpptrace { `cpptrace::absorb_trace_exceptions`: Configure whether the library silently absorbs internal exceptions and continues. Default is true. -`cpptrace::enable_inlined_call_resolution`: Configure whether the library will attempt to resolve inlined call +`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 @@ -283,7 +283,7 @@ performed. ```cpp namespace cpptrace { void absorb_trace_exceptions(bool absorb); - void enable_inlined_call_resolution(bool enable); + void ctrace_enable_inlined_call_resolution(bool enable); enum class cache_mode { // Only minimal lookup tables From adefa5c234676055149e7d45188f9973b3c28f14 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Wed, 7 Feb 2024 21:34:41 -0600 Subject: [PATCH 48/63] Bump libdwarf hashes and use the lightweight mirror --- .github/workflows/build.yml | 12 ++++++------ .github/workflows/test.yml | 12 ++++++------ CMakeLists.txt | 9 +++++---- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cd32829..8125dc2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,8 +32,8 @@ jobs: mkdir libdwarf cd libdwarf git init - git remote add origin https://github.com/jeremy-rifkin/libdwarf-code.git - git fetch --depth 1 origin 308b55331b564d4fdbe3bc6856712270e5b2395b + 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 @@ -70,8 +70,8 @@ jobs: mkdir libdwarf cd libdwarf git init - git remote add origin https://github.com/jeremy-rifkin/libdwarf-code.git - git fetch --depth 1 origin 308b55331b564d4fdbe3bc6856712270e5b2395b + 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 @@ -115,8 +115,8 @@ jobs: mkdir libdwarf cd libdwarf git init - git remote add origin https://github.com/jeremy-rifkin/libdwarf-code.git - git fetch --depth 1 origin 308b55331b564d4fdbe3bc6856712270e5b2395b + 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 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 553a20d..07dcdba 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -35,8 +35,8 @@ jobs: mkdir libdwarf cd libdwarf git init - git remote add origin https://github.com/jeremy-rifkin/libdwarf-code.git - git fetch --depth 1 origin 308b55331b564d4fdbe3bc6856712270e5b2395b + 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 @@ -71,8 +71,8 @@ jobs: mkdir libdwarf cd libdwarf git init - git remote add origin https://github.com/jeremy-rifkin/libdwarf-code.git - git fetch --depth 1 origin 308b55331b564d4fdbe3bc6856712270e5b2395b + 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 @@ -131,8 +131,8 @@ jobs: mkdir libdwarf cd libdwarf git init - git remote add origin https://github.com/jeremy-rifkin/libdwarf-code.git - git fetch --depth 1 origin 308b55331b564d4fdbe3bc6856712270e5b2395b + 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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 8096487..701010e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -377,13 +377,14 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) # GIT_REPOSITORY https://github.com/davea42/libdwarf-code.git # GIT_TAG 6216e185863f41d6f19ab850caabfff7326020d7 # v0.8.0 # GIT_TAG 8b0bd09d8c77d45a68cb1bb00a54186a92b683d9 # v0.9.0 - # GIT_TAG c0cfba34ec80996426b5be2523f6447a2c9b7b39 # v0.9.0 + mach-o changes + # 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_REPOSITORY https://github.com/jeremy-rifkin/libdwarf-lite.git # GIT_TAG c78e984f3abbd20f6e01d6f51819e826b1691f65 # v0.8.0 # GIT_TAG 71090c680b4c943448ba87a0f1f864f174e4edda # v0.9.0 - GIT_REPOSITORY https://github.com/jeremy-rifkin/libdwarf-code.git - GIT_TAG 308b55331b564d4fdbe3bc6856712270e5b2395b + 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) From 60dd6b5065d8e15e9269cbc5348d97f7bc5a2de6 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Wed, 7 Feb 2024 21:40:52 -0600 Subject: [PATCH 49/63] Fix issues related to export directives --- CMakeLists.txt | 3 ++- cmake/in/cpptrace-config-cmake.in | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 701010e..55ea3a4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -243,8 +243,9 @@ endif() # ---- Generate Build Info Headers ---- # used in export header generated below -if(NOT CPPTRACE_BUILD_SHARED) +if(build_type STREQUAL "STATIC") target_compile_definitions(${target_name} PUBLIC CPPTRACE_STATIC_DEFINE) + set(CPPTRACE_STATIC_DEFINE TRUE) endif() # ---- Library Properties ---- diff --git a/cmake/in/cpptrace-config-cmake.in b/cmake/in/cpptrace-config-cmake.in index a196307..412a9d0 100644 --- a/cmake/in/cpptrace-config-cmake.in +++ b/cmake/in/cpptrace-config-cmake.in @@ -15,3 +15,7 @@ if(NOT TARGET cpptrace::cpptrace) include("${CMAKE_CURRENT_LIST_DIR}/@package_name@-targets.cmake") endif() + +if(@CPPTRACE_STATIC_DEFINE@) + target_compile_definitions(cpptrace::cpptrace INTERFACE CPPTRACE_STATIC_DEFINE) +endif() From df6e02087e473b221922874247ae015ee8a511dd Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Wed, 7 Feb 2024 22:08:45 -0600 Subject: [PATCH 50/63] Update for vcpkg --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 55ea3a4..dab7167 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -407,7 +407,7 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) 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 $,libdwarf::dwarf-static,libdwarf::dwarf-shared>) + target_link_libraries(${target_name} PRIVATE libdwarf::dwarf) target_compile_definitions(${target_name} PRIVATE CPPTRACE_USE_NESTED_LIBDWARF_HEADER_PATH) elseif(CPPTRACE_USE_EXTERNAL_LIBDWARF) if(DEFINED LIBDWARF_LIBRARIES) From 41f37a344684148b726d764832c1e4689311dd95 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Wed, 7 Feb 2024 22:24:48 -0600 Subject: [PATCH 51/63] Run on the new macos 14 runners with a newer default xcode to try to workaround a linker internal error --- .github/workflows/build.yml | 2 +- .github/workflows/cmake-integration.yml | 6 +++--- .github/workflows/test.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8125dc2..717175e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -45,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: diff --git a/.github/workflows/cmake-integration.yml b/.github/workflows/cmake-integration.yml index 8274c73..84a6b7a 100644 --- a/.github/workflows/cmake-integration.yml +++ b/.github/workflows/cmake-integration.yml @@ -66,7 +66,7 @@ jobs: ./main test-macos-fetchcontent: - runs-on: macos-13 + runs-on: macos-14 strategy: fail-fast: false matrix: @@ -85,7 +85,7 @@ jobs: make ./main test-macos-findpackage: - runs-on: macos-13 + runs-on: macos-14 strategy: fail-fast: false matrix: @@ -108,7 +108,7 @@ jobs: make ./main test-macos-add_subdirectory: - runs-on: macos-13 + runs-on: macos-14 strategy: fail-fast: false matrix: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 07dcdba..5090241 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -48,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: From 59272bccd69fed1383c0482f3ab45e2827e826ff Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Wed, 7 Feb 2024 22:34:23 -0600 Subject: [PATCH 52/63] printbug... --- src/binary/mach-o.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/binary/mach-o.hpp b/src/binary/mach-o.hpp index 02fd776..13b1e27 100644 --- a/src/binary/mach-o.hpp +++ b/src/binary/mach-o.hpp @@ -451,6 +451,8 @@ namespace detail { off_t mach_header_offset = (off_t)arch.offset; arch_offset += arch_size; std::uint32_t magic = load_bytes(file, mach_header_offset); + std::cerr<<"xxx: "<cputype<(mhp->cpusubtype & ~CPU_SUBTYPE_MASK)<cputype && static_cast(mhp->cpusubtype & ~CPU_SUBTYPE_MASK) == arch.cpusubtype From 37630f479d377926842b366d106f0b9ed927cc7b Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Thu, 8 Feb 2024 00:38:56 -0600 Subject: [PATCH 53/63] Try to make fat mach architecture handling more robust --- src/binary/mach-o.hpp | 69 +++++++++++++++++++++++++++++++------------ 1 file changed, 50 insertions(+), 19 deletions(-) diff --git a/src/binary/mach-o.hpp b/src/binary/mach-o.hpp index 13b1e27..2a02f5c 100644 --- a/src/binary/mach-o.hpp +++ b/src/binary/mach-o.hpp @@ -26,6 +26,7 @@ #include #include #include +#include namespace cpptrace { namespace detail { @@ -176,7 +177,7 @@ namespace detail { } } // somehow no __TEXT section was found... - PANIC("Couldn't find __TEXT section while parsing Mach-O object"); + throw std::runtime_error("Couldn't find __TEXT section while parsing Mach-O object"); return 0; } @@ -410,7 +411,7 @@ namespace detail { if(allow_arch_mismatch) { return; } else { - PANIC("Mach-O file cpu type and subtype do not match current machine " + object_path); + throw std::runtime_error("Mach-O file cpu type and subtype do not match current machine " + object_path); } } cputype = header.cputype; @@ -441,34 +442,64 @@ namespace detail { if(should_swap()) { swap_fat_header(&header, NX_UnknownByteOrder); } - thread_local static struct LP(mach_header)* mhp = _NSGetMachExecuteHeader(); + // 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(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(file, mach_header_offset); + // std::cerr<<"xxx: "<cputype<(mhp->cpusubtype & ~CPU_SUBTYPE_MASK)<cputype && + // static_cast(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_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(file, arch_offset); if(should_swap()) { swap_fat_arch(&arch, 1, NX_UnknownByteOrder); } - off_t mach_header_offset = (off_t)arch.offset; + 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(file, mach_header_offset); - std::cerr<<"xxx: "<cputype<(mhp->cpusubtype & ~CPU_SUBTYPE_MASK)<cputype && - static_cast(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; + load_base = mach_header_offset; + fat_index = best - fat_arches.data(); + if(is_magic_64(magic)) { + load_mach<64>(true); + } else { + load_mach<32>(true); } + return; } // 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"); + throw std::runtime_error("Couldn't find appropriate architecture in fat Mach-O"); } template From 2e9c586ba7345ae7e29c5171f7f98ce31fd8ffb5 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sat, 10 Feb 2024 19:31:35 -0600 Subject: [PATCH 54/63] Printbug --- .github/workflows/test.yml | 3 +++ ci/test-all-configs.py | 11 ++++++++++- src/binary/mach-o.hpp | 6 +++++- src/binary/object.hpp | 4 +++- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5090241..dd51aa7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -80,6 +80,9 @@ jobs: make -j sudo make install cd ../../cpptrace + file /usr/lib/dyld + otool -l /usr/lib/dyld + uname -a - name: dependencies run: | pip3 install colorama diff --git a/ci/test-all-configs.py b/ci/test-all-configs.py index 1930bfb..fd96fc3 100644 --- a/ci/test-all-configs.py +++ b/ci/test-all-configs.py @@ -110,7 +110,7 @@ def output_matches(raw_output: str, params: Tuple[str]): 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) @@ -126,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]): @@ -255,6 +260,8 @@ def build_full_or_auto(matrix): def test(matrix): if platform.system() != "Windows": + if platform.system() == "Darwin": + run_command("otool", "-l", "./test", always_output=True) return run_test( "./test", (matrix["compiler"], matrix["unwind"], matrix["symbols"], matrix["demangle"]) @@ -273,6 +280,8 @@ def test(matrix): def test_full_or_auto(matrix): if platform.system() != "Windows": + if platform.system() == "Darwin": + run_command("otool", "-l", "./test", always_output=True) return run_test( "./test", (matrix["compiler"],) diff --git a/src/binary/mach-o.hpp b/src/binary/mach-o.hpp index 2a02f5c..66046fe 100644 --- a/src/binary/mach-o.hpp +++ b/src/binary/mach-o.hpp @@ -141,6 +141,7 @@ namespace detail { public: mach_o(const std::string& object_path) : object_path(object_path) { + fprintf(stderr, "new mach -------------------------------------- %s\n", object_path.c_str()); file = std::fopen(object_path.c_str(), "rb"); if(file == nullptr) { throw file_error("Unable to read object file " + object_path); @@ -166,11 +167,13 @@ namespace detail { } std::uintptr_t get_text_vmaddr() { + fprintf(stderr, "get_text_vmaddr------------- %s\n", object_path.c_str()); 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, "foo \"%s\", %d\n", segment.segname, std::strcmp(segment.segname, "__TEXT") == 0); if(std::strcmp(segment.segname, "__TEXT") == 0) { return segment.vmaddr; } @@ -178,7 +181,6 @@ namespace detail { } // somehow no __TEXT section was found... throw std::runtime_error("Couldn't find __TEXT section while parsing Mach-O object"); - return 0; } std::size_t get_fat_index() const { @@ -422,6 +424,7 @@ namespace detail { flags = header.flags; // handle load commands std::uint32_t ncmds = header.ncmds; + fprintf(stderr, "ncmds: %u\n", ncmds); std::uint32_t load_commands_offset = load_base + header_size; // iterate load commands std::uint32_t actual_offset = load_commands_offset; @@ -491,6 +494,7 @@ namespace detail { std::uint32_t magic = load_bytes(file, mach_header_offset); load_base = mach_header_offset; fat_index = best - fat_arches.data(); + fprintf(stderr, "INDEX: %llu\n", to_ull(fat_index)); if(is_magic_64(magic)) { load_mach<64>(true); } else { diff --git a/src/binary/object.hpp b/src/binary/object.hpp index efb3ae0..7d8f569 100644 --- a/src/binary/object.hpp +++ b/src/binary/object.hpp @@ -56,7 +56,9 @@ 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 = mach_o(object_path).get_text_vmaddr(); + mach_o obj(object_path); + obj.print_segments(); + auto base = obj.get_text_vmaddr(); cache.insert(it, {object_path, base}); return base; } else { From 612d5e6d6573530b31a5c5722b40d713a3ee1ba8 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 11 Feb 2024 01:52:36 -0600 Subject: [PATCH 55/63] Fix architecture handling in load_mach --- src/binary/mach-o.hpp | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/src/binary/mach-o.hpp b/src/binary/mach-o.hpp index 66046fe..658f6c1 100644 --- a/src/binary/mach-o.hpp +++ b/src/binary/mach-o.hpp @@ -153,9 +153,9 @@ namespace detail { } else { fat_index = 0; if(is_magic_64(magic)) { - load_mach<64>(false); + load_mach<64>(); } else { - load_mach<32>(false); + load_mach<32>(); } } } @@ -393,9 +393,7 @@ namespace detail { private: template - void load_mach( - bool allow_arch_mismatch - ) { + void load_mach() { static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument"); bits = Bits; using Mach_Header = typename std::conditional::type; @@ -405,17 +403,6 @@ namespace detail { if(should_swap()) { swap_mach_header(header); } - thread_local static struct LP(mach_header)* mhp = _NSGetMachExecuteHeader(); - if( - header.cputype != mhp->cputype || - static_cast(mhp->cpusubtype & ~CPU_SUBTYPE_MASK) != header.cpusubtype - ) { - if(allow_arch_mismatch) { - return; - } else { - throw std::runtime_error("Mach-O file cpu type and subtype do not match current machine " + object_path); - } - } cputype = header.cputype; cpusubtype = header.cpusubtype; filetype = header.filetype; @@ -496,9 +483,9 @@ namespace detail { fat_index = best - fat_arches.data(); fprintf(stderr, "INDEX: %llu\n", to_ull(fat_index)); if(is_magic_64(magic)) { - load_mach<64>(true); + load_mach<64>(); } else { - load_mach<32>(true); + load_mach<32>(); } return; } From cdaab64be2c60cf2a17adf88cc40b61d9f977105 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 11 Feb 2024 01:58:29 -0600 Subject: [PATCH 56/63] Revert "Printbug" This reverts commit 2e9c586ba7345ae7e29c5171f7f98ce31fd8ffb5. --- .github/workflows/test.yml | 3 --- ci/test-all-configs.py | 11 +---------- src/binary/mach-o.hpp | 6 +----- src/binary/object.hpp | 4 +--- 4 files changed, 3 insertions(+), 21 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dd51aa7..5090241 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -80,9 +80,6 @@ jobs: make -j sudo make install cd ../../cpptrace - file /usr/lib/dyld - otool -l /usr/lib/dyld - uname -a - name: dependencies run: | pip3 install colorama diff --git a/ci/test-all-configs.py b/ci/test-all-configs.py index fd96fc3..1930bfb 100644 --- a/ci/test-all-configs.py +++ b/ci/test-all-configs.py @@ -110,7 +110,7 @@ def output_matches(raw_output: str, params: Tuple[str]): return not errored -def run_command(*args: List[str], always_output=False): +def run_command(*args: List[str]): 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) @@ -126,11 +126,6 @@ def run_command(*args: List[str], always_output=False): 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]): @@ -260,8 +255,6 @@ def build_full_or_auto(matrix): def test(matrix): if platform.system() != "Windows": - if platform.system() == "Darwin": - run_command("otool", "-l", "./test", always_output=True) return run_test( "./test", (matrix["compiler"], matrix["unwind"], matrix["symbols"], matrix["demangle"]) @@ -280,8 +273,6 @@ def test(matrix): def test_full_or_auto(matrix): if platform.system() != "Windows": - if platform.system() == "Darwin": - run_command("otool", "-l", "./test", always_output=True) return run_test( "./test", (matrix["compiler"],) diff --git a/src/binary/mach-o.hpp b/src/binary/mach-o.hpp index 658f6c1..a2d8c02 100644 --- a/src/binary/mach-o.hpp +++ b/src/binary/mach-o.hpp @@ -141,7 +141,6 @@ namespace detail { public: mach_o(const std::string& object_path) : object_path(object_path) { - fprintf(stderr, "new mach -------------------------------------- %s\n", object_path.c_str()); file = std::fopen(object_path.c_str(), "rb"); if(file == nullptr) { throw file_error("Unable to read object file " + object_path); @@ -167,13 +166,11 @@ namespace detail { } std::uintptr_t get_text_vmaddr() { - fprintf(stderr, "get_text_vmaddr------------- %s\n", object_path.c_str()); 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, "foo \"%s\", %d\n", segment.segname, std::strcmp(segment.segname, "__TEXT") == 0); if(std::strcmp(segment.segname, "__TEXT") == 0) { return segment.vmaddr; } @@ -181,6 +178,7 @@ namespace detail { } // somehow no __TEXT section was found... throw std::runtime_error("Couldn't find __TEXT section while parsing Mach-O object"); + return 0; } std::size_t get_fat_index() const { @@ -411,7 +409,6 @@ namespace detail { flags = header.flags; // handle load commands std::uint32_t ncmds = header.ncmds; - fprintf(stderr, "ncmds: %u\n", ncmds); std::uint32_t load_commands_offset = load_base + header_size; // iterate load commands std::uint32_t actual_offset = load_commands_offset; @@ -481,7 +478,6 @@ namespace detail { std::uint32_t magic = load_bytes(file, mach_header_offset); load_base = mach_header_offset; fat_index = best - fat_arches.data(); - fprintf(stderr, "INDEX: %llu\n", to_ull(fat_index)); if(is_magic_64(magic)) { load_mach<64>(); } else { diff --git a/src/binary/object.hpp b/src/binary/object.hpp index 7d8f569..efb3ae0 100644 --- a/src/binary/object.hpp +++ b/src/binary/object.hpp @@ -56,9 +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 - mach_o obj(object_path); - obj.print_segments(); - auto base = obj.get_text_vmaddr(); + auto base = mach_o(object_path).get_text_vmaddr(); cache.insert(it, {object_path, base}); return base; } else { From 40bc2992b2ce6178f09f8ed90a5b5df0c87bdc7a Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 11 Feb 2024 02:00:53 -0600 Subject: [PATCH 57/63] re-add something that's useful during testing --- ci/test-all-configs.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ci/test-all-configs.py b/ci/test-all-configs.py index 1930bfb..a3ac065 100644 --- a/ci/test-all-configs.py +++ b/ci/test-all-configs.py @@ -110,7 +110,7 @@ def output_matches(raw_output: str, params: Tuple[str]): 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) @@ -126,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]): From 9a7c45bda750785f090313804bbf00dd6204d879 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 11 Feb 2024 02:05:00 -0600 Subject: [PATCH 58/63] remove return that wasn't needed --- src/binary/mach-o.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/binary/mach-o.hpp b/src/binary/mach-o.hpp index a2d8c02..b0260a3 100644 --- a/src/binary/mach-o.hpp +++ b/src/binary/mach-o.hpp @@ -178,7 +178,6 @@ namespace detail { } // somehow no __TEXT section was found... throw std::runtime_error("Couldn't find __TEXT section while parsing Mach-O object"); - return 0; } std::size_t get_fat_index() const { From 58d09dd010a4b801aa56a58a0d11296c20443b6a Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 11 Feb 2024 02:05:21 -0600 Subject: [PATCH 59/63] Remove CPPTRACE_NO_EXPORT_ATTR / CPPTRACE_DEPRECATED_ATTR as they aren't used --- include/cpptrace/cpptrace.hpp | 4 ---- include/ctrace/ctrace.h | 4 ---- 2 files changed, 8 deletions(-) diff --git a/include/cpptrace/cpptrace.hpp b/include/cpptrace/cpptrace.hpp index 38aa7c6..2a03162 100644 --- a/include/cpptrace/cpptrace.hpp +++ b/include/cpptrace/cpptrace.hpp @@ -13,13 +13,9 @@ #ifdef _WIN32 #define CPPTRACE_EXPORT_ATTR __declspec(dllexport) #define CPPTRACE_IMPORT_ATTR __declspec(dllimport) -#define CPPTRACE_NO_EXPORT_ATTR -#define CPPTRACE_DEPRECATED_ATTR __declspec(deprecated) #else #define CPPTRACE_EXPORT_ATTR __attribute__((visibility("default"))) #define CPPTRACE_IMPORT_ATTR __attribute__((visibility("default"))) -#define CPPTRACE_NO_EXPORT_ATTR __attribute__((visibility("hidden"))) -#define CPPTRACE_DEPRECATE_ATTR __attribute__((__deprecated__)) #endif #ifdef CPPTRACE_STATIC_DEFINE diff --git a/include/ctrace/ctrace.h b/include/ctrace/ctrace.h index 186dfb9..e9cdffc 100644 --- a/include/ctrace/ctrace.h +++ b/include/ctrace/ctrace.h @@ -8,13 +8,9 @@ #ifdef _WIN32 #define CPPTRACE_EXPORT_ATTR __declspec(dllexport) #define CPPTRACE_IMPORT_ATTR __declspec(dllimport) -#define CPPTRACE_NO_EXPORT_ATTR -#define CPPTRACE_DEPRECATED_ATTR __declspec(deprecated) #else #define CPPTRACE_EXPORT_ATTR __attribute__((visibility("default"))) #define CPPTRACE_IMPORT_ATTR __attribute__((visibility("default"))) -#define CPPTRACE_NO_EXPORT_ATTR __attribute__((visibility("hidden"))) -#define CPPTRACE_DEPRECATE_ATTR __attribute__((__deprecated__)) #endif #ifdef CPPTRACE_STATIC_DEFINE From 90802c6be10dcea03ab49eb519e5b41f669bdce9 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 11 Feb 2024 02:11:12 -0600 Subject: [PATCH 60/63] deal with a warning under testing build that's annoyed me for a while --- test/signal_demo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/signal_demo.cpp b/test/signal_demo.cpp index c7a2db2..dc7decc 100644 --- a/test/signal_demo.cpp +++ b/test/signal_demo.cpp @@ -12,7 +12,7 @@ #include void trace() { - *(char*)0 = 2; + *(volatile char*)0 = 2; } void bar() { From e166c9804da8d35c43bac4d853b1eb862e73fc1e Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 11 Feb 2024 11:43:32 -0600 Subject: [PATCH 61/63] Update standalone instructions --- README.md | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6adfbe7..cb0b884 100644 --- a/README.md +++ b/README.md @@ -659,12 +659,23 @@ the libraries are installed to `~/scratch/cpptrace-test/resources`. mkdir -p ~/scratch/cpptrace-test/resources cd ~/scratch/cpptrace-test -git clone https://github.com/davea42/libdwarf-code.git -cd libdwarf-code -git checkout 6216e185863f41d6f19ab850caabfff7326020d7 # looks like some cmake stuff changed upstream, checking out the commit cpptrace currently uses +git clone https://github.com/facebook/zstd.git +cd zstd +git checkout 63779c798237346c2b245c546c40b72a5a5913fe +cd build/cmake mkdir build cd build -cmake .. -DPIC_ALWAYS=On -DBUILD_DWARFDUMP=Off -DCMAKE_INSTALL_PREFIX=~/scratch/cpptrace-test/resources +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 From c771c4d3f548f985edcf60c2e93370487f20f3d2 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 11 Feb 2024 11:44:31 -0600 Subject: [PATCH 62/63] Update #include for ctrace docs --- docs/c-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/c-api.md b/docs/c-api.md index 2d8086d..b079f62 100644 --- a/docs/c-api.md +++ b/docs/c-api.md @@ -18,7 +18,7 @@ Cpptrace provides a C API under the name ctrace, documented below. All ctrace declarations are in the `ctrace.h` header: ```c -#include +#include ``` ### Stack Traces From bc662438f3b89dbaf1d22a31293d79aa50c45529 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 11 Feb 2024 11:53:33 -0600 Subject: [PATCH 63/63] Small cmake adjustments --- CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dab7167..aa4a1e9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -242,7 +242,6 @@ endif() # ---- Generate Build Info Headers ---- -# used in export header generated below if(build_type STREQUAL "STATIC") target_compile_definitions(${target_name} PUBLIC CPPTRACE_STATIC_DEFINE) set(CPPTRACE_STATIC_DEFINE TRUE) @@ -408,7 +407,6 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) target_compile_definitions(${target_name} PRIVATE CPPTRACE_USE_NESTED_LIBDWARF_HEADER_PATH) elseif(CPPTRACE_VCPKG) target_link_libraries(${target_name} PRIVATE libdwarf::dwarf) - target_compile_definitions(${target_name} PRIVATE CPPTRACE_USE_NESTED_LIBDWARF_HEADER_PATH) elseif(CPPTRACE_USE_EXTERNAL_LIBDWARF) if(DEFINED LIBDWARF_LIBRARIES) target_link_libraries(${target_name} PRIVATE ${LIBDWARF_LIBRARIES})