diff options
| author | blasty <blasty@local> | 2026-08-06 15:11:54 +0200 |
|---|---|---|
| committer | blasty <blasty@local> | 2026-08-06 15:11:54 +0200 |
| commit | 92306a3f148edd465d0f4bb58cb2dba87cca59f2 (patch) | |
| tree | 526f792aa7a9fe84ddde0f4133a1cbe0802455c7 /tests/test_rawimage_rpc.py | |
| parent | rpc: an opfmt verb, and 'drive fmt' (diff) | |
| download | ida-tui-92306a3f148edd465d0f4bb58cb2dba87cca59f2.tar.gz ida-tui-92306a3f148edd465d0f4bb58cb2dba87cca59f2.tar.xz ida-tui-92306a3f148edd465d0f4bb58cb2dba87cca59f2.zip | |
tests: cover the literal formats
Pilot scenarios for the listing and the pseudocode, for the mark moving
between operands, for a refusal not being swallowed by the previous
success, and for the cursor staying on its literal across a reflow. Plus
experiments/opfmt_tools.py, which runs the real injected tool sources
against a live database with the decorators stubbed -- faster than the
pilot and the right place for the IDA-side edge cases.
Also fixes two pre-existing bugs the work surfaced, both of which made
edits happen off screen: cursor_on searched from row 0 of the whole
segment and never scrolled, so a driver's word= edit landed in an
unrelated function while reporting success; and the cursor verb didn't
scroll either. Both now go through rpc.place_cursor.
Diffstat (limited to 'tests/test_rawimage_rpc.py')
| -rw-r--r-- | tests/test_rawimage_rpc.py | 45 |
1 files changed, 45 insertions, 0 deletions
diff --git a/tests/test_rawimage_rpc.py b/tests/test_rawimage_rpc.py index 4da6348..df1166d 100644 --- a/tests/test_rawimage_rpc.py +++ b/tests/test_rawimage_rpc.py @@ -22,6 +22,7 @@ Requires: tmux or zellij, IDA (idalib). ~2min. """ import json import os +import re import subprocess import sys import tempfile @@ -118,6 +119,50 @@ def main() -> int: check("define rejects an unknown kind", bad is not None and "unknown define kind" in bad, str(bad)) + # -- opfmt (how a literal is displayed) --------------------- # + # Thumb code is full of small immediates -- the thing 'o' exists + # for -- but the ENTRY POINT hasn't got one, so find a line that + # has. `show` asks without editing, which is how a driver does + # that: the rendered text alone can't be trusted (a listing read + # before an ARM/Thumb switch shows the old decoding). + c.call("goto", target="0x0", delay_ms=0) + seen = c.call("view", lines=40).get("lines", []) + lit = None + for ln in seen: + m = re.match(r"([0-9A-F]{8})\s+(.*)", ln.get("text", "")) + if not (m and re.search(r"#(0x[0-9A-Fa-f]{2,}|[1-9]\d+)\b", + m.group(2))): + continue + ea_s = "0x" + m.group(1) + st = c.call("opfmt", mode="show", target=ea_s, delay_ms=0 + ).get("opfmt", {}).get("status", "") + if "no literal" not in st: + lit = (ea_s, st) + break + check("the blob has an immediate to reformat", lit is not None, + json.dumps([ln.get("text") for ln in seen[:8]])) + if lit is not None: + tgt, st0 = lit + check("opfmt show reports the stops without editing", + "[" in st0 and "dec" in st0, st0) + r = c.call("opfmt", mode="dec", target=tgt, delay_ms=0) + st1 = r.get("opfmt", {}).get("status", "") + check("opfmt sets a named format", "dec" in st1, st1) + r = c.call("opfmt", mode="cycle") + st2 = r.get("opfmt", {}).get("status", "") + check("opfmt cycles on from there", "\u2192" in st2, st2) + r = c.call("opfmt", mode="default") + check("opfmt hands the operand back to IDA", + "default" in r.get("opfmt", {}).get("status", ""), + r.get("opfmt", {}).get("status", "")) + badfmt = None + try: + c.call("opfmt", mode="roman") + except RpcError as e: + badfmt = str(e) + check("opfmt rejects an unknown mode", badfmt is not None + and "unknown opfmt mode" in badfmt, str(badfmt)) + # -- rename_many -------------------------------------------- # fns = c.call("functions", limit=200) ea = min(f["ea"] for f in fns) if fns else None |
