Files
maple/maple-py/re/scripts/CallTree.java
T
2026-06-08 23:22:16 +10:00

86 lines
3.8 KiB
Java

// Walk the call tree down from the export orchestrator and surface the page
// serializer: flag functions that call file-I/O APIs or use the 4096 page size,
// then decompile those candidates. Output -> ~/ghidra_proj/calltree.txt
import java.io.PrintWriter;
import java.util.*;
import ghidra.app.script.GhidraScript;
import ghidra.app.decompiler.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.scalar.Scalar;
public class CallTree extends GhidraScript {
static final String ROOT = "0x14119d2d0";
static final int MAX_DEPTH = 4;
boolean hasPageConst(Function f) {
InstructionIterator it = currentProgram.getListing().getInstructions(f.getBody(), true);
while (it.hasNext()) {
Instruction insn = it.next();
for (int op = 0; op < insn.getNumOperands(); op++)
for (Object o : insn.getOpObjects(op))
if (o instanceof Scalar) {
long v = ((Scalar) o).getUnsignedValue();
if (v == 0x1000L || v == 4096L) return true;
}
}
return false;
}
String ioCalls(Function f) {
StringBuilder sb = new StringBuilder();
for (Function c : f.getCalledFunctions(monitor)) {
String n = c.getName().toLowerCase();
if (n.contains("write") || n.contains("createfile") || n.contains("fopen")
|| n.contains("fwrite") || n.contains("mapviewof") || n.contains("setfilepointer")
|| n.contains("flushview") || n.contains("fclose"))
sb.append(c.getName()).append(" ");
}
return sb.toString();
}
@Override
public void run() throws Exception {
PrintWriter out = new PrintWriter("/home/ntr/ghidra_proj/calltree.txt");
FunctionManager fm = currentProgram.getFunctionManager();
Function root = fm.getFunctionContaining(currentProgram.getAddressFactory().getAddress(ROOT));
Map<Function, Integer> depth = new LinkedHashMap<>();
Deque<Function> q = new ArrayDeque<>();
depth.put(root, 0); q.add(root);
while (!q.isEmpty() && !monitor.isCancelled()) {
Function f = q.poll();
int d = depth.get(f);
if (d >= MAX_DEPTH) continue;
for (Function c : f.getCalledFunctions(monitor))
if (!depth.containsKey(c)) { depth.put(c, d + 1); q.add(c); }
}
out.println("call tree from " + root.getName() + " depth<=" + MAX_DEPTH + ": " + depth.size() + " funcs\n");
List<Function> candidates = new ArrayList<>();
for (Map.Entry<Function, Integer> e : depth.entrySet()) {
Function f = e.getKey();
boolean page = hasPageConst(f);
String io = ioCalls(f);
String flags = (page ? "[4096]" : "") + (io.isEmpty() ? "" : "[io:" + io.trim() + "]");
out.println(String.format("d%d %-12s size=%-6d %s %s",
e.getValue(), f.getEntryPoint(), f.getBody().getNumAddresses(), f.getName(), flags));
if (page || !io.isEmpty()) candidates.add(f);
}
DecompInterface di = new DecompInterface();
di.openProgram(currentProgram);
out.println("\n===== CANDIDATE DECOMPILES (" + candidates.size() + ") =====");
for (Function f : candidates) {
if (monitor.isCancelled()) break;
out.println("\n//===== " + f.getName() + " @ " + f.getEntryPoint() + " =====");
DecompileResults r = di.decompileFunction(f, 60, monitor);
if (r != null && r.decompileCompleted())
out.println(r.getDecompiledFunction().getC());
else
out.println("// decompile failed");
}
out.flush(); out.close();
println("wrote calltree.txt: " + depth.size() + " funcs, " + candidates.size() + " candidates");
}
}