diff options
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.md | 179 |
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. |
