From da739d30c5e28ffc5abe8a31f2a202cc6110547a Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Sun, 3 Mar 2024 12:12:25 -0600 Subject: [PATCH] Add can_signal_safe_unwind and update some documentation surrounding signal-safe stack tracing --- README.md | 18 ++++++++++++------ docs/c-api.md | 1 + docs/signal-safe-tracing.md | 15 +++++++++------ include/cpptrace/cpptrace.hpp | 1 + include/ctrace/ctrace.h | 1 + src/cpptrace.cpp | 4 ++++ src/ctrace.cpp | 4 ++++ src/unwind/unwind.hpp | 2 ++ src/unwind/unwind_with_dbghelp.cpp | 4 ++++ src/unwind/unwind_with_execinfo.cpp | 4 ++++ src/unwind/unwind_with_libunwind.cpp | 4 ++++ src/unwind/unwind_with_nothing.cpp | 4 ++++ src/unwind/unwind_with_unwind.cpp | 4 ++++ src/unwind/unwind_with_winapi.cpp | 4 ++++ 14 files changed, 58 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 7840eca..66b7c11 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,8 @@ Cpptrace also has a C API, docs [here](docs/c-api.md). - [Conan](#conan) - [Vcpkg](#vcpkg) - [Platform Logistics](#platform-logistics) + - [Windows](#windows) + - [macOS](#macos) - [Library Back-Ends](#library-back-ends) - [Summary of Library Configurations](#summary-of-library-configurations) - [Testing Methodology](#testing-methodology) @@ -454,16 +456,20 @@ namespace cpptrace { object_frame resolve() const; // To be called outside a signal handler. Not signal safe. }; void get_safe_object_frame(frame_ptr address, safe_object_frame* out); + bool can_signal_safe_unwind(); } ``` -**Note:** Not all back-ends and platforms support these interfaces. If signal-safe unwinding isn't supported, -`safe_generate_raw_trace` will just produce an empty trace, and if object information can't be resolved in a signal-safe -way then `get_safe_object_frame` will not populate fields beyond the `raw_address`. +> [!IMPORTANT] +> Currently signal-safe stack unwinding is only possible with `libunwind`, which must be +> [manually enabled](#library-back-ends). If signal-safe unwinding isn't supported, `safe_generate_raw_trace` will just +> produce an empty trace. `can_signal_safe_unwind` can be used to check for signal-safe unwinding support. If object +> information can't be resolved in a signal-safe way then `get_safe_object_frame` will not populate fields beyond the +> `raw_address`. -**Another big note:** Calls to shared objects can be lazy-loaded where the first call to the shared object invokes -non-signal-safe functions such as `malloc()`. To avoid this, call these routines in `main()` ahead of a signal handler -to "warm up" the library. +> [!CAUTION] +> Calls to shared objects can be lazy-loaded where the first call to the shared object invokes non-signal-safe functions +> such as `malloc()`. To avoid this, call these routines in `main()` ahead of a signal handler to "warm up" the library. Because signal-safe tracing is an involved process, I have written up a comprehensive overview of what is involved at [signal-safe-tracing.md](docs/signal-safe-tracing.md). diff --git a/docs/c-api.md b/docs/c-api.md index 6b79908..43f91d8 100644 --- a/docs/c-api.md +++ b/docs/c-api.md @@ -168,4 +168,5 @@ struct ctrace_safe_object_frame { }; size_t ctrace_safe_generate_raw_trace(ctrace_frame_ptr* buffer, size_t size, size_t skip, size_t max_depth); void ctrace_get_safe_object_frame(ctrace_frame_ptr address, ctrace_safe_object_frame* out); +ctrace_bool can_signal_safe_unwind(); ``` diff --git a/docs/signal-safe-tracing.md b/docs/signal-safe-tracing.md index 3dbebdb..681c27e 100644 --- a/docs/signal-safe-tracing.md +++ b/docs/signal-safe-tracing.md @@ -32,6 +32,13 @@ FAQ: What's the worst that could happen if you call `cpptrace::generate_trace(). signal handler? In many cases you might be able to get away with it but you risk deadlocking or memory corruption. +> [!IMPORTANT] +> Currently signal-safe stack unwinding is only possible with `libunwind`, more details later. + +> [!CAUTION] +> Calls to shared objects can be lazy-loaded where the first call to the shared object invokes non-signal-safe functions +> such as `malloc()`. Because of this, the signal safe api must be "warmed up" ahead of a signal handler. + # API Cpptrace provides APIs for generating raw trace information safely and then also safely resolving @@ -53,6 +60,8 @@ namespace cpptrace { // signal-safe void get_safe_object_frame(frame_ptr address, safe_object_frame* out); + // signal-safe + bool can_signal_safe_unwind(); } ``` @@ -88,12 +97,6 @@ Currently the only back-end that can unwind safely is libunwind. Currently, the information in a signal-safe manner is `_dl_find_object`, which doesn't exist on macos (or windows of course). If anyone knows ways to do these safely on other platforms, I'd be much appreciative. -# A pitfall to be aware of - -Calls to functions in shared objects can be lazy-loaded where the first call to the shared object invokes -non-signal-safe functions such as `malloc()`. To avoid this, call these safe cpptrace routines in `main()` ahead of a -signal handler to "warm up" the library. - # Signal-Safe Tracing With `fork()` + `exec()` Of the three strategies, `fork()` + `exec()`, is the most technically involved and the only way to resolve while the diff --git a/include/cpptrace/cpptrace.hpp b/include/cpptrace/cpptrace.hpp index 5282671..83cf3e4 100644 --- a/include/cpptrace/cpptrace.hpp +++ b/include/cpptrace/cpptrace.hpp @@ -239,6 +239,7 @@ namespace cpptrace { }; // signal-safe CPPTRACE_EXPORT void get_safe_object_frame(frame_ptr address, safe_object_frame* out); + CPPTRACE_EXPORT bool can_signal_safe_unwind(); // utilities: CPPTRACE_EXPORT std::string demangle(const std::string& name); diff --git a/include/ctrace/ctrace.h b/include/ctrace/ctrace.h index 237a5f4..c29b861 100644 --- a/include/ctrace/ctrace.h +++ b/include/ctrace/ctrace.h @@ -131,6 +131,7 @@ CTRACE_BEGIN_DEFINITIONS /* ctrace::safe: */ CPPTRACE_EXPORT size_t ctrace_safe_generate_raw_trace(ctrace_frame_ptr* buffer, size_t size, size_t skip, size_t max_depth); CPPTRACE_EXPORT void ctrace_get_safe_object_frame(ctrace_frame_ptr address, ctrace_safe_object_frame* out); + CPPTRACE_EXPORT ctrace_bool can_signal_safe_unwind(); /* ctrace::io: */ CPPTRACE_EXPORT ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color); diff --git a/src/cpptrace.cpp b/src/cpptrace.cpp index 1b913f4..f38087a 100644 --- a/src/cpptrace.cpp +++ b/src/cpptrace.cpp @@ -399,6 +399,10 @@ namespace cpptrace { detail::get_safe_object_frame(address, out); } + bool can_signal_safe_unwind() { + return detail::has_safe_unwind(); + } + std::string demangle(const std::string& name) { return detail::demangle(name); } diff --git a/src/ctrace.cpp b/src/ctrace.cpp index d64ee9d..a84b4b0 100644 --- a/src/ctrace.cpp +++ b/src/ctrace.cpp @@ -310,6 +310,10 @@ extern "C" { cpptrace::get_safe_object_frame(address, reinterpret_cast(out)); } + ctrace_bool can_signal_safe_unwind() { + return cpptrace::can_signal_safe_unwind(); + } + // ctrace::io: ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color) { if(!trace || !trace->frames) { diff --git a/src/unwind/unwind.hpp b/src/unwind/unwind.hpp index ae57504..7f9450c 100644 --- a/src/unwind/unwind.hpp +++ b/src/unwind/unwind.hpp @@ -20,6 +20,8 @@ namespace detail { 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); + + bool has_safe_unwind(); } } diff --git a/src/unwind/unwind_with_dbghelp.cpp b/src/unwind/unwind_with_dbghelp.cpp index 4ba36e1..5e49fe5 100644 --- a/src/unwind/unwind_with_dbghelp.cpp +++ b/src/unwind/unwind_with_dbghelp.cpp @@ -161,6 +161,10 @@ namespace detail { #if IS_MSVC #pragma warning(pop) #endif + + bool has_safe_unwind() { + return false; + } } } diff --git a/src/unwind/unwind_with_execinfo.cpp b/src/unwind/unwind_with_execinfo.cpp index 629d571..62632b3 100644 --- a/src/unwind/unwind_with_execinfo.cpp +++ b/src/unwind/unwind_with_execinfo.cpp @@ -35,6 +35,10 @@ namespace detail { // Can't safe trace with execinfo return 0; } + + bool has_safe_unwind() { + return false; + } } } diff --git a/src/unwind/unwind_with_libunwind.cpp b/src/unwind/unwind_with_libunwind.cpp index 0e3c2ac..639df80 100644 --- a/src/unwind/unwind_with_libunwind.cpp +++ b/src/unwind/unwind_with_libunwind.cpp @@ -70,6 +70,10 @@ namespace detail { } return i; } + + bool has_safe_unwind() { + return true; + } } } diff --git a/src/unwind/unwind_with_nothing.cpp b/src/unwind/unwind_with_nothing.cpp index 88d45b6..3b7c9b9 100644 --- a/src/unwind/unwind_with_nothing.cpp +++ b/src/unwind/unwind_with_nothing.cpp @@ -15,6 +15,10 @@ namespace detail { std::size_t safe_capture_frames(frame_ptr*, std::size_t, std::size_t, std::size_t) { return 0; } + + bool has_safe_unwind() { + return false; + } } } diff --git a/src/unwind/unwind_with_unwind.cpp b/src/unwind/unwind_with_unwind.cpp index 48bf29f..a6376b0 100644 --- a/src/unwind/unwind_with_unwind.cpp +++ b/src/unwind/unwind_with_unwind.cpp @@ -65,6 +65,10 @@ namespace detail { // Can't safe trace with _Unwind return 0; } + + bool has_safe_unwind() { + return false; + } } } diff --git a/src/unwind/unwind_with_winapi.cpp b/src/unwind/unwind_with_winapi.cpp index a499357..2eaf5ee 100644 --- a/src/unwind/unwind_with_winapi.cpp +++ b/src/unwind/unwind_with_winapi.cpp @@ -43,6 +43,10 @@ namespace detail { // Can't safe trace with winapi return 0; } + + bool has_safe_unwind() { + return false; + } } }