Skip to content

Report the nop and ret encodings a big-endian machine holds in memory - #371

Open
zardus wants to merge 1 commit into
masterfrom
feature/fix-archinfo-nop-ret
Open

Report the nop and ret encodings a big-endian machine holds in memory#371
zardus wants to merge 1 commit into
masterfrom
feature/fix-archinfo-nop-ret

Conversation

@zardus

@zardus zardus commented Aug 14, 2026

Copy link
Copy Markdown
Member

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

Arch.__init__ byte-swaps nop_instruction and ret_instruction for every big-endian architecture through reverse_ends, which works in four-byte words and zero-extends anything shorter. ArchS390X("Iend_BE") therefore reported 00000707/0000f407 for two-byte instructions, and ArchAArch64("Iend_BE") and ArchRISCV64("Iend_BE") had a correct little-endian encoding reversed, since both keep little-endian instructions whatever the endness of data. None of the six byte strings decodes to anything.

The swap now runs only when instruction_endness is big-endian, and reverse_ends no longer invents bytes for a length that is not a multiple of four. PowerPC, MIPS and ARM, whose instruction endness does follow data endness, are byte-identical, as is every little-endian architecture. The s390x literals move to the little-endian spelling the other classes use, and ret_instruction becomes br %r14, the ABI return, rather than br %r4.

tests/test_instruction_bytes.py pins the encoding every architecture reports in both endnesses.

Fixes #370. Validation: #371 (comment)

@zardus

zardus commented Aug 14, 2026

Copy link
Copy Markdown
Member Author

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

Validation record for head b798d53a393005d7f72d7446a736343ec7ec12e7 against baseline da171ca0dd8eec1e16dd9c4f04576b2c958e2aea.

  • Regression: python -m pytest tests/test_instruction_bytes.py — 6 failed on baseline (S390X, AArch64-BE and RISCV64-BE encodings, and the two reverse_ends length cases), 5 passed / 34 subtests passed on head
  • Focused: python -m pytest tests/ — 21 passed, 34 subtests passed
  • Lint/type: run-ci-diff-checks.py --repository archinfo — pylint arch.py 9.70 -> 9.70, arch_s390x.py 9.82 -> 9.82, new tests/test_instruction_bytes.py 10.00; pyright badness arch.py 0.2227 -> 0.2218, arch_s390x.py unchanged
  • pre-commit run --all-files — all hooks pass, tree unchanged
  • Workspace gate: workspace, test-inputs, pre-commit, feature-instances, archinfo — pass. pypcode, pyvex, cle, claripy, angr, angr-rust and angr-management are skipped because this feature adopts only archinfo; see the corpus row for what covers them.

Every architecture, both endnesses, before and after. Only three rows move, and all three were undecodable before:

arch endness nop before nop after ret before ret after
S390X BE 00000707 0707 0000f407 07fe
AArch64 BE d503201f 1f2003d5 d65f03c0 c0035fd6
RISCV64 BE 00000001 0100 00008082 8280
X86, AMD64 LE 90 90 c3 c3
ARM, ARMEL, ARMHF, ARMCortexM LE / BE 00000000 00000000 1eff2fe1 / e12fff1e unchanged
MIPS32, MIPS64 LE / BE 00000000 00000000 0800e00325082000 / 03e0000800200825 unchanged
PPC32, PPC64 LE / BE 00000060 / 60000000 unchanged 2000804e / 4e800020 unchanged
AArch64, RISCV64 LE unchanged unchanged unchanged unchanged

Checked against Capstone rather than against angr. capstone.Cs(CS_ARCH_SYSZ, CS_MODE_BIG_ENDIAN) decodes 0707 as bcr 0, %r7, 07fe as br %r14 and 07f4 as br %r4, and decodes neither 00000707 nor 0000f407. CS_ARCH_ARM64 decodes 1f2003d5 as nop and c0035fd6 as ret; it decodes d503201f as fnmadd s21, s30, s0, s0 and does not decode d65f03c0. CS_ARCH_RISCV decodes 0100 as c.nop and 8280 as c.jr ra; 00000001 decodes as c.unimp; c.addi4spn and 00008082 stops after two bytes. Both 07fe and 07f4 occur in binaries/tests/s390x/fauxware and binaries/tests/s390x/libc.so.6; 0000f407 occurs in neither.

Blast radius, 1710 objects over 138 architecture/container strata, 55 architectures, 16 containers, scored with CFGFast(normalize=True, data_references=False, resolve_indirect_jumps=True, force_complete_scan=False) before and after in the same worktree: 1255 loaded, 399 CLE load errors and 56 unsupported containers on both sides. One line differed — an x86 CaRT object that hit the 180s cap on one side under load; re-run alone, both sides give ok, 51603 blocks, 6804 functions. No other object changed, and no new error or timeout.

That flat result rules out a regression rather than showing a benefit: grep over angr, cle, pyvex, claripy and pypcode finds no reader of either attribute, and no caller of reverse_ends outside archinfo. The only consumer in the ecosystem is angr-management, which pads a shortened patch with arch.nop_instruction; on s390x it previously wrote 00 00 07 07 in four-byte units, and now writes 07 07 in two.

Caveats: no big-endian AArch64 or RISC-V object exists in the corpus, so those two rows rest on the ISA and on Capstone, not on a binary. Nothing exercises ret_instruction anywhere in the ecosystem, so the br %r4 -> br %r14 correction has no runtime evidence behind it beyond the comment already in the class and the s390x ABI.

@angr-bot

Copy link
Copy Markdown
Member

Corpus decompilation diffs can be found at angr/dec-snapshots@master...angr/archinfo_371

Arch.__init__ swapped nop_instruction and ret_instruction for every
big-endian architecture, through reverse_ends, which works in four-byte
words and zero-extends anything shorter. Three architectures came out
with bytes that decode to nothing:

  ArchS390X("Iend_BE")    nop 00000707  ret 0000f407
  ArchAArch64("Iend_BE")  nop d503201f  ret d65f03c0
  ArchRISCV64("Iend_BE")  nop 00000001  ret 00008082

s390x instructions are two bytes here, and the padding to four turns
them into a string that appears in no s390x binary. AArch64 and RISC-V
declare instruction_endness LE: their instructions stay little-endian
however data is stored, so the swap reverses an encoding that was
already right.

Swap only when instructions themselves are big-endian, and stop
reverse_ends inventing bytes for a length that is not a multiple of
four. PowerPC, MIPS and ARM, whose instruction endness does follow data
endness, are unchanged, as are all little-endian architectures.

The s390x literals are now written little-endian like every other
class, and ret_instruction encodes br %r14, the ABI return, rather than
br %r4.

  ArchS390X("Iend_BE")    nop 0707      ret 07fe
  ArchAArch64("Iend_BE")  nop 1f2003d5  ret c0035fd6
  ArchRISCV64("Iend_BE")  nop 0100      ret 8280

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@zardus
zardus force-pushed the feature/fix-archinfo-nop-ret branch from b798d53 to b43aa15 Compare August 22, 2026 12:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

nop_instruction and ret_instruction are byte-swapped by data endianness and padded to four bytes

2 participants