exportcov
Tool
tools/triage/exportcov.py(program, Triage). How much of a program's code a Ghidra export covers, where the gaps are, and where the functions it missed probably start.
Run it with ./gameframe run exportcov ARGS or python3 tools/triage/exportcov.py. Source on GitHub.
Its own help
This is the help text in the tool itself, as ./gameframe tool exportcov prints it.
text
How much of a program's code a Ghidra export covers, where the gaps are, and where the
functions it missed probably start.
Usage: exportcov.py BINARY functions.tsv [--bodies FILE] [--gaps N] [--starts FILE] [--min PERCENT]
BINARY the Windows PE that was exported (32-bit or 64-bit)
functions.tsv the export's function list (address, size, name), from scripts/ghidra-export.sh
--bodies FILE each piece of each function (start, end, entry; bodies.tsv from the same
export, read by default when it sits beside functions.tsv). Without it, each
function counts as one run of its size from its entry, which undercounts a
function whose body is in several pieces
--gaps N list the N largest gaps (default 10)
--starts FILE write the likely function starts in the gaps, one address a line, ready for
`scripts/ghidra-run.sh BINARY decompile OUT.txt $(cat FILE)`
--min PERCENT exit 1 when the coverage is below PERCENT (default 90)
It prints the bytes of the executable sections that lie in some function, as a share of all
code bytes; the largest runs of code in no function; and, for those gaps, the likely starts of
functions Ghidra missed:
- call targets: every `E8 rel32` call in the code whose target lies in a gap (a direct call
from anywhere, even from other missed code);
- gap starts: the first instruction of a gap after its padding (`nop` of any length, `int3`),
when it is on a 16-byte boundary, as compilers align functions, or begins a common prologue
(push ebp / mov ebp, esp; push of a saved register; sub esp; on x64, sub rsp or a mov to the
shadow space);
- starts after a return: in a gap, the first instruction after a `ret` (C3, or C2 nn nn) and
its padding, by the same rule.
Why: Ghidra's analysis follows direct calls from code it knows. Functions reached only through
pointers (per-object step functions, handler tables, callbacks) are missed, and so is code
between them. In one 1997 game a quarter of the code section was outside every function after
the first export (docs/knowhow/decompile-missed-pointer-functions.md). Run this after every
export.
The starts are candidates, not proof: a byte pattern in data can look like a prologue. The
batch decompile skips an address that nothing refers to unless it follows a ret or padding.
Single-byte padding (`int3`, `nop` and zeros) at the ends of a gap is not counted as missed
code. Needs no Ghidra; reads the PE with pefile and decodes with capstone (.venv).Named by
Skills: re-ghidra.