From de2fa194278bb132e23edddd65bdad84f32ec37d Mon Sep 17 00:00:00 2001 From: blasty Date: Fri, 24 Jul 2026 23:38:30 +0200 Subject: split-view phase 3: rich per-line instruction region highlight MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- server/patch_server.py | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) (limited to 'server') 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" -- cgit v1.3.1-sl0p