diff options
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.py | 40 |
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: |
