- Batches: claim every open function of a unit or address range (td claim --unit/--range), submit the file once; one compile, per-function routing, merged as a whole. A failing function returns the batch; peer verdicts carry over to functions whose compiled code is unchanged (code fingerprints). Submissions may not change functions they do not hold. - Byte-identical functions are approved and merged without human review (matchPolicy.autoMergeIdentical); peer-confirmed ones need one click per batch. - Remote check: td check compiles on the server when there is no local toolchain. - /api/original: assembly and pseudocode of the original from the server's Ghidra export (objdump before an export exists); td disasm/decompile fall back to it. - td as a standalone executable (Node single executable application; tools/build-td), one-line installers (install.ps1 / install.sh) with checksum verification, td connect for any MCP client, td mcp --workspace, self-update (td update, hourly check). - Server-side Ghidra project: built once per version, refreshed after merges, downloadable for contributors (td ghidra download); JDK 21 and the pinned Ghidra in deploy/install.sh. - Agent instructions: work continuously, batches first, peer reviews in between. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RNwtXPYqFu3oKaaXwYwNWW
113 lines
4.8 KiB
Java
113 lines
4.8 KiB
Java
// Exports the disassembly and the decompiler output of every function as JSON lines, for the
|
|
// coordinator's /api/original (contributors then need no local Ghidra):
|
|
// {"addr":"0x140099b90","asm":"...","c":"..."}
|
|
// Args: <output.jsonl> [decompile timeout seconds, default 30]
|
|
// Run on a project built with `td ghidra build` (symbol names applied), with -readOnly.
|
|
// @category ClankTogether
|
|
|
|
import java.io.BufferedWriter;
|
|
import java.io.OutputStreamWriter;
|
|
import java.io.FileOutputStream;
|
|
import java.nio.charset.StandardCharsets;
|
|
|
|
import ghidra.app.decompiler.DecompInterface;
|
|
import ghidra.app.decompiler.DecompileResults;
|
|
import ghidra.app.script.GhidraScript;
|
|
import ghidra.program.model.address.Address;
|
|
import ghidra.program.model.listing.Function;
|
|
import ghidra.program.model.listing.FunctionIterator;
|
|
import ghidra.program.model.listing.Instruction;
|
|
import ghidra.program.model.listing.InstructionIterator;
|
|
import ghidra.program.model.symbol.Reference;
|
|
import ghidra.program.model.symbol.Symbol;
|
|
|
|
public class ExportOriginals extends GhidraScript {
|
|
|
|
@Override
|
|
protected void run() throws Exception {
|
|
String[] args = getScriptArgs();
|
|
int timeout = args.length > 1 ? Integer.parseInt(args[1]) : 30;
|
|
DecompInterface ifc = new DecompInterface();
|
|
ifc.openProgram(currentProgram);
|
|
int n = 0;
|
|
int failed = 0;
|
|
try (BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(args[0]), StandardCharsets.UTF_8))) {
|
|
FunctionIterator it = currentProgram.getFunctionManager().getFunctions(true);
|
|
while (it.hasNext() && !monitor.isCancelled()) {
|
|
Function f = it.next();
|
|
if (f.isExternal() || f.isThunk()) {
|
|
continue;
|
|
}
|
|
String c;
|
|
DecompileResults r = ifc.decompileFunction(f, timeout, monitor);
|
|
if (r.decompileCompleted() && r.getDecompiledFunction() != null) {
|
|
c = r.getDecompiledFunction().getC();
|
|
} else {
|
|
c = null;
|
|
failed++;
|
|
}
|
|
out.write("{\"addr\":\"0x" + Long.toHexString(f.getEntryPoint().getOffset()) + "\",\"asm\":" + json(disassemble(f)) + ",\"c\":" + (c == null ? "null" : json(c)) + "}\n");
|
|
if (++n % 1000 == 0) {
|
|
println("exported " + n + " functions");
|
|
}
|
|
}
|
|
} finally {
|
|
ifc.dispose();
|
|
}
|
|
println("exported " + n + " functions, " + failed + " without decompiler output");
|
|
}
|
|
|
|
private String disassemble(Function f) {
|
|
StringBuilder sb = new StringBuilder();
|
|
Address entry = f.getEntryPoint();
|
|
sb.append("; ").append(f.getName(true)).append(" @ ").append(entry).append(" ").append(f.getSignature().getPrototypeString()).append('\n');
|
|
InstructionIterator it = currentProgram.getListing().getInstructions(f.getBody(), true);
|
|
while (it.hasNext()) {
|
|
Instruction insn = it.next();
|
|
Address a = insn.getAddress();
|
|
if (!a.equals(entry)) {
|
|
for (Symbol s : currentProgram.getSymbolTable().getSymbols(a)) {
|
|
sb.append(s.getName(true)).append(":\n");
|
|
}
|
|
}
|
|
StringBuilder bytes = new StringBuilder();
|
|
try {
|
|
for (byte b : insn.getBytes()) {
|
|
bytes.append(String.format("%02x ", b & 0xff));
|
|
}
|
|
} catch (Exception e) {
|
|
bytes.append("?? ");
|
|
}
|
|
StringBuilder refs = new StringBuilder();
|
|
for (Reference ref : insn.getReferencesFrom()) {
|
|
Symbol s = currentProgram.getSymbolTable().getPrimarySymbol(ref.getToAddress());
|
|
if (s != null) {
|
|
refs.append(" ; -> ").append(s.getName(true));
|
|
}
|
|
}
|
|
sb.append(String.format(" +0x%04x %08x %-24s %s%s%n", a.subtract(entry), a.getOffset(), bytes.toString().trim(), insn.toString(), refs));
|
|
}
|
|
return sb.toString();
|
|
}
|
|
|
|
private static String json(String s) {
|
|
StringBuilder sb = new StringBuilder("\"");
|
|
for (int i = 0; i < s.length(); i++) {
|
|
char ch = s.charAt(i);
|
|
switch (ch) {
|
|
case '"': sb.append("\\\""); break;
|
|
case '\\': sb.append("\\\\"); break;
|
|
case '\n': sb.append("\\n"); break;
|
|
case '\r': sb.append("\\r"); break;
|
|
case '\t': sb.append("\\t"); break;
|
|
default:
|
|
if (ch < 0x20) {
|
|
sb.append(String.format("\\u%04x", (int) ch));
|
|
} else {
|
|
sb.append(ch);
|
|
}
|
|
}
|
|
}
|
|
return sb.append('"').toString();
|
|
}
|
|
}
|