| Commit message (Collapse) | Author | Files | Lines |
|
A design reference for bringing the idatui spawn/drive/introspect experience
to other terminal apps. Captures the invariants worth keeping, the reusable
core vs per-target Adapter split, a target taxonomy (embedded / control-
channel / black-box PTY), the three hard problems (injection, settle,
introspection) with technique gradients, tmux as the universal substrate,
and a concrete gdb worked example (TUI pane + gdb-Python plugin as the
adapter). No code.
|
|
The comment prompt is single-line, so a long note rendered as one giant
'//' line that ran off the right edge and got clipped. Interpret a literal
'\\n' (backslash-n) in the comment text as a real newline in _do_comment
(the single choke point for the ';' prompt and the RPC 'comment' verb) —
Hex-Rays renders each as its own '//' line. Verified live and locked in
the comment_func scenario (multi-line render); suite 104 green.
|
|
Round out idatui.drive with 'save' and 'retype <fn> <proto>' so the whole
common RE loop (orient/understand/act/persist) has a terse command. Update
docs/RPC.md and both skills (idatui, idatui-rpc) to recommend idatui.drive
as the day-to-day driving surface, with rpcclient/raw as the fallback for
unwrapped verbs.
|
|
The clock/title Header added noise (and made screen() non-deterministic);
remove it from the app itself — the Footer stays. Layout shifts up a row;
scenario suite still 101 green.
xrefs_from on a function was near-useless: the server's xrefs_from is
address-scoped, so passing the entry ea only returned the fall-through
from the first instruction. For a function target it now returns
whole-body references from the decompiler (callees + string/data refs:
{to,name,string,is_func,type}); an explicit 0xADDR stays address-scoped.
Verified live: xrefs_from main now lists setlocale/getopt_long/sub_* etc.
rpc_smoke: 25 green.
|
|
cursor_on {word,line?,occurrence?} places the cursor on a token, verified
with the app's own tokenizer (so 'main' won't match inside 'domain');
rename/retype/follow gain an optional word= that runs it first — the
ergonomic way to edit a named symbol without a manual view+cursor dance.
Single-driver gate: the socket now serves one client at a time; a second
concurrent connection is refused with 'busy' (no multi-driver support yet,
by request). Sequential connections are unaffected.
screen gains format=text|html|svg (html/svg are colored — for an
out-of-band web viewer). rpc_smoke: 24 green.
|
|
Introspection (offloaded to a thread so a big fetch can't freeze the
render): pseudocode (full body), disassembly (bounded), xrefs_to/from
(structured), resolve (name->ea). These let an agent reason about a
function and the call graph without scraping screen().
Semantic: select (choose the highlighted/nth item in the open xrefs or
symbol-palette list and activate it — the natural follow-up to xrefs/
symbols), search (/ or ? incremental in the active code view), save
(Ctrl+S). rpc_smoke exercises all of them end-to-end (18 green).
|
|
|
|
Replace the docked names pane as the primary way to jump to symbols with a
command-palette overlay: Ctrl+N opens SymbolPalette, type to fuzzy-find (scored
subsequence match with matched-char highlighting), up/down to select, Enter to
open, Esc to close. Mouse-clickable too.
The docked FunctionsPanel now starts hidden and is still reachable/toggleable
with Ctrl+B (classic table view, sort, filter, goto all unchanged); a code view
takes focus on startup. Status hints 'Ctrl+N: find symbol'.
Pilot checks for open/fuzzy/subsequence/select/close. Tests reveal the docked
pane (Ctrl+B) for the existing table-driven checks. full suite 74/74.
|
|
DecompView.goto derived its scroll with _scroll_cursor_into_view() -> a plain
scroll_to, which updates scroll_offset but doesn't repaint at the new offset
(Textual only repaints on a rounded scroll change, and there's no layout pass
since the pane isn't reloaded). So a same-function xref jump landed the cursor
correctly but painted the old scroll until the next cursor move nudged a repaint.
Route goto's scroll (derived or explicit) through _apply_scroll, which re-applies
after the next refresh with layout=True -- the same pattern show() uses. The
cross-function path already worked because reloading + clearing the loading cover
forces a fresh paint. Document the gotcha in TEXTUAL_NOTES.
full suite 64/64.
|
|
Companion to PAGING_FINDINGS.md capturing the hard-won Textual behaviour
(bindings/mixins, ScrollView repaint & scroll-clamp, loading cover blurs focus,
mouse offset mapping, Input/Footer overlap, no cpp grammar) and the app patterns
(name-gen cache invalidation, cursor+scroll history, name+address follow).
|
|
Root cause of 'Maximum idalib worker count reached': the app bumped idle_ttl to
~never on every open, so sessions never freed and hit the default cap of 4.
- app: drop the immortal bump_idle_ttl(1e9); rely on the KeepAlive heartbeat
(120s) to keep the running session warm, and open with a moderate idle_ttl
(1800s) so the worker self-exits and frees its slot after the TUI closes.
- spawn.sh: set IDA_MCP_MAX_WORKERS (default 8) for headroom.
- doc: supervisor prunes unreachable workers before counting, so killing an idle
worker frees a slot on the next open.
(Verified separately: big/truncated decompile results propagate renames fine via
the earlier force_recompile fix.)
|
|
Root cause: idalib workers run an idle watchdog (worker_lifecycle.py) that
self-exits after idle_ttl_sec (default 600s) of no requests -- deliberate
resource hygiene for a shared/ephemeral supervisor, wrong for an interactive
TUI. CLI-opened startup binary always gets 600s (no flag).
Fix (both verified against a 12s-TTL session):
- IDAClient.bump_idle_ttl(1e9): re-open (idempotent) re-applies TTL, no upper
cap -> effectively immortal. Primary fix, call once at startup.
- IDAClient.keepalive()/KeepAlive: background server_health heartbeat resets the
watchdog; covers adopted sessions too. Safety net.
tests/test_keepalive.py: 4/4 (heartbeat + bump both keep a short-TTL worker
alive past 2x TTL). docs updated with why + fix.
|
|
- Program: model registry + small prefetch pool; cache invalidation hooks
- FunctionIndex: lazy pagination (advance by len, clamp page=500), filter globs,
viewport windows; full 10,092-func enumerate matches survey in ~3.1s
- DisasmModel: block-cached windowed disasm (256/block), forward+back prefetch,
cached instruction totals; deep window revisit 196ms -> 0ms (cache proven)
- Decompilation: truncation-aware, hard-failure surfaced as data (monster funcs)
- resolve(): int/hex/symbol via lookup_funcs (string-array query shape)
- tests/test_domain.py: 17/17 on both ls (~290 funcs) and libcrypto (10k+52k-insn)
- smoke_client no longer assumes 'main' exists (works on libraries)
- doc: idle_ttl worker self-exit finding ("not reachable" needs re-open)
|
|
Measured against libcrypto.so.3 (10,092 funcs, biggest 52,120 insns):
- list_* count cap ~700, disasm max_instructions cap ~500, then SILENT
collapse to 10 (not clamped) -> must clamp client-side (use 500).
- pagination must advance by len(data); next_offset=offset+count skips data.
- disasm offset paging is O(offset): 6ms@0 -> 180ms@50k, no resumable cursor
-> domain layer must cache windows + prefetch + over-fetch.
- include_total scans whole func (~217ms on monster) -> fetch once, cache.
- decompile hard-fails on huge funcs as a soft error (code=null) -> handle.
- idb_open needs a writable path for the .i64.
tests/stress_paging.py: durable paging benchmark harness
docs/PAGING_FINDINGS.md: constraints that drive the paging layer
|