use std::collections::HashMap;
use crate::memory::{Endian, Memory, Version};
use crate::{Context, Error, Expr, Width};
mod pcode;
mod vex;
pub use pcode::pcode;
pub use vex::vex;
#[cfg(feature = "prove")]
mod llvm;
#[cfg(feature = "prove")]
pub use llvm::llvm;
#[derive(Debug)]
#[non_exhaustive]
pub struct Block {
pub inputs: Vec<(String, Expr)>,
pub outputs: Vec<(String, Expr)>,
pub stores: Vec<(Expr, Expr)>,
pub exits: Vec<(Expr, Expr)>,
pub next: Option<Expr>,
pub memory: Memory,
pub version: Version,
}
impl Block {
pub fn register(&self, name: &str) -> Option<Expr> {
let find = |list: &[(String, Expr)]| {
list.iter()
.find(|(n, _)| n.eq_ignore_ascii_case(name))
.map(|&(_, e)| e)
};
find(&self.outputs).or_else(|| find(&self.inputs))
}
}
const X86_64: &[(&str, &str, u16, u16)] = &[
("rax", "rax", 0, 8),
("eax", "rax", 0, 4),
("ax", "rax", 0, 2),
("al", "rax", 0, 1),
("ah", "rax", 1, 1),
("rbx", "rbx", 0, 8),
("ebx", "rbx", 0, 4),
("bx", "rbx", 0, 2),
("bl", "rbx", 0, 1),
("bh", "rbx", 1, 1),
("rcx", "rcx", 0, 8),
("ecx", "rcx", 0, 4),
("cx", "rcx", 0, 2),
("cl", "rcx", 0, 1),
("ch", "rcx", 1, 1),
("rdx", "rdx", 0, 8),
("edx", "rdx", 0, 4),
("dx", "rdx", 0, 2),
("dl", "rdx", 0, 1),
("dh", "rdx", 1, 1),
("rsi", "rsi", 0, 8),
("esi", "rsi", 0, 4),
("si", "rsi", 0, 2),
("sil", "rsi", 0, 1),
("rdi", "rdi", 0, 8),
("edi", "rdi", 0, 4),
("di", "rdi", 0, 2),
("dil", "rdi", 0, 1),
("rbp", "rbp", 0, 8),
("ebp", "rbp", 0, 4),
("bp", "rbp", 0, 2),
("bpl", "rbp", 0, 1),
("rsp", "rsp", 0, 8),
("esp", "rsp", 0, 4),
("sp", "rsp", 0, 2),
("spl", "rsp", 0, 1),
];
pub(crate) fn alias(name: &str) -> (String, u16, Option<u16>) {
let lower = name.to_ascii_lowercase();
if let Some(&(_, base, off, size)) = X86_64.iter().find(|(n, ..)| *n == lower) {
return (keep_case(name, base), off, Some(size));
}
if let Some(rest) = lower.strip_prefix('r')
&& let Some(end) = rest.find(|c: char| !c.is_ascii_digit())
&& let Ok(k) = rest[..end].parse::<u8>()
&& (8..=15).contains(&k)
{
let size = match &rest[end..] {
"d" => 4,
"w" => 2,
"b" | "l" => 1,
_ => return (name.to_string(), 0, None),
};
return (keep_case(name, &format!("r{k}")), 0, Some(size));
}
(name.to_string(), 0, None)
}
fn keep_case(name: &str, base: &str) -> String {
if name.chars().any(|c| c.is_ascii_uppercase()) {
base.to_ascii_uppercase()
} else {
base.to_string()
}
}
pub(crate) struct Registers {
size: HashMap<String, u16>,
value: HashMap<String, Expr>,
pub(crate) inputs: Vec<(String, Expr)>,
written: Vec<String>,
}
impl Registers {
pub(crate) fn new() -> Self {
Registers {
size: HashMap::new(),
value: HashMap::new(),
inputs: Vec::new(),
written: Vec::new(),
}
}
pub(crate) fn note(&mut self, base: &str, off: u16, size: u16) {
let s = self.size.entry(base.to_string()).or_insert(0);
*s = (*s).max(off + size);
}
fn full(&mut self, cx: &mut Context, base: &str) -> Result<Expr, Error> {
if let Some(&v) = self.value.get(base) {
return Ok(v);
}
let bytes = self.size.get(base).copied().unwrap_or(8);
let s = cx.symbol(base, Width::new(8 * bytes)?)?;
self.inputs.push((base.to_string(), s));
self.value.insert(base.to_string(), s);
Ok(s)
}
pub(crate) fn read(
&mut self,
cx: &mut Context,
base: &str,
off: u16,
size: u16,
) -> Result<Expr, Error> {
let v = self.full(cx, base)?;
let w = cx.width(v)?.bits();
if off == 0 && 8 * size == w {
return Ok(v);
}
cx.extract(v, 8 * off, Width::new(8 * size)?)
}
pub(crate) fn write(
&mut self,
cx: &mut Context,
base: &str,
off: u16,
value: Expr,
) -> Result<(), Error> {
let v = self.full(cx, base)?;
let w = cx.width(v)?.bits();
let vw = cx.width(value)?.bits();
let new = if off == 0 && vw == w {
value
} else {
let lo_bits = 8 * off;
let hi_lo = lo_bits + vw;
let mut acc = value;
if lo_bits > 0 {
let lo = cx.extract(v, 0, Width::new(lo_bits)?)?;
acc = cx.concat(acc, lo)?;
}
if hi_lo < w {
let hi = cx.extract(v, hi_lo, Width::new(w - hi_lo)?)?;
acc = cx.concat(hi, acc)?;
}
acc
};
self.value.insert(base.to_string(), new);
if !self.written.iter().any(|n| n == base) {
self.written.push(base.to_string());
}
Ok(())
}
pub(crate) fn outputs(&self) -> Vec<(String, Expr)> {
self.written
.iter()
.map(|n| (n.clone(), self.value[n]))
.collect()
}
}
pub(crate) fn memory(addr: Width, endian: Endian) -> Memory {
Memory::new("mem", addr, Width::W8, endian)
}
pub(crate) fn at_line(line: usize, m: impl core::fmt::Display) -> Error {
Error::Unsupported(format!("line {line}: {m}"))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::engine::Engine;
fn simplified(cx: &mut Context, e: Expr) -> String {
let out = Engine::standard().simplify(cx, e).unwrap();
cx.display(out.expr).to_string()
}
#[test]
fn pcode_blocks() {
let mut cx = Context::new();
let b = pcode(
&mut cx,
"0x1000: (unique, 0xe80, 8) = COPY (register, 0x28, 8)
(register, 0x20, 8) = INT_SUB (register, 0x20, 8) , (const, 0x8, 8)
STORE (const, 0x1b1, 4) , (register, 0x20, 8) , (unique, 0xe80, 8)
(register, 0x28, 8) = COPY (register, 0x20, 8)
(unique, 0x3100, 8) = INT_ADD (register, 0x28, 8) , (const, 0xfffffffffffffff8, 8)
STORE (const, 0x1b1, 4) , (unique, 0x3100, 8) , (register, 0x38, 8)
(unique, 0x3100, 8) = INT_ADD (register, 0x28, 8) , (const, 0xfffffffffffffff8, 8)
(register, 0x0, 4) = LOAD (const, 0x1b1, 4) , (unique, 0x3100, 8)
(register, 0x0, 4) = INT_ADD (register, 0x0, 4) , (const, 0x1, 4)
(register, 0x28, 8) = LOAD (const, 0x1b1, 4) , (register, 0x20, 8)
(register, 0x20, 8) = INT_ADD (register, 0x20, 8) , (const, 0x8, 8)
RETURN (register, 0x288, 8)",
)
.unwrap();
let rax = b.register("reg_0x0").unwrap();
let eax = cx.extract(rax, 0, Width::W32).unwrap();
assert_eq!(simplified(&mut cx, eax), "trunc<32>(reg_0x38) + 1");
let rbp = b.register("reg_0x28").unwrap();
assert_eq!(simplified(&mut cx, rbp), "reg_0x28");
let rsp = b.register("reg_0x20").unwrap();
assert_eq!(simplified(&mut cx, rsp), "reg_0x20");
assert_eq!(b.stores.len(), 2);
assert!(b.next.is_some());
let mut cx = Context::new();
let b = pcode(
&mut cx,
"(register, AL, 1) = INT_ADD (register, AL, 1) , (register, AH, 1)
(unique, 0x10, 1) = INT_EQUAL (register, AL, 1) , (const, 0x0, 1)
CBRANCH (ram, 0x401020, 8) , (unique, 0x10, 1)
(register, RCX, 8) = INT_ZEXT (register, AX, 2)",
)
.unwrap();
let rcx = b.register("RCX").unwrap();
assert_eq!(
simplified(&mut cx, rcx),
"concat(extract<8, 56>(RAX), extract<8, 8>(RAX) + trunc<8>(RAX)) & 65535"
);
assert_eq!(b.exits.len(), 1);
}
#[test]
fn vex_blocks() {
let mut cx = Context::new();
let b = vex(
&mut cx,
"IRSB {
t0:Ity_I64 t1:Ity_I64 t2:Ity_I64 t3:Ity_I64 t4:Ity_I64 t5:Ity_I64 t6:Ity_I1
00 | ------ IMark(0x401000, 3, 0) ------
01 | t0 = GET:I64(rdi)
02 | t1 = GET:I64(rsi)
03 | t2 = Xor64(t0,t1)
04 | t3 = And64(t0,t1)
05 | t4 = Shl64(t3,0x01)
06 | t5 = Add64(t2,t4)
07 | PUT(rax) = t5
08 | STle(t0) = t5
09 | t6 = CmpEQ64(t5,0x0000000000000000)
10 | if (t6) { PUT(rip) = 0x0000000000401010; Ijk_Boring }
NEXT: PUT(rip) = 0x0000000000401008; Ijk_Boring
}",
)
.unwrap();
let rax = b.register("rax").unwrap();
assert_eq!(simplified(&mut cx, rax), "rdi + rsi");
assert_eq!(b.stores.len(), 1);
assert_eq!(b.exits.len(), 1);
let mut cx = Context::new();
let b = vex(
&mut cx,
"t0 = GET:I64(rsp)
STle(t0) = 0x0000002a
t1 = LDle:I32(t0)
t2 = 32Uto64(t1)
PUT(rax) = t2
PUT(ecx) = t1",
)
.unwrap();
assert_eq!(simplified(&mut cx, b.register("rax").unwrap()), "42:64");
let rcx = b.register("rcx").unwrap();
assert_eq!(
simplified(&mut cx, rcx),
"concat(extract<32, 32>(rcx), 42:32)"
);
}
#[cfg(feature = "prove")]
#[test]
fn llvm_functions() {
let mut cx = Context::new();
let b = llvm(
&mut cx,
"define i32 @f(ptr %p, i32 %x, i32 %y) {
%a = xor i32 %x, %y
%b = and i32 %x, %y
%c = shl i32 %b, 1
%s = getelementptr inbounds i32, ptr %p, i64 1
store i32 %a, ptr %s, align 4
store i32 %c, ptr %p, align 4
%l0 = load i32, ptr %p, align 4
%l1 = load i32, ptr %s, align 4
%r = add i32 %l1, %l0
ret i32 %r
}",
None,
)
.unwrap();
let ret = b.register("ret").unwrap();
assert_eq!(simplified(&mut cx, ret), "x + y");
assert_eq!(b.stores.len(), 2);
}
}