| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
`e` in graph mode cycles auto -> native -> triskel, and `auto` prefers
triskel where it is installed and the function is at most 250 blocks.
Why: our layered engine draws wide-and-short pictures with a lot of
crossings on anything branchy. Triskel splits the CFG into Single-Entry
Single-Exit regions first and lays each out on its own, which on the
128-function corpus means fewer crossings on 12 functions, equal on 9,
worse on 3 -- and the wins are the hairballs (sub_5CA0 41 -> 6,
sub_2C90 32 -> 7, sub_2C00 12 -> 0). It also routes loop edges around
the side of the graph the way IDA does, which was a known gap here.
It is not free: ~2x slower at 87 blocks, 10x at 424, hence the cap.
The library needed a fork (~/dev/triskel, branch idatui) before it could
be used from Python at all -- its get_waypoints() threw on every
published version, an empty graph segfaulted the interpreter, and its
spacing constants were pixels baked in at compile time. Making those
settable is what makes this integration cheap: we hand it CELLS, so
its output is integral and two edge lanes can never round onto the same
row. The feared quantisation problem measured out backwards -- cells
claimed by more than one edge: native 131, triskel 35.
Not trusted with degenerate input, all handled before the call:
self-loops and disconnected components make it throw, and one corpus
edge comes back routed through a block, which we detour and re-verify.
A triskel failure is never fatal; it falls back to native.
Two things the second engine flushed out of the existing code:
- the canvas was sized from boxes alone, which is exact only because
native's dummy nodes reserve the space. Triskel routes outside that
bounding box and the edges were being clipped.
- arrowhead placement read e.back, conflating "this is a loop edge"
(style) with "this polyline runs against control flow" (geometry).
Now Edge.flipped, which is also a latent fix for residual-cycle edges
whose succ/pred were being reported backwards.
tests/test_graph.py runs its whole suite once per available engine
(943 checks); new graph_engine scenario covers the live toggle.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The README told people to "git clone ... ../ida-codemode" and pyproject carried
a matching [tool.uv.sources] path override. That is not a convention, it is
Duncan's development layout, inherited from the port patch and never
questioned: it writes into the parent of the user's checkout, breaks if the
directory is named anything else, and pins everyone to whatever happens to be
in that working tree.
ida-codemode is published on PyPI (0.3.1, the same version the clone was
sitting at), so the dependency is now an ordinary versioned requirement and
"uv sync" is the whole install. Verified end to end: uv lock resolves, uv sync
--frozen installs from the registry, and ida_codemode.client.DatabaseHandle
imports from the resulting venv.
The path override is still the right answer when hacking on both at once, so
that stays in the README as one "uv add --editable" line rather than as the
default everyone pays for.
Also fixed the plugin command it now documents: upstream's own README passes
--prerelease=allow, and without it uvx refuses to resolve, because ida-codemode
depends on ida-domain>=0.5.1.dev2. Checked that --install-plugin is a real flag
and that the command runs, instead of copying it over on faith.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The old one had drifted into a wall of prose that described the project to
someone who already knew it. Rewritten around what a reader actually needs:
what this is, what it needs, how to run it, the keys, and what is in it --
with the logo up top and the sharp edges at the bottom instead of a wall of
disclaimers at the top.
Corrected while checking every claim rather than trusting it:
- pyproject depended on 'ida-codemode-mcp' from '../ida-codemode-mcp'. The
package is 'ida-codemode' and the repo is '../ida-codemode', so 'uv sync'
-- the documented install -- could not have worked for anyone. Fixed, and
uv.lock regenerated (it now resolves, 39 packages).
- test counts were 257/733, they are 302/788
- the keys table is generated from the real BINDINGS: c/d/a/p/u, o/O/B, s for
split, ctrl+t for structs, " for strings, all verified against app.py
- dropped the ida-pro-mcp/worker-era architecture section, which described
files that no longer exist, and replaced it with the current layout
- links docs/CODEMODE_UPSTREAM.md and .fastfeedback/SPEED.md, which did not
exist when the README was last touched
Every path and doc link in it is checked to exist.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Mechanical part of the port: the 27-file patch was cut against a base ~148
commits behind us, so it did not apply. Resolved 11 conflicts (all of them
diff drift, not semantic clashes) and the three file deletions:
- app.py: the patch re-inserted _do_rename/_do_name_addr/_seek_split etc. as
"theirs" because our tree moved them to edit_ctl.py/trace_ctl.py. Kept ours
and applied the real intent (WorkerClient->CodeModeClient, .call->.invoke,
_open_worker_client->_open_database_client) at their current homes.
- domain.py: kept Head as a NamedTuple -- the patch reverted it to a frozen
dataclass, which the perf work measured at 2.9us vs 1.9us per row on a
quarter-million-row walk. Dropped _fetch_output (no download_url under Code
Mode) and its now-dead urllib/json imports.
- pane.py: the patch's deletion swallowed our zellij support along with the
worker-reaping block it meant to remove. Kept zellij, removed the reaping.
- test_scenarios.py: the idb_save->save_database teardown hunk belongs to
tests/_fixtures.py in our tree; applied it there and kept our pc_num_format
scenario that the drift landed on.
Three defects in the patch itself, fixed here:
- It made "import idatui" hard-require ida_codemode, so every offline suite
died at import -- including the pure ones (graph/index/trace) that are the
house rule for "tests/run.py --fast". The import is now deferred and gated
on the binding, which is also what lets the port's own contract tests
inject a fake DatabaseHandle.
- project.stage() inlined an ida_codemode.registry import and treated "library
not installed" as "someone owns this database", which broke IDA-free project
staging. Ownership lookup moved to codemode_client.database_owner().
- tests/test_codemode_client.py had no NEEDS_IDA marker, which tests/run.py
rejects outright.
Offline suite: 301 passed, 0 failed. Against master's 344 the whole delta is
accounted for: -40 worker_client (module deleted), -18 launch sweep checks
(behaviour deliberately removed) +3 guarding that it stays removed, +2 pool
(GUI-save semantics), +13 new codemode_client contract tests.
NOT yet done, and the port is not functional without it: the adapter is
missing five operations our tree grew since the patch's base (flowchart,
op_format, pc_nums, pc_num_format, survey_binary) and its "heads" predates
back-walking and digest/expect.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The idalib worker is the only backend now, so remove the dead HTTP/supervisor
surface entirely (~2200 lines):
* deleted idatui/client.py (the IDAClient HTTP/JSON-RPC transport + session
manager), idatui/tui.py (the old mcp TUI entry, superseded by launch.py),
spawn.sh, and systemd/ (the supervisor unit).
* deleted the mcp-only tests (stress_client, smoke_client, test_keepalive,
stress_paging, rpc_smoke, serverctl.sh, pane_smoke, test_domain) -- the worker
pilot (tests/test_scenarios.py) supersedes them.
* migrated the tmux RPC harness (idatui/pane.py) to the worker: it spawns
`idatui.launch <binary> --rpc <sock>` instead of the mcp `idatui.tui`, drops
the supervisor auto-start/ensure machinery, and reaps our own worker
(idatui/worker.py) instead of ida_pro_mcp.idalib_server. --db/--url/--no-
ensure-server are gone; --open is required.
* __init__ / __main__ / domain no longer import client (exceptions come from
errors.py, the domain client hint is WorkerClient); pyproject points both
console scripts at idatui.launch; README + ida-tui header describe the
worker-only flow.
What stays (by design): the ida_pro_mcp *package* (the worker reuses its @tool
functions in-process) and server/patch_server.py (the worker injects its custom
tools on startup). Verified: whole package imports + IdaTui constructs + pilot
lists 31 scenarios. The worker pilot (134 pass / 2 known flakes) is the E2E gate.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A caveman entry point so you don't hand-craft the plumbing every time. It:
* ensures the ida-pro-mcp supervisor is up — starts spawn.sh detached
(start_new_session, tmux-free) and waits for the port if it's down;
* recovers a binary wedged by a hard-killed worker — sweeps the stale
unpacked .id0/.id1/.id2/.nam/.til next to the .i64 and retries (the packed
.i64 is never touched);
* adopts an already-open session for the same binary (idempotent), else
idb_opens it with a sane idle-TTL;
* launches the TUI attached to that session with keepalive on.
./ida-tui /path/to/binary # open a binary and drive it
./ida-tui # attach to the sole session
./ida-tui --db <id> # attach to a specific session
Pieces: idatui/launch.py (logic, reuses pane.py's server probe), a repo-root
`ida-tui` sh wrapper (resolves ~/ida-venv python, keeps idatui importable from
any cwd), and an `ida-tui` console-script in pyproject. --help works without
textual (app imported late). README documents both the one-liner and the
manual recovery. gitignore bin/ (binary targets, like targets/).
Verified live: ensure_server up-detection, open, adopt (same session id), and
lock-sweep all work against a running supervisor.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Highlighting: Textual has NO C/C++ tree-sitter grammar (and the syntax extra
wasn't installed), so language='cpp' was a silent no-op. Replace TextArea with a
virtualized, Pygments-highlighted ScrollView:
- idatui/highlight.py: CLexer -> Rich styles, per-line Segment lists
- DecompView: lines highlighted ONCE at load, cached as Strips; O(1) scroll
Jank: profiling showed our code is ~3.6ms/move (render_line 0.018ms) -- the cost
was terminal repaint volume, since every cursor move refreshed the whole ~43-line
viewport. Now:
- cursor reactive repaint=False (no implicit full refresh)
- region-limited refresh: in-place moves repaint only the 2 changed rows
(measured 43 -> 2 render_line calls/move), full refresh only on scroll
Applied to both DisasmView and DecompView.
pilot suite 15/15 (adds highlighting assertion).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
- idatui/app.py: keyboard-first Textual app
* FunctionsPanel: lazy-streamed function list (DataTable) with glob filter
* DisasmView: line-VIRTUALIZED ScrollView -- paints from domain block cache,
background-fetches misses, prefetches neighbors. Proven: 52,120-insn func,
jump-to-bottom in 0.31s with only ~6 blocks (~1.5k lines) ever materialized.
* address-history nav stack (Enter follow / Esc back); goto by name or 0xADDR
* bump_idle_ttl + KeepAlive on startup so the session never idles out
* all MCP work on Textual worker threads; UI never blocks
- idatui/tui.py: launcher (python -m idatui.tui / console script 'idatui')
- tests/test_tui.py: headless Pilot test, 9/9 (boot, load 10k funcs, open,
scroll, bg-cache, goto-bottom, filter)
- pyproject: textual extra + idatui script entry
|
|
|
thread-safe
- IDAClient: single handshake, ~7ms warm calls (vs ~60ms cold CLI)
- keep-alive connection pool over http.client; concurrent-safe (40 threads OK)
- grounded error taxonomy: IDAToolError on isError, soft per-item errors as data
- session-expiry recovery (404 -> re-handshake -> retry), bounded transport retries
- auto db-injection + resolve_db (ignores stale empty-id sessions)
- live smoke test: 13/13 pass
|