diff options
| author | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-09-23 12:11:04 -0400 |
|---|---|---|
| committer | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-09-23 12:11:04 -0400 |
| commit | 21b8ebc9db1d0ae649d6406087ea22b54e3b5775 (patch) | |
| tree | 192aa5d3c76e0cf3a785936d9902c6848609d37e /userspace/cuda/tools/ghidra | |
| parent | 631bd20c63a4058775a4fa549b9473915c918020 (diff) | |
| download | open-iov-21b8ebc9db1d0ae649d6406087ea22b54e3b5775.tar.gz open-iov-21b8ebc9db1d0ae649d6406087ea22b54e3b5775.zip | |
userspace/cuda: add CUDA userspace driver analysis, tooling and name maps
Add the analysis of the NVIDIA CUDA userspace driver used by Open-IOV VMs:
* docs/01-08: architecture (layers, object model, threads, sync, memory,
channels, launch), build and execution model, the driver/kernel/GPU
interface contract, virtualization approaches, the 570.144 decompilation
map with the corrected libcuda crash path and the current engine-topology
blocker, tooling, and a version-upgrade playbook.
* tools/: version-agnostic toolchain - stdlib ELF/interface toolkit
(exports, unwind-table function map, wrapper resolution, version delta,
crash address decode), callgraph walker, corpus annotator, and Ghidra
analysis/decompilation/name-map scripts with resumable parallel drivers.
* 570.144/annotations/: our name map (exports + resolved wrappers + verified
internals) and subsystem index, tracked so naming work accumulates.
* READ-ME-FIRST.md: reading guide with the fast lookup recipes.
The full decompiled corpus and annotated corpus stay untracked (vendor-
derived, gitignored under the version run/ tree).
Diffstat (limited to 'userspace/cuda/tools/ghidra')
| -rw-r--r-- | userspace/cuda/tools/ghidra/ApplyNameMap.java | 86 | ||||
| -rw-r--r-- | userspace/cuda/tools/ghidra/BatchDecompile.java | 141 | ||||
| -rw-r--r-- | userspace/cuda/tools/ghidra/ConfigureAnalysis.java | 84 | ||||
| -rw-r--r-- | userspace/cuda/tools/ghidra/DecompileAt.java | 86 | ||||
| -rw-r--r-- | userspace/cuda/tools/ghidra/ExportFunctionIndex.java | 192 | ||||
| -rw-r--r-- | userspace/cuda/tools/ghidra/PrintProgramInfo.java | 38 |
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); + } +} |
