diff --git a/examples/script/aarch64/basic b/examples/script/aarch64/basic new file mode 100755 index 000000000..fbaf91b12 Binary files /dev/null and b/examples/script/aarch64/basic differ diff --git a/examples/script/aarch64/basic.c b/examples/script/aarch64/basic.c new file mode 100644 index 000000000..0ecf6a040 --- /dev/null +++ b/examples/script/aarch64/basic.c @@ -0,0 +1,28 @@ +// gcc -g -static -o basic basic.c + +#include +#include +#include + +int main(int argc, char* argv[], char* envp[]){ + unsigned int cmd; + + if (read(0, &cmd, sizeof(cmd)) != sizeof(cmd)) + { + printf("Error reading stdin!"); + exit(-1); + } + + if (cmd > 0x41) + { + printf("Message: It is greater than 0x41\n"); + } + else + { + printf("Message: It is less than or equal to 0x41\n"); + } + +return 0; +} + + diff --git a/examples/script/aarch64/basic.py b/examples/script/aarch64/basic.py new file mode 100755 index 000000000..c3adb751f --- /dev/null +++ b/examples/script/aarch64/basic.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 + +import os +import struct +import sys + +from manticore.native import Manticore + +# Examples: +# printf "\x41\x00\x00\x00" | PYTHONPATH=. ./examples/script/aarch64/basic.py +# printf "++\x00\x00" | PYTHONPATH=. ./examples/script/aarch64/basic.py +# printf "++++" | PYTHONPATH=. ./examples/script/aarch64/basic.py +# printf "ffffff" | PYTHONPATH=. ./examples/script/aarch64/basic.py + +DIR = os.path.dirname(__file__) +FILE = os.path.join(DIR, 'basic') +STDIN = sys.stdin.readline() + +# Avoid writing anything to 'STDIN' here. Do it in the 'init' hook as that's +# more flexible. +m = Manticore(FILE, concrete_start='', stdin_size=0) + + +@m.init +def init(state): + state.platform.input.write(state.symbolicate_buffer(STDIN, label='STDIN')) + + +# Hook the 'if' case. +@m.hook(0x4006bc) +def hook_if(state): + print('hook if') + state.abandon() + + +# Hook the 'else' case. +@m.hook(0x4006cc) +def hook_else(state): + print('hook else') + # See how the constraints are affected by input. + print_constraints(state, 6) + + w0 = state.cpu.W0 + + if isinstance(w0, int): # concrete + print(hex(w0)) + else: + print(w0) # symbolic + + solved = state.solve_one(w0) + print(struct.pack("= nlines: + break + print(c) + i += 1 + + +m.run() diff --git a/examples/script/aarch64/hello42 b/examples/script/aarch64/hello42 new file mode 100755 index 000000000..3f174fefd Binary files /dev/null and b/examples/script/aarch64/hello42 differ diff --git a/examples/script/aarch64/hello42.c b/examples/script/aarch64/hello42.c new file mode 100644 index 000000000..86fd4929b --- /dev/null +++ b/examples/script/aarch64/hello42.c @@ -0,0 +1,8 @@ +// gcc -g -static -o hello42 hello42.c +#include + +int main() +{ + puts("hello"); + return 42; +} diff --git a/examples/script/aarch64/hello42.py b/examples/script/aarch64/hello42.py new file mode 100755 index 000000000..1bd87ac37 --- /dev/null +++ b/examples/script/aarch64/hello42.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 + +import os + +from manticore.native import Manticore + +# Modified 'count_instructions.py' to demonstrate execution of a +# statically-linked "Hello, world!" AArch64 binary. + +DIR = os.path.dirname(__file__) +FILE = os.path.join(DIR, 'hello42') + +if __name__ == '__main__': + m = Manticore(FILE) + + with m.locked_context() as context: + context['count'] = 0 + + @m.hook(None) + def explore(state): + with m.locked_context() as context: + context['count'] += 1 + + if state.cpu.PC == 0x406f10: # puts + s = state.cpu.read_string(state.cpu.X0) + assert s == 'hello' + print(f'puts argument: {s}') + + elif state.cpu.PC == 0x40706c: # puts result + result = state.cpu.X0 + assert result >= 0 + print(f'puts result: {result}') + + elif state.cpu.PC == 0x415e50: # exit + status = state.cpu.X0 + syscall = state.cpu.X8 + assert syscall == 94 # sys_exit_group + print(f'exit status: {status}') + + def execute_instruction(self, insn, msg): + print(f'{msg}: 0x{insn.address:x}: {insn.mnemonic} {insn.op_str}') + + m.subscribe('will_execute_instruction', lambda self, state, pc, insn: + execute_instruction(self, insn, 'next')) + m.subscribe('did_execute_instruction', lambda self, state, last_pc, pc, insn: + execute_instruction(self, insn, 'done')) + + m.run(procs=1) + + print(f"Executed {m.context['count']} instructions") diff --git a/manticore/native/cpu/aarch64.py b/manticore/native/cpu/aarch64.py new file mode 100644 index 000000000..9b67777e2 --- /dev/null +++ b/manticore/native/cpu/aarch64.py @@ -0,0 +1,5447 @@ +import warnings + +import capstone as cs +import collections +import re +import struct + +from .abstractcpu import ( + Cpu, CpuException, Interruption, InstructionNotImplementedError, + RegisterFile, Abi, SyscallAbi, Operand, instruction +) +from .arm import HighBit, Armv7Operand +from .bitwise import SInt, UInt, ASR, LSL, LSR, ROR, Mask, GetNBits +from .register import Register +from ...core.smtlib import Operators + + +# Unless stated otherwise, all references assume the ARM Architecture Reference +# Manual: ARMv8, for ARMv8-A architecture profile, 31 October 2018. +# ARM DDI 0487D.a, ID 103018. + + +class Aarch64InvalidInstruction(CpuException): + pass + + +# Map different instructions to a single implementation. +OP_NAME_MAP = { + # Make these go through 'MOV' to ensure that code path is reached. + 'MOVZ': 'MOV', + 'MOVN': 'MOV' +} + + +# See "C1.2.4 Condition code". +Condspec = collections.namedtuple('CondSpec', 'inverse func') +COND_MAP = { + cs.arm64.ARM64_CC_EQ: Condspec(cs.arm64.ARM64_CC_NE, lambda n, z, c, v: z == 1), + cs.arm64.ARM64_CC_NE: Condspec(cs.arm64.ARM64_CC_EQ, lambda n, z, c, v: z == 0), + + cs.arm64.ARM64_CC_HS: Condspec(cs.arm64.ARM64_CC_LO, lambda n, z, c, v: c == 1), + cs.arm64.ARM64_CC_LO: Condspec(cs.arm64.ARM64_CC_HS, lambda n, z, c, v: c == 0), + + cs.arm64.ARM64_CC_MI: Condspec(cs.arm64.ARM64_CC_PL, lambda n, z, c, v: n == 1), + cs.arm64.ARM64_CC_PL: Condspec(cs.arm64.ARM64_CC_MI, lambda n, z, c, v: n == 0), + + cs.arm64.ARM64_CC_VS: Condspec(cs.arm64.ARM64_CC_VC, lambda n, z, c, v: v == 1), + cs.arm64.ARM64_CC_VC: Condspec(cs.arm64.ARM64_CC_VS, lambda n, z, c, v: v == 0), + + cs.arm64.ARM64_CC_HI: Condspec(cs.arm64.ARM64_CC_LS, lambda n, z, c, v: Operators.AND(c == 1, z == 0)), + cs.arm64.ARM64_CC_LS: Condspec(cs.arm64.ARM64_CC_HI, lambda n, z, c, v: Operators.NOT(Operators.AND(c == 1, z == 0))), + + cs.arm64.ARM64_CC_GE: Condspec(cs.arm64.ARM64_CC_LT, lambda n, z, c, v: n == v), + cs.arm64.ARM64_CC_LT: Condspec(cs.arm64.ARM64_CC_GE, lambda n, z, c, v: n != v), + + cs.arm64.ARM64_CC_GT: Condspec(cs.arm64.ARM64_CC_LE, lambda n, z, c, v: Operators.AND(z == 0, n == v)), + cs.arm64.ARM64_CC_LE: Condspec(cs.arm64.ARM64_CC_GT, lambda n, z, c, v: Operators.NOT(Operators.AND(z == 0, n == v))), + + cs.arm64.ARM64_CC_AL: Condspec(None, lambda n, z, c, v: True), + cs.arm64.ARM64_CC_NV: Condspec(None, lambda n, z, c, v: True) +} + + +# XXX: Support other system registers. +# System registers map. +SYS_REG_MAP = { + 0xc082: 'CPACR_EL1', + 0xd807: 'DCZID_EL0', + 0xde82: 'TPIDR_EL0' +} + + +class Aarch64RegisterFile(RegisterFile): + Regspec = collections.namedtuple('RegSpec', 'parent size') + + # Register table. + _table = {} + + # See "B1.2 Registers in AArch64 Execution state". + + # General-purpose registers. + for i in range(31): + _table[f'X{i}'] = Regspec(f'X{i}', 64) + _table[f'W{i}'] = Regspec(f'X{i}', 32) + + # Stack pointer. + # See "D1.8.2 SP alignment checking". + _table['SP'] = Regspec('SP', 64) + _table['WSP'] = Regspec('SP', 32) + + # Program counter. + # See "D1.8.1 PC alignment checking". + _table['PC'] = Regspec('PC', 64) + + # SIMD and FP registers. + # See "A1.4 Supported data types". + for i in range(32): + _table[f'V{i}'] = Regspec(f'V{i}', 128) + _table[f'Q{i}'] = Regspec(f'V{i}', 128) + _table[f'D{i}'] = Regspec(f'V{i}', 64) + _table[f'S{i}'] = Regspec(f'V{i}', 32) + _table[f'H{i}'] = Regspec(f'V{i}', 16) + _table[f'B{i}'] = Regspec(f'V{i}', 8) + + # SIMD and FP control and status registers. + _table['FPCR'] = Regspec('FPCR', 64) + _table['FPSR'] = Regspec('FPSR', 64) + + # Condition flags. + # See "C5.2.9 NZCV, Condition Flags". + _table['NZCV'] = Regspec('NZCV', 64) + + # Zero register. + # See "C1.2.5 Register names". + _table['XZR'] = Regspec('XZR', 64) + _table['WZR'] = Regspec('XZR', 32) + + # XXX: Check the current exception level before reading from or writing to a + # system register. + # System registers. + # See "D12.2 General system control registers". + + # See "D12.2.29 CPACR_EL1, Architectural Feature Access Control Register". + _table['CPACR_EL1'] = Regspec('CPACR_EL1', 64) + + # See "D12.2.35 DCZID_EL0, Data Cache Zero ID register". + _table['DCZID_EL0'] = Regspec('DCZID_EL0', 64) + + # See "D12.2.106 TPIDR_EL0, EL0 Read/Write Software Thread ID Register". + _table['TPIDR_EL0'] = Regspec('TPIDR_EL0', 64) + + def __init__(self): + # Register aliases. + _aliases = { + # This one is required by the 'Cpu' class. + 'STACK': 'SP', + # See "5.1 Machine Registers" in the Procedure Call Standard for the ARM + # 64-bit Architecture (AArch64), 22 May 2013. ARM IHI 0055B. + # Frame pointer. + 'FP': 'X29', + # Intra-procedure-call temporary registers. + 'IP1': 'X17', + 'IP0': 'X16', + # Link register. + 'LR': 'X30' + } + super().__init__(_aliases) + + # Used for validity checking. + self._all_registers = set() + + # XXX: Used for 'UnicornEmulator._step'. + self._parent_registers = set() + + # Initialize the register cache. + # Only the full registers are stored here (called "parents"). + # If a smaller register is used, it must find its "parent" in order to + # be stored here. + self._registers = {} + for name in self._table.keys(): + self._all_registers.add(name) + + parent, size = self._table[name] + if name != parent: + continue + self._registers[name] = Register(size) + self._parent_registers.add(name) + + def read(self, register): + assert register in self + name = self._alias(register) + parent, size = self._table[name] + value = self._registers[parent].read() + + # XXX: Prohibit the DC ZVA instruction until it's implemented. + # XXX: Allow to set this when a register is declared. + if parent == 'DCZID_EL0': + return 0b10000 + + if name != parent: + _, parent_size = self._table[parent] + if size < parent_size: + value = Operators.EXTRACT(value, 0, size) + + return value + + def write(self, register, value): + assert register in self + name = self._alias(register) + parent, size = self._table[name] + if isinstance(value, int): + assert value <= 2 ** size - 1 + else: + assert value.size == size + + # DCZID_EL0 is read-only. + # XXX: Allow to set this when a register is declared. + if parent == 'DCZID_EL0': + raise Aarch64InvalidInstruction + + # Ignore writes to the zero register. + # XXX: Allow to set this when a register is declared. + if parent != 'XZR': + self._registers[parent].write(value) + + def size(self, register): + assert register in self + name = self._alias(register) + return self._table[name].size + + @property + def canonical_registers(self): + # XXX: 'UnicornEmulator._step' goes over all registers returned from + # here, reading from and writing to them as needed. But 'read' and + # 'write' methods of this class internally operate only on "parent" + # registers. So if '_step' reads a 32-bit register that internally + # stores a 64-bit value, 'read' will truncate the result to 32 bits, + # which is okay, but '_step' will then put the truncated value back in + # the register. So return only "parent" registers from here. + # + # XXX: And Unicorn doesn't support these: + not_supported = set() + not_supported.add('FPSR') + not_supported.add('FPCR') + + # XXX: Unicorn doesn't allow to write to and read from system + # registers directly (see 'arm64_reg_write' and 'arm64_reg_read'). + # The only way to propagate this information is via the MSR + # (register) and MRS instructions, without resetting the emulator + # state in between. + # Note: in HEAD, this is fixed for some system registers, so revise + # this after upgrading from 1.0.1. + system = set(SYS_REG_MAP.values()) + + return self._parent_registers - not_supported - system + + @property + def all_registers(self): + return self._all_registers + + # See "C5.2.9 NZCV, Condition Flags". + @property + def nzcv(self): + nzcv = self.read('NZCV') + n = Operators.EXTRACT(nzcv, 31, 1) + z = Operators.EXTRACT(nzcv, 30, 1) + c = Operators.EXTRACT(nzcv, 29, 1) + v = Operators.EXTRACT(nzcv, 28, 1) + return (n, z, c, v) + + @nzcv.setter + def nzcv(self, value): + for b in value: + if isinstance(b, int): + assert b in [0, 1] + else: + assert b.size == 1 + + n, z, c, v = value + + n = LSL(n, 31, 64) + z = LSL(z, 30, 64) + c = LSL(c, 29, 64) + v = LSL(v, 28, 64) + + result = n | z | c | v + self.write('NZCV', result) + + +# XXX: Add more instructions. +class Aarch64Cpu(Cpu): + """ + Cpu specialization handling the ARM64 architecture. + """ + address_bit_size = 64 + max_instr_width = 4 + # XXX: Possible values: 'aarch64_be', 'aarch64', 'armv8b', 'armv8l'. + # See 'UTS_MACHINE' and 'COMPAT_UTS_MACHINE' in the Linux kernel source. + # https://stackoverflow.com/a/45125525 + machine = 'aarch64' + arch = cs.CS_ARCH_ARM64 + # See "A1.3.2 The ARMv8 instruction sets". + mode = cs.CS_ARCH_ARM + + def __init__(self, memory): + super().__init__(Aarch64RegisterFile(), memory) + + def _wrap_operands(self, ops): + return [Aarch64Operand(self, op) for op in ops] + + @staticmethod + def canonicalize_instruction_name(insn): + # Using 'mnemonic' rather than 'insn_name' because the latter doesn't + # work for B.cond. Instead of being set to something like 'b.eq', + # it just returns 'b'. + name = insn.mnemonic.upper() + name = OP_NAME_MAP.get(name, name) + ops = insn.operands + name_list = name.split('.') + + # Make sure MOV (bitmask immediate) and MOV (register) go through 'MOV'. + if (name == 'ORR' and len(ops) == 3 and + ops[1].type == cs.arm64.ARM64_OP_REG and + ops[1].reg in ['WZR', 'XZR'] and + not ops[2].is_shifted()): + name = 'MOV' + insn._raw.mnemonic = name.lower().encode('ascii') + del ops[1] + + # Map all B.cond variants to a single implementation. + elif (len(name_list) == 2 and + name_list[0] == 'B' and + insn.cc != cs.arm64.ARM64_CC_INVALID): + name = 'B_cond' + + # XXX: BFI is only valid when Rn != 11111: + # https://github.com/aquynh/capstone/issues/1441 + elif (name == 'BFI' and len(ops) == 4 and + ops[1].type == cs.arm64.ARM64_OP_REG and + ops[1].reg in ['WZR', 'XZR']): + name = 'BFC' + insn._raw.mnemonic = name.lower().encode('ascii') + del ops[1] + + # XXX: CMEQ incorrectly sets the type to 'ARM64_OP_FP' for + # 'cmeq v0.16b, v1.16b, #0': + # https://github.com/aquynh/capstone/issues/1443 + elif (name == 'CMEQ' and len(ops) == 3 and + ops[2].type == cs.arm64.ARM64_OP_FP): + ops[2]._type = cs.arm64.ARM64_OP_IMM + + return name + + @property + def insn_bit_str(self): + # XXX: Hardcoded endianness and instruction size. + insn = struct.unpack("= -256 and imm <= 255 + + if ldur: + result = cpu.read_int(base + imm, reg_op.size) + reg_op.write(result) + else: + reg = reg_op.read() + cpu.write_int(base + imm, reg, reg_op.size) + + def _ADD_extended_register(cpu, res_op, reg_op1, reg_op2): + """ + ADD (extended register). + + :param res_op: destination register. + :param reg_op1: source register. + :param reg_op2: source register. + """ + cpu._adds_subs_extended_register(res_op, reg_op1, reg_op2, mnem='add') + + def _ADD_immediate(cpu, res_op, reg_op, imm_op): + """ + ADD (immediate). + + :param res_op: destination register. + :param reg_op: source register. + :param imm_op: immediate. + """ + cpu._adds_subs_immediate(res_op, reg_op, imm_op, mnem='add') + + def _ADD_shifted_register(cpu, res_op, reg_op1, reg_op2): + """ + ADD (shifted register). + + :param res_op: destination register. + :param reg_op1: source register. + :param reg_op2: source register. + """ + cpu._adds_subs_shifted_register(res_op, reg_op1, reg_op2, mnem='add') + + def _ADD_vector(cpu, res_op, reg_op1, reg_op2): + """ + ADD (vector). + + :param res_op: destination register. + :param reg_op1: source register. + :param reg_op2: source register. + """ + cpu._add_sub_vector(res_op, reg_op1, reg_op2, add=True) + + @instruction + def ADD(cpu, res_op, reg_op, reg_imm_op): + """ + Combines ADD (extended register), ADD (immediate), ADD (shifted + register), and ADD (vector). + + :param res_op: destination register. + :param reg_op: source register. + :param reg_imm_op: source register or immediate. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert reg_imm_op.type in [cs.arm64.ARM64_OP_REG, cs.arm64.ARM64_OP_IMM] + + bit21 = cpu.insn_bit_str[-22] + bit24 = cpu.insn_bit_str[-25] + + if reg_imm_op.type == cs.arm64.ARM64_OP_IMM: + cpu._ADD_immediate(res_op, reg_op, reg_imm_op) + + elif reg_imm_op.type == cs.arm64.ARM64_OP_REG and bit24 == '0': + cpu._ADD_vector(res_op, reg_op, reg_imm_op) + + elif reg_imm_op.type == cs.arm64.ARM64_OP_REG and bit24 == '1' and bit21 == '0': + cpu._ADD_shifted_register(res_op, reg_op, reg_imm_op) + + elif reg_imm_op.type == cs.arm64.ARM64_OP_REG and bit24 == '1' and bit21 == '1': + cpu._ADD_extended_register(res_op, reg_op, reg_imm_op) + + else: + raise Aarch64InvalidInstruction + + def _ADDP_scalar(cpu, res_op, reg_op): + """ + ADDP (scalar). + + :param res_op: destination register. + :param reg_op: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + + insn_rx = '01' + insn_rx += '0' + insn_rx += '11110' + insn_rx += '[01]{2}' # size + insn_rx += '11000' + insn_rx += '11011' + insn_rx += '10' + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + # XXX: Check if trapped. + + reg = reg_op.read() + + assert reg_op.op.vas == cs.arm64.ARM64_VAS_2D + hi = Operators.EXTRACT(reg, 64, 64) + lo = Operators.EXTRACT(reg, 0, 64) + + result = UInt(hi + lo, res_op.size) + res_op.write(result) + + def _ADDP_vector(cpu, res_op, reg_op1, reg_op2): + """ + ADDP (vector). + + :param res_op: destination register. + :param reg_op1: source register. + :param reg_op2: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op1.type is cs.arm64.ARM64_OP_REG + assert reg_op2.type is cs.arm64.ARM64_OP_REG + + insn_rx = '0' + insn_rx += '[01]' # Q + insn_rx += '0' + insn_rx += '01110' + insn_rx += '[01]{2}' # size + insn_rx += '1' + insn_rx += '[01]{5}' # Rm + insn_rx += '10111' + insn_rx += '1' + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + # XXX: Check if trapped. + + reg1 = reg_op1.read() + reg2 = reg_op2.read() + vas = res_op.op.vas + + if vas == cs.arm64.ARM64_VAS_8B: + elem_size = 8 + elem_count = 8 + + elif vas == cs.arm64.ARM64_VAS_16B: + elem_size = 8 + elem_count = 16 + + elif vas == cs.arm64.ARM64_VAS_4H: + elem_size = 16 + elem_count = 4 + + elif vas == cs.arm64.ARM64_VAS_8H: + elem_size = 16 + elem_count = 8 + + elif vas == cs.arm64.ARM64_VAS_2S: + elem_size = 32 + elem_count = 2 + + elif vas == cs.arm64.ARM64_VAS_4S: + elem_size = 32 + elem_count = 4 + + elif vas == cs.arm64.ARM64_VAS_2D: + elem_size = 64 + elem_count = 2 + + else: + raise Aarch64InvalidInstruction + + size = elem_size * elem_count + + reg1 = Operators.EXTRACT(reg1, 0, size) + reg2 = Operators.EXTRACT(reg2, 0, size) + + reg1 = Operators.ZEXTEND(reg1, size * 2) + reg2 = Operators.ZEXTEND(reg2, size * 2) + + concat = UInt((reg2 << size) | reg1, size * 2) + + result = 0 + for i in range(elem_count): + elem1 = Operators.EXTRACT(concat, (2 * i) * elem_size, elem_size) + elem2 = Operators.EXTRACT(concat, (2 * i + 1) * elem_size, elem_size) + elem = UInt(elem1 + elem2, elem_size) + elem = Operators.ZEXTEND(elem, res_op.size) + result |= elem << (i * elem_size) + + result = UInt(result, res_op.size) + res_op.write(result) + + @instruction + def ADDP(cpu, res_op, reg_op1, mreg_op2=None): + """ + Combines ADDP (scalar) and ADDP (vector). + + :param res_op: destination register. + :param reg_op1: source register. + :param mreg_op2: None or source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op1.type is cs.arm64.ARM64_OP_REG + assert not mreg_op2 or mreg_op2.type is cs.arm64.ARM64_OP_REG + + if mreg_op2: + cpu._ADDP_vector(res_op, reg_op1, mreg_op2) + else: + cpu._ADDP_scalar(res_op, reg_op1) + + def _ADDS_extended_register(cpu, res_op, reg_op1, reg_op2): + """ + ADDS (extended register). + + :param res_op: destination register. + :param reg_op1: source register. + :param reg_op2: source register. + """ + cpu._adds_subs_extended_register(res_op, reg_op1, reg_op2, mnem='adds') + + def _ADDS_immediate(cpu, res_op, reg_op, imm_op): + """ + ADDS (immediate). + + :param res_op: destination register. + :param reg_op: source register. + :param imm_op: immediate. + """ + cpu._adds_subs_immediate(res_op, reg_op, imm_op, mnem='adds') + + def _ADDS_shifted_register(cpu, res_op, reg_op1, reg_op2): + """ + ADDS (shifted register). + + :param res_op: destination register. + :param reg_op1: source register. + :param reg_op2: source register. + """ + cpu._adds_subs_shifted_register(res_op, reg_op1, reg_op2, mnem='adds') + + @instruction + def ADDS(cpu, res_op, reg_op, reg_imm_op): + """ + Combines ADDS (extended register), ADDS (immediate), and ADDS (shifted + register). + + :param res_op: destination register. + :param reg_op: source register. + :param reg_imm_op: source register or immediate. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert reg_imm_op.type in [cs.arm64.ARM64_OP_REG, cs.arm64.ARM64_OP_IMM] + + bit21 = cpu.insn_bit_str[-22] + + if reg_imm_op.type == cs.arm64.ARM64_OP_IMM: + cpu._ADDS_immediate(res_op, reg_op, reg_imm_op) + + elif reg_imm_op.type == cs.arm64.ARM64_OP_REG and bit21 == '0': + cpu._ADDS_shifted_register(res_op, reg_op, reg_imm_op) + + elif reg_imm_op.type == cs.arm64.ARM64_OP_REG and bit21 == '1': + cpu._ADDS_extended_register(res_op, reg_op, reg_imm_op) + + else: + raise Aarch64InvalidInstruction + + @instruction + def ADR(cpu, res_op, imm_op): + """ + ADR. + + :param res_op: destination register. + :param imm_op: immediate. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert imm_op.type is cs.arm64.ARM64_OP_IMM + + insn_rx = '0' # op + insn_rx += '[01]{2}' # immlo + insn_rx += '10000' + insn_rx += '[01]{19}' # immhi + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + imm = imm_op.op.imm # PC + offset + res_op.write(imm) + + @instruction + def ADRP(cpu, res_op, imm_op): + """ + ADRP. + + :param res_op: destination register. + :param imm_op: immediate. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert imm_op.type is cs.arm64.ARM64_OP_IMM + + insn_rx = '1' # op + insn_rx += '[01]{2}' # immlo + insn_rx += '10000' + insn_rx += '[01]{19}' # immhi + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + imm = imm_op.op.imm # PC + offset + res_op.write(imm) + + def _AND_immediate(cpu, res_op, reg_op, imm_op): + """ + AND (immediate). + + :param res_op: destination register. + :param reg_op: source register. + :param imm_op: immediate. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert imm_op.type is cs.arm64.ARM64_OP_IMM + + insn_rx = '[01]' # sf + insn_rx += '00' # opc + insn_rx += '100100' + insn_rx += '[01]' # N + insn_rx += '[01]{6}' # immr + insn_rx += '[01]{6}' # imms + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + reg = reg_op.read() + imm = imm_op.op.imm + result = UInt(reg & imm, res_op.size) + res_op.write(result) + + def _AND_shifted_register(cpu, res_op, reg_op1, reg_op2): + """ + AND (shifted register). + + :param res_op: destination register. + :param reg_op1: source register. + :param reg_op2: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op1.type is cs.arm64.ARM64_OP_REG + assert reg_op2.type is cs.arm64.ARM64_OP_REG + + insn_rx = '[01]' # sf + insn_rx += '00' # opc + insn_rx += '01010' + insn_rx += '[01]{2}' # shift + insn_rx += '0' # N + insn_rx += '[01]{5}' # Rm + insn_rx += '[01]{6}' # imm6 + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + cpu._shifted_register( + res_op=res_op, + reg_op1=reg_op1, + reg_op2=reg_op2, + action=lambda x, y: (x & y, None), + shifts=[ + cs.arm64.ARM64_SFT_LSL, + cs.arm64.ARM64_SFT_LSR, + cs.arm64.ARM64_SFT_ASR, + cs.arm64.ARM64_SFT_ROR + ]) + + def _AND_vector(cpu, res_op, reg_op1, reg_op2): + """ + AND (vector). + + :param res_op: destination register. + :param reg_op1: source register. + :param reg_op2: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op1.type is cs.arm64.ARM64_OP_REG + assert reg_op2.type is cs.arm64.ARM64_OP_REG + + insn_rx = '0' + insn_rx += '[01]' # Q + insn_rx += '0' + insn_rx += '01110' + insn_rx += '00' # size + insn_rx += '1' + insn_rx += '[01]{5}' # Rm + insn_rx += '00011' + insn_rx += '1' + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + # XXX: Check if trapped. + + reg1 = reg_op1.read() + reg2 = reg_op2.read() + vas = res_op.op.vas + + if vas == cs.arm64.ARM64_VAS_8B: + reg1 = Operators.EXTRACT(reg1, 0, 64) + reg2 = Operators.EXTRACT(reg2, 0, 64) + + elif vas == cs.arm64.ARM64_VAS_16B: + pass + + else: + raise Aarch64InvalidInstruction + + result = UInt(reg1 & reg2, res_op.size) + res_op.write(result) + + @instruction + def AND(cpu, res_op, reg_op, reg_imm_op): + """ + Combines AND (immediate), AND (shifted register), and AND (vector). + + :param res_op: destination register. + :param reg_op: source register. + :param reg_imm_op: source register or immediate. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert reg_imm_op.type in [cs.arm64.ARM64_OP_REG, cs.arm64.ARM64_OP_IMM] + + bit21 = cpu.insn_bit_str[-22] + + if reg_imm_op.type == cs.arm64.ARM64_OP_REG and bit21 == '0': + cpu._AND_shifted_register(res_op, reg_op, reg_imm_op) + + elif reg_imm_op.type == cs.arm64.ARM64_OP_REG and bit21 == '1': + cpu._AND_vector(res_op, reg_op, reg_imm_op) + + elif reg_imm_op.type == cs.arm64.ARM64_OP_IMM: + cpu._AND_immediate(res_op, reg_op, reg_imm_op) + + else: + raise Aarch64InvalidInstruction + + def _ANDS_immediate(cpu, res_op, reg_op, imm_op): + """ + ANDS (immediate). + + :param res_op: destination register. + :param reg_op: source register. + :param imm_op: immediate. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert imm_op.type is cs.arm64.ARM64_OP_IMM + + insn_rx = '[01]' # sf + insn_rx += '11' # opc + insn_rx += '100100' + insn_rx += '[01]' # N + insn_rx += '[01]{6}' # immr + insn_rx += '[01]{6}' # imms + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + reg = reg_op.read() + imm = imm_op.op.imm + result = UInt(reg & imm, res_op.size) + res_op.write(result) + + n = Operators.EXTRACT(result, res_op.size - 1, 1) + z = Operators.ITEBV(1, result == 0, 1, 0) + cpu.regfile.nzcv = (n, z, 0, 0) + + def _ANDS_shifted_register(cpu, res_op, reg_op1, reg_op2): + """ + ANDS (shifted register). + + :param res_op: destination register. + :param reg_op1: source register. + :param reg_op2: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op1.type is cs.arm64.ARM64_OP_REG + assert reg_op2.type is cs.arm64.ARM64_OP_REG + + insn_rx = '[01]' # sf + insn_rx += '11' # opc + insn_rx += '01010' + insn_rx += '[01]{2}' # shift + insn_rx += '0' # N + insn_rx += '[01]{5}' # Rm + insn_rx += '[01]{6}' # imm6 + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + def action(x, y): + result = x & y + n = Operators.EXTRACT(result, res_op.size - 1, 1) + z = Operators.ITEBV(1, result == 0, 1, 0) + return (result, (n, z, 0, 0)) + + cpu._shifted_register( + res_op=res_op, + reg_op1=reg_op1, + reg_op2=reg_op2, + action=lambda x, y: action(x, y), + flags=True, + shifts=[ + cs.arm64.ARM64_SFT_LSL, + cs.arm64.ARM64_SFT_LSR, + cs.arm64.ARM64_SFT_ASR, + cs.arm64.ARM64_SFT_ROR + ]) + + @instruction + def ANDS(cpu, res_op, reg_op, reg_imm_op): + """ + Combines ANDS (immediate) and ANDS (shifted register). + + :param res_op: destination register. + :param reg_op: source register. + :param reg_imm_op: source register or immediate. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert reg_imm_op.type in [cs.arm64.ARM64_OP_REG, cs.arm64.ARM64_OP_IMM] + + if reg_imm_op.type == cs.arm64.ARM64_OP_REG: + cpu._ANDS_shifted_register(res_op, reg_op, reg_imm_op) + + elif reg_imm_op.type == cs.arm64.ARM64_OP_IMM: + cpu._ANDS_immediate(res_op, reg_op, reg_imm_op) + + else: + raise Aarch64InvalidInstruction + + def _ASR_immediate(cpu, res_op, reg_op, immr_op): + """ + ASR (immediate). + + :param res_op: destination register. + :param reg_op: source register. + :param immr_op: immediate. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert immr_op.type is cs.arm64.ARM64_OP_IMM + + insn_rx = '[01]' # sf + insn_rx += '00' # opc + insn_rx += '100110' + insn_rx += '[01]' # N + insn_rx += '[01]{6}' # immr + insn_rx += '[01]1{5}' # imms + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + # Fake an immediate operand. + imms_op = Aarch64Operand.make_imm(cpu, res_op.size - 1) + + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.SBFM.__wrapped__(cpu, res_op, reg_op, immr_op, imms_op) + + def _ASR_register(cpu, res_op, reg_op1, reg_op2): + """ + ASR (register). + + :param res_op: destination register. + :param reg_op1: source register. + :param reg_op2: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op1.type is cs.arm64.ARM64_OP_REG + assert reg_op2.type is cs.arm64.ARM64_OP_REG + + insn_rx = '[01]' # sf + insn_rx += '0' + insn_rx += '0' + insn_rx += '11010110' + insn_rx += '[01]{5}' # Rm + insn_rx += '0010' + insn_rx += '10' # op2 + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.ASRV.__wrapped__(cpu, res_op, reg_op1, reg_op2) + + @instruction + def ASR(cpu, res_op, reg_op, reg_imm_op): + """ + Combines ASR (register) and ASR (immediate). + + :param res_op: destination register. + :param reg_op: source register. + :param reg_imm_op: source register or immediate. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert reg_imm_op.type in [cs.arm64.ARM64_OP_REG, cs.arm64.ARM64_OP_IMM] + + if reg_imm_op.type == cs.arm64.ARM64_OP_REG: + cpu._ASR_register(res_op, reg_op, reg_imm_op) + + elif reg_imm_op.type == cs.arm64.ARM64_OP_IMM: + cpu._ASR_immediate(res_op, reg_op, reg_imm_op) + + else: + raise Aarch64InvalidInstruction + + @instruction + def ASRV(cpu, res_op, reg_op1, reg_op2): + """ + ASRV. + + :param res_op: destination register. + :param reg_op1: source register. + :param reg_op2: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op1.type is cs.arm64.ARM64_OP_REG + assert reg_op2.type is cs.arm64.ARM64_OP_REG + + insn_rx = '[01]' # sf + insn_rx += '0' + insn_rx += '0' + insn_rx += '11010110' + insn_rx += '[01]{5}' # Rm + insn_rx += '0010' + insn_rx += '10' # op2 + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + reg = reg_op1.read() + sft = reg_op2.read() + + result = ASR(reg, sft % res_op.size, res_op.size) + res_op.write(result) + + @instruction + def B_cond(cpu, imm_op): + """ + B.cond. + + :param imm_op: immediate. + """ + assert imm_op.type is cs.arm64.ARM64_OP_IMM + + insn_rx = '0101010' + insn_rx += '0' + insn_rx += '[01]{19}' # imm19 + insn_rx += '0' + insn_rx += '[01]{4}' # cond + + assert re.match(insn_rx, cpu.insn_bit_str) + + imm = imm_op.op.imm + + cpu.PC = Operators.ITEBV( + cpu.regfile.size('PC'), + cpu.cond_holds(cpu.instruction.cc), + imm, + cpu.PC + ) + + @instruction + def B(cpu, imm_op): + """ + B. + + :param imm_op: immediate. + """ + assert imm_op.type is cs.arm64.ARM64_OP_IMM + + insn_rx = '0' # op + insn_rx += '00101' + insn_rx += '[01]{26}' # imm26 + + assert re.match(insn_rx, cpu.insn_bit_str) + + imm = imm_op.op.imm + cpu.PC = imm + + @instruction + def BFC(cpu, res_op, lsb_op, width_op): + """ + BFC. + + :param res_op: destination register. + :param lsb_op: immediate. + :param width_op: immediate. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert lsb_op.type is cs.arm64.ARM64_OP_IMM + assert width_op.type is cs.arm64.ARM64_OP_IMM + + insn_rx = '[01]' # sf + insn_rx += '01' # opc + insn_rx += '100110' + insn_rx += '[01]' # N + insn_rx += '[01]{6}' # immr + insn_rx += '[01]{6}' # imms + insn_rx += '1{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + lsb = lsb_op.op.imm + lsb_op.value.imm = -lsb % res_op.size + width_op.value.imm -= 1 + + # Fake a register operand. + if res_op.size == 32: + zr = Aarch64Operand.make_reg(cpu, cs.arm64.ARM64_REG_WZR) + elif res_op.size == 64: + zr = Aarch64Operand.make_reg(cpu, cs.arm64.ARM64_REG_XZR) + else: + raise Aarch64InvalidInstruction + + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.BFM.__wrapped__(cpu, res_op, zr, lsb_op, width_op) + + @instruction + def BFI(cpu, res_op, reg_op, lsb_op, width_op): + """ + BFI. + + :param res_op: destination register. + :param reg_op: source register. + :param lsb_op: immediate. + :param width_op: immediate. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert lsb_op.type is cs.arm64.ARM64_OP_IMM + assert width_op.type is cs.arm64.ARM64_OP_IMM + + insn_rx = '[01]' # sf + insn_rx += '01' # opc + insn_rx += '100110' + insn_rx += '[01]' # N + insn_rx += '[01]{6}' # immr + insn_rx += '[01]{6}' # imms + insn_rx += '(?!1{5})[01]{5}' # Rn != 11111 + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + lsb = lsb_op.op.imm + lsb_op.value.imm = -lsb % res_op.size + width_op.value.imm -= 1 + + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.BFM.__wrapped__(cpu, res_op, reg_op, lsb_op, width_op) + + @instruction + def BFM(cpu, res_op, reg_op, immr_op, imms_op): + """ + BFM. + + :param res_op: destination register. + :param reg_op: source register. + :param immr_op: immediate. + :param imms_op: immediate. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert immr_op.type is cs.arm64.ARM64_OP_IMM + assert imms_op.type is cs.arm64.ARM64_OP_IMM + + insn_rx = '[01]' # sf + insn_rx += '01' # opc + insn_rx += '100110' + insn_rx += '[01]' # N + insn_rx += '[01]{6}' # immr + insn_rx += '[01]{6}' # imms + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + res = res_op.read() + reg = reg_op.read() + immr = immr_op.op.imm + imms = imms_op.op.imm + + assert immr in range(res_op.size) + assert imms in range(res_op.size) + + if imms >= immr: + width = imms - immr + 1 + copy_from = immr + copy_to = 0 + else: + width = imms + 1 + copy_from = 0 + copy_to = res_op.size - immr + + result = ((reg & (Mask(width) << copy_from)) >> copy_from) << copy_to + result |= res & ~(Mask(width) << copy_to) + res_op.write(result) + + @instruction + def BFXIL(cpu, res_op, reg_op, lsb_op, width_op): + """ + BFXIL. + + :param res_op: destination register. + :param reg_op: source register. + :param lsb_op: immediate. + :param width_op: immediate. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert lsb_op.type is cs.arm64.ARM64_OP_IMM + assert width_op.type is cs.arm64.ARM64_OP_IMM + + insn_rx = '[01]' # sf + insn_rx += '01' # opc + insn_rx += '100110' + insn_rx += '[01]' # N + insn_rx += '[01]{6}' # immr + insn_rx += '[01]{6}' # imms + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + lsb = lsb_op.op.imm + width = width_op.op.imm + width_op.value.imm = lsb + width - 1 + + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.BFM.__wrapped__(cpu, res_op, reg_op, lsb_op, width_op) + + # XXX: Support BIC (vector, immediate) and BIC (vector, register). + @instruction + def BIC(cpu, res_op, reg_op1, reg_op2): + """ + BIC (shifted register). + + :param res_op: destination register. + :param reg_op1: source register. + :param reg_op2: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op1.type is cs.arm64.ARM64_OP_REG + assert reg_op2.type is cs.arm64.ARM64_OP_REG + + insn_rx = '[01]' # sf + insn_rx += '00' # opc + insn_rx += '01010' + insn_rx += '[01]{2}' # shift + insn_rx += '1' # N + insn_rx += '[01]{5}' # Rm + insn_rx += '[01]{6}' # imm6 + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + cpu._shifted_register( + res_op=res_op, + reg_op1=reg_op1, + reg_op2=reg_op2, + action=lambda x, y: (x & ~y, None), + shifts=[ + cs.arm64.ARM64_SFT_LSL, + cs.arm64.ARM64_SFT_LSR, + cs.arm64.ARM64_SFT_ASR, + cs.arm64.ARM64_SFT_ROR + ]) + + @instruction + def BICS(cpu, res_op, reg_op1, reg_op2): + """ + BICS (shifted register). + + :param res_op: destination register. + :param reg_op1: source register. + :param reg_op2: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op1.type is cs.arm64.ARM64_OP_REG + assert reg_op2.type is cs.arm64.ARM64_OP_REG + + insn_rx = '[01]' # sf + insn_rx += '11' # opc + insn_rx += '01010' + insn_rx += '[01]{2}' # shift + insn_rx += '1' # N + insn_rx += '[01]{5}' # Rm + insn_rx += '[01]{6}' # imm6 + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + def action(x, y): + result = x & ~y + n = Operators.EXTRACT(result, res_op.size - 1, 1) + z = Operators.ITEBV(1, result == 0, 1, 0) + return (result, (n, z, 0, 0)) + + cpu._shifted_register( + res_op=res_op, + reg_op1=reg_op1, + reg_op2=reg_op2, + action=lambda x, y: action(x, y), + flags=True, + shifts=[ + cs.arm64.ARM64_SFT_LSL, + cs.arm64.ARM64_SFT_LSR, + cs.arm64.ARM64_SFT_ASR, + cs.arm64.ARM64_SFT_ROR + ]) + + @instruction + def BL(cpu, imm_op): + """ + BL. + + :param imm_op: immediate. + """ + assert imm_op.type is cs.arm64.ARM64_OP_IMM + + insn_rx = '1' # op + insn_rx += '00101' + insn_rx += '[01]{26}' # imm26 + + assert re.match(insn_rx, cpu.insn_bit_str) + + imm = imm_op.op.imm + # The 'instruction' decorator makes PC point to the next instruction. + cpu.X30 = cpu.PC + cpu.PC = imm + + @instruction + def BLR(cpu, reg_op): + """ + BLR. + + :param reg_op: register. + """ + assert reg_op.type is cs.arm64.ARM64_OP_REG + + insn_rx = '1101011' + insn_rx += '0' # Z + insn_rx += '0' + insn_rx += '01' # op + insn_rx += '1{5}' + insn_rx += '0{4}' + insn_rx += '0' # A + insn_rx += '0' # M + insn_rx += '[01]{5}' # Rn + insn_rx += '0{5}' # Rm + + assert re.match(insn_rx, cpu.insn_bit_str) + + reg = reg_op.read() + # The 'instruction' decorator makes PC point to the next instruction. + cpu.X30 = cpu.PC + cpu.PC = reg + + @instruction + def BR(cpu, reg_op): + """ + BR. + + :param reg_op: register. + """ + assert reg_op.type is cs.arm64.ARM64_OP_REG + + insn_rx = '1101011' + insn_rx += '0' # Z + insn_rx += '0' + insn_rx += '00' # op + insn_rx += '1{5}' + insn_rx += '0{4}' + insn_rx += '0' # A + insn_rx += '0' # M + insn_rx += '[01]{5}' # Rn + insn_rx += '0{5}' # Rm + + assert re.match(insn_rx, cpu.insn_bit_str) + + reg = reg_op.read() + cpu.PC = reg + + @instruction + def CBNZ(cpu, reg_op, imm_op): + """ + CBNZ. + + :param reg_op: register. + :param imm_op: immediate. + """ + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert imm_op.type is cs.arm64.ARM64_OP_IMM + + insn_rx = '[01]' # sf + insn_rx += '011010' + insn_rx += '1' # op + insn_rx += '[01]{19}' # imm19 + insn_rx += '[01]{5}' # Rt + + assert re.match(insn_rx, cpu.insn_bit_str) + + reg = reg_op.read() + imm = imm_op.op.imm + + cpu.PC = Operators.ITEBV( + cpu.regfile.size('PC'), + reg != 0, + imm, + cpu.PC + ) + + @instruction + def CBZ(cpu, reg_op, imm_op): + """ + CBZ. + + :param reg_op: register. + :param imm_op: immediate. + """ + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert imm_op.type is cs.arm64.ARM64_OP_IMM + + insn_rx = '[01]' # sf + insn_rx += '011010' + insn_rx += '0' # op + insn_rx += '[01]{19}' # imm19 + insn_rx += '[01]{5}' # Rt + + assert re.match(insn_rx, cpu.insn_bit_str) + + reg = reg_op.read() + imm = imm_op.op.imm + + cpu.PC = Operators.ITEBV( + cpu.regfile.size('PC'), + reg == 0, + imm, + cpu.PC + ) + + def _CCMP_immediate(cpu, reg_op, imm_op, nzcv_op): + """ + CCMP (immediate). + + :param reg_op: register. + :param imm_op: immediate. + :param nzcv_op: immediate. + """ + cpu._ccmp_imm_reg(reg_op, imm_op, nzcv_op, imm=True) + + def _CCMP_register(cpu, reg_op1, reg_op2, nzcv_op): + """ + CCMP (register). + + :param reg_op1: register. + :param reg_op2: register. + :param nzcv_op: immediate. + """ + cpu._ccmp_imm_reg(reg_op1, reg_op2, nzcv_op, imm=False) + + @instruction + def CCMP(cpu, reg_op, reg_imm_op, nzcv_op): + """ + Combines CCMP (register) and CCMP (immediate). + + :param reg_op: register. + :param reg_imm_op: register or immediate. + :param nzcv_op: immediate. + """ + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert reg_imm_op.type in [cs.arm64.ARM64_OP_REG, cs.arm64.ARM64_OP_IMM] + assert nzcv_op.type is cs.arm64.ARM64_OP_IMM + + if reg_imm_op.type == cs.arm64.ARM64_OP_REG: + cpu._CCMP_register(reg_op, reg_imm_op, nzcv_op) + + elif reg_imm_op.type == cs.arm64.ARM64_OP_IMM: + cpu._CCMP_immediate(reg_op, reg_imm_op, nzcv_op) + + else: + raise Aarch64InvalidInstruction + + @instruction + def CINC(cpu, res_op, reg_op): + """ + CINC. + + :param res_op: destination register. + :param reg_op: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + + insn_rx = '[01]' # sf + insn_rx += '0' # op + insn_rx += '0' + insn_rx += '11010100' + insn_rx += '(?!1{5})[01]{5}' # Rm != 11111 + insn_rx += '(?!111[01])[01]{4}' # cond != 111x + insn_rx += '0' + insn_rx += '1' # o2 + insn_rx += '(?!1{5})[01]{5}' # Rn != 11111 + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + cond = cpu.invert_cond(cpu.instruction.cc) + + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.CSINC.__wrapped__(cpu, res_op, reg_op, reg_op, cond) + + @instruction + def CINV(cpu, res_op, reg_op): + """ + CINV. + + :param res_op: destination register. + :param reg_op: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + + insn_rx = '[01]' # sf + insn_rx += '1' # op + insn_rx += '0' + insn_rx += '11010100' + insn_rx += '(?!1{5})[01]{5}' # Rm != 11111 + insn_rx += '(?!111[01])[01]{4}' # cond != 111x + insn_rx += '0' + insn_rx += '0' # o2 + insn_rx += '(?!1{5})[01]{5}' # Rn != 11111 + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + cond = cpu.invert_cond(cpu.instruction.cc) + + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.CSINV.__wrapped__(cpu, res_op, reg_op, reg_op, cond) + + # XXX: Support CLZ (vector). + @instruction + def CLZ(cpu, res_op, reg_op): + """ + CLZ. + + :param res_op: destination register. + :param reg_op: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + + insn_rx = '[01]' # sf + insn_rx += '1' + insn_rx += '0' + insn_rx += '11010110' + insn_rx += '0{5}' + insn_rx += '00010' + insn_rx += '0' # op + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + # XXX: Copied from 'CLZ' in 'arm.py'. + reg = reg_op.read() + msb = res_op.size - 1 + result = res_op.size + + for pos in range(res_op.size): + cond = Operators.EXTRACT(reg, pos, 1) == 1 + result = Operators.ITEBV(res_op.size, cond, msb - pos, result) + + res_op.write(result) + + def _CMEQ_register(cpu, res_op, reg_op1, reg_op2): + """ + CMEQ (register). + + :param res_op: destination register. + :param reg_op1: source register. + :param reg_op2: source register. + """ + cpu._cmeq(res_op, reg_op1, reg_op2, register=True) + + def _CMEQ_zero(cpu, res_op, reg_op, imm_op): + """ + CMEQ (zero). + + :param res_op: destination register. + :param reg_op: source register. + :param imm_op: immediate (zero). + """ + cpu._cmeq(res_op, reg_op, imm_op, register=False) + + @instruction + def CMEQ(cpu, res_op, reg_op, reg_imm_op): + """ + Combines CMEQ (register) and CMEQ (zero). + + :param res_op: destination register. + :param reg_op: source register. + :param reg_imm_op: source register or immediate (zero). + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert reg_imm_op.type in [cs.arm64.ARM64_OP_REG, cs.arm64.ARM64_OP_IMM] + + if reg_imm_op.type == cs.arm64.ARM64_OP_REG: + cpu._CMEQ_register(res_op, reg_op, reg_imm_op) + + else: + cpu._CMEQ_zero(res_op, reg_op, reg_imm_op) + + def _CMN_extended_register(cpu, reg_op1, reg_op2): + """ + CMN (extended register). + + :param reg_op1: source register. + :param reg_op2: source register. + """ + assert reg_op1.type is cs.arm64.ARM64_OP_REG + assert reg_op2.type is cs.arm64.ARM64_OP_REG + + insn_rx = '[01]' # sf + insn_rx += '0' # op + insn_rx += '1' # S + insn_rx += '01011' + insn_rx += '00' + insn_rx += '1' + insn_rx += '[01]{5}' # Rm + insn_rx += '[01]{3}' # option + insn_rx += '[01]{3}' # imm3 + insn_rx += '[01]{5}' # Rn + insn_rx += '1{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + # Fake a register operand. + if reg_op1.size == 32: + zr = Aarch64Operand.make_reg(cpu, cs.arm64.ARM64_REG_WZR) + elif reg_op1.size == 64: + zr = Aarch64Operand.make_reg(cpu, cs.arm64.ARM64_REG_XZR) + else: + raise Aarch64InvalidInstruction + + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.ADDS.__wrapped__(cpu, zr, reg_op1, reg_op2) + + def _CMN_immediate(cpu, reg_op, imm_op): + """ + CMN (immediate). + + :param reg_op: source register. + :param imm_op: immediate. + """ + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert imm_op.type is cs.arm64.ARM64_OP_IMM + + insn_rx = '[01]' # sf + insn_rx += '0' # op + insn_rx += '1' # S + insn_rx += '10001' + insn_rx += '(?!1[01])[01]{2}' # shift != 1x + insn_rx += '[01]{12}' # imm12 + insn_rx += '[01]{5}' # Rn + insn_rx += '1{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + # Fake a register operand. + if reg_op.size == 32: + zr = Aarch64Operand.make_reg(cpu, cs.arm64.ARM64_REG_WZR) + elif reg_op.size == 64: + zr = Aarch64Operand.make_reg(cpu, cs.arm64.ARM64_REG_XZR) + else: + raise Aarch64InvalidInstruction + + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.ADDS.__wrapped__(cpu, zr, reg_op, imm_op) + + def _CMN_shifted_register(cpu, reg_op1, reg_op2): + """ + CMN (shifted register). + + :param reg_op1: source register. + :param reg_op2: source register. + """ + assert reg_op1.type is cs.arm64.ARM64_OP_REG + assert reg_op2.type is cs.arm64.ARM64_OP_REG + + insn_rx = '[01]' # sf + insn_rx += '0' # op + insn_rx += '1' # S + insn_rx += '01011' + insn_rx += '[01]{2}' # shift + insn_rx += '0' + insn_rx += '[01]{5}' # Rm + insn_rx += '[01]{6}' # imm6 + insn_rx += '[01]{5}' # Rn + insn_rx += '1{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + # Fake a register operand. + if reg_op1.size == 32: + zr = Aarch64Operand.make_reg(cpu, cs.arm64.ARM64_REG_WZR) + elif reg_op1.size == 64: + zr = Aarch64Operand.make_reg(cpu, cs.arm64.ARM64_REG_XZR) + else: + raise Aarch64InvalidInstruction + + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.ADDS.__wrapped__(cpu, zr, reg_op1, reg_op2) + + @instruction + def CMN(cpu, reg_op, reg_imm_op): + """ + Combines CMN (extended register), CMN (immediate), and CMN (shifted + register). + + :param reg_op: source register. + :param reg_imm_op: source register or immediate. + """ + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert reg_imm_op.type in [cs.arm64.ARM64_OP_REG, cs.arm64.ARM64_OP_IMM] + + bit21 = cpu.insn_bit_str[-22] + + if reg_imm_op.type == cs.arm64.ARM64_OP_IMM: + cpu._CMN_immediate(reg_op, reg_imm_op) + + elif reg_imm_op.type == cs.arm64.ARM64_OP_REG and bit21 == '0': + cpu._CMN_shifted_register(reg_op, reg_imm_op) + + elif reg_imm_op.type == cs.arm64.ARM64_OP_REG and bit21 == '1': + cpu._CMN_extended_register(reg_op, reg_imm_op) + + else: + raise Aarch64InvalidInstruction + + def _CMP_extended_register(cpu, reg_op1, reg_op2): + """ + CMP (extended register). + + :param reg_op1: source register. + :param reg_op2: source register. + """ + assert reg_op1.type is cs.arm64.ARM64_OP_REG + assert reg_op2.type is cs.arm64.ARM64_OP_REG + + insn_rx = '[01]' # sf + insn_rx += '1' # op + insn_rx += '1' # S + insn_rx += '01011' + insn_rx += '00' + insn_rx += '1' + insn_rx += '[01]{5}' # Rm + insn_rx += '[01]{3}' # option + insn_rx += '[01]{3}' # imm3 + insn_rx += '[01]{5}' # Rn + insn_rx += '1{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + # Fake a register operand. + if reg_op1.size == 32: + zr = Aarch64Operand.make_reg(cpu, cs.arm64.ARM64_REG_WZR) + elif reg_op1.size == 64: + zr = Aarch64Operand.make_reg(cpu, cs.arm64.ARM64_REG_XZR) + else: + raise Aarch64InvalidInstruction + + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.SUBS.__wrapped__(cpu, zr, reg_op1, reg_op2) + + def _CMP_immediate(cpu, reg_op, imm_op): + """ + CMP (immediate). + + :param reg_op: source register. + :param imm_op: immediate. + """ + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert imm_op.type is cs.arm64.ARM64_OP_IMM + + insn_rx = '[01]' # sf + insn_rx += '1' # op + insn_rx += '1' # S + insn_rx += '10001' + insn_rx += '(?!1[01])[01]{2}' # shift != 1x + insn_rx += '[01]{12}' # imm12 + insn_rx += '[01]{5}' # Rn + insn_rx += '1{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + # Fake a register operand. + if reg_op.size == 32: + zr = Aarch64Operand.make_reg(cpu, cs.arm64.ARM64_REG_WZR) + elif reg_op.size == 64: + zr = Aarch64Operand.make_reg(cpu, cs.arm64.ARM64_REG_XZR) + else: + raise Aarch64InvalidInstruction + + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.SUBS.__wrapped__(cpu, zr, reg_op, imm_op) + + def _CMP_shifted_register(cpu, reg_op1, reg_op2): + """ + CMP (shifted register). + + :param reg_op1: source register. + :param reg_op2: source register. + """ + assert reg_op1.type is cs.arm64.ARM64_OP_REG + assert reg_op2.type is cs.arm64.ARM64_OP_REG + + insn_rx = '[01]' # sf + insn_rx += '1' # op + insn_rx += '1' # S + insn_rx += '01011' + insn_rx += '[01]{2}' # shift + insn_rx += '0' + insn_rx += '[01]{5}' # Rm + insn_rx += '[01]{6}' # imm6 + insn_rx += '[01]{5}' # Rn + insn_rx += '1{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + # Fake a register operand. + if reg_op1.size == 32: + zr = Aarch64Operand.make_reg(cpu, cs.arm64.ARM64_REG_WZR) + elif reg_op1.size == 64: + zr = Aarch64Operand.make_reg(cpu, cs.arm64.ARM64_REG_XZR) + else: + raise Aarch64InvalidInstruction + + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.SUBS.__wrapped__(cpu, zr, reg_op1, reg_op2) + + @instruction + def CMP(cpu, reg_op, reg_imm_op): + """ + Combines CMP (extended register), CMP (immediate), and CMP (shifted + register). + + :param reg_op: source register. + :param reg_imm_op: source register or immediate. + """ + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert reg_imm_op.type in [cs.arm64.ARM64_OP_REG, cs.arm64.ARM64_OP_IMM] + + bit21 = cpu.insn_bit_str[-22] + + if reg_imm_op.type == cs.arm64.ARM64_OP_IMM: + cpu._CMP_immediate(reg_op, reg_imm_op) + + elif reg_imm_op.type == cs.arm64.ARM64_OP_REG and bit21 == '0': + cpu._CMP_shifted_register(reg_op, reg_imm_op) + + elif reg_imm_op.type == cs.arm64.ARM64_OP_REG and bit21 == '1': + cpu._CMP_extended_register(reg_op, reg_imm_op) + + else: + raise Aarch64InvalidInstruction + + @instruction + def CSEL(cpu, res_op, reg_op1, reg_op2): + """ + CSEL. + + :param res_op: destination register. + :param reg_op1: source register. + :param reg_op2: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op1.type is cs.arm64.ARM64_OP_REG + assert reg_op2.type is cs.arm64.ARM64_OP_REG + + insn_rx = '[01]' # sf + insn_rx += '0' # op + insn_rx += '0' + insn_rx += '11010100' + insn_rx += '[01]{5}' # Rm + insn_rx += '[01]{4}' # cond + insn_rx += '0' + insn_rx += '0' # o2 + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + reg1 = reg_op1.read() + reg2 = reg_op2.read() + + result = Operators.ITEBV( + res_op.size, + cpu.cond_holds(cpu.instruction.cc), + reg1, + reg2 + ) + + res_op.write(result) + + @instruction + def CSET(cpu, res_op): + """ + CSET. + + :param res_op: destination register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + + insn_rx = '[01]' # sf + insn_rx += '0' # op + insn_rx += '0' + insn_rx += '11010100' + insn_rx += '1{5}' # Rm + insn_rx += '(?!111[01])[01]{4}' # cond != 111x + insn_rx += '0' + insn_rx += '1' # o2 + insn_rx += '1{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + cond = cpu.invert_cond(cpu.instruction.cc) + + # Fake a register operand. + if res_op.size == 32: + zr = Aarch64Operand.make_reg(cpu, cs.arm64.ARM64_REG_WZR) + elif res_op.size == 64: + zr = Aarch64Operand.make_reg(cpu, cs.arm64.ARM64_REG_XZR) + else: + raise Aarch64InvalidInstruction + + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.CSINC.__wrapped__(cpu, res_op, zr, zr, cond) + + @instruction + def CSETM(cpu, res_op): + """ + CSETM. + + :param res_op: destination register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + + insn_rx = '[01]' # sf + insn_rx += '1' # op + insn_rx += '0' + insn_rx += '11010100' + insn_rx += '1{5}' # Rm + insn_rx += '(?!111[01])[01]{4}' # cond != 111x + insn_rx += '0' + insn_rx += '0' # o2 + insn_rx += '1{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + cond = cpu.invert_cond(cpu.instruction.cc) + + # Fake a register operand. + if res_op.size == 32: + zr = Aarch64Operand.make_reg(cpu, cs.arm64.ARM64_REG_WZR) + elif res_op.size == 64: + zr = Aarch64Operand.make_reg(cpu, cs.arm64.ARM64_REG_XZR) + else: + raise Aarch64InvalidInstruction + + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.CSINV.__wrapped__(cpu, res_op, zr, zr, cond) + + @instruction + def CSINC(cpu, res_op, reg_op1, reg_op2, cond=None): + """ + CSINC. + + :param res_op: destination register. + :param reg_op1: source register. + :param reg_op2: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op1.type is cs.arm64.ARM64_OP_REG + assert reg_op2.type is cs.arm64.ARM64_OP_REG + + insn_rx = '[01]' # sf + insn_rx += '0' # op + insn_rx += '0' + insn_rx += '11010100' + insn_rx += '[01]{5}' # Rm + insn_rx += '[01]{4}' # cond + insn_rx += '0' + insn_rx += '1' # o2 + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + reg1 = reg_op1.read() + reg2 = reg_op2.read() + cond = cond if cond else cpu.instruction.cc + + result = Operators.ITEBV( + res_op.size, + cpu.cond_holds(cond), + reg1, + reg2 + 1 + ) + + res_op.write(UInt(result, res_op.size)) + + @instruction + def CSINV(cpu, res_op, reg_op1, reg_op2, cond=None): + """ + CSINV. + + :param res_op: destination register. + :param reg_op1: source register. + :param reg_op2: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op1.type is cs.arm64.ARM64_OP_REG + assert reg_op2.type is cs.arm64.ARM64_OP_REG + + insn_rx = '[01]' # sf + insn_rx += '1' # op + insn_rx += '0' + insn_rx += '11010100' + insn_rx += '[01]{5}' # Rm + insn_rx += '[01]{4}' # cond + insn_rx += '0' + insn_rx += '0' # o2 + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + reg1 = reg_op1.read() + reg2 = reg_op2.read() + cond = cond if cond else cpu.instruction.cc + + result = Operators.ITEBV( + res_op.size, + cpu.cond_holds(cond), + reg1, + ~reg2 + ) + + res_op.write(UInt(result, res_op.size)) + + @instruction + def DMB(cpu, bar_imm_op): + """ + DMB. + + :param bar_imm_op: barrier or immediate. + """ + assert bar_imm_op.type in [cs.arm64.ARM64_OP_BARRIER, cs.arm64.ARM64_OP_IMM] + + insn_rx = '1101010100' + insn_rx += '0' + insn_rx += '00' + insn_rx += '011' + insn_rx += '0011' + insn_rx += '[01]{4}' # CRm + insn_rx += '1' + insn_rx += '01' # opc + insn_rx += '1{5}' + + assert re.match(insn_rx, cpu.insn_bit_str) + + # XXX: Assumes sequential execution. + pass + + # XXX: Support DUP (element). + @instruction + def DUP(cpu, res_op, reg_op): + """ + DUP (general). + + :param res_op: destination register. + :param reg_op: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + + insn_rx = '0' + insn_rx += '[01]' # Q + insn_rx += '0' + insn_rx += '01110000' + insn_rx += '[01]{5}' # imm5 + insn_rx += '0' + insn_rx += '0001' + insn_rx += '1' + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + # XXX: Check if trapped. + + reg = reg_op.read() + vas = res_op.op.vas + + if vas == cs.arm64.ARM64_VAS_8B: + elem_size = 8 + elem_count = 8 + + elif vas == cs.arm64.ARM64_VAS_16B: + elem_size = 8 + elem_count = 16 + + elif vas == cs.arm64.ARM64_VAS_4H: + elem_size = 16 + elem_count = 4 + + elif vas == cs.arm64.ARM64_VAS_8H: + elem_size = 16 + elem_count = 8 + + elif vas == cs.arm64.ARM64_VAS_2S: + elem_size = 32 + elem_count = 2 + + elif vas == cs.arm64.ARM64_VAS_4S: + elem_size = 32 + elem_count = 4 + + elif vas == cs.arm64.ARM64_VAS_2D: + elem_size = 64 + elem_count = 2 + + else: + raise Aarch64InvalidInstruction + + reg = Operators.EXTRACT(reg, 0, elem_size) + reg = Operators.ZEXTEND(reg, res_op.size) + result = 0 + for i in range(elem_count): + result |= reg << (i * elem_size) + + result = UInt(result, res_op.size) + res_op.write(result) + + # XXX: Support EOR (immediate) and EOR (vector). + @instruction + def EOR(cpu, res_op, reg_op1, reg_op2): + """ + EOR (shifted register). + + :param res_op: destination register. + :param reg_op1: source register. + :param reg_op2: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op1.type is cs.arm64.ARM64_OP_REG + assert reg_op2.type is cs.arm64.ARM64_OP_REG + + insn_rx = '[01]' # sf + insn_rx += '10' # opc + insn_rx += '01010' + insn_rx += '[01]{2}' # shift + insn_rx += '0' # N + insn_rx += '[01]{5}' # Rm + insn_rx += '[01]{6}' # imm6 + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + cpu._shifted_register( + res_op=res_op, + reg_op1=reg_op1, + reg_op2=reg_op2, + action=lambda x, y: (x ^ y, None), + shifts=[ + cs.arm64.ARM64_SFT_LSL, + cs.arm64.ARM64_SFT_LSR, + cs.arm64.ARM64_SFT_ASR, + cs.arm64.ARM64_SFT_ROR + ]) + + # XXX: Support LD1 (single structure). + @instruction + def LD1(cpu, op1, op2, op3=None, op4=None, op5=None, op6=None): + """ + LD1 (multiple structures). + + :param op1: register. + :param op2: memory or register. + :param op3: None, memory, register, or immediate. + :param op4: None, memory, register, or immediate. + :param op5: None, memory, register, or immediate. + :param op6: None, register, or immediate. + """ + assert op1.type is cs.arm64.ARM64_OP_REG + assert op2.type in [cs.arm64.ARM64_OP_MEM, cs.arm64.ARM64_OP_REG] + assert not op3 or op3.type in [cs.arm64.ARM64_OP_MEM, cs.arm64.ARM64_OP_REG, cs.arm64.ARM64_OP_IMM] + assert not op4 or op4.type in [cs.arm64.ARM64_OP_MEM, cs.arm64.ARM64_OP_REG, cs.arm64.ARM64_OP_IMM] + assert not op5 or op5.type in [cs.arm64.ARM64_OP_MEM, cs.arm64.ARM64_OP_REG, cs.arm64.ARM64_OP_IMM] + assert not op6 or op6.type in [cs.arm64.ARM64_OP_REG, cs.arm64.ARM64_OP_IMM] + + no_offset_rx = '0' + no_offset_rx += '[01]' # Q + no_offset_rx += '0011000' + no_offset_rx += '1' # L + no_offset_rx += '000000' + no_offset_rx += '[01]{2}1[01]' # opcode + no_offset_rx += '[01]{2}' # size + no_offset_rx += '[01]{5}' # Rn + no_offset_rx += '[01]{5}' # Rt + + post_index_rx = '0' + post_index_rx += '[01]' # Q + post_index_rx += '0011001' + post_index_rx += '1' # L + post_index_rx += '0' + post_index_rx += '[01]{5}' # Rm + post_index_rx += '[01]{2}1[01]' # opcode + post_index_rx += '[01]{2}' # size + post_index_rx += '[01]{5}' # Rn + post_index_rx += '[01]{5}' # Rt + + assert ( + re.match(no_offset_rx, cpu.insn_bit_str) or + re.match(post_index_rx, cpu.insn_bit_str) + ) + + # XXX: Check if trapped. + + # Four registers. + if (op1.type == cs.arm64.ARM64_OP_REG and + op2.type == cs.arm64.ARM64_OP_REG and + op3.type == cs.arm64.ARM64_OP_REG and + op4.type == cs.arm64.ARM64_OP_REG): + res_ops = [op1, op2, op3, op4] + mem_op = op5 + wback_op = op6 + + # Three registers. + elif (op1.type == cs.arm64.ARM64_OP_REG and + op2.type == cs.arm64.ARM64_OP_REG and + op3.type == cs.arm64.ARM64_OP_REG): + res_ops = [op1, op2, op3] + mem_op = op4 + wback_op = op5 + + # Two registers. + elif (op1.type == cs.arm64.ARM64_OP_REG and + op2.type == cs.arm64.ARM64_OP_REG): + res_ops = [op1, op2] + mem_op = op3 + wback_op = op4 + + # One register. + else: + res_ops = [op1] + mem_op = op2 + wback_op = op3 + + i = 0 + for res_op in res_ops: + base = cpu.regfile.read(mem_op.mem.base) + vas = res_op.op.vas + + if vas == cs.arm64.ARM64_VAS_8B: + elem_size = 8 + elem_count = 8 + + elif vas == cs.arm64.ARM64_VAS_16B: + elem_size = 8 + elem_count = 16 + + elif vas == cs.arm64.ARM64_VAS_4H: + elem_size = 16 + elem_count = 4 + + elif vas == cs.arm64.ARM64_VAS_8H: + elem_size = 16 + elem_count = 8 + + elif vas == cs.arm64.ARM64_VAS_2S: + elem_size = 32 + elem_count = 2 + + elif vas == cs.arm64.ARM64_VAS_4S: + elem_size = 32 + elem_count = 4 + + elif vas == cs.arm64.ARM64_VAS_1D: + elem_size = 64 + elem_count = 1 + + elif vas == cs.arm64.ARM64_VAS_2D: + elem_size = 64 + elem_count = 2 + + else: + raise Aarch64InvalidInstruction + + size = elem_size * elem_count + assert size <= res_op.size + result = cpu.read_int(base + i * (size // 8), size) + res_op.write(result) + + i += 1 + + if cpu.instruction.writeback: + wback = wback_op.read() + wback = UInt(base + wback, cpu.address_bit_size) + cpu.regfile.write(mem_op.mem.base, wback) + + @instruction + def LDAXR(cpu, reg_op, mem_op): + """ + LDAXR. + + :param reg_op: destination register. + :param mem_op: memory. + """ + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert mem_op.type is cs.arm64.ARM64_OP_MEM + + insn_rx = '1[01]' # size + insn_rx += '001000' + insn_rx += '0' + insn_rx += '1' # L + insn_rx += '0' + insn_rx += '1{5}' # Rs + insn_rx += '1' # o0 + insn_rx += '1{5}' # Rt2 + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rt + + assert re.match(insn_rx, cpu.insn_bit_str) + + # XXX: Support exclusive access. + + base = cpu.regfile.read(mem_op.mem.base) + imm = mem_op.mem.disp + assert imm == 0 + + result = cpu.read_int(base, reg_op.size) + reg_op.write(result) + + # XXX: Support LDP (SIMD&FP). + @instruction + def LDP(cpu, reg_op1, reg_op2, mem_op, mimm_op=None): + """ + LDP. + + :param reg_op1: destination register. + :param reg_op2: destination register. + :param mem_op: memory. + :param mimm_op: None or immediate. + """ + cpu._ldp_stp(reg_op1, reg_op2, mem_op, mimm_op, ldp=True) + + def _LDR_immediate(cpu, reg_op, mem_op, mimm_op): + """ + LDR (immediate). + + :param reg_op: destination register. + :param mem_op: memory. + :param mimm_op: None or immediate. + """ + cpu._ldr_str_immediate(reg_op, mem_op, mimm_op, ldr=True) + + def _LDR_literal(cpu, reg_op, imm_op): + """ + LDR (literal). + + :param reg_op: destination register. + :param imm_op: immediate. + """ + cpu._ldr_literal(reg_op, imm_op) + + def _LDR_register(cpu, reg_op, mem_op): + """ + LDR (register). + + :param reg_op: destination register. + :param mem_op: memory. + """ + cpu._ldr_str_register(reg_op, mem_op, ldr=True) + + # XXX: Support LDR (immediate, SIMD&FP), LDR (literal, SIMD&FP), and LDR + # (register, SIMD&FP). + @instruction + def LDR(cpu, res_op, mem_imm_op, mimm_op=None): + """ + Combines LDR (immediate), LDR (literal), and LDR (register). + + :param res_op: destination register. + :param mem_imm_op: memory or immediate. + :param mimm_op: None or immediate. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert mem_imm_op.type in [cs.arm64.ARM64_OP_MEM, cs.arm64.ARM64_OP_IMM] + assert not mimm_op or mimm_op.type is cs.arm64.ARM64_OP_IMM + + if mem_imm_op.type == cs.arm64.ARM64_OP_MEM: + if mem_imm_op.mem.index: + cpu._LDR_register(res_op, mem_imm_op) + else: + cpu._LDR_immediate(res_op, mem_imm_op, mimm_op) + + elif mem_imm_op.type == cs.arm64.ARM64_OP_IMM: + cpu._LDR_literal(res_op, mem_imm_op) + + else: + raise Aarch64InvalidInstruction + + def _LDRB_immediate(cpu, reg_op, mem_op, mimm_op): + """ + LDRB (immediate). + + :param reg_op: destination register. + :param mem_op: memory. + :param mimm_op: None or immediate. + """ + cpu._ldr_str_immediate(reg_op, mem_op, mimm_op, ldr=True, size=8) + + def _LDRB_register(cpu, reg_op, mem_op): + """ + LDRB (register). + + :param reg_op: destination register. + :param mem_op: memory. + """ + cpu._ldr_str_register(reg_op, mem_op, ldr=True, size=8) + + @instruction + def LDRB(cpu, reg_op, mem_op, mimm_op=None): + """ + Combines LDRB (immediate) and LDRB (register). + + :param reg_op: destination register. + :param mem_op: memory. + :param mimm_op: None or immediate. + """ + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert mem_op.type is cs.arm64.ARM64_OP_MEM + assert not mimm_op or mimm_op.type is cs.arm64.ARM64_OP_IMM + + if mem_op.mem.index: + cpu._LDRB_register(reg_op, mem_op) + else: + cpu._LDRB_immediate(reg_op, mem_op, mimm_op) + + def _LDRH_immediate(cpu, reg_op, mem_op, mimm_op): + """ + LDRH (immediate). + + :param reg_op: destination register. + :param mem_op: memory. + :param mimm_op: None or immediate. + """ + cpu._ldr_str_immediate(reg_op, mem_op, mimm_op, ldr=True, size=16) + + def _LDRH_register(cpu, reg_op, mem_op): + """ + LDRH (register). + + :param reg_op: destination register. + :param mem_op: memory. + """ + cpu._ldr_str_register(reg_op, mem_op, ldr=True, size=16) + + @instruction + def LDRH(cpu, reg_op, mem_op, mimm_op=None): + """ + Combines LDRH (immediate) and LDRH (register). + + :param reg_op: destination register. + :param mem_op: memory. + :param mimm_op: None or immediate. + """ + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert mem_op.type is cs.arm64.ARM64_OP_MEM + assert not mimm_op or mimm_op.type is cs.arm64.ARM64_OP_IMM + + if mem_op.mem.index: + cpu._LDRH_register(reg_op, mem_op) + else: + cpu._LDRH_immediate(reg_op, mem_op, mimm_op) + + def _LDRSW_immediate(cpu, reg_op, mem_op, mimm_op): + """ + LDRSW (immediate). + + :param reg_op: destination register. + :param mem_op: memory. + :param mimm_op: None or immediate. + """ + cpu._ldr_str_immediate(reg_op, mem_op, mimm_op, ldr=True, size=32, sextend=True) + + def _LDRSW_literal(cpu, reg_op, imm_op): + """ + LDRSW (literal). + + :param reg_op: destination register. + :param imm_op: immediate. + """ + cpu._ldr_literal(reg_op, imm_op, size=32, sextend=True) + + def _LDRSW_register(cpu, reg_op, mem_op): + """ + LDRSW (register). + + :param reg_op: destination register. + :param mem_op: memory. + """ + cpu._ldr_str_register(reg_op, mem_op, ldr=True, size=32, sextend=True) + + @instruction + def LDRSW(cpu, res_op, mem_imm_op, mimm_op=None): + """ + Combines LDRSW (immediate), LDRSW (literal), and LDRSW (register). + + :param res_op: destination register. + :param mem_imm_op: memory or immediate. + :param mimm_op: None or immediate. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert mem_imm_op.type in [cs.arm64.ARM64_OP_MEM, cs.arm64.ARM64_OP_IMM] + assert not mimm_op or mimm_op.type is cs.arm64.ARM64_OP_IMM + + if mem_imm_op.type == cs.arm64.ARM64_OP_MEM: + if mem_imm_op.mem.index: + cpu._LDRSW_register(res_op, mem_imm_op) + else: + cpu._LDRSW_immediate(res_op, mem_imm_op, mimm_op) + + elif mem_imm_op.type == cs.arm64.ARM64_OP_IMM: + cpu._LDRSW_literal(res_op, mem_imm_op) + + else: + raise Aarch64InvalidInstruction + + # XXX: Support LDUR (SIMD&FP). + @instruction + def LDUR(cpu, reg_op, mem_op): + """ + LDUR. + + :param reg_op: destination register. + :param mem_op: memory. + """ + cpu._ldur_stur(reg_op, mem_op, ldur=True) + + @instruction + def LDXR(cpu, reg_op, mem_op): + """ + LDXR. + + :param reg_op: destination register. + :param mem_op: memory. + """ + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert mem_op.type is cs.arm64.ARM64_OP_MEM + + insn_rx = '1[01]' # size + insn_rx += '001000' + insn_rx += '0' + insn_rx += '1' # L + insn_rx += '0' + insn_rx += '1{5}' # Rs + insn_rx += '0' # o0 + insn_rx += '1{5}' # Rt2 + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rt + + assert re.match(insn_rx, cpu.insn_bit_str) + + # XXX: Support exclusive access. + + base = cpu.regfile.read(mem_op.mem.base) + imm = mem_op.mem.disp + assert imm == 0 + + result = cpu.read_int(base, reg_op.size) + reg_op.write(result) + + def _LSL_immediate(cpu, res_op, reg_op, imm_op): + """ + LSL (immediate). + + :param res_op: destination register. + :param reg_op: source register. + :param imm_op: immediate. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert imm_op.type is cs.arm64.ARM64_OP_IMM + + insn_rx = '[01]' # sf + insn_rx += '10' # opc + insn_rx += '100110' + insn_rx += '[01]' # N + insn_rx += '[01]{6}' # immr + insn_rx += '[01]{6}' # imms + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + imm = imm_op.op.imm + + # Fake immediate operands. + immr_op = Aarch64Operand.make_imm(cpu, -imm % res_op.size) + imms_op = Aarch64Operand.make_imm(cpu, res_op.size - 1 - imm) + + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.UBFM.__wrapped__(cpu, res_op, reg_op, immr_op, imms_op) + + def _LSL_register(cpu, res_op, reg_op1, reg_op2): + """ + LSL (register). + + :param res_op: destination register. + :param reg_op1: source register. + :param reg_op2: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op1.type is cs.arm64.ARM64_OP_REG + assert reg_op2.type is cs.arm64.ARM64_OP_REG + + insn_rx = '[01]' # sf + insn_rx += '0' + insn_rx += '0' + insn_rx += '11010110' + insn_rx += '[01]{5}' # Rm + insn_rx += '0010' + insn_rx += '00' # op2 + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.LSLV.__wrapped__(cpu, res_op, reg_op1, reg_op2) + + @instruction + def LSL(cpu, res_op, reg_op, reg_imm_op): + """ + Combines LSL (register) and LSL (immediate). + + :param res_op: destination register. + :param reg_op: source register. + :param reg_imm_op: source register or immediate. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert reg_imm_op.type in [cs.arm64.ARM64_OP_REG, cs.arm64.ARM64_OP_IMM] + + if reg_imm_op.type == cs.arm64.ARM64_OP_REG: + cpu._LSL_register(res_op, reg_op, reg_imm_op) + + elif reg_imm_op.type == cs.arm64.ARM64_OP_IMM: + cpu._LSL_immediate(res_op, reg_op, reg_imm_op) + + else: + raise Aarch64InvalidInstruction + + @instruction + def LSLV(cpu, res_op, reg_op1, reg_op2): + """ + LSLV. + + :param res_op: destination register. + :param reg_op1: source register. + :param reg_op2: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op1.type is cs.arm64.ARM64_OP_REG + assert reg_op2.type is cs.arm64.ARM64_OP_REG + + insn_rx = '[01]' # sf + insn_rx += '0' + insn_rx += '0' + insn_rx += '11010110' + insn_rx += '[01]{5}' # Rm + insn_rx += '0010' + insn_rx += '00' # op2 + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + reg = reg_op1.read() + sft = reg_op2.read() + + result = LSL(reg, sft % res_op.size, res_op.size) + res_op.write(result) + + def _LSR_immediate(cpu, res_op, reg_op, immr_op): + """ + LSR (immediate). + + :param res_op: destination register. + :param reg_op: source register. + :param immr_op: immediate. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert immr_op.type is cs.arm64.ARM64_OP_IMM + + insn_rx = '[01]' # sf + insn_rx += '10' # opc + insn_rx += '100110' + insn_rx += '[01]' # N + insn_rx += '[01]{6}' # immr + insn_rx += '[01]1{5}' # imms + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + # Fake an immediate operand. + imms_op = Aarch64Operand.make_imm(cpu, res_op.size - 1) + + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.UBFM.__wrapped__(cpu, res_op, reg_op, immr_op, imms_op) + + def _LSR_register(cpu, res_op, reg_op1, reg_op2): + """ + LSR (register). + + :param res_op: destination register. + :param reg_op1: source register. + :param reg_op2: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op1.type is cs.arm64.ARM64_OP_REG + assert reg_op2.type is cs.arm64.ARM64_OP_REG + + insn_rx = '[01]' # sf + insn_rx += '0' + insn_rx += '0' + insn_rx += '11010110' + insn_rx += '[01]{5}' # Rm + insn_rx += '0010' + insn_rx += '01' # op2 + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.LSRV.__wrapped__(cpu, res_op, reg_op1, reg_op2) + + @instruction + def LSR(cpu, res_op, reg_op, reg_imm_op): + """ + Combines LSR (register) and LSR (immediate). + + :param res_op: destination register. + :param reg_op: source register. + :param reg_imm_op: source register or immediate. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert reg_imm_op.type in [cs.arm64.ARM64_OP_REG, cs.arm64.ARM64_OP_IMM] + + if reg_imm_op.type == cs.arm64.ARM64_OP_REG: + cpu._LSR_register(res_op, reg_op, reg_imm_op) + + elif reg_imm_op.type == cs.arm64.ARM64_OP_IMM: + cpu._LSR_immediate(res_op, reg_op, reg_imm_op) + + else: + raise Aarch64InvalidInstruction + + @instruction + def LSRV(cpu, res_op, reg_op1, reg_op2): + """ + LSRV. + + :param res_op: destination register. + :param reg_op1: source register. + :param reg_op2: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op1.type is cs.arm64.ARM64_OP_REG + assert reg_op2.type is cs.arm64.ARM64_OP_REG + + insn_rx = '[01]' # sf + insn_rx += '0' + insn_rx += '0' + insn_rx += '11010110' + insn_rx += '[01]{5}' # Rm + insn_rx += '0010' + insn_rx += '01' # op2 + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + reg = reg_op1.read() + sft = reg_op2.read() + + result = LSR(reg, sft % res_op.size, res_op.size) + res_op.write(result) + + @instruction + def MADD(cpu, res_op, reg_op1, reg_op2, reg_op3): + """ + MADD. + + :param res_op: destination register. + :param reg_op1: source register. + :param reg_op2: source register. + :param reg_op3: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op1.type is cs.arm64.ARM64_OP_REG + assert reg_op2.type is cs.arm64.ARM64_OP_REG + assert reg_op3.type is cs.arm64.ARM64_OP_REG + + insn_rx = '[01]' # sf + insn_rx += '00' + insn_rx += '11011' + insn_rx += '000' + insn_rx += '[01]{5}' # Rm + insn_rx += '0' # o0 + insn_rx += '[01]{5}' # Ra + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + reg1 = reg_op1.read() + reg2 = reg_op2.read() + reg3 = reg_op3.read() + + result = reg3 + (reg1 * reg2) + res_op.write(UInt(result, res_op.size)) + + def _MOV_to_general(cpu, res_op, reg_op): + """ + MOV (to general). + + :param res_op: destination register. + :param reg_op: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + + insn_rx = '0' + insn_rx += '[01]' # Q + insn_rx += '0' + insn_rx += '01110000' + insn_rx += '[01]{3}00' # imm5 + insn_rx += '0' + insn_rx += '01' + insn_rx += '1' + insn_rx += '1' + insn_rx += '1' + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + # XXX: Check if trapped. + + # XXX: Capstone doesn't set 'vess' for this alias: + # https://github.com/aquynh/capstone/issues/1452 + if res_op.size == 32: + reg_op.op.vess = cs.arm64.ARM64_VESS_S + + elif res_op.size == 64: + reg_op.op.vess = cs.arm64.ARM64_VESS_D + + else: + raise Aarch64InvalidInstruction + + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.UMOV.__wrapped__(cpu, res_op, reg_op) + + # XXX: Support MOV (scalar), MOV (element), MOV (from general), and MOV + # (vector). + @instruction + def MOV(cpu, res_op, reg_imm_op): + """ + Combines MOV (to/from SP), MOV (inverted wide immediate), MOV (wide + immediate), MOV (bitmask immediate), MOV (register), and MOV (to + general). + + :param res_op: destination register. + :param reg_imm_op: source register or immediate. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_imm_op.type in [cs.arm64.ARM64_OP_REG, cs.arm64.ARM64_OP_IMM] + + # Fake a register operand. + if res_op.size == 32: + zr = Aarch64Operand.make_reg(cpu, cs.arm64.ARM64_REG_WZR) + elif res_op.size == 64: + zr = Aarch64Operand.make_reg(cpu, cs.arm64.ARM64_REG_XZR) + else: + raise Aarch64InvalidInstruction + + opc = cpu.insn_bit_str[1:3] # 'op S' for MOV (to/from SP) + bit26 = cpu.insn_bit_str[-27] + + if reg_imm_op.type is cs.arm64.ARM64_OP_REG: + # MOV (to general). + if bit26 == '1': + cpu._MOV_to_general(res_op, reg_imm_op) + + # MOV (to/from SP). + elif bit26 == '0' and opc == '00': + # Fake an immediate operand. + zero = Aarch64Operand.make_imm(cpu, 0) + + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.ADD.__wrapped__(cpu, res_op, reg_imm_op, zero) + + # MOV (register). + elif bit26 == '0' and opc == '01': + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.ORR.__wrapped__(cpu, res_op, zr, reg_imm_op) + + else: + raise Aarch64InvalidInstruction + + elif reg_imm_op.type is cs.arm64.ARM64_OP_IMM: + # MOV (inverted wide immediate). + if opc == '00': + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.MOVN.__wrapped__(cpu, res_op, reg_imm_op) + + # MOV (wide immediate). + elif opc == '10': + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.MOVZ.__wrapped__(cpu, res_op, reg_imm_op) + + # MOV (bitmask immediate). + elif opc == '01': + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.ORR.__wrapped__(cpu, res_op, zr, reg_imm_op) + + else: + raise Aarch64InvalidInstruction + + else: + raise Aarch64InvalidInstruction + + @instruction + def MOVK(cpu, res_op, imm_op): + """ + MOVK. + + :param res_op: destination register. + :param imm_op: immediate. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert imm_op.type is cs.arm64.ARM64_OP_IMM + + insn_rx = '[01]' # sf + insn_rx += '11' # opc + insn_rx += '100101' + insn_rx += '[01]{2}' # hw + insn_rx += '[01]{16}' # imm16 + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + res = res_op.read() + imm = imm_op.op.imm + sft = imm_op.op.shift.value + + if imm_op.is_shifted(): + assert imm_op.op.shift.type == cs.arm64.ARM64_SFT_LSL + + assert imm >= 0 and imm <= 65535 + assert ( + (res_op.size == 32 and sft in [0, 16]) or + (res_op.size == 64 and sft in [0, 16, 32, 48]) + ) + + imm = LSL(imm, sft, res_op.size) + mask = LSL(65535, sft, res_op.size) + result = (res & ~mask) | imm + res_op.write(result) + + @instruction + def MOVN(cpu, res_op, imm_op): + """ + MOVN. + + :param res_op: destination register. + :param imm_op: immediate. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert imm_op.type is cs.arm64.ARM64_OP_IMM + + insn_rx = '[01]' # sf + insn_rx += '00' # opc + insn_rx += '100101' + insn_rx += '[01]{2}' # hw + insn_rx += '[01]{16}' # imm16 + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + imm = imm_op.op.imm + sft = imm_op.op.shift.value + + if imm_op.is_shifted(): + assert imm_op.op.shift.type == cs.arm64.ARM64_SFT_LSL + + assert imm >= 0 and imm <= 65535 + assert ( + (res_op.size == 32 and sft in [0, 16]) or + (res_op.size == 64 and sft in [0, 16, 32, 48]) + ) + + result = UInt(~LSL(imm, sft, res_op.size), res_op.size) + res_op.write(result) + + @instruction + def MOVZ(cpu, res_op, imm_op): + """ + MOVZ. + + :param res_op: destination register. + :param imm_op: immediate. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert imm_op.type is cs.arm64.ARM64_OP_IMM + + insn_rx = '[01]' # sf + insn_rx += '10' # opc + insn_rx += '100101' + insn_rx += '[01]{2}' # hw + insn_rx += '[01]{16}' # imm16 + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + imm = imm_op.op.imm + sft = imm_op.op.shift.value + + if imm_op.is_shifted(): + assert imm_op.op.shift.type == cs.arm64.ARM64_SFT_LSL + + assert imm >= 0 and imm <= 65535 + assert ( + (res_op.size == 32 and sft in [0, 16]) or + (res_op.size == 64 and sft in [0, 16, 32, 48]) + ) + + result = UInt(LSL(imm, sft, res_op.size), res_op.size) + res_op.write(result) + + @instruction + def MRS(cpu, res_op, reg_op): + """ + MRS. + + :param res_op: destination register. + :param reg_op: source system register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG_MRS + + insn_rx = '1101010100' + insn_rx += '1' # L + insn_rx += '1' + insn_rx += '[01]' # o0 + insn_rx += '[01]{3}' # op1 + insn_rx += '[01]{4}' # CRn + insn_rx += '[01]{4}' # CRm + insn_rx += '[01]{3}' # op2 + insn_rx += '[01]{5}' # Rt + + assert re.match(insn_rx, cpu.insn_bit_str) + + reg = reg_op.read() + res_op.write(reg) + + # XXX: Support MSR (immediate). + @instruction + def MSR(cpu, res_op, reg_op): + """ + MSR (register). + + :param res_op: destination system register. + :param reg_op: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG_MSR + assert reg_op.type is cs.arm64.ARM64_OP_REG + + insn_rx = '1101010100' + insn_rx += '0' # L + insn_rx += '1' + insn_rx += '[01]' # o0 + insn_rx += '[01]{3}' # op1 + insn_rx += '[01]{4}' # CRn + insn_rx += '[01]{4}' # CRm + insn_rx += '[01]{3}' # op2 + insn_rx += '[01]{5}' # Rt + + assert re.match(insn_rx, cpu.insn_bit_str) + + reg = reg_op.read() + res_op.write(reg) + + @instruction + def MSUB(cpu, res_op, reg_op1, reg_op2, reg_op3): + """ + MSUB. + + :param res_op: destination register. + :param reg_op1: source register. + :param reg_op2: source register. + :param reg_op3: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op1.type is cs.arm64.ARM64_OP_REG + assert reg_op2.type is cs.arm64.ARM64_OP_REG + assert reg_op3.type is cs.arm64.ARM64_OP_REG + + insn_rx = '[01]' # sf + insn_rx += '00' + insn_rx += '11011' + insn_rx += '000' + insn_rx += '[01]{5}' # Rm + insn_rx += '1' # o0 + insn_rx += '[01]{5}' # Ra + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + reg1 = reg_op1.read() + reg2 = reg_op2.read() + reg3 = reg_op3.read() + + result = reg3 - (reg1 * reg2) + res_op.write(UInt(result, res_op.size)) + + # XXX: Support MUL (by element) and MUL (vector). + @instruction + def MUL(cpu, res_op, reg_op1, reg_op2): + """ + MUL. + + :param res_op: destination register. + :param reg_op1: source register. + :param reg_op2: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op1.type is cs.arm64.ARM64_OP_REG + assert reg_op2.type is cs.arm64.ARM64_OP_REG + + insn_rx = '[01]' # sf + insn_rx += '00' + insn_rx += '11011' + insn_rx += '000' + insn_rx += '[01]{5}' # Rm + insn_rx += '0' # o0 + insn_rx += '1{5}' # Ra + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + # Fake a register operand. + if res_op.size == 32: + zr = Aarch64Operand.make_reg(cpu, cs.arm64.ARM64_REG_WZR) + elif res_op.size == 64: + zr = Aarch64Operand.make_reg(cpu, cs.arm64.ARM64_REG_XZR) + else: + raise Aarch64InvalidInstruction + + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.MADD.__wrapped__(cpu, res_op, reg_op1, reg_op2, zr) + + # XXX: Support NEG (vector). + @instruction + def NEG(cpu, res_op, reg_op): + """ + NEG (shifted register). + + :param res_op: destination register. + :param reg_op: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + + insn_rx = '[01]' # sf + insn_rx += '1' # op + insn_rx += '0' # S + insn_rx += '01011' + insn_rx += '[01]{2}' # shift + insn_rx += '0' + insn_rx += '[01]{5}' # Rm + insn_rx += '[01]{6}' # imm6 + insn_rx += '1{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + # Fake a register operand. + if res_op.size == 32: + zr = Aarch64Operand.make_reg(cpu, cs.arm64.ARM64_REG_WZR) + elif res_op.size == 64: + zr = Aarch64Operand.make_reg(cpu, cs.arm64.ARM64_REG_XZR) + else: + raise Aarch64InvalidInstruction + + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.SUB.__wrapped__(cpu, res_op, zr, reg_op) + + @instruction + def NOP(cpu): + """ + NOP. + """ + insn_rx = '1101010100' + insn_rx += '0' + insn_rx += '00' + insn_rx += '011' + insn_rx += '0010' + insn_rx += '0000' # CRm + insn_rx += '000' # op2 + insn_rx += '11111' + + assert re.match(insn_rx, cpu.insn_bit_str) + + def _ORR_immediate(cpu, res_op, reg_op, imm_op): + """ + ORR (immediate). + + :param res_op: destination register. + :param reg_op: source register. + :param imm_op: immediate. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert imm_op.type is cs.arm64.ARM64_OP_IMM + + insn_rx = '[01]' # sf + insn_rx += '01' # opc + insn_rx += '100100' + insn_rx += '[01]' # N + insn_rx += '[01]{6}' # immr + insn_rx += '[01]{6}' # imms + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + reg = reg_op.read() + imm = imm_op.op.imm + result = UInt(reg | imm, res_op.size) + res_op.write(result) + + def _ORR_shifted_register(cpu, res_op, reg_op1, reg_op2): + """ + ORR (shifted register). + + :param res_op: destination register. + :param reg_op1: source register. + :param reg_op2: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op1.type is cs.arm64.ARM64_OP_REG + assert reg_op2.type is cs.arm64.ARM64_OP_REG + + insn_rx = '[01]' # sf + insn_rx += '01' # opc + insn_rx += '01010' + insn_rx += '[01]{2}' # shift + insn_rx += '0' # N + insn_rx += '[01]{5}' # Rm + insn_rx += '[01]{6}' # imm6 + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + cpu._shifted_register( + res_op=res_op, + reg_op1=reg_op1, + reg_op2=reg_op2, + action=lambda x, y: (x | y, None), + shifts=[ + cs.arm64.ARM64_SFT_LSL, + cs.arm64.ARM64_SFT_LSR, + cs.arm64.ARM64_SFT_ASR, + cs.arm64.ARM64_SFT_ROR + ]) + + def _ORR_vector_register(cpu, res_op, reg_op1, reg_op2): + """ + ORR (vector, register). + + :param res_op: destination register. + :param reg_op1: source register. + :param reg_op2: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op1.type is cs.arm64.ARM64_OP_REG + assert reg_op2.type is cs.arm64.ARM64_OP_REG + + insn_rx = '0' + insn_rx += '[01]' # Q + insn_rx += '0' + insn_rx += '01110' + insn_rx += '10' # size + insn_rx += '1' + insn_rx += '[01]{5}' # Rm + insn_rx += '00011' + insn_rx += '1' + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + # XXX: Check if trapped. + + reg1 = reg_op1.read() + reg2 = reg_op2.read() + vas = res_op.op.vas + + if vas == cs.arm64.ARM64_VAS_8B: + elem_size = 8 + elem_count = 8 + + elif vas == cs.arm64.ARM64_VAS_16B: + elem_size = 8 + elem_count = 16 + + else: + raise Aarch64InvalidInstruction + + result = 0 + for i in range(elem_count): + elem1 = Operators.EXTRACT(reg1, i * elem_size, elem_size) + elem2 = Operators.EXTRACT(reg2, i * elem_size, elem_size) + elem = UInt(elem1 | elem2, elem_size) + elem = Operators.ZEXTEND(elem, res_op.size) + result |= elem << (i * elem_size) + + result = UInt(result, res_op.size) + res_op.write(result) + + # XXX: Support ORR (vector, immediate). + @instruction + def ORR(cpu, res_op, reg_op, reg_imm_op): + """ + Combines ORR (immediate), ORR (shifted register), and ORR (vector, + register). + + :param res_op: destination register. + :param reg_op: source register. + :param reg_imm_op: source register or immediate. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert reg_imm_op.type in [cs.arm64.ARM64_OP_REG, cs.arm64.ARM64_OP_IMM] + + bit21 = cpu.insn_bit_str[-22] + + if reg_imm_op.type == cs.arm64.ARM64_OP_IMM: + cpu._ORR_immediate(res_op, reg_op, reg_imm_op) + + elif reg_imm_op.type == cs.arm64.ARM64_OP_REG and bit21 == '0': + cpu._ORR_shifted_register(res_op, reg_op, reg_imm_op) + + elif reg_imm_op.type == cs.arm64.ARM64_OP_REG and bit21 == '1': + cpu._ORR_vector_register(res_op, reg_op, reg_imm_op) + + else: + raise Aarch64InvalidInstruction + + # XXX: Support RBIT (vector). + @instruction + def RBIT(cpu, res_op, reg_op): + """ + RBIT. + + :param res_op: destination register. + :param reg_op: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + + insn_rx = '[01]' # sf + insn_rx += '1' + insn_rx += '0' + insn_rx += '11010110' + insn_rx += '0{5}' + insn_rx += '0{4}' + insn_rx += '0{2}' + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + reg = reg_op.read() + size = reg_op.size + + result = 0 + for pos in range(size): + bit = Operators.EXTRACT(reg, pos, 1) + bit = Operators.ZEXTEND(bit, res_op.size) + result <<= 1 + result |= bit + + res_op.write(result) + + @instruction + def RET(cpu, reg_op=None): + """ + RET. + + :param reg_op: None or register. + """ + assert not reg_op or reg_op.type is cs.arm64.ARM64_OP_REG + + insn_rx = '1101011' + insn_rx += '0' # Z + insn_rx += '0' + insn_rx += '10' # op + insn_rx += '1{5}' + insn_rx += '0{4}' + insn_rx += '0' # A + insn_rx += '0' # M + insn_rx += '[01]{5}' # Rn + insn_rx += '0{5}' # Rm + + assert re.match(insn_rx, cpu.insn_bit_str) + + if reg_op: + reg = reg_op.read() + else: + reg = cpu.X30 + cpu.PC = reg + + @instruction + def REV(cpu, res_op, reg_op): + """ + REV. + + :param res_op: destination register. + :param reg_op: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + + insn_rx = '[01]' # sf + insn_rx += '1' + insn_rx += '0' + insn_rx += '11010110' + insn_rx += '0{5}' + insn_rx += '0{4}' + insn_rx += '1[01]' # opc + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + reg = reg_op.read() + size = reg_op.size + + result = 0 + step = 8 + for pos in range(0, size, step): + byte = Operators.EXTRACT(reg, pos, step) + byte = Operators.ZEXTEND(byte, res_op.size) + result <<= step + result |= byte + + res_op.write(result) + + @instruction + def SBFIZ(cpu, res_op, reg_op, lsb_op, width_op): + """ + SBFIZ. + + :param res_op: destination register. + :param reg_op: source register. + :param lsb_op: immediate. + :param width_op: immediate. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert lsb_op.type is cs.arm64.ARM64_OP_IMM + assert width_op.type is cs.arm64.ARM64_OP_IMM + + insn_rx = '[01]' # sf + insn_rx += '00' # opc + insn_rx += '100110' + insn_rx += '[01]' # N + insn_rx += '[01]{6}' # immr + insn_rx += '[01]{6}' # imms + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + lsb = lsb_op.op.imm + lsb_op.value.imm = -lsb % res_op.size + width_op.value.imm -= 1 + + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.SBFM.__wrapped__(cpu, res_op, reg_op, lsb_op, width_op) + + @instruction + def SBFM(cpu, res_op, reg_op, immr_op, imms_op): + """ + SBFM. + + :param res_op: destination register. + :param reg_op: source register. + :param immr_op: immediate. + :param imms_op: immediate. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert immr_op.type is cs.arm64.ARM64_OP_IMM + assert imms_op.type is cs.arm64.ARM64_OP_IMM + + insn_rx = '[01]' # sf + insn_rx += '00' # opc + insn_rx += '100110' + insn_rx += '[01]' # N + insn_rx += '[01]{6}' # immr + insn_rx += '[01]{6}' # imms + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + reg = reg_op.read() + immr = immr_op.op.imm + imms = imms_op.op.imm + + assert immr in range(res_op.size) + assert imms in range(res_op.size) + + if imms >= immr: + width = imms - immr + 1 + copy_from = immr + copy_to = 0 + else: + width = imms + 1 + copy_from = 0 + copy_to = res_op.size - immr + + result = ((reg & (Mask(width) << copy_from)) >> copy_from) << copy_to + result = Operators.ZEXTEND(result, res_op.size) + result = Operators.ITEBV( + res_op.size, + Operators.EXTRACT(result, width + copy_to - 1, 1) == 1, + (Mask(res_op.size) & ~Mask(width + copy_to)) | result, + result + ) + + res_op.write(result) + + @instruction + def SBFX(cpu, res_op, reg_op, lsb_op, width_op): + """ + SBFX. + + :param res_op: destination register. + :param reg_op: source register. + :param lsb_op: immediate. + :param width_op: immediate. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert lsb_op.type is cs.arm64.ARM64_OP_IMM + assert width_op.type is cs.arm64.ARM64_OP_IMM + + insn_rx = '[01]' # sf + insn_rx += '00' # opc + insn_rx += '100110' + insn_rx += '[01]' # N + insn_rx += '[01]{6}' # immr + insn_rx += '[01]{6}' # imms + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + lsb = lsb_op.op.imm + width = width_op.op.imm + width_op.value.imm = lsb + width - 1 + + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.SBFM.__wrapped__(cpu, res_op, reg_op, lsb_op, width_op) + + # XXX: Add tests. + @instruction + def STLXR(cpu, stat_op, reg_op, mem_op): + """ + STLXR. + + :param stat_op: status register. + :param reg_op: source register. + :param mem_op: memory. + """ + assert stat_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert mem_op.type is cs.arm64.ARM64_OP_MEM + + insn_rx = '1[01]' # size + insn_rx += '001000' + insn_rx += '0' + insn_rx += '0' # L + insn_rx += '0' + insn_rx += '[01]{5}' # Rs + insn_rx += '1' # o0 + insn_rx += '1{5}' # Rt2 + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rt + + assert re.match(insn_rx, cpu.insn_bit_str) + + # XXX: Support exclusive access and check alignment. + + base = cpu.regfile.read(mem_op.mem.base) + imm = mem_op.mem.disp + assert imm == 0 + reg = reg_op.read() + + cpu.write_int(base, reg, reg_op.size) + stat_op.write(0) # XXX: always succeeds + + @instruction + def STP(cpu, reg_op1, reg_op2, mem_op, mimm_op=None): + """ + STP. + + :param reg_op1: source register. + :param reg_op2: source register. + :param mem_op: memory. + :param mimm_op: None or immediate. + """ + cpu._ldp_stp(reg_op1, reg_op2, mem_op, mimm_op, ldp=False) + + def _STR_immediate(cpu, reg_op, mem_op, mimm_op): + """ + STR (immediate). + + :param reg_op: source register. + :param mem_op: memory. + :param mimm_op: None or immediate. + """ + cpu._ldr_str_immediate(reg_op, mem_op, mimm_op, ldr=False) + + def _STR_register(cpu, reg_op, mem_op): + """ + STR (register). + + :param reg_op: source register. + :param mem_op: memory. + """ + cpu._ldr_str_register(reg_op, mem_op, ldr=False) + + # XXX: Support STR (immediate, SIMD&FP) and STR (register, SIMD&FP). + @instruction + def STR(cpu, reg_op, mem_op, mimm_op=None): + """ + Combines STR (immediate) and STR (register). + + :param reg_op: source register. + :param mem_op: memory. + :param mimm_op: None or immediate. + """ + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert mem_op.type is cs.arm64.ARM64_OP_MEM + assert not mimm_op or mimm_op.type is cs.arm64.ARM64_OP_IMM + + if mem_op.mem.index: + cpu._STR_register(reg_op, mem_op) + else: + cpu._STR_immediate(reg_op, mem_op, mimm_op) + + def _STRB_immediate(cpu, reg_op, mem_op, mimm_op): + """ + STRB (immediate). + + :param reg_op: source register. + :param mem_op: memory. + :param mimm_op: None or immediate. + """ + cpu._ldr_str_immediate(reg_op, mem_op, mimm_op, ldr=False, size=8) + + def _STRB_register(cpu, reg_op, mem_op): + """ + STRB (register). + + :param reg_op: source register. + :param mem_op: memory. + """ + cpu._ldr_str_register(reg_op, mem_op, ldr=False, size=8) + + @instruction + def STRB(cpu, reg_op, mem_op, mimm_op=None): + """ + Combines STRB (immediate) and STRB (register). + + :param reg_op: source register. + :param mem_op: memory. + :param mimm_op: None or immediate. + """ + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert mem_op.type is cs.arm64.ARM64_OP_MEM + assert not mimm_op or mimm_op.type is cs.arm64.ARM64_OP_IMM + + if mem_op.mem.index: + cpu._STRB_register(reg_op, mem_op) + else: + cpu._STRB_immediate(reg_op, mem_op, mimm_op) + + def _STRH_immediate(cpu, reg_op, mem_op, mimm_op): + """ + STRH (immediate). + + :param reg_op: source register. + :param mem_op: memory. + :param mimm_op: None or immediate. + """ + cpu._ldr_str_immediate(reg_op, mem_op, mimm_op, ldr=False, size=16) + + def _STRH_register(cpu, reg_op, mem_op): + """ + STRH (register). + + :param reg_op: source register. + :param mem_op: memory. + """ + cpu._ldr_str_register(reg_op, mem_op, ldr=False, size=16) + + @instruction + def STRH(cpu, reg_op, mem_op, mimm_op=None): + """ + Combines STRH (immediate) and STRH (register). + + :param reg_op: source register. + :param mem_op: memory. + :param mimm_op: None or immediate. + """ + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert mem_op.type is cs.arm64.ARM64_OP_MEM + assert not mimm_op or mimm_op.type is cs.arm64.ARM64_OP_IMM + + if mem_op.mem.index: + cpu._STRH_register(reg_op, mem_op) + else: + cpu._STRH_immediate(reg_op, mem_op, mimm_op) + + # XXX: Support STUR (SIMD&FP). + @instruction + def STUR(cpu, reg_op, mem_op): + """ + STUR. + + :param reg_op: source register. + :param mem_op: memory. + """ + cpu._ldur_stur(reg_op, mem_op, ldur=False) + + # XXX: Add tests. + @instruction + def STXR(cpu, stat_op, reg_op, mem_op): + """ + STXR. + + :param stat_op: status register. + :param reg_op: source register. + :param mem_op: memory. + """ + assert stat_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert mem_op.type is cs.arm64.ARM64_OP_MEM + + insn_rx = '1[01]' # size + insn_rx += '001000' + insn_rx += '0' + insn_rx += '0' # L + insn_rx += '0' + insn_rx += '[01]{5}' # Rs + insn_rx += '0' # o0 + insn_rx += '1{5}' # Rt2 + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rt + + assert re.match(insn_rx, cpu.insn_bit_str) + + # XXX: Support exclusive access and check alignment. + + base = cpu.regfile.read(mem_op.mem.base) + imm = mem_op.mem.disp + assert imm == 0 + reg = reg_op.read() + + cpu.write_int(base, reg, reg_op.size) + stat_op.write(0) # XXX: always succeeds + + def _SUB_extended_register(cpu, res_op, reg_op1, reg_op2): + """ + SUB (extended register). + + :param res_op: destination register. + :param reg_op1: source register. + :param reg_op2: source register. + """ + cpu._adds_subs_extended_register(res_op, reg_op1, reg_op2, mnem='sub') + + def _SUB_immediate(cpu, res_op, reg_op, imm_op): + """ + SUB (immediate). + + :param res_op: destination register. + :param reg_op: source register. + :param imm_op: immediate. + """ + cpu._adds_subs_immediate(res_op, reg_op, imm_op, mnem='sub') + + def _SUB_shifted_register(cpu, res_op, reg_op1, reg_op2): + """ + SUB (shifted register). + + :param res_op: destination register. + :param reg_op1: source register. + :param reg_op2: source register. + """ + cpu._adds_subs_shifted_register(res_op, reg_op1, reg_op2, mnem='sub') + + def _SUB_vector(cpu, res_op, reg_op1, reg_op2): + """ + SUB (vector). + + :param res_op: destination register. + :param reg_op1: source register. + :param reg_op2: source register. + """ + cpu._add_sub_vector(res_op, reg_op1, reg_op2, add=False) + + @instruction + def SUB(cpu, res_op, reg_op, reg_imm_op): + """ + Combines SUB (extended register), SUB (immediate), SUB (shifted + register), and SUB (vector). + + :param res_op: destination register. + :param reg_op: source register. + :param reg_imm_op: source register or immediate. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert reg_imm_op.type in [cs.arm64.ARM64_OP_REG, cs.arm64.ARM64_OP_IMM] + + bit21 = cpu.insn_bit_str[-22] + bit24 = cpu.insn_bit_str[-25] + + if reg_imm_op.type == cs.arm64.ARM64_OP_IMM: + cpu._SUB_immediate(res_op, reg_op, reg_imm_op) + + elif reg_imm_op.type == cs.arm64.ARM64_OP_REG and bit24 == '0': + cpu._SUB_vector(res_op, reg_op, reg_imm_op) + + elif reg_imm_op.type == cs.arm64.ARM64_OP_REG and bit24 == '1' and bit21 == '0': + cpu._SUB_shifted_register(res_op, reg_op, reg_imm_op) + + elif reg_imm_op.type == cs.arm64.ARM64_OP_REG and bit24 == '1' and bit21 == '1': + cpu._SUB_extended_register(res_op, reg_op, reg_imm_op) + + else: + raise Aarch64InvalidInstruction + + def _SUBS_extended_register(cpu, res_op, reg_op1, reg_op2): + """ + SUBS (extended register). + + :param res_op: destination register. + :param reg_op1: source register. + :param reg_op2: source register. + """ + cpu._adds_subs_extended_register(res_op, reg_op1, reg_op2, mnem='subs') + + def _SUBS_immediate(cpu, res_op, reg_op, imm_op): + """ + SUBS (immediate). + + :param res_op: destination register. + :param reg_op: source register. + :param imm_op: immediate. + """ + cpu._adds_subs_immediate(res_op, reg_op, imm_op, mnem='subs') + + def _SUBS_shifted_register(cpu, res_op, reg_op1, reg_op2): + """ + SUBS (shifted register). + + :param res_op: destination register. + :param reg_op1: source register. + :param reg_op2: source register. + """ + cpu._adds_subs_shifted_register(res_op, reg_op1, reg_op2, mnem='subs') + + @instruction + def SUBS(cpu, res_op, reg_op, reg_imm_op): + """ + Combines SUBS (extended register), SUBS (immediate), and SUBS (shifted + register). + + :param res_op: destination register. + :param reg_op: source register. + :param reg_imm_op: source register or immediate. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert reg_imm_op.type in [cs.arm64.ARM64_OP_REG, cs.arm64.ARM64_OP_IMM] + + bit21 = cpu.insn_bit_str[-22] + + if reg_imm_op.type == cs.arm64.ARM64_OP_IMM: + cpu._SUBS_immediate(res_op, reg_op, reg_imm_op) + + elif reg_imm_op.type == cs.arm64.ARM64_OP_REG and bit21 == '0': + cpu._SUBS_shifted_register(res_op, reg_op, reg_imm_op) + + elif reg_imm_op.type == cs.arm64.ARM64_OP_REG and bit21 == '1': + cpu._SUBS_extended_register(res_op, reg_op, reg_imm_op) + + else: + raise Aarch64InvalidInstruction + + @instruction + def SVC(cpu, imm_op): + """ + SVC. + + :param imm_op: immediate. + """ + assert imm_op.type is cs.arm64.ARM64_OP_IMM + + imm = imm_op.op.imm + assert imm >= 0 and imm <= 65535 + + if imm != 0: + raise InstructionNotImplementedError(f'SVC #{imm}') + raise Interruption(imm) + + @instruction + def SXTB(cpu, res_op, reg_op): + """ + SXTB. + + :param res_op: destination register. + :param reg_op: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + + insn_rx = '[01]' # sf + insn_rx += '00' # opc + insn_rx += '100110' + insn_rx += '[01]' # N + insn_rx += '0{6}' # immr + insn_rx += '000111' # imms + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + # Fake immediate operands. + immr_op = Aarch64Operand.make_imm(cpu, 0) + imms_op = Aarch64Operand.make_imm(cpu, 7) + + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.SBFM.__wrapped__(cpu, res_op, reg_op, immr_op, imms_op) + + @instruction + def SXTH(cpu, res_op, reg_op): + """ + SXTH. + + :param res_op: destination register. + :param reg_op: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + + insn_rx = '[01]' # sf + insn_rx += '00' # opc + insn_rx += '100110' + insn_rx += '[01]' # N + insn_rx += '0{6}' # immr + insn_rx += '001111' # imms + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + # Fake immediate operands. + immr_op = Aarch64Operand.make_imm(cpu, 0) + imms_op = Aarch64Operand.make_imm(cpu, 15) + + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.SBFM.__wrapped__(cpu, res_op, reg_op, immr_op, imms_op) + + @instruction + def SXTW(cpu, res_op, reg_op): + """ + SXTW. + + :param res_op: destination register. + :param reg_op: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + + insn_rx = '1' # sf + insn_rx += '00' # opc + insn_rx += '100110' + insn_rx += '1' # N + insn_rx += '000000' # immr + insn_rx += '011111' # imms + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + # Fake immediate operands. + immr_op = Aarch64Operand.make_imm(cpu, 0) + imms_op = Aarch64Operand.make_imm(cpu, 31) + + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.SBFM.__wrapped__(cpu, res_op, reg_op, immr_op, imms_op) + + @instruction + def TBNZ(cpu, reg_op, imm_op, lab_op): + """ + TBNZ. + + :param reg_op: register. + :param imm_op: immediate. + :param lab_op: immediate. + """ + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert imm_op.type is cs.arm64.ARM64_OP_IMM + assert lab_op.type is cs.arm64.ARM64_OP_IMM + + insn_rx = '[01]' # b5 + insn_rx += '011011' + insn_rx += '1' # op + insn_rx += '[01]{5}' # b40 + insn_rx += '[01]{14}' # imm14 + insn_rx += '[01]{5}' # Rt + + assert re.match(insn_rx, cpu.insn_bit_str) + + reg = reg_op.read() + imm = imm_op.op.imm + lab = lab_op.op.imm + + assert imm in range(reg_op.size) + + cpu.PC = Operators.ITEBV( + cpu.regfile.size('PC'), + Operators.EXTRACT(reg, imm, 1) != 0, + lab, + cpu.PC + ) + + @instruction + def TBZ(cpu, reg_op, imm_op, lab_op): + """ + TBZ. + + :param reg_op: register. + :param imm_op: immediate. + :param lab_op: immediate. + """ + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert imm_op.type is cs.arm64.ARM64_OP_IMM + assert lab_op.type is cs.arm64.ARM64_OP_IMM + + insn_rx = '[01]' # b5 + insn_rx += '011011' + insn_rx += '0' # op + insn_rx += '[01]{5}' # b40 + insn_rx += '[01]{14}' # imm14 + insn_rx += '[01]{5}' # Rt + + assert re.match(insn_rx, cpu.insn_bit_str) + + reg = reg_op.read() + imm = imm_op.op.imm + lab = lab_op.op.imm + + assert imm in range(reg_op.size) + + cpu.PC = Operators.ITEBV( + cpu.regfile.size('PC'), + Operators.EXTRACT(reg, imm, 1) == 0, + lab, + cpu.PC + ) + + def _TST_immediate(cpu, reg_op, imm_op): + """ + TST (immediate). + + :param reg_op: source register. + :param imm_op: immediate. + """ + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert imm_op.type is cs.arm64.ARM64_OP_IMM + + insn_rx = '[01]' # sf + insn_rx += '11' # opc + insn_rx += '100100' + insn_rx += '[01]' # N + insn_rx += '[01]{6}' # immr + insn_rx += '[01]{6}' # imms + insn_rx += '[01]{5}' # Rn + insn_rx += '1{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + # Fake a register operand. + if reg_op.size == 32: + zr = Aarch64Operand.make_reg(cpu, cs.arm64.ARM64_REG_WZR) + elif reg_op.size == 64: + zr = Aarch64Operand.make_reg(cpu, cs.arm64.ARM64_REG_XZR) + else: + raise Aarch64InvalidInstruction + + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.ANDS.__wrapped__(cpu, zr, reg_op, imm_op) + + def _TST_shifted_register(cpu, reg_op1, reg_op2): + """ + TST (shifted register). + + :param reg_op1: source register. + :param reg_op2: source register. + """ + assert reg_op1.type is cs.arm64.ARM64_OP_REG + assert reg_op2.type is cs.arm64.ARM64_OP_REG + + insn_rx = '[01]' # sf + insn_rx += '11' # opc + insn_rx += '01010' + insn_rx += '[01]{2}' # shift + insn_rx += '0' # N + insn_rx += '[01]{5}' # Rm + insn_rx += '[01]{6}' # imm6 + insn_rx += '[01]{5}' # Rn + insn_rx += '1{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + # Fake a register operand. + if reg_op1.size == 32: + zr = Aarch64Operand.make_reg(cpu, cs.arm64.ARM64_REG_WZR) + elif reg_op1.size == 64: + zr = Aarch64Operand.make_reg(cpu, cs.arm64.ARM64_REG_XZR) + else: + raise Aarch64InvalidInstruction + + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.ANDS.__wrapped__(cpu, zr, reg_op1, reg_op2) + + @instruction + def TST(cpu, reg_op, reg_imm_op): + """ + Combines TST (immediate) and TST (shifted register). + + :param reg_op: source register. + :param reg_imm_op: source register or immediate. + """ + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert reg_imm_op.type in [cs.arm64.ARM64_OP_REG, cs.arm64.ARM64_OP_IMM] + + if reg_imm_op.type == cs.arm64.ARM64_OP_REG: + cpu._TST_shifted_register(reg_op, reg_imm_op) + + elif reg_imm_op.type == cs.arm64.ARM64_OP_IMM: + cpu._TST_immediate(reg_op, reg_imm_op) + + else: + raise Aarch64InvalidInstruction + + @instruction + def UBFIZ(cpu, res_op, reg_op, lsb_op, width_op): + """ + UBFIZ. + + :param res_op: destination register. + :param reg_op: source register. + :param lsb_op: immediate. + :param width_op: immediate. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert lsb_op.type is cs.arm64.ARM64_OP_IMM + assert width_op.type is cs.arm64.ARM64_OP_IMM + + insn_rx = '[01]' # sf + insn_rx += '10' # opc + insn_rx += '100110' + insn_rx += '[01]' # N + insn_rx += '[01]{6}' # immr + insn_rx += '[01]{6}' # imms + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + lsb = lsb_op.op.imm + lsb_op.value.imm = -lsb % res_op.size + width_op.value.imm -= 1 + + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.UBFM.__wrapped__(cpu, res_op, reg_op, lsb_op, width_op) + + @instruction + def UBFM(cpu, res_op, reg_op, immr_op, imms_op): + """ + UBFM. + + :param res_op: destination register. + :param reg_op: source register. + :param immr_op: immediate. + :param imms_op: immediate. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert immr_op.type is cs.arm64.ARM64_OP_IMM + assert imms_op.type is cs.arm64.ARM64_OP_IMM + + insn_rx = '[01]' # sf + insn_rx += '10' # opc + insn_rx += '100110' + insn_rx += '[01]' # N + insn_rx += '[01]{6}' # immr + insn_rx += '[01]{6}' # imms + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + reg = reg_op.read() + immr = immr_op.op.imm + imms = imms_op.op.imm + + assert immr in range(res_op.size) + assert imms in range(res_op.size) + + if imms >= immr: + width = imms - immr + 1 + copy_from = immr + copy_to = 0 + else: + width = imms + 1 + copy_from = 0 + copy_to = res_op.size - immr + + mask = Mask(width) + result = ((reg & (mask << copy_from)) >> copy_from) << copy_to + res_op.write(result) + + @instruction + def UBFX(cpu, res_op, reg_op, lsb_op, width_op): + """ + UBFX. + + :param res_op: destination register. + :param reg_op: source register. + :param lsb_op: immediate. + :param width_op: immediate. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + assert lsb_op.type is cs.arm64.ARM64_OP_IMM + assert width_op.type is cs.arm64.ARM64_OP_IMM + + insn_rx = '[01]' # sf + insn_rx += '10' # opc + insn_rx += '100110' + insn_rx += '[01]' # N + insn_rx += '[01]{6}' # immr + insn_rx += '[01]{6}' # imms + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + lsb = lsb_op.op.imm + width = width_op.op.imm + width_op.value.imm = lsb + width - 1 + + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.UBFM.__wrapped__(cpu, res_op, reg_op, lsb_op, width_op) + + @instruction + def UDIV(cpu, res_op, reg_op1, reg_op2): + """ + UDIV. + + :param res_op: destination register. + :param reg_op1: source register. + :param reg_op2: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op1.type is cs.arm64.ARM64_OP_REG + assert reg_op2.type is cs.arm64.ARM64_OP_REG + + insn_rx = '[01]' # sf + insn_rx += '0' + insn_rx += '0' + insn_rx += '11010110' + insn_rx += '[01]{5}' # Rm + insn_rx += '00001' + insn_rx += '0' # o1 + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + reg1 = UInt(reg_op1.read(), reg_op1.size) + reg2 = UInt(reg_op2.read(), reg_op2.size) + + # Compute regardless of the second argument, so it can be referenced in + # 'ITEBV'. But the result shouldn't be used if 'reg2 == 0'. + try: + quot = Operators.UDIV(reg1, reg2) # rounds toward zero + except ZeroDivisionError: + quot = 0 + + result = Operators.ITEBV( + res_op.size, + reg2 == 0, + 0, + quot + ) + + res_op.write(result) + + @instruction + def UMOV(cpu, res_op, reg_op): + """ + UMOV. + + :param res_op: destination register. + :param reg_op: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + + insn_rx = '0' + insn_rx += '[01]' # Q + insn_rx += '0' + insn_rx += '01110000' + insn_rx += '[01]{5}' # imm5 + insn_rx += '0' + insn_rx += '01' + insn_rx += '1' + insn_rx += '1' + insn_rx += '1' + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + # XXX: Check if trapped. + + reg = reg_op.read() + index = reg_op.op.vector_index + vess = reg_op.op.vess + + if vess == cs.arm64.ARM64_VESS_B: + elem_size = 8 + + elif vess == cs.arm64.ARM64_VESS_H: + elem_size = 16 + + elif vess == cs.arm64.ARM64_VESS_S: + elem_size = 32 + + elif vess == cs.arm64.ARM64_VESS_D: + elem_size = 64 + + else: + raise Aarch64InvalidInstruction + + result = Operators.EXTRACT(reg, index * elem_size, elem_size) + res_op.write(UInt(result, res_op.size)) + + @instruction + def UMULH(cpu, res_op, reg_op1, reg_op2): + """ + UMULH. + + :param res_op: destination register. + :param reg_op1: source register. + :param reg_op2: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op1.type is cs.arm64.ARM64_OP_REG + assert reg_op2.type is cs.arm64.ARM64_OP_REG + + insn_rx = '1' + insn_rx += '00' + insn_rx += '11011' + insn_rx += '1' # U + insn_rx += '10' + insn_rx += '[01]{5}' # Rm + insn_rx += '0' + insn_rx += '1{5}' # Ra + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + reg1 = UInt(reg_op1.read(), reg_op1.size) + reg2 = UInt(reg_op2.read(), reg_op2.size) + + reg1 = Operators.ZEXTEND(reg1, 128) + reg2 = Operators.ZEXTEND(reg2, 128) + + result = Operators.EXTRACT(reg1 * reg2, 64, 64) + res_op.write(result) + + @instruction + def UXTB(cpu, res_op, reg_op): + """ + UXTB. + + :param res_op: destination register. + :param reg_op: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + + insn_rx = '0' # sf + insn_rx += '10' # opc + insn_rx += '100110' + insn_rx += '0' # N + insn_rx += '0{6}' # immr + insn_rx += '000111' # imms + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + # Fake immediate operands. + immr_op = Aarch64Operand.make_imm(cpu, 0) + imms_op = Aarch64Operand.make_imm(cpu, 7) + + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.UBFM.__wrapped__(cpu, res_op, reg_op, immr_op, imms_op) + + @instruction + def UXTH(cpu, res_op, reg_op): + """ + UXTH. + + :param res_op: destination register. + :param reg_op: source register. + """ + assert res_op.type is cs.arm64.ARM64_OP_REG + assert reg_op.type is cs.arm64.ARM64_OP_REG + + insn_rx = '0' # sf + insn_rx += '10' # opc + insn_rx += '100110' + insn_rx += '0' # N + insn_rx += '0{6}' # immr + insn_rx += '001111' # imms + insn_rx += '[01]{5}' # Rn + insn_rx += '[01]{5}' # Rd + + assert re.match(insn_rx, cpu.insn_bit_str) + + # Fake immediate operands. + immr_op = Aarch64Operand.make_imm(cpu, 0) + imms_op = Aarch64Operand.make_imm(cpu, 15) + + # The 'instruction' decorator advances PC, so call the original + # method. + cpu.UBFM.__wrapped__(cpu, res_op, reg_op, immr_op, imms_op) + + +class Aarch64CdeclAbi(Abi): + """Aarch64/arm64 cdecl function call ABI""" + + # XXX: Floating-point arguments are not supported. + # For floats and doubles, the first 8 arguments are passed via registers + # (S0-S7 for floats, D0-D7 for doubles), then on stack. + # The result is returned in S0 for floats and D0 for doubles. + + def get_arguments(self): + # First 8 arguments are passed via X0-X7 (or W0-W7 if they are 32-bit), + # then on stack. + + for reg in ('X0', 'X1', 'X2', 'X3', 'X4', 'X5', 'X6', 'X7'): + yield reg + + for address in self.values_from(self._cpu.STACK): + yield address + + def write_result(self, result): + self._cpu.X0 = result + + def ret(self): + self._cpu.PC = self._cpu.LR + + +class Aarch64LinuxSyscallAbi(SyscallAbi): + """Aarch64/arm64 Linux system call ABI""" + + # From 'man 2 syscall': + # arch/ABI: arm64 + # instruction: svc #0 + # syscall number: x8 + # arguments: x0-x5 (arg1-arg6) + # return value: x0 + def syscall_number(self): + return self._cpu.X8 + + def get_arguments(self): + return ('X{}'.format(i) for i in range(6)) + + def write_result(self, result): + self._cpu.X0 = result + + +class Aarch64Operand(Operand): + def __init__(self, cpu, op, **kwargs): + super(Aarch64Operand, self).__init__(cpu, op) + + if self.op.type not in ( + cs.arm64.ARM64_OP_REG, + cs.arm64.ARM64_OP_REG_MRS, + cs.arm64.ARM64_OP_REG_MSR, + cs.arm64.ARM64_OP_MEM, + cs.arm64.ARM64_OP_IMM, + cs.arm64.ARM64_OP_FP, + cs.arm64.ARM64_OP_BARRIER + ): + raise NotImplementedError( + f"Unsupported operand type: '{self.op.type}'" + ) + + self._type = self.op.type + + @classmethod + def make_imm(cls, cpu, value): + imm_op = cs.arm64.Arm64Op() + imm_op.value.imm = value + imm_op.type = cs.arm64.ARM64_OP_IMM + imm_op = cls(cpu, imm_op) + return imm_op + + @classmethod + def make_reg(cls, cpu, value): + reg_op = cs.arm64.Arm64Op() + reg_op.value.reg = value + reg_op.type = cs.arm64.ARM64_OP_REG + reg_op = cls(cpu, reg_op) + return reg_op + + @property + def type(self): + return self._type + + @property + def size(self): + # XXX: Support other operand types. + assert self.type is cs.arm64.ARM64_OP_REG + return self.cpu.regfile._table[self.reg].size + + def is_shifted(self): + """ + :return: True if operand is shifted, otherwise False. + """ + return self.op.shift.type != cs.arm64.ARM64_SFT_INVALID + + def is_extended(self): + """ + :return: True if operand is extended, otherwise False. + """ + return self.op.ext != cs.arm64.ARM64_EXT_INVALID + + def read(self): + if self.type == cs.arm64.ARM64_OP_REG: + return self.cpu.regfile.read(self.reg) + elif self.type == cs.arm64.ARM64_OP_REG_MRS: + name = SYS_REG_MAP.get(self.op.sys) + if not name: + raise NotImplementedError( + f"Unsupported system register: '0x{self.op.sys:x}'" + ) + return self.cpu.regfile.read(name) + elif self.type == cs.arm64.ARM64_OP_IMM: + return self.op.imm + else: + raise NotImplementedError(f"Unsupported operand type: '{self.type}'") + + def write(self, value): + if self.type == cs.arm64.ARM64_OP_REG: + self.cpu.regfile.write(self.reg, value) + elif self.type == cs.arm64.ARM64_OP_REG_MSR: + name = SYS_REG_MAP.get(self.op.sys) + if not name: + raise NotImplementedError( + f"Unsupported system register: '0x{self.op.sys:x}'" + ) + self.cpu.regfile.write(name, value) + else: + raise NotImplementedError(f"Unsupported operand type: '{self.type}'") diff --git a/manticore/native/cpu/abstractcpu.py b/manticore/native/cpu/abstractcpu.py index 1c992f656..1a0f05b44 100644 --- a/manticore/native/cpu/abstractcpu.py +++ b/manticore/native/cpu/abstractcpu.py @@ -20,6 +20,8 @@ from ...utils.fallback_emulator import UnicornEmulator from ...utils.helpers import issymbolic +from capstone import CS_ARCH_ARM64, CS_ARCH_X86 +from capstone.arm64 import ARM64_REG_ENDING from capstone.x86 import X86_REG_ENDING logger = logging.getLogger(__name__) @@ -146,7 +148,9 @@ def _reg_name(self, reg_id): :param int reg_id: Register ID """ - if reg_id >= X86_REG_ENDING: + # XXX: Support other architectures. + if ((self.cpu.arch == CS_ARCH_ARM64 and reg_id >= ARM64_REG_ENDING) or + (self.cpu.arch == CS_ARCH_X86 and reg_id >= X86_REG_ENDING)): logger.warning("Trying to get register name for a non-register") return None cs_reg_name = self.cpu.instruction.reg_name(reg_id) diff --git a/manticore/native/cpu/bitwise.py b/manticore/native/cpu/bitwise.py index 7e5e87c27..bd10ba49f 100644 --- a/manticore/native/cpu/bitwise.py +++ b/manticore/native/cpu/bitwise.py @@ -75,7 +75,7 @@ def UInt(value, width): return GetNBits(value, width) -def LSL_C(value, amount, width): +def LSL_C(value, amount, width, with_carry=True): """ The ARM LSL_C (logical left shift with carry) operation. @@ -86,12 +86,17 @@ def LSL_C(value, amount, width): :return: Resultant value and the carry result :rtype tuple """ - assert amount > 0 + if isinstance(amount, int): + assert amount > 0 value = Operators.ZEXTEND(value, width * 2) + amount = Operators.ZEXTEND(amount, width * 2) shifted = value << amount result = GetNBits(shifted, width) - carry = Bit(shifted, width) - return (result, carry) + if with_carry: + carry = Bit(shifted, width) + return (result, carry) + else: + return result def LSL(value, amount, width): @@ -105,14 +110,14 @@ def LSL(value, amount, width): :return: Resultant value :rtype int or BitVec """ - if amount == 0: + if isinstance(amount, int) and amount == 0: return value - result, _ = LSL_C(value, amount, width) + result = LSL_C(value, amount, width, with_carry=False) return result -def LSR_C(value, amount, width): +def LSR_C(value, amount, width, with_carry=True): """ The ARM LSR_C (logical shift right with carry) operation. @@ -123,10 +128,14 @@ def LSR_C(value, amount, width): :return: Resultant value and carry result :rtype tuple """ - assert amount > 0 + if isinstance(amount, int): + assert amount > 0 result = GetNBits(value >> amount, width) - carry = Bit(value >> (amount - 1), 0) - return (result, carry) + if with_carry: + carry = Bit(value >> (amount - 1), 0) + return (result, carry) + else: + return result def LSR(value, amount, width): @@ -140,13 +149,13 @@ def LSR(value, amount, width): :return: Resultant value :rtype int or BitVec """ - if amount == 0: + if isinstance(amount, int) and amount == 0: return value - result, _ = LSR_C(value, amount, width) + result = LSR_C(value, amount, width, with_carry=False) return result -def ASR_C(value, amount, width): +def ASR_C(value, amount, width, with_carry=True): """ The ARM ASR_C (arithmetic shift right with carry) operation. @@ -157,13 +166,20 @@ def ASR_C(value, amount, width): :return: Resultant value and carry result :rtype tuple """ - assert amount <= width - assert amount > 0 - assert amount + width <= width * 2 + if isinstance(amount, int) and isinstance(width, int): + assert amount <= width + if isinstance(amount, int): + assert amount > 0 + if isinstance(amount, int) and isinstance(width, int): + assert amount + width <= width * 2 value = Operators.SEXTEND(value, width, width * 2) + amount = Operators.ZEXTEND(amount, width * 2) result = GetNBits(value >> amount, width) - carry = Bit(value, amount - 1) - return (result, carry) + if with_carry: + carry = Bit(value, amount - 1) + return (result, carry) + else: + return result def ASR(value, amount, width): @@ -177,14 +193,14 @@ def ASR(value, amount, width): :return: Resultant value :rtype int or BitVec """ - if amount == 0: + if isinstance(amount, int) and amount == 0: return value - result, _ = ASR_C(value, amount, width) + result = ASR_C(value, amount, width, with_carry=False) return result -def ROR_C(value, amount, width): +def ROR_C(value, amount, width, with_carry=True): """ The ARM ROR_C (rotate right with carry) operation. @@ -195,14 +211,19 @@ def ROR_C(value, amount, width): :return: Resultant value and carry result :rtype tuple """ - assert amount <= width - assert amount > 0 + if isinstance(amount, int) and isinstance(width, int): + assert amount <= width + if isinstance(amount, int): + assert amount > 0 m = amount % width right, _ = LSR_C(value, m, width) left, _ = LSL_C(value, width - m, width) result = left | right - carry = Bit(result, width - 1) - return (result, carry) + if with_carry: + carry = Bit(result, width - 1) + return (result, carry) + else: + return result def ROR(value, amount, width): @@ -216,13 +237,13 @@ def ROR(value, amount, width): :return: Resultant value :rtype int or BitVec """ - if amount == 0: + if isinstance(amount, int) and amount == 0: return value - result, _ = ROR_C(value, amount, width) + result = ROR_C(value, amount, width, with_carry=False) return result -def RRX_C(value, carry, width): +def RRX_C(value, carry, width, with_carry=True): """ The ARM RRX (rotate right with extend and with carry) operation. @@ -233,9 +254,12 @@ def RRX_C(value, carry, width): :return: Resultant value and carry result :rtype tuple """ - carry_out = Bit(value, 0) result = (value >> 1) | (carry << (width - 1)) - return (result, carry_out) + if with_carry: + carry_out = Bit(value, 0) + return (result, carry_out) + else: + return result def RRX(value, carry, width): @@ -249,5 +273,5 @@ def RRX(value, carry, width): :return: Resultant value :rtype int or BitVec """ - result, _ = RRX_C(value, carry, width) + result = RRX_C(value, carry, width, with_carry=False) return result diff --git a/manticore/native/cpu/cpufactory.py b/manticore/native/cpu/cpufactory.py index 8b0c9baa3..57470767b 100644 --- a/manticore/native/cpu/cpufactory.py +++ b/manticore/native/cpu/cpufactory.py @@ -1,5 +1,6 @@ -from .x86 import AMD64Cpu, I386Cpu, AMD64LinuxSyscallAbi, I386LinuxSyscallAbi, I386CdeclAbi, SystemVAbi +from .aarch64 import Aarch64Cpu, Aarch64CdeclAbi, Aarch64LinuxSyscallAbi from .arm import Armv7Cpu, Armv7CdeclAbi, Armv7LinuxSyscallAbi +from .x86 import AMD64Cpu, I386Cpu, AMD64LinuxSyscallAbi, I386LinuxSyscallAbi, I386CdeclAbi, SystemVAbi class CpuFactory: @@ -7,6 +8,21 @@ class CpuFactory: 'i386': I386Cpu, 'amd64': AMD64Cpu, 'armv7': Armv7Cpu, + 'aarch64': Aarch64Cpu + } + + _linux_abis = { + 'i386': I386CdeclAbi, + 'amd64': SystemVAbi, + 'armv7': Armv7CdeclAbi, + 'aarch64': Aarch64CdeclAbi, + } + + _linux_syscalls_abis = { + 'i386': I386LinuxSyscallAbi, + 'amd64': AMD64LinuxSyscallAbi, + 'armv7': Armv7LinuxSyscallAbi, + 'aarch64': Aarch64LinuxSyscallAbi } @staticmethod @@ -17,22 +33,14 @@ def get_cpu(mem, machine): @staticmethod def get_function_abi(cpu, os, machine): - if os == 'linux' and machine == 'i386': - return I386CdeclAbi(cpu) - elif os == 'linux' and machine == 'amd64': - return SystemVAbi(cpu) - elif os == 'linux' and machine == 'armv7': - return Armv7CdeclAbi(cpu) - else: - return NotImplementedError(f"OS and machine combination not supported: {os}/{machine}") + if os != 'linux' or machine not in CpuFactory._linux_abis: + raise NotImplementedError(f"OS and machine combination not supported: {os}/{machine}") + + return CpuFactory._linux_abis[machine](cpu) @staticmethod def get_syscall_abi(cpu, os, machine): - if os == 'linux' and machine == 'i386': - return I386LinuxSyscallAbi(cpu) - elif os == 'linux' and machine == 'amd64': - return AMD64LinuxSyscallAbi(cpu) - elif os == 'linux' and machine == 'armv7': - return Armv7LinuxSyscallAbi(cpu) - else: - return NotImplementedError(f"OS and machine combination not supported: {os}/{machine}") + if os != 'linux' or machine not in CpuFactory._linux_syscalls_abis: + raise NotImplementedError(f"OS and machine combination not supported: {os}/{machine}") + + return CpuFactory._linux_syscalls_abis[machine](cpu) diff --git a/manticore/platforms/linux.py b/manticore/platforms/linux.py index 6c3ed2d03..2294a9155 100644 --- a/manticore/platforms/linux.py +++ b/manticore/platforms/linux.py @@ -426,7 +426,7 @@ def __init__(self, program, argv=None, envp=None, disasm='capstone', **kwargs): self.elf = ELFFile(open(program, 'rb')) # FIXME (theo) self.arch is actually mode as initialized in the CPUs, # make things consistent and perhaps utilize a global mapping for this - self.arch = {'x86': 'i386', 'x64': 'amd64', 'ARM': 'armv7'}[self.elf.get_machine_arch()] + self.arch = {'x86': 'i386', 'x64': 'amd64', 'ARM': 'armv7', 'AArch64': 'aarch64'}[self.elf.get_machine_arch()] self._init_cpu(self.arch) self._init_std_fds() @@ -893,7 +893,7 @@ def load(self, filename, env): elf = self.elf arch = self.arch env = dict(var.split('=') for var in env if '=' in var) - addressbitsize = {'x86': 32, 'x64': 64, 'ARM': 32}[elf.get_machine_arch()] + addressbitsize = {'x86': 32, 'x64': 64, 'ARM': 32, 'AArch64': 64}[elf.get_machine_arch()] logger.debug("Loading %s as a %s elf", filename, arch) assert elf.header.e_type in ['ET_DYN', 'ET_EXEC', 'ET_CORE'] @@ -915,6 +915,7 @@ def load(self, filename, env): interpreter = ELFFile(open(interpreter_path_filename, 'rb')) break break + if interpreter is not None: assert interpreter.get_machine_arch() == elf.get_machine_arch() assert interpreter.header.e_type in ['ET_DYN', 'ET_EXEC'] @@ -1651,13 +1652,15 @@ def sys_close(self, fd): def sys_readlink(self, path, buf, bufsize): """ - Read + Read the value of a symbolic link. :rtype: int - :param path: the "link path id" - :param buf: the buffer where the bytes will be putted. - :param bufsize: the max size for read the link. - :todo: Out eax number of bytes actually sent | EAGAIN | EBADF | EFAULT | EINTR | errno.EINVAL | EIO | ENOSPC | EPIPE + :param path: symbolic link. + :param buf: destination buffer. + :param bufsize: size to read. + :return: number of bytes placed in buffer on success, -errno on error. + + :todo: return -errno on error. """ if bufsize <= 0: return -errno.EINVAL @@ -1667,8 +1670,34 @@ def sys_readlink(self, path, buf, bufsize): else: data = os.readlink(filename)[:bufsize] self.current.write_bytes(buf, data) + # XXX: Return an appropriate value on error. return len(data) + def sys_readlinkat(self, dir_fd, path, buf, bufsize): + """ + Read the value of a symbolic link relative to a directory file descriptor. + :rtype: int + + :param dir_fd: directory file descriptor. + :param path: symbolic link. + :param buf: destination buffer. + :param bufsize: size to read. + :return: number of bytes placed in buffer on success, -errno on error. + + :todo: return -errno on error, full 'dir_fd' support. + """ + _path = self.current.read_string(path) + _dir_fd = ctypes.c_int32(dir_fd).value + + if not (os.path.isabs(_path) or _dir_fd == self.FCNTL_FDCWD): + raise NotImplementedError( + "Only absolute paths or paths relative to CWD are supported" + ) + + # XXX: Use 'dir_fd'. + # XXX: Return an appropriate value on error. + return self.sys_readlink(path, buf, bufsize) + def sys_mmap_pgoff(self, address, size, prot, flags, fd, offset): """Wrapper for mmap2""" return self.sys_mmap2(address, size, prot, flags, fd, offset) @@ -2458,7 +2487,7 @@ def _stat(self, path, buf, is64bit): return ret def _arch_specific_init(self): - assert self.arch in {'i386', 'amd64', 'armv7'} + assert self.arch in {'i386', 'amd64', 'armv7', 'aarch64'} if self.arch == 'i386': self._uname_machine = 'i386' @@ -2469,6 +2498,11 @@ def _arch_specific_init(self): self._init_arm_kernel_helpers() self.current._set_mode_by_val(self.current.PC) self.current.PC &= ~1 + elif self.arch == 'aarch64': + # XXX: Possible values: 'aarch64_be', 'aarch64', 'armv8b', 'armv8l'. + # See 'UTS_MACHINE' and 'COMPAT_UTS_MACHINE' in the Linux kernel source. + # https://stackoverflow.com/a/45125525 + self._uname_machine = 'aarch64' # Establish segment registers for x86 architectures if self.arch in {'i386', 'amd64'}: diff --git a/manticore/platforms/linux_syscalls.py b/manticore/platforms/linux_syscalls.py index 7132f537d..32579385b 100644 --- a/manticore/platforms/linux_syscalls.py +++ b/manticore/platforms/linux_syscalls.py @@ -1095,3 +1095,280 @@ 983044: "sys_ARM_NR_usr32", 983045: "sys_ARM_NR_set_tls", } +aarch64 = { + 0: "sys_io_setup", + 1: "sys_io_destroy", + 2: "sys_io_submit", + 3: "sys_io_cancel", + 4: "sys_io_getevents", + 5: "sys_setxattr", + 6: "sys_lsetxattr", + 7: "sys_fsetxattr", + 8: "sys_getxattr", + 9: "sys_lgetxattr", + 10: "sys_fgetxattr", + 11: "sys_listxattr", + 12: "sys_llistxattr", + 13: "sys_flistxattr", + 14: "sys_removexattr", + 15: "sys_lremovexattr", + 16: "sys_fremovexattr", + 17: "sys_getcwd", + 18: "sys_lookup_dcookie", + 19: "sys_eventfd2", + 20: "sys_epoll_create1", + 21: "sys_epoll_ctl", + 22: "sys_epoll_pwait", + 23: "sys_dup", + 24: "sys_dup3", + 25: "sys_fcntl", + 26: "sys_inotify_init1", + 27: "sys_inotify_add_watch", + 28: "sys_inotify_rm_watch", + 29: "sys_ioctl", + 30: "sys_ioprio_set", + 31: "sys_ioprio_get", + 32: "sys_flock", + 33: "sys_mknodat", + 34: "sys_mkdirat", + 35: "sys_unlinkat", + 36: "sys_symlinkat", + 37: "sys_linkat", + 38: "sys_renameat", + 39: "sys_umount", + 40: "sys_mount", + 41: "sys_pivot_root", + 43: "sys_statfs", + 44: "sys_fstatfs", + 45: "sys_truncate", + 46: "sys_ftruncate", + 47: "sys_fallocate", + 48: "sys_faccessat", + 49: "sys_chdir", + 50: "sys_fchdir", + 51: "sys_chroot", + 52: "sys_fchmod", + 53: "sys_fchmodat", + 54: "sys_fchownat", + 55: "sys_fchown", + 56: "sys_openat", + 57: "sys_close", + 58: "sys_vhangup", + 59: "sys_pipe2", + 60: "sys_quotactl", + 61: "sys_getdents64", + 62: "sys_lseek", + 63: "sys_read", + 64: "sys_write", + 65: "sys_readv", + 66: "sys_writev", + 67: "sys_pread64", + 68: "sys_pwrite64", + 69: "sys_preadv", + 70: "sys_pwritev", + 71: "sys_sendfile64", + 72: "sys_pselect6", + 73: "sys_ppoll", + 74: "sys_signalfd4", + 75: "sys_vmsplice", + 76: "sys_splice", + 77: "sys_tee", + 78: "sys_readlinkat", + 79: "sys_newfstatat", + 80: "sys_newfstat", + 81: "sys_sync", + 82: "sys_fsync", + 83: "sys_fdatasync", + 84: "sys_sync_file_range", + 85: "sys_timerfd_create", + 86: "sys_timerfd_settime", + 87: "sys_timerfd_gettime", + 88: "sys_utimensat", + 89: "sys_acct", + 90: "sys_capget", + 91: "sys_capset", + 92: "sys_personality", + 93: "sys_exit", + 94: "sys_exit_group", + 95: "sys_waitid", + 96: "sys_set_tid_address", + 97: "sys_unshare", + 98: "sys_futex", + 99: "sys_set_robust_list", + 100: "sys_get_robust_list", + 101: "sys_nanosleep", + 102: "sys_getitimer", + 103: "sys_setitimer", + 104: "sys_kexec_load", + 105: "sys_init_module", + 106: "sys_delete_module", + 107: "sys_timer_create", + 108: "sys_timer_gettime", + 109: "sys_timer_getoverrun", + 110: "sys_timer_settime", + 111: "sys_timer_delete", + 112: "sys_clock_settime", + 113: "sys_clock_gettime", + 114: "sys_clock_getres", + 115: "sys_clock_nanosleep", + 116: "sys_syslog", + 117: "sys_ptrace", + 118: "sys_sched_setparam", + 119: "sys_sched_setscheduler", + 120: "sys_sched_getscheduler", + 121: "sys_sched_getparam", + 122: "sys_sched_setaffinity", + 123: "sys_sched_getaffinity", + 124: "sys_sched_yield", + 125: "sys_sched_get_priority_max", + 126: "sys_sched_get_priority_min", + 127: "sys_sched_rr_get_interval", + 128: "sys_restart_syscall", + 129: "sys_kill", + 130: "sys_tkill", + 131: "sys_tgkill", + 132: "sys_sigaltstack", + 133: "sys_rt_sigsuspend", + 134: "sys_rt_sigaction", + 135: "sys_rt_sigprocmask", + 136: "sys_rt_sigpending", + 137: "sys_rt_sigtimedwait", + 138: "sys_rt_sigqueueinfo", + 139: "sys_rt_sigreturn", + 140: "sys_setpriority", + 141: "sys_getpriority", + 142: "sys_reboot", + 143: "sys_setregid", + 144: "sys_setgid", + 145: "sys_setreuid", + 146: "sys_setuid", + 147: "sys_setresuid", + 148: "sys_getresuid", + 149: "sys_setresgid", + 150: "sys_getresgid", + 151: "sys_setfsuid", + 152: "sys_setfsgid", + 153: "sys_times", + 154: "sys_setpgid", + 155: "sys_getpgid", + 156: "sys_getsid", + 157: "sys_setsid", + 158: "sys_getgroups", + 159: "sys_setgroups", + 160: "sys_newuname", + 161: "sys_sethostname", + 162: "sys_setdomainname", + 163: "sys_getrlimit", + 164: "sys_setrlimit", + 165: "sys_getrusage", + 166: "sys_umask", + 167: "sys_prctl", + 168: "sys_getcpu", + 169: "sys_gettimeofday", + 170: "sys_settimeofday", + 171: "sys_adjtimex", + 172: "sys_getpid", + 173: "sys_getppid", + 174: "sys_getuid", + 175: "sys_geteuid", + 176: "sys_getgid", + 177: "sys_getegid", + 178: "sys_gettid", + 179: "sys_sysinfo", + 180: "sys_mq_open", + 181: "sys_mq_unlink", + 182: "sys_mq_timedsend", + 183: "sys_mq_timedreceive", + 184: "sys_mq_notify", + 185: "sys_mq_getsetattr", + 186: "sys_msgget", + 187: "sys_msgctl", + 188: "sys_msgrcv", + 189: "sys_msgsnd", + 190: "sys_semget", + 191: "sys_semctl", + 192: "sys_semtimedop", + 193: "sys_semop", + 194: "sys_shmget", + 195: "sys_shmctl", + 196: "sys_shmat", + 197: "sys_shmdt", + 198: "sys_socket", + 199: "sys_socketpair", + 200: "sys_bind", + 201: "sys_listen", + 202: "sys_accept", + 203: "sys_connect", + 204: "sys_getsockname", + 205: "sys_getpeername", + 206: "sys_sendto", + 207: "sys_recvfrom", + 208: "sys_setsockopt", + 209: "sys_getsockopt", + 210: "sys_shutdown", + 211: "sys_sendmsg", + 212: "sys_recvmsg", + 213: "sys_readahead", + 214: "sys_brk", + 215: "sys_munmap", + 216: "sys_mremap", + 217: "sys_add_key", + 218: "sys_request_key", + 219: "sys_keyctl", + 220: "sys_clone", + 221: "sys_execve", + 222: "sys_mmap", + 223: "sys_fadvise64_64", + 224: "sys_swapon", + 225: "sys_swapoff", + 226: "sys_mprotect", + 227: "sys_msync", + 228: "sys_mlock", + 229: "sys_munlock", + 230: "sys_mlockall", + 231: "sys_munlockall", + 232: "sys_mincore", + 233: "sys_madvise", + 234: "sys_remap_file_pages", + 235: "sys_mbind", + 236: "sys_get_mempolicy", + 237: "sys_set_mempolicy", + 238: "sys_migrate_pages", + 239: "sys_move_pages", + 240: "sys_rt_tgsigqueueinfo", + 241: "sys_perf_event_open", + 242: "sys_accept4", + 243: "sys_recvmmsg", + 260: "sys_wait4", + 261: "sys_prlimit64", + 262: "sys_fanotify_init", + 263: "sys_fanotify_mark", + 264: "sys_name_to_handle_at", + 265: "sys_open_by_handle_at", + 266: "sys_clock_adjtime", + 267: "sys_syncfs", + 268: "sys_setns", + 269: "sys_sendmmsg", + 270: "sys_process_vm_readv", + 271: "sys_process_vm_writev", + 272: "sys_kcmp", + 273: "sys_finit_module", + 274: "sys_sched_setattr", + 275: "sys_sched_getattr", + 276: "sys_renameat2", + 277: "sys_seccomp", + 278: "sys_getrandom", + 279: "sys_memfd_create", + 280: "sys_bpf", + 281: "sys_execveat", + 282: "sys_userfaultfd", + 283: "sys_membarrier", + 284: "sys_mlock2", + 285: "sys_copy_file_range", + 286: "sys_preadv2", + 287: "sys_pwritev2", + 288: "sys_pkey_mprotect", + 289: "sys_pkey_alloc", + 290: "sys_pkey_free", + 291: "sys_statx", +} diff --git a/manticore/utils/fallback_emulator.py b/manticore/utils/fallback_emulator.py index ff227471a..9b3a82f04 100644 --- a/manticore/utils/fallback_emulator.py +++ b/manticore/utils/fallback_emulator.py @@ -9,6 +9,7 @@ from unicorn import * from unicorn.x86_const import * from unicorn.arm_const import * +from unicorn.arm64_const import * from capstone import * @@ -42,10 +43,9 @@ def __init__(self, cpu): elif self._cpu.arch == CS_ARCH_ARM64: self._uc_arch = UC_ARCH_ARM64 - self._uc_mode = { - CS_MODE_ARM: UC_MODE_ARM, - CS_MODE_THUMB: UC_MODE_THUMB - }[self._cpu.mode] + self._uc_mode = UC_MODE_ARM + if self._cpu.mode != UC_MODE_ARM: + raise Exception('Aarch64/Arm64 cannot have different uc mode than ARM.') elif self._cpu.arch == CS_ARCH_X86: self._uc_arch = UC_ARCH_X86 @@ -88,11 +88,15 @@ def _create_emulated_mapping(self, uc, address): def get_unicorn_pc(self): if self._cpu.arch == CS_ARCH_ARM: return self._emu.reg_read(UC_ARM_REG_R15) + elif self._cpu.arch == CS_ARCH_ARM64: + return self._emu.reg_read(UC_ARM64_REG_PC) elif self._cpu.arch == CS_ARCH_X86: if self._cpu.mode == CS_MODE_32: return self._emu.reg_read(UC_X86_REG_EIP) elif self._cpu.mode == CS_MODE_64: return self._emu.reg_read(UC_X86_REG_RIP) + else: + raise Exception(f"Getting PC after unicorn emulation for {self._cpu.arch} architecture is not implemented") def _hook_xfer_mem(self, uc, access, address, size, value, data): """ @@ -152,14 +156,16 @@ def _to_unicorn_id(self, reg_name): reg_name = 'CPSR' if self._cpu.arch == CS_ARCH_ARM: return globals()['UC_ARM_REG_' + reg_name] + elif self._cpu.arch == CS_ARCH_ARM64: + return globals()['UC_ARM64_REG_' + reg_name] elif self._cpu.arch == CS_ARCH_X86: # TODO(yan): This needs to handle AF register return globals()['UC_X86_REG_' + reg_name] else: # TODO(yan): raise a more appropriate exception - raise TypeError + raise TypeError(f"Cannot convert {reg_name} to unicorn register id") - def emulate(self, instruction): + def emulate(self, instruction, reset=True): """ Emulate a single instruction. """ @@ -167,14 +173,21 @@ def emulate(self, instruction): # The emulation might restart if Unicorn needs to bring in a memory map # or bring a value from Manticore state. while True: - - self.reset() - - # Establish Manticore state, potentially from past emulation - # attempts - for base in self._should_be_mapped: - size, perms = self._should_be_mapped[base] - self._emu.mem_map(base, size, perms) + # XXX: Unicorn doesn't allow to write to and read from system + # registers directly (see 'arm64_reg_write' and 'arm64_reg_read'). + # The only way to propagate this information is via the MSR + # (register) and MRS instructions, without resetting the emulator + # state in between. + # Note: in HEAD, this is fixed for some system registers, so revise + # this after upgrading from 1.0.1. + if reset: + self.reset() + + # Establish Manticore state, potentially from past emulation + # attempts + for base in self._should_be_mapped: + size, perms = self._should_be_mapped[base] + self._emu.mem_map(base, size, perms) for address, values in self._should_be_written.items(): for offset, byte in enumerate(values, start=address): @@ -250,7 +263,13 @@ def _step(self, instruction): pc = self._cpu.PC if self._cpu.arch == CS_ARCH_ARM and self._uc_mode == UC_MODE_THUMB: pc |= 1 - self._emu.emu_start(pc, self._cpu.PC + instruction.size, count=1) + # XXX: 'timeout' is needed to avoid hanging: + # https://github.com/unicorn-engine/unicorn/issues/1061 + # Unfortunately, this may lead to race conditions if the value is + # too small. Tests that work fine without 'timeout' start to fail + # because registers are not being written to. + self._emu.emu_start(pc, self._cpu.PC + instruction.size, + count=1, timeout=1000000) # microseconds except UcError as e: # We request re-execution by signaling error; if we we didn't set # _should_try_again, it was likely an actual error @@ -280,6 +299,9 @@ def _step(self, instruction): if saved_PC == mu_pc: self._cpu.PC = saved_PC + instruction.size + else: + self._cpu.PC = mu_pc + # Raise the exception from a hook that Unicorn would have eaten if self._to_raise: raise self._to_raise diff --git a/scripts/extract_syscalls.py b/scripts/extract_syscalls.py index 73d4c1da6..fa075cb60 100755 --- a/scripts/extract_syscalls.py +++ b/scripts/extract_syscalls.py @@ -13,19 +13,35 @@ """ import argparse +import os +import re +import subprocess import sys +import tempfile from urllib.request import urlopen -base_url = 'https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/plain/{}?id=refs/tags/v{}' +BASE_URL = 'https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/plain/{}?id=refs/tags/v{}' -arch_tables = { - 'armv7': 'arch/arm/tools/syscall.tbl', - 'i386': 'arch/x86/entry/syscalls/syscall_32.tbl', - 'amd64': 'arch/x86/entry/syscalls/syscall_64.tbl', -} +# Use an associative list rather than a dict to get deterministic output. +ARCH_TABLES = [ + ('i386', 'arch/x86/entry/syscalls/syscall_32.tbl'), + ('amd64', 'arch/x86/entry/syscalls/syscall_64.tbl'), + ('armv7', 'arch/arm/tools/syscall.tbl'), +] + +BITSPERLONG_HDR = 'arch/{}/include/uapi/asm/bitsperlong.h' +ARCH_UNISTD_HDR = 'arch/{}/include/uapi/asm/unistd.h' +UNISTD_HDR = 'include/uapi/asm-generic/unistd.h' + +# Format: Manticore arch, Linux arch. +# XXX: Code that uses this might need to be tweaked for other architectures to +# work properly. +UNISTD = [ + ('aarch64', 'arm64') +] __ARM_NR_BASE = 0xf0000 -additional_syscalls = { +ADDITIONAL_SYSCALLS = { 'armv7': [ ('sys_ARM_NR_breakpoint', __ARM_NR_BASE + 1), ('sys_ARM_NR_cacheflush', __ARM_NR_BASE + 2), @@ -36,7 +52,24 @@ } -if __name__=='__main__': +def open_url(url): + res = urlopen(url) + if res.code // 100 != 2: + sys.stderr.write("Failed retrieving file; check version and connection.\n") + sys.stderr.write(f"Url: {url}\n") + sys.exit(1) + return res + + +def write_without_includes(f, res): + for line in res.readlines(): + line = line.decode() + line = line.strip() + if not line.startswith("#include"): + f.write(line + '\n') + + +if __name__ == '__main__': parser = argparse.ArgumentParser(description='Generate syscall tables') parser.add_argument('output', help='Python output to generate tables') parser.add_argument('--linux_version', help='Major version of the Linux kernel to use', default='4.11') @@ -46,17 +79,13 @@ output.write('#\n#\n# AUTOGENERATED, DO NOT EDIT\n#\n') output.write(f'# From version: {args.linux_version}\n#\n\n') - for arch, path in arch_tables.items(): - url = base_url.format(path, args.linux_version) - tbl = urlopen(url) - - if tbl.code // 100 != 2: - sys.stderr.write("Failed retrieving table; check version and connection.\n") - sys.stderr.write(f"Url: {url}\n") - sys.exit(1) + for arch, path in ARCH_TABLES: + url = BASE_URL.format(path, args.linux_version) + res = open_url(url) output.write(f'{arch} = {{\n') - for line in tbl.readlines(): + + for line in res.readlines(): line = line.decode() line = line.strip() if line.startswith('#'): @@ -66,8 +95,56 @@ continue num, abi, name, entry = columns[:4] output.write(f' {num}: "{entry}",\n') - for entry, num in additional_syscalls.get(arch, {}): + + for entry, num in ADDITIONAL_SYSCALLS.get(arch, {}): output.write(f' {num}: "{entry}",\n') + output.write('}\n') + for march, larch in UNISTD: + bitsperlong_hdr = BITSPERLONG_HDR.format(larch) + arch_unistd_hdr = ARCH_UNISTD_HDR.format(larch) + + bitsperlong_url = BASE_URL.format(bitsperlong_hdr, args.linux_version) + arch_unistd_url = BASE_URL.format(arch_unistd_hdr, args.linux_version) + unistd_url = BASE_URL.format(UNISTD_HDR, args.linux_version) + + bitsperlong_res = open_url(bitsperlong_url) + arch_unistd_res = open_url(arch_unistd_url) + unistd_res = open_url(unistd_url) + + syscall_rx = 'SYSCALL: (\d+) ([a-z_0-9]+)' + syscall_define = '#define __SYSCALL(nr, sym) SYSCALL: nr sym' + + output.write(f'{march} = {{\n') + + fd, tmp_path = tempfile.mkstemp() + try: + with os.fdopen(fd, 'w') as tmp: + # The order is important here for CPP to work correctly. + tmp.write(syscall_define + '\n') + write_without_includes(tmp, bitsperlong_res) + write_without_includes(tmp, arch_unistd_res) + write_without_includes(tmp, unistd_res) + + process = subprocess.Popen( + ['cpp', '-E', tmp_path], + stdout=subprocess.PIPE, + encoding='utf-8' + ) + out, _ = process.communicate() + lines = out.split('\n') + for line in lines: + m = re.search(syscall_rx, line) + if m: + num = m.group(1) + entry = m.group(2) + if entry != 'sys_ni_syscall': # not implemented syscall + output.write(f' {num}: "{entry}",\n') + finally: + os.remove(tmp_path) + + for entry, num in ADDITIONAL_SYSCALLS.get(march, {}): + output.write(f' {num}: "{entry}",\n') + output.write('}\n') diff --git a/tests/native/test_aarch64cpu.py b/tests/native/test_aarch64cpu.py new file mode 100644 index 000000000..d46222b35 --- /dev/null +++ b/tests/native/test_aarch64cpu.py @@ -0,0 +1,304 @@ +import unittest + +from capstone import CS_MODE_ARM +from functools import wraps +from keystone import Ks, KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN +from nose.tools import nottest + +from manticore.core.smtlib import ConstraintSet, solver +from manticore.native.memory import SMemory64, Memory64 +from manticore.native.cpu.aarch64 import Aarch64Cpu as Cpu +from manticore.native.cpu.abstractcpu import ( + Interruption, InstructionNotImplementedError, ConcretizeRegister +) +from manticore.native.cpu.bitwise import LSL, Mask +from manticore.utils.fallback_emulator import UnicornEmulator +from tests.native.test_aarch64rf import MAGIC_64, MAGIC_32 + +ks = Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN) + + +def assemble(asm): + ords = ks.asm(asm)[0] + if not ords: + raise Exception(f"Bad assembly: '{asm}'") + return ''.join(map(chr, ords)) + + +# XXX: These functions are taken from 'test_armv7cpu' and modified to be more +# generic, to support running under Unicorn and Manticore from the same test +# definitions. It would be nice to do the same for Armv7 code as well. + + +def itest_setregs(*preds): + def instr_dec(custom_func): + @wraps(custom_func) + def wrapper(self): + for p in preds: + dest, src = p.split('=') + + try: + src = int(src, 0) + except ValueError: + self.fail() + + self._setreg(dest, src) + + custom_func(self) + + return wrapper + + return instr_dec + + +def skip_sym(msg): + def instr_dec(assertions_func): + @wraps(assertions_func) + def wrapper(self): + if self.__class__.__name__ == 'Aarch64SymInstructions': + self.skipTest(msg) + + return wrapper + + return instr_dec + + +def itest(asm): + def instr_dec(assertions_func): + @wraps(assertions_func) + def wrapper(self): + self._setupCpu(asm) + self._execute() + assertions_func(self) + + return wrapper + + return instr_dec + + +def itest_custom(asms, multiple_insts=False): + def instr_dec(custom_func): + @wraps(custom_func) + def wrapper(self): + self._setupCpu( + asms, + mode=CS_MODE_ARM, + multiple_insts=multiple_insts + ) + custom_func(self) + + return wrapper + + return instr_dec + + +def itest_multiple(asms, count=None): + def instr_dec(assertions_func): + @wraps(assertions_func) + def wrapper(self): + self._setupCpu(asms, mode=CS_MODE_ARM, multiple_insts=True) + for i in range(count if count else len(asms)): + self._execute() + assertions_func(self) + + return wrapper + + return instr_dec + + +NZCV_COND_MAP = { + # name: (true, false) + 'eq': (0x40000000, 0xb0000000), + 'ne': (0xb0000000, 0x40000000), + 'cs': (0x20000000, 0xd0000000), + 'hs': (0x20000000, 0xd0000000), + 'cc': (0xd0000000, 0x20000000), + 'lo': (0xd0000000, 0x20000000), + 'mi': (0x80000000, 0x70000000), + 'pl': (0x70000000, 0x80000000), + 'vs': (0x10000000, 0xe0000000), + 'vc': (0xe0000000, 0x10000000), + 'hi': (0x20000000, 0x40000000), + 'ls': (0x40000000, 0x20000000), + 'ge': (0xd0000000, 0xc0000000), + 'lt': (0xc0000000, 0xd0000000), + 'gt': (0x90000000, 0xd0000000), + 'le': (0xd0000000, 0x90000000), + 'al': (0xf0000000, None), + 'nv': (0, None) +} + + +# XXX: Armv7 also has these methods: stack_pop, stack_push, stack_peek. +class Aarch64CpuTest(unittest.TestCase): + # XXX: Adapted from the Armv7 test code. + _multiprocess_can_split_ = True + + def setUp(self): + cs = ConstraintSet() + self.cpu = Cpu(SMemory64(cs)) + self.rf = self.cpu.regfile + self._setupStack() + + def _setupStack(self): + self.stack = self.cpu.memory.mmap(0xf000, 0x1000, 'rw') + self.rf.write('SP', self.stack + 0x1000) + + def test_read_init(self): + self.assertEqual(self.rf.read('X0'), 0) + + def test_read_stack(self): + self.cpu.STACK = 0x1337 + self.assertEqual(self.rf.read('SP'), 0x1337) + + def test_read_stack2(self): + self.cpu.STACK = 0x1337 - 1 + self.assertEqual(self.rf.read('SP'), 0x1336) + + def test_read_stack3(self): + self.cpu.STACK = 0x1337 + 1 + self.assertEqual(self.rf.read('SP'), 0x1338) + + def test_read_stack4(self): + self.cpu.STACK = 0x1337 + self.assertEqual(self.cpu.STACK, 0x1337) + + def test_write_read_int(self): + self.cpu.STACK -= 8 + self.cpu.write_int(self.cpu.STACK, MAGIC_64, 64) + self.assertEqual(self.cpu.read_int(self.cpu.STACK), MAGIC_64) + + +class Aarch64Instructions: + _multiprocess_can_split_ = True + + def _setupCpu(self, asm, mode=CS_MODE_ARM, multiple_insts=False): + if mode != CS_MODE_ARM: + raise Exception(f"Unsupported mode: '{mode}'") + + # XXX: Adapted from the Armv7 test code. + self.code = self.mem.mmap(0x1000, 0x1000, 'rwx') + self.data = self.mem.mmap(0xd000, 0x1000, 'rw') + # Some tests rely on SP being in a particular range, so make sure that + # all tests work before changing this. + self.stack = self.mem.mmap(0x7ffffffffffff000, 0x1000, 'rw') + + start = self.code + if multiple_insts: + offset = 0 + for asm_single in asm: + asm_inst = assemble(asm_single) + self.mem.write(start + offset, asm_inst) + offset += len(asm_inst) + else: + self.mem.write(start, assemble(asm)) + + self.rf.write('PC', start) + self.rf.write('SP', self.stack + 0x1000 - 8) + self.cpu.mode = mode + + def _setreg(self, reg, val): + reg = reg.upper() + + if self.mem.__class__.__name__ == 'Memory64': + self.rf.write(reg, val) + elif self.mem.__class__.__name__ == 'SMemory64': + size = self.rf.size(reg) + self.rf.write(reg, self.cs.new_bitvec(size, name=reg)) + self.cs.add(self.rf.read(reg) == val) + else: + self.fail() + + +class Aarch64CpuInstructions(unittest.TestCase, Aarch64Instructions): + def setUp(self): + # XXX: Adapted from the Armv7 test code. + self.cpu = Cpu(Memory64()) + self.mem = self.cpu.memory + self.rf = self.cpu.regfile + + def _execute(self, check_pc=True, **kwargs): + pc = self.cpu.PC + self.cpu.execute() + if check_pc: + self.assertEqual(self.cpu.PC, pc + 4) + + +class Aarch64UnicornInstructions(unittest.TestCase, Aarch64Instructions): + def setUp(self): + # XXX: Adapted from the Armv7 test code. + self.cpu = Cpu(Memory64()) + self.mem = self.cpu.memory + self.rf = self.cpu.regfile + + def _setupCpu(self, asm, mode=CS_MODE_ARM, multiple_insts=False): + super()._setupCpu(asm, mode, multiple_insts) + self.backup_emu = UnicornEmulator(self.cpu) + # Make sure that 'self._emu' is set. + self.backup_emu.reset() + # XXX: Map the data region as well? + # Map the stack. + self.backup_emu._create_emulated_mapping(self.backup_emu._emu, self.cpu.STACK) + + # XXX: Unicorn doesn't allow to write to and read from system + # registers directly (see 'arm64_reg_write' and 'arm64_reg_read'). + # The only way to propagate this information is via the MSR + # (register) and MRS instructions, without resetting the emulator + # state in between. + # Note: in HEAD, this is fixed for some system registers, so revise + # this after upgrading from 1.0.1. + def _execute(self, check_pc=True, reset=True, **kwargs): + pc = self.cpu.PC + insn = self.cpu.decode_instruction(pc) + self.backup_emu.emulate(insn, reset=reset) + if check_pc: + self.assertEqual(self.cpu.PC, pc + 4) + + +class Aarch64SymInstructions(unittest.TestCase, Aarch64Instructions): + def setUp(self): + # XXX: Adapted from the Armv7 test code. + self.cs = ConstraintSet() + self.cpu = Cpu(SMemory64(self.cs)) + self.mem = self.cpu.memory + self.rf = self.cpu.regfile + + def _get_all_values1(self, expr): + values = solver.get_all_values(self.cs, expr) + self.assertEqual(len(values), 1) + return values[0] + + def _execute(self, check_pc=True, check_cs=True, **kwargs): + # Make sure there are some constraints. Otherwise, it would be the same + # as testing concrete values. + if check_cs: + self.assertTrue(len(self.cs) > 0) + + pc = self.cpu.PC + + # XXX: Copied from 'test_x86.py'. + done = False + while not done: + try: + self.cpu.execute() + done = True + except ConcretizeRegister as e: + expr = getattr(self.cpu, e.reg_name) + value = self._get_all_values1(expr) + setattr(self.cpu, e.reg_name, value) + + if check_pc: + self.assertEqual(self.cpu.PC, pc + 4) + + def assertEqual(self, actual, expected, *args, **kwargs): + if isinstance(actual, int): + pass + else: + actual = self._get_all_values1(actual) + + if isinstance(expected, int): + pass + else: + expected = self._get_all_values1(expected) + + super().assertEqual(actual, expected, *args, **kwargs) diff --git a/tests/native/test_aarch64rf.py b/tests/native/test_aarch64rf.py new file mode 100644 index 000000000..65024a154 --- /dev/null +++ b/tests/native/test_aarch64rf.py @@ -0,0 +1,259 @@ +import unittest + +from manticore.native.cpu.aarch64 import Aarch64RegisterFile as RF + +MAX_128 = 2 ** 128 - 1 +MAX_64 = 2 ** 64 - 1 +MAX_32 = 2 ** 32 - 1 +MAX_16 = 2 ** 16 - 1 +MAX_8 = 2 ** 8 - 1 + +MAGIC_128 = 0x41424344454647484950515253545556 +MAGIC_64 = MAGIC_128 & MAX_64 +MAGIC_32 = MAGIC_128 & MAX_32 +MAGIC_16 = MAGIC_128 & MAX_16 +MAGIC_8 = MAGIC_128 & MAX_8 + + +# XXX: Test vectors. +class Aarch64RFTest(unittest.TestCase): + _multiprocess_can_split_ = True + + def setUp(self): + self.r = RF() + + def test_init_state(self): + for i in range(31): + self.assertEqual(self.r.read(f'X{i}'), 0) + self.assertEqual(self.r.read(f'W{i}'), 0) + + self.assertEqual(self.r.read('SP'), 0) + self.assertEqual(self.r.read('WSP'), 0) + + self.assertEqual(self.r.read('PC'), 0) + + for i in range(32): + self.assertEqual(self.r.read(f'Q{i}'), 0) + self.assertEqual(self.r.read(f'D{i}'), 0) + self.assertEqual(self.r.read(f'S{i}'), 0) + self.assertEqual(self.r.read(f'H{i}'), 0) + self.assertEqual(self.r.read(f'B{i}'), 0) + + self.assertEqual(self.r.read('FPCR'), 0) + self.assertEqual(self.r.read('FPSR'), 0) + + self.assertEqual(self.r.read('NZCV'), 0) + + self.assertEqual(self.r.read('XZR'), 0) + self.assertEqual(self.r.read('WZR'), 0) + + # Aliases. + self.assertEqual(self.r.read('STACK'), 0) + self.assertEqual(self.r.read('FP'), 0) + self.assertEqual(self.r.read('IP1'), 0) + self.assertEqual(self.r.read('IP0'), 0) + self.assertEqual(self.r.read('LR'), 0) + + def test_register_independence(self): + for i in range(31): + self.r.write(f'X{i}', i) + + self.r.write('SP', 31) + self.r.write('PC', 32) + + for i in range(33, 65): + self.r.write(f'Q{i - 33}', i) + + self.r.write('FPCR', 65) + self.r.write('FPSR', 66) + self.r.write('NZCV', 67) + self.r.write('XZR', 68) + + for i in range(31): + self.assertEqual(self.r.read(f'X{i}'), i) + self.assertEqual(self.r.read(f'W{i}'), i) + + self.assertEqual(self.r.read('IP0'), 16) + self.assertEqual(self.r.read('IP1'), 17) + self.assertEqual(self.r.read('FP'), 29) + self.assertEqual(self.r.read('LR'), 30) + + self.assertEqual(self.r.read('STACK'), 31) + self.assertEqual(self.r.read('SP'), 31) + self.assertEqual(self.r.read('WSP'), 31) + + self.assertEqual(self.r.read('PC'), 32) + + for i in range(33, 65): + self.assertEqual(self.r.read(f'Q{i - 33}'), i) + self.assertEqual(self.r.read(f'D{i - 33}'), i) + self.assertEqual(self.r.read(f'S{i - 33}'), i) + self.assertEqual(self.r.read(f'H{i - 33}'), i) + self.assertEqual(self.r.read(f'B{i - 33}'), i) + + self.assertEqual(self.r.read('FPCR'), 65) + self.assertEqual(self.r.read('FPSR'), 66) + self.assertEqual(self.r.read('NZCV'), 67) + self.assertEqual(self.r.read('XZR'), 0) + self.assertEqual(self.r.read('WZR'), 0) + + def test_write_read_same(self): + self.r.write('PC', MAGIC_64) + self.assertEqual(self.r.read('PC'), MAGIC_64) + + self.r.write('FPCR', MAGIC_64) + self.assertEqual(self.r.read('FPCR'), MAGIC_64) + + self.r.write('FPSR', MAGIC_64) + self.assertEqual(self.r.read('FPSR'), MAGIC_64) + + self.r.write('NZCV', MAGIC_64) + self.assertEqual(self.r.read('NZCV'), MAGIC_64) + + def test_write_read_large_small(self): + for i in range(31): + self.r.write(f'X{i}', MAGIC_64) + self.assertEqual(self.r.read(f'X{i}'), MAGIC_64) + self.assertEqual(self.r.read(f'W{i}'), MAGIC_32) + + self.assertEqual(self.r.read('IP0'), MAGIC_64) + self.assertEqual(self.r.read('IP1'), MAGIC_64) + self.assertEqual(self.r.read('FP'), MAGIC_64) + self.assertEqual(self.r.read('LR'), MAGIC_64) + + self.r.write('SP', MAGIC_64) + self.assertEqual(self.r.read('STACK'), MAGIC_64) + self.assertEqual(self.r.read('SP'), MAGIC_64) + self.assertEqual(self.r.read('WSP'), MAGIC_32) + + for i in range(32): + self.r.write(f'Q{i}', MAGIC_128) + self.assertEqual(self.r.read(f'Q{i}'), MAGIC_128) + self.assertEqual(self.r.read(f'D{i}'), MAGIC_64) + self.assertEqual(self.r.read(f'S{i}'), MAGIC_32) + self.assertEqual(self.r.read(f'H{i}'), MAGIC_16) + self.assertEqual(self.r.read(f'B{i}'), MAGIC_8) + + self.r.write('XZR', MAGIC_64) + self.assertEqual(self.r.read('XZR'), 0) + self.assertEqual(self.r.read('WZR'), 0) + + def test_write_read_small_large(self): + for i in range(31): + self.r.write(f'W{i}', MAGIC_32) + self.assertEqual(self.r.read(f'X{i}'), MAGIC_32) + self.assertEqual(self.r.read(f'W{i}'), MAGIC_32) + + self.assertEqual(self.r.read('IP0'), MAGIC_32) + self.assertEqual(self.r.read('IP1'), MAGIC_32) + self.assertEqual(self.r.read('FP'), MAGIC_32) + self.assertEqual(self.r.read('LR'), MAGIC_32) + + self.r.write('WSP', MAGIC_32) + self.assertEqual(self.r.read('STACK'), MAGIC_32) + self.assertEqual(self.r.read('SP'), MAGIC_32) + self.assertEqual(self.r.read('WSP'), MAGIC_32) + + for i in range(32): + self.r.write(f'B{i}', MAGIC_8) + self.assertEqual(self.r.read(f'Q{i}'), MAGIC_8) + + self.r.write(f'H{i}', MAGIC_16) + self.assertEqual(self.r.read(f'Q{i}'), MAGIC_16) + + self.r.write(f'S{i}', MAGIC_32) + self.assertEqual(self.r.read(f'Q{i}'), MAGIC_32) + + self.r.write(f'D{i}', MAGIC_64) + self.assertEqual(self.r.read(f'Q{i}'), MAGIC_64) + + self.r.write(f'Q{i}', MAGIC_128) + self.assertEqual(self.r.read(f'Q{i}'), MAGIC_128) + + self.r.write('WZR', MAGIC_32) + self.assertEqual(self.r.read('XZR'), 0) + self.assertEqual(self.r.read('WZR'), 0) + + def test_invalid_write_size(self): + with self.assertRaises(AssertionError): + self.r.write('PC', MAX_64 + 1) + + with self.assertRaises(AssertionError): + self.r.write('FPCR', MAX_64 + 1) + + with self.assertRaises(AssertionError): + self.r.write('FPSR', MAX_64 + 1) + + with self.assertRaises(AssertionError): + self.r.write('NZCV', MAX_64 + 1) + + for i in range(31): + with self.assertRaises(AssertionError): + self.r.write(f'X{i}', MAX_64 + 1) + with self.assertRaises(AssertionError): + self.r.write(f'W{i}', MAX_32 + 1) + + with self.assertRaises(AssertionError): + self.r.write('SP', MAX_64 + 1) + with self.assertRaises(AssertionError): + self.r.write('WSP', MAX_32 + 1) + + for i in range(32): + with self.assertRaises(AssertionError): + self.r.write(f'B{i}', MAX_8 + 1) + with self.assertRaises(AssertionError): + self.r.write(f'H{i}', MAX_16 + 1) + with self.assertRaises(AssertionError): + self.r.write(f'S{i}', MAX_32 + 1) + with self.assertRaises(AssertionError): + self.r.write(f'D{i}', MAX_64 + 1) + with self.assertRaises(AssertionError): + self.r.write(f'Q{i}', MAX_128 + 1) + + with self.assertRaises(AssertionError): + self.r.write('XZR', MAX_64 + 1) + with self.assertRaises(AssertionError): + self.r.write('WZR', MAX_32 + 1) + + # Aliases. + with self.assertRaises(AssertionError): + self.r.write('STACK', MAX_64 + 1) + with self.assertRaises(AssertionError): + self.r.write('FP', MAX_64 + 1) + with self.assertRaises(AssertionError): + self.r.write('IP1', MAX_64 + 1) + with self.assertRaises(AssertionError): + self.r.write('IP0', MAX_64 + 1) + with self.assertRaises(AssertionError): + self.r.write('LR', MAX_64 + 1) + + def test_invalid_write_name(self): + with self.assertRaises(AssertionError): + self.r.write('INVALID', 42) + + def test_invalid_read_name(self): + with self.assertRaises(AssertionError): + self.r.read('INVALID') + + # Write to aliases first. + def test_write_read_aliases(self): + self.r.write('STACK', MAGIC_64) + self.assertEqual(self.r.read('STACK'), MAGIC_64) + self.assertEqual(self.r.read('SP'), MAGIC_64) + self.assertEqual(self.r.read('WSP'), MAGIC_32) + + self.r.write('FP', MAGIC_64) + self.assertEqual(self.r.read('FP'), MAGIC_64) + self.assertEqual(self.r.read('X29'), MAGIC_64) + + self.r.write('IP1', MAGIC_64) + self.assertEqual(self.r.read('IP1'), MAGIC_64) + self.assertEqual(self.r.read('X17'), MAGIC_64) + + self.r.write('IP0', MAGIC_64) + self.assertEqual(self.r.read('IP0'), MAGIC_64) + self.assertEqual(self.r.read('X16'), MAGIC_64) + + self.r.write('LR', MAGIC_64) + self.assertEqual(self.r.read('LR'), MAGIC_64) + self.assertEqual(self.r.read('X30'), MAGIC_64) diff --git a/tests/native/test_abi.py b/tests/native/test_abi.py index 3331b6a2d..255b9f670 100644 --- a/tests/native/test_abi.py +++ b/tests/native/test_abi.py @@ -5,6 +5,7 @@ from manticore.native.cpu.abstractcpu import ConcretizeArgument, ConcretizeRegister, ConcretizeMemory from manticore.native.cpu.arm import Armv7Cpu, Armv7LinuxSyscallAbi, Armv7CdeclAbi +from manticore.native.cpu.aarch64 import Aarch64Cpu, Aarch64LinuxSyscallAbi, Aarch64CdeclAbi from manticore.native.cpu.x86 import I386Cpu, AMD64Cpu, I386LinuxSyscallAbi, I386StdcallAbi, I386CdeclAbi, AMD64LinuxSyscallAbi, SystemVAbi from manticore.native.memory import SMemory32, SMemory64 from manticore.core.smtlib import ConstraintSet, Operators @@ -13,12 +14,18 @@ class ABITest(unittest.TestCase): _multiprocess_can_split_ = True + def setUp(self): mem32 = SMemory32(ConstraintSet()) mem32.mmap(0x1000, 0x1000, 'rw ') mem64 = SMemory64(ConstraintSet()) mem64.mmap(0x1000, 0x1000, 'rw ') + self._cpu_aarch64 = Aarch64Cpu(mem64) + self._cpu_aarch64.SP = 0x1080 + self._cpu_aarch64.func_abi = Aarch64CdeclAbi(self._cpu_aarch64) + self._cpu_aarch64.syscall_abi = Aarch64LinuxSyscallAbi(self._cpu_aarch64) + self._cpu_arm = Armv7Cpu(mem32) self._cpu_arm.SP = 0x1080 self._cpu_arm.func_abi = Armv7CdeclAbi(self._cpu_arm) @@ -37,13 +44,71 @@ def setUp(self): def write(mem, where, val, size): mem[where:where + size // 8] = [Operators.CHR(Operators.EXTRACT(val, offset, 8)) for offset in range(0, size, 8)] for val in range(0, 0x100, 4): - write(mem32, 0x1000+val, val, 32) + write(mem32, 0x1000 + val, val, 32) for val in range(0, 0x100, 8): - write(mem64, 0x1000+val, val, 64) + write(mem64, 0x1000 + val, val, 64) def test_executor(self): pass + def test_aarch64_abi(self): + cpu = self._cpu_aarch64 + + for i in range(8): + cpu.write_register(f'X{i}', i) + + cpu.LR = 0x1234 + + self.assertEqual(cpu.read_int(cpu.SP), 0x80) + + # First 8 arguments in registers, then on stack. + def test(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10): + self.assertEqual(a0, 0) + self.assertEqual(a1, 1) + self.assertEqual(a2, 2) + self.assertEqual(a3, 3) + self.assertEqual(a4, 4) + self.assertEqual(a5, 5) + self.assertEqual(a6, 6) + self.assertEqual(a7, 7) + self.assertEqual(a8, 0x80) + self.assertEqual(a9, 0x88) + self.assertEqual(a10, 0x90) + + self.assertEqual(cpu.SP, 0x1080) + return 42 + + cpu.func_abi.invoke(test) + + # Result is correctly captured. + self.assertEqual(cpu.X0, 42) + # SP is unchanged. + self.assertEqual(cpu.SP, 0x1080) + # Returned correctly. + self.assertEqual(cpu.PC, cpu.LR) + + def test_aarch64_syscall(self): + cpu = self._cpu_aarch64 + + cpu.X8 = 6 + for i in range(6): + cpu.write_register(f'X{i}', i) + + def test(a0, a1, a2, a3, a4, a5): + self.assertEqual(a0, 0) + self.assertEqual(a1, 1) + self.assertEqual(a2, 2) + self.assertEqual(a3, 3) + self.assertEqual(a4, 4) + self.assertEqual(a5, 5) + return 42 + + self.assertEqual(cpu.syscall_abi.syscall_number(), 6) + + cpu.syscall_abi.invoke(test) + + self.assertEqual(cpu.X0, 42) + def test_arm_abi_simple(self): cpu = self._cpu_arm @@ -52,11 +117,11 @@ def test_arm_abi_simple(self): cpu.LR = 0x1234 - def test(one, two, three, four): - self.assertEqual(one, 0) - self.assertEqual(two, 1) - self.assertEqual(three, 2) - self.assertEqual(four, 3) + def test(a0, a1, a2, a3): + self.assertEqual(a0, 0) + self.assertEqual(a1, 1) + self.assertEqual(a2, 2) + self.assertEqual(a3, 3) return 34 cpu.func_abi.invoke(test) @@ -78,16 +143,16 @@ def test_arm_abi(self): self.assertEqual(cpu.read_int(cpu.SP), 0x80) - def test(one, two, three, four, five, six, seven): - self.assertEqual(one, 0) - self.assertEqual(two, 1) - self.assertEqual(three, 2) - self.assertEqual(four, 3) - self.assertEqual(five, 0x80) - self.assertEqual(six, 0x84) - self.assertEqual(seven, 0x88) + def test(a0, a1, a2, a3, a4, a5, a6): + self.assertEqual(a0, 0) + self.assertEqual(a1, 1) + self.assertEqual(a2, 2) + self.assertEqual(a3, 3) + self.assertEqual(a4, 0x80) + self.assertEqual(a5, 0x84) + self.assertEqual(a6, 0x88) - self.assertEqual(cpu.SP, 0x1080) + self.assertEqual(cpu.SP, 0x1080) return 34 cpu.func_abi.invoke(test) @@ -108,7 +173,7 @@ def test_arm_abi_concretize_register(self): previous_r0 = cpu.R0 self.assertEqual(cpu.read_int(cpu.SP), 0x80) - def test(one, two, three, four, five, six): + def test(a0, a1, a2, a3, a4, a5): raise ConcretizeArgument(cpu, 0) with self.assertRaises(ConcretizeRegister) as cr: @@ -127,7 +192,7 @@ def test_arm_abi_concretize_memory(self): previous_r0 = cpu.R0 self.assertEqual(cpu.read_int(cpu.SP), 0x80) - def test(one, two, three, four, five): + def test(a0, a1, a2, a3, a4): raise ConcretizeArgument(cpu, 4) with self.assertRaises(ConcretizeMemory) as cr: @@ -145,12 +210,12 @@ def test_i386_cdecl(self): self.assertEqual(cpu.read_int(cpu.ESP), 0x80) cpu.push(0x1234, cpu.address_bit_size) - def test(one, two, three, four, five): - self.assertEqual(one, 0x80) - self.assertEqual(two, 0x84) - self.assertEqual(three, 0x88) - self.assertEqual(four, 0x8c) - self.assertEqual(five, 0x90) + def test(a0, a1, a2, a3, a4): + self.assertEqual(a0, 0x80) + self.assertEqual(a1, 0x84) + self.assertEqual(a2, 0x88) + self.assertEqual(a3, 0x8c) + self.assertEqual(a4, 0x90) return 3 cpu.func_abi.invoke(test) @@ -169,12 +234,12 @@ def test_i386_stdcall(self): cpu.push(0x1234, cpu.address_bit_size) - def test(one, two, three, four, five): - self.assertEqual(one, 0x80) - self.assertEqual(two, 0x84) - self.assertEqual(three, 0x88) - self.assertEqual(four, 0x8c) - self.assertEqual(five, 0x90) + def test(a0, a1, a2, a3, a4): + self.assertEqual(a0, 0x80) + self.assertEqual(a1, 0x84) + self.assertEqual(a2, 0x88) + self.assertEqual(a3, 0x8c) + self.assertEqual(a4, 0x90) return 3 abi = I386StdcallAbi(cpu) @@ -195,7 +260,8 @@ def test_i386_stdcall_concretize(self): eip = 0xDEADBEEF base = cpu.ESP cpu.EIP = eip - def test(one, two, three, four, five): + + def test(a0, a1, a2, a3, a4): raise ConcretizeArgument(cpu, 2) abi = I386StdcallAbi(cpu) @@ -217,8 +283,8 @@ def test_i386_cdecl_concretize(self): self.assertEqual(cpu.read_int(cpu.ESP), 0x80) cpu.push(0x1234, cpu.address_bit_size) - def test(one, two, three, four, five): - raise ConcretizeArgument(cpu, 0) # 0x1068 + def test(a0, a1, a2, a3, a4): + raise ConcretizeArgument(cpu, 0) # 0x1068 return 3 with self.assertRaises(ConcretizeMemory) as cr: @@ -229,10 +295,9 @@ def test(one, two, three, four, five): # Make sure eax is unchanged self.assertEqual(cpu.EAX, prev_eax) # Make sure EIP wasn't popped - self.assertEqual(base, cpu.ESP+4) + self.assertEqual(base, cpu.ESP + 4) self.assertNotEqual(cpu.EIP, 0x1234) - def test_i386_vararg(self): cpu = self._cpu_x86 @@ -251,7 +316,6 @@ def test(params): cpu.func_abi.invoke(test) self.assertEqual(cpu.EIP, 0x1234) - def test_amd64_basic_funcall(self): cpu = self._cpu_x64 @@ -264,13 +328,13 @@ def test_amd64_basic_funcall(self): cpu.push(0x1234, cpu.address_bit_size) - def test(one, two, three, four, five, six): - self.assertEqual(one, 1) - self.assertEqual(two, 2) - self.assertEqual(three, 3) - self.assertEqual(four, 4) - self.assertEqual(five, 5) - self.assertEqual(six, 6) + def test(a0, a1, a2, a3, a4, a5): + self.assertEqual(a0, 1) + self.assertEqual(a1, 2) + self.assertEqual(a2, 3) + self.assertEqual(a3, 4) + self.assertEqual(a4, 5) + self.assertEqual(a5, 6) cpu.func_abi.invoke(test) @@ -288,15 +352,15 @@ def test_amd64_reg_mem_funcall(self): cpu.push(0x1234, cpu.address_bit_size) - def test(one, two, three, four, five, six, seven, eight): - self.assertEqual(one, 1) - self.assertEqual(two, 2) - self.assertEqual(three, 3) - self.assertEqual(four, 4) - self.assertEqual(five, 5) - self.assertEqual(six, 6) - self.assertEqual(seven, 0x80) - self.assertEqual(eight, 0x88) + def test(a0, a1, a2, a3, a4, a5, a6, a7): + self.assertEqual(a0, 1) + self.assertEqual(a1, 2) + self.assertEqual(a2, 3) + self.assertEqual(a3, 4) + self.assertEqual(a4, 5) + self.assertEqual(a5, 6) + self.assertEqual(a6, 0x80) + self.assertEqual(a7, 0x88) cpu.func_abi.invoke(test) @@ -307,7 +371,7 @@ def test_amd64_basic_funcall_concretize(self): cpu.push(0x1234, cpu.address_bit_size) - def test(one, two, three, four, five, six): + def test(a0, a1, a2, a3, a4, a5): raise ConcretizeArgument(cpu, 0) with self.assertRaises(ConcretizeRegister) as cr: @@ -343,13 +407,13 @@ def test_i386_syscall(self): for idx, reg in enumerate(['EBX', 'ECX', 'EDX', 'ESI', 'EDI', 'EBP']): cpu.write_register(reg, idx) - def test(one, two, three, four, five, six): - self.assertEqual(one, 0) - self.assertEqual(two, 1) - self.assertEqual(three, 2) - self.assertEqual(four, 3) - self.assertEqual(five, 4) - self.assertEqual(six, 5) + def test(a0, a1, a2, a3, a4, a5): + self.assertEqual(a0, 0) + self.assertEqual(a1, 1) + self.assertEqual(a2, 2) + self.assertEqual(a3, 3) + self.assertEqual(a4, 4) + self.assertEqual(a5, 5) return 34 self.assertEqual(cpu.syscall_abi.syscall_number(), 5) @@ -365,13 +429,13 @@ def test_amd64_syscall(self): for idx, reg in enumerate(['RDI', 'RSI', 'RDX', 'R10', 'R8', 'R9']): cpu.write_register(reg, idx) - def test(one, two, three, four, five, six): - self.assertEqual(one, 0) - self.assertEqual(two, 1) - self.assertEqual(three, 2) - self.assertEqual(four, 3) - self.assertEqual(five, 4) - self.assertEqual(six, 5) + def test(a0, a1, a2, a3, a4, a5): + self.assertEqual(a0, 0) + self.assertEqual(a1, 1) + self.assertEqual(a2, 2) + self.assertEqual(a3, 3) + self.assertEqual(a4, 4) + self.assertEqual(a5, 5) return 34 self.assertEqual(cpu.syscall_abi.syscall_number(), 5) @@ -413,10 +477,9 @@ def test_funcall_method(self): class Kls: def method(self, a, b): - return a+b + return a + b obj = Kls() result = cpu.func_abi.invoke(obj.method) self.assertEqual(result, 3) - diff --git a/tests/native/test_unicorn.py b/tests/native/test_armv7unicorn.py similarity index 99% rename from tests/native/test_unicorn.py rename to tests/native/test_armv7unicorn.py index 8c69f0014..d20488796 100644 --- a/tests/native/test_unicorn.py +++ b/tests/native/test_armv7unicorn.py @@ -5,6 +5,7 @@ from capstone import CS_MODE_THUMB, CS_MODE_ARM from functools import wraps from keystone import Ks, KS_ARCH_ARM, KS_MODE_ARM, KS_MODE_THUMB +import logging from unicorn import UC_QUERY_MODE, UC_MODE_THUMB from manticore.native.cpu.abstractcpu import ConcretizeRegister @@ -18,8 +19,6 @@ ks = Ks(KS_ARCH_ARM, KS_MODE_ARM) ks_thumb = Ks(KS_ARCH_ARM, KS_MODE_THUMB) -import logging - logger = logging.getLogger("ARM_TESTS") __doc__ = ''' @@ -28,6 +27,7 @@ to make sure symbolic values get properly concretized. ''' + def assemble(asm, mode=CS_MODE_ARM): if CS_MODE_ARM == mode: ords = ks.asm(asm)[0] @@ -39,6 +39,7 @@ def assemble(asm, mode=CS_MODE_ARM): raise Exception(f'bad assembly: {asm}') return ''.join(map(chr, ords)) + def emulate_next(cpu): 'Read the next instruction and emulate it with Unicorn ' cpu.decode_instruction(cpu.PC) @@ -69,7 +70,7 @@ def wrapper(self): try: src = int(src, 0) - except Exception: + except ValueError: pass self.rf.write(dest.upper(), src) @@ -98,6 +99,7 @@ class Armv7UnicornInstructions(unittest.TestCase): all semantics match. ''' _multiprocess_can_split_ = True + def setUp(self): self.cpu = Cpu(Memory32()) self.mem = self.cpu.memory @@ -1343,7 +1345,6 @@ def get_state(cls): cls.cpu = platform._mk_proc('armv7') return (cls.cpu, cls.state) - def setUp(self): self.cpu, self.state = self.__class__.get_state() self.mem = self.cpu.memory @@ -1382,7 +1383,7 @@ def test_load_symbolic_correct_address(self): self.assertFalse(True) except ConcretizeMemory as e: sp = self.rf.read('SP') - self.assertTrue(e.address in range(sp, sp+len(val))) + self.assertTrue(e.address in range(sp, sp + len(val))) @itest_custom("mov r1, r2") def test_load_symbolic_from_register(self): @@ -1417,10 +1418,9 @@ def test_arm_constant(self): emulate_next(self.cpu) - self.assertEqual(self.rf.read('PC'), self.code+8) + self.assertEqual(self.rf.read('PC'), self.code + 8) self.assertEqual(self.rf.read('R0'), 0x12345678) - @itest_custom("mov r1, r2") def test_concretize_register_isnt_consumed(self): val = self.state.new_symbolic_value(32) @@ -1428,4 +1428,3 @@ def test_concretize_register_isnt_consumed(self): with self.assertRaises(ConcretizeRegister): self.cpu.emulate(self.cpu.decode_instruction(self.cpu.PC)) - diff --git a/tests/native/test_linux.py b/tests/native/test_linux.py index 2e6854ea2..52e6b827d 100644 --- a/tests/native/test_linux.py +++ b/tests/native/test_linux.py @@ -20,10 +20,15 @@ class LinuxTest(unittest.TestCase): def setUp(self): self.linux = linux.Linux(self.BIN_PATH) - self.symbolic_linux = linux.SLinux.empty_platform('armv7') + self.symbolic_linux_armv7 = linux.SLinux.empty_platform('armv7') + self.symbolic_linux_aarch64 = linux.SLinux.empty_platform('aarch64') def tearDown(self): - for f in self.linux.files + self.symbolic_linux.files: + for f in ( + self.linux.files + + self.symbolic_linux_armv7.files + + self.symbolic_linux_aarch64.files + ): if isinstance(f, linux.File): f.close() @@ -49,13 +54,13 @@ def test_stack_init(self): self.assertEqual(cpu.read_int(cpu.STACK), 4) argv_ptr = cpu.STACK + 8 - envp_ptr = argv_ptr + len(real_argv)*8 + 8 + envp_ptr = argv_ptr + len(real_argv) * 8 + 8 for i, arg in enumerate(real_argv): - self.assertEqual(cpu.read_string(cpu.read_int(argv_ptr + i*8)), arg) + self.assertEqual(cpu.read_string(cpu.read_int(argv_ptr + i * 8)), arg) for i, env in enumerate(envp): - self.assertEqual(cpu.read_string(cpu.read_int(envp_ptr + i*8)), env) + self.assertEqual(cpu.read_string(cpu.read_int(envp_ptr + i * 8)), env) def test_load_maps(self): mappings = self.linux.current.memory.mappings() @@ -72,13 +77,43 @@ def test_load_maps(self): self.assertEqual(first_map_name, 'basic_linux_amd64') self.assertEqual(second_map_name, 'basic_linux_amd64') - def test_syscall_fstat(self): + def test_aarch64_syscall_write(self): + nr_write = 64 + + # Create a minimal state. + platform = self.symbolic_linux_aarch64 + platform.current.memory.mmap(0x1000, 0x1000, 'rw ') + platform.current.SP = 0x2000 - 8 + + # Create a buffer. + buf = platform.current.SP - 0x100 + s = "hello\n" + platform.current.write_bytes(buf, s) + + fd = 1 # stdout + size = len(s) + + # Invoke the syscall. + platform.current.X0 = fd + platform.current.X1 = buf + platform.current.X2 = size + platform.current.X8 = nr_write + self.assertEqual(linux_syscalls.aarch64[nr_write], 'sys_write') + + platform.syscall() + + self.assertEqual(platform.current.regfile.read('X0'), size) + + res = ''.join(map(chr, platform.output.read(size))) + self.assertEqual(res, s) + + def test_armv7_syscall_fstat(self): nr_fstat64 = 197 # Create a minimal state - platform = self.symbolic_linux + platform = self.symbolic_linux_armv7 platform.current.memory.mmap(0x1000, 0x1000, 'rw ') - platform.current.SP = 0x2000-4 + platform.current.SP = 0x2000 - 4 # open a file filename = platform.current.push_bytes('/bin/true\x00') @@ -94,9 +129,9 @@ def test_syscall_fstat(self): print(hexlify(b''.join(platform.current.read_bytes(stat, 100)))) - def test_linux_symbolic_files_workspace_files(self): + def test_armv7_linux_symbolic_files_workspace_files(self): fname = 'symfile' - platform = self.symbolic_linux + platform = self.symbolic_linux_armv7 # create symbolic file with open(fname, 'w') as f: @@ -107,7 +142,7 @@ def test_linux_symbolic_files_workspace_files(self): # create filename in memory platform.current.memory.mmap(0x1000, 0x1000, 'rw ') - platform.current.SP = 0x2000-4 + platform.current.SP = 0x2000 - 4 fname_ptr = platform.current.push_bytes(fname + '\x00') # open and close file @@ -124,9 +159,8 @@ def test_linux_symbolic_files_workspace_files(self): self.assertIn(fname, files) self.assertEqual(len(files[fname]), 1) - - def test_linux_workspace_files(self): - platform = self.symbolic_linux + def test_armv7_linux_workspace_files(self): + platform = self.symbolic_linux_armv7 platform.argv = ["arg1", "arg2"] files = platform.generate_workspace_files() @@ -139,21 +173,23 @@ def test_linux_workspace_files(self): self.assertIn('stderr', files) self.assertIn('net', files) - def test_syscall_events(self): + def test_armv7_syscall_events(self): nr_fstat64 = 197 class Receiver: def __init__(self): self.nevents = 0 + def will_exec(self, pc, i): self.nevents += 1 + def did_exec(self, last_pc, pc, i): self.nevents += 1 # Create a minimal state - platform = self.symbolic_linux + platform = self.symbolic_linux_armv7 platform.current.memory.mmap(0x1000, 0x1000, 'rw ') - platform.current.SP = 0x2000-4 + platform.current.SP = 0x2000 - 4 platform.current.memory.mmap(0x2000, 0x2000, 'rwx') platform.current.PC = 0x2000 platform.current.write_int(platform.current.PC, 0x050f) @@ -175,16 +211,16 @@ def did_exec(self, last_pc, pc, i): platform.execute() post_icount = platform.current.icount - self.assertEqual(pre_icount+1, post_icount) + self.assertEqual(pre_icount + 1, post_icount) self.assertEqual(r.nevents, 2) - def _create_openat_state(self): + def _armv7_create_openat_state(self): nr_openat = 322 # Create a minimal state - platform = self.symbolic_linux + platform = self.symbolic_linux_armv7 platform.current.memory.mmap(0x1000, 0x1000, 'rw ') - platform.current.SP = 0x2000-4 + platform.current.SP = 0x2000 - 4 dir_path = tempfile.mkdtemp() file_name = "file" @@ -193,9 +229,9 @@ def _create_openat_state(self): f.write(b'test') # open a file + directory - dirname = platform.current.push_bytes(dir_path+'\x00') + dirname = platform.current.push_bytes(dir_path + '\x00') dirfd = platform.sys_open(dirname, os.O_RDONLY, 0o700) - filename = platform.current.push_bytes(file_name+'\x00') + filename = platform.current.push_bytes(file_name + '\x00') stat = platform.current.SP - 0x100 platform.current.R0 = dirfd @@ -207,16 +243,16 @@ def _create_openat_state(self): return platform, dir_path - def test_syscall_openat_concrete(self): - platform, temp_dir = self._create_openat_state() + def test_armv7_syscall_openat_concrete(self): + platform, temp_dir = self._armv7_create_openat_state() try: platform.syscall() self.assertGreater(platform.current.R0, 2) finally: shutil.rmtree(temp_dir) - def test_syscall_openat_symbolic(self): - platform, temp_dir = self._create_openat_state() + def test_armv7_syscall_openat_symbolic(self): + platform, temp_dir = self._armv7_create_openat_state() try: platform.current.R0 = BitVecVariable(32, 'fd') @@ -227,15 +263,15 @@ def test_syscall_openat_symbolic(self): _min, _max = solver.minmax(platform.constraints, e.cpu.read_register(e.reg_name)) self.assertLess(_min, len(platform.files)) - self.assertGreater(_max, len(platform.files)-1) + self.assertGreater(_max, len(platform.files) - 1) finally: shutil.rmtree(temp_dir) - def test_chroot(self): + def test_armv7_chroot(self): # Create a minimal state - platform = self.symbolic_linux + platform = self.symbolic_linux_armv7 platform.current.memory.mmap(0x1000, 0x1000, 'rw ') - platform.current.SP = 0x2000-4 + platform.current.SP = 0x2000 - 4 # should error with ENOENT this_file = os.path.realpath(__file__) @@ -256,12 +292,12 @@ def test_symbolic_argv_envp(self): envp={'TEST': '+'}) state = self.m.initial_state - ptr = state.cpu.read_int(state.cpu.RSP + (8*2)) # get argv[1] + ptr = state.cpu.read_int(state.cpu.RSP + (8 * 2)) # get argv[1] mem = state.cpu.read_bytes(ptr, 2) self.assertTrue(issymbolic(mem[0])) self.assertEqual(mem[1], b'\0') - ptr = state.cpu.read_int(state.cpu.RSP + (8*4)) # get envp[0] + ptr = state.cpu.read_int(state.cpu.RSP + (8 * 4)) # get envp[0] mem = state.cpu.read_bytes(ptr, 7) self.assertEqual(b''.join(mem[:5]), b'TEST=') self.assertEqual(mem[6], b'\0')