diff options
| author | blasty <blasty@local> | 2026-07-26 00:04:38 +0200 |
|---|---|---|
| committer | blasty <blasty@local> | 2026-07-26 00:04:38 +0200 |
| commit | cf8b4645e2744fff455fc09167f91d011a3e620a (patch) | |
| tree | f8480964ec9e531ed98916f3cb6a5d513833a2b6 | |
| parent | loading: ask how to load an unrecognised file, like IDA does (diff) | |
| download | ida-tui-cf8b4645e2744fff455fc09167f91d011a3e620a.tar.gz ida-tui-cf8b4645e2744fff455fc09167f91d011a3e620a.tar.xz ida-tui-cf8b4645e2744fff455fc09167f91d011a3e620a.zip | |
formats: verify every offered processor name against a real IDA
The curated list was written from the procs/ directory listing. Two of the
twenty names were wrong, and wrong here is not a soft failure: IDA REFUSES to
open the database (rc=4) with nothing useful said. The load dialog would have
handed people a dead end from inside the UI that exists to rescue them — the
same silent-failure class the dialog was built to kill.
h8 -> h8300 (h8.so is the module FILENAME, not a processor name)
sparc -> sparcb / sparcl (and SPARC has endianness variants, like MIPS/PPC)
Also probed the aliases people reach for first: arm64, aarch64, mips, m68k are
all invalid. 'arm' covers AArch64 (verified: an AArch64 blob analyses to 35
functions under -parm), so those names now live in the human labels, where the
filter still finds them — typing "arm64" finds ARM, "m68k" finds 68k, "mips"
finds both endiannesses.
tools/verify_procs.py does the check: open a scratch blob with -p<name>, read
back inf_get_procname(), compare. Fresh temp dir per name, because once a
database exists IDA ignores the load switches and every name after the first
would "pass". 21/21 verified.
tests: +11 formats, including the verified-set guard (adding a processor without
re-running the script fails on purpose) and checks that the rejected aliases are
NOT offered but ARE still findable by typing them. 32/0 formats, 199/0 scenarios.
| -rw-r--r-- | docs/PROJECTS.md | 9 | ||||
| -rw-r--r-- | idatui/formats.py | 20 | ||||
| -rw-r--r-- | tests/test_formats.py | 29 | ||||
| -rw-r--r-- | tools/verify_procs.py | 64 |
4 files changed, 118 insertions, 4 deletions
diff --git a/docs/PROJECTS.md b/docs/PROJECTS.md index 5f8e327..88ce2f6 100644 --- a/docs/PROJECTS.md +++ b/docs/PROJECTS.md @@ -258,6 +258,15 @@ when no loader matches: │ Enter accept · Tab base address · Esc load as IDA │ └─────────────────────────────────────────────────────┘ +Every name in that list is verified against a real IDA by `tools/verify_procs.py` +(open a scratch blob with `-p<name>`, read back `inf_get_procname()`). A wrong +name is *rejected* — rc=4, nothing useful said — which would be a dead end handed +out from inside the dialog meant to rescue you. Two of the first twenty were +wrong: `h8` and `sparc` are module filenames (`procs/h8.so`), not processor +names; the real ones are `h8300` and `sparcb`/`sparcl`. The aliases people reach +for first — `arm64`, `aarch64`, `mips`, `m68k` — are all invalid too, so they +live in the human labels where the filter still finds them. + `formats.sniff()` decides whether to ask, and only recognises formats IDA definitely handles (ELF, PE, Mach-O, dex, wasm, ar, COFF, Intel HEX, S-records). Being cautious the wrong way costs one dismissible dialog; being cautious the diff --git a/idatui/formats.py b/idatui/formats.py index 744276d..b2f784d 100644 --- a/idatui/formats.py +++ b/idatui/formats.py @@ -82,8 +82,19 @@ def needs_load_options(path: str) -> bool: #: long is a worse answer than a short one plus free text. These are the targets #: that actually turn up in firmware work, endianness spelled out because #: getting it wrong is the most common way to end up with zero functions. +#: +#: EVERY NAME HERE IS VERIFIED against a real IDA by opening a scratch blob with +#: ``-p<name>`` and reading back ``inf_get_procname()`` — see +#: ``tools/verify_procs.py``. That is not ceremony: a wrong name is REJECTED by +#: IDA (rc=4) with no useful message, which is the same silent-failure class this +#: dialog exists to prevent. Two of the first twenty were wrong ("h8" and +#: "sparc" are module filenames, not processor names), and the aliases people +#: reach for first — arm64, aarch64, mips, m68k — are all invalid. Re-run the +#: script before adding to this list. PROCESSORS: tuple[tuple[str, str], ...] = ( - ("arm", "ARM / AArch64 — little-endian"), + # 'arm' covers AArch64 too; arm64/aarch64 are NOT valid -p names, so they + # live in the label where the filter can still find them. + ("arm", "ARM / AArch64 / arm64 — little-endian"), ("armb", "ARM — big-endian"), ("metapc", "x86 / x86-64"), ("mipsl", "MIPS — little-endian"), @@ -91,7 +102,7 @@ PROCESSORS: tuple[tuple[str, str], ...] = ( ("ppc", "PowerPC — big-endian"), ("ppcl", "PowerPC — little-endian"), ("sh4", "SuperH SH-4"), - ("68k", "Motorola 68000"), + ("68k", "Motorola 68000 / m68k"), ("riscv", "RISC-V"), ("tricore", "Infineon TriCore"), ("xtensa", "Tensilica Xtensa (ESP32 etc.)"), @@ -100,8 +111,9 @@ PROCESSORS: tuple[tuple[str, str], ...] = ( ("tms320c6", "TI TMS320C6x DSP"), ("m32r", "Renesas M32R"), ("arc", "Synopsys ARC"), - ("h8", "Renesas H8"), - ("sparc", "SPARC"), + ("h8300", "Renesas H8/300"), + ("sparcb", "SPARC — big-endian"), + ("sparcl", "SPARC — little-endian"), ("s390", "IBM S/390"), ) diff --git a/tests/test_formats.py b/tests/test_formats.py index f88021c..ab72e8d 100644 --- a/tests/test_formats.py +++ b/tests/test_formats.py @@ -79,6 +79,35 @@ def main() -> int: check("the processor list leads with the common targets", [n for n, _ in PROCESSORS[:3]] == ["arm", "armb", "metapc"], f"{[n for n, _ in PROCESSORS[:3]]}") + + # Every offered name must have been checked against a real IDA, because a + # wrong one is REJECTED (rc=4) with nothing useful said — handing the user a + # dead end from inside the dialog meant to rescue them. This list is the + # output of tools/verify_procs.py; adding a processor without re-running it + # fails here on purpose. + VERIFIED = { + "arm", "armb", "metapc", "mipsl", "mipsb", "ppc", "ppcl", "sh4", "68k", + "riscv", "tricore", "xtensa", "avr", "z80", "tms320c6", "m32r", "arc", + "h8300", "sparcb", "sparcl", "s390", + } + offered = {n for n, _ in PROCESSORS} + check("every offered processor name is IDA-verified", + offered <= VERIFIED, f"unverified: {sorted(offered - VERIFIED)}") + + # These are module FILENAMES or common aliases, not -p names. IDA refuses + # them; they were in the list until a real run said otherwise. + for wrong in ("h8", "sparc", "arm64", "aarch64", "mips", "m68k"): + check(f"{wrong!r} is not offered (IDA rejects it)", wrong not in offered) + + # ...but someone WILL type them, so the labels have to carry the alias. + def finds(q): + ql = q.lower() + return [n for n, d in PROCESSORS if ql in n.lower() or ql in d.lower()] + check("typing 'arm64' still finds ARM", "arm" in finds("arm64"), f"{finds('arm64')}") + check("typing 'aarch64' still finds ARM", "arm" in finds("aarch64")) + check("typing 'm68k' still finds 68k", "68k" in finds("m68k"), f"{finds('m68k')}") + check("typing 'mips' finds both endiannesses", + set(finds("mips")) == {"mipsl", "mipsb"}, f"{finds('mips')}") check("every processor entry has a human label", all(n and d for n, d in PROCESSORS)) check("endianness is spelled out where it matters", diff --git a/tools/verify_procs.py b/tools/verify_procs.py new file mode 100644 index 0000000..8012931 --- /dev/null +++ b/tools/verify_procs.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +"""Check every name in ``formats.PROCESSORS`` against a real IDA. + +Run this before adding a processor to the list. + +A wrong ``-p`` name is not a soft failure: IDA refuses to open the database +(rc=4) with nothing useful said, which is the same silent-failure class the load +dialog exists to prevent. Offering a name that doesn't work would hand the user +a dead end from inside the very UI meant to rescue them. + +Two of the first twenty entries were wrong — ``h8`` and ``sparc`` are module +FILENAMES (procs/h8.so, procs/sparc.so), not processor names; the real ones are +``h8300`` and ``sparcb``/``sparcl``. The aliases people reach for first +(``arm64``, ``aarch64``, ``mips``, ``m68k``) are all invalid too, which is why +they appear in the human labels instead, where the filter can still find them. + + /usr/bin/python tools/verify_procs.py # needs idalib, not textual +""" + +from __future__ import annotations + +import os +import sys +import tempfile + +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from idatui.formats import PROCESSORS # noqa: E402 + + +def main() -> int: + import idapro + idapro.enable_console_messages(False) + import ida_auto + import ida_ida + + blob = bytes(range(256)) * 8 + bad: list[tuple[str, int, str]] = [] + for name, desc in PROCESSORS: + # A fresh directory each time: once a database exists IDA ignores the + # load switches, and every name after the first would "pass". + d = tempfile.mkdtemp() + path = os.path.join(d, "probe.bin") + with open(path, "wb") as f: + f.write(blob) + rc = idapro.open_database(path, run_auto_analysis=False, args=f"-p{name}") + got = "" + if rc == 0: + ida_auto.auto_wait() + got = ida_ida.inf_get_procname() + idapro.close_database(save=False) + ok = rc == 0 and got.lower() == name.lower() + print(f" {'ok ' if ok else 'BAD '} {name:<12} rc={rc} -> {got!r} {desc}") + if not ok: + bad.append((name, rc, got)) + + print(f"\n{len(PROCESSORS) - len(bad)}/{len(PROCESSORS)} verified") + for name, rc, got in bad: + print(f" {name}: rc={rc} procname={got!r}") + return 1 if bad else 0 + + +if __name__ == "__main__": + raise SystemExit(main()) |
