A PDB file is an MSF (multi-stream format) file: a small file system that stores numbered streams in fixed-size blocks, so that the linker can rewrite parts of it in place. Microsoft has published its own reader and writer source in the microsoft-pdb repository, and the LLVM project documents the layout in The PDB File Format; this page follows both. All numbers are little-endian. The streams themselves are on the streams page.
Superblock
The superblock is at offset 0, in block 0.
| Offset | Size | Name | Meaning |
|---|---|---|---|
| 0 | 32 | FileMagic | Microsoft C/C++ MSF 7.00, then 0D 0A 1A 44 53 00 00 00 (carriage return, line feed, end-of-file mark, DS and three zero bytes). |
| 32 | 4 | BlockSize | Size of every block: 512, 1024, 2048 or 4096. LLVM reads and writes only 4096. |
| 36 | 4 | FreeBlockMapBlock | Which free block map is active: 1 or 2. |
| 40 | 4 | NumBlocks | Blocks in the file; NumBlocks × BlockSize is the file size. |
| 44 | 4 | NumDirectoryBytes | Size of the stream directory in bytes. |
| 48 | 4 | Unknown | Not used by readers. |
| 52 | 4 | BlockMapAddr | Number of the block that holds the list of the directory’s blocks: an array of ⌈NumDirectoryBytes / BlockSize⌉ 32-bit block numbers. |
00000000 4D 69 63 72 6F 73 6F 66 74 20 43 2F 43 2B 2B 20 Microsoft C/C++
00000010 4D 53 46 20 37 2E 30 30 0D 0A 1A 44 53 00 00 00 MSF 7.00...DS...
00000020 00 10 00 00 01 00 00 00 9A 02 00 00 B8 03 00 00 ................
00000030 00 00 00 00 99 02 00 00 ........
Blocks and intervals
The file is divided into intervals of BlockSize blocks. Each interval starts with one data block, then a block of free block map 1, then a block of free block map 2, then BlockSize − 3 more data blocks. With 4096-byte blocks:
| Blocks | Contents |
|---|---|
| 0 | Superblock. |
| 1 | Free block map 1 (FPM1). |
| 2 | Free block map 2 (FPM2). |
| 3–4095 | Data. |
| 4096 | Data. |
| 4097, 4098 | FPM1 and FPM2. |
| 4099–8191 | Data, and so on. |
The file may end after any block.
Free block maps
Each free block map is a bit array with one bit per block of the file: 1 means the block is free, 0 that it is in use. One map block of 4096 bytes covers 32,768 blocks, eight intervals, so most map blocks after the first are not needed; they are kept for compatibility.
There are two maps so that a change can be committed safely: the writer builds the new map in the inactive one, then switches FreeBlockMapBlock in the superblock.
Stream directory
The directory is itself stored in blocks listed at BlockMapAddr. Read those blocks in order and join them; the first NumDirectoryBytes bytes are:
| Field | Size | Meaning |
|---|---|---|
| NumStreams | 4 | Number of streams. |
| StreamSizes | 4 × NumStreams | Size of each stream in bytes. |
| StreamBlocks | 4 per block | For each stream in turn, the numbers of its ⌈size / BlockSize⌉ blocks. |
A stream’s blocks may be anywhere and in any order, and streams may interleave. To read stream n: skip the block lists of streams 0 to n−1, take its block numbers, read each block (block × BlockSize) and keep the first StreamSizes[n] bytes. With 4096-byte blocks, a 9,000-byte stream has 3 blocks and a 16,000-byte stream 4.
Stream 0 holds the previous version of the directory, kept from before the last write. Streams 1 to 4 have fixed meanings; the rest are found through them (see streams).
Sources
LLVM, The MSF File Format (superblock, intervals, free block maps, stream directory) and The PDB File Format (stream 0). The hexadecimal dump is an illustration built from those field definitions, not a copy of a real file.