import copy
import re
from pyvex.const import U1, get_type_size, ty_to_const_class, vex_int_class
from pyvex.enums import IRCallee
from pyvex.expr import ITE, Binop, CCall, Const, Get, Load, RdTmp, Unop
from pyvex.stmt import Dirty, Exit, IMark, NoOp, Put, Store, WrTmp
[docs]
class JumpKind:
Boring = "Ijk_Boring"
Call = "Ijk_Call"
Ret = "Ijk_Ret"
Segfault = "Ijk_SigSEGV"
Exit = "Ijk_Exit"
Syscall = "Ijk_Sys_syscall"
Sysenter = "Ijk_Sys_sysenter"
Invalid = "Ijk_INVALID"
NoDecode = "Ijk_NoDecode"
[docs]
class Type(metaclass=TypeMeta):
__metaclass__ = TypeMeta
ieee_float_16 = "Ity_F16"
ieee_float_32 = "Ity_F32"
ieee_float_64 = "Ity_F64"
ieee_float_128 = "Ity_F128"
decimal_float_32 = "Ity_D32"
decimal_float_64 = "Ity_D64"
decimal_float_128 = "Ity_D128"
simd_vector_128 = "Ity_V128"
simd_vector_256 = "Ity_V256"
[docs]
def mkbinop(fstring):
return lambda self, expr_a, expr_b: self.op_binary(make_format_op_generator(fstring))(expr_a, expr_b)
[docs]
def mkunop(fstring):
return lambda self, expr_a: self.op_unary(make_format_op_generator(fstring))(expr_a)
[docs]
def mkcmpop(fstring_fragment, signedness=""):
def cmpop(self, expr_a, expr_b):
ty = self.get_type(expr_a)
fstring = f"Iop_Cmp{fstring_fragment}{{arg_t[0]}}{signedness}"
retval = mkbinop(fstring)(self, expr_a, expr_b)
return self.cast_to(retval, ty)
return cmpop
[docs]
class IRSBCustomizer:
op_add = mkbinop("Iop_Add{arg_t[0]}")
op_sub = mkbinop("Iop_Sub{arg_t[0]}")
op_umul = mkbinop("Iop_Mul{arg_t[0]}")
op_smul = mkbinop("Iop_MullS{arg_t[0]}")
op_sdiv = mkbinop("Iop_DivS{arg_t[0]}")
op_udiv = mkbinop("Iop_DivU{arg_t[0]}")
# Custom operation that does not exist in libVEX
op_mod = mkbinop("Iop_Mod{arg_t[0]}")
op_or = mkbinop("Iop_Or{arg_t[0]}")
op_and = mkbinop("Iop_And{arg_t[0]}")
op_xor = mkbinop("Iop_Xor{arg_t[0]}")
op_shr = mkbinop("Iop_Shr{arg_t[0]}") # Shift Right (logical)
op_shl = mkbinop("Iop_Shl{arg_t[0]}") # Shift Left (logical)
op_sar = mkbinop("Iop_Sar{arg_t[0]}") # Shift Arithmetic Right operation
op_not = mkunop("Iop_Not{arg_t[0]}")
op_cmp_eq = mkcmpop("EQ")
op_cmp_ne = mkcmpop("NE")
op_cmp_slt = mkcmpop("LT", "S")
op_cmp_sle = mkcmpop("LE", "S")
op_cmp_ult = mkcmpop("LT", "U")
op_cmp_ule = mkcmpop("LE", "U")
op_cmp_sge = mkcmpop("GE", "S")
op_cmp_uge = mkcmpop("GE", "U")
op_cmp_sgt = mkcmpop("GT", "S")
op_cmp_ugt = mkcmpop("GT", "U")
[docs]
def __init__(self, irsb):
self.arch = irsb.arch
self.irsb = irsb
[docs]
def get_type(self, rdt):
return rdt.result_type(self.irsb.tyenv)
# Statements (no return value)
def _append_stmt(self, stmt):
self.irsb.statements += [stmt]
[docs]
def imark(self, int_addr, int_length, int_delta=0):
self._append_stmt(IMark(int_addr, int_length, int_delta))
[docs]
def get_reg(self, regname): # TODO move this into the lifter
return self.arch.registers[regname][0]
[docs]
def put(self, expr_val, tuple_reg):
self._append_stmt(Put(copy.copy(expr_val), tuple_reg))
[docs]
def store(self, addr, expr):
self._append_stmt(Store(copy.copy(addr), copy.copy(expr), self.arch.memory_endness))
[docs]
def noop(self):
self._append_stmt(NoOp())
[docs]
def add_exit(self, guard, dst, jk, ip):
"""
Add an exit out of the middle of an IRSB.
(e.g., a conditional jump)
:param guard: An expression, the exit is taken if true
:param dst: the destination of the exit (a Const)
:param jk: the JumpKind of this exit (probably Ijk_Boring)
:param ip: The address of this exit's source
"""
self.irsb.statements.append(Exit(guard, dst.con, jk, ip))
# end statements
[docs]
def goto(self, addr):
self.irsb.next = addr
self.irsb.jumpkind = JumpKind.Boring
[docs]
def ret(self, addr):
self.irsb.next = addr
self.irsb.jumpkind = JumpKind.Ret
[docs]
def call(self, addr):
self.irsb.next = addr
self.irsb.jumpkind = JumpKind.Call
def _add_tmp(self, t):
return self.irsb.tyenv.add(t)
def _rdtmp(self, tmp):
return RdTmp.get_instance(tmp)
def _settmp(self, expr):
ty = self.get_type(expr)
tmp = self._add_tmp(ty)
self._append_stmt(WrTmp(tmp, expr))
return self._rdtmp(tmp)
[docs]
def rdreg(self, reg, ty):
return self._settmp(Get(reg, ty))
[docs]
def load(self, addr, ty):
return self._settmp(Load(self.arch.memory_endness, ty, copy.copy(addr)))
[docs]
def op_ccall(self, retty, funcstr, args):
return self._settmp(CCall(retty, IRCallee(len(args), funcstr, 0xFFFF), args))
[docs]
def dirty(self, retty, funcstr, args):
if retty is None:
tmp = 0xFFFFFFFF
else:
tmp = self._add_tmp(retty)
self._append_stmt(Dirty(IRCallee(len(args), funcstr, 0xFFFF), Const(U1(1)), args, tmp, None, None, None, None))
return self._rdtmp(tmp)
[docs]
def ite(self, condrdt, iftruerdt, iffalserdt):
return self._settmp(ITE(copy.copy(condrdt), copy.copy(iffalserdt), copy.copy(iftruerdt)))
[docs]
def mkconst(self, val, ty):
cls = ty_to_const_class(ty)
return Const(cls(val))
# Operations
[docs]
def op_generic(self, Operation, op_generator):
def instance(*args): # Note: The args here are all RdTmps
for arg in args:
assert isinstance(arg, RdTmp) or isinstance(arg, Const)
arg_types = [self.get_type(arg) for arg in args]
# two operations should never share the same argument instances, copy them here to ensure that
args = [copy.copy(a) for a in args]
op = Operation(op_generator(arg_types), args)
msg = "operation needs to be well typed: " + str(op)
assert op.typecheck(self.irsb.tyenv), msg + "\ntypes: " + str(self.irsb.tyenv)
return self._settmp(op)
return instance
[docs]
def op_binary(self, op_format_str):
return self.op_generic(Binop, op_format_str)
[docs]
def op_unary(self, op_format_str):
return self.op_generic(Unop, op_format_str)
[docs]
def cast_to(self, rdt, tydest, signed=False, high=False):
goalwidth = get_type_size(tydest)
rdtwidth = self.get_rdt_width(rdt)
if rdtwidth > goalwidth:
return self.op_narrow_int(rdt, tydest, high_half=high)
elif rdtwidth < goalwidth:
return self.op_widen_int(rdt, tydest, signed=signed)
else:
return rdt
[docs]
def op_to_one_bit(self, rdt):
rdtty = self.get_type(rdt)
if rdtty not in [Type.int_64, Type.int_32]:
rdt = self.op_widen_int_unsigned(rdt, Type.int_32)
onebit = self.op_narrow_int(rdt, Type.int_1)
return onebit
[docs]
def op_narrow_int(self, rdt, tydest, high_half=False):
op_name = "{op}{high}to{dest}".format(
op="Iop_{arg_t[0]}", high="HI" if high_half else "", dest=get_op_format_from_const_ty(tydest)
)
return self.op_unary(make_format_op_generator(op_name))(rdt)
[docs]
def op_widen_int(self, rdt, tydest, signed=False):
op_name = "{op}{sign}to{dest}".format(
op="Iop_{arg_t[0]}", sign="S" if signed else "U", dest=get_op_format_from_const_ty(tydest)
)
return self.op_unary(make_format_op_generator(op_name))(rdt)
[docs]
def op_widen_int_signed(self, rdt, tydest):
return self.op_widen_int(rdt, tydest, signed=True)
[docs]
def op_widen_int_unsigned(self, rdt, tydest):
return self.op_widen_int(rdt, tydest, signed=False)
[docs]
def get_msb(self, tmp, ty):
width = get_type_size(ty)
return self.get_bit(tmp, width - 1)
[docs]
def get_bit(self, rdt, idx):
shifted = self.op_shr(rdt, idx)
bit = self.op_extract_lsb(shifted)
return bit
[docs]
def set_bit(self, rdt, idx, bval):
currbit = self.get_bit(rdt, idx)
areequalextrabits = self.op_xor(bval, currbit)
one = self.mkconst(1, self.get_type(areequalextrabits))
areequal = self.op_and(areequalextrabits, one)
shifted = self.op_shl(areequal, idx)
return self.op_xor(rdt, shifted)
[docs]
def set_bits(self, rdt, idxsandvals):
ty = self.get_type(rdt)
if all([isinstance(idx, Const) for idx, _ in idxsandvals]):
relevantbits = self.mkconst(sum([1 << idx.con.value for idx, _ in idxsandvals]), ty)
else:
relevantbits = self.mkconst(0, ty)
for idx, _ in idxsandvals:
shifted = self.op_shl(self.mkconst(1, ty), idx)
relevantbits = self.op_or(relevantbits, shifted)
setto = self.mkconst(0, ty)
for idx, bval in idxsandvals:
bvalbit = self.op_extract_lsb(bval)
shifted = self.op_shl(bvalbit, idx)
setto = self.op_or(setto, shifted)
shouldflip = self.op_and(self.op_xor(setto, rdt), relevantbits)
return self.op_xor(rdt, shouldflip)
[docs]
def get_rdt_width(self, rdt):
return rdt.result_size(self.irsb.tyenv)