From 0a8ec4d8f14365fb334ddaebd6113f8cd1131e15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 11 Oct 2026 09:26:20 +0200 Subject: [PATCH] Invalidate code that HLE and the debugger write over Helper thread stubs, fake syscall stubs and the module start trampoline go into memory that may have held code (freed kernel memory, a reused thread stack), and the debugger memory views can edit code. Invalidate what they write, which so far only happened by accident when the write hit the first instruction of a JIT block. Co-Authored-By: Claude Opus 5.5 (1M context) --- Core/HLE/HLEHelperThread.cpp | 4 ++++ Core/HLE/sceKernelModule.cpp | 2 ++ Core/HLE/sceKernelThread.cpp | 2 ++ UI/ImDebugger/ImMemView.cpp | 8 +++++++- Windows/Debugger/CtrlMemView.cpp | 7 ++++++- 5 files changed, 21 insertions(+), 2 deletions(-) diff --git a/Core/HLE/HLEHelperThread.cpp b/Core/HLE/HLEHelperThread.cpp index 939a910c46..71b8bbc701 100644 --- a/Core/HLE/HLEHelperThread.cpp +++ b/Core/HLE/HLEHelperThread.cpp @@ -24,6 +24,7 @@ #include "Core/HLE/KernelWaitHelpers.h" #include "Core/HLE/sceKernelThread.h" #include "Core/HLE/sceKernelMemory.h" +#include "Core/MIPS/MIPS.h" #include "Core/MIPS/MIPSCodeUtils.h" HLEHelperThread::HLEHelperThread(const char *threadName, const u32 instructions[], u32 instrCount, u32 prio, int stacksize) { @@ -36,6 +37,8 @@ HLEHelperThread::HLEHelperThread(const char *threadName, const u32 instructions[ // Just to simplify things, we add the return here. Memory::WriteUnchecked_U32(MIPS_MAKE_JR_RA(), entry_ + instrBytes + 0); Memory::WriteUnchecked_U32(MIPS_MAKE_NOP(), entry_ + instrBytes + 4); + // The memory may have held another helper's code, already compiled. + currentMIPS->InvalidateICacheRangeDeferred(entry_, totalBytes); Create(threadName, prio, stacksize); } @@ -46,6 +49,7 @@ HLEHelperThread::HLEHelperThread(const char *threadName, const char *module, con _dbg_assert_(Memory::IsValid4AlignedAddress(entry_)); // after AllocEntry. Memory::WriteUnchecked_U32(MIPS_MAKE_JR_RA(), entry_ + 0); Memory::WriteUnchecked_U32(MIPS_MAKE_SYSCALL(module, func), entry_ + 4); + currentMIPS->InvalidateICacheRangeDeferred(entry_, bytes); Create(threadName, prio, stacksize); } diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index 3b6883d972..403311b718 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -2808,6 +2808,8 @@ int __KernelStartModule(SceUID moduleId, u32 argsize, u32 argAddr, u32 returnVal Memory::WriteUnchecked_U32(MIPS_MAKE_LW(MIPS_REG_RA, MIPS_REG_SP, 8), tramp + 0x24); Memory::WriteUnchecked_U32(MIPS_MAKE_J(entryAddr), tramp + 0x28); Memory::WriteUnchecked_U32(MIPS_MAKE_ADDIU(MIPS_REG_SP, MIPS_REG_SP, 16), tramp + 0x2C); + // Thread stacks get reused, so an older trampoline may already be compiled here. + currentMIPS->InvalidateICacheRangeDeferred(tramp, 0x30); NotifyMemInfo(MemBlockFlags::WRITE, tramp, 0x30, "ModuleStartTrampoline"); startThread->context.pc = tramp; startThread->nt.entrypoint = tramp; diff --git a/Core/HLE/sceKernelThread.cpp b/Core/HLE/sceKernelThread.cpp index 22d94cb22d..5bd4a67f7f 100644 --- a/Core/HLE/sceKernelThread.cpp +++ b/Core/HLE/sceKernelThread.cpp @@ -788,6 +788,8 @@ static void __KernelWriteFakeSysCall(u32 nid, u32 *ptr, u32 &pos) { *ptr = pos; pos += 8; WriteHLESyscall("FakeSysCalls", nid, *ptr); + // The memory may have held code that's already compiled. + currentMIPS->InvalidateICacheRangeDeferred(*ptr, 8); } u32 HLEMipsCallReturnAddress() { diff --git a/UI/ImDebugger/ImMemView.cpp b/UI/ImDebugger/ImMemView.cpp index 85da5f27ab..d2c317ec9f 100644 --- a/UI/ImDebugger/ImMemView.cpp +++ b/UI/ImDebugger/ImMemView.cpp @@ -13,6 +13,7 @@ #include "Common/File/FileUtil.h" #include "Core/Config.h" #include "Core/MemMap.h" +#include "Core/MIPS/MIPS.h" #include "Core/Reporting.h" #include "Core/RetroAchievements.h" #include "Common/System/Display.h" @@ -354,6 +355,7 @@ void ImMemView::EditMemory(int newNibble) { out = (og & 0x0F) | (newNibble << 4); } Memory::WriteUnchecked_U8((u8)out, curAddress_); + currentMIPS->InvalidateICacheRangeDeferred(curAddress_ & ~3, 4); ScrollCursor(1, GotoMode::RESET); } @@ -365,8 +367,10 @@ void ImMemView::CopyToByteClipboard() { } void ImMemView::PasteFromByteClipboard() { - if (editableMemory_) + if (editableMemory_) { Memory::MemcpyUnchecked(curAddress_, byteClipboard_.data(), (u32)byteClipboard_.size()); + currentMIPS->InvalidateICacheRangeDeferred(curAddress_, (u32)byteClipboard_.size()); + } } void ImMemView::toggleEditableMemory(bool toggle) { @@ -388,6 +392,7 @@ void ImMemView::onChar(int c) { if (asciiSelected_) { Memory::WriteUnchecked_U8((u8)c, curAddress_); + currentMIPS->InvalidateICacheRangeDeferred(curAddress_ & ~3, 4); ScrollCursor(1, GotoMode::RESET); } else { c = tolower(c); @@ -403,6 +408,7 @@ void ImMemView::onChar(int c) { oldValue &= ~(0xF << shiftAmount); u8 newValue = oldValue | (inputValue << shiftAmount); Memory::WriteUnchecked_U8(newValue, curAddress_); + currentMIPS->InvalidateICacheRangeDeferred(curAddress_ & ~3, 4); ScrollCursor(1, GotoMode::RESET); } } diff --git a/Windows/Debugger/CtrlMemView.cpp b/Windows/Debugger/CtrlMemView.cpp index 4d0a7807ff..8ff58677d9 100644 --- a/Windows/Debugger/CtrlMemView.cpp +++ b/Windows/Debugger/CtrlMemView.cpp @@ -8,6 +8,7 @@ #include "Core/Core.h" #include "Core/System.h" #include "Core/MemMap.h" +#include "Core/MIPS/MIPS.h" #include "Core/Reporting.h" #include "Core/RetroAchievements.h" #include "Windows/W32Util/ContextMenu.h" @@ -439,7 +440,10 @@ void CtrlMemView::onChar(WPARAM wParam, LPARAM lParam) { // Route the actual memory write to the CPU thread instead of poking at it directly from this // GUI thread - see Core_RunOnCPUThread() in Core.h. No need to force the core to pause first. if (asciiSelected_) { - Core_RunOnCPUThread([&] { Memory::WriteUnchecked_U8((u8)wParam, curAddress_); }); + Core_RunOnCPUThread([&] { + Memory::WriteUnchecked_U8((u8)wParam, curAddress_); + currentMIPS->InvalidateICacheRangeDeferred(curAddress_ & ~3, 4); + }); ScrollCursor(1, GotoMode::RESET); } else { wParam = tolower(wParam); @@ -456,6 +460,7 @@ void CtrlMemView::onChar(WPARAM wParam, LPARAM lParam) { oldValue &= ~(0xF << shiftAmount); u8 newValue = oldValue | (inputValue << shiftAmount); Memory::WriteUnchecked_U8(newValue, curAddress_); + currentMIPS->InvalidateICacheRangeDeferred(curAddress_ & ~3, 4); }); ScrollCursor(1, GotoMode::RESET); }