aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/RPC.md2
-rw-r--r--idatui/app.py3
-rw-r--r--idatui/rpc.py21
-rw-r--r--tests/rpc_smoke.py14
4 files changed, 35 insertions, 5 deletions
diff --git a/docs/RPC.md b/docs/RPC.md
index b8ee87f..a4d077f 100644
--- a/docs/RPC.md
+++ b/docs/RPC.md
@@ -62,7 +62,7 @@ stalls. `target` is a name, `0xADDR`, or omitted (= current function).
| `pseudocode` | `target?` | `{ea,name,code,failed,error,truncated}` — the **whole** decompiled body. |
| `disassembly` | `target?`, `max?=2000` | `{ea,name,total,lines:[{ea,text}]}`. |
| `xrefs_to` | `target`, `limit?=200` | `[{frm,to,type,fn_addr,fn_name}]` — who references it. |
-| `xrefs_from` | `target`, `limit?=200` | `[{frm,to,type,fn_addr,fn_name}]` — what it references. |
+| `xrefs_from` | `target`, `limit?=200` | what it references. For a **function** (name/entry ea): whole-body callees + string/data refs from the decompiler, `[{to,name,string,is_func,type}]`. For an explicit **0xADDR**: address-scoped `[{frm,to,type,fn_addr,fn_name}]`. |
| `resolve` | `name` | `{ea}` (or `{ea:null}`). |
### Semantic verbs
diff --git a/idatui/app.py b/idatui/app.py
index ebf928c..086785c 100644
--- a/idatui/app.py
+++ b/idatui/app.py
@@ -36,7 +36,7 @@ from textual.screen import ModalScreen
from textual.scroll_view import ScrollView
from textual.strip import Strip
from textual.widgets import (
- DataTable, Footer, Header, Input, OptionList, Static, TextArea,
+ DataTable, Footer, Input, OptionList, Static, TextArea,
)
from textual.widgets.option_list import Option
@@ -1763,7 +1763,6 @@ class IdaTui(App):
# -- layout ------------------------------------------------------------ #
def compose(self) -> ComposeResult:
- yield Header(show_clock=True)
with Horizontal(id="panes"):
fp = FunctionsPanel(id="left")
fp.display = False # overlay-first: reveal the docked pane with Ctrl+B
diff --git a/idatui/rpc.py b/idatui/rpc.py
index 73e10d2..426b337 100644
--- a/idatui/rpc.py
+++ b/idatui/rpc.py
@@ -52,7 +52,8 @@ METHODS = {
"pseudocode": "{target?} -> full decompiled body",
"disassembly": "{target?,max?=2000} -> {total,lines:[{ea,text}]}",
"xrefs_to": "{target,limit?=200} -> [{frm,to,type,fn_addr,fn_name}]",
- "xrefs_from": "{target,limit?=200} -> same shape",
+ "xrefs_from": "{target,limit?=200} -> callees/refs; function-scoped for a "
+ "function (decomp refs), address-scoped for a 0xADDR",
"resolve": "{name} -> {ea}",
"keys": "{keys:[str],settle?,timeout?} raw key injection (supports 'wait:<ms>')",
"text": "{text,delay_ms?,settle?} type a literal string into the focused input",
@@ -319,8 +320,24 @@ def xrefs_to(app, target, limit: int = 200) -> list[dict[str, Any]]:
def xrefs_from(app, target, limit: int = 200) -> list[dict[str, Any]]:
+ """References *out of* the target. For a function (a bare name / its entry ea)
+ this is whole-body — the callees, string and data refs from the decompiler
+ (the server's xrefs_from is address-scoped, so it only sees the entry
+ instruction). For an explicit mid-function 0xADDR it stays address-scoped."""
ea = _target_ea(app, target)
- return _xref_dicts(app.program.xrefs_from(ea), limit) if ea is not None else []
+ if ea is None:
+ return []
+ fn = app.program.function_of(ea)
+ if fn is not None and fn.addr == ea: # function target -> decomp outgoing refs
+ out = []
+ for r in app.program.decompile(ea).refs[:limit]:
+ tf = app.program.function_of(r.addr)
+ is_func = bool(tf and tf.addr == r.addr)
+ out.append({"to": r.addr, "name": r.name or (tf.name if tf else None),
+ "string": r.string, "is_func": is_func,
+ "type": "code" if is_func else "data"})
+ return out
+ return _xref_dicts(app.program.xrefs_from(ea), limit)
def resolve(app, name) -> dict[str, Any]:
diff --git a/tests/rpc_smoke.py b/tests/rpc_smoke.py
index 5d24978..fe2b79b 100644
--- a/tests/rpc_smoke.py
+++ b/tests/rpc_smoke.py
@@ -186,6 +186,20 @@ async def run(db):
callee is not None and all("frm" in x for x in callee[1]),
"no referenced function found" if callee is None else "")
+ # xrefs_from a function is whole-body (decomp refs), not just the entry:
+ # find a function that actually calls something.
+ caller = next((f["name"] for f in funcs if f["name"] == "main"), None)
+ xf = []
+ for name in ([caller] if caller else []) + [f["name"] for f in funcs]:
+ xf = (await c.call("xrefs_from", target=name)).get("result", [])
+ if any(x.get("is_func") for x in xf):
+ caller = name
+ break
+ check("xrefs_from a function lists whole-body callees",
+ isinstance(xf, list) and len(xf) > 1
+ and any(x.get("is_func") for x in xf) and all("to" in x for x in xf),
+ f"caller={caller} n={len(xf)}")
+
# --- modal select: open xrefs on the callee, pick the first site --- #
if callee is not None:
f, xr = callee