PDB TPI 和 IPI 流¶
简介¶
PDB TPI 流(索引 2)和 IPI 流(索引 4)包含程序中使用的所有类型信息。它被组织成一个头,后面跟着一个CodeView 类型记录列表。类型通过其类型索引在 PDB 中的各个流和记录中被引用。通常,在头之后的一系列类型记录形成了一个拓扑排序的 DAG(有向无环图),这意味着类型记录 B 只能引用类型 A,如果A.TypeIndex < B.TypeIndex
。虽然在某些罕见情况下此属性不成立(尤其是在处理使用 MASM 编译的对象文件时),但实现应尽力使此属性成立,因为它意味着整个类型图可以在一次遍历中构建。
重要
类型记录形成一个拓扑排序的 DAG(有向无环图)。
TPI 与 IPI 流¶
PDB 格式的最新版本(即本文档涵盖的所有版本)具有 2 个布局相同的流,以下分别称为 TPI 流和 IPI 流。本文档后续内容描述的磁盘格式同样适用于 TPI 流或 IPI 流。这两个流之间的唯一区别在于允许哪些 CodeView 记录出现在每个流中,下表对此进行了总结。
TPI 流 |
IPI 流 |
---|---|
LF_POINTER |
LF_FUNC_ID |
LF_MODIFIER |
LF_MFUNC_ID |
LF_PROCEDURE |
LF_BUILDINFO |
LF_MFUNCTION |
LF_SUBSTR_LIST |
LF_LABEL |
LF_STRING_ID |
LF_ARGLIST |
LF_UDT_SRC_LINE |
LF_FIELDLIST |
LF_UDT_MOD_SRC_LINE |
LF_ARRAY |
|
LF_CLASS |
|
LF_STRUCTURE |
|
LF_INTERFACE |
|
LF_UNION |
|
LF_ENUM |
|
LF_TYPESERVER2 |
|
LF_VFTABLE |
|
LF_VTSHAPE |
|
LF_BITFIELD |
|
LF_METHODLIST |
|
LF_PRECOMP |
|
LF_ENDPRECOMP |
这些记录的用法在CodeView 类型记录中进行了更详细的描述。
类型索引¶
类型索引是一个 32 位整数,用于唯一标识对象文件.debug$T
节或 PDB 文件的 TPI 或 IPI 流中的类型。TPI 流中第一个类型记录的类型索引值由TPI 流头的TypeIndexBegin
成员给出,尽管在实践中此值始终等于 0x1000(4096)。
任何最高位设置为 1 的类型索引都被认为来自 IPI 流,尽管这似乎更像是一种技巧,LLVM 不会生成这种类型的索引。但是,它们偶尔可以在 Microsoft PDB 中观察到,因此应该准备好处理它们。请注意,最高位设置为 1 不是确定类型索引是否来自 IPI 流的必要条件,它只是一个充分条件。
一旦最高位被清零,任何类型索引 >= TypeIndexBegin
都被认为来自相应的流,任何小于此值的类型索引都是一个位掩码,可以分解如下:
.---------------------------.------.----------.
| Unused | Mode | Kind |
'---------------------------'------'----------'
|+32 |+12 |+8 |+0
**种类** - 来自以下枚举的值
enum class SimpleTypeKind : uint32_t {
None = 0x0000, // uncharacterized type (no type)
Void = 0x0003, // void
NotTranslated = 0x0007, // type not translated by cvpack
HResult = 0x0008, // OLE/COM HRESULT
SignedCharacter = 0x0010, // 8 bit signed
UnsignedCharacter = 0x0020, // 8 bit unsigned
NarrowCharacter = 0x0070, // really a char
WideCharacter = 0x0071, // wide char
Character16 = 0x007a, // char16_t
Character32 = 0x007b, // char32_t
Character8 = 0x007c, // char8_t
SByte = 0x0068, // 8 bit signed int
Byte = 0x0069, // 8 bit unsigned int
Int16Short = 0x0011, // 16 bit signed
UInt16Short = 0x0021, // 16 bit unsigned
Int16 = 0x0072, // 16 bit signed int
UInt16 = 0x0073, // 16 bit unsigned int
Int32Long = 0x0012, // 32 bit signed
UInt32Long = 0x0022, // 32 bit unsigned
Int32 = 0x0074, // 32 bit signed int
UInt32 = 0x0075, // 32 bit unsigned int
Int64Quad = 0x0013, // 64 bit signed
UInt64Quad = 0x0023, // 64 bit unsigned
Int64 = 0x0076, // 64 bit signed int
UInt64 = 0x0077, // 64 bit unsigned int
Int128Oct = 0x0014, // 128 bit signed int
UInt128Oct = 0x0024, // 128 bit unsigned int
Int128 = 0x0078, // 128 bit signed int
UInt128 = 0x0079, // 128 bit unsigned int
Float16 = 0x0046, // 16 bit real
Float32 = 0x0040, // 32 bit real
Float32PartialPrecision = 0x0045, // 32 bit PP real
Float48 = 0x0044, // 48 bit real
Float64 = 0x0041, // 64 bit real
Float80 = 0x0042, // 80 bit real
Float128 = 0x0043, // 128 bit real
Complex16 = 0x0056, // 16 bit complex
Complex32 = 0x0050, // 32 bit complex
Complex32PartialPrecision = 0x0055, // 32 bit PP complex
Complex48 = 0x0054, // 48 bit complex
Complex64 = 0x0051, // 64 bit complex
Complex80 = 0x0052, // 80 bit complex
Complex128 = 0x0053, // 128 bit complex
Boolean8 = 0x0030, // 8 bit boolean
Boolean16 = 0x0031, // 16 bit boolean
Boolean32 = 0x0032, // 32 bit boolean
Boolean64 = 0x0033, // 64 bit boolean
Boolean128 = 0x0034, // 128 bit boolean
};
**模式** - 来自以下枚举的值
enum class SimpleTypeMode : uint32_t {
Direct = 0, // Not a pointer
NearPointer = 1, // Near pointer
FarPointer = 2, // Far pointer
HugePointer = 3, // Huge pointer
NearPointer32 = 4, // 32 bit near pointer
FarPointer32 = 5, // 32 bit far pointer
NearPointer64 = 6, // 64 bit near pointer
NearPointer128 = 7 // 128 bit near pointer
};
请注意,对于指针,位数在模式中表示。因此,如果为 32 位构建,则void*
将具有Mode=NearPointer32, Kind=Void
的类型索引,但如果为 64 位构建,则将具有Mode=NearPointer64, Kind=Void
的类型索引。
按照惯例,std::nullptr_t
的类型索引与void*
的类型索引构造方式相同,但使用无位枚举值NearPointer
。
流头¶
TPI 流偏移量 0 处有一个具有以下布局的头:
struct TpiStreamHeader {
uint32_t Version;
uint32_t HeaderSize;
uint32_t TypeIndexBegin;
uint32_t TypeIndexEnd;
uint32_t TypeRecordBytes;
uint16_t HashStreamIndex;
uint16_t HashAuxStreamIndex;
uint32_t HashKeySize;
uint32_t NumHashBuckets;
int32_t HashValueBufferOffset;
uint32_t HashValueBufferLength;
int32_t IndexOffsetBufferOffset;
uint32_t IndexOffsetBufferLength;
int32_t HashAdjBufferOffset;
uint32_t HashAdjBufferLength;
};
**版本** - 来自以下枚举的值。
enum class TpiStreamVersion : uint32_t {
V40 = 19950410,
V41 = 19951122,
V50 = 19961031,
V70 = 19990903,
V80 = 20040203,
};
与PDB 流类似,此值似乎始终为V80
,并且没有观察到其他值。假设如果观察到其他值,则本文档描述的布局可能不准确。
**HeaderSize** -
sizeof(TpiStreamHeader)
**TypeIndexBegin** - 表示 TPI 流中第一个类型记录的类型索引的数值。这通常是值 0x1000,因为小于此值的类型索引是保留的(有关保留类型索引的讨论,请参见类型索引)。
**TypeIndexEnd** - 表示 TPI 流中最后一个类型记录的类型索引的数值加 1。TPI 流中类型记录的总数可以计算为
TypeIndexEnd - TypeIndexBegin
。**TypeRecordBytes** - 头之后类型记录数据的字节数。
**HashStreamIndex** - 包含每个类型记录的哈希列表的流的索引。此值可能为 -1,表示哈希信息不存在。在实践中始终观察到有效的流索引,因此任何生产者实现都应准备好发出此流,以确保与可能期望它存在的工具兼容。
**HashAuxStreamIndex** - 推测包含单独哈希表的流的索引,尽管在实践中尚未观察到这一点,也不清楚它可能用于什么目的。
**HashKeySize** - 哈希值的尺寸(通常为 4 字节)。
**NumHashBuckets** - 用于生成上述哈希流中哈希值的桶数。
**HashValueBufferOffset / HashValueBufferLength** - 哈希值列表在 TPI 哈希流中的偏移量和大小。应假设哈希值数量为 0 或等于 TPI 流中类型记录的数量(
TypeIndexEnd - TypeEndBegin
)。因此,如果HashBufferLength
不等于(TypeIndexEnd - TypeEndBegin) * HashKeySize
,我们可以认为 PDB 格式错误。**IndexOffsetBufferOffset / IndexOffsetBufferLength** - 类型索引偏移量缓冲区在 TPI 哈希流中的偏移量和大小。这是一个 uint32_t 对列表,其中第一个值为类型索引,第二个值为具有此索引的类型的类型记录数据中的偏移量。这可以用于执行二分查找,然后进行线性查找,以实现 O(log n) 的类型索引查找。
**HashAdjBufferOffset / HashAdjBufferLength** - 一个序列化哈希表在 TPI 哈希流中的偏移量和大小,其键是哈希值缓冲区中的哈希值,其值是类型索引。这似乎在增量链接场景中很有用,因此,如果类型被修改,可以创建一个条目将旧哈希值映射到新的类型索引,以便 PDB 文件使用者始终拥有类型的最新版本,而无需强制增量链接器进行垃圾回收并更新指向旧版本的引用,现在指向新版本。此哈希表的布局在PDB 序列化哈希表格式中进行了描述。
CodeView 类型记录列表¶
在头之后,有TypeRecordBytes
字节的数据,表示CodeView 类型记录的可变长度数组。此类记录的数量(例如,数组的长度)可以通过计算值Header.TypeIndexEnd - Header.TypeIndexBegin
来确定。
通过前面描述的类型索引偏移量数组(如果存在),可以提供 O(log(n)) 的访问。