# 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 | | 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 `$#`, 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/-/ 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 `; 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 `addrhitssymbol`, 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.