mirror of
https://github.com/catchorg/Catch2.git
synced 2024-11-05 05:39:53 +01:00
8f277a54c0
Because new glibc has changed `MINSIGSTKSZ` to be a syscall instead of being constant, the signal posix handling needed changes, as it used the value in constexpr context, for deciding size of an array. It would be simple to fix it by having the handler determine the signal handling stack size and allocate the memory every time the handler is being installed, but that would add another allocation and a syscall every time a test case is entered. Instead, I split apart the idea of preparing fatal error handlers, and engaging them, so that the memory can be allocated only once and still be guarded by RAII. Also turns out that Catch2's use of `MINSIGSTKSZ` was wrong, and we should've been using `SIGSTKSZ` the whole time, which we use now. Closes #2178
245 lines
8.7 KiB
C++
245 lines
8.7 KiB
C++
/*
|
|
* 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.
|
|
FatalConditionHandler::engage_platform() {}
|
|
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
|