mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
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:
@@ -62,9 +62,10 @@ bool ProcessMemoryProvider::read(uint64_t addr, void* buf, int len) const
|
|||||||
if (!m_handle || len <= 0) return false;
|
if (!m_handle || len <= 0) return false;
|
||||||
|
|
||||||
SIZE_T bytesRead = 0;
|
SIZE_T bytesRead = 0;
|
||||||
if (ReadProcessMemory(m_handle, (LPCVOID)(m_base + addr), buf, (SIZE_T)len, &bytesRead))
|
ReadProcessMemory(m_handle, (LPCVOID)(m_base + addr), buf, (SIZE_T)len, &bytesRead);
|
||||||
return bytesRead == (SIZE_T)len;
|
if ((int)bytesRead < len)
|
||||||
return false;
|
memset((char*)buf + bytesRead, 0, len - bytesRead);
|
||||||
|
return bytesRead > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ProcessMemoryProvider::write(uint64_t addr, const void* buf, int len)
|
bool ProcessMemoryProvider::write(uint64_t addr, const void* buf, int len)
|
||||||
@@ -298,9 +299,9 @@ ProcessMemoryProvider::~ProcessMemoryProvider()
|
|||||||
int ProcessMemoryProvider::size() const
|
int ProcessMemoryProvider::size() const
|
||||||
{
|
{
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
return m_handle ? INT_MAX : 0;
|
return m_handle ? 0x10000 : 0;
|
||||||
#elif defined(__linux__)
|
#elif defined(__linux__)
|
||||||
return m_fd ? INT_MAX : 0;
|
return (m_fd >= 0) ? 0x10000 : 0;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ public:
|
|||||||
QString kind() const override { return QStringLiteral("LocalProcess"); }
|
QString kind() const override { return QStringLiteral("LocalProcess"); }
|
||||||
QString getSymbol(uint64_t addr) const override;
|
QString getSymbol(uint64_t addr) const override;
|
||||||
|
|
||||||
|
bool isLive() const override { return true; }
|
||||||
|
uint64_t base() const override { return m_base; }
|
||||||
|
void setBase(uint64_t b) override { m_base = b; }
|
||||||
|
|
||||||
// Process-specific helpers
|
// Process-specific helpers
|
||||||
uint32_t pid() const { return m_pid; }
|
uint32_t pid() const { return m_pid; }
|
||||||
uint64_t baseAddress() const { return m_base; }
|
uint64_t baseAddress() const { return m_base; }
|
||||||
|
|||||||
@@ -447,9 +447,12 @@ void RcxController::connectEditor(RcxEditor* editor) {
|
|||||||
|
|
||||||
// Apply provider or show error
|
// Apply provider or show error
|
||||||
if (provider) {
|
if (provider) {
|
||||||
|
uint64_t newBase = provider->base();
|
||||||
m_doc->undoStack.clear();
|
m_doc->undoStack.clear();
|
||||||
m_doc->provider = std::move(provider);
|
m_doc->provider = std::move(provider);
|
||||||
m_doc->dataPath.clear();
|
m_doc->dataPath.clear();
|
||||||
|
m_doc->tree.baseAddress = newBase;
|
||||||
|
resetSnapshot();
|
||||||
emit m_doc->documentChanged();
|
emit m_doc->documentChanged();
|
||||||
refresh();
|
refresh();
|
||||||
} else if (!errorMsg.isEmpty()) {
|
} 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>) {
|
} else if constexpr (std::is_same_v<T, cmd::ChangeBase>) {
|
||||||
tree.baseAddress = isUndo ? c.oldBase : c.newBase;
|
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>) {
|
} else if constexpr (std::is_same_v<T, cmd::WriteBytes>) {
|
||||||
const QByteArray& bytes = isUndo ? c.oldBytes : c.newBytes;
|
const QByteArray& bytes = isUndo ? c.oldBytes : c.newBytes;
|
||||||
if (!m_doc->provider->writeBytes(c.addr, bytes))
|
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->undoStack.clear();
|
||||||
m_doc->provider = std::make_shared<ProcessProvider>(
|
m_doc->provider = std::make_shared<ProcessProvider>(
|
||||||
hProc, base, regionSize, processName);
|
hProc, base, regionSize, processName);
|
||||||
@@ -1662,6 +1675,8 @@ void RcxController::onRefreshTick() {
|
|||||||
|
|
||||||
// Capture shared_ptr copy — keeps provider alive during async read
|
// Capture shared_ptr copy — keeps provider alive during async read
|
||||||
auto prov = m_doc->provider;
|
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 {
|
m_refreshWatcher->setFuture(QtConcurrent::run([prov, extent]() -> QByteArray {
|
||||||
return prov->readBytes(0, extent);
|
return prov->readBytes(0, extent);
|
||||||
}));
|
}));
|
||||||
@@ -1709,19 +1724,20 @@ void RcxController::onReadComplete() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int RcxController::computeDataExtent() const {
|
int RcxController::computeDataExtent() const {
|
||||||
// Use provider size as the extent (for ProcessProvider this is the module/region size)
|
// Prefer tree-based extent: exact bytes needed for rendering
|
||||||
int provSize = m_doc->provider->size();
|
int treeExtent = 0;
|
||||||
if (provSize > 0) return provSize;
|
|
||||||
|
|
||||||
// Fallback: walk tree to find maximum byte offset
|
|
||||||
int maxEnd = 0;
|
|
||||||
for (int i = 0; i < m_doc->tree.nodes.size(); i++) {
|
for (int i = 0; i < m_doc->tree.nodes.size(); i++) {
|
||||||
int64_t off = m_doc->tree.computeOffset(i);
|
int64_t off = m_doc->tree.computeOffset(i);
|
||||||
int sz = m_doc->tree.nodes[i].byteSize();
|
int sz = m_doc->tree.nodes[i].byteSize();
|
||||||
int end = (int)(off + sz);
|
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() {
|
void RcxController::resetSnapshot() {
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ public:
|
|||||||
|
|
||||||
// MCP bridge accessors
|
// MCP bridge accessors
|
||||||
void setSuppressRefresh(bool v) { m_suppressRefresh = v; }
|
void setSuppressRefresh(bool v) { m_suppressRefresh = v; }
|
||||||
|
void attachToProcess(uint32_t pid, const QString& processName);
|
||||||
const QVector<SavedSourceEntry>& savedSources() const { return m_savedSources; }
|
const QVector<SavedSourceEntry>& savedSources() const { return m_savedSources; }
|
||||||
int activeSourceIndex() const { return m_activeSourceIdx; }
|
int activeSourceIndex() const { return m_activeSourceIdx; }
|
||||||
void switchSource(int idx) { switchToSavedSource(idx); }
|
void switchSource(int idx) { switchToSavedSource(idx); }
|
||||||
@@ -147,7 +148,6 @@ private:
|
|||||||
void handleMarginClick(RcxEditor* editor, int margin, int line, Qt::KeyboardModifiers mods);
|
void handleMarginClick(RcxEditor* editor, int margin, int line, Qt::KeyboardModifiers mods);
|
||||||
void updateCommandRow();
|
void updateCommandRow();
|
||||||
void performRealignment(uint64_t structId, int targetAlign);
|
void performRealignment(uint64_t structId, int targetAlign);
|
||||||
void attachToProcess(uint32_t pid, const QString& processName);
|
|
||||||
void switchToSavedSource(int idx);
|
void switchToSavedSource(int idx);
|
||||||
void pushSavedSourcesToEditors();
|
void pushSavedSourcesToEditors();
|
||||||
void showTypeSelectorPopup(RcxEditor* editor);
|
void showTypeSelectorPopup(RcxEditor* editor);
|
||||||
|
|||||||
@@ -209,8 +209,9 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) {
|
|||||||
// Load plugins
|
// Load plugins
|
||||||
m_pluginManager.LoadPlugins();
|
m_pluginManager.LoadPlugins();
|
||||||
|
|
||||||
// MCP bridge (stopped by default — user starts via File → Start MCP)
|
// MCP bridge (on by default)
|
||||||
m_mcp = new McpBridge(this, this);
|
m_mcp = new McpBridge(this, this);
|
||||||
|
m_mcp->start();
|
||||||
|
|
||||||
connect(m_mdiArea, &QMdiArea::subWindowActivated,
|
connect(m_mdiArea, &QMdiArea::subWindowActivated,
|
||||||
this, [this](QMdiSubWindow*) {
|
this, [this](QMdiSubWindow*) {
|
||||||
@@ -248,7 +249,7 @@ void MainWindow::createMenus() {
|
|||||||
file->addSeparator();
|
file->addSeparator();
|
||||||
file->addAction(makeIcon(":/vsicons/export.svg"), "Export &C++ Header...", this, &MainWindow::exportCpp);
|
file->addAction(makeIcon(":/vsicons/export.svg"), "Export &C++ Header...", this, &MainWindow::exportCpp);
|
||||||
file->addSeparator();
|
file->addSeparator();
|
||||||
m_mcpAction = file->addAction("Start &MCP Server", this, &MainWindow::toggleMcp);
|
m_mcpAction = file->addAction("Stop &MCP Server", this, &MainWindow::toggleMcp);
|
||||||
file->addSeparator();
|
file->addSeparator();
|
||||||
file->addAction(makeIcon(":/vsicons/close.svg"), "E&xit", QKeySequence(Qt::Key_Close), this, &QMainWindow::close);
|
file->addAction(makeIcon(":/vsicons/close.svg"), "E&xit", QKeySequence(Qt::Key_Close), this, &QMainWindow::close);
|
||||||
|
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ QJsonObject McpBridge::errReply(const QJsonValue& id, int code, const QString& m
|
|||||||
void McpBridge::sendJson(const QJsonObject& obj) {
|
void McpBridge::sendJson(const QJsonObject& obj) {
|
||||||
if (!m_client) return;
|
if (!m_client) return;
|
||||||
QByteArray data = QJsonDocument(obj).toJson(QJsonDocument::Compact);
|
QByteArray data = QJsonDocument(obj).toJson(QJsonDocument::Compact);
|
||||||
|
qDebug() << "[MCP] >>" << data.left(200);
|
||||||
data.append('\n');
|
data.append('\n');
|
||||||
m_client->write(data);
|
m_client->write(data);
|
||||||
m_client->flush();
|
m_client->flush();
|
||||||
@@ -151,6 +152,7 @@ QJsonObject McpBridge::makeTextResult(const QString& text, bool isError) {
|
|||||||
// ════════════════════════════════════════════════════════════════════
|
// ════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
void McpBridge::processLine(const QByteArray& line) {
|
void McpBridge::processLine(const QByteArray& line) {
|
||||||
|
qDebug() << "[MCP] <<" << line.trimmed().left(200);
|
||||||
auto doc = QJsonDocument::fromJson(line);
|
auto doc = QJsonDocument::fromJson(line);
|
||||||
if (!doc.isObject()) {
|
if (!doc.isObject()) {
|
||||||
sendJson(errReply(QJsonValue(), -32700, "Parse error"));
|
sendJson(errReply(QJsonValue(), -32700, "Parse error"));
|
||||||
@@ -263,7 +265,7 @@ QJsonObject McpBridge::handleToolsList(const QJsonValue& id) {
|
|||||||
tools.append(QJsonObject{
|
tools.append(QJsonObject{
|
||||||
{"name", "source.switch"},
|
{"name", "source.switch"},
|
||||||
{"description", "Switch active data source (provider). Use sourceIndex for saved sources, "
|
{"description", "Switch active data source (provider). Use sourceIndex for saved sources, "
|
||||||
"or filePath to load a new binary file."},
|
"filePath to load a binary file, or pid to attach to a live process."},
|
||||||
{"inputSchema", QJsonObject{
|
{"inputSchema", QJsonObject{
|
||||||
{"type", "object"},
|
{"type", "object"},
|
||||||
{"properties", QJsonObject{
|
{"properties", QJsonObject{
|
||||||
@@ -271,6 +273,10 @@ QJsonObject McpBridge::handleToolsList(const QJsonValue& id) {
|
|||||||
{"description", "MDI tab index (0-based). Omit for active tab."}}},
|
{"description", "MDI tab index (0-based). Omit for active tab."}}},
|
||||||
{"sourceIndex", QJsonObject{{"type", "integer"}}},
|
{"sourceIndex", QJsonObject{{"type", "integer"}}},
|
||||||
{"filePath", QJsonObject{{"type", "string"}}},
|
{"filePath", QJsonObject{{"type", "string"}}},
|
||||||
|
{"pid", QJsonObject{{"type", "integer"},
|
||||||
|
{"description", "Process ID to attach to for live memory reading."}}},
|
||||||
|
{"processName", QJsonObject{{"type", "string"},
|
||||||
|
{"description", "Display name for the process (optional with pid)."}}},
|
||||||
{"allViews", QJsonObject{{"type", "boolean"}}}
|
{"allViews", QJsonObject{{"type", "boolean"}}}
|
||||||
}}
|
}}
|
||||||
}}
|
}}
|
||||||
@@ -549,10 +555,12 @@ QJsonObject McpBridge::toolTreeApply(const QJsonObject& args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Phase 2: Execute in undo macro
|
// Phase 2: Execute in undo macro
|
||||||
ctrl->setSuppressRefresh(true);
|
if (!m_slowMode)
|
||||||
|
ctrl->setSuppressRefresh(true);
|
||||||
doc->undoStack.beginMacro(macroName);
|
doc->undoStack.beginMacro(macroName);
|
||||||
|
|
||||||
int applied = 0;
|
int applied = 0;
|
||||||
|
uint64_t lastRootStructId = 0; // track root-level struct inserts
|
||||||
QStringList skippedOps;
|
QStringList skippedOps;
|
||||||
for (int i = 0; i < ops.size(); i++) {
|
for (int i = 0; i < ops.size(); i++) {
|
||||||
// Safety valve: keep paint events flowing for large batches
|
// Safety valve: keep paint events flowing for large batches
|
||||||
@@ -594,6 +602,8 @@ QJsonObject McpBridge::toolTreeApply(const QJsonObject& args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
doc->undoStack.push(new RcxCommand(ctrl, cmd::Insert{n, {}}));
|
doc->undoStack.push(new RcxCommand(ctrl, cmd::Insert{n, {}}));
|
||||||
|
if (n.parentId == 0 && n.kind == NodeKind::Struct)
|
||||||
|
lastRootStructId = n.id;
|
||||||
applied++;
|
applied++;
|
||||||
}
|
}
|
||||||
else if (opType == "remove") {
|
else if (opType == "remove") {
|
||||||
@@ -722,10 +732,22 @@ QJsonObject McpBridge::toolTreeApply(const QJsonObject& args) {
|
|||||||
else {
|
else {
|
||||||
skippedOps.append(QStringLiteral("op[%1]: unknown op '%2'").arg(i).arg(opType));
|
skippedOps.append(QStringLiteral("op[%1]: unknown op '%2'").arg(i).arg(opType));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Slow mode: refresh after each operation for visual feedback
|
||||||
|
if (m_slowMode && applied > 0) {
|
||||||
|
ctrl->refresh();
|
||||||
|
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 16);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
doc->undoStack.endMacro();
|
doc->undoStack.endMacro();
|
||||||
ctrl->setSuppressRefresh(false);
|
if (!m_slowMode)
|
||||||
|
ctrl->setSuppressRefresh(false);
|
||||||
|
|
||||||
|
// Auto-switch view to newly created root struct
|
||||||
|
if (lastRootStructId)
|
||||||
|
ctrl->setViewRootId(lastRootStructId);
|
||||||
|
|
||||||
ctrl->refresh();
|
ctrl->refresh();
|
||||||
|
|
||||||
// Build response with assigned placeholder IDs
|
// Build response with assigned placeholder IDs
|
||||||
@@ -770,6 +792,14 @@ QJsonObject McpBridge::toolSourceSwitch(const QJsonObject& args) {
|
|||||||
" (" + sources[idx].displayName + ")");
|
" (" + sources[idx].displayName + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (args.contains("pid")) {
|
||||||
|
uint32_t pid = (uint32_t)args.value("pid").toInteger();
|
||||||
|
QString name = args.value("processName").toString();
|
||||||
|
if (name.isEmpty()) name = QString("PID %1").arg(pid);
|
||||||
|
ctrl->attachToProcess(pid, name);
|
||||||
|
return makeTextResult("Attached to process " + name + " (PID " + QString::number(pid) + ")");
|
||||||
|
}
|
||||||
|
|
||||||
if (args.contains("filePath")) {
|
if (args.contains("filePath")) {
|
||||||
QString path = args.value("filePath").toString();
|
QString path = args.value("filePath").toString();
|
||||||
doc->loadData(path);
|
doc->loadData(path);
|
||||||
@@ -777,7 +807,7 @@ QJsonObject McpBridge::toolSourceSwitch(const QJsonObject& args) {
|
|||||||
return makeTextResult("Loaded file: " + path);
|
return makeTextResult("Loaded file: " + path);
|
||||||
}
|
}
|
||||||
|
|
||||||
return makeTextResult("Provide sourceIndex or filePath", true);
|
return makeTextResult("Provide sourceIndex, filePath, or pid", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ════════════════════════════════════════════════════════════════════
|
// ════════════════════════════════════════════════════════════════════
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ public:
|
|||||||
void stop();
|
void stop();
|
||||||
bool isRunning() const { return m_server != nullptr; }
|
bool isRunning() const { return m_server != nullptr; }
|
||||||
|
|
||||||
|
bool slowMode() const { return m_slowMode; }
|
||||||
|
void setSlowMode(bool v) { m_slowMode = v; }
|
||||||
|
|
||||||
// Call from controller refresh / data change to notify MCP clients
|
// Call from controller refresh / data change to notify MCP clients
|
||||||
void notifyTreeChanged();
|
void notifyTreeChanged();
|
||||||
void notifyDataChanged();
|
void notifyDataChanged();
|
||||||
@@ -30,6 +33,7 @@ private:
|
|||||||
QLocalSocket* m_client = nullptr; // single client for v1
|
QLocalSocket* m_client = nullptr; // single client for v1
|
||||||
QByteArray m_readBuffer;
|
QByteArray m_readBuffer;
|
||||||
bool m_initialized = false;
|
bool m_initialized = false;
|
||||||
|
bool m_slowMode = false;
|
||||||
|
|
||||||
// JSON-RPC plumbing
|
// JSON-RPC plumbing
|
||||||
void onNewConnection();
|
void onNewConnection();
|
||||||
|
|||||||
@@ -38,10 +38,13 @@ public:
|
|||||||
bool isReadable(uint64_t, int len) const override { return len >= 0; }
|
bool isReadable(uint64_t, int len) const override { return len >= 0; }
|
||||||
|
|
||||||
bool read(uint64_t addr, void* buf, int len) const override {
|
bool read(uint64_t addr, void* buf, int len) const override {
|
||||||
|
if (!m_handle || len <= 0) return false;
|
||||||
SIZE_T got = 0;
|
SIZE_T got = 0;
|
||||||
BOOL ok = ReadProcessMemory(m_handle,
|
ReadProcessMemory(m_handle,
|
||||||
(LPCVOID)(m_base + addr), buf, len, &got);
|
(LPCVOID)(m_base + addr), buf, len, &got);
|
||||||
return ok && (int)got == len;
|
if ((int)got < len)
|
||||||
|
memset((char*)buf + got, 0, len - got);
|
||||||
|
return got > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isWritable() const override { return true; }
|
bool isWritable() const override { return true; }
|
||||||
@@ -73,6 +76,8 @@ public:
|
|||||||
|
|
||||||
HANDLE handle() const { return m_handle; }
|
HANDLE handle() const { return m_handle; }
|
||||||
uint64_t baseAddress() const { return m_base; }
|
uint64_t baseAddress() const { return m_base; }
|
||||||
|
uint64_t base() const override { return m_base; }
|
||||||
|
void setBase(uint64_t b) override { m_base = b; }
|
||||||
void refreshModules() { m_modules.clear(); cacheModules(); }
|
void refreshModules() { m_modules.clear(); cacheModules(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -33,6 +33,11 @@ public:
|
|||||||
// Examples: "File", "Process", "Socket"
|
// Examples: "File", "Process", "Socket"
|
||||||
virtual QString kind() const { return QStringLiteral("File"); }
|
virtual QString kind() const { return QStringLiteral("File"); }
|
||||||
|
|
||||||
|
// Base address for providers that offset reads (e.g. process memory).
|
||||||
|
// For file/buffer providers this is always 0.
|
||||||
|
virtual uint64_t base() const { return 0; }
|
||||||
|
virtual void setBase(uint64_t newBase) { Q_UNUSED(newBase); }
|
||||||
|
|
||||||
// Resolve an absolute address to a symbol name.
|
// Resolve an absolute address to a symbol name.
|
||||||
// Returns empty string if no symbol is known.
|
// Returns empty string if no symbol is known.
|
||||||
// ProcessProvider: "ntdll.dll+0x1A30"
|
// ProcessProvider: "ntdll.dll+0x1A30"
|
||||||
|
|||||||
Reference in New Issue
Block a user