#!/usr/bin/env python3 """s05 - snapshot diff: which control-block fields did one command change? Real scenario (Handoff-vGPU-IO.md PART 91 ADDENDUM 6, 2026-10-01 cycle 12/13): the fn72 ``GSP_SET_SYSTEM_INFO`` payload carried *unpacked* identity fields and the reference sends raw config-space dwords; the server read the device id back as zero and only a field-level comparison of the live payload/reply could confirm the fix ("all zeroed fn72 fields cannot matter to this failure" was settled by decoding, not by reading). Static review (REVIEW-FN72.md, cycle 13's 1656-byte static-config reply) still needed a live before/after of the control block to call the fix verified. Analog: snapshot the decoded control block before and after one command, attribute every changed byte run to its named schema field, and show ECHO's buffer side-effect separately. """ from __future__ import annotations import os try: # package import (tests) or direct script execution (run_all) from . import _common as C except ImportError: import sys as _sys _sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import _common as C def _fields_hit(schema, diffs) -> list[str]: names = [] for diff in diffs: for field in schema.fields: start = field.offset end = field.offset + field.nbytes if start <= diff.addr < end: if field.name not in names: names.append(field.name) return names def run() -> dict: import greeneye as ge from greeneye.memory import Memory backend = C.choose_backend("native") out = C.artifact_dir("s05_snapshot_diff") commands = [ 's.snapshot(gsp_ctrl, 0x188, "before")', "write mailbox cmd=2 (ECHO) with a known string; run", 's.snapshot(gsp_ctrl, 0x188, "after")', "Memory.diff(before, after); decode both via gsp_ctrl.schema.json", ] scenario = C.scenario( ids=[ "Handoff-vGPU-IO.md PART 91 ADDENDUM 6 cycle 12 (2026-10-01): unpacked fn72 identity fields", "PART 91 ADDENDUM 7 (2026-10-01): 1656-byte static-config reply decoded field-by-field", "docs/06-ACCURACY.md M6: memory side-effect agreement / snapshot hashes", ], baseline=( "the malformed identity value was suspected statically (REVIEW-FN72) " "but accepting the fix required a live boot per iteration; ten NOCAT " "cycles ran before the field-level root cause was visible" ), projection=( "the same before/after diff runs against any firmware region once " "the H2 device model exposes the real RM control pages" ), blocked=True, device="H2.2 RM control/reply pages for the real GSP-RM", ) C.render_banner( "s05_snapshot_diff", "Which named control-block fields did this one command change?", "fn72/static-config field-level validation (identity fix, cycle 12/13)", scenario["static_baseline"], commands, ) schema = C.load_schema(ge) session = ge.launch(kernel=C.DEMO_ELF, backend=backend, name="vd-s05") try: ctrl = session.symbols.lookup("gsp_ctrl") session.run(duration=0.25) before = session.snapshot(ctrl, C.CTRL_SIZE, "before-echo") message = b"hello-risc-v-gsp-rm-debugger" # Locate the string literal in the loaded image so the stimulus is # self-describing (no hardcoded address). hits = session.memory.search(0x80000000, 0x2000, message) literal = hits[0] if hits else 0 session.memory.write_u64(ctrl + C.CTRL_ARG0, literal) session.memory.write_u64(ctrl + C.CTRL_MAILBOX, 2) session.run(duration=0.25) after = session.snapshot(ctrl, C.CTRL_SIZE, "after-echo") before_dec = C.decode_ctrl(ge, before, schema) after_dec = C.decode_ctrl(ge, after, schema) diffs = Memory.diff(before, after) changed_fields = _fields_hit(schema, diffs) changed_names = [name for name in changed_fields if before_dec.get(name) != after_dec.get(name)] rendered = Memory.render_diff(diffs, symbols=session.symbols) payload = { "success": bool(changed_names) and set(changed_names) >= {"rpc_count", "last_rpc", "result", "buf"} and after_dec.get("result") == len(message), "backend": backend, "changed_field_names": changed_names, "changed_byte_runs": len(diffs), "before": {k: v for k, v in before_dec.items() if k in changed_names}, "after": {k: v for k, v in after_dec.items() if k in changed_names}, "rendered_diff": rendered, "question": "attribute every changed byte in one region to a named field", "answer": ( f"ECHO changed {len(changed_names)} named fields {changed_names}; " f"buf={after_dec.get('buf')!r} result={after_dec.get('result')} " f"(len={len(message)}); {len(diffs)} byte runs exactly covered by the schema - " "the field-level 'what did the reply actually consume' check" ), "success_criteria": { "pass": "rpc_count/last_rpc/result/buf change, result equals the ECHO length, no unattributed run", "fail": "a region changes with no named field, or the ECHO reply length disagrees", }, "real_case": scenario, } C.write_json(os.path.join(out, "snapshot-diff.json"), payload) C.write_text( os.path.join(out, "snapshot-diff.md"), "# snapshot diff (analog)\n\n" f"* changed fields: {changed_names}\n" f"* changed byte runs: {len(diffs)}\n\n" "```\n" + rendered + "\n```\n", ) finally: session.close() return payload def main() -> int: return 0 if C.timed_demo(run, C.artifact_dir("s05_snapshot_diff"))["success"] else 1 if __name__ == "__main__": raise SystemExit(main())