From 33506dd4bf8a5109ffca57798491d2720ddc7f05 Mon Sep 17 00:00:00 2001 From: ovaar <1405257+ovaar@users.noreply.github.com> Date: Tue, 26 Aug 2025 16:00:31 +0200 Subject: [PATCH] fix(backward): Add std::atomic for Windows to prevent deadlock in Debug due to CRT's SIGABRT handler behaviour Scenario: 1. On Windows, the first crash is handled via UnhandledExceptionFilter -> crash_handler(EXCEPTION_POINTERS*), which wakes the reporter thread to collect and print the stack trace. 2. After printing, our handler calls abort(). In Debug CRT, abort raises SIGABRT and invokes our installed signal handler again. 3. On this second entry, crash_handler(int) sets crashed() = crash_status::crashed and waits on the condition variable. But the reporter thread has already finished (set crashed() = ending) and exited, so no one flips the state anymore. The wait blocks forever, causing a deadlock that only reproduces in Debug. Fix * Add atomic reentry guard to ensure crash handling runs only once across all entry points (SEH, SIGABRT, terminate and invalid parameter). Where subsequent entries return to prevent deadlock. --- backward.hpp | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/backward.hpp b/backward.hpp index 263cd1b..f28a1ac 100644 --- a/backward.hpp +++ b/backward.hpp @@ -336,6 +336,10 @@ #include #include +#ifdef BACKWARD_ATLEAST_CXX11 +#include +#endif + #include #ifdef _WIN64 @@ -4393,6 +4397,18 @@ class SignalHandling { } private: +#ifdef BACKWARD_ATLEAST_CXX11 + // Re-entrancy guard: ensure we only handle a crash once. + static std::atomic &handling_in_progress() { + static std::atomic flag{false}; + return flag; + } + + static bool begin_handling_once() { + bool expected = false; + return handling_in_progress().compare_exchange_strong(expected, true, std::memory_order_acq_rel); + } +#endif static CONTEXT *ctx() { static CONTEXT data; return &data; @@ -4442,11 +4458,21 @@ class SignalHandling { } static inline void terminator() { +#ifdef BACKWARD_ATLEAST_CXX11 + if (!begin_handling_once()) { + return; + } +#endif crash_handler(signal_skip_recs); abort(); } static inline void signal_handler(int) { +#ifdef BACKWARD_ATLEAST_CXX11 + if (!begin_handling_once()) { + return; + } +#endif crash_handler(signal_skip_recs); abort(); } @@ -4456,11 +4482,22 @@ class SignalHandling { const wchar_t *, unsigned int, uintptr_t) { +#ifdef BACKWARD_ATLEAST_CXX11 + if (!begin_handling_once()) { + return; + } +#endif crash_handler(signal_skip_recs); abort(); } NOINLINE static LONG WINAPI crash_handler(EXCEPTION_POINTERS *info) { +#ifdef BACKWARD_ATLEAST_CXX11 + // Only the first crash should be processed. + if (!begin_handling_once()) { + return EXCEPTION_CONTINUE_SEARCH; + } +#endif // The exception info supplies a trace from exactly where the issue was, // no need to skip records crash_handler(0, info->ContextRecord);