Library / SDK
eliben/pyelftools avatar
eliben/pyelftools

pyelftools: reading ELF containers and DWARF debug data from Python

Parsing ELF and DWARF in Python

2,281 stars547 forksPythonNOASSERTION

At a glance

What is it?
A pure Python, dependency free library for parsing ELF binaries and their DWARF debugging information, with example scripts for addresses, line programs and relocations.
Who is it for?
pyelftools does one thing and does it without asking you to install anything: it turns ELF files and DWARF debug information into Python objects you can walk. That makes it the right tool when a build script, a static analysis pass or a debugging tool needs section headers, symbol tables, relocations or line program information, and the alternatives would be shelling out to `readelf` and parsing its output.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 68 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 28, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A dependency free parser for two related formats

The README is short and the pitch fits in one sentence: pyelftools is a pure-Python library for parsing and analyzing ELF files and DWARF debugging information. Those are two formats with a specific relationship. ELF is the container, the executable and shared object format used by Linux and the BSDs. DWARF is the debugging data that compilers embed inside that container, describing types, variables, line numbers and call frame information.

Handling both in one library is the point. A tool that wants to map an address back to a source file needs the ELF section headers to find the debug sections and then needs a DWARF line program to do the mapping, and having one library own both halves avoids the impedance mismatch of stitching two parsers together.

There are no external dependencies, which the README calls out specifically as a reason the library is easy to use without installing anything: clone the source and adjust `PYTHONPATH` instead. That is a real advantage in environments where you cannot add packages, and it is also why the package installs cleanly into anything.

The package layout shows how the two formats are separated

The `pyproject.toml` lists every subpackage explicitly, which makes it an accurate map of the codebase:

toml
[tool.setuptools]
packages = [
    "elftools",
    "elftools.elf",
    "elftools.common",
    "elftools.dwarf",
    "elftools.ehabi",
    "elftools.construct",
    "elftools.construct.lib",
]
script-files = [
    "scripts/readelf.py",
]

`elftools.elf` is the container side: sections, segments, symbols, relocations and notes. `elftools.dwarf` is the debugging side: debug information entries, line programs, location expressions, ranges and public names. `elftools.common` holds the utilities both need, and `elftools.ehabi` covers the exception handling ABI tables, which are how a parser reads unwind information.

The `elftools.construct` and `elftools.construct.lib` pair is worth a second look, because it tells you something about the design. Those are vendored copies of Construct, a declarative parsing library. Vendoring it is what makes the no dependency claim possible: the library defines binary structures declaratively and parses them without requiring anything from PyPI. The cost is that a construct library upgrade lands in this repository rather than arriving independently.

`script-files` also shows that a `readelf.py` script ships with the package, so the familiar GNU tool interface is available as a reference implementation of how to drive the library.

The examples directory is the real documentation

Thirteen example scripts sit in `examples/`, and they are the fastest way to learn the API because each one solves a single concrete question. There is a script to decode an address, one to walk the DIE tree, one to pull filenames out of a line program, one to read location information, one to look up types through public names, and one to handle range lists.

text
elftools/
examples/
doc/
test/
scripts/
.github/

That last line matters more than it looks. Shipping a sample binary with the examples means you can run every script immediately, without needing a compiler or a matching toolchain on the machine. When your own binary does not have debug info, that saved you the first debugging session.

There is also a `reference_output/` directory, which implies the examples are checked against expected output. If you have seen other Python parsing libraries, you will recognise this pattern: scripts that double as regression tests.

The names themselves suggest what the library considers the common questions. Which source file is this address in. What type is this variable. Which functions need range list lookups because their addresses are not contiguous. These are the questions a debugger, a profiler and a symbolication service all need answered.

Public domain licensing and the typing work in progress

The licence is public domain, and the README is unambiguous that the code is in the public domain with the `LICENSE` file holding the details. The repository metadata shows `NOASSERTION` for the licence field while `pyproject.toml` declares `license = {text = "Public domain"}`, so the authoritative statement is the file in the repository rather than the GitHub sidebar.

Public domain for a parser library is a genuinely good outcome for anyone building tooling on top of it. There is no copyleft obligation, no attribution requirement in the way AGPL or GPL has one, and no question about whether your commercial product needs a licence review.

The classifiers describe the project as `Development Status :: 5 - Production/Stable`, operating system independent, with topics covering file formats, compilers, debuggers and disassemblers. Requires Python 3.10 or newer, and the authors and maintainers fields both name Eli Bendersky.

There is also a `typing` dependency group with `mypy`, `pyright`, `typeguard` and `typing_extensions`, plus a `.vscode/` directory and a `pyelftools.sublime-project` file. The presence of two independent type checkers is a reasonable signal that annotation coverage is being taken seriously, though the group being optional rather than required suggests it is work in progress rather than a finished promise.

How the test and lint tooling is wired up

The `Makefile` is the clearest statement of the development workflow, and it is short enough to read in full:

makefile
.PHONY: check test

check:
	$(RUFF) check .
	$(TY) check

test:
	python3 test/all_tests.py

Two lint passes, `ruff` and `ty`, with their versions pinned as overridable variables at the top of the file and executed through `uvx`. Running the checkers through `uvx` means contributors get the pinned version without a separate install step, and `RUFF_VERSION ?=` style variables let you override them when a new release lands.

The test entry point is a single script, `test/all_tests.py`, run with `python3` directly rather than through pytest. For a library whose examples double as regression tests, that keeps the whole suite one command with no test runner to install. The repository also has a `CHANGES` file, a `TODO`, a `SECURITY.md`, a `Makefile`, a `MANIFEST.in`, `doc/` and `test/`, plus a GitHub Actions badge in the README for the test workflow.

The last push was on 2026-07-30, with 2281 stars, 547 forks and 57 open issues on the `main` branch. There are no tagged releases to read, so the `CHANGES` file is the change history.

Where pyelftools sits against the alternatives

The obvious alternative is shelling out to `readelf`, `objdump` or `nm` and parsing the text. That works, and it is what many scripts do, but it means a subprocess per query, a text format to parse, and output that differs between binutils versions and between platforms. pyelftools reads the bytes directly, so it gives structured objects, no fork, and the same behaviour everywhere.

Against a C library like libelf, the tradeoff is speed. Pure Python parsing is slower than C, which matters if you are parsing gigabytes of binaries in a tight loop. For most uses, an ELF file is a few megabytes and the parse happens once, so the difference is not the deciding factor, and the absence of a native dependency is worth more.

Against writing your own parser, the answer is the version count problem. ELF has enough variants across endianness, class width and operating system specific extensions that a hand written parser accumulates bugs quietly. The `elftools.ehabi` subpackage is a good illustration: unwind tables are specified per architecture, and having them maintained in a widely used library rather than in your script is the whole point.

What pyelftools does not do is disassemble. It reads structure and debug data, so feeding it a binary gives you symbols, sections and line information rather than instructions. For that you still want a separate disassembler.

Editorial conclusion

pyelftools does one thing and does it without asking you to install anything: it turns ELF files and DWARF debug information into Python objects you can walk. That makes it the right tool when a build script, a static analysis pass or a debugging tool needs section headers, symbol tables, relocations or line program information, and the alternatives would be shelling out to `readelf` and parsing its output. The `examples/` directory is unusually good teaching material, with scripts for decoding an address, walking a DIE tree, extracting filenames from a line program and dumping relocations, plus a sample binary to run them against. It is public domain code with no runtime dependencies, classified as production stable, requiring Python 3.10 or newer. Start with `examples/elf_show_debug_sections.py` against `sample_exe64.elf`, then read `doc/user-guide.md` before writing your own container walks.

Frequently asked questions

How to read a .elf file?

Open it with ELFFile from elftools.elf and iterate over the sections, or walk the debug information with the DWARF API in elftools.dwarf. The examples directory has runnable scripts for the common cases, and a sample binary ships with the library so you can try them without compiling anything first.

What is the .elf file format used for?

ELF is the executable and object file format used by Linux and the BSDs for binaries, shared libraries and object files. It is a container: it holds section headers, program segments, symbol tables, relocations and notes, and it commonly carries DWARF debugging information inside it, which is the other format pyelftools parses.

Does pyelftools need any dependencies to install?

No. It is pure Python with no external dependencies, and the README notes that this makes it easy to use straight from a clone by adjusting PYTHONPATH instead of installing anything. Requires Python 3.10 or newer, and the code is in the public domain.

Official sources

  1. eliben/pyelftools on GitHub
  2. Issues
  3. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/eliben-pyelftools.svg)](https://hysenlabs.com/projects/eliben-pyelftools)