diff options
| author | blasty <blasty@local> | 2026-07-30 13:37:46 +0200 |
|---|---|---|
| committer | blasty <blasty@local> | 2026-07-30 13:37:46 +0200 |
| commit | 214076f2e87e69e50600f75723af09d21dcc9ffc (patch) | |
| tree | 27a3f70b3e7f8be759f25c40a591eb65acf91a39 /idatui/app.py | |
| parent | trace: seek verbs — next/previous execution, and who set this register (diff) | |
| download | ida-tui-214076f2e87e69e50600f75723af09d21dcc9ffc.tar.gz ida-tui-214076f2e87e69e50600f75723af09d21dcc9ffc.tar.xz ida-tui-214076f2e87e69e50600f75723af09d21dcc9ffc.zip | |
trace: a stale navigation no longer drags the view back
Fixes the bug found while building M3. Navigations run in workers and are applied
when they land; the trace's OPENING seek goes to t=0, which for a normal binary
is _start, and that navigation is slow. It arrived after later seeks and won,
leaving the cursor and _cur on _start while the trace's pc was elsewhere — and it
never settled, measured stable for 3+ seconds. Anything cursor-based done just
after a seek (`>` asks about the address under the cursor) then acted on the
wrong address.
The decompiler path has had a staleness guard since 756589a; the listing path
never got one. It has one now (_open_at_if_current), and a seek bumps _nav_seq so
older in-flight navigations are dropped.
Verified both directions on the exact reproduction: seek to the first execution
of a repeated instruction, seek to the second, wait — cursor stays put with the
guard, and with the guard removed it drifts to 0x34d0 (_start) exactly as
reported.
Scope, deliberately narrow. I first bumped _nav_seq in _goto_ea for EVERY
navigation, which is the more general rule, and a full run then failed
follow_xrefs — a follow can be dropped by whatever navigates next. That check has
flaked before so it is not proof, but the mechanism is real and my evidence is
only about seeks, so the bump lives in _seek. TODO records what would justify the
general version and what test it needs.
tests: +1 trace UI (39) — seek, seek again, wait 3s, and the cursor is still on
the instruction the trace is at. Two consecutive full runs 212/0 after narrowing.
Diffstat (limited to 'idatui/app.py')
| -rw-r--r-- | idatui/app.py | 25 |
1 files changed, 24 insertions, 1 deletions
diff --git a/idatui/app.py b/idatui/app.py index 55f21ef..2f19ffe 100644 --- a/idatui/app.py +++ b/idatui/app.py @@ -5654,6 +5654,16 @@ class IdaTui(App): t = self._trace if t is None or not t.length: return + # A seek invalidates any navigation still in flight. They run in workers + # and finish out of order: the trace's opening seek lands on the entry + # point, takes a while, and used to arrive AFTER later seeks — dragging + # the cursor back to _start while the trace was elsewhere, permanently. + # + # Bumped HERE and not in _goto_ea. Doing it for every navigation is the + # more general rule ("the last thing you asked for wins") but it also + # lets an ordinary follow be dropped by whatever navigates next, and the + # only evidence I have is about seeks. Narrow fix for the measured bug. + self._nav_seq += 1 self._t = max(0, min(int(idx), t.length - 1)) self.query_one(TraceDock).show(t, self._t) self._paint_trail() @@ -6029,7 +6039,8 @@ class IdaTui(App): idx = max(lm.ensure_ea(ea), 0) if lm is not None else 0 name = fn.name if fn is not None else self.program.region_label(ea) self.app.call_from_thread( - self._open_at, ea, name, idx, push, -1, 0, fn is None, focus_name) + self._open_at_if_current, seq, ea, name, idx, push, fn is None, + focus_name) def _open_decomp_entry(self, fn_addr: int, fn_name: str, dec_idx: int, dec_cursor_x: int, push: bool, @@ -6202,6 +6213,18 @@ class IdaTui(App): cur = row_of(fallback_ea) return (cur, row_of(a.top_ea)) + def _open_at_if_current(self, seq: int, ea: int, name: str, cursor: int, + push: bool, is_region: bool, + focus_name: str | None) -> None: + """Apply a navigation result only if it's still the one being awaited. + + The decompiler path has had this since 756589a; the listing path hadn't, + so a slow navigation could still land on top of a newer one. + """ + if seq != self._nav_seq: + return + self._open_at(ea, name, cursor, push, -1, 0, is_region, focus_name) + def _open_at(self, ea: int, name: str, cursor: int, push: bool, dec_cursor: int = -1, dec_cursor_x: int = 0, is_region: bool = False, focus_name: str | None = None, |
