summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/harness/value_demos/s07_version_diff.py
diff options
context:
space:
mode:
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/harness/value_demos/s07_version_diff.py')
-rw-r--r--debug/risc-v-gsp-rm-vm/harness/value_demos/s07_version_diff.py179
1 files changed, 179 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/harness/value_demos/s07_version_diff.py b/debug/risc-v-gsp-rm-vm/harness/value_demos/s07_version_diff.py
new file mode 100644
index 0000000..e4748d7
--- /dev/null
+++ b/debug/risc-v-gsp-rm-vm/harness/value_demos/s07_version_diff.py
@@ -0,0 +1,179 @@
+#!/usr/bin/env python3
+"""s07 - version diff: same stimulus against two firmware versions.
+
+Real scenario (Handoff-vGPU-IO.md PART 91, 2026-10-01 cycles 2-3): the staged
+570.211 image and a verified 570.144 image both failed ``GSP_INIT_DONE`` with
+``rpc_result=0x59``; disproving the image-pairing hypothesis cost one full
+supervised boot cycle per version, and docs/14 section 4.1 exists because
+"the code changed" and "the behaviour moved" must be separated before the
+emulation is touched. Sorting a new image into the decision table
+(static changed x hit/write profile changed) is the H4.1 triage.
+
+Analog: byte-patch the demo's PING reply constant (0xbeef -> 0xbef0) to make a
+labelled "new version", run the *same* recorded stimulus on both, and diff the
+sessions and the decoded control block. The answer is one live-path field.
+"""
+from __future__ import annotations
+
+import os
+import shutil
+
+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
+
+#: addiw a1, a1, -0x111 (the 0xbeef PING key) -> -0x110 (0xbef0)
+ADDW_OLD = bytes.fromhex("9b85f5ee")
+ADDW_NEW = bytes.fromhex("9b8505ef")
+STRING_OLD = b"hello-green-eye"
+STRING_NEW = b"hello-green-eyf"
+
+
+def _make_variant(src: str, dst: str) -> dict:
+ with open(src, "rb") as fh:
+ data = bytearray(fh.read())
+ count = data.count(ADDW_OLD)
+ if count == 1:
+ offset = data.find(ADDW_OLD)
+ old_bytes = bytes(data[offset : offset + 4])
+ data[offset : offset + 4] = ADDW_NEW
+ kind = "ping-key-0xbeef->0xbef0"
+ else:
+ count = data.count(STRING_OLD)
+ if count != 1:
+ raise C.DemoError(f"no unique patch site (addiw x{data.count(ADDW_OLD)}, string x{count})")
+ offset = data.find(STRING_OLD)
+ old_bytes = bytes(data[offset : offset + len(STRING_OLD)])
+ data[offset : offset + len(STRING_OLD)] = STRING_NEW
+ kind = "echo-string"
+ with open(dst, "wb") as fh:
+ fh.write(data)
+ return {
+ "kind": kind,
+ "offset": offset,
+ "old_bytes": old_bytes.hex(),
+ "source_sha256": C.sha256_file(src),
+ "variant_sha256": C.sha256_file(dst),
+ }
+
+
+def run() -> dict:
+ import greeneye as ge
+ from greeneye.analyze import compare_sessions
+
+ backend = C.choose_backend("native")
+ out = C.artifact_dir("s07_version_diff")
+ va = os.path.join(out, "uproc_demo_vA.elf")
+ vb = os.path.join(out, "uproc_demo_vB.elf")
+ shutil.copyfile(C.DEMO_ELF, va)
+ patch = _make_variant(C.DEMO_ELF, vb)
+
+ commands = [
+ "byte-patch PING key 0xbeef -> 0xbef0 to create version B",
+ 'for v in (A,B): s = ge.launch(v, backend="%s", record=...)' % backend,
+ "identical stimulus: mailbox cmd=1 arg0=0x1234; snapshot+decode gsp_ctrl",
+ "compare_sessions(A, B, symbols) + decoded field diff",
+ ]
+ scenario = C.scenario(
+ ids=[
+ "Handoff-vGPU-IO.md PART 91 (2026-10-01) cycles 2-3: 570.211 vs 570.144 pairing disproven",
+ "PART 91 ADDENDUM 6 (2026-10-01): LOGRM id + fn72 identity changed behaviour between cycles",
+ "docs/14-ADVERSARIAL-INTEROP-GUIDE.md 4.1: version-diff decision table",
+ ],
+ baseline=(
+ "one full supervised boot (~5-6 min) per version plus manual log "
+ "comparison; the pairing hypothesis took cycles 2 and 3 back-to-back"
+ ),
+ projection=(
+ "green-eye cannot boot the real image without the H2 device model; "
+ "the same same-stimulus/compare-sessions loop runs on re-hosted "
+ "firmware and the produced symbol/session deltas are the H4.1 artifact"
+ ),
+ blocked=True,
+ device="H2.2 device model for real .fwimage versions",
+ )
+ C.render_banner(
+ "s07_version_diff",
+ "On the same stimulus, which live-path behaviour moved between two firmware versions?",
+ "570.211 vs 570.144 image pairing; semantic diff before touching the emulation",
+ scenario["static_baseline"],
+ commands,
+ )
+
+ schema = C.load_schema(ge)
+ results = {}
+ for label, path in (("A", va), ("B", vb)):
+ session = ge.launch(kernel=path, backend=backend, record=os.path.join(out, f"session-{label}"), name=f"vd-s07-{label}")
+ try:
+ ctrl = session.symbols.lookup("gsp_ctrl")
+ dispatches = []
+ session.hook(
+ "rpc_dispatch",
+ on_enter=lambda hit, d=dispatches: d.append({"cmd": hit.arg(0), "arg0": hit.arg(1)}),
+ )
+ session.watch(ctrl + C.CTRL_RESULT, size=8, kind="w")
+ session.run(duration=0.25)
+ session.memory.write_u64(ctrl + C.CTRL_ARG0, 0x1234)
+ session.memory.write_u64(ctrl + C.CTRL_MAILBOX, 1)
+ session.run(duration=0.25)
+ decoded = C.decode_ctrl(ge, session.snapshot(ctrl, C.CTRL_SIZE, f"ctrl-{label}"), schema)
+ session.note("version", label=label, patch=patch["kind"], reply=decoded.get("result"))
+ results[label] = {
+ "path": os.path.relpath(path, C.TOOL_ROOT),
+ "sha256": C.sha256_file(path),
+ "dispatches": dispatches,
+ "reply": decoded.get("result"),
+ "rpc_count": decoded.get("rpc_count"),
+ "last_rpc": decoded.get("last_rpc"),
+ }
+ finally:
+ session.close()
+
+ compare_md = compare_sessions(
+ os.path.join(out, "session-A"), os.path.join(out, "session-B"), symbols=session.symbols, max_rows=20
+ )
+ C.write_text(os.path.join(out, "compare-sessions.md"), compare_md)
+ moved = sorted(
+ key
+ for key in ("reply", "rpc_count", "last_rpc")
+ if results["A"][key] != results["B"][key]
+ )
+ payload = {
+ "success": results["A"]["reply"] == 0xACDB and results["B"]["reply"] == 0xACC4 and moved == ["reply"],
+ "backend": backend,
+ "patch": patch,
+ "version_A": results["A"],
+ "version_B": results["B"],
+ "moved_fields": moved,
+ "decision_table_row": {
+ "static_code_changed": True,
+ "live_profile_changed": bool(moved),
+ "reading": "new logic on a live path - prioritise (docs/14 4.1)",
+ },
+ "compare_report": os.path.relpath(os.path.join(out, "compare-sessions.md"), C.TOOL_ROOT),
+ "question": "separate 'the code changed' from 'the behaviour moved' on a fixed stimulus",
+ "answer": (
+ f"version A replies {results['A']['reply']:#x}, version B replies {results['B']['reply']:#x}; "
+ f"moved fields {moved}; the patch site is {patch['kind']} at file offset {patch['offset']:#x} - "
+ "the decision-table row is 'code changed + profile changed -> prioritise'"
+ ),
+ "success_criteria": {
+ "pass": "exactly the patched reply field moves on the live path; both sessions replay from the same stimulus",
+ "fail": "unrelated fields move or a version cannot be replayed",
+ },
+ "real_case": scenario,
+ }
+ C.write_json(os.path.join(out, "version-diff.json"), payload)
+ return payload
+
+
+def main() -> int:
+ return 0 if C.timed_demo(run, C.artifact_dir("s07_version_diff"))["success"] else 1
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())