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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
|
# 11 — GSP RM firmware service developer guide
> Status: implemented, documented and tested (audit: docs/27-TOOL-AUDIT.md; value demos: docs/25-VALUE-VALIDATION.md).
> See [06-ACCURACY](06-ACCURACY.md) for the fidelity caveats.
## 1. What this guide is for
The GSP runs a resource manager (RM) as **services**: message handlers, state
machines and engines that own objects (devices, address spaces, channels,
memory, notifiers) and answer controls. Open-IOV emulates those services; the
long-term goal of risc-v-gsp-rm-debugger is to let a developer **write, inject, run and
debug their own versions of these services** before (or instead of) changing
the product emulation.
This guide teaches the loop on the demo uproc — which implements the same
shape as a GSP service — and maps each step to the future GSP-RM work in
[`RISC-V-GSP-RM-DEBUGGER-ROADMAP.md`](../RISC-V-GSP-RM-DEBUGGER-ROADMAP.md) H1.3.
## 2. Service anatomy
| Concept | Demo uproc (today) | GSP-RM (roadmap H1.3) |
|---|---|---|
| Entry point | exported symbol `ge_demo_service_dispatch` | service entry registered with the RM core |
| Dispatch ABI | `u64 dispatch(u64 cmd, u64 a0, u64 a1)` | same shape via the service registry shim |
| Reachability | firmware mailbox commands 16..23 select handler slot 0..7; `arg0` is the service command, `arg1`/`arg2` are parameters | RM message/control dispatch routed by the simulator's RM-core |
| State | service-local (stack/registers) or the control block | RM objects and service-private memory |
| Talking to peers | control-block fields + mailbox; debugger calls | RM-core calls and inter-service messages |
| Observability | hooks/watchpoints/sessions/TCG trace | same, plus RM-core call interception |
A service must be **position-independent**: no globals, no libc, no absolute
addresses. Everything it calls is either its own code or an address passed in
by the injector (the future RM-core shim will pass a table of callable
entry points, which is also the interception point).
## 3. Writing a service
1. Copy the SDK shape from `sdk/`:
* `greeneye_service.h` — the ABI header;
* `injected_demo.c` — a working multi-command service.
2. Implement `ge_service_fn`:
```c
__attribute__((noinline)) ge_u64 my_service_dispatch(ge_u64 cmd, ge_u64 a0, ge_u64 a1)
{
switch (cmd) {
case 1: return a0 ^ 0xbeefu;
case 2: return a0 + a1;
default: return ~cmd;
}
}
```
3. Keep it PIC-clean: no `static` data with pointers, no string globals, no
division (the demo build has no libc), no stack beyond a few KiB.
4. Build:
```bash
make -C sdk # or point the Makefile at your source
make -C sdk relocs # must print "no relocations in this file"
```
5. Fix any relocation the linker reports before injecting; relocations mean
the blob will not run at an arbitrary load address.
## 4. Injecting and invoking
```bash
./risc-v-gsp-rm-debugger.sh inject --service sdk/build/injected_demo.elf \
--cmd 1 --arg0 0x41 --arg1 0x42
```
What happens (all recorded):
1. Every `PT_LOAD` is written into RAM (default `0x81000000`; auto-sized to
fit the target).
2. The service dispatch entry is patched into handler slot 6
(`--slot` selects 0..7).
3. A **direct debugger-side call** runs the service immediately
(`greeneye.inject.invoke_remote`): this is the unit-test path, independent
of firmware state.
4. After the firmware settles, a **firmware-dispatched call** writes
`arg0=cmd, arg1=a0, arg2=a1, mailbox=16+slot` and reads `result`
(`greeneye.inject.invoke_via_firmware`): this is the integration path.
Programmatic form:
```python
from greeneye.inject import load_service, invoke_remote, invoke_via_firmware
svc = load_service(s, "sdk/build/injected_demo.elf", slot=6)
print(hex(invoke_remote(s, svc, 2, 0x10, 0x20))) # 0x30
s.run(duration=0.3)
print(hex(invoke_via_firmware(s, svc, 2, 0x10, 0x20))) # 0x30
```
## 5. Talking to other services and the host
On the demo:
* the **control block** is the shared state (`gsp_ctrl`); services coordinate
through its fields and the mailbox command range;
* the **host/debugger** is a peer via hooks, watchpoints and `Session.call`;
* a service can call another service if it is handed the other entry address
(the SDK's second export `ge_demo_service_answer` demonstrates a second
callable point; the injector resolves any exported symbol).
On real GSP-RM (H1.3) the same roles are filled by RM-core allocation and
control calls and by messages between services. The simulator will intercept
those calls so they can be logged, stubbed, or forwarded to the real
implementation — which is exactly what makes the environment powerful for
prototyping: a service you write can be exercised against recorded RM-core
behaviour before the product emulation implements it.
## 6. Resource surfaces an engine can use
Category map (details are RM-version-specific):
| Surface | Purpose | How risc-v-gsp-rm-debugger helps today |
|---|---|---|
| Memory / descriptors | allocate, map, free, register host or vidmem ranges | watchpoints on allocation state; snapshot/diff; schema decode |
| Address spaces / page tables | GPU VA translation, mapping updates | hooks on map/unmap routines; memory diffs; TCG trace of accesses |
| Channels / GPFIFO / USERD | submission plumbing | hooks and remote calls; observe ring state via snapshots |
| Sync: semaphores, notifiers, interrupts | completion and signalling | watchpoints on sem values; event ordering in sessions |
| Engine scheduling / runlists | prioritisation, preemption | call-graph and hook evidence; compare runs |
| Controls / messages | configuration and queries | declarative probes; replay/compare across versions |
| Device identity / caps / clocks | what the RM reports | schema decode of the static-config block; A/B vs hardware oracle |
Everything here is observed through the same primitives, so an engine
prototype is analysed exactly like firmware code.
## 7. Debugging workflow
1. **Unit-test with `call`:** breakpoints and hooks around the service entry,
`Session.call(entry, (cmd, a0, a1))`, inspect the return and watchpoints on
its data. Failures raise `RspTimeout` and leave the session recording.
2. **Integration run:** `invoke_via_firmware` with the mailbox path, while
hooks on the firmware's dispatcher and watchpoints on the control block
record the full route.
3. **Ghidra-driven:** `./risc-v-gsp-rm-debugger.sh ghidra` + `GreenEyeControl.java` — execute
until a service address, set breakpoints from the decompiler, read
registers/memory, export hits (docs/13).
4. **Coverage:** run with the TCG plugin filtered to the service load range
(`qemu/greeneye_trace.c`, `start=`/`end=` args) to see every access.
5. **Regression:** record a session per command; `./risc-v-gsp-rm-debugger.sh compare` detects
changed hit/watch/write profiles when the service or firmware changes.
## 8. Rules and pitfalls
* **PIC purity first.** If the blob needs relocation, it is not injectable
at an arbitrary address.
* **No absolute addresses from the host.** Use exported symbols and
`load_service`, never hardcoded addresses.
* **No unbounded loops.** A service that never returns blocks the call loop
until the timeout and then leaves the target stopped; the recorder shows
the last PC.
* **Watchpoint the state, hook the entry.** Watchpoints are immediate (one
instruction step) and ideal for counters; hooks give arguments/returns.
* **Never ship a service.** Injected code is a development experiment; the
product changes go through `vgsp` with the accuracy loop.
* **Record everything.** A claim about a service without a session is not
evidence.
## 9. Worked example: add a command
```c
/* sdk/injected_demo.c */
case 7:
return (a0 | (a1 << 8)) & 0xFFFFFFFFull;
```
```bash
make -C sdk
./risc-v-gsp-rm-debugger.sh inject --cmd 7 --arg0 0xab --arg1 0xcd
# direct call -> 0xcdab
# firmware call -> 0xcdab
./risc-v-gsp-rm-debugger.sh analyze <printed session dir> # event/hit report
```
## 10. What comes next (roadmap)
* H1.3: services built against an RM shim, registered with a simulated RM
core, calling intercepted RM-core surfaces.
* H2: the device model that lets real GSP-RM run in the simulator at all.
* H4: adversarial navigation — diff service behaviour across firmware
versions, mutate inputs, infer capabilities.
* H5: services that interact with PMU/SEC2/ACR models.
|