From d1b7a61353b895d2f0b33fd99216d65a79282a17 Mon Sep 17 00:00:00 2001 From: huangqinjin Date: Tue, 16 Apr 2024 16:39:07 +0800 Subject: [PATCH] Implement stacktrace from current exception for MSVC (#159) std::current_exception() makes a copy of current exception object into returned std::exception_ptr. So the tracking of the original exception object and its stacktrace are lost. --- build/Jamfile.v2 | 3 +- doc/stacktrace.qbk | 2 +- include/boost/stacktrace/stacktrace.hpp | 22 ++++ include/boost/stacktrace/this_thread.hpp | 8 ++ src/from_exception.cpp | 142 +++++++++++++++++++++++ test/Jamfile.v2 | 12 ++ test/test_from_exception.cpp | 57 ++++++++- 7 files changed, 242 insertions(+), 4 deletions(-) diff --git a/build/Jamfile.v2 b/build/Jamfile.v2 index bafe644..2f52ca8 100644 --- a/build/Jamfile.v2 +++ b/build/Jamfile.v2 @@ -140,13 +140,12 @@ lib boost_stacktrace_from_exception : # requirements all linux:dl - windows:no # not supported at the moment # Command line option to disable build off:no # Require usable libbacktrace on other platforms - [ check-target-builds ../build//libbacktrace : : no ] + #[ check-target-builds ../build//libbacktrace : : no ] : # default build : # usage-requirements #shared:BOOST_STACKTRACE_DYN_LINK=1 diff --git a/doc/stacktrace.qbk b/doc/stacktrace.qbk index 2405dfc..5e0a6c9 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/include/boost/stacktrace/stacktrace.hpp b/include/boost/stacktrace/stacktrace.hpp index 6de3131..2257351 100644 --- a/include/boost/stacktrace/stacktrace.hpp +++ b/include/boost/stacktrace/stacktrace.hpp @@ -33,6 +33,26 @@ # pragma warning(disable:2196) // warning #2196: routine is both "inline" and "noinline" #endif +#if defined(BOOST_MSVC) + +extern "C" { + +BOOST_SYMBOL_EXPORT inline void* boost_stacktrace_impl_return_nullptr() { return nullptr; } +const char* boost_stacktrace_impl_current_exception_stacktrace(); +bool* boost_stacktrace_impl_ref_capture_stacktraces_at_throw(); + +} + +#ifdef _M_IX86 +# pragma comment(linker, "/ALTERNATENAME:_boost_stacktrace_impl_current_exception_stacktrace=_boost_stacktrace_impl_return_nullptr") +# pragma comment(linker, "/ALTERNATENAME:_boost_stacktrace_impl_ref_capture_stacktraces_at_throw=_boost_stacktrace_impl_return_nullptr") +#else +# pragma comment(linker, "/ALTERNATENAME:boost_stacktrace_impl_current_exception_stacktrace=boost_stacktrace_impl_return_nullptr") +# pragma comment(linker, "/ALTERNATENAME:boost_stacktrace_impl_ref_capture_stacktraces_at_throw=boost_stacktrace_impl_return_nullptr") +#endif + +#endif + namespace boost { namespace stacktrace { namespace impl { @@ -381,6 +401,8 @@ class basic_stacktrace { if (impl::current_exception_stacktrace) { trace = impl::current_exception_stacktrace(); } +#elif defined(BOOST_MSVC) + trace = boost_stacktrace_impl_current_exception_stacktrace(); #endif if (trace) { diff --git a/include/boost/stacktrace/this_thread.hpp b/include/boost/stacktrace/this_thread.hpp index 1e0f408..97436ff 100644 --- a/include/boost/stacktrace/this_thread.hpp +++ b/include/boost/stacktrace/this_thread.hpp @@ -27,6 +27,10 @@ inline void set_capture_stacktraces_at_throw(bool enable = true) noexcept { if (impl::ref_capture_stacktraces_at_throw) { impl::ref_capture_stacktraces_at_throw() = enable; } +#elif defined(BOOST_MSVC) + if (bool* p = boost_stacktrace_impl_ref_capture_stacktraces_at_throw()) { + *p = enable; + } #endif (void)enable; } @@ -45,6 +49,10 @@ inline bool get_capture_stacktraces_at_throw() noexcept { if (impl::ref_capture_stacktraces_at_throw) { return impl::ref_capture_stacktraces_at_throw(); } +#elif defined(BOOST_MSVC) + if (bool* p = boost_stacktrace_impl_ref_capture_stacktraces_at_throw()) { + return *p; + } #endif return false; } diff --git a/src/from_exception.cpp b/src/from_exception.cpp index 33dbe58..ec4d606 100644 --- a/src/from_exception.cpp +++ b/src/from_exception.cpp @@ -4,6 +4,147 @@ // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) +#if defined(_MSC_VER) + +#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 { + 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; + +inline bool PER_IS_MSVC_EH(PEXCEPTION_RECORD p) noexcept { + const DWORD EH_EXCEPTION_NUMBER = 0xE06D7363; + const ULONG_PTR EH_MAGIC_NUMBER1 = 0x19930520; + + return p->ExceptionCode == EH_EXCEPTION_NUMBER && + (p->NumberParameters == 3 || p->NumberParameters == 4) && + p->ExceptionInformation[0] == EH_MAGIC_NUMBER1; +} + +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) && + !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; + } + + 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; +} + +struct veh_installer { + PVOID h; + veh_installer() noexcept : h(AddVectoredExceptionHandler(1, veh)) {} + ~veh_installer() noexcept { RemoveVectoredExceptionHandler(h); } +} installer; + +} + +extern "C" { + +BOOST_SYMBOL_EXPORT const char* boost_stacktrace_impl_current_exception_stacktrace() { + 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() { + return &data.capture_stacktraces_at_throw; +} + +} + +namespace boost { namespace stacktrace { namespace impl { + +BOOST_SYMBOL_EXPORT void assert_no_pending_traces() noexcept { +} + +}}} // namespace boost::stacktrace::impl + +#else + #include "exception_headers.h" // At the moment the file is used only on POSIX. _Unwind_Backtrace may be @@ -222,3 +363,4 @@ BOOST_SYMBOL_EXPORT void assert_no_pending_traces() noexcept { }}} // namespace boost::stacktrace::impl +#endif diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 901d450..452aae6 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -183,17 +183,29 @@ test-suite stacktrace_tests [ run test_from_exception_none.cpp : : : $(FORCE_SYMBOL_EXPORT) $(BASIC_DEPS) on : from_exception_none_basic_ho ] [ run test_from_exception_none.cpp : : : $(LINKSHARED_BT) on : from_exception_none_bt ] [ run test_from_exception_none.cpp : : : BOOST_STACKTRACE_USE_BACKTRACE $(BT_DEPS) on : from_exception_none_bt_ho ] + [ run test_from_exception_none.cpp : : : $(LINKSHARED_WIND) on : from_exception_none_windbg ] + [ run test_from_exception_none.cpp : : : BOOST_STACKTRACE_USE_WINDBG $(WIND_DEPS) on : from_exception_none_windbg_ho ] + [ run test_from_exception_none.cpp : : : $(LINKSHARED_WIND_CACHED) on : from_exception_none_windbg_cached ] + [ run test_from_exception_none.cpp : : : BOOST_STACKTRACE_USE_WINDBG_CACHED $(WICA_DEPS) on : from_exception_none_windbg_cached_ho ] [ run test_from_exception_none.cpp : : : /boost/stacktrace//boost_stacktrace_from_exception $(LINKSHARED_BASIC) on : from_exception_disabled_basic ] [ run test_from_exception_none.cpp : : : /boost/stacktrace//boost_stacktrace_from_exception $(FORCE_SYMBOL_EXPORT) $(BASIC_DEPS) on : from_exception_disabled_basic_ho ] [ run test_from_exception_none.cpp : : : /boost/stacktrace//boost_stacktrace_from_exception $(LINKSHARED_BT) on : from_exception_disabled_bt ] [ run test_from_exception_none.cpp : : : /boost/stacktrace//boost_stacktrace_from_exception BOOST_STACKTRACE_USE_BACKTRACE $(BT_DEPS) : from_exception_disabled_bt_ho ] + [ run test_from_exception_none.cpp : : : /boost/stacktrace//boost_stacktrace_from_exception $(LINKSHARED_WIND) on : from_exception_disabled_windbg ] + [ run test_from_exception_none.cpp : : : /boost/stacktrace//boost_stacktrace_from_exception BOOST_STACKTRACE_USE_WINDBG $(WIND_DEPS) on : from_exception_disabled_windbg_ho ] + [ run test_from_exception_none.cpp : : : /boost/stacktrace//boost_stacktrace_from_exception $(LINKSHARED_WIND_CACHED) on : from_exception_disabled_windbg_cached ] + [ run test_from_exception_none.cpp : : : /boost/stacktrace//boost_stacktrace_from_exception BOOST_STACKTRACE_USE_WINDBG_CACHED $(WICA_DEPS) on : from_exception_disabled_windbg_cached_ho ] [ link test_from_exception.cpp : /boost/stacktrace//boost_stacktrace_from_exception $(LINKSHARED_BASIC) on : from_exception_basic ] [ run test_from_exception.cpp : : : /boost/stacktrace//boost_stacktrace_from_exception $(LINKSHARED_BT) on : from_exception_bt ] + [ run test_from_exception.cpp : : : /boost/stacktrace//boost_stacktrace_from_exception $(LINKSHARED_WIND) on : from_exception_windbg ] + [ run test_from_exception.cpp : : : /boost/stacktrace//boost_stacktrace_from_exception $(LINKSHARED_WIND_CACHED) on : from_exception_windbg_cached ] [ link test_from_exception.cpp : /boost/stacktrace//boost_stacktrace_from_exception $(FORCE_SYMBOL_EXPORT) $(BASIC_DEPS) on : from_exception_basic_ho ] [ run test_from_exception.cpp : : : /boost/stacktrace//boost_stacktrace_from_exception BOOST_STACKTRACE_USE_BACKTRACE $(BT_DEPS) on : from_exception_bt_ho ] + [ run test_from_exception.cpp : : : /boost/stacktrace//boost_stacktrace_from_exception BOOST_STACKTRACE_USE_WINDBG $(WIND_DEPS) on : from_exception_windbg_ho ] + [ run test_from_exception.cpp : : : /boost/stacktrace//boost_stacktrace_from_exception BOOST_STACKTRACE_USE_WINDBG_CACHED $(WICA_DEPS) on : from_exception_windbg_cached_ho ] ; # Assuring that examples compile and run. Adding sources from `examples` directory to the `type_index` test suite. diff --git a/test/test_from_exception.cpp b/test/test_from_exception.cpp index bb1c5fe..bf23461 100644 --- a/test/test_from_exception.cpp +++ b/test/test_from_exception.cpp @@ -31,7 +31,27 @@ BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void in_test_throw_1(const char* msg) { BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void in_test_throw_2(const char* msg) { std::string new_msg{msg}; - throw std::runtime_error(new_msg); + throw std::logic_error(new_msg); +} + +BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void in_test_rethrow_1(const char* msg) { + try { + in_test_throw_1(msg); + } catch (const std::exception&) { + throw; + } +} + +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() { @@ -67,6 +87,31 @@ BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void test_after_other_exception() { } } +BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void test_rethrow() { + try { + in_test_rethrow_1("test rethrow"); + } catch (const std::exception&) { + auto trace = stacktrace::from_current_exception(); + BOOST_TEST(trace); + std::cout << "Tarce in test_rethrow(): " << trace << '\n'; + BOOST_TEST(to_string(trace).find("in_test_throw_1") != std::string::npos); + BOOST_TEST(to_string(trace).find("in_test_rethrow_1") != std::string::npos); + } +} + +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"); @@ -103,7 +148,11 @@ BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void test_rethrow_nested() { BOOST_TEST(trace); std::cout << "Tarce in test_rethrow_nested(): " << trace << '\n'; BOOST_TEST(to_string(trace).find("in_test_throw_1") == std::string::npos); +#if defined(BOOST_MSVC) + BOOST_TEST(to_string(trace).find("in_test_throw_2") == std::string::npos); +#else BOOST_TEST(to_string(trace).find("in_test_throw_2") != std::string::npos); +#endif } } @@ -133,7 +182,11 @@ BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void test_from_other_thread() { BOOST_TEST(trace); std::cout << "Tarce in test_rethrow_nested(): " << trace << '\n'; BOOST_TEST(to_string(trace).find("in_test_throw_1") == std::string::npos); +#if defined(BOOST_MSVC) + BOOST_TEST(to_string(trace).find("in_test_throw_2") == std::string::npos); +#else BOOST_TEST(to_string(trace).find("in_test_throw_2") != std::string::npos); +#endif } #endif } @@ -146,6 +199,8 @@ int main() { test_no_exception(); test_trace_from_exception(); test_after_other_exception(); + test_rethrow(); + test_rethrow_after_other_exception(); test_nested(); test_rethrow_nested(); test_from_other_thread();