# 13 — Ghidra control bridge > Status: implemented, documented and tested (audit: docs/27-TOOL-AUDIT.md; value demos: docs/25-VALUE-VALIDATION.md). The bridge lets a GhidraScript drive a live risc-v-gsp-rm-debugger 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) ./risc-v-gsp-rm-debugger.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 -process -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": , "cmd": "", "args": {...}}` Response: `{"id": , "ok": true, "result": ...}` or `{"id": , "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 `./risc-v-gsp-rm-debugger.sh 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). risc-v-gsp-rm-debugger 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`) | | `risc-v-gsp-rm-debugger 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).