diff --git a/doc/stacktrace.qbk b/doc/stacktrace.qbk index d0fda13..429e308 100644 --- a/doc/stacktrace.qbk +++ b/doc/stacktrace.qbk @@ -137,7 +137,7 @@ See [link stacktrace.theoretical_async_signal_safety "Theoretical async signal s [section Stacktrace from arbitrary exception] [warning At the moment the functionality is only available for some of the - popular C++ runtimes for POSIX systems and requires *libbacktrace*. + popular C++ runtimes for POSIX systems with *libbacktrace* and for Windows. Make sure that your platform is supported by running some tests. ] diff --git a/src/from_exception.cpp b/src/from_exception.cpp index 0692eaa..ec4d606 100644 --- a/src/from_exception.cpp +++ b/src/from_exception.cpp @@ -9,15 +9,30 @@ #include #include +extern "C" void** __cdecl __current_exception(); // exported from vcruntime.dll +#define _pCurrentException static_cast(*__current_exception()) + namespace { constexpr std::size_t kStacktraceDumpSize = 4096; +struct thrown_info { + ULONG_PTR object; + char* dump; +}; + struct exception_data { - char dump_buffer[kStacktraceDumpSize]; - ULONG_PTR thrown_object = 0; - bool rethrow = false; bool capture_stacktraces_at_throw = true; + unsigned count = 0; + thrown_info* info = nullptr; + + ~exception_data() noexcept { + HANDLE hHeap = GetProcessHeap(); + for (unsigned i = 0; i < count; ++i) { + HeapFree(hHeap, 0, info[i].dump); + } + HeapFree(hHeap, 0, info); + } }; thread_local exception_data data; @@ -35,18 +50,66 @@ inline ULONG_PTR PER_PEXCEPTOBJ(PEXCEPTION_RECORD p) noexcept { return p->ExceptionInformation[1]; } +unsigned current_cxx_exception_index() noexcept { + if (PEXCEPTION_RECORD current_cxx_exception = _pCurrentException) { + for (unsigned i = data.count; i > 0;) { + --i; + if (data.info[i].object == PER_PEXCEPTOBJ(current_cxx_exception)) { + return i; + } + } + } + return data.count; +} + +bool is_processing_rethrow(PEXCEPTION_RECORD p) noexcept { + // Processing flow: + // 0. throw; + // 1. _CxxThrowException(pExceptionObject = nullptr) + // 2. VEH & SEH (may throw new c++ exceptions!) + // 3. __RethrowException(_pCurrentException) + // 4. VEH + if (PER_PEXCEPTOBJ(p) == 0) return true; + PEXCEPTION_RECORD current_cxx_exception = _pCurrentException; + if (current_cxx_exception == nullptr) return false; + return PER_PEXCEPTOBJ(p) == PER_PEXCEPTOBJ(current_cxx_exception); +} + LONG NTAPI veh(PEXCEPTION_POINTERS p) { - if (data.capture_stacktraces_at_throw && PER_IS_MSVC_EH(p->ExceptionRecord)) { - if (PER_PEXCEPTOBJ(p->ExceptionRecord) == 0) { - data.rethrow = true; - } else if (data.rethrow && PER_PEXCEPTOBJ(p->ExceptionRecord) == data.thrown_object) { - data.rethrow = false; - } else { - data.rethrow = false; - data.thrown_object = PER_PEXCEPTOBJ(p->ExceptionRecord); + if (data.capture_stacktraces_at_throw && + PER_IS_MSVC_EH(p->ExceptionRecord) && + !is_processing_rethrow(p->ExceptionRecord)) { + HANDLE hHeap = GetProcessHeap(); + unsigned index = current_cxx_exception_index(); + unsigned new_count = 1 + (index < data.count ? index + 1 : 0); + + for (unsigned i = new_count; i < data.count; ++i) { + HeapFree(hHeap, 0, data.info[i].dump); + data.info[i].dump = nullptr; + } - const std::size_t kSkip = 4; - boost::stacktrace::safe_dump_to(kSkip, data.dump_buffer, kStacktraceDumpSize); + void* new_info; + if (data.info) { + new_info = HeapReAlloc(hHeap, HEAP_ZERO_MEMORY, data.info, sizeof(thrown_info) * new_count); + } else { + new_info = HeapAlloc(hHeap, HEAP_ZERO_MEMORY, sizeof(thrown_info) * new_count); + } + if (new_info) { + data.count = new_count; + data.info = static_cast(new_info); + data.info[data.count - 1].object = PER_PEXCEPTOBJ(p->ExceptionRecord); + char*& dump_ptr = data.info[data.count - 1].dump; + if (dump_ptr == nullptr) { + dump_ptr = static_cast(HeapAlloc(hHeap, 0, kStacktraceDumpSize)); + } + if (dump_ptr != nullptr) { + const std::size_t kSkip = 4; + boost::stacktrace::safe_dump_to(kSkip, dump_ptr, kStacktraceDumpSize); + } + } else if (new_count <= data.count) { + data.count = new_count - 1; + HeapFree(hHeap, 0, data.info[data.count - 1].dump); + data.info[data.count - 1].dump = nullptr; } } return EXCEPTION_CONTINUE_SEARCH; @@ -63,7 +126,8 @@ struct veh_installer { extern "C" { BOOST_SYMBOL_EXPORT const char* boost_stacktrace_impl_current_exception_stacktrace() { - return data.capture_stacktraces_at_throw ? data.dump_buffer : nullptr; + unsigned index = current_cxx_exception_index(); + return index < data.count ? data.info[index].dump : nullptr; } BOOST_SYMBOL_EXPORT bool* boost_stacktrace_impl_ref_capture_stacktraces_at_throw() { diff --git a/test/test_from_exception.cpp b/test/test_from_exception.cpp index 3fc3e7b..bf23461 100644 --- a/test/test_from_exception.cpp +++ b/test/test_from_exception.cpp @@ -42,6 +42,18 @@ BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void in_test_rethrow_1(const char* msg) { } } +BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void in_test_rethrow_2(const char* msg) { + try { + in_test_throw_2(msg); + } catch (const std::exception&) { + try { + in_test_throw_1(msg); + } catch (const std::exception&) {} + + throw; + } +} + BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void test_no_exception() { auto trace = stacktrace::from_current_exception(); BOOST_TEST(!trace); @@ -70,13 +82,8 @@ BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void test_after_other_exception() { auto trace = stacktrace::from_current_exception(); BOOST_TEST(trace); std::cout << "Tarce in test_after_other_exception(): " << trace; -#if defined(BOOST_MSVC) - BOOST_TEST(to_string(trace).find("in_test_throw_1") == std::string::npos); - BOOST_TEST(to_string(trace).find("in_test_throw_2") != std::string::npos); -#else BOOST_TEST(to_string(trace).find("in_test_throw_1") != std::string::npos); BOOST_TEST(to_string(trace).find("in_test_throw_2") == std::string::npos); -#endif } } @@ -92,6 +99,19 @@ BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void test_rethrow() { } } +BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void test_rethrow_after_other_exception() { + try { + in_test_rethrow_2("test_rethrow_after_other_exception"); + } catch (const std::exception&) { + auto trace = stacktrace::from_current_exception(); + BOOST_TEST(trace); + std::cout << "Tarce in test_rethrow_after_other_exception(): " << trace << '\n'; + BOOST_TEST(to_string(trace).find("in_test_throw_1") == std::string::npos); + BOOST_TEST(to_string(trace).find("in_test_throw_2") != std::string::npos); + BOOST_TEST(to_string(trace).find("in_test_rethrow_2") != std::string::npos); + } +} + BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void test_nested() { try { in_test_throw_1("test_other_exception_active"); @@ -180,6 +200,7 @@ int main() { test_trace_from_exception(); test_after_other_exception(); test_rethrow(); + test_rethrow_after_other_exception(); test_nested(); test_rethrow_nested(); test_from_other_thread();