Skip to content

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.

Code: Apache 2.0. Content: CC0 1.0. No game material: bring your own copy.