aboutsummaryrefslogtreecommitdiffstats
path: root/idatui/domain.py
diff options
context:
space:
mode:
Diffstat (limited to 'idatui/domain.py')
-rw-r--r--idatui/domain.py62
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)."""