Merge pull request #4 from KjellKod/windows-vector-exceptions

Windows vector exceptions
This commit is contained in:
Kjell Hedstrom 2015-02-18 03:59:28 -07:00
commit 29d7aa7267
8 changed files with 115 additions and 45 deletions

View File

@ -31,7 +31,7 @@ ELSEIF("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
ELSEIF(MSVC)
ELSEIF (MSVC OR MINGW)
set(PLATFORM_LINK_LIBRIES dbghelp)
# VC11 bug: http://code.google.com/p/googletest/issues/detail?id=408
# add_definition(-D_VARIADIC_MAX=10)

View File

@ -1,13 +1,10 @@
# -DUSE_DYNAMIC_LOGGING_LEVELS=ON : run-type turn on/off levels
option (USE_DYNAMIC_LOGGING_LEVELS
"Turn ON/OFF log levels. An disabled level will not push logs of that level to the sink. By default dynamic logging is disabled" OFF)
IF(USE_DYNAMIC_LOGGING_LEVELS)
add_definitions(-DG2_DYNAMIC_LOGGING)
MESSAGE("-DUSE_DYNAMIC_LOGGING_LEVELS=ON
\tDynamic logging levels can be turned on. Make sure to have \n\t\t[#define G2_DYNAMIC_LOGGING 1] in your source code")
MESSAGE("-DUSE_DYNAMIC_LOGGING_LEVELS=ON")
MESSAGE("\tDynamic logging levels can be turned on. Make sure to have \n\t\t[#define G2_DYNAMIC_LOGGING 1] in your source code")
MESSAGE("\t\tUse [g2::setLogLevel(LEVEL boolean)] to enable/disable logging on specified levels")
ELSE()
MESSAGE("-DUSE_DYNAMIC_LOGGING_LEVELS=OFF")
@ -20,13 +17,32 @@ ENDIF(USE_DYNAMIC_LOGGING_LEVELS)
# predefined DEBUG for their own purposes
option (CHANGE_G3LOG_DEBUG_TO_DBUG
"Use DBUG logging level instead of DEBUG. By default DEBUG is the debugging level" OFF)
IF(CHANGE_G3LOG_DEBUG_TO_DBUG)
add_definitions(-DCHANGE_G3LOG_DEBUG_TO_DBUG)
MESSAGE("-DCHANGE_G3LOG_DEBUG_TO_DBUG=ON
\tDBUG instead of DEBUG logging level can be used. Make sure to have \n\t\t[#define CHANGE_G3LOG_DEBUG_TO_DBUG 1] in your source code")
MESSAGE("-DCHANGE_G3LOG_DEBUG_TO_DBUG=ON")
MESSAGE("\tDBUG instead of DEBUG logging level can be used. Make sure to have \n\t\t[#define CHANGE_G3LOG_DEBUG_TO_DBUG 1] in your source code")
ELSE()
MESSAGE("-DCHANGE_G3LOG_DEBUG_TO_DBUG=OFF. Debuggin logging level is 'DEBUG'")
MESSAGE("-DCHANGE_G3LOG_DEBUG_TO_DBUG=OFF")
MESSAGE("\tDebuggin logging level is 'DEBUG'")
ENDIF(CHANGE_G3LOG_DEBUG_TO_DBUG)
# WINDOWS OPTION
# -DENABLE_VECTORED_EXCEPTIONHANDLING=ON : defualt change the
# By default vectored exception handling is enabled, you can disable it with this option.
# Please know that only known fatal exceptions will be caught, these exceptions are the ones
# enumerated in src/stacktrace_windows.cpp
IF (MSVC OR MINGW)
option (ENABLE_VECTORED_EXCEPTIONHANDLING
"Vectored exception / crash handling with improved stack trace" ON)
IF(NOT ENABLE_VECTORED_EXCEPTIONHANDLING)
add_definitions(-DDISABLE_VECTORED_EXCEPTIONHANDLING)
MESSAGE("-DENABLE_VECTORED_EXCEPTIONHANDLING=OFF")
MESSAGE("\tVectored exception handling is disabled")
ELSE()
MESSAGE("-DENABLE_VECTORED_EXCEPTIONHANDLING=ON")
MESSAGE("\tVectored exception handling is enabled")
ENDIF(NOT ENABLE_VECTORED_EXCEPTIONHANDLING)
ENDIF (MSVC OR MINGW)

View File

@ -136,6 +136,7 @@ void ExecuteDeathFunction(const bool runInNewThread, int fatalChoice) {
case 8: exitFunction = &OutOfBoundsArrayIndexing; break;
case 9: exitFunction = &AccessViolation; break;
case 10: exitFunction = &RaiseSIGABRTAndAccessViolation; break;
case 11: throw 1233210; break;
default: break;
}
if (runInNewThread) {
@ -191,12 +192,14 @@ int ChoiceOfFatalExit() {
std::cout << "[8] Out of bounds array indexing " << std::endl;
std::cout << "[9] Access violation" << std::endl;
std::cout << "[10] Rasing SIGABRT + Access Violation in two separate threads" << std::endl;
std::cout << "[11] Just throw" << std::endl;
std::cout << std::flush;
try {
std::getline(std::cin, option);
choice = std::stoi(option);
if (choice <= 0 || choice > 10) {
if (choice <= 0 || choice > 11) {
std::cout << "Invalid choice: [" << option << "\n\n";
} else {
return choice;

View File

@ -37,7 +37,7 @@ namespace {
// Dump of stack,. then exit through g2log background worker
// ALL thanks to this thread at StackOverflow. Pretty much borrowed from:
// Ref: http://stackoverflow.com/questions/77005/how-to-generate-a-stacktrace-when-my-gcc-c-app-crashes
void signalHandler(int signal_number, siginfo_t *info, void *unused_context) {
void signalHandler(int signal_number, siginfo_t* info, void* unused_context) {
using namespace g2::internal;
{
const auto dump = stackdump();
@ -47,7 +47,7 @@ void signalHandler(int signal_number, siginfo_t *info, void *unused_context) {
fatal_stream << "(" << signal_number << ")\tPID: " << getpid() << std::endl;
fatal_stream << "\n***** SIGNAL " << fatal_reason << "(" << signal_number << ")" << std::endl;
LogCapture trigger(FATAL_SIGNAL, static_cast<g2::SignalType>(signal_number), dump.c_str());
trigger.stream() << fatal_stream.str();
trigger.stream() << fatal_stream.str();
} // message sent to g2LogWorker
// wait to die
}
@ -80,22 +80,22 @@ bool blockForFatalHandling() {
/// Generate stackdump. Or in case a stackdump was pre-generated and non-empty just use that one
/// i.e. the latter case is only for Windows and test purposes
std::string stackdump(const char *rawdump) {
std::string stackdump(const char* rawdump) {
if (nullptr != rawdump && !std::string(rawdump).empty()) {
return {rawdump};
}
const size_t max_dump_size = 50;
void *dump[max_dump_size];
void* dump[max_dump_size];
size_t size = backtrace(dump, max_dump_size);
char **messages = backtrace_symbols(dump, size); // overwrite sigaction with caller's address
char** messages = backtrace_symbols(dump, size); // overwrite sigaction with caller's address
// dump stack: skip first frame, since that is here
std::ostringstream oss;
for (size_t idx = 1; idx < size && messages != nullptr; ++idx) {
char *mangled_name = 0, *offset_begin = 0, *offset_end = 0;
char* mangled_name = 0, *offset_begin = 0, *offset_end = 0;
// find parantheses and +address offset surrounding mangled name
for (char *p = messages[idx]; *p; ++p) {
for (char* p = messages[idx]; *p; ++p) {
if (*p == '(') {
mangled_name = p;
} else if (*p == '+') {
@ -114,7 +114,7 @@ std::string stackdump(const char *rawdump) {
*offset_end++ = '\0';
int status;
char *real_name = abi::__cxa_demangle(mangled_name, 0, 0, &status);
char* real_name = abi::__cxa_demangle(mangled_name, 0, 0, &status);
// if demangling is successful, output the demangled function name
if (status == 0) {
oss << "\n\tstack dump [" << idx << "] " << messages[idx] << " : " << real_name << "+";
@ -183,11 +183,15 @@ std::string exitReasonName(const LEVELS& level, g2::SignalType fatal_id) {
void exitWithDefaultSignalHandler(const LEVELS& level, g2::SignalType fatal_signal_id) {
const int signal_number = static_cast<int>(fatal_signal_id);
std::cerr << "Exiting due to " << level.text << ", " << signal_number << " " << std::flush;
#if !(defined(DISABLE_FATAL_SIGNALHANDLING))
struct sigaction action;
memset(&action, 0, sizeof (action)); //
sigemptyset(&action.sa_mask);
action.sa_handler = SIG_DFL; // take default action for the signal
sigaction(signal_number, &action, NULL);
#endif
kill(getpid(), signal_number);
abort(); // should never reach this
}
@ -198,6 +202,7 @@ void exitWithDefaultSignalHandler(const LEVELS& level, g2::SignalType fatal_sign
// Installs FATAL signal handler that is enough to handle most fatal events
// on *NIX systems
void installSignalHandler() {
#if !(defined(DISABLE_FATAL_SIGNALHANDLING))
struct sigaction action;
memset(&action, 0, sizeof (action));
sigemptyset(&action.sa_mask);
@ -216,6 +221,7 @@ void installSignalHandler() {
perror("sigaction - SIGSEGV");
if (sigaction(SIGTERM, &action, NULL) < 0)
perror("sigaction - SIGTERM");
#endif
}

View File

@ -11,6 +11,7 @@
#endif
#include <windows.h>
#include <intrin.h>
#include <csignal>
#include <cstring>
#include <cstdlib>
@ -24,21 +25,29 @@
#include "g2logmessage.hpp"
#include "g2logmessagecapture.hpp"
namespace {
std::atomic<bool> gBlockForFatal {true};
//void* g_vector_exception_handler = nullptr;
LPTOP_LEVEL_EXCEPTION_FILTER g_previous_unexpected_exception_handler = nullptr;
#if !(defined(DISABLE_FATAL_SIGNALHANDLING))
g2_thread_local bool g_installed_thread_signal_handler = false;
#endif
#if !(defined(DISABLE_VECTORED_EXCEPTIONHANDLING))
void* g_vector_exception_handler = nullptr;
#endif
// Restore back to default fatal event handling
void ReverseToOriginalFatalHandling() {
SetUnhandledExceptionFilter (g_previous_unexpected_exception_handler);
//RemoveVectoredExceptionHandler (g_vector_exception_handler);
#if !(defined(DISABLE_VECTORED_EXCEPTIONHANDLING))
RemoveVectoredExceptionHandler (g_vector_exception_handler);
#endif
#if !(defined(DISABLE_FATAL_SIGNALHANDLING))
if (SIG_ERR == signal(SIGABRT, SIG_DFL))
perror("signal - SIGABRT");
@ -53,9 +62,11 @@ void ReverseToOriginalFatalHandling() {
if (SIG_ERR == signal(SIGTERM, SIG_DFL))
perror("signal - SIGABRT");
#endif
}
// called for fatal signals SIGABRT, SIGFPE, SIGSEGV, SIGILL, SIGTERM
void signalHandler(int signal_number) {
using namespace g2::internal;
@ -68,42 +79,61 @@ void signalHandler(int signal_number) {
LogCapture trigger(FATAL_SIGNAL, static_cast<g2::SignalType>(signal_number), dump.c_str());
trigger.stream() << fatal_stream.str();
// Trigger debug break point, if we're in debug. This breakpoint CAN cause a slowdown when it happens.
// Be patient. The "Debug" dialogue should pop-up eventually if you doing it in Visual Studio.
// For fatal signals only, not exceptions.
// This is a way to tell the IDE (if in dev mode) that it can stop at this breakpoint
// Note that at this time the fatal log event with stack trace is NOT yet flushed to the logger
#if (!defined(NDEBUG) && defined(DEBUG_BREAK_AT_FATAL_SIGNAL))
__debugbreak();
#endif
} // scope exit - message sent to LogWorker, wait to die...
// Unhandled exception catching
LONG WINAPI exceptionHandling(EXCEPTION_POINTERS* info) {
LONG WINAPI exceptionHandling(EXCEPTION_POINTERS* info, const std::string& handler) {
std::string dump = stacktrace::stackdump(info);
std::ostringstream fatal_stream;
const g2::SignalType exception_code = info->ExceptionRecord->ExceptionCode;
fatal_stream << "\n***** Received fatal exception " << g2::internal::exitReasonName(g2::internal::FATAL_EXCEPTION, exception_code);
fatal_stream << "\n***** " << handler << ": Received fatal exception " << g2::internal::exitReasonName(g2::internal::FATAL_EXCEPTION, exception_code);
fatal_stream << "\tPID: " << getpid() << std::endl;
const auto fatal_id = static_cast<g2::SignalType>(exception_code);
LogCapture trigger(g2::internal::FATAL_EXCEPTION, fatal_id, dump.c_str());
trigger.stream() << fatal_stream.str();
return EXCEPTION_CONTINUE_SEARCH; //EXCEPTION_EXECUTE_HANDLER;
// FATAL Exception: It doesn't necessarily stop here we pass on continue search
// if no one else will catch that then it's goodbye anyhow,
// the RISK here is if someone is cathing this and returning "EXCEPTION_EXECUTE_HANDLER"
// but does not shutdown then the software will be running with g3log shutdown.
// .... However... this must be seen as a bug from standard handling of fatal exceptions
// https://msdn.microsoft.com/en-us/library/6wxdsc38.aspx
return EXCEPTION_CONTINUE_SEARCH;
}
// Unhandled exception catching
LONG WINAPI unexpectedExceptionHandling(EXCEPTION_POINTERS* info) {
ReverseToOriginalFatalHandling();
return exceptionHandling(info);
return exceptionHandling(info, "Unexpected Exception Handler");
}
/// Setup through (Windows API) AddVectoredExceptionHandler
/// Ref: http://blogs.msdn.com/b/zhanli/archive/2010/06/25/c-tips-addvectoredexceptionhandler-addvectoredcontinuehandler-and-setunhandledexceptionfilter.aspx
#if 0
LONG WINAPI vectorExceptionHandling(PEXCEPTION_POINTERS p) {
ReverseToOriginalFatalHandling();
return exceptionHandling(p);
}
#if !(defined(DISABLE_VECTORED_EXCEPTIONHANDLING))
LONG WINAPI vectorExceptionHandling(PEXCEPTION_POINTERS p) {
const g2::SignalType exception_code = p->ExceptionRecord->ExceptionCode;
if (false == stacktrace::isKnownException(exception_code)) {
LOG(WARNING) << "Vectored exception handling received an UNKNOWN exception: " << exception_code << ". The exception is IGNORED and hopefully caught by another exception handler";
return EXCEPTION_CONTINUE_SEARCH;
} else {
ReverseToOriginalFatalHandling();
return exceptionHandling(p, "Vectored Exception Handler");
}
}
#endif
@ -124,7 +154,7 @@ bool blockForFatalHandling() {
/// Generate stackdump. Or in case a stackdump was pre-generated and
/// non-empty just use that one. i.e. the latter case is only for
/// non-empty just use that one. i.e. the latter case is only for
/// Windows and test purposes
std::string stackdump(const char* dump) {
if (nullptr != dump && !std::string(dump).empty()) {
@ -192,6 +222,7 @@ void installSignalHandler() {
/// you can also use this function call, per thread so make sure these three
/// fatal signals are covered in your thread (even if you don't do a LOG(...) call
void installSignalHandlerForThread() {
#if !(defined(DISABLE_FATAL_SIGNALHANDLING))
if (!g_installed_thread_signal_handler) {
g_installed_thread_signal_handler = true;
if (SIG_ERR == signal(SIGTERM, signalHandler))
@ -205,14 +236,20 @@ void installSignalHandlerForThread() {
if (SIG_ERR == signal(SIGILL, signalHandler))
perror("signal - SIGILL");
}
#endif
}
void installCrashHandler() {
internal::installSignalHandler();
//const size_t kFirstExceptionHandler = 1; // Kept here for documentational purposes. last exception seems more what we want
const size_t kLastExceptionHandler = 0;
//g_vector_exception_handler = AddVectoredExceptionHandler(kLastExceptionHandler, vectorExceptionHandling);
g_previous_unexpected_exception_handler = SetUnhandledExceptionFilter(unexpectedExceptionHandling);
#if !(defined(DISABLE_VECTORED_EXCEPTIONHANDLING))
// const size_t kFirstExceptionHandler = 1;
// kFirstExeptionsHandler is kept here for documentational purposes.
// The last exception seems more what we want
const size_t kLastExceptionHandler = 0;
g_vector_exception_handler = AddVectoredExceptionHandler(kLastExceptionHandler, vectorExceptionHandling);
#endif
}
} // end namespace g2

View File

@ -9,7 +9,6 @@
#include "g2logmessagecapture.hpp"
#include "crashhandler.hpp"
// For Windows we need force a thread_local install per thread of three
// signals that must have a signal handler instealled per thread-basis
// It is really a royal pain. Seriously Microsoft? Seriously?

View File

@ -34,7 +34,7 @@
#define g2_MAP_PAIR_STRINGIFY(x) {x, #x}
namespace {
g2_thread_local bool g_thread_local_recursive_crash_check = false;
g2_thread_local size_t g_thread_local_recursive_crash_check = 0;
const std::map<g2::SignalType, std::string> kExceptionsAsText = {
g2_MAP_PAIR_STRINGIFY(EXCEPTION_ACCESS_VIOLATION)
@ -146,19 +146,24 @@ std::string convertFramesToText(std::vector<uint64_t>& frame_pointers) {
namespace stacktrace {
const std::string kUnknown = {"UNKNOWN EXCEPTION"};
/// return the text description of a Windows exception code
/// From MSDN GetExceptionCode http://msdn.microsoft.com/en-us/library/windows/desktop/ms679356(v=vs.85).aspx
std::string exceptionIdToText(g2::SignalType id) {
const auto iter = kExceptionsAsText.find(id);
if ( iter == kExceptionsAsText.end()) {
std::string unknown {"Unknown/" + std::to_string(id)};
std::string unknown = {kUnknown + ":" + std::to_string(id)};
return unknown;
}
return iter->second;
}
/// Yes a double lookup: first for isKnownException and then exceptionIdToText
/// for vectored exceptions we only deal with known exceptions so this tiny
/// overhead we can live with
bool isKnownException(g2::SignalType id) {
return (kExceptionsAsText.end() != kExceptionsAsText.find(id));
}
/// helper function: retrieve stackdump from no excisting exception pointer
std::string stackdump() {
@ -179,12 +184,12 @@ std::string stackdump(EXCEPTION_POINTERS* info) {
/// main stackdump function. retrieve stackdump, from the given context
std::string stackdump(CONTEXT* context) {
if (g_thread_local_recursive_crash_check) {
if (g_thread_local_recursive_crash_check >= 2) { // In Debug scenarious we allow one extra pass
std::string recursive_crash = {"\n\n\n***** Recursive crash detected"};
recursive_crash.append(", cannot continue stackdump traversal. *****\n\n\n");
return recursive_crash;
}
g_thread_local_recursive_crash_check = true;
++g_thread_local_recursive_crash_check;
static std::mutex m;
std::lock_guard<std::mutex> lock(m);

View File

@ -27,6 +27,10 @@ namespace stacktrace {
/// return the text description of a Windows exception code
std::string exceptionIdToText(g2::SignalType id);
/// return whether or not the exception is a known exception, i.e.
/// an exception that we should treat as a fatal event
bool isKnownException(g2::SignalType id);
/// helper function: retrieve stackdump from no excisting exception pointer
std::string stackdump();