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 | 1e701396b1b6d204dfd8e5a8ddcd3007038029ac (patch) | |
| tree | 8dde895cf9d3e66ce912d01cb1a72ff674dc850f /debug/risc-v-gsp-rm-vm/greeneye/inject.py | |
| parent | ab48e44832db55fdedadcf44f55fa9904ca36f2f (diff) | |
| download | open-iov-1e701396b1b6d204dfd8e5a8ddcd3007038029ac.tar.gz open-iov-1e701396b1b6d204dfd8e5a8ddcd3007038029ac.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/greeneye/inject.py')
| -rw-r--r-- | debug/risc-v-gsp-rm-vm/greeneye/inject.py | 129 |
1 files changed, 129 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/greeneye/inject.py b/debug/risc-v-gsp-rm-vm/greeneye/inject.py new file mode 100644 index 0000000..f9605fc --- /dev/null +++ b/debug/risc-v-gsp-rm-vm/greeneye/inject.py @@ -0,0 +1,129 @@ +"""Frida-like service injection for green-eye. + +Loads a position-independent RV64 service blob into a live target, registers +its dispatch entry with the firmware (on the demo uproc: a handler-table slot +that commands 16..23 dispatch to), and invokes it either directly through +``Session.call`` (a debugger-side remote function call) or through the +firmware's own mailbox dispatcher. + +On a future GSP-RM simulator the same shape becomes "inject an RM engine and +register it with the simulated RM core"; see ``docs/12-INJECTION-SDK.md`` and +``GREEN-EYE-ROADMAP.md``. +""" +from __future__ import annotations + +from dataclasses import dataclass + +from .elf import load_elf +from .errors import GreenEyeError + +#: Symbol the injector calls in a service blob. +ENTRY_SYMBOL = "ge_demo_service_dispatch" + +#: Demo uproc control-block field offsets (see firmware/uproc_demo). +CTRL_HANDLERS = 0x150 +CTRL_MAILBOX = 0x20 +CTRL_ARG0 = 0x28 +CTRL_ARG1 = 0x30 +CTRL_ARG2 = 0x38 +CTRL_RESULT = 0x40 +INJECT_CMD_BASE = 16 + + +@dataclass +class InjectedService: + path: str + name: str + load_addr: int + entry_addr: int + dispatch_offset: int + slot: int + size: int + + def summary(self) -> str: + return ( + f"{self.name}: {self.size} bytes at {self.load_addr:#x}, " + f"entry {self.entry_addr:#x} (offset {self.dispatch_offset:#x}), slot {self.slot}" + ) + + +def load_service( + session, + service_elf: str, + *, + load_addr: int | None = None, + slot: int = 6, + entry_symbol: str = ENTRY_SYMBOL, +) -> InjectedService: + """Load a service blob and register it with the demo firmware.""" + image = load_elf(service_elf) + entry = None + for symbol in image.symbols: + if symbol.name == entry_symbol: + entry = symbol + break + if entry is None: + available = ", ".join(sorted(s.name for s in image.symbols if s.is_function)) or "<none>" + raise GreenEyeError(f"service {service_elf}: symbol {entry_symbol!r} not found (functions: {available})") + if not 0 <= slot <= 7: + raise GreenEyeError(f"slot must be 0..7 (got {slot})") + if load_addr is None: + base = getattr(session.target, "ram_base", 0x80000000) if session.target else 0x80000000 + size = getattr(session.target, "memory_mb", 256) * 1024 * 1024 if session.target else 256 * 1024 * 1024 + load_addr = base + 0x01000000 + if load_addr + image.max_vaddr + 0x1000 >= base + size: + load_addr = base + max(0x1000, size - image.max_vaddr - 0x2000) + if getattr(session.target, "memory_mb", 0): + ram_end = getattr(session.target, "ram_base", 0x80000000) + session.target.memory_mb * 1024 * 1024 + if load_addr + image.max_vaddr > ram_end: + raise GreenEyeError( + f"service does not fit: load {load_addr:#x} + {image.max_vaddr:#x} exceeds RAM end {ram_end:#x}" + ) + + total = 0 + for segment in image.load_segments: + data = image.segment_bytes(segment) + if data: + session.memory.write(load_addr + segment.p_vaddr, data) + total += len(data) + entry_addr = load_addr + entry.value + + ctrl = session.symbols.get("gsp_ctrl") + if ctrl is not None: + session.memory.write_u64(ctrl.addr + CTRL_HANDLERS + slot * 8, entry_addr) + + service = InjectedService( + path=service_elf, + name=service_elf.rsplit("/", 1)[-1], + load_addr=load_addr, + entry_addr=entry_addr, + dispatch_offset=entry.value, + slot=slot, + size=total, + ) + session.recorder.event( + "service_injected", + path=service.path, + name=service.name, + load_addr=load_addr, + entry_addr=entry_addr, + slot=slot, + size=total, + ) + return service + + +def invoke_remote(session, service: InjectedService, cmd: int, a0: int = 0, a1: int = 0, timeout: float = 3.0) -> int: + """Call the service directly from the debugger (Frida NativeFunction).""" + return session.call(service.entry_addr, (cmd, a0, a1), timeout=timeout) + + +def invoke_via_firmware(session, service: InjectedService, cmd: int, a0: int = 0, a1: int = 0, settle: float = 0.25) -> int: + """Invoke the service through the firmware's mailbox dispatcher.""" + ctrl = session.symbols.lookup("gsp_ctrl") + session.memory.write_u64(ctrl + CTRL_ARG0, cmd) + session.memory.write_u64(ctrl + CTRL_ARG1, a0) + session.memory.write_u64(ctrl + CTRL_ARG2, a1) + session.memory.write_u64(ctrl + CTRL_MAILBOX, INJECT_CMD_BASE + service.slot) + session.run(duration=settle) + return session.memory.read_u64(ctrl + CTRL_RESULT) |
