Fix process memory provider base address sync and live refresh

Provider base address now stays in sync with tree base address when
changed via ChangeBase command, fixing reads from arbitrary memory
regions like KUSER_SHARED_DATA at 0x7FFE0000. ReadProcessMemory
handles partial reads gracefully. Snapshot extent uses tree-based
calculation instead of provider size to avoid oversized reads.
MCP source.switch gains pid parameter for programmatic process attach.
MCP server starts by default with logging and slow mode support.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
IChooseYou
2026-02-10 13:04:28 -07:00
committed by sysadmin
parent 6bd61a6b78
commit 5f1fd56171
9 changed files with 88 additions and 22 deletions

View File

@@ -447,9 +447,12 @@ void RcxController::connectEditor(RcxEditor* editor) {
// Apply provider or show error
if (provider) {
uint64_t newBase = provider->base();
m_doc->undoStack.clear();
m_doc->provider = std::move(provider);
m_doc->dataPath.clear();
m_doc->tree.baseAddress = newBase;
resetSnapshot();
emit m_doc->documentChanged();
refresh();
} else if (!errorMsg.isEmpty()) {
@@ -860,6 +863,13 @@ void RcxController::applyCommand(const Command& command, bool isUndo) {
}
} else if constexpr (std::is_same_v<T, cmd::ChangeBase>) {
tree.baseAddress = isUndo ? c.oldBase : c.newBase;
qDebug() << "[ChangeBase] tree.baseAddress =" << Qt::hex << tree.baseAddress
<< "provider =" << (m_doc->provider ? "yes" : "null");
if (m_doc->provider) {
m_doc->provider->setBase(tree.baseAddress);
qDebug() << "[ChangeBase] provider->base() now =" << Qt::hex << m_doc->provider->base();
}
resetSnapshot();
} else if constexpr (std::is_same_v<T, cmd::WriteBytes>) {
const QByteArray& bytes = isUndo ? c.oldBytes : c.newBytes;
if (!m_doc->provider->writeBytes(c.addr, bytes))
@@ -1585,6 +1595,9 @@ void RcxController::attachToProcess(uint32_t pid, const QString& processName) {
}
}
qDebug() << "[AttachProcess]" << processName << "PID" << pid
<< "base" << Qt::hex << base << "regionSize" << regionSize;
m_doc->undoStack.clear();
m_doc->provider = std::make_shared<ProcessProvider>(
hProc, base, regionSize, processName);
@@ -1662,6 +1675,8 @@ void RcxController::onRefreshTick() {
// Capture shared_ptr copy — keeps provider alive during async read
auto prov = m_doc->provider;
uint64_t base = prov->base();
qDebug() << "[Refresh] reading" << extent << "bytes from base" << Qt::hex << base;
m_refreshWatcher->setFuture(QtConcurrent::run([prov, extent]() -> QByteArray {
return prov->readBytes(0, extent);
}));
@@ -1709,19 +1724,20 @@ void RcxController::onReadComplete() {
}
int RcxController::computeDataExtent() const {
// Use provider size as the extent (for ProcessProvider this is the module/region size)
int provSize = m_doc->provider->size();
if (provSize > 0) return provSize;
// Fallback: walk tree to find maximum byte offset
int maxEnd = 0;
// Prefer tree-based extent: exact bytes needed for rendering
int treeExtent = 0;
for (int i = 0; i < m_doc->tree.nodes.size(); i++) {
int64_t off = m_doc->tree.computeOffset(i);
int sz = m_doc->tree.nodes[i].byteSize();
int end = (int)(off + sz);
if (end > maxEnd) maxEnd = end;
if (end > treeExtent) treeExtent = end;
}
return maxEnd;
if (treeExtent > 0) return treeExtent;
// Fallback: provider size (empty tree)
int provSize = m_doc->provider->size();
if (provSize > 0) return provSize;
return 0;
}
void RcxController::resetSnapshot() {