PDB TPI 與 IPI 串流

簡介

PDB TPI 串流(索引 2)與 IPI 串流(索引 4)包含程式中使用的所有類型資訊。它組織為一個標頭,後跟一個 CodeView 類型記錄 列表。類型透過其類型索引在整個 PDB 中的各種串流和記錄中被引用。一般來說,標頭之後的類型記錄序列形成一個拓撲排序的 DAG(有向無環圖),這表示類型記錄 B 只有在 A.TypeIndex < B.TypeIndex 的情況下才能引用類型 A。雖然在極少數情況下,此屬性可能不成立(特別是在處理使用 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 串流內的類型。TypeIndexBegin 成員給出了 TPI 串流中第一個類型記錄的類型索引值,儘管實際上此值始終等於 0x1000 (4096),TPI 串流標頭

任何設定了高位元的類型索引都被認為來自 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 串流中最後一個類型記錄的類型索引的數值大 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)) 存取是透過先前描述的類型索引偏移量陣列(如果存在)提供的。