/** * UniversalSlashingSimulator - Crash Handler Implementation */ #include "CrashHandler.h" #include "../Logging/Log.h" #include #include #include #pragma comment(lib, "DbgHelp.lib") namespace USS { // Static member initialization bool FCrashHandler::s_bInitialized = false; bool FCrashHandler::s_bShowMessageBox = true; bool FCrashHandler::s_bWriteMinidump = true; LPTOP_LEVEL_EXCEPTION_FILTER FCrashHandler::s_pPreviousFilter = nullptr; std::terminate_handler FCrashHandler::s_pPreviousTerminate = nullptr; // Maximum frames to capture in callstack constexpr int MAX_CALLSTACK_FRAMES = 64; // ======================================================================== // Exception Type Conversion // ======================================================================== const char* ExceptionTypeToString(EExceptionType Type) { switch (Type) { case EExceptionType::CPlusPlusException: return "C++ Exception"; case EExceptionType::AccessViolation: return "Access Violation"; case EExceptionType::StackOverflow: return "Stack Overflow"; case EExceptionType::DivideByZero: return "Divide By Zero"; case EExceptionType::IllegalInstruction: return "Illegal Instruction"; case EExceptionType::PrivilegedInstruction: return "Privileged Instruction"; case EExceptionType::InvalidHandle: return "Invalid Handle"; case EExceptionType::HeapCorruption: return "Heap Corruption"; case EExceptionType::PureVirtualCall: return "Pure Virtual Call"; case EExceptionType::InvalidParameter: return "Invalid Parameter"; case EExceptionType::Other: return "Other Exception"; default: return "Unknown Exception"; } } EExceptionType FCrashHandler::GetExceptionType(DWORD ExceptionCode) { switch (ExceptionCode) { case EXCEPTION_ACCESS_VIOLATION: return EExceptionType::AccessViolation; case EXCEPTION_STACK_OVERFLOW: return EExceptionType::StackOverflow; case EXCEPTION_INT_DIVIDE_BY_ZERO: return EExceptionType::DivideByZero; case EXCEPTION_ILLEGAL_INSTRUCTION: return EExceptionType::IllegalInstruction; case EXCEPTION_PRIV_INSTRUCTION: return EExceptionType::PrivilegedInstruction; case EXCEPTION_INVALID_HANDLE: return EExceptionType::InvalidHandle; case STATUS_HEAP_CORRUPTION: return EExceptionType::HeapCorruption; case 0xE06D7363: return EExceptionType::CPlusPlusException; // MSVC C++ exception default: return EExceptionType::Other; } } // ======================================================================== // Initialization / Shutdown // ======================================================================== EResult FCrashHandler::Initialize() { if (s_bInitialized) return EResult::AlreadyInitialized; SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES); if (!SymInitialize(GetCurrentProcess(), nullptr, TRUE)) { // Non-fatal - callstacks just won't have symbol names OutputDebugStringA("[USS] Warning: SymInitialize failed, callstacks will not have symbol names\n"); } s_pPreviousFilter = SetUnhandledExceptionFilter(UnhandledExceptionHandler); s_pPreviousTerminate = std::set_terminate(TerminateHandler); _set_purecall_handler(PureCallHandler); _set_invalid_parameter_handler(InvalidParameterHandler); s_bInitialized = true; USS_LOG("Crash handler initialized"); return EResult::Success; } void FCrashHandler::Shutdown() { if (!s_bInitialized) return; if (s_pPreviousFilter) SetUnhandledExceptionFilter(s_pPreviousFilter); if (s_pPreviousTerminate) std::set_terminate(s_pPreviousTerminate); SymCleanup(GetCurrentProcess()); s_bInitialized = false; } // ======================================================================== // Callstack Capture // ======================================================================== void FCrashHandler::CaptureAndLogCallstack(CONTEXT* pContext) { void* Stack[MAX_CALLSTACK_FRAMES]; WORD FrameCount = 0; if (pContext) { // Use StackWalk64 for more accurate results when we have a context STACKFRAME64 StackFrame = {}; DWORD MachineType; #ifdef _M_X64 MachineType = IMAGE_FILE_MACHINE_AMD64; StackFrame.AddrPC.Offset = pContext->Rip; StackFrame.AddrPC.Mode = AddrModeFlat; StackFrame.AddrFrame.Offset = pContext->Rbp; StackFrame.AddrFrame.Mode = AddrModeFlat; StackFrame.AddrStack.Offset = pContext->Rsp; StackFrame.AddrStack.Mode = AddrModeFlat; #else MachineType = IMAGE_FILE_MACHINE_I386; StackFrame.AddrPC.Offset = pContext->Eip; StackFrame.AddrPC.Mode = AddrModeFlat; StackFrame.AddrFrame.Offset = pContext->Ebp; StackFrame.AddrFrame.Mode = AddrModeFlat; StackFrame.AddrStack.Offset = pContext->Esp; StackFrame.AddrStack.Mode = AddrModeFlat; #endif HANDLE hProcess = GetCurrentProcess(); HANDLE hThread = GetCurrentThread(); // Make a copy of context since StackWalk64 can modify it CONTEXT ContextCopy = *pContext; for (WORD i = 0; i < MAX_CALLSTACK_FRAMES; ++i) { if (!StackWalk64(MachineType, hProcess, hThread, &StackFrame, &ContextCopy, nullptr, SymFunctionTableAccess64, SymGetModuleBase64, nullptr)) { break; } if (StackFrame.AddrPC.Offset == 0) break; Stack[FrameCount++] = reinterpret_cast(StackFrame.AddrPC.Offset); } } else { // No context provided, capture current callstack FrameCount = CaptureStackBackTrace(0, MAX_CALLSTACK_FRAMES, Stack, nullptr); } // Log callstack header Log::Write(ELogLevel::Fatal, "========== CALLSTACK =========="); if (FrameCount == 0) { Log::Write(ELogLevel::Fatal, " (No frames captured)"); return; } // Prepare symbol info buffer constexpr size_t MaxNameLen = 256; char SymbolBuffer[sizeof(SYMBOL_INFO) + MaxNameLen]; SYMBOL_INFO* pSymbol = reinterpret_cast(SymbolBuffer); pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO); pSymbol->MaxNameLen = MaxNameLen; IMAGEHLP_LINE64 LineInfo = {}; LineInfo.SizeOfStruct = sizeof(IMAGEHLP_LINE64); HANDLE hProcess = GetCurrentProcess(); for (WORD i = 0; i < FrameCount; ++i) { DWORD64 Address = reinterpret_cast(Stack[i]); // Get module name HMODULE hModule = nullptr; char ModuleName[MAX_PATH] = ""; if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, reinterpret_cast(Address), &hModule)) { GetModuleFileNameA(hModule, ModuleName, MAX_PATH); char* LastSlash = strrchr(ModuleName, '\\'); if (LastSlash) memmove(ModuleName, LastSlash + 1, strlen(LastSlash)); } DWORD64 Displacement64 = 0; const char* SymbolName = ""; if (SymFromAddr(hProcess, Address, &Displacement64, pSymbol)) { SymbolName = pSymbol->Name; } DWORD Displacement32 = 0; char LocationBuffer[512]; if (SymGetLineFromAddr64(hProcess, Address, &Displacement32, &LineInfo)) { snprintf(LocationBuffer, sizeof(LocationBuffer), " [%02d] 0x%llX %s!%s + 0x%llX (%s:%lu)", i, Address, ModuleName, SymbolName, Displacement64, LineInfo.FileName, LineInfo.LineNumber); } else { snprintf(LocationBuffer, sizeof(LocationBuffer), " [%02d] 0x%llX %s!%s + 0x%llX", i, Address, ModuleName, SymbolName, Displacement64); } Log::Write(ELogLevel::Fatal, "%s", LocationBuffer); } Log::Write(ELogLevel::Fatal, "================================"); } // ======================================================================== // Exception Info Logging // ======================================================================== void FCrashHandler::LogExceptionInfo(EXCEPTION_POINTERS* pExceptionInfo) { if (!pExceptionInfo || !pExceptionInfo->ExceptionRecord) return; EXCEPTION_RECORD* pRecord = pExceptionInfo->ExceptionRecord; EExceptionType Type = GetExceptionType(pRecord->ExceptionCode); Log::Write(ELogLevel::Fatal, ""); Log::Write(ELogLevel::Fatal, "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); Log::Write(ELogLevel::Fatal, "!! UNHANDLED EXCEPTION !!"); Log::Write(ELogLevel::Fatal, "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); Log::Write(ELogLevel::Fatal, ""); Log::Write(ELogLevel::Fatal, "Exception Type: %s", ExceptionTypeToString(Type)); Log::Write(ELogLevel::Fatal, "Exception Code: 0x%08X", pRecord->ExceptionCode); Log::Write(ELogLevel::Fatal, "Exception Address: 0x%llX", reinterpret_cast(pRecord->ExceptionAddress)); if (pRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION && pRecord->NumberParameters >= 2) { const char* AccessType = (pRecord->ExceptionInformation[0] == 0) ? "reading" : (pRecord->ExceptionInformation[0] == 1) ? "writing" : "executing"; Log::Write(ELogLevel::Fatal, "Access Type: %s address 0x%llX", AccessType, pRecord->ExceptionInformation[1]); } if (pExceptionInfo->ContextRecord) { CONTEXT* pCtx = pExceptionInfo->ContextRecord; Log::Write(ELogLevel::Fatal, ""); Log::Write(ELogLevel::Fatal, "========== REGISTERS =========="); #ifdef _M_X64 Log::Write(ELogLevel::Fatal, "RAX: 0x%016llX RBX: 0x%016llX", pCtx->Rax, pCtx->Rbx); Log::Write(ELogLevel::Fatal, "RCX: 0x%016llX RDX: 0x%016llX", pCtx->Rcx, pCtx->Rdx); Log::Write(ELogLevel::Fatal, "RSI: 0x%016llX RDI: 0x%016llX", pCtx->Rsi, pCtx->Rdi); Log::Write(ELogLevel::Fatal, "RBP: 0x%016llX RSP: 0x%016llX", pCtx->Rbp, pCtx->Rsp); Log::Write(ELogLevel::Fatal, "R8: 0x%016llX R9: 0x%016llX", pCtx->R8, pCtx->R9); Log::Write(ELogLevel::Fatal, "R10: 0x%016llX R11: 0x%016llX", pCtx->R10, pCtx->R11); Log::Write(ELogLevel::Fatal, "R12: 0x%016llX R13: 0x%016llX", pCtx->R12, pCtx->R13); Log::Write(ELogLevel::Fatal, "R14: 0x%016llX R15: 0x%016llX", pCtx->R14, pCtx->R15); Log::Write(ELogLevel::Fatal, "RIP: 0x%016llX", pCtx->Rip); #else Log::Write(ELogLevel::Fatal, "EAX: 0x%08X EBX: 0x%08X", pCtx->Eax, pCtx->Ebx); Log::Write(ELogLevel::Fatal, "ECX: 0x%08X EDX: 0x%08X", pCtx->Ecx, pCtx->Edx); Log::Write(ELogLevel::Fatal, "ESI: 0x%08X EDI: 0x%08X", pCtx->Esi, pCtx->Edi); Log::Write(ELogLevel::Fatal, "EBP: 0x%08X ESP: 0x%08X", pCtx->Ebp, pCtx->Esp); Log::Write(ELogLevel::Fatal, "EIP: 0x%08X", pCtx->Eip); #endif Log::Write(ELogLevel::Fatal, "================================"); } Log::Write(ELogLevel::Fatal, ""); CaptureAndLogCallstack(pExceptionInfo->ContextRecord); } // ======================================================================== // Minidump Writing // ======================================================================== void FCrashHandler::WriteMinidumpFile(EXCEPTION_POINTERS* pExceptionInfo) { if (!s_bWriteMinidump) return; char DumpPath[MAX_PATH]; time_t Now = time(nullptr); struct tm TimeInfo; localtime_s(&TimeInfo, &Now); char Timestamp[64]; strftime(Timestamp, sizeof(Timestamp), "%Y%m%d_%H%M%S", &TimeInfo); snprintf(DumpPath, sizeof(DumpPath), "USS_Crash_%s.dmp", Timestamp); HANDLE hFile = CreateFileA(DumpPath, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); if (hFile == INVALID_HANDLE_VALUE) { Log::Write(ELogLevel::Fatal, "Failed to create minidump file: %s", DumpPath); return; } MINIDUMP_EXCEPTION_INFORMATION ExInfo = {}; ExInfo.ThreadId = GetCurrentThreadId(); ExInfo.ExceptionPointers = pExceptionInfo; ExInfo.ClientPointers = FALSE; BOOL bSuccess = MiniDumpWriteDump( GetCurrentProcess(), GetCurrentProcessId(), hFile, static_cast(MiniDumpWithDataSegs | MiniDumpWithHandleData | MiniDumpWithThreadInfo), pExceptionInfo ? &ExInfo : nullptr, nullptr, nullptr); CloseHandle(hFile); if (bSuccess) { Log::Write(ELogLevel::Fatal, "Minidump written to: %s", DumpPath); } else { Log::Write(ELogLevel::Fatal, "Failed to write minidump (error: %lu)", GetLastError()); } } // ======================================================================== // Exception Handlers // ======================================================================== LONG WINAPI FCrashHandler::UnhandledExceptionHandler(EXCEPTION_POINTERS* pExceptionInfo) { LogExceptionInfo(pExceptionInfo); WriteMinidumpFile(pExceptionInfo); if (s_bShowMessageBox) { char Message[512]; EExceptionType Type = GetExceptionType(pExceptionInfo->ExceptionRecord->ExceptionCode); snprintf(Message, sizeof(Message), "UniversalSlashingSimulator has crashed!\n\n" "Exception: %s (0x%08X)\n\n" "A crash dump has been written.\n" "Check the log file for detailed callstack information.", ExceptionTypeToString(Type), pExceptionInfo->ExceptionRecord->ExceptionCode); MessageBoxA(nullptr, Message, "USS Crash", MB_OK | MB_ICONERROR); } if (s_pPreviousFilter) return s_pPreviousFilter(pExceptionInfo); return EXCEPTION_EXECUTE_HANDLER; } void FCrashHandler::TerminateHandler() { Log::Write(ELogLevel::Fatal, ""); Log::Write(ELogLevel::Fatal, "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); Log::Write(ELogLevel::Fatal, "!! std::terminate() CALLED !!"); Log::Write(ELogLevel::Fatal, "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); Log::Write(ELogLevel::Fatal, ""); std::exception_ptr pException = std::current_exception(); if (pException) { try { std::rethrow_exception(pException); } catch (const std::exception& e) { Log::Write(ELogLevel::Fatal, "C++ Exception: %s", e.what()); } catch (...) { Log::Write(ELogLevel::Fatal, "Unknown C++ exception"); } } CaptureAndLogCallstack(nullptr); if (s_bShowMessageBox) { MessageBoxA(nullptr, "UniversalSlashingSimulator has terminated unexpectedly!\n\n" "An unhandled C++ exception occurred.\n\n" "Check the log file for detailed callstack information.", "USS Crash", MB_OK | MB_ICONERROR); } if (s_pPreviousTerminate) s_pPreviousTerminate(); else std::abort(); } void FCrashHandler::PureCallHandler() { Log::Write(ELogLevel::Fatal, ""); Log::Write(ELogLevel::Fatal, "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); Log::Write(ELogLevel::Fatal, "!! PURE VIRTUAL FUNCTION CALL !!"); Log::Write(ELogLevel::Fatal, "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); Log::Write(ELogLevel::Fatal, ""); CaptureAndLogCallstack(nullptr); if (s_bShowMessageBox) { MessageBoxA(nullptr, "UniversalSlashingSimulator has crashed!\n\n" "A pure virtual function was called.\n\n" "Check the log file for detailed callstack information.", "USS Crash", MB_OK | MB_ICONERROR); } std::abort(); } void FCrashHandler::InvalidParameterHandler( const wchar_t* Expression, const wchar_t* Function, const wchar_t* File, unsigned int Line, uintptr_t Reserved) { (void)Reserved; Log::Write(ELogLevel::Fatal, ""); Log::Write(ELogLevel::Fatal, "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); Log::Write(ELogLevel::Fatal, "!! INVALID CRT PARAMETER !!"); Log::Write(ELogLevel::Fatal, "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); Log::Write(ELogLevel::Fatal, ""); if (Expression) Log::Write(ELogLevel::Fatal, "Expression: %ls", Expression); if (Function) Log::Write(ELogLevel::Fatal, "Function: %ls", Function); if (File) Log::Write(ELogLevel::Fatal, "File: %ls:%u", File, Line); CaptureAndLogCallstack(nullptr); if (s_bShowMessageBox) { MessageBoxA(nullptr, "UniversalSlashingSimulator has crashed!\n\n" "An invalid parameter was passed to a CRT function.\n\n" "Check the log file for detailed callstack information.", "USS Crash", MB_OK | MB_ICONERROR); } std::abort(); } // ======================================================================== // Manual Dump // ======================================================================== void FCrashHandler::DumpCallstack(const char* Reason) { Log::Write(ELogLevel::Warning, ""); Log::Write(ELogLevel::Warning, "========== MANUAL CALLSTACK DUMP =========="); if (Reason) Log::Write(ELogLevel::Warning, "Reason: %s", Reason); Log::Write(ELogLevel::Warning, ""); CaptureAndLogCallstack(nullptr); } }