PDB DBI (偵錯資訊) 串流¶
簡介¶
PDB DBI 串流(索引 3)是 PDB 檔案中最大且最重要的串流之一。它包含程式編譯方式的相關資訊(例如,編譯標誌等等)、用於連結程式的編譯單元(例如,物件檔案)、用於建置程式的原始程式碼檔案,以及對其他串流的參考,這些串流包含關於每個編譯單元的更詳細資訊,例如每個編譯單元中包含的 CodeView 符號記錄,以及函數和每個編譯單元內其他符號的原始碼和行號資訊。
串流標頭¶
DBI 串流的偏移量 0 處是一個具有以下佈局的標頭
struct DbiStreamHeader {
int32_t VersionSignature;
uint32_t VersionHeader;
uint32_t Age;
uint16_t GlobalStreamIndex;
uint16_t BuildNumber;
uint16_t PublicStreamIndex;
uint16_t PdbDllVersion;
uint16_t SymRecordStream;
uint16_t PdbDllRbld;
int32_t ModInfoSize;
int32_t SectionContributionSize;
int32_t SectionMapSize;
int32_t SourceInfoSize;
int32_t TypeServerMapSize;
uint32_t MFCTypeServerIndex;
int32_t OptionalDbgHeaderSize;
int32_t ECSubstreamSize;
uint16_t Flags;
uint16_t Machine;
uint32_t Padding;
};
VersionSignature - 未知含義。 似乎始終為
-1
。VersionHeader - 來自以下列舉的值。
enum class DbiStreamVersion : uint32_t {
VC41 = 930803,
V50 = 19960307,
V60 = 19970606,
V70 = 19990903,
V110 = 20091201
};
類似於 PDB 串流,此值始終顯示為 V70
,並且不清楚其他值的用途。
Age - PDB 被寫入的次數。 等於來自 PDB 串流標頭 的相同欄位。
GlobalStreamIndex - 全域符號串流 的索引,其中包含所有全域符號的 CodeView 符號記錄。 實際記錄儲存在符號記錄串流中,並從此串流參考。
BuildNumber - 一個位元欄位,包含表示用於建置程式的工具鏈的主要和次要版本號的值(例如,MSVC 2013 的 12.0),具有以下佈局
uint16_t MinorVersion : 8;
uint16_t MajorVersion : 7;
uint16_t NewVersionFormat : 1;
為了 LLVM 的目的,我們假設 NewVersionFormat
始終為 true
。 如果它是 false
,則上面的佈局不適用,讀取器應查閱 Microsoft 原始碼 以獲得進一步的指導。
PublicStreamIndex - 公開符號串流 的索引,其中包含所有公開符號的 CodeView 符號記錄。 實際記錄儲存在符號記錄串流中,並從此串流參考。
PdbDllVersion - 用於產生此 PDB 的
mspdbXXXX.dll
的版本號。 請注意,這顯然不適用於 LLVM,因為 LLVM 不使用mspdb.dll
。SymRecordStream - 包含程式使用的所有 CodeView 符號記錄的串流。 這用於重複資料刪除,以便許多不同的編譯單元可以參考相同的符號,而無需在每個模組串流中包含完整的記錄內容。
PdbDllRbld - 未知
MFCTypeServerIndex - 類型伺服器地圖子串流 中 MFC 類型伺服器的索引。
Flags - 具有以下佈局的位元欄位,包含有關程式建置方式的各種資訊
uint16_t WasIncrementallyLinked : 1;
uint16_t ArePrivateSymbolsStripped : 1;
uint16_t HasConflictingTypes : 1;
uint16_t Reserved : 13;
這些標誌中唯一不是不言自明的是 HasConflictingTypes
。 雖然沒有文件記錄,但 link.exe
包含一個隱藏標誌 /DEBUG:CTYPES
。 如果將其傳遞給 link.exe
,則將設定此欄位。 否則不會設定。 目前尚不清楚此標誌的作用,儘管它似乎對用於查找類型記錄的演算法有細微的影響。
Machine - 來自 CV_CPU_TYPE_e 列舉的值。 常見值為
0x8664
(x86-64) 和0x14C
(x86)。
緊接在固定大小的 DBI 串流標頭之後是 7
個可變長度的子串流。 DBI 串流標頭的以下 7
個欄位指定相應子串流的位元組數。 每個子串流的內容將在 下面 詳細描述。 整個 DBI 串流的長度應等於 64
(上面標頭的長度)加上以下 7
個欄位中每個欄位的值。
子串流¶
模組資訊子串流¶
從 標頭 之後的偏移量 0
處開始。 模組資訊子串流是一個可變長度記錄的陣列,每個記錄描述連結到程式中的單個模組(例如,物件檔案)。 陣列中的每個記錄都具有以下格式
struct ModInfo {
uint32_t Unused1;
struct SectionContribEntry {
uint16_t Section;
char Padding1[2];
int32_t Offset;
int32_t Size;
uint32_t Characteristics;
uint16_t ModuleIndex;
char Padding2[2];
uint32_t DataCrc;
uint32_t RelocCrc;
} SectionContr;
uint16_t Flags;
uint16_t ModuleSymStream;
uint32_t SymByteSize;
uint32_t C11ByteSize;
uint32_t C13ByteSize;
uint16_t SourceFileCount;
char Padding[2];
uint32_t Unused2;
uint32_t SourceFileNameIndex;
uint32_t PdbFilePathNameIndex;
char ModuleName[];
char ObjFileName[];
};
SectionContr - 描述最終二進位檔中區段的屬性,該區段包含來自此模組的程式碼和資料。
SectionContr.Characteristics
對應於 IMAGE_SECTION_HEADER 結構的Characteristics
欄位。Flags - 具有以下格式的位元欄位
// ``true`` if this ModInfo has been written since reading the PDB. This is
// likely used to support incremental linking, so that the linker can decide
// if it needs to commit changes to disk.
uint16_t Dirty : 1;
// ``true`` if EC information is present for this module. EC is presumed to
// stand for "Edit & Continue", which LLVM does not support. So this flag
// will always be false.
uint16_t EC : 1;
uint16_t Unused : 6;
// Type Server Index for this module. This is assumed to be related to /Zi,
// but as LLVM treats /Zi as /Z7, this field will always be invalid for LLVM
// generated PDBs.
uint16_t TSM : 8;
ModuleSymStream - 包含此模組符號資訊的串流索引。 這包括 CodeView 符號資訊以及原始碼和行號資訊。 如果此欄位為 -1,則此模組將不存在其他偵錯資訊(例如,當您從 PDB 中剝離私有符號時,就會發生這種情況)。
SymByteSize - 來自
ModuleSymStream
識別的串流的資料位元組數,這些資料表示 CodeView 符號記錄。C11ByteSize - 來自
ModuleSymStream
識別的串流的資料位元組數,這些資料表示 C11 樣式的 CodeView 行號資訊。C13ByteSize - 來自
ModuleSymStream
識別的串流的資料位元組數,這些資料表示 C13 樣式的 CodeView 行號資訊。C11ByteSize
和C13ByteSize
中最多只有一個為非零。 現代 PDB 始終使用 C13 而不是 C11。SourceFileCount - 在編譯期間對此模組做出貢獻的原始程式碼檔案數。
SourceFileNameIndex - 用於建置此模組的主要翻譯單元在名稱緩衝區中的偏移量。 到目前為止觀察到的所有 PDB 檔案都將此值設為 0。
PdbFilePathNameIndex - 包含此模組符號資訊的 PDB 檔案在名稱緩衝區中的偏移量。 僅對於特殊的
* Linker *
模組觀察到此值為非零。ModuleName - 模組名稱。 這通常是物件檔案的完整路徑(直接傳遞給
link.exe
或來自封存檔),或Import:<dll name>
形式的字串。ObjFileName - 物件檔案名稱。 在直接傳遞給
link.exe
的模組的情況下,這與 ModuleName 相同。 對於來自封存檔的模組,這通常是封存檔的完整路徑。
區段貢獻子串流¶
從 模組資訊子串流 結束後立即開始的偏移量 0
處開始,並消耗 Header->SectionContributionSize
個位元組。 此子串流以單個 uint32_t
開頭,它將是以下值之一
enum class SectionContrSubstreamVersion : uint32_t {
Ver60 = 0xeffe0000 + 19970605,
V2 = 0xeffe0000 + 20140516
};
Ver60
是到目前為止在 PDB 中觀察到的唯一值。 之後是一個固定長度結構的陣列。 如果版本為 Ver60
,則它是 SectionContribEntry
結構的陣列(這是 ModInfo
類型中的巢狀結構)。 如果版本為 V2
,則它是 SectionContribEntry2
結構的陣列,定義如下
struct SectionContribEntry2 {
SectionContribEntry SC;
uint32_t ISectCoff;
};
第二個欄位的目的尚不清楚。 名稱暗示它是 COFF 區段的索引,但這也描述了現有的欄位 SectionContribEntry::Section
。
區段地圖子串流¶
從 區段貢獻子串流 結束後立即開始的偏移量 0
處開始,並消耗 Header->SectionMapSize
個位元組。 此子串流以 4
位元組標頭開頭,後跟一個固定長度記錄的陣列。 標頭和記錄具有以下佈局
struct SectionMapHeader {
uint16_t Count; // Number of segment descriptors
uint16_t LogCount; // Number of logical segment descriptors
};
struct SectionMapEntry {
uint16_t Flags; // See the SectionMapEntryFlags enum below.
uint16_t Ovl; // Logical overlay number
uint16_t Group; // Group index into descriptor array.
uint16_t Frame;
uint16_t SectionName; // Byte index of segment / group name in string table, or 0xFFFF.
uint16_t ClassName; // Byte index of class in string table, or 0xFFFF.
uint32_t Offset; // Byte offset of the logical segment within physical segment. If group is set in flags, this is the offset of the group.
uint32_t SectionLength; // Byte count of the segment or group.
};
enum class SectionMapEntryFlags : uint16_t {
Read = 1 << 0, // Segment is readable.
Write = 1 << 1, // Segment is writable.
Execute = 1 << 2, // Segment is executable.
AddressIs32Bit = 1 << 3, // Descriptor describes a 32-bit linear address.
IsSelector = 1 << 8, // Frame represents a selector.
IsAbsoluteAddress = 1 << 9, // Frame represents an absolute address.
IsGroup = 1 << 10 // If set, descriptor represents a group.
};
其中許多欄位尚不清楚,因此將不再進一步討論。
檔案資訊子串流¶
從 區段地圖子串流 結束後立即開始的偏移量 0
處開始,並消耗 Header->SourceInfoSize
個位元組。 此子串流定義從模組到對該模組做出貢獻的原始程式碼檔案的映射。 由於多個模組可以使用相同的原始程式碼檔案(例如,標頭檔),因此此子串流使用字串表來僅儲存每個唯一檔案名稱一次,然後讓每個模組使用字串表中的偏移量,而不是直接嵌入字串的值。 此子串流的格式如下
struct FileInfoSubstream {
uint16_t NumModules;
uint16_t NumSourceFiles;
uint16_t ModIndices[NumModules];
uint16_t ModFileCounts[NumModules];
uint32_t FileNameOffsets[NumSourceFiles];
char NamesBuffer[][NumSourceFiles];
};
NumModules - 包含在此子串流中的原始程式碼檔案資訊的模組數。 應與 ref:dbi_header 中的對應值相符。
NumSourceFiles:理論上,這應該包含此子串流包含資訊的原始程式碼檔案數。 但這會帶來一個問題,因為此欄位的寬度為 16
位,這將阻止程式中擁有超過 64K 個原始程式碼檔案。 在檔案格式的早期版本中,情況似乎就是這樣。 為了支援更多,此欄位被簡單地忽略,並通過對 ModFileCounts
陣列的值求和來動態計算(如下討論)。 簡而言之,應忽略此值。
ModIndices - 此陣列存在,但似乎沒有用處。
ModFileCountArray - NumModules
整數的陣列,每個整數都包含對指定索引處的模組做出貢獻的原始程式碼檔案數。 雖然每個單獨的模組都限制為 64K 個貢獻原始程式碼檔案,但所有模組的原始程式碼檔案的聯集可能大於 64K。 因此,原始程式碼檔案的實際數量是通過對此陣列求和來計算的。 請注意,對此陣列求和不會給出唯一原始程式碼檔案的數量,而僅給出對模組的原始程式碼檔案貢獻的總數。
FileNameOffsets - NumSourceFiles 整數的陣列(其中 NumSourceFiles 在此處是指從對 ModFileCountArray 求和獲得的 32 位元值),其中每個整數都是指向以 null 結尾的字串的 NamesBuffer 中的偏移量。
NamesBuffer - 包含實際原始程式碼檔案名稱的以 null 結尾的字串陣列。
類型伺服器地圖子串流¶
從 檔案資訊子串流 結束後立即開始的偏移量 0
處開始,並消耗 Header->TypeServerMapSize
個位元組。 儘管假定它以某種方式與 /Zi
和 mspdbsrv.exe
的使用有關,但此子串流的用途和佈局均不清楚。 此子串流將不再進一步討論。
EC 子串流¶
從 類型伺服器地圖子串流 結束後立即開始的偏移量 0
處開始,並消耗 Header->ECSubstreamSize
個位元組。 據推測,這與 MSVC 中的編輯和繼續支援有關。 LLVM 不支援編輯和繼續,因此將不再進一步討論此串流。
選用偵錯標頭串流¶
從 EC 子串流 結束後立即開始的偏移量 0
處開始,並消耗 Header->OptionalDbgHeaderSize
個位元組。 此欄位是一個串流索引陣列(例如,uint16_t
),每個索引都識別較大的 MSF 檔案中的串流索引,其中包含一些額外的偵錯資訊。 此陣列的每個位置都有特殊的含義,允許人們確定參考串流中偵錯資訊的類型。 目前已理解 11
個索引,儘管有可能會有更多。 每個串流的佈局通常與 PE/COFF 檔案中的特定偵錯資料目錄類型完全對應。 這些欄位的格式可以在 Microsoft PE/COFF 規範中找到。 如果這些欄位中的任何一個為 -1,則表示 PDB 中不存在相應類型的偵錯資訊。
FPO Data - DbgStreamArray[0]
。 參考串流中的資料是 FPO_DATA
結構的陣列。 這包含來自任何連結器輸入的任何 .debug$F
區段的重新定位內容。
Exception Data - DbgStreamArray[1]
。 參考串流中的資料是 IMAGE_DEBUG_TYPE_EXCEPTION
類型的偵錯資料目錄。
Fixup Data - DbgStreamArray[2]
。 參考串流中的資料是 IMAGE_DEBUG_TYPE_FIXUP
類型的偵錯資料目錄。
Omap To Src Data - DbgStreamArray[3]
。 參考串流中的資料是 IMAGE_DEBUG_TYPE_OMAP_TO_SRC
類型的偵錯資料目錄。 這用於在檢測程式碼和未檢測程式碼之間映射位址。
Omap From Src Data - DbgStreamArray[4]
。 參考串流中的資料是 IMAGE_DEBUG_TYPE_OMAP_FROM_SRC
類型的偵錯資料目錄。 這用於在檢測程式碼和未檢測程式碼之間映射位址。
Section Header Data - DbgStreamArray[5]
。 來自原始可執行檔的所有區段標頭的傾印。
Token / RID Map - DbgStreamArray[6]
。 此串流的佈局尚不清楚,但假定它是從 CLR Token
到 CLR Record ID
的映射。 有關更多資訊,請參閱 ECMA 335。
Xdata - DbgStreamArray[7]
。 .xdata
區段從可執行檔的副本。
Pdata - DbgStreamArray[8]
。 假定這是 .pdata
區段從可執行檔的副本,但這會使其與 DbgStreamArray[1]
相同。 這兩個索引之間的差異尚不清楚。
New FPO Data - DbgStreamArray[9]
。 參考串流中的資料是 IMAGE_DEBUG_TYPE_FPO
類型的偵錯資料目錄。 請注意,這與 DbgStreamArray[0]
不同,因為 .debug$F
區段僅由 MASM 發出。 因此,如果 MASM 物件檔案和 cl 物件檔案都連結到同一個程式中,則兩者都可能出現在同一個 PDB 中。
Original Section Header Data - DbgStreamArray[10]
。 類似於 DbgStreamArray[5]
,但包含在執行任何二進位翻譯之前的區段標頭。 這可以與 DebugStreamArray[3]
和 DbgStreamArray[4]
結合使用,以映射檢測程式碼和未檢測位址。