Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add missing support for ARM modified immediate encodings #1638

Merged
merged 40 commits into from
Mar 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
be06139
Add unit tests for ARMv7 MVN instruction
Mar 20, 2020
ee3952e
Add unit tests for ARMv7 MOV instruction
Mar 20, 2020
88506f0
Add unit tests for ARMv7 ADD instruction
Mar 20, 2020
950dc13
Add unit tests for ARMv7 ADC instruction
Mar 20, 2020
ae58311
Add unit tests for ARMv7 ADR instruction
Mar 20, 2020
ee1abc2
Add unit tests for ARMv7 CMN instruction
Mar 20, 2020
8c242bb
Add unit tests for ARMv7 CMP instruction
Mar 20, 2020
5e6161c
Add unit tests for ARMv7 SUB instruction
Mar 20, 2020
4c20478
Add unit tests for ARMv7 RSC instruction
Mar 20, 2020
db1792f
Add unit tests for ARMv7 SBC instruction
Mar 20, 2020
78c36cd
Add unit tests for ARMv7 ORR instruction
Mar 20, 2020
98341cd
Add unit tests for ARMv7 EOR instruction
Mar 20, 2020
77a3eec
Add unit tests for ARMv7 TST instruction
Mar 20, 2020
9f17471
Add unit tests for ARMv7 TEQ instruction
Mar 20, 2020
c496230
Add unit tests for ARMv7 AND instruction
Mar 20, 2020
0568546
Add unit tests for ARMv7 RSB instruction
Mar 20, 2020
bdefd8d
Add unit tests for ARMv7 BIC instruction
Mar 20, 2020
4eae487
blacken test_armv7cpu.py
Mar 20, 2020
b775637
Enhance the ARMv7 `instruction` decorator to normalize modified immed…
Mar 21, 2020
b7e8eae
Simplify the `Armv7Cpu._bitwise_instruction` method
Mar 21, 2020
564aa5a
Fix ARMv7 MOV implementation for modified immediates
Mar 21, 2020
107eacb
Fix ARMv7 ADC implementation for modified immediates
Mar 21, 2020
2f4b096
Fix ARMv7 ADD implementation for modified immediates
Mar 21, 2020
b1faa4d
Fix ARMv7 RSB implementation for modified immediates
Mar 21, 2020
ef5c1a9
Fix ARMv7 RSC implementation for modified immediates
Mar 21, 2020
9e2add0
Fix ARMv7 SUB implementation for modified immediates
Mar 21, 2020
a0650af
Fix ARMv7 SBC implementation for modified immediates
Mar 21, 2020
ba3a565
Fix ARMv7 CMP implementation for modified immediates
Mar 21, 2020
8d42339
Fix ARMv7 ORR implementation for modified immediates
Mar 21, 2020
4758249
Fix ARMv7 ORN implementation for modified immediates
Mar 21, 2020
42193fd
Fix ARMv7 EOR implementation for modified immediates
Mar 21, 2020
1fe99aa
Fix ARMv7 AND implementation for modified immediates
Mar 21, 2020
3a2d397
Fix ARMv7 TEQ implementation for modified immediates
Mar 21, 2020
a0d6629
Fix ARMv7 TST implementation for modified immediates
Mar 21, 2020
6232fa6
Fix ARMv7 CMN implementation for modified immediates
Mar 21, 2020
d3908ec
Fix ARMv7 MVN implementation for modified immediates
Mar 21, 2020
dc05633
Fix ARMv7 BIC implementation for modified immediates
Mar 21, 2020
e4116ee
blacken manticore/native/cpu/arm.py
Mar 21, 2020
ac902c4
Expand comments about modified immediates; simplify decorator slightly
Mar 23, 2020
9b5b508
Rename parameter to avoid shadowing
Mar 23, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 150 additions & 47 deletions manticore/native/cpu/arm.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from functools import wraps
from inspect import signature as inspect_signature
import logging
import struct

Expand All @@ -10,6 +11,8 @@
from .register import Register
from ...core.smtlib import Operators, BitVecConstant, issymbolic

from typing import NamedTuple

logger = logging.getLogger(__name__)

# map different instructions to a single impl here
Expand All @@ -20,35 +23,135 @@ def HighBit(n):
return Bit(n, 31)


def instruction(body):
@wraps(body)
def instruction_implementation(cpu, *args, **kwargs):
ret = None
ARMV7_CPU_ADDRESS_BIT_SIZE = 32


def instruction(instruction_body=None, *, can_take_denormalized_mod_imm: bool = False):
"""
This decorator is used to annotate Armv7Cpu methods as
instruction-implementing methods.

This centralizes some common ARM-specific logic about CPU flags in one place.

Additionally, this optionally adds /modified immediate normalization/ logic
to the wrapped method.

should_execute = cpu.should_execute_conditional()
This decorator works both as `@instruction` and as
`@instruction(can_take_denormalized_mod_imm=True)`.


What is this normalization logic?

First, it helps to understand how ARM encodes immediate constants.
In encoded ARM instructions, immediate constant values are encoded as an
8-bit unsigned number and a 4-bit rotation value; you can read about the
details in the ARM Architecture Reference Manual, ARMv7-A and ARMv7-R
edition, section A5.2.3, "Data-processing (immediate)".

Second, it turns out that the Capstone disassembler we use will sometimes
disassemble an ARM immediate constant value into /two/ immediate operand
values, explicitly representing the 8-bit unsigned number and two times the
4-bit shift. In particular, it seems that Capstone uses this explicit
representation when the modified immediate value is encoded in a
non-canonical form. A blog post has some more explanation around this:

https://alisdair.mcdiarmid.org/arm-immediate-value-encoding/

So, finally, the /modified immediate normalization/ logic that this
decorator adds converts an explicitly-represented unsigned number and
rotation into a single immediate operand-like value (`_ImmediatePseudoOperand`)
that has the appropriate integer value, so that the actual implementation
of an ARM instruction here can expect only normalized immediates, and not
have to concern itself with this quirk of Capstone.
"""

def decorator(body):
bradlarsen marked this conversation as resolved.
Show resolved Hide resolved
if can_take_denormalized_mod_imm:
# Need to possibly normalize a modified immediate argument that's
# been explicitly split by Capstone into (number, rotation)
# components.

body_sig = inspect_signature(body)
# subtract 1 to account for the first parameter (`cpu`), present in
# all instruction methods.
num_body_params = len(body_sig.parameters) - 1
assert num_body_params > 0
bradlarsen marked this conversation as resolved.
Show resolved Hide resolved

def normalize_mod_imm_arg(args):
if len(args) == num_body_params + 1:
bradlarsen marked this conversation as resolved.
Show resolved Hide resolved
# We got 1 more argument than the wrapped function expects;
# this is the case of a modified immediate represented
# explicitly as 2 immediate operands.
# Normalize the two into one!
args = list(args)
rot = args.pop()
assert rot.type == "immediate"
num = args.pop()
assert num.type == "immediate"
imm = ROR(num.imm, rot.imm, ARMV7_CPU_ADDRESS_BIT_SIZE)
args.append(_ImmediatePseudoOperand(imm))
return args

if cpu._at_symbolic_conditional == cpu.instruction.address:
cpu._at_symbolic_conditional = None
should_execute = True
else:
if issymbolic(should_execute):
# Let's remember next time we get here we should not do this again
cpu._at_symbolic_conditional = cpu.instruction.address
i_size = cpu.instruction.size
cpu.PC = Operators.ITEBV(
cpu.address_bit_size, should_execute, cpu.PC - i_size, cpu.PC
)
return
# No normalization of a modified immediate required; return what's given.
def normalize_mod_imm_arg(args):
return args

if should_execute:
ret = body(cpu, *args, **kwargs)
@wraps(body)
def instruction_implementation(cpu, *args, **kwargs):
should_execute = cpu.should_execute_conditional()

if cpu.should_commit_flags():
cpu.commit_flags()
if cpu._at_symbolic_conditional == cpu.instruction.address:
cpu._at_symbolic_conditional = None
should_execute = True
else:
if issymbolic(should_execute):
# Let's remember next time we get here we should not do this again
cpu._at_symbolic_conditional = cpu.instruction.address
i_size = cpu.instruction.size
cpu.PC = Operators.ITEBV(
cpu.address_bit_size, should_execute, cpu.PC - i_size, cpu.PC
)
return

if should_execute:
ret = body(cpu, *normalize_mod_imm_arg(args), **kwargs)
else:
ret = None

return ret
if cpu.should_commit_flags():
cpu.commit_flags()

return abstract_instruction(instruction_implementation)
return ret

return abstract_instruction(instruction_implementation)

# Here's where we support using this decorator both like
# `@instruction` and like `@instruction(can_take_denormalized_mod_imm=True)`.
# See https://stackoverflow.com/questions/3888158/making-decorators-with-optional-arguments
# for some decorator-fu.
if instruction_body is not None:
return decorator(instruction_body)
else:
return decorator


class _ImmediatePseudoOperand(NamedTuple):
"""
This is a hacky class that is used to represent an object that looks close
enough to an Armv7Operand to be used in the places where immediate operands
are used.

See the `instruction` decorator for more detail.
"""

imm: int

def read(self, with_carry: bool = False):
if with_carry:
return self.imm, 0
else:
return self.imm


_TYPE_MAP = {
Expand Down Expand Up @@ -482,7 +585,7 @@ class Armv7Cpu(Cpu):
current instruction + 8 (section A2.3).
"""

address_bit_size = 32
address_bit_size = ARMV7_CPU_ADDRESS_BIT_SIZE
max_instr_width = 4
machine = "armv7"
arch = cs.CS_ARCH_ARM
Expand Down Expand Up @@ -768,7 +871,7 @@ def SEL(cpu, dest, op1, op2):
)
dest.write(Operators.CONCAT(32, *reversed(result)))

@instruction
@instruction(can_take_denormalized_mod_imm=True)
def MOV(cpu, dest, src):
"""
Implement the MOV{S} instruction.
Expand Down Expand Up @@ -1014,7 +1117,7 @@ def _ADD(cpu, _op1, _op2, carry=0):

return result, carry_out, overflow

@instruction
@instruction(can_take_denormalized_mod_imm=True)
def ADC(cpu, dest, op1, op2=None):
carry = cpu.regfile.read("APSR_C")
if op2 is not None:
Expand All @@ -1024,7 +1127,7 @@ def ADC(cpu, dest, op1, op2=None):
dest.write(result)
return result, carry, overflow

@instruction
@instruction(can_take_denormalized_mod_imm=True)
def ADD(cpu, dest, src, add=None):
if add is not None:
result, carry, overflow = cpu._ADD(src.read(), add.read())
Expand All @@ -1034,22 +1137,22 @@ def ADD(cpu, dest, src, add=None):
dest.write(result)
return result, carry, overflow

@instruction
@instruction(can_take_denormalized_mod_imm=True)
def RSB(cpu, dest, src, add):
inv_src = GetNBits(~src.read(), cpu.address_bit_size)
result, carry, overflow = cpu._ADD(inv_src, add.read(), 1)
dest.write(result)
return result, carry, overflow

@instruction
@instruction(can_take_denormalized_mod_imm=True)
def RSC(cpu, dest, src, add):
carry = cpu.regfile.read("APSR_C")
inv_src = GetNBits(~src.read(), cpu.address_bit_size)
result, carry, overflow = cpu._ADD(inv_src, add.read(), carry)
dest.write(result)
return result, carry, overflow

@instruction
@instruction(can_take_denormalized_mod_imm=True)
def SUB(cpu, dest, src, add=None):
if add is not None:
result, carry, overflow = cpu._ADD(src.read(), ~add.read(), 1)
Expand All @@ -1060,7 +1163,7 @@ def SUB(cpu, dest, src, add=None):
dest.write(result)
return result, carry, overflow

@instruction
@instruction(can_take_denormalized_mod_imm=True)
def SBC(cpu, dest, op1, op2=None):
carry = cpu.regfile.read("APSR_C")
if op2 is not None:
Expand Down Expand Up @@ -1248,7 +1351,7 @@ def TBH(cpu, dest):

cpu.PC += offset << 1

@instruction
@instruction(can_take_denormalized_mod_imm=True)
def CMP(cpu, reg, compare):
notcmp = ~compare.read() & Mask(cpu.address_bit_size)
cpu._ADD(reg.read(), notcmp, 1)
Expand Down Expand Up @@ -1407,9 +1510,9 @@ def STMDA(cpu, base, *regs):
def STMDB(cpu, base, *regs):
cpu._STM(cs.arm.ARM_INS_STMDB, base, regs)

def _bitwise_instruction(cpu, operation, dest, op1, *op2):
def _bitwise_instruction(cpu, operation, dest, op1, op2=None):
if op2:
op2_val, carry = op2[0].read(with_carry=True)
op2_val, carry = op2.read(with_carry=True)
result = operation(op1.read(), op2_val)
else:
op1_val, carry = op1.read(with_carry=True)
Expand All @@ -1418,43 +1521,43 @@ def _bitwise_instruction(cpu, operation, dest, op1, *op2):
dest.write(result)
cpu.set_flags(C=carry, N=HighBit(result), Z=(result == 0))

@instruction
@instruction(can_take_denormalized_mod_imm=True)
def ORR(cpu, dest, op1, op2=None):
if op2 is not None:
cpu._bitwise_instruction(lambda x, y: x | y, dest, op1, op2)
else:
cpu._bitwise_instruction(lambda x, y: x | y, dest, dest, op1)

@instruction
@instruction(can_take_denormalized_mod_imm=True)
def ORN(cpu, dest, op1, op2=None):
if op2 is not None:
cpu._bitwise_instruction(lambda x, y: x | ~y, dest, op1, op2)
else:
cpu._bitwise_instruction(lambda x, y: x | ~y, dest, dest, op1)

@instruction
@instruction(can_take_denormalized_mod_imm=True)
def EOR(cpu, dest, op1, op2=None):
if op2 is not None:
cpu._bitwise_instruction(lambda x, y: x ^ y, dest, op1, op2)
else:
cpu._bitwise_instruction(lambda x, y: x ^ y, dest, dest, op1)

@instruction
@instruction(can_take_denormalized_mod_imm=True)
def AND(cpu, dest, op1, op2=None):
if op2 is not None:
cpu._bitwise_instruction(lambda x, y: x & y, dest, op1, op2)
else:
cpu._bitwise_instruction(lambda x, y: x & y, dest, dest, op1)

@instruction
def TEQ(cpu, *operands):
cpu._bitwise_instruction(lambda x, y: x ^ y, None, *operands)
@instruction(can_take_denormalized_mod_imm=True)
def TEQ(cpu, op1, op2=None):
cpu._bitwise_instruction(lambda x, y: x ^ y, None, op1, op2)
cpu.commit_flags()

@instruction
def TST(cpu, Rn, Rm):
shifted, carry = Rm.read(with_carry=True)
result = Rn.read() & shifted
@instruction(can_take_denormalized_mod_imm=True)
def TST(cpu, op1, op2):
shifted, carry = op2.read(with_carry=True)
result = op1.read() & shifted
cpu.set_flags(N=HighBit(result), Z=(result == 0), C=carry)

@instruction
Expand All @@ -1463,7 +1566,7 @@ def SVC(cpu, op):
logger.warning(f"Bad SVC number: {op.read():08}")
raise Interruption(0)

@instruction
@instruction(can_take_denormalized_mod_imm=True)
def CMN(cpu, src, add):
result, carry, overflow = cpu._ADD(src.read(), add.read())
return result, carry, overflow
Expand Down Expand Up @@ -1543,7 +1646,7 @@ def MUL(cpu, dest, src1, src2):
dest.write(result & Mask(width))
cpu.set_flags(N=HighBit(result), Z=(result == 0))

@instruction
@instruction(can_take_denormalized_mod_imm=True)
def MVN(cpu, dest, op):
cpu._bitwise_instruction(lambda x: ~x, dest, op)

Expand All @@ -1558,7 +1661,7 @@ def MLA(cpu, dest, op1, op2, addend):
dest.write(result & Mask(cpu.address_bit_size))
cpu.set_flags(N=HighBit(result), Z=(result == 0))

@instruction
@instruction(can_take_denormalized_mod_imm=True)
def BIC(cpu, dest, op1, op2=None):
if op2 is not None:
result = (op1.read() & ~op2.read()) & Mask(cpu.address_bit_size)
Expand Down
Loading