// ExportSymbols.java — headless, read-only: export all USER_DEFINED symbols (our manual RE work) to // a committable text file, so the annotations are version-controlled / re-importable independent of the // gitignored Ghidra .rep. This is the durable, diffable record of the symbols (sotn model; Phase-5 splat // will consume/refine config/symbols.us.txt). // // analyzeHeadless ~/bfm-decomp/ghidra bfm -process SLUS_007.26 -noanalysis -readOnly \ // -scriptPath ~/bfm-decomp/tools/ghidra_scripts -postScript ExportSymbols.java import ghidra.app.script.GhidraScript; import ghidra.program.model.symbol.Symbol; import ghidra.program.model.symbol.SymbolTable; import ghidra.program.model.symbol.SourceType; import ghidra.program.model.symbol.SymbolType; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collections; public class ExportSymbols extends GhidraScript { public void run() throws Exception { SymbolTable st = currentProgram.getSymbolTable(); ArrayList keys = new ArrayList<>(); // [addr] ArrayList lines = new ArrayList<>(); java.util.TreeMap rows = new java.util.TreeMap<>(); int n = 0; for (Symbol s : st.getAllSymbols(true)) { if (s.getSource() != SourceType.USER_DEFINED) continue; if (s.isExternal()) continue; long a = s.getAddress().getOffset(); // only KSEG0 program space (skip GTE/register-space user labels if any) String kind = (s.getSymbolType() == SymbolType.FUNCTION) ? "func" : "data"; rows.put((a << 4) | (kind.equals("func") ? 0 : 1), String.format("%-32s = 0x%08X; // %s", s.getName(), a, kind)); n++; } StringBuilder sb = new StringBuilder(); sb.append("// config/symbols.us.txt — USER_DEFINED symbols exported from the Ghidra project.\n"); sb.append("// Auto-generated by tools/ghidra_scripts/ExportSymbols.java; re-run to refresh.\n"); sb.append("// Durable text record of manual RE work (the .rep is gitignored). Phase-5 splat refines this.\n"); sb.append("// count=").append(n).append("\n\n"); for (String v : rows.values()) sb.append(v).append("\n"); // P33 B5 (rule R15): NEVER overwrite the curated config/symbols.us.txt — re-exporting is a reviewed // re-merge. Arg 1 = output path (default .run/ghidra_export/.symbols.txt under the CWD); // an existing output is refused unless arg 2 is the literal `overwrite`. String[] args = getScriptArgs(); java.nio.file.Path out = (args != null && args.length > 0) ? Paths.get(args[0]) : Paths.get(".run", "ghidra_export", currentProgram.getName() + ".symbols.txt"); boolean overwrite = args != null && args.length > 1 && "overwrite".equals(args[1]); if (Files.exists(out) && !overwrite) { println("BFMSYM REFUSED: " + out + " exists (pass `overwrite` as the 2nd arg; config/symbols*.txt is merged by hand, R15)"); return; } if (out.toAbsolutePath().normalize().toString().contains("/config/")) { println("BFMSYM REFUSED: will not write into config/ (R15 — merge the export by hand): " + out); return; } Files.createDirectories(out.toAbsolutePath().getParent()); Files.write(out, sb.toString().getBytes()); println("BFMSYM exported " + n + " USER_DEFINED symbols -> " + out); } }