diff options
author | BobTheBob9 <for.oliver.kirkham@gmail.com> | 2023-01-04 18:08:13 +0000 |
---|---|---|
committer | BobTheBob9 <for.oliver.kirkham@gmail.com> | 2023-01-04 18:08:13 +0000 |
commit | e9f8a34dc3ca736f307cf195390aee7c8f5cb456 (patch) | |
tree | e65d1c2b1888895baf65bbde73e0a1a02cbb091a /NorthstarDLL/logging/crashhandler.cpp | |
parent | 6c45c7e94634c340019d12615b7eae8f3c0f49b3 (diff) | |
parent | 4fb1ae07d8f078e7abde99900c6a8286148a380a (diff) | |
download | NorthstarLauncher-e9f8a34dc3ca736f307cf195390aee7c8f5cb456.tar.gz NorthstarLauncher-e9f8a34dc3ca736f307cf195390aee7c8f5cb456.zip |
Merge remote-tracking branch 'origin/main' into experimental-bots-pr
Diffstat (limited to 'NorthstarDLL/logging/crashhandler.cpp')
-rw-r--r-- | NorthstarDLL/logging/crashhandler.cpp | 376 |
1 files changed, 376 insertions, 0 deletions
diff --git a/NorthstarDLL/logging/crashhandler.cpp b/NorthstarDLL/logging/crashhandler.cpp new file mode 100644 index 00000000..d4a54169 --- /dev/null +++ b/NorthstarDLL/logging/crashhandler.cpp @@ -0,0 +1,376 @@ +#include "pch.h" +#include "crashhandler.h" +#include "dedicated/dedicated.h" +#include "config/profile.h" +#include "util/version.h" +#include "mods/modmanager.h" + +#include <minidumpapiset.h> + +HANDLE hExceptionFilter; + +std::shared_ptr<ExceptionLog> storedException {}; + +#define RUNTIME_EXCEPTION 3765269347 +// clang format did this :/ +std::map<int, std::string> ExceptionNames = { + {EXCEPTION_ACCESS_VIOLATION, "Access Violation"}, {EXCEPTION_IN_PAGE_ERROR, "Access Violation"}, + {EXCEPTION_ARRAY_BOUNDS_EXCEEDED, "Array bounds exceeded"}, {EXCEPTION_DATATYPE_MISALIGNMENT, "Datatype misalignment"}, + {EXCEPTION_FLT_DENORMAL_OPERAND, "Denormal operand"}, {EXCEPTION_FLT_DIVIDE_BY_ZERO, "Divide by zero (float)"}, + {EXCEPTION_FLT_INEXACT_RESULT, "Inexact float result"}, {EXCEPTION_FLT_INVALID_OPERATION, "Invalid operation"}, + {EXCEPTION_FLT_OVERFLOW, "Numeric overflow (float)"}, {EXCEPTION_FLT_STACK_CHECK, "Stack check"}, + {EXCEPTION_FLT_UNDERFLOW, "Numeric underflow (float)"}, {EXCEPTION_ILLEGAL_INSTRUCTION, "Illegal instruction"}, + {EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero (int)"}, {EXCEPTION_INT_OVERFLOW, "Numeric overfloat (int)"}, + {EXCEPTION_INVALID_DISPOSITION, "Invalid disposition"}, {EXCEPTION_NONCONTINUABLE_EXCEPTION, "Non-continuable exception"}, + {EXCEPTION_PRIV_INSTRUCTION, "Priviledged instruction"}, {EXCEPTION_STACK_OVERFLOW, "Stack overflow"}, + {RUNTIME_EXCEPTION, "Uncaught runtime exception:"}, +}; + +void PrintExceptionLog(ExceptionLog& exc) +{ + // General crash message + spdlog::error("Northstar version: {}", version); + spdlog::error("Northstar has crashed! a minidump has been written and exception info is available below:"); + if (g_pModManager) + { + spdlog::error("Loaded mods: "); + for (const auto& mod : g_pModManager->m_LoadedMods) + { + if (mod.m_bEnabled) + { + spdlog::error("{} {}", mod.Name, mod.Version); + } + } + } + spdlog::error(exc.cause); + // If this was a runtime error, print the message + if (exc.runtimeInfo.length() != 0) + spdlog::error("\"{}\"", exc.runtimeInfo); + spdlog::error("At: {} + {}", exc.trace[0].name, exc.trace[0].relativeAddress); + spdlog::error(""); + spdlog::error("Stack trace:"); + + // Generate format string for stack trace + std::stringstream formatString; + formatString << " {:<" << exc.longestModuleNameLength + 2 << "} {:<" << exc.longestRelativeAddressLength << "} {}"; + std::string guide = fmt::format(formatString.str(), "Module Name", "Offset", "Full Address"); + std::string line(guide.length() + 2, '-'); + spdlog::error(guide); + spdlog::error(line); + + for (const auto& module : exc.trace) + spdlog::error(formatString.str(), module.name, module.relativeAddress, module.address); + + // Print dump of most CPU registers + spdlog::error(""); + for (const auto& reg : exc.registerDump) + spdlog::error(reg); + + if (!IsDedicatedServer()) + MessageBoxA( + 0, + "Northstar has crashed! Crash info can be found in R2Northstar/logs", + "Northstar has crashed!", + MB_ICONERROR | MB_OK | MB_SYSTEMMODAL); +} + +std::string GetExceptionName(ExceptionLog& exc) +{ + const DWORD exceptionCode = exc.exceptionRecord.ExceptionCode; + auto name = ExceptionNames[exceptionCode]; + if (exceptionCode == EXCEPTION_ACCESS_VIOLATION || exceptionCode == EXCEPTION_IN_PAGE_ERROR) + { + std::stringstream returnString; + returnString << name << ": "; + + auto exceptionInfo0 = exc.exceptionRecord.ExceptionInformation[0]; + auto exceptionInfo1 = exc.exceptionRecord.ExceptionInformation[1]; + + if (!exceptionInfo0) + returnString << "Attempted to read from: 0x" << (void*)exceptionInfo1; + else if (exceptionInfo0 == 1) + returnString << "Attempted to write to: 0x" << (void*)exceptionInfo1; + else if (exceptionInfo0 == 8) + returnString << "Data Execution Prevention (DEP) at: 0x" << (void*)std::hex << exceptionInfo1; + else + returnString << "Unknown access violation at: 0x" << (void*)exceptionInfo1; + return returnString.str(); + } + return name; +} + +// Custom formatter for the Xmm registers +template <> struct fmt::formatter<M128A> : fmt::formatter<string_view> +{ + template <typename FormatContext> auto format(const M128A& obj, FormatContext& ctx) + { + // Masking the top and bottom half of the long long + int v1 = obj.Low & INT_MAX; + int v2 = obj.Low >> 32; + int v3 = obj.High & INT_MAX; + int v4 = obj.High >> 32; + return fmt::format_to( + ctx.out(), + "[ {:G}, {:G}, {:G}, {:G}], [ 0x{:x}, 0x{:x}, 0x{:x}, 0x{:x} ]", + *reinterpret_cast<float*>(&v1), + *reinterpret_cast<float*>(&v2), + *reinterpret_cast<float*>(&v3), + *reinterpret_cast<float*>(&v4), + v1, + v2, + v3, + v4); + } +}; + +void GenerateTrace(ExceptionLog& exc, bool skipErrorHandlingFrames = true, int numSkipFrames = 0) +{ + + MODULEINFO crashedModuleInfo; + GetModuleInformation(GetCurrentProcess(), exc.crashedModule, &crashedModuleInfo, sizeof(crashedModuleInfo)); + + char crashedModuleFullName[MAX_PATH]; + GetModuleFileNameExA(GetCurrentProcess(), exc.crashedModule, crashedModuleFullName, MAX_PATH); + char* crashedModuleName = strrchr(crashedModuleFullName, '\\') + 1; + + DWORD64 crashedModuleOffset = ((DWORD64)exc.exceptionRecord.ExceptionAddress) - ((DWORD64)crashedModuleInfo.lpBaseOfDll); + + PVOID framesToCapture[62]; + int frames = RtlCaptureStackBackTrace(0, 62, framesToCapture, NULL); + bool haveSkippedErrorHandlingFrames = false; + + for (int i = 0; i < frames; i++) + { + + HMODULE backtraceModuleHandle; + GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, static_cast<LPCSTR>(framesToCapture[i]), &backtraceModuleHandle); + + char backtraceModuleFullName[MAX_PATH]; + GetModuleFileNameExA(GetCurrentProcess(), backtraceModuleHandle, backtraceModuleFullName, MAX_PATH); + char* backtraceModuleName = strrchr(backtraceModuleFullName, '\\') + 1; + + if (!haveSkippedErrorHandlingFrames) + { + if (!strncmp(backtraceModuleFullName, crashedModuleFullName, MAX_PATH) && + !strncmp(backtraceModuleName, crashedModuleName, MAX_PATH)) + { + haveSkippedErrorHandlingFrames = true; + } + else + { + continue; + } + } + + if (numSkipFrames > 0) + { + numSkipFrames--; + continue; + } + + void* actualAddress = (void*)framesToCapture[i]; + void* relativeAddress = (void*)(uintptr_t(actualAddress) - uintptr_t(backtraceModuleHandle)); + std::string s_moduleName {backtraceModuleName}; + std::string s_relativeAddress {fmt::format("{}", relativeAddress)}; + // These are used for formatting later on + if (s_moduleName.length() > exc.longestModuleNameLength) + { + exc.longestModuleNameLength = s_moduleName.length(); + } + if (s_relativeAddress.length() > exc.longestRelativeAddressLength) + { + exc.longestRelativeAddressLength = s_relativeAddress.length(); + } + + exc.trace.push_back(BacktraceModule {s_moduleName, s_relativeAddress, fmt::format("{}", actualAddress)}); + } + + CONTEXT* exceptionContext = &exc.contextRecord; + + exc.registerDump.push_back(fmt::format("Flags: 0b{0:b}", exceptionContext->ContextFlags)); + exc.registerDump.push_back(fmt::format("RIP: 0x{0:x}", exceptionContext->Rip)); + exc.registerDump.push_back(fmt::format("CS : 0x{0:x}", exceptionContext->SegCs)); + exc.registerDump.push_back(fmt::format("DS : 0x{0:x}", exceptionContext->SegDs)); + exc.registerDump.push_back(fmt::format("ES : 0x{0:x}", exceptionContext->SegEs)); + exc.registerDump.push_back(fmt::format("SS : 0x{0:x}", exceptionContext->SegSs)); + exc.registerDump.push_back(fmt::format("FS : 0x{0:x}", exceptionContext->SegFs)); + exc.registerDump.push_back(fmt::format("GS : 0x{0:x}", exceptionContext->SegGs)); + + exc.registerDump.push_back(fmt::format("RAX: 0x{0:x}", exceptionContext->Rax)); + exc.registerDump.push_back(fmt::format("RBX: 0x{0:x}", exceptionContext->Rbx)); + exc.registerDump.push_back(fmt::format("RCX: 0x{0:x}", exceptionContext->Rcx)); + exc.registerDump.push_back(fmt::format("RDX: 0x{0:x}", exceptionContext->Rdx)); + exc.registerDump.push_back(fmt::format("RSI: 0x{0:x}", exceptionContext->Rsi)); + exc.registerDump.push_back(fmt::format("RDI: 0x{0:x}", exceptionContext->Rdi)); + exc.registerDump.push_back(fmt::format("RBP: 0x{0:x}", exceptionContext->Rbp)); + exc.registerDump.push_back(fmt::format("RSP: 0x{0:x}", exceptionContext->Rsp)); + exc.registerDump.push_back(fmt::format("R8 : 0x{0:x}", exceptionContext->R8)); + exc.registerDump.push_back(fmt::format("R9 : 0x{0:x}", exceptionContext->R9)); + exc.registerDump.push_back(fmt::format("R10: 0x{0:x}", exceptionContext->R10)); + exc.registerDump.push_back(fmt::format("R11: 0x{0:x}", exceptionContext->R11)); + exc.registerDump.push_back(fmt::format("R12: 0x{0:x}", exceptionContext->R12)); + exc.registerDump.push_back(fmt::format("R13: 0x{0:x}", exceptionContext->R13)); + exc.registerDump.push_back(fmt::format("R14: 0x{0:x}", exceptionContext->R14)); + exc.registerDump.push_back(fmt::format("R15: 0x{0:x}", exceptionContext->R15)); + + exc.registerDump.push_back(fmt::format("Xmm0 : {}", exceptionContext->Xmm0)); + exc.registerDump.push_back(fmt::format("Xmm1 : {}", exceptionContext->Xmm1)); + exc.registerDump.push_back(fmt::format("Xmm2 : {}", exceptionContext->Xmm2)); + exc.registerDump.push_back(fmt::format("Xmm3 : {}", exceptionContext->Xmm3)); + exc.registerDump.push_back(fmt::format("Xmm4 : {}", exceptionContext->Xmm4)); + exc.registerDump.push_back(fmt::format("Xmm5 : {}", exceptionContext->Xmm5)); + exc.registerDump.push_back(fmt::format("Xmm6 : {}", exceptionContext->Xmm6)); + exc.registerDump.push_back(fmt::format("Xmm7 : {}", exceptionContext->Xmm7)); + exc.registerDump.push_back(fmt::format("Xmm8 : {}", exceptionContext->Xmm8)); + exc.registerDump.push_back(fmt::format("Xmm9 : {}", exceptionContext->Xmm9)); + exc.registerDump.push_back(fmt::format("Xmm10: {}", exceptionContext->Xmm10)); + exc.registerDump.push_back(fmt::format("Xmm11: {}", exceptionContext->Xmm11)); + exc.registerDump.push_back(fmt::format("Xmm12: {}", exceptionContext->Xmm12)); + exc.registerDump.push_back(fmt::format("Xmm13: {}", exceptionContext->Xmm13)); + exc.registerDump.push_back(fmt::format("Xmm14: {}", exceptionContext->Xmm14)); + exc.registerDump.push_back(fmt::format("Xmm15: {}", exceptionContext->Xmm15)); +} + +void CreateMiniDump(EXCEPTION_POINTERS* exceptionInfo) +{ + time_t time = std::time(nullptr); + tm currentTime = *std::localtime(&time); + std::stringstream stream; + stream << std::put_time(¤tTime, (GetNorthstarPrefix() + "/logs/nsdump%Y-%m-%d %H-%M-%S.dmp").c_str()); + + auto hMinidumpFile = CreateFileA(stream.str().c_str(), GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); + if (hMinidumpFile) + { + MINIDUMP_EXCEPTION_INFORMATION dumpExceptionInfo; + dumpExceptionInfo.ThreadId = GetCurrentThreadId(); + dumpExceptionInfo.ExceptionPointers = exceptionInfo; + dumpExceptionInfo.ClientPointers = false; + + MiniDumpWriteDump( + GetCurrentProcess(), + GetCurrentProcessId(), + hMinidumpFile, + MINIDUMP_TYPE(MiniDumpWithIndirectlyReferencedMemory | MiniDumpScanMemory), + &dumpExceptionInfo, + nullptr, + nullptr); + CloseHandle(hMinidumpFile); + } + else + spdlog::error("Failed to write minidump file {}!", stream.str()); +} + +long GenerateExceptionLog(EXCEPTION_POINTERS* exceptionInfo) +{ + storedException->exceptionRecord = *exceptionInfo->ExceptionRecord; + storedException->contextRecord = *exceptionInfo->ContextRecord; + const DWORD exceptionCode = exceptionInfo->ExceptionRecord->ExceptionCode; + + void* exceptionAddress = exceptionInfo->ExceptionRecord->ExceptionAddress; + + storedException->cause = GetExceptionName(*storedException); + + HMODULE crashedModuleHandle; + GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, static_cast<LPCSTR>(exceptionAddress), &crashedModuleHandle); + + storedException->crashedModule = crashedModuleHandle; + + // When encountering a runtime exception, we store the exception to be displayed later + // We then have to return EXCEPTION_CONTINUE_SEARCH so that our runtime handler may be called + // This might possibly cause some issues if client and server are crashing at the same time, but honestly i don't care + if (exceptionCode == RUNTIME_EXCEPTION) + { + GenerateTrace(*storedException, false, 2); + storedException = storedException; + return EXCEPTION_CONTINUE_SEARCH; + } + + GenerateTrace(*storedException, true, 0); + CreateMiniDump(exceptionInfo); + PrintExceptionLog(*storedException); + return EXCEPTION_EXECUTE_HANDLER; +} + +long __stdcall ExceptionFilter(EXCEPTION_POINTERS* exceptionInfo) +{ + if (!IsDebuggerPresent()) + { + // Check if we are capable of handling this type of exception + if (ExceptionNames.find(exceptionInfo->ExceptionRecord->ExceptionCode) == ExceptionNames.end()) + return EXCEPTION_CONTINUE_SEARCH; + if (exceptionInfo->ExceptionRecord->ExceptionCode == RUNTIME_EXCEPTION) + { + storedException = std::make_shared<ExceptionLog>(); + storedException->exceptionRecord = *exceptionInfo->ExceptionRecord; + storedException->contextRecord = *exceptionInfo->ContextRecord; + } + else + { + CreateMiniDump(exceptionInfo); + return GenerateExceptionLog(exceptionInfo); + } + } + + return EXCEPTION_EXECUTE_HANDLER; +} + +void RuntimeExceptionHandler() +{ + auto ptr = std::current_exception(); + if (ptr) + { + try + { + // This is to generate an actual std::exception object that we can then inspect + std::rethrow_exception(ptr); + } + catch (std::exception& e) + { + storedException->runtimeInfo = e.what(); + } + catch (...) + { + storedException->runtimeInfo = "Unknown runtime exception type"; + } + EXCEPTION_POINTERS test {}; + test.ContextRecord = &storedException->contextRecord; + test.ExceptionRecord = &storedException->exceptionRecord; + CreateMiniDump(&test); + GenerateExceptionLog(&test); + PrintExceptionLog(*storedException); + exit(-1); + } + else + { + spdlog::error( + "std::current_exception() returned nullptr while being handled by RuntimeExceptionHandler. This should never happen!"); + std::abort(); + } +} + +BOOL WINAPI ConsoleHandlerRoutine(DWORD eventCode) +{ + switch (eventCode) + { + case CTRL_CLOSE_EVENT: + // User closed console, shut everything down + spdlog::info("Exiting due to console close..."); + RemoveCrashHandler(); + exit(EXIT_SUCCESS); + return FALSE; + } + + return TRUE; +} + +void InitialiseCrashHandler() +{ + hExceptionFilter = AddVectoredExceptionHandler(TRUE, ExceptionFilter); + SetConsoleCtrlHandler(ConsoleHandlerRoutine, true); + std::set_terminate(RuntimeExceptionHandler); +} + +void RemoveCrashHandler() +{ + RemoveVectoredExceptionHandler(hExceptionFilter); +} |