v0.1.3
Highlights
- Field names where they can be proved — the snapshot's surviving
Fieldobjects plus implicit accessor symbols, rendered as attributed comments:x0 = mem((local_0), 0x17); /* _FutureListener.result (off 0x18) */. The two sources are independent and agree on 40 of 41 entries (39/39 on x64), with zero conflicts. - The decompiler's output is valid Dart —
dart analyzeerrors went 680,515 → 0 on a real Flutter app (412 files, 10,245 functions), and 0 across all 27 corpora. 87–92% of functions come out with structured control flow; the rest keep an honestgotoLabel+ NOTE header. - Progressive mode — list first (
dae libs / classes / functions / strings / fields / largest / callers / disasm), then decompile one class, method or library.dae getclass Footakes 0.03 s against 1.9 s for a full export; stdout is the data channel, so it pipes.
What's changed
Decompiler (--decompile)
- Structure, not goto. Dominators find the natural loops (back edge = header dominates its tail), diamonds become
if/else, loop headerswhile, exitsbreak/continue. Dart AOT merges identical function bodies, so those shared chunks are adopted; forward shared tails are duplicated; backward jumps to a non-header are genuinely irreducible loops and keepgotoLabel. - The output compiles. Machine syntax is rewritten (
mem/memSet/memRead2/callIndirect/gotoLabel), names are sanitised into identifiers (mixin-application class names contain&), and each file opens with a pseudo-runtime preamble that states where the machine layer ends and Dart begins. - Object-pool constants are inlined:
ldr x0, [PP, #0x17f8]becomesx0 = "Hello" /* pp+0x17f8 */(1,922 literals on the Flutter app). - Field names, when provable.
host_offset_or_field_id_is a Smi and Smis are merged into the Mint cluster, so the Mint integer is the field's word index → byte offset = word × word_size → machine displacement = offset − 1. Pinned down four ways (four_FutureListenergetters,_Uri.pathas the 5th declared field, a generic class's unboxed bitmap,Error._stackTraceat word 1). The second route reads implicit getter/setter names (kind6/7) whose body touches exactly one field; hand-written accessors are excluded because their names lie (get:_ignoreErrorreads_state). - Address fixes for appended snapshots (Mach-O
LC_NOTE, and the 2.12–2.14dart compile exetrailer → inner ELF). These had been decompiling the wrong bytes while every name-based metric stayed green; the gate now carries a prologue-rate floor (51–58% broken vs 91–100% correct). - Instructions:
cselfolds throughcmp,brkis a terminator, frame/barrier instructions become// frame:comments rather than pretending to be data flow; unmapped lines dropped to single digits on most corpora and the count is printed in the run summary.
New outputs and commands
text/fields.txt+dae fields <binary> [pattern]— class, field, source (rec= snapshot,accessor= symbol-derived) and byte offset.text/stubs.txt— instruction-table entries with no Code object (1,982 on the app, 1,295 named from their prologues), and allocation-stub names used at call sites.call_edges.txt+callgraph.dot— direct call edges and indirect call sites.dae info / libs / classes / functions / strings / fields / largest / callers / disasm / getclass / getmethod / getlib;--lib/--class/--funcalso produce a filtered export (object-layer dumps stay complete).
Gates (run on every change)
tests/dart_valid.rs— realdart analyze, zero errors expected.tests/decompiler_shape.rs— brace balance per file, statement termination, structured-rate floor, and address self-consistency.tests/field_names.rs— the two field-name routes must agree, zero conflicts, and every annotation in the output must exist in the recovered table (the no-fabrication rule).tests/cli.rs— stdout purity and ASCII-only artifacts;tests/ground_truth.rs— differential against the binaries' own.symtab.
Numbers
| arm64 sample | Flutter app (412 files) | |
|---|---|---|
dart analyze errors |
0 | 0 (was 680,515) |
| functions fully structured | 89.2% | 92.7% |
| named fields recovered | 61 | 367 |
| annotated field accesses | 218 | 438 |
All 25 SDK-version regression samples still match their archived object-layer output byte for byte.
Install
cargo install dae-rs # crates.io
brew install ejfkdev/tap/dae # macOS / Linux (Homebrew)
scoop install dae # Windows (scoop bucket)Or download the binary for your platform below (Windows/macOS/Linux × x64/arm64; x64 builds are UPX-compressed).