diff options
| author | blasty <blasty@local> | 2026-07-23 02:43:30 +0200 |
|---|---|---|
| committer | blasty <blasty@local> | 2026-07-23 02:43:30 +0200 |
| commit | fc6ad44b4a462560c57c97bf00b25b770c3b6204 (patch) | |
| tree | c4a95bc00a4bf4325a385024e1a45f271aee58db | |
| parent | listing: expand struct-typed globals into member rows (M4 item 3) (diff) | |
| download | ida-tui-fc6ad44b4a462560c57c97bf00b25b770c3b6204.tar.gz ida-tui-fc6ad44b4a462560c57c97bf00b25b770c3b6204.tar.xz ida-tui-fc6ad44b4a462560c57c97bf00b25b770c3b6204.zip | |
unify: back the function disasm view on the heads listing walker (M4 item 4)
The function disasm view is now a filtered listing: DisasmModel sources its
lines from the heads walker bounded to [func.start, func.end) instead of the
disasm tool, so both code views render from the one listing mechanism.
DisasmView UI (opcode bytes, o-toggle, Tab, rename) preserved -> no test churn.
total() stays on disasm include_total: paging heads for the count hit the MCP
response-size limit (count=1000 truncated -> wrong count) and corrupted prime.
include_total is one fast exact call; for a code function it equals the heads
row count backing the lines. _fetch_block uses count=257 (safe).
Verified: disasm-heavy scenarios 56/0; full suite 127/1 (1 = flaky filter).
M4 complete.
| -rw-r--r-- | TODO | 15 | ||||
| -rw-r--r-- | idatui/domain.py | 27 |
2 files changed, 29 insertions, 13 deletions
@@ -33,12 +33,15 @@ hard: [x] back-paging / streaming: listing primes the viewport instantly and streams the rest in the background (was load_all -> blank pane for seconds on a big .text). concurrency-safe page loads. - [ ] struct-typed data expansion (render a struct global as its fields). - deferred: model change (one head -> many lines); the data is already - functional via `d my_struct`, expansion is display polish. - [ ] unify the function disasm view as a filtered listing. deferred: a - maintainer refactor (no new capability) with real regression risk on - the battle-tested function-view path; not worth doing blind. + [x] struct-typed data expansion: a struct global expands into indented + member rows (+off name type) in the listing. + [x] unify the function disasm view as a filtered listing: DisasmModel now + sources its lines from the `heads` walker bounded to [func.start, + func.end) (total via disasm include_total to avoid response-size + truncation). Both code views render from the one listing mechanism; + the DisasmView UI (opcode bytes/Tab/rename) is preserved. Full suite + 127/1 (1 = pre-existing flaky filter). + -> M4 COMPLETE. crazy: [ ] multi/split view ala ghidra? diff --git a/idatui/domain.py b/idatui/domain.py index e70182c..3fe0c51 100644 --- a/idatui/domain.py +++ b/idatui/domain.py @@ -306,8 +306,14 @@ class DisasmModel: self._lock = threading.Lock() self._inflight: set[int] = set() + def _end_kw(self) -> dict: + end = self._function_end() + return {"end": hex(end)} if end is not None else {} + def total(self) -> int: - """Total instruction count (fetched once; ~200ms on huge funcs).""" + """Instruction/row count of the function (fetched once). Uses disasm's + ``include_total`` — one fast call, no response-size truncation. For a + code function this equals the heads row count that backs the lines.""" if self._total is not None: return self._total payload = self._prog.client.call( @@ -361,15 +367,22 @@ class DisasmModel: self._max_raw = biggest return out + @staticmethod + def _line_from_head(r: dict) -> Line: + """Adapt a ``heads`` row to a disasm Line (label = the head's name).""" + return Line(ea=_as_int(r["ea"]), text=r.get("text", ""), + label=r.get("name")) + def _fetch_block(self, b: int) -> list[Line]: - # Over-fetch one instruction so the block knows where its last - # instruction ends (variable-length archs give no size field). + # The function disasm view is a listing filtered to the function: fetch a + # block of heads (one per instruction for code). Over-fetch one row so + # the block knows where its last instruction ends (opcode-byte sizing). payload = self._prog.client.call( - "disasm", addr=hex(self.ea), offset=b * self.BLOCK, - max_instructions=self.BLOCK + 1, + "heads", addr=hex(self.ea), offset=b * self.BLOCK, + count=self.BLOCK + 1, **self._end_kw(), ) - raw = payload.get("asm", {}).get("lines", []) if isinstance(payload, dict) else [] - fetched = [Line.from_raw(r) for r in raw] + rows = payload.get("heads", []) if isinstance(payload, dict) else [] + fetched = [self._line_from_head(r) for r in rows] lines = fetched[:self.BLOCK] if len(fetched) > self.BLOCK: end_ea: int | None = fetched[self.BLOCK].ea |
