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型レコードで詳しく説明されています。

型インデックス

型インデックスは、オブジェクトファイルの.debug$TセクションまたはPDBファイルのTPIストリームまたはIPIストリーム内の型を一意に識別する32ビット整数です。TPIストリームの最初の型レコードの型インデックスの値は、TPIストリームヘッダーTypeIndexBeginメンバーによって指定されますが、実際にはこの値は常に0x1000(4096)です。

上位ビットが設定されている型インデックスは、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
};

ポインタの場合、ビット数はモードで表されます。void*は、32ビットでビルドされた場合は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ハッシュストリーム内のオフセットとサイズ。これは、最初の値が型インデックスで、2番目の値がこのインデックスを持つ型の型レコードデータ内のオフセットであるuint32_tのペアのリストです。これは、二分探索に続いて線形探索を行うことで、O(log n)の型インデックスによるルックアップを行うために使用できます。

  • **HashAdjBufferOffset / HashAdjBufferLength** - シリアル化されたハッシュテーブルのTPIハッシュストリーム内のオフセットとサイズ。このハッシュテーブルのキーはハッシュ値バッファ内のハッシュ値であり、値は型インデックスです。これは、増分リンクのシナリオで役立ちます。つまり、型が変更された場合、古いハッシュ値を新しい型インデックスにマッピングするエントリを作成できるため、PDBファイルのコンシューマは、増分リンカがガベージコレクションを行い、古いバージョンを指す参照を新しいバージョンを指すように更新することを強制することなく、常に最新の型のバージョンを持つことができます。このハッシュテーブルのレイアウトは、PDBシリアル化ハッシュテーブル形式で説明されています。

CodeView型レコードリスト

ヘッダーの後には、CodeView型レコードの可変長配列を表すTypeRecordBytesバイトのデータがあります。このようなレコードの数(つまり、配列の長さ)は、Header.TypeIndexEnd - Header.TypeIndexBeginを計算することで決定できます。

O(log(n))アクセスは、前に説明した型インデックスオフセット配列(存在する場合)によって提供されます。