From b18e9a1eb98871df424f309e7aaed797c3c4debb Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Mon, 24 Feb 2025 22:52:48 -0600 Subject: [PATCH 01/23] Support gcc 4.8.5, resolves #220 --- CMakeLists.txt | 17 +++++++++++++++-- src/binary/elf.cpp | 2 +- src/from_current.cpp | 3 ++- src/symbols/dwarf/dwarf_resolver.cpp | 2 +- src/symbols/dwarf/dwarf_utils.hpp | 9 ++++++++- src/utils/common.hpp | 7 +++++++ 6 files changed, 34 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c54a477..eaafbc91 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,12 +34,20 @@ endif() 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") + include(CheckCXXCompilerFlag) + check_cxx_compiler_flag(-fdiagnostics-color=always HAS_CXX_FDIAGNOSTICS_COLOR) + if(HAS_CXX_FDIAGNOSTICS_COLOR) + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color=always") + endif() 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") + include(CheckCCompilerFlag) + check_c_compiler_flag(-fdiagnostics-color=always HAS_C_FDIAGNOSTICS_COLOR) + if(HAS_C_FDIAGNOSTICS_COLOR) + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fdiagnostics-color=always") + endif() elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fcolor-diagnostics") endif() @@ -176,6 +184,11 @@ target_compile_options( ${warning_options} ) +if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0) + # https://godbolt.org/z/qYh89E6rq + target_compile_options(${target_name} PRIVATE -Wno-missing-field-initializers) +endif() + set(CPPTRACE_VERSION_MAJOR ${CMAKE_PROJECT_VERSION_MAJOR}) set(CPPTRACE_VERSION_MINOR ${CMAKE_PROJECT_VERSION_MINOR}) set(CPPTRACE_VERSION_PATCH ${CMAKE_PROJECT_VERSION_PATCH}) diff --git a/src/binary/elf.cpp b/src/binary/elf.cpp index 19a438fa..1117f13c 100644 --- a/src/binary/elf.cpp +++ b/src/binary/elf.cpp @@ -424,7 +424,7 @@ namespace detail { static std::unordered_map> cache; auto it = cache.find(object_path); if(it == cache.end()) { - auto res = cache.insert({ object_path, elf::open_elf(object_path) }); + auto res = cache.emplace(object_path, elf::open_elf(object_path)); VERIFY(res.second); it = res.first; } diff --git a/src/from_current.cpp b/src/from_current.cpp index d099a76c..6ffce4ef 100644 --- a/src/from_current.cpp +++ b/src/from_current.cpp @@ -272,7 +272,8 @@ namespace cpptrace { memcpy(new_vtable_page, type_info_vtable_pointer, vtable_size * sizeof(void*)); // ninja in the custom __do_catch interceptor auto new_vtable = static_cast(new_vtable_page); - new_vtable[6] = reinterpret_cast(do_catch_function); + // double cast is done here because older (and some newer gcc versions) warned about it under -Wpedantic + new_vtable[6] = reinterpret_cast(reinterpret_cast(do_catch_function)); // make the page read-only mprotect_page(new_vtable_page, page_size, memory_readonly); diff --git a/src/symbols/dwarf/dwarf_resolver.cpp b/src/symbols/dwarf/dwarf_resolver.cpp index 0aee279e..782ae433 100644 --- a/src/symbols/dwarf/dwarf_resolver.cpp +++ b/src/symbols/dwarf/dwarf_resolver.cpp @@ -324,7 +324,7 @@ namespace libdwarf { char** dw_srcfiles; Dwarf_Signed dw_filecount; VERIFY(wrap(dwarf_srcfiles, cu_die.get(), &dw_srcfiles, &dw_filecount) == DW_DLV_OK); - it = srcfiles_cache.insert(it, {off, srcfiles{cu_die.dbg, dw_srcfiles, dw_filecount}}); + it = srcfiles_cache.emplace_hint(it, off, srcfiles{cu_die.dbg, dw_srcfiles, dw_filecount}); } if(file_i < it->second.count()) { // dwarf is using 1-indexing diff --git a/src/symbols/dwarf/dwarf_utils.hpp b/src/symbols/dwarf/dwarf_utils.hpp index 56a46e5b..7676ddb7 100644 --- a/src/symbols/dwarf/dwarf_utils.hpp +++ b/src/symbols/dwarf/dwarf_utils.hpp @@ -67,7 +67,14 @@ namespace libdwarf { // sorted range entries for dies template< typename T, - typename std::enable_if::value && sizeof(T) <= 16, int>::type = 0 + typename std::enable_if< + // old gcc doesn't support this trait https://godbolt.org/z/fKWT9jTK7 + #if !(defined(__GNUC__) && (__GNUC__ < 5)) + std::is_trivially_copyable::value && + #endif + sizeof(T) <= 16, + int + >::type = 0 > class die_cache { public: diff --git a/src/utils/common.hpp b/src/utils/common.hpp index 954f6942..b99001c7 100644 --- a/src/utils/common.hpp +++ b/src/utils/common.hpp @@ -24,6 +24,13 @@ #define NODISCARD #endif +// workaround a bizarre gcc bug https://godbolt.org/z/s78vnf7jv +// https://github.com/jeremy-rifkin/cpptrace/issues/220 +#if defined(__GNUC__) && (__GNUC__ < 7) + #undef NODISCARD + #define NODISCARD +#endif + #if IS_MSVC #define MSVC_CDECL __cdecl #else From 8df7b180fa3326837eed8a10f96948592b74d292 Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Mon, 3 Mar 2025 18:30:30 -0600 Subject: [PATCH 02/23] Don't call dwarf_dealloc on strings from dwarf_formstring or dwarf_diename. Fixes https://github.com/jeremy-rifkin/libassert/issues/123, related to https://github.com/davea42/libdwarf-code/issues/279. --- src/symbols/dwarf/dwarf.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/symbols/dwarf/dwarf.hpp b/src/symbols/dwarf/dwarf.hpp index 6fb3bfc6..a3878044 100644 --- a/src/symbols/dwarf/dwarf.hpp +++ b/src/symbols/dwarf/dwarf.hpp @@ -134,8 +134,8 @@ namespace libdwarf { std::string get_name() const { char empty[] = ""; char* name = empty; + // Note: It's important to not free the string from this function. int ret = wrap(dwarf_diename, die, &name); - auto wrapper = raii_wrap(name, [this] (char* str) { dwarf_dealloc(dbg, str, DW_DLA_STRING); }); std::string str; if(ret != DW_DLV_NO_ENTRY) { str = name; @@ -148,8 +148,9 @@ namespace libdwarf { if(wrap(dwarf_attr, die, attr_num, &attr) == DW_DLV_OK) { auto attwrapper = raii_wrap(attr, [] (Dwarf_Attribute attr) { dwarf_dealloc_attribute(attr); }); char* raw_str; + // Note: It's important to not free the string from this function. + // https://github.com/davea42/libdwarf-code/issues/279 VERIFY(wrap(dwarf_formstring, attr, &raw_str) == DW_DLV_OK); - auto strwrapper = raii_wrap(raw_str, [this] (char* str) { dwarf_dealloc(dbg, str, DW_DLA_STRING); }); std::string str = raw_str; return str; } else { From e2711232c5c63df069c9b0034b2dd97f8110f166 Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sat, 8 Mar 2025 13:44:06 -0600 Subject: [PATCH 03/23] Don't wet -Wall for clang-cl --- CMakeLists.txt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eaafbc91..aa29f5ab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -162,9 +162,15 @@ target_include_directories( src ) +if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" AND "${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "MSVC") + set(IS_CLANG_CL 1) +else() + set(IS_CLANG_CL 0) +endif() + set( warning_options - $<$>:-Wall -Wextra -Werror=return-type -Wundef> + $<$,$>>:-Wall -Wextra -Werror=return-type -Wundef> $<$:-Wuseless-cast -Wmaybe-uninitialized> $<$:/W4 /permissive-> ) From ec2976fa1b25a6e965eb0787fa9f2dfccdde1ab0 Mon Sep 17 00:00:00 2001 From: Jeaye Wilkerson Date: Fri, 21 Mar 2025 20:59:43 -0700 Subject: [PATCH 04/23] Add move-based frame transformation (#229) This could have been added directly in `print_frame_inner`, so we only need to add it in one place. However, by adding it in `print_internal`, prior to filtering, we allow transformation and filtering to cooperate. This allows a transformer to modify a frame such that it can later be marked for filtering, which would avoid duplicated code between those functions. I don't have this use case right now, but it made sense from an overall product design perspective. This closes #227. This closes #228. Perf difference between this and #228 is nominal. Pick whichever you like more. --- include/cpptrace/formatting.hpp | 1 + src/formatting.cpp | 21 ++++++++++++++++++--- test/unit/lib/formatting.cpp | 27 +++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/include/cpptrace/formatting.hpp b/include/cpptrace/formatting.hpp index c1d87f72..79dcec7f 100644 --- a/include/cpptrace/formatting.hpp +++ b/include/cpptrace/formatting.hpp @@ -46,6 +46,7 @@ namespace cpptrace { formatter& columns(bool); formatter& filtered_frame_placeholders(bool); formatter& filter(std::function); + formatter& transform(std::function); std::string format(const stacktrace_frame&) const; std::string format(const stacktrace_frame&, bool color) const; diff --git a/src/formatting.cpp b/src/formatting.cpp index ef7db8a3..4b845be9 100644 --- a/src/formatting.cpp +++ b/src/formatting.cpp @@ -24,6 +24,7 @@ namespace cpptrace { bool columns = true; bool show_filtered_frames = true; std::function filter; + std::function transform; } options; public: @@ -54,9 +55,15 @@ namespace cpptrace { void filter(std::function filter) { options.filter = filter; } + void transform(std::function transform) { + options.transform = std::move(transform); + } - std::string format(const stacktrace_frame& frame, detail::optional color_override = detail::nullopt) const { + std::string format(stacktrace_frame frame, detail::optional color_override = detail::nullopt) const { std::ostringstream oss; + if(options.transform) { + frame = options.transform(std::move(frame)); + } print_frame_inner(oss, frame, color_override.value_or(options.color == color_mode::always)); return std::move(oss).str(); } @@ -148,7 +155,11 @@ namespace cpptrace { return; } const auto frame_number_width = detail::n_digits(static_cast(frames.size()) - 1); - for(const auto& frame : frames) { + for(size_t i = 0; i < frames.size(); ++i) { + auto frame = frames[i]; + if(options.transform) { + frame = options.transform(std::move(frame)); + } if(options.filter && !options.filter(frame)) { if(!options.show_filtered_frames) { counter++; @@ -170,7 +181,7 @@ namespace cpptrace { } } } - if(newline_at_end || &frame != &frames.back()) { + if(newline_at_end || i + 1 != frames.size()) { stream << '\n'; } counter++; @@ -293,6 +304,10 @@ namespace cpptrace { pimpl->filter(std::move(filter)); return *this; } + formatter& formatter::transform(std::function transform) { + pimpl->transform(std::move(transform)); + return *this; + } std::string formatter::format(const stacktrace_frame& frame) const { return pimpl->format(frame); diff --git a/test/unit/lib/formatting.cpp b/test/unit/lib/formatting.cpp index f718a7af..62523994 100644 --- a/test/unit/lib/formatting.cpp +++ b/test/unit/lib/formatting.cpp @@ -255,6 +255,33 @@ TEST(FormatterTest, DontShowFilteredFrames) { ); } +TEST(FormatterTest, Transforming) { + auto formatter = cpptrace::formatter{} + .transform([](cpptrace::stacktrace_frame &&frame) { + static size_t count = 0; + frame.symbol = cpptrace::microfmt::format("sym{}", count++); + return std::move(frame); + }); + auto res = split(formatter.format(make_test_stacktrace()), "\n"); + EXPECT_THAT( + res, + ElementsAre( + "Stack trace (most recent call first):", + "#0 0x0000000000000001 in sym0 at foo.cpp:20:30", + "#1 0x0000000000000002 in sym1 at bar.cpp:30:40", + "#2 0x0000000000000003 in sym2 at foo.cpp:40:25" + ) + ); + + auto frame_res = split(formatter.format(make_test_stacktrace().frames[1]), "\n"); + EXPECT_THAT( + frame_res, + ElementsAre( + "0x0000000000000002 in sym3 at bar.cpp:30:40" + ) + ); +} + TEST(FormatterTest, MoveSemantics) { auto formatter = cpptrace::formatter{} .filter([] (const cpptrace::stacktrace_frame& frame) -> bool { From 0526a4c2d91d861644504f1fa33e07b3de455be1 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 16 Mar 2025 11:06:27 -0500 Subject: [PATCH 05/23] Improve macro and definition leaking, switch most compile definitions to private --- CMakeLists.txt | 46 ++++++++++++++++++++++---------------------- src/from_current.cpp | 6 +++--- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index aa29f5ab..0e6d1150 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -262,19 +262,19 @@ endif() # =================================================== Back-end setup =================================================== if(HAS_CXX_EXCEPTION_TYPE) - target_compile_definitions(${target_name} PUBLIC CPPTRACE_HAS_CXX_EXCEPTION_TYPE) + target_compile_definitions(${target_name} PRIVATE CPPTRACE_HAS_CXX_EXCEPTION_TYPE) endif() if(HAS_DL_FIND_OBJECT) - target_compile_definitions(${target_name} PUBLIC CPPTRACE_HAS_DL_FIND_OBJECT) + target_compile_definitions(${target_name} PRIVATE CPPTRACE_HAS_DL_FIND_OBJECT) endif() if(HAS_DLADDR1) - target_compile_definitions(${target_name} PUBLIC CPPTRACE_HAS_DLADDR1) + target_compile_definitions(${target_name} PRIVATE CPPTRACE_HAS_DLADDR1) endif() if(HAS_MACH_VM) - target_compile_definitions(${target_name} PUBLIC HAS_MACH_VM) + target_compile_definitions(${target_name} PRIVATE CPPTRACE_HAS_MACH_VM) endif() # Symbols @@ -286,12 +286,12 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE) message(WARNING "Cpptrace: Using libbacktrace for symbols but libbacktrace doesn't appear installed or configured properly. You may need to specify CPPTRACE_BACKTRACE_PATH.") endif() endif() - target_compile_definitions(${target_name} PUBLIC CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE) + target_compile_definitions(${target_name} PRIVATE CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE) target_link_libraries(${target_name} PRIVATE backtrace ${CMAKE_DL_LIBS}) endif() if(CPPTRACE_GET_SYMBOLS_WITH_LIBDL) - target_compile_definitions(${target_name} PUBLIC CPPTRACE_GET_SYMBOLS_WITH_LIBDL) + target_compile_definitions(${target_name} PRIVATE CPPTRACE_GET_SYMBOLS_WITH_LIBDL) target_link_libraries(${target_name} PRIVATE ${CMAKE_DL_LIBS}) endif() @@ -299,7 +299,7 @@ if(CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE) # set(CPPTRACE_ADDR2LINE_PATH "" CACHE STRING "Absolute path to the addr2line executable you want to use.") # option(CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH "" OFF) if(CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH) - target_compile_definitions(${target_name} PUBLIC CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH) + target_compile_definitions(${target_name} PRIVATE CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH) else() if("${CPPTRACE_ADDR2LINE_PATH}" STREQUAL "") if(APPLE) @@ -311,16 +311,16 @@ if(CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE) set(CPPTRACE_ADDR2LINE_PATH_FINAL "${CPPTRACE_ADDR2LINE_PATH}") endif() message(STATUS "Cpptrace: Using ${CPPTRACE_ADDR2LINE_PATH_FINAL} for addr2line path") - target_compile_definitions(${target_name} PUBLIC CPPTRACE_ADDR2LINE_PATH="${CPPTRACE_ADDR2LINE_PATH_FINAL}") + target_compile_definitions(${target_name} PRIVATE CPPTRACE_ADDR2LINE_PATH="${CPPTRACE_ADDR2LINE_PATH_FINAL}") endif() - target_compile_definitions(${target_name} PUBLIC CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE) + target_compile_definitions(${target_name} PRIVATE CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE) if(UNIX) target_link_libraries(${target_name} PRIVATE ${CMAKE_DL_LIBS}) endif() endif() if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) - target_compile_definitions(${target_name} PUBLIC CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) + target_compile_definitions(${target_name} PRIVATE CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) if(CPPTRACE_USE_EXTERNAL_LIBDWARF) if(NOT CPPTRACE_FIND_LIBDWARF_WITH_PKGCONFIG) find_package(libdwarf REQUIRED) @@ -431,12 +431,12 @@ if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) endif() if(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP) - target_compile_definitions(${target_name} PUBLIC CPPTRACE_GET_SYMBOLS_WITH_DBGHELP) + target_compile_definitions(${target_name} PRIVATE CPPTRACE_GET_SYMBOLS_WITH_DBGHELP) target_link_libraries(${target_name} PRIVATE dbghelp) endif() if(CPPTRACE_GET_SYMBOLS_WITH_NOTHING) - target_compile_definitions(${target_name} PUBLIC CPPTRACE_GET_SYMBOLS_WITH_NOTHING) + target_compile_definitions(${target_name} PRIVATE CPPTRACE_GET_SYMBOLS_WITH_NOTHING) endif() # Unwinding @@ -444,7 +444,7 @@ if(CPPTRACE_UNWIND_WITH_UNWIND) if(NOT HAS_UNWIND) message(WARNING "Cpptrace: CPPTRACE_UNWIND_WITH_UNWIND specified but libgcc unwind doesn't seem to be available.") endif() - target_compile_definitions(${target_name} PUBLIC CPPTRACE_UNWIND_WITH_UNWIND) + target_compile_definitions(${target_name} PRIVATE CPPTRACE_UNWIND_WITH_UNWIND) endif() if(CPPTRACE_UNWIND_WITH_LIBUNWIND) @@ -480,7 +480,7 @@ if(CPPTRACE_UNWIND_WITH_LIBUNWIND) target_include_directories(${target_name} PRIVATE ${LIBUNWIND_INCLUDE_DIRS}) target_link_libraries(${target_name} PRIVATE ${LIBUNWIND_LDFLAGS}) endif() - target_compile_definitions(${target_name} PUBLIC CPPTRACE_UNWIND_WITH_LIBUNWIND UNW_LOCAL_ONLY) + target_compile_definitions(${target_name} PRIVATE CPPTRACE_UNWIND_WITH_LIBUNWIND UNW_LOCAL_ONLY) endif() endif() @@ -488,23 +488,23 @@ if(CPPTRACE_UNWIND_WITH_EXECINFO) if(NOT HAS_EXECINFO) message(WARNING "Cpptrace: CPPTRACE_UNWIND_WITH_EXECINFO specified but execinfo.h doesn't seem to be available.") endif() - target_compile_definitions(${target_name} PUBLIC CPPTRACE_UNWIND_WITH_EXECINFO) + target_compile_definitions(${target_name} PRIVATE CPPTRACE_UNWIND_WITH_EXECINFO) endif() if(CPPTRACE_UNWIND_WITH_WINAPI) - target_compile_definitions(${target_name} PUBLIC CPPTRACE_UNWIND_WITH_WINAPI) + target_compile_definitions(${target_name} PRIVATE CPPTRACE_UNWIND_WITH_WINAPI) endif() if(CPPTRACE_UNWIND_WITH_DBGHELP) if(NOT HAS_STACKWALK) message(WARNING "Cpptrace: CPPTRACE_UNWIND_WITH_DBGHELP specified but dbghelp stackwalk64 doesn't seem to be available.") endif() - target_compile_definitions(${target_name} PUBLIC CPPTRACE_UNWIND_WITH_DBGHELP) + target_compile_definitions(${target_name} PRIVATE CPPTRACE_UNWIND_WITH_DBGHELP) target_link_libraries(${target_name} PRIVATE dbghelp) endif() if(CPPTRACE_UNWIND_WITH_NOTHING) - target_compile_definitions(${target_name} PUBLIC CPPTRACE_UNWIND_WITH_NOTHING) + target_compile_definitions(${target_name} PRIVATE CPPTRACE_UNWIND_WITH_NOTHING) endif() # Demangling @@ -512,24 +512,24 @@ if(CPPTRACE_DEMANGLE_WITH_CXXABI) if(NOT HAS_CXXABI) message(WARNING "Cpptrace: CPPTRACE_DEMANGLE_WITH_CXXABI specified but cxxabi.h doesn't seem to be available.") endif() - target_compile_definitions(${target_name} PUBLIC CPPTRACE_DEMANGLE_WITH_CXXABI) + target_compile_definitions(${target_name} PRIVATE CPPTRACE_DEMANGLE_WITH_CXXABI) endif() if(CPPTRACE_DEMANGLE_WITH_WINAPI) - target_compile_definitions(${target_name} PUBLIC CPPTRACE_DEMANGLE_WITH_WINAPI) + target_compile_definitions(${target_name} PRIVATE CPPTRACE_DEMANGLE_WITH_WINAPI) target_link_libraries(${target_name} PRIVATE dbghelp) endif() if(CPPTRACE_DEMANGLE_WITH_NOTHING) - target_compile_definitions(${target_name} PUBLIC CPPTRACE_DEMANGLE_WITH_NOTHING) + target_compile_definitions(${target_name} PRIVATE CPPTRACE_DEMANGLE_WITH_NOTHING) endif() if(NOT "${CPPTRACE_BACKTRACE_PATH}" STREQUAL "") - target_compile_definitions(${target_name} PUBLIC CPPTRACE_BACKTRACE_PATH=${CPPTRACE_BACKTRACE_PATH}) + target_compile_definitions(${target_name} PRIVATE CPPTRACE_BACKTRACE_PATH=${CPPTRACE_BACKTRACE_PATH}) endif() if(NOT "${CPPTRACE_HARD_MAX_FRAMES}" STREQUAL "") - target_compile_definitions(${target_name} PUBLIC CPPTRACE_HARD_MAX_FRAMES=${CPPTRACE_HARD_MAX_FRAMES}) + target_compile_definitions(${target_name} PRIVATE CPPTRACE_HARD_MAX_FRAMES=${CPPTRACE_HARD_MAX_FRAMES}) endif() # ====================================================== Install ======================================================= diff --git a/src/from_current.cpp b/src/from_current.cpp index 6ffce4ef..01534f57 100644 --- a/src/from_current.cpp +++ b/src/from_current.cpp @@ -20,7 +20,7 @@ #include #if IS_APPLE #include - #ifdef HAS_MACH_VM + #ifdef CPPTRACE_HAS_MACH_VM #include #endif #else @@ -118,7 +118,7 @@ namespace cpptrace { #if IS_APPLE int get_page_protections(void* page) { // https://stackoverflow.com/a/12627784/15675011 - #ifdef HAS_MACH_VM + #ifdef CPPTRACE_HAS_MACH_VM mach_vm_size_t vmsize; mach_vm_address_t address = (mach_vm_address_t)page; #else @@ -130,7 +130,7 @@ namespace cpptrace { sizeof(size_t) == 8 ? VM_REGION_BASIC_INFO_COUNT_64 : VM_REGION_BASIC_INFO_COUNT; memory_object_name_t object; kern_return_t status = - #ifdef HAS_MACH_VM + #ifdef CPPTRACE_HAS_MACH_VM mach_vm_region #else vm_region_64 From 7c4fcc7a094cb379745df8a55d6fcc987f8c92e2 Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Mon, 24 Mar 2025 23:11:27 -0500 Subject: [PATCH 06/23] Split error.hpp into source/header and simplify implementation --- CMakeLists.txt | 1 + src/utils/error.cpp | 54 +++++++++++++++++++++++++++++++++++++++ src/utils/error.hpp | 61 ++++++++------------------------------------- 3 files changed, 65 insertions(+), 51 deletions(-) create mode 100644 src/utils/error.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e6d1150..29df436c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -144,6 +144,7 @@ target_sources( src/unwind/unwind_with_nothing.cpp src/unwind/unwind_with_unwind.cpp src/unwind/unwind_with_winapi.cpp + src/utils/error.cpp src/utils/microfmt.cpp src/utils/utils.cpp src/platform/dbghelp_utils.cpp diff --git a/src/utils/error.cpp b/src/utils/error.cpp new file mode 100644 index 00000000..1f00b756 --- /dev/null +++ b/src/utils/error.cpp @@ -0,0 +1,54 @@ +#include "utils/error.hpp" + +namespace cpptrace { +namespace detail { + internal_error::internal_error(std::string message) : msg("Cpptrace internal error: " + std::move(message)) {} + + constexpr const char* assert_actions[] = {"assertion", "verification", "panic"}; + constexpr const char* assert_names[] = {"ASSERT", "VERIFY", "PANIC"}; + + void assert_fail( + assert_type type, + const char* expression, + const char* signature, + source_location location, + const char* message + ) { + const char* action = assert_actions[static_cast::type>(type)]; + const char* name = assert_names[static_cast::type>(type)]; + if(message == nullptr) { + throw internal_error( + "Cpptrace {} failed at {}:{}: {}\n" + " {}({});\n", + action, location.file, location.line, signature, + name, expression + ); + } else { + throw internal_error( + "Cpptrace {} failed at {}:{}: {}: {}\n" + " {}({});\n", + action, location.file, location.line, signature, message, + name, expression + ); + } + } + + void panic( + const char* signature, + source_location location, + const std::string& message + ) { + if(message == "") { + throw internal_error( + "Cpptrace panic {}:{}: {}\n", + location.file, location.line, signature + ); + } else { + throw internal_error( + "Cpptrace panic {}:{}: {}: {}\n", + location.file, location.line, signature, message.c_str() + ); + } + } +} +} diff --git a/src/utils/error.hpp b/src/utils/error.hpp index 02480b15..1eb496ec 100644 --- a/src/utils/error.hpp +++ b/src/utils/error.hpp @@ -19,7 +19,7 @@ namespace detail { class internal_error : public std::exception { std::string msg; public: - internal_error(std::string message) : msg("Cpptrace internal error: " + std::move(message)) {} + internal_error(std::string message); template internal_error(const char* format, Args&&... args) : internal_error(microfmt::format(format, args...)) {} const char* what() const noexcept override { @@ -45,52 +45,19 @@ namespace detail { panic, }; - constexpr const char* assert_actions[] = {"assertion", "verification", "panic"}; - constexpr const char* assert_names[] = {"ASSERT", "VERIFY", "PANIC"}; - - [[noreturn]] inline void assert_fail( + [[noreturn]] void assert_fail( assert_type type, const char* expression, const char* signature, source_location location, const char* message - ) { - const char* action = assert_actions[static_cast::type>(type)]; - const char* name = assert_names[static_cast::type>(type)]; - if(message == nullptr) { - throw internal_error( - "Cpptrace {} failed at {}:{}: {}\n" - " {}({});\n", - action, location.file, location.line, signature, - name, expression - ); - } else { - throw internal_error( - "Cpptrace {} failed at {}:{}: {}: {}\n" - " {}({});\n", - action, location.file, location.line, signature, message, - name, expression - ); - } - } + ); - [[noreturn]] inline void panic( + [[noreturn]] void panic( const char* signature, source_location location, const std::string& message = "" - ) { - if(message == "") { - throw internal_error( - "Cpptrace panic {}:{}: {}\n", - location.file, location.line, signature - ); - } else { - throw internal_error( - "Cpptrace panic {}:{}: {}: {}\n", - location.file, location.line, signature, message.c_str() - ); - } - } + ); template void nullfn() { @@ -99,12 +66,6 @@ namespace detail { #define PHONY_USE(...) (nullfn()) - // Work around a compiler warning - template - bool as_bool(T&& value) { - return static_cast(std::forward(value)); - } - // Work around a compiler warning template std::string as_string(T&& value) { @@ -118,23 +79,21 @@ namespace detail { // Check condition in both debug and release. std::runtime_error on failure. #define PANIC(...) ((::cpptrace::detail::panic)(CPPTRACE_PFUNC, CPPTRACE_CURRENT_LOCATION, ::cpptrace::detail::as_string(__VA_ARGS__))) - template - void assert_impl( - T condition, + inline void assert_impl( + bool condition, const char* message, assert_type type, const char* args, const char* signature, source_location location ) { - if(!as_bool(condition)) { + if(!condition) { assert_fail(type, args, signature, location, message); } } - template - void assert_impl( - T condition, + inline void assert_impl( + bool condition, assert_type type, const char* args, const char* signature, From 1333c22df34855115b8d4e25f3c96aaaf684a348 Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Mon, 24 Mar 2025 23:21:12 -0500 Subject: [PATCH 07/23] Export details::asser_fail for tests --- src/utils/error.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/utils/error.hpp b/src/utils/error.hpp index 1eb496ec..e7780683 100644 --- a/src/utils/error.hpp +++ b/src/utils/error.hpp @@ -8,6 +8,8 @@ #include "platform/platform.hpp" #include "utils/microfmt.hpp" +#include + #if IS_MSVC #define CPPTRACE_PFUNC __FUNCSIG__ #else @@ -45,7 +47,7 @@ namespace detail { panic, }; - [[noreturn]] void assert_fail( + CPPTRACE_EXPORT [[noreturn]] void assert_fail( assert_type type, const char* expression, const char* signature, From dc3bfcb9fe7f38a5cd80e701ace132b52a8e869f Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Mon, 24 Mar 2025 23:34:54 -0500 Subject: [PATCH 08/23] Appease clang --- src/utils/error.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/error.hpp b/src/utils/error.hpp index e7780683..9b476f5d 100644 --- a/src/utils/error.hpp +++ b/src/utils/error.hpp @@ -47,7 +47,7 @@ namespace detail { panic, }; - CPPTRACE_EXPORT [[noreturn]] void assert_fail( + [[noreturn]] CPPTRACE_EXPORT void assert_fail( assert_type type, const char* expression, const char* signature, From f27ade677288dfcffed8fd26523f5e76c83c234e Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Tue, 25 Mar 2025 00:02:57 -0500 Subject: [PATCH 09/23] Add a string_view and cstring_view impelementation and convert most uses of const std::string& to use these --- CMakeLists.txt | 1 + src/binary/elf.cpp | 8 +- src/binary/elf.hpp | 4 +- src/binary/mach-o.cpp | 6 +- src/binary/mach-o.hpp | 8 +- src/binary/pe.cpp | 4 +- src/binary/pe.hpp | 2 +- src/ctrace.cpp | 14 +-- src/platform/path.hpp | 5 +- src/symbols/dwarf/debug_map_resolver.cpp | 2 +- src/symbols/dwarf/dwarf_resolver.cpp | 4 +- src/symbols/dwarf/resolver.hpp | 7 +- src/utils/error.cpp | 4 +- src/utils/error.hpp | 3 +- src/utils/microfmt.hpp | 4 + src/utils/string_view.cpp | 48 ++++++++ src/utils/string_view.hpp | 142 +++++++++++++++++++++++ src/utils/utils.cpp | 3 +- src/utils/utils.hpp | 10 +- tools/symbol_tables/main.cpp | 2 +- 20 files changed, 238 insertions(+), 43 deletions(-) create mode 100644 src/utils/string_view.cpp create mode 100644 src/utils/string_view.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 29df436c..1f9318eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -146,6 +146,7 @@ target_sources( src/unwind/unwind_with_winapi.cpp src/utils/error.cpp src/utils/microfmt.cpp + src/utils/string_view.cpp src/utils/utils.cpp src/platform/dbghelp_utils.cpp ) diff --git a/src/binary/elf.cpp b/src/binary/elf.cpp index 1117f13c..0b2e8e5f 100644 --- a/src/binary/elf.cpp +++ b/src/binary/elf.cpp @@ -17,12 +17,12 @@ namespace cpptrace { namespace detail { elf::elf( file_wrapper file, - const std::string& object_path, + cstring_view object_path, bool is_little_endian, bool is_64 ) : file(std::move(file)), object_path(object_path), is_little_endian(is_little_endian), is_64(is_64) {} - Result elf::open_elf(const std::string& object_path) { + Result elf::open_elf(cstring_view object_path) { auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter); if(file == nullptr) { return internal_error("Unable to read object file {}", object_path); @@ -33,7 +33,7 @@ namespace detail { return std::move(magic).unwrap_error(); } if(magic.unwrap_value() != (std::array{0x7F, 'E', 'L', 'F'})) { - return internal_error("File is not ELF " + object_path); + return internal_error("File is not ELF {}", object_path); } auto ei_class = load_bytes(file, 4); if(ei_class.is_error()) { @@ -50,7 +50,7 @@ namespace detail { return std::move(ei_version).unwrap_error(); } if(ei_version.unwrap_value() != 1) { - return internal_error("Unexpected ELF version " + object_path); + return internal_error("Unexpected ELF version {}", object_path); } return elf(std::move(file), object_path, is_little_endian, is_64); } diff --git a/src/binary/elf.hpp b/src/binary/elf.hpp index ffe9d92d..ce528160 100644 --- a/src/binary/elf.hpp +++ b/src/binary/elf.hpp @@ -68,10 +68,10 @@ namespace detail { bool did_load_dynamic_symtab = false; optional dynamic_symtab; - elf(file_wrapper file, const std::string& object_path, bool is_little_endian, bool is_64); + elf(file_wrapper file, cstring_view object_path, bool is_little_endian, bool is_64); public: - static NODISCARD Result open_elf(const std::string& object_path); + static NODISCARD Result open_elf(cstring_view object_path); elf(elf&&) = default; diff --git a/src/binary/mach-o.cpp b/src/binary/mach-o.cpp index 12efa05d..b8333b9b 100644 --- a/src/binary/mach-o.cpp +++ b/src/binary/mach-o.cpp @@ -44,7 +44,7 @@ namespace detail { } } - bool file_is_mach_o(const std::string& object_path) noexcept { + bool file_is_mach_o(cstring_view object_path) noexcept { auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter); if(file == nullptr) { return false; @@ -122,7 +122,7 @@ namespace detail { } } - Result mach_o::open_mach_o(const std::string& object_path) { + Result mach_o::open_mach_o(cstring_view object_path) { auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter); if(file == nullptr) { return internal_error("Unable to read object file {}", object_path); @@ -658,7 +658,7 @@ namespace detail { return should_swap_bytes(magic); } - Result macho_is_fat(const std::string& object_path) { + Result macho_is_fat(cstring_view object_path) { auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter); if(file == nullptr) { return internal_error("Unable to read object file {}", object_path); diff --git a/src/binary/mach-o.hpp b/src/binary/mach-o.hpp index 8b3c59e5..a4b6e52a 100644 --- a/src/binary/mach-o.hpp +++ b/src/binary/mach-o.hpp @@ -19,7 +19,7 @@ namespace cpptrace { namespace detail { - bool file_is_mach_o(const std::string& object_path) noexcept; + bool file_is_mach_o(cstring_view object_path) noexcept; struct load_command_entry { std::uint32_t file_offset; @@ -75,7 +75,7 @@ namespace detail { mach_o( file_wrapper file, - const std::string& object_path, + cstring_view object_path, std::uint32_t magic ) : file(std::move(file)), @@ -85,7 +85,7 @@ namespace detail { Result load(); public: - static NODISCARD Result open_mach_o(const std::string& object_path); + static NODISCARD Result open_mach_o(cstring_view object_path); mach_o(mach_o&&) = default; ~mach_o() = default; @@ -136,7 +136,7 @@ namespace detail { bool should_swap() const; }; - Result macho_is_fat(const std::string& object_path); + Result macho_is_fat(cstring_view object_path); NODISCARD Result, internal_error> open_mach_o_cached(const std::string& object_path); } diff --git a/src/binary/pe.cpp b/src/binary/pe.cpp index dc0b728a..1a6b910a 100644 --- a/src/binary/pe.cpp +++ b/src/binary/pe.cpp @@ -27,7 +27,7 @@ namespace detail { } } - Result pe_get_module_image_base(const std::string& object_path) { + Result pe_get_module_image_base(cstring_view object_path) { // https://drive.google.com/file/d/0B3_wGJkuWLytbnIxY1J5WUs4MEk/view?pli=1&resourcekey=0-n5zZ2UW39xVTH8ZSu6C2aQ // https://0xrick.github.io/win-internals/pe3/ // Endianness should always be little for dos and pe headers @@ -71,7 +71,7 @@ namespace detail { WORD optional_header_magic = pe_byteswap_if_needed(optional_header_magic_raw.unwrap_value()); VERIFY( optional_header_magic == IMAGE_NT_OPTIONAL_HDR_MAGIC, - ("PE file does not match expected bit-mode " + object_path).c_str() + ("PE file does not match expected bit-mode " + std::string(object_path)).c_str() ); // finally get image base if(optional_header_magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) { diff --git a/src/binary/pe.hpp b/src/binary/pe.hpp index 051fc03a..35663efc 100644 --- a/src/binary/pe.hpp +++ b/src/binary/pe.hpp @@ -10,7 +10,7 @@ namespace cpptrace { namespace detail { - Result pe_get_module_image_base(const std::string& object_path); + Result pe_get_module_image_base(cstring_view object_path); } } diff --git a/src/ctrace.cpp b/src/ctrace.cpp index 91439a99..3bc62454 100644 --- a/src/ctrace.cpp +++ b/src/ctrace.cpp @@ -10,6 +10,7 @@ #include "utils/utils.hpp" #include "binary/object.hpp" #include "binary/safe_dl.hpp" +#include "utils/string_view.hpp" #define ESC "\033[" #define RESET ESC "0m" @@ -66,19 +67,14 @@ CTRACE_FORMAT_EPILOGUE return !str || std::char_traits::length(str) == 0; } - static ctrace_owning_string generate_owning_string(const char* raw_string) noexcept { + static ctrace_owning_string generate_owning_string(cpptrace::detail::string_view 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'; + char* new_string = new char[raw_string.size() + 1]; + std::char_traits::copy(new_string, raw_string.data(), raw_string.size()); + new_string[raw_string.size()] = '\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; diff --git a/src/platform/path.hpp b/src/platform/path.hpp index 849ad663..1c90cd15 100644 --- a/src/platform/path.hpp +++ b/src/platform/path.hpp @@ -2,6 +2,7 @@ #define PATH_HPP #include "platform/platform.hpp" +#include "utils/string_view.hpp" #include #include @@ -17,7 +18,7 @@ namespace cpptrace { namespace detail { #if IS_WINDOWS constexpr char PATH_SEP = '\\'; - inline bool is_absolute(const std::string& path) { + inline bool is_absolute(string_view path) { // I don't want to bring in shlwapi as a dependency just for PathIsRelativeA so I'm following the guidance of // https://stackoverflow.com/a/71941552/15675011 and // https://github.com/wine-mirror/wine/blob/b210a204137dec8d2126ca909d762454fd47e963/dlls/kernelbase/path.c#L982 @@ -34,7 +35,7 @@ namespace detail { } #else constexpr char PATH_SEP = '/'; - inline bool is_absolute(const std::string& path) { + inline bool is_absolute(string_view path) { if(path.empty()) { return false; } diff --git a/src/symbols/dwarf/debug_map_resolver.cpp b/src/symbols/dwarf/debug_map_resolver.cpp index a7bdbae3..4d8f4bc0 100644 --- a/src/symbols/dwarf/debug_map_resolver.cpp +++ b/src/symbols/dwarf/debug_map_resolver.cpp @@ -197,7 +197,7 @@ namespace libdwarf { }; }; - std::unique_ptr make_debug_map_resolver(const std::string& object_path) { + std::unique_ptr make_debug_map_resolver(cstring_view object_path) { return detail::make_unique(object_path); } #endif diff --git a/src/symbols/dwarf/dwarf_resolver.cpp b/src/symbols/dwarf/dwarf_resolver.cpp index 782ae433..b77bfd30 100644 --- a/src/symbols/dwarf/dwarf_resolver.cpp +++ b/src/symbols/dwarf/dwarf_resolver.cpp @@ -103,7 +103,7 @@ namespace libdwarf { public: CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING - dwarf_resolver(const std::string& object_path_, optional split_ = nullopt) + dwarf_resolver(cstring_view object_path_, optional split_ = nullopt) : object_path(object_path_), skeleton(std::move(split_)) { @@ -1009,7 +1009,7 @@ namespace libdwarf { } }; - std::unique_ptr make_dwarf_resolver(const std::string& object_path) { + std::unique_ptr make_dwarf_resolver(cstring_view object_path) { return detail::make_unique(object_path); } } diff --git a/src/symbols/dwarf/resolver.hpp b/src/symbols/dwarf/resolver.hpp index d366ac5b..5c663861 100644 --- a/src/symbols/dwarf/resolver.hpp +++ b/src/symbols/dwarf/resolver.hpp @@ -4,6 +4,7 @@ #include #include "symbols/symbols.hpp" #include "platform/platform.hpp" +#include "utils/string_view.hpp" #include @@ -26,7 +27,7 @@ namespace libdwarf { class null_resolver : public symbol_resolver { public: null_resolver() = default; - null_resolver(const std::string&) {} + null_resolver(cstring_view) {} CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING frame_with_inlines resolve_frame(const object_frame& frame_info) override { @@ -45,9 +46,9 @@ namespace libdwarf { }; }; - std::unique_ptr make_dwarf_resolver(const std::string& object_path); + std::unique_ptr make_dwarf_resolver(cstring_view object_path); #if IS_APPLE - std::unique_ptr make_debug_map_resolver(const std::string& object_path); + std::unique_ptr make_debug_map_resolver(cstring_view object_path); #endif } } diff --git a/src/utils/error.cpp b/src/utils/error.cpp index 1f00b756..23ffb04f 100644 --- a/src/utils/error.cpp +++ b/src/utils/error.cpp @@ -36,7 +36,7 @@ namespace detail { void panic( const char* signature, source_location location, - const std::string& message + string_view message ) { if(message == "") { throw internal_error( @@ -46,7 +46,7 @@ namespace detail { } else { throw internal_error( "Cpptrace panic {}:{}: {}: {}\n", - location.file, location.line, signature, message.c_str() + location.file, location.line, signature, message ); } } diff --git a/src/utils/error.hpp b/src/utils/error.hpp index 9b476f5d..5dea81de 100644 --- a/src/utils/error.hpp +++ b/src/utils/error.hpp @@ -7,6 +7,7 @@ #include "platform/platform.hpp" #include "utils/microfmt.hpp" +#include "utils/string_view.hpp" #include @@ -58,7 +59,7 @@ namespace detail { [[noreturn]] void panic( const char* signature, source_location location, - const std::string& message = "" + string_view message = "" ); template diff --git a/src/utils/microfmt.hpp b/src/utils/microfmt.hpp index 27bb1e96..4dd2d70c 100644 --- a/src/utils/microfmt.hpp +++ b/src/utils/microfmt.hpp @@ -15,6 +15,8 @@ #include #endif +#include "utils/string_view.hpp" + // https://github.com/jeremy-rifkin/microfmt // Format: {[align][width][:[fill][base]]} # width: number or {} @@ -139,6 +141,8 @@ namespace microfmt { format_value(std::string_view sv) : string_view_value{sv.data(), sv.size()}, value(value_type::string_view_value) {} #endif + format_value(cpptrace::detail::string_view sv) + : string_view_value{sv.data(), sv.size()}, value(value_type::string_view_value) {} format_value(const char* c_string) : c_string_value(c_string), value(value_type::c_string_value) {} int unwrap_int() const { diff --git a/src/utils/string_view.cpp b/src/utils/string_view.cpp new file mode 100644 index 00000000..3042c6e9 --- /dev/null +++ b/src/utils/string_view.cpp @@ -0,0 +1,48 @@ +#include "utils/string_view.hpp" + +#include "utils/error.hpp" +#include "utils/microfmt.hpp" + +#include +#include + +namespace cpptrace { +namespace detail { + char string_view::operator[](size_t i) const { + ASSERT(i < size()); + return ptr[i]; + } + char string_view::at(size_t i) const { + if(i >= size()) { + throw std::runtime_error(microfmt::format("Out of bounds access {} >= {}", i, size())); + } + return ptr[i]; + } + + std::size_t string_view::find_last_of(string_view chars) const { + if(empty() || chars.empty()) { + return npos; + } + std::size_t pos = size(); + while(pos-- > 0) { + if(std::find(chars.begin(), chars.end(), ptr[pos]) != chars.end()) { + return pos; + } + } + return npos; + } + + bool operator==(string_view a, string_view b) { + return a.size() == b.size() && std::memcmp(a.data(), b.data(), a.size()); + } + + cstring_view cstring_view::substr(std::size_t pos) const { + ASSERT(pos <= count); + return {ptr + pos, count - pos}; + } + + void cstring_view::check_null() const { + ASSERT(ptr[count] == 0); + } +} +} diff --git a/src/utils/string_view.hpp b/src/utils/string_view.hpp new file mode 100644 index 00000000..7d77c85e --- /dev/null +++ b/src/utils/string_view.hpp @@ -0,0 +1,142 @@ +#ifndef STRING_VIEW_HPP +#define STRING_VIEW_HPP + +#include +#include +#include +#include + +#include + +namespace cpptrace { +namespace detail { + // Simple string view implementations + // I haven't implemented all members because I don't need most of them currently, more may be added as needed + // members exported for tests + + class string_view { + const char* ptr; + std::size_t count; + + public: + using traits_type = std::char_traits; + using value_type = char; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using pointer = char*; + using const_pointer = const char*; + using reference = char&; + using const_reference = const char&; + using iterator = char*; + using const_iterator = const char*; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + static constexpr std::size_t npos = std::string::npos; + + string_view() : ptr(nullptr), count(0) {} + string_view(const char* str) : ptr(str), count(std::strlen(str)) {} + string_view(const std::string& str) : ptr(str.c_str()), count(str.size()) {} + string_view(const char* ptr, std::size_t count) : ptr(ptr), count(count) {} + + explicit operator std::string() { + return std::string(ptr, ptr + count); + } + + const char* data() const noexcept { + return ptr; + } + std::size_t size() const noexcept { + return count; + } + bool empty() const noexcept { + return count == 0; + } + + CPPTRACE_EXPORT char operator[](size_t i) const; + CPPTRACE_EXPORT char at(size_t i) const; + + CPPTRACE_EXPORT std::size_t find_last_of(string_view chars) const; + + const_iterator begin() const noexcept { + return ptr; + } + const_iterator end() const noexcept { + return ptr + count; + } + }; + + bool operator==(string_view, string_view); + + class cstring_view { + const char* ptr; + std::size_t count; + + public: + using traits_type = std::char_traits; + using value_type = char; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using pointer = char*; + using const_pointer = const char*; + using reference = char&; + using const_reference = const char&; + using iterator = char*; + using const_iterator = const char*; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + static constexpr std::size_t npos = string_view::npos; + + cstring_view() : ptr(nullptr), count(0) {} + cstring_view(const char* str) : ptr(str), count(std::strlen(str)) {} + cstring_view(const std::string& str) : ptr(str.c_str()), count(str.size()) {} + cstring_view(const char* ptr, std::size_t count) : ptr(ptr), count(count) { + check_null(); + } + + explicit operator std::string() { + return std::string(ptr, ptr + count); + } + + operator string_view() const noexcept { + return string_view(ptr, count); + } + + const char* data() const noexcept { + return ptr; + } + const char* c_str() const noexcept { + return ptr; + } + std::size_t size() const noexcept { + return count; + } + bool empty() const noexcept { + return count == 0; + } + + char operator[](size_t i) const { + return operator string_view().operator[](i); + } + char at(size_t i) const { + return operator string_view().at(i); + } + + std::size_t find_last_of(string_view chars) const { + return operator string_view().find_last_of(chars); + } + + CPPTRACE_EXPORT cstring_view substr(std::size_t pos) const; + + const_iterator begin() const noexcept { + return ptr; + } + const_iterator end() const noexcept { + return ptr + count; + } + private: + CPPTRACE_EXPORT void check_null() const; + }; +} +} + +#endif diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index 4a2061e3..4bd4aa2a 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -1,4 +1,5 @@ #include "utils/utils.hpp" +#include "utils/string_view.hpp" #if IS_WINDOWS #include @@ -46,7 +47,7 @@ namespace detail { #endif } - bool directory_exists(const std::string& path) { + bool directory_exists(cstring_view path) { #if IS_WINDOWS DWORD dwAttrib = GetFileAttributesA(path.c_str()); return dwAttrib != INVALID_FILE_ATTRIBUTES && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY); diff --git a/src/utils/utils.hpp b/src/utils/utils.hpp index afdd9f86..432d6509 100644 --- a/src/utils/utils.hpp +++ b/src/utils/utils.hpp @@ -160,15 +160,15 @@ namespace detail { } // shamelessly stolen from stackoverflow - bool directory_exists(const std::string& path); + bool directory_exists(cstring_view path); - inline std::string basename(const std::string& path, bool maybe_windows = false) { + inline std::string basename(cstring_view path, bool maybe_windows = false) { // Assumes no trailing /'s auto pos = path.find_last_of(maybe_windows ? "/\\" : "/"); - if(pos == std::string::npos) { - return path; + if(pos == cstring_view::npos) { + return std::string(path); } else { - return path.substr(pos + 1); + return std::string(path.substr(pos + 1)); } } diff --git a/tools/symbol_tables/main.cpp b/tools/symbol_tables/main.cpp index c4e4dc45..0644cb09 100644 --- a/tools/symbol_tables/main.cpp +++ b/tools/symbol_tables/main.cpp @@ -32,7 +32,7 @@ void dump_symtab_result(const Result>, i } void dump_symbols(const std::filesystem::path& path) { - auto elf_ = elf::open_elf(path); + auto elf_ = elf::open_elf(path.native()); if(!elf_) { fmt::println(stderr, "Error reading file: {}", elf_.unwrap_error().what()); } From 00215eedb42fb795721567b30d3b92fccc29dbc5 Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Tue, 25 Mar 2025 00:22:46 -0500 Subject: [PATCH 10/23] Fix for mac --- src/symbols/dwarf/debug_map_resolver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/symbols/dwarf/debug_map_resolver.cpp b/src/symbols/dwarf/debug_map_resolver.cpp index 4d8f4bc0..5ffedd94 100644 --- a/src/symbols/dwarf/debug_map_resolver.cpp +++ b/src/symbols/dwarf/debug_map_resolver.cpp @@ -107,7 +107,7 @@ namespace libdwarf { std::vector target_objects; std::vector symbols; public: - debug_map_resolver(const std::string& source_object_path) { + debug_map_resolver(cstring_view source_object_path) { // load mach-o // TODO: Cache somehow? auto mach_o_object = open_mach_o_cached(source_object_path); From f3d32eab9ac689021a461723178dc6452522c7c0 Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Tue, 25 Mar 2025 00:23:27 -0500 Subject: [PATCH 11/23] Add basic span utility --- src/utils/span.hpp | 78 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 src/utils/span.hpp diff --git a/src/utils/span.hpp b/src/utils/span.hpp new file mode 100644 index 00000000..3b1c4477 --- /dev/null +++ b/src/utils/span.hpp @@ -0,0 +1,78 @@ +#ifndef SPAN_HPP +#define SPAN_HPP + +#include +#include +#include +#include +#include + +namespace cpptrace { +namespace detail { + // basic span implementation + // I haven't implemented most members because I don't need them, more will be added as needed + + template + class span { + T* ptr; + std::size_t count; + + public: + using element_type = T; + using value_type = typename std::remove_cv::type; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; + using iterator = T*; + using const_iterator = const T*; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + span() : ptr(nullptr), count(0) {} + span(T* ptr, std::size_t count) : ptr(ptr), count(count) {} + template + span(It begin, It end) : ptr(std::addressof(*begin)), count(end - begin) {} + + T* data() const noexcept { + return ptr; + } + std::size_t size() const noexcept { + return count; + } + bool empty() const noexcept { + return count == 0; + } + + iterator begin() noexcept { + return ptr; + } + iterator end() noexcept { + return ptr + count; + } + const_iterator begin() const noexcept { + return ptr; + } + const_iterator end() const noexcept { + return ptr + count; + } + }; + + using bspan = span; + using cbspan = span; + + template + auto make_span(It begin, It end) { + return span::type>(begin, end); + } + + template::value, int>::type = 0> + auto make_bspan(T object) { + return span(reinterpret_cast(std::addressof(object)), sizeof(T)); + } +} +} + +#endif From d6d66debf5ccf48d362016c322b2bc13f14b7a4b Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Tue, 25 Mar 2025 18:07:46 -0500 Subject: [PATCH 12/23] Fix mac --- src/symbols/dwarf/debug_map_resolver.cpp | 4 ++-- src/symbols/dwarf/resolver.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/symbols/dwarf/debug_map_resolver.cpp b/src/symbols/dwarf/debug_map_resolver.cpp index 5ffedd94..a7bdbae3 100644 --- a/src/symbols/dwarf/debug_map_resolver.cpp +++ b/src/symbols/dwarf/debug_map_resolver.cpp @@ -107,7 +107,7 @@ namespace libdwarf { std::vector target_objects; std::vector symbols; public: - debug_map_resolver(cstring_view source_object_path) { + debug_map_resolver(const std::string& source_object_path) { // load mach-o // TODO: Cache somehow? auto mach_o_object = open_mach_o_cached(source_object_path); @@ -197,7 +197,7 @@ namespace libdwarf { }; }; - std::unique_ptr make_debug_map_resolver(cstring_view object_path) { + std::unique_ptr make_debug_map_resolver(const std::string& object_path) { return detail::make_unique(object_path); } #endif diff --git a/src/symbols/dwarf/resolver.hpp b/src/symbols/dwarf/resolver.hpp index 5c663861..161c2fe7 100644 --- a/src/symbols/dwarf/resolver.hpp +++ b/src/symbols/dwarf/resolver.hpp @@ -48,7 +48,7 @@ namespace libdwarf { std::unique_ptr make_dwarf_resolver(cstring_view object_path); #if IS_APPLE - std::unique_ptr make_debug_map_resolver(cstring_view object_path); + std::unique_ptr make_debug_map_resolver(const std::string& object_path); #endif } } From 270de57fa190f005d1fd6246576350f6803d4ac6 Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sat, 29 Mar 2025 15:44:55 -0500 Subject: [PATCH 13/23] Add preliminary JIT support (#230) Resolves #226 ![image](https://github.com/user-attachments/assets/4af17f95-fb6f-4969-8f25-950d3e7cba84) --- .gitignore | 2 + CMakeLists.txt | 3 + include/cpptrace/basic.hpp | 5 + include/cpptrace/gdb_jit.hpp | 51 +++++++++ src/binary/elf.cpp | 118 +++++++++++++------- src/binary/elf.hpp | 23 +++- src/binary/mach-o.cpp | 77 +++++++++---- src/binary/mach-o.hpp | 27 ++--- src/cpptrace.cpp | 14 +++ src/jit/jit_objects.cpp | 144 +++++++++++++++++++++++++ src/jit/jit_objects.hpp | 31 ++++++ src/symbols/symbols_core.cpp | 15 ++- src/symbols/symbols_with_addr2line.cpp | 3 + src/symbols/symbols_with_libdwarf.cpp | 53 +++++++-- src/utils/io/base_file.hpp | 38 +++++++ src/utils/io/file.cpp | 28 +++++ src/utils/io/file.hpp | 30 ++++++ src/utils/io/memory_file_view.cpp | 23 ++++ src/utils/io/memory_file_view.hpp | 26 +++++ src/utils/span.hpp | 24 ++++- src/utils/string_view.cpp | 2 +- src/utils/utils.hpp | 13 ++- test/jank/Dockerfile | 30 ++++++ test/jank/Makefile | 43 ++++++++ test/jank/entry.sh | 23 ++++ tools/symbol_tables/main.cpp | 2 +- 26 files changed, 743 insertions(+), 105 deletions(-) create mode 100644 include/cpptrace/gdb_jit.hpp create mode 100644 src/jit/jit_objects.cpp create mode 100644 src/jit/jit_objects.hpp create mode 100644 src/utils/io/base_file.hpp create mode 100644 src/utils/io/file.cpp create mode 100644 src/utils/io/file.hpp create mode 100644 src/utils/io/memory_file_view.cpp create mode 100644 src/utils/io/memory_file_view.hpp create mode 100644 test/jank/Dockerfile create mode 100644 test/jank/Makefile create mode 100644 test/jank/entry.sh diff --git a/.gitignore b/.gitignore index 9b233772..e6c19e08 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ scratch tmp/ bazel-*/ cmake-build-*/ + +test/jank/data diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f9318eb..ff4ea2c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -127,6 +127,7 @@ target_sources( src/demangle/demangle_with_cxxabi.cpp src/demangle/demangle_with_nothing.cpp src/demangle/demangle_with_winapi.cpp + src/jit/jit_objects.cpp src/snippets/snippet.cpp src/symbols/dwarf/debug_map_resolver.cpp src/symbols/dwarf/dwarf_options.cpp @@ -144,6 +145,8 @@ target_sources( src/unwind/unwind_with_nothing.cpp src/unwind/unwind_with_unwind.cpp src/unwind/unwind_with_winapi.cpp + src/utils/io/file.cpp + src/utils/io/memory_file_view.cpp src/utils/error.cpp src/utils/microfmt.cpp src/utils/string_view.cpp diff --git a/include/cpptrace/basic.hpp b/include/cpptrace/basic.hpp index 735e39ec..cea4aa14 100644 --- a/include/cpptrace/basic.hpp +++ b/include/cpptrace/basic.hpp @@ -236,6 +236,11 @@ namespace cpptrace { CPPTRACE_EXPORT void get_safe_object_frame(frame_ptr address, safe_object_frame* out); CPPTRACE_EXPORT bool can_signal_safe_unwind(); CPPTRACE_EXPORT bool can_get_safe_object_frame(); + + // JIT API + CPPTRACE_EXPORT void register_jit_object(const char*, std::size_t); + CPPTRACE_EXPORT void unregister_jit_object(const char*); + CPPTRACE_EXPORT void clear_all_jit_objects(); } #ifdef _MSC_VER diff --git a/include/cpptrace/gdb_jit.hpp b/include/cpptrace/gdb_jit.hpp new file mode 100644 index 00000000..ad921d64 --- /dev/null +++ b/include/cpptrace/gdb_jit.hpp @@ -0,0 +1,51 @@ +#ifndef CPPTRACE_GDB_JIT_HPP +#define CPPTRACE_GDB_JIT_HPP + +#include + +#include + +namespace cpptrace { + namespace detail { + // https://sourceware.org/gdb/current/onlinedocs/gdb.html/JIT-Interface.html + extern "C" { + typedef enum + { + JIT_NOACTION = 0, + JIT_REGISTER_FN, + JIT_UNREGISTER_FN + } jit_actions_t; + + struct jit_code_entry + { + struct jit_code_entry *next_entry; + struct jit_code_entry *prev_entry; + const char *symfile_addr; + uint64_t symfile_size; + }; + + struct jit_descriptor + { + uint32_t version; + /* This type should be jit_actions_t, but we use uint32_t + to be explicit about the bitwidth. */ + uint32_t action_flag; + struct jit_code_entry *relevant_entry; + struct jit_code_entry *first_entry; + }; + + extern struct jit_descriptor __jit_debug_descriptor; + } + } + + inline void register_jit_objects_from_gdb_jit_interface() { + clear_all_jit_objects(); + detail::jit_code_entry* entry = detail::__jit_debug_descriptor.first_entry; + while(entry) { + register_jit_object(entry->symfile_addr, entry->symfile_size); + entry = entry->next_entry; + } + } +} + +#endif diff --git a/src/binary/elf.cpp b/src/binary/elf.cpp index 0b2e8e5f..1f2a240d 100644 --- a/src/binary/elf.cpp +++ b/src/binary/elf.cpp @@ -1,5 +1,11 @@ #include "binary/elf.hpp" + +#include "utils/error.hpp" +#include "utils/io/base_file.hpp" +#include "utils/io/memory_file_view.hpp" #include "utils/optional.hpp" +#include "utils/io/file.hpp" +#include "utils/string_view.hpp" #if IS_LINUX @@ -16,43 +22,51 @@ namespace cpptrace { namespace detail { elf::elf( - file_wrapper file, - cstring_view object_path, + std::unique_ptr file, bool is_little_endian, bool is_64 - ) : file(std::move(file)), object_path(object_path), is_little_endian(is_little_endian), is_64(is_64) {} + ) : file(std::move(file)), is_little_endian(is_little_endian), is_64(is_64) {} - Result elf::open_elf(cstring_view object_path) { - auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter); - if(file == nullptr) { - return internal_error("Unable to read object file {}", object_path); - } + Result elf::open(std::unique_ptr file) { // Initial checks/metadata - auto magic = load_bytes>(file, 0); + auto magic = file->read>(0); if(magic.is_error()) { return std::move(magic).unwrap_error(); } if(magic.unwrap_value() != (std::array{0x7F, 'E', 'L', 'F'})) { - return internal_error("File is not ELF {}", object_path); + return internal_error("File is not ELF {}", file->path()); } - auto ei_class = load_bytes(file, 4); + auto ei_class = file->read(4); if(ei_class.is_error()) { return std::move(ei_class).unwrap_error(); } bool is_64 = ei_class.unwrap_value() == 2; - auto ei_data = load_bytes(file, 5); + auto ei_data = file->read(5); if(ei_data.is_error()) { return std::move(ei_data).unwrap_error(); } bool is_little_endian = ei_data.unwrap_value() == 1; - auto ei_version = load_bytes(file, 6); + auto ei_version = file->read(6); if(ei_version.is_error()) { return std::move(ei_version).unwrap_error(); } if(ei_version.unwrap_value() != 1) { - return internal_error("Unexpected ELF version {}", object_path); + return internal_error("Unexpected ELF version {}", file->path()); } - return elf(std::move(file), object_path, is_little_endian, is_64); + return elf(std::move(file), is_little_endian, is_64); + } + + Result elf::open(cstring_view object_path) { + auto file_res = file::open(object_path); + if(!file_res) { + return internal_error("Unable to read object file {}", object_path); + } + auto& file = file_res.unwrap_value(); + return open(make_unique(std::move(file))); + } + + Result elf::open(cbspan object) { + return open(make_unique(object)); } Result elf::get_module_image_base() { @@ -77,7 +91,7 @@ namespace detail { // Should be somewhat reliable https://stackoverflow.com/q/61568612/15675011 // It should occur at the beginning but may as well loop just in case for(unsigned i = 0; i < header_info.e_phnum; i++) { - auto loaded_ph = load_bytes(file, header_info.e_phoff + header_info.e_phentsize * i); + auto loaded_ph = file->read(header_info.e_phoff + header_info.e_phentsize * i); if(loaded_ph.is_error()) { return std::move(loaded_ph).unwrap_error(); } @@ -135,6 +149,31 @@ namespace detail { return nullopt; } + Result, internal_error> elf::get_pc_ranges() { + std::vector vec; + auto header_info_ = get_header_info(); + if(header_info_.is_error()) { + return header_info_.unwrap_error(); + } + auto& header_info = header_info_.unwrap_value(); + auto strtab_ = get_strtab(header_info.e_shstrndx); + if(strtab_.is_error()) { + return strtab_.unwrap_error(); + } + auto& strtab = strtab_.unwrap_value(); + auto sections_res = get_sections(); + if(!sections_res) { + return sections_res.unwrap_error(); + } + const auto& sections = sections_res.unwrap_value(); + for(const auto& section : sections) { + if(string_view(strtab.data() + section.sh_name) == ".text") { + vec.push_back(pc_range{section.sh_addr, section.sh_addr + section.sh_size}); + } + } + return vec; + } + Result>, internal_error> elf::get_symtab_entries() { return resolve_symtab_entries(get_symtab()); } @@ -187,7 +226,7 @@ namespace detail { return std::ref(header.unwrap()); } if(tried_to_load_header) { - return internal_error("previous header load failed " + object_path); + return internal_error("previous header load failed {}", file->path()); } tried_to_load_header = true; if(is_64) { @@ -201,13 +240,13 @@ namespace detail { Result elf::get_header_info_impl() { static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument"); using Header = typename std::conditional::type; - auto loaded_header = load_bytes
(file, 0); + auto loaded_header = file->read
(0); if(loaded_header.is_error()) { return std::move(loaded_header).unwrap_error(); } const Header& file_header = loaded_header.unwrap_value(); if(file_header.e_ehsize != sizeof(Header)) { - return internal_error("ELF file header size mismatch" + object_path); + return internal_error("ELF file header size mismatch {}", file->path()); } header_info info; info.e_phoff = byteswap_if_needed(file_header.e_phoff); @@ -216,6 +255,7 @@ namespace detail { info.e_shoff = byteswap_if_needed(file_header.e_shoff); info.e_shnum = byteswap_if_needed(file_header.e_shnum); info.e_shentsize = byteswap_if_needed(file_header.e_shentsize); + info.e_shstrndx = byteswap_if_needed(file_header.e_shstrndx); header = info; return header.unwrap(); } @@ -225,7 +265,7 @@ namespace detail { return sections; } if(tried_to_load_sections) { - return internal_error("previous sections load failed " + object_path); + return internal_error("previous sections load failed {}", file->path()); } tried_to_load_sections = true; if(is_64) { @@ -245,12 +285,13 @@ namespace detail { } const auto& header_info = header.unwrap_value(); for(unsigned i = 0; i < header_info.e_shnum; i++) { - auto loaded_sh = load_bytes(file, header_info.e_shoff + header_info.e_shentsize * i); + auto loaded_sh = file->read(header_info.e_shoff + header_info.e_shentsize * i); if(loaded_sh.is_error()) { return std::move(loaded_sh).unwrap_error(); } const SHeader& section_header = loaded_sh.unwrap_value(); section_info info; + info.sh_name = byteswap_if_needed(section_header.sh_name); info.sh_type = byteswap_if_needed(section_header.sh_type); info.sh_addr = byteswap_if_needed(section_header.sh_addr); info.sh_offset = byteswap_if_needed(section_header.sh_offset); @@ -273,7 +314,7 @@ namespace detail { return entry.data; } if(entry.tried_to_load_strtab) { - return internal_error("previous strtab load failed {}", object_path); + return internal_error("previous strtab load failed {}", file->path()); } } entry.tried_to_load_strtab = true; @@ -287,14 +328,12 @@ namespace detail { } const auto& section = sections[index]; if(section.sh_type != SHT_STRTAB) { - return internal_error("requested strtab section not a strtab (requested {} of {})", index, object_path); + return internal_error("requested strtab section not a strtab (requested {} of {})", index, file->path()); } entry.data.resize(section.sh_size + 1); - if(std::fseek(file, section.sh_offset, SEEK_SET) != 0) { - return internal_error("fseek error while loading elf string table"); - } - if(std::fread(entry.data.data(), sizeof(char), section.sh_size, file) != section.sh_size) { - return internal_error("fread error while loading elf string table"); + auto read_res = file->read_bytes(span{entry.data.data(), section.sh_size}, section.sh_offset); + if(!read_res) { + return read_res.unwrap_error(); } entry.data[section.sh_size] = 0; // just out of an abundance of caution entry.did_load_strtab = true; @@ -306,7 +345,7 @@ namespace detail { return symtab; } if(tried_to_load_symtab) { - return internal_error("previous symtab load failed {}", object_path); + return internal_error("previous symtab load failed {}", file->path()); } tried_to_load_symtab = true; if(is_64) { @@ -335,7 +374,7 @@ namespace detail { return dynamic_symtab; } if(tried_to_load_dynamic_symtab) { - return internal_error("previous dynamic symtab load failed {}", object_path); + return internal_error("previous dynamic symtab load failed {}", file->path()); } tried_to_load_dynamic_symtab = true; if(is_64) { @@ -375,17 +414,15 @@ namespace detail { for(const auto& section : sections) { if(section.sh_type == (dynamic ? SHT_DYNSYM : SHT_SYMTAB)) { if(section.sh_entsize != sizeof(SymEntry)) { - return internal_error("elf seems corrupted, sym entry mismatch {}", object_path); + return internal_error("elf seems corrupted, sym entry mismatch {}", file->path()); } if(section.sh_size % section.sh_entsize != 0) { - return internal_error("elf seems corrupted, sym entry vs section size mismatch {}", object_path); + return internal_error("elf seems corrupted, sym entry vs section size mismatch {}", file->path()); } std::vector buffer(section.sh_size / section.sh_entsize); - if(std::fseek(file, section.sh_offset, SEEK_SET) != 0) { - return internal_error("fseek error while loading elf symbol table"); - } - if(std::fread(buffer.data(), section.sh_entsize, buffer.size(), file) != buffer.size()) { - return internal_error("fread error while loading elf symbol table"); + auto res = file->read_span(make_span(buffer.begin(), buffer.end()), section.sh_offset); + if(!res) { + return res.unwrap_error(); } symbol_table = symtab_info{}; symbol_table.unwrap().entries.reserve(buffer.size()); @@ -414,8 +451,11 @@ namespace detail { } Result, internal_error> open_elf_cached(const std::string& object_path) { + if(object_path.empty()) { + return internal_error{"empty object_path"}; + } if(get_cache_mode() == cache_mode::prioritize_memory) { - return elf::open_elf(object_path) + return elf::open(object_path) .transform([](elf&& obj) { return maybe_owned{detail::make_unique(std::move(obj))}; }); } else { std::mutex m; @@ -424,7 +464,7 @@ namespace detail { static std::unordered_map> cache; auto it = cache.find(object_path); if(it == cache.end()) { - auto res = cache.emplace(object_path, elf::open_elf(object_path)); + auto res = cache.emplace(object_path, elf::open(object_path)); VERIFY(res.second); it = res.first; } diff --git a/src/binary/elf.hpp b/src/binary/elf.hpp index ce528160..b6aef4d6 100644 --- a/src/binary/elf.hpp +++ b/src/binary/elf.hpp @@ -1,7 +1,10 @@ #ifndef ELF_HPP #define ELF_HPP +#include "cpptrace/forward.hpp" #include "utils/common.hpp" +#include "utils/io/base_file.hpp" +#include "utils/span.hpp" #include "utils/utils.hpp" #if IS_LINUX @@ -12,9 +15,9 @@ namespace cpptrace { namespace detail { + // TODO: make methods const and a bunch of members mutable class elf { - file_wrapper file; - std::string object_path; + std::unique_ptr file; bool is_little_endian; bool is_64; @@ -25,11 +28,13 @@ namespace detail { uint64_t e_shoff; uint32_t e_shnum; uint32_t e_shentsize; + uint16_t e_shstrndx; }; bool tried_to_load_header = false; optional header; struct section_info { + uint32_t sh_name; uint32_t sh_type; uint64_t sh_addr; uint64_t sh_offset; @@ -68,10 +73,13 @@ namespace detail { bool did_load_dynamic_symtab = false; optional dynamic_symtab; - elf(file_wrapper file, cstring_view object_path, bool is_little_endian, bool is_64); + elf(std::unique_ptr file, bool is_little_endian, bool is_64); + + static NODISCARD Result open(std::unique_ptr file); public: - static NODISCARD Result open_elf(cstring_view object_path); + static NODISCARD Result open(cstring_view object_path); + static NODISCARD Result open(cbspan object); elf(elf&&) = default; @@ -87,6 +95,13 @@ namespace detail { optional lookup_symbol(frame_ptr pc, const optional& maybe_symtab); public: + struct pc_range { + frame_ptr low; + frame_ptr high; // not inclusive + }; + // for in-memory JIT elves + Result, internal_error> get_pc_ranges(); + struct symbol_entry { std::string st_name; uint16_t st_shndx; diff --git a/src/binary/mach-o.cpp b/src/binary/mach-o.cpp index b8333b9b..871ee17b 100644 --- a/src/binary/mach-o.cpp +++ b/src/binary/mach-o.cpp @@ -2,6 +2,8 @@ #include "utils/common.hpp" #include "utils/utils.hpp" +#include "utils/io/file.hpp" +#include "utils/io/memory_file_view.hpp" #if IS_APPLE @@ -122,19 +124,15 @@ namespace detail { } } - Result mach_o::open_mach_o(cstring_view object_path) { - auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter); - if(file == nullptr) { - return internal_error("Unable to read object file {}", object_path); - } - auto magic = load_bytes(file, 0); + Result mach_o::open(std::unique_ptr file) { + auto magic = file->read(0); if(!magic) { return magic.unwrap_error(); } if(!is_mach_o(magic.unwrap_value())) { - return internal_error("File is not mach-o {}", object_path); + return internal_error("File is not mach-o {}", file->path()); } - mach_o obj(std::move(file), object_path, magic.unwrap_value()); + mach_o obj(std::move(file), magic.unwrap_value()); auto result = obj.load(); if(result.is_error()) { return result.unwrap_error(); @@ -143,6 +141,19 @@ namespace detail { } } + Result mach_o::open(cstring_view object_path) { + auto file_res = file::open(object_path); + if(!file_res) { + return internal_error("Unable to read object file {}", object_path); + } + auto& file = file_res.unwrap_value(); + return open(make_unique(std::move(file))); + } + + Result mach_o::open(cbspan object) { + return open(make_unique(object)); + } + Result mach_o::get_text_vmaddr() { for(const auto& command : load_commands) { if(command.cmd == LC_SEGMENT_64 || command.cmd == LC_SEGMENT) { @@ -193,6 +204,25 @@ namespace detail { } } + Result, internal_error> mach_o::get_pc_ranges() { + std::vector ranges; + for(const auto& command : load_commands) { + if(command.cmd == LC_SEGMENT_64 || command.cmd == LC_SEGMENT) { + auto segment_res = command.cmd == LC_SEGMENT_64 + ? load_segment_command<64>(command.file_offset) + : load_segment_command<32>(command.file_offset); + if(segment_res.is_error()) { + return std::move(segment_res).unwrap_error(); + } + auto& segment = segment_res.unwrap_value(); + if(std::strcmp(segment.segname, "__TEXT") == 0) { + ranges.push_back({segment.vmaddr, segment.vmaddr + segment.vmsize}); + } + } + } + return ranges; + } + Result>, internal_error> mach_o::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 @@ -465,7 +495,7 @@ namespace detail { bits = Bits; using Mach_Header = typename std::conditional::type; std::size_t header_size = sizeof(Mach_Header); - auto load_header = load_bytes(file, load_base); + auto load_header = file->read(load_base); if(!load_header) { return load_header.unwrap_error(); } @@ -486,7 +516,7 @@ namespace detail { // iterate load commands std::uint32_t actual_offset = load_commands_offset; for(std::uint32_t i = 0; i < ncmds; i++) { - auto load_cmd = load_bytes(file, actual_offset); + auto load_cmd = file->read(actual_offset); if(!load_cmd) { return load_cmd.unwrap_error(); } @@ -503,7 +533,7 @@ namespace detail { Result mach_o::load_fat_mach() { std::size_t header_size = sizeof(fat_header); std::size_t arch_size = sizeof(fat_arch); - auto load_header = load_bytes(file, 0); + auto load_header = file->read(0); if(!load_header) { return load_header.unwrap_error(); } @@ -541,7 +571,7 @@ namespace detail { 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++) { - auto load_arch = load_bytes(file, arch_offset); + auto load_arch = file->read(arch_offset); if(!load_arch) { return load_arch.unwrap_error(); } @@ -561,7 +591,7 @@ namespace detail { ); if(best) { off_t mach_header_offset = (off_t)best->offset; - auto magic = load_bytes(file, mach_header_offset); + auto magic = file->read(mach_header_offset); if(!magic) { return magic.unwrap_error(); } @@ -581,7 +611,7 @@ namespace detail { template Result mach_o::load_segment_command(std::uint32_t offset) const { using Segment_Command = typename std::conditional::type; - auto load_segment = load_bytes(file, offset); + auto load_segment = file->read(offset); if(!load_segment) { return load_segment.unwrap_error(); } @@ -608,7 +638,7 @@ namespace detail { } Result mach_o::load_symbol_table_command(std::uint32_t offset) const { - auto load_symtab = load_bytes(file, offset); + auto load_symtab = file->read(offset); if(!load_symtab) { return load_symtab.unwrap_error(); } @@ -624,7 +654,7 @@ namespace detail { Result mach_o::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); - auto load_entry = load_bytes(file, offset); + auto load_entry = file->read(offset); if(!load_entry) { return load_entry.unwrap_error(); } @@ -644,11 +674,9 @@ namespace detail { Result, internal_error> mach_o::load_string_table(std::uint32_t offset, std::uint32_t byte_count) const { std::vector buffer(byte_count + 1); - if(std::fseek(file, load_base + offset, SEEK_SET) != 0) { - return internal_error("fseek error while loading mach-o symbol table"); - } - if(std::fread(buffer.data(), sizeof(char), byte_count, file) != byte_count) { - return internal_error("fread error while loading mach-o symbol table"); + auto read_res = file->read_bytes(span{buffer.data(), byte_count}, load_base + offset); + if(!read_res) { + return read_res.unwrap_error(); } buffer[byte_count] = 0; // just out of an abundance of caution return buffer; @@ -672,8 +700,11 @@ namespace detail { } Result, internal_error> open_mach_o_cached(const std::string& object_path) { + if(object_path.empty()) { + return internal_error{"empty object_path"}; + } if(get_cache_mode() == cache_mode::prioritize_memory) { - return mach_o::open_mach_o(object_path) + return mach_o::open(object_path) .transform([](mach_o&& obj) { return maybe_owned{detail::make_unique(std::move(obj))}; }); @@ -684,7 +715,7 @@ namespace detail { static std::unordered_map> cache; auto it = cache.find(object_path); if(it == cache.end()) { - auto res = cache.insert({ object_path, mach_o::open_mach_o(object_path) }); + auto res = cache.insert({ object_path, mach_o::open(object_path) }); VERIFY(res.second); it = res.first; } diff --git a/src/binary/mach-o.hpp b/src/binary/mach-o.hpp index a4b6e52a..7a86d3ac 100644 --- a/src/binary/mach-o.hpp +++ b/src/binary/mach-o.hpp @@ -3,6 +3,8 @@ #include "utils/common.hpp" #include "utils/utils.hpp" +#include "utils/span.hpp" +#include "utils/io/base_file.hpp" #if IS_APPLE @@ -44,9 +46,7 @@ namespace detail { using debug_map = std::unordered_map>; private: - - file_wrapper file; - std::string object_path; + std::unique_ptr file; std::uint32_t magic; cpu_type_t cputype; cpu_subtype_t cpusubtype; @@ -73,19 +73,15 @@ namespace detail { bool tried_to_load_symbols = false; optional> symbols; - mach_o( - file_wrapper file, - cstring_view object_path, - std::uint32_t magic - ) : - file(std::move(file)), - object_path(object_path), - magic(magic) {} + mach_o(std::unique_ptr file, std::uint32_t magic) : file(std::move(file)), magic(magic) {} Result load(); + static NODISCARD Result open(std::unique_ptr file); + public: - static NODISCARD Result open_mach_o(cstring_view object_path); + static NODISCARD Result open(cstring_view object_path); + static NODISCARD Result open(cbspan object); mach_o(mach_o&&) = default; ~mach_o() = default; @@ -96,6 +92,13 @@ namespace detail { void print_segments() const; + struct pc_range { + frame_ptr low; + frame_ptr high; // not inclusive + }; + // for in-memory JIT mach-o's + Result, internal_error> get_pc_ranges(); + Result>, internal_error> get_symtab_info(); void print_symbol_table_entry( diff --git a/src/cpptrace.cpp b/src/cpptrace.cpp index 9f044307..2a9d8889 100644 --- a/src/cpptrace.cpp +++ b/src/cpptrace.cpp @@ -9,8 +9,10 @@ #include #include #include +#include #include "cpptrace/basic.hpp" +#include "jit/jit_objects.hpp" #include "symbols/symbols.hpp" #include "unwind/unwind.hpp" #include "demangle/demangle.hpp" @@ -337,4 +339,16 @@ namespace cpptrace { bool can_get_safe_object_frame() { return detail::has_get_safe_object_frame(); } + + void register_jit_object(const char* ptr, std::size_t size) { + detail::register_jit_object(ptr, size); + } + + void unregister_jit_object(const char* ptr) { + detail::unregister_jit_object(ptr); + } + + void clear_all_jit_objects() { + detail::clear_all_jit_objects(); + } } diff --git a/src/jit/jit_objects.cpp b/src/jit/jit_objects.cpp new file mode 100644 index 00000000..77b57c35 --- /dev/null +++ b/src/jit/jit_objects.cpp @@ -0,0 +1,144 @@ +#include "jit/jit_objects.hpp" + +#include "cpptrace/forward.hpp" +#include "utils/error.hpp" +#include "utils/optional.hpp" +#include "utils/span.hpp" +#include "binary/elf.hpp" +#include "binary/mach-o.hpp" + +#include +#include +#include +#include +#include + +namespace cpptrace { +namespace detail { + #if IS_LINUX || IS_APPLE + class jit_object_manager { + struct object_entry { + const char* object_start; + std::unique_ptr object; + }; + std::vector objects; + + struct range_entry { + frame_ptr low; + frame_ptr high; // not inclusive + const char* object_start; + jit_object_type* object; + bool operator<(const range_entry& other) const { + return low < other.low; + } + }; + // TODO: Maybe use a set... + std::vector range_list; + + public: + void add_jit_object(cbspan object) { + auto object_res = jit_object_type::open(object); + if(object_res.is_error()) { + if(!should_absorb_trace_exceptions()) { + object_res.drop_error(); + } + return; + } + objects.push_back({object.data(), make_unique(std::move(object_res).unwrap_value())}); + auto* object_file = objects.back().object.get(); + auto ranges_res = object_file->get_pc_ranges(); + if(ranges_res.is_error()) { + if(!should_absorb_trace_exceptions()) { + ranges_res.drop_error(); + } + return; + } + auto& ranges = ranges_res.unwrap_value(); + for(auto range : ranges) { + range_entry entry{range.low, range.high, object.data(), object_file}; + // TODO: Perf + range_list.insert(std::upper_bound(range_list.begin(), range_list.end(), entry), entry); + } + } + + void remove_jit_object(const char* ptr) { + // TODO: Perf + objects.erase( + std::remove_if( + objects.begin(), + objects.end(), + [&](const object_entry& entry) { return entry.object_start == ptr; } + ), + objects.end() + ); + range_list.erase( + std::remove_if( + range_list.begin(), + range_list.end(), + [&](const range_entry& entry) { return entry.object_start == ptr; } + ), + range_list.end() + ); + } + + optional lookup(frame_ptr pc) const { + auto it = first_less_than_or_equal( + range_list.begin(), + range_list.end(), + pc, + [](frame_ptr pc, const range_entry& entry) { + return pc < entry.low; + } + ); + if(it == range_list.end()) { + return nullopt; + } + ASSERT(pc >= it->low); + if(pc < it->high) { + return jit_object_lookup_result{*it->object, it->low}; + } else { + return nullopt; + } + } + + void clear_all_jit_objects() { + objects.clear(); + range_list.clear(); + } + }; + #else + class jit_object_manager { + public: + void add_jit_object(cbspan) {} + void remove_jit_object(const char*) {} + void clear_all_jit_objects() {} + }; + #endif + + jit_object_manager& get_jit_object_manager() { + static jit_object_manager manager; + return manager; + } + + void register_jit_object(const char* ptr, std::size_t size) { + auto& manager = get_jit_object_manager(); + manager.add_jit_object(make_span(ptr, size)); + } + + void unregister_jit_object(const char* ptr) { + auto& manager = get_jit_object_manager(); + manager.remove_jit_object(ptr); + } + + void clear_all_jit_objects() { + auto& manager = get_jit_object_manager(); + manager.clear_all_jit_objects(); + } + + #if IS_LINUX || IS_APPLE + optional lookup_jit_object(frame_ptr pc) { + return get_jit_object_manager().lookup(pc); + } + #endif +} +} diff --git a/src/jit/jit_objects.hpp b/src/jit/jit_objects.hpp new file mode 100644 index 00000000..ef4a0cf3 --- /dev/null +++ b/src/jit/jit_objects.hpp @@ -0,0 +1,31 @@ +#ifndef JIT_OBJECTS_HPP +#define JIT_OBJECTS_HPP + +#include "binary/elf.hpp" +#include "binary/mach-o.hpp" +#include "cpptrace/forward.hpp" +#include "utils/optional.hpp" +#include "platform/platform.hpp" + +namespace cpptrace { +namespace detail { + void register_jit_object(const char*, std::size_t); + void unregister_jit_object(const char*); + void clear_all_jit_objects(); + + #if IS_LINUX || IS_APPLE + #if IS_LINUX + using jit_object_type = elf; + #elif IS_APPLE + using jit_object_type = mach_o; + #endif + struct jit_object_lookup_result { + jit_object_type& object; + frame_ptr base; + }; + optional lookup_jit_object(frame_ptr pc); + #endif +} +} + +#endif diff --git a/src/symbols/symbols_core.cpp b/src/symbols/symbols_core.cpp index 7160c5d7..46485513 100644 --- a/src/symbols/symbols_core.cpp +++ b/src/symbols/symbols_core.cpp @@ -18,14 +18,13 @@ namespace detail { std::unordered_map entries; for(std::size_t i = 0; i < frames.size(); i++) { const auto& entry = frames[i]; - // If libdl fails to find the shared object for a frame, the path will be empty. I've observed this - // on macos when looking up the shared object containing `start`. - if(!entry.object_path.empty()) { - entries[entry.object_path].emplace_back( - entry, - trace[i] - ); - } + // The path may be empty. This can happens if libdl fails to find the shared object for a frame, e.g. I've + // observed this on macos when looking up the shared object containing `start`. + // It can also happen for JIT frames. As such, we don't exclude them from the output. + entries[entry.object_path].emplace_back( + entry, + trace[i] + ); } return entries; } diff --git a/src/symbols/symbols_with_addr2line.cpp b/src/symbols/symbols_with_addr2line.cpp index 5182b043..47609350 100644 --- a/src/symbols/symbols_with_addr2line.cpp +++ b/src/symbols/symbols_with_addr2line.cpp @@ -283,6 +283,9 @@ namespace addr2line { for(const auto& entry : entries) { try { const auto& object_name = entry.first; + if(object_name.empty()) { + continue; + } const auto& entries_vec = entry.second; // You may ask why it'd ever happen that there could be an empty entries_vec array, if there're // no addresses why would get_addr2line_targets do anything? The reason is because if things in diff --git a/src/symbols/symbols_with_libdwarf.cpp b/src/symbols/symbols_with_libdwarf.cpp index 5687eac1..e1fd332e 100644 --- a/src/symbols/symbols_with_libdwarf.cpp +++ b/src/symbols/symbols_with_libdwarf.cpp @@ -3,11 +3,13 @@ #include "symbols/symbols.hpp" #include + #include "dwarf/resolver.hpp" #include "utils/common.hpp" #include "utils/utils.hpp" #include "binary/elf.hpp" #include "binary/mach-o.hpp" +#include "jit/jit_objects.hpp" #include #include @@ -16,7 +18,6 @@ #include #include - namespace cpptrace { namespace detail { namespace libdwarf { @@ -84,6 +85,36 @@ namespace libdwarf { return final_trace; } + #if IS_LINUX || IS_APPLE + CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING + void try_resolve_jit_frame(const cpptrace::object_frame& dlframe, frame_with_inlines& frame) { + auto object_res = lookup_jit_object(dlframe.raw_address); + // TODO: At some point, dwarf resolution + if(object_res) { + frame.frame.symbol = object_res.unwrap().object + .lookup_symbol(dlframe.raw_address - object_res.unwrap().base).value_or(""); + } + } + #endif + + CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING + void try_resolve_frame( + symbol_resolver* resolver, + const cpptrace::object_frame& dlframe, + frame_with_inlines& frame + ) { + try { + frame = resolver->resolve_frame(dlframe); + } catch(...) { + frame.frame.raw_address = dlframe.raw_address; + frame.frame.object_address = dlframe.object_address; + frame.frame.filename = dlframe.object_path; + if(!should_absorb_trace_exceptions()) { + throw; + } + } + } + CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING std::vector resolve_frames(const std::vector& frames) { std::vector trace(frames.size(), {null_frame, {}}); @@ -94,6 +125,14 @@ namespace libdwarf { for(const auto& group : collate_frames(frames, trace)) { try { const auto& object_name = group.first; + if(object_name.empty()) { + #if IS_LINUX || IS_APPLE + for(const auto& entry : group.second) { + try_resolve_jit_frame(entry.first.get(), entry.second.get()); + } + #endif + continue; + } // TODO PERF: Potentially a duplicate open and parse with module base stuff (and debug map resolver) #if IS_LINUX auto object = open_elf_cached(object_name); @@ -104,17 +143,9 @@ namespace libdwarf { for(const auto& entry : group.second) { const auto& dlframe = entry.first.get(); auto& frame = entry.second.get(); - try { - frame = resolver->resolve_frame(dlframe); - } catch(...) { - frame.frame.raw_address = dlframe.raw_address; - frame.frame.object_address = dlframe.object_address; - frame.frame.filename = dlframe.object_path; - if(!should_absorb_trace_exceptions()) { - throw; - } - } + try_resolve_frame(resolver.get(), dlframe, frame); #if IS_LINUX || IS_APPLE + // fallback to symbol tables if(frame.frame.symbol.empty() && object.has_value()) { frame.frame.symbol = object .unwrap_value() diff --git a/src/utils/io/base_file.hpp b/src/utils/io/base_file.hpp new file mode 100644 index 00000000..ba6be31a --- /dev/null +++ b/src/utils/io/base_file.hpp @@ -0,0 +1,38 @@ +#ifndef BASE_FILE_HPP +#define BASE_FILE_HPP + +#include "utils/span.hpp" +#include "utils/utils.hpp" + +#include + +namespace cpptrace { +namespace detail { + class base_file { + public: + virtual ~base_file() = default; + virtual string_view path() const = 0; + virtual Result read_bytes(bspan buffer, off_t offset) const = 0; + + template::value && !is_span::value, int>::type = 0> + Result read(off_t offset) { + T object{}; + auto res = read_bytes(make_bspan(object), offset); + if(!res) { + return res.unwrap_error(); + } + return object; + } + + template::value, int>::type = 0> + Result read_span(span items, off_t offset) { + return read_bytes( + make_span(reinterpret_cast(items.data()), reinterpret_cast(items.data() + items.size())), + offset + ); + } + }; +} +} + +#endif diff --git a/src/utils/io/file.cpp b/src/utils/io/file.cpp new file mode 100644 index 00000000..a4932fd5 --- /dev/null +++ b/src/utils/io/file.cpp @@ -0,0 +1,28 @@ +#define _CRT_SECURE_NO_WARNINGS +#include "utils/io/file.hpp" + +namespace cpptrace { +namespace detail { + string_view file::path() const { + return object_path; + } + + Result file::open(cstring_view object_path) { + auto file_obj = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter); + if(file_obj == nullptr) { + return internal_error("Unable to read object file {}", object_path); + } + return file(std::move(file_obj), object_path); + } + + Result file::read_bytes(bspan buffer, off_t offset) const { + if(std::fseek(file_obj, offset, SEEK_SET) != 0) { + return internal_error("fseek error in {} at offset {}", path(), offset); + } + if(std::fread(buffer.data(), buffer.size(), 1, file_obj) != 1) { + return internal_error("fread error in {} at offset {} for {} bytes", path(), offset, buffer.size()); + } + return monostate{}; + } +} +} diff --git a/src/utils/io/file.hpp b/src/utils/io/file.hpp new file mode 100644 index 00000000..c5b53066 --- /dev/null +++ b/src/utils/io/file.hpp @@ -0,0 +1,30 @@ +#ifndef FILE_HPP +#define FILE_HPP + +#include "utils/string_view.hpp" +#include "utils/span.hpp" +#include "utils/io/base_file.hpp" +#include "utils/utils.hpp" + +namespace cpptrace { +namespace detail { + class file : public base_file { + file_wrapper file_obj; + std::string object_path; + + file(file_wrapper file_obj, string_view path) : file_obj(std::move(file_obj)), object_path(path) {} + + public: + file(file&&) = default; + ~file() override = default; + + string_view path() const override; + + static Result open(cstring_view object_path); + + virtual Result read_bytes(bspan buffer, off_t offset) const override; + }; +} +} + +#endif diff --git a/src/utils/io/memory_file_view.cpp b/src/utils/io/memory_file_view.cpp new file mode 100644 index 00000000..5989aaf2 --- /dev/null +++ b/src/utils/io/memory_file_view.cpp @@ -0,0 +1,23 @@ +#include "utils/io/memory_file_view.hpp" + +namespace cpptrace { +namespace detail { + string_view memory_file_view::path() const { + return object_path; + } + + Result memory_file_view::read_bytes(bspan buffer, off_t offset) const { + if(offset < 0) { + return internal_error("Illegal read in memory file {}: offset {}", path(), offset); + } + if(offset + buffer.size() > data.size()) { + return internal_error( + "Illegal read in memory file {}: offset = {}, size = {}, file size = {}", + path(), offset, buffer.size(), data.size() + ); + } + std::memcpy(buffer.data(), data.data() + offset, buffer.size()); + return monostate{}; + } +} +} diff --git a/src/utils/io/memory_file_view.hpp b/src/utils/io/memory_file_view.hpp new file mode 100644 index 00000000..676976b3 --- /dev/null +++ b/src/utils/io/memory_file_view.hpp @@ -0,0 +1,26 @@ +#ifndef MEMORY_FILE_VIEW_HPP +#define MEMORY_FILE_VIEW_HPP + +#include "utils/error.hpp" +#include "utils/span.hpp" +#include "utils/io/base_file.hpp" +#include "utils/utils.hpp" + +namespace cpptrace { +namespace detail { + class memory_file_view : public base_file { + cbspan data; + std::string object_path = ""; + + public: + memory_file_view(cbspan data) : data(data) {} + ~memory_file_view() override = default; + + string_view path() const override; + + virtual Result read_bytes(bspan buffer, off_t offset) const override; + }; +} +} + +#endif diff --git a/src/utils/span.hpp b/src/utils/span.hpp index 3b1c4477..39c98d40 100644 --- a/src/utils/span.hpp +++ b/src/utils/span.hpp @@ -1,6 +1,8 @@ #ifndef SPAN_HPP #define SPAN_HPP +#include "utils/utils.hpp" + #include #include #include @@ -30,6 +32,7 @@ namespace detail { using const_iterator = const T*; using reverse_iterator = std::reverse_iterator; using const_reverse_iterator = std::reverse_iterator; + using i_am_span = void; span() : ptr(nullptr), count(0) {} span(T* ptr, std::size_t count) : ptr(ptr), count(count) {} @@ -63,14 +66,25 @@ namespace detail { using bspan = span; using cbspan = span; + template + struct is_span : std::false_type {}; + + template + struct is_span> : std::true_type {}; + + template + auto make_span(It begin, It end) -> span::type> { + return {begin, end}; + } + template - auto make_span(It begin, It end) { - return span::type>(begin, end); + auto make_span(It begin, std::size_t count) -> span::type> { + return {begin, count}; } - template::value, int>::type = 0> - auto make_bspan(T object) { - return span(reinterpret_cast(std::addressof(object)), sizeof(T)); + template::value && !is_span::value, int>::type = 0> + span make_bspan(T& object) { + return span(reinterpret_cast(std::addressof(object)), sizeof(object)); } } } diff --git a/src/utils/string_view.cpp b/src/utils/string_view.cpp index 3042c6e9..e4ed9452 100644 --- a/src/utils/string_view.cpp +++ b/src/utils/string_view.cpp @@ -33,7 +33,7 @@ namespace detail { } bool operator==(string_view a, string_view b) { - return a.size() == b.size() && std::memcmp(a.data(), b.data(), a.size()); + return a.size() == b.size() && std::memcmp(a.data(), b.data(), a.size()) == 0; } cstring_view cstring_view::substr(std::size_t pos) const { diff --git a/src/utils/utils.hpp b/src/utils/utils.hpp index 432d6509..257f1564 100644 --- a/src/utils/utils.hpp +++ b/src/utils/utils.hpp @@ -195,6 +195,9 @@ namespace detail { return old; } + template + using void_t = void; + struct monostate {}; // TODO: Rework some stuff here. Not sure deleters should be optional or moved. @@ -258,11 +261,16 @@ namespace detail { using file_wrapper = raii_wrapper; - template + template auto make_unique(Args&&... args) -> typename std::enable_if::value, std::unique_ptr>::type { return std::unique_ptr(new T(std::forward(args)...)); } + template + auto make_unique(T&& arg) -> typename std::enable_if::value, std::unique_ptr>::type { + return std::unique_ptr(new T(std::forward(arg))); + } + template class maybe_owned { std::unique_ptr owned; @@ -276,6 +284,9 @@ namespace detail { T& operator*() { return *ptr; } + T* get() { + return ptr; + } }; template diff --git a/test/jank/Dockerfile b/test/jank/Dockerfile new file mode 100644 index 00000000..98f6ff0f --- /dev/null +++ b/test/jank/Dockerfile @@ -0,0 +1,30 @@ +FROM ubuntu:24.10 + +RUN apt update +RUN apt install -y curl git git-lfs zip build-essential entr libssl-dev libdouble-conversion-dev pkg-config ninja-build cmake zlib1g-dev libffi-dev clang libclang-dev llvm llvm-dev libzip-dev libbz2-dev doctest-dev gcc g++ libgc-dev +RUN apt install -y vim gdb lldb file valgrind + +# Setup a user +RUN groupadd cpptracegroup +RUN useradd -m -g cpptracegroup -s /bin/bash cpptrace +RUN mkdir /opt/work/ && chown cpptrace:cpptracegroup /opt/work/ +USER cpptrace + +WORKDIR /opt/ +WORKDIR /opt/work + +# RUN git clone --recurse-submodules https://github.com/jank-lang/jank.git +# WORKDIR /opt/work/jank/compiler+runtime +# RUN git checkout 5668b16 +# RUN CC=clang CXX=clang++ ./bin/configure -GNinja -DCMAKE_BUILD_TYPE=Debug +# RUN ./bin/compile + +# WORKDIR /opt/work/jank +# WORKDIR /opt/work/jank/compiler+runtime + +COPY ./entry.sh . +ENTRYPOINT ["./entry.sh"] +# ENTRYPOINT ["/bin/bash"] + +# podman build -t cpptrace-container . +# podman run --user=cpptrace --cap-drop=all --network none -it cpptrace-container diff --git a/test/jank/Makefile b/test/jank/Makefile new file mode 100644 index 00000000..d193284b --- /dev/null +++ b/test/jank/Makefile @@ -0,0 +1,43 @@ +default: help + +# The general philosophy and functionality of this makefile is shamelessly stolen from compiler explorer + +help: # with thanks to Ben Rady + @grep -E '^[0-9a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' + +PODMAN=podman + +data/.built: Dockerfile entry.sh + $(PODMAN) build -t cpptrace-container . + mkdir -p data + touch data/.built + +.PHONY: build +build: data/.built ## build the container + +.PHONY: run +run: data/.built data/.cloned ## run the container + rm -rfv data/jank/compiler+runtime/third-party/cpptrace/include + rm -rfv data/jank/compiler+runtime/third-party/cpptrace/src + cp -avp ../../include data/jank/compiler+runtime/third-party/cpptrace/include + cp -avp ../../src data/jank/compiler+runtime/third-party/cpptrace/src + cp -v ../../CMakeLists.txt data/jank/compiler+runtime/third-party/cpptrace/ + $(PODMAN) run \ + --user=cpptrace \ + --cap-drop=all \ + -v $(realpath data/jank):/opt/work/jank:rw \ + --tmpfs /opt/work/jank/compiler+runtime/build:rw \ + -it \ + cpptrace-container + +.PHONY: clone +clone: data/.cloned ## clone code + +data/.cloned: + mkdir -p data + cd data && git clone --recurse-submodules https://github.com/jank-lang/jank.git && cd jank && git checkout 5668b16 + touch data/.cloned + +.PHONY: clean +clean: ## clean + rm -rf data diff --git a/test/jank/entry.sh b/test/jank/entry.sh new file mode 100644 index 00000000..6ce42bd6 --- /dev/null +++ b/test/jank/entry.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +echo "------------------------------------ BUILDING ------------------------------------" + +cd jank/compiler+runtime + +CC=clang CXX=clang++ ./bin/configure -GNinja -DCMAKE_BUILD_TYPE=Debug && ./bin/compile + +cat > test.jank << EOF +(ns test) + +(defn foo [] + (throw "meow")) + +(defn -main [& args] + (foo)) +(-main) +EOF + +./build/jank run test.jank +# gdb --args ./build/jank run test.jank + +exec /bin/bash diff --git a/tools/symbol_tables/main.cpp b/tools/symbol_tables/main.cpp index 0644cb09..2f8b3cc6 100644 --- a/tools/symbol_tables/main.cpp +++ b/tools/symbol_tables/main.cpp @@ -32,7 +32,7 @@ void dump_symtab_result(const Result>, i } void dump_symbols(const std::filesystem::path& path) { - auto elf_ = elf::open_elf(path.native()); + auto elf_ = elf::open(path.native()); if(!elf_) { fmt::println(stderr, "Error reading file: {}", elf_.unwrap_error().what()); } From bcef81c3ab234603d9a49a98b5e10b80c238491c Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Mon, 7 Apr 2025 20:24:54 -0500 Subject: [PATCH 14/23] Move register_jit_objects_from_gdb_jit_interface to experimental --- include/cpptrace/gdb_jit.hpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/include/cpptrace/gdb_jit.hpp b/include/cpptrace/gdb_jit.hpp index ad921d64..b6969bc5 100644 --- a/include/cpptrace/gdb_jit.hpp +++ b/include/cpptrace/gdb_jit.hpp @@ -38,12 +38,14 @@ namespace cpptrace { } } - inline void register_jit_objects_from_gdb_jit_interface() { - clear_all_jit_objects(); - detail::jit_code_entry* entry = detail::__jit_debug_descriptor.first_entry; - while(entry) { - register_jit_object(entry->symfile_addr, entry->symfile_size); - entry = entry->next_entry; + namespace experimental { + inline void register_jit_objects_from_gdb_jit_interface() { + clear_all_jit_objects(); + detail::jit_code_entry* entry = detail::__jit_debug_descriptor.first_entry; + while(entry) { + register_jit_object(entry->symfile_addr, entry->symfile_size); + entry = entry->next_entry; + } } } } From fe794b19e8dcc7baf5136f5640c588f6325593a1 Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Mon, 7 Apr 2025 21:07:39 -0500 Subject: [PATCH 15/23] Slightly rework the stacktrace transform callback, optimize to only copy if needed --- include/cpptrace/formatting.hpp | 2 +- src/formatting.cpp | 20 +++++++++++++------- test/unit/lib/formatting.cpp | 29 ++++++++++++++++++++++++++++- 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/include/cpptrace/formatting.hpp b/include/cpptrace/formatting.hpp index 79dcec7f..1cfa9be6 100644 --- a/include/cpptrace/formatting.hpp +++ b/include/cpptrace/formatting.hpp @@ -46,7 +46,7 @@ namespace cpptrace { formatter& columns(bool); formatter& filtered_frame_placeholders(bool); formatter& filter(std::function); - formatter& transform(std::function); + formatter& transform(std::function); std::string format(const stacktrace_frame&) const; std::string format(const stacktrace_frame&, bool color) const; diff --git a/src/formatting.cpp b/src/formatting.cpp index 4b845be9..8f119397 100644 --- a/src/formatting.cpp +++ b/src/formatting.cpp @@ -24,7 +24,7 @@ namespace cpptrace { bool columns = true; bool show_filtered_frames = true; std::function filter; - std::function transform; + std::function transform; } options; public: @@ -55,15 +55,20 @@ namespace cpptrace { void filter(std::function filter) { options.filter = filter; } - void transform(std::function transform) { + void transform(std::function transform) { options.transform = std::move(transform); } - std::string format(stacktrace_frame frame, detail::optional color_override = detail::nullopt) const { + std::string format( + const stacktrace_frame& input_frame, + detail::optional color_override = detail::nullopt + ) const { std::ostringstream oss; + detail::optional transformed_frame; if(options.transform) { - frame = options.transform(std::move(frame)); + transformed_frame = options.transform(input_frame); } + const stacktrace_frame& frame = options.transform ? transformed_frame.unwrap() : input_frame; print_frame_inner(oss, frame, color_override.value_or(options.color == color_mode::always)); return std::move(oss).str(); } @@ -156,10 +161,11 @@ namespace cpptrace { } const auto frame_number_width = detail::n_digits(static_cast(frames.size()) - 1); for(size_t i = 0; i < frames.size(); ++i) { - auto frame = frames[i]; + detail::optional transformed_frame; if(options.transform) { - frame = options.transform(std::move(frame)); + transformed_frame = options.transform(frames[i]); } + const stacktrace_frame& frame = options.transform ? transformed_frame.unwrap() : frames[i]; if(options.filter && !options.filter(frame)) { if(!options.show_filtered_frames) { counter++; @@ -304,7 +310,7 @@ namespace cpptrace { pimpl->filter(std::move(filter)); return *this; } - formatter& formatter::transform(std::function transform) { + formatter& formatter::transform(std::function transform) { pimpl->transform(std::move(transform)); return *this; } diff --git a/test/unit/lib/formatting.cpp b/test/unit/lib/formatting.cpp index 62523994..e77736e5 100644 --- a/test/unit/lib/formatting.cpp +++ b/test/unit/lib/formatting.cpp @@ -257,7 +257,34 @@ TEST(FormatterTest, DontShowFilteredFrames) { TEST(FormatterTest, Transforming) { auto formatter = cpptrace::formatter{} - .transform([](cpptrace::stacktrace_frame &&frame) { + .transform([](cpptrace::stacktrace_frame frame) { + static size_t count = 0; + frame.symbol = cpptrace::microfmt::format("sym{}", count++); + return frame; + }); + auto res = split(formatter.format(make_test_stacktrace()), "\n"); + EXPECT_THAT( + res, + ElementsAre( + "Stack trace (most recent call first):", + "#0 0x0000000000000001 in sym0 at foo.cpp:20:30", + "#1 0x0000000000000002 in sym1 at bar.cpp:30:40", + "#2 0x0000000000000003 in sym2 at foo.cpp:40:25" + ) + ); + + auto frame_res = split(formatter.format(make_test_stacktrace().frames[1]), "\n"); + EXPECT_THAT( + frame_res, + ElementsAre( + "0x0000000000000002 in sym3 at bar.cpp:30:40" + ) + ); +} + +TEST(FormatterTest, TransformingRvalueRef) { + auto formatter = cpptrace::formatter{} + .transform([](cpptrace::stacktrace_frame&& frame) { static size_t count = 0; frame.symbol = cpptrace::microfmt::format("sym{}", count++); return std::move(frame); From 5f514deb3a1f3ffe0f2c07bf86abb92fc3e0d6fe Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Mon, 7 Apr 2025 21:10:53 -0500 Subject: [PATCH 16/23] Document formatter::transform --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index d59c87b5..a58e4c75 100644 --- a/README.md +++ b/README.md @@ -348,6 +348,7 @@ namespace cpptrace { formatter& columns(bool); formatter& filtered_frame_placeholders(bool); formatter& filter(std::function); + formatter& transform(std::function); std::string format(const stacktrace_frame&) const; std::string format(const stacktrace_frame&, bool color) const; @@ -384,6 +385,7 @@ Options: | `columns` | Whether to include column numbers if present | `true` | | `filtered_frame_placeholders` | Whether to still print filtered frames as just `#n (filtered)` | `true` | | `filter` | A predicate to filter frames with | None | +| `transform` | A transformer which takes a stacktrace frame and modifies it | None | The `automatic` color mode attempts to detect if a stream that may be attached to a terminal. As such, it will not use colors for the `formatter::format` method and it may not be able to detect if some ostreams correspond to terminals or @@ -400,6 +402,20 @@ namespace cpptrace { } ``` +### Transforming + +A transform function can be specified for the formatter. This function is called before the configured `filter` is +checked. For example: + +```cpp +auto formatter = cpptrace::formatter{} + .transform([](cpptrace::stacktrace_frame frame) { + frame.symbol = replace_all(frame, "std::__cxx11::", "std::"); + return frame; + }); +``` + + ## Configuration `cpptrace::absorb_trace_exceptions`: Configure whether the library silently absorbs internal exceptions and continues. From 561156ef14f7afc7314e4de84a053156bf1a7286 Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Mon, 7 Apr 2025 21:19:55 -0500 Subject: [PATCH 17/23] Document the JIT interface --- README.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/README.md b/README.md index a58e4c75..e8f55235 100644 --- a/README.md +++ b/README.md @@ -897,6 +897,46 @@ Explanation: table for compile units emitted by many compilers. Cpptrace uses these by default if they are present since they can speed up resolution, however, they can also result in significant memory usage. +## JIT Support + +Cpptrace has support for resolving symbols from frames in JIT-compiled code. To do this, cpptrace relies on in-memory +object files (elf on linux or mach-o on mac) that contain symbol tables and dwarf debug information. The main reason for +this is many JIT implementations already produce these for debugger support. + +These in-memory object files must be set up in such a way that the symbol table and debug symbol addresses match the +run-time addresses of the JIT code. + +The basic interface for informing cpptrace about these in-memory object files is as follows: + +```cpp +namespace cpptrace { + void register_jit_object(const char*, std::size_t); + void unregister_jit_object(const char*); + void clear_all_jit_objects(); +} +``` + +Many JIT implementations follow the GDB [JIT Compilation Interface][jitci] so that JIT code can be debugged. The +interface, at a high level, entails adding in-memory object files to a linked list of object files that GDB and other +debuggers can reference (stored in the `__jit_debug_descriptor`). Cpptrace provides, as a utility, a mechanism for +loading all in-memory object files present in the `__jit_debug_descriptor` linked list via ``: + +```cpp +namespace cpptrace { + namespace experimental { + void register_jit_objects_from_gdb_jit_interface(); + } +} +``` + +Note: Your program must be able to link against a global C symbol `__jit_debug_descriptor`. + +Note: Calling `cpptrace::experimental::register_jit_objects_from_gdb_jit_interface` clears all jit objects previously +registered with cpptrace. + + +[jitci]: https://sourceware.org/gdb/current/onlinedocs/gdb.html/JIT-Interface.html + # Supported Debug Formats | Format | Supported | From 3a89b9e0057731e3d873f04b9d8c810074e21d64 Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Mon, 7 Apr 2025 21:21:40 -0500 Subject: [PATCH 18/23] Updates and tweaks --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e8f55235..b4fb7b6a 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ Cpptrace also has a C API, docs [here](docs/c-api.md). - [Raw Traces](#raw-traces) - [Utilities](#utilities) - [Formatting](#formatting) + - [Transforms](#transforms) - [Configuration](#configuration) - [Traces From All Exceptions](#traces-from-all-exceptions) - [Removing the `CPPTRACE_` prefix](#removing-the-cpptrace_-prefix) @@ -37,6 +38,7 @@ Cpptrace also has a C API, docs [here](docs/c-api.md). - [Utility Types](#utility-types) - [Headers](#headers) - [Libdwarf Tuning](#libdwarf-tuning) + - [JIT Support](#jit-support) - [Supported Debug Formats](#supported-debug-formats) - [How to Include The Library](#how-to-include-the-library) - [CMake FetchContent](#cmake-fetchcontent) @@ -402,7 +404,7 @@ namespace cpptrace { } ``` -### Transforming +### Transforms A transform function can be specified for the formatter. This function is called before the configured `filter` is checked. For example: @@ -415,7 +417,6 @@ auto formatter = cpptrace::formatter{} }); ``` - ## Configuration `cpptrace::absorb_trace_exceptions`: Configure whether the library silently absorbs internal exceptions and continues. @@ -867,6 +868,7 @@ Cpptrace provides a handful of headers to make inclusion more minimal. | `cpptrace/formatting.hpp` | Configurable formatter API | | `cpptrace/utils.hpp` | Utility functions, configuration functions, and terminate utilities ([Utilities](#utilities), [Configuration](#configuration), and [Terminate Handling](#terminate-handling)) | | `cpptrace/version.hpp` | Library version macros | +| `cpptrace/gdb_jit.hpp` | Provides a special utility related to [JIT support](#jit-support) | The main cpptrace header is `cpptrace/cpptrace.hpp` which includes everything other than `from_current.hpp` and `version.hpp`. From 9a158500224b268328457f69a8b06e1997c8c71e Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Mon, 7 Apr 2025 21:26:47 -0500 Subject: [PATCH 19/23] Minor format fix --- src/symbols/dwarf/dwarf_utils.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/symbols/dwarf/dwarf_utils.hpp b/src/symbols/dwarf/dwarf_utils.hpp index 7676ddb7..e6d2b325 100644 --- a/src/symbols/dwarf/dwarf_utils.hpp +++ b/src/symbols/dwarf/dwarf_utils.hpp @@ -72,7 +72,7 @@ namespace libdwarf { #if !(defined(__GNUC__) && (__GNUC__ < 5)) std::is_trivially_copyable::value && #endif - sizeof(T) <= 16, + sizeof(T) <= 16, int >::type = 0 > From 4e1ca0503e03b30422a573dbdb2f49594e1c9970 Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Mon, 7 Apr 2025 21:48:30 -0500 Subject: [PATCH 20/23] Bump libdwarf to v0.12.0 --- cmake/OptionVariables.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/OptionVariables.cmake b/cmake/OptionVariables.cmake index 5bfbf83a..fc4a39a6 100644 --- a/cmake/OptionVariables.cmake +++ b/cmake/OptionVariables.cmake @@ -182,7 +182,7 @@ option(CPPTRACE_UNPREFIXED_TRY_CATCH "" OFF) option(CPPTRACE_USE_EXTERNAL_GTEST "" OFF) set(CPPTRACE_ZSTD_URL "https://github.com/facebook/zstd/releases/download/v1.5.7/zstd-1.5.7.tar.gz" CACHE STRING "") set(CPPTRACE_LIBDWARF_REPO "https://github.com/jeremy-rifkin/libdwarf-lite.git" CACHE STRING "") -set(CPPTRACE_LIBDWARF_TAG "fe09ca800b988e2ff21225ac5e7468ceade2a30e" CACHE STRING "") # v0.11.1 +set(CPPTRACE_LIBDWARF_TAG "5e71a74491dddc231664bbcd6a8cf8a8643918e9" CACHE STRING "") # v0.12.0 set(CPPTRACE_LIBDWARF_SHALLOW "1" CACHE STRING "") option(CPPTRACE_PROVIDE_EXPORT_SET "" ON) option(CPPTRACE_PROVIDE_EXPORT_SET_FOR_LIBDWARF "" OFF) From b32dea36c571406f194309a407f3f813b8b1553a Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Mon, 7 Apr 2025 22:04:52 -0500 Subject: [PATCH 21/23] Add some tests --- test/CMakeLists.txt | 2 ++ test/unit/internals/span.cpp | 52 +++++++++++++++++++++++++++++ test/unit/internals/string_view.cpp | 28 ++++++++++++++++ 3 files changed, 82 insertions(+) create mode 100644 test/unit/internals/span.cpp create mode 100644 test/unit/internals/string_view.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f06cb187..65c138ef 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -88,6 +88,8 @@ if(NOT CPPTRACE_SKIP_UNIT) unit/internals/result.cpp unit/internals/string_utils.cpp unit/internals/general.cpp + unit/internals/span.cpp + unit/internals/string_view.cpp unit/lib/formatting.cpp unit/lib/nullable.cpp ) diff --git a/test/unit/internals/span.cpp b/test/unit/internals/span.cpp new file mode 100644 index 00000000..22e7c2da --- /dev/null +++ b/test/unit/internals/span.cpp @@ -0,0 +1,52 @@ +#include +#include +#include +#include + +#include "utils/span.hpp" + +using cpptrace::detail::span; +using cpptrace::detail::make_span; +using cpptrace::detail::make_bspan; + +namespace { + +TEST(SpanTest, Basic) { + std::array arr{1, 2, 3, 4, 5}; + auto span = make_span(arr.begin(), arr.end()); + EXPECT_EQ(span.data(), arr.data()); + EXPECT_EQ(span.size(), 5); + EXPECT_EQ(span.data()[0], 1); + EXPECT_EQ(span.data()[1], 2); + EXPECT_EQ(span.data()[2], 3); + EXPECT_EQ(span.data()[3], 4); + EXPECT_EQ(span.data()[4], 5); +} + +TEST(SpanTest, PtrSize) { + std::array arr{1, 2, 3, 4, 5}; + auto span = make_span(arr.begin(), arr.size()); + EXPECT_EQ(span.data(), arr.data()); + EXPECT_EQ(span.size(), 5); + EXPECT_EQ(span.data()[0], 1); + EXPECT_EQ(span.data()[1], 2); + EXPECT_EQ(span.data()[2], 3); + EXPECT_EQ(span.data()[3], 4); + EXPECT_EQ(span.data()[4], 5); +} + +TEST(SpanTest, Bspan) { + struct S { + std::array data; + }; + S s{{'a', 'b', 'c', 'd'}}; + auto span = make_bspan(s); + EXPECT_EQ(span.data(), s.data.data()); + EXPECT_EQ(span.size(), 4); + EXPECT_EQ(span.data()[0], 'a'); + EXPECT_EQ(span.data()[1], 'b'); + EXPECT_EQ(span.data()[2], 'c'); + EXPECT_EQ(span.data()[3], 'd'); +} + +} diff --git a/test/unit/internals/string_view.cpp b/test/unit/internals/string_view.cpp new file mode 100644 index 00000000..f11fc789 --- /dev/null +++ b/test/unit/internals/string_view.cpp @@ -0,0 +1,28 @@ +#include +#include +#include +#include + +#include + +#include "utils/string_view.hpp" + +using cpptrace::detail::string_view; +using cpptrace::detail::cstring_view; + +namespace { + +TEST(StringViewTest, Basic) { + string_view sv = "foo"; + EXPECT_EQ(sv.size(), 3); + EXPECT_EQ(sv.data(), std::string("foo")); + EXPECT_EQ(sv[0], 'f'); + EXPECT_EQ(sv[1], 'o'); + EXPECT_EQ(sv.find_last_of("f"), 0); + EXPECT_EQ(sv.find_last_of("o"), 2); + EXPECT_EQ(sv.find_last_of("asfd"), 0); + EXPECT_EQ(sv.find_last_of("asod"), 2); + EXPECT_EQ(sv.find_last_of("bar"), sv.npos); +} + +} From af8fe9524f5011a084c2ece16bd850bbf09e14b9 Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Tue, 8 Apr 2025 00:20:22 -0500 Subject: [PATCH 22/23] Fixes for msvc --- test/unit/internals/span.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/unit/internals/span.cpp b/test/unit/internals/span.cpp index 22e7c2da..81afc3d2 100644 --- a/test/unit/internals/span.cpp +++ b/test/unit/internals/span.cpp @@ -3,6 +3,8 @@ #include #include +#include + #include "utils/span.hpp" using cpptrace::detail::span; @@ -13,7 +15,8 @@ namespace { TEST(SpanTest, Basic) { std::array arr{1, 2, 3, 4, 5}; - auto span = make_span(arr.begin(), arr.end()); + // thanks microsoft for using horrible non-standard iterators, otherwise this would test with begin()/end() + auto span = make_span(arr.data(), arr.data() + arr.size()); EXPECT_EQ(span.data(), arr.data()); EXPECT_EQ(span.size(), 5); EXPECT_EQ(span.data()[0], 1); @@ -25,7 +28,7 @@ TEST(SpanTest, Basic) { TEST(SpanTest, PtrSize) { std::array arr{1, 2, 3, 4, 5}; - auto span = make_span(arr.begin(), arr.size()); + auto span = make_span(arr.data(), arr.size()); EXPECT_EQ(span.data(), arr.data()); EXPECT_EQ(span.size(), 5); EXPECT_EQ(span.data()[0], 1); From ce639ebfcec47a7c74233b4bab50017cb34e615b Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Tue, 8 Apr 2025 09:33:30 -0500 Subject: [PATCH 23/23] Bump to v0.8.3 --- CHANGELOG.md | 20 ++++++++++++++++++++ CMakeLists.txt | 2 +- README.md | 14 +++++++------- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ddc13e7..8fb743cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog - [Changelog](#changelog) +- [v0.8.3](#v083) - [v0.8.2](#v082) - [v0.8.1](#v081) - [v0.8.0](#v080) @@ -28,6 +29,25 @@ - [v0.1.1](#v011) - [v0.1](#v01) +# v0.8.3 + +Added: +- Added basic JIT support https://github.com/jeremy-rifkin/cpptrace/issues/226 +- Added `cpptrace::formatter::transform` https://github.com/jeremy-rifkin/cpptrace/issues/227 +- Added support for gcc 4.8.5 https://github.com/jeremy-rifkin/cpptrace/issues/220 + +Fixed: +- Fixed bug related to calling `dwarf_dealloc` on strings from `dwarf_formstring` and `dwarf_diename` https://github.com/davea42/libdwarf-code/issues/279 +- Fixed incorrect cmake version variable https://github.com/jeremy-rifkin/cpptrace/issues/231 +- Fixed `address_mode::none` not working https://github.com/jeremy-rifkin/cpptrace/issues/221 +- Fixed use of `-Wall` for clang-cl + +Other: +- Added ARM CI +- Miscellaneous work on supporting old compilers +- Updated cpptrace cmake target configuration to not add public compile definitions +- Internal refactoring, cleanup, and code improvements + # v0.8.2 Fixed: diff --git a/CMakeLists.txt b/CMakeLists.txt index d0b5840c..3396f1c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,7 @@ set(package_name "cpptrace") project( cpptrace - VERSION 0.8.2 + VERSION 0.8.3 DESCRIPTION "Simple, portable, and self-contained stacktrace library for C++11 and newer " HOMEPAGE_URL "https://github.com/jeremy-rifkin/cpptrace" LANGUAGES C CXX diff --git a/README.md b/README.md index b4fb7b6a..72fd664d 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,7 @@ include(FetchContent) FetchContent_Declare( cpptrace GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git - GIT_TAG v0.8.2 # + GIT_TAG v0.8.3 # ) FetchContent_MakeAvailable(cpptrace) target_link_libraries(your_target cpptrace::cpptrace) @@ -963,7 +963,7 @@ include(FetchContent) FetchContent_Declare( cpptrace GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git - GIT_TAG v0.8.2 # + GIT_TAG v0.8.3 # ) FetchContent_MakeAvailable(cpptrace) target_link_libraries(your_target cpptrace::cpptrace) @@ -979,7 +979,7 @@ information. ```sh git clone https://github.com/jeremy-rifkin/cpptrace.git -git checkout v0.8.2 +git checkout v0.8.3 mkdir cpptrace/build cd cpptrace/build cmake .. -DCMAKE_BUILD_TYPE=Release @@ -1022,7 +1022,7 @@ you when installing new libraries. ```ps1 git clone https://github.com/jeremy-rifkin/cpptrace.git -git checkout v0.8.2 +git checkout v0.8.3 mkdir cpptrace/build cd cpptrace/build cmake .. -DCMAKE_BUILD_TYPE=Release @@ -1040,7 +1040,7 @@ To install just for the local user (or any custom prefix): ```sh git clone https://github.com/jeremy-rifkin/cpptrace.git -git checkout v0.8.2 +git checkout v0.8.3 mkdir cpptrace/build cd cpptrace/build cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$HOME/wherever @@ -1123,7 +1123,7 @@ make install cd ~/scratch/cpptrace-test git clone https://github.com/jeremy-rifkin/cpptrace.git cd cpptrace -git checkout v0.8.2 +git checkout v0.8.3 mkdir build cd build cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=On -DCPPTRACE_USE_EXTERNAL_LIBDWARF=On -DCMAKE_PREFIX_PATH=~/scratch/cpptrace-test/resources -DCMAKE_INSTALL_PREFIX=~/scratch/cpptrace-test/resources @@ -1143,7 +1143,7 @@ cpptrace and its dependencies. Cpptrace is available through conan at https://conan.io/center/recipes/cpptrace. ``` [requires] -cpptrace/0.8.2 +cpptrace/0.8.3 [generators] CMakeDeps CMakeToolchain