| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Following a call to strcmp reached the PLT/extern entry and stopped there —
Hex-Rays has nothing to decompile, because the code lives in a library this
binary only references. With the other binary open in the same project we
already had everything needed to cross that gap; we just weren't indexing it.
Index each binary's imports and exports (KIND_IMPORT / KIND_EXPORT) alongside its
functions and strings. On a follow, _import_stub asks whether the target address
is one of this binary's import stubs; if so _cross_binary_impl asks the index who
exports that name, and we switch there instead of landing on the thunk.
Verified end to end on a real echo + libc project: Enter on `strrchr(a1, 47)` in
echo's pseudocode switches to libc.so.6 and lands on strrchr at 0xaf960.
Three things it turns on:
* ELF symbol versioning. The importer sees strrchr@@GLIBC_2.2.5 while the
provider may export any of three spellings, so raw names resolve almost
nothing. domain.link_name() cuts at the first '@'; Linkage.raw keeps what IDA
reported, which is what the listing shows.
* Exact match, not substring — ProjectIndex.exact(), so `read` doesn't bind to
pread/read_line/thread_start. It also answers below the 3-char trigram floor,
and plenty of real exports are that short.
* Resolution reads the on-disk index, so a provider resolves while its worker is
evicted. That's what the index was for.
When nothing in the project provides the symbol _follow_import declines and the
normal navigation runs: landing on the stub is still the honest answer, and a
single-binary session is unchanged. The PLT-stub PRESENTATION item stays open —
an unprovided import should say "imported, provider not in project" rather than
show a decompiler error.
server/patch_server.py gains list_linkage (idautils.Entries + enum_import_names);
a worker without it degrades to no linkage rather than failing.
tests: index join +8 (exact vs substring, short names, exclude-self, reverse
join, kind isolation, forget unresolves) and link_name +4. 36/0 index, 195/0
scenarios, 23/0 project UI, 33/0 project, 22/0 pool.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
In the decompiler, 'y' on a local variable already worked (func_types -> lvars ->
set_lvar_type), but a GLOBAL fell through every case and silently retyped the
ENCLOSING FUNCTION'S PROTOTYPE — worse than not working, since the prompt said
"prototype" while you thought you were typing a variable.
* server/patch_server.py: new data_type tool — {addr,name,type,size,is_func} for
a data item, so the prompt can prefill the current type and the caller can tell
a global from a function.
* domain: Program.data_type() + set_data_type() (set_type with kind="global").
* app: _prepare_retype gains the data case between "function" and the
current-function fallback, with a size-based prefill when the global is still
untyped; _do_retype routes kind="data" to set_data_type.
Classification verified on echo/main: 'v3' -> lvar (prefill 'char *'), 'stdout'
-> data (prefill 'FILE *'), 'main' -> func prototype, an unresolvable token ->
the enclosing prototype (unchanged fallback).
Also fixes a latent crash found while probing this: on_listing/decomp_view_
cursor_moved called self.query_one(ListingView), but App.query_one searches the
TOP screen — a cursor-moved message landing while any modal is up (loading
overlay, project switch) raised NoMatches out of a message handler and killed the
app. Both handlers now go through _try_view().
Pilot `retype` extended to 9 checks covering all three flavours, each asserting
the other targets are left alone. Two of the new checks needed settles: applying
a retype recompiles asynchronously, so scanning/indexing the pseudocode without
waiting reads text that's about to be replaced (this also cut the scenario from
30s to 2.6s of previously-wasted timeout). Full suite 174/2-flake.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
ida-pro-mcp exposes no full strings list (only a filtered/capped "interesting"
survey), so this is a new injected tool plus a filterable browser.
* server/patch_server.py: list_strings(offset,count,min_len,refresh) — every
literal from idautils.Strings() as {addr,text,len,type}, paginated, with a
module-level cache keyed by min_len (rebuilding is O(n) and the browser pages
the whole list).
* domain: StrLit dataclass + Program.strings() — pages the full list once and
caches it.
* app: StringsPalette modal (mirrors SymbolPalette) — case-insensitive substring
filter with the match highlighted, addr/len/text columns, ↑↓/Enter/Esc.
Bodies are sanitized to one printable line (\n/\r/\t escaped, non-printables
dropped, long strings clipped) so control chars can't break the layout;
display strings are pre-rendered+pre-lowered once since filtering runs per
keystroke. Enter jumps to the literal in the unified listing via _goto_ea.
Bound to '"' and Shift+F12, plus a "Strings…" command-palette entry.
Verified on echo: 150 strings listed with addr/len/text, filtering 'usage'
narrows to 2 (case-insensitive), Enter lands the listing cursor on the literal.
Pilot `strings` scenario 6/6; full suite 165/2-flake.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The Ghidra "region band": moving the pseudocode cursor now lights up EVERY
instruction that C line owns, not just one.
* server/patch_server.py: new decomp_map tool — sweeps cfunc.get_line_item across
each pseudocode line's columns and collects the ea from each item's dstr()
('EA: desc', matching the /*ea*/ marker source so it aligns with the display
lines). Returns {addr, lines:[{ea, eas:[...]}]}. (First tried item.get_ea(),
which reports a different ea and didn't align — dstr() is the right source.)
* domain: Program.decomp_map(ea) -> per-line ea lists, cached by name-gen.
* app: _load_split_map fetches it off-thread into _split_eamap/_split_ea2line;
_sync_split bands the full instruction region for a C line (decomp drives) and
uses the exact ea->line inverse (listing drives), falling back to the single
marker until the map lands. Maps cleared on leaving split.
Pilot split_view gains: decomp_map returns/aligns with the markers, and a
multi-instruction C line bands >1 listing row (13/13). Full suite 154/2-flake.
The idalib spike ran on the pilot's own worker (the standalone worker kept
getting reaped in this sandbox).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
ida-pro-mcp's xref_query only classifies xrefs as code/data (xr.iscode). Add an
injected `xref_types` tool (server/patch_server.py) that mirrors xref_query's
query/envelope shape but derives a fine `kind` from the IDA xref type:
call/jump/flow for code (fl_CF/CN/JF/JN/F), read/write/offset/text/info for data
(dr_R/W/O/T/I). The worker self-injects it on startup like the other custom tools.
* domain: Xref gains a `kind` field; _parse_xrefs reads it; xrefs_to() now calls
xref_types (falling back to xref_query if absent). xrefs_from is unchanged.
* app: the `x` dialog shows the kind as an aligned column after the address
(`000034F4 read sub_34F0+0x4`).
* rpc: the structured xrefs_to read carries `kind` too.
Verified live over the worker/RPC harness on targets/echo: sub_2C00 callers ->
call; __progname -> offset (GOT), read (sub_34F0), write (sub_3500); stdout ->
read/offset.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Opening a binary now defaults to our own idalib worker; the ida-pro-mcp HTTP
supervisor path is deprecated (kept only for --db/attach and --backend mcp).
* launch.py: --backend default resolves to worker for a fresh binary open, mcp
for the attach modes (--db / bare `ida-tui`, which have no worker equivalent);
explicit --backend or IDATUI_BACKEND still wins. Logs a deprecation notice when
the mcp path is used.
* Deprecation markers on client.py and server/patch_server.py; the ida-tui shell
header and README now describe the worker as primary and note
$IDATUI_WORKER_PYTHON. TODO tracks the removal checklist.
No code deleted yet — the mcp fallback stays until the worker is proven on a box
where idalib can spawn (pilot against --backend worker is the gate). Backend
resolution matrix verified: `ida-tui bash`->worker, bare/`--db`->mcp, explicit
flag/env honored.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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).
|
| |
|
|
|
|
|
|
|
|
|
| |
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).
|
| |
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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).
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
Add a file_regions server tool (get_fileregion_offset per segment) and
Program.file_regions/file_offset so a virtual address maps to its raw
on-disk offset without a format-specific header parser. The hex view now
shows a VA column and a file-offset column side by side, and the status
line reports file+off.
Goto moves to its own top-level #goto Input instead of overloading the
function-filter box (which is hidden with the names pane, so goto was
unreachable there). In the hex view 'g' jumps the cursor to an address
(bounds-checked against the image); elsewhere it navigates as before.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Instead of parsing pseudocode text, add structured server tools (server/
patch_server.py, alongside del_type):
- func_types(addr): prototype + local variables (name/type/is_arg)
- set_lvar_type(addr,var,type): retype a decompiler local, working on auto/
register vars too (stock set_type only updates already-user-modified lvars)
'y' in a code view retypes what's under the cursor: a local variable (prompt
prefilled with its current type) or a function (prompt prefilled with its full
prototype). Applies via set_lvar_type / set_type, then recompiles + refreshes.
Copy-line moved off 'y' to Ctrl+Y.
domain: Program.func_types / set_function_type / set_lvar_type (LVar/FuncTypes).
Pilot: function-prototype retype (prefill + apply). full suite 94/94.
|
|
|
ida-pro-mcp has no delete-type tool, so struct-editor delete couldn't work. We
don't vendor/fork the server; instead server/patch_server.py idempotently appends
a del_type @tool to the installed ida_pro_mcp/ida_mcp/api_types.py, which every
worker (python -m ida_pro_mcp.idalib_server) imports -> it self-registers on the
shared MCP_SERVER. spawn.sh runs the patch before launching, so it's re-applied
on each start (survives reinstalls) and tracked in-repo instead of hand-editing
site-packages.
del_type calls ida_typeinf.del_named_type(get_idati(), name, NTF_TYPE). After a
supervisor restart, Program.delete_type() now succeeds; the struct editor's Del
removes the type and the list refreshes. Verified end-to-end; pilot 84/84.
|