From b13945d9e56519a0a6dcf77c599bdf85f8874b84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 15 Oct 2026 17:03:12 +0200 Subject: [PATCH] Show the crash screen whenever the core stops with a runtime error EmuScreen only draws the crash info when an exception was recorded, and showed just the Resume/Reset/Back buttons otherwise. Several ways of stopping the core with CORE_RUNTIME_ERROR recorded nothing: - A bad memory access in JIT code that MemFault can't skip, either because it can't decode the host instruction or because ignoring is only configured through the exception action, went to the crash handler, while Core_MemoryException returned early under the default Ignore action. A halting access is now always recorded. - HLEReturnFromMipsCall on a corrupt stack, and a failed LoadExec, set the state directly. They now go through Core_HLEError, which records a new HLE exception type with a message that the crash screen shows. Co-Authored-By: Claude Opus 5.5 (1M context) --- Core/Core.cpp | 21 ++++++++++++++++++--- Core/Core.h | 7 ++++++- Core/HLE/HLE.cpp | 6 ++---- Core/HLE/sceKernel.cpp | 2 +- Core/HLE/sceKernelModule.cpp | 2 +- Core/MemFault.cpp | 2 +- UI/DebugOverlay.cpp | 7 +++++++ 7 files changed, 36 insertions(+), 11 deletions(-) diff --git a/Core/Core.cpp b/Core/Core.cpp index 9f33616469..d37365d54b 100644 --- a/Core/Core.cpp +++ b/Core/Core.cpp @@ -780,6 +780,7 @@ const char *ExceptionTypeAsString(MIPSExceptionType type) { case MIPSExceptionType::BREAK: return "Break"; case MIPSExceptionType::BAD_EXEC_ADDR: return "Bad Execution Address"; case MIPSExceptionType::FPU: return "FPU Exception"; + case MIPSExceptionType::HLE: return "HLE Error"; default: return "N/A"; } } @@ -836,7 +837,7 @@ static std::string ModuleAddressSuffix(u32 address) { } } -void Core_MemoryException(u32 address, u32 accessSize, u32 pc, MemoryExceptionType type, std::string_view additionalInfo) { +void Core_MemoryException(u32 address, u32 accessSize, u32 pc, MemoryExceptionType type, std::string_view additionalInfo, bool halting) { // In jit, we only flush PC when bIgnoreBadMemAccess is off. char pcDetails[128]; pcDetails[0] = 0; @@ -877,14 +878,14 @@ void Core_MemoryException(u32 address, u32 accessSize, u32 pc, MemoryExceptionTy const char *desc = MemoryExceptionTypeAsString(type); char msg[512]; snprintf(msg, sizeof(msg), "%s: SIGSEGV at %08x%s (size: %d bytes) %s\nHost:%.*s", desc, address, addressSuffix.c_str(), accessSize, pcDetails, STR_VIEW(additionalInfo)); - if (action == ExceptionAction::Ignore) { + if (action == ExceptionAction::Ignore && !halting) { Core_SendDebugOutput(LogLevel::LWARNING, msg); return; } const std::string stackTrace = FormatStackTrace(WalkCurrentStack(-1)); // Do the most detailed logging we can. Core_SendDebugOutput(LogLevel::LERROR, StringFromFormat("%sMIPS call stack:\n%s", msg, stackTrace.c_str())); - if (action == ExceptionAction::Break) { + if (action == ExceptionAction::Break || halting) { MIPSExceptionInfo &e = g_exceptionInfo; e = {}; e.type = MIPSExceptionType::MEMORY; @@ -1061,6 +1062,20 @@ void Core_BreakException(u32 pc) { } } +void Core_HLEError(std::string_view message) { + const std::string stackTrace = FormatStackTrace(WalkCurrentStack(-1)); + Core_SendDebugOutput(LogLevel::LERROR, StringFromFormat("HLE error: %.*s\n%s", STR_VIEW(message), stackTrace.c_str())); + + MIPSExceptionInfo &e = g_exceptionInfo; + e = {}; + e.type = MIPSExceptionType::HLE; + e.info = message; + e.stackTrace = stackTrace; + e.pc = currentMIPS->pc; + e.ra = currentMIPS->r[MIPS_REG_RA]; + Core_UpdateState(CORE_RUNTIME_ERROR); +} + // We can't actually deliver the trap to the game's exception handler - nothing in PPSSPP emulates // the FPU exception vector - so all we can do is tell the user about it. Execution continues after // this returns, just without the destination register having been written (as on hardware). diff --git a/Core/Core.h b/Core/Core.h index 1fde9982a0..0645b5f4b9 100644 --- a/Core/Core.h +++ b/Core/Core.h @@ -281,9 +281,13 @@ enum class FPUExceptionType { DIVIDE_BY_ZERO, }; -void Core_MemoryException(u32 address, u32 accessSize, u32 pc, MemoryExceptionType type, std::string_view additionalInfo = ""); +// halting: the caller stops emulation whatever the configured action, so the exception is recorded +// for the crash screen even when the action is to ignore it. +void Core_MemoryException(u32 address, u32 accessSize, u32 pc, MemoryExceptionType type, std::string_view additionalInfo = "", bool halting = false); void Core_ExecException(u32 address, u32 pc, ExecExceptionType type); void Core_BreakException(u32 pc); +// For HLE that finds it can't go on (a corrupt callback stack, a failed LoadExec) and stops the core. +void Core_HLEError(std::string_view message); // Only called for exceptions the game has unmasked in fcr31 - a masked one just sets the flag bit // and produces the IEEE default result, without coming through here. void Core_FPUException(u32 pc, FPUExceptionType type); @@ -325,6 +329,7 @@ enum class MIPSExceptionType { BREAK, BAD_EXEC_ADDR, FPU, + HLE, }; struct MIPSExceptionInfo { diff --git a/Core/HLE/HLE.cpp b/Core/HLE/HLE.cpp index 2a41a58336..a15ae8f701 100644 --- a/Core/HLE/HLE.cpp +++ b/Core/HLE/HLE.cpp @@ -879,8 +879,7 @@ void HLEReturnFromMipsCall() { stackData.ptr = sp; if ((stackData->nextOff & 0x0000000F) != 0 || !Memory::IsValidAddress(sp + stackData->nextOff)) { - ERROR_LOG(Log::HLE, "Corrupt stack on HLE mips call return: %08x", stackData->nextOff); - Core_UpdateState(CORE_RUNTIME_ERROR); + Core_HLEError(StringFromFormat("Corrupt stack on HLE mips call return: %08x", stackData->nextOff)); hleNoLogVoid(); return; } @@ -896,8 +895,7 @@ void HLEReturnFromMipsCall() { } if (finalMarker->nextOff != 0xFFFFFFFF) { - ERROR_LOG(Log::HLE, "Corrupt stack on HLE mips call return action: %08x", finalMarker->nextOff); - Core_UpdateState(CORE_RUNTIME_ERROR); + Core_HLEError(StringFromFormat("Corrupt stack on HLE mips call return action: %08x", finalMarker->nextOff)); hleNoLogVoid(); return; } diff --git a/Core/HLE/sceKernel.cpp b/Core/HLE/sceKernel.cpp index 27db7a4645..43f32907c5 100644 --- a/Core/HLE/sceKernel.cpp +++ b/Core/HLE/sceKernel.cpp @@ -1046,7 +1046,7 @@ static int sceKernelLoadExecBufferVSHUsbWlan(int bufferSize, u32 bufferAddr, u32 const u8 *data = Memory::GetPointerUnchecked(bufferAddr); std::string error_string; if (!__KernelLoadExecFromBuffer(currentMIPS, data, (size_t)bufferSize, paramPtr, &error_string)) { - Core_UpdateState(CORE_RUNTIME_ERROR); + Core_HLEError("sceKernelLoadExecBufferVSHUsbWlan failed: " + error_string); return hleLogError(Log::sceKernel, -1, "failed: %s", error_string.c_str()); } if (gpu) { diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index fc39fa7135..dce0c2120b 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -2560,7 +2560,7 @@ int sceKernelLoadExec(const char *filename, u32 paramPtr) { DEBUG_LOG(Log::sceModule, "sceKernelLoadExec(name=%s,...): loading %s", filename, exec_filename.c_str()); std::string error_string; if (!__KernelLoadExec(currentMIPS, exec_filename.c_str(), paramPtr, &error_string)) { - Core_UpdateState(CORE_RUNTIME_ERROR); + Core_HLEError("sceKernelLoadExec failed: " + error_string); return hleLogError(Log::sceModule, -1, "failed: %s", error_string.c_str());; } if (gpu) { diff --git a/Core/MemFault.cpp b/Core/MemFault.cpp index 841e14916d..f86d5522d5 100644 --- a/Core/MemFault.cpp +++ b/Core/MemFault.cpp @@ -271,7 +271,7 @@ bool HandleFault(uintptr_t hostAddress, void *ctx) { uint32_t approximatePC = currentMIPS->pc; // TODO: Determine access size from the disassembled native instruction. We have some partial info already, // just need to clean it up. - Core_MemoryException(guestAddress, info.OperandSizeInBytes(), approximatePC, type, infoString); + Core_MemoryException(guestAddress, info.OperandSizeInBytes(), approximatePC, type, infoString, true); // There's a small chance we can resume from this type of crash. g_lastCrashAddress = codePtr; diff --git a/UI/DebugOverlay.cpp b/UI/DebugOverlay.cpp index 1b9ff7711f..0bf44c726c 100644 --- a/UI/DebugOverlay.cpp +++ b/UI/DebugOverlay.cpp @@ -419,6 +419,13 @@ PC: %08x )", info.pc); ctx->Draw()->DrawTextShadow(ubuntu24, statbuf, x, y, 0xFFFFFFFF); y += 180; + } else if (info.type == MIPSExceptionType::HLE) { + snprintf(statbuf, sizeof(statbuf), R"( +%s +PC: %08x +RA: %08x)", info.info.c_str(), info.pc, info.ra); + ctx->Draw()->DrawTextShadow(ubuntu24, statbuf, x, y, 0xFFFFFFFF); + y += 180; } else { snprintf(statbuf, sizeof(statbuf), R"( Invalid / Unknown (%d)