1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
|
#!/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())
|