mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
fix: add missing symbol store and PDB debug info sources
These files were referenced in CMakeLists.txt and main.cpp but never committed, breaking the CI build.
This commit is contained in:
@@ -147,6 +147,12 @@ add_executable(Reclass
|
||||
src/mcp/mcp_bridge.cpp
|
||||
src/addressparser.h
|
||||
src/addressparser.cpp
|
||||
src/symbolstore.h
|
||||
src/symbolstore.cpp
|
||||
src/symbol_downloader.h
|
||||
src/symbol_downloader.cpp
|
||||
src/imports/pe_debug_info.h
|
||||
src/imports/pe_debug_info.cpp
|
||||
src/disasm.h
|
||||
src/disasm.cpp
|
||||
third_party/fadec/decode.c
|
||||
@@ -415,7 +421,7 @@ if(BUILD_TESTING)
|
||||
if(BUILD_UI_TESTS)
|
||||
|
||||
add_executable(test_controller tests/test_controller.cpp
|
||||
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp
|
||||
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp src/symbolstore.cpp
|
||||
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
|
||||
src/typeselectorpopup.cpp
|
||||
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
|
||||
@@ -429,7 +435,7 @@ if(BUILD_TESTING)
|
||||
add_test(NAME test_controller COMMAND test_controller)
|
||||
|
||||
add_executable(test_context_menu tests/test_context_menu.cpp
|
||||
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp
|
||||
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp src/symbolstore.cpp
|
||||
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
|
||||
src/typeselectorpopup.cpp
|
||||
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
|
||||
@@ -443,7 +449,7 @@ if(BUILD_TESTING)
|
||||
add_test(NAME test_context_menu COMMAND test_context_menu)
|
||||
|
||||
add_executable(test_source_management tests/test_source_management.cpp
|
||||
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp
|
||||
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp src/symbolstore.cpp
|
||||
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
|
||||
src/typeselectorpopup.cpp
|
||||
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
|
||||
@@ -475,7 +481,7 @@ if(BUILD_TESTING)
|
||||
add_test(NAME test_rendered_view COMMAND test_rendered_view)
|
||||
|
||||
add_executable(test_type_selector tests/test_type_selector.cpp
|
||||
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp
|
||||
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp src/symbolstore.cpp
|
||||
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
|
||||
src/typeselectorpopup.cpp
|
||||
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
|
||||
@@ -489,7 +495,7 @@ if(BUILD_TESTING)
|
||||
add_test(NAME test_type_selector COMMAND test_type_selector)
|
||||
|
||||
add_executable(test_type_visibility tests/test_type_visibility.cpp
|
||||
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp
|
||||
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp src/symbolstore.cpp
|
||||
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
|
||||
src/typeselectorpopup.cpp
|
||||
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS})
|
||||
@@ -509,7 +515,7 @@ if(BUILD_TESTING)
|
||||
add_test(NAME test_options_dialog COMMAND test_options_dialog)
|
||||
|
||||
add_executable(test_source_provider tests/test_source_provider.cpp
|
||||
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp
|
||||
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp src/symbolstore.cpp
|
||||
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
|
||||
src/typeselectorpopup.cpp
|
||||
src/themes/theme.cpp src/themes/thememanager.cpp ${DISASM_SRCS}
|
||||
|
||||
193
src/imports/pe_debug_info.cpp
Normal file
193
src/imports/pe_debug_info.cpp
Normal file
@@ -0,0 +1,193 @@
|
||||
#include "pe_debug_info.h"
|
||||
#include "../providers/provider.h"
|
||||
#include <cstring>
|
||||
|
||||
namespace rcx {
|
||||
|
||||
// Minimal PE structures (no Windows SDK dependency)
|
||||
#pragma pack(push, 1)
|
||||
struct DosHeader {
|
||||
uint16_t e_magic; // 'MZ'
|
||||
uint8_t pad[58];
|
||||
int32_t e_lfanew; // offset to PE signature
|
||||
};
|
||||
|
||||
struct CoffHeader {
|
||||
uint16_t Machine;
|
||||
uint16_t NumberOfSections;
|
||||
uint32_t TimeDateStamp;
|
||||
uint32_t PointerToSymbolTable;
|
||||
uint32_t NumberOfSymbols;
|
||||
uint16_t SizeOfOptionalHeader;
|
||||
uint16_t Characteristics;
|
||||
};
|
||||
|
||||
struct DataDirectory {
|
||||
uint32_t VirtualAddress;
|
||||
uint32_t Size;
|
||||
};
|
||||
|
||||
// Only the fields we need from the optional header
|
||||
struct OptionalHeader32 {
|
||||
uint16_t Magic; // 0x10b = PE32, 0x20b = PE32+
|
||||
uint8_t pad[90];
|
||||
uint32_t NumberOfRvaAndSizes;
|
||||
// DataDirectory[0] = Export, [1] = Import, ... [6] = Debug
|
||||
};
|
||||
|
||||
struct OptionalHeader64 {
|
||||
uint16_t Magic; // 0x20b = PE32+
|
||||
uint8_t pad[106];
|
||||
uint32_t NumberOfRvaAndSizes;
|
||||
};
|
||||
|
||||
struct DebugDirectory {
|
||||
uint32_t Characteristics;
|
||||
uint32_t TimeDateStamp;
|
||||
uint16_t MajorVersion;
|
||||
uint16_t MinorVersion;
|
||||
uint32_t Type;
|
||||
uint32_t SizeOfData;
|
||||
uint32_t AddressOfRawData; // RVA when loaded
|
||||
uint32_t PointerToRawData; // file offset (not used for memory reads)
|
||||
};
|
||||
|
||||
struct CvInfoPdb70 {
|
||||
uint32_t Signature; // 'RSDS'
|
||||
uint8_t Guid[16];
|
||||
uint32_t Age;
|
||||
// char PdbFileName[] follows
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
static constexpr uint16_t kMZ = 0x5A4D;
|
||||
static constexpr uint32_t kPE = 0x00004550;
|
||||
static constexpr uint16_t kPE32 = 0x10b;
|
||||
static constexpr uint16_t kPE32P = 0x20b;
|
||||
static constexpr uint32_t kRSDS = 0x53445352;
|
||||
static constexpr uint32_t kDebugType_CodeView = 2;
|
||||
|
||||
static QString guidToString(const uint8_t guid[16]) {
|
||||
// Windows GUID is mixed-endian: Data1(4B LE), Data2(2B LE), Data3(2B LE), Data4(8B sequential)
|
||||
// MS symbol server expects native integer values for Data1/2/3, sequential for Data4
|
||||
uint32_t d1; memcpy(&d1, guid, 4);
|
||||
uint16_t d2; memcpy(&d2, guid + 4, 2);
|
||||
uint16_t d3; memcpy(&d3, guid + 6, 2);
|
||||
QString s = QStringLiteral("%1%2%3")
|
||||
.arg(d1, 8, 16, QLatin1Char('0'))
|
||||
.arg(d2, 4, 16, QLatin1Char('0'))
|
||||
.arg(d3, 4, 16, QLatin1Char('0'));
|
||||
for (int i = 8; i < 16; i++)
|
||||
s += QStringLiteral("%1").arg(guid[i], 2, 16, QLatin1Char('0'));
|
||||
return s.toUpper();
|
||||
}
|
||||
|
||||
PdbDebugInfo extractPdbDebugInfo(const Provider& prov, uint64_t moduleBase) {
|
||||
PdbDebugInfo result;
|
||||
|
||||
// Read DOS header
|
||||
DosHeader dos;
|
||||
if (!prov.read(moduleBase, &dos, sizeof(dos)))
|
||||
return result;
|
||||
if (dos.e_magic != kMZ)
|
||||
return result;
|
||||
|
||||
uint64_t peOffset = moduleBase + dos.e_lfanew;
|
||||
|
||||
// Read PE signature
|
||||
uint32_t peSig = 0;
|
||||
if (!prov.read(peOffset, &peSig, 4))
|
||||
return result;
|
||||
if (peSig != kPE)
|
||||
return result;
|
||||
|
||||
// Read COFF header
|
||||
uint64_t coffOffset = peOffset + 4;
|
||||
CoffHeader coff;
|
||||
if (!prov.read(coffOffset, &coff, sizeof(coff)))
|
||||
return result;
|
||||
|
||||
// Read optional header magic to determine PE32 vs PE32+
|
||||
uint64_t optOffset = coffOffset + sizeof(CoffHeader);
|
||||
uint16_t optMagic = 0;
|
||||
if (!prov.read(optOffset, &optMagic, 2))
|
||||
return result;
|
||||
|
||||
// Locate debug data directory (index 6)
|
||||
uint32_t numRvaAndSizes = 0;
|
||||
uint64_t dataDirsOffset = 0;
|
||||
|
||||
if (optMagic == kPE32) {
|
||||
// PE32: NumberOfRvaAndSizes at offset 92, data dirs at offset 96
|
||||
if (!prov.read(optOffset + 92, &numRvaAndSizes, 4))
|
||||
return result;
|
||||
dataDirsOffset = optOffset + 96;
|
||||
} else if (optMagic == kPE32P) {
|
||||
// PE32+: NumberOfRvaAndSizes at offset 108, data dirs at offset 112
|
||||
if (!prov.read(optOffset + 108, &numRvaAndSizes, 4))
|
||||
return result;
|
||||
dataDirsOffset = optOffset + 112;
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (numRvaAndSizes <= 6)
|
||||
return result; // no debug directory
|
||||
|
||||
DataDirectory debugDir;
|
||||
if (!prov.read(dataDirsOffset + 6 * sizeof(DataDirectory), &debugDir, sizeof(debugDir)))
|
||||
return result;
|
||||
|
||||
if (debugDir.VirtualAddress == 0 || debugDir.Size == 0)
|
||||
return result;
|
||||
|
||||
// Read debug directory entries
|
||||
int numEntries = debugDir.Size / sizeof(DebugDirectory);
|
||||
for (int i = 0; i < numEntries; i++) {
|
||||
DebugDirectory entry;
|
||||
uint64_t entryAddr = moduleBase + debugDir.VirtualAddress + i * sizeof(DebugDirectory);
|
||||
if (!prov.read(entryAddr, &entry, sizeof(entry)))
|
||||
continue;
|
||||
|
||||
if (entry.Type != kDebugType_CodeView)
|
||||
continue;
|
||||
|
||||
// Read CodeView info (RSDS)
|
||||
if (entry.AddressOfRawData == 0 || entry.SizeOfData < sizeof(CvInfoPdb70) + 1)
|
||||
continue;
|
||||
|
||||
CvInfoPdb70 cv;
|
||||
uint64_t cvAddr = moduleBase + entry.AddressOfRawData;
|
||||
if (!prov.read(cvAddr, &cv, sizeof(cv)))
|
||||
continue;
|
||||
|
||||
if (cv.Signature != kRSDS)
|
||||
continue;
|
||||
|
||||
// Read PDB filename (null-terminated string after the struct)
|
||||
int nameMaxLen = entry.SizeOfData - sizeof(CvInfoPdb70);
|
||||
if (nameMaxLen > 260) nameMaxLen = 260;
|
||||
char nameBuf[261] = {};
|
||||
if (!prov.read(cvAddr + sizeof(CvInfoPdb70), nameBuf, nameMaxLen))
|
||||
continue;
|
||||
nameBuf[nameMaxLen] = '\0';
|
||||
|
||||
result.pdbName = QString::fromLatin1(nameBuf);
|
||||
// Extract just the filename if it contains a path
|
||||
int lastSlash = result.pdbName.lastIndexOf('\\');
|
||||
if (lastSlash >= 0)
|
||||
result.pdbName = result.pdbName.mid(lastSlash + 1);
|
||||
int lastFwdSlash = result.pdbName.lastIndexOf('/');
|
||||
if (lastFwdSlash >= 0)
|
||||
result.pdbName = result.pdbName.mid(lastFwdSlash + 1);
|
||||
|
||||
result.guidString = guidToString(cv.Guid);
|
||||
result.age = cv.Age;
|
||||
result.valid = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace rcx
|
||||
20
src/imports/pe_debug_info.h
Normal file
20
src/imports/pe_debug_info.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
#include <QString>
|
||||
#include <cstdint>
|
||||
|
||||
namespace rcx {
|
||||
|
||||
class Provider;
|
||||
|
||||
struct PdbDebugInfo {
|
||||
QString pdbName; // e.g. "ntoskrnl.pdb"
|
||||
QString guidString; // 32 hex chars, no dashes, uppercase
|
||||
uint32_t age = 0;
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
// Extract PDB debug info (GUID, age, filename) from a PE module in memory.
|
||||
// Reads DOS header → PE header → debug directory → CodeView RSDS record.
|
||||
PdbDebugInfo extractPdbDebugInfo(const Provider& prov, uint64_t moduleBase);
|
||||
|
||||
} // namespace rcx
|
||||
123
src/symbol_downloader.cpp
Normal file
123
src/symbol_downloader.cpp
Normal file
@@ -0,0 +1,123 @@
|
||||
#include "symbol_downloader.h"
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QStandardPaths>
|
||||
#include <QUrl>
|
||||
|
||||
namespace rcx {
|
||||
|
||||
SymbolDownloader::SymbolDownloader(QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_nam(new QNetworkAccessManager(this))
|
||||
{
|
||||
}
|
||||
|
||||
QString SymbolDownloader::cacheDir() {
|
||||
QString base = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
|
||||
return base + QStringLiteral("/SymbolCache");
|
||||
}
|
||||
|
||||
QString SymbolDownloader::findCached(const DownloadRequest& req) const {
|
||||
// Cache layout: cacheDir/pdbName/GUID+age/pdbName
|
||||
QString path = cacheDir() + QStringLiteral("/%1/%2%3/%1")
|
||||
.arg(req.pdbName, req.guidString, QString::number(req.age, 16));
|
||||
if (QFile::exists(path))
|
||||
return path;
|
||||
return {};
|
||||
}
|
||||
|
||||
QString SymbolDownloader::findLocal(const QString& moduleFullPath, const QString& pdbName) {
|
||||
if (moduleFullPath.isEmpty() || pdbName.isEmpty())
|
||||
return {};
|
||||
// Check same directory as the module
|
||||
QString dir = QFileInfo(moduleFullPath).absolutePath();
|
||||
QString candidate = dir + QStringLiteral("/") + pdbName;
|
||||
if (QFile::exists(candidate))
|
||||
return candidate;
|
||||
return {};
|
||||
}
|
||||
|
||||
void SymbolDownloader::download(const DownloadRequest& req) {
|
||||
// URL: https://msdl.microsoft.com/download/symbols/{pdbName}/{GUID}{age}/{pdbName}
|
||||
QString url = QStringLiteral("https://msdl.microsoft.com/download/symbols/%1/%2%3/%1")
|
||||
.arg(req.pdbName, req.guidString, QString::number(req.age, 16));
|
||||
|
||||
QUrl reqUrl(url);
|
||||
QNetworkRequest netReq(reqUrl);
|
||||
netReq.setHeader(QNetworkRequest::UserAgentHeader,
|
||||
QStringLiteral("Microsoft-Symbol-Server/10.0.0.0"));
|
||||
netReq.setAttribute(QNetworkRequest::RedirectPolicyAttribute,
|
||||
QNetworkRequest::NoLessSafeRedirectPolicy);
|
||||
|
||||
cancel(); // cancel any previous
|
||||
m_activeReply = m_nam->get(netReq);
|
||||
|
||||
QString moduleName = req.moduleName;
|
||||
QString pdbName = req.pdbName;
|
||||
QString guidString = req.guidString;
|
||||
uint32_t age = req.age;
|
||||
|
||||
connect(m_activeReply, &QNetworkReply::downloadProgress,
|
||||
this, [this, moduleName](qint64 received, qint64 total) {
|
||||
emit progress(moduleName, static_cast<int>(received), static_cast<int>(total));
|
||||
});
|
||||
|
||||
connect(m_activeReply, &QNetworkReply::finished,
|
||||
this, [this, moduleName, pdbName, guidString, age]() {
|
||||
auto* reply = m_activeReply;
|
||||
m_activeReply = nullptr;
|
||||
|
||||
if (!reply) return;
|
||||
reply->deleteLater();
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
emit finished(moduleName, {}, false,
|
||||
QStringLiteral("Download failed: %1").arg(reply->errorString()));
|
||||
return;
|
||||
}
|
||||
|
||||
int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
if (httpStatus != 200) {
|
||||
emit finished(moduleName, {}, false,
|
||||
QStringLiteral("HTTP %1").arg(httpStatus));
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray data = reply->readAll();
|
||||
if (data.isEmpty()) {
|
||||
emit finished(moduleName, {}, false, QStringLiteral("Empty response"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Save to cache
|
||||
QString dir = cacheDir() + QStringLiteral("/%1/%2%3")
|
||||
.arg(pdbName, guidString, QString::number(age, 16));
|
||||
QDir().mkpath(dir);
|
||||
QString path = dir + QStringLiteral("/") + pdbName;
|
||||
|
||||
QFile f(path);
|
||||
if (!f.open(QIODevice::WriteOnly)) {
|
||||
emit finished(moduleName, {}, false,
|
||||
QStringLiteral("Cannot write: %1").arg(f.errorString()));
|
||||
return;
|
||||
}
|
||||
f.write(data);
|
||||
f.close();
|
||||
|
||||
emit finished(moduleName, path, true, {});
|
||||
});
|
||||
}
|
||||
|
||||
void SymbolDownloader::cancel() {
|
||||
if (m_activeReply) {
|
||||
m_activeReply->abort();
|
||||
m_activeReply->deleteLater();
|
||||
m_activeReply = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rcx
|
||||
50
src/symbol_downloader.h
Normal file
50
src/symbol_downloader.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include <cstdint>
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
|
||||
namespace rcx {
|
||||
|
||||
class SymbolDownloader : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SymbolDownloader(QObject* parent = nullptr);
|
||||
|
||||
struct DownloadRequest {
|
||||
QString moduleName; // display name (e.g. "ntoskrnl.exe")
|
||||
QString pdbName; // PDB filename (e.g. "ntoskrnl.pdb")
|
||||
QString guidString; // 32 hex chars, no dashes
|
||||
uint32_t age = 0;
|
||||
};
|
||||
|
||||
// Check if PDB exists in local cache. Returns path or empty.
|
||||
QString findCached(const DownloadRequest& req) const;
|
||||
|
||||
// Check if PDB exists next to the module on disk. Returns path or empty.
|
||||
static QString findLocal(const QString& moduleFullPath, const QString& pdbName);
|
||||
|
||||
// Start downloading a PDB from MS symbol server.
|
||||
// Emits finished() when done (success or failure).
|
||||
void download(const DownloadRequest& req);
|
||||
|
||||
// Cancel any in-progress download.
|
||||
void cancel();
|
||||
|
||||
// Local symbol cache directory.
|
||||
static QString cacheDir();
|
||||
|
||||
signals:
|
||||
void progress(const QString& moduleName, int bytesReceived, int bytesTotal);
|
||||
void finished(const QString& moduleName, const QString& localPath,
|
||||
bool success, const QString& error);
|
||||
|
||||
private:
|
||||
QNetworkAccessManager* m_nam = nullptr;
|
||||
QNetworkReply* m_activeReply = nullptr;
|
||||
};
|
||||
|
||||
} // namespace rcx
|
||||
171
src/symbolstore.cpp
Normal file
171
src/symbolstore.cpp
Normal file
@@ -0,0 +1,171 @@
|
||||
#include "symbolstore.h"
|
||||
#include "providers/provider.h"
|
||||
#include <QDebug>
|
||||
|
||||
namespace rcx {
|
||||
|
||||
uint64_t SymbolStore::getModuleBase(const Provider* provider, const QString& canonical) const {
|
||||
if (!provider)
|
||||
return 0;
|
||||
uint64_t base = provider->symbolToAddress(canonical);
|
||||
if (base == 0)
|
||||
base = provider->symbolToAddress(canonical + QStringLiteral(".exe"));
|
||||
if (base == 0)
|
||||
base = provider->symbolToAddress(canonical + QStringLiteral(".dll"));
|
||||
if (base == 0)
|
||||
base = provider->symbolToAddress(canonical + QStringLiteral(".sys"));
|
||||
return base;
|
||||
}
|
||||
|
||||
int SymbolStore::addModule(const QString& moduleName, const QString& pdbPath,
|
||||
const QVector<QPair<QString, uint32_t>>& symbols) {
|
||||
QString canonical = resolveAlias(moduleName);
|
||||
|
||||
PdbSymbolSet set;
|
||||
set.pdbPath = pdbPath;
|
||||
set.moduleName = canonical;
|
||||
set.nameToRva.reserve(symbols.size());
|
||||
set.rvaToName.reserve(symbols.size());
|
||||
|
||||
for (const auto& sym : symbols) {
|
||||
if (set.nameToRva.contains(sym.first))
|
||||
continue;
|
||||
set.nameToRva.insert(sym.first, sym.second);
|
||||
set.rvaToName.append({sym.second, sym.first});
|
||||
}
|
||||
|
||||
set.sortRvaIndex();
|
||||
int count = set.nameToRva.size();
|
||||
|
||||
// Register the raw module name as an alias if it differs from canonical
|
||||
QString rawLower = moduleName.toLower();
|
||||
if (rawLower.endsWith(QStringLiteral(".exe")) || rawLower.endsWith(QStringLiteral(".dll")) ||
|
||||
rawLower.endsWith(QStringLiteral(".sys")))
|
||||
rawLower = rawLower.left(rawLower.lastIndexOf('.'));
|
||||
if (rawLower != canonical)
|
||||
m_aliases[rawLower] = canonical;
|
||||
|
||||
m_modules[canonical] = std::move(set);
|
||||
|
||||
qDebug() << "[SymbolStore] loaded" << count << "symbols for module" << canonical
|
||||
<< "(from" << pdbPath << ")";
|
||||
return count;
|
||||
}
|
||||
|
||||
void SymbolStore::unloadModule(const QString& moduleName) {
|
||||
QString canonical = resolveAlias(moduleName);
|
||||
m_modules.remove(canonical);
|
||||
}
|
||||
|
||||
uint64_t SymbolStore::resolve(const QString& token, const Provider* provider, bool* ok) const {
|
||||
*ok = false;
|
||||
|
||||
// Check for "module!symbol" syntax
|
||||
int bangIdx = token.indexOf('!');
|
||||
if (bangIdx > 0 && bangIdx < token.size() - 1) {
|
||||
QString modPart = token.left(bangIdx);
|
||||
QString symPart = token.mid(bangIdx + 1);
|
||||
QString canonical = resolveAlias(modPart);
|
||||
|
||||
auto modIt = m_modules.find(canonical);
|
||||
if (modIt == m_modules.end())
|
||||
return 0;
|
||||
|
||||
auto symIt = modIt->nameToRva.find(symPart);
|
||||
if (symIt == modIt->nameToRva.end())
|
||||
return 0;
|
||||
|
||||
uint32_t rva = *symIt;
|
||||
uint64_t moduleBase = getModuleBase(provider, canonical);
|
||||
// Also try the user-supplied module name form
|
||||
if (moduleBase == 0)
|
||||
moduleBase = getModuleBase(provider, modPart);
|
||||
|
||||
*ok = true;
|
||||
return moduleBase + rva;
|
||||
}
|
||||
|
||||
// Bare symbol — search all loaded modules
|
||||
uint32_t foundRva = 0;
|
||||
QString foundModule;
|
||||
int matches = 0;
|
||||
|
||||
for (auto it = m_modules.begin(); it != m_modules.end(); ++it) {
|
||||
auto symIt = it->nameToRva.find(token);
|
||||
if (symIt != it->nameToRva.end()) {
|
||||
foundRva = *symIt;
|
||||
foundModule = it.key();
|
||||
matches++;
|
||||
if (matches > 1)
|
||||
return 0; // ambiguous
|
||||
}
|
||||
}
|
||||
|
||||
if (matches == 1) {
|
||||
uint64_t moduleBase = getModuleBase(provider, foundModule);
|
||||
*ok = true;
|
||||
return moduleBase + foundRva;
|
||||
}
|
||||
|
||||
// Fallback: treat bare token as a module name (e.g. "ntdll" → ntdll base)
|
||||
if (matches == 0) {
|
||||
QString canonical = resolveAlias(token);
|
||||
uint64_t moduleBase = getModuleBase(provider, canonical);
|
||||
if (moduleBase != 0) {
|
||||
*ok = true;
|
||||
return moduleBase;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
QString SymbolStore::getSymbolForAddress(uint64_t addr, const Provider* provider) const {
|
||||
if (m_modules.isEmpty() || !provider)
|
||||
return {};
|
||||
|
||||
for (auto it = m_modules.begin(); it != m_modules.end(); ++it) {
|
||||
const PdbSymbolSet& set = *it;
|
||||
|
||||
uint64_t moduleBase = getModuleBase(provider, set.moduleName);
|
||||
if (moduleBase == 0)
|
||||
continue;
|
||||
|
||||
if (addr < moduleBase)
|
||||
continue;
|
||||
|
||||
uint32_t rva = static_cast<uint32_t>(addr - moduleBase);
|
||||
|
||||
if (set.rvaToName.isEmpty())
|
||||
continue;
|
||||
|
||||
// Binary search: find last entry with RVA <= target
|
||||
auto upper = std::upper_bound(set.rvaToName.begin(), set.rvaToName.end(), rva,
|
||||
[](uint32_t val, const QPair<uint32_t, QString>& entry) {
|
||||
return val < entry.first;
|
||||
});
|
||||
|
||||
if (upper == set.rvaToName.begin())
|
||||
continue;
|
||||
|
||||
--upper;
|
||||
uint32_t displacement = rva - upper->first;
|
||||
|
||||
static constexpr uint32_t kMaxDisplacement = 0x1000;
|
||||
if (displacement > kMaxDisplacement)
|
||||
continue;
|
||||
|
||||
if (displacement == 0)
|
||||
return set.moduleName + QStringLiteral("!") + upper->second;
|
||||
return set.moduleName + QStringLiteral("!") + upper->second
|
||||
+ QStringLiteral("+0x") + QString::number(displacement, 16);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void SymbolStore::addAlias(const QString& alias, const QString& canonicalModule) {
|
||||
m_aliases[alias.toLower()] = canonicalModule.toLower();
|
||||
}
|
||||
|
||||
} // namespace rcx
|
||||
95
src/symbolstore.h
Normal file
95
src/symbolstore.h
Normal file
@@ -0,0 +1,95 @@
|
||||
#pragma once
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QHash>
|
||||
#include <QVector>
|
||||
#include <QPair>
|
||||
#include <algorithm>
|
||||
|
||||
namespace rcx {
|
||||
|
||||
class Provider; // forward declaration
|
||||
|
||||
struct PdbSymbolSet {
|
||||
QString pdbPath;
|
||||
QString moduleName; // canonical lowercase name (e.g. "ntoskrnl")
|
||||
QHash<QString, uint32_t> nameToRva;
|
||||
QVector<QPair<uint32_t, QString>> rvaToName; // sorted by RVA for binary search
|
||||
|
||||
void sortRvaIndex() {
|
||||
std::sort(rvaToName.begin(), rvaToName.end(),
|
||||
[](const auto& a, const auto& b) { return a.first < b.first; });
|
||||
}
|
||||
};
|
||||
|
||||
class SymbolStore {
|
||||
public:
|
||||
static SymbolStore& instance() {
|
||||
static SymbolStore s;
|
||||
return s;
|
||||
}
|
||||
|
||||
// Add a pre-extracted symbol set for a module.
|
||||
// moduleName is the canonical name (e.g. "ntoskrnl").
|
||||
// Returns the number of unique symbols stored.
|
||||
int addModule(const QString& moduleName, const QString& pdbPath,
|
||||
const QVector<QPair<QString, uint32_t>>& symbols);
|
||||
|
||||
// Unload symbols for a module.
|
||||
void unloadModule(const QString& moduleName);
|
||||
|
||||
// Resolve a token from the expression parser.
|
||||
// Handles "module!symbol" (qualified) and bare "symbol" (unqualified).
|
||||
// Uses provider->symbolToAddress() to get the module's runtime base address.
|
||||
uint64_t resolve(const QString& token, const Provider* provider, bool* ok) const;
|
||||
|
||||
// Reverse lookup: given an absolute address and a provider, find the nearest symbol.
|
||||
// Returns "module!symbol" or "module!symbol+0xN", or empty if no match.
|
||||
QString getSymbolForAddress(uint64_t addr, const Provider* provider) const;
|
||||
|
||||
// Check if any symbols are loaded.
|
||||
bool hasSymbols() const { return !m_modules.isEmpty(); }
|
||||
|
||||
// List loaded module names.
|
||||
QStringList loadedModules() const { return m_modules.keys(); }
|
||||
|
||||
// Number of loaded modules.
|
||||
int moduleCount() const { return m_modules.size(); }
|
||||
|
||||
// Access module data by name (returns nullptr if not found).
|
||||
const PdbSymbolSet* moduleData(const QString& moduleName) const {
|
||||
QString canonical = resolveAlias(moduleName);
|
||||
auto it = m_modules.find(canonical);
|
||||
return it != m_modules.end() ? &*it : nullptr;
|
||||
}
|
||||
|
||||
// Add a module alias (e.g. "nt" → "ntoskrnl").
|
||||
void addAlias(const QString& alias, const QString& canonicalModule);
|
||||
|
||||
// Resolve alias to canonical module name (public for callers that need it)
|
||||
QString resolveAlias(const QString& name) const {
|
||||
QString lower = name.toLower();
|
||||
if (lower.endsWith(QStringLiteral(".exe")) || lower.endsWith(QStringLiteral(".dll")) ||
|
||||
lower.endsWith(QStringLiteral(".sys")))
|
||||
lower = lower.left(lower.lastIndexOf('.'));
|
||||
auto it = m_aliases.find(lower);
|
||||
return it != m_aliases.end() ? *it : lower;
|
||||
}
|
||||
|
||||
private:
|
||||
SymbolStore() {
|
||||
// Common Windows kernel aliases
|
||||
m_aliases[QStringLiteral("nt")] = QStringLiteral("ntoskrnl");
|
||||
m_aliases[QStringLiteral("ntkrnlmp")] = QStringLiteral("ntoskrnl");
|
||||
m_aliases[QStringLiteral("ntkrnlpa")] = QStringLiteral("ntoskrnl");
|
||||
m_aliases[QStringLiteral("ntkrpamp")] = QStringLiteral("ntoskrnl");
|
||||
}
|
||||
|
||||
// Get the module base address, trying various name forms
|
||||
uint64_t getModuleBase(const Provider* provider, const QString& canonical) const;
|
||||
|
||||
QHash<QString, PdbSymbolSet> m_modules; // canonical lowercase name → symbol set
|
||||
QHash<QString, QString> m_aliases; // alias → canonical name
|
||||
};
|
||||
|
||||
} // namespace rcx
|
||||
Reference in New Issue
Block a user