Legacy Win32 debugger: fix the painting problem with a frame-scoped mutex

Debugger windows (register list, disassembly view, memory view, breakpoint/
thread/module/stack lists, watch list) read CPU-thread-owned state directly
from the GUI thread's WM_PAINT/list-fill handlers, racing against the CPU
thread. Routing every read through Core_RunOnCPUThread would be too slow for
something invoked continuously on paint/list-refresh.

Add g_frameMutex (Core.h/Core.cpp), held by NativeFrame() only across the
span where it actually touches that state (running the CPU, processing
breakpoints, running the ImGui debugger) - not across input handling or the
present/frame-pacing waits. Debugger windows now hold the same mutex while
reading, giving synchronized reads without the round-trip cost of queuing
to the CPU thread.

CtrlRegisterList::onPaint() goes back to always reading live values (now
safe under the lock) and grays them out by color alone while the core is
running, rather than the earlier snapshot-caching approach.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hqm11k99viLfbJm2MkH4BH
This commit is contained in:
Henrik RydgårdandClaude Sonnet 5 committed 2026-08-08 17:22:12 +02:00
1 parent 03dcfd3931
commit f72709feb0
10 files changed
+129 -38

No files matched your search

+10 -1
View File
@@ -60,6 +60,10 @@ void CtrlDisAsmView::deinit()
void CtrlDisAsmView::scanVisibleFunctions()
{
// Reads live memory/symbol state to detect function boundaries - hold g_frameMutex for the
// duration, which NativeFrame() also holds while it's actually touching that state. See
// g_frameMutex in Core.h.
std::lock_guard<std::mutex> frameGuard(g_frameMutex);
g_disassemblyManager.analyze(windowStart, g_disassemblyManager.getNthNextAddress(windowStart,visibleRows)-windowStart);
}
@@ -453,9 +457,14 @@ void CtrlDisAsmView::drawArguments(HDC hdc, const DisassemblyLineInfo &line, int
void CtrlDisAsmView::onPaint(WPARAM wParam, LPARAM lParam)
{
auto memLock = Memory::Lock();
Memory::MemoryInitedLock memLock = Memory::Lock();
if (!debugger->isAlive() || Achievements::HardcoreModeActive()) return;
// Reading live disassembly/symbol/breakpoint state here on the GUI thread would otherwise race
// with the CPU thread - hold g_frameMutex for the duration of the read, which NativeFrame()
// also holds while it's actually touching that state. See g_frameMutex in Core.h.
std::lock_guard<std::mutex> frameGuard(g_frameMutex);
PAINTSTRUCT ps;
HDC actualHdc = BeginPaint(wnd, &ps);
HDC hdc = CreateCompatibleDC(actualHdc);