| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The feature coverage was right, the prose was not: eleven paragraphs that
explained MECHANISM in an intro document. The keys table right above it
already proved that scannable beats thorough here.
Every feature is now name + key + at most two lines. Longest block went from
~70 words to 35; the section from 80 lines to 53.
What got cut was rationale, not information, and none of it is lost -- each
one is already the docstring of the module that implements it: why findings
has to journal edits (journal.py), how search guesses text vs bytes
(search.py), trail painting and unseen bytes as ?? (trace.py), why terminal
graphics support is asked for rather than sniffed (kittygfx.py). The README
now says what a feature IS and links onward.
Also folded hex/strings/palettes/projects/splash into one 'Also' line (all
already in the keys table), and gave the same treatment to the Run prose and
the agent-skill paragraph.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Follows the Agent Skills standard (agentskills.io) rather than a contrib/
dir: harnesses discover .agents/skills/<name>/SKILL.md automatically from
the repo root, and it is vendor-neutral (not .claude/ or .pi/ specific).
Not a verbatim copy of the local one -- it was ~85% generic and the rest
had to go before redistributing:
* the backend is described as a normal pyproject dependency, with the
editable-git-checkout setup demoted to a conditional note (that is one
maintainer's layout, but the 'upstream pull silently swaps the backend'
warning is worth keeping for anyone who does it);
* personal agent tooling (bgrun/waitfor wrappers) replaced by the
underlying lesson -- a backgrounded subshell holds the inherited stdout,
so redirect all three fds -- which is true without those scripts;
* maintainer commit authorship dropped;
* absolute ~/dev paths made relative to the repo root; ~/ida-venv kept but
marked as this project's assumption, matching the README.
Also fixes the README's stale full-run count (788/~2m -> 1031/~50s), lists
the docs that were missing from the index, and warns that a user-level skill
of the same name shadows this one -- otherwise a personal copy silently wins
and the in-repo copy rots.
|
| | |
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
`/` only ever searched the lines of the view you were in. This adds the
search you actually need on a binary: over the entire database, either
through the rendered disassembly or through the image.
* **text** matches the line as displayed, whitespace-normalised, so
`call cs:` finds `call cs:getenv_ptr` (IDA's column padding is not
something anyone types). Smartcase; `regex` available over RPC.
* **bytes** is IDA's own `find_bytes`, so the pattern language people
already know works unchanged: hex pairs, `?` wildcards for a whole byte
or one nibble (`48 8? ?? 24`), quoted literals (`"Hello", 0`). Commas,
no separators (`488B05C3`) and ragged spacing all normalise.
**Which mode you meant is guessed, and the guess is biased on purpose.**
`dead`, `add`, `cafe` and `ff` are valid hex AND ordinary things to search
for, so a bare hex-looking word stays TEXT; nobody types `48 8b ?? c3`
meaning prose. `hex:`/`text:` prefixes and F2 override it.
The subtle case is a *typo* in a byte pattern. `48 zz c3` first fell
through to a text search and reported "no match" — indistinguishable from
"those bytes are not in this binary", which is the most misleading answer
a search can give. Now any query whose tokens are all byte-sized is
treated as bytes, and a bad token is refused BY NAME. IDA does the same
thing quietly (find_bytes answers a malformed pattern with zero hits and
no error), so the validation lives in Program.search, not just in the UI.
Enter searches, then Enter opens the highlighted hit; the title says which
it will do, because a database-wide scan is far too slow to run on every
keystroke like the other palettes. Navigation goes to the item head — a
byte match can start mid-instruction — and the status names the exact
address.
Also: the `find` RPC verb and `drive find`, which is the one an agent
wants (`drive find '48 8b ?? c3'`).
idatui/search.py holds the classification and is pure, so the whole
question of "what did they mean" is tested offline: tests/test_search.py,
35 checks, 0.1s. Pilot scenario db_search covers the UI end to end.
Full suite: 890 passed, 0 failed, 51.3s.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The output of an RE session is what you worked out, and it was locked in a
.i64 that only IDA can read. Ctrl+E (or `drive export`, or the `export` RPC
verb) writes it out: your comments grouped by function with the line each
annotates, the names and prototypes you set, the types you declared.
**The hard part was provenance, and it needed a mechanism, not a filter.**
A database does not record WHO wrote a comment or a name. IDA's analyzer
sets `; switch 73 cases` and `; s1` with the same `set_cmt` a person uses,
and the ELF loader sets `elf_gnu_hash_nbuckets` and `File class: 64-bit`
the same way. Four probes, all negative: the FF_COMM flag is identical,
`get_cmt` returns them all, `generate_disasm_line` tags every one of them
COLOR_REGCMT (not COLOR_AUTOCMT), and they survive with auto-comments
switched off. A first cut filtered by shape and produced a report whose
first screen was ELF header trivia and `; jumptable ... case 99`.
So idatui journals its own edits (idatui/journal.py) into a netnode in the
database: it rides along in the .i64, it is still there next session, and
the report is then exactly what was done here -- 2 findings out of a
database carrying 693 other annotations. Recorded at the choke points in
edit_ctl (rename, name-address, comment, retype) and in the struct editor;
flushed on save, on export and on quit, so no edit pays a round trip.
Without a journal (a database worked on in the IDA GUI, or predating this)
the report falls back to filtering by shape -- dummy names, imports, loader
segments, the analyzer's stereotyped switch/jumptable strings -- and says
so in the document rather than claiming authorship it cannot prove.
idatui/findings.py splits gather (needs IDA) from render (does not), so the
formatting, grouping, sorting, escaping and the empty cases are tested
offline: tests/test_findings.py, 32 checks, no worker, 0.1s. The pilot
scenario covers the round trip that matters -- edit through the UI, export,
find it in the file, and reload the journal from the .i64.
Full suite: 842 passed, 0 failed, 51.2s.
|
| | |
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Ten scenes over the RPC layer: goto, listing scroll, pseudocode, the literal
format ring, follow/back, the graph (zoom, minimap, edge walking), split view,
xrefs, a rename and a comment, the strings/symbols/structs/hex browsers, and
search. It drives the semantic verbs, so prompts are typed character by
character and a recording shows the app being used rather than poked.
--spawn opens its own pane on a COPY of the target (binary plus .i64, so the
analysis is not re-paid) and tears it down afterwards; --sock drives a session
you set up yourself, for control over pane size and zoom. Edits are reverted at
the end, including a re-navigate so the last frame does not still show the
demo's rename. --speed scales every pause, --only runs a subset, --list prints
the scenes.
Writing it found a real bug in the RPC hex verb: it waited for `app.is_hex` to
become true, but backslash TOGGLES the hex view, so the call that leaves hex
could never satisfy its predicate and always timed out after 20s. Any driver
could open the hex view and never close it. It now waits for the mode to flip,
the way toggle_view already did.
Verified by running every scene against a live database and asserting the UI
actually moved -- landed on main, decomp reachable, graph reports 280 blocks,
split toggles off, no modal left open, the rename reaches the database and the
revert removes it. 15/15. Full suite still 788 passed, 0 failed.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
README: "Known sharp edges" removed.
docs/GRAPH_VIEW.md pointed the graph's one backend call at
server/patch_server.py, a file the port deleted. It is
idatui/codemode_client.py now. This matters because GRAPH_VIEW.md is one of
only two docs the README links -- a wrong path in a linked doc is worse than
no doc.
docs/RPC.md was missing five verbs rpc.py serves: ping/methods/quit (now a
Session table) and trace/binaries/switch (with the semantic verbs). The trace
verb drives a whole feature the README advertises, so its absence was the real
gap. Every verb in rpc.py's METHODS table is now documented, checked
mechanically -- the remainder of what looked missing turned out to be values
of the "move" verb's dir parameter, not verbs.
Deleted:
- docs/CODEMODE_PORT.md. Written mid-port, and its Result section claims the
port covered "the complete current TUI feature set". It did not: the graph
view was dead, the operand-format keys were dead, pseudocode comments never
appeared, and bulk rename was broken. Keeping a doc that states the opposite
of what we found is worse than having none, and docs/CODEMODE_UPSTREAM.md
carries the accurate account of the API limits.
- docs/TUI_DRIVING_BLUEPRINT.md. A design sketch for generalizing the
drive-a-TUI-over-a-socket pattern to other programs, with gdb as the main
example -- which now exists for real in ~/dev/gdb-driver. Superseded by the
thing it proposed.
Nothing linked either file. Both remain in git history.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
The <img> tag existed only to set a width, and the <div align=center> only to
centre things -- neither of which markdown can express. Sizing the image file
itself removes the reason for both: logo-trans.png was a 1024x1024 1.7MB
master being scaled down in the browser on every page load, and is now 400x400
and 219KB. Nothing else uses it (the splash reads logo.png and logo.ans), and
the original is in git history if it is ever wanted back.
So the README is now plain markdown end to end: ![](), no divs, and the footer
is a link instead of a centred <sub>. Left-aligned rather than centred, which
is what markdown does.
|
| |
|
|
|
|
|
|
|
| |
Layout was internal orientation, not something a reader of the README needs;
the source is right there. Docs listed six files, four of which are working
notes from problems already solved (paging findings, Textual pitfalls, the
port's own coverage doc, the upstream findings) -- interesting once, noise in
a front page. What is left is the two a user actually opens, and both are
still linked inline from the feature that needs them.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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 split resync loop (f898350) had no test. Two attempts at one were worthless
and are not in this commit: a scroll-based guard passed with the bug
reintroduced, and a constructed anchor -- inside the loaded function, outside
its mapped span -- skipped, because on this target the map covers the whole
function. The real trigger is the race window while the decomp map lags the
decompiler re-pointing, which is tedious to force but wide open in split_view's
own flow.
So split_view counts lookup_funcs across its body and bounds it. Verified both
ways, which is the only reason it's worth having: 29,227 calls with the bug put
back, under 500 with the fix. The bound is loose because the bug was three
orders of magnitude out, not a near miss.
733 checks, 139s.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Fourteen test files, each its own __main__, and no way to run them but from
memory -- so in practice you ran the one you were working on and hoped. Worse,
nothing said which files need a licensed IDA and a real worker (minutes) and
which are pure stdlib (milliseconds), so the cheap ones nobody ran either.
tests/run.py runs the lot and prints one table. --fast selects only the suites
that need nothing, which is 257 checks in half a second under any python3 --
that's the one you run between edits.
The classification lives in the test files, not in a table here that would rot
the first time someone adds a test: each declares NEEDS_IDA at module scope and
run.py reads it with ast (it can't import them -- they run their suite at
import). A file without the marker is a hard error rather than a silent guess.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
logo.ans is 60x33 cells of half-blocks -- a 60x66 pixel image. logo.png is
474x516. On a terminal that speaks the kitty graphics protocol we now send the
real thing, in the same cell footprint (fit() lands on exactly 60x33, so the
layout is unchanged), and fall back to the block art everywhere else.
Three findings, each of which cost a round of "it renders nothing":
Support cannot be sniffed from the environment. Under a multiplexer that passes
the protocol through, TERM is xterm-256color and KITTY_WINDOW_ID, TERM_PROGRAM
and COLORTERM are all empty while the protocol answers OK -- detection by
terminal name would disable graphics on exactly the terminal that supports them.
So we ask: a 1x1 graphics query plus a Primary Device Attributes request, with
DA1 as the sync point.
Unicode placeholders are not usable. The tidy way to put an image in a TUI is a
virtual placement plus U+10EEEE cells that the compositor clips and moves like
text -- and it is what every Textual image library builds on -- but this
terminal answers ENOTSUPPORTED for placeholders while supporting everything
else. So the image is placed directly, anchored to screen cells Textual knows
nothing about. The splash therefore owns its lifetime: place after layout,
re-anchor when the note repaints (throttled; a placement is one short escape
with no image data), delete on unmount, or a leftover would sit on top of the
disassembly forever.
The query and the upload go on OPPOSITE sides of the alternate screen. The query
must run before Textual starts, which reads stdin on its own thread and would
eat the reply. The image must be uploaded after Textual has switched to the
alternate screen: an image uploaded to the primary screen cannot be placed from
the alternate one, and the placement reports success while drawing nothing. That
silent failure is why detection lives in launch.py and upload lives in the
splash's on_mount.
$IDATUI_KITTY_LOG traces the decisions, since none of this is visible to a test
-- correct escape sequences and visible pixels are not the same thing here.
Off-tty (the pilot suite, a pipe) detection returns False and the block art is
used, so the tests are unaffected.
|
| |
|
|
|
|
|
|
| |
cfg_dump freezes real CFGs to JSON; graph_spike renders one or --stats a
whole corpus through the shipping engine; graph_smoke is the end-to-end
tool->domain->layout check; graph_shot renders the real view headless at a
chosen size, which is the only sane way to judge it (a tiled pane is far
too narrow and the minimap sits on top of the graph).
|
| | |
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
M2. Trace.memory(addr, length, idx) reconstructs what memory held at a moment,
returning the bytes AND a per-byte "known" mask. The mask is the point: a trace
knows what it observed and nothing else, so a byte nobody read or wrote is
genuinely unknown and must not be drawn as zero. That distinction is the whole
reason to read memory from a trace instead of the database — the database has
the file's bytes, the trace has what was actually there.
Reads count as evidence, not just writes: an instruction reading a byte reveals
what it held then.
Indexed by ADDRESS (sorted once, bisect per query), because the question is
"what was in this window at time t" and the accesses that matter are the few
touching that window, not the tens of thousands in the trace.
Where the memory actually is: measured, 0% of accesses in either real trace fall
inside the image — every one is stack or heap. So the primary view is the STACK,
in the dock, anchored at SP:
stack (rsp)
▸7ffff6f99470 ????????????????
7ffff6f99478 00007ffff6fb0b00
7ffff6f99488 00007ffff6fa94e5
The hex view overlays trace bytes on the file's contents (green = the trace saw
this byte at this timestamp, grey = still the file's idea). Correct, and it will
matter for a program that writes globals, but on these traces it shows nothing —
which is why the stack pane is the deliverable and not a nice-to-have.
One bug the work surfaced: MemOp.addr was having the image slide applied to it,
which is nonsense for a stack address — it produced -0xc838. The slide relocates
the IMAGE; stack and heap have no database counterpart. Memory op addresses now
stay in trace space, and memory_raw() queries there, while memory() takes
database addresses for the hex view.
tests: +9 model (35) covering the known-mask, reads-as-evidence, partial
coverage and the writers/accessors queries; +1 differential (12) checking
reconstructed memory state against Tenet's own get_memory at sampled timestamps;
+5 UI (30) for the stack pane — present, anchored at SP, marks unseen bytes,
follows time. 212/0 scenarios.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Both code views now show where you came from and where you're going: the
instruction you're on ('now'), the ~96 steps behind it ('past', warm) and the
~96 ahead ('future', cool).
A trail, not all of history. Painting every address the trace ever touched says
almost nothing on a loop-heavy program; the last and next few dozen steps say
how you GOT here. Where an address appears on both sides — a loop body, which is
most of them — the nearer side wins, because that's the one explaining the step
you just took or are about to.
**The pseudocode is painted too**, which is the reason to build this here rather
than use Tenet. A trace records instructions, so that's what Tenet paints. We
already have decomp_map from the split-view work, saying which instructions each
C line covers, so the same trail lands on the decompilation:
line 46 now | v3 = getenv("POSIXLY_CORRECT");
line 47 future | v4 = (__int64)*a2;
line 49 future | if ( v3 )
A C line covers many instructions, so it takes the strongest kind present: now
beats past beats future — if the instruction you're standing on belongs to this
line, this line is where you are.
Two things kept cheap: the trail is recomputed per SEEK rather than per repaint
(~200 lookups, and repaints vastly outnumber steps), and decomp_map is cached
per function because it's an RPC and stepping is interactive.
The colours sit deliberately under the code palette — the trail says "you came
through here", the text still has to read as code.
tests: +8 UI (21) — the listing carries now/past/future and it reaches the
screen; pseudocode is painted; exactly ONE C line is 'now' and it is the line
covering the current instruction (not merely some executed line, which is the
mistake this check exists to catch). 209/0 scenarios, 27/0 model, 10/0 diff.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
M0 of the trace viewer. --trace FILE loads a Tenet trace beside the binary and
docks a pane on the right: where you are in time, the register state there, and
a timeline.
Docked rather than modal on purpose. A trace turns every other view into "state
at time T", so time and registers are context you read WHILE looking at code,
not something you open and dismiss.
The registers the current instruction WROTE are highlighted. That difference is
the entire reason a delta trace is readable, and it's free — the trace already
says which registers each line changed.
] / [ step one instruction. } / { step over, by following the stack pointer: a
call pushes, so the callee runs with SP below where we started, and stepping
until SP comes back up lands after the return. That's cheaper and more portable
than recognising call instructions per architecture, and it degrades correctly —
on an instruction that calls nothing, SP is already >= the start and it's one
step. Verified on a real call: t=13 -> 18, past 5 instructions, where a plain
step gives 14.
The load waits for the function index because rebasing needs the database's
addresses: our echo trace runs at 0x7ffff6faa000 and the same code sits at
0x2000 in the database. Rebased -0x7ffff6fa8000, 12 functions touched.
Register values stay as the trace recorded them (they're machine state) while
everything else on screen is in database addresses, so the header shows both —
"pc 0x2aed (trace 0x7ffff6faaaed)" — rather than leaving the two to be puzzled
over side by side.
tests: test_trace_ui.py (13) records its own trace with the QEMU tracer and
drives the real UI — loads, rebases onto real functions, the dock renders, ] and
[ step and the code view follows, and } steps OVER a call found in that trace
rather than at a hardcoded index. Skips with a message if the tracer isn't
built. 209/0 scenarios.
One thing worth recording: my first attempt to add the key bindings SILENTLY did
nothing — the pattern contained a literal \\u2026 where the file has a real
ellipsis, so the replace matched nothing and the bindings never appeared. The
action worked when called directly, which made it look like a key-routing
problem. Assert on the replacement, not on the diff looking plausible.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
`c` could not carve Thumb code. Thumb isn't a property of the bytes — it's a
mode the CPU is in — so a raw image gives IDA nothing to detect: at a Thumb entry
point it decodes 16-bit instructions as 32-bit ARM and produces confident
nonsense. experiments/fibonacci.bin starts with `08 b5` = push {r3,lr}, which
IDA reads as SVCLT 0xBF00.
`t` on the listing switches the mode at the cursor and disassembles in it:
Thumb @ 0x0 (segment set to 32-bit; Thumb needs ARM32) — 10 instructions
0x0 PUSH {R3,LR} 0x2 MOVS R1, #0 0x4 MOV R4, R0 0x6 BL unk_E3C
Setting the T segment register is only half of it. Thumb does not exist in
AArch64, and a headerless blob loaded with -parm comes up 64-bit, so T alone
changes nothing and looks broken — I watched exactly that happen while probing
the API. Asking for Thumb IS asking for ARM32, so set_thumb forces the segment
to 32-bit and says so rather than doing it silently.
It also has to del_items over the range first: the bytes are currently decoded
in the old mode, and leaving that item defined pins the wrong instruction length
so the new mode has nothing to apply to.
Implemented as a `thumb` kind in the existing edit-item flow, so it inherits the
shared reload — same cache bump, same ViewAnchor restore, same status flash. It
switches AND disassembles, because flipping T and leaving the bytes undefined
shows you nothing and reading the code was the point.
tests: new tests/test_thumb_ui.py (8) driving the real Thumb binary — `c` alone
does NOT produce the prologue, `t` does, the instructions are 16-bit wide (in ARM
mode those three rows would be one 4-byte instruction), the run continues, the
status explains the 32-bit forcing, and `t` toggles back. Deletes the .i64 first,
because T and the segment's addressing mode are saved in it and a stale database
would answer the question for us.
209/0 scenarios, 26/0 blob, 8/0 thumb.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A raw firmware dump has no format to detect, so IDA fell back to x86 at address
0. It doesn't fail — it opens, analyses, and finds nothing. An AArch64 image
loaded this way gave 0 functions; told the truth it gives 35.
ida-tui fw.bin --processor arm --base 0x8000000
and per binary in a project, which is what a multi-image firmware actually
needs:
{"path": "app.bin", "processor": "arm", "base": "0x8000000"}
idapro.open_database() already accepted IDA command-line switches; nothing was
passing any. Plumbed BinaryRef -> WorkerPool -> WorkerClient -> worker argv, plus
a load_args for the single-binary path that has no project ref.
base is written the way people say it (0x8000000, int or string, any base).
IDA's -b is in PARAGRAPHS — -b1000 loads at 0x10000 — so BinaryRef.load_args
converts, and a base that isn't 16-byte aligned is refused rather than silently
landing 16x off. ida_args passes anything else through.
Two bugs found by testing the whole path rather than the happy one:
* Project.load() whitelisted path/label when normalising entries, so the load
options were dropped the first time a project was reopened — set a processor,
come back tomorrow, it's gone.
* Re-passing the switches to an EXISTING database makes IDA refuse the open
(rc != 0, no functions). The .i64 already records how the image was loaded, so
the worker skips them once a database exists. My first guard checked
splitext(path) + ".i64" and never fired, because IDA names it "<file>.i64" —
keeping the extension. It checks both spellings now.
Verified on a real AArch64 blob: fresh load 35 functions based at 0x8002440,
reopen 35 again, and the CLI rejects an unaligned or non-numeric --base.
tests: +6 project (options recorded, paragraph conversion, file round-trip,
add() takes them, an ELF passes nothing, hex-string base). 39/0 project, 195/0
scenarios, 30/0 project UI, 36/0 index, 27/0 pool.
|
| |
|
|
|
|
| |
Three shipped features were missing from the feature list: the Ghidra-style
synced split view (s), the filterable strings browser ("), and the ida-tui
command palette (Ctrl+P).
|
| |
|
|
|
|
|
|
|
| |
The Architecture section still described the deleted client.py (HTTP MCP client);
replace it with worker.py + worker_client.py + errors.py, and note the
patch_server custom-tool injection. Modernize "What it does" (unified IDA-style
listing as the default view, F5/Tab decompile, xref kinds, the new line-motion
keys, fuzzy palette) and drop stale mentions of the 127.0.0.1:8745 supervisor and
"page over the MCP server".
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|