Fix top level exception handler on Windows
This commit is contained in:
parent
dd500d631c
commit
355da63a1d
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
Loading…
Reference in New Issue