aboutsummaryrefslogtreecommitdiffstats
path: root/server/patch_server.py
diff options
context:
space:
mode:
authorblasty <blasty@local>2026-07-26 15:04:51 +0200
committerblasty <blasty@local>2026-07-26 15:04:51 +0200
commit2c2124aff2f4ed6df23512603b6e1ecdcd4b699b (patch)
tree2b2c2afe69e7f33e87ccb58e3bc466a94a4e689e /server/patch_server.py
parentarm: switch ARM/Thumb decoding with `t` (diff)
downloadida-tui-2c2124aff2f4ed6df23512603b6e1ecdcd4b699b.tar.gz
ida-tui-2c2124aff2f4ed6df23512603b6e1ecdcd4b699b.tar.xz
ida-tui-2c2124aff2f4ed6df23512603b6e1ecdcd4b699b.zip
arm: offer 32-bit ARM at load, and fix `p` on carved code
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.
Diffstat (limited to 'server/patch_server.py')
-rw-r--r--server/patch_server.py61
1 files changed, 60 insertions, 1 deletions
diff --git a/server/patch_server.py b/server/patch_server.py
index e40310a..ee7600a 100644
--- a/server/patch_server.py
+++ b/server/patch_server.py
@@ -1002,6 +1002,8 @@ def set_thumb(
if not seg:
return {"addr": hex(ea), "error": "no segment"}
+ import ida_ida
+ db64 = ida_ida.inf_get_app_bitness() == 64
cur = ida_segregs.get_sreg(ea, treg)
cur = 0 if cur in (None, 0xFFFFFFFF, -1) else int(cur)
want = {"on": 1, "off": 0}.get(str(mode).lower(), 0 if cur else 1)
@@ -1023,7 +1025,64 @@ def set_thumb(
now = ida_segregs.get_sreg(ea, treg)
return {"addr": hex(ea), "thumb": bool(now), "was": bool(cur), "ok": ok,
"bitness": ida_segment.getseg(ea).bitness,
- "forced_32bit": changed_bits}
+ "forced_32bit": changed_bits,
+ # The DATABASE's bitness is fixed at load and can't be corrected
+ # here (setting it post-hoc makes the decompiler INTERR). In a
+ # 64-bit database a 32-bit function disassembles but Hex-Rays
+ # refuses it outright, so say so instead of leaving the user to
+ # discover that F5 does nothing.
+ "db_64bit": bool(db64 and want)}
+
+@tool
+@idasync
+def define_func_run(
+ addr: Annotated[str, "Entry point of the function to create"],
+) -> dict:
+ """Create a function at ``addr``, working out its end if IDA can't.
+
+ ida_funcs.add_func(ea) asks IDA to find the end itself, and on hand-carved
+ code it often can't — a run that ends in a tail call, or whose last
+ instruction isn't recognised as a return, simply fails with no reason given.
+ You then have a disassembled routine that refuses to become a function, and
+ F5 has nothing to work with.
+
+ So: try IDA's way, and if that fails, use the end of the contiguous
+ instruction run starting at ``addr``."""
+ import ida_bytes
+ import ida_funcs
+ import ida_segment
+ import idaapi
+
+ try:
+ ea = parse_address(addr)
+ except Exception as e:
+ return {"addr": str(addr), "error": str(e), "ok": False}
+ fn = idaapi.get_func(ea)
+ if fn is not None and fn.start_ea == ea:
+ return {"addr": hex(ea), "ok": True, "start": hex(fn.start_ea),
+ "end": hex(fn.end_ea), "how": "existed"}
+ if ida_funcs.add_func(ea):
+ f = idaapi.get_func(ea)
+ return {"addr": hex(ea), "ok": True, "start": hex(f.start_ea),
+ "end": hex(f.end_ea), "how": "auto"}
+
+ seg = ida_segment.getseg(ea)
+ hi = seg.end_ea if seg else ea
+ end = ea
+ while end < hi and ida_bytes.is_code(ida_bytes.get_flags(end)):
+ nxt = ida_bytes.get_item_end(end)
+ if nxt <= end:
+ break
+ end = nxt
+ if end <= ea:
+ return {"addr": hex(ea), "ok": False,
+ "error": "no code here to make a function from"}
+ if not ida_funcs.add_func(ea, end):
+ return {"addr": hex(ea), "ok": False,
+ "error": f"IDA refused a function at {ea:#x}..{end:#x}"}
+ f = idaapi.get_func(ea)
+ return {"addr": hex(ea), "ok": True, "start": hex(f.start_ea),
+ "end": hex(f.end_ea), "how": "explicit-end"}
'''
SNIPPET = f"{BEGIN}\n{BODY.strip()}\n{END}\n"