// 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 -noanalysis \ // -postScript ExportFunctionIndex.java // // Outputs (all TSV, one row per item, with a header row): // /functions.tsv addr size name namespace is_exported // is_external is_thunk has_body entry_bytes // /exports.tsv addr size name // /imports.tsv addr name library // /blocks.tsv name start end size r/w/x // /callgraph.tsv caller_addr callee_addr call_sites // /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 "); 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 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)); } }