summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/harness/control_discovery.py
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-02 11:30:46 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-02 11:30:46 -0400
commit2fd653044127a665e4677a2cdfb58120d7b3e16b (patch)
tree5adaeaecb30c4c553e79cd0d674d7b4029905937 /debug/risc-v-gsp-rm-vm/harness/control_discovery.py
parentf02ba2b6b0059e55e4777cb3a2e6fa87dc6438f3 (diff)
downloadopen-iov-2fd653044127a665e4677a2cdfb58120d7b3e16b.tar.gz
open-iov-2fd653044127a665e4677a2cdfb58120d7b3e16b.zip
debug: green-eye porting automation - new-device and driver-version port scripts, simulator packaging, porting docs; control-discovery fix
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/harness/control_discovery.py')
-rw-r--r--debug/risc-v-gsp-rm-vm/harness/control_discovery.py40
1 files changed, 38 insertions, 2 deletions
diff --git a/debug/risc-v-gsp-rm-vm/harness/control_discovery.py b/debug/risc-v-gsp-rm-vm/harness/control_discovery.py
index eecf082..b44636a 100644
--- a/debug/risc-v-gsp-rm-vm/harness/control_discovery.py
+++ b/debug/risc-v-gsp-rm-vm/harness/control_discovery.py
@@ -115,6 +115,10 @@ SETTLE_MARKER = "SELFTEST DONE"
SETTLE_SLICE = 0.02
RUN_SLICE = 0.02
TRAP_GRACE = 0.05
+#: Bound on instruction-driven single-steps used to retire a dispatcher's
+#: store sequence after the wall-clock observation window expires mid-epilogue
+#: (keeps findings byte-reproducible instead of timer-dependent).
+EPILOGUE_MAX_STEPS = 64
#: gdb signals that carry a hazard (illegal, trap, bus, segv); SIGINT from the
#: budget interrupt is wall-clock dependent and must never enter a finding.
HAZARD_SIGNALS = (4, 5, 7, 11)
@@ -662,12 +666,16 @@ class ProbeRunner:
Completion is a ``result`` store (the dispatcher writes it after every
handler) or a trap/grace for ids that never return to the dispatcher.
+ The wall-clock loop is the budget for the no-progress/hung cases; when
+ a dispatch has been seen the store sequence is then retired with
+ bounded single-steps so the recorded effects cannot depend on where
+ the timer happened to expire.
"""
deadline = time.monotonic() + self.options.timeout
handler_seen_at: float | None = None
while time.monotonic() < deadline:
if not session.target.alive():
- return
+ break
remaining = max(0.005, min(RUN_SLICE, deadline - time.monotonic()))
session.run(duration=remaining, max_stops=64)
if handlers and handler_seen_at is None:
@@ -675,9 +683,37 @@ class ProbeRunner:
result_written = any(hit.get("field") == "result" for hit in observer.hits)
trapped = any(sig.get("signal") == 5 for sig in signals) or session.pc < 0x100
if dispatches and (result_written or trapped):
- return
+ break
if handler_seen_at is not None and time.monotonic() - handler_seen_at >= TRAP_GRACE:
# Slow/hung handler: one grace window after the entry, then stop.
+ break
+ self._retire_dispatch(session, dispatches, signals, observer)
+
+ def _retire_dispatch(self, session, dispatches, signals, observer) -> None:
+ """Finish a dispatched command's store sequence with single-steps.
+
+ The demo dispatcher stores ``result``, ``last_rpc`` and finally
+ ``rpc_count``; a wall-clock observation window can expire between
+ those stores, which made findings timing-dependent. Once a dispatch
+ has been seen, step (bounded and instruction-driven) until the final
+ watched store retires, a trap is seen, or the budget is exhausted.
+ """
+ if not dispatches or not any(watched.name == "rpc_count" for watched in observer.watched):
+ return
+ if any(hit.get("field") == "rpc_count" for hit in observer.hits):
+ return
+ if any(sig.get("signal") == 5 for sig in signals):
+ return
+ for _ in range(EPILOGUE_MAX_STEPS):
+ if not session.target.alive():
+ return
+ try:
+ session.step()
+ except (RspTimeout, GreenEyeError):
+ return
+ if any(hit.get("field") == "rpc_count" for hit in observer.hits):
+ return
+ if any(sig.get("signal") == 5 for sig in signals):
return
def _keep(self, outcome: str) -> bool: