From e80afd460b4b3422ac61639c18ea75ad389bf6bc Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 12 Nov 2023 23:31:41 -0500 Subject: [PATCH] Add safe tracing interface --- include/cpptrace/cpptrace.hpp | 13 +++++++++++ src/cpptrace.cpp | 20 ++++++++++++++++ src/unwind/unwind.hpp | 4 ++++ src/unwind/unwind_with_dbghelp.cpp | 6 +++++ src/unwind/unwind_with_execinfo.cpp | 6 +++++ src/unwind/unwind_with_libunwind.cpp | 35 ++++++++++++++++++++++++++++ src/unwind/unwind_with_nothing.cpp | 5 ++++ src/unwind/unwind_with_unwind.cpp | 6 +++++ src/unwind/unwind_with_winapi.cpp | 6 +++++ 9 files changed, 101 insertions(+) diff --git a/include/cpptrace/cpptrace.hpp b/include/cpptrace/cpptrace.hpp index ce35914..a1364d9 100644 --- a/include/cpptrace/cpptrace.hpp +++ b/include/cpptrace/cpptrace.hpp @@ -26,6 +26,7 @@ namespace cpptrace { struct CPPTRACE_EXPORT raw_trace { std::vector frames; + static raw_trace from_buffer(frame_ptr* buffer, std::size_t size); static raw_trace current(std::uint_least32_t skip = 0); static raw_trace current(std::uint_least32_t skip, std::uint_least32_t max_depth); object_trace resolve_object_trace() const; @@ -124,6 +125,18 @@ namespace cpptrace { CPPTRACE_EXPORT stacktrace generate_trace(std::uint_least32_t skip = 0); CPPTRACE_EXPORT stacktrace generate_trace(std::uint_least32_t skip, std::uint_least32_t max_depth); + CPPTRACE_EXPORT std::size_t safe_generate_raw_trace( + frame_ptr* buffer, + std::size_t size, + std::uint_least32_t skip = 0 + ); + CPPTRACE_EXPORT std::size_t safe_generate_raw_trace( + frame_ptr* buffer, + std::size_t size, + std::uint_least32_t skip, + std::uint_least32_t max_depth + ); + // utilities: CPPTRACE_EXPORT std::string demangle(const std::string& name); CPPTRACE_EXPORT void absorb_trace_exceptions(bool absorb); diff --git a/src/cpptrace.cpp b/src/cpptrace.cpp index c0634c6..bbb02a2 100644 --- a/src/cpptrace.cpp +++ b/src/cpptrace.cpp @@ -28,6 +28,11 @@ #define CYAN ESC "36m" namespace cpptrace { + CPPTRACE_FORCE_NO_INLINE + raw_trace raw_trace::from_buffer(frame_ptr* buffer, std::size_t size) { + return raw_trace{std::vector(buffer, buffer + size)}; + } + CPPTRACE_FORCE_NO_INLINE raw_trace raw_trace::current(std::uint_least32_t skip) { return generate_raw_trace(skip + 1); @@ -263,6 +268,21 @@ namespace cpptrace { } } + CPPTRACE_FORCE_NO_INLINE + std::size_t safe_generate_raw_trace(frame_ptr* buffer, std::size_t size, std::uint_least32_t skip) { + return detail::safe_capture_frames(buffer, size, skip + 1, UINT_LEAST32_MAX); + } + + CPPTRACE_FORCE_NO_INLINE + std::size_t safe_generate_raw_trace( + frame_ptr* buffer, + std::size_t size, + std::uint_least32_t skip, + std::uint_least32_t max_depth + ) { + return detail::safe_capture_frames(buffer, size, skip + 1, max_depth); + } + CPPTRACE_FORCE_NO_INLINE object_trace generate_object_trace(std::uint_least32_t skip) { try { diff --git a/src/unwind/unwind.hpp b/src/unwind/unwind.hpp index f42588c..ed89bf2 100644 --- a/src/unwind/unwind.hpp +++ b/src/unwind/unwind.hpp @@ -14,8 +14,12 @@ namespace detail { #else constexpr std::size_t hard_max_frames = 100; #endif + CPPTRACE_FORCE_NO_INLINE std::vector capture_frames(std::size_t skip, std::size_t max_depth); + + CPPTRACE_FORCE_NO_INLINE + std::size_t safe_capture_frames(frame_ptr* buffer, std::size_t size, std::size_t skip, std::size_t max_depth); } } diff --git a/src/unwind/unwind_with_dbghelp.cpp b/src/unwind/unwind_with_dbghelp.cpp index 4e912c9..c2f99ca 100644 --- a/src/unwind/unwind_with_dbghelp.cpp +++ b/src/unwind/unwind_with_dbghelp.cpp @@ -152,6 +152,12 @@ namespace detail { } return trace; } + + CPPTRACE_FORCE_NO_INLINE + std::size_t safe_capture_frames(frame_ptr* buffer, std::size_t size, std::size_t skip, std::size_t max_depth) { + // Can't safe trace with dbghelp + return 0; + } #if IS_MSVC #pragma warning(pop) #endif diff --git a/src/unwind/unwind_with_execinfo.cpp b/src/unwind/unwind_with_execinfo.cpp index 3f4c2fa..cddc952 100644 --- a/src/unwind/unwind_with_execinfo.cpp +++ b/src/unwind/unwind_with_execinfo.cpp @@ -27,6 +27,12 @@ namespace detail { } return frames; } + + CPPTRACE_FORCE_NO_INLINE + std::size_t safe_capture_frames(frame_ptr* buffer, std::size_t size, std::size_t skip, std::size_t max_depth) { + // Can't safe trace with execinfo + return 0; + } } } diff --git a/src/unwind/unwind_with_libunwind.cpp b/src/unwind/unwind_with_libunwind.cpp index c01e09d..de38490 100644 --- a/src/unwind/unwind_with_libunwind.cpp +++ b/src/unwind/unwind_with_libunwind.cpp @@ -36,6 +36,41 @@ namespace detail { } while(unw_step(&cursor) > 0 && frames.size() < max_depth); return frames; } + + CPPTRACE_FORCE_NO_INLINE + std::size_t safe_capture_frames(frame_ptr* buffer, std::size_t size, std::size_t skip, std::size_t max_depth) { + // some code duplication, but whatever + skip++; + std::vector frames; + unw_context_t context; + unw_cursor_t cursor; + // thread and signal-safe https://www.nongnu.org/libunwind/man/unw_getcontext(3).html + unw_getcontext(&context); + // thread and signal-safe https://www.nongnu.org/libunwind/man/unw_init_local(3).html + unw_init_local(&cursor, &context); + size_t i = 0; + while(i < size && i < max_depth) { + unw_word_t pc; + unw_word_t sp; + // thread and signal-safe https://www.nongnu.org/libunwind/man/unw_get_reg(3).html + unw_get_reg(&cursor, UNW_REG_IP, &pc); + unw_get_reg(&cursor, UNW_REG_SP, &sp); + if(skip) { + skip--; + } else { + // pc is the instruction after the `call`, adjust back to the previous instruction + // just a cast, signal safe + buffer[i] = to_frame_ptr(pc) - 1; + i++; + } + // thread and signal-safe as long as the cursor is in the local address space, which it is + // https://www.nongnu.org/libunwind/man/unw_step(3).html + if(unw_step(&cursor) <= 0) { + break; + } + } + return i; + } } } diff --git a/src/unwind/unwind_with_nothing.cpp b/src/unwind/unwind_with_nothing.cpp index 55ebf27..88715f8 100644 --- a/src/unwind/unwind_with_nothing.cpp +++ b/src/unwind/unwind_with_nothing.cpp @@ -10,6 +10,11 @@ namespace detail { std::vector capture_frames(std::size_t, std::size_t) { return {}; } + + CPPTRACE_FORCE_NO_INLINE + std::size_t safe_capture_frames(frame_ptr* buffer, std::size_t size, std::size_t skip, std::size_t max_depth) { + return 0; + } } } diff --git a/src/unwind/unwind_with_unwind.cpp b/src/unwind/unwind_with_unwind.cpp index a38a342..4bef83a 100644 --- a/src/unwind/unwind_with_unwind.cpp +++ b/src/unwind/unwind_with_unwind.cpp @@ -62,6 +62,12 @@ namespace detail { frames.shrink_to_fit(); return frames; } + + CPPTRACE_FORCE_NO_INLINE + std::size_t safe_capture_frames(frame_ptr* buffer, std::size_t size, std::size_t skip, std::size_t max_depth) { + // Can't safe trace with _Unwind + return 0; + } } } diff --git a/src/unwind/unwind_with_winapi.cpp b/src/unwind/unwind_with_winapi.cpp index 42e3a3e..ee51926 100644 --- a/src/unwind/unwind_with_winapi.cpp +++ b/src/unwind/unwind_with_winapi.cpp @@ -37,6 +37,12 @@ namespace detail { } return frames; } + + CPPTRACE_FORCE_NO_INLINE + std::size_t safe_capture_frames(frame_ptr* buffer, std::size_t size, std::size_t skip, std::size_t max_depth) { + // Can't safe trace with winapi + return 0; + } } }