From 4a10fa28088a4a698b5dcad3df5691e967c5115d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 17 Aug 2026 01:12:14 +0200 Subject: [PATCH] Hold the shutdown lock across all of CPU_Shutdown It was only held across Memory::Shutdown(), but everything else in there frees state the debugger UIs read from other threads - kernel objects (__KernelShutdown), the symbol map, replacements - so a Win32 debugger window painting while a game is reset could read freed memory. It's recursive, so the nested acquire in Memory::Shutdown() is unaffected. No lock-order risk: on the paths where CPU_Shutdown already runs under g_frameMutex it now takes these in the same frame-then-shutdown order the GUI side uses, and on the paths where it doesn't (EmuScreen::sendMessage, ProcessScreenSwitches - both above where NativeFrame takes the guard) it takes only this one. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9 --- Core/System.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Core/System.cpp b/Core/System.cpp index 054aba96fe..3e1e452ca5 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -568,6 +568,12 @@ static bool CPU_Init(FileLoader *fileLoader, IdentifiedFileType type, std::strin } void CPU_Shutdown(bool success) { + // Held across the whole teardown, not just Memory::Shutdown() further down. Everything below + // frees state the debugger UIs read from other threads - kernel objects, the symbol map, the + // memory map - and this is the lock they take to be sure none of it goes away mid-read. See + // Memory::Lock(); it's recursive, so the nested acquire in Memory::Shutdown() is fine. + Memory::MemoryInitedLock coreLock = Memory::Lock(); + UninstallExceptionHandler(); GPURecord::Replay_Unload();