summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/greeneye/inject.py
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-02 09:13:00 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-02 09:13:00 -0400
commit1e701396b1b6d204dfd8e5a8ddcd3007038029ac (patch)
tree8dde895cf9d3e66ce912d01cb1a72ff674dc850f /debug/risc-v-gsp-rm-vm/greeneye/inject.py
parentab48e44832db55fdedadcf44f55fa9904ca36f2f (diff)
downloadopen-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.py129
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)