catch_fatal_condition_handler.cpp
1 2 // Copyright Catch2 Authors 3 // Distributed under the Boost Software License, Version 1.0. 4 // (See accompanying file LICENSE.txt or copy at 5 // https://www.boost.org/LICENSE_1_0.txt) 6 7 // SPDX-License-Identifier: BSL-1.0 8 9 /** \file 10 * This file provides platform specific implementations of FatalConditionHandler 11 * 12 * This means that there is a lot of conditional compilation, and platform 13 * specific code. Currently, Catch2 supports a dummy handler (if no 14 * handler is desired), and 2 platform specific handlers: 15 * * Windows' SEH 16 * * POSIX signals 17 * 18 * Consequently, various pieces of code below are compiled if either of 19 * the platform specific handlers is enabled, or if none of them are 20 * enabled. It is assumed that both cannot be enabled at the same time, 21 * and doing so should cause a compilation error. 22 * 23 * If another platform specific handler is added, the compile guards 24 * below will need to be updated taking these assumptions into account. 25 */ 26 27 #include <catch2/internal/catch_fatal_condition_handler.hpp> 28 29 #include <catch2/internal/catch_context.hpp> 30 #include <catch2/internal/catch_enforce.hpp> 31 #include <catch2/interfaces/catch_interfaces_capture.hpp> 32 #include <catch2/internal/catch_windows_h_proxy.hpp> 33 #include <catch2/internal/catch_stdstreams.hpp> 34 35 #include <algorithm> 36 37 #if !defined( CATCH_CONFIG_WINDOWS_SEH ) && !defined( CATCH_CONFIG_POSIX_SIGNALS ) 38 39 namespace Catch { 40 41 // If neither SEH nor signal handling is required, the handler impls 42 // do not have to do anything, and can be empty. 43 void FatalConditionHandler::engage_platform() {} 44 void FatalConditionHandler::disengage_platform() noexcept {} 45 FatalConditionHandler::FatalConditionHandler() = default; 46 FatalConditionHandler::~FatalConditionHandler() = default; 47 48 } // end namespace Catch 49 50 #endif // !CATCH_CONFIG_WINDOWS_SEH && !CATCH_CONFIG_POSIX_SIGNALS 51 52 #if defined( CATCH_CONFIG_WINDOWS_SEH ) && defined( CATCH_CONFIG_POSIX_SIGNALS ) 53 #error "Inconsistent configuration: Windows' SEH handling and POSIX signals cannot be enabled at the same time" 54 #endif // CATCH_CONFIG_WINDOWS_SEH && CATCH_CONFIG_POSIX_SIGNALS 55 56 #if defined( CATCH_CONFIG_WINDOWS_SEH ) || defined( CATCH_CONFIG_POSIX_SIGNALS ) 57 58 namespace { 59 //! Signals fatal error message to the run context 60 void reportFatal( char const * const message ) { 61 Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition( message ); 62 } 63 64 //! Minimal size Catch2 needs for its own fatal error handling. 65 //! Picked empirically, so it might not be sufficient on all 66 //! platforms, and for all configurations. 67 constexpr std::size_t minStackSizeForErrors = 32 * 1024; 68 } // end unnamed namespace 69 70 #endif // CATCH_CONFIG_WINDOWS_SEH || CATCH_CONFIG_POSIX_SIGNALS 71 72 #if defined( CATCH_CONFIG_WINDOWS_SEH ) 73 74 namespace Catch { 75 76 struct SignalDefs { DWORD id; const char* name; }; 77 78 // There is no 1-1 mapping between signals and windows exceptions. 79 // Windows can easily distinguish between SO and SigSegV, 80 // but SigInt, SigTerm, etc are handled differently. 81 static SignalDefs signalDefs[] = { 82 { EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal" }, 83 { EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow" }, 84 { EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal" }, 85 { EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" }, 86 }; 87 88 static LONG CALLBACK topLevelExceptionFilter(PEXCEPTION_POINTERS ExceptionInfo) { 89 for (auto const& def : signalDefs) { 90 if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) { 91 reportFatal(def.name); 92 } 93 } 94 // If its not an exception we care about, pass it along. 95 // This stops us from eating debugger breaks etc. 96 return EXCEPTION_CONTINUE_SEARCH; 97 } 98 99 // Since we do not support multiple instantiations, we put these 100 // into global variables and rely on cleaning them up in outlined 101 // constructors/destructors 102 static LPTOP_LEVEL_EXCEPTION_FILTER previousTopLevelExceptionFilter = nullptr; 103 104 105 // For MSVC, we reserve part of the stack memory for handling 106 // memory overflow structured exception. 107 FatalConditionHandler::FatalConditionHandler() { 108 ULONG guaranteeSize = static_cast<ULONG>(minStackSizeForErrors); 109 if (!SetThreadStackGuarantee(&guaranteeSize)) { 110 // We do not want to fully error out, because needing 111 // the stack reserve should be rare enough anyway. 112 Catch::cerr() 113 << "Failed to reserve piece of stack." 114 << " Stack overflows will not be reported successfully."; 115 } 116 } 117 118 // We do not attempt to unset the stack guarantee, because 119 // Windows does not support lowering the stack size guarantee. 120 FatalConditionHandler::~FatalConditionHandler() = default; 121 122 123 void FatalConditionHandler::engage_platform() { 124 // Register as a the top level exception filter. 125 previousTopLevelExceptionFilter = SetUnhandledExceptionFilter(topLevelExceptionFilter); 126 } 127 128 void FatalConditionHandler::disengage_platform() noexcept { 129 if (SetUnhandledExceptionFilter(previousTopLevelExceptionFilter) != topLevelExceptionFilter) { 130 Catch::cerr() 131 << "Unexpected SEH unhandled exception filter on disengage." 132 << " The filter was restored, but might be rolled back unexpectedly."; 133 } 134 previousTopLevelExceptionFilter = nullptr; 135 } 136 137 } // end namespace Catch 138 139 #endif // CATCH_CONFIG_WINDOWS_SEH 140 141 #if defined( CATCH_CONFIG_POSIX_SIGNALS ) 142 143 #include <signal.h> 144 145 namespace Catch { 146 147 struct SignalDefs { 148 int id; 149 const char* name; 150 }; 151 152 static SignalDefs signalDefs[] = { 153 { SIGINT, "SIGINT - Terminal interrupt signal" }, 154 { SIGILL, "SIGILL - Illegal instruction signal" }, 155 { SIGFPE, "SIGFPE - Floating point error signal" }, 156 { SIGSEGV, "SIGSEGV - Segmentation violation signal" }, 157 { SIGTERM, "SIGTERM - Termination request signal" }, 158 { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } 159 }; 160 161 // Older GCCs trigger -Wmissing-field-initializers for T foo = {} 162 // which is zero initialization, but not explicit. We want to avoid 163 // that. 164 #if defined(__GNUC__) 165 # pragma GCC diagnostic push 166 # pragma GCC diagnostic ignored "-Wmissing-field-initializers" 167 #endif 168 169 static char* altStackMem = nullptr; 170 static std::size_t altStackSize = 0; 171 static stack_t oldSigStack{}; 172 static struct sigaction oldSigActions[sizeof(signalDefs) / sizeof(SignalDefs)]{}; 173 174 static void restorePreviousSignalHandlers() noexcept { 175 // We set signal handlers back to the previous ones. Hopefully 176 // nobody overwrote them in the meantime, and doesn't expect 177 // their signal handlers to live past ours given that they 178 // installed them after ours.. 179 for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { 180 sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); 181 } 182 // Return the old stack 183 sigaltstack(&oldSigStack, nullptr); 184 } 185 186 static void handleSignal( int sig ) { 187 char const * name = "<unknown signal>"; 188 for (auto const& def : signalDefs) { 189 if (sig == def.id) { 190 name = def.name; 191 break; 192 } 193 } 194 // We need to restore previous signal handlers and let them do 195 // their thing, so that the users can have the debugger break 196 // when a signal is raised, and so on. 197 restorePreviousSignalHandlers(); 198 reportFatal( name ); 199 raise( sig ); 200 } 201 202 FatalConditionHandler::FatalConditionHandler() { 203 assert(!altStackMem && "Cannot initialize POSIX signal handler when one already exists"); 204 if (altStackSize == 0) { 205 altStackSize = std::max(static_cast<size_t>(SIGSTKSZ), minStackSizeForErrors); 206 } 207 altStackMem = new char[altStackSize](); 208 } 209 210 FatalConditionHandler::~FatalConditionHandler() { 211 delete[] altStackMem; 212 // We signal that another instance can be constructed by zeroing 213 // out the pointer. 214 altStackMem = nullptr; 215 } 216 217 void FatalConditionHandler::engage_platform() { 218 stack_t sigStack; 219 sigStack.ss_sp = altStackMem; 220 sigStack.ss_size = altStackSize; 221 sigStack.ss_flags = 0; 222 sigaltstack(&sigStack, &oldSigStack); 223 struct sigaction sa = { }; 224 225 sa.sa_handler = handleSignal; 226 sa.sa_flags = SA_ONSTACK; 227 for (std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i) { 228 sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); 229 } 230 } 231 232 #if defined(__GNUC__) 233 # pragma GCC diagnostic pop 234 #endif 235 236 237 void FatalConditionHandler::disengage_platform() noexcept { 238 restorePreviousSignalHandlers(); 239 } 240 241 } // end namespace Catch 242 243 #endif // CATCH_CONFIG_POSIX_SIGNALS