Inspect a Windows PDB on Linux with llvm-pdbutil-18
You will finish with a read-only workflow for inspecting a Windows Program Database (PDB) file on Linux. It covers the container summary, stream layout, raw bytes and a YAML representation, while keeping the original file untouched. Allow about fifteen minutes if you already have a PDB file and its path.
The route
Jump straight to the step you need, or tick off Done means at the end.
You need the llvm-18 package and a readable PDB. The installed command here is Ubuntu LLVM 18.1.3. The local manual page carries an older generated LLVM version marker and does not describe every option exposed by this binary, so check the installed command's own help before copying a script between LLVM releases.
Checkpoint: set a real input path before running the examples. Do not guess a PDB from a similarly named executable. The placeholder below is deliberately invalid until you replace it:
$ command -v llvm-pdbutil-18
/usr/bin/llvm-pdbutil-18
$ llvm-pdbutil-18 --version
Ubuntu LLVM version 18.1.3
$ PDB='/path/to/program.pdb'
$ test -r "$PDB" && echo 'PDB is readable'
1. Read the PDB container metadata
Start with dump --summary. It reads the MSF container and reports facts such as block size, stream count, GUID, age and whether debug information, types, globals and publics are present:
$ llvm-pdbutil-18 dump --summary "$PDB"
Summary
============================================================
Block Size: 4096
Number of blocks: 1529
Number of streams: 313
Signature: 1758167168
Age: 1
Has Debug Info: true
Has Types: true
Has Globals: true
Has Publics: true
The numbers are properties of the input, not defaults. A different compiler, linker or PDB will produce different values. A non-zero exit status means the file could not be opened or parsed; it is not a reason to run the tool as root.
2. Map the streams before looking for records
Use dump --streams to see the numbered streams and their sizes. This is the useful first map when you do not yet know whether a file contains type, module or symbol data:
$ llvm-pdbutil-18 dump --streams "$PDB" | sed -n '1,18p'
Streams
============================================================
Stream 0 ( 48 bytes): [Old MSF Directory]
Stream 1 ( 161 bytes): [PDB Stream]
Stream 2 (1276044 bytes): [TPI Stream]
Stream 3 ( 414296 bytes): [DBI Stream]
Stream 4 ( 296198 bytes): [IPI Stream]
Stream numbers are file-specific. Do not assume that a stream number found in one PDB has the same meaning in another. Use the listing you just obtained before selecting a stream for a later command.
3. Inspect symbols or type records selectively
The dump subcommand can print much more than the container. Add one focused option at a time so that a large PDB does not bury the result you are looking for. For example, these commands ask for global symbols, module information, or one TPI type index:
$ llvm-pdbutil-18 dump --globals "$PDB" | sed -n '1,80p'
$ llvm-pdbutil-18 dump --modules "$PDB" | sed -n '1,80p'
$ llvm-pdbutil-18 dump --types --type-index=0x1000 "$PDB"
The type index is an example value, not a claim that every PDB contains it. Discover useful indexes from a broader --types run or from the file's own diagnostics. In a large investigation, --modi=N limits module-oriented output to module number N. The option applies to operations that iterate over modules, so it does not turn every other dump option into a module filter.
For a deeper dependency view, combine --dependents with --type-index or --id-index. Treat indexes as hexadecimal where the option says so, and copy them from the output rather than inventing them.
4. Examine bytes without editing the file
Use bytes when the question is about the encoded representation rather than the decoded records. A small byte range is a safe first test:
$ llvm-pdbutil-18 bytes --byte-range=0-31 "$PDB"
MSF Bytes
============================================================
Bytes (
0000: 4D696372 6F736F66 7420432F 432B2B20 4D534620 372E3030
)
The range is inclusive in the example's output, and the command only reads the source PDB. For a stream-relative view, use the stream number and optional offset and size in the form STREAM[:START]@SIZE, such as --stream-data=7:3@12. Confirm the stream number with dump --streams first.
5. Export a YAML description for review
pdb2yaml produces a textual description that can be inspected or used as a starting point for a reconstruction. Keep it in a new file rather than overwriting the source:
$ llvm-pdbutil-18 pdb2yaml --minimal "$PDB" > /tmp/program-pdb.yaml
$ sed -n '1,24p' /tmp/program-pdb.yaml
---
MSF:
SuperBlock:
FreeBlockMap: 1
NumBlocks: 1529
NumDirectoryBytes: 7324
...
--minimal omits fields with default values. Without it, the output can be much larger. The YAML is a description, not a replacement for the original binary. Keep the original until you have verified any reconstructed file.
6. Treat YAML reconstruction and merging as write operations
yaml2pdb and merge create a PDB named by --pdb=FILE. They can change an existing destination, so choose a new path in a scratch directory and check it before moving it into a build tree. These operations are not needed for ordinary inspection:
$ llvm-pdbutil-18 yaml2pdb --pdb=/tmp/rebuilt-program.pdb /tmp/program-pdb.yaml
$ test -s /tmp/rebuilt-program.pdb && echo 'new PDB exists'
$ llvm-pdbutil-18 dump --summary /tmp/rebuilt-program.pdb | sed -n '1,14p'
$ llvm-pdbutil-18 merge --pdb=/tmp/merged-program.pdb /path/to/first.pdb /path/to/second.pdb
There is no undo inside llvm-pdbutil-18. If a destination already matters, copy it before using a write command, or use a fresh filename as above. Remove only scratch files after checking them. Do not merge unrelated PDBs merely because their executable names match.
7. Diagnose the common failures
A missing or unreadable path is an input problem:
$ llvm-pdbutil-18 dump --summary /tmp/does-not-exist.pdb
llvm-pdbutil: File /tmp/does-not-exist.pdb not found
$ printf 'exit status: %s\n' "$?"
exit status: 1
Check the path with ls -l and test -r. A PDB may also be truncated, from a different toolchain, or not a PDB at all. Preserve the original and capture the exact command and diagnostic before trying another option.
The manual describes pretty as a Windows DIA SDK mode and says it is unsupported on non-Windows platforms. This LLVM 18 binary exposes a --native option and several additional subcommands, but their presence does not make every older manual-page claim portable. For a Linux script, use the locally verified native-oriented commands above and record the output of llvm-pdbutil-18 --help with the investigation.
Done means
- You confirmed the installed binary is LLVM 18.1.3 and used its help alongside the local manual.
- You checked the PDB container before selecting stream or record indexes.
- You used focused
dumporbytescommands and checked their exit status. - You wrote YAML or reconstructed PDB data to a new scratch path.
- The source PDB remains untouched, and any destination replacement has an explicit recovery path.