From d4bd0f246237cd218a75e3014f80d7379e25194b Mon Sep 17 00:00:00 2001 From: blasty Date: Wed, 22 Jul 2026 23:50:40 +0200 Subject: server: coalesce undefined byte runs in the heads walker The M2 fix (step by get_item_end so undefined bytes render and arbitrary addresses land exactly) emits one row per undefined byte, which would explode a large .bss/undefined region into millions of rows and make load_all crawl. Collapse a run of consecutive undefined bytes into a single `db N dup(?)` row (its end found in O(1) via next_head, which skips undefined). A single stray undefined byte still renders normally (shows its value), so exact-address navigation and the c/p/u workflow are unchanged: a 42-byte gap is one row at the run start, and 'p' there auto-analyzes the function. Verified: undefining a function yields one `db 42 dup(?)` row (not 42); echo .text stays 5036 heads; data segments unaffected. region_define + listing_view + test_domain[listing] all green. Needs a supervisor restart. --- server/patch_server.py | 47 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 6 deletions(-) (limited to 'server/patch_server.py') diff --git a/server/patch_server.py b/server/patch_server.py index 3e5193b..52b81a6 100644 --- a/server/patch_server.py +++ b/server/patch_server.py @@ -209,6 +209,23 @@ def _idatui_head_row(ea): return row +def _idatui_unknown_row(ea, size): + """One collapsed row for a run of ``size`` undefined bytes starting at + ``ea``. A single byte is rendered normally (shows its value); a longer run + collapses to ``db N dup(?)`` so a big .bss/gap doesn't explode into millions + of one-byte rows.""" + import ida_name + + if size <= 1: + return _idatui_head_row(ea) + row = {"ea": hex(ea), "kind": "unknown", "size": int(size), + "text": f"db {size} dup(?)"} + nm = ida_name.get_ea_name(ea) + if nm: + row["name"] = nm + return row + + @tool @idasync def heads( @@ -264,24 +281,42 @@ def heads( # Walk by item END (not next_head): next_head SKIPS undefined bytes, but a # flat listing must show them (IDA renders undefined as `db ?` lines, and - # navigating to an unmarked address must land ON it). get_item_end steps by - # the item's size for code/data and by 1 through undefined bytes. - def _step(e): + # navigating to an unmarked address must land ON it). Defined items advance + # by get_item_end; a run of undefined bytes is COLLAPSED into one row (its + # end found in O(1) via next_head, which skips undefined) so a large .bss or + # gap doesn't explode into millions of one-byte rows. + def _is_unknown(e): + f = ida_bytes.get_flags(e) + return not (ida_bytes.is_code(f) or ida_bytes.is_data(f)) + + def _run_end(e): + """End (exclusive) of the undefined run starting at ``e``.""" + nh = ida_bytes.next_head(e, hi) + return nh if (nh != idaapi.BADADDR and e < nh <= hi) else hi + + def _advance(e): + if _is_unknown(e): + return _run_end(e) nxt = ida_bytes.get_item_end(e) return nxt if nxt > e else e + 1 + def _row(e): + if _is_unknown(e): + return _idatui_unknown_row(e, _run_end(e) - e) + return _idatui_head_row(e) + ea = ida_bytes.get_item_head(start) for _ in range(offset): if ea >= hi or ea == idaapi.BADADDR: break - ea = _step(ea) + ea = _advance(ea) more = False while ea != idaapi.BADADDR and ea < hi: if len(rows) >= count: more = True break - rows.append(_idatui_head_row(ea)) - ea = _step(ea) + rows.append(_row(ea)) + ea = _advance(ea) cursor = {"next": hex(ea)} if more else {"done": True} return {"addr": str(addr), "heads": rows, "cursor": cursor} ''' -- cgit v1.3.1-sl0p