diff options
| author | blasty <blasty@local> | 2026-07-24 23:38:30 +0200 |
|---|---|---|
| committer | blasty <blasty@local> | 2026-07-24 23:38:30 +0200 |
| commit | de2fa194278bb132e23edddd65bdad84f32ec37d (patch) | |
| tree | 67d18789676b3377892d3edf4a6fbe4d497967af /server/patch_server.py | |
| parent | split-view phase 2: cursor sync + linked-row highlight (diff) | |
| download | ida-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/patch_server.py')
| -rw-r--r-- | server/patch_server.py | 53 |
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" |
