From 4b5584002802b3b41262764c8e770598cedb35fe Mon Sep 17 00:00:00 2001 From: blasty Date: Thu, 23 Jul 2026 00:41:19 +0200 Subject: perf: read_raw tool — bulk byte reads for the hex view (5–8x) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hex view's lazy-load was dominated by the byte-read path, which was slow on two axes: * server: get_bytes uses read_bytes_bss_safe, a per-byte loop (is_loaded + get_byte = 2 IDA calls/byte → ~8k calls for a 4KB block), and encodes the result as "0x00 0x01 ..." text (~5x wire bloat); * client: read_bytes re-parsed that with int(tok,16) per byte; * and get_bytes silently TRUNCATES ≥16KB responses to "0x..." (wrong data). New injected `read_raw` tool does a single bulk ida_bytes.get_bytes (C-speed) and only re-checks is_loaded for the sparse 0xFF bss-sentinel bytes, returning one contiguous hex string. Client decodes with bytes.fromhex (C-speed). domain read_bytes uses it with a transparent fallback to get_bytes on older servers (cached _no_read_raw flag). HEX_BLOCK 4096→16384 now that big blocks are cheap and no longer truncate → 4x fewer round-trips while scrolling. Measured (echo, warm): 4KB 22.7ms→4.7ms (4.9x), 8KB 51.6ms→6.6ms (7.8x), and 16KB works (15.7ms) where get_bytes truncates outright. Byte-exact match vs the old path; disasm opcode bytes (also read_bytes) benefit too. Pilot hex + disasm_nav 12/0. Needs a supervisor restart (new server tool). --- server/patch_server.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) (limited to 'server/patch_server.py') diff --git a/server/patch_server.py b/server/patch_server.py index 52b81a6..4764367 100644 --- a/server/patch_server.py +++ b/server/patch_server.py @@ -180,6 +180,45 @@ def file_regions() -> dict: return {"regions": out} +@tool +@idasync +def read_raw( + addr: Annotated[str, "Start address (hex or name)"], + size: Annotated[int, "Number of bytes to read"], +) -> dict: + """Read ``size`` bytes at ``addr`` as ONE contiguous lowercase hex string + (no per-byte '0x'/spaces). The hot path for the hex view and disasm opcode + bytes. + + Fast: does a single bulk ``ida_bytes.get_bytes`` (C-speed) instead of the + per-byte read_bytes_bss_safe loop (2 IDA calls/byte). Unloaded bytes come + back from IDA as the 0xFF sentinel, so we only re-check is_loaded for the + (usually sparse) 0xFF bytes and zero the genuinely-unloaded ones — matching + get_bytes' bss semantics without paying per-byte for the whole range. + + Encoding is compact hex (~2.5x smaller than get_bytes' '0x..'-with-spaces) + and, unlike get_bytes, does not truncate on large reads.""" + import ida_bytes + + ea = parse_address(addr) + n = max(int(size), 0) + if n == 0: + return {"addr": addr, "hex": "", "n": 0} + raw = ida_bytes.get_bytes(ea, n) + if raw is None or len(raw) < n: # nothing (or not all) mapped + base = bytearray(raw or b"") + base.extend(b"\\xff" * (n - len(base))) + raw = bytes(base) + ba = bytearray(raw) + # Only unloaded bytes read as 0xFF; correct just those to 0 (bss => zero). + i = ba.find(0xFF) + while i != -1: + if not ida_bytes.is_loaded(ea + i): + ba[i] = 0 + i = ba.find(0xFF, i + 1) + return {"addr": addr, "hex": bytes(ba).hex(), "n": len(ba)} + + def _idatui_head_row(ea): """One flat-listing row for the head at ``ea``: kind (code/data/unknown), byte size, rendered text, and any symbol name.""" -- cgit v1.3.1-sl0p