dwarf-expert
Analyzes DWARF debug information in compiled binaries. Use when inspecting .debug_* sections, DIE trees, or DW_TAG_/DW_AT_ entries with dwarfdump/llvm-dwarfdump or readelf, verifying debug info with llvm-dwarfdump --verify, answering DWARF standard questions, or writing code that parses DWARF (libdw
By trailofbits · 4,534 installs
npx skills add trailofbits/skills --skill dwarf-expert
Source repository · Upstream listing
DWARF Expert
Expertise for DWARF debug info: parsing and searching it, verifying its
integrity, answering questions about the standard, and writing code that
consumes it. Out of scope: runtime debugging (use gdb/lldb), reverse
engineering beyond the DWARF sections (use Ghidra/IDA), and compiler specific
DWARF generation bugs.
Authoritative Sources
When precision matters, look standard details up instead of answering from memory:
1. dwarfstd.org — the official specification. Web search specific sections,
e.g. "DWARF5 DW TAG subprogram attributes site:dwarfstd.org".
2. LLVM — llvm/lib/DebugInfo/DWARF/ is a reliable reference implementation:
DWARFDie.cpp (DIE and attribute access), DWARFUnit.cpp (compilation units),
DWARFDebugLine.cpp (line tables), DWARFVerifier.cpp (validation).
3. libdwarf — the reference C implementation at github.com/davea42/libdwarf code.
Parsing and Searching with dwarfdump
Prefer dwarfdump over readelf for DWARF specific work. Two implementations
exist — libdwarf's dwarfdump and LLVM's llvm dwarfdump — with different
options, and a bare dwarfdump command may be either: check dwarfdump version
first. The options below are LLVM's.
On macOS, linked Mach O executables do not carry DWARF: it stays in the .o
files until dsymutil collects it into a .dSYM bundle. Point dwarfdump at
the dSYM (or the object files), not the executable. pyelftools is ELF only —
for Mach O scripted work, stay with the LLVM tools.
all : dump every DWARF section; debug info , debug line , etc. dump one
show children [ recurse depth=<n ] : include child DIEs when printing
selected entries — parameters, locals, and struct members are children of
function and type DIEs
show parents [ parent recurse depth=<n ] : include parent DIEs
show form : print attribute form types, for when encoding details matter
find=<name : exact name lookup via the accelerator tables — fast but not
exhaustive; fall back to name when it misses
name=<pattern [ ignore case] [ regex] : exhaustive DIE name search
lookup=<address : find the DIE covering an address
verbose : print low level encoding detail
Searching DIEs
Escalate through these strategies as the query grows more complex:
1. Name or address match : find , then name ; lookup for addresses.
2. Attribute or type queries (e.g. all parameters of type float ): dump and
filter. grep B pulls in the header line carrying each DIE's offset:
llvm dwarfdump file grep B 5 "float \ " grep DW TAG formal parameter ,
then print each DIE at its offset with debug info=<offset show children
( lookup takes a program address, not a DIE offset).
3. Multi attribute or structural queries : when grep pipelines turn brittle,
write a Python script using pyelftools instead.
Verifying DWARF Integrity
llvm dwarfdump verify <binary : structural checks (unit chains, DIE
relationships, address ranges). error display=<quiet summary details full
controls detail; verify json=<path writes a machine readable error
summary; quiet for exit code only checks.
llvm dwarfdump statistics <binary : debug info quality metrics as JSON —
compare across compiler versions or optimization levels to catch regressions.
Verify after producing DWARF (compilers, binary rewriters), when a debugger
misbehaves on a binary, and when developing DWARF tooling against known good
files.
When a current generation compiler emitted an old DWARF version, the build
explicitly passed gdwarf N — modern gcc and clang default to v4/v5, so check
the build system rather than assuming a toolchain default. GCC embeds its flags
in DW AT producer , so the pin is often readable right there; clang's producer
string carries no flags. Old versions remain common in the wild and read the
same way apart from surface forms: in v2 output, member offsets appear as
location expressions ( DW OP plus uconst ) and linkage names as
DW AT MIPS linkage name .
readelf
For general ELF structure, or when dwarfdump is unavailable:
debug dump=<section : dump a DWARF section ( info , line , ...)
dwarf depth=<n / dwarf start=<n : limit DIE depth / start offset
Writing Code That Parses DWARF
Prefer an existing library over parsing by hand:
Library Language Notes
libdwarf C/C++ github.com/davea42/libdwarf code — low level; used to implement dwarfdump
pyelftools Python github.com/eliben/pyelftools — also parses ELF in general
gimli Rust github.com/gimli rs/gimli — pair with object to load container files
debug/dwarf Go standard library
LibObjectFile .NET github.com/xoofx/LibObjectFile — also handles ELF/PE object files
Default to Python with pyelftools for one off scripts unless the task dictates
otherwise.
DWARF specific pitfalls to handle — and to check for when reviewing DWARF code:
Attributes are optional: a DIE may omit DW AT name , DW AT type , ranges, etc.
Attribute indirection: a DIE's attributes may live on the DIE referenced by its
DW AT abstract origin (inlined instances) or DW AT specification
(out of line definitions) — resolve the chain before concluding data is absent.
Type chains: qualifiers and modifiers ( DW TAG const type ,
DW TAG pointer type , ...) wrap the underlying type; walk DW AT type links to
reach the base type.