summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/docs/01-DESIGN.md
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-02 08:36:35 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-02 08:36:35 -0400
commit31e27d455969d33190d28917ef98f034888d217b (patch)
treec0c4dc18e78e6a1702fcda43bc2806e7cfb7c931 /debug/risc-v-gsp-rm-vm/docs/01-DESIGN.md
parentc155ee8de5b642cbd407bf2231041f17ebe43734 (diff)
downloadopen-iov-31e27d455969d33190d28917ef98f034888d217b.tar.gz
open-iov-31e27d455969d33190d28917ef98f034888d217b.zip
debug: add green-eye GSP/uproc RISC-V VM runtime analysis tooling
Adds open-iov/debug/risc-v-gsp-rm-vm: a GDB-free instrumentation stack that boots uproc/GSP firmware in qemu-system-riscv64 and provides Frida-like hooks, breakpoints, watchpoints, memory dumps, structure decoding, snapshot diffs, recorded sessions, declarative probes, a plugin API and a Ghidra symbol/hit bridge. Verified: 25 tests green (including a live QEMU smoke test); the demo uproc boots with correct hook/leave/watch/schema/snapshot/fault evidence; the optional TCG trace plugin builds and recorded 15M trace lines in 3 s; the TU104 570.144 fwimage probe reports its first exception (load access fault at firmware vaddr 0x4009014 reading 0x2000000000000A00). Documentation: lean tool docs under debug/risc-v-gsp-rm-vm/docs/00-10, a new docs/05-operations/03-risc-v-gsp-rm-vm.md debugging document, and '# TOOL IN DEVELOPMENT - NOT READY YET!' markers in docs/README.md, the onboarding tooling table and the debugging workflows doc. Vendors qemu-plugin.h from QEMU v8.2.2 (GPL-2.0-or-later), with its license header intact, so the optional TCG plugin builds without a QEMU source tree.
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/docs/01-DESIGN.md')
-rw-r--r--debug/risc-v-gsp-rm-vm/docs/01-DESIGN.md179
1 files changed, 179 insertions, 0 deletions
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.