aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorblasty <blasty@local>2026-07-22 23:29:26 +0200
committerblasty <blasty@local>2026-07-22 23:29:26 +0200
commit5878bc98590335190cd0e9a82400b3acae8ffad3 (patch)
tree89c070add9a560186c7254efd7d31287e1ae8c82 /tests
parentapp: non-function regions — open a flat listing + c/p/u edit verbs (M0) (diff)
downloadida-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.py36
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