1 /*
2  *  Created by Phil on 21/08/2014
3  *  Copyright 2014 Two Blue Cubes Ltd. All rights reserved.
4  *
5  *  Distributed under the Boost Software License, Version 1.0. (See accompanying
6  *  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7  *
8  */
9 
10 #include "catch_fatal_condition.h"
11 
12 #include "catch_context.h"
13 #include "catch_interfaces_capture.h"
14 
15 #if defined(__GNUC__)
16 #    pragma GCC diagnostic push
17 #    pragma GCC diagnostic ignored "-Wmissing-field-initializers"
18 #endif
19 
20 #if defined( CATCH_CONFIG_WINDOWS_SEH ) || defined( CATCH_CONFIG_POSIX_SIGNALS )
21 
22 namespace {
23     // Report the error condition
reportFatal(char const * const message)24     void reportFatal( char const * const message ) {
25         Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition( message );
26     }
27 }
28 
29 #endif // signals/SEH handling
30 
31 #if defined( CATCH_CONFIG_WINDOWS_SEH )
32 
33 namespace Catch {
34     struct SignalDefs { DWORD id; const char* name; };
35 
36     // There is no 1-1 mapping between signals and windows exceptions.
37     // Windows can easily distinguish between SO and SigSegV,
38     // but SigInt, SigTerm, etc are handled differently.
39     static SignalDefs signalDefs[] = {
40         { static_cast<DWORD>(EXCEPTION_ILLEGAL_INSTRUCTION),  "SIGILL - Illegal instruction signal" },
41         { static_cast<DWORD>(EXCEPTION_STACK_OVERFLOW), "SIGSEGV - Stack overflow" },
42         { static_cast<DWORD>(EXCEPTION_ACCESS_VIOLATION), "SIGSEGV - Segmentation violation signal" },
43         { static_cast<DWORD>(EXCEPTION_INT_DIVIDE_BY_ZERO), "Divide by zero error" },
44     };
45 
handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo)46     LONG CALLBACK FatalConditionHandler::handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) {
47         for (auto const& def : signalDefs) {
48             if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) {
49                 reportFatal(def.name);
50             }
51         }
52         // If its not an exception we care about, pass it along.
53         // This stops us from eating debugger breaks etc.
54         return EXCEPTION_CONTINUE_SEARCH;
55     }
56 
FatalConditionHandler()57     FatalConditionHandler::FatalConditionHandler() {
58         isSet = true;
59         // 32k seems enough for Catch to handle stack overflow,
60         // but the value was found experimentally, so there is no strong guarantee
61         guaranteeSize = 32 * 1024;
62         exceptionHandlerHandle = nullptr;
63         // Register as first handler in current chain
64         exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException);
65         // Pass in guarantee size to be filled
66         SetThreadStackGuarantee(&guaranteeSize);
67     }
68 
reset()69     void FatalConditionHandler::reset() {
70         if (isSet) {
71             RemoveVectoredExceptionHandler(exceptionHandlerHandle);
72             SetThreadStackGuarantee(&guaranteeSize);
73             exceptionHandlerHandle = nullptr;
74             isSet = false;
75         }
76     }
77 
~FatalConditionHandler()78     FatalConditionHandler::~FatalConditionHandler() {
79         reset();
80     }
81 
82 bool FatalConditionHandler::isSet = false;
83 ULONG FatalConditionHandler::guaranteeSize = 0;
84 PVOID FatalConditionHandler::exceptionHandlerHandle = nullptr;
85 
86 
87 } // namespace Catch
88 
89 #elif defined( CATCH_CONFIG_POSIX_SIGNALS )
90 
91 namespace Catch {
92 
93     struct SignalDefs {
94         int id;
95         const char* name;
96     };
97 
98     // 32kb for the alternate stack seems to be sufficient. However, this value
99     // is experimentally determined, so that's not guaranteed.
100     static constexpr std::size_t sigStackSize = 32768 >= MINSIGSTKSZ ? 32768 : MINSIGSTKSZ;
101 
102     static SignalDefs signalDefs[] = {
103         { SIGINT,  "SIGINT - Terminal interrupt signal" },
104         { SIGILL,  "SIGILL - Illegal instruction signal" },
105         { SIGFPE,  "SIGFPE - Floating point error signal" },
106         { SIGSEGV, "SIGSEGV - Segmentation violation signal" },
107         { SIGTERM, "SIGTERM - Termination request signal" },
108         { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" }
109     };
110 
111 
handleSignal(int sig)112     void FatalConditionHandler::handleSignal( int sig ) {
113         char const * name = "<unknown signal>";
114         for (auto const& def : signalDefs) {
115             if (sig == def.id) {
116                 name = def.name;
117                 break;
118             }
119         }
120         reset();
121         reportFatal(name);
122         raise( sig );
123     }
124 
FatalConditionHandler()125     FatalConditionHandler::FatalConditionHandler() {
126         isSet = true;
127         stack_t sigStack;
128         sigStack.ss_sp = altStackMem;
129         sigStack.ss_size = sigStackSize;
130         sigStack.ss_flags = 0;
131         sigaltstack(&sigStack, &oldSigStack);
132         struct sigaction sa = { };
133 
134         sa.sa_handler = handleSignal;
135         sa.sa_flags = SA_ONSTACK;
136         for (std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i) {
137             sigaction(signalDefs[i].id, &sa, &oldSigActions[i]);
138         }
139     }
140 
141 
~FatalConditionHandler()142     FatalConditionHandler::~FatalConditionHandler() {
143         reset();
144     }
145 
reset()146     void FatalConditionHandler::reset() {
147         if( isSet ) {
148             // Set signals back to previous values -- hopefully nobody overwrote them in the meantime
149             for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) {
150                 sigaction(signalDefs[i].id, &oldSigActions[i], nullptr);
151             }
152             // Return the old stack
153             sigaltstack(&oldSigStack, nullptr);
154             isSet = false;
155         }
156     }
157 
158     bool FatalConditionHandler::isSet = false;
159     struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs)/sizeof(SignalDefs)] = {};
160     stack_t FatalConditionHandler::oldSigStack = {};
161     char FatalConditionHandler::altStackMem[sigStackSize] = {};
162 
163 
164 } // namespace Catch
165 
166 #else
167 
168 namespace Catch {
reset()169     void FatalConditionHandler::reset() {}
170 }
171 
172 #endif // signals/SEH handling
173 
174 #if defined(__GNUC__)
175 #    pragma GCC diagnostic pop
176 #endif
177