diff options
| author | blasty <blasty@local> | 2026-08-07 14:32:56 +0200 |
|---|---|---|
| committer | blasty <blasty@local> | 2026-08-07 14:32:56 +0200 |
| commit | 21971d0a9ce9b8a2fb1db2ec67303696cea9cbbb (patch) | |
| tree | 7122697940630116eae1b7e75756b68292cfe9b9 /idatui/codemode_client.py | |
| parent | codemode: three defects the A/B benchmark found in the decompiler path (diff) | |
| download | ida-tui-21971d0a9ce9b8a2fb1db2ec67303696cea9cbbb.tar.gz ida-tui-21971d0a9ce9b8a2fb1db2ec67303696cea9cbbb.tar.xz ida-tui-21971d0a9ce9b8a2fb1db2ec67303696cea9cbbb.zip | |
tests: blob_ui 39.8s -> 4.1s, and a fatal it was hiding
Three separate wastes, all of the same family: waiting on a guess instead of a
signal, and paying for work that never had to be repeated.
1. The 64KB blob was built with os.urandom into a fresh TemporaryDirectory on
every run. New bytes at a new path means the pristine-database cache can
never apply, so full auto-analysis of 64KB of AArch64-decoded noise was paid
every single run. It is now built from a seeded PRNG at a stable path
(tests/.synthetic/, gitignored) and staged through the existing cache.
Determinism is also a correctness fix: whether 64KB of chance bytes contains
something IDA reads as a function is luck, and this suite asserts "and really
has no functions".
2. `wait(lambda: lst.model is not old, ..., 30)` after commenting. The perf work
made an item edit KEEP the listing's walk and re-render in place, so the
model object is never replaced and this waited out its full 30s timeout on
every run -- and then "commenting leaves the view where it was" passed
vacuously, because nothing had happened at all. A test that burns 30s to
check nothing is worse than no test.
3. Two `pause(2.0)`/`pause(2.5)` after a carve, replaced with settle() on a real
condition. The second one deliberately has NO predicate: that spot is random
data, so the carve may legitimately produce nothing, and "the row became
code" would never hold -- gating on it cost another 30s timeout. What that
check is about is the VIEW not moving, so the gate is "the app finished
reacting".
Fixing (1) exposed a real bug in the client, fixed here too: reopening a
database that already exists while passing loader switches is FATAL in IDA --
FATAL ERROR: Switch '-b400' can be used only when loading a new file
which kills the worker before it can report anything. Loader switches describe
an IMPORT and are recorded in the database they produce, so they are now sent
only when there is an import to describe. This was never reachable from the old
suite (a fresh random blob never had a database to reopen), but it is reachable
by any user who opens a raw blob with --ida-args twice.
30 passed, 0 failed.
Diffstat (limited to 'idatui/codemode_client.py')
| -rw-r--r-- | idatui/codemode_client.py | 25 |
1 files changed, 22 insertions, 3 deletions
diff --git a/idatui/codemode_client.py b/idatui/codemode_client.py index 3bf01cc..9d91335 100644 --- a/idatui/codemode_client.py +++ b/idatui/codemode_client.py @@ -1062,6 +1062,17 @@ class CodeModeClient: self._last_entry: RegistryEntry | None = None self._connect_lock = threading.Lock() + def _database_exists(self) -> bool: + """Whether the IDB this open would target is already on disk. + + Its loader switches are baked in, so they must not be sent again. + """ + try: + target = self._output_database or expected_idb_path(self._path) + except Exception: # noqa: BLE001 -- resolver unavailable: assume fresh + return False + return bool(target) and os.path.exists(target) + def connect(self, timeout: float = 1800.0, progress=None) -> "CodeModeClient": _require_codemode() with self._connect_lock: @@ -1078,17 +1089,25 @@ class CodeModeClient: deadline = time.monotonic() + min(timeout, 60.0) while True: try: + # Loader switches describe how to IMPORT a raw file and + # are recorded in the database it produces. Sending them + # again for a database that already exists is a FATAL + # error in IDA itself ("Switch '-b400' can be used only + # when loading a new file"), which kills the worker + # before it can report anything useful. So: describe the + # import only when there is an import to describe. + fresh = self._new_database or not self._database_exists() handle = DatabaseHandle.open( self._path, spawn=self._spawn, timeout=max(0.1, timeout), output_database=self._output_database, - processor=self._processor, + processor=self._processor if fresh else None, # DatabaseHandle calls this image_base and wants the # natural (16-byte aligned) address; it does the # conversion to IDA's paragraph-based -b itself. - image_base=self._loading_address, - file_type=self._file_type, + image_base=self._loading_address if fresh else None, + file_type=self._file_type if fresh else None, new_database=self._new_database, ) break |
