Metadata-Version: 2.5
Name: purepdb
Version: 0.2.0
Summary: A minimal, dependency-free pure-Python parser for Microsoft PDB debug-info files
Project-URL: Homepage, https://github.com/danielplohmann/purepdb
Project-URL: Repository, https://github.com/danielplohmann/purepdb
Project-URL: Issues, https://github.com/danielplohmann/purepdb/issues
Author-email: Daniel Plohmann <daniel.plohmann@fkie.fraunhofer.de>
License-Expression: BSD-3-Clause
License-File: LICENSE
License-File: NOTICE
Keywords: codeview,debug-info,msf,pdb,reverse-engineering
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Information Technology
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Debuggers
Classifier: Topic :: Software Development :: Disassemblers
Classifier: Typing :: Typed
Requires-Python: >=3.9
Provides-Extra: dev
Requires-Dist: build; extra == 'dev'
Requires-Dist: hatchling; extra == 'dev'
Requires-Dist: pytest>=7; extra == 'dev'
Requires-Dist: twine; extra == 'dev'
Description-Content-Type: text/markdown

# 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](NOTICE) for provenance and prior art.

## Install

```bash
uv pip install -e '.[dev]'     # dev extra is just pytest
```

Runtime dependencies: none. Python 3.9+.

## Usage

```python
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

```bash
.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.
