aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorblasty <blasty@local>2026-07-23 01:34:48 +0200
committerblasty <blasty@local>2026-07-23 01:34:48 +0200
commit7a47d42230d49f052b5599bb0b8f25cf71174436 (patch)
treedef9082289291714ba64272fd47f9c0f0b6625dd
parentapp: listing 'n' names the address (can name a bare/undefined byte) (diff)
downloadida-tui-7a47d42230d49f052b5599bb0b8f25cf71174436.tar.gz
ida-tui-7a47d42230d49f052b5599bb0b8f25cf71174436.tar.xz
ida-tui-7a47d42230d49f052b5599bb0b8f25cf71174436.zip
perf: derive segment map from file_regions, not survey_binary (hex open ~3400x)
The slow hex load wasn't the byte reads (read_raw fixed those) -- it was opening the pane at all: hex_model -> image_range -> sections called survey_binary, which computes function counts/strings/stats and takes ~24s on libcrypto, blocking the first hex open (and section_of/segment_bounds/ region_label in the listing). sections/file_regions/image_range now share one cheap _segments source backed by the injected file_regions tool (a plain segment walk, ~7ms), which gains a name field so it fully replaces survey_binary for the segment map. Falls back to survey_binary only if the tool is missing. Measured (libcrypto): segment map 24240ms -> 7.1ms (~3400x); hex open goes from ~24s to ~11ms. Correctness verified (names, section_of/segment_bounds/ file_offset); hex+listing scenarios 24/0, full suite 123/1 flaky. Needs a supervisor restart.
-rw-r--r--idatui/domain.py65
-rw-r--r--server/patch_server.py7
2 files changed, 47 insertions, 25 deletions
diff --git a/idatui/domain.py b/idatui/domain.py
index 0d5d0aa..58e4be9 100644
--- a/idatui/domain.py
+++ b/idatui/domain.py
@@ -718,6 +718,7 @@ class Program:
self._listings: dict[int, ListingModel] = {} # keyed by segment start
self._decomp: dict[int, tuple[Decompilation, int]] = {}
self._name_gen = 0 # bumped on rename; invalidates stale name caches
+ self._segments_cache: list[tuple[int, int, int, str]] | None = None
self._sections: list[tuple[int, int, str]] | None = None
self._fileregions: list[tuple[int, int, int]] | None = None
self._hexmodel: "HexModel | None" = None
@@ -744,23 +745,48 @@ class Program:
return idx
# -- sections / segments ---------------------------------------------- #
+ def _segments(self) -> list[tuple[int, int, int, str]]:
+ """Sorted raw segment map [(start, end, file_off, name)] — the single
+ source for sections()/file_regions()/image_range. Cached.
+
+ Uses the injected ``file_regions`` tool (a plain segment walk, ~ms).
+ This deliberately AVOIDS ``survey_binary``, which also computes function
+ counts / strings / stats and takes *seconds* on a large IDB (it was the
+ cause of the multi-second hex-pane open). Falls back to survey_binary
+ only if the injected tool is missing.
+ """
+ if self._segments_cache is not None:
+ return self._segments_cache
+ segs: list[tuple[int, int, int, str]] = []
+ try:
+ r = self.client.call("file_regions")
+ for d in (r.get("regions", []) if isinstance(r, dict) else []):
+ if isinstance(d, dict) and "start" in d:
+ segs.append((_as_int(d["start"]), _as_int(d["end"]),
+ int(d.get("file_off", -1)), d.get("name", "") or ""))
+ except IDAToolError:
+ segs = []
+ if not segs: # older server without file_regions -> survey_binary (slow)
+ try:
+ sb = self.client.call("survey_binary")
+ for s in (sb.get("segments", []) if isinstance(sb, dict) else []):
+ try:
+ segs.append((_as_int(s["start"]), _as_int(s["end"]), -1,
+ s.get("name", "") or ""))
+ except (KeyError, ValueError, TypeError):
+ continue
+ except Exception: # noqa: BLE001 -- best-effort; callers handle empty
+ segs = []
+ segs.sort()
+ with self._lock:
+ self._segments_cache = segs
+ return segs
+
def sections(self) -> list[tuple[int, int, str]]:
- """Sorted, non-overlapping [(start, end, name)] segment map (cached).
- Fetched once from ``survey_binary`` (~0.7s) on first use."""
+ """Sorted, non-overlapping [(start, end, name)] segment map (cached)."""
if self._sections is not None:
return self._sections
- secs: list[tuple[int, int, str]] = []
- try:
- sb = self.client.call("survey_binary")
- for s in (sb.get("segments", []) if isinstance(sb, dict) else []):
- try:
- secs.append((_as_int(s["start"]), _as_int(s["end"]),
- s.get("name", "")))
- except (KeyError, ValueError, TypeError):
- continue
- except Exception: # noqa: BLE001 -- best-effort; callers handle None
- secs = []
- secs.sort()
+ secs = [(s, e, nm) for s, e, _fo, nm in self._segments()]
with self._lock:
self._sections = secs
return secs
@@ -788,16 +814,7 @@ class Program:
the injected ``file_regions`` server tool."""
if self._fileregions is not None:
return self._fileregions
- regions: list[tuple[int, int, int]] = []
- try:
- r = self.client.call("file_regions")
- for d in (r.get("regions", []) if isinstance(r, dict) else []):
- if isinstance(d, dict) and "start" in d:
- regions.append((_as_int(d["start"]), _as_int(d["end"]),
- int(d.get("file_off", -1))))
- except IDAToolError:
- regions = []
- regions.sort()
+ regions = [(s, e, fo) for s, e, fo, _nm in self._segments()]
with self._lock:
self._fileregions = regions
return regions
diff --git a/server/patch_server.py b/server/patch_server.py
index 4764367..4b2661f 100644
--- a/server/patch_server.py
+++ b/server/patch_server.py
@@ -175,7 +175,12 @@ def file_regions() -> dict:
fo = -1
if fo < 0 or fo >= (1 << 48):
fo = -1
- out.append({"start": hex(seg.start_ea), "end": hex(seg.end_ea), "file_off": fo})
+ try:
+ nm = ida_segment.get_segm_name(seg) or ""
+ except Exception:
+ nm = ""
+ out.append({"start": hex(seg.start_ea), "end": hex(seg.end_ea),
+ "file_off": fo, "name": nm})
seg = ida_segment.get_next_seg(seg.start_ea)
return {"regions": out}