summaryrefslogtreecommitdiffstats
path: root/userspace/cuda/tools/ghidra/ExportFunctionIndex.java
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-09-23 12:11:04 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-09-23 12:11:04 -0400
commitf874d709e5a45d63c8ee9ab6b0300195aa5d20a5 (patch)
tree2d07208f789e402686ac960e5ad7456b6fd21cfd /userspace/cuda/tools/ghidra/ExportFunctionIndex.java
parentae18e08d02500579f084616874d8f2ae57f8281b (diff)
downloadopen-iov-f874d709e5a45d63c8ee9ab6b0300195aa5d20a5.tar.gz
open-iov-f874d709e5a45d63c8ee9ab6b0300195aa5d20a5.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/ExportFunctionIndex.java')
-rw-r--r--userspace/cuda/tools/ghidra/ExportFunctionIndex.java192
1 files changed, 192 insertions, 0 deletions
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));
+ }
+}