diff options
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/docs')
| -rw-r--r-- | debug/risc-v-gsp-rm-vm/docs/00-OVERVIEW.md | 98 | ||||
| -rw-r--r-- | debug/risc-v-gsp-rm-vm/docs/01-DESIGN.md | 179 | ||||
| -rw-r--r-- | debug/risc-v-gsp-rm-vm/docs/02-USAGE.md | 168 | ||||
| -rw-r--r-- | debug/risc-v-gsp-rm-vm/docs/03-SCRIPTING.md | 219 | ||||
| -rw-r--r-- | debug/risc-v-gsp-rm-vm/docs/04-PLUGINS.md | 123 | ||||
| -rw-r--r-- | debug/risc-v-gsp-rm-vm/docs/05-GHIDRA-BRIDGE.md | 105 | ||||
| -rw-r--r-- | debug/risc-v-gsp-rm-vm/docs/06-ACCURACY.md | 136 | ||||
| -rw-r--r-- | debug/risc-v-gsp-rm-vm/docs/07-PORTABILITY-PLAYBOOKS.md | 126 | ||||
| -rw-r--r-- | debug/risc-v-gsp-rm-vm/docs/08-ROADMAP.md | 116 | ||||
| -rw-r--r-- | debug/risc-v-gsp-rm-vm/docs/09-GSP-FIRMWARE-NOTES.md | 104 | ||||
| -rw-r--r-- | debug/risc-v-gsp-rm-vm/docs/10-RELATED-WORK.md | 76 |
11 files changed, 1450 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/docs/00-OVERVIEW.md b/debug/risc-v-gsp-rm-vm/docs/00-OVERVIEW.md new file mode 100644 index 0000000..a9f4251 --- /dev/null +++ b/debug/risc-v-gsp-rm-vm/docs/00-OVERVIEW.md @@ -0,0 +1,98 @@ +# 00 — green-eye overview + +> **# TOOL IN DEVELOPMENT - NOT READY YET!** +> Experimental tooling for simulating and inspecting the GSP firmware's +> runtime memory, for `gsp-rm-vfio` accuracy measurements and GSP/uproc +> reverse engineering. Interfaces and file formats may change without notice. +> It is development-only: never shipped, never a dependency of the emulation, +> never present in a guest. + +## What problem it solves + +Open-IOV emulates the GSP interface and (in the physical backend) boots the +real GSP firmware. Both efforts need the same missing capability: to *watch +the firmware do things* — which handler runs, what it reads and writes, what +it returns, how its state machine evolves — without a physical GPU, without +WPR2/hazard exposure, and repeatably. + +green-eye runs uproc/GSP firmware in `qemu-system-riscv64` and gives the +developer Frida-like instrumentation over its memory and execution: + +* breakpoints and enter/leave **hooks** (RV64 ABI aware), +* **watchpoints** on firmware variables, +* **memory** read/write/hexdump/search/scan, +* **structure decoding** from schemas, +* **snapshot + diff** of memory regions, +* **sessions**: a recorded, replayable dump of runtime memory activity, +* **post-run analysis** with symbol annotation, +* **plugins** for new tools, harnesses and virtualization updates, +* a **Ghidra bridge** joining static addresses to runtime hits, +* A/B tooling for **bare-metal vs vfio-mdev** result comparison. + +It is deliberately not a GDB wrapper: a pure-Python RSP client makes the +whole stack scriptable, and QEMU is just the first execution backend. + +## Status (v0.1, 2026-10-02) + +| Capability | State | +|---|---| +| RV64 QEMU boot + gdbstub transport (no GDB) | working, live-verified | +| Hooks / breakpoints / watchpoints / step / continue | working, live-verified | +| Memory tools + struct decode + snapshot diff | working, live-verified | +| Session recording + `analyze` report | working, live-verified | +| Declarative probe specs | working, live-verified | +| Plugin loader + example plugin | working | +| Test suite | 22 tests green (live QEMU smoke included) | +| Ghidra bridge | scripts + docs | +| Real GSP `.fwimage` probe (first-fault report) | working (honest: it does not boot without a TU104 model — see 06) | +| TCG full-memory tracing plugin | source + optional build | +| MCP server / custom simulator / full device model | roadmap only (08) | + +## Quick start + +```bash +cd open-iov/debug/risc-v-gsp-rm-vm +make demo # build the bundled RV64 uproc demo +./greeneye.sh demo # canned instrumentation pass, records a session +./greeneye.sh info # environment check +./greeneye.sh probe # run the real GSP fwimage and report first divergence +``` + +Interactively: + +```bash +./greeneye.sh demo --no-build --duration 0 # launches; prints the gdbstub port +./greeneye.sh attach --port <port> --kernel firmware/uproc_demo/build/uproc_demo.elf +``` + +## Document map + +| Doc | Read it for | +|---|---| +| [01-DESIGN](01-DESIGN.md) | how the pieces fit, transport, data model, limits | +| [02-USAGE](02-USAGE.md) | CLI and REPL recipes | +| [03-SCRIPTING](03-SCRIPTING.md) | writing programs against the API | +| [04-PLUGINS](04-PLUGINS.md) | extending green-eye | +| [05-GHIDRA-BRIDGE](05-GHIDRA-BRIDGE.md) | static/dynamic address join | +| [06-ACCURACY](06-ACCURACY.md) | what the simulation does and does not prove | +| [07-PORTABILITY-PLAYBOOKS](07-PORTABILITY-PLAYBOOKS.md) | new GSP / device / architecture | +| [08-ROADMAP](08-ROADMAP.md) | MCP, custom simulator, device model | +| [09-GSP-FIRMWARE-NOTES](09-GSP-FIRMWARE-NOTES.md) | uproc/falcon/NV-RISCV facts used here | +| [10-RELATED-WORK](10-RELATED-WORK.md) | Frida/GDB/QEMU-plugin comparison | + +## Where this fits in the Open-IOV workflow + +green-eye is a **development and research** tool, alongside the Ghidra corpus +and the guest diagnostics. It does not replace either: + +* **Ghidra** answers *what is the code*; green-eye answers *what did it do*. +* **Guest diagnostics** (`tools/guest-diag`) answer *what did the guest + driver do*; green-eye answers *what did the firmware server do*. +* **The physical backend** (PLAN B) answers *what does real silicon do*; + green-eye provides a safe place to repeat, mutate and single-step the same + firmware when the hardware path is hazardous or unavailable. + +The intended accuracy loop is: run the same workload against the real +GSP-RM (hardware or mdev), record its RPC/state behaviour, then run the +firmware in green-eye, record the same events, and compare with `compare` +(see 06 and 07). diff --git a/debug/risc-v-gsp-rm-vm/docs/01-DESIGN.md b/debug/risc-v-gsp-rm-vm/docs/01-DESIGN.md new file mode 100644 index 0000000..c5de2da --- /dev/null +++ b/debug/risc-v-gsp-rm-vm/docs/01-DESIGN.md @@ -0,0 +1,179 @@ +# 01 — green-eye design + +> **# TOOL IN DEVELOPMENT - NOT READY YET!** See 00 for status. + +## 1. Why this shape + +Two facts drive the design: + +1. **Frida does not run here.** Frida upstream ships no RISC-V target + (verified against the 17.20 release assets: x86/x86_64, arm/arm64, mips, + macOS/Windows/QNX/Prospero — no riscv64), and its agent model requires a + hosted process. The GSP image is bare-metal RV64IMAC with its own libos. + So the instrumentation layer is built from QEMU's gdbstub and QEMU TCG + plugins instead (see 10 for the full comparison). +2. **The project needs scripting, not a debugger.** The recurring workflow is + "run firmware, capture a symbol-annotated map of behaviour, diff it against + another run". A pure-Python RSP client makes that a library call; GDB + would make it a subprocess and a parsing problem. + +Design goals, in order: direct time-to-value, scriptability, extensibility, +robustness, and honest fidelity reporting. + +## 2. Component map + +``` + +-----------------------------+ + | qemu-system-riscv64 (virt) | + | -bios none -kernel <elf> | + | gdbstub <-- TCP | + +--------------+--------------+ + | RSP + greeneye/rsp.py pure-Python RSP client + | | + | registers, memory, Z0-Z4, continue/step/interrupt, qRcmd, qXfer + v + greeneye/target.py QEMU process lifecycle, loader segments, UART/qemu logs + greeneye/elf.py ELF64/32 parse: segments, symbols, entry + greeneye/symbols.py symbol DB (ELF + Ghidra TSV + JSON) + address translation + greeneye/session.py hooks, breakpoints, watchpoints, stop dispatch, run loop + greeneye/memory.py read/write/hexdump/search/snapshot/diff + greeneye/structs.py declarative structure schemas + render + greeneye/probes.py declarative probe specs + conditions + greeneye/record.py session dir: events.jsonl + snapshots + session.json + greeneye/analyze.py summary/compare reports (symbol annotated) + greeneye/plugins.py plugin discovery + API + greeneye/ghidra.py Ghidra export/import bridge + greeneye/cli.py info/demo/run/attach/probe/record/analyze/compare/symbols +``` + +## 3. Transport: the RSP client + +`rsp.py` implements the gdbstub subset QEMU needs: + +* packet framing `$<data>#<checksum>`, ack/no-ack, retransmit on bad checksum; +* `QStartNoAckMode` (QEMU acknowledges with an **empty packet**, then + switches once the client acks — a bring-up bug worth remembering); +* `qSupported`, including the stub's `PacketSize` (memory payloads are chunked + so hex expansion never exceeds it); +* `qXfer:features:read:target.xml`, recursively inlining `xi:include` register + files; RV64 register layout with a 33×8-byte fallback; +* `g`/`p`/`P`, `m`/`M`/`X`, `Z0-Z4`, `c`/`s`, interrupt `0x03`, `qRcmd`; +* stop-reply parsing (`T`/`S`/`W`/`X`, `watch:`/`rwatch:`/`awatch:`, + `thread:`). + +The client is deliberately blocking and simple; async/concurrency belongs in +the plugin layer, not the transport. + +## 4. Session semantics (the Frida-like core) + +* **Breakpoint kinds**: `sw` (default; stepped over transparently) and `hw`. +* **Hooks**: `hook(target, on_enter, on_leave)`. `on_leave` is implemented by + reading `ra` on entry and arming a *return breakpoint*; nested calls at the + same return address are queued (LIFO). Tail calls and asynchronous + interrupts are not tracked — documented limitation. +* **Watchpoints**: `Z2/Z3/Z4` (write/read/access). QEMU stops *at* the + accessing instruction, so green-eye temporarily clears the watch, steps one + instruction, and re-arms it. Without this the target livelocks on the same + store. +* **Stop dispatch**: every stop reads the register file, resolves the PC to a + symbol, records an event, invokes matching hooks/watchpoints, and resumes. +* **Run loop**: `run(duration=…, max_stops=…)` services events until the + wall-clock budget, stop budget, or target exit; the deadline path interrupts + **once**, dispatches that stop, and returns (never interrupt twice). +* **Faults**: `ebreak`/traps surface as plain stops; `step()` walks past them. + The demo uses this to demonstrate trap capture. + +## 5. Address spaces and the symbol layer + +Three address spaces must never be confused (the CUDA docs learned this the +hard way): + +| Space | Meaning | Example | +|---|---|---| +| file vaddr | offset in the binary file (`libcuda+0x…`) | `0x353ef0` | +| program addr | what Ghidra shows (file vaddr + image base) | `0x453ef0` (base `0x100000`) | +| runtime addr | link address + relocation bias | `0x80000048` | + +`SymbolDB` stores runtime addresses. Loaders take a `bias`: + +```python +db.load_elf(image, bias=relocation) # demo firmware: bias 0 +db.load_names_tsv("names.tsv", bias=0x100000) # libcuda: file vaddr -> runtime +db.load_index_tsv("decompiled_index.tsv") # program+file addr columns +db.load_callgraph("callgraph.tsv") # caller/callee extras +``` + +`resolve()` returns the containing symbol (or nearest preceding within a +delta), and `annotate()` renders `addr name+0xoff`. + +## 6. Recording format (`greeneye-session/1`) + +A session directory is the durable artifact: + +``` +dumps/<stamp>-<name>/ + session.json # format, name, created, counts, snapshot inventory, meta + events.jsonl # one JSON object per event, monotonic t + fields + snapshots/*.bin.z # zlib-compressed region captures (sha256 recorded) + blobs/* # plugin/probe dumps + summary.md # rendered analysis (analyze/render_summary) +``` + +Event types: `session_start`, `image_loaded`, `hook`, `watch`, `bp_hit`, +`leave`, `watch_hit`, `stop`, `step`, `mem_read`, `mem_write`, `snapshot`, +`snapshot_saved`, `probe_log`, `probe_dump`, `plugin*`, `note`, `exit`, +`session_end`. The format is append-only JSON lines so partial sessions stay +readable, and `compare` can profile two sessions symbolically. + +This is the "complete map and recorded dump of runtime memory activity": the +event stream is the map, the snapshot/blob files are the dump. + +## 7. Structures + +Schemas are JSON data (`structs.schema_from_json`) with integer, float, +`bytes`, `cstr` and `ptr` fields plus optional `enum` names and notes. The +demo control block schema lives at +`firmware/uproc_demo/gsp_ctrl.schema.json`. Schemas are intentionally +declarative so they can come from a Ghidra type export or a plugin later. + +## 8. Execution backends + +* `QemuTarget` (`virt`, RAM, NS16550A UART) is the default and the only + supported backend today. +* The **kernel fast path** passes `-kernel <elf>`; QEMU loads segments and + sets PC. +* The **probe path** passes each PT_LOAD segment as a raw loader device and + green-eye sets PC through the gdbstub — this lets a firmware linked for GPU + physical addresses be relocated into RAM for observation. +* A **custom simulator** can be added as a session backend later (roadmap, + not v0.1) because `Session` only assumes the RSP-like operations. + +## 9. Ghidra bridge + +* Out: `greeneye symbols export --names … --index … --exports … --callgraph …` + builds `greeneye-symbols.json` from the project's existing TSV exports. +* In: `greeneye.ghidra.export_hits(session_dir, out_tsv)` writes + `addr<TAB>hits<TAB>symbol`, and `MarkGreenEyeHits.java` annotates those + functions in a Ghidra project (plate comments + bookmarks). +* Mapping a hit to source: symbols carrying `bucket`/`line` extras resolve to + the decompiled corpus via `greeneye.ghidra.corpus_location()`. + +## 10. Plugins + +`plugins.py` discovers modules and calls `register(ge)` with a `GreenEyeAPI` +(`session`, `memory`, `symbols`, `recorder`, `hook`, `watch`, `on_event`, +`add_command`, `log`). Errors are recorded as `plugin_error` and never break a +session. MCP is a planned plugin, not an implementation (08). + +## 11. Known limits (be honest) + +1. QEMU `virt` models **no TU104**: GPU MMIO, falcon windows, WPR2, PMU, + timers and FB DMA do not exist. The real GSP image will fault early; the + `probe` command reports exactly where. Not a boot environment (06). +2. The transport is blocking; no replay of async interrupts mid-run. +3. Return hooks miss tail calls and interrupt-driven returns. +4. `qRcmd` monitor commands are host-tooling only and not portable. +5. Recorded events describe *observed* stops; instructions executed between + stops are invisible unless the TCG plugin is enabled or a step trace is + taken. diff --git a/debug/risc-v-gsp-rm-vm/docs/02-USAGE.md b/debug/risc-v-gsp-rm-vm/docs/02-USAGE.md new file mode 100644 index 0000000..53cf19f --- /dev/null +++ b/debug/risc-v-gsp-rm-vm/docs/02-USAGE.md @@ -0,0 +1,168 @@ +# 02 — green-eye usage + +> **# TOOL IN DEVELOPMENT - NOT READY YET!** See 00 for status. + +All commands are run from `open-iov/debug/risc-v-gsp-rm-vm/`. + +## 0. Requirements + +* `qemu-system-riscv64` (Ubuntu: `apt install qemu-system-misc`); +* Python 3.10+ (stdlib only); +* for the demo firmware: `clang` and `lld` (`apt install lld-18` provides + `/usr/lib/llvm-18/bin/ld.lld`). + +Check everything: + +```bash +./greeneye.sh info +make test # builds the demo then runs unit + QEMU smoke tests +``` + +## 1. The canned demo (fastest way to see it work) + +```bash +make demo +./greeneye.sh demo +``` + +It boots `firmware/uproc_demo/build/uproc_demo.elf`, installs hooks on all +`rpc_*` handlers and a watchpoint on `rpc_count`, runs the firmware self-test, +injects a `PING` from the host, decodes and diffs the control block, then +injects `FAULT` to capture the `ebreak` trap. A session is recorded under +`dumps/` and a `summary.md` is written. + +Options: `--no-build`, `--duration SECONDS`, `--kernel PATH`, `--record PATH`. + +## 2. Interactive REPL + +```bash +./greeneye.sh attach --kernel firmware/uproc_demo/build/uproc_demo.elf +``` + +`attach` launches the target itself when `--port` is omitted. To share a +target between terminals, launch it separately and attach by port: + +```bash +./greeneye.sh launch --kernel <elf> # prints: gdbstub: 127.0.0.1:<port> +./greeneye.sh attach --port <port> --kernel <elf> +``` + +REPL commands: + +| Command | Meaning | +|---|---| +| `regs` / `reg <name> [value]` | read/write registers | +| `x <addr> [len]` | annotated hexdump | +| `mem <addr> <len>` | raw bytes (hex) | +| `w <addr> <hex>` / `wstr <addr> <text>` | write memory | +| `bp <addr\|symbol> [sw\|hw]` | breakpoint that prints on hit | +| `hook <addr\|symbol>` | enter/leave hook with printing | +| `watch <addr\|symbol> [size] [w\|r\|a]` | watchpoint | +| `s [n]` / `c [seconds]` | step n; continue for N seconds | +| `snap <label> <addr> <len>` | snapshot a region into the session | +| `sym <addr\|name>` | symbol resolution | +| `events [n]` | tail the recorded event stream | +| `note <text>` | annotate the session | +| `quit` | close and exit | + +Plugin-provided commands appear automatically (e.g. `gehits` from the example +plugin when loaded with `--plugin plugins/example_plugin.py`). + +## 3. Scripted sessions + +```bash +./greeneye.sh run examples/scripts/trace_rpc.py +./greeneye.sh run examples/scripts/dump_diff.py +./greeneye.sh run my_script.py --kernel <elf> --plugin plugins/example_plugin.py +``` + +The script runs with `ge`, `s`/`session`, `memory`, `symbols`, `recorder` in +scope and is recorded by default. See [03-SCRIPTING](03-SCRIPTING.md). + +## 4. Recording, analysis, comparison + +```bash +./greeneye.sh record --duration 2.0 --watch gsp_ctrl+0x10:8 --snapshot gsp_ctrl:392 +./greeneye.sh analyze dumps/20261002-082721-demo +./greeneye.sh analyze dumps/<a> --symbols symbols.json > a.md +./greeneye.sh compare dumps/<run-a> dumps/<run-b> --symbols +``` + +`analyze` prints a markdown report: event counts, breakpoint hits by symbol, +watchpoint hits, written pages, stepped coverage, snapshot inventory and a +timeline. `compare` diffs two sessions symbolically (hit-count deltas, watch +deltas, written-page deltas) — the A/B primitive for bare-metal vs +vfio-mdev and old-firmware vs new-firmware runs. + +## 5. Probing an arbitrary firmware image + +```bash +./greeneye.sh probe # the project's TU104 570.144 fwimage +./greeneye.sh probe path/to/fw.elf --duration 0.5 +./greeneye.sh probe --instructions 2000 --no-relocate +``` + +Segments are flattened into a raw kernel loaded at `0x80000000`; because QEMU +starts raw kernels at the load base, a tiny PC-relative trampoline at offset 0 +jumps to the image's real (relocated) entry. The run is bounded and the report +shows the **first exception** (faulting instruction, access address/size, +cause) plus the QEMU log tail. For the real GSP image expect an early +load-access fault on an unmodelled GPU register — that is the honest result +(see [06-ACCURACY](06-ACCURACY.md)). + +## 6. Symbol databases + +```bash +./greeneye.sh symbols export \ + --names ../../userspace/cuda/570.144/annotations/names.tsv \ + --index <run>/analysis/decompiled/decompiled_index.tsv \ + --callgraph <run>/analysis/elf-index/callgraph.tsv \ + --bias 0x100000 -o symbols-570.json +./greeneye.sh symbols resolve --db symbols-570.json 0x453ef0 +``` + +`--bias` normalises file vaddrs to runtime addresses (`0x100000` is the +libcuda Ghidra image base; use 0 for firmware linked at its load address). + +## 7. Plugins + +```bash +./greeneye.sh plugins +./greeneye.sh run examples/scripts/trace_rpc.py --plugin plugins/example_plugin.py +GREENEYE_PLUGINS=/path/to/plugins ./greeneye.sh demo +``` + +See [04-PLUGINS](04-PLUGINS.md). + +## 8. Make targets + +| Target | Action | +|---|---| +| `make demo` | build the demo firmware | +| `make test` | build + unit tests + live QEMU smoke | +| `make demo-run` | `greeneye demo --no-build` | +| `make probe-real` | run the TU104 fwimage probe | +| `make plugin` | build the optional TCG plugin | +| `make clean` | remove builds, dumps and `__pycache__` | + +## 9. Troubleshooting + +* **`qemu-system-riscv64 not found`** — install `qemu-system-misc` or set + `GREENEYE_QEMU`. +* **`gdbstub port not ready`** — QEMU exited early; the error includes its + stderr. Check the ELF actually loads (`./greeneye.sh info`). +* **A run seems stuck** — sessions never `pkill -f greeneye` (it matches the + invoking shell). Use `pkill -9 -f '[q]emu-system-riscv64'` to clean up + leftover emulators. +* **Target stops immediately at `0x1000`** — the firmware was not passed to + QEMU; check the `-kernel` path (known historical bug, fixed). +* **`Permission denied` on `/dev/kvm`** — not needed here; QEMU `virt` may + fall back to TCG. For speed, ensure the user can use KVM. + +## 10. Safety + +green-eye touches no GPU, no mdev, no kernel module and no host driver. It +runs unprivileged QEMU instances on loopback gdbstub ports. The only host +side effects are temporary directories under `/tmp` and `dumps/` under the +tool tree. It is safe to run alongside the CUDA/PLAN-B work, but keep an eye +on RAM/CPU if large snapshots are recorded. diff --git a/debug/risc-v-gsp-rm-vm/docs/03-SCRIPTING.md b/debug/risc-v-gsp-rm-vm/docs/03-SCRIPTING.md new file mode 100644 index 0000000..c677c8d --- /dev/null +++ b/debug/risc-v-gsp-rm-vm/docs/03-SCRIPTING.md @@ -0,0 +1,219 @@ +# 03 — green-eye scripting + +> **# TOOL IN DEVELOPMENT - NOT READY YET!** See 00 for status. + +green-eye scripts are ordinary Python programs run with +`./greeneye.sh run <script.py>` (or `python3 -m greeneye run …`). The +names `ge`, `session`/`s`, `memory`, `symbols`, `recorder` are preloaded. + +## 1. Attach or launch + +```python +import greeneye as ge + +# Launch the bundled demo (QEMU -kernel fast path), recording by default. +s = ge.launch(kernel="firmware/uproc_demo/build/uproc_demo.elf", record=True, name="my-run") + +# Or attach to an already-running gdbstub (e.g. started by `greeneye launch`). +# s = ge.attach(port=4444, record="dumps/attach-run") + +try: + ... +finally: + s.close() # clears breakpoints/watchpoints, kills owned QEMU +``` + +`launch(..., keep_alive=True)` leaves QEMU running after `close()`. + +## 2. Symbols + +```python +sym = s.symbols.get("rpc_sum") # Symbol | None +addr = s.symbols.lookup("rpc_sum") # int, raises if missing +s.symbols.annotate(0x80000048) # "0x80000048 rpc_ping" +s.symbols.resolve(0x80000050) # Symbol (containing or nearest) +s.resolve("rpc_sum") # int | int passthrough | "0x..." prefix +``` + +Symbols come from ELF (demo), Ghidra `names.tsv`/`decompiled_index.tsv` +(`symbols export`), or native JSON. + +## 3. Hooks and breakpoints + +```python +events = [] + +def on_sum_enter(hit): + events.append(("enter", hit.arg(0), hit.arg(1))) # a0, a1 + +def on_sum_leave(hit): + events.append(("leave", hit.retval)) # a0 at return + +handle = s.hook("rpc_sum", on_enter=on_sum_enter, on_leave=on_sum_leave) +handle.hits, handle.label, handle.addr +handle.detach() + +bp = s.breakpoint(0x800000fe, callback=lambda hit: print(hit), temporary=True, kind="sw") +s.remove_breakpoint(bp) +``` + +`Hit` carries `kind`, `addr`, `regs` (full register dict), `symbol`, +`retval` (leave hits), `watch`, `stop`, `session`, plus helpers +`hit.arg(n)` and `hit.label()`. + +A hook's `on_leave` is realised as a return breakpoint; the callback's +`hit.addr`/`hit.symbol` are the hooked function (not the return site), and +`hit.retval` is `a0`. Tail calls and interrupt returns are not tracked. + +## 4. Watchpoints + +```python +def on_write(hit): + print(f"{hit.label()} written; pc={hit.pc:#x}") + +wp = s.watch("gsp_ctrl+0x10", size=8, kind="w", callback=on_write) # w|r|a +wp.hits +s.unwatch(wp) +``` + +The session transparently steps over the triggering store (required with +QEMU; see 01 §4). + +## 5. Memory + +```python +s.memory.read(0x80000840, 64) +s.memory.write_u64(0x80000860, 1) # mailbox = PING +s.memory.hexdump(0x80000840, 128) # annotated +s.memory.search_string(0x80000000, 0x10000, "GREEN") +s.memory.search_u32(0x80000000, 0x10000, 0x47535031) +s.memory.read_cstring(0x800007d0) +s.memory.snapshot(0x80000840, 392, name="ctrl") +``` + +`Memory.read`/`write` record `mem_read`/`mem_write` events; snapshots are +saved and hashed by the recorder. + +## 6. Structures + +```python +import json +from greeneye.structs import schema_from_json, render, decode + +schema = schema_from_json(json.load(open("firmware/uproc_demo/gsp_ctrl.schema.json"))) +values = decode(schema, s.memory.read(ctrl, schema.size), base=ctrl) +print(render(schema, values)) +``` + +A schema is JSON: `{"name":…, "fields":[{"name","offset","fmt","size?","enum?","note?"}]}` +with `fmt` in `u8..u64`, `s8..s64`, `f32/f64`, `bool`, `bytes`, `cstr`, +`ptr`. + +## 7. Snapshots and diffs + +```python +from greeneye import Memory + +before = s.snapshot(ctrl, schema.size, "before") +s.memory.write_u64(ctrl + 32, 1) +s.run(duration=0.4) +after = s.snapshot(ctrl, schema.size, "after") +print(Memory.render_diff(Memory.diff(before, after, base=ctrl), s.symbols)) +``` + +## 8. Running + +```python +s.run(duration=1.0) # service events for one second +s.run(max_stops=20) # or until 20 stops +hits = s.step(10) # single-step and dispatch hooks +s.stop() # interrupt and dispatch +``` + +`run` returns the number of stops serviced. Every stop is recorded. + +## 9. Declarative probes + +For simple instrumentation, ship a JSON spec instead of Python: + +```json +{ + "probes": [ + {"name": "sum", "symbol": "rpc_sum", "when": "a1 > 0", + "on_enter": {"log": ["a0", "a1"], + "dump": [{"reg": "a0", "len": 32, "label": "sum-input"}], + "note": "sum called"}, + "on_leave": {"log": ["retval"]}} + ] +} +``` + +```python +from greeneye.probes import load_spec, install +install(s, load_spec("examples/probes/demo_probes.json")) +s.run(duration=1.0) +``` + +Conditions accept register names (`a0..a7`, `pc`, `ra`, `sp`, `x0..x31`, +`retval`), integer/hex literals and `and/or/not` with comparisons; arbitrary +code is rejected. Actions: `log`, `dump` (save a blob), `snapshot`, `note`. + +## 10. Event stream and plugins + +```python +s.on_event("watch_hit", lambda hit: print("wrote", hit.label())) +s.note("starting phase 2") + +# In a plugin module: +def register(ge): + ge.add_command("mycmd", lambda session, args: 0) +``` + +See [04-PLUGINS](04-PLUGINS.md). + +## 11. Recording and post-analysis from Python + +```python +s.recorder.dir # session directory (or None) +s.recorder.event("phase", name="p2") +s.recorder.close() # writes session.json + summary.md + +from greeneye.analyze import render_summary, compare_sessions +print(render_summary("dumps/run-a", symbols=s.symbols)) +print(compare_sessions("dumps/run-a", "dumps/run-b", symbols=s.symbols)) +``` + +## 12. A complete worked script + +```python +"""Drive the demo, capture a PING and a FAULT, and summarise.""" +import greeneye as ge + +s = ge.launch(kernel="firmware/uproc_demo/build/uproc_demo.elf", record=True, name="worked") +ctrl = s.symbols.lookup("gsp_ctrl") +hits = [] +try: + for name in ("rpc_ping", "rpc_sum", "rpc_fault"): + if s.symbols.get(name): + s.hook(name, on_enter=lambda h: hits.append(h.label())) + s.watch(ctrl + 0x10, size=8, kind="w") + s.run(duration=0.5) # self-test + s.memory.write_u64(ctrl + 0x20, 1) # PING + s.memory.write_u64(ctrl + 0x28, 0x1234) + s.run(duration=0.5) + s.memory.write_u64(ctrl + 0x20, 6) # FAULT -> ebreak + s.run(duration=1.0, max_stops=1) + print("pc:", hex(s.pc), s.symbols.annotate(s.pc)) + s.step() # past the trap + print("hits:", hits) +finally: + print("recording:", getattr(s.recorder, "dir", None)) + s.close() +``` + +## 13. Errors and timeouts + +All green-eye errors derive from `greeneye.GreenEyeError`: +`RspTimeout` (transport), `RspProtocolError`, `TargetError`, +`SymbolNotFound`, `StructError`. `RspClient.timeout` (default 10 s) bounds a +single packet read; `run`'s budget bounds the whole pass. diff --git a/debug/risc-v-gsp-rm-vm/docs/04-PLUGINS.md b/debug/risc-v-gsp-rm-vm/docs/04-PLUGINS.md new file mode 100644 index 0000000..fa0fe0c --- /dev/null +++ b/debug/risc-v-gsp-rm-vm/docs/04-PLUGINS.md @@ -0,0 +1,123 @@ +# 04 — writing green-eye plugins + +> **# TOOL IN DEVELOPMENT - NOT READY YET!** See 00 for status. + +A plugin is a Python module exposing `register(ge)`. It is loaded before the +session runs (CLI `--plugin PATH`, `GREENEYE_PLUGINS`, or the bundled +`plugins/` directory) and can add instrumentation, CLI commands and harness +logic without touching green-eye itself. + +## 1. The API object + +```python +def register(ge): + # live objects + ge.session # Session + ge.memory # Memory + ge.symbols # SymbolDB + ge.recorder # Recorder | NullRecorder + + # instrumentation + ge.hook(target, on_enter=None, on_leave=None, kind="sw") + ge.watch(target, size=8, kind="w", callback=None) + ge.on_event(kind, callback) # kind in {"*", "bp_hit", "watch_hit", ...} + ge.resolve(target) # symbol/addr -> runtime address + + # CLI + logging + ge.add_command("name", fn, help="…") # fn(session, args: list[str]) -> int + ge.log("message", any="field") # recorded as a "plugin" event + ge.commands # dict of registered commands +``` + +Everything a plugin records lands in the session's `events.jsonl`, so plugin +findings are replayable and comparable like any other event. + +## 2. Lifecycle and safety + +* `register()` runs once, immediately after the session is attached and + symbols are loaded; the target is still stopped, so hooks are in place + before the first instruction. +* A plugin that raises is recorded as `plugin_error` and skipped; it never + aborts the session or the other plugins. +* Session close detaches all hooks/watchpoints, including plugin-installed + ones. +* Plugins must be stdlib-only unless the dependency is documented; they are + development tooling and never shipped. + +## 3. The four plugin classes + +**(a) Runtime RE tools** — add analysis that green-eye does not have: + +```python +def register(ge): + def on_state(hit): + ge.recorder.event("state_machine", result=hit.retval, pc=hit.pc) + ge.hook("rpc_state", on_leave=on_state) +``` + +Useful additions: allocation graphs, ring-buffer watermarks, page-table walks, +coverage collectors built from `on_event("stop")` plus the TCG trace, entropy +or checksum probes over structures. + +**(b) Harnesses** — drive the firmware and assert: + +```python +def register(ge): + def check(session, args): + ctrl = session.symbols.lookup("gsp_ctrl") + before = session.memory.read_u64(ctrl + 0x10) + session.memory.write_u64(ctrl + 0x20, 1) # PING + session.run(duration=0.3) + after = session.memory.read_u64(ctrl + 0x10) + assert after == before + 1, "PING did not dispatch" + print("PING ok") + return 0 + ge.add_command("check-ping", check) +``` + +**(c) Virtualization-environment updates** — alter how the target is built or +driven: extra QEMU args, a rehosting shim for uproc sources, a device-model +stub, a replay layer that feeds recorded events back into a run. Keep such +changes behind plugin-level flags and document them in the session metadata +(`s.recorder.meta`). + +**(d) AI/harness integration** — export sessions/reports to external tools, +generate probe specs, score runs. **MCP servers are planned as a plugin** +(`docs/08-ROADMAP.md`), not part of v0.1; do not add MCP dependencies to the +core package before that rung is scheduled. + +## 4. Example plugin + +`plugins/example_plugin.py` demonstrates (a), (b) and logging: + +```python +def register(ge): + ge.log("example plugin loaded") + def on_state(hit): + ge.recorder.event("plugin_state_transition", result=hit.retval) + if ge.symbols.get("rpc_state"): + ge.hook("rpc_state", on_leave=on_state) + ge.add_command("gehits", lambda session, args: print("…and 0"), help="…") +``` + +Run it with: + +```bash +./greeneye.sh run examples/scripts/trace_rpc.py --plugin plugins/example_plugin.py +``` + +## 5. Testing a plugin + +1. Load it against the demo firmware (`./greeneye.sh demo --plugin …`) — the + session must remain green and the plugin's events must appear in + `events.jsonl`. +2. Unit-test pure logic outside green-eye; keep hooks small. +3. Never let a plugin assume a particular firmware: gate on + `ge.symbols.get(name)` and log a skip instead of failing. + +## 6. Distribution + +Plugins can live anywhere. For in-tree plugins that document the workflow, +put them under `plugins/` in this tool directory; for local or +target-specific tooling, use `GREENEYE_PLUGINS` and keep paths out of tracked +docs (project rule). diff --git a/debug/risc-v-gsp-rm-vm/docs/05-GHIDRA-BRIDGE.md b/debug/risc-v-gsp-rm-vm/docs/05-GHIDRA-BRIDGE.md new file mode 100644 index 0000000..857102e --- /dev/null +++ b/debug/risc-v-gsp-rm-vm/docs/05-GHIDRA-BRIDGE.md @@ -0,0 +1,105 @@ +# 05 — Ghidra bridge + +> **# TOOL IN DEVELOPMENT - NOT READY YET!** See 00 for status. + +Ghidra answers *what is the code*; green-eye answers *what did it do*. The +bridge joins them in both directions: Ghidra symbols into the runtime session, +runtime hit addresses back into the Ghidra project. + +## 1. Address conventions (read this first) + +| Space | Meaning | Transformation | +|---|---|---| +| file vaddr | offset in the binary (`libcuda+0x…`) | base | +| Ghidra program addr | what the decompiler shows | file vaddr + image base (`0x100000` for libcuda) | +| runtime addr | live process / emulator address | file vaddr + load bias | + +* Firmware linked at its load address: bias 0 (the demo, and a + relocated-to-RAM GSP image when using the probe bias). +* CUDA `libcuda` in the host oracle: runtime base from `/proc/<pid>/maps`; + `--bias` normalises it if you want runtime addresses in the DB. +* The project's `decompiled_index.tsv`/bucket TSVs carry **both** program and + file addresses; green-eye parses both and stores the program address in + `symbol.extras["program_addr"]`. + +## 2. Build a symbol DB from Ghidra exports + +```bash +./greeneye.sh symbols export \ + --names ../../userspace/cuda/570.144/annotations/names.tsv \ + --index ../../userspace/cuda/570.144/run/analysis/decompiled/decompiled_index.tsv \ + --exports ../../userspace/cuda/570.144/run/analysis/elf-index/exports.tsv \ + --callgraph ../../userspace/cuda/570.144/run/analysis/elf-index/callgraph.tsv \ + --bias 0 -o symbols-cuda.json +``` + +For the GSP firmware project (`tools/ghidra/gsp-fwimage-570.144/`), first run +the existing export scripts (`ExportFunctionIndex.java`, +`DecompileFunctions.java`, …) to produce TSVs, then: + +```bash +./greeneye.sh symbols export --index <export.tsv> --bias 0 -o symbols-gsp.json +``` + +`load_index_tsv` is column-aware: it accepts +`<program>\t<file>\t<size>\t<name>\t<bucket>\t<line>\t<status>`, the 4-column +exports form, or a plain `<addr>\t<name>` two-column form. + +## 3. Runtime addresses in the session + +```python +db = SymbolDB() +db.load_json("symbols-cuda.json") +db.load_names_tsv("names.tsv", bias=runtime_base - file_base) +s.symbols = db # or pass symbols= to attach()/launch() +``` + +`resolve()` then annotates every hit with the Ghidra name, and +`greeneye.ghidra.corpus_location(symbol, corpus_root)` returns +`<bucket>:<line>` when the index carried that information — the pointer from a +live hit to the decompiled body. + +## 4. Annotate hits back into Ghidra + +```bash +./greeneye.sh analyze dumps/<run> --symbols symbols-gsp.json --out run.md +python3 - <<'PY' +from greeneye.ghidra import export_hits +from greeneye.symbols import SymbolDB +db = SymbolDB(); db.load_json("symbols-gsp.json") +print(export_hits("dumps/<run>", "greeneye_hits.tsv", symbols=db)) +PY +``` + +`greeneye_hits.tsv` is `addr<TAB>hits<TAB>symbol<TAB>annotation`. Then run the +bundled Java script in Ghidra headless: + +```bash +$GHIDRA/support/analyzeHeadless <proj_dir> <project> -process <program> -noanalysis \ + -scriptPath debug/risc-v-gsp-rm-vm/ghidra \ + -postScript MarkGreenEyeHits.java /abs/path/greeneye_hits.tsv +``` + +`ExportForGreenEye.java` is the reverse helper: it walks the current program +and writes a green-eye-compatible `addr<TAB>name<TAB>size` TSV. + +## 5. Breakpoints from Ghidra + +Two practical routes today: + +1. `greeneye decompose/decompile` → note the file vaddr → script + `s.hook("0x…")` (green-eye translates to runtime via the bias you loaded). +2. Use `symbols export` from the Ghidra index, then `s.hook("<name>")` by + name — the most robust route across recompilations. + +A future Ghidra-side green-eye console (roadmap item) can call +`greeneye` through its CLI to set breakpoints directly from the tool. + +## 6. Limits + +* Wrapper-generated functions are re-laid-out between driver patch versions; + transfer names, never raw wrapper addresses (the CUDA playbook's lesson). +* The decompiler does not recover struct types; structure schemas for + green-eye decoding must be written or exported separately. +* `decompiled_index.tsv` buckets are large and gitignored; keep symbol JSONs + small (name/size/subsystem only) if they must be tracked. diff --git a/debug/risc-v-gsp-rm-vm/docs/06-ACCURACY.md b/debug/risc-v-gsp-rm-vm/docs/06-ACCURACY.md new file mode 100644 index 0000000..1a1ae82 --- /dev/null +++ b/debug/risc-v-gsp-rm-vm/docs/06-ACCURACY.md @@ -0,0 +1,136 @@ +# 06 — Accuracy: what the green-eye test bed does and does not prove + +> **# TOOL IN DEVELOPMENT - NOT READY YET!** See 00 for status. +> This document is deliberately conservative. Treat every claim without a +> recorded A/B as unverified. + +## 1. Purpose + +green-eye exists to measure how accurately Open-IOV's GSP emulation +(`gsp-rm-vfio` and the in-kernel emulation) reproduces the behaviour of the +real GSP firmware, and to make the firmware itself analysable. That makes +accuracy a first-class deliverable, not an assumption. This document defines +the fidelity levels, the measurement contract, and the rules for honest +conclusions. + +## 2. Fidelity levels + +| Level | Property | green-eye today | +|---|---|---| +| **L0 — instruction semantics** | RV64IMAC executes as on the device core | high (QEMU TCG, same ISA family) | +| **L1 — memory behaviour** | reads/writes/ordering as firmware expects | high for RAM-resident code, *conditional* on mapping the same physical layout | +| **L2 — time/concurrency** | cycle counts, DMA latency, interrupt timing | **not modelled** (do not claim) | +| **L3 — device environment** | falcon windows, WPR2, CSB/TRANSCOM, PMU, timers, FB DMA, IRQ routing | **absent** (roadmap) | + +The practical reading: green-eye is an **execution and memory** microscope, +not a timing or silicon model. Conclusions of the form "the firmware writes X +before Y" are in scope; "the firmware takes N µs" is not. + +## 3. The real boot chain vs a bare QEMU VM + +The physical path the project already proved is: + +``` +FWSEC (falcon) -> WPR2 carve -> Booter Load (SEC2) -> RISC-V start + -> bootloader/appMain -> GSP-RM init -> RPC server +``` + +The GSP application's own entry supports this model: `_start` receives +bootargs from the bootloader in `a0`–`a5` (bootargs PA/VA, ELF PA/size, load +base PA, write-protected-region id), sets `stvec`, sets its stack, and calls +`appMain` (see `uproc/gsp/src/kernel/start.S`). It expects falcon/CSB register +windows, a TCM/MPU layout and a PMU-provided environment that `qemu virt` +does not have. + +**Therefore:** loading the raw `.fwimage` into `qemu virt` cannot boot it. +The `greeneye probe` command is designed for exactly this: it loads the +segments, runs bounded, and reports the first divergence +(`guest_errors,unimp,int` log + stopped PC + symbol). That report is a *memory +and control-flow map* of what the firmware tries to touch first — useful data, +but not a boot. + +**Measured first-touch (2026-10-02, TU104 570.144 image).** The firmware +enters at relocated `0x84009000`, executes six instructions, and faults at +firmware vaddr `0x4009014` on a 4-byte read of `0x2000000000000A00` +(load access fault), after which it falls into an `mtvec=0` fetch loop. That +address is the first entry for the R1 device model's register map. + +## 4. Re-hosting the uproc components (the higher-fidelity path) + +The GSP firmware contains the uproc components (`gsp`, `os/libos`, +`nvriscv`, `sec2*`, `soe*`, `pmu_sw`, `conf_compute`, …). Re-hosting them +means compiling a *subset* against a QEMU-virt platform shim: + +| Area | Can be faithful | Cannot be faithful | +|---|---|---| +| task/RPC logic, parsers, state machines | yes — this is ordinary code | — | +| memory layout of app-owned structures | yes, if schemas/addresses are pinned | TCM/MPU protections | +| libos scheduler semantics | approximated (round-robin, timers) | real interrupt latencies, partition switching | +| register/CSB access, falcon windows | stubbed with recorded behaviour | true device semantics | +| PMU/SEC2/ACR/Booter interactions | modelled as messages | boot ROM, secure state machines | + +Re-host fidelity must be stated per module in the session metadata, and every +"this is what the firmware does" claim should be cross-checked against the +physical RPC traces (PLAN B) before it is used to change the emulation. + +## 5. Measurement contract for `gsp-rm-vfio` accuracy + +Define these metrics once and use them everywhere: + +| # | Metric | How it is measured | +|---|---|---| +| M1 | **Inventory coverage** | set of controls/RPCs observed in a real run vs the emulation's handled set (and green-eye's observed set) | +| M2 | **Reply agreement** | field-by-field diff of replies (real vs emulated); canonical-size and consumed-field checks included | +| M3 | **Ordering agreement** | sequence alignment of the control/event stream (edit distance / LCS) | +| M4 | **State agreement** | state-machine transitions observed in the firmware vs the emulation's internal state model | +| M5 | **Error-path agreement** | injected faults produce the same status/notifier path | +| M6 | **Memory side-effects** | read/write map agreement against the emulation's shadow state; snapshot hashes for fixed points | +| M7 | **Timing** | *not claimed*; only order-of-events budgets (e.g. "reply before the 4 s poll") | + +Reference capture: +* real hardware/backend: the project's existing RPC logs (module trace, + `gsp-phys-log-dump`, status-queue drains) and the static-config oracle; +* green-eye: `events.jsonl` + snapshots, symbol-annotated. + +Comparison: `greeneye compare dumps/real-<run> dumps/greeneye-<run> +--symbols`, then a short table of M1–M6 with counts. Never publish a single +"accuracy %" number without the metric breakdown. + +## 6. Adversarial interoperability use + +green-eye is also a safe place to be adversarial: corrupt a reply, flip a +capability bit, truncate a control payload, or inject a fault at a chosen +firmware PC, then observe whether the emulation and the firmware agree on the +failure path. The rules: + +1. The simulator's reaction is *evidence about the firmware*, not about + silicon. Confirm any conclusion against a physical cycle before it changes + the product. +2. Keep the injected fault and the observed path in the session recording. +3. Do not use simulator timing as a security proof (side-channel claims need + physical measurement). + +## 7. Validating the tool itself + +Before trusting a green-eye session: + +* the demo firmware self-test must pass (`greeneye demo`, `make test`); +* the session must contain the expected `hook`/`watch` registrations and a + coherent `bp_hit` sequence (the analyzer highlights empty sessions); +* the UART banner and control-block magic are cheap liveness checks; +* golden sessions (checked-in expectations) for the demo detect regressions + in the toolchain itself. + +## 8. Known accuracy gaps (living list) + +1. No TU104 device model: no boot beyond the first unmapped access. +2. No time/concurrency model; multi-core and partition-switch behaviour is + approximated. +3. PMU/SEC2/ACR/Booter behaviour is message-level only. +4. Re-hosted uproc code is compiled for `qemu virt`, so compiler/layout + differences with the shipped image must be accounted for when comparing + control flow. +5. Interrupt delivery and MSI semantics differ; error-path M5 is the weakest + metric until the device model exists (08). + +Each gap maps to a roadmap item; do not silently ignore them in reports. diff --git a/debug/risc-v-gsp-rm-vm/docs/07-PORTABILITY-PLAYBOOKS.md b/debug/risc-v-gsp-rm-vm/docs/07-PORTABILITY-PLAYBOOKS.md new file mode 100644 index 0000000..5426bf1 --- /dev/null +++ b/debug/risc-v-gsp-rm-vm/docs/07-PORTABILITY-PLAYBOOKS.md @@ -0,0 +1,126 @@ +# 07 — Portability playbooks: new GSP, new device, new architecture + +> **# TOOL IN DEVELOPMENT - NOT READY YET!** See 00 for status. + +These playbooks cover the three churn axes the project expects: a new GSP +firmware version, the same firmware on a new device, and a new GPU +architecture (e.g. GB10 / DGX Spark / Surface-class). They reuse the +project's existing version discipline and add green-eye's runtime layer. + +## 0. Common setup + +1. Acquire the firmware for the target (driver runfile or `linux-firmware`): + extract the GSP boot image (`.fwimage`) and record its hash. For TU10x, + `firmware/57X-GSP/<ver>/gsp-<ver>.fwimage.bin` is the extracted form. +2. Lay down a versioned directory; never overwrite a working version. +3. Build a symbol DB for the new image: + * import the fwimage into the existing Ghidra project tooling + (`tools/ghidra/analyze_fwimage.py`, `DecompileFunctions.java`), + * `./greeneye.sh symbols export --index <export.tsv> -o symbols-<ver>.json`. +4. Record a baseline session of the previous version (see P1.4) so the + comparison has an anchor. + +## P1. New GSP firmware version (same family) + +**Goal:** know what changed in the firmware's runtime behaviour before the +emulation is adjusted. + +1. **Static triage** — the project's existing flow: decompile the new + fwimage, diff the RPC dispatcher, subcommands, message layouts and version + strings against the previous version (`cudatool.py delta`-style discipline + for firmware; `diff_corpus.py` for the corpus). +2. **Probe the new image** — `./greeneye.sh probe <new.fwimage>`: record the + first divergence and the first MMIO/read/write addresses. Compare with the + previous version's probe; changed first-touch addresses are early + structure-change signals. +3. **Re-host / demo run** — if uproc sources for the family are available, + build the rehosted subset against the QEMU-virt shim and run the same + scripted stimulus used for the previous version. +4. **Baseline + candidate sessions** — for each version capture: + * `greeneye record --kernel <rehosted elf> --duration N --snapshot <regions>` + or the scripted harness (`run`), + * the real-backend reference traces for the same workload. +5. **Compare** — `./greeneye.sh compare dumps/<old> dumps/<new> --symbols`: + hit-count deltas per function, watchpoint deltas, written-page deltas. + Investigate every function whose call-count changed before touching the + emulation. +6. **Metrics** — fill in M1–M6 from `06-ACCURACY.md` for the new version + against the real backend. Attach the table to the version-bump PR. +7. **Acceptance** — no rung regression in the existing guest ladder, the + control inventory diff is explained, and every new/changed control has a + version-keyed handler or an explicit "not used" note. + +## P2. Same GSP version, new device + +**Goal:** prove device-profile portability (the project direction is +"configuration + verification, not new code"). + +1. Collect the device profile: PCI IDs, BAR layout/sizes, FB geometry, + engine inventory, interrupt topology, VBIOS/ROM specifics. +2. Verify the firmware pairing: the image family (TU10x/GH100/LS10 …), FMC + partition manifests, and the version string; a mismatched pairing presents + as an emulation bug (the project learned this with 570.144 vs 570.211). +3. `./greeneye.sh probe <device fwimage>` on the new device image; compare + first-divergence maps with the reference device. Differences here usually + point at profile data, not logic. +4. Re-run the P1.4/1.5 loop with the device profile applied; record probe + + compare evidence in the portability matrix. +5. Validate on the physical device with the supervised harness before + claiming support (`docs/06-portability/hardware-software-matrix.md` + discipline: tested and documented, not "should work"). + +## P3. New architecture (GB10 / DGX Spark / Surface-class) + +**Goal:** survive a new silicon generation (likely a newer NV-RISCV core, +newer PMU/SEC2 images and possibly RV64GC rather than RV64IMAC). + +1. **Confirm the ISA and image shape** — `file`/`readelf` the new fwimage: + ISA string, segments, entry, compressed/float extensions. Update the + rehost build flags (`-march=…`) accordingly; note that QEMU supports + RV64GC even if green-eye's demo uses IMAC. +2. **Check the platform's boot variant** — newer platforms use FMC/manifest + partition models (`uproc/*_riscv/fmc`, `safety/PMU-*`, `conf_compute` + Init→ACR→RM flow). Record which partition model the image uses; the + rehost shim needs a matching entry/return convention. +3. **PMU/SOE deltas** — diff the PMU/SOE headers and firmware + (`pmu_sw`, `uproc/soe*`) because the GSP environment provisions identity + and clock state through them. Any change here invalidates prior + environment assumptions. +4. **Run the P1 loop** with the new architecture profile; expect the probe to + show *different* first-touch addresses (new register windows). Update the + device-model scope in `08-ROADMAP.md` with what the probe revealed. +5. **Host portability** — green-eye itself is Python and QEMU; on aarch64 + hosts install `qemu-system-misc` there and the same commands run. Keep + host-specific paths out of session metadata. + +## P4. A/B: bare metal vs vfio-mdev (the core accuracy ritual) + +**Goal:** quantify how closely the virtual function's emulation tracks the +real firmware for a chosen workload. + +1. **Reference capture (real):** + * hardware path: the supervised physical backend cycle, capturing RPC + records/log dumps and the static-config oracle; + * or the vfio-mdev path with the same workload. + Save the reference event stream in a session-compatible form (or the + vendor logs plus a conversion note). +2. **green-eye capture:** run the same workload's firmware-side stimulus in + the rehosted simulator; record hooks, watchpoints, snapshots. +3. **Compare:** `greeneye compare` for hit/watch/write profiles; for RPC + payloads use M2 field agreement and M3 ordering (a small script or a + plugin can emit per-control events). +4. **Report:** metric table (M1–M6), changed-function list, and an explicit + statement of L2/L3 limits from `06-ACCURACY.md`. +5. **Escalation:** a divergence that changes emulation behaviour must be + reproduced on the physical backend before the fix lands; simulator-only + divergence that cannot be reproduced is recorded as a fidelity gap. + +## Evidence checklist (attach to any portability task) + +* firmware hash + version string + pairing notes; +* `probe` output (first divergence, first-touch map); +* symbol DB hash and the Ghidra export provenance; +* session directories for baseline and candidate (or their sha256 list); +* `compare` report; +* M1–M6 table with the reference-capture method; +* unexplained diffs list (empty is a claim, not a default). diff --git a/debug/risc-v-gsp-rm-vm/docs/08-ROADMAP.md b/debug/risc-v-gsp-rm-vm/docs/08-ROADMAP.md new file mode 100644 index 0000000..c8b4638 --- /dev/null +++ b/debug/risc-v-gsp-rm-vm/docs/08-ROADMAP.md @@ -0,0 +1,116 @@ +# 08 — green-eye roadmap + +> **# TOOL IN DEVELOPMENT - NOT READY YET!** See 00 for status. +> Items here are **not** v0.1. Do not start them ad hoc; each has a gate. + +## R0. Product principles + +* green-eye stays a **development tool**: no shipped dependencies, no guest + presence, no kernel-module coupling. +* Fidelity improvements must be *measurable* (metrics table in 06) and must + not slow the fast path (demo + probe) below usefulness. +* Every new backend must satisfy the same `Session` contract so scripts, + probes, plugins and recordings survive. + +## R1. Platform device model (raise L3) + +**Why:** a bare `qemu virt` cannot boot the real fwimage; the probe shows the +first unmapped access and stops. + +**Scope:** model the minimum GSP environment: +falcon windows (FALCON/CSB/TRANSCOM registers), WPR2/FWSEC state registers +with read-back semantics, PTIMER, PMU/SEC2 mailbox stubs, FB DMA/IOMMU +address translation for the firmware's DMA descriptors, and interrupt +routing. QEMU `qemu-system-riscv64` with a custom device (`-device`) is the +preferred vehicle; TCG plugins provide the observation points. + +**Gate:** R1 starts only when a portability task (P2/P3) or an accuracy +measurement (P1.6) requires booting real firmware in-simulator. Until then, +use the probe + rehosted uproc paths. + +**Exit criteria:** `greeneye probe <fwimage>` reaches a stable address and +reports "waiting for host message" rather than a fault; the first host↔GSP +RPC records are captured in a session; M3 ordering can be measured against +the physical backend. + +## R2. Custom simulator (explicitly requested; not first version) + +**Why:** QEMU is an excellent execution engine but a poor *workflow* engine +for this project: no in-process API, RSP serialisation for every access, +limited snapshotting, and a device model designed for whole boards rather +than firmware servers. A custom simulator can be deterministic, replayable +and scriptable at native speed. + +**Shape (to build later, not now):** +* an RV64IMAC interpreter/JIT with a **pluggable memory map** (RAM, MMIO + hooks, DMA windows) and a **deterministic scheduler** (no realtime claim); +* native `Session` integration — replace the RSP transport with a + `Backend` interface, keep hooks/watchpoints/memory/recording identical; +* first-class **snapshot/restore** of full machine state (time-travel + debugging, branch-flip experiments); +* an **event bus** feeding the existing `events.jsonl` schema; +* a **stub DSL** for device registers (read/write scripts with state), so a + new GPU generation is configuration, matching the project's portability + direction. + +**Candidates to evaluate at that time:** extending QEMU TCG with a thin +in-process backdoor, `libriscv`, `rv8`, or a from-scratch interpreter. +Realtime is explicitly *not* a requirement; determinism and introspection are. + +**Gate:** R1 must have stabilised the device semantics (otherwise the custom +simulator inherits an unvalidated model). Estimated multi-week work; schedule +only after the guest-bridge work (PLAN B) no longer depends on it. + +## R3. MCP server plugin + +**Why:** expose green-eye sessions to AI harnesses and other agents under the +plugin model the user requested. + +**Shape:** a plugin (`plugins/mcp_server.py` when built) that wraps the +session/analysis API as MCP tools: `list_sessions`, `open_session`, +`symbols_resolve`, `set_breakpoint`, `run`, `memory_read`, `memory_hexdump`, +`snapshot`, `compare_sessions`, `export_hits`. Transport local stdio first; +no network exposure by default. The plugin depends on an MCP library only at +plugin load time; the core stays stdlib-only. + +**Gate:** v0.1 must be stable (acceptance list in 00) and the session format +frozen. Explicitly deferred by the owner; do not add MCP code before then. + +## R4. Full-memory trace (TCG plugin integration) + +The optional `qemu/greeneye_trace.c` plugin records instruction execution and +memory accesses. Roadmap: build it against the vendored QEMU headers, stream +its JSONL into the session format as `exec`/`access` events, and teach +`analyze` to render coverage/heatmaps and written-page timelines. This is the +"complete map" upgrade from stop-based observation to full access tracing. + +**Gate:** available once the QEMU plugin headers are vendored (or the build +environment provides them). Does not block v0.1 — watchpoint recording plus +step traces already cover the common cases. + +## R5. Fuzzing and mutation harness + +Use AFL++ QEMU mode (or a custom mutator) against the rehosted firmware's +RPC input surface, with green-eye recording crash/trap paths and symbol +annotation. Requires R1/R2 for stable input plumbing. Output: a corpus of +malformed controls and the firmware's error paths, feeding the emulation's +fail-closed rules (project security hygiene). + +## R6. Multi-version orchestration + +Scripts that, given a set of firmware images and device profiles, run the +probe + rehost + compare pipeline for the whole matrix and emit the M1–M6 +tables. This automates `07-PORTABILITY-PLAYBOOKS` P1–P3. + +## R7. Ghidra two-way console + +A Ghidra script/plugin that talks to a live green-eye session (via the CLI or +a local socket) to toggle breakpoints/watchpoints from the decompiler and to +highlight hit addresses. Until then, the hit-TSV + `MarkGreenEyeHits.java` +flow is the supported path. + +## Not on the green-eye roadmap + +* Graphics/display emulation, Windows guest internals, multi-tenant policy — + these belong to the main project roadmap, not to this analysis tool. +* Replacing Ghidra: green-eye is the dynamic complement, never a decompiler. diff --git a/debug/risc-v-gsp-rm-vm/docs/09-GSP-FIRMWARE-NOTES.md b/debug/risc-v-gsp-rm-vm/docs/09-GSP-FIRMWARE-NOTES.md new file mode 100644 index 0000000..216bf93 --- /dev/null +++ b/debug/risc-v-gsp-rm-vm/docs/09-GSP-FIRMWARE-NOTES.md @@ -0,0 +1,104 @@ +# 09 — GSP/uproc firmware notes used by green-eye + +> **# TOOL IN DEVELOPMENT - NOT READY YET!** See 00 for status. +> Exact local-source provenance is recorded in the local task record; this +> tracked document intentionally keeps source locations generic and cites +> public material. + +## 1. The microcontrollers around the GPU + +From the open-iov wiki **GPU Firmware** page and the linked **NVIDIA RISC-V +Story** presentation (the project mirrors the wiki locally; the PDF is a +public RISC-V summit deck): + +* **Falcon (FAst Logic CONtroller)** is NVIDIA's proprietary control + processor, used across >15 hardware engines for over a decade. It was + designed for flexibility, long memory latency, low area and security. + Limits that forced a successor: small addressing range, modest performance + (~0.67 DMIPS/MHz, ~1.4 CoreMark/MHz), no data cache, no rich OS support. +* **NV-RISCV** is NVIDIA's own implementation of a RISC-V core chosen as the + next-generation falcon: more performance (>2× falcon claimed), wider + addressing, TCM, vectored interrupts, security features (TZ-style), with + area targets under 0.1 mm² at 16FF. +* The GPU hosts several of these cores: the **GSP** (GPU System Processor) + running a rich firmware stack, plus security/boot engines (**SEC2**, ACR, + SOE) and the **PMU** microcontroller. The RISC-V Story deck documents the + falcon-to-RV transition and the DMA/mailbox lines between engines. + +## 2. GSP application structure + +The GSP firmware is an ELF64 RV64IMAC image (the TU104 570.144 `.fwimage` used +by the probe has entry `0x4009000`, 33 program headers, stripped). Its +application layer is a multi-task RTOS program: + +* `_start` (hand-written assembly) sets the trap vector to an early panic + handler, initialises the stack, pushes two zero words to help debugger + backtracing, and calls `appMain`. The **bootloader passes arguments in + a0–a5**: bootargs PA and VA, ELF PA and size, load base PA, and the + write-protected region id. A re-host harness must reproduce this ABI. +* `init.c` creates the tasks and starts the RTOS; the included driver set + covers MPU, ODP (on-demand paging), memory, and the RISC-V system library. +* The boot configuration selects `NVRISCV_PARTITION_SWITCH` variants, i.e. + images can run under a separation-kernel partition model. + +## 3. RTOS and libos layers + +Two OS layers appear in the vendor snapshots: + +* **SafeRTOS/NVRTOS** — the classic resident RTOS: priority scheduling, + timers, events/signals, IPC and DMA support; ~38 KiB TCM-resident kernel; + missing paging, task reset, capability security. Used by the GSP test + firmware partition model. +* **LIBOS** (exokernel design, early draft) — designed to be TCM-resident + (projected 1–6 KiB), capability-based security, round-robin scheduler, + DMEM on-demand paging, module configuration so unused features are not + paid for. This is the direction the GSP stack evolves toward. + +**Implication for the simulator:** the scheduler is not the interesting part +for RPC accuracy; the task/dispatcher semantics are. Green-eye's demo mirrors +that shape (mailbox → dispatcher → handler → counters), and a rehost shim +should stub the scheduler while keeping task entry points and message +handling real. + +## 4. Boot and partition models + +* **MP-SK/FMC** (separation kernel): images are built as partitions. One + observed GSP test image has two partitions: a SafeRTOS first-stage + bootloader partition (clobbered after hand-off via partition switch), and a + high-sec test partition that reads performance counters, writes them to + shared memory and switches back. +* **Confidential-compute skeleton**: six partition stubs — RM Proxy, RM + (mutually exclusive with RM Proxy), SPDM, ACR, Init and Attestation — with + the boot flow **Init → ACR → RM**. The vendor tooling side-loads images to + a live GSP with a debug command (`nvwatch …`) for bring-up. + +These models matter for portability: a new architecture may change the +partition layout or boot order, and green-eye's probe is the quickest way to +see the new first-touch sequence before a device model exists. + +## 5. Memory, DMA and registers + +* The firmware assumes **TCM** and an MPU (protection regions are generated + at link time); `qemu virt` has neither, so the probe loads segments into + RAM and faults at the first register access. +* The engines exchange data over **CSB/TRANSCOM windows and mailboxes** with + DMA into host/FB memory; the physical backend work in Open-IOV already + models the message-queue side (status/command queues, doorbells) — that is + the layer green-eye can emulate first when R1 lands. +* The GSP kernel's start path receives its memory permissions as boot args + from the bootloader rather than discovering them, which is why a + re-host should pass the same structure rather than re-derive it. + +## 6. How green-eye models this today (and honestly, does not) + +| Firmware aspect | green-eye v0.1 | +|---|---| +| Task/dispatcher/state-machine logic | modelled faithfully by the demo and by rehosted code | +| RPC handler input/output behaviour | observable and recordable; schemas decode structures | +| RTOS scheduling, interrupts, partition switch | approximated / absent | +| Falcon/CSB/TCM/PMU registers | absent (probe reports first touch; R1 roadmap) | +| Boot ROM chain (FWSEC/booter/SEC2) | out of scope for the VM; the physical backend owns it | + +Use the notes above when designing new rehost shims or a device model: the +goal is to keep everything *above* the register/mailbox boundary real, and to +model that boundary explicitly rather than pretend it away. diff --git a/debug/risc-v-gsp-rm-vm/docs/10-RELATED-WORK.md b/debug/risc-v-gsp-rm-vm/docs/10-RELATED-WORK.md new file mode 100644 index 0000000..a2a45db --- /dev/null +++ b/debug/risc-v-gsp-rm-vm/docs/10-RELATED-WORK.md @@ -0,0 +1,76 @@ +# 10 — related work: why not Frida, and what each tool is for + +> **# TOOL IN DEVELOPMENT - NOT READY YET!** See 00 for status. + +## 1. Frida + +**Verdict for the firmware VM: not applicable.** Upstream Frida ships no +RISC-V target (checked against the 17.20 release assets: x86/x86_64, +arm/arm64, mips, macOS/Windows/QNX/Prospero — no `linux-riscv64`), and +frida-agent requires a hosted OS process. The GSP image is bare-metal RV64 +with its own libos; there is nothing to inject into, and even a hypothetical +RISC-V port would need an OS layer the firmware does not have. + +**Where the Frida *idea* lives in green-eye:** the scripting model. `Session` +offers `hook`/`watch`/`memory`/`events` with Python callbacks, which is the +part of Frida that matters for this workflow. The difference is transport: +RSP to QEMU instead of an injected agent. + +**Where Frida is applicable in the project:** host- or guest-userland x86_64 +work (e.g. the CUDA userspace layer) if a dynamic tracer is ever wanted there. +That is outside this tool's scope; the existing ptrace tracer covers the +guest today. + +## 2. GDB / gdbstub + +QEMU's gdbstub is the transport both tools share. GDB itself is still useful +for *debugging green-eye* (stub behaviour, protocol traces), and `gdb +-multiarch` can inspect the same target interactively. It is not the +instrumentation layer here because: + +* every operation becomes a subprocess/parse problem instead of an API call; +* recording, symbol annotation, structure decoding and session diffing would + have to be rebuilt around GDB/MI anyway; +* the project already uses GDB Python scripts for the CUDA corpus; green-eye + is the equivalent for firmware, with a stronger data model. + +## 3. QEMU TCG plugins + +The correct complement, not a competitor: plugins execute inside QEMU and can +observe **every** instruction and memory access, which gdbstub breakpoints +cannot. green-eye's `qemu/greeneye_trace.c` emits a JSONL access trace for +exactly this; integrating it into the session format is R4 on the roadmap. +Rule of thumb: use green-eye hooks for semantics ("handler X ran with these +args") and TCG plugins for coverage/heatmaps ("every write to this window"). + +## 4. Unicorn / emulator libraries + +Unicorn (and similar CPU emulators) can execute RV64 code faster to set up +than QEMU and embed cleanly in Python, but they model no board at all and +have weaker debug infrastructure. They are candidates for the **custom +simulator** (R2) once the required device semantics are known — the same +reason the roadmap defers that work. + +## 5. `rr` / record-replay + +`rr` does not support RISC-V guests and records at the host-process level. +QEMU's own record/replay (`-icount`) can provide deterministic reruns later; +green-eye's session recording occupies a different niche (semantic events, +not full replay) and can coexist. + +## 6. Comparison table + +| Tool | Layer | RISC-V firmware | green-eye relationship | +|---|---|---|---| +| Frida | userland process injection | no | conceptual model only | +| GDB + gdbstub | execution control | yes (transport) | debugging green-eye; same stub | +| QEMU TCG plugin | full instruction/memory trace | yes | complement (R4) | +| green-eye | semantic instrumentation + recording + analysis | yes | this tool | +| Unicorn/rv8/etc. | CPU emulation library | yes | R2 candidate | +| Ghidra | static decompilation | yes | symbol/hit bridge (05) | + +## 7. Reading list + +* `docs/01-DESIGN.md` — where each layer sits. +* `docs/05-GHIDRA-BRIDGE.md` — the static/dynamic join. +* `docs/08-ROADMAP.md` — R1 device model, R2 custom simulator, R3 MCP. |
