Skip to content

Commit

Permalink
Implement stacktrace from current exception for MSVC (#159)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
huangqinjin authored Apr 16, 2024
1 parent 351b03d commit d1b7a61
Show file tree
Hide file tree
Showing 7 changed files with 242 additions and 4 deletions.
3 changes: 1 addition & 2 deletions build/Jamfile.v2
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,12 @@ lib boost_stacktrace_from_exception
: # requirements
<warnings>all
<target-os>linux:<library>dl
<target-os>windows:<build>no # not supported at the moment

# Command line option to disable build
<boost.stacktrace.from_exception>off:<build>no

# Require usable libbacktrace on other platforms
[ check-target-builds ../build//libbacktrace : : <build>no ]
#[ check-target-builds ../build//libbacktrace : : <build>no ]
: # default build
: # usage-requirements
#<link>shared:<define>BOOST_STACKTRACE_DYN_LINK=1
Expand Down
2 changes: 1 addition & 1 deletion doc/stacktrace.qbk
Original file line number Diff line number Diff line change
Expand Up @@ -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.
]

Expand Down
22 changes: 22 additions & 0 deletions include/boost/stacktrace/stacktrace.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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) {
Expand Down
8 changes: 8 additions & 0 deletions include/boost/stacktrace/this_thread.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand Down
142 changes: 142 additions & 0 deletions src/from_exception.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <boost/stacktrace/safe_dump_to.hpp>
#include <windows.h>

extern "C" void** __cdecl __current_exception(); // exported from vcruntime.dll
#define _pCurrentException static_cast<PEXCEPTION_RECORD>(*__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<thrown_info*>(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<char*>(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
Expand Down Expand Up @@ -222,3 +363,4 @@ BOOST_SYMBOL_EXPORT void assert_no_pending_traces() noexcept {

}}} // namespace boost::stacktrace::impl

#endif
12 changes: 12 additions & 0 deletions test/Jamfile.v2
Original file line number Diff line number Diff line change
Expand Up @@ -183,17 +183,29 @@ test-suite stacktrace_tests
[ run test_from_exception_none.cpp : : : $(FORCE_SYMBOL_EXPORT) $(BASIC_DEPS) <debug-symbols>on : from_exception_none_basic_ho ]
[ run test_from_exception_none.cpp : : : $(LINKSHARED_BT) <debug-symbols>on : from_exception_none_bt ]
[ run test_from_exception_none.cpp : : : <define>BOOST_STACKTRACE_USE_BACKTRACE $(BT_DEPS) <debug-symbols>on : from_exception_none_bt_ho ]
[ run test_from_exception_none.cpp : : : $(LINKSHARED_WIND) <debug-symbols>on : from_exception_none_windbg ]
[ run test_from_exception_none.cpp : : : <define>BOOST_STACKTRACE_USE_WINDBG $(WIND_DEPS) <debug-symbols>on : from_exception_none_windbg_ho ]
[ run test_from_exception_none.cpp : : : $(LINKSHARED_WIND_CACHED) <debug-symbols>on : from_exception_none_windbg_cached ]
[ run test_from_exception_none.cpp : : : <define>BOOST_STACKTRACE_USE_WINDBG_CACHED $(WICA_DEPS) <debug-symbols>on : from_exception_none_windbg_cached_ho ]

[ run test_from_exception_none.cpp : : : <library>/boost/stacktrace//boost_stacktrace_from_exception $(LINKSHARED_BASIC) <debug-symbols>on : from_exception_disabled_basic ]
[ run test_from_exception_none.cpp : : : <library>/boost/stacktrace//boost_stacktrace_from_exception $(FORCE_SYMBOL_EXPORT) $(BASIC_DEPS) <debug-symbols>on : from_exception_disabled_basic_ho ]
[ run test_from_exception_none.cpp : : : <library>/boost/stacktrace//boost_stacktrace_from_exception $(LINKSHARED_BT) <debug-symbols>on : from_exception_disabled_bt ]
[ run test_from_exception_none.cpp : : : <library>/boost/stacktrace//boost_stacktrace_from_exception <define>BOOST_STACKTRACE_USE_BACKTRACE $(BT_DEPS) : from_exception_disabled_bt_ho ]
[ run test_from_exception_none.cpp : : : <library>/boost/stacktrace//boost_stacktrace_from_exception $(LINKSHARED_WIND) <debug-symbols>on : from_exception_disabled_windbg ]
[ run test_from_exception_none.cpp : : : <library>/boost/stacktrace//boost_stacktrace_from_exception <define>BOOST_STACKTRACE_USE_WINDBG $(WIND_DEPS) <debug-symbols>on : from_exception_disabled_windbg_ho ]
[ run test_from_exception_none.cpp : : : <library>/boost/stacktrace//boost_stacktrace_from_exception $(LINKSHARED_WIND_CACHED) <debug-symbols>on : from_exception_disabled_windbg_cached ]
[ run test_from_exception_none.cpp : : : <library>/boost/stacktrace//boost_stacktrace_from_exception <define>BOOST_STACKTRACE_USE_WINDBG_CACHED $(WICA_DEPS) <debug-symbols>on : from_exception_disabled_windbg_cached_ho ]

[ link test_from_exception.cpp : <library>/boost/stacktrace//boost_stacktrace_from_exception $(LINKSHARED_BASIC) <debug-symbols>on : from_exception_basic ]
[ run test_from_exception.cpp : : : <library>/boost/stacktrace//boost_stacktrace_from_exception $(LINKSHARED_BT) <debug-symbols>on : from_exception_bt ]
[ run test_from_exception.cpp : : : <library>/boost/stacktrace//boost_stacktrace_from_exception $(LINKSHARED_WIND) <debug-symbols>on : from_exception_windbg ]
[ run test_from_exception.cpp : : : <library>/boost/stacktrace//boost_stacktrace_from_exception $(LINKSHARED_WIND_CACHED) <debug-symbols>on : from_exception_windbg_cached ]

[ link test_from_exception.cpp : <library>/boost/stacktrace//boost_stacktrace_from_exception $(FORCE_SYMBOL_EXPORT) $(BASIC_DEPS) <debug-symbols>on : from_exception_basic_ho ]
[ run test_from_exception.cpp : : : <library>/boost/stacktrace//boost_stacktrace_from_exception <define>BOOST_STACKTRACE_USE_BACKTRACE $(BT_DEPS) <debug-symbols>on : from_exception_bt_ho ]
[ run test_from_exception.cpp : : : <library>/boost/stacktrace//boost_stacktrace_from_exception <define>BOOST_STACKTRACE_USE_WINDBG $(WIND_DEPS) <debug-symbols>on : from_exception_windbg_ho ]
[ run test_from_exception.cpp : : : <library>/boost/stacktrace//boost_stacktrace_from_exception <define>BOOST_STACKTRACE_USE_WINDBG_CACHED $(WICA_DEPS) <debug-symbols>on : from_exception_windbg_cached_ho ]
;

# Assuring that examples compile and run. Adding sources from `examples` directory to the `type_index` test suite.
Expand Down
57 changes: 56 additions & 1 deletion test/test_from_exception.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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
}
}

Expand Down Expand Up @@ -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
}
Expand All @@ -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();
Expand Down

0 comments on commit d1b7a61

Please sign in to comment.