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 | 3fe5360d0efc439d59f2b7a9bf2f713aa9cdb918 (patch) | |
| tree | e30e26701f5a67080404e5f9cb71967ee275e604 /debug/risc-v-gsp-rm-vm/greeneye/cli.py | |
| parent | 964f34ee2e49d14ce81ee9a6bd9297c88e320c52 (diff) | |
| download | open-iov-3fe5360d0efc439d59f2b7a9bf2f713aa9cdb918.tar.gz open-iov-3fe5360d0efc439d59f2b7a9bf2f713aa9cdb918.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/cli.py')
| -rw-r--r-- | debug/risc-v-gsp-rm-vm/greeneye/cli.py | 96 |
1 files changed, 96 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/greeneye/cli.py b/debug/risc-v-gsp-rm-vm/greeneye/cli.py index 5bfcee2..94102a4 100644 --- a/debug/risc-v-gsp-rm-vm/greeneye/cli.py +++ b/debug/risc-v-gsp-rm-vm/greeneye/cli.py @@ -332,6 +332,15 @@ class GreenEyeRepl(cmd.Cmd): stops = self.session.run(duration=seconds) self._say(f"resumed for {seconds}s, serviced {stops} stops") + def do_call(self, arg): + """call <addr|symbol> [a0 [a1 ...]] -- remote function call; prints the result.""" + parts = arg.split() + if not parts: + return self._say("usage: call <addr|symbol> [args...]") + values = [int(part, 0) for part in parts[1:]] + retval = self.session.call(parts[0], values) + self._say(f" -> {retval:#x}") + def do_events(self, arg): """events [n] -- show the last n recorded events (default 20).""" from .record import load_events @@ -512,6 +521,12 @@ def cmd_probe(args) -> int: print(f"[probe] total exceptions: {len(faults)}") else: print("[probe] no exception recorded (execution stayed in mapped memory)") + recorder = session.recorder + if hasattr(recorder, "meta"): + recorder.meta["qemu_fault_count"] = len(faults) + recorder.meta["qemu_first_fault"] = faults[0] if faults else None + if qlog: + recorder.meta["qemu_log_blob"] = recorder.blob("qemu.log", qlog.encode("utf-8")) tail = qlog.strip().splitlines() if tail: print("[probe] qemu log tail:") @@ -523,6 +538,64 @@ def cmd_probe(args) -> int: return 0 +def cmd_inject(args) -> int: + _ensure_dumps_env() + from .inject import invoke_remote, invoke_via_firmware, load_service + from .target import free_port # noqa: F401 (kept for symmetry with launch) + + service_path = Path(args.service) + if not service_path.exists(): + raise GreenEyeError( + f"service not found: {service_path} (build it with `make sdk`)" + ) + kernel = args.kernel or str(_build_demo()) + session, _manager = _start_session( + kernel=str(kernel), port=args.port, record=args.record or True, name="inject", plugins=args.plugin + ) + try: + service = load_service(session, str(service_path), load_addr=args.load_addr, slot=args.slot) + print(f"[inject] {service.summary()}") + remote = invoke_remote(session, service, args.cmd, args.arg0, args.arg1) + print(f"[inject] direct call dispatch(cmd={args.cmd}, a0={args.arg0:#x}, a1={args.arg1:#x}) -> {remote:#x}") + if not args.no_firmware: + session.run(duration=args.duration) + fw = invoke_via_firmware(session, service, args.cmd, args.arg0, args.arg1) + print(f"[inject] firmware call mailbox={16 + service.slot} -> result {fw:#x}") + _print_session_dir(session) + finally: + session.close() + return 0 + + +def cmd_ghidra(args) -> int: + """Serve a live session to GhidraScripts over the JSON-lines bridge.""" + _ensure_dumps_env() + from .gdbridge import GhidraBridge + + kernel = args.kernel + if kernel: + kernel = str(Path(kernel)) + elif not args.target_port: + kernel = str(_build_demo()) + session, _manager = _start_session( + kernel=kernel, port=args.target_port, record=args.record or True, name="ghidra", plugins=args.plugin + ) + bridge = GhidraBridge(session, port=args.bridge_port) + port = bridge.start() + print(f"[green-eye] ghidra bridge on 127.0.0.1:{port}") + print(f"[green-eye] session: {getattr(session.recorder, 'dir', None)}") + print("[green-eye] in Ghidra: analyzeHeadless ... -postScript GreenEyeControl.java " + str(port)) + print("[green-eye] Ctrl-C to stop the bridge") + try: + bridge.serve_forever() + except KeyboardInterrupt: + pass + finally: + bridge.stop() + session.close() + return 0 + + def cmd_analyze(args) -> int: symbols = None if args.symbols: @@ -627,6 +700,21 @@ def build_parser() -> argparse.ArgumentParser: probe.add_argument("--record", nargs="?", const=True, default=None) probe.set_defaults(fn=cmd_probe) + inject = sub.add_parser("inject", help="load and invoke an injected RV64 service (Frida-like)") + inject.add_argument("--service", default=str(TOOL_ROOT / "sdk" / "build" / "injected_demo.elf")) + inject.add_argument("--kernel") + inject.add_argument("--port", type=int) + inject.add_argument("--load-addr", type=lambda v: int(v, 0)) + inject.add_argument("--slot", type=int, default=6, help="demo handler slot 0..7 (default 6)") + inject.add_argument("--cmd", type=lambda v: int(v, 0), default=1) + inject.add_argument("--arg0", type=lambda v: int(v, 0), default=0x41) + inject.add_argument("--arg1", type=lambda v: int(v, 0), default=0x42) + inject.add_argument("--duration", type=float, default=0.3, help="firmware settle time before the mailbox call") + inject.add_argument("--no-firmware", action="store_true", help="only make the direct debugger-side call") + inject.add_argument("--record", nargs="?", const=True, default=None) + inject.add_argument("--plugin", action="append", default=[]) + inject.set_defaults(fn=cmd_inject) + record = sub.add_parser("record", help="boot and record a memory-activity session") record.add_argument("--kernel") record.add_argument("--name", default="record") @@ -636,6 +724,14 @@ def build_parser() -> argparse.ArgumentParser: record.add_argument("--record", nargs="?", const=True, default=None) record.set_defaults(fn=cmd_record) + ghidra = sub.add_parser("ghidra", help="serve a live session to GhidraScripts (JSON-lines bridge)") + ghidra.add_argument("--kernel") + ghidra.add_argument("--target-port", type=int, help="attach to an existing gdbstub instead of launching") + ghidra.add_argument("--bridge-port", type=int, default=7000) + ghidra.add_argument("--record", nargs="?", const=True, default=None) + ghidra.add_argument("--plugin", action="append", default=[]) + ghidra.set_defaults(fn=cmd_ghidra) + analyze = sub.add_parser("analyze", help="render a recorded session report") analyze.add_argument("session") analyze.add_argument("--symbols") |
