diff --git a/Core/Debugger/MemBlockInfo.cpp b/Core/Debugger/MemBlockInfo.cpp index 9a9379f473..6f2f36f745 100644 --- a/Core/Debugger/MemBlockInfo.cpp +++ b/Core/Debugger/MemBlockInfo.cpp @@ -601,6 +601,11 @@ std::vector FindMemInfo(uint32_t start, uint32_t size) { if (pendingNotifyMinAddr2 < start + size && pendingNotifyMaxAddr2 >= start) FlushPendingMemInfo(); + // pendingReadMutex doesn't just guard the pending queue - it's also what keeps + // the background flush thread's Mark() calls (which mutate the slab maps' + // linked lists via Split()/Merge()/delete) from running concurrently with the + // traversal below, which used to be completely unsynchronized against it. + std::lock_guard guard(pendingReadMutex); std::vector results; allocMap.Find(MemBlockFlags::ALLOC, start, size, results); suballocMap.Find(MemBlockFlags::SUB_ALLOC, start, size, results); @@ -617,6 +622,8 @@ std::vector FindMemInfoByFlag(MemBlockFlags flags, uint32_t start, if (pendingNotifyMinAddr2 < start + size && pendingNotifyMaxAddr2 >= start) FlushPendingMemInfo(); + // See the comment in FindMemInfo() above. + std::lock_guard guard(pendingReadMutex); std::vector results; if (flags & MemBlockFlags::ALLOC) allocMap.Find(MemBlockFlags::ALLOC, start, size, results); @@ -639,6 +646,10 @@ static const char *FindWriteTagByFlag(MemBlockFlags flags, uint32_t start, uint3 FlushPendingMemInfo(); } + // 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) @@ -745,6 +756,8 @@ void MemBlockInfoDoState(PointerWrap &p) { return; FlushPendingMemInfo(); + // See the comment in FindMemInfo() above. + std::lock_guard guard(pendingReadMutex); allocMap.DoState(p); suballocMap.DoState(p); writeMap.DoState(p);