summaryrefslogtreecommitdiffstats
path: root/userspace/cuda/tools/ghidra
diff options
context:
space:
mode:
Diffstat (limited to 'userspace/cuda/tools/ghidra')
-rw-r--r--userspace/cuda/tools/ghidra/ApplyNameMap.java86
-rw-r--r--userspace/cuda/tools/ghidra/BatchDecompile.java141
-rw-r--r--userspace/cuda/tools/ghidra/ConfigureAnalysis.java84
-rw-r--r--userspace/cuda/tools/ghidra/DecompileAt.java86
-rw-r--r--userspace/cuda/tools/ghidra/ExportFunctionIndex.java192
-rw-r--r--userspace/cuda/tools/ghidra/PrintProgramInfo.java38
6 files changed, 627 insertions, 0 deletions
diff --git a/userspace/cuda/tools/ghidra/ApplyNameMap.java b/userspace/cuda/tools/ghidra/ApplyNameMap.java
new file mode 100644
index 0000000..b8be68f
--- /dev/null
+++ b/userspace/cuda/tools/ghidra/ApplyNameMap.java
@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: BSD-3-Clause
+//
+// ApplyNameMap.java - Ghidra post-script: apply a symbol name map to a
+// program. This is the version-transfer hook of the Open-IOV CUDA tooling:
+// once a name map exists for version N (derived from public documentation,
+// decompiled behaviour or an earlier version), it can be applied to version
+// N+1 in seconds, so analysis effort accumulates instead of restarting.
+//
+// Input format (TSV, no header required):
+// <hex-addr> <name> [<optional namespace>]
+// Lines starting with '#' and empty lines are ignored. Addresses are file
+// virtual addresses ("libcuda+0x..."), not Ghidra image-base offsets.
+//
+// Usage (analyzeHeadless):
+// -process <program> -noanalysis \
+// -postScript ApplyNameMap.java <namemap.tsv> [--dry-run]
+//
+// The script prints how many names were applied, and leaves a
+// "cuda-names-applied" user property on the program with the map path and
+// count so a later session can see it happened.
+
+import ghidra.app.script.GhidraScript;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.listing.Function;
+import ghidra.program.model.listing.Listing;
+import ghidra.program.model.symbol.SourceType;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+
+public class ApplyNameMap extends GhidraScript {
+
+ @Override
+ public void run() throws Exception {
+ String[] args = getScriptArgs();
+ if (args.length < 1) {
+ println("usage: ApplyNameMap.java <namemap.tsv> [--dry-run]");
+ return;
+ }
+ boolean dryRun = args.length > 1 && args[1].equals("--dry-run");
+ Listing listing = currentProgram.getListing();
+ long imageBase = currentProgram.getImageBase().getOffset();
+ int applied = 0, missing = 0, lines = 0;
+
+ try (BufferedReader r = new BufferedReader(new FileReader(args[0]))) {
+ String line;
+ while ((line = r.readLine()) != null && !monitor.isCancelled()) {
+ line = line.trim();
+ if (line.isEmpty() || line.startsWith("#")) continue;
+ lines++;
+ String[] parts = line.split("[ \t]+");
+ if (parts.length < 2) continue;
+ long addr;
+ try {
+ addr = Long.parseUnsignedLong(parts[0].replaceFirst("^0x", ""), 16);
+ } catch (NumberFormatException e) {
+ continue;
+ }
+ String name = parts[1];
+ // Map keys are file virtual addresses (libcuda+0x...).
+ Address a = toAddr(addr + imageBase);
+ Function f = listing.getFunctionAt(a);
+ if (f == null) {
+ missing++;
+ continue;
+ }
+ if (!dryRun) {
+ try {
+ f.setName(name, SourceType.USER_DEFINED);
+ } catch (Exception e) {
+ println(" cannot rename 0x" + Long.toHexString(addr)
+ + " -> " + name + ": " + e.getMessage());
+ continue;
+ }
+ }
+ applied++;
+ }
+ }
+ if (!dryRun) {
+ currentProgram.getOptions("OpenIOV")
+ .setString("cuda-names-applied", args[0] + " (" + applied + " names)");
+ }
+ println(String.format("[ApplyNameMap] lines=%d applied=%s missing=%d%s",
+ lines, String.valueOf(applied), missing, dryRun ? " (dry run)" : ""));
+ }
+}
diff --git a/userspace/cuda/tools/ghidra/BatchDecompile.java b/userspace/cuda/tools/ghidra/BatchDecompile.java
new file mode 100644
index 0000000..0319681
--- /dev/null
+++ b/userspace/cuda/tools/ghidra/BatchDecompile.java
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: BSD-3-Clause
+//
+// BatchDecompile.java - Ghidra post-script: decompile every function whose
+// entry address lies in [start, end) and write the C output plus a
+// machine-readable index.
+//
+// Designed for a very large stripped vendor library (~100k functions, 70 MB).
+// The caller splits the code address range into chunks and runs one
+// analyzeHeadless process per chunk (all with -readOnly against the same
+// analyzed project), which parallelizes and makes the run resumable: a chunk
+// whose output already exists is skipped.
+//
+// Usage (analyzeHeadless, on an already analyzed program):
+// -process <program> -readOnly -noanalysis \
+// -postScript BatchDecompile.java <startAddrHex> <endAddrHex> <outdir> [timeoutSecs]
+//
+// Outputs:
+// <outdir>/buckets/bucket_<start>_<end>.c decompiled C, functions in order
+// <outdir>/index/bucket_<start>_<end>.tsv addr size name file line status
+//
+// Decompiled output is vendor-derived analysis output: it stays in the
+// version's gitignored run/ tree and is NEVER committed.
+
+import ghidra.app.script.GhidraScript;
+import ghidra.app.decompiler.DecompInterface;
+import ghidra.app.decompiler.DecompileResults;
+import ghidra.program.model.listing.Function;
+import ghidra.program.model.listing.FunctionIterator;
+import ghidra.program.model.listing.Listing;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.PrintWriter;
+
+public class BatchDecompile extends GhidraScript {
+
+ private static int countLines(String s) {
+ int n = 0;
+ for (int i = 0; i < s.length(); i++) {
+ if (s.charAt(i) == '\n') n++;
+ }
+ return n;
+ }
+
+ @Override
+ public void run() throws Exception {
+ String[] args = getScriptArgs();
+ if (args.length < 3) {
+ println("usage: BatchDecompile.java <startAddrHex> <endAddrHex> <outdir> [timeoutSecs]");
+ return;
+ }
+ long start = Long.parseUnsignedLong(args[0].replaceFirst("^0x", ""), 16);
+ long end = Long.parseUnsignedLong(args[1].replaceFirst("^0x", ""), 16);
+ String outdir = args[2];
+ int timeout = args.length > 3 ? Integer.parseInt(args[3]) : 120;
+
+ File buckets = new File(outdir, "buckets");
+ File indexDir = new File(outdir, "index");
+ buckets.mkdirs();
+ indexDir.mkdirs();
+ String tag = String.format("%08x_%08x", start, end);
+ File outFile = new File(buckets, "bucket_" + tag + ".c");
+ File idxFile = new File(indexDir, "bucket_" + tag + ".tsv");
+
+ if (outFile.isFile() && idxFile.isFile()) {
+ println("[BatchDecompile] chunk " + tag + " already done, skipping");
+ return;
+ }
+
+ Listing listing = currentProgram.getListing();
+ long imageBase = currentProgram.getImageBase().getOffset();
+ DecompInterface dif = new DecompInterface();
+ dif.toggleCCode(true);
+ dif.toggleSyntaxTree(false);
+ dif.openProgram(currentProgram);
+
+ int n = 0, fail = 0, skip = 0, lines = 0;
+
+ try (PrintWriter idx = new PrintWriter(new BufferedWriter(new FileWriter(idxFile)));
+ PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(outFile)))) {
+
+ String banner = "/* CUDA userspace driver decompilation - Open-IOV project\n"
+ + " * program: " + currentProgram.getName() + "\n"
+ + " * range: [0x" + Long.toHexString(start) + ", 0x"
+ + Long.toHexString(end) + ")\n"
+ + " * Machine-generated analysis output; not part of the public tree.\n"
+ + " */\n";
+ out.print(banner);
+ lines += countLines(banner);
+ idx.println("addr\tfile_addr\tsize\tname\tfile\tline\tstatus");
+
+ FunctionIterator fit = listing.getFunctions(true);
+ while (fit.hasNext() && !monitor.isCancelled()) {
+ Function f = fit.next();
+ if (f.isExternal()) continue;
+ long addr = f.getEntryPoint().getOffset();
+ if (addr < start || addr >= end) continue;
+
+ long size = f.getBody().getNumAddresses();
+ String header = String.format(
+ "\n/* ------------------------------------------------------------------"
+ + "----------\n"
+ + " * %s\n"
+ + " * addr=0x%08x file_addr=libcuda+0x%x size=%d\n"
+ + " *-------------------------------------------------------------------"
+ + "-------- */\n",
+ f.getName(), addr, addr - imageBase, size);
+ out.print(header);
+ lines += countLines(header);
+
+ String status = "ok";
+ String code;
+ if (size > 200000) {
+ status = "skipped-huge";
+ skip++;
+ code = "/* skipped: body too large (" + size + " bytes) */\n";
+ } else {
+ DecompileResults res = dif.decompileFunction(f, timeout, monitor);
+ if (res != null && res.decompileCompleted()) {
+ code = res.getDecompiledFunction().getC();
+ } else {
+ status = "failed";
+ fail++;
+ code = "/* decompilation failed */\n";
+ }
+ }
+ out.print(code);
+ out.print("\n");
+ lines += countLines(code) + 1;
+ idx.printf("0x%08x\t0x%08x\t%d\t%s\t%s\t%d\t%s%n",
+ addr, addr - imageBase, size, f.getName(),
+ outFile.getName(), lines - countLines(code), status);
+ n++;
+ }
+ }
+
+ println(String.format("[BatchDecompile] %s: functions=%d failed=%d skipped=%d -> %s",
+ tag, n, fail, skip, outFile.getAbsolutePath()));
+ }
+}
diff --git a/userspace/cuda/tools/ghidra/ConfigureAnalysis.java b/userspace/cuda/tools/ghidra/ConfigureAnalysis.java
new file mode 100644
index 0000000..71bb75e
--- /dev/null
+++ b/userspace/cuda/tools/ghidra/ConfigureAnalysis.java
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: BSD-3-Clause
+//
+// ConfigureAnalysis.java - Ghidra pre-script used by the Open-IOV CUDA
+// tooling to make headless auto-analysis of a large stripped vendor library
+// tractable and reproducible.
+//
+// The CUDA userspace driver is a ~70 MB, fully stripped x86-64 shared object
+// with roughly a hundred thousand functions. The default analyzer set spends
+// the majority of its time in the two optional "identifier" analyzers, which
+// are not needed to obtain readable decompilation:
+//
+// * Decompiler Parameter ID - decompiles every function to recover
+// parameter types (very expensive, no value
+// for a stripped binary where we have no
+// type definitions for internal structs)
+// * Function ID (FID) - matches function bodies against .fidb
+// signature databases (liberal, and we have
+// no curated database for this toolchain)
+//
+// Everything else (disassembly, function start search, aggressive instruction
+// finder, exception handling, stack analysis, reference analyzers, ...) is
+// kept, because function discovery quality on a stripped binary depends on
+// them.
+//
+// Usage (analyzeHeadless):
+// -preScript ConfigureAnalysis.java [verbose]
+//
+// Re-runnable: the same settings are applied on every analysis of a program.
+
+import ghidra.app.script.GhidraScript;
+import ghidra.framework.options.Options;
+
+public class ConfigureAnalysis extends GhidraScript {
+
+ private static final String[] DISABLE = {
+ "Decompiler Parameter ID",
+ "Function ID",
+ };
+
+ private static final String[] ENABLE = {
+ "Aggressive Instruction Finder",
+ "GCC Exception Handlers",
+ "Function Start Search",
+ "Shared Return Calls",
+ "Non-Returning Functions - Known",
+ "Non-Returning Functions - Discovered",
+ };
+
+ @Override
+ public void run() throws Exception {
+ boolean verbose = getScriptArgs().length > 0;
+ Options opts = currentProgram.getOptions("Analyzers");
+
+ if (verbose) {
+ println("[ConfigureAnalysis] analyzer options before:");
+ for (String name : opts.getOptionNames()) {
+ println(" " + name + " = " + opts.getObject(name, null));
+ }
+ }
+
+ for (String name : DISABLE) {
+ if (opts.contains(name)) {
+ opts.setBoolean(name, false);
+ println("[ConfigureAnalysis] disabled: " + name);
+ } else {
+ println("[ConfigureAnalysis] WARN not found: " + name);
+ }
+ }
+
+ for (String name : ENABLE) {
+ if (opts.contains(name)) {
+ opts.setBoolean(name, true);
+ println("[ConfigureAnalysis] enabled: " + name);
+ }
+ }
+
+ if (verbose) {
+ println("[ConfigureAnalysis] analyzer options after:");
+ for (String name : opts.getOptionNames()) {
+ println(" " + name + " = " + opts.getObject(name, null));
+ }
+ }
+ }
+}
diff --git a/userspace/cuda/tools/ghidra/DecompileAt.java b/userspace/cuda/tools/ghidra/DecompileAt.java
new file mode 100644
index 0000000..ed8cf5d
--- /dev/null
+++ b/userspace/cuda/tools/ghidra/DecompileAt.java
@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: BSD-3-Clause
+//
+// DecompileAt.java - Ghidra post-script: decompile the function containing a
+// given file virtual address. Version-agnostic copy of the per-version
+// helper used with ghidra_decompile_at.sh.
+//
+// Usage:
+// analyzeHeadless <proj> <name> -process <program> -readOnly -noanalysis \
+// -postScript DecompileAt.java <hex-vaddr> [outfile]
+//
+// If the address does not fall inside a known function, the script tries to
+// disassemble at the address; if the target is outside any function body it
+// prints a comment with the nearest preceding function instead of failing.
+
+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 ghidra.program.model.listing.FunctionIterator;
+import ghidra.program.model.listing.Listing;
+
+import java.io.FileWriter;
+
+public class DecompileAt extends GhidraScript {
+ @Override
+ public void run() throws Exception {
+ String[] args = getScriptArgs();
+ if (args.length < 1) {
+ println("usage: DecompileAt.java <hex-vaddr> [outfile]");
+ return;
+ }
+ long fileAddr = Long.parseUnsignedLong(args[0].replaceFirst("^0x", ""), 16);
+ // Arguments are FILE virtual addresses (libcuda+0x...). Ghidra may have
+ // loaded the program at a non-zero image base (0x100000 in this build),
+ // so translate before resolving. If image base is 0 this is a no-op.
+ long imageBase = currentProgram.getImageBase().getOffset();
+ Address addr = toAddr(fileAddr + imageBase);
+ long target = addr.getOffset();
+
+ Function f = getFunctionContaining(addr);
+ if (f == null) {
+ Listing listing = currentProgram.getListing();
+ FunctionIterator it = listing.getFunctions(addr, false);
+ if (it.hasNext()) f = it.next();
+ }
+ if (f == null) {
+ disassemble(addr);
+ f = createFunction(addr, null);
+ }
+ StringBuilder sb = new StringBuilder();
+ if (f == null) {
+ sb.append("// no function at 0x").append(Long.toHexString(target)).append("\n");
+ } else {
+ DecompInterface dif = new DecompInterface();
+ dif.toggleCCode(true);
+ dif.toggleSyntaxTree(true);
+ dif.openProgram(currentProgram);
+ DecompileResults res = dif.decompileFunction(f, 300, monitor);
+ sb.append("// function ").append(f.getName())
+ .append(" at libcuda+0x")
+ .append(Long.toHexString(f.getEntryPoint().getOffset() - imageBase))
+ .append(" (program 0x").append(Long.toHexString(f.getEntryPoint().getOffset()))
+ .append(", query file 0x").append(Long.toHexString(fileAddr)).append(")\n");
+ if (res != null && res.decompileCompleted()) {
+ sb.append("#include <stdint.h>\n")
+ .append("typedef unsigned char undefined1;\n")
+ .append("typedef unsigned short undefined2;\n")
+ .append("typedef unsigned int undefined4;\n")
+ .append("typedef unsigned long long undefined8;\n")
+ .append("typedef long long longlong;\n")
+ .append("typedef unsigned long long ulonglong;\n\n")
+ .append(res.getDecompiledFunction().getC());
+ } else {
+ sb.append("// decompilation failed\n");
+ }
+ }
+ String text = sb.toString();
+ if (args.length > 1) {
+ try (FileWriter w = new FileWriter(args[1])) {
+ w.write(text);
+ }
+ }
+ println(text);
+ }
+}
diff --git a/userspace/cuda/tools/ghidra/ExportFunctionIndex.java b/userspace/cuda/tools/ghidra/ExportFunctionIndex.java
new file mode 100644
index 0000000..c36f98f
--- /dev/null
+++ b/userspace/cuda/tools/ghidra/ExportFunctionIndex.java
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: BSD-3-Clause
+//
+// ExportFunctionIndex.java - Ghidra post-script: dump a complete inventory of
+// the analyzed program: functions, symbols (exports/imports), memory blocks
+// and per-function references to interesting callees, plus the address of
+// every function that is reachable from the exported CUDA entry points.
+//
+// This is the primary "map" artifact for a CUDA userspace driver version: it
+// lets later tooling and analysis answer "what is at libcuda+0xNNNNNN" and
+// "which internal function implements exported cuFoo".
+//
+// Usage (analyzeHeadless, on an already analyzed program):
+// -process <program> -noanalysis \
+// -postScript ExportFunctionIndex.java <outdir>
+//
+// Outputs (all TSV, one row per item, with a header row):
+// <outdir>/functions.tsv addr size name namespace is_exported
+// is_external is_thunk has_body entry_bytes
+// <outdir>/exports.tsv addr size name
+// <outdir>/imports.tsv addr name library
+// <outdir>/blocks.tsv name start end size r/w/x
+// <outdir>/callgraph.tsv caller_addr callee_addr call_sites
+// <outdir>/strings.tsv addr length string (first 200 chars)
+//
+// The callgraph is produced from Ghidra's reference model for call
+// instructions inside each function body.
+
+import ghidra.app.script.GhidraScript;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.address.AddressSetView;
+import ghidra.program.model.listing.Data;
+import ghidra.program.model.listing.DataIterator;
+import ghidra.program.model.listing.Function;
+import ghidra.program.model.listing.FunctionIterator;
+import ghidra.program.model.listing.Listing;
+import ghidra.program.model.mem.MemoryBlock;
+import ghidra.program.model.symbol.Reference;
+import ghidra.program.model.symbol.ReferenceIterator;
+import ghidra.program.model.symbol.Symbol;
+import ghidra.program.model.symbol.SymbolIterator;
+import ghidra.program.model.symbol.SymbolTable;
+
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+public class ExportFunctionIndex extends GhidraScript {
+
+ private PrintWriter open(String dir, String name) throws Exception {
+ return new PrintWriter(new BufferedWriter(new FileWriter(dir + "/" + name)));
+ }
+
+ @Override
+ public void run() throws Exception {
+ String[] args = getScriptArgs();
+ if (args.length < 1) {
+ println("usage: ExportFunctionIndex.java <outdir>");
+ return;
+ }
+ String outdir = args[0];
+ new java.io.File(outdir).mkdirs();
+
+ Listing listing = currentProgram.getListing();
+ SymbolTable symtab = currentProgram.getSymbolTable();
+ // Ghidra may load a PIE/shared object at a non-zero image base (this
+ // build uses 0x100000). All *file virtual addresses* (what the public
+ // docs cite as libcuda+0x...) are program addresses minus this base.
+ long imageBase = currentProgram.getImageBase().getOffset();
+
+ // ---- functions -------------------------------------------------------
+ int nfunc = 0, nexport = 0, nexternal = 0;
+ Map<Long, String> exported = new HashMap<>();
+ try (PrintWriter w = open(outdir, "functions.tsv")) {
+ w.println("addr\tfile_addr\tsize\tname\tnamespace\texported\texternal\tthunk\tbody");
+ FunctionIterator fit = listing.getFunctions(true);
+ while (fit.hasNext() && !monitor.isCancelled()) {
+ Function f = fit.next();
+ Address ep = f.getEntryPoint();
+ boolean isExt = f.isExternal();
+ boolean isThunk = f.isThunk();
+ String ns = f.getParentNamespace() != null
+ ? f.getParentNamespace().getName(true) : "";
+ String name = f.getName();
+ long size = f.getBody().getNumAddresses();
+ w.printf("0x%08x\t0x%08x\t%d\t%s\t%s\t%s\t%s\t%s\t%s%n",
+ ep.getOffset(), ep.getOffset() - imageBase, size, name, ns,
+ isExt ? "0" : (symtab.isExternalEntryPoint(ep) ? "1" : "0"),
+ isExt ? "1" : "0", isThunk ? "1" : "0",
+ size > 0 ? "1" : "0");
+ nfunc++;
+ if (!isExt) {
+ for (Symbol s : symtab.getSymbols(ep)) {
+ if (s.isExternalEntryPoint() || s.isGlobal()) {
+ exported.put(ep.getOffset(), s.getName(true));
+ }
+ }
+ }
+ if (isExt) nexternal++;
+ }
+ }
+
+ // ---- exports: symbols flagged external-entry / global in .text ------
+ // The loader marks ELF .dynsym definitions; enumerating them via the
+ // symbol table is exact and independent of the function iterator.
+ try (PrintWriter w = open(outdir, "exports.tsv")) {
+ w.println("addr\tfile_addr\tsize\tname");
+ SymbolIterator sit = symtab.getAllSymbols(true);
+ while (sit.hasNext() && !monitor.isCancelled()) {
+ Symbol s = sit.next();
+ Address a = s.getAddress();
+ if (a == null || !a.isMemoryAddress()) continue;
+ if (!symtab.isExternalEntryPoint(a)) continue;
+ Function f = listing.getFunctionAt(a);
+ long size = f != null ? f.getBody().getNumAddresses() : 0;
+ w.printf("0x%08x\t0x%08x\t%d\t%s%n", a.getOffset(), a.getOffset() - imageBase, size, s.getName(true));
+ nexport++;
+ }
+ }
+
+ // ---- imports: external functions and the library they come from ----
+ try (PrintWriter w = open(outdir, "imports.tsv")) {
+ w.println("addr\tfile_addr\tname\tlibrary");
+ FunctionIterator fit = listing.getFunctions(true);
+ while (fit.hasNext() && !monitor.isCancelled()) {
+ Function f = fit.next();
+ if (!f.isExternal()) continue;
+ String lib = f.getParentNamespace() != null
+ ? f.getParentNamespace().getName(true) : "";
+ w.printf("0x%08x\t0x%08x\t%s\t%s%n",
+ f.getEntryPoint().getOffset(),
+ f.getEntryPoint().getOffset() - imageBase, f.getName(), lib);
+ }
+ }
+
+ // ---- memory blocks ---------------------------------------------------
+ try (PrintWriter w = open(outdir, "blocks.tsv")) {
+ w.println("name\tstart\tend\tsize\tr\tw\tx\tinitialized");
+ for (MemoryBlock b : currentProgram.getMemory().getBlocks()) {
+ w.printf("%s\t0x%08x\t0x%08x\t%d\t%s\t%s\t%s\t%s%n",
+ b.getName(), b.getStart().getOffset(), b.getEnd().getOffset(),
+ b.getSize(), b.isRead() ? "1" : "0", b.isWrite() ? "1" : "0",
+ b.isExecute() ? "1" : "0", b.isInitialized() ? "1" : "0");
+ }
+ }
+
+ // ---- call graph ------------------------------------------------------
+ // Function.getCalledFunctions() is the reliable API: it resolves both
+ // direct calls and call-through references recorded by the analyzers,
+ // independent of how the reference manager indexes them.
+ int edges = 0;
+ try (PrintWriter w = open(outdir, "callgraph.tsv")) {
+ w.println("caller\tcallee\tcaller_file\tcallee_file");
+ FunctionIterator fit = listing.getFunctions(true);
+ while (fit.hasNext() && !monitor.isCancelled()) {
+ Function f = fit.next();
+ if (f.isExternal()) continue;
+ for (Function callee : f.getCalledFunctions(monitor)) {
+ w.printf("0x%08x\t0x%08x\t0x%08x\t0x%08x%n",
+ f.getEntryPoint().getOffset(),
+ callee.getEntryPoint().getOffset(),
+ f.getEntryPoint().getOffset() - imageBase,
+ callee.getEntryPoint().getOffset() - imageBase);
+ edges++;
+ }
+ }
+ }
+
+ // ---- defined strings -------------------------------------------------
+ int nstr = 0;
+ try (PrintWriter w = open(outdir, "strings.tsv")) {
+ w.println("addr\tfile_addr\tlength\tstring");
+ DataIterator dit = listing.getDefinedData(true);
+ while (dit.hasNext() && !monitor.isCancelled()) {
+ Data d = dit.next();
+ if (!d.hasStringValue()) continue;
+ Object v = d.getValue();
+ if (v == null) continue;
+ String s = v.toString().replace("\t", " ").replace("\n", "\\n");
+ if (s.length() > 200) s = s.substring(0, 200);
+ w.printf("0x%08x\t0x%08x\t%d\t%s%n", d.getAddress().getOffset(),
+ d.getAddress().getOffset() - imageBase, d.getLength(), s);
+ nstr++;
+ }
+ }
+
+ println(String.format(
+ "[ExportFunctionIndex] functions=%d exports=%d imports=%d callgraph_edges=%d strings=%d",
+ nfunc, nexport, nexternal, edges, nstr));
+ }
+}
diff --git a/userspace/cuda/tools/ghidra/PrintProgramInfo.java b/userspace/cuda/tools/ghidra/PrintProgramInfo.java
new file mode 100644
index 0000000..86a31b3
--- /dev/null
+++ b/userspace/cuda/tools/ghidra/PrintProgramInfo.java
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: BSD-3-Clause
+//
+// PrintProgramInfo.java - Ghidra post-script: print the program's image base,
+// executable block ranges and function count. Used to establish the address
+// convention of a project before running decompilation tools.
+//
+// Usage:
+// analyzeHeadless <proj> <name> -process <program> -readOnly -noanalysis \
+// -postScript PrintProgramInfo.java
+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.mem.MemoryBlock;
+
+public class PrintProgramInfo extends GhidraScript {
+ @Override
+ public void run() throws Exception {
+ println("program = " + currentProgram.getName());
+ println("language = " + currentProgram.getLanguageID());
+ println("image base = 0x" + Long.toHexString(currentProgram.getImageBase().getOffset()));
+ println("executable blocks:");
+ for (MemoryBlock b : currentProgram.getMemory().getBlocks()) {
+ if (b.isExecute()) {
+ println(" 0x" + Long.toHexString(b.getStart().getOffset())
+ + " - 0x" + Long.toHexString(b.getEnd().getOffset())
+ + " " + b.getName());
+ }
+ }
+ int n = 0;
+ FunctionIterator fit = currentProgram.getListing().getFunctions(true);
+ while (fit.hasNext() && !monitor.isCancelled()) {
+ Function f = fit.next();
+ if (!f.isExternal()) n++;
+ }
+ println("functions = " + n);
+ }
+}