diff options
| author | blasty <blasty@local> | 2026-07-22 23:29:26 +0200 |
|---|---|---|
| committer | blasty <blasty@local> | 2026-07-22 23:29:26 +0200 |
| commit | 5878bc98590335190cd0e9a82400b3acae8ffad3 (patch) | |
| tree | 89c070add9a560186c7254efd7d31287e1ae8c82 /tests | |
| parent | app: non-function regions — open a flat listing + c/p/u edit verbs (M0) (diff) | |
| download | ida-tui-5878bc98590335190cd0e9a82400b3acae8ffad3.tar.gz ida-tui-5878bc98590335190cd0e9a82400b3acae8ffad3.tar.xz ida-tui-5878bc98590335190cd0e9a82400b3acae8ffad3.zip | |
server+domain: heads walker + ListingModel — flat code/data listing (M1)
The keystone for a real disassembly-listing view (unlike DisasmModel, which
is one function, code-only). Two pieces:
server/patch_server.py: inject a `heads` tool. It walks item heads over a
segment with next_head/prev_head and renders each via generate_disasm_line,
so it returns a flat listing where code, data (db/dw/dd, strings, jump
tables) and undefined bytes all appear as typed rows {ea,kind,size,text,name}.
Unlike `disasm` (code-only, bails at the first data byte) it shows the whole
segment. Address-paged: chain forward via cursor.next, page up with back=true
(returns the N heads ending before addr, in forward order, + cursor.prev).
domain.py: ListingModel — a lazily-grown, segment-scoped head index
(FunctionIndex-style forward paging via cursor.next; line index == position
in the walked list). ensure_ea() gives random access to an address (resolving
a mid-item byte to its containing head). New Head dataclass, Program.listing()
(cached per segment) + segment_bounds(); section_of() now derives from it;
bump_items() clears the listing cache too.
Verified live: heads renders strings/jump-tables/unknown correctly, forward
chaining + back-paging work; ListingModel walks echo .text (5036 heads,
~276ms) with cached windows and mid-item address resolution. tests/test_domain
gains a [listing] section (27 passed). Pilot hex/region_define/disasm_nav green.
Note: adding a server tool needs a supervisor restart (workers respawn).
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test_domain.py | 36 |
1 files changed, 36 insertions, 0 deletions
diff --git a/tests/test_domain.py b/tests/test_domain.py index 6875b66..24cb781 100644 --- a/tests/test_domain.py +++ b/tests/test_domain.py @@ -146,6 +146,42 @@ def main(argv): except KeyError as e: check("resolve symbol name", False, str(e)) + # ---- flat listing (code + data + undefined heads) ------------------ # + print("\n[listing]") + seg = prog.segment_bounds(fattest.addr) + check("segment_bounds finds the .text segment", seg is not None and seg[0] <= fattest.addr < seg[1], + str(seg)) + lm = prog.listing(fattest.addr) + check("listing() returns a model for a mapped address", lm is not None) + if lm is not None: + lm.ensure(20) + w = lm.window(0, 20) + check("listing window returns heads", len(w) == 20, str(len(w))) + eas = [h.ea for h in w] + check("listing head addrs strictly increasing", all(b > a for a, b in zip(eas, eas[1:])), + str(eas[:4])) + check("listing heads carry a kind", all(h.kind in ("code", "data", "unknown") for h in w), + str({h.kind for h in w})) + check("listing head sizes positive", all(h.size >= 1 for h in w), + str([h.size for h in w[:6]])) + # random access to a mid-segment address lands on the containing head + mid = w[10].ea + li = lm.ensure_ea(mid) + check("ensure_ea lands on the exact head for a head address", + li >= 0 and lm.get(li).ea == mid, f"idx={li}") + # a mid-item byte resolves to its containing head + if w[4].size > 1: + inside = w[4].ea + 1 + li2 = lm.ensure_ea(inside) + check("ensure_ea resolves a mid-item byte to its head", + li2 >= 0 and lm.get(li2).ea == w[4].ea, f"idx={li2} ea={w[4].ea:#x}") + dt_cold, _ = ms(lambda: lm.window(0, 20)) + check("listing window revisit is cached/instant", dt_cold < 5, f"{dt_cold:.1f}ms") + dt_all, _ = ms(lambda: lm.load_all()) + check("listing load_all completes the segment", lm.complete and len(lm) > 20, + f"n={len(lm)} complete={lm.complete}") + print(f" {seg[2]}: {len(lm)} heads walked in {dt_all:.0f}ms") + prog.close() print(f"\n{PASS} passed, {FAIL} failed") return 1 if FAIL else 0 |
