| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Regression from the decomp-jump feature: _open_decomp_entry clears
_decomp_return, so Tab out of a jumped-to pseudocode view hit the fallback that
just re-showed the (stale) listing widget WITHOUT reopening it and left
_cur.view == "decomp". Two visible symptoms:
* the listing showed the wrong/old function, and
* a subsequent rename ran _reload_active_code -> _open_entry(cur), which saw
view == "decomp" and bounced back into the decompiler; the rename also
appeared not to propagate (you were looking at stale pseudocode).
Fixes:
* Tab from decomp with no F5 snapshot now opens the listing at the current
pseudocode line's address (a real listing view, _cur.view = "listing"),
so the correct function shows.
* _reload_active_code forces _cur.view = "listing" when refreshing the
listing, keeping the entry consistent with what's on screen.
Verified: F5 -> follow ref in decomp -> Tab -> listing shows the TARGET function;
renaming a label there stays in the listing and reflects the new name
(RELABELED: and 'jnz RELABELED'). view_toggle/decomp_nav/decomp_follow_self/
follow_xrefs/xref_labels/rename/rename_history/disasm_nav/search/mouse/
listing_view/continuous_view/func_banners 70/0.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Following a reference or picking an xref target from the pseudocode dropped you
back into the linear listing. Now such jumps stay in the decompiler when the
target is a decompilable function, landing on the pseudocode line that matches
the target address; non-decompilable targets still fall back to the listing.
* NavEntry gains a `view` field ("listing"/"decomp"); _open_entry restores an
entry in whichever view it was seen in (so back/forward also keep pseudocode).
* _do_navigate/_goto_ea take prefer_decomp; when set and the target function
decompiles, _open_decomp_entry opens it in the decompiler as a real nav-history
push (snapshotting the source pseudocode position so 'back' returns to it).
* _follow_decomp and _on_xref_chosen pass prefer_decomp when the source view is
the decompiler.
Verified: follow a ref from decomp -> lands in the target's pseudocode; 'back'
returns to the source pseudocode; listing follow still lands in the listing.
decomp_nav/decomp_follow_self/follow_xrefs/xref_labels/view_toggle/disasm_nav/
search/mouse/listing_view/func_banners/continuous_view 62/0.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Extend the jump-context behaviour: if the target is already visible in the
current viewport (same listing model, cursor index within [top, top+height)),
leave the scroll offset untouched and only move the cursor to the target. Only
off-screen jumps re-scroll (target parked _JUMP_CONTEXT lines below the top).
Verified: an in-viewport jump keeps scroll_top constant while moving the cursor
onto the target; off-screen jumps still get the 4-line context margin.
scroll_restore/disasm_nav/follow_xrefs/xref_labels/mouse/search/listing_view/
continuous_view/func_banners 54/0.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Jumping (goto/follow/xref/open) parked the target's first line on the very top
row of the viewport, which is cramped. Now a fresh navigation scrolls so the
target sits _JUMP_CONTEXT (4) lines down, leaving surrounding context above it;
the cursor still lands on the target's first instruction.
Only applies when the scroll is being derived (NavEntry.scroll_y == -1). Back/
forward restores carry their own saved scroll_y, so they still return to the
exact previous viewport.
Verified: jumps to sub_3720/sub_2060/main land the cursor on the entry with a
4-line margin above; scroll_restore/disasm_nav/follow_xrefs/mouse/search/
listing_view/continuous_view/func_banners 51/0.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
resolve(name) went through lookup_funcs, which for a mid-function label
(loc_/locret_) does get_name_ea -> label ea, then get_function(ea) and reports
the CONTAINING function's entry address. So double-clicking (or following) a
label jumped to the top of the function instead of the label's own address.
Add a server tool resolve_names that returns the address a name actually denotes
via idaapi.get_name_ea (functions, labels and data alike), and route
Program.resolve() through it, keeping lookup_funcs only as a fallback for the
"did you mean ..." suggestion on an unknown name.
Verified: resolve('loc_2040') -> 0x2040 (was the function entry); double-click
follow on a label row lands on the label ea. Regression sweep
follow_xrefs/xref_labels/rename/rename_history/decomp_follow_self/search/
listing_view/disasm_nav/mouse 50/0. Needs a supervisor restart (new tool).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Real, data-loss-capable bug: opening a function that is already the current one
(e.g. after scrolling away from it) schedules an async re-navigation. If the user
opens the search prompt ('/') before that navigation lands, the navigation's
_show_active() called lst.focus() and yanked focus OUT of the search box. The
next typed characters then routed to the listing as verbs -- and 'u' = undefine,
silently deleting a function. (This is what produced the flaky
"search finds nothing" + "no function for <ea>" cascade in the pilot.)
Fix: _show_active() no longer grabs focus for the code view while any prompt
overlay (search/rename/comment/retype/goto/func-filter) is visible -- those own
the keyboard until dismissed. View visibility is unchanged; only the focus grab
is suppressed.
Also harden the pilot's open(): wait for the listing cursor to actually LAND on
the target address, not merely for _cur.ea to match (which is stale-true when
re-opening the same function), so tests can't proceed on a not-yet-moved cursor.
Verified: the exact failing order disasm_nav->search->follow_xrefs is green;
broad focus-sensitive sweep (startup/disasm_nav/view_toggle/hex/search/rename/
comment_func/retype/follow_xrefs/decomp_nav/mouse/listing*/continuous_view/
func_banners/region_define) 89/0.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Revert the "search only the loaded portion" shortcut from f5aaf50 and go back to
a single, guarded background load_all before matching, so a search finds every
occurrence in the segment (not just whatever streamed in so far). The guard
(_search_loading + queued callbacks) shares ONE load across per-keystroke
updates instead of spawning/cancelling a worker each keypress.
The wedge that shortcut was meant to avoid turned out to be unrelated to the
search load: it is a pre-existing navigation bug (opening a function right after
scrolling to the bottom of another leaves the listing cursor out of range and
'/' fails to focus the search input, so typed chars leak into the view as
listing verbs, one of which is 'u'=undefine). Reproduces at 0c097f5, before any
of this work; tracked separately.
Verified in isolation: search 3.1s, matches found; search/mouse/func_banners/
listing_view/continuous_view/decomp_nav/follow_xrefs 45/0.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Editor-style highlight-all: whenever the cursor lands on an identifier, every
other occurrence of that same whole-word token is highlighted across the visible
listing AND the decompiler view (IDA's identifier-highlight behaviour).
* new _word_occurrences() finds whole-word spans in a line (reuses the same
char==cell alignment search already relies on).
* ColumnCursor gains _hl_word + _refresh_hl(): after any cursor move (h/l, w/b,
0/eol, j/k, mouse click, search jump) it recomputes the token under the cursor
and repaints the viewport only when it changed. Tokens must be >=2 chars and
start with a letter/underscore.
* ListingView.render_line and DecompView.render_line overlay every occurrence
with the existing _S_WORD background; the block cursor still marks the current.
Verified: cursor on 'rbp' highlights 5 visible listing rows; cursor on 'v23'
highlights it across the decompiler; mouse/disasm_nav/decomp_nav/listing_view/
func_banners/follow_xrefs/continuous_view/decomp_follow_self 42/0.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Code labels (loc_XXX/jump targets) get their OWN line at depth 0, like IDA,
instead of inline with the instruction: the heads walker (annotate) emits a
kind=label row ('loc_XXX:') before a named non-function-start code head and
strips the name from the instruction. ListingModel doesn't index label rows
(goto/xref/follow still land on the code); ListingView renders them at depth 0.
Also fix a search regression the extra rows exposed: search no longer forces a
blocking full-segment load_all (which stalled/thrashed the worker and wedged the
session on the larger annotated listing). It searches the loaded portion; the
background grower streams the rest and unloaded lines are skipped until they
arrive.
Verified: labels on their own line; search 2.9s (was 51.9s+wedge); search/mouse/
rename_history/continuous_view/func_banners/follow_xrefs 26/0.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Polish per feedback: proc header drops the meaningless 'near' ('name proc');
the function-name/proc header now shows the address like every line; two-level
indent (function names at depth 0, opcode bytes + instruction text one level
deeper, matching IDA's label/instruction columns); 'o' now CYCLES the opcode
column off->limited->full and is shown in the footer; 'limited' mode caps long
runs at the first 8 bytes + ellipsis so 15-byte x86-64 insns don't blow out the
column (width capped to match).
_line_plain/render_line share the layout so cursor/search stay aligned.
Verified func_banners+disasm_nav/mouse/region_define/listing_view/
listing_struct_expand/listing_name_addr/search/continuous_view green. Needs a
supervisor restart.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Each function gets clear boundary annotations in the continuous listing: a blank
+ '; ==== S U B R O U T I N E ====' separator and a 'name proc near' header
before the entry, and a 'name endp' + rule after the last item. The function
name moves to the proc header (stripped from the inline entry instruction).
Server: heads gains an annotate flag (default off, so DisasmModel/test_domain
stay 1:1 with instructions); when on, _rows_for emits kind=sep/funchdr rows at
function starts/ends. ListingModel requests annotate=true and does NOT index
banner rows by ea, so goto/xref/follow land on real code. ListingView renders
sep (dim) and funchdr (bold gold); new _S_SEP/_S_FUNCHDR styles.
Verified: func_banners 5/0; region_define/listing_view/listing_name_addr/
listing_struct_expand/continuous_view/follow_xrefs/disasm_nav/scroll_restore/
paging green. Needs a supervisor restart (heads tool changed).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Full IDA-style unification. The function-bounded DisasmView is gone from the UI:
navigation opens ONE continuous segment listing (functions+data+undefined
interleaved) at the target; F5/Tab decompiles the function under the cursor and
back. The listing gained the opcode column (Head.raw, 'o' toggle) so rendering
matches disasm.
Routing: _do_navigate/_open_function/_open_entry target the listing;
_show_active drops disasm; _active in {listing,decomp,hex}; _pref=listing.
Decomp is a per-function toggle with a listing fallback on failure. Edits
reload in place (_reload_active_code); bump_names() drops the listing cache so
renames refresh. Listing 'n' is symbol-aware. ListingModel gains
cached_line/lines shims; Head.label. DisasmView removed from compose/handlers;
rpc updated. DisasmModel kept (domain/test_domain).
Tests migrated (c.dis->ListingView; open(decomp) F5s; xref/search re-pointed).
Per-scenario green across view_toggle/rename/follow_xrefs/xref_labels/decomp_*/
search/mouse/startup/continuous_view (30/1, 1 cold-decompile flake).
|
| |
|
|
|
|
|
|
|
|
|
| |
In the continuous listing, F5 (or Tab) decompiles the defined function the
cursor is inside; F5/Tab again returns to the linear listing exactly where you
left. Not-in-a-function -> a message; decompile failure falls back to the
listing (not the bounded disasm). Listing position is snapshotted so the
round-trip is lossless.
Verified: continuous_view F5 round-trip + view_toggle/disasm_nav/follow_xrefs/
region_define/listing_view 45/0.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The flat listing was a subset of the disasm view (no opcodes). Now the listing
carries an opcode-bytes column with the same format + o toggle: Head gains a raw
field, ListingModel fills it for code heads via one bulk read per page (bounded,
variable-length safe) + tracks the widest opcode; ListingView renders/pads the
column, settling width as pages stream in. render_line/_line_plain share the
addr+opcodes+label+mnem/text layout with DisasmView.
Test harness all_funcs() reloads the index if a prior bump_items() cleared it
(was crashing biggest() with max([])).
Verified: code heads carry raw (max_raw_len 11); listing suite + continuous_view
opcode-parity 26/0.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Function views are bounded to one function; 'L' from any code view opens the
whole-segment ListingView at the cursor instead -- one long flat listing where
functions/data/undefined interleave and you scroll straight from one function
into the next. Reuses the region listing path (_goto_continuous).
New continuous_view scenario: open a function, press L, assert continuous
listing opens, cursor at the origin function, listing spans past the function
bounds, code interleaves with data. 22/0 with disasm_nav/view_toggle/
region_define (L is additive).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The function disasm view is now a filtered listing: DisasmModel sources its
lines from the heads walker bounded to [func.start, func.end) instead of the
disasm tool, so both code views render from the one listing mechanism.
DisasmView UI (opcode bytes, o-toggle, Tab, rename) preserved -> no test churn.
total() stays on disasm include_total: paging heads for the count hit the MCP
response-size limit (count=1000 truncated -> wrong count) and corrupted prime.
include_total is one fast exact call; for a code function it equals the heads
row count backing the lines. _fetch_block uses count=257 (safe).
Verified: disasm-heavy scenarios 56/0; full suite 127/1 (1 = flaky filter).
M4 complete.
|
| |
|
|
|
|
|
|
|
|
|
| |
A struct-typed data item rendered as one dense 'MyS <...>' line. Now the heads
walker emits indented member rows after the summary (from get_udt_details):
+off name type, each a head of kind member at ea+offset. ListingView renders
them indented/dimmed; _line_plain matches for cursor/search.
Verified: a struct global expands to +0 a int / +4 b char[4] / +8 c __int16,
member rows carry field addresses. New listing_struct_expand scenario; listing
suite 22/0 (in-process).
|
| |
|
|
| |
struct-expand/unify deferred
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
Opening a listing walked the WHOLE segment (load_all) before showing anything --
a blank pane for seconds on a big .text. Now _prime loads just the viewport
around the cursor (appears instantly) and _grow streams the rest in the
background, growing virtual_size as pages land (throttled). Search finishes
loading first (needs the whole segment).
ListingModel gains a _load_lock so the grower and an in-view search can both
drive page loads without double-fetching; new public load_next_page.
Verified: ensure(1100)->partial(1500,incomplete), load_all->full(5036,complete);
3 concurrent loaders -> 0 duplicate heads; UI listing+search 30/0 (in-process).
|
| |
|
|
|
|
|
|
|
|
|
|
| |
IDA's 'A' key. New make_string server tool (ida_bytes.create_strlit, auto-length
to the terminator, undefines items in the way first, returns size + decoded
contents). Program.make_string wraps it; 'a' on a listing head routes through
the existing edit plumbing (EditItemRequested kind=string -> _do_edit_item ->
make_string -> bump_items -> reopen).
Verified: make_string at a .rodata addr creates the literal and IDA auto-names
it (aUsage for 'usage'). New listing_make_string scenario drives it through the
UI; listing suite 15/0. Needs a supervisor restart.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The slow hex load wasn't the byte reads (read_raw fixed those) -- it was
opening the pane at all: hex_model -> image_range -> sections called
survey_binary, which computes function counts/strings/stats and takes ~24s on
libcrypto, blocking the first hex open (and section_of/segment_bounds/
region_label in the listing).
sections/file_regions/image_range now share one cheap _segments source backed
by the injected file_regions tool (a plain segment walk, ~7ms), which gains a
name field so it fully replaces survey_binary for the segment map. Falls back
to survey_binary only if the tool is missing.
Measured (libcrypto): segment map 24240ms -> 7.1ms (~3400x); hex open goes from
~24s to ~11ms. Correctness verified (names, section_of/segment_bounds/
file_offset); hex+listing scenarios 24/0, full suite 123/1 flaky. Needs a
supervisor restart.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Repro: shrink a u16 data field to u8 in the listing; the freed byte at addr+1
becomes undefined, but you couldn't name it. 'n' posted RenameRequested with
word_under_cursor(), and _do_rename treats that as an existing symbol to rename
-- a 'db ?' line has no symbol, so it failed.
In the ListingView, 'n' now names the ADDRESS under the cursor: opens the name
prompt prefilled with the head's current name (empty for an unnamed byte) and
applies it via rename {data:{addr,new}} (the server's address form of the data
rename, which set_name's the ea even when nothing is defined there), then
bump_items + reopen so the label shows. Code-view symbol rename is unchanged.
Server already supported it; new listing_name_addr scenario reproduces the full
workflow. Full suite 123 pass / 1 pre-existing flaky (filter).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Instead of staring at an empty pane after the function list loads, jump
somewhere useful: once the index is complete and nothing's open yet, open
main() (tries main/_main/wmain/WinMain/wWinMain in order); if there's no entry
function, pop the fuzzy symbol picker so you can pick one. Fires exactly once
(guarded by _cur + _did_auto_land) and never steals focus from a user who
already navigated during load.
Tests: new auto_land scenario covers both branches (jump-to-main when present,
picker fallback otherwise) + the idempotency guard. Boot/startup now key "load
complete" off _func_index.complete instead of the status string (auto-land
legitimately overwrites the "N functions" status with the landed fn). Full
suite 120 pass / 1 pre-existing flaky (filter).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
`Ctrl+N` then typing crashed with `AttributeError: 'NoneType' object has no
attribute 'lower'` when a function had no name: Func.from_raw took `d["name"]`
verbatim, so a server-returned null/missing name became None and blew up
_fuzzy (and would break sort/rename-prefill too).
Two-layer fix:
* Func.from_raw synthesizes IDA's `sub_<ADDR>` for a null/empty/missing name
(and tolerates a missing size) so `name` is always a str for every
consumer; and
* _fuzzy guards against a falsy name defensively.
Verified: Func.from_raw({addr, name:null}) -> "sub_1000"; _fuzzy(None,'m') ->
None (no raise). Pilot palette + startup 9/0.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The hex view's lazy-load was dominated by the byte-read path, which was slow
on two axes:
* server: get_bytes uses read_bytes_bss_safe, a per-byte loop (is_loaded +
get_byte = 2 IDA calls/byte → ~8k calls for a 4KB block), and encodes the
result as "0x00 0x01 ..." text (~5x wire bloat);
* client: read_bytes re-parsed that with int(tok,16) per byte;
* and get_bytes silently TRUNCATES ≥16KB responses to "0x..." (wrong data).
New injected `read_raw` tool does a single bulk ida_bytes.get_bytes (C-speed)
and only re-checks is_loaded for the sparse 0xFF bss-sentinel bytes, returning
one contiguous hex string. Client decodes with bytes.fromhex (C-speed). domain
read_bytes uses it with a transparent fallback to get_bytes on older servers
(cached _no_read_raw flag). HEX_BLOCK 4096→16384 now that big blocks are cheap
and no longer truncate → 4x fewer round-trips while scrolling.
Measured (echo, warm): 4KB 22.7ms→4.7ms (4.9x), 8KB 51.6ms→6.6ms (7.8x), and
16KB works (15.7ms) where get_bytes truncates outright. Byte-exact match vs the
old path; disasm opcode bytes (also read_bytes) benefit too. Pilot hex +
disasm_nav 12/0. Needs a supervisor restart (new server tool).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
Press 'd' on a listing head to define typed data at that address via a prompt
(the ".data type definitions" backlog item). Mirrors the retype prompt flow:
MakeDataRequested -> #makedata Input (prefilled with a size-appropriate default
type: unsigned __int8/16/32/64 or char[N]) -> _do_make_data worker ->
Program.make_data -> bump_items -> reopen the listing in place. Accepts any C
type IDA's SetType understands: int, char[16], my_struct, T *arr[4], ...
Pilot: listing_view gains a deterministic sub-test — undefine a data head to
synthesize an unknown run, assert it renders as `unknown`, then 'd' char[4]
over it and assert it becomes a `data` head. 118 pass / 1 pre-existing flaky
(filter); rpc_smoke 29/0.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The M2 fix (step by get_item_end so undefined bytes render and arbitrary
addresses land exactly) emits one row per undefined byte, which would explode
a large .bss/undefined region into millions of rows and make load_all crawl.
Collapse a run of consecutive undefined bytes into a single `db N dup(?)` row
(its end found in O(1) via next_head, which skips undefined). A single stray
undefined byte still renders normally (shows its value), so exact-address
navigation and the c/p/u workflow are unchanged: a 42-byte gap is one row at
the run start, and 'p' there auto-analyzes the function.
Verified: undefining a function yields one `db 42 dup(?)` row (not 42);
echo .text stays 5036 heads; data segments unaffected. region_define +
listing_view + test_domain[listing] all green. Needs a supervisor restart.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The real disassembly-listing view. ListingView is a line-virtualized
ScrollView (reusing ColumnCursor/SearchMixin/NavMixin) backed by
ListingModel, rendering code, data (db/dw/dd, strings, jump tables) and
undefined bytes interleaved with per-kind styling. Non-function regions now
open in it instead of the M0 DisasmModel stopgap.
Integration (IdaTui): a new `listing` active mode. _open_entry routes region
entries to ListingView; _show_active/compose add it; follow/xrefs/comment and
the c/p/u edit verbs handle it (define_func upgrades a region straight to the
function view); backslash reaches hex and returns via _code_mode (so leaving
hex from a region lands back on the listing, not a func view); Tab explains
there's no pseudocode for a region. rpc._active_widget learns `listing`.
Server fix (the important one): the `heads` walker now steps by get_item_end
instead of next_head. next_head SKIPS undefined bytes, so after undefining a
function its start address wasn't even a listing line and the cursor snapped to
the next defined item; stepping by item-end renders undefined bytes as `db ?`
lines (IDA-accurate) and makes navigation land exactly on any address —
essential for "go to unmarked bytes and hit c". Needs a supervisor restart.
Tests: region_define rewritten to assert the ListingView path + segment-wide
listing + p-upgrade; new listing_view scenario (data-segment listing renders
data heads, cursor reports head ea, backslash->hex->back). Pilot 115 pass / 1
pre-existing flaky (filter); rpc_smoke 29/0; test_domain 27/0.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The keystone for a real disassembly-listing view (unlike DisasmModel, which
is one function, code-only). Two pieces:
server/patch_server.py: inject a `heads` tool. It walks item heads over a
segment with next_head/prev_head and renders each via generate_disasm_line,
so it returns a flat listing where code, data (db/dw/dd, strings, jump
tables) and undefined bytes all appear as typed rows {ea,kind,size,text,name}.
Unlike `disasm` (code-only, bails at the first data byte) it shows the whole
segment. Address-paged: chain forward via cursor.next, page up with back=true
(returns the N heads ending before addr, in forward order, + cursor.prev).
domain.py: ListingModel — a lazily-grown, segment-scoped head index
(FunctionIndex-style forward paging via cursor.next; line index == position
in the walked list). ensure_ea() gives random access to an address (resolving
a mid-item byte to its containing head). New Head dataclass, Program.listing()
(cached per segment) + segment_bounds(); section_of() now derives from it;
bump_items() clears the listing cache too.
Verified live: heads renders strings/jump-tables/unknown correctly, forward
chaining + back-paging work; ListingModel walks echo .text (5036 heads,
~276ms) with cached windows and mid-item address resolution. tests/test_domain
gains a [listing] section (27 passed). Pilot hex/region_define/disasm_nav green.
Note: adding a server tool needs a supervisor restart (workers respawn).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Navigating to an address not inside a function no longer refuses ("no
function contains X"): _do_navigate now opens a region view (flat listing
anchored at the EA, forced to disasm since there's no pseudocode). The
server's no-function disasm already walks heads to the segment end, so
DisasmModel is reused as-is; NavEntry gains is_region.
Adds the IDA c/p/u structure-edit verbs on the disasm view:
c = define_code (undefine-first, since create_insn won't carve a live item)
p = define_func
u = undefine
wired via EditItemRequested -> _do_edit_item worker -> Program.define_code/
define_func/undefine. define_func upgrades the region to a real function
view in place.
New Program.bump_items() invalidates the item/function structure caches
(disasm blocks, decomp, function indices + name gen) — broader than
bump_names(), which only covers renames.
Pilot: new `region_define` scenario undefines a small function, navigates
to the bare region via the 'g' prompt (asserts it opens, not refused),
then 'p' recreates the function and upgrades the view; restores the IDB in
a finally. 107 passed / 1 pre-existing flaky (filter, fails on baseline).
|
| |
|
|
| |
spawn-hang + bare KeyError)
|
| | |
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
Fetch per-instruction opcode bytes alongside the disasm listing and show
them in a column between the address and the mnemonic. Instruction length
comes from consecutive addresses (variable-length safe: x86 movabs shows
its full 10 bytes); the block over-fetches one instruction for the last
line's boundary, falling back to the function end for the final insn.
The column pads to the widest instruction across the whole function:
_fetch_block tracks a running max, and on load a background scan_bytes()
settles a stable width so padding doesn't jump as blocks stream in.
_op_field is shared by the rendered strip, _line_plain, and search _fmt
so cursor/match offsets stay aligned. 'o' toggles the column.
|
| | |
|
| | |
|
| | |
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
toggle_view's settle predicate (lambda: app._active != before) never
fired when tabbing toward pseudocode on a function Hex-Rays can't
decompile: App._apply_decomp snaps the view back to disasm, so _active
returns to its prior value -> full 20s settle timeout (x2 in _show_decomp,
~40s for drive pc). Recognize the decomp-failed fallback as settled.
Also harden two amplifiers surfaced by the same case:
- rpcclient: the CLI socket had no read timeout and would block forever
on any server slowness; add a bounded settimeout (IDATUI_RPC_TIMEOUT,
default 90s) with a clear error.
- domain.decompile: pass a bounded 15s timeout and cache failures, so a
failing decompile can't sit at the 30s client default or be re-run by
transport retries.
|
| |
|
|
|
|
|
|
| |
The pc command called only the read-only pseudocode RPC (runs off the UI
loop, never touches the screen), so LLM-driven sessions showed nothing on
the live pane. Compose goto + toggle_view + search so the real TUI
navigates to the function, makes the decomp pane the visibly-active view,
and jumps the cursor to the needle — then return the same text as before.
|
| |
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Driving via rpcclient meant re-exporting the socket every fresh shell,
long invocations, and piping ~every call through python -c to pull one
field or grep pseudocode. idatui.drive fixes that: it auto-resolves the
socket (the single live pane from the registry), prints compact text
instead of JSON, and bundles the common gestures:
where | go | pc <fn> [substr] | dis <fn> [n] | callees/callers <fn> |
names <substr> | rename <old> <new> | mv old=new... | note <fn> <text> |
screen | raw <method> k=v
So 'goto+rename+parse' becomes 'drive rename old new', and 'pseudocode |
python -c grep' becomes 'drive pc fn needle'. rpcclient stays the raw
transport. rpc_smoke drives it end-to-end (29 green).
|
| |
|
|
|
|
|
|
|
|
| |
Pressing ';' on the decompiler's signature or local-declaration lines did
nothing ('no address on this line to comment') because those lines carry
no /*0xEA*/ marker. Fall back to the current function's entry ea so
commenting the header annotates the function itself; the prompt says
'function comment @ ...'. Body-line comments are unchanged.
New scenario comment_func locks it (suite 104 green).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Before creating the TUI pane, spawn probes 127.0.0.1:8745 and, if nothing
is listening, launches ./spawn.sh in its own detached tmux pane and waits
for the port (server_started/server_pane are reported in the JSON). Only
for a local server, never a duplicate (spawn.sh binds the port), and it's
not killed on 'stop' (shared across TUIs). --no-ensure-server opts out;
IDATUI_SERVER_CMD overrides the launch command (used by the test).
tests/pane_smoke.py exercises the machinery with a dummy port-binder in a
pane (start / detect / idempotent / remote-guard), 5 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.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
python -m idatui.pane spawn --open <bin> (or --db <session>) splits a
tmux pane running the TUI on a fresh RPC socket, blocks until it reports
ready (ping.ready; --open runs full auto-analysis so the default wait is
generous), and prints {sock,pane,ready,functions,module} as JSON. The
agent then drives it via idatui.rpcclient and tears it down with
'stop --sock <s>' (graceful quit -> kill-pane -> unlink). A small
registry in $XDG_RUNTIME_DIR tracks panes for 'list'/'stop'.
Verified end-to-end: spawn -> goto/pseudocode -> list -> stop.
|
| |
|
|
|
|
|
|
|
|
|
| |
Spawn-and-drive needs to know when the freshly-launched TUI is usable:
ping/state now carry {ready,functions,complete} (cheap; no health call)
and ping adds the module name. Add a graceful 'quit' verb (acks, then
exits on a short delay so the reply still flushes) and a self-documenting
'methods' table. Program-dependent verbs are refused with a clear
'not ready' instead of exploding in a worker while still loading.
rpc_smoke covers readiness, methods and quit (21 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).
|