diff options
| author | blasty <blasty@local> | 2026-07-24 01:42:43 +0200 |
|---|---|---|
| committer | blasty <blasty@local> | 2026-07-24 01:42:43 +0200 |
| commit | 784df98d19b70eca5906ce0ac7fcf2bfcd0f3515 (patch) | |
| tree | 21a8bc2b8fce7df70a79ac0361cc51c70eb64061 /idatui/worker_client.py | |
| parent | app: --backend {mcp,worker} — run the TUI on our idalib worker (migration s... (diff) | |
| download | ida-tui-784df98d19b70eca5906ce0ac7fcf2bfcd0f3515.tar.gz ida-tui-784df98d19b70eca5906ce0ac7fcf2bfcd0f3515.tar.xz ida-tui-784df98d19b70eca5906ce0ac7fcf2bfcd0f3515.zip | |
worker: surface the real startup failure (not just "code 1")
The worker's stderr was swallowed by the TUI, so an open failure showed only
"worker exited during startup (code 1)". Now:
* WorkerClient captures the worker's stdout+stderr to /tmp/idatui-worker-*.log
and, on a startup exit, surfaces the last meaningful line in the error (the
worker prints a clean 'WORKER-FATAL: ...' marker; _log_tail prefers it).
* worker.py wraps main() to print that marker + traceback before exiting 1, and
gives an ACTIONABLE open error: "failed to open <bin>: the .i64 is likely held
by a running ida-mcp worker (pkill -f idalib) or wedged (delete .id0/.id1/
.id2/.nam/.til)". Also calls ida_auto.auto_wait() after open to fully match
ida-mcp's session manager (open_database + auto_wait).
Root cause of the reported failure is almost certainly a leftover ida-mcp worker
still holding bash's .i64 from earlier --backend mcp runs: idalib can't open a
database another process has locked. Fix: pkill -f idalib, then retry
--backend worker; the error message now says so instead of "code 1".
Diffstat (limited to 'idatui/worker_client.py')
| -rw-r--r-- | idatui/worker_client.py | 27 |
1 files changed, 24 insertions, 3 deletions
diff --git a/idatui/worker_client.py b/idatui/worker_client.py index 49da8fa..70de11f 100644 --- a/idatui/worker_client.py +++ b/idatui/worker_client.py @@ -46,7 +46,9 @@ class WorkerClient: python: str | None = None) -> None: self._bin = os.path.abspath(os.path.expanduser(binary_path)) self._python = python or sys.executable - self._sock_path = f"/tmp/idatui-worker-{os.getpid()}-{uuid.uuid4().hex[:8]}.sock" + tag = f"{os.getpid()}-{uuid.uuid4().hex[:8]}" + self._sock_path = f"/tmp/idatui-worker-{tag}.sock" + self._log_path = f"/tmp/idatui-worker-{tag}.log" self._proc: subprocess.Popen | None = None self._sock: socket.socket | None = None self._sid = uuid.uuid4().hex[:8] @@ -64,6 +66,8 @@ class WorkerClient: [self._python, "-m", "idatui.worker", self._sock_path, self._bin], cwd=os.path.dirname(os.path.dirname(os.path.abspath(__file__))), + stdout=open(self._log_path, "wb"), + stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL, ) deadline = time.time() + timeout t0 = time.time() @@ -76,8 +80,8 @@ class WorkerClient: except OSError: if self._proc.poll() is not None: raise IDAConnectionError( - f"worker exited during startup (code " - f"{self._proc.returncode})") + f"worker exited (code {self._proc.returncode}): " + f"{self._log_tail()} [full log: {self._log_path}]") if progress: progress(f"auto-analyzing {os.path.basename(self._bin)}… " f"({int(time.time() - t0)}s)") @@ -157,6 +161,23 @@ class WorkerClient: def keepalive(self, interval: float = 120.0) -> _NoopKeepAlive: return _NoopKeepAlive() + def _log_tail(self, n: int = 400) -> str: + """Last meaningful line(s) of the worker log (skip IDA's licence banner), + so a startup crash surfaces the real cause instead of just 'code 1'.""" + try: + with open(self._log_path, encoding="utf-8", errors="replace") as f: + lines = [ln.strip() for ln in f if ln.strip()] + except OSError: + return "(no worker log)" + # the worker prints a clean 'WORKER-FATAL: ...' line on a startup crash + for ln in reversed(lines): + if ln.startswith("WORKER-FATAL:"): + return ln[len("WORKER-FATAL:"):].strip()[-n:] + skip = ("thank you", "licensed to", "[mcp]", "ida ", "hex-rays") + meaningful = [ln for ln in lines + if not any(s in ln.lower() for s in skip)] + return " | ".join((meaningful or lines)[-3:])[-n:] + # context manager parity with IDAClient def __enter__(self) -> "WorkerClient": return self.connect() |
