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:
IChooseYou
2026-03-14 08:13:58 -06:00
committed by IChooseYou
parent 5b2cf1ae1f
commit 5d2d324946
7 changed files with 664 additions and 6 deletions

View File

@@ -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}

View 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

View 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
View 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
View 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
View 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
View 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