diff options
| author | blasty <blasty@local> | 2026-07-26 09:30:12 +0200 |
|---|---|---|
| committer | blasty <blasty@local> | 2026-07-26 09:30:12 +0200 |
| commit | 4e65c3ba16b13c3bb042830b020788a745cee9a9 (patch) | |
| tree | 791b3a94aec4e57801377943088a1dc0870704b4 /idatui/domain.py | |
| parent | listing: make every undefined byte its own row, so you can carve anywhere (diff) | |
| download | ida-tui-4e65c3ba16b13c3bb042830b020788a745cee9a9.tar.gz ida-tui-4e65c3ba16b13c3bb042830b020788a745cee9a9.tar.xz ida-tui-4e65c3ba16b13c3bb042830b020788a745cee9a9.zip | |
listing: `c` disassembles until something stops it
One instruction per keypress means pressing `c` once per opcode for the length
of a routine, which on a raw image is the whole job. IDA's `c` runs; ours now
does too.
New define_code_run tool: create instructions consecutively and report why it
stopped — 'undecodable' (bytes aren't an instruction), 'flow' (control flow ends
here), 'defined' (ran into existing code/data), 'segment' or 'limit'. It loops
inside the worker; from the client this would be one round trip per instruction,
minutes on a real image.
Stops AT a ret rather than past it: beyond the end of a routine the bytes are
usually padding or data, and running on turns a clean carve into something you
have to undo by hand. Stopping at already-defined items is the same principle —
undefining someone's existing work to keep a speculative run going isn't a trade
the user asked for.
The ret test is ida_idp.is_ret_insn, NOT canonical features: on AArch64
insn.get_canon_feature() returns 0 for RET, so a CF_STOP check silently never
fires and the run walks straight through the end of the function. Verified
against a live IDA before relying on it.
`c` on something already defined now says "already defined @ addr" instead of
claiming the instruction failed to be created — count==0 from a run means two
very different things.
Also: the result message survives the reload. Defining an item rebuilds the view,
and the reload's own cursor handler had the last word, so every edit reported
itself as "ROM @ 0x4040 [listing]". A one-shot _flash is handed to whichever
status write lands first after the edit. (Third time this clobber pattern has
turned up: split view, the no-functions hint, now this.)
Verified: nop/nop/nop/ret at 0x4040 -> "defined 4 instructions (0x4040–0x4050) —
control flow ends here", with 0x4050 left as an undefined byte. Starting on
existing code -> no-op. Random bytes -> stops at the first that won't decode.
tests: +4 blob UI (runs to the end of flow, stops at the ret, doesn't touch the
junk after it, and the status reports it). 22/0 blob, 202/0 scenarios, 30/0
project UI.
Diffstat (limited to 'idatui/domain.py')
| -rw-r--r-- | idatui/domain.py | 16 |
1 files changed, 16 insertions, 0 deletions
diff --git a/idatui/domain.py b/idatui/domain.py index 846af89..e4fbec9 100644 --- a/idatui/domain.py +++ b/idatui/domain.py @@ -1366,6 +1366,22 @@ class Program: if res.get("error"): raise IDAToolError("define_code", f"@ {ea:#x}: {res['error']}") + def define_code_run(self, ea: int, limit: int = 20000) -> dict: + """Disassemble consecutively from ``ea`` until something stops it. + + Falls back to a single instruction when the worker predates the tool, so + an old worker degrades to the previous behaviour instead of failing. + """ + try: + r = self.client.call("define_code_run", addr=hex(ea), limit=int(limit)) + except IDAToolError: + self.define_code(ea) + return {"count": 1, "stopped": "single", "end": hex(ea)} + if not isinstance(r, dict) or r.get("error"): + raise IDAToolError("define_code_run", + f"@ {ea:#x}: {(r or {}).get('error', 'failed')}") + return r + def define_func(self, ea: int) -> None: """Create a function starting at ``ea`` (IDA's 'p').""" res = self._first_result( |
