aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--TODO9
-rw-r--r--idatui/app.py46
2 files changed, 34 insertions, 21 deletions
diff --git a/TODO b/TODO
index 5d037a7..cb8ad91 100644
--- a/TODO
+++ b/TODO
@@ -109,7 +109,8 @@ waits on the rest. The tempting fallback — nearest mapped address at or before
the pc — is UNSOUND: C lines are not monotonic in address, and it resolved an
instruction early in main to a line near the end of the function.
-Left alone: _split_ea2line and _split_range are still fed by that guarded path,
-so the split view's own sync can still work from the map of the function you
-just left. It has not caused a visible problem outside stepping, but it is the
-same latent issue.
+FIXED: the split view and the trace path used to keep two parallel maps of the
+same thing, fetched separately and keyed differently — the split one on _cur (the
+cursor's function), the trace one on the decompiler's loaded function. That is
+how they ended up describing different functions. There is now one index, keyed
+to what the decompiler HOLDS, and both read it.
diff --git a/idatui/app.py b/idatui/app.py
index d44cc20..9d99122 100644
--- a/idatui/app.py
+++ b/idatui/app.py
@@ -5618,22 +5618,16 @@ class IdaTui(App):
dec.trail = {}
return
if self._trail_map_ea != ea:
- # Cached per function: decomp_map is an RPC and stepping is
- # interactive, so paying it per keystroke would be felt.
+ # One index, built once per decompiled function and shared with the
+ # split view (_apply_split_map fills the same fields). decomp_map is
+ # an RPC and stepping is interactive, so paying it per keystroke —
+ # or twice, once for each of two parallel maps — would be felt.
try:
- self._trail_map = self.program.decomp_map(ea)
+ self._apply_split_map(ea, self.program.decomp_map(ea))
except Exception: # noqa: BLE001
- self._trail_map = []
- self._trail_map_ea = ea
- # ea -> line, built once per function: stepping asks for it on every
- # keypress and a scan per step would be quadratic in the function.
- self._trail_line_of = {}
- for i, eas in enumerate(self._trail_map or []):
- for a in eas:
- self._trail_line_of.setdefault(a, i)
- self._trail_eas = sorted(self._trail_line_of)
- self._trail_span = ((self._trail_eas[0], self._trail_eas[-1])
- if self._trail_eas else None)
+ self._trail_map, self._trail_map_ea = [], ea
+ self._trail_line_of, self._trail_eas = {}, []
+ self._trail_span = None
rank = {"future": 0, "past": 1, "now": 2}
lines: dict[int, str] = {}
for i, eas in enumerate(self._trail_map or []):
@@ -6564,8 +6558,18 @@ class IdaTui(App):
self.app.call_from_thread(self._apply_split_map, ea, m)
def _apply_split_map(self, ea: int, m: list) -> None:
- if not self._split or self._cur is None or self._cur.ea != ea:
- return # left split / navigated away
+ """Index the per-line instruction map for the decompiled function.
+
+ Keyed to what the DECOMPILER holds, not to _cur, and not conditional on
+ split being on. The old guard dropped the result whenever _cur had moved
+ while the fetch was in flight — during trace stepping that is almost
+ always — leaving the split view working from the map of the function you
+ just left. _cur follows the cursor; this map describes the pseudocode on
+ screen, and those are different things.
+ """
+ dec = self._try_view(DecompView)
+ if dec is not None and dec.loaded_ea is not None and ea != dec.loaded_ea:
+ return # a stale fetch for a function we no longer show
self._split_eamap = m
self._split_ea2line = {}
alleas = []
@@ -6576,7 +6580,15 @@ class IdaTui(App):
# ea span of the decompiled function: when the listing cursor leaves it,
# _sync_split re-points the decomp to the function under the cursor.
self._split_range = (min(alleas), max(alleas)) if alleas else None
- self._sync_split(self._active) # re-link with the region map
+ # ONE index, shared with the trace path: it used to keep a parallel copy
+ # of exactly this, fetched separately and keyed differently, which is how
+ # the two ended up describing different functions.
+ self._trail_map, self._trail_map_ea = m, ea
+ self._trail_line_of = dict(self._split_ea2line)
+ self._trail_eas = sorted(self._trail_line_of)
+ self._trail_span = self._split_range
+ if self._split:
+ self._sync_split(self._active) # re-link with the region map
def on_decomp_view_cursor_moved(self, msg: DecompView.CursorMoved) -> None:
dv = self._try_view(DecompView)