From 9afab39bc8ca4f74d11be22bf442dd1a7d514685 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 11 Aug 2026 15:56:53 +0200 Subject: [PATCH] MemBlockInfo: fix double-lock deadlock in FindWriteTagByFlag (previous commit) FindWriteTagByFlag(flush=false) is only called from FormatMemWriteTagAtNoFlush(), which is itself only called from within FlushPendingMemInfo() - which already holds pendingReadMutex for its entire body. The previous commit added an unconditional lock of that same (non-recursive) mutex here, so any path that reaches a memory write's tag formatting while a flush is in progress double-locks it and hangs/crashes. Repro: PPSSPPHeadless --graphics=software on pspautotests/tests/gpu/clipping/homogeneous.prx reliably hit this. Only take the lock when flush=true (i.e. when we're not already guaranteed to be called from inside FlushPendingMemInfo's locked section), using a defer_lock so the two call sites stay consistent. Verified fixed: 4/4 clean runs of the repro above, plus the full UnitTest suite (49/49) still passes. --- Core/Debugger/MemBlockInfo.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Core/Debugger/MemBlockInfo.cpp b/Core/Debugger/MemBlockInfo.cpp index 6f2f36f745..12d2857f3b 100644 --- a/Core/Debugger/MemBlockInfo.cpp +++ b/Core/Debugger/MemBlockInfo.cpp @@ -639,17 +639,23 @@ std::vector FindMemInfoByFlag(MemBlockFlags flags, uint32_t start, static const char *FindWriteTagByFlag(MemBlockFlags flags, uint32_t start, uint32_t size, size_t *tagLen, bool flush = true) { start = NormalizeAddress(start); + // See the comment in FindMemInfo() above. Note: the returned tag pointer is + // only valid until the next Mark() call per FastFindWriteTag()'s own contract, + // so callers must treat it as transient exactly as they already do. + // + // flush=false means we're being called from FormatMemWriteTagAtNoFlush(), which + // is only ever called from within FlushPendingMemInfo() - which already holds + // pendingReadMutex for its whole body. Locking it again here would deadlock (or + // be undefined behavior, since it's a plain non-recursive std::mutex), so only + // take the lock ourselves when we might not already be holding it. + std::unique_lock guard(pendingReadMutex, std::defer_lock); if (flush) { if (pendingNotifyMinAddr1 < start + size && pendingNotifyMaxAddr1 >= start) FlushPendingMemInfo(); if (pendingNotifyMinAddr2 < start + size && pendingNotifyMaxAddr2 >= start) FlushPendingMemInfo(); + guard.lock(); } - - // See the comment in FindMemInfo() above. Note: the returned tag pointer is - // only valid until the next Mark() call per FastFindWriteTag()'s own contract, - // so callers must treat it as transient exactly as they already do. - std::lock_guard guard(pendingReadMutex); if (flags & MemBlockFlags::ALLOC) { const char *tag = allocMap.FastFindWriteTag(MemBlockFlags::ALLOC, start, size, tagLen); if (tag)