<feed xmlns='http://www.w3.org/2005/Atom'>
<title>ida-tui.git/tests/test_thumb_ui.py, branch main</title>
<subtitle>tui for headless ida</subtitle>
<id>https://git.sl0p.foo/ida-tui.git/atom/tests/test_thumb_ui.py?h=main</id>
<link rel='self' href='https://git.sl0p.foo/ida-tui.git/atom/tests/test_thumb_ui.py?h=main'/>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/'/>
<updated>2026-07-26T18:49:30Z</updated>
<entry>
<title>arm: find Thumb entry points from a vector table (Shift+T)</title>
<updated>2026-07-26T18:49:30Z</updated>
<author>
<name>blasty</name>
<email>blasty@local</email>
</author>
<published>2026-07-26T18:49:30Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/commit/?id=781c5eff4218d7ffdc9905f652ca08ce10953e43'/>
<id>urn:sha1:781c5eff4218d7ffdc9905f652ca08ce10953e43</id>
<content type='text'>
An ARM function pointer carries the mode in bit 0: odd means Thumb. A Cortex-M
vector table is therefore a list of Thumb entry points, and IDA won't follow them
on a headerless image because nothing tells it those words are pointers at all.
Shift+T scans forward from the cursor and marks them.

  0 functions -&gt; 3 Thumb entries found, 3 disassembled

A word only counts when it is odd, lands in a loaded segment, and its target is
executable and not already data. The even words in a vector table — the initial
stack pointer — fail the first test, which is the point: marking a data word as
code corrupts the listing, so a false positive costs more than a miss. The
fixture includes an even in-range word and an odd OUT-of-range word to keep that
honest.

A note on how this started: I recommended this feature, then probed
experiments/fibonacci.bin for the signal and found ZERO odd in-range pointers —
it's a flat code blob, not a firmware image. Rather than build a detector I
couldn't test, I wrote experiments/cortexm.bin: a real vector table pointing at
small self-contained Thumb handlers. The first version of that fixture aimed its
handlers into the middle of copied code, so two "entries" were really inside one
function — the tool was right and the fixture was wrong, which is worth stating
because I nearly filed it as a bug.

Function creation goes through one _idatui_add_func helper now, shared with
define_func_run: add_func(ea) alone fails on freshly-marked code (IDA can't find
the end), and the scan hit exactly the same wall `p` did.

Status precedence, fixed properly this time. An action's result kept being
overwritten by the reload it triggered — cursor moved, filter re-applied,
functions re-counted. I patched that at FIVE separate call sites before
admitting it's one problem. _status(text, priority=True) now marks a result: it
holds the bar for 8s or until the next keypress, and routine chatter can't
outrank it. The per-site special cases are gone.

tests: +4 thumb (20) — a bare vector table gives IDA nothing, scanning finds
exactly the three handlers, the non-pointer words are ignored, and the result
survives both the reload and the reindex. 209/0 scenarios, 30/0 blob, 30/0
project UI.
</content>
</entry>
<entry>
<title>decomp: say the FIX, not the diagnosis</title>
<updated>2026-07-26T18:35:41Z</updated>
<author>
<name>blasty</name>
<email>blasty@local</email>
</author>
<published>2026-07-26T18:35:41Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/commit/?id=e02dae30a36a730620740bea195bb8eeba40d8bd'/>
<id>urn:sha1:e02dae30a36a730620740bea195bb8eeba40d8bd</id>
<content type='text'>
The 64-bit failure now reported Hex-Rays verbatim — "only 64-bit functions can
be decompiled in the current database" — with the actionable half appended after
it. A status bar cuts off the end, so the user got a perfect description of their
problem and nothing about what to do, which is the same dead end as before with
extra words.

  before: sub_0: cannot decompile — only 64-bit functions can be decompiled in
          the current database — Ctrl+L and pick arm:ARMv7-A      (125 chars)
  after:  sub_0: cannot decompile — this database is 64-bit — Ctrl+L, pick
          arm:ARMv7-A                                              (76 chars)

For this one failure the instruction IS the whole message: it can't be fixed in
place (bitness is decided at load), so describing the database serves nobody.
Other Hex-Rays reasons still pass through verbatim — they're usually about the
function, and there the description is the useful part.

Verified the whole path in a live pane on experiments/fibonacci.bin: the load
dialog now shows arm vs arm:ARMv7-A/M/v6-M/v5TE with their bitness spelled out,
picking arm:ARMv7-A gives 54 functions, and sub_0 decompiles:

  void __fastcall __noreturn sub_0(int a1) { ... v2 = sub_E3C(a1, 0); ... }

tests: thumb (16) now asserts the message names the fix rather than quoting
Hex-Rays, and that it fits under 110 chars — the truncation is what made the
last version useless, so it's worth a check. 209/0 scenarios, 30/0 blob.
</content>
</entry>
<entry>
<title>decomp: say WHY a function won't decompile</title>
<updated>2026-07-26T13:38:06Z</updated>
<author>
<name>blasty</name>
<email>blasty@local</email>
</author>
<published>2026-07-26T13:38:06Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/commit/?id=fb275522b143d3891f5f8e9b1e6eaa635537ec15'/>
<id>urn:sha1:fb275522b143d3891f5f8e9b1e6eaa635537ec15</id>
<content type='text'>
"I can't decompile my freshly defined function" — and the app's answer was to
flash and go back to the listing. It knew the reason and threw it away twice.

The plain decompile tool reports "Decompilation failed at 0x0 (address: 0x0)".
Hex-Rays itself fills in a hexrays_failure_t with the actual sentence, and in
this case it is the whole answer: "only 64-bit functions can be decompiled in
the current database". Nobody can guess that from a flash, and it is not fixable
in place — the database's bitness is set at load — so without the message there
is no way forward at all.

New decomp_error tool returns Hex-Rays' own description; _load_decomp asks for it
in the same worker when a decompile fails, and the status now reads:

  sub_0: cannot decompile — only 64-bit functions can be decompiled in the
  current database — Ctrl+L and pick arm:ARMv7-A

Then it got thrown away a second time, by the reload. Falling back to the
listing reopens it, and the reload writes its own status afterwards — the same
clobber that has now bitten four times. The fix this round is the last one:
_flash is SHOWN by idle status writes but no longer CONSUMED by them, because a
reload emits several (prime, then cursor) and consuming on the first meant the
second erased the message. It clears on the next keypress instead — when the
user has actually moved on.

(That also fixes a self-inflicted "status: None": the old code read _flash back
after something else had already consumed it.)

tests: +2 thumb (15) — a failed decompile says why in Hex-Rays' words, and the
reason survives the view reloading under it. 209/0 scenarios, 30/0 blob, 30/0
project UI.
</content>
</entry>
<entry>
<title>arm: offer 32-bit ARM at load, and fix `p` on carved code</title>
<updated>2026-07-26T13:04:51Z</updated>
<author>
<name>blasty</name>
<email>blasty@local</email>
</author>
<published>2026-07-26T13:04:51Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/commit/?id=2c2124aff2f4ed6df23512603b6e1ecdcd4b699b'/>
<id>urn:sha1:2c2124aff2f4ed6df23512603b6e1ecdcd4b699b</id>
<content type='text'>
Reported as "after c a few times and p at the entry point, Tab just flashes and
nothing decompiles". Three separate things, found by following it down:

**1. `p` failed on hand-carved code.** ida_funcs.add_func(ea) asks IDA to find
the function's end and on carved code it often can't — a run ending in a tail
call, or whose last instruction isn't recognised as a return, fails with no
reason given. add_func(ea, end) with an explicit end succeeds. define_func_run
tries IDA's way first, then falls back to the end of the contiguous instruction
run, and says which it used.

**2. The database was 64-bit, so Hex-Rays refused it regardless.** Bare `-parm`
gives an AArch64 database. Ask Hex-Rays for the failure object rather than
reading None as "dunno" and it says exactly what's wrong: "only 64-bit functions
can be decompiled in the current database". So the disassembly looked right and
F5 could never work.

That is decided at LOAD and cannot be corrected — inf_set_app_bitness(32)
afterwards makes the decompiler INTERR 50735. The fix is at the load dialog:
arm:ARMv7-A (most firmware), arm:ARMv7-M / arm:ARMv6-M (Cortex-M, Thumb only)
and arm:ARMv5TE now sit alongside 64-bit `arm`, labelled with their bitness.
With arm:ARMv7-A, experiments/fibonacci.bin decompiles:

  void __fastcall __noreturn sub_0(int a1) { int v2; v2 = sub_E3C(a1, 0); ... }

— and IDA's own auto-analysis finds 54 Thumb functions on load, versus none as
plain `arm`.

**3. `t` was silently building an undecompilable state.** It forced the SEGMENT
to 32-bit in a 64-bit database, which produces correct-looking disassembly that
F5 will never touch. It now says so and names the fix (Ctrl+L, arm:ARMv7-A)
rather than leaving you to discover it.

tools/verify_procs.py now reports each processor's resulting bitness, since that
is the reason the variants exist — and it compares against the base module name,
because a variant reports "ARM".

tests: test_thumb_ui.py +5 (13 total) — a 64-bit database warns and names the
fix, a 32-bit one finds functions by itself, Tab decompiles a Thumb function and
the result reads like C. test_formats.py +2 (34) pinning that a 32-bit variant is
offered and the ARM labels state their bitness. 209/0 scenarios, 26/0 blob, 30/0
project UI.
</content>
</entry>
<entry>
<title>arm: switch ARM/Thumb decoding with `t`</title>
<updated>2026-07-26T12:51:07Z</updated>
<author>
<name>blasty</name>
<email>blasty@local</email>
</author>
<published>2026-07-26T12:51:07Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/ida-tui.git/commit/?id=7bdf3741c91f76bfff3f3e054a5cf41b7f476d0b'/>
<id>urn:sha1:7bdf3741c91f76bfff3f3e054a5cf41b7f476d0b</id>
<content type='text'>
`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.
</content>
</entry>
</feed>
