summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/harness/value_demos/s07_version_diff.py
blob: e75280762a1173b2642cfabf3f8a903e2b73dff4 (plain)
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
#!/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-risc-v-gsp-rm-debugger"
STRING_NEW = b"hello-risc-v-gsp-rm-debuggef"


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=(
            "risc-v-gsp-rm-debugger 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 = {}
    session_dirs = {}
    for label, path in (("A", va), ("B", vb)):
        session_dirs[label] = C.session_dir(out, f"session-{label}")
        session = ge.launch(kernel=path, backend=backend, record=session_dirs[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(
        session_dirs["A"], session_dirs["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())