aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/patch_server.py66
1 files changed, 50 insertions, 16 deletions
diff --git a/server/patch_server.py b/server/patch_server.py
index a34a4a9..a991158 100644
--- a/server/patch_server.py
+++ b/server/patch_server.py
@@ -282,14 +282,19 @@ def read_raw(
return {"addr": addr, "hex": bytes(ba).hex(), "n": len(ba)}
-def _idatui_head_row(ea):
+def _idatui_head_row(ea, flags=None):
"""One flat-listing row for the head at ``ea``: kind (code/data/unknown),
- byte size, rendered text, and any symbol name."""
+ byte size, rendered text, and any symbol name.
+
+ ``flags`` lets a caller that already asked for them say so -- the walk in
+ ``heads`` used to fetch them three times per head (here, in _is_unknown from
+ _advance, and again from _rows_for).
+ """
import ida_bytes
import ida_lines
import ida_name
- f = ida_bytes.get_flags(ea)
+ f = ida_bytes.get_flags(ea) if flags is None else flags
if ida_bytes.is_code(f):
kind = "code"
elif ida_bytes.is_data(f):
@@ -590,11 +595,23 @@ def _idatui_rows_digest(rows):
hashes to that. One worker, one process, one hash seed.
"""
acc = 0
+ # The per-line render is memoised, so one spans list is shared by every row
+ # that says the same thing -- about 45% of them within a page. Hash each
+ # distinct list once and key that by identity, rather than rebuilding a
+ # tuple of tuples per row (which is the exact cost that was measured and
+ # removed from the client side for the same reason).
+ seen = {}
for r in rows:
sp = r.get("spans")
+ if sp is None:
+ sh = None
+ else:
+ key = id(sp)
+ sh = seen.get(key)
+ if sh is None:
+ sh = seen[key] = hash(tuple(map(tuple, sp)))
acc = hash((acc, r.get("ea"), r.get("kind"), r.get("size"),
- r.get("text"), r.get("name"),
- tuple(map(tuple, sp)) if sp else None))
+ r.get("text"), r.get("name"), sh))
return acc
@@ -735,8 +752,7 @@ def heads(
# 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)
+ def _is_unknown_f(f):
return not (ida_bytes.is_code(f) or ida_bytes.is_data(f))
def _run_end(e):
@@ -744,21 +760,37 @@ def heads(
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):
+ def _advance(e, f):
+ if _is_unknown_f(f):
return _run_end(e)
nxt = ida_bytes.get_item_end(e)
return nxt if nxt > e else e + 1
- def _rows_for(e):
- if _is_unknown(e):
+ # The function the walk is currently inside, reused while it stays inside.
+ # get_func is ~0.5us and the walk asks per head; a head is nearly always in
+ # the same function as the one before it. Only ever consulted when ``e``
+ # falls in [start_ea, end_ea), so a tail chunk elsewhere cannot be
+ # misattributed -- checked against get_func over 437k heads of
+ # bash/ls_ttl/echo with zero disagreements.
+ fn_cache = [None]
+
+ def _func_at(e):
+ cur = fn_cache[0]
+ if cur is not None and cur.start_ea <= e < cur.end_ea:
+ return cur
+ cur = idaapi.get_func(e)
+ fn_cache[0] = cur
+ return cur
+
+ def _rows_for(e, f):
+ if _is_unknown_f(f):
return [_idatui_unknown_row(e, _run_end(e) - e)]
- func = idaapi.get_func(e) if annotate else None
+ func = _func_at(e) if annotate else None
at_start = func is not None and func.start_ea == e
out = []
if at_start:
out.extend(_idatui_func_header_rows(e))
- row = _idatui_head_row(e)
+ row = _idatui_head_row(e, f)
if at_start:
row = dict(row)
row["name"] = None # the name is shown on the proc header line
@@ -778,17 +810,19 @@ def heads(
return out
ea = ida_bytes.get_item_head(start)
+ get_flags = ida_bytes.get_flags
for _ in range(offset):
if ea >= hi or ea == idaapi.BADADDR:
break
- ea = _advance(ea)
+ ea = _advance(ea, get_flags(ea))
more = False
while ea != idaapi.BADADDR and ea < hi:
if len(rows) >= count:
more = True
break
- rows.extend(_rows_for(ea)) # a struct head expands into member rows
- ea = _advance(ea)
+ f = get_flags(ea) # once per head, not once per consumer
+ rows.extend(_rows_for(ea, f)) # a struct head expands into member rows
+ ea = _advance(ea, f)
cursor = {"next": hex(ea)} if more else {"done": True}
out = {"addr": str(addr), "cursor": cursor,
"digest": _idatui_rows_digest(rows), "count": len(rows)}