aboutsummaryrefslogtreecommitdiffstats
path: root/idatui/rpc.py
diff options
context:
space:
mode:
Diffstat (limited to 'idatui/rpc.py')
-rw-r--r--idatui/rpc.py75
1 files changed, 74 insertions, 1 deletions
diff --git a/idatui/rpc.py b/idatui/rpc.py
index 59eec86..7741f48 100644
--- a/idatui/rpc.py
+++ b/idatui/rpc.py
@@ -72,6 +72,9 @@ METHODS = {
"search": "{term,direction?=1} incremental search in the code view",
"select": "{index?} choose the highlighted/nth item in the open modal",
"save": "persist the .i64 (Ctrl+S)",
+ "trace": "{seek|goto|step,over?} navigate the execution trace (seek '!50' = percent)",
+ "binaries": "-> project binaries {label,active,resident,indexed} (project mode)",
+ "switch": "{binary,addr?} make another project binary active (addr also jumps)",
"close": "dismiss a modal (Escape)",
"move": "{dir,n?=1} fast movement (down/up/.../pagedown)",
"cursor": "{line?,col?} set the code-pane cursor directly",
@@ -168,12 +171,14 @@ def snapshot(app) -> dict[str, Any]:
pass
return {
"active": app._active,
- "pref": app._pref,
+ "pref": app._code_mode(), # kept for wire compat; a constant now
"function": ({"ea": cur.ea, "name": cur.name} if cur else None),
"cursor": _cursor_info(app, w),
"status": st,
"filter": app._filter_term,
+ "binary": app._binary, # None outside project mode
"nav_depth": len(app._nav),
+ "hops": list(getattr(app, "_hops", [])),
"dirty": bool(app._dirty),
"modal": _modal_snapshot(app),
**_readiness(app),
@@ -526,6 +531,74 @@ class RpcServer:
if method == "functions":
return functions(app, params.get("filter"), int(params.get("limit", 50)))
+ # -- projects ------------------------------------------------------ #
+ if method == "trace":
+ if app._trace is None:
+ raise ValueError("no trace loaded (launch with --trace FILE)")
+ t = app._trace
+ if "seek" in params:
+ v = params["seek"]
+ # "!50" seeks a percentage, like Tenet's timestamp shell.
+ if isinstance(v, str) and v.startswith("!"):
+ idx = int(float(v[1:]) * (t.length - 1) / 100.0)
+ else:
+ idx = int(str(v).replace(",", ""), 0) if isinstance(v, str) else int(v)
+ app._seek(idx)
+ elif "goto" in params: # first execution of an address/name
+ tgt = params["goto"]
+ ea = (int(str(tgt), 0) if str(tgt).lower().startswith("0x")
+ else app.program.resolve(str(tgt)))
+ first = t.first_execution(ea)
+ if first is None:
+ raise ValueError(f"{tgt} never executed in this trace")
+ app._seek(first)
+ elif "step" in params:
+ n = int(params.get("step") or 1)
+ over = bool(params.get("over"))
+ for _ in range(abs(n)):
+ (app._step_over if over else app._step)(1 if n > 0 else -1)
+ await settle(app, timeout=float(params.get("timeout", 20.0)))
+ snap = snapshot(app)
+ snap["trace"] = {"idx": app._t, "length": t.length,
+ "pc": hex(t.ip(app._t)),
+ "changed": sorted(t.changed(app._t))}
+ return snap
+
+ if method == "binaries":
+ if app._project is None:
+ raise ValueError("not a project session (launch with --project)")
+ counts = app._index.counts() if app._index is not None else {}
+ resident = set(app._pool.resident()) if app._pool is not None else set()
+ return {"active": app._binary, "hops": list(app._hops),
+ "binaries": [{"label": r.label, "source": r.source,
+ "active": r.label == app._binary,
+ "resident": r.label in resident,
+ "indexed": int(counts.get(r.label, 0))}
+ for r in app._project.refs]}
+
+ if method == "switch":
+ if app._project is None:
+ raise ValueError("not a project session (launch with --project)")
+ label = str(params.get("binary") or params.get("label") or "")
+ if app._project.by_label(label) is None:
+ have = ", ".join(r.label for r in app._project.refs)
+ raise ValueError(f"no such binary {label!r} (have: {have})")
+ addr = params.get("addr")
+ if label == app._binary and addr is None:
+ return snapshot(app)
+ if addr is None:
+ app._switch_binary(label)
+ else:
+ # Same path a project search hit takes, so it records a hop and
+ # Esc comes back here.
+ app._switch_then_goto(label, int(str(addr), 0)
+ if isinstance(addr, str) else int(addr))
+ await settle(app, lambda: app._binary == label
+ and app._func_index is not None
+ and app._func_index.complete,
+ timeout=float(params.get("timeout", 300.0)))
+ return snapshot(app)
+
# -- structured introspection (heavy: run off the UI loop) -------- #
loop = asyncio.get_running_loop()
if method == "pseudocode":