From f720a16f8e4ac538e6b880960fd411357114656d Mon Sep 17 00:00:00 2001 From: user Date: Fri, 31 Jul 2026 09:14:41 +0200 Subject: rpc: stop lying to the driver about renames, modals and teardown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five defects found while an agent drove a long RE session over the socket. Each one was reproduced on a live spawned pane first (an in-process pilot would not have shown any of them), then fixed: pane stop truncated the save. `stop` asked the app to quit, slept 400 ms, then unconditionally killed the pane. Quitting runs App.on_unmount, which writes every dirty database; a 90 MB .i64 takes tens of seconds, so the kill landed mid-write and a whole session's annotations went to /dev/null with a cheerful {"stopped": [...]} on stdout. Now it waits for the pane to actually exit (--timeout, default 600 s) and only force-kills on timeout, saying so. The quit verb bypassed the dirty check. It called app.exit() directly rather than the path a human gets, so the "unsaved changes" logic never ran. It now routes through _on_quit_choice and reports {saving, dirty}. Naming a function start from the listing never reached the function index. `goto ` puts the cursor on the address token, so `n` takes the name-an-address path, which called bump_items() but left FunctionIndex holding the old name. Result: the rename response snapshot showed the section label, and functions()/names()/the palette all reported the rename had not happened — so a driver that trusts its readbacks redoes work it already did. Twice, in the session that prompted this. _do_name_addr now updates the index, the nav stack and the table cell when the address is a function start. A stripped binary with no entry function started up *inside a modal*. _auto_land pushed the symbol palette when main() was missing, while ping still answered ready:true. Every keystroke an RPC driver injected went into the palette's search box and was silently swallowed. It now lands on the first function instead and hints at Ctrl+N. Verbs that inject keystrokes now refuse when a modal is on top, naming it, instead of failing with "'goto' prompt did not open (word under cursor?)" — a message that blamed the cursor for what was always a focus problem. Also: `drive raw` passes k=v values through as strings, so `view lines=8` died with "'<' not supported between instances of 'int' and 'str'". Numeric params are now coerced centrally rather than at each call site. tests/test_scenarios.py: 212 passed, 0 failed. --- idatui/app.py | 48 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 5 deletions(-) (limited to 'idatui/app.py') diff --git a/idatui/app.py b/idatui/app.py index 2f19ffe..1edc65e 100644 --- a/idatui/app.py +++ b/idatui/app.py @@ -4034,7 +4034,19 @@ class IdaTui(App): if fn is not None: self._open_function(fn.addr, fn.name) elif len(self._func_index): - self.action_symbols() + # No main(): land on the first function rather than pushing the + # symbol palette. A modal as the *startup* state leaves a human + # staring at a picker over an empty pane, and silently swallows + # every keystroke an RPC driver injects while `ping` still says + # ready:true. Landing somewhere real is better for both; Ctrl+N is + # one keypress away. + first = self._func_index.get(0) + if first is not None: + self._open_function(first.addr, first.name) + self._status(f"no entry function — opened {first.name} " + "(Ctrl+N: find symbol)") + else: + self.action_symbols() else: self._land_without_functions() @@ -5468,12 +5480,38 @@ class IdaTui(App): return # The label shows in the listing's head rows -> invalidate + reopen. self.program.bump_items() + # Naming the address *of a function start* is a function rename by any + # other name. Without this the cached index kept the old name, so + # `functions`/`names`/resolve/the palette all reported the rename had + # not happened -- and a driver that trusts those readbacks redoes work + # it already did. + fn = None + try: + fn = self.program.function_of(addr) + except Exception: # noqa: BLE001 + fn = None + is_func_start = fn is not None and fn.addr == addr lm = self.program.listing(addr) - label = self.program.region_label(addr) + label = name if is_func_start else self.program.region_label(addr) idx = max(lm.ensure_ea(addr), 0) if lm is not None else 0 - self.app.call_from_thread(self._open_at_named, label, addr, idx, name) - - def _open_at_named(self, label: str, addr: int, idx: int, name: str) -> None: + self.app.call_from_thread(self._open_at_named, label, addr, idx, name, + is_func_start) + + def _open_at_named(self, label: str, addr: int, idx: int, name: str, + is_func_start: bool = False) -> None: + if is_func_start: + self.program.bump_names() + if self._func_index is not None: + self._func_index.update_name(addr, name) + for e in self._nav: + if e.ea == addr: + e.name = name + try: + table = self.query_one("#func-table", DataTable) + name_col = list(table.columns.keys())[1] + table.update_cell(str(addr), name_col, name) + except Exception: # noqa: BLE001 -- row filtered out / not streamed + pass self._open_at(addr, label, idx, False, -1, 0, True) self._dirty = True self._status(f"named {addr:#x} → {name} (Ctrl+S to save)") -- cgit v1.3.1-sl0p