Add can_signal_safe_unwind and update some documentation surrounding signal-safe stack tracing

This commit is contained in:
Jeremy 2024-03-03 12:12:25 -06:00
parent ec264aa0eb
commit da739d30c5
No known key found for this signature in database
GPG Key ID: BE03111EB7ED6E2E
14 changed files with 58 additions and 12 deletions

View File

@ -42,6 +42,8 @@ Cpptrace also has a C API, docs [here](docs/c-api.md).
- [Conan](#conan) - [Conan](#conan)
- [Vcpkg](#vcpkg) - [Vcpkg](#vcpkg)
- [Platform Logistics](#platform-logistics) - [Platform Logistics](#platform-logistics)
- [Windows](#windows)
- [macOS](#macos)
- [Library Back-Ends](#library-back-ends) - [Library Back-Ends](#library-back-ends)
- [Summary of Library Configurations](#summary-of-library-configurations) - [Summary of Library Configurations](#summary-of-library-configurations)
- [Testing Methodology](#testing-methodology) - [Testing Methodology](#testing-methodology)
@ -454,16 +456,20 @@ namespace cpptrace {
object_frame resolve() const; // To be called outside a signal handler. Not signal safe. 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); 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, > [!IMPORTANT]
`safe_generate_raw_trace` will just produce an empty trace, and if object information can't be resolved in a signal-safe > Currently signal-safe stack unwinding is only possible with `libunwind`, which must be
way then `get_safe_object_frame` will not populate fields beyond the `raw_address`. > [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 > [!CAUTION]
non-signal-safe functions such as `malloc()`. To avoid this, call these routines in `main()` ahead of a signal handler > Calls to shared objects can be lazy-loaded where the first call to the shared object invokes non-signal-safe functions
to "warm up" the library. > 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 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). what is involved at [signal-safe-tracing.md](docs/signal-safe-tracing.md).

View File

@ -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); 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); void ctrace_get_safe_object_frame(ctrace_frame_ptr address, ctrace_safe_object_frame* out);
ctrace_bool can_signal_safe_unwind();
``` ```

View File

@ -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 signal handler? In many cases you might be able to get away with it but you risk deadlocking or
memory corruption. 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 # API
Cpptrace provides APIs for generating raw trace information safely and then also safely resolving Cpptrace provides APIs for generating raw trace information safely and then also safely resolving
@ -53,6 +60,8 @@ namespace cpptrace {
// signal-safe // signal-safe
void get_safe_object_frame(frame_ptr address, safe_object_frame* out); 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 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. 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()` # 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 Of the three strategies, `fork()` + `exec()`, is the most technically involved and the only way to resolve while the

View File

@ -239,6 +239,7 @@ namespace cpptrace {
}; };
// signal-safe // signal-safe
CPPTRACE_EXPORT void get_safe_object_frame(frame_ptr address, safe_object_frame* out); CPPTRACE_EXPORT void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
CPPTRACE_EXPORT bool can_signal_safe_unwind();
// utilities: // utilities:
CPPTRACE_EXPORT std::string demangle(const std::string& name); CPPTRACE_EXPORT std::string demangle(const std::string& name);

View File

@ -131,6 +131,7 @@ CTRACE_BEGIN_DEFINITIONS
/* ctrace::safe: */ /* ctrace::safe: */
CPPTRACE_EXPORT size_t ctrace_safe_generate_raw_trace(ctrace_frame_ptr* buffer, size_t size, size_t skip, size_t max_depth); CPPTRACE_EXPORT 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 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: */ /* ctrace::io: */
CPPTRACE_EXPORT ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color); CPPTRACE_EXPORT ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color);

View File

@ -399,6 +399,10 @@ namespace cpptrace {
detail::get_safe_object_frame(address, out); detail::get_safe_object_frame(address, out);
} }
bool can_signal_safe_unwind() {
return detail::has_safe_unwind();
}
std::string demangle(const std::string& name) { std::string demangle(const std::string& name) {
return detail::demangle(name); return detail::demangle(name);
} }

View File

@ -310,6 +310,10 @@ extern "C" {
cpptrace::get_safe_object_frame(address, reinterpret_cast<cpptrace::safe_object_frame*>(out)); cpptrace::get_safe_object_frame(address, reinterpret_cast<cpptrace::safe_object_frame*>(out));
} }
ctrace_bool can_signal_safe_unwind() {
return cpptrace::can_signal_safe_unwind();
}
// ctrace::io: // ctrace::io:
ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color) { ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color) {
if(!trace || !trace->frames) { if(!trace || !trace->frames) {

View File

@ -20,6 +20,8 @@ namespace detail {
CPPTRACE_FORCE_NO_INLINE 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); 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();
} }
} }

View File

@ -161,6 +161,10 @@ namespace detail {
#if IS_MSVC #if IS_MSVC
#pragma warning(pop) #pragma warning(pop)
#endif #endif
bool has_safe_unwind() {
return false;
}
} }
} }

View File

@ -35,6 +35,10 @@ namespace detail {
// Can't safe trace with execinfo // Can't safe trace with execinfo
return 0; return 0;
} }
bool has_safe_unwind() {
return false;
}
} }
} }

View File

@ -70,6 +70,10 @@ namespace detail {
} }
return i; return i;
} }
bool has_safe_unwind() {
return true;
}
} }
} }

View File

@ -15,6 +15,10 @@ namespace detail {
std::size_t safe_capture_frames(frame_ptr*, std::size_t, std::size_t, std::size_t) { std::size_t safe_capture_frames(frame_ptr*, std::size_t, std::size_t, std::size_t) {
return 0; return 0;
} }
bool has_safe_unwind() {
return false;
}
} }
} }

View File

@ -65,6 +65,10 @@ namespace detail {
// Can't safe trace with _Unwind // Can't safe trace with _Unwind
return 0; return 0;
} }
bool has_safe_unwind() {
return false;
}
} }
} }

View File

@ -43,6 +43,10 @@ namespace detail {
// Can't safe trace with winapi // Can't safe trace with winapi
return 0; return 0;
} }
bool has_safe_unwind() {
return false;
}
} }
} }