223 lines
6.7 KiB
C++
223 lines
6.7 KiB
C++
#include "signal.h"
|
|
|
|
#include <exception>
|
|
#include <string>
|
|
|
|
#include <windows.h>
|
|
#include <dbghelp.h>
|
|
|
|
#include "external/stackwalker/stackwalker.h"
|
|
#include "hooks/libraryhook.h"
|
|
#include "launcher/shutdown.h"
|
|
#include "util/detour.h"
|
|
#include "util/libutils.h"
|
|
#include "util/logging.h"
|
|
#include "cfg/configurator.h"
|
|
|
|
#include "logger.h"
|
|
|
|
// MSVC compatibility
|
|
#ifdef exception_code
|
|
#undef exception_code
|
|
#endif
|
|
|
|
static decltype(MiniDumpWriteDump) *MiniDumpWriteDump_local = nullptr;
|
|
|
|
namespace launcher::signal {
|
|
|
|
// settings
|
|
bool DISABLE = false;
|
|
}
|
|
|
|
#define V(variant) case variant: return #variant
|
|
|
|
static std::string control_code(DWORD dwCtrlType) {
|
|
switch (dwCtrlType) {
|
|
V(CTRL_C_EVENT);
|
|
V(CTRL_BREAK_EVENT);
|
|
V(CTRL_CLOSE_EVENT);
|
|
V(CTRL_LOGOFF_EVENT);
|
|
V(CTRL_SHUTDOWN_EVENT);
|
|
default:
|
|
return "Unknown(0x" + to_hex(dwCtrlType) + ")";
|
|
}
|
|
}
|
|
|
|
static std::string exception_code(struct _EXCEPTION_RECORD *ExceptionRecord) {
|
|
switch (ExceptionRecord->ExceptionCode) {
|
|
V(EXCEPTION_ACCESS_VIOLATION);
|
|
V(EXCEPTION_ARRAY_BOUNDS_EXCEEDED);
|
|
V(EXCEPTION_BREAKPOINT);
|
|
V(EXCEPTION_DATATYPE_MISALIGNMENT);
|
|
V(EXCEPTION_FLT_DENORMAL_OPERAND);
|
|
V(EXCEPTION_FLT_DIVIDE_BY_ZERO);
|
|
V(EXCEPTION_FLT_INEXACT_RESULT);
|
|
V(EXCEPTION_FLT_INVALID_OPERATION);
|
|
V(EXCEPTION_FLT_OVERFLOW);
|
|
V(EXCEPTION_FLT_STACK_CHECK);
|
|
V(EXCEPTION_FLT_UNDERFLOW);
|
|
V(EXCEPTION_ILLEGAL_INSTRUCTION);
|
|
V(EXCEPTION_IN_PAGE_ERROR);
|
|
V(EXCEPTION_INT_DIVIDE_BY_ZERO);
|
|
V(EXCEPTION_INT_OVERFLOW);
|
|
V(EXCEPTION_INVALID_DISPOSITION);
|
|
V(EXCEPTION_NONCONTINUABLE_EXCEPTION);
|
|
V(EXCEPTION_PRIV_INSTRUCTION);
|
|
V(EXCEPTION_SINGLE_STEP);
|
|
V(EXCEPTION_STACK_OVERFLOW);
|
|
V(DBG_CONTROL_C);
|
|
default:
|
|
return "Unknown(0x" + to_hex(ExceptionRecord->ExceptionCode) + ")";
|
|
}
|
|
}
|
|
|
|
#undef V
|
|
|
|
static BOOL WINAPI HandlerRoutine(DWORD dwCtrlType) {
|
|
log_info("signal", "console ctrl handler called: {}", control_code(dwCtrlType));
|
|
|
|
if (dwCtrlType == CTRL_C_EVENT) {
|
|
launcher::shutdown();
|
|
} else if (dwCtrlType == CTRL_CLOSE_EVENT) {
|
|
launcher::shutdown();
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static LONG WINAPI TopLevelExceptionFilter(struct _EXCEPTION_POINTERS *ExceptionInfo) {
|
|
|
|
// ignore signal if disabled or no exception info provided
|
|
if (!launcher::signal::DISABLE && ExceptionInfo != nullptr) {
|
|
|
|
// get exception record
|
|
struct _EXCEPTION_RECORD *ExceptionRecord = ExceptionInfo->ExceptionRecord;
|
|
|
|
// print signal
|
|
log_warning("signal", "exception raised: {}", exception_code(ExceptionRecord));
|
|
|
|
// walk the exception chain
|
|
struct _EXCEPTION_RECORD *record_cause = ExceptionRecord->ExceptionRecord;
|
|
while (record_cause != nullptr) {
|
|
log_warning("signal", "caused by: {}", exception_code(record_cause));
|
|
record_cause = record_cause->ExceptionRecord;
|
|
}
|
|
|
|
// print stacktrace
|
|
StackWalker sw;
|
|
log_info("signal", "printing callstack");
|
|
if (!sw.ShowCallstack(GetCurrentThread(), ExceptionInfo->ContextRecord)) {
|
|
log_warning("signal", "failed to print callstack");
|
|
}
|
|
|
|
if (MiniDumpWriteDump_local != nullptr) {
|
|
HANDLE minidump_file = CreateFileA(
|
|
"minidump.dmp",
|
|
GENERIC_WRITE,
|
|
0,
|
|
nullptr,
|
|
CREATE_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
nullptr);
|
|
|
|
if (minidump_file != INVALID_HANDLE_VALUE) {
|
|
MINIDUMP_EXCEPTION_INFORMATION ExceptionParam {};
|
|
ExceptionParam.ThreadId = GetCurrentThreadId();
|
|
ExceptionParam.ExceptionPointers = ExceptionInfo;
|
|
ExceptionParam.ClientPointers = FALSE;
|
|
|
|
MiniDumpWriteDump_local(
|
|
GetCurrentProcess(),
|
|
GetCurrentProcessId(),
|
|
minidump_file,
|
|
MiniDumpNormal,
|
|
&ExceptionParam,
|
|
nullptr,
|
|
nullptr);
|
|
|
|
CloseHandle(minidump_file);
|
|
} else {
|
|
log_warning("signal", "failed to create 'minidump.dmp' for minidump: 0x{:08x}",
|
|
GetLastError());
|
|
}
|
|
} else {
|
|
log_warning("signal", "minidump creation function not available, skipping");
|
|
}
|
|
|
|
log_fatal("signal", "end");
|
|
}
|
|
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
|
|
static BOOL WINAPI SetConsoleCtrlHandler_hook(PHANDLER_ROUTINE pHandlerRoutine, BOOL Add) {
|
|
log_misc("signal", "SetConsoleCtrlHandler hook hit");
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter_hook(
|
|
LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter)
|
|
{
|
|
log_info("signal", "SetUnhandledExceptionFilter hook hit");
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
static PVOID WINAPI AddVectoredExceptionHandler_hook(ULONG First, PVECTORED_EXCEPTION_HANDLER Handler) {
|
|
log_info("signal", "AddVectoredExceptionHandler hook hit");
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void launcher::signal::attach() {
|
|
|
|
if (launcher::signal::DISABLE) {
|
|
return;
|
|
}
|
|
|
|
log_info("signal", "attaching...");
|
|
|
|
// set a `std::terminate` handler so `std::abort()` is not called by default
|
|
std::set_terminate([]() {
|
|
log_warning("signal", "std::terminate called");
|
|
|
|
launcher::kill();
|
|
});
|
|
|
|
// NOTE: inline hooks are not used here as they have caused EXCEPTION_ACCESS_VIOLATION in the past
|
|
// when hooking these methods
|
|
|
|
// hook relevant functions
|
|
libraryhook_hook_proc("SetConsoleCtrlHandler", SetConsoleCtrlHandler_hook);
|
|
libraryhook_hook_proc("SetUnhandledExceptionFilter", SetUnhandledExceptionFilter_hook);
|
|
libraryhook_hook_proc("AddVectoredExceptionHandler", AddVectoredExceptionHandler_hook);
|
|
libraryhook_enable();
|
|
|
|
// hook in all loaded modules
|
|
detour::iat_try("SetConsoleCtrlHandler", SetConsoleCtrlHandler_hook);
|
|
detour::iat_try("SetUnhandledExceptionFilter", SetUnhandledExceptionFilter_hook);
|
|
detour::iat_try("AddVectoredExceptionHandler", AddVectoredExceptionHandler_hook);
|
|
|
|
log_info("signal", "attached");
|
|
}
|
|
|
|
void launcher::signal::init() {
|
|
|
|
// load debug help library
|
|
if (!cfg::CONFIGURATOR_STANDALONE) {
|
|
auto dbghelp = libutils::try_library("dbghelp.dll");
|
|
|
|
if (dbghelp != nullptr) {
|
|
MiniDumpWriteDump_local = libutils::try_proc<decltype(MiniDumpWriteDump) *>(
|
|
dbghelp, "MiniDumpWriteDump");
|
|
}
|
|
}
|
|
|
|
// register our console ctrl handler
|
|
SetConsoleCtrlHandler(HandlerRoutine, TRUE);
|
|
|
|
// register our exception handler
|
|
SetUnhandledExceptionFilter(TopLevelExceptionFilter);
|
|
}
|