aboutsummaryrefslogtreecommitdiffstats
path: root/server/patch_server.py
diff options
context:
space:
mode:
Diffstat (limited to 'server/patch_server.py')
-rw-r--r--server/patch_server.py58
1 files changed, 42 insertions, 16 deletions
diff --git a/server/patch_server.py b/server/patch_server.py
index b75b120..a1ec1ef 100644
--- a/server/patch_server.py
+++ b/server/patch_server.py
@@ -297,33 +297,59 @@ def _idatui_head_row(ea):
else:
kind = "unknown"
line = ida_lines.generate_disasm_line(ea, 0)
- text = ida_lines.tag_remove(line) if line else ""
- text = " ".join(text.split()) # collapse IDA's column padding
+ text, spans, ops = _idatui_line_parts(line) if line else ("", None, None)
row = {
"ea": hex(ea),
"kind": kind,
"size": int(ida_bytes.get_item_size(ea)),
"text": text,
}
- if line:
- # Keep IDA's own token classification for syntax highlighting. Built from
- # the SAME line as `text`, then whitespace-collapsed identically so the
- # two never disagree about what the row says.
- spans, ops = _idatui_spans(line)
- joined = "".join(t for _k, t in spans)
- if " ".join(joined.split()) == text:
- row["spans"] = spans
- # Where each operand sits in `text`. Comes out of the same tag walk
- # (free), and is what lets the client show WHICH literal a keypress
- # would reformat before you press it.
- if ops:
- row["ops"] = ops
+ if spans is not None:
+ row["spans"] = spans
+ # Where each operand sits in `text`. Comes out of the same tag walk
+ # (free), and is what lets the client show WHICH literal a keypress
+ # would reformat before you press it.
+ if ops:
+ row["ops"] = ops
nm = ida_name.get_ea_name(ea)
if nm:
row["name"] = nm
return row
+import functools as _idatui_functools
+
+
+@_idatui_functools.lru_cache(maxsize=16384)
+def _idatui_line_parts(line):
+ """``(text, spans, ops)`` for one tagged disassembly line -- memoised.
+
+ A function of the tagged line and nothing else, so the same line always
+ gives the same answer: a rename changes the line, which changes the key.
+ And listings repeat themselves hard -- 196k lines of bash are 53k distinct
+ ones, so a 16k-entry cache serves ~70% of them and takes the per-line cost
+ from 10.4us to 3.9us. This is the most expensive thing the backend does per
+ listing row, and a jump to an address near the end of a big binary walks
+ hundreds of thousands of them.
+
+ ``spans`` is None when the tag walk and the plain text disagree about what
+ the line says (then the text wins and the row renders unhighlighted).
+
+ The returned lists are SHARED between every row that has the same line;
+ treat them as read-only. Pickle notices the sharing too, so a page of
+ repetitive disassembly also serialises smaller.
+ """
+ import ida_lines
+ text = " ".join(ida_lines.tag_remove(line).split()) # collapse the padding
+ spans, ops = _idatui_spans(line)
+ # Built from the SAME line as `text`, then whitespace-collapsed identically,
+ # so the two can never disagree about what the row says.
+ joined = "".join([t for _k, t in spans])
+ if " ".join(joined.split()) != text:
+ return (text, None, None)
+ return (text, spans, ops)
+
+
#: IDA colour tag -> the semantic kind the TUI styles. IDA already classifies
#: every token in a disassembly line, for every processor it supports, so there
#: is nothing to lex: generate_disasm_line emits \x01<tag>text\x02<tag> and the
@@ -407,7 +433,7 @@ def _idatui_spans(line):
# the most expensive thing the `heads` tool did, and a line is ~54
# characters but only ~13 tags -- everything between two tags is already
# exactly one span's worth of text.
- _IDATUI_CTL = _re.compile("([\\x01\\x02\\x03][\\s\\S])")
+ _IDATUI_CTL = _re.compile("([\\x01\\x02\\x03](?s:.))")
tags, opnds = _IDATUI_TAGS, _IDATUI_OPND_TAGS
on, off, esc = "\x01", "\x02", "\x03"
addr_tag = chr(getattr(ida_lines, "COLOR_ADDR", 0x28))