Fix top level exception handler on Windows

This commit is contained in:
Sebastian Messmer 2018-08-01 16:29:14 -07:00
parent dd500d631c
commit 355da63a1d
4 changed files with 126 additions and 60 deletions

View File

@ -145,13 +145,24 @@ namespace cpputils {
return backtrace.str();
}
namespace {
bool our_top_level_handler_set = false;
LPTOP_LEVEL_EXCEPTION_FILTER previous_top_level_handler = nullptr;
}
LONG WINAPI TopLevelExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo)
{
std::string backtrace = backtrace_to_string(pExceptionInfo->ContextRecord);
LOG(ERR, "Top level exception. Code: {}. Backtrace:\n{}", exception_code_string(pExceptionInfo->ExceptionRecord->ExceptionCode), backtrace);
return EXCEPTION_CONTINUE_SEARCH;
if (previous_top_level_handler != nullptr) {
// There was already a top level exception handler set when we called showBacktraceOnCrash(). Call it.
return (*previous_top_level_handler)(pExceptionInfo);
} else {
// previous_top_level_handler == nullptr means there was no top level exception handler set when we called showBacktraceOnCrash()
// so there's nothing else we need to call.
return EXCEPTION_CONTINUE_SEARCH;
}
}
}
@ -164,10 +175,11 @@ namespace cpputils {
}
void showBacktraceOnCrash() {
PVOID result = AddVectoredExceptionHandler(1, TopLevelExceptionHandler);
if (result == nullptr) {
throw std::runtime_error("Error setting top level exception handler");
if (our_top_level_handler_set) {
throw std::logic_error("showBackraceOnCrash: Our top level handler is already set.");
}
previous_top_level_handler = SetUnhandledExceptionFilter(TopLevelExceptionHandler);
our_top_level_handler_set = true;
}
}

View File

@ -54,9 +54,12 @@ set(SOURCES
)
add_executable(${PROJECT_NAME}_exit_status process/exit_status.cpp)
add_executable(${PROJECT_NAME}_exit_signal assert/exit_signal.cpp)
target_link_libraries(${PROJECT_NAME}_exit_signal cpp-utils)
add_executable(${PROJECT_NAME} ${SOURCES})
target_link_libraries(${PROJECT_NAME} googletest cpp-utils)
add_dependencies(${PROJECT_NAME} ${PROJECT_NAME}_exit_status ${PROJECT_NAME}_exit_signal)
add_test(${PROJECT_NAME} ${PROJECT_NAME})
target_enable_style_warnings(${PROJECT_NAME})

View File

@ -1,10 +1,24 @@
#include <gmock/gmock.h>
#include <csignal>
#include "cpp-utils/assert/backtrace.h"
#include "cpp-utils/process/subprocess.h"
using std::string;
using testing::HasSubstr;
namespace {
std::string call_process_exiting_with(const std::string& kind, const std::string& signal = "") {
#if defined(_MSC_VER)
constexpr const char* executable = "cpp-utils-test_exit_signal.exe";
#else
constexpr const char* executable = "./test/cpp-utils/cpp-utils-test_exit_signal";
#endif
const std::string command = std::string(executable) + " \"" + kind + "\" \"" + signal + "\" 2>&1";
auto result = cpputils::Subprocess::call(command);
return result.output;
}
}
TEST(BacktraceTest, ContainsExecutableName) {
string backtrace = cpputils::backtrace();
EXPECT_THAT(backtrace, HasSubstr("cpp-utils-test"));
@ -16,81 +30,82 @@ TEST(BacktraceTest, ContainsTopLevelLine) {
EXPECT_THAT(backtrace, HasSubstr("ContainsTopLevelLine"));
}
namespace {
void nullptr_access() {
cpputils::showBacktraceOnCrash();
int* ptr = nullptr;
*ptr = 5;
std::string call_process_exiting_with_nullptr_violation() {
return call_process_exiting_with("nullptr");
}
std::string call_process_exiting_with_exception(const std::string& message) {
return call_process_exiting_with("exception", message);
}
}
#if defined(_MSC_VER)
#include <Windows.h>
namespace {
void raise_sigsegv() {
cpputils::showBacktraceOnCrash();
::RaiseException(EXCEPTION_ACCESS_VIOLATION, EXCEPTION_NONCONTINUABLE, 0, NULL);
std::string call_process_exiting_with_sigsegv() {
return call_process_exiting_with("signal", std::to_string(EXCEPTION_ACCESS_VIOLATION));
}
void raise_sigill() {
cpputils::showBacktraceOnCrash();
::RaiseException(EXCEPTION_ILLEGAL_INSTRUCTION, EXCEPTION_NONCONTINUABLE, 0, NULL);
std::string call_process_exiting_with_sigill() {
return call_process_exiting_with("signal", std::to_string(EXCEPTION_ILLEGAL_INSTRUCTION));
}
void raise_code(DWORD exception_code) {
cpputils::showBacktraceOnCrash();
::RaiseException(exception_code, EXCEPTION_NONCONTINUABLE, 0, NULL);
std::string call_process_exiting_with_code(DWORD code) {
return call_process_exiting_with("signal", std::to_string(code));
}
}
#else
namespace {
void raise_sigsegv() {
cpputils::showBacktraceOnCrash();
::raise(SIGSEGV);
std::string call_process_exiting_with_sigsegv() {
return call_process_exiting_with("signal", std::to_string(SIGSEGV));
}
void raise_sigabrt() {
cpputils::showBacktraceOnCrash();
::raise(SIGABRT);
std::string call_process_exiting_with_sigabrt() {
return call_process_exiting_with("signal", std::to_string(SIGABRT));
}
void raise_sigill() {
cpputils::showBacktraceOnCrash();
::raise(SIGILL);
std::string call_process_exiting_with_sigill() {
return call_process_exiting_with("signal", std::to_string(SIGILL));
}
}
#endif
TEST(BacktraceTest, DoesntCrashOnCaughtException) {
// This is needed to make sure we don't use some kind of vectored exception handler on Windows
// that ignores the call stack and always jumps on when an exception happens.
cpputils::showBacktraceOnCrash();
try {
throw std::logic_error("exception");
} catch (const std::logic_error& e) {
// intentionally empty
}
}
TEST(BacktraceTest, ShowBacktraceOnNullptrAccess) {
EXPECT_DEATH(
nullptr_access(),
"ShowBacktraceOnNullptrAccess_Test::TestBody"
);
auto output = call_process_exiting_with_nullptr_violation();
EXPECT_THAT(output, HasSubstr("handle_exit_signal"));
}
TEST(BacktraceTest, ShowBacktraceOnSigSegv) {
EXPECT_DEATH(
raise_sigsegv(),
"ShowBacktraceOnSigSegv_Test::TestBody"
);
auto output = call_process_exiting_with_sigsegv();
EXPECT_THAT(output, HasSubstr("handle_exit_signal"));
}
TEST(BacktraceTest, ShowBacktraceOnUnhandledException) {
auto output = call_process_exiting_with_exception("my_exception_message");
EXPECT_THAT(output, HasSubstr("handle_exit_signal"));
}
TEST(BacktraceTest, ShowBacktraceOnSigIll) {
EXPECT_DEATH(
raise_sigill(),
"ShowBacktraceOnSigIll_Test::TestBody"
);
auto output = call_process_exiting_with_sigill();
EXPECT_THAT(output, HasSubstr("handle_exit_signal"));
}
#if !defined(_MSC_VER)
TEST(BacktraceTest, ShowBacktraceOnSigAbrt) {
EXPECT_DEATH(
raise_sigabrt(),
"ShowBacktraceOnSigAbrt_Test::TestBody"
);
auto output = call_process_exiting_with_sigabrt();
EXPECT_THAT(output, HasSubstr("handle_exit_signal"));
}
TEST(BacktraceTest, ShowBacktraceOnSigAbrt_ShowsCorrectSignalName) {
EXPECT_DEATH(
raise_sigabrt(),
"SIGABRT"
);
auto output = call_process_exiting_with_sigabrt();
EXPECT_THAT(output, HasSubstr("SIGABRT"));
}
#endif
@ -103,24 +118,25 @@ constexpr const char* sigill_message = "EXCEPTION_ILLEGAL_INSTRUCTION";
#endif
TEST(BacktraceTest, ShowBacktraceOnSigSegv_ShowsCorrectSignalName) {
EXPECT_DEATH(
raise_sigsegv(),
sigsegv_message
);
auto output = call_process_exiting_with_sigsegv();
EXPECT_THAT(output, HasSubstr(sigsegv_message));
}
TEST(BacktraceTest, ShowBacktraceOnSigIll_ShowsCorrectSignalName) {
EXPECT_DEATH(
raise_sigill(),
sigill_message
);
auto output = call_process_exiting_with_sigill();
EXPECT_THAT(output, HasSubstr(sigill_message));
}
#if !defined(_MSC_VER)
TEST(BacktraceTest, ShowBacktraceOnUnhandledException_ShowsCorrectExceptionMessage) {
auto output = call_process_exiting_with_exception("my_exception_message");
EXPECT_THAT(output, HasSubstr("my_exception_message"));
}
#endif
#if defined(_MSC_VER)
TEST(BacktraceTest, UnknownCode_ShowsCorrectSignalName) {
EXPECT_DEATH(
raise_code(0x12345678),
"UNKNOWN_CODE\\(0x12345678\\)"
);
auto output = call_process_exiting_with_code(0x12345678);
EXPECT_THAT(output, HasSubstr("UNKNOWN_CODE(0x12345678)"));
}
#endif

View File

@ -0,0 +1,35 @@
#include <cpp-utils/assert/backtrace.h>
#include <csignal>
#include <stdexcept>
#if defined(_MSC_VER)
#include <Windows.h>
#endif
int handle_exit_signal(char* argv[]) {
const std::string kind = argv[1];
if (kind == "exception") {
throw std::logic_error(argv[2]);
} else if (kind == "nullptr") {
int* ptr = nullptr;
*ptr = 5;
} else if (kind == "signal") {
#if defined(_MSC_VER)
DWORD code = std::atoll(argv[2]);
::RaiseException(code, EXCEPTION_NONCONTINUABLE, 0, NULL);
#else
int code = std::atoi(argv[2]);
::raise(code);
#endif
}
}
int main(int argc, char* argv[]) {
cpputils::showBacktraceOnCrash();
#if defined(_MSC_VER)
// don't show windows error box
_set_abort_behavior(0, _WRITE_ABORT_MSG);
#endif
handle_exit_signal(argv);
}