Skip to content

Commit e7b8b1d

Browse files
committed
Add SEH try/except utility
1 parent eb58832 commit e7b8b1d

File tree

5 files changed

+132
-2
lines changed

5 files changed

+132
-2
lines changed

README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ Cpptrace also has a C API, docs [here](docs/c-api.md).
3636
- [Performance](#performance)
3737
- [Rethrowing Exceptions](#rethrowing-exceptions)
3838
- [`cpptrace::try_catch`](#cpptracetry_catch)
39+
- [Traces from SEH exceptions](#traces-from-seh-exceptions)
3940
- [Traced Exception Objects](#traced-exception-objects)
4041
- [Wrapping std::exceptions](#wrapping-stdexceptions)
4142
- [Exception handling with cpptrace exception objects](#exception-handling-with-cpptrace-exception-objects)
@@ -877,6 +878,40 @@ Similar to a language `try`/`catch`, `catch` handlers will be considered in the
877878
take exactly one argument, equivalent to what would be written for a catch handler, except for `catch(...)` which can be
878879
achieved by a handler taking no arguments.
879880
881+
## Traces from SEH exceptions
882+
883+
Similar to the above section on collecting [traces from C++ exceptions](#traces-from-all-exceptions-cpptrace_try-and-cpptrace_catch),
884+
cpptrace provides `CPPTRACE_SEH_TRY` and `CPPTRACE_SEH_EXCEPT` macros that collect traces from SEH exceptions on windows
885+
with no overhead in the non-throwing (happy) path:
886+
887+
```cpp
888+
#include <cpptrace/from_current.hpp>
889+
#include <iostream>
890+
#include <windows.h>
891+
892+
void foo(int x, int y) {
893+
return x / y;
894+
}
895+
896+
int divide_zero_filter(int code) {
897+
if(code == STATUS_INTEGER_DIVIDE_BY_ZERO || code == EXCEPTION_FLT_DIVIDE_BY_ZERO) {
898+
return EXCEPTION_EXECUTE_HANDLER;
899+
}
900+
return EXCEPTION_CONTINUE_SEARCH;
901+
}
902+
903+
int main() {
904+
CPPTRACE_SEH_TRY {
905+
foo(10, 0);
906+
} CPPTRACE_SEH_EXCEPT(divide_zero_filter(GetExceptionCode())) {
907+
std::cerr<<"Division by zero happened!"<<std::endl;
908+
cpptrace::from_current_exception().print();
909+
}
910+
}
911+
```
912+
913+
The `CPPTRACE_SEH_EXCEPT` macro takes a filter expression as input, any expression valid in `__except` is valid.
914+
880915
## Traced Exception Objects
881916

882917
Cpptrace provides a handful of traced exception classes which automatically collect stack traces when thrown. These

include/cpptrace/from_current.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ CPPTRACE_BEGIN_NAMESPACE
4242
};
4343

4444
#ifdef _MSC_VER
45+
CPPTRACE_EXPORT CPPTRACE_FORCE_NO_INLINE
46+
int maybe_collect_trace(EXCEPTION_POINTERS* exception_ptrs, int filter_result);
4547
CPPTRACE_EXPORT CPPTRACE_FORCE_NO_INLINE
4648
void maybe_collect_trace(EXCEPTION_POINTERS* exception_ptrs, const std::type_info& type_info);
4749
template<typename E>

include/cpptrace/from_current_macros.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@
4848
} \
4949
}(); \
5050
} catch(param)
51+
#define CPPTRACE_SEH_TRY __try
52+
#define CPPTRACE_SEH_EXCEPT(filter) \
53+
__except(::cpptrace::detail::maybe_collect_trace(GetExceptionInformation(), (filter)))
5154
#else
5255
#define CPPTRACE_UNWIND_INTERCEPTOR_FOR(param) \
5356
::cpptrace::detail::unwind_interceptor_for<void(param)>
@@ -66,6 +69,10 @@
6669
#ifdef CPPTRACE_UNPREFIXED_TRY_CATCH
6770
#define TRY CPPTRACE_TRY
6871
#define CATCH(param) CPPTRACE_CATCH(param)
72+
#ifdef _MSC_VER
73+
#define SEH_TRY CPPTRACE_SEH_TRY
74+
#define SEH_EXCEPT(filter) CPPTRACE_SEH_EXCEPT(filter)
75+
#endif
6976
#endif
7077

7178
#endif // CPPTRACE_FROM_CURRENT_MACROS_HPP

src/from_current.cpp

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -612,12 +612,23 @@ CPPTRACE_BEGIN_NAMESPACE
612612
return false;
613613
}
614614
CPPTRACE_FORCE_NO_INLINE
615+
int maybe_collect_trace(EXCEPTION_POINTERS* exception_ptrs, int filter_result) {
616+
if(filter_result == EXCEPTION_EXECUTE_HANDLER) {
617+
#ifdef CPPTRACE_UNWIND_WITH_DBGHELP
618+
collect_current_trace(2, exception_ptrs);
619+
#else
620+
collect_current_trace(2);
621+
#endif
622+
}
623+
return filter_result;
624+
}
625+
CPPTRACE_FORCE_NO_INLINE
615626
void maybe_collect_trace(EXCEPTION_POINTERS* exception_ptrs, const std::type_info& type_info) {
616627
if(matches_exception(exception_ptrs, type_info)) {
617628
#ifdef CPPTRACE_UNWIND_WITH_DBGHELP
618-
collect_current_trace(2, exception_ptrs);
629+
collect_current_trace(2, exception_ptrs);
619630
#else
620-
collect_current_trace(2);
631+
collect_current_trace(2);
621632
#endif
622633
}
623634
}

test/unit/tracing/from_current.cpp

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,3 +180,78 @@ TEST(FromCurrent, NonThrowingPath) {
180180
}
181181
does_reach_end = true;
182182
}
183+
184+
#ifdef _MSC_VER
185+
186+
CPPTRACE_FORCE_NO_INLINE
187+
int my_div_function(int x, int y) {
188+
return x / y;
189+
}
190+
191+
int divide_zero_filter(int code) {
192+
if(code == STATUS_INTEGER_DIVIDE_BY_ZERO || code == EXCEPTION_FLT_DIVIDE_BY_ZERO) {
193+
return EXCEPTION_EXECUTE_HANDLER;
194+
}
195+
return EXCEPTION_CONTINUE_SEARCH;
196+
}
197+
198+
TEST(FromCurrent, SEHBasic) {
199+
bool does_enter_catch = false;
200+
auto guard = cpptrace::detail::scope_exit([&] {
201+
EXPECT_TRUE(does_enter_catch);
202+
});
203+
[&] () {
204+
CPPTRACE_SEH_TRY {
205+
[&] () {
206+
auto res = my_div_function(10, 0);
207+
(void)res;
208+
} ();
209+
} CPPTRACE_SEH_EXCEPT(divide_zero_filter(GetExceptionCode())) {
210+
[&] () {
211+
does_enter_catch = true;
212+
EXPECT_FALSE(cpptrace::current_exception_was_rethrown());
213+
const auto& trace = cpptrace::from_current_exception();
214+
ASSERT_GE(trace.frames.size(), 4);
215+
auto it = std::find_if(
216+
trace.frames.begin(),
217+
trace.frames.end(),
218+
[](const cpptrace::stacktrace_frame& frame) {
219+
return frame.symbol.find("my_div_function") != std::string::npos;
220+
}
221+
);
222+
EXPECT_NE(it, trace.frames.end()) << trace;
223+
size_t i = static_cast<size_t>(it - trace.frames.begin());
224+
EXPECT_FILE(trace.frames[i].filename, "from_current.cpp");
225+
} ();
226+
}
227+
} ();
228+
EXPECT_TRUE(does_enter_catch);
229+
}
230+
231+
TEST(FromCurrent, SEHCorrectHandler) {
232+
bool does_enter_catch = false;
233+
auto guard = cpptrace::detail::scope_exit([&] {
234+
EXPECT_TRUE(does_enter_catch);
235+
});
236+
[&] () {
237+
CPPTRACE_SEH_TRY {
238+
[&] () {
239+
CPPTRACE_SEH_TRY {
240+
[&] () {
241+
auto res = my_div_function(10, 0);
242+
(void)res;
243+
} ();
244+
} CPPTRACE_SEH_EXCEPT(EXCEPTION_CONTINUE_SEARCH) {
245+
[&] () {
246+
FAIL();
247+
} ();
248+
}
249+
} ();
250+
} CPPTRACE_SEH_EXCEPT(divide_zero_filter(GetExceptionCode())) {
251+
does_enter_catch = true;
252+
}
253+
} ();
254+
EXPECT_TRUE(does_enter_catch);
255+
}
256+
257+
#endif

0 commit comments

Comments
 (0)