From 34454f9d03755621fa44c4b2402bcaa80e8de9fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 27 Jul 2026 23:08:42 +0200 Subject: [PATCH] Claude's crazy exception handler hack --- Common/ExceptionHandlerSetup.cpp | 62 +++++++++++++++++++++++++++++--- Common/ExceptionHandlerSetup.h | 6 +++- Core/CmdLine.cpp | 6 ++++ Core/CmdLine.h | 3 ++ Core/Config.h | 5 +++ Core/System.cpp | 2 +- 6 files changed, 78 insertions(+), 6 deletions(-) diff --git a/Common/ExceptionHandlerSetup.cpp b/Common/ExceptionHandlerSetup.cpp index 8b1a1a5e0e..6df5cc4a56 100644 --- a/Common/ExceptionHandlerSetup.cpp +++ b/Common/ExceptionHandlerSetup.cpp @@ -15,6 +15,7 @@ #include "Common/CommonFuncs.h" #include "Common/CommonTypes.h" #include "Common/Log.h" +#include "Common/StringUtils.h" #include "Common/Thread/ThreadUtil.h" #include "Common/MachineContext.h" #include "Common/ExceptionHandlerSetup.h" @@ -27,7 +28,48 @@ static void *altStack = nullptr; // We cannot handle exceptions in UWP builds. Bleh. #if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP) +#include +#pragma comment(lib, "dbghelp.lib") + static PVOID g_vectoredExceptionHandle; +static bool g_symInitialized = false; +static bool g_logCrashStackTrace = false; + +// Logs a best-effort stack trace when we're about to let a genuinely unhandled access +// violation crash the process - e.g. a bad host pointer (not a guest PSP memory access) +// passed to a CRT function like strlen(). Only meant for diagnostics, so failures here are +// non-fatal; we just lose the extra info. +static void LogCrashStackTrace() { + void *stack[32]{}; + USHORT captured = CaptureStackBackTrace(0, (ULONG)ARRAY_SIZE(stack), stack, nullptr); + + ERROR_LOG(Log::System, "Unhandled access violation - stack trace (%d frames):", (int)captured); + + HANDLE process = GetCurrentProcess(); + char symbolBuffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME]{}; + SYMBOL_INFO *symbol = (SYMBOL_INFO *)symbolBuffer; + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); + symbol->MaxNameLen = MAX_SYM_NAME; + + for (USHORT i = 0; i < captured; i++) { + DWORD64 address = (DWORD64)(uintptr_t)stack[i]; + std::string line = StringFromFormat(" #%d %016llx", (int)i, (unsigned long long)address); + + DWORD64 displacement = 0; + if (SymFromAddr(process, address, &displacement, symbol)) { + line += StringFromFormat(" %s+0x%llx", symbol->Name, (unsigned long long)displacement); + } + + DWORD lineDisplacement = 0; + IMAGEHLP_LINE64 lineInfo{}; + lineInfo.SizeOfStruct = sizeof(lineInfo); + if (SymGetLineFromAddr64(process, address, &lineDisplacement, &lineInfo)) { + line += StringFromFormat(" (%s:%d)", lineInfo.FileName, (int)lineInfo.LineNumber); + } + + ERROR_LOG(Log::System, "%s", line.c_str()); + } +} static LONG NTAPI GlobalExceptionHandler(PEXCEPTION_POINTERS pPtrs) { switch (pPtrs->ExceptionRecord->ExceptionCode) { @@ -45,6 +87,11 @@ static LONG NTAPI GlobalExceptionHandler(PEXCEPTION_POINTERS pPtrs) { if (g_badAccessHandler(badAddress, ctx)) { return (DWORD)EXCEPTION_CONTINUE_EXECUTION; } else { + if (g_logCrashStackTrace) { + ERROR_LOG(Log::System, "Unhandled access violation (%s) at address %016llx, pc=%016llx", + accessType == 1 ? "write" : "read", (unsigned long long)badAddress, (unsigned long long)(uintptr_t)pPtrs->ExceptionRecord->ExceptionAddress); + LogCrashStackTrace(); + } // Let's not prevent debugging. return (DWORD)EXCEPTION_CONTINUE_SEARCH; } @@ -75,7 +122,8 @@ static LONG NTAPI GlobalExceptionHandler(PEXCEPTION_POINTERS pPtrs) { } } -void InstallExceptionHandler(BadAccessHandler badAccessHandler) { +void InstallExceptionHandler(BadAccessHandler badAccessHandler, bool logStackTraceOnCrash) { + g_logCrashStackTrace = logStackTraceOnCrash; if (g_vectoredExceptionHandle) { g_badAccessHandler = badAccessHandler; return; @@ -83,6 +131,12 @@ void InstallExceptionHandler(BadAccessHandler badAccessHandler) { INFO_LOG(Log::System, "Installing exception handler"); g_badAccessHandler = badAccessHandler; + + if (logStackTraceOnCrash && !g_symInitialized) { + SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_DEFERRED_LOADS | SYMOPT_UNDNAME); + g_symInitialized = SymInitialize(GetCurrentProcess(), nullptr, TRUE) != FALSE; + } + #ifdef USE_ASAN g_vectoredExceptionHandle = AddVectoredExceptionHandler(FALSE, GlobalExceptionHandler); #else @@ -182,7 +236,7 @@ static void ExceptionThread(mach_port_t port) { } } -void InstallExceptionHandler(BadAccessHandler badAccessHandler) { +void InstallExceptionHandler(BadAccessHandler badAccessHandler, bool logStackTraceOnCrash) { if (g_badAccessHandler) { // The rest of the setup we don't need to do again. g_badAccessHandler = badAccessHandler; @@ -282,7 +336,7 @@ static void sigsegv_handler(int sig, siginfo_t* info, void* raw_context) { } } -void InstallExceptionHandler(BadAccessHandler badAccessHandler) { +void InstallExceptionHandler(BadAccessHandler badAccessHandler, bool logStackTraceOnCrash) { if (!badAccessHandler) { return; } @@ -351,7 +405,7 @@ void UninstallExceptionHandler() { #else // !MACHINE_CONTEXT_SUPPORTED -void InstallExceptionHandler(BadAccessHandler badAccessHandler) { +void InstallExceptionHandler(BadAccessHandler badAccessHandler, bool logStackTraceOnCrash) { ERROR_LOG(Log::System, "Exception handler not implemented on this platform, can't install"); } void UninstallExceptionHandler() { } diff --git a/Common/ExceptionHandlerSetup.h b/Common/ExceptionHandlerSetup.h index e7917cb3f2..da47a55687 100644 --- a/Common/ExceptionHandlerSetup.h +++ b/Common/ExceptionHandlerSetup.h @@ -13,7 +13,11 @@ // Ugh, might need to abstract this better. typedef bool (*BadAccessHandler)(uintptr_t address, void *context); -void InstallExceptionHandler(BadAccessHandler accessHandler); +// logStackTraceOnCrash: if true, log a best-effort stack trace (via dbghelp on Windows) +// when a genuinely unhandled access violation is about to crash the process. Off by +// default since it links dbghelp and walks the stack from inside the exception handler - +// opt in only when you're chasing a native crash. +void InstallExceptionHandler(BadAccessHandler accessHandler, bool logStackTraceOnCrash = false); // Implementation note: This must be a no-op if InstallExceptionHandler hasn't been called. void UninstallExceptionHandler(); diff --git a/Core/CmdLine.cpp b/Core/CmdLine.cpp index 6e7508aa02..2ae4d7c73e 100644 --- a/Core/CmdLine.cpp +++ b/Core/CmdLine.cpp @@ -186,6 +186,7 @@ static const CommandLineParam g_autoParams[] = { {POFF(memReadAction), CmdParamType::Enum, "memread", '\0', "Set the action for memory read exceptions", CmdLineMode::Both, g_ExceptionActionValues, ARRAY_SIZE(g_ExceptionActionValues)}, {POFF(memWriteAction), CmdParamType::Enum, "memwrite", '\0', "Set the action for memory write exceptions", CmdLineMode::Both, g_ExceptionActionValues, ARRAY_SIZE(g_ExceptionActionValues)}, {POFF(breakAction), CmdParamType::Enum, "break", '\0', "Set the action for break exceptions", CmdLineMode::Both, g_ExceptionActionValues, ARRAY_SIZE(g_ExceptionActionValues)}, + {POFF(logNativeCrashes), CmdParamType::Bool, "log-native-crashes", '\0', "Log a native stack trace (Windows only) on an otherwise-unhandled crash", CmdLineMode::Both}, {POFF(verbose), CmdParamType::Bool, "verbose", '\0', "Enable verbose output", CmdLineMode::Both}, // TODO: At some point we should maybe simply expose all config settings to be set directly from the command line automatically? @@ -498,6 +499,11 @@ void CommandLineOptions::ApplyToConfig() const { g_Config.DoNotSaveSetting(&g_Config.iExceptionActionBreak); } + if (logNativeCrashes.has_value()) { + g_Config.bLogNativeCrashStackTraces = logNativeCrashes.value(); + g_Config.DoNotSaveSetting(&g_Config.bLogNativeCrashStackTraces); + } + // --vsh is applied by the caller (by setting their boot file name variable). // Note: dpi is not applied here - it's platform-specific. diff --git a/Core/CmdLine.h b/Core/CmdLine.h index 9387b38632..82a724288e 100644 --- a/Core/CmdLine.h +++ b/Core/CmdLine.h @@ -59,6 +59,9 @@ struct CommandLineOptions { std::optional memWriteAction; std::optional breakAction; + // Log a native stack trace (Windows only) on an otherwise-unhandled access violation. + std::optional logNativeCrashes; + // SDL only: Option to force a specific OpenGL version (42="4.2", // etc.; -1 means "try them all"). // Implemented as a workaround for https://github.com/hrydgard/ppsspp/issues/20687 diff --git a/Core/Config.h b/Core/Config.h index 3c58147744..58b2dd0854 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -218,6 +218,11 @@ public: int iExceptionActionMemWrite; int iExceptionActionBreak; + // If true, log a best-effort native stack trace (Windows only) when a genuinely + // unhandled access violation is about to crash the process. Diagnostic only, off by + // default - see --log-native-crashes in Core/CmdLine.cpp. + bool bLogNativeCrashStackTraces; + bool bFastMemory; int iCpuCore; bool bCheckForNewVersion; diff --git a/Core/System.cpp b/Core/System.cpp index 148afdeb3f..0788926fb7 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -517,7 +517,7 @@ static bool CPU_Init(FileLoader *fileLoader, IdentifiedFileType type, std::strin g_CoreParameter.gpuCore = GPUCORE_SOFTWARE; } - InstallExceptionHandler(&Memory::HandleFault); + InstallExceptionHandler(&Memory::HandleFault, g_Config.bLogNativeCrashStackTraces); return true; }