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)。
任何設定了高位的類型索引都被認為來自 IPI 串流,儘管這似乎更像是一種技巧,而且 LLVM 並不會產生這種性質的類型索引。然而,它們偶爾會出現在 Microsoft PDB 中,因此應該做好處理它們的準備。請注意,設定高位並非判斷類型索引是否來自 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 串流中最後一個類型記錄的類型索引的數值加一。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)) 訪問通過前面描述的類型索引偏移量數組(如果存在)提供。