use crate::error::JingleError;
use crate::varnode::ResolvedVarnode::{Direct, Indirect};
use crate::varnode::{ResolvedIndirectVarNode, ResolvedVarnode};
use jingle_sleigh::{
GeneralizedVarNode, PcodeOperation, SleighArchInfo, SpaceType, create_varnode,
};
use std::cmp::{Ordering, min};
use std::collections::HashSet;
use std::fmt::Debug;
use std::hash::{DefaultHasher, Hash, Hasher};
use std::ops::{Add, Neg};
use tracing::instrument;
use z3::ast::{Ast, BV, Bool};
mod block;
mod branch;
mod concretize;
mod instruction;
pub mod machine;
mod slice;
mod state;
pub mod tactics;
pub use block::ModeledBlock;
pub use branch::*;
pub use instruction::ModeledInstruction;
pub use state::State;
pub trait ModelingContext: Debug + Sized {
fn get_arch_info(&self) -> &SleighArchInfo;
fn get_address(&self) -> u64;
fn get_original_state(&self) -> &State;
fn get_final_state(&self) -> &State;
fn get_ops(&self) -> Vec<&PcodeOperation>;
fn get_inputs(&self) -> HashSet<ResolvedVarnode>;
fn get_outputs(&self) -> HashSet<ResolvedVarnode>;
fn get_branch_constraint(&self) -> &BranchConstraint;
fn should_varnode_constrain(&self, v: &ResolvedVarnode) -> bool {
match v {
Direct(d) => self
.get_final_state()
.arch_info()
.get_space(d.space_index)
.map(|o| o._type == SpaceType::IPTR_PROCESSOR)
.unwrap_or(false),
Indirect(_) => true,
}
}
fn upholds_postcondition<T: ModelingContext>(&self, other: &T) -> Result<Bool, JingleError> {
let mut output_terms = vec![];
for vn in other
.get_outputs()
.iter()
.filter(|v| self.should_varnode_constrain(v))
{
let ours = self.get_final_state().read_resolved(vn)?;
let other_bv = other.get_final_state().read_resolved(vn)?;
output_terms.push(ours.eq(&other_bv).simplify());
if let Indirect(a) = vn {
let ours = self.get_final_state().read_varnode(&a.pointer_location)?;
let other = other.get_final_state().read_varnode(&a.pointer_location)?;
output_terms.push(ours.eq(&other).simplify());
}
}
let imp_terms: Vec<&Bool> = output_terms.iter().collect();
let outputs_pairwise_equal = Bool::and(imp_terms.as_slice());
Ok(outputs_pairwise_equal)
}
fn assert_concat<T: ModelingContext>(&self, other: &T) -> Result<Bool, JingleError> {
self.get_final_state()._eq(other.get_original_state())
}
#[deprecated]
#[expect(deprecated)]
fn branch_comparison<T: ModelingContext>(
&self,
other: &T,
) -> Result<Option<Bool>, JingleError> {
if !self.get_branch_constraint().has_branch() {
Ok(None)
} else {
if !self.get_branch_constraint().conditional_branches.is_empty()
|| !other
.get_branch_constraint()
.conditional_branches
.is_empty()
{
return Ok(Some(Bool::from_bool(false)));
}
let self_bv = self.get_branch_constraint().build_bv(self)?;
let other_bv = other.get_branch_constraint().build_bv(other)?;
let self_bv = zext_to_match(self_bv, &other_bv);
let other_bv = zext_to_match(other_bv, &self_bv);
let self_bv_metadata = self.get_branch_constraint().build_bv_metadata(self)?;
let other_bv_metadata = other.get_branch_constraint().build_bv_metadata(other)?;
let self_bv_metadata =
zext_to_match(self_bv_metadata.simplify(), &other_bv_metadata.simplify());
let other_bv_metadata = zext_to_match(other_bv_metadata, &self_bv_metadata);
Ok(Some(Bool::and(&[
self_bv._eq(&other_bv).simplify(),
self_bv_metadata._eq(&other_bv_metadata).simplify(),
])))
}
}
#[expect(deprecated)]
fn can_branch_to_address(&self, addr: u64) -> Result<Bool, JingleError> {
let branch_constraint = self.get_branch_constraint().build_bv(self)?;
let addr_bv = BV::from_i64(addr as i64, branch_constraint.get_size());
Ok(branch_constraint._eq(&addr_bv))
}
}
pub(crate) trait TranslationContext: ModelingContext {
fn track_input(&mut self, input: &ResolvedVarnode);
fn track_output(&mut self, output: &ResolvedVarnode);
fn get_final_state_mut(&mut self) -> &mut State;
fn get_branch_builder(&mut self) -> &mut BranchConstraint;
fn read_and_track(&mut self, gen_varnode: GeneralizedVarNode) -> Result<BV, JingleError> {
match gen_varnode {
GeneralizedVarNode::Direct(d) => {
self.track_input(&Direct(d.clone()));
self.get_final_state().read_varnode(&d)
}
GeneralizedVarNode::Indirect(indirect) => {
self.track_input(&Direct(indirect.pointer_location.clone()));
let pointer = self
.get_final_state()
.read_varnode(&indirect.pointer_location)?
.clone();
self.track_input(&Indirect(ResolvedIndirectVarNode {
pointer,
pointer_location: indirect.pointer_location.clone(),
access_size_bytes: indirect.access_size_bytes,
pointer_space_idx: indirect.pointer_space_index,
}));
self.get_final_state().read_varnode_indirect(&indirect)
}
}
}
fn write(&mut self, r#gen: &GeneralizedVarNode, val: BV) -> Result<(), JingleError> {
match r#gen {
GeneralizedVarNode::Direct(d) => {
self.track_output(&Direct(d.clone()));
self.get_final_state_mut().write_varnode(d, val)?;
}
GeneralizedVarNode::Indirect(indirect) => {
let pointer = self.read_and_track(indirect.pointer_location.clone().into())?;
self.track_output(&Indirect(ResolvedIndirectVarNode {
pointer,
pointer_location: indirect.pointer_location.clone(),
access_size_bytes: indirect.access_size_bytes,
pointer_space_idx: indirect.pointer_space_index,
}));
self.get_final_state_mut()
.write_varnode_indirect(indirect, val)?;
}
}
Ok(())
}
#[instrument(skip_all)]
fn model_pcode_op(&mut self, op: &PcodeOperation) -> Result<(), JingleError>
where
Self: Sized,
{
match op {
PcodeOperation::Copy { input, output } => {
let val = self.read_and_track(input.into())?;
let metadata = self.get_original_state().read_varnode_metadata(input)?;
self.get_final_state_mut()
.write_varnode_metadata(output, metadata)?;
self.write(&output.into(), val)
}
PcodeOperation::IntZExt { input, output } => {
let diff = (output.size - input.size) as u32;
let val = self.read_and_track(input.into())?;
let zext = val.zero_ext(diff * 8);
self.write(&output.into(), zext)
}
PcodeOperation::IntSExt { input, output } => {
let diff = (output.size - input.size) as u32;
let val = self.read_and_track(input.into())?;
let zext = val.sign_ext(diff * 8);
self.write(&output.into(), zext)
}
PcodeOperation::Store { output, input } => {
let bv = self.read_and_track(input.into())?;
self.write(&output.into(), bv)
}
PcodeOperation::Load { input, output } => {
let bv = self.read_and_track(input.into())?;
self.write(&output.into(), bv)
}
PcodeOperation::IntAdd {
input0,
input1,
output,
} => {
let bv1 = self.read_and_track(input0.into())?;
let bv2 = self.read_and_track(input1.into())?;
let add = bv1 + bv2;
self.write(&output.into(), add)
}
PcodeOperation::IntSub {
input0,
input1,
output,
} => {
let bv1 = self.read_and_track(input0.into())?;
let bv2 = self.read_and_track(input1.into())?;
let add = bv1 - bv2;
self.write(&output.into(), add)
}
PcodeOperation::IntAnd {
input0,
input1,
output,
} => {
let bv1 = self.read_and_track(input0.into())?;
let bv2 = self.read_and_track(input1.into())?;
let and = bv1.bvand(&bv2);
self.write(&output.into(), and)
}
PcodeOperation::IntXor {
input0,
input1,
output,
} => {
let bv1 = self.read_and_track(input0.into())?;
let bv2 = self.read_and_track(input1.into())?;
let and = bv1.bvxor(&bv2);
self.write(&output.into(), and)
}
PcodeOperation::IntOr {
input0,
input1,
output,
} => {
let bv1 = self.read_and_track(input0.into())?;
let bv2 = self.read_and_track(input1.into())?;
let or = bv1.bvor(&bv2);
self.write(&output.into(), or)
}
PcodeOperation::IntNegate { input, output } => {
let bv = self.read_and_track(input.into())?;
let neg = bv.neg();
self.write(&output.into(), neg)
}
PcodeOperation::IntMult {
input0,
input1,
output,
} => {
let bv1 = self.read_and_track(input0.into())?;
let bv2 = self.read_and_track(input1.into())?;
let mul = bv1.bvmul(&bv2);
self.write(&output.into(), mul)
}
PcodeOperation::IntDiv {
input0,
input1,
output,
} => {
let bv1 = self.read_and_track(input0.into())?;
let bv2 = self.read_and_track(input1.into())?;
let mul = bv1.bvudiv(&bv2);
self.write(&output.into(), mul)
}
PcodeOperation::IntSignedDiv {
input0,
input1,
output,
} => {
let bv1 = self.read_and_track(input0.into())?;
let bv2 = self.read_and_track(input1.into())?;
let mul = bv1.bvsdiv(&bv2);
self.write(&output.into(), mul)
}
PcodeOperation::IntRem {
input0,
input1,
output,
} => {
let bv1 = self.read_and_track(input0.into())?;
let bv2 = self.read_and_track(input1.into())?;
let mul = bv1.bvurem(&bv2);
self.write(&output.into(), mul)
}
PcodeOperation::IntSignedRem {
input0,
input1,
output,
} => {
let bv1 = self.read_and_track(input0.into())?;
let bv2 = self.read_and_track(input1.into())?;
let mul = bv1.bvsrem(&bv2);
self.write(&output.into(), mul)
}
PcodeOperation::IntRightShift {
input0,
input1,
output,
} => {
let bv1 = self.read_and_track(input0.into())?;
let mut bv2 = self.read_and_track(input1.into())?;
match bv1.get_size().cmp(&bv2.get_size()) {
Ordering::Less => bv2 = bv2.extract(bv1.get_size() - 1, 0),
Ordering::Greater => bv2 = bv2.zero_ext(bv1.get_size() - bv2.get_size()),
_ => {}
}
let rshift = bv1.bvlshr(&bv2);
self.write(&output.into(), rshift)
}
PcodeOperation::IntSignedRightShift {
input0,
input1,
output,
} => {
let bv1 = self.read_and_track(input0.into())?;
let mut bv2 = self.read_and_track(input1.into())?;
match bv1.get_size().cmp(&bv2.get_size()) {
Ordering::Less => bv2 = bv2.extract(bv1.get_size() - 1, 0),
Ordering::Greater => bv2 = bv2.zero_ext(bv1.get_size() - bv2.get_size()),
_ => {}
}
let rshift = bv1.bvashr(&bv2);
self.write(&output.into(), rshift)
}
PcodeOperation::IntLeftShift {
input0,
input1,
output,
} => {
let mut bv1 = self.read_and_track(input0.into())?;
let mut bv2 = self.read_and_track(input1.into())?;
match bv1.get_size().cmp(&bv2.get_size()) {
Ordering::Less => bv1 = bv1.zero_ext(bv2.get_size() - bv1.get_size()),
Ordering::Greater => bv2 = bv2.zero_ext(bv1.get_size() - bv2.get_size()),
_ => {}
}
let lshift = bv1.bvshl(&bv2);
self.write(&output.into(), lshift)
}
PcodeOperation::IntCarry {
input0,
input1,
output,
} => {
let in0 = self.read_and_track(input0.into())?;
let in1 = self.read_and_track(input1.into())?;
let carry_bool = in0.bvadd_no_overflow(&in1, false);
let out_bv = carry_bool.ite(&BV::from_i64(0, 8), &BV::from_i64(1, 8));
self.write(&output.into(), out_bv)
}
PcodeOperation::IntSignedCarry {
input0,
input1,
output,
} => {
let in0 = self.read_and_track(input0.into())?;
let in1 = self.read_and_track(input1.into())?;
let carry_bool = in0.bvadd_no_overflow(&in1, true);
let out_bv = carry_bool.ite(&BV::from_i64(0, 8), &BV::from_i64(1, 8));
self.write(&output.into(), out_bv)
}
PcodeOperation::IntSignedBorrow {
input0,
input1,
output,
} => {
let in0 = self.read_and_track(input0.into())?;
let in1 = self.read_and_track(input1.into())?;
let borrow_bool = in0.bvsub_no_underflow(&in1, true);
let out_bv = borrow_bool.ite(&BV::from_i64(0, 8), &BV::from_i64(1, 8));
self.write(&output.into(), out_bv)
}
PcodeOperation::Int2Comp { input, output } => {
let in0 = self.read_and_track(input.into())?;
let flipped = in0.bvneg().add(BV::from_u64(1, in0.get_size()));
self.write(&output.into(), flipped)
}
PcodeOperation::IntSignedLess {
input0,
input1,
output,
} => {
let in0 = self.read_and_track(input0.into())?;
let in1 = self.read_and_track(input1.into())?;
let out_bool = in0.bvslt(&in1);
let out_bv = out_bool.ite(&BV::from_i64(1, 8), &BV::from_i64(0, 8));
self.write(&output.into(), out_bv)
}
PcodeOperation::IntSignedLessEqual {
input0,
input1,
output,
} => {
let in0 = self.read_and_track(input0.into())?;
let in1 = self.read_and_track(input1.into())?;
let out_bool = in0.bvsle(&in1);
let out_bv = out_bool.ite(&BV::from_i64(1, 8), &BV::from_i64(0, 8));
self.write(&output.into(), out_bv)
}
PcodeOperation::IntLess {
input0,
input1,
output,
} => {
let in0 = self.read_and_track(input0.into())?;
let in1 = self.read_and_track(input1.into())?;
let out_bool = in0.bvult(&in1);
let out_bv = out_bool.ite(&BV::from_i64(1, 8), &BV::from_i64(0, 8));
self.write(&output.into(), out_bv)
}
PcodeOperation::IntLessEqual {
input0,
input1,
output,
} => {
let in0 = self.read_and_track(input0.into())?;
let in1 = self.read_and_track(input1.into())?;
let out_bool = in0.bvule(&in1);
let out_bv = out_bool.ite(&BV::from_i64(1, 8), &BV::from_i64(0, 8));
self.write(&output.into(), out_bv)
}
PcodeOperation::IntEqual {
input0,
input1,
output,
} => {
let in0 = self.read_and_track(input0.into())?;
let in1 = self.read_and_track(input1.into())?;
let outsize = output.size as u32;
let out_bool = in0.eq(&in1);
let out_bv =
out_bool.ite(&BV::from_i64(1, outsize * 8), &BV::from_i64(0, outsize * 8));
self.write(&output.into(), out_bv)
}
PcodeOperation::IntNotEqual {
input0,
input1,
output,
} => {
let in0 = self.read_and_track(input0.into())?;
let in1 = self.read_and_track(input1.into())?;
let outsize = output.size as u32;
let out_bool = in0.eq(&in1).not();
let out_bv =
out_bool.ite(&BV::from_i64(1, outsize * 8), &BV::from_i64(0, outsize * 8));
self.write(&output.into(), out_bv)
}
PcodeOperation::BoolAnd {
input0,
input1,
output,
} => {
let i0 = self.read_and_track(input0.into())?;
let i1 = self.read_and_track(input1.into())?;
let result = i0.bvand(&i1).bvand(1);
self.write(&output.into(), result)
}
PcodeOperation::BoolNegate { input, output } => {
let val = self.read_and_track(input.into())?;
let negated = val.bvneg().bvand(1);
self.write(&output.into(), negated)
}
PcodeOperation::BoolOr {
input0,
input1,
output,
} => {
let i0 = self.read_and_track(input0.into())?;
let i1 = self.read_and_track(input1.into())?;
let result = (i0 | i1) & 1;
self.write(&output.into(), result)
}
PcodeOperation::BoolXor {
input0,
input1,
output,
} => {
let i0 = self.read_and_track(input0.into())?;
let i1 = self.read_and_track(input1.into())?;
let result = i0.bvxor(&i1).bvand(1);
self.write(&output.into(), result)
}
PcodeOperation::PopCount { input, output } => {
let size = output.size as u32;
let in0 = self.read_and_track(input.into())?;
let mut outbv = BV::from_i64(0, output.size as u32 * 8);
for i in 0..size * 8 {
let extract = in0.extract(i, i);
let extend = extract.zero_ext((size * 8) - 1);
outbv = outbv.bvadd(&extend);
}
self.write(&output.into(), outbv)
}
PcodeOperation::Branch { input } => {
self.get_branch_builder()
.set_last(&GeneralizedVarNode::from(input));
self.read_and_track(GeneralizedVarNode::from(input))?;
Ok(())
}
PcodeOperation::BranchInd { input } => {
self.get_branch_builder()
.set_last(&GeneralizedVarNode::from(input));
self.read_and_track(GeneralizedVarNode::from(&input.pointer_location))?;
Ok(())
}
PcodeOperation::Call { dest, .. } => {
self.get_branch_builder().set_last(&dest.into());
self.read_and_track(dest.into())?;
Ok(())
}
PcodeOperation::CBranch { input0, input1 } => {
self.get_branch_builder()
.push_conditional(&BlockConditionalBranchInfo {
condition: input1.clone(),
destination: input0.into(),
});
self.read_and_track(input0.into())?;
self.read_and_track(input1.into())?;
Ok(())
}
PcodeOperation::SubPiece {
input0,
input1,
output,
} => {
let bv0 = self.read_and_track(input0.into())?;
let input_low_byte = input1.offset as u32;
let input_size = (input0.size as u32) - input_low_byte;
let output_size = output.size as u32;
let size = min(input_size, output_size);
let input = bv0.extract((input_low_byte + size) * 8 - 1, input_low_byte * 8);
match size.cmp(&output_size) {
Ordering::Less => {
self.write(&output.into(), input.zero_ext((output_size - size) * 8))
}
Ordering::Greater => {
self.write(&output.into(), input.extract(output_size * 8 - 1, 0))
}
Ordering::Equal => self.write(&output.into(), input),
}
}
PcodeOperation::CallOther { inputs, output, .. } => {
let mut hasher = DefaultHasher::new();
for vn in inputs {
vn.hash(&mut hasher);
}
let hash = hasher.finish();
for input in inputs.iter() {
self.read_and_track(input.into())?;
}
let size = self
.get_final_state()
.get_default_code_space_info()
.index_size_bytes;
let hash_vn = create_varnode(self.get_arch_info(), "const", hash, size as usize)?;
let metadata = self
.get_final_state()
.immediate_metadata_array(true, hash_vn.size);
self.get_final_state_mut()
.write_varnode_metadata(&hash_vn, metadata)?;
self.get_branch_builder().set_last(&hash_vn.into());
if let Some(out) = output {
let size = out.size * 8;
let hash_bv = BV::from_u64(hash, size as u32);
let metadata = self
.get_final_state()
.immediate_metadata_array(true, out.size);
self.get_final_state_mut()
.write_varnode_metadata(out, metadata)?;
self.write(&out.into(), hash_bv)?;
}
Ok(())
}
PcodeOperation::CallInd { input } => {
self.get_branch_builder()
.set_last(&GeneralizedVarNode::from(input));
self.read_and_track(GeneralizedVarNode::from(&input.pointer_location))?;
Ok(())
}
PcodeOperation::Return { input } => {
self.get_branch_builder()
.set_last(&GeneralizedVarNode::from(input));
self.read_and_track(GeneralizedVarNode::from(&input.pointer_location))?;
Ok(())
}
v => Err(JingleError::UnmodeledInstruction(Box::new(v.clone()))),
}
}
}
fn zext_to_match(bv1: BV, bv2: &BV) -> BV {
if bv1.get_size() < bv2.get_size() {
bv1.zero_ext(bv2.get_size() - bv1.get_size())
} else {
bv1
}
}