Skip to content

Make export discoverable, and its output readable - #227

Open
subwire wants to merge 1 commit into
mainfrom
export-discoverability
Open

Make export discoverable, and its output readable#227
subwire wants to merge 1 commit into
mainfrom
export-discoverability

Conversation

@subwire

@subwire subwire commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Follow-up to #222. export works — it is just invisible, and its output is noisier than it needs to be.

It was never used

Across 60 solve sessions on a 40-binary benchmark, export was invoked zero times.

Not because agents tried it and rejected it. SKILL.md — 589 lines, the doc agents actually read — never mentions export. What it does say is:

Always start with list_functions and list_strings

…which is exactly the loop export exists to collapse. So that is what agents did.

The four sessions that encountered the verb at all found it by running decompiler export --help inside a batch of --help calls at command 60–85 of ~130 — help-surface spelunking two-thirds of the way in, after the analysis was already done by hand. None of them ever ran it.

One of those sessions then hand-wrote eight per-function .c files — a worse copy of export's own output — having spent 112 CLI calls to get there. That binary exports in 31 seconds.

The verb-frequency table for the arm makes the shape of the loss plain:

verb calls
list 484
batch 417
load 322
decompile 277
list_functions 165
export 0

It emits mostly stubs

Import stubs dominate the function count of any dynamically linked binary. On the crackme above:

functions pseudocode
export 117 480K
≤16-byte PLT thunks 93 (79%) 19K (45%)
real code 24 108K

Nearly half the bytes are free, werase, curs_set, __cxa_finalize. --filter cannot help here — it matches on name, and on a stripped binary the names are FUN_*. Size is the discriminator that actually separates the two populations, and the backend already knows it.

Changes

SKILL.md leads the "first moves" section with export and the export-then-grep pattern, and says when not to reach for it (very large binaries; or when you already know the one function you want). The interrogative path is unchanged and still documented — it is just no longer the only thing documented.

--min-size trims the stubs. On the same binary, --min-size 16 gives 24 functions / 108K instead of 117 / 480K, while keeping every function a human analyst annotated by hand. Opt-in: default 0 exports everything exactly as before. The manifest gains skipped_below_min_size so the trim is never silent.

Testing

New test asserts the default is unchanged (skipped_below_min_size == 0), that exported + skipped == total, that every kept function clears the threshold, and that main survives.

Note: I could not execute it locally — the fauxware fixture isn't in my container — so it runs here for the first time in CI. The behaviour it asserts I did verify directly against a real corpus binary (the 117→24 / 480K→108K figures above).

🤖 Generated with Claude Code

`export` was added in #222 to replace the load -> list_functions ->
decompile -> decompile -> ... loop that everyone otherwise scripts by
hand. Across 60 solve sessions on a 40-binary benchmark it was invoked
**zero times**.

Not because agents rejected it. SKILL.md -- 589 lines, the doc agents
actually read -- never mentions `export`. It says "Always start with
`list_functions` and `list_strings`", which is precisely the loop export
exists to collapse, so agents did that instead. The four sessions that
found the verb at all found it by running `decompiler export --help`
inside a batch of `--help` calls at command 60-85 of ~130, long after
they had done the work by hand. One of them then hand-wrote eight
per-function .c files -- a worse copy of export's own output -- having
spent 112 CLI calls to get there. A 117-function binary exports in 31
seconds.

Two changes:

**SKILL.md** now leads the "first moves" section with export, shows the
export-then-grep pattern, and says when *not* to use it (very large
binaries, or when you already know the one function you want). The
interrogative path is still documented, just no longer the only thing
documented.

**`--min-size`** makes the output worth reading rather than only
grepping. Import stubs dominate the function count of any dynamically
linked binary: on the crackme above, 93 of 117 functions were PLT
thunks and they were 45% of the exported pseudocode. `--min-size 16`
cuts that binary to 24 functions and 108K (from 117 and 480K) while
keeping every function a human analyst annotated by hand. Opt-in;
default 0 exports everything exactly as before. The manifest reports
`skipped_below_min_size` so the trim is never silent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant