Summary
PdbSymbolProvider disables itself silently when pdbparse is absent, and pdbparse is both GPL-licensed (SMDA is BSD-2-Clause) and unmaintained since 2020. In practice this means PDB-bearing PEs get no names at all, with no indication that anything was skipped.
Current behaviour
try:
import pdbparse
from pdbparse.undname import undname
except ImportError:
pdbparse = None
LOGGER.debug("3rd party library pdbparse (use fork @ ...) not installed - ...")
pdbparse is not a declared dependency, so a default install never has it. The message is at debug level, so the default logging configuration hides it. Result: a PE with a perfectly good PDB beside it yields 1 named function (original_entry_point), and nothing says why.
The dependency itself is problematic
|
|
PyPI pdbparse licence |
GPL (License :: OSI Approved :: GNU General Public License (GPL)) |
| SMDA licence |
BSD 2-Clause |
Last pdbparse release |
2020-02-01 (v1.5) |
| Upstream state |
the working variant is a third-party fork (VPaulV), not the PyPI package |
An optional import softens the licence question, but recommending a GPL library in the docstring of a BSD project is still worth a second look.
I checked whether anything better exists in 2026 — it does not
You mentioned the original goal was pure-Python with no native dependency. Re-surveying:
pdbpy ("Pure python implementation of parsing PDB debug information files") — a single 0.0.1 release, and it does not import: ModuleNotFoundError: No module named 'dtypes'. Not viable.
lief.pdb — the API exists (lief.pdb.load, DebugInfo, Function, PublicSymbol), which would be ideal since LIEF is already a dependency. But it requires LIEF "extended", the commercial build; the open PyPI wheel returns None with "Debug info not available for this build."
libpdb (shareef12) — C, Linux, would need bindings.
pdbparse — as above.
So there is still no maintained, permissively-licensed, pure-Python option.
Suggestion: an optional llvm-pdbutil provider
llvm-pdbutil is maintained, ships in distro LLVM packages, and reads rust-lld-produced PDBs correctly (verified). Parsing its output requires no new Python dependency and no GPL code:
llvm-pdbutil dump --publics <pdb> → S_PUB32 records, raw mangled names, with addr = SECT:OFFSET
llvm-pdbutil dump --symbols <pdb> → S_GPROC32 / S_LPROC32 records, with addr = SECT:OFFSET, code size = N
Virtual address = imagebase + sections[sect-1].virtual_address + offset.
Two findings from implementing this that are worth having in SMDA regardless of the parser used:
-
Publics alone are a poor source. On a Rust msvc binary, publics gave 31.4% naming; adding the S_GPROC32/S_LPROC32 procedure records took it to 54.8%. The publics stream includes imports and data and omits many functions.
-
Most of the remainder are fragments, not anonymous functions. MSVC-style codegen outlines cold paths and EH funclets into separate chunks, and SMDA reports each chunk as its own function. Of 1470 functions with no symbol at their entry, 1439 (97.9%) fall inside a procedure's [addr, addr+code size) range. Labelling those from the containing procedure took naming from 54.8% to 99.0% (99.0% of functions above the 10-instruction threshold).
Worth noting for any implementation: label such a fragment relative to its parent (<parent>$+0x<delta>) rather than with an absolute offset, so the label is stable across builds and does not break MCRIT's (pic_hash, function_name) dedup.
Measured impact
Same binary (Rust regex + flate2, x86_64-pc-windows-msvc, debuginfo=2, linked with rust-lld), 3391 functions:
|
named |
| SMDA 4.4.4 as shipped |
1 (0.0%) |
| publics only |
1066 (31.4%) |
| publics + proc records |
1858 (54.8%) |
| + fragment attribution |
3356 (99.0%) |
Minimal ask
Even without changing the parser, raising the "pdbparse not installed" message from debug to warning — or warning when a .pdb sits next to the input and no PDB provider is active — would turn a silent no-op into something diagnosable.
Versions: SMDA 4.4.4, Python 3.12.13, LLVM 14, Linux x86_64.
Summary
PdbSymbolProviderdisables itself silently whenpdbparseis absent, andpdbparseis both GPL-licensed (SMDA is BSD-2-Clause) and unmaintained since 2020. In practice this means PDB-bearing PEs get no names at all, with no indication that anything was skipped.Current behaviour
pdbparseis not a declared dependency, so a default install never has it. The message is atdebuglevel, so the default logging configuration hides it. Result: a PE with a perfectly good PDB beside it yields 1 named function (original_entry_point), and nothing says why.The dependency itself is problematic
pdbparselicenceLicense :: OSI Approved :: GNU General Public License (GPL))pdbparsereleaseAn optional import softens the licence question, but recommending a GPL library in the docstring of a BSD project is still worth a second look.
I checked whether anything better exists in 2026 — it does not
You mentioned the original goal was pure-Python with no native dependency. Re-surveying:
pdbpy("Pure python implementation of parsing PDB debug information files") — a single0.0.1release, and it does not import:ModuleNotFoundError: No module named 'dtypes'. Not viable.lief.pdb— the API exists (lief.pdb.load,DebugInfo,Function,PublicSymbol), which would be ideal since LIEF is already a dependency. But it requires LIEF "extended", the commercial build; the open PyPI wheel returnsNonewith "Debug info not available for this build."libpdb(shareef12) — C, Linux, would need bindings.pdbparse— as above.So there is still no maintained, permissively-licensed, pure-Python option.
Suggestion: an optional
llvm-pdbutilproviderllvm-pdbutilis maintained, ships in distro LLVM packages, and readsrust-lld-produced PDBs correctly (verified). Parsing its output requires no new Python dependency and no GPL code:llvm-pdbutil dump --publics <pdb>→S_PUB32records, raw mangled names, withaddr = SECT:OFFSETllvm-pdbutil dump --symbols <pdb>→S_GPROC32/S_LPROC32records, withaddr = SECT:OFFSET, code size = NVirtual address =
imagebase + sections[sect-1].virtual_address + offset.Two findings from implementing this that are worth having in SMDA regardless of the parser used:
Publics alone are a poor source. On a Rust msvc binary, publics gave 31.4% naming; adding the
S_GPROC32/S_LPROC32procedure records took it to 54.8%. The publics stream includes imports and data and omits many functions.Most of the remainder are fragments, not anonymous functions. MSVC-style codegen outlines cold paths and EH funclets into separate chunks, and SMDA reports each chunk as its own function. Of 1470 functions with no symbol at their entry, 1439 (97.9%) fall inside a procedure's
[addr, addr+code size)range. Labelling those from the containing procedure took naming from 54.8% to 99.0% (99.0% of functions above the 10-instruction threshold).Worth noting for any implementation: label such a fragment relative to its parent (
<parent>$+0x<delta>) rather than with an absolute offset, so the label is stable across builds and does not break MCRIT's(pic_hash, function_name)dedup.Measured impact
Same binary (Rust
regex+flate2,x86_64-pc-windows-msvc,debuginfo=2, linked withrust-lld), 3391 functions:Minimal ask
Even without changing the parser, raising the "pdbparse not installed" message from
debugtowarning— or warning when a.pdbsits next to the input and no PDB provider is active — would turn a silent no-op into something diagnosable.Versions: SMDA 4.4.4, Python 3.12.13, LLVM 14, Linux x86_64.