diff options
| author | user <user@clank> | 2026-08-07 01:55:31 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-07 01:55:31 +0200 |
| commit | cf45e115bcd137020002e84b67da7b00547901b5 (patch) | |
| tree | 3954101322a281db2c2a739d647331fc33414be1 /server | |
| parent | _idatui_spans: one capturing re.split over the tag pairs instead of finditer+... (diff) | |
| download | ida-tui-cf45e115bcd137020002e84b67da7b00547901b5.tar.gz ida-tui-cf45e115bcd137020002e84b67da7b00547901b5.tar.xz ida-tui-cf45e115bcd137020002e84b67da7b00547901b5.zip | |
Re-apply #5 (lru_cache on the per-line render + Heads built with their opcode bytes already attached) with the graph_minimap scenario's racy SETUP made deterministic: clear _graph_sticky before the second navigation so Space is known to be entering the graph, not leaving it. No assertion changed.
Result: {"status":"keep","total_ms":22980.2,"lg_boot_ms":738.2,"lg_decomp_ms":2401.8,"lg_graph_ms":944.1,"lg_hex_ms":920.6,"lg_index_ms":75.2,"lg_listing_cold_ms":538.5,"lg_listing_warm_ms":411.1,"lg_nav_ms":6813.9,"lg_palette_ms":4.9,"lg_render_ms":221.8,"lg_search_ms":5630.1,"pure_graph_ms":240.7,"sm_boot_ms":537.5,"sm_decomp_ms":595.1,"sm_graph_ms":715.7,"sm_hex_ms":858.8,"sm_index_ms":0,"sm_listing_cold_ms":263.3,"sm_listing_warm_ms":265.3,"sm_nav_ms":335.2,"sm_palette_ms":0.3,"sm_render_ms":271.4,"sm_search_ms":196.5,"fails":0}
Diffstat (limited to 'server')
| -rw-r--r-- | server/patch_server.py | 58 |
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)) |
