summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/docs/04-PLUGINS.md
blob: b0e6767c3730b5979db5112b3f035aac0489efc0 (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
# 04 — writing risc-v-gsp-rm-debugger plugins

> **# TOOL IN DEVELOPMENT - NOT READY YET!** See 00 for status.

A plugin is a Python module exposing `register(ge)`. It is loaded before the
session runs (CLI `--plugin PATH`, `GREENEYE_PLUGINS`, or the bundled
`plugins/` directory) and can add instrumentation, CLI commands and harness
logic without touching risc-v-gsp-rm-debugger itself.

## 1. The API object

```python
def register(ge):
    # live objects
    ge.session          # Session
    ge.memory           # Memory
    ge.symbols          # SymbolDB
    ge.recorder         # Recorder | NullRecorder

    # instrumentation
    ge.hook(target, on_enter=None, on_leave=None, kind="sw")
    ge.watch(target, size=8, kind="w", callback=None)
    ge.on_event(kind, callback)        # kind in {"*", "bp_hit", "watch_hit", ...}
    ge.resolve(target)                 # symbol/addr -> runtime address

    # CLI + logging
    ge.add_command("name", fn, help="…")   # fn(session, args: list[str]) -> int
    ge.log("message", any="field")         # recorded as a "plugin" event
    ge.commands                            # dict of registered commands
```

Everything a plugin records lands in the session's `events.jsonl`, so plugin
findings are replayable and comparable like any other event.

## 2. Lifecycle and safety

* `register()` runs once, immediately after the session is attached and
  symbols are loaded; the target is still stopped, so hooks are in place
  before the first instruction.
* A plugin that raises is recorded as `plugin_error` and skipped; it never
  aborts the session or the other plugins.
* Session close detaches all hooks/watchpoints, including plugin-installed
  ones.
* Plugins must be stdlib-only unless the dependency is documented; they are
  development tooling and never shipped.

## 3. The four plugin classes

**(a) Runtime RE tools** — add analysis that risc-v-gsp-rm-debugger does not have:

```python
def register(ge):
    def on_state(hit):
        ge.recorder.event("state_machine", result=hit.retval, pc=hit.pc)
    ge.hook("rpc_state", on_leave=on_state)
```

Useful additions: allocation graphs, ring-buffer watermarks, page-table walks,
coverage collectors built from `on_event("stop")` plus the TCG trace, entropy
or checksum probes over structures.

**(b) Harnesses** — drive the firmware and assert:

```python
def register(ge):
    def check(session, args):
        ctrl = session.symbols.lookup("gsp_ctrl")
        before = session.memory.read_u64(ctrl + 0x10)
        session.memory.write_u64(ctrl + 0x20, 1)      # PING
        session.run(duration=0.3)
        after = session.memory.read_u64(ctrl + 0x10)
        assert after == before + 1, "PING did not dispatch"
        print("PING ok")
        return 0
    ge.add_command("check-ping", check)
```

**(c) Virtualization-environment updates** — alter how the target is built or
driven: extra QEMU args, a rehosting shim for uproc sources, a device-model
stub, a replay layer that feeds recorded events back into a run. Keep such
changes behind plugin-level flags and document them in the session metadata
(`s.recorder.meta`).

**(d) AI/harness integration** — export sessions/reports to external tools,
generate probe specs, score runs. The **MCP server is implemented as a bundled
plugin** (`../plugins/mcp_server.py`, see [20-MCP-SERVER](20-MCP-SERVER.md)):
stdlib-only, read-only by default, no dependency added to the core package.

## 4. Example plugin

`plugins/example_plugin.py` demonstrates (a), (b) and logging:

```python
def register(ge):
    ge.log("example plugin loaded")
    def on_state(hit):
        ge.recorder.event("plugin_state_transition", result=hit.retval)
    if ge.symbols.get("rpc_state"):
        ge.hook("rpc_state", on_leave=on_state)
    ge.add_command("gehits", lambda session, args: print("…and 0"), help="…")
```

Run it with:

```bash
./risc-v-gsp-rm-debugger.sh run examples/scripts/trace_rpc.py --plugin plugins/example_plugin.py
```

## 5. Testing a plugin

1. Load it against the demo firmware (`./risc-v-gsp-rm-debugger.sh demo --plugin …`) — the
   session must remain green and the plugin's events must appear in
   `events.jsonl`.
2. Unit-test pure logic outside risc-v-gsp-rm-debugger; keep hooks small.
3. Never let a plugin assume a particular firmware: gate on
   `ge.symbols.get(name)` and log a skip instead of failing.

## 6. Distribution

Plugins can live anywhere. For in-tree plugins that document the workflow,
put them under `plugins/` in this tool directory; for local or
target-specific tooling, use `GREENEYE_PLUGINS` and keep paths out of tracked
docs (project rule).