Files
Drew T 5674587bcb feat(phase-21): T2 — fuel prefetch, Ghidra-C cache complete for the ROI pool
- headless DecompileFunctions over 263 uncached ROI-pool fns (MCP stopped, R23):
  263 ok / 0 fail / 0 no-func. cache 300 -> 563. ROI-pool fuel now 325/325 cached
  (28/28 giants, 310/310 reach-134, 7/7 capped) -> the unattended run never needs MCP.
- FIX DecompileFunctions.java: key output by entry address func_<UPPERHEX>.c (Ghidra's
  raw-import default is FUN_<lowerhex>, which every consumer + the existing 300 files
  miss). Renamed the 263; the tool fix makes re-prefetch reproducible.
- build_fuel_manifest.py --emit-prefetch: reproducible uncached-ROI-pool addr-list
  (for the T7 runbook). 349 reach-1 x1-leverage fns intentionally excluded (P9).
- ghidra/ db churn is the MCP-stop no-op save (R23 restart-noise, not staged).
2026-06-21 12:00:47 -06:00

60 lines
2.8 KiB
Java

// DecompileFunctions.java — headless postScript: batch-decompile a list of functions
// (one hex address per line in arg[0]) and write each function's C to arg[1]/<name>.c.
// Built for the Phase-17 harvest Ghidra pre-pass: dump whole-program Ghidra-C for the
// tractable residuals so harvest agents get local/global/callee disambiguation m2c lacks,
// WITHOUT live-MCP contention (agents read the cached files).
// Usage: -postScript DecompileFunctions.java <addr-list-file> <out-dir>
import ghidra.app.script.GhidraScript;
import ghidra.app.decompiler.DecompInterface;
import ghidra.app.decompiler.DecompileResults;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Function;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
public class DecompileFunctions extends GhidraScript {
@Override
public void run() throws Exception {
String[] a = getScriptArgs();
if (a.length < 2) {
println("USAGE: DecompileFunctions.java <addr-list-file> <out-dir>");
return;
}
String addrFile = a[0];
String outDir = a[1];
new File(outDir).mkdirs();
DecompInterface di = new DecompInterface();
di.openProgram(currentProgram);
List<String> lines = Files.readAllLines(Paths.get(addrFile));
int ok = 0, fail = 0, nofunc = 0;
for (String raw : lines) {
String line = raw.trim();
if (line.isEmpty()) continue;
Address addr = toAddr(line);
Function f = getFunctionAt(addr);
if (f == null) f = getFunctionContaining(addr);
if (f == null) { nofunc++; println("NOFUNC " + line); continue; }
DecompileResults res = di.decompileFunction(f, 60, monitor);
String out;
if (res != null && res.decompileCompleted()) {
out = res.getDecompiledFunction().getC();
ok++;
} else {
out = "// DECOMPILE FAILED: " + (res != null ? res.getErrorMessage() : "null result");
fail++;
}
// Key the cache file by the function's ENTRY ADDRESS as func_<UPPERHEX>.c — the
// convention every consumer uses (derive_canonical_sigs.py, gen_wave.py, the
// fuel manifest). Ghidra's default raw-import name is FUN_<lowerhex>, so naming by
// f.getName() would miss the cache; keying by address is reproducible + correct.
String fname = "func_" + Long.toHexString(f.getEntryPoint().getOffset()).toUpperCase() + ".c";
Files.write(Paths.get(outDir, fname), out.getBytes());
}
di.dispose();
println("DecompileFunctions: " + ok + " ok, " + fail + " decompile-fail, " + nofunc
+ " no-func -> " + outDir);
}
}