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 流的佈局,甚至可能是其他流的佈局,都會發生某些變化。

  • 簽章 - 一個 32 位元時間戳記,在寫入 PDB 檔案時使用呼叫 time() 產生。 請注意,由於使用 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

  • 沒有其他功能旗標存在

  • PDB 包含一個 IPI 串流

VC140

  • 可能存在其他功能旗標

  • PDB 包含一個 IPI 串流

NoTypeMerge

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

MinimalDebugInfo

  • 程式是使用 /DEBUG:FASTLINK 連結的

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

將 PDB 檔案與其可執行檔匹配

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

為了完成此操作,連結器會為 PDB 產生一個 guid(如果正在增量連結,則重複使用現有的 guid)並遞增年齡欄位。

可執行檔是一個 PE/COFF 檔案,而 PE/COFF 檔案的一部分是存在許多「目錄」。 就我們這裡的目的而言,我們對「除錯目錄」感興趣。 除錯目錄的確切格式由 IMAGE_DEBUG_DIRECTORY 結構 描述。 對於這種特定情況,連結器發出類型為 IMAGE_DEBUG_TYPE_CODEVIEW 的除錯目錄。 此記錄的格式在 llvm/DebugInfo/CodeView/CVDebugRecord.h 中定義,但在此說明僅包含相同的 GuidAge 欄位就足夠了。 在運行時,除錯器或工具可以掃描 COFF 可執行映像,以查找是否存在正確類型的除錯目錄,並驗證 Guid 和 Age 是否匹配。