diff options
| author | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-02 09:13:00 -0400 |
|---|---|---|
| committer | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-02 09:13:00 -0400 |
| commit | 3fe5360d0efc439d59f2b7a9bf2f713aa9cdb918 (patch) | |
| tree | e30e26701f5a67080404e5f9cb71967ee275e604 /debug/risc-v-gsp-rm-vm/docs/13-GHIDRA-CONTROL.md | |
| parent | 964f34ee2e49d14ce81ee9a6bd9297c88e320c52 (diff) | |
| download | open-iov-3fe5360d0efc439d59f2b7a9bf2f713aa9cdb918.tar.gz open-iov-3fe5360d0efc439d59f2b7a9bf2f713aa9cdb918.zip | |
debug: green-eye wave 2 - injection SDK, Frida-like calls, Ghidra control bridge, long-term docs
Adds to debug/risc-v-gsp-rm-vm/:
- Session.call / run_until: Frida-like remote function calls and run-to-cursor,
with register restoration; verified live (rpc_ping -> 0xaffe).
- symbol+offset target resolution (gsp_ctrl+0x10) across all APIs and the REPL.
- Injection SDK (sdk/) and 'greeneye inject': position-independent RV64 service
blobs (verified zero relocations), loaded into RAM, registered in the demo
handler table, and invoked either directly or through the firmware mailbox
dispatcher. Demo ABI extended: mailbox 16..23 selects slots, arg0 carries the
service command, arg2 added (struct now 0x190; schema updated).
- Ghidra control bridge (greeneye ghidra + GreenEyeControl.java): run-until-here,
breakpoints, memory/register access, remote calls, hit export; verified
end-to-end with Ghidra headless (run_until reached rpc_sum from a GhidraScript).
- MarkGreenEyeHits.java fixed for the Ghidra 12 createBookmark API; verified
headless together with ExportForGreenEye.java.
- Analyzer renders a QEMU first-fault table for probe sessions (fault count,
epc with firmware vaddr translation, access address/size, reason).
- Tests now 35 green, including injection, Ghidra bridge, remote call, run-until
and REPL command smoke tests.
- Documentation: docs/11-16 (GSP RM service developer guide, injection SDK
specification, Ghidra control, adversarial interoperability guide, agentic
GSP IDE, custom simulator design) and GREEN-EYE-ROADMAP.md (long-term H1-H7
program); doc maps and the project RISC-V VM doc updated.
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/docs/13-GHIDRA-CONTROL.md')
| -rw-r--r-- | debug/risc-v-gsp-rm-vm/docs/13-GHIDRA-CONTROL.md | 154 |
1 files changed, 154 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/docs/13-GHIDRA-CONTROL.md b/debug/risc-v-gsp-rm-vm/docs/13-GHIDRA-CONTROL.md new file mode 100644 index 0000000..26e534d --- /dev/null +++ b/debug/risc-v-gsp-rm-vm/docs/13-GHIDRA-CONTROL.md @@ -0,0 +1,154 @@ +# 13 — Ghidra control bridge + +> **# TOOL IN DEVELOPMENT - NOT READY YET!** See 00 for status. + +The bridge lets a GhidraScript drive a live green-eye session: execute the +target until a point **chosen in the decompiler**, set/clear breakpoints, read +memory and registers, make remote calls, and export hit lists back into the +Ghidra project. Nothing about the firmware has to be rebuilt or re-hosted to +answer "does execution actually reach this function?". + +``` +Ghidra (Java script) ── JSON lines over localhost ──► greeneye gdbridge ──► Session ──► QEMU + ▲ │ + └──────── greeneye_hits.tsv ── MarkGreenEyeHits.java ◄──── recorded session ┘ +``` + +## 1. Quickstart + +```bash +# Terminal 1: a live target + bridge (records by default) +./greeneye.sh ghidra --kernel firmware/uproc_demo/build/uproc_demo.elf --bridge-port 7000 +# prints: ghidra bridge on 127.0.0.1:7000 + +# Terminal 2: Ghidra headless, run until the decompiler's address +analyzeHeadless <proj> <name> -process <program> -noanalysis \ + -scriptPath open-iov/debug/risc-v-gsp-rm-vm/ghidra \ + -postScript GreenEyeControl.java 7000 0x800000ce +``` + +In the GUI, load `GreenEyeControl.java` from the Script Manager and supply the +port in the script arguments; with no address argument it uses the current +cursor (`currentAddress`). + +## 2. `GreenEyeControl.java` functions + +All functions exist on the script class and can be called from the script's +own `run()` or from other scripts via the bridge `send()` method. + +| Function | Effect | +|---|---| +| `ping()` | `pong` if the bridge is alive | +| `info()` | session name, pc, symbol count, recording path | +| `resolve(name)` | symbol or `name+0xNN` → runtime address | +| `setBreakpointAt(hexAddr)` / `setBreakpointHere()` | install a hook (prints on hit) | +| `clearBreakpointAt(longAddr)` | remove breakpoints at an address | +| `runUntil(hexAddr)` / `runUntilHere()` | resume and stop at the address; returns `{addr, symbol, kind}` | +| `readMemoryHere(size)` | hex bytes at the cursor | +| `readRegister(name)` / `writeRegister(name, value)` | register access | +| `callFunction(hexAddr, args...)` | Frida-like remote call; returns `a0` | +| `snapshotHere(size)` | record a memory snapshot in the session | +| `listHits()` | breakpoint hit counts from the session | +| `exportHits(path)` | write `greeneye_hits.tsv` for `MarkGreenEyeHits.java` | +| `send(cmd, argsJson)` / `resultOf(response)` | raw bridge access for new workflows | + +Example additions inside `run()`: + +```java +setBreakpointAt("0x800000ce"); +println(runUntil("0x800000ce")); // {addr:..., symbol:"rpc_sum", kind:"breakpoint"} +println(readMemoryHere(16)); +println(callFunction("0x80000048", 0x1111L)); // rpc_ping -> 0xaffe +println(exportHits("/tmp/greeneye_hits.tsv")); +``` + +## 3. Bridge protocol (specification) + +One JSON object per line; one response per request, same `id`. + +Request: `{"id": <int>, "cmd": "<name>", "args": {...}}` +Response: `{"id": <int>, "ok": true, "result": ...}` or +`{"id": <int>, "ok": false, "error": "..."}`. + +| `cmd` | args | result | +|---|---|---| +| `ping` | — | `"pong"` | +| `info` | — | `{name, pc, symbols, recording}` | +| `resolve` | `{name}` | address (int) | +| `bp_set` | `{target, kind?}` | address | +| `bp_clear` | `{addr}` | `true` | +| `bp_list` | — | `[{addr, label, hits, kind}]` | +| `watch_set` | `{target, size?, kind?}` | address | +| `run_until` | `{target, timeout?}` | `{addr, symbol, kind}` | +| `run` | `{duration?, max_stops?}` | stops | +| `step` | `{count?}` | `[pc, ...]` | +| `read_mem` | `{addr, size?}` | hex string | +| `write_mem` | `{addr, hex}` | `true` | +| `read_reg` | `{name}` | int | +| `write_reg` | `{name, value}` | `true` | +| `call` | `{target, args?, timeout?}` | return value | +| `snapshot` | `{addr, size, label?}` | label | +| `note` | `{text}` | `true` | +| `hits` | — | `[{addr, hits, symbol}]` | +| `export_hits` | `{path}` | absolute path | +| `close` | — | `true` (closes the connection and stops the bridge) | + +`target`/`addr` accept `"0x..."`, a symbol name, or `name+0xNN`. + +## 4. Workflows + +### 4.1 Execute until here +1. Start `greeneye ghidra` on the firmware image and load symbols + (`symbols export` from the Ghidra index) so hits are named. +2. Put the cursor on a function in Ghidra and run `GreenEyeControl.java`. +3. `runUntilHere()` sets a temporary breakpoint, services intervening stops + (including your hooks/watchpoints), and reports the symbol resolved at the + stop. If the function is never reached, the bridge returns a timeout error. + +### 4.2 Annotate the project with runtime evidence +1. Run a session (any CLI/scripted path) and `exportHits("/tmp/hits.tsv")`. +2. `MarkGreenEyeHits.java /tmp/hits.tsv` adds plate comments and bookmarks in + the Ghidra project. +3. Optionally export a symbol DB from Ghidra (`ExportForGreenEye.java`) and + feed it back with `symbols export --index`. + +### 4.3 Remote-call driven experiments +Use `callFunction()` to invoke firmware or injected-service functions from +the decompiler (unit-test style), then inspect memory with +`readMemoryHere()` and the recorded session. + +## 5. Address conventions + +Ghidra shows **program addresses** (e.g. libcuda file vaddr + image base). +green-eye resolves **runtime addresses**. For firmware linked at its load +address these coincide; otherwise export symbols with the right bias +(docs/05-GHIDRA-BRIDGE.md) and prefer `resolve(name)` over hardcoded numbers. + +## 6. Limitations (v1) + +* Request/response only: the script pulls state; the bridge does not push + asynchronous hit events. Poll `hits`/`bp_list` between commands. +* One command at a time per connection; multiple clients are accepted but are + not synchronised with each other. +* `watch_set` exists on the bridge but has no dedicated Java wrapper yet — + call `send("watch_set", ...)` directly. +* The bridge is unauthenticated **by design**: localhost only, never expose + it on a network interface. + +## 7. Troubleshooting + +| Symptom | Cause / fix | +|---|---| +| `Connection refused` | bridge not started, wrong port, or it exited (`close`) | +| `green-eye bridge closed the connection` | the server was stopped or the target session closed | +| timeout on `run_until` | address not executed; check symbol bias and that the target is not wedged | +| empty `hits` | the session was started without recording; use the CLI (records by default) | +| Java compile error on script load | Ghidra API drift; the script targets Ghidra 11/12 `GhidraScript` | + +## 8. Roadmap + +* Push notifications for breakpoint hits (server → Ghidra). +* Decompiler annotations from live watchpoints (value changes per field). +* A bridge client that lets multiple scripts share a session with a command + queue. +* Direct MCP exposure of the same command set (roadmap R3). |
