aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorblasty <blasty@local>2026-07-24 23:38:30 +0200
committerblasty <blasty@local>2026-07-24 23:38:30 +0200
commitde2fa194278bb132e23edddd65bdad84f32ec37d (patch)
tree67d18789676b3377892d3edf4a6fbe4d497967af /server
parentsplit-view phase 2: cursor sync + linked-row highlight (diff)
downloadida-tui-de2fa194278bb132e23edddd65bdad84f32ec37d.tar.gz
ida-tui-de2fa194278bb132e23edddd65bdad84f32ec37d.tar.xz
ida-tui-de2fa194278bb132e23edddd65bdad84f32ec37d.zip
split-view phase 3: rich per-line instruction region highlight
The Ghidra "region band": moving the pseudocode cursor now lights up EVERY instruction that C line owns, not just one. * server/patch_server.py: new decomp_map tool — sweeps cfunc.get_line_item across each pseudocode line's columns and collects the ea from each item's dstr() ('EA: desc', matching the /*ea*/ marker source so it aligns with the display lines). Returns {addr, lines:[{ea, eas:[...]}]}. (First tried item.get_ea(), which reports a different ea and didn't align — dstr() is the right source.) * domain: Program.decomp_map(ea) -> per-line ea lists, cached by name-gen. * app: _load_split_map fetches it off-thread into _split_eamap/_split_ea2line; _sync_split bands the full instruction region for a C line (decomp drives) and uses the exact ea->line inverse (listing drives), falling back to the single marker until the map lands. Maps cleared on leaving split. Pilot split_view gains: decomp_map returns/aligns with the markers, and a multi-instruction C line bands >1 listing row (13/13). Full suite 154/2-flake. The idalib spike ran on the pilot's own worker (the standalone worker kept getting reaped in this sandbox).
Diffstat (limited to 'server')
-rw-r--r--server/patch_server.py53
1 files changed, 53 insertions, 0 deletions
diff --git a/server/patch_server.py b/server/patch_server.py
index 6862785..c217561 100644
--- a/server/patch_server.py
+++ b/server/patch_server.py
@@ -582,6 +582,59 @@ def xref_types(
rows = rows[:count]
result.append({"query": raw, "data": rows, "next_offset": None})
return {"result": result}
+
+
+@tool
+@idasync
+def decomp_map(
+ addr: Annotated[str, "Function address or name"],
+) -> dict:
+ """Per-pseudocode-line instruction coverage for the split view's region
+ highlight: for each line, the set of EAs the decompiler attributes to it,
+ swept across the line's columns via get_line_item. Shape:
+ {addr, lines:[{ea: primary|None, eas:[hex,...]}, ...]}."""
+ import ida_hexrays
+ import idaapi
+ try:
+ ea = int(str(addr), 16)
+ except ValueError:
+ ea = idaapi.get_name_ea(idaapi.BADADDR, str(addr).strip())
+ func = idaapi.get_func(ea)
+ if not func:
+ return {"error": f"no function at {addr}"}
+ try:
+ cfunc = ida_hexrays.decompile(func.start_ea)
+ except Exception as e: # noqa: BLE001
+ return {"error": f"decompile failed: {e}"}
+ if cfunc is None:
+ return {"error": "decompile failed"}
+ lines = []
+ for sl in cfunc.get_pseudocode():
+ line = sl.line
+ eas, seen = [], set()
+ for x in range(len(line) + 1):
+ head = ida_hexrays.ctree_item_t()
+ item = ida_hexrays.ctree_item_t()
+ tail = ida_hexrays.ctree_item_t()
+ if not cfunc.get_line_item(line, x, False, head, item, tail):
+ continue
+ # Match the /*ea*/ marker's source (decompile_function_safe): the
+ # item's dstr() is 'EA: description'; get_ea() reports a different ea.
+ dstr = item.dstr()
+ if not dstr:
+ continue
+ parts = dstr.split(": ", 1)
+ if len(parts) != 2:
+ continue
+ try:
+ e = int(parts[0], 16)
+ except ValueError:
+ continue
+ if e not in seen:
+ seen.add(e)
+ eas.append(hex(e))
+ lines.append({"ea": eas[0] if eas else None, "eas": eas})
+ return {"addr": hex(func.start_ea), "lines": lines}
'''
SNIPPET = f"{BEGIN}\n{BODY.strip()}\n{END}\n"