aboutsummaryrefslogtreecommitdiffstats
path: root/idatui/index.py
diff options
context:
space:
mode:
authorblasty <blasty@local>2026-07-25 22:33:16 +0200
committerblasty <blasty@local>2026-07-25 22:33:16 +0200
commitdcb76f92e8851f747e17cae848e800a394a073e1 (patch)
tree7b2ef9e92049e7a394f707e313cb79ba65309ced /idatui/index.py
parentnav: one notion of "which pane you're in" — delete _pref (diff)
downloadida-tui-dcb76f92e8851f747e17cae848e800a394a073e1.tar.gz
ida-tui-dcb76f92e8851f747e17cae848e800a394a073e1.tar.xz
ida-tui-dcb76f92e8851f747e17cae848e800a394a073e1.zip
projects phase 3: follow an import into the binary that implements it
Following a call to strcmp reached the PLT/extern entry and stopped there — Hex-Rays has nothing to decompile, because the code lives in a library this binary only references. With the other binary open in the same project we already had everything needed to cross that gap; we just weren't indexing it. Index each binary's imports and exports (KIND_IMPORT / KIND_EXPORT) alongside its functions and strings. On a follow, _import_stub asks whether the target address is one of this binary's import stubs; if so _cross_binary_impl asks the index who exports that name, and we switch there instead of landing on the thunk. Verified end to end on a real echo + libc project: Enter on `strrchr(a1, 47)` in echo's pseudocode switches to libc.so.6 and lands on strrchr at 0xaf960. Three things it turns on: * ELF symbol versioning. The importer sees strrchr@@GLIBC_2.2.5 while the provider may export any of three spellings, so raw names resolve almost nothing. domain.link_name() cuts at the first '@'; Linkage.raw keeps what IDA reported, which is what the listing shows. * Exact match, not substring — ProjectIndex.exact(), so `read` doesn't bind to pread/read_line/thread_start. It also answers below the 3-char trigram floor, and plenty of real exports are that short. * Resolution reads the on-disk index, so a provider resolves while its worker is evicted. That's what the index was for. When nothing in the project provides the symbol _follow_import declines and the normal navigation runs: landing on the stub is still the honest answer, and a single-binary session is unchanged. The PLT-stub PRESENTATION item stays open — an unprovided import should say "imported, provider not in project" rather than show a decompiler error. server/patch_server.py gains list_linkage (idautils.Entries + enum_import_names); a worker without it degrades to no linkage rather than failing. tests: index join +8 (exact vs substring, short names, exclude-self, reverse join, kind isolation, forget unresolves) and link_name +4. 36/0 index, 195/0 scenarios, 23/0 project UI, 33/0 project, 22/0 pool.
Diffstat (limited to 'idatui/index.py')
-rw-r--r--idatui/index.py34
1 files changed, 34 insertions, 0 deletions
diff --git a/idatui/index.py b/idatui/index.py
index e7e4a41..751ad1f 100644
--- a/idatui/index.py
+++ b/idatui/index.py
@@ -32,6 +32,9 @@ MIN_TRIGRAM = 3
KIND_FUNC = "func"
KIND_STRING = "string"
+#: Cross-binary linkage: what a binary takes from, and offers to, other modules.
+KIND_IMPORT = "import"
+KIND_EXPORT = "export"
@dataclass(frozen=True)
@@ -152,6 +155,37 @@ class ProjectIndex:
return [] # malformed FTS expression: treat as no matches
return [Hit(binary=b, kind=k, addr=int(a), text=t) for b, k, a, t in rows]
+ def exact(self, name: str, kind: str, exclude: str | None = None) -> list[Hit]:
+ """Every entry whose text is EXACTLY ``name``, for the linkage join.
+
+ Deliberately not ``search()``: an import must resolve to the export of
+ that name, not to everything containing it (``read`` would otherwise
+ match ``pread``, ``read_line``, ``thread_start``). Exact match also
+ works below the trigram floor, which matters — plenty of real exports
+ are one or two characters.
+ """
+ n = (name or "").strip()
+ if not n:
+ return []
+ sql = "SELECT binary, kind, addr, text FROM entries WHERE text = ? AND kind = ?"
+ args: list = [n, kind]
+ if exclude:
+ sql += " AND binary <> ?"
+ args.append(exclude)
+ rows = self._db.execute(sql + " ORDER BY binary, addr", args).fetchall()
+ return [Hit(binary=b, kind=k, addr=int(a), text=t) for b, k, a, t in rows]
+
+ def providers(self, name: str, exclude: str | None = None) -> list[Hit]:
+ """Binaries in the project that EXPORT ``name`` (skip ``exclude``, the
+ binary asking). This is the 'follow an import to its implementation'
+ half of the join."""
+ return self.exact(name, KIND_EXPORT, exclude)
+
+ def importers(self, name: str, exclude: str | None = None) -> list[Hit]:
+ """Binaries in the project that IMPORT ``name`` — 'who in the project
+ calls this export'."""
+ return self.exact(name, KIND_IMPORT, exclude)
+
# -- introspection ------------------------------------------------------ #
def counts(self) -> dict[str, int]:
"""Indexed entry count per binary."""