Files
ppsspp/Core/Debugger/MemBlockInfo.cpp
T
Henrik Rydgård 527c50d32e MemBlockInfo: fix unsynchronized access to the slab maps from readers
The background flush thread calls FlushPendingMemInfo() at any time
while the emulator runs, holding pendingReadMutex for its whole body
while calling MemSlabMap::Mark() - which does new/delete and relinks
the intrusive Slab linked list via Split()/Merge().

FindMemInfo()/FindMemInfoByFlag()/FindWriteTagByFlag() only acquired
that lock indirectly and conditionally, inside FlushPendingMemInfo()
itself when the requested range happened to overlap pending data - the
actual .Find()/.FastFindWriteTag() traversal that followed ran
completely unsynchronized against the background thread's Mark() calls
on the same maps. This is a genuine use-after-free: a reader could
dereference a Slab* the flush thread just deleted, or race on the
shared lastFind_ pointer both sides read and write. Since a Slab's tag
is copied into the debugger's/WebSocket API's response, this could
also leak stale/freed heap bytes back to a caller. MemBlockInfoDoState
had the same gap around allocMap/suballocMap/writeMap/textureMap's
.DoState() calls.

Hold pendingReadMutex for the duration of these calls too, matching
the comment already on FlushPendingMemInfo ("This lock prevents us
from another thread reading while we're busy flushing") which wasn't
actually honored by the reader side.
2026-08-12 09:42:32 +02:00

779 lines
22 KiB
C++

// Copyright (c) 2021- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include <algorithm>
#include <atomic>
#include <cstring>
#include <mutex>
#include <condition_variable>
#include <thread>
#include "Common/Log.h"
#include "Common/Serialize/Serializer.h"
#include "Common/Serialize/SerializeFuncs.h"
#include "Common/Thread/ThreadUtil.h"
#include "Core/Config.h"
#include "Core/CoreTiming.h"
#include "Core/Debugger/Breakpoints.h"
#include "Core/Debugger/MemBlockInfo.h"
#include "Core/MIPS/MIPS.h"
#include "Common/StringUtils.h"
class MemSlabMap {
public:
MemSlabMap();
~MemSlabMap();
bool Mark(uint32_t addr, uint32_t size, uint64_t ticks, uint32_t pc, bool allocated, const char *tag, size_t tagLen);
bool Find(MemBlockFlags flags, uint32_t addr, uint32_t size, std::vector<MemBlockInfo> &results);
// Note that the returned pointer gets invalidated as soon as Mark is called.
const char *FastFindWriteTag(MemBlockFlags flags, uint32_t addr, uint32_t size, size_t *tagLen);
void Reset();
void DoState(PointerWrap &p);
private:
struct Slab {
uint32_t start = 0;
uint32_t end = 0;
uint64_t ticks = 0;
uint32_t pc = 0;
bool allocated = false;
// Intentionally not save stated.
bool bulkStorage = false;
uint8_t tagLen;
char tag[128]{};
Slab *prev = nullptr;
Slab *next = nullptr;
void DoState(PointerWrap &p);
};
static constexpr uint32_t MAX_SIZE = 0x40000000;
static constexpr uint32_t SLICES = 65536;
static constexpr uint32_t SLICE_SIZE = MAX_SIZE / SLICES;
Slab *FindSlab(uint32_t addr);
void Clear();
// Returns the new slab after size.
Slab *Split(Slab *slab, uint32_t size);
void MergeAdjacent(Slab *slab);
static inline bool Same(const Slab *a, const Slab *b);
void Merge(Slab *a, Slab *b);
void FillHeads(Slab *slab);
Slab *first_ = nullptr;
Slab *lastFind_ = nullptr;
std::vector<Slab *> heads_;
Slab *bulkStorage_ = nullptr;
};
struct PendingNotifyMem {
MemBlockFlags flags;
uint32_t start;
uint32_t size;
uint32_t copySrc;
uint64_t ticks;
uint32_t pc;
uint8_t tagLen;
char tag[127];
};
// 160 KB.
static constexpr size_t MAX_PENDING_NOTIFIES = 1024;
static constexpr size_t MAX_PENDING_NOTIFIES_THREAD = 1000;
static MemSlabMap allocMap;
static MemSlabMap suballocMap;
static MemSlabMap writeMap;
static MemSlabMap textureMap;
static std::vector<PendingNotifyMem> pendingNotifies;
static std::atomic<uint32_t> pendingNotifyMinAddr1;
static std::atomic<uint32_t> pendingNotifyMaxAddr1;
static std::atomic<uint32_t> pendingNotifyMinAddr2;
static std::atomic<uint32_t> pendingNotifyMaxAddr2;
// To prevent deadlocks, acquire Read before Write if you're going to acquire both.
static std::mutex pendingWriteMutex;
static std::mutex pendingReadMutex;
static int detailedOverride;
static std::thread flushThread;
static std::atomic<bool> flushThreadRunning;
static std::atomic<bool> flushThreadPending;
static std::mutex flushLock;
static std::condition_variable flushCond;
MemSlabMap::MemSlabMap() {
Reset();
}
MemSlabMap::~MemSlabMap() {
Clear();
}
bool MemSlabMap::Mark(uint32_t addr, uint32_t size, uint64_t ticks, uint32_t pc, bool allocated, const char *tag, size_t tagLen) {
uint32_t end = addr + size;
Slab *slab = FindSlab(addr);
Slab *firstMatch = nullptr;
while (slab != nullptr && slab->start < end) {
if (slab->start < addr)
slab = Split(slab, addr - slab->start);
// Don't replace slab, the return is the after part.
if (slab->end > end) {
Split(slab, end - slab->start);
}
slab->allocated = allocated;
if (pc != 0) {
slab->ticks = ticks;
slab->pc = pc;
}
if (tag)
slab->tagLen = (uint8_t)truncate_cpy_len(slab->tag, tag, tagLen);
// Move on to the next one.
if (firstMatch == nullptr)
firstMatch = slab;
slab = slab->next;
}
if (firstMatch != nullptr) {
// This will merge all those blocks to one.
MergeAdjacent(firstMatch);
return true;
}
return false;
}
bool MemSlabMap::Find(MemBlockFlags flags, uint32_t addr, uint32_t size, std::vector<MemBlockInfo> &results) {
uint32_t end = addr + size;
Slab *slab = FindSlab(addr);
bool found = false;
while (slab != nullptr && slab->start < end) {
if (slab->pc != 0 || slab->tag[0] != '\0') {
results.push_back({ flags, slab->start, slab->end - slab->start, slab->ticks, slab->pc, slab->tag, slab->allocated });
found = true;
}
slab = slab->next;
}
return found;
}
const char *MemSlabMap::FastFindWriteTag(MemBlockFlags flags, uint32_t addr, uint32_t size, size_t *tagLen) {
uint32_t end = addr + size;
Slab *slab = FindSlab(addr);
while (slab != nullptr && slab->start < end) {
if (slab->pc != 0 || slab->tag[0] != '\0') {
*tagLen = slab->tagLen;
return slab->tag;
}
slab = slab->next;
}
return nullptr;
}
void MemSlabMap::Reset() {
Clear();
first_ = new Slab();
first_->end = MAX_SIZE;
lastFind_ = first_;
heads_.resize(SLICES, first_);
}
void MemSlabMap::DoState(PointerWrap &p) {
auto s = p.Section("MemSlabMap", 1);
if (!s)
return;
int count = 0;
if (p.mode == p.MODE_READ) {
// Since heads_ is a static size, let's avoid clearing it.
// This helps in case a debugger call happens concurrently.
Slab *old = first_;
Slab *oldBulk = bulkStorage_;
Do(p, count);
first_ = new Slab();
first_->DoState(p);
lastFind_ = first_;
--count;
FillHeads(first_);
bulkStorage_ = new Slab[count];
Slab *slab = first_;
for (int i = 0; i < count; ++i) {
slab->next = &bulkStorage_[i];
slab->next->bulkStorage = true;
slab->next->DoState(p);
slab->next->prev = slab;
slab = slab->next;
FillHeads(slab);
}
// Now that it's entirely disconnected, delete the old slabs.
while (old != nullptr) {
Slab *next = old->next;
if (!old->bulkStorage)
delete old;
old = next;
}
delete [] oldBulk;
} else {
for (Slab *slab = first_; slab != nullptr; slab = slab->next)
++count;
Do(p, count);
first_->DoState(p);
--count;
Slab *slab = first_;
for (int i = 0; i < count; ++i) {
if (slab->next) {
slab->next->DoState(p);
slab = slab->next;
}
}
}
}
void MemSlabMap::Slab::DoState(PointerWrap &p) {
auto s = p.Section("MemSlabMapSlab", 1, 3);
if (!s)
return;
Do(p, start);
Do(p, end);
Do(p, ticks);
Do(p, pc);
Do(p, allocated);
if (s >= 3) {
Do(p, tag);
tagLen = (uint8_t)strnlen(tag, sizeof(tag) - 1);
} else if (s >= 2) {
char shortTag[32];
Do(p, shortTag);
memcpy(tag, shortTag, sizeof(shortTag));
tagLen = (uint8_t)strnlen(tag, sizeof(shortTag) - 1);
} else {
std::string stringTag;
Do(p, stringTag);
tagLen = (uint8_t)std::min(stringTag.size(), sizeof(tag) - 1);
memcpy(tag, stringTag.data(), tagLen);
tag[tagLen] = '\0';
}
}
void MemSlabMap::Clear() {
Slab *s = first_;
while (s != nullptr) {
Slab *next = s->next;
if (!s->bulkStorage)
delete s;
s = next;
}
delete [] bulkStorage_;
bulkStorage_ = nullptr;
first_ = nullptr;
lastFind_ = nullptr;
heads_.clear();
}
MemSlabMap::Slab *MemSlabMap::FindSlab(uint32_t addr) {
// Jump ahead using our index.
Slab *slab = heads_[addr / SLICE_SIZE];
// We often move forward, so check the last find.
if (lastFind_->start > slab->start && lastFind_->start <= addr)
slab = lastFind_;
while (slab != nullptr && slab->start <= addr) {
if (slab->end > addr) {
lastFind_ = slab;
return slab;
}
slab = slab->next;
}
return nullptr;
}
MemSlabMap::Slab *MemSlabMap::Split(Slab *slab, uint32_t size) {
Slab *next = new Slab();
next->start = slab->start + size;
next->end = slab->end;
next->ticks = slab->ticks;
next->pc = slab->pc;
next->allocated = slab->allocated;
next->tagLen = (uint8_t)std::min<size_t>(slab->tagLen, sizeof(next->tag) - 1);
memcpy(next->tag, slab->tag, next->tagLen);
next->tag[next->tagLen] = '\0';
next->prev = slab;
next->next = slab->next;
slab->next = next;
if (next->next)
next->next->prev = next;
// If the split is big, we might have to update our index.
FillHeads(next);
slab->end = slab->start + size;
return next;
}
bool MemSlabMap::Same(const Slab *a, const Slab *b) {
if (a->allocated != b->allocated)
return false;
if (a->pc != b->pc)
return false;
if (strcmp(a->tag, b->tag))
return false;
return true;
}
void MemSlabMap::MergeAdjacent(Slab *slab) {
while (slab->next != nullptr && Same(slab, slab->next)) {
Merge(slab, slab->next);
}
while (slab->prev != nullptr && Same(slab, slab->prev)) {
Merge(slab, slab->prev);
}
}
void MemSlabMap::Merge(Slab *a, Slab *b) {
if (a->next == b) {
_assert_(a->end == b->start);
a->end = b->end;
a->next = b->next;
if (a->next)
a->next->prev = a;
} else if (a->prev == b) {
_assert_(b->end == a->start);
a->start = b->start;
a->prev = b->prev;
if (a->prev)
a->prev->next = a;
else if (first_ == b)
first_ = a;
} else {
_assert_(false);
}
// Take over index entries b had.
FillHeads(a);
if (b->ticks > a->ticks) {
a->ticks = b->ticks;
// In case we ignore PC for same.
a->pc = b->pc;
}
if (lastFind_ == b)
lastFind_ = a;
if (!b->bulkStorage)
delete b;
}
void MemSlabMap::FillHeads(Slab *slab) {
uint32_t slice = slab->start / SLICE_SIZE;
uint32_t endSlice = (slab->end - 1) / SLICE_SIZE;
// For the first slice, only replace if it's the one we're removing.
if (slab->start == slice * SLICE_SIZE) {
heads_[slice] = slab;
}
// Now replace all the rest - we definitely cover the start of them.
Slab **next = &heads_[slice + 1];
// We want to set slice + 1 through endSlice, inclusive.
size_t c = endSlice - slice;
for (size_t i = 0; i < c; ++i) {
next[i] = slab;
}
}
size_t FormatMemWriteTagAtNoFlush(char *buf, size_t sz, const char *prefix, size_t prefixLen, uint32_t start, uint32_t size);
void FlushPendingMemInfo() {
// This lock prevents us from another thread reading while we're busy flushing.
std::lock_guard<std::mutex> guard(pendingReadMutex);
std::vector<PendingNotifyMem> thisBatch;
{
std::lock_guard<std::mutex> guard(pendingWriteMutex);
thisBatch = std::move(pendingNotifies);
pendingNotifies.clear();
pendingNotifies.reserve(MAX_PENDING_NOTIFIES);
pendingNotifyMinAddr1 = 0xFFFFFFFF;
pendingNotifyMaxAddr1 = 0;
pendingNotifyMinAddr2 = 0xFFFFFFFF;
pendingNotifyMaxAddr2 = 0;
}
for (const auto &info : thisBatch) {
if (info.copySrc != 0) {
char tagData[128];
size_t tagSize = FormatMemWriteTagAtNoFlush(tagData, sizeof(tagData), info.tag, info.tagLen, info.copySrc, info.size);
writeMap.Mark(info.start, info.size, info.ticks, info.pc, true, tagData, tagSize);
continue;
}
if (info.flags & MemBlockFlags::ALLOC) {
allocMap.Mark(info.start, info.size, info.ticks, info.pc, true, info.tag, info.tagLen);
} else if (info.flags & MemBlockFlags::FREE) {
// Maintain the previous allocation tag for debugging.
allocMap.Mark(info.start, info.size, info.ticks, 0, false, nullptr, 0);
suballocMap.Mark(info.start, info.size, info.ticks, 0, false, nullptr, 0);
}
if (info.flags & MemBlockFlags::SUB_ALLOC) {
suballocMap.Mark(info.start, info.size, info.ticks, info.pc, true, info.tag, info.tagLen);
} else if (info.flags & MemBlockFlags::SUB_FREE) {
// Maintain the previous allocation tag for debugging.
suballocMap.Mark(info.start, info.size, info.ticks, 0, false, nullptr, 0);
}
if (info.flags & MemBlockFlags::TEXTURE) {
textureMap.Mark(info.start, info.size, info.ticks, info.pc, true, info.tag, info.tagLen);
}
if (info.flags & MemBlockFlags::WRITE) {
writeMap.Mark(info.start, info.size, info.ticks, info.pc, true, info.tag, info.tagLen);
}
}
}
static inline uint32_t NormalizeAddress(uint32_t addr) {
if ((addr & 0x3F000000) == 0x04000000)
return addr & 0x041FFFFF;
return addr & 0x3FFFFFFF;
}
static inline bool MergeRecentMemInfo(const PendingNotifyMem &info, size_t copyLength) {
if (pendingNotifies.size() < 4)
return false;
for (size_t i = 1; i <= 4; ++i) {
auto &prev = pendingNotifies[pendingNotifies.size() - i];
if (prev.copySrc != 0)
return false;
if (prev.flags != info.flags)
continue;
if (prev.start >= info.start + info.size || prev.start + prev.size <= info.start)
continue;
// This means there's overlap, but not a match, so we can't combine any.
if (prev.start != info.start || prev.size > info.size)
return false;
memcpy(prev.tag, info.tag, copyLength + 1);
prev.size = info.size;
prev.ticks = info.ticks;
prev.pc = info.pc;
return true;
}
return false;
}
void NotifyMemInfoPC(MemBlockFlags flags, uint32_t start, uint32_t size, uint32_t pc, const char *tagStr, size_t strLength) {
if (size == 0) {
return;
}
// Clear the uncached and kernel bits.
start = NormalizeAddress(start);
bool needFlush = false;
// When the setting is off, we skip smaller info to keep things fast.
if (MemBlockInfoDetailed(size) && flags != MemBlockFlags::READ) {
PendingNotifyMem info{ flags, start, size };
info.ticks = CoreTiming::GetTicks();
info.pc = pc;
size_t copyLength = strLength;
if (copyLength >= sizeof(info.tag)) {
copyLength = sizeof(info.tag) - 1;
}
memcpy(info.tag, tagStr, copyLength);
info.tag[copyLength] = 0;
info.tagLen = (uint8_t)copyLength;
std::lock_guard<std::mutex> guard(pendingWriteMutex);
// Sometimes we get duplicates, quickly check.
if (!MergeRecentMemInfo(info, copyLength)) {
if (start < 0x08000000) {
pendingNotifyMinAddr1 = std::min(pendingNotifyMinAddr1.load(), start);
pendingNotifyMaxAddr1 = std::max(pendingNotifyMaxAddr1.load(), start + size);
} else {
pendingNotifyMinAddr2 = std::min(pendingNotifyMinAddr2.load(), start);
pendingNotifyMaxAddr2 = std::max(pendingNotifyMaxAddr2.load(), start + size);
}
pendingNotifies.push_back(info);
}
needFlush = pendingNotifies.size() > MAX_PENDING_NOTIFIES_THREAD;
}
if (needFlush) {
{
std::lock_guard<std::mutex> guard(flushLock);
flushThreadPending = true;
}
flushCond.notify_one();
}
if (!(flags & MemBlockFlags::SKIP_MEMCHECK)) {
if (flags & MemBlockFlags::WRITE) {
g_breakpoints.ExecMemCheck(start, true, size, pc, tagStr);
} else if (flags & MemBlockFlags::READ) {
g_breakpoints.ExecMemCheck(start, false, size, pc, tagStr);
}
}
}
void NotifyMemInfo(MemBlockFlags flags, uint32_t start, uint32_t size, const char *str, size_t strLength) {
NotifyMemInfoPC(flags, start, size, currentMIPS->pc, str, strLength);
}
void NotifyMemInfoCopy(uint32_t destPtr, uint32_t srcPtr, uint32_t size, const char *prefix, size_t prefixLen) {
if (size == 0)
return;
bool needsFlush = false;
if (g_breakpoints.HasMemChecks()) {
// This will cause a flush, but it's needed to trigger memchecks with proper data.
char tagData[128];
size_t tagSize = FormatMemWriteTagAt(tagData, sizeof(tagData), prefix, prefixLen, srcPtr, size);
NotifyMemInfo(MemBlockFlags::READ, srcPtr, size, tagData, tagSize);
NotifyMemInfo(MemBlockFlags::WRITE, destPtr, size, tagData, tagSize);
} else if (MemBlockInfoDetailed(size)) {
srcPtr = NormalizeAddress(srcPtr);
destPtr = NormalizeAddress(destPtr);
PendingNotifyMem info{ MemBlockFlags::WRITE, destPtr, size };
info.copySrc = srcPtr;
info.ticks = CoreTiming::GetTicks();
info.pc = currentMIPS->pc;
// Store the prefix for now. The correct tag will be calculated on flush.
info.tagLen = (uint8_t)std::min(sizeof(info.tag), prefixLen);
memcpy(info.tag, prefix, info.tagLen);
std::lock_guard<std::mutex> guard(pendingWriteMutex);
if (destPtr < 0x08000000) {
pendingNotifyMinAddr1 = std::min(pendingNotifyMinAddr1.load(), destPtr);
pendingNotifyMaxAddr1 = std::max(pendingNotifyMaxAddr1.load(), destPtr + size);
} else {
pendingNotifyMinAddr2 = std::min(pendingNotifyMinAddr2.load(), destPtr);
pendingNotifyMaxAddr2 = std::max(pendingNotifyMaxAddr2.load(), destPtr + size);
}
pendingNotifies.push_back(info);
needsFlush = pendingNotifies.size() > MAX_PENDING_NOTIFIES_THREAD;
}
if (needsFlush) {
{
std::lock_guard<std::mutex> guard(flushLock);
flushThreadPending = true;
}
flushCond.notify_one();
}
}
std::vector<MemBlockInfo> FindMemInfo(uint32_t start, uint32_t size) {
start = NormalizeAddress(start);
if (pendingNotifyMinAddr1 < start + size && pendingNotifyMaxAddr1 >= start)
FlushPendingMemInfo();
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<std::mutex> guard(pendingReadMutex);
std::vector<MemBlockInfo> results;
allocMap.Find(MemBlockFlags::ALLOC, start, size, results);
suballocMap.Find(MemBlockFlags::SUB_ALLOC, start, size, results);
writeMap.Find(MemBlockFlags::WRITE, start, size, results);
textureMap.Find(MemBlockFlags::TEXTURE, start, size, results);
return results;
}
std::vector<MemBlockInfo> FindMemInfoByFlag(MemBlockFlags flags, uint32_t start, uint32_t size) {
start = NormalizeAddress(start);
if (pendingNotifyMinAddr1 < start + size && pendingNotifyMaxAddr1 >= start)
FlushPendingMemInfo();
if (pendingNotifyMinAddr2 < start + size && pendingNotifyMaxAddr2 >= start)
FlushPendingMemInfo();
// See the comment in FindMemInfo() above.
std::lock_guard<std::mutex> guard(pendingReadMutex);
std::vector<MemBlockInfo> results;
if (flags & MemBlockFlags::ALLOC)
allocMap.Find(MemBlockFlags::ALLOC, start, size, results);
if (flags & MemBlockFlags::SUB_ALLOC)
suballocMap.Find(MemBlockFlags::SUB_ALLOC, start, size, results);
if (flags & MemBlockFlags::WRITE)
writeMap.Find(MemBlockFlags::WRITE, start, size, results);
if (flags & MemBlockFlags::TEXTURE)
textureMap.Find(MemBlockFlags::TEXTURE, start, size, results);
return results;
}
static const char *FindWriteTagByFlag(MemBlockFlags flags, uint32_t start, uint32_t size, size_t *tagLen, bool flush = true) {
start = NormalizeAddress(start);
if (flush) {
if (pendingNotifyMinAddr1 < start + size && pendingNotifyMaxAddr1 >= start)
FlushPendingMemInfo();
if (pendingNotifyMinAddr2 < start + size && pendingNotifyMaxAddr2 >= start)
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<std::mutex> guard(pendingReadMutex);
if (flags & MemBlockFlags::ALLOC) {
const char *tag = allocMap.FastFindWriteTag(MemBlockFlags::ALLOC, start, size, tagLen);
if (tag)
return tag;
}
if (flags & MemBlockFlags::SUB_ALLOC) {
const char *tag = suballocMap.FastFindWriteTag(MemBlockFlags::SUB_ALLOC, start, size, tagLen);
if (tag)
return tag;
}
if (flags & MemBlockFlags::WRITE) {
const char *tag = writeMap.FastFindWriteTag(MemBlockFlags::WRITE, start, size, tagLen);
if (tag)
return tag;
}
if (flags & MemBlockFlags::TEXTURE) {
const char *tag = textureMap.FastFindWriteTag(MemBlockFlags::TEXTURE, start, size, tagLen);
if (tag)
return tag;
}
*tagLen = 0;
return nullptr;
}
size_t FormatMemWriteTagAt(char *buf, size_t sz, const char *prefix, size_t prefixLen, uint32_t start, uint32_t size) {
size_t tagLen;
const char *tag = FindWriteTagByFlag(MemBlockFlags::WRITE, start, size, &tagLen);
if (tag && strcmp(tag, "MemInit") != 0) {
return truncate_cat(buf, sz, prefix, prefixLen, tag, tagLen);
}
// Fall back to alloc and texture, especially for VRAM. We prefer write above.
tag = FindWriteTagByFlag(MemBlockFlags::ALLOC | MemBlockFlags::TEXTURE, start, size, &tagLen);
if (tag) {
return truncate_cat(buf, sz, prefix, prefixLen, tag, tagLen);
}
return snprintf(buf, sz, "%s%08x_size_%08x", prefix, start, size);
}
size_t FormatMemWriteTagAtNoFlush(char *buf, size_t sz, const char *prefix, size_t prefixLen, uint32_t start, uint32_t size) {
size_t tagLen;
const char *tag = FindWriteTagByFlag(MemBlockFlags::WRITE, start, size, &tagLen, false);
if (tag && strcmp(tag, "MemInit") != 0) {
return truncate_cat(buf, sz, prefix, prefixLen, tag, tagLen);
}
// Fall back to alloc and texture, especially for VRAM. We prefer write above.
tag = FindWriteTagByFlag(MemBlockFlags::ALLOC | MemBlockFlags::TEXTURE, start, size, &tagLen, false);
if (tag) {
return truncate_cat(buf, sz, prefix, prefixLen, tag, tagLen);
}
return snprintf(buf, sz, "%s%08x_size_%08x", prefix, start, size);
}
static void FlushMemInfoThread() {
SetCurrentThreadName("FlushMemInfo");
while (flushThreadRunning.load()) {
flushThreadPending = false;
FlushPendingMemInfo();
std::unique_lock<std::mutex> guard(flushLock);
flushCond.wait(guard, [] {
return flushThreadPending.load();
});
}
}
void MemBlockInfoInit() {
std::lock_guard<std::mutex> guard(pendingReadMutex);
std::lock_guard<std::mutex> guardW(pendingWriteMutex);
pendingNotifies.reserve(MAX_PENDING_NOTIFIES);
pendingNotifyMinAddr1 = 0xFFFFFFFF;
pendingNotifyMaxAddr1 = 0;
pendingNotifyMinAddr2 = 0xFFFFFFFF;
pendingNotifyMaxAddr2 = 0;
flushThreadRunning = true;
flushThreadPending = false;
flushThread = std::thread(&FlushMemInfoThread);
}
void MemBlockInfoShutdown() {
{
std::lock_guard<std::mutex> guard(pendingReadMutex);
std::lock_guard<std::mutex> guardW(pendingWriteMutex);
allocMap.Reset();
suballocMap.Reset();
writeMap.Reset();
textureMap.Reset();
pendingNotifies.clear();
}
if (flushThreadRunning.load()) {
std::lock_guard<std::mutex> guard(flushLock);
flushThreadRunning = false;
flushThreadPending = true;
}
flushCond.notify_one();
flushThread.join();
}
void MemBlockInfoDoState(PointerWrap &p) {
auto s = p.Section("MemBlockInfo", 0, 1);
if (!s)
return;
FlushPendingMemInfo();
// See the comment in FindMemInfo() above.
std::lock_guard<std::mutex> guard(pendingReadMutex);
allocMap.DoState(p);
suballocMap.DoState(p);
writeMap.DoState(p);
textureMap.DoState(p);
}
// Used by the debugger.
void MemBlockOverrideDetailed() {
detailedOverride++;
}
void MemBlockReleaseDetailed() {
detailedOverride--;
}
bool MemBlockInfoDetailed() {
return g_Config.bDebugMemInfoDetailed || detailedOverride != 0;
}