PDB DBI(デバッグ情報)ストリーム

はじめに

PDB DBIストリーム(インデックス3)は、PDBファイルの中で最も大きく、最も重要なストリームの1つです。プログラムのコンパイル方法(コンパイルフラグなど)、プログラムをリンクするために使用されたコンパイル済み単位(オブジェクトファイルなど)、プログラムの構築に使用されたソースファイル、および各コンパイル済み単位に関するより詳細な情報を含む他のストリームへの参照(各コンパイル済み単位に含まれる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はmspdb.dllを使用しないため、LLVMには明らかに適用されません。

  • **SymRecordStream** - プログラムで使用されるすべてのCodeViewシンボルレコードを含むストリーム。これは重複排除に使用されるため、多くの異なるコンパイル済み単位が、各モジュールストリームに完全なレコードコンテンツを含めることなく、同じシンボルを参照できます。

  • **PdbDllRbld** - 不明

  • **MFCTypeServerIndex** - タイプサーバーマップサブストリーム内のMFCタイプサーバーのインデックス。

  • **Flags** - プログラムの構築方法に関するさまざまな情報を含むビットフィールド。レイアウトは以下のとおりです。

uint16_t WasIncrementallyLinked : 1;
uint16_t ArePrivateSymbolsStripped : 1;
uint16_t HasConflictingTypes : 1;
uint16_t Reserved : 13;

自己説明的でないものの1つは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行情報を表すバイト数。C11ByteSizeC13ByteSizeのうち、最大で1つがゼロ以外になります。最新のPDBは常にC11ではなくC13を使用します。

  • **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;
};

2番目のフィールドの目的はよく理解されていません。名前から、これは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個の整数の配列(ここでNumSourceFilesModFileCountArrayの合計から得られる32ビット値を指します)。各整数は、ヌル終端文字列を指すNamesBufferへのオフセットです。

NamesBuffer - 実際のソースファイル名を含むヌル終端文字列の配列。

タイプサーバーマップサブストリーム

ファイル情報サブストリームの終了直後、オフセット0から始まり、Header->TypeServerMapSizeバイトを消費します。このサブストリームの目的とレイアウトは理解されていませんが、/Zimspdbsrv.exeの使用と何らかの関連があると推測されます。このサブストリームについては、これ以上は説明しません。

ECサブストリーム

タイプサーバーマップサブストリームの終了直後、オフセット0から始まり、Header->ECSubstreamSizeバイトを消費します。これは、MSVCの編集と続行のサポートに関連していると考えられます。LLVMは編集と続行をサポートしていないため、このストリームについてはこれ以上は説明しません。

オプションのデバッグヘッダーストリーム

ECサブストリームの終了直後、オフセット0から始まり、Header->OptionalDbgHeaderSizeバイトを消費します。このフィールドはストリームインデックス(例:uint16_t)の配列であり、それぞれが追加のデバッグ情報を含むより大きなMSFファイル内のストリームインデックスを識別します。この配列の各位置には特別な意味があり、参照されているストリームにどのような種類のデバッグ情報があるかを判断できます。11個のインデックスが現在理解されていますが、もっとある可能性があります。各ストリームのレイアウトは、一般的にPE/COFFファイルの特定の種類のデバッグデータディレクトリと完全に一致します。これらのフィールドの形式は、Microsoft PE/COFF仕様にあります。これらのフィールドのいずれかが-1の場合、対応するタイプのデバッグ情報はPDBに存在しません。

FPOデータ - DbgStreamArray[0]。参照されているストリームのデータは、FPO_DATA構造体の配列です。これには、リンカー入力のいずれかからの.debug$Fセクションの再配置された内容が含まれています。

例外データ - DbgStreamArray[1]。参照されているストリームのデータは、IMAGE_DEBUG_TYPE_EXCEPTIONタイプのデバッグデータディレクトリです。

修正データ - DbgStreamArray[2]。参照されているストリームのデータは、IMAGE_DEBUG_TYPE_FIXUPタイプのデバッグデータディレクトリです。

Omap to Srcデータ - DbgStreamArray[3]。参照されているストリームのデータは、IMAGE_DEBUG_TYPE_OMAP_TO_SRCタイプのデバッグデータディレクトリです。これは、インストルメント済みコードとインストルメントされていないコード間のアドレスのマッピングに使用されます。

Omap from Srcデータ - DbgStreamArray[4]。参照されているストリームのデータは、IMAGE_DEBUG_TYPE_OMAP_FROM_SRCタイプのデバッグデータディレクトリです。これは、インストルメント済みコードとインストルメントされていないコード間のアドレスのマッピングに使用されます。

セクションヘッダーデータ - DbgStreamArray[5]。元の実行可能ファイルからのすべてのセクションヘッダーのダンプ。

トークン/RIDマップ - DbgStreamArray[6]。このストリームのレイアウトは理解されていませんが、CLR TokenからCLR Record IDへのマッピングであると推測されます。ECMA 335を参照してください。

Xdata - DbgStreamArray[7]。実行可能ファイルからの.xdataセクションのコピー。

Pdata - DbgStreamArray[8]。これは、実行可能ファイルからの.pdataセクションのコピーであると推測されますが、それはDbgStreamArray[1]と同一になります。これらの2つのインデックスの違いはよく理解されていません。

新しいFPOデータ - DbgStreamArray[9]。参照されているストリームのデータは、IMAGE_DEBUG_TYPE_FPOタイプのデバッグデータディレクトリです。.debug$FセクションはMASMによってのみ出力されるため、これはDbgStreamArray[0]とは異なります。したがって、MASMオブジェクトファイルとclオブジェクトファイルの両方が同じプログラムにリンクされている場合、両方が同じPDBに表示される可能性があります。

元のセクションヘッダーデータ - DbgStreamArray[10]DbgStreamArray[5]に似ていますが、バイナリ変換が行われる前のセクションヘッダーが含まれています。これはDebugStreamArray[3]DebugStreamArray[4]と組み合わせて、インストルメント済みアドレスとインストルメントされていないアドレスをマッピングするために使用できます。