Provider refactor: 2-method base class, ProcessProvider, ProcessPicker

Collapse Provider interface from 9 virtual methods to 2 (read + size),
move providers to src/providers/, add name()/kind()/getSymbol() virtuals.
Replace FileProvider with BufferProvider, add ProcessProvider (Win32)
with module-based symbol resolution, wire ProcessPicker dialog, and
integrate getSymbol into pointer display and command row.

- Fix isReadable overflow for large addresses
- Guard deferred showSourcePicker/showTypeAutocomplete against stale edits
- 7/7 tests pass including 3 new provider test suites

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
sysadmin
2026-02-06 06:52:44 -07:00
parent 637aa7a550
commit 44e4d88f58
23 changed files with 1457 additions and 221 deletions

View File

@@ -0,0 +1,47 @@
#pragma once
#include "provider.h"
#include <QFile>
#include <QFileInfo>
namespace rcx {
class BufferProvider : public Provider {
QByteArray m_data;
QString m_name;
public:
explicit BufferProvider(QByteArray data, const QString& name = {})
: m_data(std::move(data))
, m_name(name) {}
static BufferProvider fromFile(const QString& path) {
QFile f(path);
if (f.open(QIODevice::ReadOnly))
return BufferProvider(f.readAll(), QFileInfo(path).fileName());
return BufferProvider({});
}
int size() const override { return m_data.size(); }
bool read(uint64_t addr, void* buf, int len) const override {
if (!isReadable(addr, len)) return false;
std::memcpy(buf, m_data.constData() + addr, len);
return true;
}
bool isWritable() const override { return true; }
bool write(uint64_t addr, const void* buf, int len) override {
if (!isReadable(addr, len)) return false;
std::memcpy(m_data.data() + addr, buf, len);
return true;
}
QString name() const override { return m_name; }
QString kind() const override { return QStringLiteral("File"); }
const QByteArray& data() const { return m_data; }
QByteArray& data() { return m_data; }
};
} // namespace rcx

View File

@@ -0,0 +1,14 @@
#pragma once
#include "provider.h"
namespace rcx {
class NullProvider : public Provider {
public:
int size() const override { return 0; }
bool read(uint64_t, void*, int) const override { return false; }
// name() returns "" via base default -- triggers <Select Source> in command row
// kind() returns "File" via base default
};
} // namespace rcx

View File

@@ -0,0 +1,102 @@
#pragma once
#include "provider.h"
#ifdef _WIN32
#include <windows.h>
#include <psapi.h>
namespace rcx {
class ProcessProvider : public Provider {
HANDLE m_handle = nullptr;
uint64_t m_base = 0;
int m_size = 0;
QString m_name;
struct ModuleInfo {
QString name;
uint64_t base;
uint64_t size;
};
QVector<ModuleInfo> m_modules;
public:
ProcessProvider(HANDLE proc, uint64_t base, int regionSize, const QString& name)
: m_handle(proc), m_base(base), m_size(regionSize), m_name(name)
{
cacheModules();
}
~ProcessProvider() override {
if (m_handle) CloseHandle(m_handle);
}
ProcessProvider(const ProcessProvider&) = delete;
ProcessProvider& operator=(const ProcessProvider&) = delete;
int size() const override { return m_size; }
bool read(uint64_t addr, void* buf, int len) const override {
SIZE_T got = 0;
BOOL ok = ReadProcessMemory(m_handle,
(LPCVOID)(m_base + addr), buf, len, &got);
return ok && (int)got == len;
}
bool isWritable() const override { return true; }
bool write(uint64_t addr, const void* buf, int len) override {
SIZE_T got = 0;
BOOL ok = WriteProcessMemory(m_handle,
(LPVOID)(m_base + addr), buf, len, &got);
return ok && (int)got == len;
}
QString name() const override { return m_name; }
QString kind() const override { return QStringLiteral("Process"); }
// getSymbol takes an absolute virtual address and resolves it to
// "module.dll+0xOFFSET" using the cached module list.
QString getSymbol(uint64_t absAddr) const override {
for (const auto& mod : m_modules) {
if (absAddr >= mod.base && absAddr < mod.base + mod.size) {
uint64_t offset = absAddr - mod.base;
return QStringLiteral("%1+0x%2")
.arg(mod.name)
.arg(offset, 0, 16, QChar('0'));
}
}
return {};
}
HANDLE handle() const { return m_handle; }
uint64_t baseAddress() const { return m_base; }
void refreshModules() { m_modules.clear(); cacheModules(); }
private:
void cacheModules() {
HMODULE mods[1024];
DWORD needed = 0;
if (!EnumProcessModulesEx(m_handle, mods, sizeof(mods),
&needed, LIST_MODULES_ALL))
return;
int count = qMin((int)(needed / sizeof(HMODULE)), 1024);
m_modules.reserve(count);
for (int i = 0; i < count; ++i) {
MODULEINFO mi{};
WCHAR modName[MAX_PATH];
if (GetModuleInformation(m_handle, mods[i], &mi, sizeof(mi))
&& GetModuleBaseNameW(m_handle, mods[i], modName, MAX_PATH))
{
m_modules.append({
QString::fromWCharArray(modName),
(uint64_t)mi.lpBaseOfDll,
(uint64_t)mi.SizeOfImage
});
}
}
}
};
} // namespace rcx
#endif // _WIN32

78
src/providers/provider.h Normal file
View File

@@ -0,0 +1,78 @@
#pragma once
#include <QByteArray>
#include <QString>
#include <cstdint>
#include <cstring>
namespace rcx {
class Provider {
public:
virtual ~Provider() = default;
// --- Subclasses MUST implement these two ---
virtual bool read(uint64_t addr, void* buf, int len) const = 0;
virtual int size() const = 0;
// --- Optional overrides ---
virtual bool write(uint64_t addr, const void* buf, int len) {
Q_UNUSED(addr); Q_UNUSED(buf); Q_UNUSED(len);
return false;
}
virtual bool isWritable() const { return false; }
// Human-readable label for this source.
// Examples: "notepad.exe", "dump.bin", "tcp://10.0.0.1:1337"
virtual QString name() const { return {}; }
// Category tag for the command row Source span.
// Examples: "File", "Process", "Socket"
virtual QString kind() const { return QStringLiteral("File"); }
// Resolve an absolute address to a symbol name.
// Returns empty string if no symbol is known.
// ProcessProvider: "ntdll.dll+0x1A30"
// BufferProvider: "" (no symbols in flat files)
virtual QString getSymbol(uint64_t addr) const {
Q_UNUSED(addr);
return {};
}
// --- Derived convenience (non-virtual, never override) ---
bool isValid() const { return size() > 0; }
bool isReadable(uint64_t addr, int len) const {
if (len <= 0) return (len == 0);
uint64_t ulen = (uint64_t)len;
return addr <= (uint64_t)size() && ulen <= (uint64_t)size() - addr;
}
template<typename T>
T readAs(uint64_t addr) const {
T v{};
read(addr, &v, sizeof(T));
return v;
}
uint8_t readU8 (uint64_t a) const { return readAs<uint8_t>(a); }
uint16_t readU16(uint64_t a) const { return readAs<uint16_t>(a); }
uint32_t readU32(uint64_t a) const { return readAs<uint32_t>(a); }
uint64_t readU64(uint64_t a) const { return readAs<uint64_t>(a); }
float readF32(uint64_t a) const { return readAs<float>(a); }
double readF64(uint64_t a) const { return readAs<double>(a); }
QByteArray readBytes(uint64_t addr, int len) const {
if (len <= 0) return {};
QByteArray buf(len, Qt::Uninitialized);
if (!read(addr, buf.data(), len))
buf.fill('\0');
return buf;
}
bool writeBytes(uint64_t addr, const QByteArray& d) {
return write(addr, d.constData(), d.size());
}
};
} // namespace rcx