summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/greeneye/cli.py
diff options
context:
space:
mode:
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/greeneye/cli.py')
-rw-r--r--debug/risc-v-gsp-rm-vm/greeneye/cli.py126
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")