diff options
| author | blasty <blasty@local> | 2026-07-22 23:21:16 +0200 |
|---|---|---|
| committer | blasty <blasty@local> | 2026-07-22 23:21:16 +0200 |
| commit | ad22cf5beb0588c8f454bebfa0cc7c7897e4e7a3 (patch) | |
| tree | 8800083d7627a998dce628fe47ac1c493ee76396 /idatui/domain.py | |
| parent | pane: reap leaked idalib workers; drive: friendlier name resolution (fixes sp... (diff) | |
| download | ida-tui-ad22cf5beb0588c8f454bebfa0cc7c7897e4e7a3.tar.gz ida-tui-ad22cf5beb0588c8f454bebfa0cc7c7897e4e7a3.tar.xz ida-tui-ad22cf5beb0588c8f454bebfa0cc7c7897e4e7a3.zip | |
app: non-function regions — open a flat listing + c/p/u edit verbs (M0)
Navigating to an address not inside a function no longer refuses ("no
function contains X"): _do_navigate now opens a region view (flat listing
anchored at the EA, forced to disasm since there's no pseudocode). The
server's no-function disasm already walks heads to the segment end, so
DisasmModel is reused as-is; NavEntry gains is_region.
Adds the IDA c/p/u structure-edit verbs on the disasm view:
c = define_code (undefine-first, since create_insn won't carve a live item)
p = define_func
u = undefine
wired via EditItemRequested -> _do_edit_item worker -> Program.define_code/
define_func/undefine. define_func upgrades the region to a real function
view in place.
New Program.bump_items() invalidates the item/function structure caches
(disasm blocks, decomp, function indices + name gen) — broader than
bump_names(), which only covers renames.
Pilot: new `region_define` scenario undefines a small function, navigates
to the bare region via the 'g' prompt (asserts it opens, not refused),
then 'p' recreates the function and upgrades the view; restores the IDB in
a finally. 107 passed / 1 pre-existing flaky (filter, fails on baseline).
Diffstat (limited to 'idatui/domain.py')
| -rw-r--r-- | idatui/domain.py | 62 |
1 files changed, 62 insertions, 0 deletions
diff --git a/idatui/domain.py b/idatui/domain.py index c374b71..53fd907 100644 --- a/idatui/domain.py +++ b/idatui/domain.py @@ -853,6 +853,68 @@ class Program: for m in models: m.invalidate() + def bump_items(self) -> None: + """Signal that item/function STRUCTURE changed (define code/data/func, + undefine). Unlike a rename this can move instruction boundaries and + change function membership anywhere, so drop the disasm block caches, + the decompilation cache and the cached function indices outright, and + bump the name generation too (labels/names may appear or vanish).""" + with self._lock: + self._name_gen += 1 + self._indices.clear() + self._decomp.clear() + models = list(self._disasm.values()) + self._disasm.clear() + for m in models: + m.invalidate() + + # -- item / function structure edits (IDA c/d/u/p) --------------------- # + @staticmethod + def _first_result(payload) -> dict: + """Unwrap the first row of a batch tool response ({result:[...]} or a + bare list); soft per-item ``error`` fields ride along in the dict.""" + data = payload.get("result", payload) if isinstance(payload, dict) else payload + if isinstance(data, list): + return data[0] if data and isinstance(data[0], dict) else {} + return data if isinstance(data, dict) else {} + + def define_code(self, ea: int) -> None: + """Convert the bytes at ``ea`` into a code instruction (IDA's 'c'). + Undefine first so it works even when the bytes are currently part of a + data/align item — ``create_insn`` refuses to carve into a live item.""" + try: + self.client.call("undefine", items=[{"addr": hex(ea)}]) + except IDAToolError: + pass # nothing defined here yet -> just try to create the insn + res = self._first_result( + self.client.call("define_code", items=[{"addr": hex(ea)}])) + if res.get("error"): + raise IDAToolError(f"define code @ {ea:#x}: {res['error']}") + + def define_func(self, ea: int) -> None: + """Create a function starting at ``ea`` (IDA's 'p').""" + res = self._first_result( + self.client.call("define_func", items=[{"addr": hex(ea)}])) + if res.get("error"): + raise IDAToolError(f"create function @ {ea:#x}: {res['error']}") + + def undefine(self, ea: int, size: int | None = None) -> None: + """Undefine the item at ``ea`` back to raw bytes (IDA's 'u').""" + item: dict = {"addr": hex(ea)} + if size: + item["size"] = int(size) + res = self._first_result(self.client.call("undefine", items=[item])) + if res.get("error"): + raise IDAToolError(f"undefine @ {ea:#x}: {res['error']}") + + def region_label(self, ea: int) -> str: + """Display name for a non-function address (segment-qualified).""" + try: + sec = self.section_of(ea) + except Exception: # noqa: BLE001 + sec = None + return f"{sec} @ {ea:#x}" if sec else f"<no function> @ {ea:#x}" + @staticmethod def _fetch_output(url: str, timeout: float = 15.0): """GET the server's cached full-output blob (plain HTTP, not MCP).""" |
