/* * Created by Phil on 21/08/2014 * Copyright 2014 Two Blue Cubes Ltd. All rights reserved. * * Distributed under the Boost Software License, Version 1.0. (See accompanying * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) * */ /** \file * This file provides platform specific implementations of FatalConditionHandler * * This means that there is a lot of conditional compilation, and platform * specific code. Currently, Catch2 supports a dummy handler (if no * handler is desired), and 2 platform specific handlers: * * Windows' SEH * * POSIX signals * * Consequently, various pieces of code below are compiled if either of * the platform specific handlers is enabled, or if none of them are * enabled. It is assumed that both cannot be enabled at the same time, * and doing so should cause a compilation error. * * If another platform specific handler is added, the compile guards * below will need to be updated taking these assumptions into account. */ #include "catch_fatal_condition.h" #include "catch_context.h" #include "catch_enforce.h" #include "catch_run_context.h" #include "catch_windows_h_proxy.h" #include #if !defined( CATCH_CONFIG_WINDOWS_SEH ) && !defined( CATCH_CONFIG_POSIX_SIGNALS ) namespace Catch { // If neither SEH nor signal handling is required, the handler impls // do not have to do anything, and can be empty. void FatalConditionHandler::engage_platform() {} void FatalConditionHandler::disengage_platform() {} FatalConditionHandler::FatalConditionHandler() = default; FatalConditionHandler::~FatalConditionHandler() = default; } // end namespace Catch #endif // !CATCH_CONFIG_WINDOWS_SEH && !CATCH_CONFIG_POSIX_SIGNALS #if defined( CATCH_CONFIG_WINDOWS_SEH ) && defined( CATCH_CONFIG_POSIX_SIGNALS ) #error "Inconsistent configuration: Windows' SEH handling and POSIX signals cannot be enabled at the same time" #endif // CATCH_CONFIG_WINDOWS_SEH && CATCH_CONFIG_POSIX_SIGNALS #if defined( CATCH_CONFIG_WINDOWS_SEH ) || defined( CATCH_CONFIG_POSIX_SIGNALS ) namespace { //! Signals fatal error message to the run context void reportFatal( char const * const message ) { Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition( message ); } //! Minimal size Catch2 needs for its own fatal error handling. //! Picked anecdotally, so it might not be sufficient on all //! platforms, and for all configurations. constexpr std::size_t minStackSizeForErrors = 32 * 1024; } // end unnamed namespace #endif // CATCH_CONFIG_WINDOWS_SEH || CATCH_CONFIG_POSIX_SIGNALS #if defined( CATCH_CONFIG_WINDOWS_SEH ) namespace Catch { struct SignalDefs { DWORD id; const char* name; }; // There is no 1-1 mapping between signals and windows exceptions. // Windows can easily distinguish between SO and SigSegV, // but SigInt, SigTerm, etc are handled differently. static SignalDefs signalDefs[] = { { static_cast(EXCEPTION_ILLEGAL_INSTRUCTION), "SIGILL - Illegal instruction signal" }, { static_cast(EXCEPTION_STACK_OVERFLOW), "SIGSEGV - Stack overflow" }, { static_cast(EXCEPTION_ACCESS_VIOLATION), "SIGSEGV - Segmentation violation signal" }, { static_cast(EXCEPTION_INT_DIVIDE_BY_ZERO), "Divide by zero error" }, }; static LONG CALLBACK topLevelExceptionFilter(PEXCEPTION_POINTERS ExceptionInfo) { for (auto const& def : signalDefs) { if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) { reportFatal(def.name); } } // If its not an exception we care about, pass it along. // This stops us from eating debugger breaks etc. return EXCEPTION_CONTINUE_SEARCH; } // Since we do not support multiple instantiations, we put these // into global variables and rely on cleaning them up in outlined // constructors/destructors static LPTOP_LEVEL_EXCEPTION_FILTER previousTopLevelExceptionFilter = nullptr; // For MSVC, we reserve part of the stack memory for handling // memory overflow structured exception. FatalConditionHandler::FatalConditionHandler() { ULONG guaranteeSize = static_cast(minStackSizeForErrors); if (!SetThreadStackGuarantee(&guaranteeSize)) { // We do not want to fully error out, because needing // the stack reserve should be rare enough anyway. Catch::cerr() << "Failed to reserve piece of stack." << " Stack overflows will not be reported successfully."; } } // We do not attempt to unset the stack guarantee, because // Windows does not support lowering the stack size guarantee. FatalConditionHandler::~FatalConditionHandler() = default; void FatalConditionHandler::engage_platform() { // Register as a the top level exception filter. previousTopLevelExceptionFilter = SetUnhandledExceptionFilter(topLevelExceptionFilter); } void FatalConditionHandler::disengage_platform() noexcept { if (SetUnhandledExceptionFilter(previousTopLevelExceptionFilter) != topLevelExceptionFilter) { CATCH_RUNTIME_ERROR("Could not restore previous top level exception filter"); } previousTopLevelExceptionFilter = nullptr; } } // end namespace Catch #endif // CATCH_CONFIG_WINDOWS_SEH #if defined( CATCH_CONFIG_POSIX_SIGNALS ) #include namespace Catch { struct SignalDefs { int id; const char* name; }; static SignalDefs signalDefs[] = { { SIGINT, "SIGINT - Terminal interrupt signal" }, { SIGILL, "SIGILL - Illegal instruction signal" }, { SIGFPE, "SIGFPE - Floating point error signal" }, { SIGSEGV, "SIGSEGV - Segmentation violation signal" }, { SIGTERM, "SIGTERM - Termination request signal" }, { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } }; // Older GCCs trigger -Wmissing-field-initializers for T foo = {} // which is zero initialization, but not explicit. We want to avoid // that. #if defined(__GNUC__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wmissing-field-initializers" #endif static char* altStackMem = nullptr; static std::size_t altStackSize = 0; static stack_t oldSigStack{}; static struct sigaction oldSigActions[sizeof(signalDefs) / sizeof(SignalDefs)]{}; static void restorePreviousSignalHandlers() { // We set signal handlers back to the previous ones. Hopefully // nobody overwrote them in the meantime, and doesn't expect // their signal handlers to live past ours given that they // installed them after ours.. for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); } // Return the old stack sigaltstack(&oldSigStack, nullptr); } static void handleSignal( int sig ) { char const * name = ""; for (auto const& def : signalDefs) { if (sig == def.id) { name = def.name; break; } } // We need to restore previous signal handlers and let them do // their thing, so that the users can have the debugger break // when a signal is raised, and so on. restorePreviousSignalHandlers(); reportFatal( name ); raise( sig ); } FatalConditionHandler::FatalConditionHandler() { assert(!altStackMem && "Cannot initialize POSIX signal handler when one already exists"); if (altStackSize == 0) { altStackSize = std::max(static_cast(SIGSTKSZ), minStackSizeForErrors); } altStackMem = new char[altStackSize](); } FatalConditionHandler::~FatalConditionHandler() { delete[] altStackMem; // We signal that another instance can be constructed by zeroing // out the pointer. altStackMem = nullptr; } void FatalConditionHandler::engage_platform() { stack_t sigStack; sigStack.ss_sp = altStackMem; sigStack.ss_size = altStackSize; sigStack.ss_flags = 0; sigaltstack(&sigStack, &oldSigStack); struct sigaction sa = { }; sa.sa_handler = handleSignal; sa.sa_flags = SA_ONSTACK; for (std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i) { sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); } } #if defined(__GNUC__) # pragma GCC diagnostic pop #endif void FatalConditionHandler::disengage_platform() { restorePreviousSignalHandlers(); } } // end namespace Catch #endif // CATCH_CONFIG_POSIX_SIGNALS