use qcode::{
context::Context,
space::MemorySpaceId,
value::{
BlockId, FunctionId, InstructionId, ValueId, Varnode,
insn::{
Binary, Binop, Carry, FloatBinop, FloatToFloat, FloatToInt, Gep, InstructionRef,
IntBinop, IntToFloat, IsFloatNaN, Load, LzCount, Mnemonic, PopCount, Range, SBorrow,
SCarry, Sext, Store, Unary, Unop, Zext,
},
varnode::{VarnodeId, register::RegisterId},
},
};
mod concrete;
pub use concrete::{
BodyArg, EmulatedMemory, Emulator, EmulatorMemory, SizedValue, StandaloneEmulator,
};
#[derive(Debug, Clone)]
pub struct CallSite {
pub instruction: InstructionId,
pub block: BlockId,
pub target: FunctionId,
pub args: Vec<ValueId>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CallContinuation {
Block(BlockId),
Address(u64),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CallInterception {
PassThrough,
Handled(CallContinuation),
}
#[derive(Debug)]
pub struct EmulatorError {
pub kind: EmulatorErrorKind,
pub ctx: String,
pub address: Option<u64>,
}
impl EmulatorError {
pub fn new(kind: EmulatorErrorKind, insn: &InstructionRef<'_, '_>) -> Self {
Self {
kind,
ctx: format!(
"Instruction: {}\nBlock: {:?}\nFunction: {:?}",
insn.as_statement(),
insn.parent().map(|b| b.name()),
insn.function().map(|f| f.name())
),
address: insn.parent().and_then(|b| b.address()),
}
}
}
#[derive(Debug)]
pub enum EmulatorErrorKind {
InvalidBlockAddress(u64),
EmptyFunctionRoot(FunctionId),
UnresolvedMintedCallee(u32),
UnknownAddress(u64),
AddressOverflow(u64, usize),
MemoryReadError(u64),
MemoryWriteError(u64),
ValueError(u128),
UnknownRegister(RegisterId),
UnknownSpace(MemorySpaceId),
UnsupportedPCodeOp(Box<str>),
UnsupportedIntrinsic(Box<str>),
InterceptError(Box<str>),
StepBudgetExceeded(usize),
UnsupportedMnemonic(&'static str),
EmptyBlock(BlockId),
PoisonRead,
}
impl std::fmt::Display for EmulatorErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidBlockAddress(addr) => write!(f, "invalid block address {addr:#x}"),
Self::EmptyFunctionRoot(func) => write!(f, "function {func:?} has no root block"),
Self::UnresolvedMintedCallee(slot) => {
write!(f, "minted callee placeholder #{slot} is not executable")
}
Self::UnknownAddress(addr) => write!(f, "unknown address {addr:#x}"),
Self::AddressOverflow(addr, size) => {
write!(f, "address overflow at {addr:#x} with size {size}")
}
Self::MemoryReadError(addr) => write!(f, "memory read error at address {addr:#x}"),
Self::MemoryWriteError(addr) => write!(f, "memory write error at address {addr:#x}"),
Self::ValueError(value) => write!(f, "value {value} is too large to represent"),
Self::UnknownRegister(reg) => write!(f, "register {reg:?} not found in context"),
Self::UnknownSpace(space) => write!(f, "memory space {space:?} not initialised"),
Self::UnsupportedPCodeOp(op) => write!(f, "unsupported p-code operation `{op}`"),
Self::UnsupportedIntrinsic(op) => write!(f, "unsupported intrinsic `{op}`"),
Self::InterceptError(message) => write!(f, "call interceptor failed: {message}"),
Self::StepBudgetExceeded(budget) => {
write!(f, "emulation exceeded step budget of {budget}")
}
Self::UnsupportedMnemonic(op) => write!(f, "unsupported mnemonic `{op}`"),
Self::EmptyBlock(block) => write!(f, "block {block:?} has no instructions"),
Self::PoisonRead => write!(f, "read of a poison value (undefined bits)"),
}
}
}
impl std::fmt::Display for EmulatorError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "emulator error: {}", self.kind)?;
write!(f, " (context: {})", self.ctx)?;
Ok(())
}
}
impl std::error::Error for EmulatorError {}
pub type Result<T> = std::result::Result<T, EmulatorError>;
pub trait DomainValue: Clone + Copy {
fn size(&self) -> std::result::Result<usize, EmulatorErrorKind>;
fn value(&self) -> std::result::Result<u64, EmulatorErrorKind>;
fn from_u64(value: u64) -> Self;
fn zero(_size: usize) -> Self {
Self::from_u64(0)
}
fn is_float_nan(&self) -> std::result::Result<Self, EmulatorErrorKind>;
fn int_to_float(&self, size: usize) -> std::result::Result<Self, EmulatorErrorKind>;
fn float_to_float(&self, size: usize) -> std::result::Result<Self, EmulatorErrorKind>;
fn float_to_int(&self, size: usize) -> std::result::Result<Self, EmulatorErrorKind>;
fn zext(&self, size: usize) -> std::result::Result<Self, EmulatorErrorKind>;
fn sext(&self, size: usize) -> std::result::Result<Self, EmulatorErrorKind>;
fn range(&self, start: usize, size: usize) -> std::result::Result<Self, EmulatorErrorKind>;
fn byte_swap(&self) -> std::result::Result<Self, EmulatorErrorKind>;
fn intrinsic(
id: qcode::value::insn::IntrinsicId,
args: &[Self],
out_size: usize,
) -> std::result::Result<Self, EmulatorErrorKind>;
fn pop_count(&self) -> std::result::Result<Self, EmulatorErrorKind>;
fn lz_count(&self) -> std::result::Result<Self, EmulatorErrorKind>;
fn carry(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
fn scarry(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
fn sborrow(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
fn int_not(&self) -> std::result::Result<Self, EmulatorErrorKind>;
fn int_negate(&self) -> std::result::Result<Self, EmulatorErrorKind>;
fn float_negate(&self) -> std::result::Result<Self, EmulatorErrorKind>;
fn float_abs(&self) -> std::result::Result<Self, EmulatorErrorKind>;
fn float_sqrt(&self) -> std::result::Result<Self, EmulatorErrorKind>;
fn float_ceil(&self) -> std::result::Result<Self, EmulatorErrorKind>;
fn float_floor(&self) -> std::result::Result<Self, EmulatorErrorKind>;
fn float_round(&self) -> std::result::Result<Self, EmulatorErrorKind>;
fn int_equal(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
fn int_not_equal(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
fn int_less(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
fn int_sless(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
fn int_less_equal(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
fn int_sless_equal(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
fn int_add(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
fn int_sub(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
fn int_xor(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
fn int_and(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
fn int_or(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
fn int_shift_left(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
fn int_shift_right(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
fn int_sshift_right(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
fn int_mul(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
fn int_div(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
fn int_rem(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
fn int_sdiv(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
fn int_srem(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
fn float_add(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
fn float_sub(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
fn float_mul(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
fn float_div(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
fn float_equal(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
fn float_not_equal(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
fn float_less(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
fn float_less_equal(&self, other: &Self) -> std::result::Result<Self, EmulatorErrorKind>;
}
pub trait DomainMemory {
type V: DomainValue;
fn read(
&self,
space: MemorySpaceId,
addr: Self::V,
size: usize,
) -> std::result::Result<Self::V, EmulatorErrorKind>;
fn write(
&mut self,
space: MemorySpaceId,
addr: Self::V,
size: usize,
data: Self::V,
) -> std::result::Result<(), EmulatorErrorKind>;
}
pub trait Interpreter {
type V: DomainValue;
type M: DomainMemory<V = Self::V>;
fn ctx(&self) -> &Context<'_>;
fn memory(&mut self) -> &mut Self::M;
fn get_value(&mut self, id: ValueId) -> std::result::Result<Self::V, EmulatorErrorKind>;
fn get_varnode_value(
&mut self,
id: VarnodeId,
) -> std::result::Result<Self::V, EmulatorErrorKind> {
let varnode = Varnode::from_id(self.ctx(), id);
let space = varnode.space().id;
let addr = Self::V::from_u64(varnode.address() as u64);
let size = varnode.size();
self.memory().read(space.into(), addr, size)
}
fn set_varnode_value(
&mut self,
id: VarnodeId,
value: Self::V,
) -> std::result::Result<(), EmulatorErrorKind> {
let varnode = Varnode::from_id(self.ctx(), id);
let space = varnode.space().id;
let addr = Self::V::from_u64(varnode.address() as u64);
let size = varnode.size();
self.memory().write(space.into(), addr, size, value)?;
Ok(())
}
fn get_register_value(
&mut self,
reg_id: RegisterId,
) -> std::result::Result<Self::V, EmulatorErrorKind> {
let id = *self
.ctx()
.shared
.registers
.get(®_id)
.ok_or(EmulatorErrorKind::UnknownRegister(reg_id))?;
self.get_varnode_value(id)
}
fn set_register_value(
&mut self,
reg_id: RegisterId,
value: Self::V,
) -> std::result::Result<(), EmulatorErrorKind> {
let id = *self
.ctx()
.shared
.registers
.get(®_id)
.ok_or(EmulatorErrorKind::UnknownRegister(reg_id))?;
self.set_varnode_value(id, value)
}
fn interpret_(
&mut self,
insn: &InstructionRef<'_, '_>,
mnemonic: &Mnemonic,
) -> std::result::Result<Option<Self::V>, EmulatorErrorKind> {
let func = insn.id.func;
let v = match mnemonic {
&Mnemonic::Load(Load { space, ptr, size }) => {
let addr = self.get_value(ptr.qualify(func))?;
Some(self.memory().read(space.qualify(func), addr, size)?)
}
&Mnemonic::Store(Store {
space,
ptr,
size,
src,
}) => {
let addr = self.get_value(ptr.qualify(func))?;
let value = self.get_value(src.qualify(func))?;
self.memory()
.write(space.qualify(func), addr, size, value)?;
None
}
Mnemonic::Branch(_)
| Mnemonic::CBranch(_)
| Mnemonic::BranchInd(_)
| Mnemonic::Call(_)
| Mnemonic::CallInd(_)
| Mnemonic::Return(_)
| Mnemonic::ReturnValue(_)
| Mnemonic::Apply(_) => None,
Mnemonic::Unop(Unary { op, src }) => {
let value = self.get_value(src.qualify(func))?;
let v = match op {
Unop::IntNegate => value.int_negate(),
Unop::IntNot => value.int_not(),
Unop::FloatNegate => value.float_negate(),
Unop::FloatAbs => value.float_abs(),
Unop::FloatSqrt => value.float_sqrt(),
Unop::FloatCeil => value.float_ceil(),
Unop::FloatFloor => value.float_floor(),
Unop::FloatRound => value.float_round(),
_ => todo!("unimplemented unary operation: {:?}", op),
}?;
Some(v)
}
Mnemonic::Binop(Binary { op, lhs, rhs }) => {
let value1 = self.get_value(lhs.qualify(func))?;
let value2 = self.get_value(rhs.qualify(func))?;
let v = match *op {
Binop::Int(IntBinop::Equal) => value1.int_equal(&value2),
Binop::Int(IntBinop::NotEqual) => value1.int_not_equal(&value2),
Binop::Int(IntBinop::Less) => value1.int_less(&value2),
Binop::Int(IntBinop::SLess) => value1.int_sless(&value2),
Binop::Int(IntBinop::LessEqual) => value1.int_less_equal(&value2),
Binop::Int(IntBinop::SLessEqual) => value1.int_sless_equal(&value2),
Binop::Int(IntBinop::Add) => value1.int_add(&value2),
Binop::Int(IntBinop::Sub) => value1.int_sub(&value2),
Binop::Int(IntBinop::Xor) => value1.int_xor(&value2),
Binop::Int(IntBinop::And) => value1.int_and(&value2),
Binop::Int(IntBinop::Or) => value1.int_or(&value2),
Binop::Int(IntBinop::ShiftLeft) => value1.int_shift_left(&value2),
Binop::Int(IntBinop::ShiftRight) => value1.int_shift_right(&value2),
Binop::Int(IntBinop::SShiftRight) => value1.int_sshift_right(&value2),
Binop::Int(IntBinop::Mul) => value1.int_mul(&value2),
Binop::Int(IntBinop::Div) => value1.int_div(&value2),
Binop::Int(IntBinop::Rem) => value1.int_rem(&value2),
Binop::Int(IntBinop::Sdiv) => value1.int_sdiv(&value2),
Binop::Int(IntBinop::Srem) => value1.int_srem(&value2),
Binop::Float(FloatBinop::Add) => value1.float_add(&value2),
Binop::Float(FloatBinop::Sub) => value1.float_sub(&value2),
Binop::Float(FloatBinop::Mul) => value1.float_mul(&value2),
Binop::Float(FloatBinop::Div) => value1.float_div(&value2),
Binop::Float(FloatBinop::Equal) => value1.float_equal(&value2),
Binop::Float(FloatBinop::NotEqual) => value1.float_not_equal(&value2),
Binop::Float(FloatBinop::Less) => value1.float_less(&value2),
Binop::Float(FloatBinop::LessEqual) => value1.float_less_equal(&value2),
_ => todo!("unimplemented binary operation: {:?}", op),
}?;
Some(v)
}
&Mnemonic::PopCount(PopCount { src }) => {
let value = self.get_value(src.qualify(func))?;
Some(value.pop_count()?)
}
&Mnemonic::LzCount(LzCount { src }) => {
let value = self.get_value(src.qualify(func))?;
Some(value.lz_count()?)
}
&Mnemonic::Carry(Carry { lhs, rhs }) => {
let value1 = self.get_value(lhs.qualify(func))?;
let value2 = self.get_value(rhs.qualify(func))?;
Some(value1.carry(&value2)?)
}
&Mnemonic::SCarry(SCarry { lhs, rhs }) => {
let value1 = self.get_value(lhs.qualify(func))?;
let value2 = self.get_value(rhs.qualify(func))?;
Some(value1.scarry(&value2)?)
}
&Mnemonic::SBorrow(SBorrow { lhs, rhs }) => {
let value1 = self.get_value(lhs.qualify(func))?;
let value2 = self.get_value(rhs.qualify(func))?;
Some(value1.sborrow(&value2)?)
}
&Mnemonic::IsFloatNaN(IsFloatNaN { src }) => {
let value = self.get_value(src.qualify(func))?;
Some(value.is_float_nan()?)
}
&Mnemonic::IntToFloat(IntToFloat { src, size }) => {
let value = self.get_value(src.qualify(func))?;
Some(value.int_to_float(size)?)
}
&Mnemonic::FloatToFloat(FloatToFloat { src, size }) => {
let value = self.get_value(src.qualify(func))?;
Some(value.float_to_float(size)?)
}
&Mnemonic::FloatToInt(FloatToInt { src, size }) => {
let value = self.get_value(src.qualify(func))?;
Some(value.float_to_int(size)?)
}
&Mnemonic::Zext(Zext { src, size }) => {
let value = self.get_value(src.qualify(func))?;
Some(value.zext(size)?)
}
&Mnemonic::Sext(Sext { src, size }) => {
let value = self.get_value(src.qualify(func))?;
Some(value.sext(size)?)
}
&Mnemonic::Range(Range { src, start, size }) => {
let value = self.get_value(src.qualify(func))?;
Some(value.range(start, size)?)
}
&Mnemonic::Gep(Gep { base, offset }) => {
let base = self.get_value(base.qualify(func))?;
let offset = Self::V::from_u64(offset as u64);
Some(base.int_add(&offset)?)
}
Mnemonic::PCodeOp(op) => {
let name = self.ctx().shared.pcode_ops[op.id].clone();
match (name.as_ref(), op.args.as_slice()) {
("swap_bytes", [src]) => Some(self.get_value(src.qualify(func))?.byte_swap()?),
("undef", []) => Some(Self::V::zero(insn.size())),
("LOCK" | "UNLOCK", []) => None,
_ => return Err(EmulatorErrorKind::UnsupportedPCodeOp(name)),
}
}
Mnemonic::Intrinsic(intr) => {
let out_size = insn.size();
let mut args = Vec::with_capacity(intr.args.len());
for &arg in &intr.args {
args.push(self.get_value(arg.qualify(func))?);
}
Some(Self::V::intrinsic(intr.id, &args, out_size)?)
}
Mnemonic::Map(_) => return Err(EmulatorErrorKind::UnsupportedMnemonic("map")),
_ => todo!("unimplemented mnemonic: {mnemonic:?}"),
};
Ok(v)
}
fn interpret(
&mut self,
insn: InstructionRef<'_, '_>,
mnemonic: &Mnemonic,
) -> Result<Option<Self::V>> {
self.interpret_(&insn, mnemonic)
.map_err(|kind| EmulatorError::new(kind, &insn))
}
}