mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
fix: rescan performance overhaul, background thread, WinDbg regions
Move rescan to background thread via ScanEngine::startRescan() to prevent UI freeze. Fix populateTable bottleneck caused by QHeaderView::ResizeToContents iterating all rows (6s -> 0ms for 512 results). Add chunked batch reads (256KB spans), enumerateRegions() for WinDbg/ProcessMemory providers, cancel support, and diagnostic logging throughout the scanner pipeline.
This commit is contained in:
@@ -124,6 +124,51 @@ void ProcessMemoryProvider::cacheModules()
|
||||
}
|
||||
}
|
||||
|
||||
QVector<rcx::MemoryRegion> ProcessMemoryProvider::enumerateRegions() const
|
||||
{
|
||||
QVector<rcx::MemoryRegion> regions;
|
||||
if (!m_handle) return regions;
|
||||
|
||||
MEMORY_BASIC_INFORMATION mbi;
|
||||
uint64_t addr = 0;
|
||||
|
||||
while (VirtualQueryEx(m_handle, (LPCVOID)addr, &mbi, sizeof(mbi)) == sizeof(mbi)) {
|
||||
if (mbi.State == MEM_COMMIT &&
|
||||
!(mbi.Protect & PAGE_NOACCESS) &&
|
||||
!(mbi.Protect & PAGE_GUARD))
|
||||
{
|
||||
rcx::MemoryRegion region;
|
||||
region.base = (uint64_t)mbi.BaseAddress;
|
||||
region.size = mbi.RegionSize;
|
||||
region.readable = true;
|
||||
region.writable = (mbi.Protect & PAGE_READWRITE) ||
|
||||
(mbi.Protect & PAGE_WRITECOPY) ||
|
||||
(mbi.Protect & PAGE_EXECUTE_READWRITE) ||
|
||||
(mbi.Protect & PAGE_EXECUTE_WRITECOPY);
|
||||
region.executable = (mbi.Protect & PAGE_EXECUTE) ||
|
||||
(mbi.Protect & PAGE_EXECUTE_READ) ||
|
||||
(mbi.Protect & PAGE_EXECUTE_READWRITE) ||
|
||||
(mbi.Protect & PAGE_EXECUTE_WRITECOPY);
|
||||
|
||||
// Match module name from cached module list
|
||||
for (const auto& mod : m_modules) {
|
||||
if (region.base >= mod.base && region.base < mod.base + mod.size) {
|
||||
region.moduleName = mod.name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
regions.append(region);
|
||||
}
|
||||
|
||||
uint64_t next = (uint64_t)mbi.BaseAddress + mbi.RegionSize;
|
||||
if (next <= addr) break; // overflow protection
|
||||
addr = next;
|
||||
}
|
||||
|
||||
return regions;
|
||||
}
|
||||
|
||||
#elif defined(__linux__)
|
||||
|
||||
ProcessMemoryProvider::ProcessMemoryProvider(uint32_t pid, const QString& processName)
|
||||
@@ -282,6 +327,58 @@ void ProcessMemoryProvider::cacheModules()
|
||||
}
|
||||
}
|
||||
|
||||
QVector<rcx::MemoryRegion> ProcessMemoryProvider::enumerateRegions() const
|
||||
{
|
||||
QVector<rcx::MemoryRegion> regions;
|
||||
if (m_fd < 0) return regions;
|
||||
|
||||
QString mapsPath = QStringLiteral("/proc/%1/maps").arg(m_pid);
|
||||
std::ifstream mapsFile(mapsPath.toStdString());
|
||||
if (!mapsFile.is_open()) return regions;
|
||||
|
||||
std::string line;
|
||||
while (std::getline(mapsFile, line)) {
|
||||
std::istringstream iss(line);
|
||||
std::string addrRange, perms, offset, dev, inode, pathname;
|
||||
iss >> addrRange >> perms >> offset >> dev >> inode;
|
||||
std::getline(iss, pathname);
|
||||
|
||||
auto dash = addrRange.find('-');
|
||||
if (dash == std::string::npos) continue;
|
||||
uint64_t addrStart = std::stoull(addrRange.substr(0, dash), nullptr, 16);
|
||||
uint64_t addrEnd = std::stoull(addrRange.substr(dash + 1), nullptr, 16);
|
||||
|
||||
if (perms.size() < 4) continue;
|
||||
bool readable = (perms[0] == 'r');
|
||||
bool writable = (perms[1] == 'w');
|
||||
bool executable = (perms[2] == 'x');
|
||||
|
||||
if (!readable) continue;
|
||||
|
||||
rcx::MemoryRegion region;
|
||||
region.base = addrStart;
|
||||
region.size = addrEnd - addrStart;
|
||||
region.readable = readable;
|
||||
region.writable = writable;
|
||||
region.executable = executable;
|
||||
|
||||
// Extract module name from pathname
|
||||
size_t start = pathname.find_first_not_of(" \t");
|
||||
if (start != std::string::npos) {
|
||||
QString qpath = QString::fromStdString(pathname.substr(start));
|
||||
if (qpath.startsWith('/') && !qpath.startsWith("/dev/") &&
|
||||
!qpath.startsWith("/memfd:")) {
|
||||
QFileInfo fi(qpath);
|
||||
region.moduleName = fi.fileName();
|
||||
}
|
||||
}
|
||||
|
||||
regions.append(region);
|
||||
}
|
||||
|
||||
return regions;
|
||||
}
|
||||
|
||||
#endif // platform
|
||||
|
||||
uint64_t ProcessMemoryProvider::symbolToAddress(const QString& name) const
|
||||
|
||||
@@ -28,6 +28,7 @@ public:
|
||||
|
||||
bool isLive() const override { return true; }
|
||||
uint64_t base() const override { return m_base; }
|
||||
QVector<rcx::MemoryRegion> enumerateRegions() const override;
|
||||
bool isReadable(uint64_t, int len) const override {
|
||||
#ifdef _WIN32
|
||||
return m_handle && len >= 0;
|
||||
|
||||
@@ -165,6 +165,10 @@ void WinDbgMemoryProvider::initInterfaces()
|
||||
qDebug() << "[WinDbg] IDebugDataSpaces hr=" << Qt::hex << (unsigned long)hr
|
||||
<< "ptr=" << (void*)m_dataSpaces;
|
||||
|
||||
hr = m_client->QueryInterface(IID_IDebugDataSpaces2, (void**)&m_dataSpaces2);
|
||||
qDebug() << "[WinDbg] IDebugDataSpaces2 hr=" << Qt::hex << (unsigned long)hr
|
||||
<< "ptr=" << (void*)m_dataSpaces2;
|
||||
|
||||
hr = m_client->QueryInterface(IID_IDebugControl, (void**)&m_control);
|
||||
qDebug() << "[WinDbg] IDebugControl hr=" << Qt::hex << (unsigned long)hr
|
||||
<< "ptr=" << (void*)m_control;
|
||||
@@ -251,10 +255,11 @@ WinDbgMemoryProvider::~WinDbgMemoryProvider()
|
||||
void WinDbgMemoryProvider::cleanup()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (m_symbols) { m_symbols->Release(); m_symbols = nullptr; }
|
||||
if (m_control) { m_control->Release(); m_control = nullptr; }
|
||||
if (m_dataSpaces) { m_dataSpaces->Release(); m_dataSpaces = nullptr; }
|
||||
if (m_client) { m_client->Release(); m_client = nullptr; }
|
||||
if (m_symbols) { m_symbols->Release(); m_symbols = nullptr; }
|
||||
if (m_control) { m_control->Release(); m_control = nullptr; }
|
||||
if (m_dataSpaces2) { m_dataSpaces2->Release(); m_dataSpaces2 = nullptr; }
|
||||
if (m_dataSpaces) { m_dataSpaces->Release(); m_dataSpaces = nullptr; }
|
||||
if (m_client) { m_client->Release(); m_client = nullptr; }
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -351,6 +356,112 @@ QString WinDbgMemoryProvider::getSymbol(uint64_t addr) const
|
||||
#endif
|
||||
}
|
||||
|
||||
QVector<rcx::MemoryRegion> WinDbgMemoryProvider::enumerateRegions() const
|
||||
{
|
||||
QVector<rcx::MemoryRegion> regions;
|
||||
#ifdef _WIN32
|
||||
if (!m_dataSpaces) return regions;
|
||||
|
||||
// Enumerate modules — used for tagging (user-mode) or as the primary
|
||||
// source of regions (kernel-mode, where QueryVirtual is unavailable).
|
||||
struct ModInfo { uint64_t base; uint64_t size; QString name; };
|
||||
QVector<ModInfo> modules;
|
||||
|
||||
if (m_symbols) {
|
||||
dispatchToOwner([&]() {
|
||||
ULONG loaded = 0, unloaded = 0;
|
||||
if (FAILED(m_symbols->GetNumberModules(&loaded, &unloaded)))
|
||||
return;
|
||||
for (ULONG i = 0; i < loaded; i++) {
|
||||
ULONG64 modBase = 0;
|
||||
if (FAILED(m_symbols->GetModuleByIndex(i, &modBase)))
|
||||
continue;
|
||||
DEBUG_MODULE_PARAMETERS params = {};
|
||||
if (FAILED(m_symbols->GetModuleParameters(1, &modBase, 0, ¶ms)))
|
||||
continue;
|
||||
char nameBuf[256] = {};
|
||||
ULONG nameSize = 0;
|
||||
m_symbols->GetModuleNames(i, 0,
|
||||
nullptr, 0, nullptr,
|
||||
nameBuf, sizeof(nameBuf), &nameSize,
|
||||
nullptr, 0, nullptr);
|
||||
ModInfo mi;
|
||||
mi.base = modBase;
|
||||
mi.size = params.Size;
|
||||
mi.name = QString::fromUtf8(nameBuf);
|
||||
modules.append(mi);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Try QueryVirtual first (user-mode debugging / user-mode dumps).
|
||||
// MSDN: "This method is not available in kernel-mode debugging."
|
||||
if (m_dataSpaces2) {
|
||||
dispatchToOwner([&]() {
|
||||
ULONG64 addr = 0;
|
||||
int safety = 0;
|
||||
constexpr int kMaxRegions = 500000;
|
||||
|
||||
while (safety++ < kMaxRegions) {
|
||||
MEMORY_BASIC_INFORMATION64 mbi = {};
|
||||
HRESULT hr = m_dataSpaces2->QueryVirtual(addr, &mbi);
|
||||
if (FAILED(hr))
|
||||
break;
|
||||
|
||||
if (mbi.State == MEM_COMMIT &&
|
||||
!(mbi.Protect & PAGE_NOACCESS) &&
|
||||
!(mbi.Protect & PAGE_GUARD))
|
||||
{
|
||||
rcx::MemoryRegion region;
|
||||
region.base = mbi.BaseAddress;
|
||||
region.size = mbi.RegionSize;
|
||||
region.readable = true;
|
||||
region.writable = (mbi.Protect & PAGE_READWRITE) ||
|
||||
(mbi.Protect & PAGE_WRITECOPY) ||
|
||||
(mbi.Protect & PAGE_EXECUTE_READWRITE) ||
|
||||
(mbi.Protect & PAGE_EXECUTE_WRITECOPY);
|
||||
region.executable = (mbi.Protect & PAGE_EXECUTE) ||
|
||||
(mbi.Protect & PAGE_EXECUTE_READ) ||
|
||||
(mbi.Protect & PAGE_EXECUTE_READWRITE) ||
|
||||
(mbi.Protect & PAGE_EXECUTE_WRITECOPY);
|
||||
|
||||
for (const auto& mod : modules) {
|
||||
if (region.base >= mod.base && region.base < mod.base + mod.size) {
|
||||
region.moduleName = mod.name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
regions.append(region);
|
||||
}
|
||||
|
||||
ULONG64 next = mbi.BaseAddress + mbi.RegionSize;
|
||||
if (next <= addr) break;
|
||||
addr = next;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Fallback for kernel-mode debugging: QueryVirtual is unavailable,
|
||||
// so use loaded modules as scannable regions. Each module image
|
||||
// becomes one region — the scanner reads through module code/data.
|
||||
if (regions.isEmpty() && !modules.isEmpty()) {
|
||||
for (const auto& mod : modules) {
|
||||
if (mod.size == 0) continue;
|
||||
rcx::MemoryRegion region;
|
||||
region.base = mod.base;
|
||||
region.size = mod.size;
|
||||
region.readable = true;
|
||||
region.writable = false;
|
||||
region.executable = true;
|
||||
region.moduleName = mod.name;
|
||||
regions.append(region);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return regions;
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
// WinDbgMemoryPlugin implementation
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
@@ -408,7 +519,7 @@ bool WinDbgMemoryPlugin::selectTarget(QWidget* parent, QString* target)
|
||||
{
|
||||
QDialog dlg(parent);
|
||||
dlg.setWindowTitle("WinDbg Settings");
|
||||
dlg.resize(460, 260);
|
||||
dlg.resize(460, 300);
|
||||
|
||||
QPalette dlgPal = qApp->palette();
|
||||
dlg.setPalette(dlgPal);
|
||||
@@ -418,7 +529,9 @@ bool WinDbgMemoryPlugin::selectTarget(QWidget* parent, QString* target)
|
||||
|
||||
layout->addWidget(new QLabel(
|
||||
"Connect to a running WinDbg debug server.\n"
|
||||
"In WinDbg, run: .server tcp:port=5055"));
|
||||
"In WinDbg, run: .server tcp:port=5055\n\n"
|
||||
"Non-invasive debug and dump files only.\n"
|
||||
"Execution control (bp, g, t, p) is not supported."));
|
||||
|
||||
layout->addSpacing(8);
|
||||
layout->addWidget(new QLabel("Connection string:"));
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
// Forward declarations for DbgEng COM interfaces
|
||||
struct IDebugClient;
|
||||
struct IDebugDataSpaces;
|
||||
struct IDebugDataSpaces2;
|
||||
struct IDebugControl;
|
||||
struct IDebugSymbols;
|
||||
|
||||
@@ -59,6 +60,7 @@ public:
|
||||
QString name() const override { return m_name; }
|
||||
QString kind() const override { return QStringLiteral("WinDbg"); }
|
||||
QString getSymbol(uint64_t addr) const override;
|
||||
QVector<rcx::MemoryRegion> enumerateRegions() const override;
|
||||
|
||||
bool isLive() const override { return m_isLive; }
|
||||
uint64_t base() const override { return m_base; }
|
||||
@@ -73,10 +75,11 @@ private:
|
||||
template<typename Fn>
|
||||
void dispatchToOwner(Fn&& fn) const;
|
||||
|
||||
IDebugClient* m_client = nullptr;
|
||||
IDebugDataSpaces* m_dataSpaces = nullptr;
|
||||
IDebugControl* m_control = nullptr;
|
||||
IDebugSymbols* m_symbols = nullptr;
|
||||
IDebugClient* m_client = nullptr;
|
||||
IDebugDataSpaces* m_dataSpaces = nullptr;
|
||||
IDebugDataSpaces2* m_dataSpaces2 = nullptr;
|
||||
IDebugControl* m_control = nullptr;
|
||||
IDebugSymbols* m_symbols = nullptr;
|
||||
|
||||
QString m_name;
|
||||
uint64_t m_base = 0;
|
||||
|
||||
Reference in New Issue
Block a user