123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244 |
- /*
- * 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 <algorithm>
- #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<DWORD>(EXCEPTION_ILLEGAL_INSTRUCTION), "SIGILL - Illegal instruction signal" },
- { static_cast<DWORD>(EXCEPTION_STACK_OVERFLOW), "SIGSEGV - Stack overflow" },
- { static_cast<DWORD>(EXCEPTION_ACCESS_VIOLATION), "SIGSEGV - Segmentation violation signal" },
- { static_cast<DWORD>(EXCEPTION_INT_DIVIDE_BY_ZERO), "Divide by zero error" },
- };
- static LONG CALLBACK handleVectoredException(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 PVOID exceptionHandlerHandle = nullptr;
- // For MSVC, we reserve part of the stack memory for handling
- // memory overflow structured exception.
- FatalConditionHandler::FatalConditionHandler() {
- ULONG guaranteeSize = static_cast<ULONG>(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 first handler in current chain
- exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException);
- if (!exceptionHandlerHandle) {
- CATCH_RUNTIME_ERROR("Could not register vectored exception handler");
- }
- }
- void FatalConditionHandler::disengage_platform() {
- if (!RemoveVectoredExceptionHandler(exceptionHandlerHandle)) {
- CATCH_RUNTIME_ERROR("Could not unregister vectored exception handler");
- }
- exceptionHandlerHandle = nullptr;
- }
- } // end namespace Catch
- #endif // CATCH_CONFIG_WINDOWS_SEH
- #if defined( CATCH_CONFIG_POSIX_SIGNALS )
- #include <signal.h>
- 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 = "<unknown signal>";
- 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<size_t>(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
|