From 6c3fd5df61ff2f5cb8833ead6c05eaff9fdc92bf Mon Sep 17 00:00:00 2001 From: Arthur Hanson Rasmusson Date: Tue, 6 Oct 2026 19:28:39 -0400 Subject: risc-v-gsp-rm-debugger: land docs 45-53 + harness/sim tool set with tests rm_capture (chunked decode), launch_audit, state_seed/state_decode, valprobe, rehost, contracts+run_contracts, host_traffic, gmmu_walk, msgq_attach, libos_port, log_decode, server_snapshot; doc index updates in the tool overview, operations pages and the repo README. --- .../docs/47-VALIDATION-PREDICATE-PROBE.md | 172 +++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 debug/risc-v-gsp-rm-vm/docs/47-VALIDATION-PREDICATE-PROBE.md (limited to 'debug/risc-v-gsp-rm-vm/docs/47-VALIDATION-PREDICATE-PROBE.md') diff --git a/debug/risc-v-gsp-rm-vm/docs/47-VALIDATION-PREDICATE-PROBE.md b/debug/risc-v-gsp-rm-vm/docs/47-VALIDATION-PREDICATE-PROBE.md new file mode 100644 index 0000000..8d17859 --- /dev/null +++ b/debug/risc-v-gsp-rm-vm/docs/47-VALIDATION-PREDICATE-PROBE.md @@ -0,0 +1,172 @@ +# 47 — Validation-predicate probe (`valprobe`) — in development + +> **Status: in development (2026-10-06, wall-directed wave).** Documents +> `harness/valprobe.py`: run one firmware **validation branch** of the real +> image with crafted register/memory state (plus, where needed, one recorded +> inline stub for a dependency outside the predicate) and record the branch +> the firmware took. Born from the live GR wall's question class: *which +> control/entry shapes does 570.144 GSP-RM accept or refuse, and why?* +> +> Development-only: executes firmware on the simulator. Never touches a GPU, +> kernel module, mdev, guest or network. No timing/cycle claim (L0/L1). +> +> **See also:** `46-SURGICAL-REHOST.md` (whole-function rehost — the parent +> tool), `44-GMMU-ORACLE-AND-WORKFLOW.md` (evidence discipline), +> `39-RE-WORKFLOW-REVIEW.md` (time-to-value ranking), `06-ACCURACY.md` +> (fidelity levels). + +## 1. Why it exists + +The live GR campaign submits `GPU_PROMOTE_CTX` shapes to the real GSP-RM and +reads accept/refuse per supervised cycle. The decisive questions are +firmware-side predicates, but a whole-function rehost (`docs/46`) early-outs +on missing RM object state (`0x40`) before the predicate executes. This +harness executes the predicate branch itself: + +* **entry**: the validation branch point (not the full handler); +* **state**: exactly the registers/memory the branch reads, crafted in RAM; +* **stub**: at most one `lui/addi/ret` stub for a dependency that is *not* + the predicate (e.g. a lock/table walk), recorded in the report; +* **outcome**: execution stops on labelled outcome addresses; the verdict is + the branch taken, with the firmware's own arithmetic/indexing. + +## 2. Invocation + +```bash +# Bundled matrix: the 570.144 PROMOTE_CTX per-buffer validation (FECS etc.) +python3 harness/valprobe.py sweep # crafted-state, ~2 s +python3 harness/valprobe.py sweep --boot-to B3 # boot the image first +python3 harness/valprobe.py demo # matrix twice (determinism) +python3 harness/valprobe.py run --spec my-spec.json # generic spec +``` + +A spec is `greeneye-valprobe-spec/1` with a `target`, an `outcomes` map +(address → label), optional `boot_to`, and `variants` carrying `registers`, +`memory` (`hex|u64|u32|u16|u8`), `stubs` (`addr` + `return`), `reads` +(post-run) and `expect`. Report schemas: `greeneye-valprobe/1` per variant, +`greeneye-valprobe-matrix/1` for the run (`valprobe-matrix.json`), with one +`greeneye-session/1` per variant when recording is on. + +Outcome statuses: `outcome` (a labelled address was reached — the verdict), +`return` (`ra` sentinel), `fault` (first missing state), `ecall`, `timeout`, +`budget`. `pass` compares `outcome` to `expect`; `sweep` exits 3 on any fail. + +## 3. The current wall, answered offline (570.144, GR PROMOTE_CTX) + +Live facts this reproduces (V6 KMD-managed channel, supervised cycles): +`PM`/`PATCH` init accepted, **FECS_EVENT init refused `0x1f`**, FECS VA-only +accepted; engine dispatch still absent. The handoff's suggested next hardware +experiment was "find a FECS init form V6 accepts (e.g. a sysmem attr 0x6 / +different size / different bufferId)". + +The probe enters the per-buffer validation branch `0x5290dd4` +(`FUN_ram_05290a48`) and executes the firmware's own logic. Firmware facts +(disassembly, numeric addresses only): + +| # | Fact | +|---|---| +| 1 | `bInitialize=1`, `bufferId==9` (FECS) takes the branch at `0x5290e3c` | +| 2 | that branch reads the **global GR-context memDesc slot**: `instance = pGpu[(idx+0x37c)]`, `table = *(u64*)(instance+0x190)`, slot index from the firmware's own `0x531b944` jump table (**FECS → 6**), and returns `NV_ERR_INVALID_ARGUMENT` (`0x1f`) **iff `slot != 0`** | +| 3 | `physAttr`, `size`, `gpuPhysAddr`, `gpuVirtAddr` are **not read** in that branch | +| 4 | `bInitialize=0`: `bNonmapped && !bInitialize` → `0x1f`; otherwise the entry passes | +| 5 | init is allowed for ids 0..2, 8..11; refused for 3..7 and 12; ids > 12 refused a block earlier | + +So **if the server has already populated the FECS slot, no init form exists** +— the aperture/size variants cannot change the verdict, and the only accepted +FECS shape is `bInitialize=0` (a VA booking, matching the live VA-only +accept). The campaign can redirect from "find a FECS init form" to +"prevent/clear the server's FECS slot" or "accept VA-only and look at +dispatch elsewhere" without spending a cycle on the aperture test. + +Matrix (15/15, deterministic; run twice with identical verdicts; 1.9 s +crafted-state, 13 s with `--boot-to B3`): + +| variant | verdict | +|---|---| +| FECS init vidmem, slot non-NULL | `refuse-fecs-slot-exists-0x1f` | +| FECS init vidmem, slot NULL | accept (commit path) | +| FECS init **sysmem attr 0x6**, slot non-NULL | `refuse-fecs-slot-exists-0x1f` | +| FECS init **sysmem attr 0x6**, slot NULL | accept | +| FECS init size 0x1000, slot non-NULL | `refuse-fecs-slot-exists-0x1f` | +| FECS no-init mapped, slot non-NULL | accept (skip path) | +| FECS no-init nonmapped | `refuse-invalid-argument-0x1f` | +| PM init / PATCH init | accept | +| GFXP_CTRL_BLK (8) init | accept (skip path) | +| PRIV (10) / UNR (11) init | accept (skip path) | +| BUNDLE_CB (3) / GLOBAL_PRIV (12) init | `refuse-invalid-argument-0x1f` | +| unknown id 13 | `refuse-invalid-argument-0x1f` | + +Each variant's report also carries the firmware-mapped slot index read back +from the run (`6` for FECS) and the applied stub, so the data flow is in the +evidence, not in prose. + +### 3.1 The FECS buffer is global and one-shot (live-confirmed 2026-10-06) + +The probe's refusal condition was then joined to a live backend run and the +semantics are now settled: + +* The FECS memDesc lives in a **global GR context-buffer table shared across + channels** (per GPU/client, per gfid); the table's only writer is the + init-promote path, and a successful init leaves the slot set so that + **every later `bInitialize=1` FECS promote is refused `0x1f`** — independent + of aperture, size, address or channel. +* A live run showed the decisive ordering: the backend's own **golden-channel + 9-entry atomic promote** (including `FECS_EVENT bInitialize=1`) was accepted, + and the per-entry FECS promote on a *different channel of the same client* + was refused `0x1f` **~0.1–0.3 s later**. The refusal was the shared + one-shot rule, not a channel deficiency. +* The per-channel requirement for FECS is therefore only the **VA booking** + (`bInitialize=0`), which the same live channel already accepted. No client + "FECS init form" exists once any earlier promote initialized the shared + buffer; an atomic multi-entry set containing FECS init fails as a whole + after that point. + +This is what the probe class is for: it turns a supervised-cycle guessing loop +into a seconds-long offline verdict plus one log join. + +## 4. Honesty rules + +* A probe is **not a boot** and **not a model**: only the seeded state exists; + the report lists entry, registers, memory, stubs, outcomes and limits. +* A stub replaces an explicitly named dependency outside the predicate; the + predicate, its id→slot mapping call and its memory reads are the firmware's + own code. Prefer entering after the dependency when that is safe. +* A fault is the **first missing state observed**, not a product defect; a + timeout/budget is not a verdict. +* `--boot-to ` runs the real image first so predicates that need + initialized globals can be probed with real state; the report records the + observed rung and whether the request was reached. +* No timing/cycle claims; sessions and matrix JSON are the evidence. + +## 5. Tests + +`tests/test_valprobe.py` — stub encoding decodes as `lui/addi/jalr`, memory +spec widths, spec shape, scratch layout bounds, a live reduced matrix with +determinism, the firmware slot-index read-back (asserts `6`), boot-to-B3, and +CLI exit codes. Live tests skip without the shipped image. + +```bash +python3 -m unittest tests.test_valprobe -v +python3 harness/valprobe.py demo +``` + +## 6. Problem classes and time-to-value + +Use this tool when the question can be stated as *"what will this firmware +branch do with input/state X?"* — control-entry validation, accept/refuse +rules, error-path predicates, field-dependency questions. It answers offline, +in seconds, with labelled outcomes and a session; the alternative is static +reading (rule known, cause not), or a supervised hardware cycle per variant +(minutes to an hour plus teardown risk). + +The reference case (2026-10-06): a wall that had consumed five-plus supervised +cycles and was still misdiagnosed as a channel limitation was closed by a +15-variant probe (≈2 s, run twice, identical) plus one log join. The rule to +copy: phrase the question as a predicate → probe it offline → join the verdict +to live logs → spend the supervised cycle only on what the probe cannot see +(the engine/silicon side). + +Companion tools: `harness/rehost.py` (docs/46) for whole-function calls with +crafted state and snapshots; `sim.gsp_boot` for boot-state evidence (rung and +state); `harness/triage.py` / `version_diff.py` for release-to-release +questions. -- cgit v1.2.3