Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions archinfo/arch.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import logging
import platform as _platform
import re
import struct as _struct
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple, Type, Union

from archinfo.types import RegisterName, RegisterOffset
Expand Down Expand Up @@ -200,8 +199,9 @@ def __init__(self, endness, instruction_endness=None):
if _keystone and self.ks_mode is not None:
self.ks_mode -= _keystone.KS_MODE_LITTLE_ENDIAN
self.ks_mode += _keystone.KS_MODE_BIG_ENDIAN
self.ret_instruction = reverse_ends(self.ret_instruction)
self.nop_instruction = reverse_ends(self.nop_instruction)
if self.instruction_endness == Endness.BE:
self.ret_instruction = reverse_ends(self.ret_instruction)
self.nop_instruction = reverse_ends(self.nop_instruction)

if self.register_list and _pyvex is not None:
(_, _), max_offset = max(_pyvex.vex_ffi.guest_offsets.items(), key=lambda x: x[1])
Expand Down Expand Up @@ -929,11 +929,15 @@ def arch_from_id(ident: str, endness=Endness.ANY, bits="") -> Arch:
return cls(endness)


def reverse_ends(string):
count = (len(string) + 3) // 4
ise = "I" * count
string += b"\x00" * (count * 4 - len(string))
return _struct.pack(">" + ise, *_struct.unpack("<" + ise, string))
def reverse_ends(string: bytes) -> bytes:
"""
Swap the endness of every four-byte word in ``string``.

A trailing group of fewer than four bytes is reversed on its own, so the result always has the
same length as the input.
"""

return b"".join(string[offset : offset + 4][::-1] for offset in range(0, len(string), 4))


def get_host_arch():
Expand Down
2 changes: 1 addition & 1 deletion archinfo/arch_s390x.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def __init__(self, endness=Endness.BE):
if _keystone:
ks_arch = _keystone.KS_ARCH_SYSTEMZ
ks_mode = _keystone.KS_MODE_BIG_ENDIAN
ret_instruction = b"\x07\xf4" # br %r14
ret_instruction = b"\xfe\x07" # br %r14
nop_instruction = b"\x07\x07" # nopr %r7
instruction_alignment = 2
register_list = [
Expand Down
70 changes: 70 additions & 0 deletions tests/test_instruction_bytes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import unittest

from archinfo import (
ArchAArch64,
ArchAMD64,
ArchARM,
ArchMIPS32,
ArchMIPS64,
ArchPPC32,
ArchPPC64,
ArchRISCV64,
ArchS390X,
ArchX86,
)
from archinfo.arch import Endness, reverse_ends

# The encoding each architecture holds in memory, as a disassembler reads it.
EXPECTED = [
# (class, endness, nop_instruction, ret_instruction)
(ArchX86, Endness.LE, b"\x90", b"\xc3"), # nop / ret
(ArchAMD64, Endness.LE, b"\x90", b"\xc3"), # nop / ret
(ArchARM, Endness.LE, b"\x00\x00\x00\x00", b"\x1e\xff\x2f\xe1"), # andeq r0, r0, r0 / bx lr
(ArchARM, Endness.BE, b"\x00\x00\x00\x00", b"\xe1\x2f\xff\x1e"),
(ArchPPC32, Endness.LE, b"\x00\x00\x00\x60", b"\x20\x00\x80\x4e"), # nop / blr
(ArchPPC32, Endness.BE, b"\x60\x00\x00\x00", b"\x4e\x80\x00\x20"),
(ArchPPC64, Endness.LE, b"\x00\x00\x00\x60", b"\x20\x00\x80\x4e"),
(ArchPPC64, Endness.BE, b"\x60\x00\x00\x00", b"\x4e\x80\x00\x20"),
# jr $ra followed by its delay slot, or $at, $at, $zero
(ArchMIPS32, Endness.LE, b"\x00\x00\x00\x00", b"\x08\x00\xe0\x03\x25\x08\x20\x00"),
(ArchMIPS32, Endness.BE, b"\x00\x00\x00\x00", b"\x03\xe0\x00\x08\x00\x20\x08\x25"),
(ArchMIPS64, Endness.LE, b"\x00\x00\x00\x00", b"\x08\x00\xe0\x03\x25\x08\x20\x00"),
(ArchMIPS64, Endness.BE, b"\x00\x00\x00\x00", b"\x03\xe0\x00\x08\x00\x20\x08\x25"),
# AArch64 and RISC-V keep little-endian instructions whatever the endness of data
(ArchAArch64, Endness.LE, b"\x1f\x20\x03\xd5", b"\xc0\x03\x5f\xd6"), # nop / ret
(ArchAArch64, Endness.BE, b"\x1f\x20\x03\xd5", b"\xc0\x03\x5f\xd6"),
(ArchRISCV64, Endness.LE, b"\x01\x00", b"\x82\x80"), # c.nop / c.jr ra
(ArchRISCV64, Endness.BE, b"\x01\x00", b"\x82\x80"),
(ArchS390X, Endness.BE, b"\x07\x07", b"\x07\xfe"), # nopr %r7 / br %r14
]


class TestInstructionBytes(unittest.TestCase):
"""Check that every architecture reports the encoding a disassembler reads out of memory."""

def test_nop_and_ret_encodings(self):
for cls, endness, nop, ret in EXPECTED:
with self.subTest(arch=cls.__name__, endness=endness):
arch = cls(endness)
assert arch.nop_instruction == nop
assert arch.ret_instruction == ret

def test_instruction_length_is_preserved(self):
for cls, endness, _, _ in EXPECTED:
with self.subTest(arch=cls.__name__, endness=endness):
arch = cls(endness)
assert len(arch.nop_instruction) == len(cls.nop_instruction)
assert len(arch.ret_instruction) == len(cls.ret_instruction)

def test_reverse_ends_swaps_whole_words(self):
self.assertEqual(reverse_ends(b""), b"")
self.assertEqual(reverse_ends(b"\x01\x02\x03\x04"), b"\x04\x03\x02\x01")
self.assertEqual(reverse_ends(b"\x01\x02\x03\x04\x05\x06\x07\x08"), b"\x04\x03\x02\x01\x08\x07\x06\x05")

def test_reverse_ends_does_not_pad_a_short_word(self):
self.assertEqual(reverse_ends(b"\x01\x02"), b"\x02\x01")
self.assertEqual(reverse_ends(b"\x01\x02\x03\x04\x05\x06"), b"\x04\x03\x02\x01\x06\x05")


if __name__ == "__main__":
unittest.main()
Loading