diff options
Diffstat (limited to 'idatui')
| -rw-r--r-- | idatui/__init__.py | 6 | ||||
| -rw-r--r-- | idatui/app.py | 42 | ||||
| -rw-r--r-- | idatui/domain.py | 28 | ||||
| -rw-r--r-- | idatui/errors.py | 2 | ||||
| -rw-r--r-- | idatui/launch.py | 16 | ||||
| -rw-r--r-- | idatui/nexus_client.py (renamed from idatui/codemode_client.py) | 88 | ||||
| -rw-r--r-- | idatui/pane.py | 18 | ||||
| -rw-r--r-- | idatui/pool.py | 14 | ||||
| -rw-r--r-- | idatui/project.py | 14 | ||||
| -rw-r--r-- | idatui/remote_ops.py | 4 | ||||
| -rw-r--r-- | idatui/remote_tools.py | 12 |
11 files changed, 121 insertions, 123 deletions
diff --git a/idatui/__init__.py b/idatui/__init__.py index 7da28b3..e89d8f6 100644 --- a/idatui/__init__.py +++ b/idatui/__init__.py @@ -1,4 +1,4 @@ -"""idatui — a keyboard-first TUI using shared IDA Code Mode databases.""" +"""idatui — a keyboard-first TUI using shared IDA Nexus databases.""" from .errors import ( IDAError, @@ -10,7 +10,7 @@ from .errors import ( IDASessionError, Session, ) -from .codemode_client import CodeModeClient +from .nexus_client import NexusClient from .domain import ( Program, FunctionIndex, @@ -25,7 +25,7 @@ from .domain import ( ) __all__ = [ - "CodeModeClient", + "NexusClient", "Program", "FunctionIndex", "DisasmModel", diff --git a/idatui/app.py b/idatui/app.py index e4c2936..05b428a 100644 --- a/idatui/app.py +++ b/idatui/app.py @@ -10,7 +10,7 @@ Design notes: without ever materializing 52k lines in a widget. * All network/domain work runs in Textual worker threads; the UI never blocks. * An address-history stack backs Enter (follow) / Esc (back), IDA-style. -* Database lifecycle is lease-based through ida_codemode: matching GUI sessions +* Database lifecycle is lease-based through ida_nexus: matching GUI sessions are reused, otherwise a shared managed idalib worker is opened on demand. """ @@ -55,7 +55,7 @@ from .highlight import CTextArea, highlight_c from .journal import Journal from .errors import IDAConnectionError -from .codemode_client import CodeModeClient, registered_database +from .nexus_client import NexusClient, registered_database from .domain import Func, Head, ListingModel, Program, Struct # Styles for the disassembly listing. @@ -1087,7 +1087,7 @@ class ListingView(SearchMixin, NavMixin, ColumnCursor, ScrollView, can_focus=Tru def _span_segments(h: Head, fallback: Style): """Segments for a row's disassembly text. - Uses IDA's own token classification when Code Mode supplies it; falls + Uses IDA's own token classification when IDA Nexus supplies it; falls back to the mnemonic/rest split when spans are absent or disagree with the plain text. """ @@ -5238,7 +5238,7 @@ class IdaTui(App): self._open_path = open_path self._ttl = ttl self._load_args = load_args or "" # first-open options for a headerless blob - self._new_database = False # Ctrl+L asks Code Mode for a fresh IDB + self._new_database = False # Ctrl+L asks IDA Nexus for a fresh IDB self._title = (os.path.basename(open_path) if open_path else "") #: Where we are in the execution trace, and everything that moves us. #: Owns the trace state; the _trace/_t/_trail_* properties below @@ -5247,7 +5247,7 @@ class IdaTui(App): self._do_keepalive = keepalive self._rpc_path = rpc_path self._rpc = None - self.client: CodeModeClient | None = None + self.client: NexusClient | None = None self.program: Program | None = None self._loading_screen: LoadingScreen | None = None self._ka = None @@ -5288,7 +5288,7 @@ class IdaTui(App): self.journal = Journal() self._xref_focus_name: str | None = None self._dirty = False - # One subscription for the active database. CodeModeClient debounces + # One subscription for the active database. NexusClient debounces # bursts off the Textual worker pool; the callback re-enters here on the # UI thread to invalidate and reload the visible models. self._idb_event_watch = None @@ -5362,7 +5362,7 @@ class IdaTui(App): if self._rpc_path: self._start_rpc() # A file no loader recognises has to be described before it can be - # opened, so ask BEFORE Code Mode creates it — once IDA has made a database + # opened, so ask BEFORE IDA Nexus creates it — once IDA has made a database # the answer is baked in and changing it requires a fresh-IDB reopen. if self._project is not None: ref = self._pending_load_ref() @@ -5435,7 +5435,7 @@ class IdaTui(App): ref = self._project.by_label(self._binary) if ref is not None: path, label = ref.source, ref.label - # Release our lease first. Code Mode waits for a managed worker's final + # Release our lease first. IDA Nexus waits for a managed worker's final # lease grace, then creates the replacement IDB atomically. A GUI-backed # database is rejected by _can_reload(): the TUI must never close it. self._release_database() @@ -5634,7 +5634,7 @@ class IdaTui(App): return len(text) # -- live refresh from shared IDB changes ----------------------------- # - def _start_idb_event_watch(self, client: CodeModeClient) -> None: + def _start_idb_event_watch(self, client: NexusClient) -> None: self._stop_idb_event_watch() watch = getattr(client, "watch_idb_events", None) if watch is None: # IDA-free test doubles and pre-event adapters @@ -5661,7 +5661,7 @@ class IdaTui(App): watcher.close() def _idb_event_watch_failed( - self, client: CodeModeClient, error: BaseException + self, client: NexusClient, error: BaseException ) -> None: if client is not self.client: return @@ -5685,7 +5685,7 @@ class IdaTui(App): return anchor def _refresh_idb_events( - self, client: CodeModeClient, events: tuple[dict, ...] + self, client: NexusClient, events: tuple[dict, ...] ) -> None: """Invalidate once per external edit burst and reload the active surface.""" program = self.program @@ -5768,7 +5768,7 @@ class IdaTui(App): # -- connection loss / recovery --------------------------------------- # def _handle_exception(self, error: BaseException) -> None: - """Intercept a lost Code Mode lease so the app can rediscover the DB. + """Intercept a lost IDA Nexus lease so the app can rediscover the DB. Everything unrelated to database connectivity crashes as usual. """ @@ -5812,11 +5812,11 @@ class IdaTui(App): return if self._project is not None and self._binary is not None: ref = self._project.by_label(self._binary) - client = CodeModeClient( + client = NexusClient( ref.staged, ttl=self._ttl, load_args=ref.load_args, output_database=ref.db, spawn=False) else: - client = CodeModeClient( + client = NexusClient( self._open_path, ttl=self._ttl, load_args=self._load_args, spawn=False) client.connect(progress=lambda m: self.app.call_from_thread( @@ -5826,7 +5826,7 @@ class IdaTui(App): return self.app.call_from_thread(self._after_reconnect, client, Program(client)) - def _after_reconnect(self, client: "CodeModeClient", program: "Program") -> None: + def _after_reconnect(self, client: "NexusClient", program: "Program") -> None: old_client, old_program = self.client, self.program self._stop_idb_event_watch() if old_program is not None: @@ -5886,7 +5886,7 @@ class IdaTui(App): self._load_functions() def _open_database_client(self): # type: ignore[no-untyped-def] - """Attach through Code Mode, reusing a GUI or managed idalib database.""" + """Attach through IDA Nexus, reusing a GUI or managed idalib database.""" if self._pool is not None: # project mode: the pool owns the leases label = self._binary or self._project.refs[0].label client = self._pool.get(label, progress=lambda m: @@ -5898,13 +5898,13 @@ class IdaTui(App): return client if not self._open_path: self.app.call_from_thread( - self._status, "Code Mode needs a database or executable path") + self._status, "IDA Nexus needs a database or executable path") self.app.call_from_thread(self._dismiss_loading) return None base = os.path.basename(self._open_path) self.app.call_from_thread( - self._status, f"discovering Code Mode database for {base}…") - client = CodeModeClient(self._open_path, ttl=self._ttl, + self._status, f"discovering IDA Nexus database for {base}…") + client = NexusClient(self._open_path, ttl=self._ttl, load_args=self._load_args, new_database=self._new_database) client.connect(progress=lambda m: self.app.call_from_thread( @@ -5978,7 +5978,7 @@ class IdaTui(App): @work(thread=True, exclusive=True, group="index") def _index_binary(self) -> None: """Fold this binary's symbols + strings into the project index, so it can - be searched later even when its Code Mode lease is gone.""" + be searched later even when its IDA Nexus lease is gone.""" if self._index is None or self._project is None or self._binary is None: return ref = self._project.by_label(self._binary) @@ -6081,7 +6081,7 @@ class IdaTui(App): cursor=0, push=True, is_region=True) def _can_reload(self) -> bool: - """Whether Code Mode can replace this IDB with different options. + """Whether IDA Nexus can replace this IDB with different options. A GUI database is owned by the user and has no remote close/rollback route. Managed idalib databases can be released and reopened fresh. diff --git a/idatui/domain.py b/idatui/domain.py index b042332..1e5a863 100644 --- a/idatui/domain.py +++ b/idatui/domain.py @@ -1,4 +1,4 @@ -"""Domain / paging layer: address-centric models over IDA Code Mode. +"""Domain / paging layer: address-centric models over IDA Nexus. This is where the "millions of lines" problem is solved, so the TUI widgets only ever see a viewport-sized slice. Every hard-won constraint from @@ -7,7 +7,7 @@ ever see a viewport-sized slice. Every hard-won constraint from * Page sizes remain bounded so remote execution returns viewport-scale JSON. * Pagination advances by the number of rows actually returned. * Deep head walks are block-cached (revisits are free) and neighboring blocks - prefetch through the thread-safe Code Mode client. + prefetch through the thread-safe IDA Nexus client. * Expensive function totals are fetched once and cached. * Decompilation failures are surfaced as data, not application crashes. @@ -31,7 +31,7 @@ from . import remote_ops from .errors import IDAToolError if TYPE_CHECKING: # type hint only - from .codemode_client import CodeModeClient + from .nexus_client import NexusClient # Clamps derived from measured caps (list ~700, disasm ~500). Margin included. LIST_PAGE = 500 @@ -90,7 +90,7 @@ class Line: class Head(NamedTuple): - """One flat-listing item (from the Code Mode ``heads`` operation): a code + """One flat-listing item (from the IDA Nexus ``heads`` operation): a code instruction, a data item, or an undefined byte run. A ``NamedTuple`` rather than a dataclass because this is by far the @@ -111,7 +111,7 @@ class Head(NamedTuple): name: str | None = None raw: bytes | None = None # opcode/item bytes (filled in for code by the model) #: [(kind, text)] from IDA's own colour tags — mnem/reg/num/name/str/punct/… - #: None when Code Mode didn't provide them (or the spans + #: None when IDA Nexus didn't provide them (or the spans #: disagreed with the plain text, in which case the text wins). #: #: Held exactly as it came off the wire, and **read-only**. The worker @@ -693,7 +693,7 @@ class ListingModel: """A flat, IDA-style disassembly *listing* over one segment: code, data and undefined heads interleaved, unlike ``DisasmModel`` (one function, code only). - Backed by the Code Mode adapter's ``heads`` operation, which walks item heads + Backed by the IDA Nexus adapter's ``heads`` operation, which walks item heads and renders each via ``generate_disasm_line``. The segment is walked lazily in forward pages (``FunctionIndex`` style); line index == position in the walked head list. Random access to an address is O(distance-from-seg-start) the @@ -701,7 +701,7 @@ class ListingModel: on demand as the viewport scrolls. Synchronous + thread-safe. """ - PAGE = 500 # viewport-scale heads per Code Mode execution + PAGE = 500 # viewport-scale heads per IDA Nexus execution #: Generation marker for a skeleton (text-less) page. Never equals a real #: _text_gen, which counts up from 0, so such a page always reads as stale. _SKELETON_GEN = -1 @@ -1434,7 +1434,7 @@ class HexModel: class Program: """The bound analysis session: models, caches, and a small prefetch pool.""" - def __init__(self, client: "CodeModeClient", prefetch_workers: int = 2): + def __init__(self, client: "NexusClient", prefetch_workers: int = 2): self.client = client self._pool = ThreadPoolExecutor( max_workers=prefetch_workers, thread_name_prefix="idatui-prefetch" @@ -1485,7 +1485,7 @@ class Program: """Sorted raw segment map [(start, end, file_off, name)] — the single source for sections()/file_regions()/image_range. Cached. - Uses the Code Mode adapter's ``file_regions`` operation (a plain segment + Uses the IDA Nexus adapter's ``file_regions`` operation (a plain segment walk, ~ms), avoiding broad binary surveys on the hex-pane open path. """ if self._segments_cache is not None: @@ -1573,7 +1573,7 @@ class Program: def read_bytes(self, ea: int, n: int) -> bytes: """Raw bytes [ea, ea+n) from IDA (gaps read as zero). - The Code Mode adapter returns one contiguous hex string (C-speed in IDA). + The IDA Nexus adapter returns one contiguous hex string (C-speed in IDA). A legacy ``get_bytes`` decoding fallback remains for alternate clients. """ if n <= 0: @@ -1778,7 +1778,7 @@ class Program: except IDAToolError as e: msg = e.message if "not found" in msg.lower() and "del_type" in msg: - return "the connected Code Mode runtime cannot delete local types" + return "the connected IDA Nexus runtime cannot delete local types" return msg # -- disassembly ------------------------------------------------------- # @@ -1795,7 +1795,7 @@ class Program: """Drop local and Hex-Rays caches before an explicit view refresh. Normal edit paths use generation-based invalidation. Ctrl+R is also for - changes made by another Code Mode/IDA client, for which this Program has + changes made by another IDA Nexus/IDA client, for which this Program has seen no generation bump, so it must explicitly ask Hex-Rays to discard its cached cfunc. """ @@ -1809,7 +1809,7 @@ class Program: pass def decompile(self, ea: int, refresh: bool = False) -> Decompilation: - """Full pseudocode for a function, returned directly by Code Mode.""" + """Full pseudocode for a function, returned directly by IDA Nexus.""" if not refresh: with self._lock: hit = self._decomp.get(ea) @@ -2011,7 +2011,7 @@ class Program: def define_func(self, ea: int) -> dict: """Create a function starting at ``ea`` (IDA's 'p'). - Prefers the Code Mode operation, which works out the end when IDA can't; + Prefers the IDA Nexus operation, which works out the end when IDA can't; falls back to a plain create for alternate clients. """ try: diff --git a/idatui/errors.py b/idatui/errors.py index 7632ade..ab1365a 100644 --- a/idatui/errors.py +++ b/idatui/errors.py @@ -1,6 +1,6 @@ """TUI-facing error hierarchy and lightweight database session model. -The Code Mode adapter normalizes ``ida_codemode`` transport and execution +The IDA Nexus adapter normalizes ``ida_nexus`` transport and execution errors into these types so the domain and Textual layers do not depend on HTTP or registry implementation details. """ diff --git a/idatui/launch.py b/idatui/launch.py index e3cb091..a0201a7 100644 --- a/idatui/launch.py +++ b/idatui/launch.py @@ -1,6 +1,6 @@ -"""One-shot launcher for the IDA Code Mode-backed TUI. +"""One-shot launcher for the IDA Nexus-backed TUI. -A path first resolves to a registered GUI database; when none matches, Code Mode +A path first resolves to a registered GUI database; when none matches, IDA Nexus reuses or starts a managed idalib worker. With no path, a single registered database is selected automatically. @@ -34,9 +34,9 @@ def _log(msg: str) -> None: def _registered_databases() -> tuple[list[dict], list[dict]]: - """Ready and blocked Code Mode registrations, with normalized errors.""" + """Ready and blocked IDA Nexus registrations, with normalized errors.""" try: - from ida_codemode import InstanceState, discover_databases + from ida_nexus import InstanceState, discover_databases ready: list[dict] = [] blocked: list[dict] = [] @@ -70,7 +70,7 @@ def main(argv: list[str] | None = None) -> int: help="open a multi-binary project (created from the given " "binaries if FILE doesn't exist)") p.add_argument("--ttl", type=int, default=1800, - help="deprecated compatibility option (Code Mode uses leases)") + help="deprecated compatibility option (IDA Nexus uses leases)") p.add_argument("--no-keepalive", action="store_true", help="deprecated compatibility option (the lease is the heartbeat)") p.add_argument("--rpc", metavar="PATH", @@ -87,7 +87,7 @@ def main(argv: list[str] | None = None) -> int: g.add_argument("--base", metavar="ADDR", help="load address, e.g. 0x8000000 (any base; NOT paragraphs)") g.add_argument("--ida-args", metavar="STR", dest="ida_args", - help="legacy switches; only Code Mode-representable -p/-b/-T are accepted") + help="legacy switches; only IDA Nexus-representable -p/-b/-T are accepted") args = p.parse_args(argv) load: dict = {} @@ -166,10 +166,10 @@ def main(argv: list[str] | None = None) -> int: _log(f"attaching to registered {item.get('backend')} database: {binary}") elif not ready: detail = f" ({blocked[0].get('error')})" if blocked else "" - _log(f"no registered Code Mode database; pass a binary path{detail}") + _log(f"no registered IDA Nexus database; pass a binary path{detail}") return 2 else: - _log("several Code Mode databases are registered; pass one of these paths:") + _log("several IDA Nexus databases are registered; pass one of these paths:") for item in ready: _log(f" {item.get('exe_path') or item.get('idb_path')} " f"[{item.get('backend')}, {item.get('record_id')}]") diff --git a/idatui/codemode_client.py b/idatui/nexus_client.py index e95238f..44eb1b1 100644 --- a/idatui/codemode_client.py +++ b/idatui/nexus_client.py @@ -1,4 +1,4 @@ -"""Client adapter from ida-tui's domain operations to IDA Code Mode. +"""Client adapter from ida-tui's domain operations to IDA Nexus. ``DatabaseHandle`` is the lifecycle boundary: it discovers an already-registered GUI database, reuses a shared managed idalib worker, or starts one when needed. @@ -6,9 +6,9 @@ The TUI never owns or terminates an IDA process. Closing this client releases only its lease. Remote operations are ordinary typed Python functions declared in -``idatui.remote_ops``. Code Mode installs their content-addressed modules once -per handle; subsequent calls send only encoded arguments. The optimized -IDAPython listing/decompiler implementation remains real source in +``idatui.remote_ops``. IDA Nexus installs their content-addressed modules once +per IDA Python interpreter; subsequent calls send only encoded arguments. The +optimized IDAPython listing/decompiler implementation remains real source in ``idatui.remote_tools`` and is installed through the same module interface. """ @@ -23,55 +23,55 @@ from typing import Any from .errors import IDAConnectionError, IDATimeoutError, IDAToolError, Session -# ida_codemode is imported EAGERLY-IF-PRESENT but never at hard import cost. +# ida_nexus is imported EAGERLY-IF-PRESENT but never at hard import cost. # # The paging/graph/trace layers and their offline test suites must keep importing -# `idatui` on a machine with no IDA and no Code Mode installed -- that is the +# `idatui` on a machine with no IDA and no IDA Nexus installed -- that is the # house rule the stdlib-only worker client used to satisfy for free, and # `tests/run.py --fast` (380 checks, any python3) depends on it. A hard top-level # import here makes the whole package unimportable, so the failure is deferred to # the first operation that genuinely needs the library. -_CODEMODE_ERROR: Exception | None = None +_NEXUS_ERROR: Exception | None = None try: - from ida_codemode import ( - CodeModeConnectionError, + from ida_nexus import ( DatabaseBusyError, DatabaseDisconnectedError, DatabaseHandle, DatabaseInstance, DatabaseOpenOptions, + NexusConnectionError, RemoteError, find_database_owner, wait_database_released, ) except ImportError as _exc: # library absent: usable only for offline layers - _CODEMODE_ERROR = _exc + _NEXUS_ERROR = _exc # Bound to None rather than left undefined so the names stay patchable: the # offline contract tests inject a fake DatabaseHandle here. - CodeModeConnectionError = DatabaseDisconnectedError = RemoteError = None # type: ignore[assignment,misc] + NexusConnectionError = DatabaseDisconnectedError = RemoteError = None # type: ignore[assignment,misc] DatabaseBusyError = DatabaseHandle = DatabaseInstance = None # type: ignore[assignment,misc] DatabaseOpenOptions = find_database_owner = wait_database_released = None # type: ignore[assignment] -def _require_codemode() -> None: - """Raise an actionable error when the Code Mode library is missing. +def _require_nexus() -> None: + """Raise an actionable error when the IDA Nexus library is missing. Gated on the binding, not on the original import result, so a test that injects a fake ``DatabaseHandle`` exercises the real adapter logic. """ if DatabaseHandle is None: raise IDAConnectionError( - "ida-codemode is not installed in this environment " - f"({_CODEMODE_ERROR}). Install it (e.g. `uv sync`, or " - "`pip install ida-codemode`) so ida-tui can lease a " + "ida-nexus is not installed in this environment " + f"({_NEXUS_ERROR}). Install it (e.g. `uv sync`, or " + "`pip install ida-nexus`) so ida-tui can lease a " "database." - ) from _CODEMODE_ERROR + ) from _NEXUS_ERROR def database_owner(idb_path: str, staged_path: str | None = None): - """The Code Mode instance that owns ``idb_path``/``staged_path``, else None. + """The IDA Nexus instance that owns ``idb_path``/``staged_path``, else None. - Returns None when the Code Mode library is absent: with no library there is + Returns None when the IDA Nexus library is absent: with no library there is no client in this environment that could be holding the database, and the IDA-free layers (project staging) must keep working. Discovery errors with the library installed still propagate because unknown ownership is unsafe. @@ -89,8 +89,8 @@ def database_owner(idb_path: str, staged_path: str | None = None): def registered_database(path: str, output_database: str | None = None) -> bool: - """Whether a live/lock-held Code Mode instance owns this target.""" - _require_codemode() + """Whether a live/lock-held IDA Nexus instance owns this target.""" + _require_nexus() return ( find_database_owner( path, @@ -115,9 +115,9 @@ class _NoopKeepAlive: def _parse_load_args(value: str) -> tuple[str | None, int | None, str | None]: - """Translate ida-tui's legacy first-open switches to Code Mode options. + """Translate ida-tui's legacy first-open switches to IDA Nexus options. - Code Mode has typed options for processor, natural loading address and file + IDA Nexus has typed options for processor, natural loading address and file type. It deliberately has no arbitrary command-line escape hatch; reject switches we cannot represent instead of silently loading a blob wrongly. """ @@ -146,7 +146,7 @@ def _parse_load_args(value: str) -> tuple[str | None, int | None, str | None]: if unsupported: joined = " ".join(unsupported) raise ValueError( - "ida-codemode cannot represent arbitrary IDA load options: " + "ida-nexus cannot represent arbitrary IDA load options: " f"{joined!r}; use processor/base/file type options instead" ) return processor, loading_address, file_type @@ -155,7 +155,7 @@ def _parse_load_args(value: str) -> tuple[str | None, int | None, str | None]: class IDBEventListener: """Debounced, closeable delivery of another client's IDB changes. - Code Mode's subscription is a blocking iterator, so one daemon thread reads + IDA Nexus's subscription is a blocking iterator, so one daemon thread reads it and a second waits for a quiet period before handing a batch to the UI. Keeping the debounce here avoids a permanent Textual worker (which would make the app's worker-idle contract impossible) and bounds refresh work to @@ -164,7 +164,7 @@ class IDBEventListener: def __init__( self, - client: "CodeModeClient", + client: "NexusClient", callback: Callable[[tuple[dict[str, Any], ...]], None], *, on_error: Callable[[BaseException], None] | None = None, @@ -267,8 +267,8 @@ class IDBEventListener: subscription.close() -class CodeModeClient: - """A leased GUI/idalib database accessed through ``ida_codemode``.""" +class NexusClient: + """A leased GUI/idalib database accessed through ``ida_nexus``.""" def __init__( self, @@ -298,23 +298,23 @@ class CodeModeClient: self._last_instance: DatabaseInstance | None = None self._connect_lock = threading.Lock() - def connect(self, timeout: float = 1800.0, progress=None) -> "CodeModeClient": - _require_codemode() + def connect(self, timeout: float = 1800.0, progress=None) -> "NexusClient": + _require_nexus() with self._connect_lock: handle = self._handle if handle is not None: if handle.connected: return self raise IDAConnectionError( - "Code Mode database disconnected; explicit rediscovery required" + "IDA Nexus database disconnected; explicit rediscovery required" ) if progress: progress( - f"discovering Code Mode database for {os.path.basename(self._path)}…" + f"discovering IDA Nexus database for {os.path.basename(self._path)}…" ) try: # A Ctrl+L reload releases its current managed-worker lease, but - # that worker remains registered during Code Mode's final-lease + # that worker remains registered during IDA Nexus's final-lease # grace period. Retry only that known handoff window. A GUI or # another long-lived client remains busy and yields a clear # failure rather than being modified underneath its owner. @@ -329,7 +329,7 @@ class CodeModeClient: output_database=self._output_database, processor=self._processor, # The natural byte address is converted to IDA's - # paragraph-based -b value by Code Mode. + # paragraph-based -b value by IDA Nexus. image_base=self._loading_address, file_type=self._file_type, new_database=self._new_database, @@ -341,7 +341,7 @@ class CodeModeClient: raise if progress: progress( - "waiting for the previous Code Mode lease to close…" + "waiting for the previous IDA Nexus lease to close…" ) owner = find_database_owner( self._path, @@ -389,15 +389,15 @@ class CodeModeClient: return handle is not None and handle.owns_event(event) def subscribe_idb_events(self): - """Open Code Mode's closeable IDB-change iterator.""" + """Open IDA Nexus's closeable IDB-change iterator.""" if not self.connected: self.connect() handle = self._handle if handle is None: - raise IDAConnectionError("Code Mode database is not connected") + raise IDAConnectionError("IDA Nexus database is not connected") try: return handle.subscribe_idb_events() - except (DatabaseDisconnectedError, CodeModeConnectionError) as exc: + except (DatabaseDisconnectedError, NexusConnectionError) as exc: raise self._connection_error(exc) from exc def watch_idb_events( @@ -425,7 +425,7 @@ class CodeModeClient: self.connect() handle = self._handle if handle is None: - raise IDAConnectionError("Code Mode database is not connected") + raise IDAConnectionError("IDA Nexus database is not connected") try: return remote(handle, **args) except RemoteError as exc: @@ -435,7 +435,7 @@ class CodeModeClient: if exc.code == "operation_timeout": raise IDATimeoutError(message) from exc raise IDAToolError(name, message) from exc - except (DatabaseDisconnectedError, CodeModeConnectionError) as exc: + except (DatabaseDisconnectedError, NexusConnectionError) as exc: raise self._connection_error(exc) from exc def save_database(self) -> dict[str, Any]: @@ -443,12 +443,12 @@ class CodeModeClient: self.connect() handle = self._handle if handle is None: - raise IDAConnectionError("Code Mode database is not connected") + raise IDAConnectionError("IDA Nexus database is not connected") try: return handle.save_database() except RemoteError as exc: raise IDAToolError("save_database", str(exc)) from exc - except (DatabaseDisconnectedError, CodeModeConnectionError) as exc: + except (DatabaseDisconnectedError, NexusConnectionError) as exc: raise self._connection_error(exc) from exc def discard_database(self, timeout: float = 5.0) -> bool: @@ -477,7 +477,7 @@ class CodeModeClient: time.sleep(0.05) continue raise IDAToolError("shutdown_database", str(exc)) from exc - except (DatabaseDisconnectedError, CodeModeConnectionError) as exc: + except (DatabaseDisconnectedError, NexusConnectionError) as exc: raise self._connection_error(exc) from exc def health(self) -> dict[str, Any]: @@ -544,7 +544,7 @@ class CodeModeClient: return False return wait_database_released(instance, timeout) - def __enter__(self) -> "CodeModeClient": + def __enter__(self) -> "NexusClient": return self.connect() def __exit__(self, *exc) -> None: diff --git a/idatui/pane.py b/idatui/pane.py index c31a93c..1eee847 100644 --- a/idatui/pane.py +++ b/idatui/pane.py @@ -24,7 +24,7 @@ per pane in the registry, so stop/list/capture/keys keep working across both python -m idatui.pane keys --pane <pane> Escape Requires: running inside tmux or zellij. Each pane leases a registered GUI or -shared managed idalib database through Code Mode. Uses ~/ida-venv/bin/python for +shared managed idalib database through IDA Nexus. Uses ~/ida-venv/bin/python for the TUI (needs textual) unless --python / IDATUI_PYTHON says otherwise. """ from __future__ import annotations @@ -250,8 +250,8 @@ def _pane_keys(pane: str, keys: list[str], mux: str | None = None) -> None: subprocess.run(["tmux", "send-keys", "-t", pane, *keys], check=True) -# Code Mode owns database process lifetime: a closed pane drops its lease at the -# socket/kernel boundary and Code Mode decides whether a managed worker still +# IDA Nexus owns database process lifetime: a closed pane drops its lease at the +# socket/kernel boundary and IDA Nexus decides whether a managed worker still # has clients. There is nothing for the pane layer to reap. @@ -261,7 +261,7 @@ def _count_live_panes() -> int: def _reap_orphan_workers(force: bool = False) -> int: - """Compatibility no-op: Code Mode workers are shared and lease-managed.""" + """Compatibility no-op: IDA Nexus workers are shared and lease-managed.""" del force return 0 @@ -294,7 +294,7 @@ def spawn(args) -> int: print(f"error: no such project: {project}", file=sys.stderr) return 2 - # The pane owns only the TUI. Code Mode's lease cleanup handles crashes; + # The pane owns only the TUI. IDA Nexus's lease cleanup handles crashes; # kill-pane must never reap a shared GUI/idalib database. if project is not None: # launch takes: --project FILE [binaries...]; extra binaries are added to @@ -347,7 +347,7 @@ def _wait_ready(sock: str, timeout: float, pane: str, stuck_after: float = 45.0, mux: str | None = None) -> dict[str, Any]: """Poll the socket + ping until the TUI reports ready (or timeout). - Emits a one-time hint if Code Mode discovery/opening is still not ready after + Emits a one-time hint if IDA Nexus discovery/opening is still not ready after ``stuck_after`` seconds. """ start = time.time() @@ -370,7 +370,7 @@ def _wait_ready(sock: str, timeout: float, pane: str, why = ("RPC socket not created yet" if not os.path.exists(sock) else "TUI up but analysis not ready") print(f"still waiting ({int(time.time() - start)}s): {why}. " - f"Check Code Mode registrations and worker logs.", file=sys.stderr) + f"Check IDA Nexus registrations and worker logs.", file=sys.stderr) time.sleep(0.4) last = dict(last) last["ready"] = False @@ -467,7 +467,7 @@ def list_panes(args) -> int: def reap(args) -> int: - """Deprecated no-op; shared Code Mode workers are managed by leases.""" + """Deprecated no-op; shared IDA Nexus workers are managed by leases.""" print(json.dumps({"reaped_workers": 0, "live_panes": _count_live_panes(), "forced": args.force, "deprecated": True})) return 0 @@ -572,7 +572,7 @@ def main(argv: list[str]) -> int: ls.add_argument("--prune", action="store_true", help="drop dead panes (and their sockets)") ls.set_defaults(fn=list_panes) - rp = sub.add_parser("reap", help="deprecated no-op (Code Mode uses shared leases)") + rp = sub.add_parser("reap", help="deprecated no-op (IDA Nexus uses shared leases)") rp.add_argument("--force", action="store_true", help=argparse.SUPPRESS) rp.set_defaults(fn=reap) diff --git a/idatui/pool.py b/idatui/pool.py index 573aa75..2407b44 100644 --- a/idatui/pool.py +++ b/idatui/pool.py @@ -1,6 +1,6 @@ -"""DatabasePool — LRU leases on Code Mode databases for a project. +"""DatabasePool — LRU leases on IDA Nexus databases for a project. -Code Mode may bind a lease to an existing IDA GUI or to a shared managed idalib +IDA Nexus may bind a lease to an existing IDA GUI or to a shared managed idalib worker. The pool therefore owns *client interest*, never an IDA process. Releasing an LRU entry persists managed IDBs but does not implicitly save a GUI, then closes only this TUI's lease; other clients and GUI sessions remain alive. Managed workers exit themselves after their final lease. @@ -48,8 +48,8 @@ def _pss_mb(pid: int | None) -> int: def _default_spawn(ref: BinaryRef, ttl: int, *, new_database: bool = False): # pragma: no cover - needs IDA - from .codemode_client import CodeModeClient - return CodeModeClient( + from .nexus_client import NexusClient + return NexusClient( ref.staged, ttl=ttl, load_args=ref.load_args, @@ -59,7 +59,7 @@ def _default_spawn(ref: BinaryRef, ttl: int, *, new_database: bool = False): # class DatabasePool: - """Live Code Mode database leases, keyed by project label.""" + """Live IDA Nexus database leases, keyed by project label.""" def __init__(self, project: Project, *, budget_mb: int | None = None, ttl: int = 1800, spawn=None, mem_fn=None) -> None: @@ -101,7 +101,7 @@ class DatabasePool: """A live client for ``label``, attaching or spawning as needed. Do not sweep IDA scratch files here: a registered GUI or another Code - Mode client may own the database. Code Mode's registry locks and health + Mode client may own the database. IDA Nexus's registry locks and health probes are the authority for safe discovery and stale-record cleanup. """ client = self._clients.get(label) @@ -269,5 +269,3 @@ class DatabasePool: f"{self.memory_mb()}/{self.budget_mb}MB active={self.active}>") -# Source compatibility for callers that imported the pre-Code-Mode name. -WorkerPool = DatabasePool diff --git a/idatui/project.py b/idatui/project.py index 53f3b0a..e681dfc 100644 --- a/idatui/project.py +++ b/idatui/project.py @@ -23,7 +23,7 @@ firmware image, a cleaned build tree). A source whose size/mtime no longer matches the staged copy is re-staged, and its now-stale database is dropped (the DB describes the old bytes). -The model has no IDA imports. Staging consults ida_codemode's registry before +The model has no IDA imports. Staging consults ida_nexus's registry before replacing files so it never mutates a database owned by a GUI/shared worker. """ from __future__ import annotations @@ -57,7 +57,7 @@ class BinaryRef: #: it is, a raw firmware image doesn't, and IDA defaults to metapc at 0. processor: str = "" # IDA processor name: arm, armb, mipsb, metapc, … base: int = 0 # load address (natural, e.g. 0x8000000) - ida_args: str = "" # legacy -p/-b/-T switches accepted by Code Mode adapter + ida_args: str = "" # legacy -p/-b/-T switches accepted by IDA Nexus adapter @property def db(self) -> str: @@ -311,7 +311,7 @@ class Project: """Ensure ``ref`` is staged in the sidecar; returns the staged path. Re-staging a changed source drops its database: the DB describes the old - bytes. Refuse while Code Mode reports a GUI/idalib owner; replacing a + bytes. Refuse while IDA Nexus reports a GUI/idalib owner; replacing a staged executable or IDB underneath a shared live instance is corruption. """ if not os.path.isfile(ref.source): @@ -319,15 +319,15 @@ class Project: if not self.is_stale(ref): return ref.staged try: - from .codemode_client import database_owner + from .nexus_client import database_owner owner = database_owner(ref.db, ref.staged) except Exception as exc: raise ProjectError( - f"cannot verify Code Mode ownership before staging {ref.label}: {exc}" + f"cannot verify IDA Nexus ownership before staging {ref.label}: {exc}" ) from exc if owner is not None: raise ProjectError( - f"cannot restage {ref.label}: Code Mode instance {owner.record_id} " + f"cannot restage {ref.label}: IDA Nexus instance {owner.record_id} " f"still owns {owner.idb_path}; close/release it first" ) os.makedirs(self.bin_dir, exist_ok=True) @@ -353,7 +353,7 @@ class Project: def sweep_scratch(self, ref: BinaryRef) -> int: """Delete unpacked working files (never the ``.i64``) for maintenance. - Runtime paths no longer call this: Code Mode instances are shared, so a + Runtime paths no longer call this: IDA Nexus instances are shared, so a registry owner may still be using these files. Callers must independently prove that no GUI/idalib instance owns the database. """ diff --git a/idatui/remote_ops.py b/idatui/remote_ops.py index 91ffeb7..a67b2b7 100644 --- a/idatui/remote_ops.py +++ b/idatui/remote_ops.py @@ -1,4 +1,4 @@ -"""Typed remote operations executed through ida-codemode.""" +"""Typed remote operations executed through ida-nexus.""" from __future__ import annotations # ruff: noqa @@ -1655,7 +1655,7 @@ def _bindings() -> dict[Callable[..., Any], Any]: with _BIND_LOCK: if _BOUND is not None: return _BOUND - from ida_codemode import RemoteModule + from ida_nexus import RemoteModule operations_module = RemoteModule( Path(__file__), operation_label=operation_label, codec="json" diff --git a/idatui/remote_tools.py b/idatui/remote_tools.py index db04244..6681379 100644 --- a/idatui/remote_tools.py +++ b/idatui/remote_tools.py @@ -1,8 +1,8 @@ -"""The IDAPython ida-tui runs inside the Code Mode sandbox. +"""The IDAPython ida-tui runs inside the IDA Nexus sandbox. Two features have no ida-domain surface at all and are carried over VERBATIM from the tools ida-tui was developed against (`server/patch_server.py`'s -injected BODY, which the Code Mode port deletes): +injected BODY, which the IDA Nexus port deletes): * `heads` -- the continuous listing. ida-domain enumerates defined heads and renders plain disassembly; the listing also needs coalesced undefined runs, @@ -20,8 +20,8 @@ what you see). A re-implementation drifts from it silently. This file is SOURCE SHIPPED AS TEXT to the database process; it is never imported here, because the ida_* modules do not exist in the TUI's interpreter. -`codemode_client` reads it and prepends it to the relevant snippets. Keep it -self-contained: no relative imports, nothing beyond what Code Mode provides. +`nexus_client` reads it and prepends it to the relevant snippets. Keep it +self-contained: no relative imports, nothing beyond what IDA Nexus provides. """ # ruff: noqa @@ -29,7 +29,7 @@ import re as _re # IDAPython, imported ONCE at module scope. # -# This file is never imported by the client -- codemode_client reads it as +# This file is never imported by the client -- nexus_client reads it as # TEXT and installs it as a module inside the database process -- so the # no-IDA house rule that keeps idatui importable without IDA does not apply # here, and these need not be function-local. @@ -1740,7 +1740,7 @@ def decompile(addr, include_addresses=True): Faithful to the tool ida-tui was written against, and in particular to its COST: the per-line address anchor comes from ONE ``get_line_item`` at column - 0 per line. The Code Mode port asked for the full per-column line map (what + 0 per line. The IDA Nexus port asked for the full per-column line map (what ``decomp_map`` is for) purely to fill in that anchor, which is thousands of ``get_line_item``+``dstr()`` calls per function instead of one per line, and made every pseudocode open cost the same as opening the split view. |
