From 009ddc951cad20c4d26ce5f375981a7f85ff90fe Mon Sep 17 00:00:00 2001 From: IChooseYou Date: Sat, 14 Mar 2026 09:36:49 -0600 Subject: [PATCH] fix: commit remaining uncommitted source changes for CI Add extractPdbSymbols declaration to import_pdb.h, enumerateModules to provider.h, and other pending changes that were only local. --- plugins/ProcessMemory/ProcessMemoryPlugin.cpp | 15 +++++ plugins/ProcessMemory/ProcessMemoryPlugin.h | 2 + src/addressparser.cpp | 17 +++++- src/compose.cpp | 13 ++++- src/controller.h | 3 +- src/imports/import_pdb.h | 19 +++++++ src/main.cpp | 57 +++++++++++-------- src/providers/provider.h | 3 + src/resources.qrc | 2 + 9 files changed, 102 insertions(+), 29 deletions(-) diff --git a/plugins/ProcessMemory/ProcessMemoryPlugin.cpp b/plugins/ProcessMemory/ProcessMemoryPlugin.cpp index d97d6fe..f551f2a 100644 --- a/plugins/ProcessMemory/ProcessMemoryPlugin.cpp +++ b/plugins/ProcessMemory/ProcessMemoryPlugin.cpp @@ -185,8 +185,14 @@ void ProcessMemoryProvider::cacheModules() if ( i == 0 ) m_base = (uint64_t)mi.lpBaseOfDll; + WCHAR modPath[MAX_PATH]; + QString fullPath; + if (GetModuleFileNameExW(m_handle, mods[i], modPath, MAX_PATH)) + fullPath = QString::fromWCharArray(modPath); + m_modules.append({ QString::fromWCharArray(modName), + fullPath, (uint64_t)mi.lpBaseOfDll, (uint64_t)mi.SizeOfImage }); @@ -194,6 +200,15 @@ void ProcessMemoryProvider::cacheModules() } } +QVector ProcessMemoryProvider::enumerateModules() const +{ + QVector result; + result.reserve(m_modules.size()); + for (const auto& m : m_modules) + result.append({m.name, m.fullPath, m.base, m.size}); + return result; +} + QVector ProcessMemoryProvider::enumerateRegions() const { QVector regions; diff --git a/plugins/ProcessMemory/ProcessMemoryPlugin.h b/plugins/ProcessMemory/ProcessMemoryPlugin.h index 03a6301..77592be 100644 --- a/plugins/ProcessMemory/ProcessMemoryPlugin.h +++ b/plugins/ProcessMemory/ProcessMemoryPlugin.h @@ -43,6 +43,7 @@ public: void refreshModules() { m_modules.clear(); cacheModules(); } uint64_t peb() const override { return m_peb; } QVector tebs() const override; + QVector enumerateModules() const override; private: void cacheModules(); @@ -62,6 +63,7 @@ private: struct ModuleInfo { QString name; + QString fullPath; uint64_t base; uint64_t size; }; diff --git a/src/addressparser.cpp b/src/addressparser.cpp index e9a1c45..7b3902e 100644 --- a/src/addressparser.cpp +++ b/src/addressparser.cpp @@ -273,17 +273,32 @@ private: // Identifier or hex literal disambiguation. // Scan [a-zA-Z_][a-zA-Z0-9_]*. If it contains any non-hex char → identifier. // Otherwise → backtrack and parse as hex number. + // WinDbg-style "module!symbol" is scanned as a single identifier token. // If the identifier is followed by '(', try to parse as a built-in function call. bool parseIdentifierOrHex(uint64_t& result) { int start = m_pos; bool hasNonHex = false; - // Scan full token + // Scan full token, including "module!symbol" as one token while (!atEnd() && isIdentChar(peek())) { if (!isHexDigit(peek())) hasNonHex = true; advance(); } + // If we hit '!' and the next char is an identifier start, extend the token + // to include the second part (WinDbg module!symbol syntax) + if (!atEnd() && peek() == '!' && m_pos > start) { + int bangPos = m_pos; + advance(); // skip '!' + if (!atEnd() && isIdentStart(peek())) { + hasNonHex = true; + while (!atEnd() && isIdentChar(peek())) { + advance(); + } + } else { + m_pos = bangPos; // backtrack — '!' at end isn't module!symbol + } + } QString token = m_input.mid(start, m_pos - start); diff --git a/src/compose.cpp b/src/compose.cpp index 7429018..a1c1ddf 100644 --- a/src/compose.cpp +++ b/src/compose.cpp @@ -71,6 +71,7 @@ struct ComposeState { bool treeLines = false; // draw Unicode tree connectors in indentation bool braceWrap = false; // opening brace on its own line bool typeHints = false; // show type inference hints on hex nodes + SymbolLookupFn symbolLookup; // optional PDB symbol lookup callback QVector siblingStack; // per-depth: true = more siblings follow at this level uint64_t currentPtrBase = 0; // absolute addr of current pointer expansion target @@ -262,7 +263,7 @@ void composeLeaf(ComposeState& state, const NodeTree& tree, auto suggestions = inferTypes( reinterpret_cast(b.constData()), sz); if (!suggestions.isEmpty() && suggestions[0].strength >= 3) { - lm.typeHintStart = lineText.size() + 2; // after " " gap + lm.typeHintStart = kFoldCol + lineText.size() + 2; // after fold prefix + " " gap lm.typeHintKinds = suggestions[0].kinds; QString typeName = formatHint(suggestions[0]); QString preview = formatPreview( @@ -276,6 +277,13 @@ void composeLeaf(ComposeState& state, const NodeTree& tree, } } + // PDB symbol annotation: show symbol name if this address matches a loaded symbol + if (sub == 0 && state.symbolLookup) { + QString sym = state.symbolLookup(absAddr); + if (!sym.isEmpty()) + lineText += QStringLiteral(" // ") + sym; + } + state.emitLine(lineText, std::move(lm)); } } @@ -1087,12 +1095,13 @@ void composeNode(ComposeState& state, const NodeTree& tree, ComposeResult compose(const NodeTree& tree, const Provider& prov, uint64_t viewRootId, bool compactColumns, bool treeLines, bool braceWrap, - bool typeHints) { + bool typeHints, SymbolLookupFn symbolLookup) { ComposeState state; state.compactColumns = compactColumns; state.treeLines = treeLines; state.braceWrap = braceWrap; state.typeHints = typeHints; + state.symbolLookup = std::move(symbolLookup); // Precompute parent→children map for (int i = 0; i < tree.nodes.size(); i++) diff --git a/src/controller.h b/src/controller.h index 081551a..dcaf8b4 100644 --- a/src/controller.h +++ b/src/controller.h @@ -42,7 +42,8 @@ public: ComposeResult compose(uint64_t viewRootId = 0, bool compactColumns = false, bool treeLines = false, bool braceWrap = false, - bool typeHints = false) const; + bool typeHints = false, + SymbolLookupFn symbolLookup = {}) const; bool save(const QString& path); bool load(const QString& path); void loadData(const QString& binaryPath); diff --git a/src/imports/import_pdb.h b/src/imports/import_pdb.h index fd39aa8..f2f4f23 100644 --- a/src/imports/import_pdb.h +++ b/src/imports/import_pdb.h @@ -5,6 +5,25 @@ namespace rcx { +// ── PDB Symbol Extraction ── + +struct PdbSymbol { + QString name; + uint32_t rva; +}; + +struct PdbSymbolResult { + QString moduleName; // derived from PDB filename (e.g. "ntoskrnl") + QVector symbols; +}; + +// Extract public/global symbols (name → RVA) from a PDB file. +// This reads the DBI stream's public and global symbol sub-streams. +PdbSymbolResult extractPdbSymbols(const QString& pdbPath, + QString* errorMsg = nullptr); + +// ── PDB Type Import ── + struct PdbTypeInfo { uint32_t typeIndex; // TPI type index QString name; // struct/class/union/enum name diff --git a/src/main.cpp b/src/main.cpp index 9ebd38c..1179fec 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5165,12 +5165,40 @@ void MainWindow::createSymbolsDock() { m_symbolsTree->setExpandsOnDoubleClick(false); styleTree(m_symbolsTree); - // Debounced search + // Populate a module item's children (replaces sentinel with real symbols) + auto populateModuleItem = [this](QStandardItem* item) { + if (!item || item->parent()) return; + if (item->rowCount() == 1 && item->child(0)->text().isEmpty()) { + item->removeRows(0, 1); + QString moduleName = item->data(Qt::UserRole).toString(); + const auto* set = rcx::SymbolStore::instance().moduleData(moduleName); + if (set) { + static const QIcon symIcon(":/vsicons/symbol-method.svg"); + for (const auto& sym : set->rvaToName) { + auto* child = new QStandardItem(symIcon, + QStringLiteral("%1 [0x%2]") + .arg(sym.second) + .arg(sym.first, 8, 16, QLatin1Char('0'))); + child->setData(moduleName, Qt::UserRole); + child->setData(sym.first, Qt::UserRole + 1); + child->setData(sym.second, Qt::UserRole + 2); + item->appendRow(child); + } + } + } + }; + + // Debounced search — force-populate all modules so filter can match children auto* searchTimer = new QTimer(this); searchTimer->setSingleShot(true); searchTimer->setInterval(150); - connect(searchTimer, &QTimer::timeout, this, [this]() { + connect(searchTimer, &QTimer::timeout, this, [this, populateModuleItem]() { QString text = m_symbolsSearch->text(); + if (!text.isEmpty()) { + // Force-populate all modules that still have sentinels + for (int i = 0; i < m_symbolsModel->rowCount(); i++) + populateModuleItem(m_symbolsModel->item(i)); + } m_symbolsProxy->setFilterFixedString(text); if (!text.isEmpty()) m_symbolsTree->expandAll(); @@ -5184,30 +5212,9 @@ void MainWindow::createSymbolsDock() { symLayout->addWidget(m_symbolsTree); // Lazy-load children when a module node is expanded - connect(m_symbolsTree, &QTreeView::expanded, this, [this](const QModelIndex& proxyIdx) { + connect(m_symbolsTree, &QTreeView::expanded, this, [this, populateModuleItem](const QModelIndex& proxyIdx) { QModelIndex srcIdx = m_symbolsProxy->mapToSource(proxyIdx); - auto* item = m_symbolsModel->itemFromIndex(srcIdx); - if (!item || item->parent()) return; - - if (item->rowCount() == 1 && item->child(0)->text().isEmpty()) { - item->removeRows(0, 1); - QString moduleName = item->data(Qt::UserRole).toString(); - const auto* set = rcx::SymbolStore::instance().moduleData(moduleName); - if (set) { - for (const auto& sym : set->rvaToName) { - auto* child = new QStandardItem( - QStringLiteral("%1 [0x%2]") - .arg(sym.second) - .arg(sym.first, 8, 16, QLatin1Char('0'))); - child->setData(moduleName, Qt::UserRole); - child->setData(sym.first, Qt::UserRole + 1); - child->setData(sym.second, Qt::UserRole + 2); - static const QIcon symIcon(":/vsicons/symbol-method.svg"); - child->setIcon(symIcon); - item->appendRow(child); - } - } - } + populateModuleItem(m_symbolsModel->itemFromIndex(srcIdx)); }); // Double-click symbol → navigate to moduleBase + RVA diff --git a/src/providers/provider.h b/src/providers/provider.h index 1ba5526..86d1aff 100644 --- a/src/providers/provider.h +++ b/src/providers/provider.h @@ -87,6 +87,9 @@ public: struct ThreadInfo { uint64_t tebAddress; uint32_t threadId; }; virtual QVector tebs() const { return {}; } + struct ModuleEntry { QString name; QString fullPath; uint64_t base; uint64_t size; }; + virtual QVector enumerateModules() const { return {}; } + // --- Kernel paging capabilities (override in kernel providers) --- virtual bool hasKernelPaging() const { return false; } virtual uint64_t getCr3() const { return 0; } diff --git a/src/resources.qrc b/src/resources.qrc index 88f332a..efaf3dd 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -17,6 +17,7 @@ vsicons/file-binary.svg vsicons/debug.svg vsicons/close.svg + vsicons/cloud-download.svg vsicons/arrow-left.svg vsicons/arrow-right.svg vsicons/split-horizontal.svg @@ -55,6 +56,7 @@ vsicons/symbol-enum.svg vsicons/symbol-class.svg vsicons/symbol-variable.svg + vsicons/symbol-method.svg vsicons/server-process.svg vsicons/remote.svg vsicons/plug.svg