From 37a50c92b8bdef0163992a9b11e567f016237760 Mon Sep 17 00:00:00 2001 From: Marvin D <41352111+IcePixelx@users.noreply.github.com> Date: Fri, 23 Dec 2022 02:56:40 +0100 Subject: [PATCH] VectoredExceptionHandler init. * Only gets compiled in for release builds. * Attaches on gamesdk init, so should catch everything on dll setup also. * Fake PDB parsing still needs to be done. --- r5dev/public/utility/crashhandler.cpp | 197 ++++++++++++++++++++++++++ r5dev/vproj/clientsdk.vcxproj | 1 + r5dev/vproj/clientsdk.vcxproj.filters | 3 + r5dev/vproj/dedicated.vcxproj | 1 + r5dev/vproj/dedicated.vcxproj.filters | 3 + r5dev/vproj/gamesdk.vcxproj | 1 + r5dev/vproj/gamesdk.vcxproj.filters | 3 + 7 files changed, 209 insertions(+) create mode 100644 r5dev/public/utility/crashhandler.cpp diff --git a/r5dev/public/utility/crashhandler.cpp b/r5dev/public/utility/crashhandler.cpp new file mode 100644 index 00000000..6de4473b --- /dev/null +++ b/r5dev/public/utility/crashhandler.cpp @@ -0,0 +1,197 @@ +//=============================================================================// +// +// Purpose: Crash handling, it handles crashes! +// +//=============================================================================// +#include "core/stdafx.h" +#include "public/utility/binstream.h" + +#ifndef _DEBUG + +// Class is just for DLL init and DLL close, so we can actually define it here fine. +class CCrashHandler +{ +public: + CCrashHandler(); + ~CCrashHandler(); + +private: + void* m_hExceptionHandler; +}; + +static std::map g_ExceptionToString = +{ + { EXCEPTION_ACCESS_VIOLATION, "Access Violation" }, + { EXCEPTION_IN_PAGE_ERROR, "Access Violation" }, + { EXCEPTION_ARRAY_BOUNDS_EXCEEDED, "Array bounds exceeded" }, + { EXCEPTION_ILLEGAL_INSTRUCTION, "Illegal instruction" }, + { EXCEPTION_INVALID_DISPOSITION, "Invalid disposition" }, + { EXCEPTION_NONCONTINUABLE_EXCEPTION, "Non-continuable exception" }, + { EXCEPTION_PRIV_INSTRUCTION, "Priviledged instruction" }, + { EXCEPTION_STACK_OVERFLOW, "Stack overflow" }, + { EXCEPTION_DATATYPE_MISALIGNMENT, "Datatype misalignment" }, + { EXCEPTION_FLT_DENORMAL_OPERAND, "Denormal operand [FLT]" }, + { EXCEPTION_FLT_DIVIDE_BY_ZERO, "Divide by zero [FLT]" }, + { EXCEPTION_FLT_INEXACT_RESULT, "Inexact float result [FLT]" }, + { EXCEPTION_FLT_INVALID_OPERATION, "Invalid operation [FLT]" }, + { EXCEPTION_FLT_OVERFLOW, "Numeric overflow [FLT]" }, + { EXCEPTION_FLT_STACK_CHECK, "Stack check [FLT]" }, + { EXCEPTION_FLT_UNDERFLOW, "Numeric underflow [FLT]" }, + { EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero [INT]" }, + { EXCEPTION_INT_OVERFLOW, "Numeric overfloat [INT]" } +}; + +// Borrowed from the R2 project +template<> struct fmt::formatter : fmt::formatter +{ + template auto format(const M128A& obj, FormatContext& ctx) + { + 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(&v1), + *reinterpret_cast(&v2), + *reinterpret_cast(&v3), + *reinterpret_cast(&v4), + v1, + v2, + v3, + v4); + } +}; + +string GetModuleCrashOffsetAndName(uintptr_t address) +{ + HMODULE crashedModuleHandle; + if (!GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, static_cast((void*)address), &crashedModuleHandle)) + return {}; + + MODULEINFO crashedModuleInfo; + if (!GetModuleInformation(GetCurrentProcess(), crashedModuleHandle, &crashedModuleInfo, sizeof(crashedModuleInfo))) + return {}; + + char szCrashedModuleFullName[MAX_PATH]; + if (!GetModuleFileNameExA(GetCurrentProcess(), crashedModuleHandle, szCrashedModuleFullName, MAX_PATH)) + return {}; + + const char* szCrashedModuleName = strrchr(szCrashedModuleFullName, '\\') + 1; + + uintptr_t crashedModuleOffset = (address - reinterpret_cast(crashedModuleInfo.lpBaseOfDll)); + + return fmt::format("{0} + 0x{1:x}", szCrashedModuleName, crashedModuleOffset); +} + + +string GetExceptionReason(EXCEPTION_POINTERS* exceptionInfo) +{ + const string& svException = g_ExceptionToString.at(exceptionInfo->ExceptionRecord->ExceptionCode); + stringstream ss; + + ss << "Cause: " << svException << std::endl; + + switch (exceptionInfo->ExceptionRecord->ExceptionCode) + { + case EXCEPTION_ACCESS_VIOLATION: + case EXCEPTION_IN_PAGE_ERROR: + { + ULONG_PTR uExceptionInfo0 = exceptionInfo->ExceptionRecord->ExceptionInformation[0]; + ULONG_PTR uExceptionInfo1 = exceptionInfo->ExceptionRecord->ExceptionInformation[1]; + + // I don't remember why this has been done like this, but I trust my old self for once on this. + if (!uExceptionInfo0) + ss << "Attempted to read from: 0x" << std::setw(8) << std::setfill('0') << std::hex << uExceptionInfo1 << std::endl; + else if (uExceptionInfo0 == 1) + ss << "Attempted to write to: 0x" << std::setw(8) << std::setfill('0') << std::hex << uExceptionInfo1 << std::endl; + else if (uExceptionInfo0 == 8) + ss << "Data Execution Prevention (DEP) at: 0x" << std::setw(8) << std::setfill('0') << std::hex << uExceptionInfo1 << std::endl; + else + ss << "Unknown access violation at: 0x" << std::setw(8) << std::setfill('0') << std::hex << uExceptionInfo1 << std::endl; + + return ss.str(); + } + default: + { + return ss.str(); + } + } +} + +// TODO: PDB PARSE AND RUNTIME EXCP. +long __stdcall ExceptionFilter(EXCEPTION_POINTERS* exceptionInfo) +{ + // Avoid recursive calls. + static bool bLogged = false; + if (bLogged) + return EXCEPTION_CONTINUE_SEARCH; + + // If you are debugging you don't need this. + if (IsDebuggerPresent()) + return EXCEPTION_EXECUTE_HANDLER; + + // Goodluck on you if you somehow hit a guard page. + if (g_ExceptionToString.find(exceptionInfo->ExceptionRecord->ExceptionCode) == g_ExceptionToString.end()) + return EXCEPTION_CONTINUE_SEARCH; + + CONTEXT* pExceptionContext = exceptionInfo->ContextRecord; + + // Setup message. + string svMessage = fmt::format("{0}Module: {1}\r\n", GetExceptionReason(exceptionInfo), GetModuleCrashOffsetAndName(reinterpret_cast(exceptionInfo->ExceptionRecord->ExceptionAddress))); + + constexpr const char* stdRegs[] = { "RAX", "RCX", "RDX", "RBX", "RSP", "RBP", "RSI", "RDI", "R8", "R9", "R10", "R11", "R12", "R13", "R14", "R15", "RIP" }; + constexpr const char* xmmRegs[] = { "XMM0", "XMM1", "XMM2", "XMM3", "XMM4", "XMM5", "XMM6", "XMM7", "XMM8", "XMM9", "XMM10", "XMM11", "XMM12", "XMM13", "XMM14", "XMM15" }; + + // Normal registers now. + for (int i = 0; i < SDK_ARRAYSIZE(stdRegs); i++) + { + svMessage += fmt::format("{0}: 0x{1:X}\r\n", stdRegs[i], *(DWORD64*)((std::uintptr_t)pExceptionContext + offsetof(CONTEXT, Rax) + (sizeof(DWORD64) * i))); + } + + // FPU Time! + for (int i = 0; i < SDK_ARRAYSIZE(xmmRegs); i++) + { + svMessage += fmt::format("{0}: {1}\r\n", xmmRegs[i], *(M128A*)((std::uintptr_t)pExceptionContext + offsetof(CONTEXT, Xmm0) + (sizeof(M128A) * i))); + } + + svMessage += "\r\nStacktrace:\r\n\r\n"; + + // Now get the callstack.. + constexpr int NUM_FRAMES_TO_CAPTURE = 60; + void* pStackTrace[NUM_FRAMES_TO_CAPTURE] = { 0 }; + uint16_t nCapturedFrames = RtlCaptureStackBackTrace(0, NUM_FRAMES_TO_CAPTURE, pStackTrace, NULL); + + for (uint16_t i = 0; i < nCapturedFrames; i++) + { + svMessage += GetModuleCrashOffsetAndName(reinterpret_cast(pStackTrace[i])) + "\n"; + } + + std::time_t time = std::time(nullptr); + stringstream ss; ss << "platform\\logs\\" << "apex_crash_" << std::put_time(std::localtime(&time), "%Y-%m-%d %H-%M-%S.txt"); + + // Not using CBaseFileSystem here cause if that would crash we'd have a problem. + CIOStream ioLogFile = CIOStream(ss.str(), CIOStream::Mode_t::WRITE); + ioLogFile.WriteString(svMessage); + ioLogFile.Close(); + + MessageBoxA(0, svMessage.c_str(), "R5R Crashed :/", MB_ICONERROR | MB_OK); + + return EXCEPTION_EXECUTE_HANDLER; +} + +CCrashHandler::CCrashHandler() +{ + m_hExceptionHandler = AddVectoredExceptionHandler(TRUE, ExceptionFilter); +} + +CCrashHandler::~CCrashHandler() +{ + RemoveVectoredExceptionHandler(m_hExceptionHandler); +} + + +// Init on DLL init! +CCrashHandler* g_CrashHandler = new CCrashHandler(); + +#endif // _DEBUG \ No newline at end of file diff --git a/r5dev/vproj/clientsdk.vcxproj b/r5dev/vproj/clientsdk.vcxproj index 59c7c3d9..07a0f2d2 100644 --- a/r5dev/vproj/clientsdk.vcxproj +++ b/r5dev/vproj/clientsdk.vcxproj @@ -103,6 +103,7 @@ + diff --git a/r5dev/vproj/clientsdk.vcxproj.filters b/r5dev/vproj/clientsdk.vcxproj.filters index 2505cc74..c12ee2a7 100644 --- a/r5dev/vproj/clientsdk.vcxproj.filters +++ b/r5dev/vproj/clientsdk.vcxproj.filters @@ -648,6 +648,9 @@ sdk\public\utility + + sdk\public\utility + diff --git a/r5dev/vproj/dedicated.vcxproj b/r5dev/vproj/dedicated.vcxproj index d9469e8d..ce068d28 100644 --- a/r5dev/vproj/dedicated.vcxproj +++ b/r5dev/vproj/dedicated.vcxproj @@ -563,6 +563,7 @@ + diff --git a/r5dev/vproj/dedicated.vcxproj.filters b/r5dev/vproj/dedicated.vcxproj.filters index 41de95c6..6d92a3fc 100644 --- a/r5dev/vproj/dedicated.vcxproj.filters +++ b/r5dev/vproj/dedicated.vcxproj.filters @@ -1622,6 +1622,9 @@ sdk\public\utility + + sdk\public\utility + diff --git a/r5dev/vproj/gamesdk.vcxproj b/r5dev/vproj/gamesdk.vcxproj index ea6527ac..62ddbff2 100644 --- a/r5dev/vproj/gamesdk.vcxproj +++ b/r5dev/vproj/gamesdk.vcxproj @@ -112,6 +112,7 @@ + diff --git a/r5dev/vproj/gamesdk.vcxproj.filters b/r5dev/vproj/gamesdk.vcxproj.filters index a70d2663..634b431a 100644 --- a/r5dev/vproj/gamesdk.vcxproj.filters +++ b/r5dev/vproj/gamesdk.vcxproj.filters @@ -690,6 +690,9 @@ sdk\public\utility + + sdk\public\utility +