Make unicorn be pull-based (#97)
* Enable simple ARM register concretization for Unicorn * Add canonical_registers property to abstractcpu * cpu to self * Check for regs_access better * Emulate a single instruction * Bypass capstone 3.0.4 arm bug * Dealing with capstone * Temporary disable ASR and remobe BitVec.Bool from test * WIP WIP debug prints WIP WIP * Unicorn fallback working (using unicorn master) * HAck to support unicorn 1.0.0 * WIP * Unicorn hack to handle PC updates * [WIP] do not do anything with this commit; for debugging only * Adding before clean up * emulation more or less works; need to work out more unicorn bugs * clean up emulate() caller code * move hooks to methods; cleanup * Concretize memory when emulating * Re-add Bool() * Update tests to start at offset 4 When an instruction branches to the previous instruction, Unicorn attempts to dereference that memory. We'd like to use unit tests to also make sure Unicorn emulation is in line with our own semantics. If we start all tests at offset 4, we can jump to a previous instruction and not fault when Unicorn dereferences it. * Fix concretization * Clean up test imports; upper-case Cpu * Unicorn tests * Add tests for all the ARM semantics, but make sure they're equivalent on unicorn. * Add a few tests to make sure unicorn correctly concretizes the memory it references * Fix broken import * Add symbolic register tests * Re-introduce the unicorn hack * Add the 'ONE' concretization policy * Rm unused function * Update concretization; add comments * Add ONE policy test * Create a base class for all concretization exceptions * Remove Armv7Cpu._concretize_registers * Check for enabled logging in a more idiomatic way * [wip] intermediate testing commit * Reimplement hooks and execution with unicorn * Add a DMB (mem barrier) instruction; nop * simplify instruction resolution * improve unicorn error handling * explicitly delete emu * Handle ARM helpers inline * map fetched memory * Narrow exception handling * Update DMB docs; make __kuser_dmb match real implementation * Fix typo; add comment; remove extraneous parameter * typos++
This commit is contained in:
@@ -1,27 +1,18 @@
|
||||
from capstone import *
|
||||
from capstone.arm import *
|
||||
from capstone.x86 import *
|
||||
from unicorn import *
|
||||
from unicorn.x86_const import *
|
||||
from unicorn.arm_const import *
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from ..smtlib import Expression, Bool, BitVec, Array, Operators, Constant
|
||||
from ..memory import MemoryException
|
||||
from ..memory import MemoryException, FileMap, AnonMap
|
||||
from ...utils.helpers import issymbolic
|
||||
from ...utils.emulate import UnicornEmulator
|
||||
import sys
|
||||
from functools import wraps
|
||||
import types
|
||||
import logging
|
||||
logger = logging.getLogger("CPU")
|
||||
|
||||
######################################################################
|
||||
# Abstract classes for capstone/unicorn based cpus
|
||||
# no emulator by default
|
||||
MU = {
|
||||
(CS_ARCH_ARM, CS_MODE_ARM): Uc(UC_ARCH_ARM, UC_MODE_ARM),
|
||||
(CS_ARCH_X86, CS_MODE_32): Uc(UC_ARCH_X86, UC_MODE_32),
|
||||
(CS_ARCH_X86, CS_MODE_64): Uc(UC_ARCH_X86, UC_MODE_64)
|
||||
}
|
||||
|
||||
|
||||
SANE_SIZES = {8, 16, 32, 64, 80, 128, 256}
|
||||
# This encapsulates how to acccess operands (regs/mem/immediates) for differents cpus
|
||||
@@ -154,7 +145,6 @@ class Cpu(object):
|
||||
self._md.detail = True
|
||||
self._md.syntax = 0
|
||||
self.instruction = None
|
||||
#FIXME self.transactions = []
|
||||
|
||||
def __getstate__(self):
|
||||
state = {}
|
||||
@@ -189,6 +179,13 @@ class Cpu(object):
|
||||
:rtype: tuple[str]
|
||||
'''
|
||||
return self._regfile.all_registers
|
||||
@property
|
||||
def canonical_registers(self):
|
||||
''' Returns the list of all register names for this CPU.
|
||||
@rtype: tuple
|
||||
@return: the list of register names for this CPU.
|
||||
'''
|
||||
return self._regfile.canonical_registers
|
||||
|
||||
def write_register(self, register, value):
|
||||
'''Dynamic interface for writing cpu registers
|
||||
@@ -357,16 +354,20 @@ class Cpu(object):
|
||||
|
||||
name = self.canonicalize_instruction_name(instruction)
|
||||
|
||||
try:
|
||||
implementation = getattr(self, name)
|
||||
except AttributeError as ae:
|
||||
logger.debug("UNIMPLEMENTED INSTRUCTION: 0x%016x:\t%s\t%s\t%s", instruction.address, ' '.join(map(lambda x: '%02x'%x, instruction.bytes)), instruction.mnemonic, instruction.op_str)
|
||||
implementation = lambda *ops: self.emulate(instruction)
|
||||
def fallback_to_emulate(*operands):
|
||||
text_bytes = ' '.join('%02x'%x for x in instruction.bytes)
|
||||
logger.info("UNIMPLEMENTED INSTRUCTION: 0x%016x:\t%s\t%s\t%s",
|
||||
instruction.address, text_bytes, instruction.mnemonic,
|
||||
instruction.op_str)
|
||||
self.emulate(instruction)
|
||||
|
||||
implementation = getattr(self, name, fallback_to_emulate)
|
||||
|
||||
#log
|
||||
if logger.level == logging.DEBUG :
|
||||
for l in str(self).split('\n'):
|
||||
logger.debug(l)
|
||||
|
||||
implementation(*instruction.operands)
|
||||
self._icount+=1
|
||||
|
||||
@@ -374,111 +375,16 @@ class Cpu(object):
|
||||
def get_syscall_description(self):
|
||||
pass
|
||||
|
||||
#############################################################
|
||||
# Emulation
|
||||
def _concretize_registers(self, instruction):
|
||||
pass
|
||||
|
||||
def _unicorn(self):
|
||||
return MU[(self.arch, self.mode)]
|
||||
|
||||
def emulate(self, instruction):
|
||||
#Fix Taint propagation
|
||||
needed_pages = set()
|
||||
needed_bytes = set()
|
||||
mapped = set()
|
||||
accessed = set()
|
||||
byte_values = {}
|
||||
'''
|
||||
If we could not handle emulating an instruction, use Unicorn to emulate
|
||||
it.
|
||||
|
||||
reg_values = self._concretize_registers(instruction)
|
||||
# Request any memory nearby the memory directly needed by the memory
|
||||
# operands of the instruction.
|
||||
for op in instruction.operands:
|
||||
if op.type != {CS_ARCH_ARM: ARM_OP_MEM, CS_ARCH_X86: X86_OP_MEM}[self.arch]:
|
||||
continue
|
||||
self.PC += instruction.size
|
||||
addr = op.address() #FIXME maybe add a kwarg parameter to operand.address() with the current pc?
|
||||
self.PC -= instruction.size
|
||||
assert not issymbolic(addr)
|
||||
num_bytes = op.size/8
|
||||
needed_bytes.update(range(addr, addr + num_bytes))
|
||||
# Request the bytes of the instruction.
|
||||
needed_bytes.update(range(self.PC, self.PC + instruction.size))
|
||||
|
||||
# Concretizes the bytes of memory potentially needed by the instruction.
|
||||
for addr in needed_bytes:
|
||||
needed_pages.add(addr & (~0xFFF))
|
||||
val = self.read_int(addr, 8)
|
||||
if issymbolic(val):
|
||||
logger.debug("Concretizing bytes before passing it to unicorn")
|
||||
raise ConcretizeMemory(addr, 8, "Passing control to emulator", 'SAMPLED')
|
||||
byte_values[addr] = val
|
||||
|
||||
mu = self._unicorn()
|
||||
|
||||
touched = set()
|
||||
def hook_mem_access(uc, access, address, size, value, user_data):
|
||||
if access & UC_MEM_WRITE:
|
||||
for i in range(address, address+size):
|
||||
user_data.add(i)
|
||||
if access & UC_MEM_READ:
|
||||
for i in range(address, address+size):
|
||||
if i not in needed_bytes:
|
||||
logger.error("Not initalized memory used by emulator at %x", address)
|
||||
try:
|
||||
# Copy in the concrete values of all needed registers.
|
||||
for reg, value in reg_values.items():
|
||||
#stem = {CS_ARCH_ARM: 'UC_ARM_REG_', CS_ARCH_X86: 'UC_X86_REG_'}[self.arch]
|
||||
stem = 'UC_X86_REG_'
|
||||
mu.reg_write(globals()[stem+reg], value)
|
||||
|
||||
#Map needed pages
|
||||
for page in needed_pages:
|
||||
mapped.add(page)
|
||||
mu.mem_map(page, 0x1000, UC_PROT_ALL)
|
||||
# Copy in memory bytes needed by instruction.
|
||||
for addr, value in byte_values.items():
|
||||
mu.mem_write(addr, Operators.CHR(value))
|
||||
|
||||
# Run the instruction.
|
||||
hook_id = mu.hook_add(UC_HOOK_MEM_WRITE | UC_HOOK_MEM_READ, hook_mem_access, touched)
|
||||
mu.emu_start(self.PC, self.PC+instruction.size)
|
||||
mu.hook_del(hook_id)
|
||||
mu.emu_stop()
|
||||
|
||||
# Copy back the memory modified by the unicorn emulation.
|
||||
for addr in touched:
|
||||
if not addr in needed_bytes:
|
||||
logger.error("Some address was touched in the emulation but not provided %x", addr)
|
||||
assert addr in needed_bytes
|
||||
try:
|
||||
cpu.write_int(addr, ord(mu.mem_read(addr, 1)), 8)
|
||||
except:
|
||||
pass
|
||||
|
||||
# Copy back the new values of all registers.
|
||||
if hasattr(instruction, 'regs_access') and instruction.regs_access is not None:
|
||||
(regs_read, regs_write) = instruction.regs_access()
|
||||
regs = [ instruction.reg_name(r).upper() for r in regs_write ]
|
||||
if self.arch == CS_ARCH_X86:
|
||||
regs += ['FPSW', 'FPCW', 'FPTAG', 'FP0', 'FP1', 'FP2', 'FP3', 'FP4', 'FP5', 'FP6', 'FP7']
|
||||
else:
|
||||
regs = reg_values.keys()
|
||||
logger.debug("Emulator wrote to this regs %r", regs)
|
||||
for reg in regs:
|
||||
#stem = {CS_ARCH_ARM: 'UC_ARM_REG_', CS_ARCH_X86: 'UC_X86_REG_'}[self.arch]
|
||||
stem = 'UC_X86_REG_'
|
||||
new_value = mu.reg_read(globals()[stem+reg])
|
||||
self.write_register(reg, new_value)
|
||||
|
||||
self.PC = self.PC+instruction.size
|
||||
return
|
||||
except Exception as e:
|
||||
logger.error('Exception in emulatin code:')
|
||||
logger.error(e, exc_info=True)
|
||||
finally:
|
||||
for i in mapped:
|
||||
mu.mem_unmap(i,0x1000)
|
||||
@param instruction The instruction object to emulate
|
||||
'''
|
||||
emu = UnicornEmulator(self)
|
||||
emu.emulate(instruction)
|
||||
del emu
|
||||
|
||||
#Generic string representation
|
||||
def __str__(self):
|
||||
@@ -552,36 +458,53 @@ class Syscall(CpuInterrupt):
|
||||
def __init__(self):
|
||||
super(Syscall, self).__init__("CPU Syscall")
|
||||
|
||||
class ConcretizeRegister(Exception):
|
||||
''' '''
|
||||
def __init__(self, reg_name, message, policy='MINMAX'):
|
||||
assert policy in ['MINMAX', 'ALL', 'SAMPLED']
|
||||
super(ConcretizeRegister, self).__init__("Concretizing %s (%s). %s"%(reg_name, policy, message))
|
||||
self.reg_name = reg_name
|
||||
self.policy = policy
|
||||
# TODO(yan): Move this into State or a more appropriate location
|
||||
|
||||
class ConcretizeMemory(Exception):
|
||||
''' '''
|
||||
class ConcretizeException(Exception):
|
||||
'''
|
||||
Base class for all exceptions that trigger the concretization of a symbolic
|
||||
value.
|
||||
'''
|
||||
_ValidPolicies = ['MINMAX', 'ALL', 'SAMPLED', 'ONE']
|
||||
def __init__(self, message, policy):
|
||||
assert policy in self._ValidPolicies, "Policy must be one of: %s"%(', '.join(self._ValidPolicies),)
|
||||
self.policy = policy
|
||||
super(ConcretizeException, self).__init__("%s (Policy: %s)"%(message, policy))
|
||||
|
||||
class ConcretizeRegister(ConcretizeException):
|
||||
'''
|
||||
Raised when a symbolic register needs to be concretized.
|
||||
'''
|
||||
def __init__(self, reg_name, message, policy='MINMAX'):
|
||||
message = "Concretizing %s. %s"%(reg_name, message)
|
||||
super(ConcretizeRegister, self).__init__(message, policy)
|
||||
self.reg_name = reg_name
|
||||
|
||||
class ConcretizeMemory(ConcretizeException):
|
||||
'''
|
||||
Raised when a symbolic memory location needs to be concretized.
|
||||
'''
|
||||
def __init__(self, address, size, message, policy='MINMAX'):
|
||||
assert policy in ['MINMAX', 'ALL', 'SAMPLED']
|
||||
super(ConcretizeMemory, self).__init__("Concretizing byte at %x (%s). %s"%(address, policy, message))
|
||||
message = "Concretizing byte at %x. %s"%(address, message)
|
||||
super(ConcretizeMemory, self).__init__(message, policy)
|
||||
self.address = address
|
||||
self.size = size
|
||||
self.policy = policy
|
||||
|
||||
class ConcretizeArgument(Exception):
|
||||
''' '''
|
||||
class ConcretizeArgument(ConcretizeException):
|
||||
'''
|
||||
Raised when a symbolic argument needs to be concretized.
|
||||
'''
|
||||
def __init__(self, argnum, policy='MINMAX'):
|
||||
assert policy in ['MINMAX', 'ALL', 'SAMPLED']
|
||||
super(ConcretizeArgument, self).__init__("Concretizing argument #%d (%s): "%(argnum, policy))
|
||||
message = "Concretizing argument #%d."%(argnum,)
|
||||
super(ConcretizeArgument, self).__init__(message, policy)
|
||||
self.argnum = argnum
|
||||
self.policy = policy
|
||||
|
||||
|
||||
class SymbolicPCException(ConcretizeRegister):
|
||||
''' '''
|
||||
'''
|
||||
Raised when we attempt to execute from a symbolic location.
|
||||
'''
|
||||
def __init__(self):
|
||||
super(SymbolicPCException, self).__init__("PC", "Symbolic PC", "ALL")
|
||||
super(SymbolicPCException, self).__init__("PC", "Can't execute from a symbolic address.", "ALL")
|
||||
|
||||
class IgnoreAPI(Exception):
|
||||
def __init__(self, name):
|
||||
|
||||
@@ -277,26 +277,6 @@ class Armv7Cpu(Cpu):
|
||||
self._last_flags = state['_last_flags']
|
||||
self._force_next = state['_force_next']
|
||||
|
||||
def _concretize_registers(cpu, instruction):
|
||||
reg_values = {}
|
||||
if hasattr(instruction, 'regs_access'):
|
||||
(regs_read, regs_write) = instruction.regs_access()
|
||||
regs = [ instruction.reg_name(r).upper() for r in regs_read ]
|
||||
regs.append('R15')
|
||||
else:
|
||||
regs = self.canonical_registers
|
||||
|
||||
logger.debug("Emulator wants this regs %r", regs)
|
||||
for reg in regs:
|
||||
value = cpu.read_register(reg)
|
||||
if issymbolic(value):
|
||||
raise ConcretizeRegister(reg, "Passing control to emulator") #FIXME improve exception to handle multiple registers at a time
|
||||
reg_values[reg] = value
|
||||
|
||||
logger.info("Emulator wants this regs %r", reg_values)
|
||||
return reg_values
|
||||
|
||||
|
||||
# Flags that are the result of arithmetic instructions. Unconditionally
|
||||
# set, but conditionally committed.
|
||||
#
|
||||
@@ -855,3 +835,11 @@ class Armv7Cpu(Cpu):
|
||||
def STCL(cpu, *operands):
|
||||
pass
|
||||
|
||||
@instruction
|
||||
def DMB(cpu, *operands):
|
||||
'''
|
||||
Used by the the __kuser_dmb ARM Linux user-space handler. This is a nop
|
||||
under Manticore's memory and execution model.
|
||||
'''
|
||||
pass
|
||||
|
||||
|
||||
@@ -771,32 +771,6 @@ class X86Cpu(Cpu):
|
||||
name = OP_NAME_MAP.get(name, name)
|
||||
return name
|
||||
|
||||
|
||||
def _concretize_registers(cpu, instruction):
|
||||
reg_values = {}
|
||||
if hasattr(instruction, 'regs_access'):
|
||||
(regs_read, regs_write) = instruction.regs_access()
|
||||
regs = [ str(instruction.reg_name(r).upper()) for r in regs_read ]
|
||||
|
||||
else:
|
||||
# TODO: only concretize registers the instruction touches
|
||||
if cpu.mode == CS_MODE_64:
|
||||
regs = ('RAX', 'RCX', 'RDX', 'RBX', 'RSP', 'RBP', 'RSI', 'RDI', 'R8', 'R9', 'R10', 'R11', 'R12', 'R13', 'R14', 'R15', 'RIP', 'YMM0', 'YMM1', 'YMM2', 'YMM3', 'YMM4', 'YMM5', 'YMM6', 'YMM7', 'YMM8', 'YMM9', 'YMM10', 'YMM11', 'YMM12', 'YMM13', 'YMM14', 'YMM15')
|
||||
else:
|
||||
regs = ('EAX', 'ECX', 'EDX', 'EBX', 'ESP', 'EBP', 'ESI', 'EDI', 'EIP', 'XMM0', 'XMM1', 'XMM2', 'XMM3', 'XMM4', 'XMM5', 'XMM6', 'XMM7')
|
||||
|
||||
regs += ('FPSW', 'FPCW', 'FPTAG', 'FP0', 'FP1', 'FP2', 'FP3', 'FP4', 'FP5', 'FP6', 'FP7')
|
||||
|
||||
for reg in regs:
|
||||
value = cpu.read_register(reg)
|
||||
if issymbolic(value):
|
||||
raise ConcretizeRegister(reg, "Passing control to emulator")
|
||||
reg_values[reg] = value
|
||||
|
||||
logger.info("Emulator wants this regs %r", reg_values)
|
||||
return reg_values
|
||||
|
||||
|
||||
################################################3
|
||||
# instruction implementation
|
||||
|
||||
|
||||
@@ -440,7 +440,7 @@ class Executor(object):
|
||||
|
||||
output.write("CPU:\n{}".format(cpu))
|
||||
|
||||
if hasattr(cpu, "instruction"):
|
||||
if hasattr(cpu, "instruction") and cpu.instruction is not None:
|
||||
i = cpu.instruction
|
||||
output.write(" Instruction: 0x%x\t(%s %s)\n" %(i.address, i.mnemonic, i.op_str))
|
||||
else:
|
||||
@@ -716,7 +716,7 @@ class Executor(object):
|
||||
del vals, symbolic, setstate
|
||||
|
||||
except SymbolicMemoryException as e:
|
||||
logger.error('SymbolicMemoryException at PC: 0x%16x. Cause: %s', current_state.current.PC, e.cause)
|
||||
logger.error('SymbolicMemoryException at PC: 0x%16x. Cause: %s', current_state.PC, e.cause)
|
||||
logger.info('Constraint for crashing! %s', e.constraint)
|
||||
|
||||
children = []
|
||||
|
||||
@@ -583,7 +583,7 @@ class Memory(object):
|
||||
#remove m from the maps set
|
||||
self._maps.remove(m)
|
||||
|
||||
def _get(self, address):
|
||||
def map_containing(self, address):
|
||||
'''
|
||||
Returns the L{MMap} object containing the address.
|
||||
@rtype: L{MMap}
|
||||
@@ -592,7 +592,10 @@ class Memory(object):
|
||||
|
||||
@todo: symbolic address
|
||||
'''
|
||||
return self._page2map[self._page(address)]
|
||||
page_offset = self._page(address)
|
||||
if page_offset not in self._page2map:
|
||||
raise MemoryException("Page not mapped", address)
|
||||
return self._page2map[page_offset]
|
||||
|
||||
|
||||
def mappings(self):
|
||||
@@ -699,7 +702,7 @@ class Memory(object):
|
||||
# get the more restrictive set of perms for the range
|
||||
raise NotImplementedError('No perms for slices')
|
||||
else:
|
||||
return self._get(index).perms
|
||||
return self.map_containing(index).perms
|
||||
|
||||
def access_ok(self, index, access):
|
||||
if isinstance(index, slice):
|
||||
@@ -709,7 +712,7 @@ class Memory(object):
|
||||
while addr < index.stop:
|
||||
if addr not in self:
|
||||
return False
|
||||
m = self._get(addr)
|
||||
m = self.map_containing(addr)
|
||||
size = min(m.end-addr, index.stop-addr)
|
||||
|
||||
if not m.access_ok(access):
|
||||
@@ -720,7 +723,7 @@ class Memory(object):
|
||||
else:
|
||||
if index not in self:
|
||||
return False
|
||||
m = self._get(index)
|
||||
m = self.map_containing(index)
|
||||
return m.access_ok(access)
|
||||
|
||||
#write and read potentially symbolic bytes at symbolic indexes
|
||||
@@ -734,7 +737,7 @@ class Memory(object):
|
||||
stop = addr+size
|
||||
p = addr
|
||||
while p < stop:
|
||||
m = self._get(p)
|
||||
m = self.map_containing(p)
|
||||
|
||||
_size = min(m.end-p, stop-p)
|
||||
result += m[p:p+_size]
|
||||
@@ -754,7 +757,7 @@ class Memory(object):
|
||||
stop = addr + size
|
||||
start = addr
|
||||
while addr < stop:
|
||||
m = self._get(addr)
|
||||
m = self.map_containing(addr)
|
||||
size = min(m.end-addr, stop-addr)
|
||||
m[addr:addr+size] = buf[addr-start:addr-start+size]
|
||||
addr+=size
|
||||
@@ -886,7 +889,7 @@ class SMemory(Memory):
|
||||
#Given ALL solutions for the symbolic address
|
||||
for base in solutions:
|
||||
addr_value = base + offset
|
||||
byte = Operators.ORD(self._get(addr_value)[addr_value])
|
||||
byte = Operators.ORD(self.map_containing(addr_value)[addr_value])
|
||||
if addr_value in self._symbols:
|
||||
for condition, value in self._symbols[addr_value]:
|
||||
byte = Operators.ITEBV(8, condition, Operators.ORD(value), byte)
|
||||
|
||||
@@ -356,6 +356,7 @@ class BitVec(Expression):
|
||||
def Bool(self):
|
||||
return self != 0
|
||||
|
||||
|
||||
class BitVecVariable(BitVec, Variable):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(BitVecVariable, self).__init__(*args, **kwargs)
|
||||
@@ -605,20 +606,9 @@ class ArrayStore(ArrayOperation):
|
||||
class ArrayProxy(ArrayVariable):
|
||||
def __init__(self, array):
|
||||
assert isinstance(array, ArrayVariable)
|
||||
super(ArrayProxy, self).__init__(array.index_bits, array.index_max, array.name)
|
||||
self._array = array
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._array.name
|
||||
|
||||
@property
|
||||
def index_bits(self):
|
||||
return self._array._index_bits
|
||||
|
||||
@property
|
||||
def index_max(self):
|
||||
return self._array._index_max
|
||||
|
||||
@property
|
||||
def operands(self):
|
||||
return self._array.operands
|
||||
|
||||
@@ -176,16 +176,18 @@ class State(object):
|
||||
def concretize(self, symbolic, policy, maxcount=100):
|
||||
vals = []
|
||||
if policy == 'MINMAX':
|
||||
vals = solver.minmax(self.constraints, symbolic)
|
||||
vals = self._solver.minmax(self.constraints, symbolic)
|
||||
elif policy == 'SAMPLED':
|
||||
m, M = solver.minmax(self.constraints, symbolic)
|
||||
m, M = self._solver.minmax(self.constraints, symbolic)
|
||||
vals += [m, M]
|
||||
if M - m > 3:
|
||||
if solver.can_be_true(self.constraints, symbolic == (m + M) / 2):
|
||||
if self._solver.can_be_true(self.constraints, symbolic == (m + M) / 2):
|
||||
vals.append((m + M) / 2)
|
||||
if M - m > 100:
|
||||
vals += solver.get_all_values(self.constraints, symbolic, maxcnt=maxcount,
|
||||
silent=True)
|
||||
vals += self._solver.get_all_values(self.constraints, symbolic,
|
||||
maxcnt=maxcount, silent=True)
|
||||
elif policy == 'ONE':
|
||||
vals = [self._solver.get_value(self.constraints, symbolic)]
|
||||
else:
|
||||
assert policy == 'ALL'
|
||||
vals = solver.get_all_values(self.constraints, symbolic, maxcnt=maxcount,
|
||||
|
||||
+87
-38
@@ -361,8 +361,8 @@ class Linux(object):
|
||||
state['auxv'] = self.auxv
|
||||
state['program'] = self.program
|
||||
state['syscall_arg_regs'] = self.syscall_arg_regs
|
||||
if hasattr(self, 'tls_value'):
|
||||
state['tls_value'] = self.tls_value
|
||||
if hasattr(self, '_arm_tls_memory'):
|
||||
state['_arm_tls_memory'] = self._arm_tls_memory
|
||||
return state
|
||||
|
||||
def __setstate__(self, state):
|
||||
@@ -409,8 +409,8 @@ class Linux(object):
|
||||
self.auxv = state['auxv']
|
||||
self.program = state['program']
|
||||
self.syscall_arg_regs = state['syscall_arg_regs']
|
||||
if 'tls_value' in state:
|
||||
self.tls_value = state['tls_value']
|
||||
if '_arm_tls_memory' in state:
|
||||
self._arm_tls_memory = state['_arm_tls_memory']
|
||||
|
||||
def _read_string(self, cpu, buf):
|
||||
"""
|
||||
@@ -425,6 +425,85 @@ class Linux(object):
|
||||
filename += c
|
||||
return filename
|
||||
|
||||
def _init_arm_kernel_helpers(self, cpu):
|
||||
'''
|
||||
ARM kernel helpers
|
||||
|
||||
https://www.kernel.org/doc/Documentation/arm/kernel_user_helpers.txt
|
||||
'''
|
||||
|
||||
page_data = bytearray('\xf1\xde\xfd\xe7' * 1024)
|
||||
|
||||
# Extracted from a RPi2
|
||||
preamble = (
|
||||
'ff0300ea' +
|
||||
'650400ea' +
|
||||
'f0ff9fe5' +
|
||||
'430400ea' +
|
||||
'220400ea' +
|
||||
'810400ea' +
|
||||
'000400ea' +
|
||||
'870400ea'
|
||||
).decode('hex')
|
||||
|
||||
# XXX(yan): The following implementations of cmpxchg and cmpxchg64 were
|
||||
# handwritten to not use any exclusive instructions (e.g. ldrexd) or
|
||||
# locking. For actual implementations, refer to
|
||||
# arch/arm64/kernel/kuser32.S in the Linux source code.
|
||||
__kuser_cmpxchg64 = (
|
||||
'30002de9' + # push {r4, r5}
|
||||
'08c09de5' + # ldr ip, [sp, #8]
|
||||
'30009ce8' + # ldm ip, {r4, r5}
|
||||
'010055e1' + # cmp r5, r1
|
||||
'00005401' + # cmpeq r4, r0
|
||||
'0100a013' + # movne r0, #1
|
||||
'0000a003' + # moveq r0, #0
|
||||
'0c008c08' + # stmeq ip, {r2, r3}
|
||||
'3000bde8' + # pop {r4, r5}
|
||||
'1eff2fe1' # bx lr
|
||||
).decode('hex')
|
||||
|
||||
__kuser_dmb = (
|
||||
'5bf07ff5' + # dmb ish
|
||||
'1eff2fe1' # bx lr
|
||||
).decode('hex')
|
||||
|
||||
__kuser_cmpxchg = (
|
||||
'003092e5' + # ldr r3, [r2]
|
||||
'000053e1' + # cmp r3, r0
|
||||
'0000a003' + # moveq r0, #0
|
||||
'00108205' + # streq r1, [r2]
|
||||
'0100a013' + # movne r0, #1
|
||||
'1eff2fe1' # bx lr
|
||||
).decode('hex')
|
||||
|
||||
# Map a TLS segment
|
||||
self._arm_tls_memory = cpu.memory.mmap(None, 4, 'rw ')
|
||||
|
||||
__kuser_get_tls = (
|
||||
'04009FE5' + # ldr r0, [pc, #4]
|
||||
'010090e8' + # ldm r0, {r0}
|
||||
'1eff2fe1' # bx lr
|
||||
).decode('hex') + struct.pack('<I', self._arm_tls_memory)
|
||||
|
||||
tls_area = '\x00'*12
|
||||
|
||||
version = struct.pack('<I', 5)
|
||||
|
||||
def update(address, code):
|
||||
page_data[address:address+len(code)] = code
|
||||
|
||||
# Offsets from Documentation/arm/kernel_user_helpers.txt in Linux
|
||||
update(0x000, preamble)
|
||||
update(0xf60, __kuser_cmpxchg64)
|
||||
update(0xfa0, __kuser_dmb)
|
||||
update(0xfc0, __kuser_cmpxchg)
|
||||
update(0xfe0, __kuser_get_tls)
|
||||
update(0xff0, tls_area)
|
||||
update(0xffc, version)
|
||||
|
||||
cpu.memory.mmap(0xffff0000, len(page_data), 'r x', page_data)
|
||||
|
||||
def load_vdso(self, bits):
|
||||
#load vdso #TODO or #IGNORE
|
||||
vdso_top = {32: 0x7fff0000, 64: 0x7fff00007fff0000}[bits]
|
||||
@@ -1090,7 +1169,8 @@ class Linux(object):
|
||||
return 1000
|
||||
|
||||
def sys_ARM_NR_set_tls(self, cpu, val):
|
||||
self.tls_value = val
|
||||
if hasattr(self, '_arm_tls_memory'):
|
||||
cpu.write_int(self._arm_tls_memory, val)
|
||||
return 0
|
||||
|
||||
#Signals..
|
||||
@@ -1600,37 +1680,6 @@ class Linux(object):
|
||||
self.procs[procid].PC += self.procs[procid].instruction.size
|
||||
self.awake(procid)
|
||||
|
||||
def handleInvalidPC(self, e):
|
||||
#FIXME THIS IS ARM SPECIFIC
|
||||
cpu = self.current
|
||||
if cpu.PC == self.ARM_GET_TLS:
|
||||
if hasattr(self, 'tls_value'):
|
||||
cpu.regfile.write('R0', self.tls_value)
|
||||
elif cpu.PC == self.ARM_CMPXCHG:
|
||||
oldval = cpu.regfile.read('R0')
|
||||
newval = cpu.regfile.read('R1')
|
||||
ptr = cpu.regfile.read('R2')
|
||||
|
||||
existing = cpu.read_int(ptr, cpu.address_bit_size)
|
||||
ret = 1
|
||||
if existing == oldval:
|
||||
ret = 0
|
||||
cpu.regfile.write('APSR_C', 1)
|
||||
cpu.write_int(ptr, newval, cpu.address_bit_size)
|
||||
|
||||
cpu.regfile.write('R0', ret)
|
||||
elif cpu.PC == self.ARM_MEM_BARRIER:
|
||||
# Apply any needed memory barrier to preserve consistency with data
|
||||
# modified manually and __kuser_cmpxchg usage. Nop in our case, just
|
||||
# return
|
||||
pass
|
||||
else:
|
||||
raise e
|
||||
|
||||
# Return normally
|
||||
lr = cpu.regfile.read('R14') # 'LR'
|
||||
cpu.PC = lr
|
||||
|
||||
|
||||
def execute(self):
|
||||
"""
|
||||
@@ -1657,8 +1706,6 @@ class Linux(object):
|
||||
syscallret = self.syscall(self.current)
|
||||
except RestartSyscall:
|
||||
pass
|
||||
except InvalidPCException, e:
|
||||
self.handleInvalidPC(e)
|
||||
|
||||
return True
|
||||
|
||||
@@ -1750,6 +1797,8 @@ class Linux(object):
|
||||
self.syscall_arg_regs = ['EBX', 'ECX', 'EDX', 'ESI', 'EDI', 'EBP']
|
||||
elif arch == 'armv7':
|
||||
self.syscall_arg_regs = ['R0', 'R1', 'R2', 'R3', 'R4', 'R5', 'R6']
|
||||
self._init_arm_kernel_helpers(cpu)
|
||||
|
||||
|
||||
def _arch_reg_init(self, cpu, arch):
|
||||
if arch in {'i386', 'amd64'}:
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
|
||||
import logging
|
||||
import inspect
|
||||
|
||||
from ..core.memory import MemoryException, FileMap, AnonMap
|
||||
|
||||
from .helpers import issymbolic
|
||||
######################################################################
|
||||
# Abstract classes for capstone/unicorn based cpus
|
||||
# no emulator by default
|
||||
from unicorn import *
|
||||
from unicorn.x86_const import *
|
||||
from unicorn.arm_const import *
|
||||
|
||||
from capstone import *
|
||||
from capstone.arm import *
|
||||
from capstone.x86 import *
|
||||
|
||||
logger = logging.getLogger("EMULATOR")
|
||||
|
||||
class UnicornEmulator(object):
|
||||
'''
|
||||
Helper class to emulate a single instruction via Unicorn.
|
||||
'''
|
||||
def __init__(self, cpu):
|
||||
self._cpu = cpu
|
||||
|
||||
text = cpu.memory.map_containing(cpu.PC)
|
||||
# Keep track of all memory mappings. We start with just the text section
|
||||
self._should_be_mapped = {
|
||||
text.start: (len(text), UC_PROT_READ | UC_PROT_EXEC)
|
||||
}
|
||||
|
||||
# Keep track of all the memory Unicorn needs while executing this
|
||||
# instruction
|
||||
self._should_be_written = {}
|
||||
|
||||
def reset(self):
|
||||
self._emu = self._unicorn()
|
||||
self._to_raise = None
|
||||
|
||||
def _unicorn(self):
|
||||
if self._cpu.arch == CS_ARCH_ARM:
|
||||
return Uc(UC_ARCH_ARM, UC_MODE_ARM)
|
||||
elif self._cpu.arch == CS_ARCH_X86:
|
||||
if self._cpu.mode == CS_MODE_32:
|
||||
return Uc(UC_ARCH_X86, UC_MODE_32)
|
||||
elif self._cpu.mode == CS_MODE_64:
|
||||
return Uc(UC_ARCH_X86, UC_MODE_64)
|
||||
|
||||
raise RuntimeError("Unsupported architecture")
|
||||
|
||||
|
||||
def _create_emulated_mapping(self, uc, address):
|
||||
'''
|
||||
Create a mapping in Unicorn and note that we'll need it if we retry.
|
||||
|
||||
:param uc: The Unicorn instance.
|
||||
:param address: The address which is contained by the mapping.
|
||||
:rtype Map
|
||||
'''
|
||||
|
||||
m = self._cpu.memory.map_containing(address)
|
||||
|
||||
permissions = UC_PROT_NONE
|
||||
if 'r' in m.perms:
|
||||
permissions |= UC_PROT_READ
|
||||
if 'w' in m.perms:
|
||||
permissions |= UC_PROT_WRITE
|
||||
if 'x' in m.perms:
|
||||
permissions |= UC_PROT_EXEC
|
||||
|
||||
uc.mem_map(m.start, len(m), permissions)
|
||||
|
||||
self._should_be_mapped[m.start] = (len(m), permissions)
|
||||
|
||||
return m
|
||||
|
||||
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_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)
|
||||
|
||||
|
||||
def _hook_xfer_mem(self, uc, access, address, size, value, data):
|
||||
'''
|
||||
Handle memory operations from unicorn.
|
||||
'''
|
||||
assert access in (UC_MEM_WRITE, UC_MEM_READ, UC_MEM_FETCH)
|
||||
|
||||
if access == UC_MEM_WRITE:
|
||||
self._cpu.write_int(address, value, size*8)
|
||||
|
||||
# If client code is attempting to read a value, we need to bring it
|
||||
# in from Manticore state. If we try to mem_write it here, Unicorn
|
||||
# will segfault. We add the value to a list of things that need to
|
||||
# be written, and ask to restart the emulation.
|
||||
elif access == UC_MEM_READ:
|
||||
value = self._cpu.read_bytes(address, size)
|
||||
|
||||
if address in self._should_be_written:
|
||||
return True
|
||||
|
||||
self._should_be_written[address] = value
|
||||
|
||||
self._should_try_again = True
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def _hook_unmapped(self, uc, access, address, size, value, data):
|
||||
'''
|
||||
We hit an unmapped region; map it into unicorn.
|
||||
'''
|
||||
|
||||
try:
|
||||
m = self._create_emulated_mapping(uc, address)
|
||||
except MemoryException as e:
|
||||
self._to_raise = e
|
||||
self._should_try_again = False
|
||||
return False
|
||||
|
||||
self._should_try_again = True
|
||||
return False
|
||||
|
||||
def _interrupt(self, uc, number, data):
|
||||
'''
|
||||
Handle software interrupt (SVC/INT)
|
||||
'''
|
||||
|
||||
from ..core.cpu.abstractcpu import Interruption
|
||||
self._to_raise = Interruption(number)
|
||||
return True
|
||||
|
||||
def _to_unicorn_id(self, reg_name):
|
||||
# TODO(felipe, yan): Register naming is broken in current unicorn
|
||||
# packages, but works on unicorn git's master. We leave this hack
|
||||
# in until unicorn gets updated.
|
||||
if unicorn.__version__ <= '1.0.0' and reg_name == 'APSR':
|
||||
reg_name = 'CPSR'
|
||||
if self._cpu.arch == CS_ARCH_ARM:
|
||||
return globals()['UC_ARM_REG_' + reg_name]
|
||||
elif self._cpu.arch == CS_ARCH_X86:
|
||||
# TODO(yan): This needs to handle AF regiseter
|
||||
return globals()['UC_X86_REG_' + reg_name]
|
||||
else:
|
||||
# TODO(yan): raise a more appropriate exception
|
||||
raise TypeError
|
||||
|
||||
def emulate(self, instruction):
|
||||
'''
|
||||
Emulate a single 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)
|
||||
|
||||
for address, values in self._should_be_written.items():
|
||||
for offset, byte in enumerate(values, start=address):
|
||||
if issymbolic(byte):
|
||||
from ..core.cpu.abstractcpu import ConcretizeMemory
|
||||
raise ConcretizeMemory(offset, 8,
|
||||
"Concretizing for emulation")
|
||||
|
||||
self._emu.mem_write(address, ''.join(values))
|
||||
|
||||
# Try emulation
|
||||
self._should_try_again = False
|
||||
|
||||
self._step(instruction)
|
||||
|
||||
if not self._should_try_again:
|
||||
break
|
||||
|
||||
|
||||
def _step(self, instruction):
|
||||
'''
|
||||
A single attempt at executing an instruction.
|
||||
'''
|
||||
|
||||
registers = set(self._cpu.canonical_registers)
|
||||
|
||||
# Refer to EFLAGS instead of individual flags for x86
|
||||
if self._cpu.arch == CS_ARCH_X86:
|
||||
# The last 8 canonical registers of x86 are individual flags; replace
|
||||
# with the eflags
|
||||
registers -= set(['CF','PF','AF','ZF','SF','IF','DF','OF'])
|
||||
registers.add('EFLAGS')
|
||||
|
||||
# XXX(yan): This concretizes the entire register state. This is overly
|
||||
# aggressive. Once capstone adds consistent support for accessing
|
||||
# referred registers, make this only concretize those registers being
|
||||
# read from.
|
||||
for reg in registers:
|
||||
val = self._cpu.read_register(reg)
|
||||
if issymbolic(val):
|
||||
from ..core.cpu.abstractcpu import ConcretizeRegister
|
||||
raise ConcretizeRegister(reg, "Concretizing for emulation.",
|
||||
policy='ONE')
|
||||
self._emu.reg_write(self._to_unicorn_id(reg), val)
|
||||
|
||||
# Bring in the instruction itself
|
||||
text_bytes = self._cpu.read_bytes(self._cpu.PC, instruction.size)
|
||||
self._emu.mem_write(self._cpu.PC, ''.join(text_bytes))
|
||||
|
||||
self._emu.hook_add(UC_HOOK_MEM_READ_UNMAPPED, self._hook_unmapped)
|
||||
self._emu.hook_add(UC_HOOK_MEM_WRITE_UNMAPPED, self._hook_unmapped)
|
||||
self._emu.hook_add(UC_HOOK_MEM_FETCH_UNMAPPED, self._hook_unmapped)
|
||||
self._emu.hook_add(UC_HOOK_MEM_READ, self._hook_xfer_mem)
|
||||
self._emu.hook_add(UC_HOOK_MEM_WRITE, self._hook_xfer_mem)
|
||||
self._emu.hook_add(UC_HOOK_INTR, self._interrupt)
|
||||
|
||||
saved_PC = self._cpu.PC
|
||||
|
||||
try:
|
||||
self._emu.emu_start(self._cpu.PC, self._cpu.PC+instruction.size, count=1)
|
||||
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
|
||||
if not self._should_try_again:
|
||||
raise
|
||||
|
||||
if self._should_try_again:
|
||||
return
|
||||
|
||||
if logger.isEnabledFor(logging.DEBUG):
|
||||
logger.debug("="*10)
|
||||
for register in self._cpu.canonical_registers:
|
||||
logger.debug("Register % 3s Manticore: %08x, Unicorn %08x",
|
||||
register, self._cpu.read_register(register),
|
||||
self._emu.reg_read(self._to_unicorn_id(register)) )
|
||||
logger.debug(">"*10)
|
||||
|
||||
# Bring back Unicorn registers to Manticore
|
||||
for reg in registers:
|
||||
val = self._emu.reg_read(self._to_unicorn_id(reg))
|
||||
self._cpu.write_register(reg, val)
|
||||
|
||||
#Unicorn hack. On single step unicorn wont advance the PC register
|
||||
mu_pc = self.get_unicorn_pc()
|
||||
if saved_PC == mu_pc:
|
||||
self._cpu.PC = saved_PC + instruction.size
|
||||
|
||||
# Raise the exception from a hook that Unicorn would have eaten
|
||||
if self._to_raise:
|
||||
raise self._to_raise
|
||||
|
||||
return
|
||||
|
||||
+17
-13
@@ -2,8 +2,7 @@ import unittest
|
||||
import struct
|
||||
from functools import wraps
|
||||
|
||||
from manticore.core.cpu.arm import Armv7Cpu as cpu
|
||||
from manticore.core.cpu.arm import *
|
||||
from manticore.core.cpu.arm import Armv7Cpu as Cpu, Mask, Interruption
|
||||
from manticore.core.memory import Memory32
|
||||
|
||||
from capstone.arm import *
|
||||
@@ -25,7 +24,7 @@ def assemble(asm):
|
||||
|
||||
class Armv7CpuTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.c = cpu(Memory32(), 'armv7')
|
||||
self.c = Cpu(Memory32(), 'armv7')
|
||||
self.rf = self.c.regfile
|
||||
self._setupStack()
|
||||
|
||||
@@ -121,7 +120,6 @@ def itest_setregs(*preds):
|
||||
|
||||
return instr_dec
|
||||
|
||||
|
||||
def itest_custom(asm):
|
||||
def instr_dec(custom_func):
|
||||
@wraps(custom_func)
|
||||
@@ -136,7 +134,7 @@ def itest_custom(asm):
|
||||
|
||||
class Armv7CpuInstructions(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.cpu = cpu(Memory32(), 'armv7')
|
||||
self.cpu = Cpu(Memory32(), 'armv7')
|
||||
self.mem = self.cpu.memory
|
||||
self.rf = self.cpu.regfile
|
||||
|
||||
@@ -144,8 +142,9 @@ class Armv7CpuInstructions(unittest.TestCase):
|
||||
self.code = self.mem.mmap(0x1000, 0x1000, 'rwx')
|
||||
self.data = self.mem.mmap(0xd000, 0x1000, 'rw')
|
||||
self.stack = self.mem.mmap(0xf000, 0x1000, 'rw')
|
||||
self.mem.write(self.code, assemble(asm))
|
||||
self.rf.write('PC', self.code)
|
||||
start = self.code + 4
|
||||
self.mem.write(start, assemble(asm))
|
||||
self.rf.write('PC', start)
|
||||
self.rf.write('SP', self.stack + 0x1000)
|
||||
|
||||
def _checkFlagsNZCV(self, n, z, c, v):
|
||||
@@ -1009,16 +1008,17 @@ class Armv7CpuInstructions(unittest.TestCase):
|
||||
self.assertEqual(self.cpu.read_int(addr + 8, self.cpu.address_bit_size), 4)
|
||||
|
||||
@itest_custom("bx r1")
|
||||
@itest_setregs("R1=0x1004")
|
||||
@itest_setregs("R1=0x1008")
|
||||
def test_bx_basic(self):
|
||||
self.cpu.execute()
|
||||
self.assertEqual(self.rf.read('PC'), 0x1004)
|
||||
self.assertEqual(self.rf.read('PC'), 0x1008)
|
||||
|
||||
@itest_custom("bx r1")
|
||||
@itest_setregs("R1=0x1005")
|
||||
@itest_setregs("R1=0x1009")
|
||||
def test_bx_thumb(self):
|
||||
pre_pc = self.rf.read('PC')
|
||||
self.cpu.execute()
|
||||
self.assertEqual(self.rf.read('PC'), 0x1004)
|
||||
self.assertEqual(self.rf.read('PC'), pre_pc + 4)
|
||||
|
||||
# ORR
|
||||
|
||||
@@ -1300,13 +1300,13 @@ class Armv7CpuInstructions(unittest.TestCase):
|
||||
@itest("BLX R1")
|
||||
def test_blx_reg(self):
|
||||
self.assertEqual(self.rf.read('PC'), 0x1008)
|
||||
self.assertEqual(self.rf.read('LR'), 0x1004)
|
||||
self.assertEqual(self.rf.read('LR'), 0x1008)
|
||||
|
||||
@itest_setregs("R1=0x1009")
|
||||
@itest("BLX R1")
|
||||
def test_blx_reg_thumb(self):
|
||||
self.assertEqual(self.rf.read('PC'), 0x1008)
|
||||
self.assertEqual(self.rf.read('LR'), 0x1004)
|
||||
self.assertEqual(self.rf.read('LR'), 0x1008)
|
||||
|
||||
@itest_setregs("R1=0xffffffff", "R2=2")
|
||||
@itest("UMULLS R1, R2, R1, R2")
|
||||
@@ -1348,3 +1348,7 @@ class Armv7CpuInstructions(unittest.TestCase):
|
||||
self.assertEqual(self.rf.read('R2'), (mul >> 32) & Mask(32))
|
||||
self._checkFlagsNZCV(0, 1, pre_c, pre_v)
|
||||
|
||||
@itest("dmb ish")
|
||||
def test_dmb(self):
|
||||
# This is a nop, ensure that the instruction exists
|
||||
self.assertTrue(True)
|
||||
|
||||
@@ -273,8 +273,8 @@ class SymCPUTest(unittest.TestCase):
|
||||
self.cpu.CF = 1
|
||||
self.cpu.AF = 1
|
||||
|
||||
a = BitVecConstant(32, 1).Bool()
|
||||
b = BitVecConstant(32, 0).Bool()
|
||||
a = BitVecConstant(32, 1) != 0
|
||||
b = BitVecConstant(32, 0) != 0
|
||||
self.cpu.ZF = a
|
||||
self.cpu.SF = b
|
||||
|
||||
|
||||
@@ -60,6 +60,14 @@ class StateTest(unittest.TestCase):
|
||||
self.state.add(expr < 100)
|
||||
solved = self.state.solve_n(expr, 5)
|
||||
self.assertEqual(len(solved), 5)
|
||||
|
||||
def test_policy_one(self):
|
||||
expr = BitVecVariable(32, 'tmp')
|
||||
self.state.add(expr > 0)
|
||||
self.state.add(expr < 100)
|
||||
solved = self.state.concretize(expr, 'ONE')
|
||||
self.assertEqual(len(solved), 1)
|
||||
self.assertIn(solved[0], xrange(100))
|
||||
|
||||
def test_state(self):
|
||||
constraints = ConstraintSet()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user