purepdb
A minimal, dependency-free pure-Python parser for Microsoft PDB debug-info files. Purpose-built to answer one question well: what are the functions in this binary and where are their entry points?
It is not a reimplementation of llvm-pdbutil — it is a thin vertical slice
through the same format stack, written from the published format
documentation. See NOTICE for provenance and prior art.
Install
uv pip install -e '.[dev]' # dev extra is just pytest
Runtime dependencies: none. Python 3.9+.
Usage
from purepdb import PDB
pdb = PDB.open("app.pdb")
for fn in pdb.functions():
print(hex(fn.rva or 0), fn.name)
# fn.segment, fn.offset, fn.code_size, fn.source, fn.aliases
rva is image-relative. Add the PE image base yourself if you need virtual
addresses.
aliases holds the other names at the same entry point. Linkers fold identical
bodies (/OPT:ICF, and rust-lld by default), so one address legitimately
carries several correct names; fn.names gives all of them with fn.name
first. On sqlite3 x86 that is 357 of 3620 functions, worst case 3 names.
CLI:
purepdb functions app.pdb # name + entry-point RVA
purepdb publics app.pdb
purepdb info app.pdb
purepdb diagnose app.pdb # what the PDB contains, and why a listing is thin
When a listing comes back short
Every failure mode this parser has on real files produces an empty result
rather than an exception, so diagnose() exists to tell them apart:
$ purepdb diagnose app.pdb
proc records : 0
public records : 7400
WARNING: no procedure records in 285 module streams (dominant kinds:
0x1167x110161, S_TRAMPOLINEx4610, ...); function names can only come from the
7400 public records. This is what /DEBUG:FASTLINK and some pre-2010 toolchains
produce
The CLI prints these warnings automatically after functions and publics.
Two things worth knowing about publics
They live in the symbol-record stream. DBI's PublicStreamIndex names a
hash stream holding offsets, not records — scanning it for S_PUB32 finds
nothing at all, silently. purepdb.gsi documents the layout; the publics stream
is used only for its address map, which supplies address ordering.
The function flag is not reliable across linkers. link.exe sets
PUBLIC_FLAG_FUNCTION on every code public (all 438 of sqlite3 x86's).
rust-lld leaves it clear on 143 of 280, including mainCRTStartup and
__chkstk. So a public also counts as a function when it resolves into an
executable section — worth 36% of the functions in a Rust PE. Pass
functions(code_publics=False) for flag-only behaviour.
Scope
Supported: MSF 7.00 container; PDB info stream; DBI stream (module list,
publics/symbol-record streams, optional debug header); CodeView S_PUB32,
S_GPROC32/S_LPROC32 (and _ID variants), S_GDATA32/S_LDATA32;
section-header table for segment:offset -> RVA.
Not supported: TPI/IPI type decoding, line/source tables, demangling (names
come back raw), PDBs whose section info comes only from the DBI Section Map.
/DEBUG:FASTLINK PDBs yield publics only, and say so.
Tests
.venv/bin/python -m pytest -q
Two layers. Synthetic tests build MSF/PDB byte streams with a builder
independent of the reader, so they exercise a real serialise→parse round trip.
Golden tests run against real link.exe and rust-lld output in tests/data/
and cross-check against the companion PE image — section table, and the address
of every exported function after following its jmp thunk. The PE reader in
tests/_pe.py is stdlib-only and never consults the PDB, so agreement is
evidence rather than a shared assumption.
tests/data/ is in the repository but excluded from the sdist and wheel, so
installing purepdb does not pull down 12 MB of binaries. Those tests skip when
the data is absent — clone the repo to run them.
The suite needs no external tool. Results are also cross-checked
record-by-record against llvm-pdbutil during development, where that toolchain
is available.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file purepdb-0.2.0.tar.gz.
File metadata
- Download URL: purepdb-0.2.0.tar.gz
- Upload date:
- Size: 30.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9bde95f4a095f37b34ea13a06e736ccd7e6b27e3ebb8e706d2c24b456f840ece
|
|
| MD5 |
5071238d5894ee706d7bfb14ba9b47bb
|
|
| BLAKE2b-256 |
72e6445328c7f83d44cd19db1c483045809d094422e1d7ad84399e227559ccf2
|
File details
Details for the file purepdb-0.2.0-py3-none-any.whl.
File metadata
- Download URL: purepdb-0.2.0-py3-none-any.whl
- Upload date:
- Size: 24.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e44726e265876c9eb36500fa7a488d9ba5e5896a796c28fbef0b8b1bc3e99eb1
|
|
| MD5 |
11dcac012bbe64ab7006919c262c1e79
|
|
| BLAKE2b-256 |
f01cf32f89f94a490934ad77ca335507d87b513fd272117637a82faa5c82c455
|