use crate::JingleError;
use crate::JingleError::{MismatchedAddressSize, UnexpectedArraySort, ZeroSizedVarnode};
use jingle_sleigh::{SleighEndianness, SpaceInfo};
use std::ops::Add;
use z3::ast::{Array, Ast, BV};
use z3::{Context, Sort, Translate};
#[derive(Clone, Debug)]
pub(crate) struct ModeledSpace {
endianness: SleighEndianness,
data: Array,
metadata: Array,
space_info: SpaceInfo,
}
impl ModeledSpace {
pub(crate) fn new(space_info: &SpaceInfo) -> Self {
let domain = Sort::bitvector(space_info.index_size_bytes * 8);
let range = Sort::bitvector(space_info.word_size_bytes * 8);
Self {
endianness: space_info.endianness,
data: Array::fresh_const(&space_info.name, &domain, &range),
metadata: Array::const_array(&domain, &BV::from_u64(0, 1)),
space_info: space_info.clone(),
}
}
pub(crate) fn get_space(&self) -> &Array {
&self.data
}
pub(crate) fn read_data(&self, offset: &BV, size_bytes: usize) -> Result<BV, JingleError> {
if offset.get_size() != self.space_info.index_size_bytes * 8 {
return Err(MismatchedAddressSize);
}
read_from_array(&self.data, offset, size_bytes, self.endianness)
}
pub(crate) fn read_metadata(&self, offset: &BV, size_bytes: usize) -> Result<BV, JingleError> {
if offset.get_size() != self.space_info.index_size_bytes * 8 {
return Err(MismatchedAddressSize);
}
read_from_array(&self.metadata, offset, size_bytes, self.endianness)
}
pub(crate) fn write_data(&mut self, val: &BV, offset: &BV) -> Result<(), JingleError> {
if offset.get_size() != self.space_info.index_size_bytes * 8 {
return Err(MismatchedAddressSize);
}
self.data = write_to_array::<8>(&self.data, val, offset, self.endianness);
Ok(())
}
pub(crate) fn write_metadata(&mut self, val: &BV, offset: &BV) -> Result<(), JingleError> {
if offset.get_size() != self.space_info.index_size_bytes * 8 {
return Err(MismatchedAddressSize);
}
self.metadata = write_to_array::<1>(&self.metadata, val, offset, self.endianness);
Ok(())
}
pub(crate) fn fmt_smt_array(&self) -> String {
format!("{:?}", self.data.simplify())
}
}
unsafe impl Translate for ModeledSpace {
fn translate(&self, dest: &Context) -> Self {
ModeledSpace {
space_info: self.space_info.clone(),
endianness: self.endianness,
data: self.data.translate(dest),
metadata: self.metadata.translate(dest),
}
}
}
fn read_from_array(
array: &Array,
offset: &BV,
size_bytes: usize,
endianness: SleighEndianness,
) -> Result<BV, JingleError> {
(0..size_bytes)
.map(|i| {
array
.select(&offset.clone().add(i as u64))
.as_bv()
.ok_or(UnexpectedArraySort)
})
.reduce(|acc, byte_bv| match endianness {
SleighEndianness::Big => Ok(acc?.concat(&byte_bv?)),
SleighEndianness::Little => Ok(byte_bv?.concat(&acc?)),
})
.ok_or(ZeroSizedVarnode)?
}
fn write_to_array<const W: u32>(
array: &Array,
val: &BV,
offset: &BV,
endianness: SleighEndianness,
) -> Array {
let mut scratch = array.clone();
let size = val.get_size();
for i in 0..size / W {
let (high, low) = match endianness {
SleighEndianness::Big => (size - W * i - 1, size - W * (i + 1)),
SleighEndianness::Little => (W * (i + 1) - 1, W * i),
};
let ext = &val.extract(high, low);
scratch = scratch.store(&offset.add(i as u64), ext);
}
scratch
}
#[cfg(test)]
mod tests {
use crate::modeling::state::space::ModeledSpace;
use jingle_sleigh::{SleighEndianness, SpaceInfo, SpaceType};
use z3::ast::{Ast, BV};
fn make_space(endianness: SleighEndianness) -> ModeledSpace {
let space_info = SpaceInfo {
endianness,
name: "ram".to_string(),
word_size_bytes: 1,
index_size_bytes: 4,
index: 0,
_type: SpaceType::IPTR_PROCESSOR,
};
ModeledSpace::new(&space_info)
}
fn test_endian_write(e: SleighEndianness) {
let mut space = make_space(e);
space
.write_data(&BV::from_u64(0xdead_beef, 32), &BV::from_u64(0, 32))
.unwrap();
let expected = match e {
SleighEndianness::Big => [0xde, 0xad, 0xbe, 0xef],
SleighEndianness::Little => [0xef, 0xbe, 0xad, 0xde],
};
for i in 0..4 {
let data = space.read_data(&BV::from_u64(i, 32), 1).unwrap().simplify();
assert!(data.is_const());
assert_eq!(data.as_u64().unwrap(), expected[i as usize])
}
}
fn test_endian_read(e: SleighEndianness) {
let mut space = make_space(e);
let byte_layout = match e {
SleighEndianness::Big => [0xde, 0xad, 0xbe, 0xef],
SleighEndianness::Little => [0xef, 0xbe, 0xad, 0xde],
};
for i in 0..4 {
space
.write_data(
&BV::from_u64(byte_layout[i as usize], 8),
&BV::from_u64(i, 32),
)
.unwrap();
}
let val = space.read_data(&BV::from_u64(0, 32), 4).unwrap().simplify();
assert!(val.is_const());
assert_eq!(val.as_u64().unwrap(), 0xdead_beef)
}
fn test_single_write(e: SleighEndianness) {
let mut space = make_space(e);
space
.write_data(&BV::from_u64(0x42, 8), &BV::from_u64(0, 32))
.unwrap();
let expected = 0x42;
let data = space.read_data(&BV::from_u64(0, 32), 1).unwrap().simplify();
assert!(data.is_const());
assert_eq!(data.as_u64().unwrap(), expected)
}
#[test]
fn test_single_little_endian_write() {
test_single_write(SleighEndianness::Little)
}
#[test]
fn test_single_big_endian_write() {
test_single_write(SleighEndianness::Big)
}
#[test]
fn test_little_endian_write() {
test_endian_write(SleighEndianness::Little)
}
#[test]
fn test_big_endian_write() {
test_endian_write(SleighEndianness::Big)
}
#[test]
fn test_little_endian_read() {
test_endian_read(SleighEndianness::Little)
}
#[test]
fn test_big_endian_read() {
test_endian_read(SleighEndianness::Big)
}
}