aboutsummaryrefslogtreecommitdiffstats
path: root/idatui/drive.py
diff options
context:
space:
mode:
authorblasty <blasty@local>2026-07-25 23:03:10 +0200
committerblasty <blasty@local>2026-07-25 23:03:10 +0200
commitb8d9aa4fce78f4523cffaba8b46095d2dfb66310 (patch)
tree97e73ec6b027a19cbeafe026aaf8b90bad0773d1 /idatui/drive.py
parentprojects phase 3: follow an import into the binary that implements it (diff)
downloadida-tui-b8d9aa4fce78f4523cffaba8b46095d2dfb66310.tar.gz
ida-tui-b8d9aa4fce78f4523cffaba8b46095d2dfb66310.tar.xz
ida-tui-b8d9aa4fce78f4523cffaba8b46095d2dfb66310.zip
projects phase 4: cross-binary back, linkage-guided pre-warm, project verbs
Three things, all following from phase 3 making cross-binary jumps ordinary. **A cross-binary jump was a one-way door.** Nav history is per-binary, so arriving in another binary — a project search hit, or now following an import into the library that implements it — landed you in an empty history with nothing to take you back. _switch_then_goto records the binary it came FROM, and action_back falls through to that hop once local history is spent: Esc walks back through the function you were in, then the binary you were in. Manual Ctrl+O switching records nothing, because that isn't navigation. **Pre-warm follows the linkage graph, not list order.** _prewarm_provider warms the binary providing the most of this one's imports — where a follow is most likely to go, so its startup is paid before you ask for it. "Next in the list" would have been arbitrary; phase 3 gave us something better to ask. pool.prewarm() refuses rather than making room. Evicting a binary the user visited to speculatively load one they haven't is a straight downgrade, and it throws away that binary's caches as well; at a tight budget pre-warm just does nothing. The cost of a worker that doesn't exist yet can only be estimated, so it uses the largest resident one (same program, different database) — and if that estimate proves wrong, the speculative worker is the one evicted, never a chosen one. **Driving a project.** pane spawn --project FILE [--open BIN]; `binaries` lists the inventory (active / resident / indexed / where Esc returns to) and `switch {binary,addr?}` makes another active — with an address it takes the search-hit path, so it records a hop. state gains `binary` and `hops`, which it should have had the moment project mode existed. Verified on real sessions: drive binaries/switch against an echo+cat project pane; Esc crossing back from a switch; and prewarm on echo+libc picking libc (provider of echo's imports) and warming it after an evict. tests: +5 pool (prewarm warms, no-ops when resident, refuses at budget, evicts nothing when refusing, ignores unknown labels) and +4 project UI (jump records the hop, Esc crosses back, hop consumed). Confirmed the Esc-back checks fail with the branch removed. 195/0 scenarios, 27/0 project UI, 36/0 index, 27/0 pool, 33/0 project. Left open: project-level persistence across sessions.
Diffstat (limited to 'idatui/drive.py')
-rw-r--r--idatui/drive.py26
1 files changed, 26 insertions, 0 deletions
diff --git a/idatui/drive.py b/idatui/drive.py
index 6c96335..b6fa641 100644
--- a/idatui/drive.py
+++ b/idatui/drive.py
@@ -165,6 +165,31 @@ def cmd_names(c, args):
or "(no match)"
+def cmd_binaries(c, args):
+ """Project inventory: which binaries, which is active, which have a live
+ worker, how much of each is indexed."""
+ r = c.call("binaries")
+ out = []
+ for b in r["binaries"]:
+ mark = "*" if b["active"] else ("~" if b["resident"] else " ")
+ out.append(f" {mark} {b['label']:<28} indexed={b['indexed']:<7} {b['source']}")
+ if r.get("hops"):
+ out.append(f" (Esc returns to: {' <- '.join(r['hops'])})")
+ return "\n".join(out) + "\n * active ~ worker resident"
+
+
+def cmd_switch(c, args):
+ if not args:
+ raise SystemExit("usage: switch <binary> [addr|name]")
+ p = {"binary": args[0]}
+ if len(args) > 1:
+ a = args[1]
+ p["addr"] = a if a.lower().startswith("0x") else c.call("resolve", name=a)["ea"]
+ r = c.call("switch", **p)
+ fn = (r.get("function") or {}).get("name")
+ return f" now on {r.get('binary') or args[0]}" + (f" @ {fn}" if fn else "")
+
+
def _rename_one(c, old, new):
c.call("goto", target=old, delay_ms=0)
st = c.call("rename", name=new, word=old, delay_ms=0)
@@ -232,6 +257,7 @@ COMMANDS = {
"callees": cmd_callees, "callers": cmd_callers, "names": cmd_names,
"rename": cmd_rename, "mv": cmd_mv, "note": cmd_note, "retype": cmd_retype,
"save": cmd_save, "screen": cmd_screen, "raw": cmd_raw,
+ "binaries": cmd_binaries, "switch": cmd_switch,
}