catch_fatal_condition.cpp 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  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. /** \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. #include "catch_fatal_condition.h"
  27. #include "catch_context.h"
  28. #include "catch_enforce.h"
  29. #include "catch_run_context.h"
  30. #include "catch_windows_h_proxy.h"
  31. #include <algorithm>
  32. #if !defined( CATCH_CONFIG_WINDOWS_SEH ) && !defined( CATCH_CONFIG_POSIX_SIGNALS )
  33. namespace Catch {
  34. // If neither SEH nor signal handling is required, the handler impls
  35. // do not have to do anything, and can be empty.
  36. void FatalConditionHandler::engage_platform() {}
  37. void FatalConditionHandler::disengage_platform() {}
  38. FatalConditionHandler::FatalConditionHandler() = default;
  39. FatalConditionHandler::~FatalConditionHandler() = default;
  40. } // end namespace Catch
  41. #endif // !CATCH_CONFIG_WINDOWS_SEH && !CATCH_CONFIG_POSIX_SIGNALS
  42. #if defined( CATCH_CONFIG_WINDOWS_SEH ) && defined( CATCH_CONFIG_POSIX_SIGNALS )
  43. #error "Inconsistent configuration: Windows' SEH handling and POSIX signals cannot be enabled at the same time"
  44. #endif // CATCH_CONFIG_WINDOWS_SEH && CATCH_CONFIG_POSIX_SIGNALS
  45. #if defined( CATCH_CONFIG_WINDOWS_SEH ) || defined( CATCH_CONFIG_POSIX_SIGNALS )
  46. namespace {
  47. //! Signals fatal error message to the run context
  48. void reportFatal( char const * const message ) {
  49. Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition( message );
  50. }
  51. //! Minimal size Catch2 needs for its own fatal error handling.
  52. //! Picked anecdotally, so it might not be sufficient on all
  53. //! platforms, and for all configurations.
  54. constexpr std::size_t minStackSizeForErrors = 32 * 1024;
  55. } // end unnamed namespace
  56. #endif // CATCH_CONFIG_WINDOWS_SEH || CATCH_CONFIG_POSIX_SIGNALS
  57. #if defined( CATCH_CONFIG_WINDOWS_SEH )
  58. namespace Catch {
  59. struct SignalDefs { DWORD id; const char* name; };
  60. // There is no 1-1 mapping between signals and windows exceptions.
  61. // Windows can easily distinguish between SO and SigSegV,
  62. // but SigInt, SigTerm, etc are handled differently.
  63. static SignalDefs signalDefs[] = {
  64. { static_cast<DWORD>(EXCEPTION_ILLEGAL_INSTRUCTION), "SIGILL - Illegal instruction signal" },
  65. { static_cast<DWORD>(EXCEPTION_STACK_OVERFLOW), "SIGSEGV - Stack overflow" },
  66. { static_cast<DWORD>(EXCEPTION_ACCESS_VIOLATION), "SIGSEGV - Segmentation violation signal" },
  67. { static_cast<DWORD>(EXCEPTION_INT_DIVIDE_BY_ZERO), "Divide by zero error" },
  68. };
  69. static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) {
  70. for (auto const& def : signalDefs) {
  71. if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) {
  72. reportFatal(def.name);
  73. }
  74. }
  75. // If its not an exception we care about, pass it along.
  76. // This stops us from eating debugger breaks etc.
  77. return EXCEPTION_CONTINUE_SEARCH;
  78. }
  79. // Since we do not support multiple instantiations, we put these
  80. // into global variables and rely on cleaning them up in outlined
  81. // constructors/destructors
  82. static PVOID exceptionHandlerHandle = nullptr;
  83. // For MSVC, we reserve part of the stack memory for handling
  84. // memory overflow structured exception.
  85. FatalConditionHandler::FatalConditionHandler() {
  86. ULONG guaranteeSize = static_cast<ULONG>(minStackSizeForErrors);
  87. if (!SetThreadStackGuarantee(&guaranteeSize)) {
  88. // We do not want to fully error out, because needing
  89. // the stack reserve should be rare enough anyway.
  90. Catch::cerr()
  91. << "Failed to reserve piece of stack."
  92. << " Stack overflows will not be reported successfully.";
  93. }
  94. }
  95. // We do not attempt to unset the stack guarantee, because
  96. // Windows does not support lowering the stack size guarantee.
  97. FatalConditionHandler::~FatalConditionHandler() = default;
  98. void FatalConditionHandler::engage_platform() {
  99. // Register as first handler in current chain
  100. exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException);
  101. if (!exceptionHandlerHandle) {
  102. CATCH_RUNTIME_ERROR("Could not register vectored exception handler");
  103. }
  104. }
  105. void FatalConditionHandler::disengage_platform() {
  106. if (!RemoveVectoredExceptionHandler(exceptionHandlerHandle)) {
  107. CATCH_RUNTIME_ERROR("Could not unregister vectored exception handler");
  108. }
  109. exceptionHandlerHandle = nullptr;
  110. }
  111. } // end namespace Catch
  112. #endif // CATCH_CONFIG_WINDOWS_SEH
  113. #if defined( CATCH_CONFIG_POSIX_SIGNALS )
  114. #include <signal.h>
  115. namespace Catch {
  116. struct SignalDefs {
  117. int id;
  118. const char* name;
  119. };
  120. static SignalDefs signalDefs[] = {
  121. { SIGINT, "SIGINT - Terminal interrupt signal" },
  122. { SIGILL, "SIGILL - Illegal instruction signal" },
  123. { SIGFPE, "SIGFPE - Floating point error signal" },
  124. { SIGSEGV, "SIGSEGV - Segmentation violation signal" },
  125. { SIGTERM, "SIGTERM - Termination request signal" },
  126. { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" }
  127. };
  128. // Older GCCs trigger -Wmissing-field-initializers for T foo = {}
  129. // which is zero initialization, but not explicit. We want to avoid
  130. // that.
  131. #if defined(__GNUC__)
  132. # pragma GCC diagnostic push
  133. # pragma GCC diagnostic ignored "-Wmissing-field-initializers"
  134. #endif
  135. static char* altStackMem = nullptr;
  136. static std::size_t altStackSize = 0;
  137. static stack_t oldSigStack{};
  138. static struct sigaction oldSigActions[sizeof(signalDefs) / sizeof(SignalDefs)]{};
  139. static void restorePreviousSignalHandlers() {
  140. // We set signal handlers back to the previous ones. Hopefully
  141. // nobody overwrote them in the meantime, and doesn't expect
  142. // their signal handlers to live past ours given that they
  143. // installed them after ours..
  144. for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) {
  145. sigaction(signalDefs[i].id, &oldSigActions[i], nullptr);
  146. }
  147. // Return the old stack
  148. sigaltstack(&oldSigStack, nullptr);
  149. }
  150. static void handleSignal( int sig ) {
  151. char const * name = "<unknown signal>";
  152. for (auto const& def : signalDefs) {
  153. if (sig == def.id) {
  154. name = def.name;
  155. break;
  156. }
  157. }
  158. // We need to restore previous signal handlers and let them do
  159. // their thing, so that the users can have the debugger break
  160. // when a signal is raised, and so on.
  161. restorePreviousSignalHandlers();
  162. reportFatal( name );
  163. raise( sig );
  164. }
  165. FatalConditionHandler::FatalConditionHandler() {
  166. assert(!altStackMem && "Cannot initialize POSIX signal handler when one already exists");
  167. if (altStackSize == 0) {
  168. altStackSize = std::max(static_cast<size_t>(SIGSTKSZ), minStackSizeForErrors);
  169. }
  170. altStackMem = new char[altStackSize]();
  171. }
  172. FatalConditionHandler::~FatalConditionHandler() {
  173. delete[] altStackMem;
  174. // We signal that another instance can be constructed by zeroing
  175. // out the pointer.
  176. altStackMem = nullptr;
  177. }
  178. void FatalConditionHandler::engage_platform() {
  179. stack_t sigStack;
  180. sigStack.ss_sp = altStackMem;
  181. sigStack.ss_size = altStackSize;
  182. sigStack.ss_flags = 0;
  183. sigaltstack(&sigStack, &oldSigStack);
  184. struct sigaction sa = { };
  185. sa.sa_handler = handleSignal;
  186. sa.sa_flags = SA_ONSTACK;
  187. for (std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i) {
  188. sigaction(signalDefs[i].id, &sa, &oldSigActions[i]);
  189. }
  190. }
  191. #if defined(__GNUC__)
  192. # pragma GCC diagnostic pop
  193. #endif
  194. void FatalConditionHandler::disengage_platform() {
  195. restorePreviousSignalHandlers();
  196. }
  197. } // end namespace Catch
  198. #endif // CATCH_CONFIG_POSIX_SIGNALS