diff options
| author | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-02 10:58:21 -0400 |
|---|---|---|
| committer | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-02 10:58:21 -0400 |
| commit | 002e3d7d13c414ea874abbee8d82803dd3a72fe5 (patch) | |
| tree | 6627c12fa07f6a1cf73e5aaeb7390e698dd3a896 /debug/risc-v-gsp-rm-vm/harness/value_demos/s08_mutation_triage.py | |
| parent | 2f249817df327f5dcccf0464ad517fa60d6071d3 (diff) | |
| download | open-iov-002e3d7d13c414ea874abbee8d82803dd3a72fe5.tar.gz open-iov-002e3d7d13c414ea874abbee8d82803dd3a72fe5.zip | |
debug: green-eye wave 3 - RM harness, local simulator, device map, MCP plugin, value-validation demos, CI pipeline, toolchain docs; add host RM tools rmtool/rmtop
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/harness/value_demos/s08_mutation_triage.py')
| -rw-r--r-- | debug/risc-v-gsp-rm-vm/harness/value_demos/s08_mutation_triage.py | 161 |
1 files changed, 161 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/harness/value_demos/s08_mutation_triage.py b/debug/risc-v-gsp-rm-vm/harness/value_demos/s08_mutation_triage.py new file mode 100644 index 0000000..8fcaed1 --- /dev/null +++ b/debug/risc-v-gsp-rm-vm/harness/value_demos/s08_mutation_triage.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +"""s08 - mutation triage: mutate one field, classify the outcome. + +Real scenario (docs/18-HARNESS-AUTOMATION.md R5 and +Handoff-vGPU-IO.md PART 77 / docs/19 3.4, 2026-09-30..2026-10-02): the +error-path corpus (sentinel/boundary pointers on ECHO/SUM, invalid command +IDs) is how the emulation's fail-closed rules are derived. Statically the +demo's 4-of-24 outcomes were invisible; the discovery sweep found +``rpc_echo``/``rpc_sum`` fault on sentinel/boundary pointers while +``rpc_ping``/``rpc_state``/``rpc_fill`` stay on the handler path. + +Analog: a seeded mutation table over command/arguments, one fresh session per +attempt (the documented safety strategy), classified as ok / trap / fault / +no-progress from stop signals, ``rpc_count`` and the result field. +""" +from __future__ import annotations + +import json +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 + +FAULT_SIGNALS = {4, 7, 11} # SIGILL, SIGBUS, SIGSEGV +TRAP_SIGNAL = 5 # SIGTRAP (ebreak) + + +def _probe(ge, mutation: dict, backend: str) -> dict: + session = ge.launch(kernel=C.DEMO_ELF, backend=backend, name="vd-s08") + try: + ctrl = session.symbols.lookup("gsp_ctrl") + signals = [] + session.on_event("stop", lambda hit, sig=signals: sig.append(hit.stop.signal) if hit.stop else None) + session.run(duration=0.15) + literal = 0 + if mutation.get("arg0") == "hello": + hits = session.memory.search(0x80000000, 0x2000, b"hello-green-eye") + literal = hits[0] if hits else ctrl + C.CTRL_BUF + before = session.memory.read_u64(ctrl + C.CTRL_RPC_COUNT) + session.memory.write_u64(ctrl + C.CTRL_ARG0, literal if mutation.get("arg0") == "hello" else mutation.get("arg0", 0)) + session.memory.write_u64(ctrl + C.CTRL_ARG1, mutation.get("arg1", 0)) + session.memory.write_u64(ctrl + C.CTRL_MAILBOX, mutation["cmd"]) + # max_stops=1: a faulted target keeps re-faulting at the same PC, so + # stop at the first signal instead of spinning the fault loop. + session.run(duration=0.2, max_stops=1) + after = session.memory.read_u64(ctrl + C.CTRL_RPC_COUNT) + if after == before and not any(signals): + session.run(duration=0.3, max_stops=1) + after = session.memory.read_u64(ctrl + C.CTRL_RPC_COUNT) + result = session.memory.read_u64(ctrl + C.CTRL_RESULT) + if any(s == TRAP_SIGNAL for s in signals): + outcome = "trap" + elif any(s in FAULT_SIGNALS for s in signals): + outcome = "fault" + elif after > before: + outcome = "ok" + else: + outcome = "no-progress" + return { + "outcome": outcome, + "rpc_count_delta": after - before, + "result": result, + "result_hex": f"{result:#x}", + "signals": [s for s in signals if s], + "consumed": after > before, + } + finally: + session.close() + + +def run() -> dict: + import greeneye as ge + + out = C.artifact_dir("s08_mutation_triage") + backend = C.choose_backend("native") + mutations = [ + {"strategy": "arg-boundary", "cmd": 2, "arg0": 0, "arg1": 0, "expect": "fault"}, + {"strategy": "valid", "cmd": 2, "arg0": "hello", "arg1": 0, "expect": "ok"}, + {"strategy": "arg-boundary", "cmd": 2, "arg0": 0xFFFFFFFFFFFFFFFF, "arg1": 0, "expect": "fault"}, + {"strategy": "valid", "cmd": 4, "arg0": "hello", "arg1": 5, "expect": "ok"}, + {"strategy": "buffer-length", "cmd": 4, "arg0": "hello", "arg1": 0x8000000000000000, "expect": "no-progress"}, + {"strategy": "buffer-length", "cmd": 3, "arg0": 0x41, "arg1": 4096, "expect": "ok"}, + {"strategy": "cmd-invalid", "cmd": 7, "arg0": 0, "arg1": 0, "expect": "ok"}, + ] + commands = [ + "for each seeded mutation: fresh native session, settle, one mailbox write", + "run bounded; classify from stop signals + rpc_count + result", + "write findings.jsonl (deterministic order) for corpus diffing", + ] + scenario = C.scenario( + ids=[ + "docs/18-HARNESS-AUTOMATION.md 2 (R5): bounded seeded mutation, outcome classes", + "docs/19-CONTROL-DISCOVERY.md 3.4 (2026-10-02): sentinel/boundary pointer faults found by the sweep", + "Handoff-vGPU-IO.md PARTs 3-9 (2026-09-21..23): hand-derived assert/error paths", + ], + baseline=( + "statically the fault surface is invisible; the project derived error " + "paths by hand from asserts and NOCAT records across many physical " + "cycles; a 64-iteration QEMU mutation run is the tracked baseline" + ), + projection=( + "real RM controls land in the same mutation harness once the RM " + "surface exists (docs/19 section 5); one device-write experiment " + "per physical cycle is the current cap" + ), + blocked=True, + device="H1.3 RM control surface for mutation of real controls", + ) + C.render_banner( + "s08_mutation_triage", + "Mutate one input field deterministically and classify the outcome (ok/trap/fault/no-progress).", + "error-path corpus for fail-closed emulation rules (R5/H4.3)", + scenario["static_baseline"], + commands, + ) + + findings = [] + for index, mutation in enumerate(mutations): + observed = _probe(ge, mutation, backend) + finding = {"iteration": index, **mutation, **observed, "matched_expectation": observed["outcome"] == mutation["expect"]} + findings.append(finding) + classes: dict[str, int] = {} + for finding in findings: + classes[finding["outcome"]] = classes.get(finding["outcome"], 0) + 1 + payload = { + "success": all(f["matched_expectation"] for f in findings), + "backend": backend, + "mutations": len(findings), + "outcome_classes": classes, + "findings": findings, + "question": "enumerate and classify one-field mutations without hardware", + "answer": ( + f"{len(findings)} mutations -> {classes}; all expectations matched; " + "faults localised to the dereferencing handlers, the oversized SUM " + "stays no-progress, FILL clamps at the buffer size - a corpus entry " + "per mutation with no device writes" + ), + "success_criteria": { + "pass": "every seeded mutation produces its documented outcome class", + "fail": "an outcome contradicts the case table or a session leaks", + }, + "real_case": scenario, + } + with open(os.path.join(out, "findings.jsonl"), "w", encoding="utf-8") as fh: + for finding in findings: + fh.write(json.dumps(finding, sort_keys=True) + "\n") + C.write_json(os.path.join(out, "mutation.json"), payload) + return payload + + +def main() -> int: + return 0 if C.timed_demo(run, C.artifact_dir("s08_mutation_triage"))["success"] else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) |
