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))アクセスは、前に説明した型インデックスオフセット配列(存在する場合)によって提供されます。