# 03 — green-eye scripting > **# TOOL IN DEVELOPMENT - NOT READY YET!** See 00 for status. green-eye scripts are ordinary Python programs run with `./greeneye.sh run ` (or `python3 -m greeneye run …`). The names `ge`, `session`/`s`, `memory`, `symbols`, `recorder` are preloaded, and `__file__` points at the script path (use it to locate bundled assets). ## 1. Attach or launch ```python import greeneye as ge # Launch the bundled demo (QEMU -kernel fast path), recording by default. s = ge.launch(kernel="firmware/uproc_demo/build/uproc_demo.elf", record=True, name="my-run") # Or attach to an already-running gdbstub (e.g. started by `greeneye launch`). # s = ge.attach(port=4444, record="dumps/attach-run") try: ... finally: s.close() # clears breakpoints/watchpoints, kills owned QEMU ``` `launch(..., keep_alive=True)` leaves QEMU running after `close()`. ## 2. Symbols ```python sym = s.symbols.get("rpc_sum") # Symbol | None addr = s.symbols.lookup("rpc_sum") # int, raises if missing s.symbols.annotate(0x80000048) # "0x80000048 rpc_ping" s.symbols.resolve(0x80000050) # Symbol (containing or nearest) s.resolve("rpc_sum") # int | int passthrough | "0x..." prefix ``` Symbols come from ELF (demo), Ghidra `names.tsv`/`decompiled_index.tsv` (`symbols export`), or native JSON. ## 3. Hooks and breakpoints ```python events = [] def on_sum_enter(hit): events.append(("enter", hit.arg(0), hit.arg(1))) # a0, a1 def on_sum_leave(hit): events.append(("leave", hit.retval)) # a0 at return handle = s.hook("rpc_sum", on_enter=on_sum_enter, on_leave=on_sum_leave) handle.hits, handle.label, handle.addr handle.detach() bp = s.breakpoint(0x800000fe, callback=lambda hit: print(hit), temporary=True, kind="sw") s.remove_breakpoint(bp) ``` `Hit` carries `kind`, `addr`, `regs` (full register dict), `symbol`, `retval` (leave hits), `watch`, `stop`, `session`, plus helpers `hit.arg(n)` and `hit.label()`. A hook's `on_leave` is realised as a return breakpoint; the callback's `hit.addr`/`hit.symbol` are the hooked function (not the return site), and `hit.retval` is `a0`. Tail calls and interrupt returns are not tracked. ## 4. Watchpoints ```python def on_write(hit): print(f"{hit.label()} written; pc={hit.pc:#x}") wp = s.watch("gsp_ctrl+0x10", size=8, kind="w", callback=on_write) # w|r|a wp.hits s.unwatch(wp) ``` The session transparently steps over the triggering store (required with QEMU; see 01 §4). ## 5. Memory ```python s.memory.read(0x80000840, 64) s.memory.write_u64(0x80000860, 1) # mailbox = PING s.memory.hexdump(0x80000840, 128) # annotated s.memory.search_string(0x80000000, 0x10000, "GREEN") s.memory.search_u32(0x80000000, 0x10000, 0x47535031) s.memory.read_cstring(0x800007d0) s.memory.snapshot(0x80000840, 392, name="ctrl") ``` `Memory.read`/`write` record `mem_read`/`mem_write` events; snapshots are saved and hashed by the recorder. ## 6. Structures ```python import json from greeneye.structs import schema_from_json, render, decode schema = schema_from_json(json.load(open("firmware/uproc_demo/gsp_ctrl.schema.json"))) values = decode(schema, s.memory.read(ctrl, schema.size), base=ctrl) print(render(schema, values)) ``` A schema is JSON: `{"name":…, "fields":[{"name","offset","fmt","size?","enum?","note?"}]}` with `fmt` in `u8..u64`, `s8..s64`, `f32/f64`, `bool`, `bytes`, `cstr`, `ptr`. ## 7. Snapshots and diffs ```python from greeneye import Memory before = s.snapshot(ctrl, schema.size, "before") s.memory.write_u64(ctrl + 32, 1) s.run(duration=0.4) after = s.snapshot(ctrl, schema.size, "after") print(Memory.render_diff(Memory.diff(before, after, base=ctrl), s.symbols)) ``` ## 8. Running ```python s.run(duration=1.0) # service events for one second s.run(max_stops=20) # or until 20 stops hits = s.step(10) # single-step and dispatch hooks s.stop() # interrupt and dispatch ``` `run` returns the number of stops serviced. Every stop is recorded. ## 9. Declarative probes For simple instrumentation, ship a JSON spec instead of Python: ```json { "probes": [ {"name": "sum", "symbol": "rpc_sum", "when": "a1 > 0", "on_enter": {"log": ["a0", "a1"], "dump": [{"reg": "a0", "len": 32, "label": "sum-input"}], "note": "sum called"}, "on_leave": {"log": ["retval"]}} ] } ``` ```python from greeneye.probes import load_spec, install install(s, load_spec("examples/probes/demo_probes.json")) s.run(duration=1.0) ``` Conditions accept register names (`a0..a7`, `pc`, `ra`, `sp`, `x0..x31`, `retval`), integer/hex literals and `and/or/not` with comparisons; arbitrary code is rejected. Actions: `log`, `dump` (save a blob), `snapshot`, `note`. ## 10. Event stream and plugins ```python s.on_event("watch_hit", lambda hit: print("wrote", hit.label())) s.note("starting phase 2") # In a plugin module: def register(ge): ge.add_command("mycmd", lambda session, args: 0) ``` See [04-PLUGINS](04-PLUGINS.md). ## 11. Recording and post-analysis from Python ```python s.recorder.dir # session directory (or None) s.recorder.event("phase", name="p2") s.recorder.close() # writes session.json + summary.md from greeneye.analyze import render_summary, compare_sessions print(render_summary("dumps/run-a", symbols=s.symbols)) print(compare_sessions("dumps/run-a", "dumps/run-b", symbols=s.symbols)) ``` ## 12. A complete worked script ```python """Drive the demo, capture a PING and a FAULT, and summarise.""" import greeneye as ge s = ge.launch(kernel="firmware/uproc_demo/build/uproc_demo.elf", record=True, name="worked") ctrl = s.symbols.lookup("gsp_ctrl") hits = [] try: for name in ("rpc_ping", "rpc_sum", "rpc_fault"): if s.symbols.get(name): s.hook(name, on_enter=lambda h: hits.append(h.label())) s.watch(ctrl + 0x10, size=8, kind="w") s.run(duration=0.5) # self-test s.memory.write_u64(ctrl + 0x20, 1) # PING s.memory.write_u64(ctrl + 0x28, 0x1234) s.run(duration=0.5) s.memory.write_u64(ctrl + 0x20, 6) # FAULT -> ebreak s.run(duration=1.0, max_stops=1) print("pc:", hex(s.pc), s.symbols.annotate(s.pc)) s.step() # past the trap print("hits:", hits) finally: print("recording:", getattr(s.recorder, "dir", None)) s.close() ``` ## 13. Errors and timeouts All green-eye errors derive from `greeneye.GreenEyeError`: `RspTimeout` (transport), `RspProtocolError`, `TargetError`, `SymbolNotFound`, `StructError`. `RspClient.timeout` (default 10 s) bounds a single packet read; `run`'s budget bounds the whole pass.