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 | f874d709e5a45d63c8ee9ab6b0300195aa5d20a5 (patch) | |
| tree | 2d07208f789e402686ac960e5ad7456b6fd21cfd /userspace/cuda/tools/ghidra/ExportFunctionIndex.java | |
| parent | ae18e08d02500579f084616874d8f2ae57f8281b (diff) | |
| download | open-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.java | 192 |
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)); + } +} |
