diff options
| author | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-02 10:58:21 -0400 |
|---|---|---|
| committer | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-02 10:58:21 -0400 |
| commit | 002e3d7d13c414ea874abbee8d82803dd3a72fe5 (patch) | |
| tree | 6627c12fa07f6a1cf73e5aaeb7390e698dd3a896 /debug/risc-v-gsp-rm-vm/greeneye/cli.py | |
| parent | 2f249817df327f5dcccf0464ad517fa60d6071d3 (diff) | |
| download | open-iov-002e3d7d13c414ea874abbee8d82803dd3a72fe5.tar.gz open-iov-002e3d7d13c414ea874abbee8d82803dd3a72fe5.zip | |
debug: green-eye wave 3 - RM harness, local simulator, device map, MCP plugin, value-validation demos, CI pipeline, toolchain docs; add host RM tools rmtool/rmtop
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/greeneye/cli.py')
| -rw-r--r-- | debug/risc-v-gsp-rm-vm/greeneye/cli.py | 126 |
1 files changed, 117 insertions, 9 deletions
diff --git a/debug/risc-v-gsp-rm-vm/greeneye/cli.py b/debug/risc-v-gsp-rm-vm/greeneye/cli.py index 94102a4..e7023ec 100644 --- a/debug/risc-v-gsp-rm-vm/greeneye/cli.py +++ b/debug/risc-v-gsp-rm-vm/greeneye/cli.py @@ -1,13 +1,17 @@ """green-eye command line interface. Commands: - info environment/tooling check + info environment/tooling check (incl. native simulator availability) demo boot the bundled uproc demo and run a canned instrumentation pass run run a Python instrumentation script against a target - attach interactive REPL against a QEMU gdbstub + attach interactive REPL over a gdbstub or the native simulator + launch start a target and keep it alive for a later attach probe run an arbitrary firmware (default: the real GSP fwimage) for N instructions and report the first divergence record boot a firmware and record a memory-activity session + (native: --trace collects an exec/access trace blob) + inject load and invoke an injected RV64 service (Frida-like) + ghidra serve a live session to GhidraScripts over the JSON-lines bridge analyze render a recorded session to a symbol-annotated report compare A/B two recorded sessions at the symbolic level symbols build/inspect a green-eye symbol database from Ghidra exports @@ -82,14 +86,20 @@ def _start_session( record, name: str, plugins: list[str] | None = None, + backend: str = "qemu", + trace=None, ): if port: + if backend != "qemu": + raise GreenEyeError("--backend native cannot attach to an existing gdbstub port") + if trace is not None: + raise GreenEyeError("--trace is native-only; use the qemu/greeneye-trace.so plugin for QEMU") db = SymbolDB() if kernel: db.load_elf(kernel) session = ge.attach(port=port, record=record, symbols=db, name=name) else: - session = ge.launch(kernel=kernel, record=record, name=name) + session = ge.launch(kernel=kernel, record=record, name=name, backend=backend, trace=trace) manager = PluginManager(session) manager.load_all(plugins) return session, manager @@ -101,6 +111,63 @@ def _print_session_dir(session) -> None: print(f"[green-eye] session: {directory}") +def _add_backend_arg(parser) -> None: + """Select the execution backend; qemu stays the default reference path.""" + parser.add_argument( + "--backend", + choices=("qemu", "native"), + default="qemu", + help="execution backend: qemu (default) or the native RV64IMAC simulator", + ) + + +def _add_trace_args(parser) -> None: + """Native exec/access tracing (R4), mirroring qemu/greeneye_trace.c.""" + parser.add_argument( + "--trace", + action="store_true", + help="native backend: record an exec/access trace into the session", + ) + parser.add_argument( + "--trace-exec", + choices=("on", "off"), + default="on", + help="trace instruction exec events (default on; 'off' records accesses only)", + ) + parser.add_argument( + "--trace-start", + type=lambda v: int(v, 0), + default=None, + help="only trace PCs >= this address (inclusive)", + ) + parser.add_argument( + "--trace-end", + type=lambda v: int(v, 0), + default=None, + help="only trace PCs <= this address (inclusive)", + ) + parser.add_argument( + "--trace-max", + type=int, + default=None, + help="cap buffered trace events (0 = unlimited)", + ) + + +def _trace_spec(args): + """Build a launch_native trace spec from parsed args (None when disabled).""" + if not getattr(args, "trace", False): + return None + spec = {"exec": args.trace_exec == "on"} + if args.trace_start is not None: + spec["start"] = args.trace_start + if args.trace_end is not None: + spec["end"] = args.trace_end + if args.trace_max is not None: + spec["max_events"] = args.trace_max + return spec + + # --------------------------------------------------------------------------- # commands # --------------------------------------------------------------------------- @@ -121,6 +188,13 @@ def cmd_info(args) -> int: version = subprocess.run([resolved, "--version"], capture_output=True, text=True).stdout.strip() print(f"qemu version: {version}") + try: + from .sim_backend import SIM_IPS # noqa: F401 + + print("native simulator: available (deterministic RV64IMAC, pure-Python reference)") + print("native trace: available (--trace exec/access, --trace-start/--trace-end filters)") + except Exception as exc: # pragma: no cover - only on a broken install + print(f"native simulator: unavailable ({exc})") for label, path in [ ("demo firmware", DEMO_ELF), ("demo schema", DEMO_SCHEMA), @@ -142,7 +216,10 @@ def cmd_demo(args) -> int: raise GreenEyeError(f"demo firmware not built: {elf}") schema = _load_schema(DEMO_SCHEMA) record = True if args.record is None else args.record - session, _manager = _start_session(kernel=str(elf), port=None, record=record, name="demo") + session, _manager = _start_session( + kernel=str(elf), port=None, record=record, name="demo", backend=args.backend, + trace=_trace_spec(args), + ) hits: list[str] = [] try: ctrl = session.symbols.lookup("gsp_ctrl") @@ -204,7 +281,13 @@ def cmd_run(args) -> int: if not kernel and not args.port: kernel = str(_build_demo()) session, manager = _start_session( - kernel=kernel, port=args.port, record=args.record or True, name=script.stem, plugins=args.plugin + kernel=kernel, + port=args.port, + record=args.record or True, + name=script.stem, + plugins=args.plugin, + backend=args.backend, + trace=_trace_spec(args), ) namespace = { "ge": ge, @@ -214,6 +297,8 @@ def cmd_run(args) -> int: "symbols": session.symbols, "recorder": session.recorder, "greeneye": ge, + "__file__": str(script), + "__name__": "__main__", } try: source = script.read_text(encoding="utf-8") @@ -411,7 +496,13 @@ def cmd_attach(args) -> int: elif not args.port: kernel = str(_build_demo()) session, manager = _start_session( - kernel=kernel, port=args.port, record=args.record, name="repl", plugins=args.plugin + kernel=kernel, + port=args.port, + record=args.record, + name="repl", + plugins=args.plugin, + backend=args.backend, + trace=_trace_spec(args), ) try: GreenEyeRepl(session, manager).cmdloop() @@ -443,7 +534,10 @@ def cmd_launch(args) -> int: def cmd_record(args) -> int: _ensure_dumps_env() elf = args.kernel or str(_build_demo()) - session, _manager = _start_session(kernel=str(elf), port=None, record=args.record or True, name=args.name) + session, _manager = _start_session( + kernel=str(elf), port=None, record=args.record or True, name=args.name, + backend=args.backend, trace=_trace_spec(args), + ) try: for spec in args.watch or []: addr_or_sym, size = (spec.split(":", 1) + ["8"])[:2] @@ -550,7 +644,8 @@ def cmd_inject(args) -> int: ) 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 + kernel=str(kernel), port=args.port, record=args.record or True, name="inject", + plugins=args.plugin, backend=args.backend, trace=_trace_spec(args), ) try: service = load_service(session, str(service_path), load_addr=args.load_addr, slot=args.slot) @@ -578,7 +673,8 @@ def cmd_ghidra(args) -> int: 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 + kernel=kernel, port=args.target_port, record=args.record or True, name="ghidra", + plugins=args.plugin, backend=args.backend, trace=_trace_spec(args), ) bridge = GhidraBridge(session, port=args.bridge_port) port = bridge.start() @@ -669,6 +765,8 @@ def build_parser() -> argparse.ArgumentParser: demo.add_argument("--no-build", action="store_true", help="do not rebuild the demo") demo.add_argument("--record", nargs="?", const=True, default=None, help="record to a path (or default dumps/)") demo.add_argument("--duration", type=float, default=1.0) + _add_backend_arg(demo) + _add_trace_args(demo) demo.set_defaults(fn=cmd_demo) run = sub.add_parser("run", help="run a Python instrumentation script") @@ -677,6 +775,8 @@ def build_parser() -> argparse.ArgumentParser: run.add_argument("--port", type=int) run.add_argument("--record", nargs="?", const=True, default=None) run.add_argument("--plugin", action="append", default=[]) + _add_backend_arg(run) + _add_trace_args(run) run.set_defaults(fn=cmd_run) attach = sub.add_parser("attach", help="interactive REPL over a gdbstub (launches if no --port)") @@ -684,6 +784,8 @@ def build_parser() -> argparse.ArgumentParser: attach.add_argument("--kernel", help="firmware ELF (default: built demo)") attach.add_argument("--record", nargs="?", const=True, default=None) attach.add_argument("--plugin", action="append", default=[]) + _add_backend_arg(attach) + _add_trace_args(attach) attach.set_defaults(fn=cmd_attach) launch = sub.add_parser("launch", help="start a target and keep it alive for attach") @@ -713,6 +815,8 @@ def build_parser() -> argparse.ArgumentParser: 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=[]) + _add_backend_arg(inject) + _add_trace_args(inject) inject.set_defaults(fn=cmd_inject) record = sub.add_parser("record", help="boot and record a memory-activity session") @@ -722,6 +826,8 @@ def build_parser() -> argparse.ArgumentParser: record.add_argument("--watch", action="append", help="addr|symbol[:size]") record.add_argument("--snapshot", action="append", help="addr|symbol:len") record.add_argument("--record", nargs="?", const=True, default=None) + _add_backend_arg(record) + _add_trace_args(record) record.set_defaults(fn=cmd_record) ghidra = sub.add_parser("ghidra", help="serve a live session to GhidraScripts (JSON-lines bridge)") @@ -730,6 +836,8 @@ def build_parser() -> argparse.ArgumentParser: 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=[]) + _add_backend_arg(ghidra) + _add_trace_args(ghidra) ghidra.set_defaults(fn=cmd_ghidra) analyze = sub.add_parser("analyze", help="render a recorded session report") |
