PDB 資訊串流(又稱為 PDB 串流)

串流標頭

在 PDB 串流的偏移量 0 處是一個具有以下佈局的標頭

struct PdbStreamHeader {
  ulittle32_t Version;
  ulittle32_t Signature;
  ulittle32_t Age;
  Guid UniqueId;
};
  • 版本 - 來自以下列舉的值

enum class PdbStreamVersion : uint32_t {
  VC2 = 19941610,
  VC4 = 19950623,
  VC41 = 19950814,
  VC50 = 19960307,
  VC98 = 19970604,
  VC70Dep = 19990604,
  VC70 = 20000404,
  VC80 = 20030901,
  VC110 = 20091201,
  VC140 = 20140508,
};

雖然此欄位的含義看似顯而易見,但實際上我們從未觀察到 VC70 以外的值,即使使用現代版本的工具鏈也是如此,並且不清楚為什麼存在其他值。假設如果該值不是 VC70,則 PDB 串流佈局的某些方面,甚至可能還有其他串流的佈局,將會改變。

  • 簽章 - 在寫入 PDB 檔案時透過呼叫 time() 產生的 32 位元時間戳記。請注意,由於使用具有 1 秒粒度時間戳記的固有唯一性問題,此欄位實際上並不能達到其預期目的,因此通常會忽略它,而改用下面描述的 Guid 欄位。

  • 年齡 - PDB 檔案被寫入的次數。這可以用於與 Guid 一起將 PDB 與其對應的可執行檔進行匹配。

  • Guid - 一個 128 位元的識別碼,保證在空間和時間上都是唯一的。一般來說,這可以被認為是呼叫 Win32 API UuidCreate 的結果,儘管 LLVM 不能依賴於此,因為它必須在非 Windows 平台上工作。

已命名串流對應

標頭之後是一個序列化雜湊表,其鍵類型是字串,值類型是整數。映射 X -> Y 的存在表示名稱為 X 的資料流在底層 MSF 檔案中的資料流索引為 Y。請注意,並非所有資料流都有名稱(例如,TPI 資料流 具有固定索引,因此無需按名稱查找其索引)。在實務中,通常只有少數具名資料流,這些資料流會列在 PDB 檔案格式 的資料流程表中。因此,如果資料流確實有名稱(因此位於具名資料流映射中),則查閱具名資料流映射可能是發現資料流 MSF 資料流索引的唯一方法。幾個重要的資料流(例如,稱為 /names 的全域字串表)只能通過這種方式找到,因此正確地產生和使用它非常重要,因為沒有它,工具將無法正常運作。

重要

某些資料流通過固定索引定位(例如,TPI 資料流的索引為 2),但其他資料流通過固定名稱定位(例如,字串表稱為 /names),並且只能通過查閱具名資料流映射來定位。

具名資料流映射的磁碟上佈局由 2 個部分組成。第一個是字串資料緩衝區,前面加上一個 32 位元長度。第二個是序列化雜湊表,其鍵和值類型均為 uint32_t。鍵是指向字串資料緩衝區中以 null 結尾的字串的偏移量,該字串指定資料流的名稱,值是具有該名稱的資料流的 MSF 資料流索引。請注意,儘管鍵是一個整數,但用於查找正確儲存桶的雜湊函數會對字串資料緩衝區中相應偏移量處的字串進行雜湊。

序列化雜湊表的磁碟上佈局在 PDB 序列化雜湊表格式 中描述。

請注意,整個具名資料流映射沒有長度前綴,因此獲取其後資料的唯一方法是將其整體反序列化。

PDB 特性代碼

在具名資料流映射之後,並使用 PDB 資料流的所有剩餘位元組,是以下列舉值列表

enum class PdbRaw_FeatureSig : uint32_t {
  VC110 = 20091201,
  VC140 = 20140508,
  NoTypeMerge = 0x4D544F4E,
  MinimalDebugInfo = 0x494E494D,
};

下表總結了這些值的含義

標誌

含義

VC110

VC140

NoTypeMerge

  • 推測重複類型可能會出現在 TPI 資料流中,儘管目前尚不清楚為什麼會發生這種情況。

MinimalDebugInfo

  • 程式已連結 /DEBUG:FASTLINK

  • 沒有 TPI / IPI 資料流,所有類型資訊都包含在原始物件檔案中。

將 PDB 與其可執行檔匹配

連結器負責寫入 PDB 和最終的可執行檔,因此是唯一能夠寫入將 PDB 與可執行檔匹配所需資訊的實體。

為此,連結器會為 PDB 產生一個 GUID(如果正在遞增連結,則會重複使用現有的 GUID)並遞增 Age 欄位。

這個可執行檔是一個 PE/COFF 檔案,而 PE/COFF 檔案的一部分是存在著許多「目錄」。在這裡,我們感興趣的是「偵錯目錄」。確切的偵錯目錄格式在 IMAGE_DEBUG_DIRECTORY 結構 中有描述。對於這個特殊情況,連結器會發出類型為 IMAGE_DEBUG_TYPE_CODEVIEW 的偵錯目錄。這個記錄的格式定義在 llvm/DebugInfo/CodeView/CVDebugRecord.h 中,但這裡只需要說明它包含相同的 GuidAge 欄位即可。在執行階段,偵錯器或工具可以掃描 COFF 可執行檔映像檔中是否存在正確類型的偵錯目錄,並驗證 Guid 和 Age 是否相符。