Skip to main content

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

purepdb-0.2.0.tar.gz (30.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

purepdb-0.2.0-py3-none-any.whl (24.0 kB view details)

Uploaded Python 3

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

Hashes for purepdb-0.2.0.tar.gz
Algorithm Hash digest
SHA256 9bde95f4a095f37b34ea13a06e736ccd7e6b27e3ebb8e706d2c24b456f840ece
MD5 5071238d5894ee706d7bfb14ba9b47bb
BLAKE2b-256 72e6445328c7f83d44cd19db1c483045809d094422e1d7ad84399e227559ccf2

See more details on using hashes here.

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

Hashes for purepdb-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e44726e265876c9eb36500fa7a488d9ba5e5896a796c28fbef0b8b1bc3e99eb1
MD5 11dcac012bbe64ab7006919c262c1e79
BLAKE2b-256 f01cf32f89f94a490934ad77ca335507d87b513fd272117637a82faa5c82c455

See more details on using hashes here.

Release history Release notifications | RSS feed

0.5.0

2 files

0.4.0

2 files

0.3.0

2 files

This release

0.2.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page