Skip to content
Hydra Engine

Scripts and exports

Run any GhidraScript through the daemon, as owner or contained and rolled back, and write large exports with progress, cancel and an inspectable partial file.

On this page

Anything the session has no command for is a GhidraScript away. script runs one the way Ghidra's Script Manager does: currentProgram, currentAddress, getScriptArgs(), println and monitor all work, and isRunningHeadless() is true. Scripts are Java, or Python through a Ghidra script provider, and take arguments in args.

Owner or contained

Who may change the program decides how a script runs:

You areThe script runsIts changes
the program's owner, or one of its shared writersalone on the program (exclusive lock)stay, unsaved unless "save": true
anyone elsealongside other readers (shared lock)rolled back at the end, always

A contained script runs inside a transaction that is always rolled back, so it cannot change what other clients see or what is saved. Pass "exclusive": true to run it alone anyway. With "program": null, a script runs with only the active project, or none.

The answer carries the printed text in output, the mode (owner or contained), changed and unsaved.

Where scripts come from

{"scriptPath": ["C:/re/scripts"]}

Ghidra's own script directories are already on the path: s.call("script", script="HelloWorldScript.java") prints Hello World.

Exports: a script that writes a large file

A bulk export (every function, string or reference as JSONL, say) is a script that writes its own file. This one writes every function of the current program:

// ExportFunctions.java: one JSON line per function. args: <output path>
import java.io.BufferedWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
 
import ghidra.app.script.GhidraScript;
import ghidra.program.model.listing.Function;
 
public class ExportFunctions extends GhidraScript {
	@Override
	protected void run() throws Exception {
		Path out = Paths.get(getScriptArgs()[0]).toAbsolutePath();
		Path part = out.resolveSibling(out.getFileName() + ".part");
		monitor.initialize(currentProgram.getFunctionManager().getFunctionCount());
		long n = 0;
		try (BufferedWriter w = Files.newBufferedWriter(part, StandardCharsets.UTF_8)) {
			for (Function f : currentProgram.getFunctionManager().getFunctions(true)) {
				monitor.checkCancelled();
				w.write("{\"name\":\"" + f.getName() + "\",\"entry\":\"" + f.getEntryPoint() +
					"\",\"size\":" + f.getBody().getNumAddresses() + "}\n");
				if (++n % 1000 == 0) {
					w.flush();
					monitor.setProgress(n);
				}
			}
		}
		Files.move(part, out, StandardCopyOption.REPLACE_EXISTING);
		println("exported " + n + " functions to " + out);
	}
}

Run it on the long lane, without blocking your connection:

out = r"C:\re\out\functions.jsonl"
rid = s.send("script", script="ExportFunctions.java", args=[out], artifact=out, parallel=True)
# ... progress events arrive; s.call("status") shows the job and its artifact
r = s.wait(rid)
{"name":"FUN_140001010","entry":"140001010","size":211}

The contract an integrator can rely on, which the engine's tests exercise with a 600,000-line, 55 MB export:

  • The file is yours. The script writes wherever the daemon's user can write. Declare the same path as artifact: the result and status echo it.
  • Not capped. maxChars (200,000 by default, at most 16,000,000) limits only the printed output. Print a summary, not the data.
  • Progress. monitor.initialize, setProgress and setMessage become progress events with the request's id, at most one a second however often the script calls them.
  • Cancel. cancel with the request id, timeout, a disconnect or the daemon stopping all cancel the script's monitor. A script that calls monitor.checkCancelled() stops promptly and the request answers cancelled. One that never checks runs to the end.
  • Partial files. The session deletes nothing it did not create. Writing to <path>.part and renaming at the end, as above, means a cancelled export leaves a .part file that is valid up to its last complete line, and a reader that finds <path> knows it finished.

What scripts can reach

Most window-only workflows are already plain Ghidra API, so they need a script, not new commands: version tracking between two programs of a project, C headers parsed into a data type archive, auto-created structures, re-analysis of ranges, labels and bookmarks. The engine repository's examples/HeadlessApiProbe.java does each of these without a window.

Checked against the engine on 2026-10-26. Every detail is in the README on GitHub.