summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/docs/13-GHIDRA-CONTROL.md
blob: 368154ff3336ccb23d204014ef9d601d0d8f06de (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# 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 <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 `./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).