mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
- Enum inline editing: name/value commit handling, auto-sort by value - Bitfield support in PDB import with proper container nodes - Per-member hover/selection highlighting (kMemberBit encoding) - Context menu fixes for enum/bitfield member lines - MCP pagination (limit/offset), includeMembers param, tree.search tool - MCP status bar activity indicator for tool calls - PDB anonymous type inlining: inline <unnamed-tag> types as children - Skip anonymous pointer targets to prevent root orphans - Enum import diagnostics for debugging missing enums
1148 lines
41 KiB
C++
1148 lines
41 KiB
C++
#include "import_pdb.h"
|
|
|
|
#ifdef _WIN32
|
|
|
|
#include <windows.h>
|
|
#include <QFile>
|
|
#include <QHash>
|
|
#include <QPair>
|
|
#include <QSet>
|
|
#include <QDebug>
|
|
|
|
// ── RawPDB headers ──
|
|
#include "PDB.h"
|
|
#include "PDB_RawFile.h"
|
|
#include "PDB_TPIStream.h"
|
|
#include "PDB_TPITypes.h"
|
|
#include "PDB_DBIStream.h"
|
|
#include "PDB_InfoStream.h"
|
|
#include "PDB_CoalescedMSFStream.h"
|
|
#include "Foundation/PDB_Memory.h"
|
|
|
|
namespace rcx {
|
|
|
|
// ── Memory-mapped file (mirrors ExampleMemoryMappedFile) ──
|
|
|
|
struct MappedFile {
|
|
HANDLE hFile = INVALID_HANDLE_VALUE;
|
|
HANDLE hMapping = nullptr;
|
|
const void* base = nullptr;
|
|
size_t size = 0;
|
|
|
|
bool open(const QString& path) {
|
|
hFile = CreateFileW(reinterpret_cast<const wchar_t*>(path.utf16()),
|
|
GENERIC_READ, FILE_SHARE_READ, nullptr,
|
|
OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, nullptr);
|
|
if (hFile == INVALID_HANDLE_VALUE) return false;
|
|
|
|
hMapping = CreateFileMappingW(hFile, nullptr, PAGE_READONLY, 0, 0, nullptr);
|
|
if (!hMapping) { close(); return false; }
|
|
|
|
base = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0);
|
|
if (!base) { close(); return false; }
|
|
|
|
BY_HANDLE_FILE_INFORMATION info;
|
|
if (!GetFileInformationByHandle(hFile, &info)) { close(); return false; }
|
|
size = (static_cast<size_t>(info.nFileSizeHigh) << 32) | info.nFileSizeLow;
|
|
return true;
|
|
}
|
|
|
|
void close() {
|
|
if (base) { UnmapViewOfFile(base); base = nullptr; }
|
|
if (hMapping) { CloseHandle(hMapping); hMapping = nullptr; }
|
|
if (hFile != INVALID_HANDLE_VALUE) { CloseHandle(hFile); hFile = INVALID_HANDLE_VALUE; }
|
|
size = 0;
|
|
}
|
|
|
|
~MappedFile() { close(); }
|
|
MappedFile() = default;
|
|
MappedFile(const MappedFile&) = delete;
|
|
MappedFile& operator=(const MappedFile&) = delete;
|
|
};
|
|
|
|
// ── TypeTable (mirrors ExampleTypeTable) ──
|
|
// Builds an O(1) lookup table from type index → record pointer.
|
|
|
|
class TypeTable {
|
|
public:
|
|
explicit TypeTable(const PDB::TPIStream& tpiStream) {
|
|
m_firstIndex = tpiStream.GetFirstTypeIndex();
|
|
m_lastIndex = tpiStream.GetLastTypeIndex();
|
|
m_count = tpiStream.GetTypeRecordCount();
|
|
|
|
const PDB::DirectMSFStream& ds = tpiStream.GetDirectMSFStream();
|
|
m_stream = PDB::CoalescedMSFStream(ds, ds.GetSize(), 0u);
|
|
|
|
m_records = PDB_NEW_ARRAY(const PDB::CodeView::TPI::Record*, m_count);
|
|
uint32_t idx = 0;
|
|
tpiStream.ForEachTypeRecordHeaderAndOffset(
|
|
[this, &idx](const PDB::CodeView::TPI::RecordHeader&, size_t offset) {
|
|
m_records[idx++] = m_stream.GetDataAtOffset<const PDB::CodeView::TPI::Record>(offset);
|
|
});
|
|
}
|
|
|
|
~TypeTable() { PDB_DELETE_ARRAY(m_records); }
|
|
|
|
uint32_t firstIndex() const { return m_firstIndex; }
|
|
uint32_t lastIndex() const { return m_lastIndex; }
|
|
size_t count() const { return m_count; }
|
|
|
|
const PDB::CodeView::TPI::Record* get(uint32_t typeIndex) const {
|
|
if (typeIndex < m_firstIndex || typeIndex >= m_lastIndex) return nullptr;
|
|
return m_records[typeIndex - m_firstIndex];
|
|
}
|
|
|
|
private:
|
|
uint32_t m_firstIndex = 0;
|
|
uint32_t m_lastIndex = 0;
|
|
size_t m_count = 0;
|
|
const PDB::CodeView::TPI::Record** m_records = nullptr;
|
|
PDB::CoalescedMSFStream m_stream;
|
|
|
|
TypeTable(const TypeTable&) = delete;
|
|
TypeTable& operator=(const TypeTable&) = delete;
|
|
};
|
|
|
|
// ── Leaf numeric helpers (variable-length integer encoding) ──
|
|
|
|
using TRK = PDB::CodeView::TPI::TypeRecordKind;
|
|
|
|
static uint8_t leafSize(TRK kind) {
|
|
if (kind < TRK::LF_NUMERIC) return sizeof(TRK); // value is the kind itself
|
|
switch (kind) {
|
|
case TRK::LF_CHAR: return sizeof(TRK) + sizeof(uint8_t);
|
|
case TRK::LF_SHORT:
|
|
case TRK::LF_USHORT: return sizeof(TRK) + sizeof(uint16_t);
|
|
case TRK::LF_LONG:
|
|
case TRK::LF_ULONG: return sizeof(TRK) + sizeof(uint32_t);
|
|
case TRK::LF_QUADWORD:
|
|
case TRK::LF_UQUADWORD: return sizeof(TRK) + sizeof(uint64_t);
|
|
default: return sizeof(TRK);
|
|
}
|
|
}
|
|
|
|
static const char* leafName(const char* data, TRK kind) {
|
|
return data + leafSize(kind);
|
|
}
|
|
|
|
static uint64_t leafValue(const char* data, TRK kind) {
|
|
if (kind < TRK::LF_NUMERIC) {
|
|
return static_cast<uint16_t>(kind);
|
|
}
|
|
const char* p = data + sizeof(TRK);
|
|
switch (kind) {
|
|
case TRK::LF_CHAR: return *reinterpret_cast<const uint8_t*>(p);
|
|
case TRK::LF_SHORT: return *reinterpret_cast<const int16_t*>(p);
|
|
case TRK::LF_USHORT: return *reinterpret_cast<const uint16_t*>(p);
|
|
case TRK::LF_LONG: return *reinterpret_cast<const int32_t*>(p);
|
|
case TRK::LF_ULONG: return *reinterpret_cast<const uint32_t*>(p);
|
|
case TRK::LF_QUADWORD: return *reinterpret_cast<const int64_t*>(p);
|
|
case TRK::LF_UQUADWORD: return *reinterpret_cast<const uint64_t*>(p);
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
// ── Primitive type index mapping (< 0x1000) ──
|
|
|
|
static NodeKind mapPrimitiveType(uint32_t typeIndex) {
|
|
uint32_t base = typeIndex & 0xFF;
|
|
switch (base) {
|
|
// void
|
|
case 0x03: return NodeKind::Hex8;
|
|
// signed char
|
|
case 0x10: return NodeKind::Int8;
|
|
// unsigned char
|
|
case 0x20: return NodeKind::UInt8;
|
|
// real char
|
|
case 0x70: return NodeKind::Int8;
|
|
// wchar
|
|
case 0x71: return NodeKind::UInt16;
|
|
// char8
|
|
case 0x7c: return NodeKind::UInt8;
|
|
// char16
|
|
case 0x7a: return NodeKind::UInt16;
|
|
// char32
|
|
case 0x7b: return NodeKind::UInt32;
|
|
// short
|
|
case 0x11: return NodeKind::Int16;
|
|
// ushort
|
|
case 0x21: return NodeKind::UInt16;
|
|
// long
|
|
case 0x12: return NodeKind::Int32;
|
|
// ulong
|
|
case 0x22: return NodeKind::UInt32;
|
|
// int8
|
|
case 0x68: return NodeKind::Int8;
|
|
// uint8
|
|
case 0x69: return NodeKind::UInt8;
|
|
// int16
|
|
case 0x72: return NodeKind::Int16;
|
|
// uint16
|
|
case 0x73: return NodeKind::UInt16;
|
|
// int32
|
|
case 0x74: return NodeKind::Int32;
|
|
// uint32
|
|
case 0x75: return NodeKind::UInt32;
|
|
// quad (int64)
|
|
case 0x13: return NodeKind::Int64;
|
|
// uquad (uint64)
|
|
case 0x23: return NodeKind::UInt64;
|
|
// int64
|
|
case 0x76: return NodeKind::Int64;
|
|
// uint64
|
|
case 0x77: return NodeKind::UInt64;
|
|
// float
|
|
case 0x40: return NodeKind::Float;
|
|
// double
|
|
case 0x41: return NodeKind::Double;
|
|
// bool
|
|
case 0x30: return NodeKind::Bool;
|
|
case 0x31: return NodeKind::UInt16; // bool16
|
|
case 0x32: return NodeKind::UInt32; // bool32
|
|
case 0x33: return NodeKind::UInt64; // bool64
|
|
// HRESULT
|
|
case 0x08: return NodeKind::UInt32;
|
|
// bit
|
|
case 0x60: return NodeKind::UInt8;
|
|
// int128 / uint128 approximation
|
|
case 0x78: return NodeKind::Hex64; // int128 → Hex64 (best we can do)
|
|
case 0x79: return NodeKind::Hex64; // uint128
|
|
default: return NodeKind::Hex32;
|
|
}
|
|
}
|
|
|
|
static NodeKind hexForSize(uint64_t len) {
|
|
switch (len) {
|
|
case 1: return NodeKind::Hex8;
|
|
case 2: return NodeKind::Hex16;
|
|
case 4: return NodeKind::Hex32;
|
|
case 8: return NodeKind::Hex64;
|
|
default: return NodeKind::Hex32;
|
|
}
|
|
}
|
|
|
|
// ── Helper: read the leaf kind from the start of LF_UNION.data ──
|
|
// (LF_UNION lacks the lfEasy member that LF_CLASS has)
|
|
static TRK unionLeafKind(const char* data) {
|
|
return *reinterpret_cast<const TRK*>(data);
|
|
}
|
|
|
|
// ── Import context ──
|
|
|
|
struct PdbCtx {
|
|
NodeTree tree;
|
|
const TypeTable* tt = nullptr;
|
|
QHash<uint32_t, uint64_t> typeCache; // typeIndex → nodeId
|
|
QHash<QString, uint32_t> structDefByName; // struct/class definition name → typeIndex
|
|
QHash<QString, uint32_t> unionDefByName; // union definition name → typeIndex
|
|
bool udtDefIndexBuilt = false;
|
|
|
|
uint64_t importUDT(uint32_t typeIndex);
|
|
uint64_t importEnum(uint32_t typeIndex);
|
|
void importFieldList(uint32_t fieldListIndex, uint64_t parentId);
|
|
void importMemberType(uint32_t typeIndex, int offset, const QString& name, uint64_t parentId);
|
|
void buildUdtDefinitionIndex();
|
|
uint32_t findUdtDefinitionIndex(TRK kind, const char* typeName);
|
|
|
|
// Resolve LF_MODIFIER chain to underlying type index
|
|
uint32_t unwrapModifier(uint32_t typeIndex) const {
|
|
if (typeIndex < tt->firstIndex()) return typeIndex;
|
|
const auto* rec = tt->get(typeIndex);
|
|
if (!rec) return typeIndex;
|
|
if (rec->header.kind == TRK::LF_MODIFIER)
|
|
return rec->data.LF_MODIFIER.type;
|
|
return typeIndex;
|
|
}
|
|
};
|
|
|
|
void PdbCtx::buildUdtDefinitionIndex() {
|
|
if (udtDefIndexBuilt || !tt) return;
|
|
udtDefIndexBuilt = true;
|
|
|
|
for (uint32_t ti = tt->firstIndex(); ti < tt->lastIndex(); ti++) {
|
|
const auto* rec = tt->get(ti);
|
|
if (!rec) continue;
|
|
|
|
bool isUnion = false;
|
|
bool isFwd = false;
|
|
const char* candidateName = nullptr;
|
|
|
|
if (rec->header.kind == TRK::LF_UNION) {
|
|
isUnion = true;
|
|
isFwd = rec->data.LF_UNION.property.fwdref;
|
|
candidateName = leafName(rec->data.LF_UNION.data, unionLeafKind(rec->data.LF_UNION.data));
|
|
} else if (rec->header.kind == TRK::LF_STRUCTURE || rec->header.kind == TRK::LF_CLASS) {
|
|
isFwd = rec->data.LF_CLASS.property.fwdref;
|
|
candidateName = leafName(rec->data.LF_CLASS.data, rec->data.LF_CLASS.lfEasy.kind);
|
|
} else {
|
|
continue;
|
|
}
|
|
|
|
if (isFwd || !candidateName || candidateName[0] == '\0') continue;
|
|
|
|
QString qname = QString::fromUtf8(candidateName);
|
|
QHash<QString, uint32_t>& lookup = isUnion ? unionDefByName : structDefByName;
|
|
if (!lookup.contains(qname)) lookup.insert(qname, ti);
|
|
}
|
|
}
|
|
|
|
uint32_t PdbCtx::findUdtDefinitionIndex(TRK kind, const char* typeName) {
|
|
if (!typeName || typeName[0] == '\0') return 0;
|
|
|
|
buildUdtDefinitionIndex();
|
|
|
|
const QString qname = QString::fromUtf8(typeName);
|
|
if (kind == TRK::LF_UNION) {
|
|
auto it = unionDefByName.constFind(qname);
|
|
return (it != unionDefByName.cend()) ? it.value() : 0;
|
|
}
|
|
|
|
if (kind == TRK::LF_STRUCTURE || kind == TRK::LF_CLASS) {
|
|
auto it = structDefByName.constFind(qname);
|
|
return (it != structDefByName.cend()) ? it.value() : 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint64_t PdbCtx::importUDT(uint32_t typeIndex) {
|
|
if (typeIndex < tt->firstIndex()) return 0;
|
|
|
|
auto it = typeCache.find(typeIndex);
|
|
if (it != typeCache.end()) return it.value();
|
|
|
|
const auto* rec = tt->get(typeIndex);
|
|
if (!rec) return 0;
|
|
|
|
const char* name = nullptr;
|
|
uint32_t fieldListIndex = 0;
|
|
uint16_t fieldCount = 0;
|
|
bool isUnion = false;
|
|
const char* sizeData = nullptr;
|
|
|
|
if (rec->header.kind == TRK::LF_STRUCTURE || rec->header.kind == TRK::LF_CLASS) {
|
|
// Skip forward references — find the definition
|
|
if (rec->data.LF_CLASS.property.fwdref) return 0;
|
|
fieldCount = rec->data.LF_CLASS.count;
|
|
fieldListIndex = rec->data.LF_CLASS.field;
|
|
sizeData = rec->data.LF_CLASS.data;
|
|
name = leafName(sizeData, rec->data.LF_CLASS.lfEasy.kind);
|
|
} else if (rec->header.kind == TRK::LF_UNION) {
|
|
if (rec->data.LF_UNION.property.fwdref) return 0;
|
|
isUnion = true;
|
|
fieldCount = rec->data.LF_UNION.count;
|
|
fieldListIndex = rec->data.LF_UNION.field;
|
|
sizeData = rec->data.LF_UNION.data;
|
|
name = leafName(sizeData, unionLeafKind(sizeData));
|
|
} else {
|
|
return 0;
|
|
}
|
|
(void)fieldCount;
|
|
|
|
QString qname = name ? QString::fromUtf8(name) : QStringLiteral("<anon>");
|
|
|
|
Node s;
|
|
s.kind = NodeKind::Struct;
|
|
s.name = qname;
|
|
s.structTypeName = qname;
|
|
s.classKeyword = isUnion ? QStringLiteral("union") : QStringLiteral("struct");
|
|
s.parentId = 0;
|
|
s.collapsed = true;
|
|
int idx = tree.addNode(s);
|
|
uint64_t nodeId = tree.nodes[idx].id;
|
|
|
|
typeCache[typeIndex] = nodeId;
|
|
|
|
importFieldList(fieldListIndex, nodeId);
|
|
return nodeId;
|
|
}
|
|
|
|
uint64_t PdbCtx::importEnum(uint32_t typeIndex) {
|
|
if (typeIndex < tt->firstIndex()) return 0;
|
|
|
|
auto it = typeCache.find(typeIndex);
|
|
if (it != typeCache.end()) return it.value();
|
|
|
|
const auto* rec = tt->get(typeIndex);
|
|
if (!rec || rec->header.kind != TRK::LF_ENUM) return 0;
|
|
if (rec->data.LF_ENUM.property.fwdref) return 0;
|
|
|
|
QString qname = rec->data.LF_ENUM.name
|
|
? QString::fromUtf8(rec->data.LF_ENUM.name)
|
|
: QStringLiteral("<anon>");
|
|
|
|
Node s;
|
|
s.kind = NodeKind::Struct;
|
|
s.name = qname;
|
|
s.structTypeName = qname;
|
|
s.classKeyword = QStringLiteral("enum");
|
|
s.parentId = 0;
|
|
s.collapsed = true;
|
|
|
|
// Extract enum members from field list
|
|
uint32_t fieldListIndex = rec->data.LF_ENUM.field;
|
|
const auto* flRec = tt->get(fieldListIndex);
|
|
if (flRec && flRec->header.kind == TRK::LF_FIELDLIST) {
|
|
auto maxSize = flRec->header.size - sizeof(uint16_t);
|
|
for (size_t i = 0; i < maxSize; ) {
|
|
auto* field = reinterpret_cast<const PDB::CodeView::TPI::FieldList*>(
|
|
reinterpret_cast<const uint8_t*>(&flRec->data.LF_FIELD.list) + i);
|
|
if (field->kind != TRK::LF_ENUMERATE) break;
|
|
|
|
int64_t val = static_cast<int64_t>(leafValue(
|
|
field->data.LF_ENUMERATE.value,
|
|
field->data.LF_ENUMERATE.lfEasy.kind));
|
|
const char* eName = leafName(
|
|
field->data.LF_ENUMERATE.value,
|
|
field->data.LF_ENUMERATE.lfEasy.kind);
|
|
if (eName)
|
|
s.enumMembers.append({QString::fromUtf8(eName), val});
|
|
|
|
i += static_cast<size_t>(eName - reinterpret_cast<const char*>(field));
|
|
i += strnlen(eName, maxSize - i - 1) + 1;
|
|
i = (i + 3) & ~size_t(3);
|
|
}
|
|
}
|
|
|
|
int idx = tree.addNode(s);
|
|
uint64_t nodeId = tree.nodes[idx].id;
|
|
typeCache[typeIndex] = nodeId;
|
|
return nodeId;
|
|
}
|
|
|
|
void PdbCtx::importFieldList(uint32_t fieldListIndex, uint64_t parentId) {
|
|
const auto* rec = tt->get(fieldListIndex);
|
|
if (!rec || rec->header.kind != TRK::LF_FIELDLIST) return;
|
|
|
|
auto maximumSize = rec->header.size - sizeof(uint16_t);
|
|
QSet<QPair<int,int>> bitfieldSlots;
|
|
QHash<QPair<int,int>, uint64_t> bitfieldNodeIds;
|
|
|
|
for (size_t i = 0; i < maximumSize; ) {
|
|
auto* field = reinterpret_cast<const PDB::CodeView::TPI::FieldList*>(
|
|
reinterpret_cast<const uint8_t*>(&rec->data.LF_FIELD.list) + i);
|
|
|
|
if (field->kind == TRK::LF_MEMBER) {
|
|
// Extract offset from variable-length leaf
|
|
uint16_t offset = 0;
|
|
if (field->data.LF_MEMBER.lfEasy.kind < TRK::LF_NUMERIC)
|
|
offset = *reinterpret_cast<const uint16_t*>(field->data.LF_MEMBER.offset);
|
|
else
|
|
offset = static_cast<uint16_t>(leafValue(field->data.LF_MEMBER.offset,
|
|
field->data.LF_MEMBER.lfEasy.kind));
|
|
|
|
const char* memberName = leafName(field->data.LF_MEMBER.offset,
|
|
field->data.LF_MEMBER.lfEasy.kind);
|
|
uint32_t memberType = field->data.LF_MEMBER.index;
|
|
QString qname = memberName ? QString::fromUtf8(memberName) : QString();
|
|
|
|
// Check for bitfield type
|
|
uint32_t resolvedType = unwrapModifier(memberType);
|
|
const auto* typeRec = tt->get(resolvedType);
|
|
if (typeRec && typeRec->header.kind == TRK::LF_BITFIELD) {
|
|
uint32_t underlying = typeRec->data.LF_BITFIELD.type;
|
|
uint8_t bitLen = typeRec->data.LF_BITFIELD.length;
|
|
uint8_t bitPos = typeRec->data.LF_BITFIELD.position;
|
|
|
|
// Determine slot size from underlying type
|
|
uint64_t slotSize = 4;
|
|
if (underlying < tt->firstIndex()) {
|
|
NodeKind k = mapPrimitiveType(underlying);
|
|
slotSize = sizeForKind(k);
|
|
}
|
|
|
|
auto key = qMakePair((int)offset, (int)slotSize);
|
|
if (!bitfieldSlots.contains(key)) {
|
|
bitfieldSlots.insert(key);
|
|
// Create bitfield container node
|
|
Node n;
|
|
n.kind = NodeKind::Struct;
|
|
n.classKeyword = QStringLiteral("bitfield");
|
|
n.elementKind = hexForSize(slotSize);
|
|
n.parentId = parentId;
|
|
n.offset = offset;
|
|
n.collapsed = false;
|
|
int idx = tree.addNode(n);
|
|
bitfieldNodeIds[key] = tree.nodes[idx].id;
|
|
}
|
|
// Add this member to the bitfield container
|
|
uint64_t bfNodeId = bitfieldNodeIds[key];
|
|
int bfIdx = tree.indexOfId(bfNodeId);
|
|
if (bfIdx >= 0) {
|
|
BitfieldMember bm;
|
|
bm.name = qname;
|
|
bm.bitOffset = bitPos;
|
|
bm.bitWidth = bitLen;
|
|
tree.nodes[bfIdx].bitfieldMembers.append(bm);
|
|
}
|
|
} else {
|
|
importMemberType(memberType, offset, qname, parentId);
|
|
}
|
|
|
|
// Advance past this LF_MEMBER
|
|
i += static_cast<size_t>(memberName - reinterpret_cast<const char*>(field));
|
|
i += strnlen(memberName, maximumSize - i - 1) + 1;
|
|
i = (i + 3) & ~size_t(3); // align to 4
|
|
}
|
|
else if (field->kind == TRK::LF_BCLASS) {
|
|
const char* leafEnd = leafName(field->data.LF_BCLASS.offset,
|
|
field->data.LF_BCLASS.lfEasy.kind);
|
|
i += static_cast<size_t>(leafEnd - reinterpret_cast<const char*>(field));
|
|
i = (i + 3) & ~size_t(3);
|
|
}
|
|
else if (field->kind == TRK::LF_VBCLASS || field->kind == TRK::LF_IVBCLASS) {
|
|
TRK vbpKind = *reinterpret_cast<const TRK*>(field->data.LF_IVBCLASS.vbpOffset);
|
|
uint8_t vbpSize1 = leafSize(vbpKind);
|
|
TRK vbtKind = *reinterpret_cast<const TRK*>(field->data.LF_IVBCLASS.vbpOffset + vbpSize1);
|
|
uint8_t vbpSize2 = leafSize(vbtKind);
|
|
i += sizeof(PDB::CodeView::TPI::FieldList::Data::LF_VBCLASS) + vbpSize1 + vbpSize2;
|
|
i = (i + 3) & ~size_t(3);
|
|
}
|
|
else if (field->kind == TRK::LF_INDEX) {
|
|
// Continuation of field list in another record
|
|
importFieldList(field->data.LF_INDEX.type, parentId);
|
|
i += sizeof(PDB::CodeView::TPI::FieldList::Data::LF_INDEX);
|
|
i = (i + 3) & ~size_t(3);
|
|
}
|
|
else if (field->kind == TRK::LF_VFUNCTAB) {
|
|
i += sizeof(PDB::CodeView::TPI::FieldList::Data::LF_VFUNCTAB);
|
|
i = (i + 3) & ~size_t(3);
|
|
}
|
|
else if (field->kind == TRK::LF_NESTTYPE) {
|
|
const char* nestName = field->data.LF_NESTTYPE.name;
|
|
i += static_cast<size_t>(nestName - reinterpret_cast<const char*>(field));
|
|
i += strnlen(nestName, maximumSize - i - 1) + 1;
|
|
i = (i + 3) & ~size_t(3);
|
|
}
|
|
else if (field->kind == TRK::LF_STMEMBER) {
|
|
const char* smName = field->data.LF_STMEMBER.name;
|
|
i += static_cast<size_t>(smName - reinterpret_cast<const char*>(field));
|
|
i += strnlen(smName, maximumSize - i - 1) + 1;
|
|
i = (i + 3) & ~size_t(3);
|
|
}
|
|
else if (field->kind == TRK::LF_METHOD) {
|
|
const char* mName = field->data.LF_METHOD.name;
|
|
i += static_cast<size_t>(mName - reinterpret_cast<const char*>(field));
|
|
i += strnlen(mName, maximumSize - i - 1) + 1;
|
|
i = (i + 3) & ~size_t(3);
|
|
}
|
|
else if (field->kind == TRK::LF_ONEMETHOD) {
|
|
// Determine if it has a vbaseoff field
|
|
auto prop = static_cast<PDB::CodeView::TPI::MethodProperty>(
|
|
field->data.LF_ONEMETHOD.attributes.mprop);
|
|
const char* mName;
|
|
if (prop == PDB::CodeView::TPI::MethodProperty::Intro ||
|
|
prop == PDB::CodeView::TPI::MethodProperty::PureIntro)
|
|
mName = reinterpret_cast<const char*>(field->data.LF_ONEMETHOD.vbaseoff) + sizeof(uint32_t);
|
|
else
|
|
mName = reinterpret_cast<const char*>(field->data.LF_ONEMETHOD.vbaseoff);
|
|
|
|
i += static_cast<size_t>(mName - reinterpret_cast<const char*>(field));
|
|
i += strnlen(mName, maximumSize - i - 1) + 1;
|
|
i = (i + 3) & ~size_t(3);
|
|
}
|
|
else if (field->kind == TRK::LF_ENUMERATE) {
|
|
const char* eName = leafName(field->data.LF_ENUMERATE.value,
|
|
field->data.LF_ENUMERATE.lfEasy.kind);
|
|
i += static_cast<size_t>(eName - reinterpret_cast<const char*>(field));
|
|
i += strnlen(eName, maximumSize - i - 1) + 1;
|
|
i = (i + 3) & ~size_t(3);
|
|
}
|
|
else {
|
|
break; // unknown field kind, stop
|
|
}
|
|
}
|
|
}
|
|
|
|
void PdbCtx::importMemberType(uint32_t typeIndex, int offset, const QString& name, uint64_t parentId) {
|
|
// Handle primitive type indices (< 0x1000)
|
|
if (typeIndex < tt->firstIndex()) {
|
|
uint32_t ptrMode = (typeIndex >> 8) & 0xF;
|
|
if (ptrMode == 0x04 || ptrMode == 0x05) {
|
|
// 32-bit pointer to a base type
|
|
Node n;
|
|
n.kind = NodeKind::Pointer32;
|
|
n.name = name;
|
|
n.parentId = parentId;
|
|
n.offset = offset;
|
|
n.collapsed = true;
|
|
tree.addNode(n);
|
|
return;
|
|
}
|
|
if (ptrMode == 0x06) {
|
|
// 64-bit pointer to a base type
|
|
Node n;
|
|
n.kind = NodeKind::Pointer64;
|
|
n.name = name;
|
|
n.parentId = parentId;
|
|
n.offset = offset;
|
|
n.collapsed = true;
|
|
tree.addNode(n);
|
|
return;
|
|
}
|
|
if (ptrMode != 0x00) {
|
|
// Some other pointer mode (near, far, huge) — treat as 32-bit
|
|
Node n;
|
|
n.kind = NodeKind::Pointer32;
|
|
n.name = name;
|
|
n.parentId = parentId;
|
|
n.offset = offset;
|
|
n.collapsed = true;
|
|
tree.addNode(n);
|
|
return;
|
|
}
|
|
// Direct base type
|
|
Node n;
|
|
n.kind = mapPrimitiveType(typeIndex);
|
|
n.name = name;
|
|
n.parentId = parentId;
|
|
n.offset = offset;
|
|
tree.addNode(n);
|
|
return;
|
|
}
|
|
|
|
const auto* rec = tt->get(typeIndex);
|
|
if (!rec) {
|
|
Node n;
|
|
n.kind = NodeKind::Hex32;
|
|
n.name = name;
|
|
n.parentId = parentId;
|
|
n.offset = offset;
|
|
tree.addNode(n);
|
|
return;
|
|
}
|
|
|
|
switch (rec->header.kind) {
|
|
case TRK::LF_MODIFIER:
|
|
importMemberType(rec->data.LF_MODIFIER.type, offset, name, parentId);
|
|
break;
|
|
|
|
case TRK::LF_POINTER: {
|
|
uint32_t ptrSize = rec->data.LF_POINTER.attr.size;
|
|
uint32_t pointee = rec->data.LF_POINTER.utype;
|
|
|
|
// Unwrap modifier on pointee
|
|
uint32_t realPointee = unwrapModifier(pointee);
|
|
|
|
Node n;
|
|
n.kind = (ptrSize <= 4) ? NodeKind::Pointer32 : NodeKind::Pointer64;
|
|
n.name = name;
|
|
n.parentId = parentId;
|
|
n.offset = offset;
|
|
n.collapsed = true;
|
|
|
|
// Check if pointee is a UDT
|
|
if (realPointee >= tt->firstIndex()) {
|
|
const auto* pointeeRec = tt->get(realPointee);
|
|
if (pointeeRec) {
|
|
if (pointeeRec->header.kind == TRK::LF_STRUCTURE ||
|
|
pointeeRec->header.kind == TRK::LF_CLASS ||
|
|
pointeeRec->header.kind == TRK::LF_UNION) {
|
|
// If this is a forward ref, search for the definition
|
|
uint32_t defIndex = realPointee;
|
|
bool isFwd = false;
|
|
if (pointeeRec->header.kind == TRK::LF_UNION)
|
|
isFwd = pointeeRec->data.LF_UNION.property.fwdref;
|
|
else
|
|
isFwd = pointeeRec->data.LF_CLASS.property.fwdref;
|
|
|
|
if (isFwd) {
|
|
const char* typeName = nullptr;
|
|
if (pointeeRec->header.kind == TRK::LF_UNION)
|
|
typeName = leafName(pointeeRec->data.LF_UNION.data, unionLeafKind(pointeeRec->data.LF_UNION.data));
|
|
else
|
|
typeName = leafName(pointeeRec->data.LF_CLASS.data,
|
|
pointeeRec->data.LF_CLASS.lfEasy.kind);
|
|
|
|
uint32_t resolved = findUdtDefinitionIndex(pointeeRec->header.kind, typeName);
|
|
if (resolved != 0) defIndex = resolved;
|
|
}
|
|
// Skip anonymous pointer targets — they'd create root orphans
|
|
const char* ptName = nullptr;
|
|
const auto* defRec2 = tt->get(defIndex);
|
|
if (defRec2) {
|
|
if (defRec2->header.kind == TRK::LF_UNION)
|
|
ptName = leafName(defRec2->data.LF_UNION.data,
|
|
unionLeafKind(defRec2->data.LF_UNION.data));
|
|
else if (defRec2->header.kind == TRK::LF_STRUCTURE ||
|
|
defRec2->header.kind == TRK::LF_CLASS)
|
|
ptName = leafName(defRec2->data.LF_CLASS.data,
|
|
defRec2->data.LF_CLASS.lfEasy.kind);
|
|
}
|
|
bool isAnonTarget = !ptName || ptName[0] == '<' || ptName[0] == '\0';
|
|
if (!isAnonTarget)
|
|
n.refId = importUDT(defIndex);
|
|
} else if (pointeeRec->header.kind == TRK::LF_PROCEDURE ||
|
|
pointeeRec->header.kind == TRK::LF_MFUNCTION) {
|
|
n.kind = (ptrSize <= 4) ? NodeKind::FuncPtr32 : NodeKind::FuncPtr64;
|
|
}
|
|
}
|
|
}
|
|
tree.addNode(n);
|
|
break;
|
|
}
|
|
|
|
case TRK::LF_STRUCTURE:
|
|
case TRK::LF_CLASS:
|
|
case TRK::LF_UNION: {
|
|
// Embedded struct/union
|
|
uint32_t defIndex = typeIndex;
|
|
|
|
// Handle forward reference
|
|
bool isFwd = false;
|
|
if (rec->header.kind == TRK::LF_UNION)
|
|
isFwd = rec->data.LF_UNION.property.fwdref;
|
|
else
|
|
isFwd = rec->data.LF_CLASS.property.fwdref;
|
|
|
|
if (isFwd) {
|
|
const char* typeName = nullptr;
|
|
if (rec->header.kind == TRK::LF_UNION)
|
|
typeName = leafName(rec->data.LF_UNION.data, unionLeafKind(rec->data.LF_UNION.data));
|
|
else
|
|
typeName = leafName(rec->data.LF_CLASS.data, rec->data.LF_CLASS.lfEasy.kind);
|
|
|
|
uint32_t resolved = findUdtDefinitionIndex(rec->header.kind, typeName);
|
|
if (resolved != 0) defIndex = resolved;
|
|
}
|
|
|
|
const char* typeName = nullptr;
|
|
bool isUnion = (rec->header.kind == TRK::LF_UNION);
|
|
if (isUnion)
|
|
typeName = leafName(rec->data.LF_UNION.data, unionLeafKind(rec->data.LF_UNION.data));
|
|
else
|
|
typeName = leafName(rec->data.LF_CLASS.data, rec->data.LF_CLASS.lfEasy.kind);
|
|
|
|
// Anonymous types: inline fields directly instead of creating root orphan
|
|
bool isAnonymous = !typeName || typeName[0] == '<' || typeName[0] == '\0';
|
|
if (isAnonymous) {
|
|
// Resolve to definition if needed
|
|
const auto* defRec = tt->get(defIndex);
|
|
uint32_t fieldListIdx = 0;
|
|
if (defRec) {
|
|
if (defRec->header.kind == TRK::LF_UNION)
|
|
fieldListIdx = defRec->data.LF_UNION.field;
|
|
else if (defRec->header.kind == TRK::LF_STRUCTURE ||
|
|
defRec->header.kind == TRK::LF_CLASS)
|
|
fieldListIdx = defRec->data.LF_CLASS.field;
|
|
}
|
|
if (fieldListIdx != 0) {
|
|
// Create inline container (no refId, no root orphan)
|
|
Node n;
|
|
n.kind = NodeKind::Struct;
|
|
n.name = name;
|
|
n.classKeyword = isUnion ? QStringLiteral("union") : QStringLiteral("struct");
|
|
n.parentId = parentId;
|
|
n.offset = offset;
|
|
n.collapsed = true;
|
|
int idx = tree.addNode(n);
|
|
uint64_t inlineId = tree.nodes[idx].id;
|
|
importFieldList(fieldListIdx, inlineId);
|
|
break;
|
|
}
|
|
// Fallthrough if no field list
|
|
}
|
|
|
|
uint64_t refId = importUDT(defIndex);
|
|
|
|
Node n;
|
|
n.kind = NodeKind::Struct;
|
|
n.name = name;
|
|
n.structTypeName = typeName ? QString::fromUtf8(typeName) : QString();
|
|
n.classKeyword = isUnion ? QStringLiteral("union") : QStringLiteral("struct");
|
|
n.parentId = parentId;
|
|
n.offset = offset;
|
|
n.refId = refId;
|
|
n.collapsed = true;
|
|
tree.addNode(n);
|
|
break;
|
|
}
|
|
|
|
case TRK::LF_ARRAY: {
|
|
uint32_t elemType = rec->data.LF_ARRAY.elemtype;
|
|
uint64_t totalSize = leafValue(rec->data.LF_ARRAY.data,
|
|
*reinterpret_cast<const TRK*>(rec->data.LF_ARRAY.data));
|
|
|
|
// Get element size
|
|
uint64_t elemSize = 0;
|
|
uint32_t realElemType = unwrapModifier(elemType);
|
|
if (realElemType < tt->firstIndex()) {
|
|
NodeKind ek = mapPrimitiveType(realElemType);
|
|
elemSize = sizeForKind(ek);
|
|
} else {
|
|
const auto* elemRec = tt->get(realElemType);
|
|
if (elemRec) {
|
|
if (elemRec->header.kind == TRK::LF_STRUCTURE || elemRec->header.kind == TRK::LF_CLASS) {
|
|
const char* sizeData = elemRec->data.LF_CLASS.data;
|
|
elemSize = leafValue(sizeData, elemRec->data.LF_CLASS.lfEasy.kind);
|
|
} else if (elemRec->header.kind == TRK::LF_UNION) {
|
|
const char* sizeData = elemRec->data.LF_UNION.data;
|
|
elemSize = leafValue(sizeData, *reinterpret_cast<const TRK*>(sizeData));
|
|
} else if (elemRec->header.kind == TRK::LF_POINTER) {
|
|
elemSize = elemRec->data.LF_POINTER.attr.size;
|
|
} else if (elemRec->header.kind == TRK::LF_ENUM) {
|
|
// Size of enum's underlying type
|
|
uint32_t ut = elemRec->data.LF_ENUM.utype;
|
|
if (ut < tt->firstIndex()) {
|
|
NodeKind ek = mapPrimitiveType(ut);
|
|
elemSize = sizeForKind(ek);
|
|
} else {
|
|
elemSize = 4;
|
|
}
|
|
} else if (elemRec->header.kind == TRK::LF_ARRAY) {
|
|
// Nested array — get total size
|
|
elemSize = leafValue(elemRec->data.LF_ARRAY.data,
|
|
*reinterpret_cast<const TRK*>(elemRec->data.LF_ARRAY.data));
|
|
}
|
|
}
|
|
}
|
|
|
|
int count = (elemSize > 0) ? static_cast<int>(totalSize / elemSize) : 1;
|
|
|
|
Node n;
|
|
n.kind = NodeKind::Array;
|
|
n.name = name;
|
|
n.parentId = parentId;
|
|
n.offset = offset;
|
|
n.arrayLen = count;
|
|
|
|
// Determine element kind
|
|
if (realElemType < tt->firstIndex()) {
|
|
n.elementKind = mapPrimitiveType(realElemType);
|
|
} else {
|
|
const auto* elemRec = tt->get(realElemType);
|
|
if (elemRec) {
|
|
if (elemRec->header.kind == TRK::LF_STRUCTURE ||
|
|
elemRec->header.kind == TRK::LF_CLASS ||
|
|
elemRec->header.kind == TRK::LF_UNION) {
|
|
n.elementKind = NodeKind::Struct;
|
|
n.refId = importUDT(realElemType);
|
|
const char* tn = nullptr;
|
|
if (elemRec->header.kind == TRK::LF_UNION)
|
|
tn = leafName(elemRec->data.LF_UNION.data, unionLeafKind(elemRec->data.LF_UNION.data));
|
|
else
|
|
tn = leafName(elemRec->data.LF_CLASS.data, elemRec->data.LF_CLASS.lfEasy.kind);
|
|
if (tn) n.structTypeName = QString::fromUtf8(tn);
|
|
} else if (elemRec->header.kind == TRK::LF_POINTER) {
|
|
uint32_t sz = elemRec->data.LF_POINTER.attr.size;
|
|
n.elementKind = (sz <= 4) ? NodeKind::Pointer32 : NodeKind::Pointer64;
|
|
} else {
|
|
n.elementKind = hexForSize(elemSize);
|
|
}
|
|
}
|
|
}
|
|
tree.addNode(n);
|
|
break;
|
|
}
|
|
|
|
case TRK::LF_ENUM: {
|
|
// Map enum to its underlying integer type, link to enum definition
|
|
uint32_t utype = rec->data.LF_ENUM.utype;
|
|
uint64_t enumNodeId = importEnum(typeIndex);
|
|
Node n;
|
|
if (utype < tt->firstIndex()) {
|
|
n.kind = mapPrimitiveType(utype);
|
|
} else {
|
|
n.kind = NodeKind::UInt32; // fallback
|
|
}
|
|
n.name = name;
|
|
n.parentId = parentId;
|
|
n.offset = offset;
|
|
n.refId = enumNodeId;
|
|
tree.addNode(n);
|
|
break;
|
|
}
|
|
|
|
case TRK::LF_PROCEDURE:
|
|
case TRK::LF_MFUNCTION: {
|
|
Node n;
|
|
n.kind = NodeKind::Hex64;
|
|
n.name = name;
|
|
n.parentId = parentId;
|
|
n.offset = offset;
|
|
tree.addNode(n);
|
|
break;
|
|
}
|
|
|
|
case TRK::LF_BITFIELD: {
|
|
uint32_t underlying = rec->data.LF_BITFIELD.type;
|
|
uint8_t bitLen = rec->data.LF_BITFIELD.length;
|
|
uint8_t bitPos = rec->data.LF_BITFIELD.position;
|
|
uint64_t slotSize = 4;
|
|
if (underlying < tt->firstIndex()) {
|
|
NodeKind k = mapPrimitiveType(underlying);
|
|
slotSize = sizeForKind(k);
|
|
}
|
|
Node n;
|
|
n.kind = NodeKind::Struct;
|
|
n.classKeyword = QStringLiteral("bitfield");
|
|
n.elementKind = hexForSize(slotSize);
|
|
n.name = name;
|
|
n.parentId = parentId;
|
|
n.offset = offset;
|
|
n.bitfieldMembers.append({name, bitPos, bitLen});
|
|
tree.addNode(n);
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
// Unknown complex type — emit as Hex32
|
|
Node n;
|
|
n.kind = NodeKind::Hex32;
|
|
n.name = name;
|
|
n.parentId = parentId;
|
|
n.offset = offset;
|
|
tree.addNode(n);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Helper: open PDB and build type table ──
|
|
|
|
struct PdbFile {
|
|
MappedFile mapped;
|
|
PDB::RawFile* rawFile = nullptr;
|
|
PDB::TPIStream* tpiStream = nullptr;
|
|
TypeTable* typeTable = nullptr;
|
|
|
|
~PdbFile() {
|
|
delete typeTable;
|
|
delete tpiStream;
|
|
delete rawFile;
|
|
}
|
|
|
|
bool open(const QString& pdbPath, QString* errorMsg) {
|
|
auto setErr = [&](const QString& msg) { if (errorMsg) *errorMsg = msg; };
|
|
|
|
if (!QFile::exists(pdbPath)) {
|
|
setErr(QStringLiteral("PDB file not found: ") + pdbPath);
|
|
return false;
|
|
}
|
|
|
|
if (!mapped.open(pdbPath)) {
|
|
setErr(QStringLiteral("Failed to memory-map PDB file: ") + pdbPath);
|
|
return false;
|
|
}
|
|
|
|
if (PDB::ValidateFile(mapped.base, mapped.size) != PDB::ErrorCode::Success) {
|
|
setErr(QStringLiteral("Invalid PDB file: ") + pdbPath);
|
|
return false;
|
|
}
|
|
|
|
rawFile = new PDB::RawFile(PDB::CreateRawFile(mapped.base));
|
|
|
|
if (PDB::HasValidTPIStream(*rawFile) != PDB::ErrorCode::Success) {
|
|
setErr(QStringLiteral("PDB has no valid TPI stream: ") + pdbPath);
|
|
return false;
|
|
}
|
|
|
|
tpiStream = new PDB::TPIStream(PDB::CreateTPIStream(*rawFile));
|
|
typeTable = new TypeTable(*tpiStream);
|
|
return true;
|
|
}
|
|
};
|
|
|
|
// ── Public API: enumeratePdbTypes ──
|
|
|
|
QVector<PdbTypeInfo> enumeratePdbTypes(const QString& pdbPath, QString* errorMsg) {
|
|
PdbFile pdb;
|
|
if (!pdb.open(pdbPath, errorMsg)) return {};
|
|
|
|
const TypeTable& tt = *pdb.typeTable;
|
|
QVector<PdbTypeInfo> result;
|
|
|
|
for (uint32_t ti = tt.firstIndex(); ti < tt.lastIndex(); ti++) {
|
|
const auto* rec = tt.get(ti);
|
|
if (!rec) continue;
|
|
|
|
bool isUDT = (rec->header.kind == TRK::LF_STRUCTURE ||
|
|
rec->header.kind == TRK::LF_CLASS ||
|
|
rec->header.kind == TRK::LF_UNION);
|
|
bool isEnum = (rec->header.kind == TRK::LF_ENUM);
|
|
if (!isUDT && !isEnum) continue;
|
|
|
|
const char* name = nullptr;
|
|
uint16_t fieldCount = 0;
|
|
bool isUnion = false;
|
|
uint64_t size = 0;
|
|
|
|
if (isEnum) {
|
|
if (rec->data.LF_ENUM.property.fwdref) continue;
|
|
fieldCount = rec->data.LF_ENUM.count;
|
|
name = rec->data.LF_ENUM.name;
|
|
// Size from underlying type
|
|
uint32_t ut = rec->data.LF_ENUM.utype;
|
|
if (ut < tt.firstIndex()) {
|
|
NodeKind ek = mapPrimitiveType(ut);
|
|
size = sizeForKind(ek);
|
|
} else {
|
|
size = 4;
|
|
}
|
|
} else if (rec->header.kind == TRK::LF_UNION) {
|
|
if (rec->data.LF_UNION.property.fwdref) continue;
|
|
isUnion = true;
|
|
fieldCount = rec->data.LF_UNION.count;
|
|
const char* sizeData = rec->data.LF_UNION.data;
|
|
TRK sizeKind = *reinterpret_cast<const TRK*>(sizeData);
|
|
size = leafValue(sizeData, sizeKind);
|
|
name = leafName(sizeData, sizeKind);
|
|
} else {
|
|
if (rec->data.LF_CLASS.property.fwdref) continue;
|
|
fieldCount = rec->data.LF_CLASS.count;
|
|
const char* sizeData = rec->data.LF_CLASS.data;
|
|
size = leafValue(sizeData, rec->data.LF_CLASS.lfEasy.kind);
|
|
name = leafName(sizeData, rec->data.LF_CLASS.lfEasy.kind);
|
|
}
|
|
|
|
if (!name || name[0] == '\0') continue;
|
|
// Skip anonymous types with compiler-generated names
|
|
if (name[0] == '<') continue;
|
|
|
|
PdbTypeInfo info;
|
|
info.typeIndex = ti;
|
|
info.name = QString::fromUtf8(name);
|
|
info.size = size;
|
|
info.childCount = fieldCount;
|
|
info.isUnion = isUnion;
|
|
info.isEnum = isEnum;
|
|
result.append(info);
|
|
}
|
|
|
|
int enumCount = 0;
|
|
for (const auto& r : result)
|
|
if (r.isEnum) enumCount++;
|
|
qDebug() << "[PDB] enumeratePdbTypes:" << result.size() << "types,"
|
|
<< enumCount << "enums";
|
|
|
|
return result;
|
|
}
|
|
|
|
// ── Public API: importPdbSelected ──
|
|
|
|
NodeTree importPdbSelected(const QString& pdbPath,
|
|
const QVector<uint32_t>& typeIndices,
|
|
QString* errorMsg,
|
|
ProgressCb progressCb) {
|
|
PdbFile pdb;
|
|
if (!pdb.open(pdbPath, errorMsg)) return {};
|
|
|
|
PdbCtx ctx;
|
|
ctx.tt = pdb.typeTable;
|
|
|
|
int total = typeIndices.size();
|
|
int enumDispatched = 0, enumCreated = 0;
|
|
for (int i = 0; i < total; i++) {
|
|
uint32_t ti = typeIndices[i];
|
|
const auto* rec = pdb.typeTable->get(ti);
|
|
if (rec && rec->header.kind == TRK::LF_ENUM) {
|
|
enumDispatched++;
|
|
uint64_t id = ctx.importEnum(ti);
|
|
if (id != 0) enumCreated++;
|
|
else qDebug() << "[PDB] importEnum FAILED for typeIndex" << ti;
|
|
} else {
|
|
ctx.importUDT(ti);
|
|
}
|
|
if (progressCb && !progressCb(i + 1, total)) {
|
|
if (errorMsg) *errorMsg = QStringLiteral("Import cancelled");
|
|
return ctx.tree; // return partial result
|
|
}
|
|
}
|
|
|
|
// Count enum nodes in tree
|
|
int enumNodes = 0;
|
|
for (const auto& n : ctx.tree.nodes)
|
|
if (n.classKeyword == QLatin1String("enum")) enumNodes++;
|
|
qDebug() << "[PDB] importPdbSelected:" << total << "types,"
|
|
<< enumDispatched << "enum dispatches,"
|
|
<< enumCreated << "enum created,"
|
|
<< enumNodes << "enum nodes in tree,"
|
|
<< ctx.tree.nodes.size() << "total nodes";
|
|
|
|
if (ctx.tree.nodes.isEmpty()) {
|
|
if (errorMsg) *errorMsg = QStringLiteral("No types imported");
|
|
}
|
|
return ctx.tree;
|
|
}
|
|
|
|
// ── Public API: importPdb (legacy) ──
|
|
|
|
NodeTree importPdb(const QString& pdbPath, const QString& structFilter, QString* errorMsg) {
|
|
PdbFile pdb;
|
|
if (!pdb.open(pdbPath, errorMsg)) return {};
|
|
|
|
const TypeTable& tt = *pdb.typeTable;
|
|
PdbCtx ctx;
|
|
ctx.tt = &tt;
|
|
|
|
for (uint32_t ti = tt.firstIndex(); ti < tt.lastIndex(); ti++) {
|
|
const auto* rec = tt.get(ti);
|
|
if (!rec) continue;
|
|
|
|
bool isUDT = (rec->header.kind == TRK::LF_STRUCTURE ||
|
|
rec->header.kind == TRK::LF_CLASS ||
|
|
rec->header.kind == TRK::LF_UNION);
|
|
if (!isUDT) continue;
|
|
|
|
bool fwdref = false;
|
|
const char* name = nullptr;
|
|
|
|
if (rec->header.kind == TRK::LF_UNION) {
|
|
fwdref = rec->data.LF_UNION.property.fwdref;
|
|
name = leafName(rec->data.LF_UNION.data, unionLeafKind(rec->data.LF_UNION.data));
|
|
} else {
|
|
fwdref = rec->data.LF_CLASS.property.fwdref;
|
|
name = leafName(rec->data.LF_CLASS.data, rec->data.LF_CLASS.lfEasy.kind);
|
|
}
|
|
|
|
if (fwdref) continue;
|
|
if (!name) continue;
|
|
|
|
if (!structFilter.isEmpty()) {
|
|
if (QString::fromUtf8(name) != structFilter) continue;
|
|
}
|
|
|
|
ctx.importUDT(ti);
|
|
|
|
// If filtering to a single struct, stop after finding it
|
|
if (!structFilter.isEmpty()) break;
|
|
}
|
|
|
|
if (ctx.tree.nodes.isEmpty()) {
|
|
if (!structFilter.isEmpty()) {
|
|
if (errorMsg) *errorMsg = QStringLiteral("Type '") + structFilter +
|
|
QStringLiteral("' not found in PDB");
|
|
} else {
|
|
if (errorMsg) *errorMsg = QStringLiteral("No types found in PDB");
|
|
}
|
|
}
|
|
|
|
return ctx.tree;
|
|
}
|
|
|
|
} // namespace rcx
|
|
|
|
#else // !_WIN32
|
|
|
|
namespace rcx {
|
|
|
|
QVector<PdbTypeInfo> enumeratePdbTypes(const QString&, QString* errorMsg) {
|
|
if (errorMsg) *errorMsg = QStringLiteral("PDB import requires Windows");
|
|
return {};
|
|
}
|
|
|
|
NodeTree importPdbSelected(const QString&, const QVector<uint32_t>&,
|
|
QString* errorMsg, ProgressCb) {
|
|
if (errorMsg) *errorMsg = QStringLiteral("PDB import requires Windows");
|
|
return {};
|
|
}
|
|
|
|
NodeTree importPdb(const QString&, const QString&, QString* errorMsg) {
|
|
if (errorMsg) *errorMsg = QStringLiteral("PDB import requires Windows");
|
|
return {};
|
|
}
|
|
|
|
} // namespace rcx
|
|
|
|
#endif
|