"""green-eye: Frida-like runtime instrumentation for GSP/uproc firmware in a RISC-V QEMU VM. green-eye talks to QEMU's gdbstub directly (no GDB dependency) and layers a scriptable instrumentation API on top: breakpoints, hooks, watchpoints, memory inspection, structure decoding, session recording and post-run analysis. Public API (stable-ish for v0.1): import greeneye as ge s = ge.launch(kernel="firmware/uproc_demo/build/uproc_demo.elf") fn = s.symbols.lookup("rpc_sum") h = s.hook(fn, on_enter=lambda hit: print(hit)) s.run(duration=2.0) s.close() Everything is development tooling; see docs/00-OVERVIEW.md. """ from __future__ import annotations __version__ = "0.1.0" from .errors import ( # noqa: F401 GreenEyeError, RspError, RspTimeout, RspProtocolError, SymbolNotFound, TargetError, StructError, ) from .rsp import RspClient, StopReply, Register # noqa: F401 from .elf import FirmwareImage, Segment, ElfSymbol, load_elf # noqa: F401 from .symbols import Symbol, SymbolDB # noqa: F401 from .memory import Memory # noqa: F401 from .structs import Field, Schema, decode, schema_from_json # noqa: F401 from .record import Recorder, NullRecorder, load_events # noqa: F401 from .session import Session, Hit, HookHandle, Breakpoint, Watchpoint # noqa: F401 from .target import QemuTarget # noqa: F401 __all__ = [ "attach", "launch", "Session", "Hit", "HookHandle", "Breakpoint", "Watchpoint", "QemuTarget", "RspClient", "SymbolDB", "Symbol", "Memory", "Recorder", ] def attach( host: str = "127.0.0.1", port: int = 4444, *, timeout: float = 10.0, record: str | None = None, symbols: SymbolDB | None = None, name: str = "attach", ) -> Session: """Attach to an already-running QEMU gdbstub and return a Session.""" client = RspClient(host, port, timeout=timeout) client.connect() client.sync() return Session(client=client, symbols=symbols, record=record, name=name) def launch( *, kernel: str | None = None, machine: str = "virt", memory_mb: int = 256, gdb_port: int = 0, bios: str = "none", extra_args: list[str] | None = None, timeout: float = 10.0, record: str | None = None, symbols: SymbolDB | None = None, name: str = "session", load_symbols: bool = True, keep_alive: bool = False, backend: str = "qemu", trace=None, ) -> Session: """Launch a target (paused), attach, and return a ready Session. ``backend="qemu"`` (default) starts ``qemu-system-riscv64`` exactly as before. ``backend="native"`` uses the in-process deterministic RV64IMAC simulator (M1) via ``greeneye.sim_backend``; the Session contract is the same. ``trace`` (native only) enables the R4 exec/access trace: ``True``, a dict of ``{"exec", "start", "end", "max_events"}``, or a ``sim.trace.Trace``; it is saved to the session as ``blobs/trace.jsonl``. The target starts paused at its reset vector; use Session.load_image() to load an ELF (which also sets PC), or launch with kernel= for the fast `-kernel` path. """ if backend in ("native", "sim"): from .sim_backend import launch_native return launch_native( kernel=kernel, memory_mb=memory_mb, record=record, symbols=symbols, name=name, load_symbols=load_symbols, keep_alive=keep_alive, trace=trace, ) if trace is not None: from .errors import TargetError raise TargetError( "trace= is native-only; for QEMU build qemu/greeneye-trace.so and " "pass -plugin (see qemu/README.md)" ) if backend != "qemu": from .errors import TargetError raise TargetError(f"unknown backend {backend!r} (use 'qemu' or 'native')") target = QemuTarget( machine=machine, memory_mb=memory_mb, gdb_port=gdb_port, bios=bios, extra_args=extra_args, kernel=kernel, ) if keep_alive: target.keep_alive = True target.start(paused=True) client = RspClient(target.host, target.port, timeout=timeout) client.connect() client.sync() symdb = symbols or SymbolDB() sess = Session(client=client, target=target, symbols=symdb, record=record, name=name) if kernel: image = load_elf(kernel) sess.load_image(image, load_symbols=load_symbols) return sess