Skip to content

Commit d1b7a61

Browse files
authored
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.
1 parent 351b03d commit d1b7a61

File tree

7 files changed

+242
-4
lines changed

7 files changed

+242
-4
lines changed

build/Jamfile.v2

+1-2
Original file line numberDiff line numberDiff line change
@@ -140,13 +140,12 @@ lib boost_stacktrace_from_exception
140140
: # requirements
141141
<warnings>all
142142
<target-os>linux:<library>dl
143-
<target-os>windows:<build>no # not supported at the moment
144143

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

148147
# Require usable libbacktrace on other platforms
149-
[ check-target-builds ../build//libbacktrace : : <build>no ]
148+
#[ check-target-builds ../build//libbacktrace : : <build>no ]
150149
: # default build
151150
: # usage-requirements
152151
#<link>shared:<define>BOOST_STACKTRACE_DYN_LINK=1

doc/stacktrace.qbk

+1-1
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ See [link stacktrace.theoretical_async_signal_safety "Theoretical async signal s
137137
[section Stacktrace from arbitrary exception]
138138

139139
[warning At the moment the functionality is only available for some of the
140-
popular C++ runtimes for POSIX systems and requires *libbacktrace*.
140+
popular C++ runtimes for POSIX systems with *libbacktrace* and for Windows.
141141
Make sure that your platform is supported by running some tests.
142142
]
143143

include/boost/stacktrace/stacktrace.hpp

+22
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,26 @@
3333
# pragma warning(disable:2196) // warning #2196: routine is both "inline" and "noinline"
3434
#endif
3535

36+
#if defined(BOOST_MSVC)
37+
38+
extern "C" {
39+
40+
BOOST_SYMBOL_EXPORT inline void* boost_stacktrace_impl_return_nullptr() { return nullptr; }
41+
const char* boost_stacktrace_impl_current_exception_stacktrace();
42+
bool* boost_stacktrace_impl_ref_capture_stacktraces_at_throw();
43+
44+
}
45+
46+
#ifdef _M_IX86
47+
# pragma comment(linker, "/ALTERNATENAME:_boost_stacktrace_impl_current_exception_stacktrace=_boost_stacktrace_impl_return_nullptr")
48+
# pragma comment(linker, "/ALTERNATENAME:_boost_stacktrace_impl_ref_capture_stacktraces_at_throw=_boost_stacktrace_impl_return_nullptr")
49+
#else
50+
# pragma comment(linker, "/ALTERNATENAME:boost_stacktrace_impl_current_exception_stacktrace=boost_stacktrace_impl_return_nullptr")
51+
# pragma comment(linker, "/ALTERNATENAME:boost_stacktrace_impl_ref_capture_stacktraces_at_throw=boost_stacktrace_impl_return_nullptr")
52+
#endif
53+
54+
#endif
55+
3656
namespace boost { namespace stacktrace {
3757

3858
namespace impl {
@@ -381,6 +401,8 @@ class basic_stacktrace {
381401
if (impl::current_exception_stacktrace) {
382402
trace = impl::current_exception_stacktrace();
383403
}
404+
#elif defined(BOOST_MSVC)
405+
trace = boost_stacktrace_impl_current_exception_stacktrace();
384406
#endif
385407

386408
if (trace) {

include/boost/stacktrace/this_thread.hpp

+8
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ inline void set_capture_stacktraces_at_throw(bool enable = true) noexcept {
2727
if (impl::ref_capture_stacktraces_at_throw) {
2828
impl::ref_capture_stacktraces_at_throw() = enable;
2929
}
30+
#elif defined(BOOST_MSVC)
31+
if (bool* p = boost_stacktrace_impl_ref_capture_stacktraces_at_throw()) {
32+
*p = enable;
33+
}
3034
#endif
3135
(void)enable;
3236
}
@@ -45,6 +49,10 @@ inline bool get_capture_stacktraces_at_throw() noexcept {
4549
if (impl::ref_capture_stacktraces_at_throw) {
4650
return impl::ref_capture_stacktraces_at_throw();
4751
}
52+
#elif defined(BOOST_MSVC)
53+
if (bool* p = boost_stacktrace_impl_ref_capture_stacktraces_at_throw()) {
54+
return *p;
55+
}
4856
#endif
4957
return false;
5058
}

src/from_exception.cpp

+142
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,147 @@
44
// accompanying file LICENSE_1_0.txt or copy at
55
// http://www.boost.org/LICENSE_1_0.txt)
66

7+
#if defined(_MSC_VER)
8+
9+
#include <boost/stacktrace/safe_dump_to.hpp>
10+
#include <windows.h>
11+
12+
extern "C" void** __cdecl __current_exception(); // exported from vcruntime.dll
13+
#define _pCurrentException static_cast<PEXCEPTION_RECORD>(*__current_exception())
14+
15+
namespace {
16+
17+
constexpr std::size_t kStacktraceDumpSize = 4096;
18+
19+
struct thrown_info {
20+
ULONG_PTR object;
21+
char* dump;
22+
};
23+
24+
struct exception_data {
25+
bool capture_stacktraces_at_throw = true;
26+
unsigned count = 0;
27+
thrown_info* info = nullptr;
28+
29+
~exception_data() noexcept {
30+
HANDLE hHeap = GetProcessHeap();
31+
for (unsigned i = 0; i < count; ++i) {
32+
HeapFree(hHeap, 0, info[i].dump);
33+
}
34+
HeapFree(hHeap, 0, info);
35+
}
36+
};
37+
38+
thread_local exception_data data;
39+
40+
inline bool PER_IS_MSVC_EH(PEXCEPTION_RECORD p) noexcept {
41+
const DWORD EH_EXCEPTION_NUMBER = 0xE06D7363;
42+
const ULONG_PTR EH_MAGIC_NUMBER1 = 0x19930520;
43+
44+
return p->ExceptionCode == EH_EXCEPTION_NUMBER &&
45+
(p->NumberParameters == 3 || p->NumberParameters == 4) &&
46+
p->ExceptionInformation[0] == EH_MAGIC_NUMBER1;
47+
}
48+
49+
inline ULONG_PTR PER_PEXCEPTOBJ(PEXCEPTION_RECORD p) noexcept {
50+
return p->ExceptionInformation[1];
51+
}
52+
53+
unsigned current_cxx_exception_index() noexcept {
54+
if (PEXCEPTION_RECORD current_cxx_exception = _pCurrentException) {
55+
for (unsigned i = data.count; i > 0;) {
56+
--i;
57+
if (data.info[i].object == PER_PEXCEPTOBJ(current_cxx_exception)) {
58+
return i;
59+
}
60+
}
61+
}
62+
return data.count;
63+
}
64+
65+
bool is_processing_rethrow(PEXCEPTION_RECORD p) noexcept {
66+
// Processing flow:
67+
// 0. throw;
68+
// 1. _CxxThrowException(pExceptionObject = nullptr)
69+
// 2. VEH & SEH (may throw new c++ exceptions!)
70+
// 3. __RethrowException(_pCurrentException)
71+
// 4. VEH
72+
if (PER_PEXCEPTOBJ(p) == 0) return true;
73+
PEXCEPTION_RECORD current_cxx_exception = _pCurrentException;
74+
if (current_cxx_exception == nullptr) return false;
75+
return PER_PEXCEPTOBJ(p) == PER_PEXCEPTOBJ(current_cxx_exception);
76+
}
77+
78+
LONG NTAPI veh(PEXCEPTION_POINTERS p) {
79+
if (data.capture_stacktraces_at_throw &&
80+
PER_IS_MSVC_EH(p->ExceptionRecord) &&
81+
!is_processing_rethrow(p->ExceptionRecord)) {
82+
HANDLE hHeap = GetProcessHeap();
83+
unsigned index = current_cxx_exception_index();
84+
unsigned new_count = 1 + (index < data.count ? index + 1 : 0);
85+
86+
for (unsigned i = new_count; i < data.count; ++i) {
87+
HeapFree(hHeap, 0, data.info[i].dump);
88+
data.info[i].dump = nullptr;
89+
}
90+
91+
void* new_info;
92+
if (data.info) {
93+
new_info = HeapReAlloc(hHeap, HEAP_ZERO_MEMORY, data.info, sizeof(thrown_info) * new_count);
94+
} else {
95+
new_info = HeapAlloc(hHeap, HEAP_ZERO_MEMORY, sizeof(thrown_info) * new_count);
96+
}
97+
if (new_info) {
98+
data.count = new_count;
99+
data.info = static_cast<thrown_info*>(new_info);
100+
data.info[data.count - 1].object = PER_PEXCEPTOBJ(p->ExceptionRecord);
101+
char*& dump_ptr = data.info[data.count - 1].dump;
102+
if (dump_ptr == nullptr) {
103+
dump_ptr = static_cast<char*>(HeapAlloc(hHeap, 0, kStacktraceDumpSize));
104+
}
105+
if (dump_ptr != nullptr) {
106+
const std::size_t kSkip = 4;
107+
boost::stacktrace::safe_dump_to(kSkip, dump_ptr, kStacktraceDumpSize);
108+
}
109+
} else if (new_count <= data.count) {
110+
data.count = new_count - 1;
111+
HeapFree(hHeap, 0, data.info[data.count - 1].dump);
112+
data.info[data.count - 1].dump = nullptr;
113+
}
114+
}
115+
return EXCEPTION_CONTINUE_SEARCH;
116+
}
117+
118+
struct veh_installer {
119+
PVOID h;
120+
veh_installer() noexcept : h(AddVectoredExceptionHandler(1, veh)) {}
121+
~veh_installer() noexcept { RemoveVectoredExceptionHandler(h); }
122+
} installer;
123+
124+
}
125+
126+
extern "C" {
127+
128+
BOOST_SYMBOL_EXPORT const char* boost_stacktrace_impl_current_exception_stacktrace() {
129+
unsigned index = current_cxx_exception_index();
130+
return index < data.count ? data.info[index].dump : nullptr;
131+
}
132+
133+
BOOST_SYMBOL_EXPORT bool* boost_stacktrace_impl_ref_capture_stacktraces_at_throw() {
134+
return &data.capture_stacktraces_at_throw;
135+
}
136+
137+
}
138+
139+
namespace boost { namespace stacktrace { namespace impl {
140+
141+
BOOST_SYMBOL_EXPORT void assert_no_pending_traces() noexcept {
142+
}
143+
144+
}}} // namespace boost::stacktrace::impl
145+
146+
#else
147+
7148
#include "exception_headers.h"
8149

9150
// 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 {
222363

223364
}}} // namespace boost::stacktrace::impl
224365

366+
#endif

test/Jamfile.v2

+12
Original file line numberDiff line numberDiff line change
@@ -183,17 +183,29 @@ test-suite stacktrace_tests
183183
[ run test_from_exception_none.cpp : : : $(FORCE_SYMBOL_EXPORT) $(BASIC_DEPS) <debug-symbols>on : from_exception_none_basic_ho ]
184184
[ run test_from_exception_none.cpp : : : $(LINKSHARED_BT) <debug-symbols>on : from_exception_none_bt ]
185185
[ run test_from_exception_none.cpp : : : <define>BOOST_STACKTRACE_USE_BACKTRACE $(BT_DEPS) <debug-symbols>on : from_exception_none_bt_ho ]
186+
[ run test_from_exception_none.cpp : : : $(LINKSHARED_WIND) <debug-symbols>on : from_exception_none_windbg ]
187+
[ run test_from_exception_none.cpp : : : <define>BOOST_STACKTRACE_USE_WINDBG $(WIND_DEPS) <debug-symbols>on : from_exception_none_windbg_ho ]
188+
[ run test_from_exception_none.cpp : : : $(LINKSHARED_WIND_CACHED) <debug-symbols>on : from_exception_none_windbg_cached ]
189+
[ run test_from_exception_none.cpp : : : <define>BOOST_STACKTRACE_USE_WINDBG_CACHED $(WICA_DEPS) <debug-symbols>on : from_exception_none_windbg_cached_ho ]
186190

187191
[ run test_from_exception_none.cpp : : : <library>/boost/stacktrace//boost_stacktrace_from_exception $(LINKSHARED_BASIC) <debug-symbols>on : from_exception_disabled_basic ]
188192
[ 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 ]
189193
[ run test_from_exception_none.cpp : : : <library>/boost/stacktrace//boost_stacktrace_from_exception $(LINKSHARED_BT) <debug-symbols>on : from_exception_disabled_bt ]
190194
[ 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 ]
195+
[ run test_from_exception_none.cpp : : : <library>/boost/stacktrace//boost_stacktrace_from_exception $(LINKSHARED_WIND) <debug-symbols>on : from_exception_disabled_windbg ]
196+
[ 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 ]
197+
[ run test_from_exception_none.cpp : : : <library>/boost/stacktrace//boost_stacktrace_from_exception $(LINKSHARED_WIND_CACHED) <debug-symbols>on : from_exception_disabled_windbg_cached ]
198+
[ 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 ]
191199

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

195205
[ link test_from_exception.cpp : <library>/boost/stacktrace//boost_stacktrace_from_exception $(FORCE_SYMBOL_EXPORT) $(BASIC_DEPS) <debug-symbols>on : from_exception_basic_ho ]
196206
[ 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 ]
207+
[ 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 ]
208+
[ 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 ]
197209
;
198210

199211
# Assuring that examples compile and run. Adding sources from `examples` directory to the `type_index` test suite.

test/test_from_exception.cpp

+56-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,27 @@ BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void in_test_throw_1(const char* msg) {
3131

3232
BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void in_test_throw_2(const char* msg) {
3333
std::string new_msg{msg};
34-
throw std::runtime_error(new_msg);
34+
throw std::logic_error(new_msg);
35+
}
36+
37+
BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void in_test_rethrow_1(const char* msg) {
38+
try {
39+
in_test_throw_1(msg);
40+
} catch (const std::exception&) {
41+
throw;
42+
}
43+
}
44+
45+
BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void in_test_rethrow_2(const char* msg) {
46+
try {
47+
in_test_throw_2(msg);
48+
} catch (const std::exception&) {
49+
try {
50+
in_test_throw_1(msg);
51+
} catch (const std::exception&) {}
52+
53+
throw;
54+
}
3555
}
3656

3757
BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void test_no_exception() {
@@ -67,6 +87,31 @@ BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void test_after_other_exception() {
6787
}
6888
}
6989

90+
BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void test_rethrow() {
91+
try {
92+
in_test_rethrow_1("test rethrow");
93+
} catch (const std::exception&) {
94+
auto trace = stacktrace::from_current_exception();
95+
BOOST_TEST(trace);
96+
std::cout << "Tarce in test_rethrow(): " << trace << '\n';
97+
BOOST_TEST(to_string(trace).find("in_test_throw_1") != std::string::npos);
98+
BOOST_TEST(to_string(trace).find("in_test_rethrow_1") != std::string::npos);
99+
}
100+
}
101+
102+
BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void test_rethrow_after_other_exception() {
103+
try {
104+
in_test_rethrow_2("test_rethrow_after_other_exception");
105+
} catch (const std::exception&) {
106+
auto trace = stacktrace::from_current_exception();
107+
BOOST_TEST(trace);
108+
std::cout << "Tarce in test_rethrow_after_other_exception(): " << trace << '\n';
109+
BOOST_TEST(to_string(trace).find("in_test_throw_1") == std::string::npos);
110+
BOOST_TEST(to_string(trace).find("in_test_throw_2") != std::string::npos);
111+
BOOST_TEST(to_string(trace).find("in_test_rethrow_2") != std::string::npos);
112+
}
113+
}
114+
70115
BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void test_nested() {
71116
try {
72117
in_test_throw_1("test_other_exception_active");
@@ -103,7 +148,11 @@ BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void test_rethrow_nested() {
103148
BOOST_TEST(trace);
104149
std::cout << "Tarce in test_rethrow_nested(): " << trace << '\n';
105150
BOOST_TEST(to_string(trace).find("in_test_throw_1") == std::string::npos);
151+
#if defined(BOOST_MSVC)
152+
BOOST_TEST(to_string(trace).find("in_test_throw_2") == std::string::npos);
153+
#else
106154
BOOST_TEST(to_string(trace).find("in_test_throw_2") != std::string::npos);
155+
#endif
107156
}
108157
}
109158

@@ -133,7 +182,11 @@ BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void test_from_other_thread() {
133182
BOOST_TEST(trace);
134183
std::cout << "Tarce in test_rethrow_nested(): " << trace << '\n';
135184
BOOST_TEST(to_string(trace).find("in_test_throw_1") == std::string::npos);
185+
#if defined(BOOST_MSVC)
186+
BOOST_TEST(to_string(trace).find("in_test_throw_2") == std::string::npos);
187+
#else
136188
BOOST_TEST(to_string(trace).find("in_test_throw_2") != std::string::npos);
189+
#endif
137190
}
138191
#endif
139192
}
@@ -146,6 +199,8 @@ int main() {
146199
test_no_exception();
147200
test_trace_from_exception();
148201
test_after_other_exception();
202+
test_rethrow();
203+
test_rethrow_after_other_exception();
149204
test_nested();
150205
test_rethrow_nested();
151206
test_from_other_thread();

0 commit comments

Comments
 (0)