From 0b32df64e4b049062bda4eea38de0d95f733f85f Mon Sep 17 00:00:00 2001 From: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> Date: Mon, 18 Sep 2023 20:33:46 -0400 Subject: [PATCH] Expand cpptrace API (#37) --- .clang-tidy | 2 +- README.md | 120 ++++++++++++- include/cpptrace/cpptrace.hpp | 162 +++++++++++++++++- src/cpptrace.cpp | 117 ++++++++++--- src/platform/common.hpp | 2 + src/platform/object.hpp | 35 ++-- src/platform/pe.hpp | 1 - src/symbols/symbols.hpp | 20 ++- src/symbols/symbols_core.cpp | 53 +++++- src/symbols/symbols_with_addr2line.cpp | 13 +- src/symbols/symbols_with_dbghelp.cpp | 16 +- src/symbols/symbols_with_dl.cpp | 12 +- src/symbols/symbols_with_libbacktrace.cpp | 10 +- src/symbols/symbols_with_libdwarf.cpp | 7 +- src/symbols/symbols_with_nothing.cpp | 12 +- src/unwind/unwind.hpp | 2 +- src/unwind/unwind_with_execinfo.cpp | 16 +- src/unwind/unwind_with_nothing.cpp | 2 +- src/unwind/unwind_with_unwind.cpp | 8 +- src/unwind/unwind_with_winapi.cpp | 16 +- .../CMakeLists.txt | 1 + test/add_subdirectory-integration/main.cpp | 2 +- test/demo.cpp | 2 +- test/fetchcontent-integration/CMakeLists.txt | 1 + test/fetchcontent-integration/main.cpp | 2 +- test/findpackage-integration/CMakeLists.txt | 1 + test/findpackage-integration/main.cpp | 2 +- test/speedtest/speedtest.cpp | 2 +- 28 files changed, 513 insertions(+), 126 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 331c4f7..aefe79d 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,5 +1,5 @@ --- -Checks: '-*,clang-diagnostic-*,clang-analyzer-*,bugprone-*,cert-*,clang-analyzer-*,concurrency-*,cppcoreguidelines-*,misc-*,modernize-*,performance-*,portability-*,readability-*,-cppcoreguidelines-macro-usage,-modernize-use-trailing-return-type,-misc-non-private-member-variables-in-classes,-cppcoreguidelines-pro-type-reinterpret-cast,-cppcoreguidelines-pro-type-vararg,-cppcoreguidelines-avoid-c-arrays,-modernize-avoid-c-arrays,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-readability-else-after-return,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-cert-dcl50-cpp,-cppcoreguidelines-init-variables,-readability-implicit-bool-conversion,-cppcoreguidelines-pro-bounds-constant-array-index,-cppcoreguidelines-owning-memory,-cppcoreguidelines-pro-type-member-init,-readability-isolate-declaration,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,-cppcoreguidelines-pro-type-union-access,-cppcoreguidelines-pro-type-cstyle-cast,-readability-named-parameter,-cppcoreguidelines-avoid-goto,-readability-uppercase-literal-suffix,-performance-avoid-endl,-bugprone-easily-swappable-parameters' +Checks: '-*,clang-diagnostic-*,clang-analyzer-*,bugprone-*,cert-*,clang-analyzer-*,concurrency-*,cppcoreguidelines-*,misc-*,modernize-*,performance-*,portability-*,readability-*,-cppcoreguidelines-macro-usage,-modernize-use-trailing-return-type,-misc-non-private-member-variables-in-classes,-cppcoreguidelines-pro-type-reinterpret-cast,-cppcoreguidelines-pro-type-vararg,-cppcoreguidelines-avoid-c-arrays,-modernize-avoid-c-arrays,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-readability-else-after-return,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-cert-dcl50-cpp,-cppcoreguidelines-init-variables,-readability-implicit-bool-conversion,-cppcoreguidelines-pro-bounds-constant-array-index,-cppcoreguidelines-owning-memory,-cppcoreguidelines-pro-type-member-init,-readability-isolate-declaration,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,-cppcoreguidelines-pro-type-union-access,-cppcoreguidelines-pro-type-cstyle-cast,-readability-named-parameter,-cppcoreguidelines-avoid-goto,-readability-uppercase-literal-suffix,-performance-avoid-endl,-bugprone-easily-swappable-parameters,-cppcoreguidelines-non-private-member-variables-in-classes' WarningsAsErrors: '*' HeaderFilterRegex: '' AnalyzeTemporaryDtors: false diff --git a/README.md b/README.md index 132ada6..ddf9b10 100644 --- a/README.md +++ b/README.md @@ -46,14 +46,40 @@ Generating traces is as easy as calling `cpptrace::print_trace`: #include void trace() { - cpptrace::print_trace(); + cpptrace::generate_trace().print(); } -// ... +/* other stuff */ ``` ![Screenshot](res/screenshot.png) +Cpptrace provides access to resolved stack traces as well as lightweight raw traces (just addresses) that can be +resolved later: + +```cpp +const auto raw_trace = cpptrace::generate_raw_trace(); +// then later +raw_trace.resolve().print(); +``` + +Cpptrace also provides exception types that store stack traces: +```cpp +#include + +void trace() { + throw cpptrace::exception(); +} + +/* other stuff */ +// terminate called after throwing an instance of 'cpptrace::exception' +// what(): cpptrace::exception: +// Stack trace (most recent call first): +// #0 0x00005641c715a1b6 in trace() at demo.cpp:9 +// #1 0x00005641c715a229 in foo(int) at demo.cpp:16 +// #2 0x00005641c715a2ba in main at demo.cpp:34 +``` + ## CMake FetchContent Usage ```cmake @@ -73,8 +99,9 @@ On windows and macos some extra work is required, see [below](#platform-logistic ## API -`cpptrace::print_trace()` can be used to print a stacktrace at the current call site, `cpptrace::generate_trace()` can -be used to get raw frame information for custom use. +`cpptrace::generate_trace()` can used to generate a stacktrace object at the current call site. Resolved frames can be +accessed from this object with `.frames` and also the trace can be printed with `.print()`. Cpptrace also provides a +method to get lightweight raw traces, which are just vectors of program counters, which can be resolved at a later time. **Note:** Debug info (`-g`/`/Z7`/`/Zi`/`/DEBUG`) is generally required for good trace information. @@ -83,15 +110,96 @@ for generating these is included above. ```cpp namespace cpptrace { + /* + * Raw trace access + */ + struct raw_trace { + std::vector frames; + explicit raw_trace(std::vector&& frames); + object_trace resolve_object_trace() const; + stacktrace resolve() const; + void clear(); + /* iterators exist for this object */ + }; + + /* + * Object trace with object file information for each frame, any accessible symbol information, and the address in + * the object file for the frame's program counter. + */ + struct object_frame { + std::string obj_path; + std::string symbol; + uintptr_t raw_address = 0; + uintptr_t obj_address = 0; + }; + + struct object_trace { + std::vector frames; + explicit object_trace(std::vector&& frames); + stacktrace resolve() const; + void clear(); + /* iterators exist for this object */ + }; + + /* + * Resolved stacktrace object. + */ struct stacktrace_frame { uintptr_t address; std::uint_least32_t line; std::uint_least32_t col; std::string filename; std::string symbol; + bool operator==(const stacktrace_frame& other) const; + bool operator!=(const stacktrace_frame& other) const; }; - std::vector generate_trace(std::uint32_t skip = 0); - void print_trace(std::uint32_t skip = 0); + + struct stacktrace { + std::vector frames; + explicit stacktrace(std::vector&& frames); + void print() const; + void print(std::ostream& stream) const; + void print(std::ostream& stream, bool color) const; + std::string to_string() const; + void clear(); + /* iterators exist for this object */ + }; + + /* + * Trace generation + */ + raw_trace generate_raw_trace(std::uint32_t skip = 0); + object_trace generate_object_trace(std::uint32_t skip = 0); + stacktrace generate_trace(std::uint32_t skip = 0); + + /* + * Utilities + */ + std::string demangle(const std::string& name); + + // Traced exception class + class exception : public std::exception { + public: + explicit exception(); + const char* what() const noexcept override; + }; + + class exception_with_message : public exception { + public: + explicit exception_with_message(std::string&& message_arg); + const char* what() const noexcept override; + }; + + // All stdexcept errors have analogs here. Same constructor as exception_with_message. + class logic_error : exception_with_message { ... }; + class domain_error : exception_with_message { ... }; + class invalid_argument : exception_with_message { ... }; + class length_error : exception_with_message { ... }; + class out_of_range : exception_with_message { ... }; + class runtime_error : exception_with_message { ... }; + class range_error : exception_with_message { ... }; + class overflow_error : exception_with_message { ... }; + class underflow_error : exception_with_message { ... }; } ``` diff --git a/include/cpptrace/cpptrace.hpp b/include/cpptrace/cpptrace.hpp index 7146e0e..d944edd 100644 --- a/include/cpptrace/cpptrace.hpp +++ b/include/cpptrace/cpptrace.hpp @@ -2,6 +2,8 @@ #define CPPTRACE_HPP #include +#include +#include #include #include @@ -12,15 +14,171 @@ #endif namespace cpptrace { + struct object_trace; + struct stacktrace; + + struct raw_trace { + std::vector frames; + explicit raw_trace(std::vector&& frames_) : frames(frames_) {} + CPPTRACE_API object_trace resolve_object_trace() const; + CPPTRACE_API stacktrace resolve() const; + CPPTRACE_API void clear(); + + using iterator = std::vector::iterator; + using const_iterator = std::vector::const_iterator; + inline iterator begin() noexcept { return frames.begin(); } + inline const_iterator cbegin() const noexcept { return frames.cbegin(); } + inline iterator end() noexcept { return frames.end(); } + inline const_iterator cend() const noexcept { return frames.cend(); } + }; + + struct object_frame { + std::string obj_path; + std::string symbol; + uintptr_t raw_address = 0; + uintptr_t obj_address = 0; + }; + + struct object_trace { + std::vector frames; + explicit object_trace(std::vector&& frames_) : frames(frames_) {} + CPPTRACE_API stacktrace resolve() const; + CPPTRACE_API void clear(); + + using iterator = std::vector::iterator; + using const_iterator = std::vector::const_iterator; + inline iterator begin() noexcept { return frames.begin(); } + inline const_iterator cbegin() const noexcept { return frames.cbegin(); } + inline iterator end() noexcept { return frames.end(); } + inline const_iterator cend() const noexcept { return frames.cend(); } + }; + struct stacktrace_frame { uintptr_t address; std::uint_least32_t line; std::uint_least32_t col; std::string filename; std::string symbol; + bool operator==(const stacktrace_frame& other) const { + return address == other.address + && line == other.line + && col == other.col + && filename == other.filename + && symbol == other.symbol; + } + bool operator!=(const stacktrace_frame& other) const { + return !operator==(other); + } + }; + + struct stacktrace { + std::vector frames; + explicit stacktrace(std::vector&& frames_) : frames(frames_) {} + CPPTRACE_API void print() const; + CPPTRACE_API void print(std::ostream& stream) const; + CPPTRACE_API void print(std::ostream& stream, bool color) const; + CPPTRACE_API std::string to_string() const; + CPPTRACE_API void clear(); + + using iterator = std::vector::iterator; + using const_iterator = std::vector::const_iterator; + inline iterator begin() noexcept { return frames.begin(); } + inline const_iterator cbegin() const noexcept { return frames.cbegin(); } + inline iterator end() noexcept { return frames.end(); } + inline const_iterator cend() const noexcept { return frames.cend(); } + }; + + CPPTRACE_API raw_trace generate_raw_trace(std::uint32_t skip = 0); + CPPTRACE_API object_trace generate_object_trace(std::uint32_t skip = 0); + CPPTRACE_API stacktrace generate_trace(std::uint32_t skip = 0); + + // utilities: + CPPTRACE_API std::string demangle(const std::string& name); + + class exception : public std::exception { + protected: + mutable raw_trace trace; + mutable std::string resolved_message; + explicit exception(uint32_t skip) : trace(generate_raw_trace(skip + 1)) {} + virtual const std::string& get_resolved_message() const { + if(resolved_message.empty()) { + resolved_message = "cpptrace::exception:\n" + trace.resolve().to_string(); + trace.clear(); + } + return resolved_message; + } + public: + explicit exception() : exception(1) {} + const char* what() const noexcept override { + return get_resolved_message().c_str(); + } + }; + + class exception_with_message : public exception { + protected: + mutable std::string message; + explicit exception_with_message(std::string&& message_arg, uint32_t skip) + : exception(skip + 1), message(std::move(message_arg)) {} + const std::string& get_resolved_message() const override { + if(resolved_message.empty()) { + resolved_message = message + "\n" + trace.resolve().to_string(); + trace.clear(); + message.clear(); + } + return resolved_message; + } + public: + explicit exception_with_message(std::string&& message_arg) + : exception_with_message(std::move(message_arg), 1) {} + const char* what() const noexcept override { + return get_resolved_message().c_str(); + } + }; + + class logic_error : public exception_with_message { + public: + explicit logic_error(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {} + }; + + class domain_error : public exception_with_message { + public: + explicit domain_error(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {} + }; + + class invalid_argument : public exception_with_message { + public: + explicit invalid_argument(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {} + }; + + class length_error : public exception_with_message { + public: + explicit length_error(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {} + }; + + class out_of_range : public exception_with_message { + public: + explicit out_of_range(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {} + }; + + class runtime_error : public exception_with_message { + public: + explicit runtime_error(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {} + }; + + class range_error : public exception_with_message { + public: + explicit range_error(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {} + }; + + class overflow_error : public exception_with_message { + public: + explicit overflow_error(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {} + }; + + class underflow_error : public exception_with_message { + public: + explicit underflow_error(std::string&& message_arg) : exception_with_message(std::move(message_arg), 1) {} }; - CPPTRACE_API std::vector generate_trace(std::uint32_t skip = 0); - CPPTRACE_API void print_trace(std::uint32_t skip = 0); } #endif diff --git a/src/cpptrace.cpp b/src/cpptrace.cpp index b1fb9ce..269d214 100644 --- a/src/cpptrace.cpp +++ b/src/cpptrace.cpp @@ -2,16 +2,18 @@ #include #include -#include -#include #include #include +#include +#include +#include #include "symbols/symbols.hpp" #include "unwind/unwind.hpp" #include "demangle/demangle.hpp" #include "platform/common.hpp" #include "platform/utils.hpp" +#include "platform/object.hpp" #define ESC "\033[" #define RESET ESC "0m" @@ -23,29 +25,59 @@ #define CYAN ESC "36m" namespace cpptrace { - CPPTRACE_FORCE_NO_INLINE CPPTRACE_API - std::vector generate_trace(std::uint32_t skip) { - std::vector frames = detail::capture_frames(skip + 1); + CPPTRACE_API + object_trace raw_trace::resolve_object_trace() const { + return object_trace(detail::get_frames_object_info(frames)); + } + + CPPTRACE_API + stacktrace raw_trace::resolve() const { std::vector trace = detail::resolve_frames(frames); for(auto& frame : trace) { frame.symbol = detail::demangle(frame.symbol); } - return trace; + return stacktrace(std::move(trace)); + } + + CPPTRACE_API + void raw_trace::clear() { + frames.clear(); + } + + CPPTRACE_API + stacktrace object_trace::resolve() const { + return stacktrace(detail::resolve_frames(frames)); + } + + CPPTRACE_API + void object_trace::clear() { + frames.clear(); + } + + CPPTRACE_API + void stacktrace::print() const { + print(std::cerr, true); + } + + CPPTRACE_API + void stacktrace::print(std::ostream& stream) const { + print(stream, true); } CPPTRACE_API - void print_trace(std::uint32_t skip) { - detail::enable_virtual_terminal_processing_if_needed(); - std::cerr<<"Stack trace (most recent call first):"<"<"<(trace.size()) - 1); - for(const auto& frame : trace) { - std::cerr + const auto frame_number_width = detail::n_digits(static_cast(frames.size()) - 1); + for(const auto& frame : frames) { + stream << '#' << std::setw(static_cast(frame_number_width)) << std::left @@ -53,28 +85,65 @@ namespace cpptrace { << std::right << " " << std::hex - << BLUE + << (color ? BLUE : "") << "0x" << std::setw(2 * sizeof(uintptr_t)) << std::setfill('0') << frame.address << std::dec << std::setfill(' ') - << RESET + << (color ? RESET : "") << " in " - << YELLOW + << (color ? YELLOW : "") << frame.symbol - << RESET + << (color ? RESET : "") << " at " - << GREEN + << (color ? GREEN : "") << frame.filename - << RESET + << (color ? RESET : "") << ":" - << BLUE + << (color ? BLUE : "") << frame.line - << RESET - << (frame.col > 0 ? ":" BLUE + std::to_string(frame.col) + RESET : "") + << (color ? RESET : "") + << (frame.col > 0 ? (color ? ":" BLUE : ":") + std::to_string(frame.col) + (color ? RESET : "") : "") << std::endl; } } + + CPPTRACE_API + std::string stacktrace::to_string() const { + std::ostringstream oss; + print(oss, false); + return std::move(oss).str(); + } + + CPPTRACE_API + void stacktrace::clear() { + frames.clear(); + } + + CPPTRACE_FORCE_NO_INLINE CPPTRACE_API + raw_trace generate_raw_trace(std::uint32_t skip) { + return raw_trace(detail::capture_frames(skip + 1)); + } + + CPPTRACE_FORCE_NO_INLINE CPPTRACE_API + object_trace generate_object_trace(std::uint32_t skip) { + return object_trace(detail::get_frames_object_info(detail::capture_frames(skip + 1))); + } + + CPPTRACE_FORCE_NO_INLINE CPPTRACE_API + stacktrace generate_trace(std::uint32_t skip) { + std::vector frames = detail::capture_frames(skip + 1); + std::vector trace = detail::resolve_frames(frames); + for(auto& frame : trace) { + frame.symbol = detail::demangle(frame.symbol); + } + return stacktrace(std::move(trace)); + } + + CPPTRACE_API + std::string demangle(const std::string& name) { + return detail::demangle(name); + } } diff --git a/src/platform/common.hpp b/src/platform/common.hpp index e732156..47aef79 100644 --- a/src/platform/common.hpp +++ b/src/platform/common.hpp @@ -45,6 +45,8 @@ #include #include +#include + namespace cpptrace { namespace detail { // Placed here instead of utils because it's used by error.hpp and utils.hpp diff --git a/src/platform/object.hpp b/src/platform/object.hpp index dfc92d7..4dae939 100644 --- a/src/platform/object.hpp +++ b/src/platform/object.hpp @@ -24,13 +24,6 @@ namespace cpptrace { namespace detail { - struct dlframe { - std::string obj_path; - std::string symbol; - uintptr_t raw_address = 0; - uintptr_t obj_address = 0; - }; - #if IS_LINUX || IS_APPLE #if !IS_APPLE inline uintptr_t get_module_image_base(const std::string& obj_path) { @@ -69,19 +62,19 @@ namespace detail { } #endif // aladdr queries are needed to get pre-ASLR addresses and targets to run addr2line on - inline std::vector get_frames_object_info(const std::vector& addrs) { + inline std::vector get_frames_object_info(const std::vector& addrs) { // reference: https://github.com/bminor/glibc/blob/master/debug/backtracesyms.c - std::vector frames; + std::vector frames; frames.reserve(addrs.size()); - for(const void* addr : addrs) { + for(const uintptr_t addr : addrs) { Dl_info info; - dlframe frame; - frame.raw_address = reinterpret_cast(addr); - if(dladdr(addr, &info)) { // thread safe + object_frame frame; + frame.raw_address = addr; + if(dladdr(reinterpret_cast(addr), &info)) { // thread safe // dli_sname and dli_saddr are only present with -rdynamic, sname will be included // but we don't really need dli_saddr frame.obj_path = info.dli_fname; - frame.obj_address = reinterpret_cast(addr) + frame.obj_address = addr - reinterpret_cast(info.dli_fbase) + get_module_image_base(info.dli_fname); frame.symbol = info.dli_sname ?: ""; @@ -129,22 +122,22 @@ namespace detail { } // aladdr queries are needed to get pre-ASLR addresses and targets to run addr2line on - inline std::vector get_frames_object_info(const std::vector& addrs) { + inline std::vector get_frames_object_info(const std::vector& addrs) { // reference: https://github.com/bminor/glibc/blob/master/debug/backtracesyms.c - std::vector frames; + std::vector frames; frames.reserve(addrs.size()); - for(const void* addr : addrs) { - dlframe frame; - frame.raw_address = reinterpret_cast(addr); + for(const uintptr_t addr : addrs) { + object_frame frame; + frame.raw_address = addr; HMODULE handle; // Multithread safe as long as another thread doesn't come along and free the module if(GetModuleHandleExA( GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, - static_cast(addr), + reinterpret_cast(addr), &handle )) { frame.obj_path = get_module_name(handle); - frame.obj_address = reinterpret_cast(addr) + frame.obj_address = addr - reinterpret_cast(handle) + get_module_image_base(frame.obj_path); } else { diff --git a/src/platform/pe.hpp b/src/platform/pe.hpp index 5c55e7e..88d1818 100644 --- a/src/platform/pe.hpp +++ b/src/platform/pe.hpp @@ -31,7 +31,6 @@ namespace detail { errno_t ret = fopen_s(&file, obj_path.c_str(), "rb"); if(ret != 0 || file == nullptr) { throw file_error(); - return 0; } auto magic = load_bytes>(file, 0); CPPTRACE_VERIFY(memcmp(magic.data(), "MZ", 2) == 0); diff --git a/src/symbols/symbols.hpp b/src/symbols/symbols.hpp index 1750531..ddf5853 100644 --- a/src/symbols/symbols.hpp +++ b/src/symbols/symbols.hpp @@ -11,46 +11,48 @@ namespace cpptrace { namespace detail { using collated_vec = std::vector< - std::pair, std::reference_wrapper> + std::pair, std::reference_wrapper> >; std::unordered_map collate_frames( - const std::vector& frames, + const std::vector& frames, std::vector& trace ); #ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE namespace libbacktrace { - std::vector resolve_frames(const std::vector& frames); + std::vector resolve_frames(const std::vector& frames); } #endif #ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF namespace libdwarf { - std::vector resolve_frames(const std::vector& frames); + std::vector resolve_frames(const std::vector& frames); } #endif #ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDL namespace libdl { - std::vector resolve_frames(const std::vector& frames); + std::vector resolve_frames(const std::vector& frames); } #endif #ifdef CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE namespace addr2line { - std::vector resolve_frames(const std::vector& frames); + std::vector resolve_frames(const std::vector& frames); } #endif #ifdef CPPTRACE_GET_SYMBOLS_WITH_DBGHELP namespace dbghelp { - std::vector resolve_frames(const std::vector& frames); + std::vector resolve_frames(const std::vector& frames); } #endif #ifdef CPPTRACE_GET_SYMBOLS_WITH_NOTHING namespace nothing { - std::vector resolve_frames(const std::vector& frames); + std::vector resolve_frames(const std::vector& frames); + std::vector resolve_frames(const std::vector& frames); } #endif - std::vector resolve_frames(const std::vector& frames); + std::vector resolve_frames(const std::vector& frames); + std::vector resolve_frames(const std::vector& frames); } } diff --git a/src/symbols/symbols_core.cpp b/src/symbols/symbols_core.cpp index 556f7a4..10128db 100644 --- a/src/symbols/symbols_core.cpp +++ b/src/symbols/symbols_core.cpp @@ -3,12 +3,13 @@ #include #include +#include "../platform/common.hpp" #include "../platform/object.hpp" namespace cpptrace { namespace detail { std::unordered_map collate_frames( - const std::vector& frames, + const std::vector& frames, std::vector& trace ) { std::unordered_map entries; @@ -49,25 +50,61 @@ namespace detail { } } - std::vector resolve_frames(const std::vector& frames) { + std::vector resolve_frames(const std::vector& frames) { + std::vector trace(frames.size()); + #if defined(CPPTRACE_GET_SYMBOLS_WITH_LIBDL) \ + || defined(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP) \ + || defined(CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE) + // actually need to go backwards to a void* + std::vector raw_frames(frames.size()); + for(std::size_t i = 0; i < frames.size(); i++) { + raw_frames[i] = frames[i].raw_address; + } + #endif + #ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDL + apply_trace(trace, libdl::resolve_frames(raw_frames)); + #endif + #ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF + apply_trace(trace, libdwarf::resolve_frames(frames)); + #endif + #ifdef CPPTRACE_GET_SYMBOLS_WITH_DBGHELP + apply_trace(trace, dbghelp::resolve_frames(raw_frames)); + #endif + #ifdef CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE + apply_trace(trace, addr2line::resolve_frames(frames)); + #endif + #ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE + apply_trace(trace, libbacktrace::resolve_frames(raw_frames)); + #endif + #ifdef CPPTRACE_GET_SYMBOLS_WITH_NOTHING + apply_trace(trace, nothing::resolve_frames(frames)); + #endif + return trace; + } + + std::vector resolve_frames(const std::vector& frames) { + #if defined(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) \ + || defined(CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE) + auto dlframes = get_frames_object_info(frames); + #endif std::vector trace(frames.size()); #ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDL - apply_trace(trace, libdl::resolve_frames(frames)); + apply_trace(trace, libdl::resolve_frames(frames)); #endif #ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF - apply_trace(trace, libdwarf::resolve_frames(frames)); + apply_trace(trace, libdwarf::resolve_frames(dlframes)); #endif #ifdef CPPTRACE_GET_SYMBOLS_WITH_DBGHELP - apply_trace(trace, dbghelp::resolve_frames(frames)); + apply_trace(trace, dbghelp::resolve_frames(frames)); #endif #ifdef CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE - apply_trace(trace, addr2line::resolve_frames(frames)); + apply_trace(trace, addr2line::resolve_frames(dlframes)); #endif #ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE - apply_trace(trace, libbacktrace::resolve_frames(frames)); + apply_trace(trace, libbacktrace::resolve_frames(frames)); #endif #ifdef CPPTRACE_GET_SYMBOLS_WITH_NOTHING - apply_trace(trace, nothing::resolve_frames(frames)); + apply_trace(trace, nothing::resolve_frames(frames)); #endif return trace; } diff --git a/src/symbols/symbols_with_addr2line.cpp b/src/symbols/symbols_with_addr2line.cpp index 87590bc..8af13b8 100644 --- a/src/symbols/symbols_with_addr2line.cpp +++ b/src/symbols/symbols_with_addr2line.cpp @@ -271,18 +271,17 @@ namespace addr2line { } // NOLINTNEXTLINE(readability-convert-member-functions-to-static) - std::vector resolve_frames(const std::vector& frames) { + std::vector resolve_frames(const std::vector& frames) { // TODO: Refactor better std::vector trace(frames.size(), stacktrace_frame { 0, 0, 0, "", "" }); - const std::vector dlframes = get_frames_object_info(frames); - for(size_t i = 0; i < dlframes.size(); i++) { - trace[i].address = dlframes[i].raw_address; + for(size_t i = 0; i < frames.size(); i++) { + trace[i].address = frames[i].raw_address; // Set what is known for now, and resolutions from addr2line should overwrite - trace[i].filename = dlframes[i].obj_path; - trace[i].symbol = dlframes[i].symbol; + trace[i].filename = frames[i].obj_path; + trace[i].symbol = frames[i].symbol; } if(has_addr2line()) { - const auto entries = collate_frames(dlframes, trace); + const auto entries = collate_frames(frames, trace); for(const auto& entry : entries) { const auto& object_name = entry.first; const auto& entries_vec = entry.second; diff --git a/src/symbols/symbols_with_dbghelp.cpp b/src/symbols/symbols_with_dbghelp.cpp index 72c92c1..c8e8361 100644 --- a/src/symbols/symbols_with_dbghelp.cpp +++ b/src/symbols/symbols_with_dbghelp.cpp @@ -324,7 +324,7 @@ namespace dbghelp { std::mutex dbghelp_lock; // TODO: Handle backtrace_pcinfo calling the callback multiple times on inlined functions - stacktrace_frame resolve_frame(HANDLE proc, void* addr) { + stacktrace_frame resolve_frame(HANDLE proc, uintptr_t addr) { const std::lock_guard lock(dbghelp_lock); // all dbghelp functions are not thread safe alignas(SYMBOL_INFO) char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; SYMBOL_INFO* symbol = (SYMBOL_INFO*)buffer; @@ -332,8 +332,8 @@ namespace dbghelp { symbol->MaxNameLen = MAX_SYM_NAME; union { DWORD64 a; DWORD b; } displacement; IMAGEHLP_LINE64 line; - bool got_line = SymGetLineFromAddr64(proc, (DWORD64)addr, &displacement.b, &line); - if(SymFromAddr(proc, (DWORD64)addr, &displacement.a, symbol)) { + bool got_line = SymGetLineFromAddr64(proc, addr, &displacement.b, &line); + if(SymFromAddr(proc, addr, &displacement.a, symbol)) { if(got_line) { IMAGEHLP_STACK_FRAME frame; frame.InstructionOffset = symbol->Address; @@ -344,7 +344,7 @@ namespace dbghelp { if(SymSetContext(proc, &frame, nullptr) == FALSE && GetLastError() != ERROR_SUCCESS) { fprintf(stderr, "Stack trace: Internal error while calling SymSetContext\n"); return { - reinterpret_cast(addr), + addr, static_cast(line.LineNumber), 0, line.FileName, @@ -375,7 +375,7 @@ namespace dbghelp { static std::regex comma_re(R"(,(?=\S))"); signature = std::regex_replace(signature, comma_re, ", "); return { - reinterpret_cast(addr), + addr, static_cast(line.LineNumber), 0, line.FileName, @@ -383,7 +383,7 @@ namespace dbghelp { }; } else { return { - reinterpret_cast(addr), + addr, 0, 0, "", @@ -392,7 +392,7 @@ namespace dbghelp { } } else { return { - reinterpret_cast(addr), + addr, 0, 0, "", @@ -401,7 +401,7 @@ namespace dbghelp { } } - std::vector resolve_frames(const std::vector& frames) { + std::vector resolve_frames(const std::vector& frames) { std::vector trace; trace.reserve(frames.size()); diff --git a/src/symbols/symbols_with_dl.cpp b/src/symbols/symbols_with_dl.cpp index 352a19a..8c4ac92 100644 --- a/src/symbols/symbols_with_dl.cpp +++ b/src/symbols/symbols_with_dl.cpp @@ -12,11 +12,11 @@ namespace cpptrace { namespace detail { namespace libdl { - stacktrace_frame resolve_frame(const void* addr) { + stacktrace_frame resolve_frame(const uintptr_t addr) { Dl_info info; - if(dladdr(addr, &info)) { // thread-safe + if(dladdr(reinterpret_cast(addr), &info)) { // thread-safe return { - reinterpret_cast(addr), + addr, 0, 0, info.dli_fname ? info.dli_fname : "", @@ -24,7 +24,7 @@ namespace libdl { }; } else { return { - reinterpret_cast(addr), + addr, 0, 0, "", @@ -33,10 +33,10 @@ namespace libdl { } } - std::vector resolve_frames(const std::vector& frames) { + std::vector resolve_frames(const std::vector& frames) { std::vector trace; trace.reserve(frames.size()); - for(const void* frame : frames) { + for(const auto frame : frames) { trace.push_back(resolve_frame(frame)); } return trace; diff --git a/src/symbols/symbols_with_libbacktrace.cpp b/src/symbols/symbols_with_libbacktrace.cpp index 1d6f092..78a307d 100644 --- a/src/symbols/symbols_with_libbacktrace.cpp +++ b/src/symbols/symbols_with_libbacktrace.cpp @@ -58,12 +58,12 @@ namespace libbacktrace { } // TODO: Handle backtrace_pcinfo calling the callback multiple times on inlined functions - stacktrace_frame resolve_frame(const void* addr) { + stacktrace_frame resolve_frame(const uintptr_t addr) { stacktrace_frame frame; frame.col = 0; backtrace_pcinfo( get_backtrace_state(), - reinterpret_cast(addr), + addr, full_callback, error_callback, &frame @@ -72,7 +72,7 @@ namespace libbacktrace { // fallback, try to at least recover the symbol name with backtrace_syminfo backtrace_syminfo( get_backtrace_state(), - reinterpret_cast(addr), + addr, syminfo_callback, error_callback, &frame @@ -81,10 +81,10 @@ namespace libbacktrace { return frame; } - std::vector resolve_frames(const std::vector& frames) { + std::vector resolve_frames(const std::vector& frames) { std::vector trace; trace.reserve(frames.size()); - for(const void* frame : frames) { + for(const auto frame : frames) { trace.push_back(resolve_frame(frame)); } return trace; diff --git a/src/symbols/symbols_with_libdwarf.cpp b/src/symbols/symbols_with_libdwarf.cpp index 455dd26..50257e4 100644 --- a/src/symbols/symbols_with_libdwarf.cpp +++ b/src/symbols/symbols_with_libdwarf.cpp @@ -1018,7 +1018,7 @@ namespace libdwarf { } CPPTRACE_FORCE_NO_INLINE - stacktrace_frame resolve_frame(const dlframe& frame_info) { + stacktrace_frame resolve_frame(const object_frame& frame_info) { stacktrace_frame frame{}; frame.filename = frame_info.obj_path; frame.symbol = frame_info.symbol; @@ -1041,10 +1041,9 @@ namespace libdwarf { }; CPPTRACE_FORCE_NO_INLINE - std::vector resolve_frames(const std::vector& frames) { + std::vector resolve_frames(const std::vector& frames) { std::vector trace(frames.size(), stacktrace_frame { 0, 0, 0, "", "" }); - const auto dlframes = get_frames_object_info(frames); - for(const auto& obj_entry : collate_frames(dlframes, trace)) { + for(const auto& obj_entry : collate_frames(frames, trace)) { const auto& obj_name = obj_entry.first; dwarf_resolver resolver(obj_name); for(const auto& entry : obj_entry.second) { diff --git a/src/symbols/symbols_with_nothing.cpp b/src/symbols/symbols_with_nothing.cpp index 107c4b4..46a3762 100644 --- a/src/symbols/symbols_with_nothing.cpp +++ b/src/symbols/symbols_with_nothing.cpp @@ -8,7 +8,17 @@ namespace cpptrace { namespace detail { namespace nothing { - std::vector resolve_frames(const std::vector& frames) { + std::vector resolve_frames(const std::vector& frames) { + return std::vector(frames.size(), { + 0, + 0, + 0, + "", + "" + }); + } + + std::vector resolve_frames(const std::vector& frames) { return std::vector(frames.size(), { 0, 0, diff --git a/src/unwind/unwind.hpp b/src/unwind/unwind.hpp index d50a71a..4eab8ea 100644 --- a/src/unwind/unwind.hpp +++ b/src/unwind/unwind.hpp @@ -15,7 +15,7 @@ namespace detail { constexpr size_t hard_max_frames = 100; #endif CPPTRACE_FORCE_NO_INLINE - std::vector capture_frames(size_t skip); + std::vector capture_frames(size_t skip); } } diff --git a/src/unwind/unwind_with_execinfo.cpp b/src/unwind/unwind_with_execinfo.cpp index ff9cdd3..3a3850b 100644 --- a/src/unwind/unwind_with_execinfo.cpp +++ b/src/unwind/unwind_with_execinfo.cpp @@ -13,12 +13,16 @@ namespace cpptrace { namespace detail { CPPTRACE_FORCE_NO_INLINE - std::vector capture_frames(size_t skip) { - std::vector frames(hard_max_frames + skip, nullptr); - const int n_frames = backtrace(frames.data(), int(hard_max_frames + skip)); // thread safe - frames.resize(n_frames); - frames.erase(frames.begin(), frames.begin() + ptrdiff_t(std::min(skip + 1, frames.size()))); - frames.shrink_to_fit(); + std::vector capture_frames(size_t skip) { + std::vector addrs(hard_max_frames + skip, nullptr); + const int n_frames = backtrace(addrs.data(), int(hard_max_frames + skip)); // thread safe + addrs.resize(n_frames); + addrs.erase(addrs.begin(), addrs.begin() + ptrdiff_t(std::min(skip + 1, addrs.size()))); + addrs.shrink_to_fit(); + std::vector frames(addrs.size(), 0); + for(std::size_t i = 0; i < addrs.size(); i++) { + frames[i] = reinterpret_cast(addrs[i]); + } return frames; } } diff --git a/src/unwind/unwind_with_nothing.cpp b/src/unwind/unwind_with_nothing.cpp index e877098..dfaf7fb 100644 --- a/src/unwind/unwind_with_nothing.cpp +++ b/src/unwind/unwind_with_nothing.cpp @@ -7,7 +7,7 @@ namespace cpptrace { namespace detail { - std::vector capture_frames(size_t) { + std::vector capture_frames(size_t) { return {}; } } diff --git a/src/unwind/unwind_with_unwind.cpp b/src/unwind/unwind_with_unwind.cpp index 02b36cd..ba31344 100644 --- a/src/unwind/unwind_with_unwind.cpp +++ b/src/unwind/unwind_with_unwind.cpp @@ -17,7 +17,7 @@ namespace detail { struct unwind_state { std::size_t skip; std::size_t count; - std::vector& vec; + std::vector& vec; }; _Unwind_Reason_Code unwind_callback(_Unwind_Context* context, void* arg) { @@ -44,14 +44,14 @@ namespace detail { return _URC_END_OF_STACK; } else { // TODO: push_back?... - state.vec[state.count++] = (void*)ip; + state.vec[state.count++] = ip; return _URC_NO_REASON; } } CPPTRACE_FORCE_NO_INLINE - std::vector capture_frames(size_t skip) { - std::vector frames(hard_max_frames, nullptr); + std::vector capture_frames(size_t skip) { + std::vector frames(hard_max_frames, 0); unwind_state state{skip + 1, 0, frames}; _Unwind_Backtrace(unwind_callback, &state); // presumably thread-safe frames.resize(state.count); diff --git a/src/unwind/unwind_with_winapi.cpp b/src/unwind/unwind_with_winapi.cpp index 932e571..bf918aa 100644 --- a/src/unwind/unwind_with_winapi.cpp +++ b/src/unwind/unwind_with_winapi.cpp @@ -5,6 +5,7 @@ #include "../platform/common.hpp" #include "../platform/utils.hpp" +#include #include #include @@ -12,12 +13,15 @@ namespace cpptrace { namespace detail { CPPTRACE_FORCE_NO_INLINE - std::vector capture_frames(size_t skip) { - std::vector addrs(hard_max_frames, nullptr); - int frames = CaptureStackBackTrace(static_cast(skip + 1), hard_max_frames, addrs.data(), NULL); - addrs.resize(frames); - addrs.shrink_to_fit(); - return addrs; + std::vector capture_frames(size_t skip) { + std::vector addrs(hard_max_frames, nullptr); + int n_frames = CaptureStackBackTrace(static_cast(skip + 1), hard_max_frames, addrs.data(), NULL); + addrs.resize(n_frames); + std::vector frames(addrs.size(), 0); + for(std::size_t i = 0; i < addrs.size(); i++) { + frames[i] = reinterpret_cast(addrs[i]); + } + return frames; } } } diff --git a/test/add_subdirectory-integration/CMakeLists.txt b/test/add_subdirectory-integration/CMakeLists.txt index 3a05977..94bd9df 100644 --- a/test/add_subdirectory-integration/CMakeLists.txt +++ b/test/add_subdirectory-integration/CMakeLists.txt @@ -6,6 +6,7 @@ add_executable(main main.cpp) add_subdirectory(cpptrace) target_link_libraries(main cpptrace) +target_compile_features(main PRIVATE cxx_std_11) if(WIN32) add_custom_command( diff --git a/test/add_subdirectory-integration/main.cpp b/test/add_subdirectory-integration/main.cpp index cd935bb..7141fcd 100644 --- a/test/add_subdirectory-integration/main.cpp +++ b/test/add_subdirectory-integration/main.cpp @@ -1,7 +1,7 @@ #include void trace() { - cpptrace::print_trace(); + cpptrace::generate_trace().print(); } void foo(int) { diff --git a/test/demo.cpp b/test/demo.cpp index c7dcf8e..8c52f9d 100644 --- a/test/demo.cpp +++ b/test/demo.cpp @@ -6,7 +6,7 @@ #include void trace() { - cpptrace::print_trace(); + cpptrace::generate_trace().print(); } void foo(int n) { diff --git a/test/fetchcontent-integration/CMakeLists.txt b/test/fetchcontent-integration/CMakeLists.txt index e4deb90..31d3453 100644 --- a/test/fetchcontent-integration/CMakeLists.txt +++ b/test/fetchcontent-integration/CMakeLists.txt @@ -14,6 +14,7 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(cpptrace) target_link_libraries(main cpptrace) +target_compile_features(main PRIVATE cxx_std_11) if(WIN32) add_custom_command( diff --git a/test/fetchcontent-integration/main.cpp b/test/fetchcontent-integration/main.cpp index cd935bb..7141fcd 100644 --- a/test/fetchcontent-integration/main.cpp +++ b/test/fetchcontent-integration/main.cpp @@ -1,7 +1,7 @@ #include void trace() { - cpptrace::print_trace(); + cpptrace::generate_trace().print(); } void foo(int) { diff --git a/test/findpackage-integration/CMakeLists.txt b/test/findpackage-integration/CMakeLists.txt index 39bb68b..e93ea0c 100644 --- a/test/findpackage-integration/CMakeLists.txt +++ b/test/findpackage-integration/CMakeLists.txt @@ -6,3 +6,4 @@ add_executable(main main.cpp) find_package(cpptrace REQUIRED) target_link_libraries(main cpptrace::cpptrace) +target_compile_features(main PRIVATE cxx_std_11) diff --git a/test/findpackage-integration/main.cpp b/test/findpackage-integration/main.cpp index cd935bb..7141fcd 100644 --- a/test/findpackage-integration/main.cpp +++ b/test/findpackage-integration/main.cpp @@ -1,7 +1,7 @@ #include void trace() { - cpptrace::print_trace(); + cpptrace::generate_trace().print(); } void foo(int) { diff --git a/test/speedtest/speedtest.cpp b/test/speedtest/speedtest.cpp index 047bc7d..e56bbad 100644 --- a/test/speedtest/speedtest.cpp +++ b/test/speedtest/speedtest.cpp @@ -7,5 +7,5 @@ #include TEST(TraceTest, trace_test) { - ASSERT_THROW((cpptrace::print_trace(), false), std::logic_error); + ASSERT_THROW((cpptrace::generate_trace().print(), false), std::logic_error); }