mirror of
https://github.com/hakril/PythonForWindows
synced 2026-06-08 14:31:45 +00:00
[WIP] simple_arm64 + testsuite
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
try:
|
||||
import capstone
|
||||
except ImportError as e:
|
||||
capstone = None
|
||||
import pytest
|
||||
|
||||
import windows.native_exec.simple_arm64 as arm64
|
||||
from windows.native_exec.simple_arm64 import *
|
||||
|
||||
from windows.pycompat import int_types
|
||||
|
||||
if capstone:
|
||||
disassembleur = capstone.Cs(capstone.CS_ARCH_ARM64, capstone.CS_MODE_ARM)
|
||||
disassembleur.detail = True
|
||||
|
||||
@pytest.fixture
|
||||
def need_capstone():
|
||||
if capstone is None:
|
||||
raise pytest.skip("Capstone is not installed")
|
||||
return True
|
||||
|
||||
pytestmark = pytest.mark.usefixtures("need_capstone")
|
||||
|
||||
|
||||
def disas(x):
|
||||
return list(disassembleur.disasm(x, 0))
|
||||
|
||||
mnemonic_name_exception = {'movabs': 'mov'}
|
||||
|
||||
|
||||
class CheckInstr(object):
|
||||
def __init__(self, instr_to_test, expected_result=None, immediat_accepted=None, must_fail=None, debug=False):
|
||||
self.instr_to_test = instr_to_test
|
||||
self.immediat_accepted = immediat_accepted
|
||||
self.expected_result = expected_result
|
||||
self.must_fail = must_fail
|
||||
self.debug = debug
|
||||
|
||||
def __call__(self, *args):
|
||||
try:
|
||||
if self.debug:
|
||||
import pdb;pdb.set_trace()
|
||||
pdb.DONE = True
|
||||
arm64.DEBUG = self.debug
|
||||
res = bytes(self.instr_to_test(*args).get_code())
|
||||
if self.debug:
|
||||
print(repr(res))
|
||||
except ValueError as e:
|
||||
if self.must_fail == True:
|
||||
return True
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
if self.must_fail:
|
||||
raise ValueError("Instruction did not failed as expected")
|
||||
capres_list = disas(res)
|
||||
if len(capres_list) != 1:
|
||||
raise AssertionError("Trying to disas an instruction resulted in multiple disassembled instrs")
|
||||
capres = capres_list[0]
|
||||
print("{0} {1}".format(capres.mnemonic, capres.op_str))
|
||||
if self.expected_result is not None:
|
||||
if "{0} {1}".format(capres.mnemonic, capres.op_str) == self.expected_result:
|
||||
return True
|
||||
else:
|
||||
raise AssertionError("Expected result <{0}> got <{1}>".format(self.expected_result, "{0} {1}".format(capres.mnemonic, capres.op_str)))
|
||||
if len(res) != len(capres.bytes):
|
||||
print("<{0}> vs <{1}>".format(repr(res), repr(capres.bytes)))
|
||||
raise AssertionError("Not all bytes have been used by the disassembler")
|
||||
self.compare_mnemo(capres)
|
||||
self.compare_args(args, capres)
|
||||
|
||||
def compare_mnemo(self, capres):
|
||||
expected = self.instr_to_test.__name__.lower()
|
||||
cap_mnemo = mnemonic_name_exception.get(str(capres.mnemonic), str(capres.mnemonic))
|
||||
if expected != cap_mnemo:
|
||||
raise AssertionError("Expected menmo {0} got {1}".format(expected, str(capres.mnemonic)))
|
||||
return True
|
||||
|
||||
def compare_args(self, args, capres):
|
||||
capres_op = list(capres.operands)
|
||||
if len(args) != len(capres_op):
|
||||
raise AssertionError("Expected {0} operands got {1}".format(len(args), len(capres_op)))
|
||||
for op_args, cap_op in zip(args, capres_op):
|
||||
if isinstance(op_args, str): # Register
|
||||
if cap_op.type != capstone.arm64.ARM64_OP_REG:
|
||||
raise AssertionError("Expected args {0} operands got {1}".format(op_args, capres_op))
|
||||
if op_args.lower() != capres.reg_name(cap_op.reg).lower():
|
||||
raise AssertionError("Expected register <{0}> got {1}".format(op_args.lower(), capres.reg_name(cap_op.reg).lower()))
|
||||
elif isinstance(op_args, int_types):
|
||||
if (op_args != cap_op.imm) and not (self.immediat_accepted and self.immediat_accepted == cap_op.imm):
|
||||
raise AssertionError("Expected Immediat <{0}> got {1}".format(op_args, cap_op.imm))
|
||||
else:
|
||||
raise ValueError("Unknow argument {0} of type {1}".format(op_args, type(op_args)))
|
||||
|
||||
def test_assembler():
|
||||
CheckInstr(Add)('W0', 'W0', 0)
|
||||
CheckInstr(Add)('W1', 'W0', 0)
|
||||
CheckInstr(Add)('W30', 'W12', 0)
|
||||
CheckInstr(Add)('W0', 'W0', 1)
|
||||
|
||||
CheckInstr(Add)('X0', 'X0', 0)
|
||||
CheckInstr(Add)('X30', 'X12', 0)
|
||||
CheckInstr(Add)('X0', 'X0', 1)
|
||||
CheckInstr(Add)('X11', 'X12', 0x123)
|
||||
|
||||
# Error test todo
|
||||
# CheckInstr(Add)('X11', 'W12', 0x123)
|
||||
CheckInstr(Add)('X11', 'X12', 0x12345678)
|
||||
@@ -0,0 +1,203 @@
|
||||
import sys
|
||||
import collections
|
||||
import struct
|
||||
import binascii
|
||||
import operator
|
||||
|
||||
|
||||
# py3
|
||||
is_py3 = (sys.version_info.major >= 3)
|
||||
if is_py3:
|
||||
basestring = str
|
||||
int_types = int
|
||||
else:
|
||||
int_types = (int, long)
|
||||
|
||||
# https://documentation-service.arm.com/static/67581b3355451e3c38d97c22
|
||||
# Chapter C4: A64 Instruction Set Encoding: :
|
||||
|
||||
## C2.1.3
|
||||
# 32-bit variant (sf = 0).
|
||||
# 64-bit variant (sf = 1).
|
||||
|
||||
## C2.1.5
|
||||
# The following symbol conventions are used:
|
||||
# <Xn> The 64-bit name of a general-purpose register (X0-X30) or the zero register (XZR).
|
||||
# <Wn> The 32-bit name of a general-purpose register (W0-W30) or the zero register (WZR).
|
||||
# <Xn|SP> The 64-bit name of a general-purpose register (X0-X30) or the current stack pointer (SP).
|
||||
# <Wn|WSP> The 32-bit name of a general-purpose register (W0-W30) or the current stack pointer (WSP).
|
||||
# <Bn>, <Hn>, <Sn>, <Dn>, <Qn>
|
||||
# The 8, 16, 32, 64, or 128-bit name of a SIMD and floating-point register in a scalar context, as
|
||||
# described in Register names.
|
||||
# <Vn> The name of a SIMD and floating-point register in a vector context, as described in Register names.
|
||||
# <Zn> The name of an SVE scalable vector register, as described in Treatment of SVE scalable vector
|
||||
# registers.
|
||||
# <Pn> The name of an SVE scalable predicate register, as described in Vector predication
|
||||
|
||||
|
||||
# Make a special memoryview that match what is show in the ARM Chapter C4 ?
|
||||
|
||||
XREGISTER = {'X0', 'X1', 'X2', 'X3', 'X4', 'X5', 'X6', 'X7', 'X8', 'X9', 'X10', 'X11', 'X12', 'X13', 'X14', 'X15', 'X16', 'X17', 'X18', 'X19', 'X20', 'X21', 'X22', 'X23', 'X24', 'X25', 'X26', 'X27', 'X28', 'X29', 'X30'}
|
||||
WREGISTER = {'W0', 'W1', 'W2', 'W3', 'W4', 'W5', 'W6', 'W7', 'W8', 'W9', 'W10', 'W11', 'W12', 'W13', 'W14', 'W15', 'W16', 'W17', 'W18', 'W19', 'W20', 'W21', 'W22', 'W23', 'W24', 'W25', 'W26', 'W27', 'W28', 'W29', 'W30'}
|
||||
ALL_REGISTER = XREGISTER | WREGISTER
|
||||
SP = "SP"
|
||||
WSP = "WSP"
|
||||
|
||||
|
||||
class InstructionEncoding(object):
|
||||
def __init__(self):
|
||||
super(InstructionEncoding, self).__init__()
|
||||
# Bits are in
|
||||
# 0 1 2 3 4 ... 31
|
||||
# Translation to real little-endian is done last
|
||||
self.bytearray = bytearray(32)
|
||||
self.bits = memoryview(self.bytearray)
|
||||
|
||||
# Disable with SF = FALSE ?
|
||||
self.bitness = None
|
||||
|
||||
@classmethod
|
||||
def is_register(self, arg, accept_sp):
|
||||
arg = arg.upper()
|
||||
return (accept_sp and (arg in [SP, WSP])) or arg in ALL_REGISTER
|
||||
|
||||
@classmethod
|
||||
def is_imm12(self, arg):
|
||||
try:
|
||||
value = int(arg)
|
||||
except (ValueError, TypeError):
|
||||
return False
|
||||
return True # Check size max ?
|
||||
|
||||
@classmethod
|
||||
def is_shift(self, arg):
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def gen(cls, **encoding_array):
|
||||
class GeneratedEncoding(cls):
|
||||
ENCODING_VALUES = encoding_array
|
||||
return GeneratedEncoding
|
||||
|
||||
# Instruction filing at instanciation
|
||||
|
||||
def binencode_imm(self, immediat, outsize):
|
||||
binstr = "{:0{outsize}b}".format(immediat, outsize=outsize)
|
||||
assert len(binstr) == outsize, "Could not encode immediat {0} in {1} bits. Value take {2} bits".format(immediat, outsize, len(binstr))
|
||||
binlist = [int(c) for c in reversed(binstr)]
|
||||
return bytearray(binlist)
|
||||
|
||||
def setup_bitness(self, bitness):
|
||||
assert bitness in (32, 64)
|
||||
if self.bitness is None:
|
||||
self.bitness = bitness
|
||||
if bitness == 32:
|
||||
self.sf[:] = b"\x00"
|
||||
else: # bitness == 64:
|
||||
self.sf[:] = b"\x01"
|
||||
assert self.bitness == bitness, "bitness mismatch in instruction"
|
||||
|
||||
def encode_register(self, register, outsize=5):
|
||||
register = register.upper()
|
||||
assert register in ALL_REGISTER
|
||||
if register in XREGISTER:
|
||||
self.setup_bitness(64)
|
||||
else:
|
||||
self.setup_bitness(32)
|
||||
return self.binencode_imm(int(register[1:]), outsize)
|
||||
|
||||
def setup_register(self, regfield, register):
|
||||
encoded = self.encode_register(register)
|
||||
regfield[:] = encoded
|
||||
|
||||
# Instruction filing at instanciation
|
||||
def setup_immediat(self, immfield, value):
|
||||
immsize = len(immfield)
|
||||
immfield[:] = self.binencode_imm(value, immsize)
|
||||
return True
|
||||
|
||||
|
||||
|
||||
# C4.1.93 Data Processing - Immediate
|
||||
|
||||
class DataProcessingImmediate(InstructionEncoding):
|
||||
def __init__(self):
|
||||
super(DataProcessingImmediate, self).__init__()
|
||||
self.bits[26:29] = bytearray((0,0,1))
|
||||
self.op0 = self.bits[29:31]
|
||||
self.op1 = self.bits[22:26]
|
||||
|
||||
class AddSubtractImmediate(DataProcessingImmediate):
|
||||
SF = True
|
||||
RD = True
|
||||
RN = True
|
||||
IMM12 = True
|
||||
SH = True
|
||||
|
||||
def __init__(self, argsdict):
|
||||
super(AddSubtractImmediate, self).__init__()
|
||||
self.sf = self.bits[31:32] # Keep it a memoryview
|
||||
self.op = self.bits[30:31] # Keep it a memoryview
|
||||
self.S = self.bits[29:30] # Keep it a memoryview
|
||||
self.bits[23:29] = bytearray((0, 1, 0, 0, 0, 1))
|
||||
self.sh = self.bits[22:23]
|
||||
self.imm12 = self.bits[10:22]
|
||||
self.rn = self.bits[5:10]
|
||||
self.rd = self.bits[0:5]
|
||||
|
||||
for name, value in self.ENCODING_VALUES.items():
|
||||
print("{0} setting {1} to {2}".format(type(self).__name__, name, value))
|
||||
if isinstance(value, int):
|
||||
value = bytearray((value,))
|
||||
# self.x[:] = value
|
||||
getattr(self, name)[:] = value
|
||||
|
||||
# Change instruction based of parameter
|
||||
self.setup_register(self.rd, argsdict[0])
|
||||
self.setup_register(self.rn, argsdict[1])
|
||||
self.setup_immediat(self.imm12, argsdict[2])
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@classmethod
|
||||
def accept_arg(cls, argsdict):
|
||||
return (cls.is_register(argsdict[0], accept_sp=True) and
|
||||
cls.is_register(argsdict[1], accept_sp=True) and
|
||||
cls.is_imm12(argsdict[2]) and
|
||||
cls.is_shift(argsdict.get(3)))
|
||||
|
||||
class Instruction(object):
|
||||
encoding = []
|
||||
|
||||
def __init__(self, *args):
|
||||
argsdict = dict(enumerate(args)) # Like a list but allow arg.get(4)
|
||||
for encodcls in self.encoding:
|
||||
if encodcls.accept_arg(argsdict):
|
||||
self.encoded = encodcls(argsdict)
|
||||
return
|
||||
raise ValueError("Cannot encode <{0} {1}>:(".format(type(self).__name__, args))
|
||||
|
||||
def get_code(self):
|
||||
intlist = list(self.encoded.bits)
|
||||
if not is_py3:
|
||||
intlist = [ord(x) for x in intlist]
|
||||
# Our encoding to real little-endian
|
||||
encoding_getter = operator.itemgetter(7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8, 23, 22, 21, 20, 19, 18, 17, 16, 31, 30, 29, 28, 27, 26, 25, 24)
|
||||
dword = 0
|
||||
for bit in encoding_getter(intlist):
|
||||
assert bit in (0, 1), "Unexpected bite value in encoding of {0} : {1} in {2}".format(type(self).__name__, bit, intlist)
|
||||
dword = (dword << 1) | bit
|
||||
return struct.pack(">I", dword) # We already have handled endianess
|
||||
|
||||
|
||||
|
||||
# Fix endianned
|
||||
|
||||
|
||||
class Add(Instruction):
|
||||
encoding = [AddSubtractImmediate.gen(op=0, S=0)]
|
||||
|
||||
class Subs(Instruction):
|
||||
encoding = [AddSubtractImmediate.gen(op=1, S=1)]
|
||||
Reference in New Issue
Block a user