1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
|
# 06 — Tooling: how to analyse a CUDA userspace driver
The toolchain in `tools/` is version-agnostic. Point it at any
`libcuda.so.<version>` and it produces the same artifacts as for 570.144. All
tools are stdlib-only or Ghidra scripts; nothing needs network access.
```
userspace/cuda/tools/
├── cudatool.py ELF/interface toolkit (python3, no deps)
├── callwalk.py callee/caller trees and shortest call path
├── annotate_corpus.py annotated + name-substituted corpus
├── ghidra_full_analysis.sh import + full auto-analysis into a project
├── ghidra_export_index.sh functions/exports/callgraph/strings index
├── ghidra_batch_decompile.sh parallel, resumable full decompilation
├── ghidra_decompile_at.sh decompile one function by file vaddr
├── ghidra_index_merge.sh merge batch indexes
└── ghidra/
├── ConfigureAnalysis.java analyzer tuning for big stripped binaries
├── ExportFunctionIndex.java index exporter
├── BatchDecompile.java chunk decompiler
├── DecompileAt.java single function (file-vaddr aware)
├── ApplyNameMap.java apply/transfer a name map
└── PrintProgramInfo.java address convention + stats
```
## 1. Fast facts without Ghidra — `cudatool.py`
```
tools/cudatool.py info <lib> # build id, sections, exports, ABI note
tools/cudatool.py exports <lib> # addr size name (file vaddrs)
tools/cudatool.py functions <lib> # function ranges from .eh_frame_hdr
tools/cudatool.py nearest <lib> <addr> # function start + nearest export
tools/cudatool.py crash <lib> <map_base> <ip> # dmesg -> file vaddr
tools/cudatool.py strings <lib> [--grep RE]
tools/cudatool.py note <lib> # .note.cuda.fwd_compatibility JSON
tools/cudatool.py delta <libA> <libB> # see §4
```
Useful for: checking what changed between two driver package versions before
spending time on decompilation; converting a guest dmesg crash to a file vaddr
without gdb; getting a function list in a second.
### Address conventions
* `cudatool.py` prints **file virtual addresses** — the `libcuda+0x...` used
everywhere in this directory.
* Ghidra may load the same binary at a non-zero image base. For 570.144 it is
`0x100000`; check with `PrintProgramInfo.java`. The Ghidra wrappers here
translate file vaddr → program address automatically; raw Ghidra scripts do
not.
## 2. Full analysis — Ghidra
```
# 1. import + analyze (keep the project; can take minutes to hours)
tools/ghidra_full_analysis.sh <lib> <project_dir> <project_name>
# 2. export the index (fast, read-only)
tools/ghidra_export_index.sh <project_dir> <project_name> <program> <outdir>
# 3. decompile everything (parallel, resumable; safe to re-run)
tools/ghidra_batch_decompile.sh <project_dir> <project_name> <program> <outdir> \
<start_hex> <end_hex> [chunk_hex] [jobs] [timeout_s]
tools/ghidra_index_merge.sh <outdir>
```
Notes:
* `ConfigureAnalysis.java` disables the two expensive identifier analyzers
(parameter-ID, function-ID) and keeps everything needed for a stripped
binary. Full analysis of 71 MB then takes minutes, not hours.
* `ghidra_batch_decompile.sh` gives **every chunk its own project copy** under
`<outdir>/wk/` because Ghidra takes an exclusive project lock even in
read-only mode; completed chunks are skipped on re-run. Delete `wk/` to
reclaim disk.
* The program name passed to Ghidra is the binary's file name
(`libcuda.so.570.144`).
* Do **not** pass `-analysisTimeoutPerFile 0`: in this Ghidra build that means
"abort analysis immediately" (the original per-version helper in
`570.144/scripts/` did this and produced an analysed-looking project with no
analysis at all).
## 2b. Annotating the corpus
```
tools/annotate_corpus.py <outdir> <indexdir> <names.tsv> <annotated_outdir>
```
Rewrites every function header with the recovered name, file address, size and
direct callers/callees (named where the map has them), and substitutes
recovered names for `FUN_<addr>` references in the bodies. Runs in seconds for
30k functions; the output is gitignored like the raw corpus.
## 2c. Walking the callgraph without Ghidra
```
tools/callwalk.py <indexdir>/callgraph.tsv callees 0x31b7f0 2 10
tools/callwalk.py <indexdir>/callgraph.tsv callers 0x453ef0
tools/callwalk.py <indexdir>/callgraph.tsv chain 0x31b7f0 0x2cb940
```
Addresses are file vaddrs; `names.tsv` next to the callgraph is used for
labels.
## 3. Looking things up
```
# one function by file vaddr
tools/ghidra_decompile_at.sh <project_dir> <project_name> <program> 0x45a670 out.c
# corpus search by file address (headers are annotated)
grep -n "libcuda+0x45a670" <outdir>/buckets/*.c
# index lookups
awk -F'\t' '$2=="0x45a670"' <outdir>/decompiled_index.tsv
grep -P "^0x0055a670\t" <indexdir>/callgraph.tsv # direct callers/callees
# exports/strings
grep cuLaunchKernel <indexdir>/exports.tsv
```
## 4. Version deltas — `cudatool.py delta`
```
tools/cudatool.py delta <libA> <libB>
```
Reports, at the interface level: added/removed/moved/resized exports, import
count changes, `.text` growth, build-id and the CUDA/ABI note. This is the
first thing to run on a new driver release; it tells you immediately whether
the update is a patch (nothing moved), a feature release (new exports) or a
reshuffle (many moves).
## 5. Name transfer — `ApplyNameMap.java`
```
tools/ghidra_decompile_at.sh-free invocation:
analyzeHeadless <project_dir> <project_name> -process <program> -noanalysis \
-scriptPath tools/ghidra -postScript ApplyNameMap.java <names.tsv> [--dry-run]
```
The map is `file_vaddr<TAB>name` per line (comments allowed). Name maps live
in version directories (`570.144/annotations/names.tsv`) and are tracked, so
naming work accumulates: apply the previous version's map to a new binary,
then fix the misses. `--dry-run` reports how many names would land.
## 6. Writing new Ghidra scripts
Follow the existing pattern: a `GhidraScript` subclass in `tools/ghidra/`,
args via `getScriptArgs()`, and file-vaddr ↔ program-address translation via
`currentProgram.getImageBase()` (see `DecompileAt.java`). Keep outputs under
the version's gitignored `run/` tree unless the artifact is our own work
product (name maps, indexes of our findings), which may be tracked.
## 7. Cost guide (measured on 570.144, 16 cores)
| Step | Time | Disk |
|---|---|---|
| import alone | ~1 min | 324 MB project |
| full analysis | ~10–15 min | same project |
| index export | ~1–2 min | ~25 MB TSV |
| full decompilation (5 workers) | ~20–30 min | ~3 GB C + 2 GB project copies |
Notes for a rerun: set `GHIDRA_HEADLESS_MAXMEM` (default 12G for analysis,
4G per batch job) and keep total batch jobs ≤ 5–6 on a 32 GB machine.
|