diff options
Diffstat (limited to 'experiments/graph_smoke.py')
| -rw-r--r-- | experiments/graph_smoke.py | 107 |
1 files changed, 107 insertions, 0 deletions
diff --git a/experiments/graph_smoke.py b/experiments/graph_smoke.py new file mode 100644 index 0000000..1b6e21e --- /dev/null +++ b/experiments/graph_smoke.py @@ -0,0 +1,107 @@ +"""End-to-end smoke for the graph path: the `flowchart` tool -> domain -> +idatui.graph layout, through a real idalib worker. + + ~/ida-venv/bin/python experiments/graph_smoke.py [funcname] + +Wants: VERDICT: OK +""" +import os +import shutil +import sys +import time + +REPO = os.path.expanduser("~/dev/ida-tui-maybe") +sys.path.insert(0, REPO) +os.chdir(REPO) + +src, tmp = f"{REPO}/targets/echo", "/tmp/echo_graph" +shutil.copy(src, tmp) +for e in ".i64 .id0 .id1 .id2 .nam .til".split(): + try: + os.remove(tmp + e) + except OSError: + pass + +from idatui.worker_client import WorkerClient # noqa: E402 +from idatui.domain import Program # noqa: E402 +from idatui import graph as G # noqa: E402 + +want = sys.argv[1] if len(sys.argv) > 1 else "main" +print("spawning worker + opening echo\u2026", flush=True) +t = time.time() +cl = WorkerClient(tmp) +cl.connect(progress=lambda m: None) +print(f" worker ready in {time.time()-t:.2f}s", flush=True) +prog = Program(cl) + +ok = True +ea = prog.resolve(want) +print(f"resolve({want!r}) = {ea:#x}", flush=True) + +t = time.time() +fc = prog.flowchart(ea) +print(f"flowchart() -> {time.time()-t:.2f}s", flush=True) +if fc is None: + print("VERDICT: FAIL (no flowchart)") + raise SystemExit(1) + +print(f" {fc.name} @ {fc.func_ea:#x}: {len(fc.blocks)} blocks, entry={fc.entry}") +nrows = sum(len(b.rows) for b in fc.blocks) +print(f" {nrows} listing rows attached") +ok &= nrows > 0 +if not nrows: + print(" !! no rows attached to any block") + +empty = [b for b in fc.blocks if not b.rows] +if empty: + ok = False + print(f" !! {len(empty)} blocks have NO rows, e.g. " + f"{[hex(b.start) for b in empty[:4]]}") + +b0 = fc.blocks[fc.entry] +print(f" entry block {b0.start:#x}-{b0.end:#x}:") +for h in b0.rows[:5]: + tags = ",".join(k for k, _ in (h.spans or ()))[:40] + print(f" {h.ea:#010x} {h.kind:<7} {h.text[:42]!r} spans[{tags}]") +ok &= any(h.spans for b in fc.blocks for h in b.rows) +if not any(h.spans for b in fc.blocks for h in b.rows): + print(" !! no colour spans came through \u2014 highlighting would be dead") + +# rows must tile the block exactly, or boxes will have holes +for b in fc.blocks: + eas = [h.ea for h in b.rows] + if eas and (min(eas) < b.start or max(eas) >= b.end): + ok = False + print(f" !! block {b.start:#x} has rows outside its range") + +blocks = [G.Block(id=b.id, start=b.start, end=b.end, succs=list(b.succs)) + for b in fc.blocks] + + +def sizer(b): + src_b = fc.blocks[b.id] + w = max([len(f"loc_{b.start:X}")] + + [len(h.text) + 12 for h in src_b.rows]) + 4 + return (w, len(src_b.rows) + 3) + + +t = time.time() +lay = G.layout(blocks, sizer, entry=fc.entry) +print(f"layout() -> {(time.time()-t)*1000:.0f} ms {lay.stats}", flush=True) +print(f" canvas {lay.width}x{lay.height}") +ok &= len(lay.nodes) == len(blocks) + +# every block must be reachable in the drawing, and rows must index it +covered = {n.id for r in range(lay.height) for n in lay.nodes_at_row(r)} +ok &= covered == {b.id for b in blocks} +if covered != {b.id for b in blocks}: + print(f" !! row index misses {sorted({b.id for b in blocks} - covered)[:5]}") + +hits = sum(1 for r in range(min(lay.height, 400)) + if lay.painting.cells_at_row(r, 0, lay.width)) +print(f" {hits} of the first {min(lay.height,400)} rows carry edge cells") +ok &= hits > 0 + +cl.close() +print("VERDICT:", "OK" if ok else "FAIL") +raise SystemExit(0 if ok else 1) |
