use crate::ir::*;
use pcode_ir::AddressSpaceId;
const FLAG_OFFSETS: &[u64] = &[
512, 513, 514, 518, 519, 521, 523, 256, 257, 258, 259, 261, 262, 263,
264, 96, 97, 98, 99, 100, 101, 102, 103,
104, ];
const RSP_OFFSET: u64 = 32; const ESP_OFFSET: u64 = 16; const RIP_OFFSET: u64 = 648;
pub const RAX_OFFSET: u64 = 0;
const SYSV_ARG_REGS: &[u64] = &[56, 48, 16, 8, 128, 136];
const WIN64_ARG_REGS: &[u64] = &[8, 16, 128, 136];
const AARCH64_ARG_REGS: &[u64] = &[16384, 16392, 16400, 16408, 16416, 16424, 16432, 16440];
const ARM32_ARG_REGS: &[u64] = &[32, 36, 40, 44];
const SYSV_FLOAT_ARG_REGS: &[u64] = &[4608, 4672, 4736, 4800, 4864, 4928, 4992, 5056];
const WIN64_FLOAT_ARG_REGS: &[u64] = &[4608, 4672, 4736, 4800];
const AARCH64_FLOAT_ARG_REGS: &[u64] = &[20480, 20512, 20544, 20576, 20608, 20640, 20672, 20704];
const GO_AMD64_ARG_REGS: &[u64] = &[0, 24, 8, 56, 48, 128, 136, 144, 152];
const GO_AMD64_FLOAT_ARG_REGS: &[u64] = &[
4608, 4672, 4736, 4800, 4864, 4928, 4992, 5056, 5120, 5184, 5248, 5312, 5376, 5440, 5504,
];
std::thread_local! {
static ARG_REG_OFFSETS_TLS: std::cell::RefCell<&'static [u64]> = const { std::cell::RefCell::new(SYSV_ARG_REGS) };
}
std::thread_local! {
static FLOAT_ARG_REG_OFFSETS_TLS: std::cell::RefCell<&'static [u64]> = const { std::cell::RefCell::new(SYSV_FLOAT_ARG_REGS) };
}
fn arg_reg_offsets() -> &'static [u64] {
ARG_REG_OFFSETS_TLS.with(|r| *r.borrow())
}
fn float_arg_reg_offsets() -> &'static [u64] {
FLOAT_ARG_REG_OFFSETS_TLS.with(|r| *r.borrow())
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum CallingConv {
SysV, Win64, Cdecl32, Stdcall32, Thiscall32, Fastcall32, AArch64, Arm32, GoAmd64, }
#[derive(Clone, Copy, Debug)]
pub struct Abi {
pub int_args: &'static [u64],
pub float_args: &'static [u64],
pub return_reg_int: Option<u64>,
pub return_reg_float: Option<u64>,
pub callee_cleanup_stack: bool,
pub shadow_space_bytes: u32,
pub allows_varargs: bool,
}
const X86_32_ECX: u64 = 0x4;
const X86_32_EDX: u64 = 0x8;
const THISCALL32_ARG_REGS: &[u64] = &[X86_32_ECX]; const FASTCALL32_ARG_REGS: &[u64] = &[X86_32_ECX, X86_32_EDX];
pub fn abi(cc: CallingConv) -> Abi {
match cc {
CallingConv::SysV => Abi {
int_args: SYSV_ARG_REGS,
float_args: SYSV_FLOAT_ARG_REGS,
return_reg_int: Some(RAX_OFFSET),
return_reg_float: Some(4608), callee_cleanup_stack: false,
shadow_space_bytes: 0,
allows_varargs: true,
},
CallingConv::Win64 => Abi {
int_args: WIN64_ARG_REGS,
float_args: WIN64_FLOAT_ARG_REGS,
return_reg_int: Some(RAX_OFFSET),
return_reg_float: Some(4608),
callee_cleanup_stack: false,
shadow_space_bytes: 32,
allows_varargs: true,
},
CallingConv::Cdecl32 => Abi {
int_args: &[],
float_args: &[],
return_reg_int: Some(RAX_OFFSET), return_reg_float: None, callee_cleanup_stack: false,
shadow_space_bytes: 0,
allows_varargs: true,
},
CallingConv::Stdcall32 => Abi {
int_args: &[],
float_args: &[],
return_reg_int: Some(RAX_OFFSET),
return_reg_float: None,
callee_cleanup_stack: true,
shadow_space_bytes: 0,
allows_varargs: false,
},
CallingConv::Thiscall32 => Abi {
int_args: THISCALL32_ARG_REGS,
float_args: &[],
return_reg_int: Some(RAX_OFFSET),
return_reg_float: None,
callee_cleanup_stack: true,
shadow_space_bytes: 0,
allows_varargs: false,
},
CallingConv::Fastcall32 => Abi {
int_args: FASTCALL32_ARG_REGS,
float_args: &[],
return_reg_int: Some(RAX_OFFSET),
return_reg_float: None,
callee_cleanup_stack: true,
shadow_space_bytes: 0,
allows_varargs: false,
},
CallingConv::AArch64 => Abi {
int_args: AARCH64_ARG_REGS,
float_args: AARCH64_FLOAT_ARG_REGS,
return_reg_int: Some(16384), return_reg_float: Some(20480), callee_cleanup_stack: false,
shadow_space_bytes: 0,
allows_varargs: true,
},
CallingConv::Arm32 => Abi {
int_args: ARM32_ARG_REGS,
float_args: &[],
return_reg_int: Some(32), return_reg_float: None,
callee_cleanup_stack: false,
shadow_space_bytes: 0,
allows_varargs: true,
},
CallingConv::GoAmd64 => Abi {
int_args: GO_AMD64_ARG_REGS,
float_args: GO_AMD64_FLOAT_ARG_REGS,
return_reg_int: Some(RAX_OFFSET),
return_reg_float: Some(4608),
callee_cleanup_stack: false,
shadow_space_bytes: 0,
allows_varargs: false,
},
}
}
pub fn fold(ssa: &mut SsaCfg) {
fold_with_cc(ssa, CallingConv::SysV);
}
pub fn fold_with_cc(ssa: &mut SsaCfg, cc: CallingConv) {
let abi_for_cc = abi(cc);
ARG_REG_OFFSETS_TLS.with(|r| {
*r.borrow_mut() = abi_for_cc.int_args;
});
FLOAT_ARG_REG_OFFSETS_TLS.with(|r| {
*r.borrow_mut() = abi_for_cc.float_args;
});
let _ = crate::ir::take_safe_var_oob_count();
collect_call_arguments(ssa);
recount_uses(ssa);
if matches!(
cc,
CallingConv::SysV | CallingConv::Win64 | CallingConv::Cdecl32 | CallingConv::GoAmd64
) {
for v in ssa.vars.iter_mut() {
if matches!(v.expr, Expr::Unknown)
&& v.varnode.space == AddressSpaceId::Register
&& v.varnode.offset == 522
{
v.expr = Expr::Const(0, v.size);
}
}
}
name_parameters_with_cc(ssa, cc);
for _round in 0..8 {
let before = count_live_stmts(ssa);
fold_once(ssa);
recount_uses(ssa);
propagate_register_constants(ssa);
propagate_call_returns(ssa);
recount_uses(ssa);
eliminate_dead(ssa);
recount_uses(ssa);
recover_conditions(ssa);
mba_simplify(ssa);
detect_return_values(ssa);
recount_uses(ssa);
name_loop_phis(ssa);
name_parameters_with_cc(ssa, cc); let after = count_live_stmts(ssa);
if before == after {
break;
}
}
infer_types(ssa);
recognize_field_access(ssa);
if matches!(cc, CallingConv::GoAmd64) {
infer_go_header_params(ssa);
}
let oob = crate::ir::take_safe_var_oob_count();
if oob > 0 {
ssa.diagnostics.push(crate::ir::Diagnostic {
severity: crate::ir::Severity::Warn,
kind: crate::ir::DiagKind::OobVarId,
addr: None,
detail: format!(
"safe_var sentinel fallback fired {} time(s) during fold; \
SSA references a VarId past end of vars[]",
oob
),
});
}
}
fn infer_go_header_params(ssa: &mut SsaCfg) {
let mut param_vars: std::collections::BTreeMap<String, Vec<usize>> =
std::collections::BTreeMap::new();
for (i, v) in ssa.vars.iter().enumerate() {
if let Some(n) = v.param_name.as_ref() {
if n.starts_with("param_") {
param_vars.entry(n.clone()).or_default().push(i);
}
}
}
let mut indexed: Vec<(u32, String)> = param_vars
.keys()
.filter_map(|n| {
n.strip_prefix("param_")
.and_then(|s| s.parse::<u32>().ok())
.map(|i| (i, n.clone()))
})
.collect();
indexed.sort();
if indexed.is_empty() {
return;
}
let closure_from = |roots: &[usize], ssa: &SsaCfg| -> std::collections::HashSet<usize> {
let mut set: std::collections::HashSet<usize> = roots.iter().copied().collect();
let mut changed = true;
let mut guard = 0;
while changed && guard < 8 {
changed = false;
guard += 1;
for (idx, v) in ssa.vars.iter().enumerate() {
if set.contains(&idx) {
continue;
}
let parents: &[VarId] = match &v.expr {
Expr::Var(a) => std::slice::from_ref(a),
Expr::UnaryOp(_, a) => std::slice::from_ref(a),
Expr::BinOp(_, a, b) => {
if set.contains(&(a.0 as usize)) || set.contains(&(b.0 as usize)) {
set.insert(idx);
changed = true;
}
continue;
}
_ => continue,
};
if parents.iter().any(|p| set.contains(&(p.0 as usize))) {
set.insert(idx);
changed = true;
}
}
}
set
};
let is_pointer_like = |p: &str,
param_vars: &std::collections::BTreeMap<String, Vec<usize>>,
ssa: &SsaCfg|
-> bool {
let vars = match param_vars.get(p) {
Some(v) => v,
None => return false,
};
for &vi in vars {
if ssa.vars[vi].inferred_type == InferredType::Pointer {
return true;
}
}
let derived = closure_from(vars, ssa);
for blk in &ssa.blocks {
for stmt in &blk.stmts {
match stmt {
Stmt::Store { addr, .. } => {
if derived.contains(&(addr.0 as usize)) {
return true;
}
}
Stmt::Assign(vid) => match &ssa.vars[vid.0 as usize].expr {
Expr::Load(ptr) if derived.contains(&(ptr.0 as usize)) => return true,
Expr::FieldAccess(base, _) if derived.contains(&(base.0 as usize)) => {
return true
}
_ => {}
},
_ => {}
}
}
}
false
};
let is_length_like = |p: &str,
param_vars: &std::collections::BTreeMap<String, Vec<usize>>,
ssa: &SsaCfg|
-> bool {
let vars = match param_vars.get(p) {
Some(v) => v,
None => return false,
};
for v in ssa.vars.iter() {
if let Expr::BinOp(kind, l, r) = &v.expr {
if matches!(
kind,
BinOpKind::Less
| BinOpKind::LessEq
| BinOpKind::SLess
| BinOpKind::SLessEq
| BinOpKind::Eq
| BinOpKind::NotEq
) {
if vars.contains(&(l.0 as usize)) || vars.contains(&(r.0 as usize)) {
return true;
}
}
}
}
for blk in &ssa.blocks {
for stmt in &blk.stmts {
if let Stmt::Call { args, .. } = stmt {
for a in args {
if vars.contains(&(a.0 as usize)) {
return true;
}
}
}
}
}
false
};
let len_is_bound_by_data = |a_name: &str,
b_name: &str,
param_vars: &std::collections::BTreeMap<String, Vec<usize>>,
ssa: &SsaCfg|
-> bool {
let a_vars = match param_vars.get(a_name) {
Some(v) => v,
None => return false,
};
let b_vars = match param_vars.get(b_name) {
Some(v) => v,
None => return false,
};
let a_derived = closure_from(a_vars, ssa);
let mut load_chain: std::collections::HashSet<usize> = std::collections::HashSet::new();
load_chain.extend(a_derived.iter().copied());
for (idx, v) in ssa.vars.iter().enumerate() {
match &v.expr {
Expr::Load(p) if a_derived.contains(&(p.0 as usize)) => {
load_chain.insert(idx);
}
Expr::FieldAccess(p, _) if a_derived.contains(&(p.0 as usize)) => {
load_chain.insert(idx);
}
_ => {}
}
}
let load_chain = closure_from(&load_chain.iter().copied().collect::<Vec<_>>(), ssa);
for v in ssa.vars.iter() {
if let Expr::BinOp(kind, l, r) = &v.expr {
if matches!(
kind,
BinOpKind::Less
| BinOpKind::LessEq
| BinOpKind::SLess
| BinOpKind::SLessEq
| BinOpKind::Eq
| BinOpKind::NotEq
) {
let l_b = b_vars.contains(&(l.0 as usize));
let r_b = b_vars.contains(&(r.0 as usize));
let l_a = load_chain.contains(&(l.0 as usize));
let r_a = load_chain.contains(&(r.0 as usize));
if (l_b && r_a) || (r_b && l_a) {
return true;
}
}
}
}
false
};
let mut i = 0;
while i + 1 < indexed.len() {
let (ai, a_name) = &indexed[i];
let (bi, b_name) = &indexed[i + 1];
if *bi != *ai + 1 {
i += 1;
continue;
}
let a_ptr = is_pointer_like(a_name, ¶m_vars, ssa);
let b_len = is_length_like(b_name, ¶m_vars, ssa);
if !(a_ptr && b_len) {
i += 1;
continue;
}
if len_is_bound_by_data(a_name, b_name, ¶m_vars, ssa) {
i += 1;
continue;
}
let mut stride = 2;
let mut is_slice = false;
if i + 2 < indexed.len() {
let (ci, c_name) = &indexed[i + 2];
if *ci == *bi + 1 && is_length_like(c_name, ¶m_vars, ssa) {
stride = 3;
is_slice = true;
}
}
let suffix_data = *ai;
let suffix_len = *bi;
let (data_name, len_name) = if is_slice {
(
format!("slice_data_{}", suffix_data),
format!("slice_len_{}", suffix_len),
)
} else {
(
format!("s_data_{}", suffix_data),
format!("s_len_{}", suffix_len),
)
};
rename_param(ssa, a_name, &data_name);
rename_param(ssa, b_name, &len_name);
if is_slice {
let (ci, c_name) = &indexed[i + 2];
let cap_name = format!("slice_cap_{}", *ci);
rename_param(ssa, c_name, &cap_name);
}
i += stride;
}
}
fn rename_param(ssa: &mut SsaCfg, old: &str, new: &str) {
for v in &mut ssa.vars {
if v.param_name.as_deref() == Some(old) {
v.param_name = Some(new.to_string());
}
}
}
fn count_live_stmts(ssa: &SsaCfg) -> usize {
ssa.blocks.iter().map(|b| b.stmts.len()).sum()
}
fn combine_frame_offset(op1: BinOpKind, c1: u64, op2: BinOpKind, c2: u64) -> (BinOpKind, u64) {
let s1 = c1 as i64;
let s2 = c2 as i64;
let delta1: i64 = if matches!(op1, BinOpKind::Sub) {
-s1
} else {
s1
};
let delta2: i64 = if matches!(op2, BinOpKind::Sub) {
-s2
} else {
s2
};
let combined = delta1.wrapping_add(delta2);
if combined < 0 {
(BinOpKind::Sub, (-combined) as u64)
} else {
(BinOpKind::Add, combined as u64)
}
}
fn negate_eq_op(op: BinOpKind) -> Option<BinOpKind> {
match op {
BinOpKind::Eq => Some(BinOpKind::NotEq),
BinOpKind::NotEq => Some(BinOpKind::Eq),
_ => None,
}
}
fn fold_once(ssa: &mut SsaCfg) {
for v in 0..ssa.vars.len() {
if let Expr::Phi(inputs) = &ssa.vars[v].expr {
if inputs.is_empty() {
continue;
}
let first = inputs[0];
if inputs.iter().all(|i| *i == first) {
ssa.vars[v].expr = Expr::Var(first);
}
}
}
for v in 0..ssa.vars.len() {
if let Expr::Ternary(cond, then_val, else_val) = &ssa.vars[v].expr {
let c = *cond;
let t = *then_val;
let e = *else_val;
if matches!(&ssa.vars[c.0 as usize].expr, Expr::Const(v, _) if *v != 0) {
ssa.vars[v].expr = Expr::Var(t);
}
else if matches!(&ssa.vars[c.0 as usize].expr, Expr::Const(0, _)) {
ssa.vars[v].expr = Expr::Var(e);
}
else if t == e {
ssa.vars[v].expr = Expr::Var(t);
}
}
}
for v in 0..ssa.vars.len() {
let expr = ssa.vars[v].expr.clone();
ssa.vars[v].expr = simplify_expr(expr, &ssa.vars);
}
for v in 0..ssa.vars.len() {
if matches!(
&ssa.vars[v].expr,
Expr::BinOp(_, _, _) | Expr::UnaryOp(_, _)
) {
if let Some((val, sz)) = const_fold_expr(&ssa.vars[v].expr, &ssa.vars) {
ssa.vars[v].expr = Expr::Const(val, sz);
}
}
}
for v in 0..ssa.vars.len() {
let (op2, inner_id, c2_id) = match &ssa.vars[v].expr {
Expr::BinOp(op, inner, c2) if matches!(op, BinOpKind::Add | BinOpKind::Sub) => {
(*op, *inner, *c2)
}
_ => continue,
};
let c2_val = match ssa.vars[c2_id.0 as usize].expr {
Expr::Const(val, _) => val,
_ => continue,
};
let (op1, frame_id, c1_id) = match &ssa.vars[inner_id.0 as usize].expr {
Expr::BinOp(op, frame, c1) if matches!(op, BinOpKind::Add | BinOpKind::Sub) => {
(*op, *frame, *c1)
}
_ => continue,
};
let c1_val = match ssa.vars[c1_id.0 as usize].expr {
Expr::Const(val, _) => val,
_ => continue,
};
let frame_vdef = &ssa.vars[frame_id.0 as usize];
if frame_vdef.varnode.space != AddressSpaceId::Register
|| frame_vdef.varnode.offset != RSP_OFFSET
|| !matches!(frame_vdef.expr, Expr::Unknown)
{
continue;
}
let (combined_op, combined_c) = combine_frame_offset(op1, c1_val, op2, c2_val);
let sz = ssa.vars[c1_id.0 as usize].size;
let varnode = ssa.vars[c1_id.0 as usize].varnode;
let new_const_id = ssa.new_var(varnode, Expr::Const(combined_c, sz), sz);
ssa.vars[v].expr = Expr::BinOp(combined_op, frame_id, new_const_id);
}
mba_simplify(ssa);
{
let prev_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(|_| { }));
for v in 0..ssa.vars.len() {
let depth = expr_depth(&ssa.vars[v].expr, &ssa.vars, 0);
if depth >= 5 {
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
crate::eqsat::simplify_expr(v, &mut ssa.vars)
}));
if let Ok(Some(simplified)) = result {
ssa.vars[v].expr = simplified;
}
}
}
std::panic::set_hook(prev_hook);
}
let inline_candidates: Vec<(VarId, Expr)> = (0..ssa.vars.len())
.filter_map(|v| {
let vdef = &ssa.vars[v];
if vdef.use_count == 1 && vdef.varnode.space == AddressSpaceId::Unique {
Some((vdef.id, vdef.expr.clone()))
} else if matches!(vdef.expr, Expr::Const(_, _)) {
Some((vdef.id, vdef.expr.clone()))
} else {
None
}
})
.collect();
for v in 0..ssa.vars.len() {
let expr = ssa.vars[v].expr.clone();
ssa.vars[v].expr = substitute_expr(&expr, &inline_candidates);
}
propagate_register_copies(ssa);
}
fn mba_simplify(ssa: &mut SsaCfg) {
for _pass in 0..4 {
let mut changed = false;
for v in 0..ssa.vars.len() {
let new_expr = mba_simplify_expr(v, &ssa.vars);
if let Some(new_expr) = new_expr {
ssa.vars[v].expr = new_expr;
changed = true;
}
}
if !changed {
break;
}
}
mba_oracle_simplify(ssa);
}
fn mba_oracle_simplify(ssa: &mut SsaCfg) {
for _pass in 0..6 {
let mut changed = false;
for v in 0..ssa.vars.len() {
if try_simba_at(v, &mut ssa.vars) {
changed = true;
}
}
for v in (0..ssa.vars.len()).rev() {
if try_simba_at(v, &mut ssa.vars) {
changed = true;
}
}
if !changed {
break;
}
}
}
fn try_simba_at(v: usize, vars: &mut Vec<VarDef>) -> bool {
let depth = expr_depth(&vars[v].expr, vars, 0);
if depth < 2 {
return false;
}
if let Some((val, sz)) = const_fold_expr(&vars[v].expr, vars) {
vars[v].expr = Expr::Const(val, sz);
return true;
}
let mut bases: Vec<VarId> = Vec::new();
collect_base_vars(&vars[v].expr, vars, &mut bases, 0);
bases.sort_by_key(|id| id.0);
bases.dedup_by_key(|id| id.0);
if bases.is_empty() || bases.len() > 4 {
return false;
}
let sz = vars[v].size;
let mask = if sz >= 8 {
u64::MAX
} else {
(1u64 << (sz * 8)).wrapping_sub(1)
};
let simplified = match bases.len() {
1 => simba_simplify_1var(v, vars, bases[0], mask, sz),
2 => simba_simplify_2var(v, vars, &bases, mask, sz),
3 => simba_simplify_3var(v, vars, &bases, mask, sz),
4 => simba_simplify_4var(v, vars, &bases, mask, sz),
_ => None,
};
if let Some(simple) = simplified {
vars[v].expr = simple;
return true;
}
false
}
fn expr_depth(expr: &Expr, vars: &[VarDef], depth: usize) -> usize {
if depth > 10 {
return depth;
} match expr {
Expr::Const(_, _) | Expr::Unknown => 0,
Expr::Var(id) => {
if id.0 as usize >= vars.len() {
return 0;
}
expr_depth(&vars[id.0 as usize].expr, vars, depth + 1)
}
Expr::BinOp(_, left, right) => {
let ld = expr_depth(&vars[left.0 as usize].expr, vars, depth + 1);
let rd = expr_depth(&vars[right.0 as usize].expr, vars, depth + 1);
1 + ld.max(rd)
}
Expr::UnaryOp(_, inner) => 1 + expr_depth(&vars[inner.0 as usize].expr, vars, depth + 1),
_ => 0,
}
}
fn collect_base_vars(expr: &Expr, vars: &[VarDef], bases: &mut Vec<VarId>, depth: usize) {
if depth > 10 {
return;
}
match expr {
Expr::Const(_, _) => {}
Expr::Unknown => {}
Expr::Var(id) => {
if id.0 as usize >= vars.len() {
return;
}
let inner = &vars[id.0 as usize].expr;
if matches!(inner, Expr::Unknown)
|| matches!(inner, Expr::Load(_))
|| matches!(inner, Expr::Phi(_))
|| matches!(inner, Expr::FieldAccess(_, _))
|| matches!(inner, Expr::Ternary(_, _, _))
{
bases.push(*id); } else {
collect_base_vars(inner, vars, bases, depth + 1);
}
}
Expr::BinOp(_, left, right) => {
collect_base_vars(&Expr::Var(*left), vars, bases, depth + 1);
collect_base_vars(&Expr::Var(*right), vars, bases, depth + 1);
}
Expr::UnaryOp(_, inner) => {
collect_base_vars(&Expr::Var(*inner), vars, bases, depth + 1);
}
_ => {}
}
}
fn eval_expr(
expr: &Expr,
vars: &[VarDef],
env: &std::collections::HashMap<u32, u64>,
mask: u64,
depth: usize,
) -> Option<u64> {
if depth > 20 {
return None;
}
match expr {
Expr::Const(val, _) => Some(*val & mask),
Expr::Unknown
| Expr::Load(_)
| Expr::Phi(_)
| Expr::FieldAccess(_, _)
| Expr::Ternary(_, _, _)
| Expr::UserOp { .. } => None,
Expr::Var(id) => {
if let Some(&val) = env.get(&id.0) {
Some(val & mask)
} else if (id.0 as usize) < vars.len() {
eval_expr(&vars[id.0 as usize].expr, vars, env, mask, depth + 1)
} else {
None
}
}
Expr::BinOp(kind, left, right) => {
let l = eval_expr(&Expr::Var(*left), vars, env, mask, depth + 1)?;
let r = eval_expr(&Expr::Var(*right), vars, env, mask, depth + 1)?;
let result = match kind {
BinOpKind::Add => l.wrapping_add(r),
BinOpKind::Sub => l.wrapping_sub(r),
BinOpKind::Mult => l.wrapping_mul(r),
BinOpKind::And => l & r,
BinOpKind::Or => l | r,
BinOpKind::Xor => l ^ r,
BinOpKind::Lsl => l.wrapping_shl((r & 63) as u32),
BinOpKind::Lsr => l.wrapping_shr((r & 63) as u32),
BinOpKind::Asr => ((l as i64).wrapping_shr((r & 63) as u32)) as u64,
_ => return None,
};
Some(result & mask)
}
Expr::UnaryOp(kind, inner) => {
let v = eval_expr(&Expr::Var(*inner), vars, env, mask, depth + 1)?;
let result = match kind {
UnaryOpKind::Neg => (-(v as i64)) as u64,
UnaryOpKind::Not => !v,
_ => return None,
};
Some(result & mask)
}
}
}
fn simba_simplify_2var(
var_idx: usize,
vars: &[VarDef],
bases: &[VarId],
mask: u64,
sz: u32,
) -> Option<Expr> {
if bases.len() != 2 {
return None;
}
let (a_id, b_id) = (bases[0], bases[1]);
let mut env = std::collections::HashMap::new();
env.clear();
env.insert(a_id.0, 0u64);
env.insert(b_id.0, 0u64);
let f00 = eval_expr(&vars[var_idx].expr, vars, &env, mask, 0)?;
env.clear();
env.insert(a_id.0, 1u64);
env.insert(b_id.0, 0u64);
let f10 = eval_expr(&vars[var_idx].expr, vars, &env, mask, 0)?;
env.clear();
env.insert(a_id.0, 0u64);
env.insert(b_id.0, 1u64);
let f01 = eval_expr(&vars[var_idx].expr, vars, &env, mask, 0)?;
env.clear();
env.insert(a_id.0, 1u64);
env.insert(b_id.0, 1u64);
let f11 = eval_expr(&vars[var_idx].expr, vars, &env, mask, 0)?;
let c0 = f00;
let c1 = f10.wrapping_sub(f00) & mask;
let c2 = f01.wrapping_sub(f00) & mask;
let c3 = f11.wrapping_sub(f10).wrapping_sub(f01).wrapping_add(f00) & mask;
env.clear();
env.insert(a_id.0, 0xAA);
env.insert(b_id.0, 0x55);
let f_test = eval_expr(&vars[var_idx].expr, vars, &env, mask, 0)?;
let expected = (c0
.wrapping_add(c1.wrapping_mul(0xAA))
.wrapping_add(c2.wrapping_mul(0x55))
.wrapping_add(c3.wrapping_mul(0xAA & 0x55)))
& mask;
if f_test != expected {
return None;
}
env.clear();
env.insert(a_id.0, 0xFF);
env.insert(b_id.0, 0x42);
let f_test2 = eval_expr(&vars[var_idx].expr, vars, &env, mask, 0)?;
let expected2 = (c0
.wrapping_add(c1.wrapping_mul(0xFF))
.wrapping_add(c2.wrapping_mul(0x42))
.wrapping_add(c3.wrapping_mul(0xFF & 0x42)))
& mask;
if f_test2 != expected2 {
return None;
}
let neg1 = mask; let neg2 = mask.wrapping_sub(1);
if c0 == 0 {
match (c1, c2, c3) {
(0, 0, 0) => return Some(Expr::Const(0, sz)),
(1, 0, 0) => return Some(Expr::Var(a_id)),
(0, 1, 0) => return Some(Expr::Var(b_id)),
(1, 1, 0) => return Some(Expr::BinOp(BinOpKind::Add, a_id, b_id)),
_ if c1 == 1 && c2 == neg1 && c3 == 0 => {
return Some(Expr::BinOp(BinOpKind::Sub, a_id, b_id))
}
_ if c1 == neg1 && c2 == 1 && c3 == 0 => {
return Some(Expr::BinOp(BinOpKind::Sub, b_id, a_id))
}
(0, 0, 1) => return Some(Expr::BinOp(BinOpKind::And, a_id, b_id)),
_ if c1 == 1 && c2 == 1 && c3 == neg1 => {
return Some(Expr::BinOp(BinOpKind::Or, a_id, b_id))
}
_ if c1 == 1 && c2 == 1 && c3 == neg2 => {
return Some(Expr::BinOp(BinOpKind::Xor, a_id, b_id))
}
_ if c1 == 1 && c2 == 0 && c3 == neg1 => {
return Some(Expr::BinOp(BinOpKind::Sub, a_id, b_id)); }
_ if c1 == 0 && c2 == 1 && c3 == neg1 => {
return Some(Expr::BinOp(BinOpKind::Sub, b_id, a_id)); }
_ => {}
}
}
if c0 != 0 && c3 == 0 {
if c1 == 1 && c2 == 0 {
return None;
}
if c1 == 0 && c2 == 1 {
return None; }
}
if c0 == neg1 {
match (c1, c2, c3) {
_ if c1 == neg1 && c2 == neg1 && c3 == 1 => {
return None; }
_ if c1 == neg1 && c2 == 0 && c3 == 0 => {
return Some(Expr::UnaryOp(UnaryOpKind::Not, a_id));
}
_ if c1 == 0 && c2 == neg1 && c3 == 0 => {
return Some(Expr::UnaryOp(UnaryOpKind::Not, b_id));
}
_ => {}
}
}
None
}
fn simba_simplify_3var(
var_idx: usize,
vars: &[VarDef],
bases: &[VarId],
mask: u64,
_sz: u32,
) -> Option<Expr> {
if bases.len() != 3 {
return None;
}
let (a_id, b_id, c_id) = (bases[0], bases[1], bases[2]);
let mut env = std::collections::HashMap::new();
let mut f = [0u64; 8];
let combos: [(u64, u64, u64); 8] = [
(0, 0, 0),
(1, 0, 0),
(0, 1, 0),
(0, 0, 1),
(1, 1, 0),
(1, 0, 1),
(0, 1, 1),
(1, 1, 1),
];
for (i, (a, b, c)) in combos.iter().enumerate() {
env.clear();
env.insert(a_id.0, *a);
env.insert(b_id.0, *b);
env.insert(c_id.0, *c);
f[i] = eval_expr(&vars[var_idx].expr, vars, &env, mask, 0)?;
}
let c0 = f[0];
let c1 = f[1].wrapping_sub(f[0]) & mask;
let c2 = f[2].wrapping_sub(f[0]) & mask;
let c3 = f[3].wrapping_sub(f[0]) & mask;
let c4 = f[4]
.wrapping_sub(f[1])
.wrapping_sub(f[2])
.wrapping_add(f[0])
& mask;
let c5 = f[5]
.wrapping_sub(f[1])
.wrapping_sub(f[3])
.wrapping_add(f[0])
& mask;
let c6 = f[6]
.wrapping_sub(f[2])
.wrapping_sub(f[3])
.wrapping_add(f[0])
& mask;
let c7 = f[7]
.wrapping_sub(f[4])
.wrapping_sub(f[5])
.wrapping_sub(f[6])
.wrapping_add(f[1])
.wrapping_add(f[2])
.wrapping_add(f[3])
.wrapping_sub(f[0])
& mask;
env.clear();
env.insert(a_id.0, 0xAA);
env.insert(b_id.0, 0x55);
env.insert(c_id.0, 0x42);
let f_test = eval_expr(&vars[var_idx].expr, vars, &env, mask, 0)?;
let (ta, tb, tc) = (0xAAu64, 0x55u64, 0x42u64);
let expected = c0
.wrapping_add(c1.wrapping_mul(ta))
.wrapping_add(c2.wrapping_mul(tb))
.wrapping_add(c3.wrapping_mul(tc))
.wrapping_add(c4.wrapping_mul(ta & tb))
.wrapping_add(c5.wrapping_mul(ta & tc))
.wrapping_add(c6.wrapping_mul(tb & tc))
.wrapping_add(c7.wrapping_mul(ta & tb & tc))
& mask;
if f_test != expected {
return None;
}
let neg1 = mask;
let neg2 = mask.wrapping_sub(1);
if c0 != 0 {
return None;
}
let coeffs = [c1, c2, c3, c4, c5, c6, c7];
let nonzero = coeffs.iter().filter(|&&c| c != 0).count();
if nonzero == 1 {
if c1 == 1 {
return Some(Expr::Var(a_id));
}
if c2 == 1 {
return Some(Expr::Var(b_id));
}
if c3 == 1 {
return Some(Expr::Var(c_id));
}
if c4 == 1 {
return Some(Expr::BinOp(BinOpKind::And, a_id, b_id));
}
if c5 == 1 {
return Some(Expr::BinOp(BinOpKind::And, a_id, c_id));
}
if c6 == 1 {
return Some(Expr::BinOp(BinOpKind::And, b_id, c_id));
}
if c1 == neg1 {
return Some(Expr::UnaryOp(UnaryOpKind::Neg, a_id));
}
if c2 == neg1 {
return Some(Expr::UnaryOp(UnaryOpKind::Neg, b_id));
}
if c3 == neg1 {
return Some(Expr::UnaryOp(UnaryOpKind::Neg, c_id));
}
}
if c1 == 1 && c2 == 1 && c3 == 0 && c4 == 0 && c5 == 0 && c6 == 0 && c7 == 0 {
return Some(Expr::BinOp(BinOpKind::Add, a_id, b_id));
}
if c1 == 1 && c3 == 1 && c2 == 0 && c4 == 0 && c5 == 0 && c6 == 0 && c7 == 0 {
return Some(Expr::BinOp(BinOpKind::Add, a_id, c_id));
}
if c2 == 1 && c3 == 1 && c1 == 0 && c4 == 0 && c5 == 0 && c6 == 0 && c7 == 0 {
return Some(Expr::BinOp(BinOpKind::Add, b_id, c_id));
}
if c1 == 1 && c2 == 1 && c4 == neg2 && c3 == 0 && c5 == 0 && c6 == 0 && c7 == 0 {
return Some(Expr::BinOp(BinOpKind::Xor, a_id, b_id));
}
if c1 == 1 && c3 == 1 && c5 == neg2 && c2 == 0 && c4 == 0 && c6 == 0 && c7 == 0 {
return Some(Expr::BinOp(BinOpKind::Xor, a_id, c_id));
}
if c2 == 1 && c3 == 1 && c6 == neg2 && c1 == 0 && c4 == 0 && c5 == 0 && c7 == 0 {
return Some(Expr::BinOp(BinOpKind::Xor, b_id, c_id));
}
if c1 == 1 && c2 == 1 && c4 == neg1 && c3 == 0 && c5 == 0 && c6 == 0 && c7 == 0 {
return Some(Expr::BinOp(BinOpKind::Or, a_id, b_id));
}
if c1 == 1 && c3 == 1 && c5 == neg1 && c2 == 0 && c4 == 0 && c6 == 0 && c7 == 0 {
return Some(Expr::BinOp(BinOpKind::Or, a_id, c_id));
}
if c2 == 1 && c3 == 1 && c6 == neg1 && c1 == 0 && c4 == 0 && c5 == 0 && c7 == 0 {
return Some(Expr::BinOp(BinOpKind::Or, b_id, c_id));
}
if c1 == 1 && c2 == neg1 && c3 == 0 && c4 == 0 && c5 == 0 && c6 == 0 && c7 == 0 {
return Some(Expr::BinOp(BinOpKind::Sub, a_id, b_id));
}
if c1 == 1 && c3 == neg1 && c2 == 0 && c4 == 0 && c5 == 0 && c6 == 0 && c7 == 0 {
return Some(Expr::BinOp(BinOpKind::Sub, a_id, c_id));
}
if c1 == 1 && c2 == 1 && c3 == 1 && c4 == 0 && c5 == 0 && c6 == 0 && c7 == 0 {
return None; }
if c1 == 1 && c2 == 1 && c3 == 1 && c4 == neg2 && c5 == neg2 && c6 == neg2 && c7 == 4 {
return None; }
None
}
fn simba_simplify_4var(
var_idx: usize,
vars: &[VarDef],
bases: &[VarId],
mask: u64,
_sz: u32,
) -> Option<Expr> {
if bases.len() != 4 {
return None;
}
let ids = [bases[0], bases[1], bases[2], bases[3]];
let mut env = std::collections::HashMap::new();
let _f = [[[[0u64; 1]; 2]; 2]; 2]; let mut vals = std::collections::HashMap::new();
for a in 0u64..=1 {
for b in 0u64..=1 {
for c in 0u64..=1 {
for d in 0u64..=1 {
env.clear();
env.insert(ids[0].0, a);
env.insert(ids[1].0, b);
env.insert(ids[2].0, c);
env.insert(ids[3].0, d);
let v = eval_expr(&vars[var_idx].expr, vars, &env, mask, 0)?;
vals.insert((a, b, c, d), v);
}
}
}
}
let v = |a: u64, b: u64, c: u64, d: u64| -> u64 { *vals.get(&(a, b, c, d)).unwrap() };
let c_1 = v(0, 0, 0, 0);
let c_a = v(1, 0, 0, 0).wrapping_sub(v(0, 0, 0, 0)) & mask;
let c_b = v(0, 1, 0, 0).wrapping_sub(v(0, 0, 0, 0)) & mask;
let c_c = v(0, 0, 1, 0).wrapping_sub(v(0, 0, 0, 0)) & mask;
let c_d = v(0, 0, 0, 1).wrapping_sub(v(0, 0, 0, 0)) & mask;
let c_ab = v(1, 1, 0, 0)
.wrapping_sub(v(1, 0, 0, 0))
.wrapping_sub(v(0, 1, 0, 0))
.wrapping_add(v(0, 0, 0, 0))
& mask;
let c_ac = v(1, 0, 1, 0)
.wrapping_sub(v(1, 0, 0, 0))
.wrapping_sub(v(0, 0, 1, 0))
.wrapping_add(v(0, 0, 0, 0))
& mask;
let c_ad = v(1, 0, 0, 1)
.wrapping_sub(v(1, 0, 0, 0))
.wrapping_sub(v(0, 0, 0, 1))
.wrapping_add(v(0, 0, 0, 0))
& mask;
let c_bc = v(0, 1, 1, 0)
.wrapping_sub(v(0, 1, 0, 0))
.wrapping_sub(v(0, 0, 1, 0))
.wrapping_add(v(0, 0, 0, 0))
& mask;
let c_bd = v(0, 1, 0, 1)
.wrapping_sub(v(0, 1, 0, 0))
.wrapping_sub(v(0, 0, 0, 1))
.wrapping_add(v(0, 0, 0, 0))
& mask;
let c_cd = v(0, 0, 1, 1)
.wrapping_sub(v(0, 0, 1, 0))
.wrapping_sub(v(0, 0, 0, 1))
.wrapping_add(v(0, 0, 0, 0))
& mask;
let c_abc = v(1, 1, 1, 0)
.wrapping_sub(v(1, 1, 0, 0))
.wrapping_sub(v(1, 0, 1, 0))
.wrapping_sub(v(0, 1, 1, 0))
.wrapping_add(v(1, 0, 0, 0))
.wrapping_add(v(0, 1, 0, 0))
.wrapping_add(v(0, 0, 1, 0))
.wrapping_sub(v(0, 0, 0, 0))
& mask;
let c_abd = v(1, 1, 0, 1)
.wrapping_sub(v(1, 1, 0, 0))
.wrapping_sub(v(1, 0, 0, 1))
.wrapping_sub(v(0, 1, 0, 1))
.wrapping_add(v(1, 0, 0, 0))
.wrapping_add(v(0, 1, 0, 0))
.wrapping_add(v(0, 0, 0, 1))
.wrapping_sub(v(0, 0, 0, 0))
& mask;
let c_acd = v(1, 0, 1, 1)
.wrapping_sub(v(1, 0, 1, 0))
.wrapping_sub(v(1, 0, 0, 1))
.wrapping_sub(v(0, 0, 1, 1))
.wrapping_add(v(1, 0, 0, 0))
.wrapping_add(v(0, 0, 1, 0))
.wrapping_add(v(0, 0, 0, 1))
.wrapping_sub(v(0, 0, 0, 0))
& mask;
let c_bcd = v(0, 1, 1, 1)
.wrapping_sub(v(0, 1, 1, 0))
.wrapping_sub(v(0, 1, 0, 1))
.wrapping_sub(v(0, 0, 1, 1))
.wrapping_add(v(0, 1, 0, 0))
.wrapping_add(v(0, 0, 1, 0))
.wrapping_add(v(0, 0, 0, 1))
.wrapping_sub(v(0, 0, 0, 0))
& mask;
let c_abcd = v(1, 1, 1, 1)
.wrapping_sub(v(1, 1, 1, 0))
.wrapping_sub(v(1, 1, 0, 1))
.wrapping_sub(v(1, 0, 1, 1))
.wrapping_sub(v(0, 1, 1, 1))
.wrapping_add(v(1, 1, 0, 0))
.wrapping_add(v(1, 0, 1, 0))
.wrapping_add(v(1, 0, 0, 1))
.wrapping_add(v(0, 1, 1, 0))
.wrapping_add(v(0, 1, 0, 1))
.wrapping_add(v(0, 0, 1, 1))
.wrapping_sub(v(1, 0, 0, 0))
.wrapping_sub(v(0, 1, 0, 0))
.wrapping_sub(v(0, 0, 1, 0))
.wrapping_sub(v(0, 0, 0, 1))
.wrapping_add(v(0, 0, 0, 0))
& mask;
env.clear();
env.insert(ids[0].0, 0xAA);
env.insert(ids[1].0, 0x55);
env.insert(ids[2].0, 0x42);
env.insert(ids[3].0, 0xDE);
let f_test = eval_expr(&vars[var_idx].expr, vars, &env, mask, 0)?;
let (ta, tb, tc, td) = (0xAAu64, 0x55u64, 0x42u64, 0xDEu64);
let expected = c_1
.wrapping_add(c_a.wrapping_mul(ta))
.wrapping_add(c_b.wrapping_mul(tb))
.wrapping_add(c_c.wrapping_mul(tc))
.wrapping_add(c_d.wrapping_mul(td))
.wrapping_add(c_ab.wrapping_mul(ta & tb))
.wrapping_add(c_ac.wrapping_mul(ta & tc))
.wrapping_add(c_ad.wrapping_mul(ta & td))
.wrapping_add(c_bc.wrapping_mul(tb & tc))
.wrapping_add(c_bd.wrapping_mul(tb & td))
.wrapping_add(c_cd.wrapping_mul(tc & td))
.wrapping_add(c_abc.wrapping_mul(ta & tb & tc))
.wrapping_add(c_abd.wrapping_mul(ta & tb & td))
.wrapping_add(c_acd.wrapping_mul(ta & tc & td))
.wrapping_add(c_bcd.wrapping_mul(tb & tc & td))
.wrapping_add(c_abcd.wrapping_mul(ta & tb & tc & td))
& mask;
if f_test != expected {
return None;
}
if c_1 != 0 {
return None;
}
let all_coeffs = [
(c_a, "a"),
(c_b, "b"),
(c_c, "c"),
(c_d, "d"),
(c_ab, "ab"),
(c_ac, "ac"),
(c_ad, "ad"),
(c_bc, "bc"),
(c_bd, "bd"),
(c_cd, "cd"),
(c_abc, "abc"),
(c_abd, "abd"),
(c_acd, "acd"),
(c_bcd, "bcd"),
(c_abcd, "abcd"),
];
let nonzero: Vec<_> = all_coeffs.iter().filter(|(c, _)| *c != 0).collect();
let neg1 = mask;
let neg2 = mask.wrapping_sub(1);
if nonzero.len() == 1 {
let (coeff, name) = nonzero[0];
if *coeff == 1 {
return match *name {
"a" => Some(Expr::Var(ids[0])),
"b" => Some(Expr::Var(ids[1])),
"c" => Some(Expr::Var(ids[2])),
"d" => Some(Expr::Var(ids[3])),
"ab" => Some(Expr::BinOp(BinOpKind::And, ids[0], ids[1])),
"ac" => Some(Expr::BinOp(BinOpKind::And, ids[0], ids[2])),
"ad" => Some(Expr::BinOp(BinOpKind::And, ids[0], ids[3])),
"bc" => Some(Expr::BinOp(BinOpKind::And, ids[1], ids[2])),
"bd" => Some(Expr::BinOp(BinOpKind::And, ids[1], ids[3])),
"cd" => Some(Expr::BinOp(BinOpKind::And, ids[2], ids[3])),
_ => None,
};
}
if *coeff == neg1 {
return match *name {
"a" => Some(Expr::UnaryOp(UnaryOpKind::Neg, ids[0])),
"b" => Some(Expr::UnaryOp(UnaryOpKind::Neg, ids[1])),
"c" => Some(Expr::UnaryOp(UnaryOpKind::Neg, ids[2])),
"d" => Some(Expr::UnaryOp(UnaryOpKind::Neg, ids[3])),
_ => None,
};
}
}
let pairs: &[(usize, usize, &str)] = &[
(0, 1, "ab"),
(0, 2, "ac"),
(0, 3, "ad"),
(1, 2, "bc"),
(1, 3, "bd"),
(2, 3, "cd"),
];
for &(i, j, pair_name) in pairs {
let ci = all_coeffs[i].0; let cj = all_coeffs[j].0; let cpair = all_coeffs
.iter()
.find(|(_, n)| *n == pair_name)
.map(|(c, _)| *c)
.unwrap_or(0);
let others_zero = all_coeffs.iter().all(|(c, name)| {
*c == 0 || *name == all_coeffs[i].1 || *name == all_coeffs[j].1 || *name == pair_name
});
if !others_zero {
continue;
}
if ci == 1 && cj == 1 && cpair == neg2 {
return Some(Expr::BinOp(BinOpKind::Xor, ids[i], ids[j]));
}
if ci == 1 && cj == 1 && cpair == neg1 {
return Some(Expr::BinOp(BinOpKind::Or, ids[i], ids[j]));
}
if ci == 1 && cj == 1 && cpair == 0 {
return Some(Expr::BinOp(BinOpKind::Add, ids[i], ids[j]));
}
if ci == 1 && cj == neg1 && cpair == 0 {
return Some(Expr::BinOp(BinOpKind::Sub, ids[i], ids[j]));
}
if ci == neg1 && cj == 1 && cpair == 0 {
return Some(Expr::BinOp(BinOpKind::Sub, ids[j], ids[i]));
}
}
None
}
fn simba_simplify_1var(
var_idx: usize,
vars: &[VarDef],
base: VarId,
mask: u64,
sz: u32,
) -> Option<Expr> {
let mut env = std::collections::HashMap::new();
env.insert(base.0, 0u64);
let f0 = eval_expr(&vars[var_idx].expr, vars, &env, mask, 0)?;
env.clear();
env.insert(base.0, 1u64);
let f1 = eval_expr(&vars[var_idx].expr, vars, &env, mask, 0)?;
let c0 = f0;
let c1 = f1.wrapping_sub(f0) & mask;
for probe in [0x42u64, u64::MAX, 0xAAAAAAAAAAAAAAAA, 0x0123456789ABCDEF] {
env.clear();
env.insert(base.0, probe & mask);
let f_test = eval_expr(&vars[var_idx].expr, vars, &env, mask, 0)?;
let expected = c0.wrapping_add(c1.wrapping_mul(probe & mask)) & mask;
if f_test != expected {
return None;
}
}
let neg1 = mask;
match (c0, c1) {
(0, 0) => Some(Expr::Const(0, sz)),
(0, 1) => Some(Expr::Var(base)),
_ if c0 == neg1 && c1 == neg1 => Some(Expr::UnaryOp(UnaryOpKind::Not, base)),
(0, _) if c1 == neg1 => Some(Expr::UnaryOp(UnaryOpKind::Neg, base)),
_ if c0 != 0 && c1 == 0 => Some(Expr::Const(c0, sz)),
_ => None,
}
}
fn mba_simplify_expr(var_idx: usize, vars: &[VarDef]) -> Option<Expr> {
let expr = &vars[var_idx].expr;
match expr {
Expr::BinOp(BinOpKind::Sub, left, right) => {
if let Expr::BinOp(BinOpKind::Sub, inner_left, inner_right) =
&vars[right.0 as usize].expr
{
if left == inner_left || same_varnode(*left, *inner_left, vars) {
return Some(Expr::Var(*inner_right));
}
}
if let Expr::BinOp(BinOpKind::Add, add_left, add_right) = &vars[left.0 as usize].expr {
if right == add_left || same_varnode(*right, *add_left, vars) {
return Some(Expr::Var(*add_right));
}
if right == add_right || same_varnode(*right, *add_right, vars) {
return Some(Expr::Var(*add_left));
}
}
if left == right || same_varnode(*left, *right, vars) {
return Some(Expr::Const(0, vars[left.0 as usize].size));
}
None
}
Expr::BinOp(BinOpKind::Xor, left, right) => {
if let Expr::BinOp(BinOpKind::Xor, inner_left, inner_right) =
&vars[right.0 as usize].expr
{
if left == inner_left || same_varnode(*left, *inner_left, vars) {
return Some(Expr::Var(*inner_right));
}
if left == inner_right || same_varnode(*left, *inner_right, vars) {
return Some(Expr::Var(*inner_left));
}
}
if let Expr::BinOp(BinOpKind::Xor, inner_left, inner_right) =
&vars[left.0 as usize].expr
{
if right == inner_left || same_varnode(*right, *inner_left, vars) {
return Some(Expr::Var(*inner_right));
}
if right == inner_right || same_varnode(*right, *inner_right, vars) {
return Some(Expr::Var(*inner_left));
}
}
if is_const_zero(*right, vars) {
return Some(Expr::Var(*left));
}
if is_const_zero(*left, vars) {
return Some(Expr::Var(*right));
}
None
}
Expr::BinOp(BinOpKind::Add, left, right) => {
if let Expr::UnaryOp(UnaryOpKind::Neg, inner) = &vars[right.0 as usize].expr {
if left == inner || same_varnode(*left, *inner, vars) {
return Some(Expr::Const(0, vars[left.0 as usize].size));
}
}
if let Expr::UnaryOp(UnaryOpKind::Neg, inner) = &vars[left.0 as usize].expr {
if right == inner || same_varnode(*right, *inner, vars) {
return Some(Expr::Const(0, vars[right.0 as usize].size));
}
}
if let Expr::BinOp(BinOpKind::Sub, sub_left, sub_right) = &vars[left.0 as usize].expr {
if right == sub_right || same_varnode(*right, *sub_right, vars) {
return Some(Expr::Var(*sub_left));
}
}
if let Expr::BinOp(BinOpKind::Sub, sub_left, sub_right) = &vars[right.0 as usize].expr {
if left == sub_right || same_varnode(*left, *sub_right, vars) {
return Some(Expr::Var(*sub_left));
}
}
None
}
Expr::BinOp(BinOpKind::And, left, right) => {
if let Expr::BinOp(BinOpKind::Or, or_left, _or_right) = &vars[right.0 as usize].expr {
if left == or_left || same_varnode(*left, *or_left, vars) {
return Some(Expr::Var(*left));
}
}
if let Expr::BinOp(BinOpKind::Or, or_left, _or_right) = &vars[left.0 as usize].expr {
if right == or_left || same_varnode(*right, *or_left, vars) {
return Some(Expr::Var(*right));
}
}
None
}
Expr::BinOp(BinOpKind::Or, left, right) => {
if let Expr::BinOp(BinOpKind::And, and_left, _and_right) = &vars[right.0 as usize].expr
{
if left == and_left || same_varnode(*left, *and_left, vars) {
return Some(Expr::Var(*left));
}
}
if let Expr::BinOp(BinOpKind::And, and_left, _and_right) = &vars[left.0 as usize].expr {
if right == and_left || same_varnode(*right, *and_left, vars) {
return Some(Expr::Var(*right));
}
}
None
}
Expr::UnaryOp(UnaryOpKind::Neg, inner) => {
if let Expr::UnaryOp(UnaryOpKind::Neg, inner2) = &vars[inner.0 as usize].expr {
return Some(Expr::Var(*inner2));
}
None
}
Expr::UnaryOp(UnaryOpKind::Not, inner) => {
if let Expr::UnaryOp(UnaryOpKind::Not, inner2) = &vars[inner.0 as usize].expr {
return Some(Expr::Var(*inner2));
}
None
}
Expr::UnaryOp(UnaryOpKind::BoolNot, inner) => {
if let Expr::UnaryOp(UnaryOpKind::BoolNot, inner2) = &vars[inner.0 as usize].expr {
return Some(Expr::Var(*inner2));
}
if let Expr::BinOp(cmp_op, a, b) = vars[inner.0 as usize].expr {
if let Some(neg_op) = negate_eq_op(cmp_op) {
return Some(Expr::BinOp(neg_op, a, b));
}
if matches!(cmp_op, BinOpKind::Sub) {
return Some(Expr::BinOp(BinOpKind::Eq, a, b));
}
}
None
}
Expr::BinOp(BinOpKind::Eq, inner_id, zero_id) => {
if matches!(vars[zero_id.0 as usize].expr, Expr::Const(0, _)) {
let mut resolved = *inner_id;
for _ in 0..4 {
if let Expr::Var(next) = vars[resolved.0 as usize].expr {
resolved = next;
} else {
break;
}
}
if let Expr::BinOp(cmp_op, a, b) = vars[resolved.0 as usize].expr {
if let Some(neg_op) = negate_eq_op(cmp_op) {
return Some(Expr::BinOp(neg_op, a, b));
}
if matches!(cmp_op, BinOpKind::Sub) {
return Some(Expr::BinOp(BinOpKind::Eq, a, b));
}
}
}
None
}
Expr::BinOp(BinOpKind::NotEq, inner_id, zero_id) => {
if matches!(vars[zero_id.0 as usize].expr, Expr::Const(0, _)) {
let mut resolved = *inner_id;
for _ in 0..4 {
if let Expr::Var(next) = vars[resolved.0 as usize].expr {
resolved = next;
} else {
break;
}
}
if let Expr::BinOp(cmp_op, a, b) = vars[resolved.0 as usize].expr {
if matches!(cmp_op, BinOpKind::Sub) {
return Some(Expr::BinOp(BinOpKind::NotEq, a, b));
}
}
if let Expr::BinOp(_, _, _) = vars[resolved.0 as usize].expr {
return Some(Expr::Var(resolved));
}
}
None
}
Expr::BinOp(BinOpKind::SDiv, left, right) => {
if let Expr::BinOp(BinOpKind::Or, or_left, or_right) = &vars[left.0 as usize].expr {
let val_from_or = extract_cdq_value(*or_left, *or_right, vars)
.or_else(|| extract_cdq_value(*or_right, *or_left, vars));
if let Some(val_id) = val_from_or {
let div_id = if let Expr::UnaryOp(UnaryOpKind::Zext, inner) =
&vars[right.0 as usize].expr
{
*inner
} else {
*right
};
return Some(Expr::BinOp(BinOpKind::SDiv, val_id, div_id));
}
}
None
}
Expr::BinOp(BinOpKind::SRem, left, right) => {
if let Expr::BinOp(BinOpKind::Or, or_left, or_right) = &vars[left.0 as usize].expr {
let val_from_or = extract_cdq_value(*or_left, *or_right, vars)
.or_else(|| extract_cdq_value(*or_right, *or_left, vars));
if let Some(val_id) = val_from_or {
let div_id = if let Expr::UnaryOp(UnaryOpKind::Zext, inner) =
&vars[right.0 as usize].expr
{
*inner
} else {
*right
};
return Some(Expr::BinOp(BinOpKind::SRem, val_id, div_id));
}
}
None
}
Expr::BinOp(BinOpKind::Mult, left, right) => {
if is_const_zero(*left, vars) || is_const_zero(*right, vars) {
return Some(Expr::Const(0, vars[left.0 as usize].size));
}
if is_const_one(*left, vars) {
return Some(Expr::Var(*right));
}
if is_const_one(*right, vars) {
return Some(Expr::Var(*left));
}
None
}
_ => None,
}
}
fn simplify_expr(expr: Expr, vars: &[VarDef]) -> Expr {
match &expr {
Expr::BinOp(BinOpKind::And, left, right) => {
if left == right || same_varnode(*left, *right, vars) {
Expr::Var(*left)
} else if is_const_zero(*right, vars) || is_const_zero(*left, vars) {
Expr::Const(0, vars[left.0 as usize].size)
} else if is_const_all_ones(*right, vars) {
Expr::Var(*left)
} else if is_const_all_ones(*left, vars) {
Expr::Var(*right)
} else if is_const_mask_noop(*left, *right, vars) {
Expr::Var(*left)
} else if let Expr::BinOp(BinOpKind::Or, l, r) = &vars[right.0 as usize].expr {
if *l == *left || *r == *left {
Expr::Var(*left)
} else {
expr
}
} else if let Expr::BinOp(BinOpKind::Or, l, r) = &vars[left.0 as usize].expr {
if *l == *right || *r == *right {
Expr::Var(*right)
} else {
expr
}
} else {
expr
}
}
Expr::BinOp(BinOpKind::Xor, left, right) => {
if left == right || same_varnode(*left, *right, vars) {
Expr::Const(0, vars[left.0 as usize].size)
} else if is_const_zero(*right, vars) {
Expr::Var(*left)
} else if is_const_zero(*left, vars) {
Expr::Var(*right)
} else if is_const_all_ones(*right, vars) {
Expr::UnaryOp(UnaryOpKind::Not, *left)
} else if is_const_all_ones(*left, vars) {
Expr::UnaryOp(UnaryOpKind::Not, *right)
} else {
expr
}
}
Expr::BinOp(BinOpKind::Or, left, right) => {
if left == right || same_varnode(*left, *right, vars) {
Expr::Var(*left)
} else if is_const_zero(*right, vars) {
Expr::Var(*left)
} else if is_const_zero(*left, vars) {
Expr::Var(*right)
} else if is_const_all_ones(*right, vars) {
Expr::Var(*right)
} else if is_const_all_ones(*left, vars) {
Expr::Var(*left)
} else {
expr
}
}
Expr::BinOp(BinOpKind::Add, left, right) => {
if is_const_zero(*right, vars) {
Expr::Var(*left)
} else if is_const_zero(*left, vars) {
Expr::Var(*right)
}
else if left == right || same_varnode(*left, *right, vars) {
Expr::BinOp(BinOpKind::Lsl, *left, *right) }
else if let Expr::UnaryOp(UnaryOpKind::Neg, y) = &vars[right.0 as usize].expr {
Expr::BinOp(BinOpKind::Sub, *left, *y)
} else if let Expr::UnaryOp(UnaryOpKind::Neg, y) = &vars[left.0 as usize].expr {
Expr::BinOp(BinOpKind::Sub, *right, *y)
} else {
expr
}
}
Expr::BinOp(BinOpKind::Sub, left, right) => {
if is_const_zero(*right, vars) {
Expr::Var(*left)
} else if left == right || same_varnode(*left, *right, vars) {
Expr::Const(0, vars[left.0 as usize].size)
} else if is_const_zero(*left, vars) {
Expr::UnaryOp(UnaryOpKind::Neg, *right)
} else if let Expr::UnaryOp(UnaryOpKind::Neg, y) = &vars[right.0 as usize].expr {
Expr::BinOp(BinOpKind::Add, *left, *y)
} else {
expr
}
}
Expr::BinOp(BinOpKind::Mult, left, right) => {
if is_const_one(*right, vars) {
Expr::Var(*left)
} else if is_const_one(*left, vars) {
Expr::Var(*right)
} else if is_const_zero(*right, vars) {
Expr::Const(0, vars[left.0 as usize].size)
} else if is_const_zero(*left, vars) {
Expr::Const(0, vars[right.0 as usize].size)
} else {
expr
}
}
Expr::BinOp(BinOpKind::Lsr | BinOpKind::Lsl | BinOpKind::Asr, left, right) => {
if is_const_zero(*right, vars) {
Expr::Var(*left)
} else {
expr
}
}
Expr::BinOp(BinOpKind::Div | BinOpKind::SDiv, left, right) => {
if is_const_one(*right, vars) {
Expr::Var(*left)
} else {
expr
}
}
Expr::BinOp(BinOpKind::Rem | BinOpKind::SRem, left, right) => {
if is_const_one(*right, vars) {
Expr::Const(0, vars[left.0 as usize].size)
} else {
expr
}
}
Expr::BinOp(BinOpKind::BoolAnd, left, right) => {
if left == right || same_varnode(*left, *right, vars) {
Expr::Var(*left)
} else if is_const_zero(*right, vars) || is_const_zero(*left, vars) {
Expr::Const(0, 1)
} else if is_const_one(*right, vars) {
Expr::Var(*left)
} else if is_const_one(*left, vars) {
Expr::Var(*right)
} else {
expr
}
}
Expr::BinOp(BinOpKind::BoolOr, left, right) => {
if left == right || same_varnode(*left, *right, vars) {
Expr::Var(*left)
} else if is_const_zero(*right, vars) {
Expr::Var(*left)
} else if is_const_zero(*left, vars) {
Expr::Var(*right)
} else if is_const_one(*right, vars) || is_const_one(*left, vars) {
Expr::Const(1, 1)
} else {
expr
}
}
Expr::BinOp(BinOpKind::BoolXor, left, right) => {
if left == right || same_varnode(*left, *right, vars) {
Expr::Const(0, 1)
} else if is_const_zero(*right, vars) {
Expr::Var(*left)
} else if is_const_zero(*left, vars) {
Expr::Var(*right)
} else {
expr
}
}
Expr::BinOp(BinOpKind::Eq, left, right) => {
if left == right || same_varnode(*left, *right, vars) {
Expr::Const(1, 1)
} else {
expr
}
}
Expr::BinOp(BinOpKind::NotEq, left, right) => {
if left == right || same_varnode(*left, *right, vars) {
Expr::Const(0, 1)
} else {
expr
}
}
Expr::BinOp(BinOpKind::Less | BinOpKind::SLess, left, right) => {
if left == right || same_varnode(*left, *right, vars) {
Expr::Const(0, 1)
} else {
expr
}
}
Expr::BinOp(BinOpKind::LessEq | BinOpKind::SLessEq, left, right) => {
if left == right || same_varnode(*left, *right, vars) {
Expr::Const(1, 1)
} else {
expr
}
}
Expr::UnaryOp(UnaryOpKind::Not, inner) => {
if let Expr::UnaryOp(UnaryOpKind::Not, x) = &vars[inner.0 as usize].expr {
Expr::Var(*x)
} else {
expr
}
}
Expr::UnaryOp(UnaryOpKind::Neg, inner) => {
match &vars[inner.0 as usize].expr {
Expr::UnaryOp(UnaryOpKind::Neg, x) => Expr::Var(*x),
Expr::BinOp(BinOpKind::Sub, a, b) => Expr::BinOp(BinOpKind::Sub, *b, *a),
_ => expr,
}
}
Expr::UnaryOp(UnaryOpKind::BoolNot, inner) => {
if let Expr::UnaryOp(UnaryOpKind::BoolNot, x) = &vars[inner.0 as usize].expr {
Expr::Var(*x)
} else {
expr
}
}
_ => expr,
}
}
fn extract_cdq_value(high_part: VarId, low_part: VarId, vars: &[VarDef]) -> Option<VarId> {
let val_id = match &vars[low_part.0 as usize].expr {
Expr::UnaryOp(UnaryOpKind::Zext, inner) => *inner,
_ => return None,
};
let (shift_input, shift_amount) = match &vars[high_part.0 as usize].expr {
Expr::BinOp(BinOpKind::Lsl, left, right) => (*left, *right),
_ => return None,
};
match &vars[shift_amount.0 as usize].expr {
Expr::Const(32, _) => {}
_ => return None,
}
let inner_of_shift = match &vars[shift_input.0 as usize].expr {
Expr::UnaryOp(UnaryOpKind::Zext | UnaryOpKind::Sext, inner) => *inner,
_ => return None,
};
if let Expr::BinOp(BinOpKind::Asr, asr_val, asr_amount) = &vars[inner_of_shift.0 as usize].expr
{
if let Expr::Const(31, _) = &vars[asr_amount.0 as usize].expr {
if same_varnode(*asr_val, val_id, vars) || asr_val == &val_id {
return Some(val_id);
}
}
}
if same_varnode(inner_of_shift, val_id, vars) || inner_of_shift == val_id {
return Some(val_id);
}
if let Expr::Var(src) = &vars[inner_of_shift.0 as usize].expr {
if same_varnode(*src, val_id, vars) || *src == val_id {
return Some(val_id);
}
}
if let Expr::UnaryOp(UnaryOpKind::Sext, sext_inner) = &vars[inner_of_shift.0 as usize].expr {
if same_varnode(*sext_inner, val_id, vars) || *sext_inner == val_id {
return Some(val_id);
}
}
let resolved = match &vars[inner_of_shift.0 as usize].expr {
Expr::Var(v) => *v,
_ => inner_of_shift,
};
if resolved != inner_of_shift {
if let Expr::UnaryOp(UnaryOpKind::Sext, sext_inner) = &vars[resolved.0 as usize].expr {
if same_varnode(*sext_inner, val_id, vars) || *sext_inner == val_id {
return Some(val_id);
}
}
if same_varnode(resolved, val_id, vars) || resolved == val_id {
return Some(val_id);
}
}
None
}
fn const_fold_expr(expr: &Expr, vars: &[VarDef]) -> Option<(u64, u32)> {
match expr {
Expr::Const(val, sz) => Some((*val, *sz)),
Expr::Var(id) => const_fold_expr(&vars[id.0 as usize].expr, vars),
Expr::BinOp(kind, left, right) => {
let (lv, lsz) = const_fold_expr(&vars[left.0 as usize].expr, vars)?;
let (rv, _) = const_fold_expr(&vars[right.0 as usize].expr, vars)?;
let mask = if lsz >= 8 {
u64::MAX
} else {
(1u64 << (lsz * 8)) - 1
};
let result = match kind {
BinOpKind::Add => lv.wrapping_add(rv) & mask,
BinOpKind::Sub => lv.wrapping_sub(rv) & mask,
BinOpKind::Mult => lv.wrapping_mul(rv) & mask,
BinOpKind::And => lv & rv,
BinOpKind::Or => lv | rv,
BinOpKind::Xor => lv ^ rv,
BinOpKind::Lsl => (lv << (rv & 63)) & mask,
BinOpKind::Lsr => lv >> (rv & 63),
BinOpKind::Asr => ((lv as i64) >> (rv & 63)) as u64 & mask,
_ => return None,
};
Some((result, lsz))
}
Expr::UnaryOp(kind, inner) => {
let (v, sz) = const_fold_expr(&vars[inner.0 as usize].expr, vars)?;
let mask = if sz >= 8 {
u64::MAX
} else {
(1u64 << (sz * 8)) - 1
};
let result = match kind {
UnaryOpKind::Neg => (-(v as i64) as u64) & mask,
UnaryOpKind::Not => (!v) & mask,
_ => return None,
};
Some((result, sz))
}
_ => None,
}
}
fn same_varnode(a: VarId, b: VarId, vars: &[VarDef]) -> bool {
let va = &vars[a.0 as usize];
let vb = &vars[b.0 as usize];
va.varnode.space == AddressSpaceId::Register
&& vb.varnode.space == AddressSpaceId::Register
&& va.varnode.offset == vb.varnode.offset
&& va.varnode.size == vb.varnode.size
}
fn is_const_zero(id: VarId, vars: &[VarDef]) -> bool {
matches!(&vars[id.0 as usize].expr, Expr::Const(0, _))
}
fn is_const_one(id: VarId, vars: &[VarDef]) -> bool {
matches!(&vars[id.0 as usize].expr, Expr::Const(1, _))
}
fn is_const_mask_noop(val_id: VarId, mask_id: VarId, vars: &[VarDef]) -> bool {
if let (Expr::Const(val, _), Expr::Const(mask, _)) = (
&vars[val_id.0 as usize].expr,
&vars[mask_id.0 as usize].expr,
) {
*val & *mask == *val && *val != 0
} else {
false
}
}
fn is_const_all_ones(id: VarId, vars: &[VarDef]) -> bool {
if let Expr::Const(val, sz) = &vars[id.0 as usize].expr {
let mask = if *sz >= 8 {
u64::MAX
} else {
(1u64 << (*sz * 8)) - 1
};
*val == mask
} else {
false
}
}
fn propagate_register_constants(ssa: &mut SsaCfg) {
let mut reg_consts: std::collections::HashMap<u64, (u64, u32)> =
std::collections::HashMap::new();
for v in &ssa.vars {
if v.varnode.space == AddressSpaceId::Register && v.param_name.is_none() {
if let Expr::Const(val, sz) = &v.expr {
reg_consts.insert(v.varnode.offset, (*val, *sz));
}
}
}
for v in &mut ssa.vars {
if v.varnode.space == AddressSpaceId::Register && matches!(&v.expr, Expr::Unknown)
&& v.param_name.is_none()
&& v.use_count <= 2 && !v.call_return && !FLAG_OFFSETS.contains(&v.varnode.offset)
&& v.varnode.offset != RSP_OFFSET
&& v.varnode.offset != RIP_OFFSET
&& v.varnode.offset != 40
{
if let Some(&(val, _const_sz)) = reg_consts.get(&v.varnode.offset) {
let mask = match v.varnode.size {
1 => 0xFF,
2 => 0xFFFF,
4 => 0xFFFFFFFF,
_ => u64::MAX,
};
v.expr = Expr::Const(val & mask, v.varnode.size);
}
}
}
}
fn propagate_register_copies(ssa: &mut SsaCfg) {
for bi in 0..ssa.blocks.len() {
let mut reg_expr: std::collections::HashMap<(u64, u32), (VarId, Expr)> =
std::collections::HashMap::new();
let mut replacements: Vec<(usize, Expr)> = Vec::new();
let stmts = &ssa.blocks[bi].stmts;
for i in 0..stmts.len() {
if let Stmt::Assign(var_id) = &stmts[i] {
let vdef = &ssa.vars[var_id.0 as usize];
if vdef.varnode.space != AddressSpaceId::Register {
continue;
}
let key = (vdef.varnode.offset, vdef.varnode.size);
if let Expr::BinOp(kind, left, right) = &vdef.expr {
let left_var = &ssa.vars[left.0 as usize];
if left_var.varnode.space == AddressSpaceId::Register
&& left_var.varnode.offset == vdef.varnode.offset
&& left_var.use_count <= 1
{
if let Some((prev_id, _prev_expr)) = reg_expr.get(&key) {
replacements.push((i, Expr::BinOp(*kind, *prev_id, *right)));
}
}
}
reg_expr.insert(key, (*var_id, vdef.expr.clone()));
}
}
for (idx, new_expr) in replacements {
if let Stmt::Assign(var_id) = &ssa.blocks[bi].stmts[idx] {
ssa.vars[var_id.0 as usize].expr = new_expr;
}
}
}
}
fn substitute_expr(expr: &Expr, candidates: &[(VarId, Expr)]) -> Expr {
match expr {
Expr::Var(id) => candidates
.iter()
.find(|(cid, _)| cid == id)
.map(|(_, r)| r.clone())
.unwrap_or_else(|| expr.clone()),
_ => expr.clone(),
}
}
fn eliminate_dead(ssa: &mut SsaCfg) {
for block in &mut ssa.blocks {
let mut read_after: std::collections::HashSet<(u64, u32)> =
std::collections::HashSet::new();
match &block.terminator {
SsaTerminator::CBranch { cond, .. } => {
collect_var_reads(*cond, &ssa.vars, &mut read_after);
}
SsaTerminator::Return(Some(v)) | SsaTerminator::Indirect(v) => {
collect_var_reads(*v, &ssa.vars, &mut read_after);
}
SsaTerminator::Call { args, .. } => {
for a in args {
collect_var_reads(*a, &ssa.vars, &mut read_after);
}
}
_ => {}
}
let mut dead_indices = Vec::new();
for i in (0..block.stmts.len()).rev() {
match &block.stmts[i] {
Stmt::Assign(var_id) => {
let vdef = &ssa.vars[var_id.0 as usize];
let key = (vdef.varnode.offset, vdef.varnode.size);
if vdef.varnode.space == AddressSpaceId::Register
&& FLAG_OFFSETS.contains(&vdef.varnode.offset)
&& vdef.use_count == 0
{
dead_indices.push(i);
continue;
}
if vdef.varnode.space == AddressSpaceId::Unique
&& vdef.use_count == 0
&& !matches!(&vdef.expr, Expr::UserOp { .. })
{
dead_indices.push(i);
continue;
}
if vdef.use_count == 0 {
if matches!(
&vdef.expr,
Expr::BinOp(
BinOpKind::Carry | BinOpKind::SCarry | BinOpKind::SBorrow,
_,
_
)
) {
dead_indices.push(i);
continue;
}
}
if vdef.varnode.space == AddressSpaceId::Register
&& vdef.varnode.offset == RIP_OFFSET
{
dead_indices.push(i);
continue;
}
let is_arg_reg = arg_reg_offsets().contains(&vdef.varnode.offset)
&& vdef.varnode.space == AddressSpaceId::Register;
let precedes_call =
block.stmts.get(i + 1..).map_or(false, |rest| {
rest.iter().any(|s| matches!(s, Stmt::Call { .. }))
}) || matches!(block.terminator, SsaTerminator::Call { .. });
if vdef.varnode.space == AddressSpaceId::Register
&& !read_after.contains(&key)
&& vdef.use_count == 0
&& !(is_arg_reg && precedes_call)
{
dead_indices.push(i);
continue;
}
let mut visited = std::collections::HashSet::new();
collect_expr_reads_inner(&vdef.expr, &ssa.vars, &mut read_after, &mut visited);
}
Stmt::Store { addr, val } => {
let val_def = &ssa.vars[val.0 as usize];
let addr_def = &ssa.vars[addr.0 as usize];
if is_rsp_derived(&addr_def.varnode, &addr_def.expr, &ssa.vars)
|| is_esp_derived(&addr_def.varnode, &addr_def.expr, &ssa.vars)
{
if let Expr::Const(_, _) = &val_def.expr {
dead_indices.push(i);
continue;
}
}
collect_var_reads(*addr, &ssa.vars, &mut read_after);
collect_var_reads(*val, &ssa.vars, &mut read_after);
}
Stmt::Call { args, .. } => {
for a in args {
collect_var_reads(*a, &ssa.vars, &mut read_after);
}
}
}
}
dead_indices.sort_unstable();
dead_indices.dedup();
for &i in dead_indices.iter().rev() {
block.stmts.remove(i);
}
}
}
fn collect_var_reads(
id: VarId,
vars: &[VarDef],
reads: &mut std::collections::HashSet<(u64, u32)>,
) {
let mut visited = std::collections::HashSet::new();
collect_var_reads_inner(id, vars, reads, &mut visited);
}
fn collect_var_reads_inner(
id: VarId,
vars: &[VarDef],
reads: &mut std::collections::HashSet<(u64, u32)>,
visited: &mut std::collections::HashSet<u32>,
) {
if !visited.insert(id.0) {
return;
} let vdef = &vars[id.0 as usize];
if vdef.varnode.space == AddressSpaceId::Register {
reads.insert((vdef.varnode.offset, vdef.varnode.size));
}
collect_expr_reads_inner(&vdef.expr, vars, reads, visited);
}
fn collect_expr_reads_inner(
expr: &Expr,
vars: &[VarDef],
reads: &mut std::collections::HashSet<(u64, u32)>,
visited: &mut std::collections::HashSet<u32>,
) {
match expr {
Expr::Var(id) => {
let v = &vars[id.0 as usize];
if v.varnode.space == AddressSpaceId::Register {
reads.insert((v.varnode.offset, v.varnode.size));
}
}
Expr::BinOp(_, l, r) => {
collect_var_reads_inner(*l, vars, reads, visited);
collect_var_reads_inner(*r, vars, reads, visited);
}
Expr::UnaryOp(_, i) | Expr::Load(i) | Expr::FieldAccess(i, _) => {
collect_var_reads_inner(*i, vars, reads, visited)
}
Expr::Phi(inputs) => {
for i in inputs {
collect_var_reads_inner(*i, vars, reads, visited);
}
}
Expr::Ternary(c, t, e) => {
collect_var_reads_inner(*c, vars, reads, visited);
collect_var_reads_inner(*t, vars, reads, visited);
collect_var_reads_inner(*e, vars, reads, visited);
}
_ => {}
}
}
fn is_rsp_derived(vn: &pcode_ir::Varnode, expr: &Expr, vars: &[VarDef]) -> bool {
if vn.space == AddressSpaceId::Register && vn.offset == RSP_OFFSET {
return true;
}
match expr {
Expr::Var(id) | Expr::BinOp(_, id, _) => {
let v = &vars[id.0 as usize];
v.varnode.space == AddressSpaceId::Register && v.varnode.offset == RSP_OFFSET
}
_ => false,
}
}
fn is_esp_derived(vn: &pcode_ir::Varnode, expr: &Expr, vars: &[VarDef]) -> bool {
if vn.space == AddressSpaceId::Register && vn.offset == ESP_OFFSET && vn.size == 4 {
return true;
}
match expr {
Expr::Var(id) | Expr::BinOp(_, id, _) => {
let v = &vars[id.0 as usize];
v.varnode.space == AddressSpaceId::Register
&& v.varnode.offset == ESP_OFFSET
&& v.varnode.size == 4
}
_ => false,
}
}
fn recover_conditions(ssa: &mut SsaCfg) {
let mut to_recover: Vec<(usize, VarId)> = Vec::new();
for (bi, block) in ssa.blocks.iter().enumerate() {
if let SsaTerminator::CBranch { cond, .. } = &block.terminator {
let vdef = &ssa.vars[cond.0 as usize];
let dominated_by_flags = is_flag_derived(*cond, ssa);
let already_comparison = if let Expr::BinOp(k, l, r) = &vdef.expr {
is_comparison(*k) && !is_flag_derived(*l, ssa) && !is_flag_derived(*r, ssa)
} else {
false
};
if dominated_by_flags && !already_comparison {
to_recover.push((bi, *cond));
}
}
}
for (bi, cond_id) in to_recover {
if let Some(new_cond) = try_recover_condition(cond_id, bi, ssa) {
if let SsaTerminator::CBranch {
taken, fallthrough, ..
} = ssa.blocks[bi].terminator
{
ssa.blocks[bi].terminator = SsaTerminator::CBranch {
cond: new_cond,
taken,
fallthrough,
};
}
}
}
{
let is_of = |id: VarId, ssa: &SsaCfg| -> bool {
is_flag_ref(id, 523, ssa) || is_flag_ref(id, 259, ssa)
};
let is_sf = |id: VarId, ssa: &SsaCfg| -> bool {
is_flag_ref(id, 519, ssa) || is_flag_ref(id, 256, ssa)
};
let mut rewrites: Vec<(usize, Expr, InferredType)> = Vec::new();
for (idx, v) in ssa.vars.iter().enumerate() {
let (is_pair, kind, l, r) = match &v.expr {
Expr::BinOp(BinOpKind::NotEq, l, r) => {
let pair =
(is_of(*l, ssa) && is_sf(*r, ssa)) || (is_sf(*l, ssa) && is_of(*r, ssa));
(pair, BinOpKind::SLess, *l, *r)
}
Expr::BinOp(BinOpKind::Eq, l, r) => {
let pair =
(is_of(*l, ssa) && is_sf(*r, ssa)) || (is_sf(*l, ssa) && is_of(*r, ssa));
(pair, BinOpKind::SLessEq, *l, *r)
}
_ => (false, BinOpKind::Eq, VarId(0), VarId(0)),
};
if !is_pair {
continue;
}
let extract_ab = |flag_var: VarId| -> Option<(VarId, VarId)> {
match &ssa.vars[flag_var.0 as usize].expr {
Expr::BinOp(_, a, b) => Some((*a, *b)),
_ => None,
}
};
let ab = extract_ab(l).or_else(|| extract_ab(r));
if let Some((a, b)) = ab {
let (final_l, final_r) = match kind {
BinOpKind::SLess => (a, b),
BinOpKind::SLessEq => (b, a),
_ => (a, b),
};
rewrites.push((idx, Expr::BinOp(kind, final_l, final_r), InferredType::Bool));
}
}
for (idx, expr, ty) in rewrites {
ssa.vars[idx].expr = expr;
ssa.vars[idx].inferred_type = ty;
}
}
let mut sub_cond: Vec<(usize, VarId, VarId)> = Vec::new(); for (bi, block) in ssa.blocks.iter().enumerate() {
if let SsaTerminator::CBranch { cond, .. } = &block.terminator {
if is_flag_derived(*cond, ssa) {
continue;
}
let mut resolved = *cond;
for _ in 0..4 {
if let Expr::Var(next) = ssa.vars[resolved.0 as usize].expr {
resolved = next;
} else {
break;
}
}
if let Expr::BinOp(BinOpKind::Sub, a, b) = ssa.vars[resolved.0 as usize].expr {
sub_cond.push((bi, a, b));
}
}
}
for (bi, a, b) in sub_cond {
let cond_varnode = if let SsaTerminator::CBranch { cond, .. } = ssa.blocks[bi].terminator {
ssa.vars[cond.0 as usize].varnode
} else {
continue;
};
let new_cond = ssa.new_var(cond_varnode, Expr::BinOp(BinOpKind::NotEq, a, b), 1);
if let SsaTerminator::CBranch {
taken, fallthrough, ..
} = ssa.blocks[bi].terminator
{
ssa.blocks[bi].terminator = SsaTerminator::CBranch {
cond: new_cond,
taken,
fallthrough,
};
}
}
let mut ternary_to_recover: Vec<(usize, VarId, usize)> = Vec::new(); for (bi, block) in ssa.blocks.iter().enumerate() {
for stmt in &block.stmts {
if let Stmt::Assign(vid) = stmt {
let vi = vid.0 as usize;
if let Expr::Ternary(cond, _, _) = &ssa.vars[vi].expr {
if is_flag_derived(*cond, ssa) {
let already = if let Expr::BinOp(k, l, r) = &ssa.vars[cond.0 as usize].expr
{
is_comparison(*k)
&& !is_flag_derived(*l, ssa)
&& !is_flag_derived(*r, ssa)
} else {
false
};
if !already {
ternary_to_recover.push((vi, *cond, bi));
}
}
}
}
}
}
for (vi, cond_id, block_idx) in ternary_to_recover {
if let Some(new_cond) = try_recover_condition(cond_id, block_idx, ssa) {
if let Expr::Ternary(_, then_val, else_val) = ssa.vars[vi].expr {
ssa.vars[vi].expr = Expr::Ternary(new_cond, then_val, else_val);
}
}
}
let mut cset_recoveries: Vec<(usize, VarId, i64, i64, usize)> = Vec::new();
for (bi, block) in ssa.blocks.iter().enumerate() {
for stmt in &block.stmts {
if let Stmt::Assign(vid) = stmt {
let vi = vid.0 as usize;
if let Some((cond_id, then_val, else_val)) = extract_cset_pattern(vi, ssa) {
if is_flag_derived(cond_id, ssa) {
cset_recoveries.push((vi, cond_id, then_val, else_val, bi));
}
}
}
}
}
for (vi, cond_id, then_val, else_val, block_idx) in cset_recoveries {
let recovered = try_recover_condition(cond_id, block_idx, ssa);
let final_cond = recovered.unwrap_or(cond_id);
let size = ssa.vars[vi].size;
let then_var = ssa.new_var(
pcode_ir::Varnode::constant(then_val as u64, size),
Expr::Const(then_val as u64, size),
size,
);
let else_var = ssa.new_var(
pcode_ir::Varnode::constant(else_val as u64, size),
Expr::Const(else_val as u64, size),
size,
);
ssa.vars[vi].expr = Expr::Ternary(final_cond, then_var, else_var);
}
}
fn extract_cset_pattern(var_idx: usize, ssa: &SsaCfg) -> Option<(VarId, i64, i64)> {
let vdef = &ssa.vars[var_idx];
let inner_idx = if let Expr::UnaryOp(UnaryOpKind::Zext, inner) = &vdef.expr {
inner.0 as usize
} else {
var_idx
};
let inner_def = &ssa.vars[inner_idx];
if let Expr::BinOp(BinOpKind::Mult, left, right) = &inner_def.expr {
for (zext_side, neg_side) in [(*left, *right), (*right, *left)] {
let zext_def = &ssa.vars[zext_side.0 as usize];
let neg_def = &ssa.vars[neg_side.0 as usize];
let is_neg_one = match &neg_def.expr {
Expr::UnaryOp(UnaryOpKind::Neg, c) => {
matches!(&ssa.vars[c.0 as usize].expr, Expr::Const(1, _))
}
Expr::Const(val, sz) => {
let mask = if *sz >= 8 {
u64::MAX
} else {
(1u64 << (sz * 8)) - 1
};
*val == mask
}
_ => false,
};
if !is_neg_one {
continue;
}
if let Expr::UnaryOp(UnaryOpKind::Zext, cond_id) = &zext_def.expr {
return Some((*cond_id, -1, 0));
}
}
}
if inner_idx != var_idx {
if let Expr::UnaryOp(UnaryOpKind::Zext, cond_id) = &inner_def.expr {
if is_flag_derived(*cond_id, ssa) {
return Some((*cond_id, 1, 0));
}
}
if is_flag_derived(VarId(inner_idx as u32), ssa) && inner_def.size <= 1 {
return Some((VarId(inner_idx as u32), 1, 0));
}
}
None
}
fn is_flag_derived(id: VarId, ssa: &SsaCfg) -> bool {
is_flag_derived_depth(id, ssa, 5)
}
fn is_flag_derived_depth(id: VarId, ssa: &SsaCfg, depth: u32) -> bool {
if depth == 0 {
return false;
}
let vdef = &ssa.vars[id.0 as usize];
if vdef.varnode.space == AddressSpaceId::Register && FLAG_OFFSETS.contains(&vdef.varnode.offset)
{
return true;
}
match &vdef.expr {
Expr::Var(inner) => is_flag_derived_depth(*inner, ssa, depth - 1),
Expr::BinOp(_, l, r) => {
is_flag_derived_depth(*l, ssa, depth - 1) || is_flag_derived_depth(*r, ssa, depth - 1)
}
Expr::UnaryOp(_, i) => is_flag_derived_depth(*i, ssa, depth - 1),
_ => false,
}
}
fn try_recover_condition(cond_id: VarId, block_idx: usize, ssa: &mut SsaCfg) -> Option<VarId> {
let vdef = &ssa.vars[cond_id.0 as usize];
if let Expr::BinOp(kind, l, r) = &vdef.expr {
if is_comparison(*kind) && !is_flag_derived(*l, ssa) && !is_flag_derived(*r, ssa) {
return Some(cond_id);
}
}
let cmp_result = find_cmp_operands(block_idx, ssa)
.or_else(|| trace_cond_to_cmp(cond_id, ssa, 8));
let (cmp_left, cmp_right) = cmp_result?;
let classified = classify_jcc_condition(cond_id, ssa);
if let Some((kind, swap)) = classified {
let (left, right) = if swap {
(cmp_right, cmp_left)
} else {
(cmp_left, cmp_right)
};
let new_var = ssa.new_var(
ssa.vars[cond_id.0 as usize].varnode,
Expr::BinOp(kind, left, right),
1,
);
return Some(new_var);
}
let vdef = &ssa.vars[cond_id.0 as usize];
if let Expr::Var(inner_id) = &vdef.expr {
let inner = &ssa.vars[inner_id.0 as usize];
if let Expr::BinOp(kind, l, r) = &inner.expr {
if is_comparison(*kind) && !is_flag_derived(*l, ssa) && !is_flag_derived(*r, ssa) {
return Some(*inner_id);
}
}
}
if let Expr::BinOp(kind, l, r) = &vdef.expr {
if is_comparison(*kind) && !is_flag_derived(*l, ssa) && !is_flag_derived(*r, ssa) {
return Some(cond_id);
}
}
let ba_parts = if let Expr::BinOp(BinOpKind::BoolAnd, l, r) = &ssa.vars[cond_id.0 as usize].expr
{
Some((*l, *r))
} else {
None
};
if let Some((ba_left, ba_right)) = ba_parts {
let is_of_sf_eq = |id: VarId| -> bool {
if let Expr::BinOp(BinOpKind::Eq, a, b) = &ssa.vars[id.0 as usize].expr {
let a_of = is_flag_ref(*a, 523, ssa)
|| is_flag_ref(*a, 259, ssa)
|| is_flag_ref(*a, 262, ssa)
|| is_flag_ref(*a, 99, ssa);
let a_sf = is_flag_ref(*a, 519, ssa)
|| is_flag_ref(*a, 256, ssa)
|| is_flag_ref(*a, 263, ssa)
|| is_flag_ref(*a, 96, ssa);
let b_of = is_flag_ref(*b, 523, ssa)
|| is_flag_ref(*b, 259, ssa)
|| is_flag_ref(*b, 262, ssa)
|| is_flag_ref(*b, 99, ssa);
let b_sf = is_flag_ref(*b, 519, ssa)
|| is_flag_ref(*b, 256, ssa)
|| is_flag_ref(*b, 263, ssa)
|| is_flag_ref(*b, 96, ssa);
(a_of && b_sf) || (a_sf && b_of)
} else {
false
}
};
let r_is_of_sf = is_of_sf_eq(ba_right);
let l_is_of_sf = is_of_sf_eq(ba_left);
let neq_side = if r_is_of_sf {
Some(ba_left)
} else if l_is_of_sf {
Some(ba_right)
} else {
None
};
let neq_pair = neq_side.and_then(|id| {
if let Expr::BinOp(BinOpKind::NotEq, l, r) = &ssa.vars[id.0 as usize].expr {
if !is_flag_derived(*l, ssa) && !is_flag_derived(*r, ssa) {
return Some((*l, *r));
}
}
None
});
if let Some((neq_l, neq_r)) = neq_pair {
let ra = resolve_cmp_operand(neq_l, ssa);
let rb = resolve_cmp_operand(neq_r, ssa);
let ca = resolve_cmp_operand(cmp_left, ssa);
let cb = resolve_cmp_operand(cmp_right, ssa);
if (ra == ca && rb == cb) || (ra == cb && rb == ca) {
let varnode = ssa.vars[cond_id.0 as usize].varnode;
let new_var = ssa.new_var(
varnode,
Expr::BinOp(BinOpKind::SLess, cmp_right, cmp_left),
1,
);
return Some(new_var);
}
}
}
None
}
fn trace_cond_to_cmp(cond_id: VarId, ssa: &SsaCfg, depth: u32) -> Option<(VarId, VarId)> {
if depth == 0 {
return None;
}
let vdef = &ssa.vars[cond_id.0 as usize];
match &vdef.expr {
Expr::BinOp(BinOpKind::SLess, result_id, zero_id) => {
let zero = &ssa.vars[zero_id.0 as usize];
if matches!(&zero.expr, Expr::Const(0, _)) {
return trace_to_cmp_with_zero(*result_id, ssa, Some(*zero_id));
}
None
}
Expr::BinOp(
BinOpKind::Carry | BinOpKind::SCarry | BinOpKind::SBorrow | BinOpKind::Less,
left,
right,
) => Some((*left, *right)),
Expr::UnaryOp(UnaryOpKind::BoolNot, inner) => trace_cond_to_cmp(*inner, ssa, depth - 1),
Expr::UnaryOp(UnaryOpKind::Zext | UnaryOpKind::Sext, inner) => {
trace_cond_to_cmp(*inner, ssa, depth - 1)
}
Expr::Var(inner) => trace_cond_to_cmp(*inner, ssa, depth - 1),
Expr::BinOp(BinOpKind::BoolAnd | BinOpKind::BoolOr, left, right) => {
trace_cond_to_cmp(*left, ssa, depth - 1)
.or_else(|| trace_cond_to_cmp(*right, ssa, depth - 1))
}
Expr::BinOp(BinOpKind::Eq | BinOpKind::NotEq, left, right) => {
let rdef = &ssa.vars[right.0 as usize];
if matches!(&rdef.expr, Expr::Const(0, _)) {
if let Some(result) = trace_to_cmp_with_zero(*left, ssa, Some(*right)) {
return Some(result);
}
return Some((*left, *right));
}
trace_cond_to_cmp(*left, ssa, depth - 1)
.or_else(|| trace_cond_to_cmp(*right, ssa, depth - 1))
}
_ => None,
}
}
fn find_cmp_operands(block_idx: usize, ssa: &SsaCfg) -> Option<(VarId, VarId)> {
if let Some(result) = find_cmp_in_block(block_idx, ssa) {
return Some(result);
}
for bi in (0..ssa.blocks.len()).rev() {
if bi == block_idx {
continue;
}
if let Some(result) = find_cmp_in_block(bi, ssa) {
return Some(result);
}
}
None
}
fn find_cmp_in_block(block_idx: usize, ssa: &SsaCfg) -> Option<(VarId, VarId)> {
let block = &ssa.blocks[block_idx];
for stmt in block.stmts.iter().rev() {
if let Stmt::Assign(vid) = stmt {
let v = &ssa.vars[vid.0 as usize];
if v.varnode.space == AddressSpaceId::Register && v.varnode.offset == 518 {
if let Expr::BinOp(BinOpKind::Eq, result_id, zero_id) = &v.expr {
let zero = &ssa.vars[zero_id.0 as usize];
if matches!(&zero.expr, Expr::Const(0, _)) {
return trace_to_cmp_with_zero(*result_id, ssa, Some(*zero_id));
}
}
}
if v.varnode.space == AddressSpaceId::Register && v.varnode.offset == 519 {
if let Expr::BinOp(BinOpKind::SLess, result_id, zero_id) = &v.expr {
let zero = &ssa.vars[zero_id.0 as usize];
if matches!(&zero.expr, Expr::Const(0, _)) {
return trace_to_cmp_with_zero(*result_id, ssa, Some(*zero_id));
}
}
}
if v.varnode.space == AddressSpaceId::Register
&& matches!(v.varnode.offset, 512 | 523 | 258 | 259 | 261 | 262)
{
if let Expr::BinOp(
BinOpKind::Carry | BinOpKind::SCarry | BinOpKind::SBorrow | BinOpKind::Less,
left,
right,
) = &v.expr
{
return Some((*left, *right));
}
}
if v.varnode.space == AddressSpaceId::Register && matches!(v.varnode.offset, 257 | 264)
{
if let Expr::BinOp(BinOpKind::Eq, result_id, zero_id) = &v.expr {
let zero = &ssa.vars[zero_id.0 as usize];
if matches!(&zero.expr, Expr::Const(0, _)) {
return trace_to_cmp_with_zero(*result_id, ssa, Some(*zero_id));
}
}
}
if v.varnode.space == AddressSpaceId::Register && matches!(v.varnode.offset, 256 | 263)
{
if let Expr::BinOp(BinOpKind::SLess, result_id, zero_id) = &v.expr {
let zero = &ssa.vars[zero_id.0 as usize];
if matches!(&zero.expr, Expr::Const(0, _)) {
return trace_to_cmp_with_zero(*result_id, ssa, Some(*zero_id));
}
}
}
if v.varnode.space == AddressSpaceId::Register && matches!(v.varnode.offset, 97 | 101)
{
if let Expr::BinOp(BinOpKind::Eq, result_id, zero_id) = &v.expr {
let zero = &ssa.vars[zero_id.0 as usize];
if matches!(&zero.expr, Expr::Const(0, _)) {
return trace_to_cmp_with_zero(*result_id, ssa, Some(*zero_id));
}
}
}
if v.varnode.space == AddressSpaceId::Register && matches!(v.varnode.offset, 96 | 100)
{
if let Expr::BinOp(BinOpKind::SLess, result_id, zero_id) = &v.expr {
let zero = &ssa.vars[zero_id.0 as usize];
if matches!(&zero.expr, Expr::Const(0, _)) {
return trace_to_cmp_with_zero(*result_id, ssa, Some(*zero_id));
}
}
}
if v.varnode.space == AddressSpaceId::Register
&& matches!(v.varnode.offset, 98 | 99 | 102 | 103)
{
if let Expr::BinOp(
BinOpKind::Carry | BinOpKind::SCarry | BinOpKind::SBorrow | BinOpKind::Less,
left,
right,
) = &v.expr
{
return Some((*left, *right));
}
}
}
}
None
}
fn trace_to_cmp_with_zero(
result_id: VarId,
ssa: &SsaCfg,
zero_id: Option<VarId>,
) -> Option<(VarId, VarId)> {
let v = &ssa.vars[result_id.0 as usize];
match &v.expr {
Expr::BinOp(BinOpKind::Sub, left, right) => Some((
resolve_cmp_operand(*left, ssa),
resolve_cmp_operand(*right, ssa),
)),
Expr::BinOp(BinOpKind::And, left, right) => {
let l = resolve_cmp_operand(*left, ssa);
let r = resolve_cmp_operand(*right, ssa);
if let Some(z) = zero_id {
if ssa.vars[l.0 as usize].varnode == ssa.vars[r.0 as usize].varnode {
Some((l, z))
} else {
Some((result_id, z))
}
} else {
Some((l, r))
}
}
Expr::UnaryOp(UnaryOpKind::Neg, inner) => {
if let Some(z) = zero_id {
Some((z, resolve_cmp_operand(*inner, ssa)))
} else {
None
}
}
Expr::Var(inner) => trace_to_cmp_with_zero(*inner, ssa, zero_id),
_ => None,
}
}
fn resolve_cmp_operand(id: VarId, ssa: &SsaCfg) -> VarId {
resolve_cmp_operand_depth(id, ssa, 8)
}
fn resolve_cmp_operand_depth(id: VarId, ssa: &SsaCfg, depth: u32) -> VarId {
if depth == 0 {
return id;
}
let v = &ssa.vars[id.0 as usize];
if v.varnode.space == AddressSpaceId::Register {
if let Expr::Var(src) = &v.expr {
let sv = &ssa.vars[src.0 as usize];
if matches!(&sv.expr, Expr::Load(_)) || sv.param_name.is_some() {
return *src;
}
if sv.varnode.space == AddressSpaceId::Register {
if let Expr::Var(inner) = &sv.expr {
let iv = &ssa.vars[inner.0 as usize];
if matches!(&iv.expr, Expr::Load(_)) || iv.param_name.is_some() {
return *inner;
}
}
if let Expr::Load(_) = &sv.expr {
return *src;
}
}
return *src;
}
if let Expr::Load(_) = &v.expr {
return id;
}
}
if v.varnode.space == AddressSpaceId::Unique {
if let Expr::Var(src) = &v.expr {
return resolve_cmp_operand_depth(*src, ssa, depth - 1);
}
}
id
}
fn classify_jcc_condition(cond_id: VarId, ssa: &SsaCfg) -> Option<(BinOpKind, bool)> {
let vdef = &ssa.vars[cond_id.0 as usize];
if let Expr::UnaryOp(UnaryOpKind::Zext | UnaryOpKind::Sext, inner) = &vdef.expr {
return classify_jcc_condition(*inner, ssa);
}
if let Expr::Var(inner) = &vdef.expr {
if ssa.vars[inner.0 as usize].varnode.space == AddressSpaceId::Register
|| ssa.vars[inner.0 as usize].varnode.space == AddressSpaceId::Unique
{
return classify_jcc_condition(*inner, ssa);
}
}
if let Expr::UnaryOp(UnaryOpKind::BoolNot, inner) = &vdef.expr {
if let Some((kind, swap)) = classify_jcc_condition(*inner, ssa) {
let inverted = match kind {
BinOpKind::Eq => BinOpKind::NotEq,
BinOpKind::NotEq => BinOpKind::Eq,
BinOpKind::Less => BinOpKind::LessEq, BinOpKind::LessEq => BinOpKind::Less, BinOpKind::SLess => BinOpKind::SLessEq, BinOpKind::SLessEq => BinOpKind::SLess, _ => return None,
};
return Some((inverted, !swap));
}
}
let is_zf = |id: VarId| {
is_flag_ref(id, 518, ssa)
|| is_flag_ref(id, 257, ssa)
|| is_flag_ref(id, 264, ssa)
|| is_flag_ref(id, 97, ssa)
};
let is_cf = |id: VarId| {
is_flag_ref(id, 512, ssa)
|| is_flag_ref(id, 258, ssa)
|| is_flag_ref(id, 261, ssa)
|| is_flag_ref(id, 98, ssa)
};
let is_of = |id: VarId| {
is_flag_ref(id, 523, ssa)
|| is_flag_ref(id, 259, ssa)
|| is_flag_ref(id, 262, ssa)
|| is_flag_ref(id, 99, ssa)
};
let is_sf = |id: VarId| {
is_flag_ref(id, 519, ssa)
|| is_flag_ref(id, 256, ssa)
|| is_flag_ref(id, 263, ssa)
|| is_flag_ref(id, 96, ssa)
};
match &vdef.expr {
_ if is_zf(cond_id) => Some((BinOpKind::Eq, false)),
Expr::UnaryOp(UnaryOpKind::BoolNot, inner) if is_zf(*inner) => {
Some((BinOpKind::NotEq, false))
}
_ if is_cf(cond_id) => Some((BinOpKind::Less, false)),
Expr::UnaryOp(UnaryOpKind::BoolNot, inner) if is_cf(*inner) => {
Some((BinOpKind::LessEq, true))
}
Expr::BinOp(BinOpKind::Eq, left, right)
if (is_of(*left) && is_sf(*right)) || (is_sf(*left) && is_of(*right)) =>
{
Some((BinOpKind::SLessEq, true))
}
Expr::BinOp(BinOpKind::NotEq, left, right)
if (is_of(*left) && is_sf(*right)) || (is_sf(*left) && is_of(*right)) =>
{
Some((BinOpKind::SLess, false))
}
_ if is_sf(cond_id) => Some((BinOpKind::SLess, false)),
Expr::BinOp(BinOpKind::BoolOr, left, right)
if (is_cf(*left) && is_zf(*right)) || (is_zf(*left) && is_cf(*right)) =>
{
Some((BinOpKind::LessEq, false))
}
Expr::BinOp(BinOpKind::BoolOr, left, right) => {
let left_def = &ssa.vars[left.0 as usize];
let right_def = &ssa.vars[right.0 as usize];
let zf_or_sfneqof = (is_zf(*left)
&& matches!(&right_def.expr,
Expr::BinOp(BinOpKind::NotEq, a, b)
if (is_of(*a) && is_sf(*b)) || (is_sf(*a) && is_of(*b))))
|| (is_zf(*right)
&& matches!(&left_def.expr,
Expr::BinOp(BinOpKind::NotEq, a, b)
if (is_of(*a) && is_sf(*b)) || (is_sf(*a) && is_of(*b))));
if zf_or_sfneqof {
return Some((BinOpKind::SLessEq, false));
}
let not_cy_or_zr = (matches!(&left_def.expr, Expr::UnaryOp(UnaryOpKind::BoolNot, inner) if is_cf(*inner))
&& is_zf(*right))
|| (matches!(&right_def.expr, Expr::UnaryOp(UnaryOpKind::BoolNot, inner) if is_cf(*inner))
&& is_zf(*left));
if not_cy_or_zr {
return Some((BinOpKind::LessEq, false));
}
let zr_or_ngneqov = (is_zf(*left)
&& matches!(&right_def.expr,
Expr::BinOp(BinOpKind::NotEq, a, b)
if (is_of(*a) && is_sf(*b)) || (is_sf(*a) && is_of(*b))))
|| (is_zf(*right)
&& matches!(&left_def.expr,
Expr::BinOp(BinOpKind::NotEq, a, b)
if (is_of(*a) && is_sf(*b)) || (is_sf(*a) && is_of(*b))));
if zr_or_ngneqov {
return Some((BinOpKind::SLessEq, false));
}
None
}
Expr::BinOp(BinOpKind::BoolAnd, left, right) => {
let left_def = &ssa.vars[left.0 as usize];
let right_def = &ssa.vars[right.0 as usize];
let left_is_not_zf = matches!(&left_def.expr,
Expr::UnaryOp(UnaryOpKind::BoolNot, inner) if is_zf(*inner));
let right_is_sf_eq_of = matches!(&right_def.expr,
Expr::BinOp(BinOpKind::Eq, a, b)
if (is_of(*a) && is_sf(*b)) || (is_sf(*a) && is_of(*b)));
if left_is_not_zf && right_is_sf_eq_of {
Some((BinOpKind::SLess, true)) } else {
let left_is_not_cf = matches!(&left_def.expr,
Expr::UnaryOp(UnaryOpKind::BoolNot, inner) if is_cf(*inner));
let right_is_not_zf = matches!(&right_def.expr,
Expr::UnaryOp(UnaryOpKind::BoolNot, inner) if is_zf(*inner));
if left_is_not_cf && right_is_not_zf {
Some((BinOpKind::Less, true)) } else if left_is_not_zf {
Some((BinOpKind::NotEq, false))
} else {
None
}
}
}
_ => None,
}
}
fn is_flag_ref(id: VarId, flag_offset: u64, ssa: &SsaCfg) -> bool {
let v = &ssa.vars[id.0 as usize];
if v.varnode.space == AddressSpaceId::Register && v.varnode.offset == flag_offset {
return true;
}
if let Expr::Var(inner) = &v.expr {
let inner_v = &ssa.vars[inner.0 as usize];
if inner_v.varnode.space == AddressSpaceId::Register
&& inner_v.varnode.offset == flag_offset
{
return true;
}
}
false
}
fn is_comparison(kind: BinOpKind) -> bool {
matches!(
kind,
BinOpKind::Eq
| BinOpKind::NotEq
| BinOpKind::Less
| BinOpKind::LessEq
| BinOpKind::SLess
| BinOpKind::SLessEq
)
}
fn detect_return_values(ssa: &mut SsaCfg) {
let has_arm32_regs = ssa.vars.iter().any(|v| {
v.varnode.space == AddressSpaceId::Register
&& v.varnode.offset == 32
&& v.varnode.size == 4
&& matches!(v.varnode.offset, 32..=92)
}); let has_aarch64_regs = ssa.vars.iter().any(|v| {
v.varnode.space == AddressSpaceId::Register
&& v.varnode.offset >= 16384 && v.varnode.offset <= 16440 && (v.varnode.size == 4 || v.varnode.size == 8)
});
let ret_reg_offset = if has_arm32_regs {
32
} else if has_aarch64_regs {
16384
}
else {
RAX_OFFSET
};
if has_aarch64_regs {
for bi in 0..ssa.blocks.len() {
if let SsaTerminator::Return(Some(var_id)) = ssa.blocks[bi].terminator {
if let Expr::UnaryOp(UnaryOpKind::Zext, inner) = &ssa.vars[var_id.0 as usize].expr {
if ssa.vars[inner.0 as usize].varnode.size == 4 {
let inner_id = *inner;
ssa.blocks[bi].terminator = SsaTerminator::Return(Some(inner_id));
}
}
}
}
}
for bi in 0..ssa.blocks.len() {
if let SsaTerminator::Return(ref ret_val) = ssa.blocks[bi].terminator {
if ret_val.is_some() {
continue;
}
} else {
continue;
}
let mut found = find_ret_reg_in_block(&ssa.blocks[bi].stmts, &ssa.vars, ret_reg_offset);
if found.is_none() {
for stmt in ssa.blocks[bi].stmts.iter().rev() {
if let Stmt::Assign(var_id) = stmt {
let vdef = &ssa.vars[var_id.0 as usize];
if vdef.call_return {
found = Some(*var_id);
ssa.diagnostics.push(crate::ir::Diagnostic {
severity: crate::ir::Severity::Info,
kind: crate::ir::DiagKind::StaleReturnInherited,
addr: None,
detail: format!(
"block {} return inferred from call_return; \
function may actually be void",
bi
),
});
break;
}
}
}
}
if found.is_none() {
for pred_bi in 0..ssa.blocks.len() {
if pred_bi == bi {
continue;
}
let flows_to_bi = match &ssa.blocks[pred_bi].terminator {
SsaTerminator::Fallthrough(b) | SsaTerminator::Branch(b) => b.0 == bi,
SsaTerminator::CBranch {
taken, fallthrough, ..
} => taken.0 == bi || fallthrough.0 == bi,
SsaTerminator::Call { fallthrough, .. } => fallthrough.0 == bi,
_ => false,
};
if !flows_to_bi {
continue;
}
let pred_found =
find_ret_reg_in_block(&ssa.blocks[pred_bi].stmts, &ssa.vars, ret_reg_offset);
if pred_found.is_some() {
found = pred_found;
break;
}
if matches!(&ssa.blocks[pred_bi].terminator, SsaTerminator::Call { .. }) {
for stmt in ssa.blocks[pred_bi].stmts.iter().rev() {
if let Stmt::Assign(var_id) = stmt {
if ssa.vars[var_id.0 as usize].call_return {
found = Some(*var_id);
break;
}
}
}
}
if found.is_some() {
break;
}
}
}
if found.is_none() {
let mut visited = std::collections::HashSet::new();
let mut frontier = vec![bi];
visited.insert(bi);
for _hop in 0..3 {
let mut next_frontier = Vec::new();
for &target_bi in &frontier {
for pred_bi in 0..ssa.blocks.len() {
if visited.contains(&pred_bi) {
continue;
}
let flows = match &ssa.blocks[pred_bi].terminator {
SsaTerminator::Fallthrough(b) | SsaTerminator::Branch(b) => {
b.0 == target_bi
}
SsaTerminator::CBranch {
taken, fallthrough, ..
} => taken.0 == target_bi || fallthrough.0 == target_bi,
SsaTerminator::Call { fallthrough, .. } => fallthrough.0 == target_bi,
_ => false,
};
if !flows {
continue;
}
visited.insert(pred_bi);
next_frontier.push(pred_bi);
if let Some(var_id) = find_ret_reg_in_block(
&ssa.blocks[pred_bi].stmts,
&ssa.vars,
ret_reg_offset,
) {
found = Some(var_id);
break;
}
for stmt in ssa.blocks[pred_bi].stmts.iter().rev() {
if let Stmt::Assign(var_id) = stmt {
if ssa.vars[var_id.0 as usize].call_return {
found = Some(*var_id);
break;
}
}
}
if found.is_some() {
break;
}
}
if found.is_some() {
break;
}
}
if found.is_some() {
break;
}
frontier = next_frontier;
}
}
if found.is_none() {
let has_float_ops = ssa.vars.iter().any(|v| {
matches!(
&v.expr,
Expr::BinOp(
BinOpKind::FloatAdd
| BinOpKind::FloatSub
| BinOpKind::FloatMult
| BinOpKind::FloatDiv,
_,
_
) | Expr::UnaryOp(
UnaryOpKind::FloatNeg
| UnaryOpKind::FloatAbs
| UnaryOpKind::FloatSqrt
| UnaryOpKind::Int2Float
| UnaryOpKind::Float2Float,
_
)
)
});
if has_float_ops {
const XMM0_OFFSET: u64 = 4608;
found = find_float_ret_in_block(&ssa.blocks[bi].stmts, &ssa.vars, XMM0_OFFSET);
if found.is_none() {
for pred_bi in 0..ssa.blocks.len() {
if pred_bi == bi {
continue;
}
let flows_to_bi = match &ssa.blocks[pred_bi].terminator {
SsaTerminator::Fallthrough(b) | SsaTerminator::Branch(b) => b.0 == bi,
SsaTerminator::CBranch {
taken, fallthrough, ..
} => taken.0 == bi || fallthrough.0 == bi,
SsaTerminator::Call { fallthrough, .. } => fallthrough.0 == bi,
_ => false,
};
if !flows_to_bi {
continue;
}
found = find_float_ret_in_block(
&ssa.blocks[pred_bi].stmts,
&ssa.vars,
XMM0_OFFSET,
);
if found.is_some() {
break;
}
}
}
if found.is_none() {
let mut best: Option<VarId> = None;
for (vi, vd) in ssa.vars.iter().enumerate() {
if vd.varnode.space == AddressSpaceId::Register
&& vd.varnode.offset == XMM0_OFFSET
&& matches!(
&vd.expr,
Expr::BinOp(
BinOpKind::FloatAdd
| BinOpKind::FloatSub
| BinOpKind::FloatMult
| BinOpKind::FloatDiv,
_,
_
) | Expr::UnaryOp(
UnaryOpKind::FloatNeg
| UnaryOpKind::FloatAbs
| UnaryOpKind::FloatSqrt
| UnaryOpKind::Int2Float
| UnaryOpKind::Float2Float,
_
) | Expr::Var(_)
)
{
if vd.param_name.is_some() {
continue;
}
best = Some(VarId(vi as u32));
}
}
found = best;
}
}
}
if let Some(var_id) = found {
let actual_id = if has_aarch64_regs {
if let Expr::UnaryOp(UnaryOpKind::Zext, inner) = &ssa.vars[var_id.0 as usize].expr {
if ssa.vars[inner.0 as usize].varnode.size == 4 {
*inner
} else {
var_id
}
} else {
var_id
}
} else {
var_id
};
if let SsaTerminator::Return(ref mut ret_val) = ssa.blocks[bi].terminator {
*ret_val = Some(actual_id);
}
}
}
}
fn find_float_ret_in_block(
stmts: &[Stmt],
vars: &[VarDef],
float_ret_offset: u64,
) -> Option<VarId> {
for stmt in stmts.iter().rev() {
if let Stmt::Assign(var_id) = stmt {
let vdef = &vars[var_id.0 as usize];
if vdef.varnode.space == AddressSpaceId::Register
&& vdef.varnode.offset == float_ret_offset
{
return Some(*var_id);
}
}
}
None
}
fn find_ret_reg_in_block(stmts: &[Stmt], vars: &[VarDef], ret_reg_offset: u64) -> Option<VarId> {
for stmt in stmts {
if let Stmt::Assign(var_id) = stmt {
let vdef = &vars[var_id.0 as usize];
if vdef.varnode.space == AddressSpaceId::Register
&& vdef.varnode.offset == ret_reg_offset
&& vdef.varnode.size >= 4
&& matches!(&vdef.expr, Expr::Phi(_))
{
return Some(*var_id);
}
}
}
for stmt in stmts.iter().rev() {
if let Stmt::Assign(var_id) = stmt {
let vdef = &vars[var_id.0 as usize];
if vdef.varnode.space == AddressSpaceId::Register
&& vdef.varnode.offset == ret_reg_offset
&& vdef.varnode.size >= 4
{
return Some(*var_id);
}
}
}
None
}
fn collect_call_arguments(ssa: &mut SsaCfg) {
let is_x86_32 = arg_reg_offsets().is_empty();
for bi in 0..ssa.blocks.len() {
let mut all_consumed: Vec<usize> = Vec::new();
let call_info = match &ssa.blocks[bi].terminator {
SsaTerminator::Call {
target,
fallthrough,
..
} => Some((target.clone(), *fallthrough)),
_ => None,
};
if let Some((target, fallthrough)) = call_info {
let n_stmts = ssa.blocks[bi].stmts.len();
let mut args = if is_x86_32 {
let (args, consumed) =
collect_stack_args_from_block(&ssa.blocks[bi].stmts, &ssa.vars, n_stmts);
if !args.is_empty() {
all_consumed.extend(consumed);
}
args
} else {
collect_reg_args_from_block(&ssa.blocks[bi].stmts, &ssa.vars, n_stmts)
};
if !args.is_empty() {
let existing_out =
if let SsaTerminator::Call { out, .. } = &ssa.blocks[bi].terminator {
*out
} else {
None
};
ssa.blocks[bi].terminator = SsaTerminator::Call {
target,
args,
out: existing_out,
fallthrough,
};
}
}
let call_indices: Vec<usize> = (0..ssa.blocks[bi].stmts.len())
.filter(|si| {
matches!(&ssa.blocks[bi].stmts[*si],
Stmt::Call { args, .. } if args.is_empty())
})
.collect();
for &si in call_indices.iter().rev() {
let mut args = if is_x86_32 {
let (args, consumed) =
collect_stack_args_from_block(&ssa.blocks[bi].stmts, &ssa.vars, si);
if !args.is_empty() {
all_consumed.extend(consumed);
}
args
} else {
collect_reg_args_from_block(&ssa.blocks[bi].stmts, &ssa.vars, si)
};
if !args.is_empty() {
if let Stmt::Call { target, out, .. } = &ssa.blocks[bi].stmts[si] {
let target = target.clone();
let out = *out;
ssa.blocks[bi].stmts[si] = Stmt::Call { target, args, out };
}
}
}
all_consumed.sort_unstable();
all_consumed.dedup();
for &i in all_consumed.iter().rev() {
if i < ssa.blocks[bi].stmts.len() {
ssa.blocks[bi].stmts.remove(i);
}
}
}
}
fn collect_reg_args_from_block(stmts: &[Stmt], vars: &[VarDef], up_to: usize) -> Vec<VarId> {
let arg_offsets = arg_reg_offsets();
if arg_offsets.is_empty() {
return Vec::new();
}
let mut args: Vec<(u64, VarId)> = Vec::new();
for j in (0..up_to).rev() {
if let Stmt::Assign(var_id) = &stmts[j] {
let vdef = safe_var(vars, *var_id);
if vdef.call_return && matches!(&vdef.expr, Expr::Unknown) {
continue;
}
if vdef.varnode.space == AddressSpaceId::Register
&& arg_offsets.contains(&vdef.varnode.offset)
{
if !args.iter().any(|(off, _)| *off == vdef.varnode.offset) {
args.push((vdef.varnode.offset, *var_id));
}
}
if vdef.varnode.space == AddressSpaceId::Register
&& !arg_offsets.contains(&vdef.varnode.offset)
{
for &arg_off in arg_offsets {
if arg_off == vdef.varnode.offset
&& !args.iter().any(|(off, _)| *off == arg_off)
{
args.push((arg_off, *var_id));
}
}
}
}
if matches!(&stmts[j], Stmt::Call { .. }) {
break;
}
}
let float_offsets = float_arg_reg_offsets();
if !float_offsets.is_empty() {
let mut float_args: Vec<(u64, VarId)> = Vec::new();
for j in (0..up_to).rev() {
if let Stmt::Assign(var_id) = &stmts[j] {
let vdef = safe_var(vars, *var_id);
if vdef.varnode.space == AddressSpaceId::Register
&& float_offsets.contains(&vdef.varnode.offset)
{
if !float_args
.iter()
.any(|(off, _)| *off == vdef.varnode.offset)
{
float_args.push((vdef.varnode.offset, *var_id));
}
}
}
if matches!(&stmts[j], Stmt::Call { .. }) {
break;
}
}
float_args
.sort_by_key(|(off, _)| float_offsets.iter().position(|o| o == off).unwrap_or(99));
args.extend(float_args);
}
args.sort_by_key(|(off, _)| {
arg_reg_offsets()
.iter()
.position(|o| o == off)
.unwrap_or(99)
});
args.into_iter().map(|(_, v)| v).collect()
}
fn collect_stack_args_from_block(
stmts: &[Stmt],
vars: &[VarDef],
up_to: usize,
) -> (Vec<VarId>, Vec<usize>) {
let mut pushed_values: Vec<VarId> = Vec::new();
let mut consumed_indices: Vec<usize> = Vec::new();
let mut i = up_to;
while i > 0 {
i -= 1;
match &stmts[i] {
Stmt::Store { addr, val } => {
let addr_def = &vars[addr.0 as usize];
if is_esp_var(addr_def, vars) {
pushed_values.push(*val);
consumed_indices.push(i);
continue;
}
continue;
}
Stmt::Assign(v) => {
let vdef = &vars[v.0 as usize];
if vdef.varnode.space == AddressSpaceId::Register
&& vdef.varnode.offset == ESP_OFFSET
&& vdef.varnode.size == 4
{
consumed_indices.push(i);
continue;
}
if FLAG_OFFSETS.contains(&vdef.varnode.offset) {
continue;
}
if vdef.varnode.space == AddressSpaceId::Unique {
consumed_indices.push(i);
continue;
}
break;
}
Stmt::Call { .. } => break, }
}
pushed_values.reverse();
(pushed_values, consumed_indices)
}
fn is_esp_var(vdef: &VarDef, vars: &[VarDef]) -> bool {
if vdef.varnode.space == AddressSpaceId::Register
&& vdef.varnode.offset == ESP_OFFSET
&& vdef.varnode.size == 4
{
return true;
}
if vdef.varnode.space == AddressSpaceId::Unique {
match &vdef.expr {
Expr::BinOp(BinOpKind::Sub, left, _) | Expr::BinOp(BinOpKind::Add, left, _) => {
let left_def = &vars[left.0 as usize];
return left_def.varnode.space == AddressSpaceId::Register
&& left_def.varnode.offset == ESP_OFFSET
&& left_def.varnode.size == 4;
}
Expr::Var(v) => {
let inner = &vars[v.0 as usize];
return inner.varnode.space == AddressSpaceId::Register
&& inner.varnode.offset == ESP_OFFSET
&& inner.varnode.size == 4;
}
_ => {}
}
}
false
}
fn infer_types(ssa: &mut SsaCfg) {
let n = ssa.vars.len();
for vi in 0..n {
let ty = seed_type_from_expr(&ssa.vars[vi].expr, &ssa.vars);
if ty != InferredType::Unknown {
ssa.vars[vi].inferred_type = ty;
}
}
for bi in 0..ssa.blocks.len() {
for stmt in &ssa.blocks[bi].stmts {
if let Stmt::Store { addr, .. } = stmt {
let cur = ssa.vars[addr.0 as usize].inferred_type;
ssa.vars[addr.0 as usize].inferred_type = cur.merge(InferredType::Pointer);
}
}
}
for vi in 0..n {
match ssa.vars[vi].expr {
Expr::Load(ptr) => {
let cur = ssa.vars[ptr.0 as usize].inferred_type;
ssa.vars[ptr.0 as usize].inferred_type = cur.merge(InferredType::Pointer);
}
Expr::FieldAccess(base, _) => {
let cur = ssa.vars[base.0 as usize].inferred_type;
ssa.vars[base.0 as usize].inferred_type = cur.merge(InferredType::Pointer);
}
_ => {}
}
}
for _ in 0..2 {
for vi in 0..n {
let expr = ssa.vars[vi].expr.clone();
let propagated = forward_propagate_type(&expr, &ssa.vars);
if propagated != InferredType::Unknown
&& ssa.vars[vi].inferred_type == InferredType::Unknown
{
ssa.vars[vi].inferred_type = propagated;
}
}
}
for vi in 0..n {
let ty = ssa.vars[vi].inferred_type;
if ty == InferredType::Unknown {
continue;
}
match ssa.vars[vi].expr.clone() {
Expr::BinOp(_, left, right) => {
backward_propagate(ssa, left, ty);
backward_propagate(ssa, right, ty);
}
Expr::UnaryOp(_, input) => {
backward_propagate(ssa, input, ty);
}
Expr::Var(v) => {
backward_propagate(ssa, v, ty);
}
Expr::Ternary(_, t, e) => {
backward_propagate(ssa, t, ty);
backward_propagate(ssa, e, ty);
}
_ => {}
}
}
for vi in 0..n {
if ssa.vars[vi].size == 1 {
if let Expr::BinOp(kind, _, _) = &ssa.vars[vi].expr {
match kind {
BinOpKind::Eq
| BinOpKind::NotEq
| BinOpKind::Less
| BinOpKind::LessEq
| BinOpKind::SLess
| BinOpKind::SLessEq
| BinOpKind::FloatEq
| BinOpKind::FloatNotEq
| BinOpKind::FloatLess
| BinOpKind::FloatLessEq
| BinOpKind::Carry
| BinOpKind::SCarry
| BinOpKind::SBorrow
| BinOpKind::BoolAnd
| BinOpKind::BoolOr
| BinOpKind::BoolXor => {
ssa.vars[vi].inferred_type = InferredType::Bool;
}
_ => {}
}
}
if let Expr::UnaryOp(UnaryOpKind::BoolNot | UnaryOpKind::FloatNan, _) =
&ssa.vars[vi].expr
{
ssa.vars[vi].inferred_type = InferredType::Bool;
}
}
}
}
fn seed_type_from_expr(expr: &Expr, _vars: &[VarDef]) -> InferredType {
match expr {
Expr::BinOp(kind, _, _) => match kind {
BinOpKind::FloatAdd
| BinOpKind::FloatSub
| BinOpKind::FloatMult
| BinOpKind::FloatDiv => InferredType::Float,
BinOpKind::FloatEq
| BinOpKind::FloatNotEq
| BinOpKind::FloatLess
| BinOpKind::FloatLessEq => InferredType::Bool,
BinOpKind::SDiv | BinOpKind::SRem => InferredType::Signed,
BinOpKind::SLess | BinOpKind::SLessEq => InferredType::Bool,
BinOpKind::Div | BinOpKind::Rem => InferredType::Unsigned,
BinOpKind::Less | BinOpKind::LessEq => InferredType::Bool,
BinOpKind::Eq | BinOpKind::NotEq => InferredType::Bool,
BinOpKind::BoolAnd | BinOpKind::BoolOr | BinOpKind::BoolXor => InferredType::Bool,
_ => InferredType::Unknown,
},
Expr::UnaryOp(kind, _) => match kind {
UnaryOpKind::FloatNeg
| UnaryOpKind::FloatAbs
| UnaryOpKind::FloatSqrt
| UnaryOpKind::FloatCeil
| UnaryOpKind::FloatFloor
| UnaryOpKind::FloatRound
| UnaryOpKind::Int2Float
| UnaryOpKind::Float2Float => InferredType::Float,
UnaryOpKind::FloatNan => InferredType::Bool,
UnaryOpKind::Trunc => InferredType::Signed,
UnaryOpKind::Sext | UnaryOpKind::Neg => InferredType::Signed,
UnaryOpKind::Zext => InferredType::Unsigned,
UnaryOpKind::BoolNot => InferredType::Bool,
_ => InferredType::Unknown,
},
_ => InferredType::Unknown,
}
}
fn forward_propagate_type(expr: &Expr, vars: &[VarDef]) -> InferredType {
match expr {
Expr::Var(v) => vars[v.0 as usize].inferred_type,
Expr::BinOp(BinOpKind::Add | BinOpKind::Sub | BinOpKind::Mult, left, right) => {
let lt = vars[left.0 as usize].inferred_type;
let rt = vars[right.0 as usize].inferred_type;
if lt == InferredType::Float || rt == InferredType::Float {
InferredType::Float
} else if lt == InferredType::Signed || rt == InferredType::Signed {
InferredType::Signed
} else {
InferredType::Unknown
}
}
Expr::UnaryOp(UnaryOpKind::Sext, input) => {
let it = vars[input.0 as usize].inferred_type;
if it == InferredType::Unknown {
InferredType::Signed
} else {
it
}
}
Expr::UnaryOp(UnaryOpKind::Zext, input) => {
let it = vars[input.0 as usize].inferred_type;
if it == InferredType::Unknown {
InferredType::Unsigned
} else {
it
}
}
Expr::UnaryOp(UnaryOpKind::Neg, _) => InferredType::Signed,
_ => InferredType::Unknown,
}
}
fn backward_propagate(ssa: &mut SsaCfg, var: VarId, ty: InferredType) {
let cur = ssa.vars[var.0 as usize].inferred_type;
if cur == InferredType::Unknown {
match ty {
InferredType::Signed | InferredType::Float => {
ssa.vars[var.0 as usize].inferred_type = ty;
}
_ => {}
}
}
}
pub(crate) fn recount_uses(ssa: &mut SsaCfg) {
let mut use_counts = vec![0u32; ssa.vars.len()];
for v in 0..ssa.vars.len() {
match &ssa.vars[v].expr {
Expr::Var(id) => use_counts[id.0 as usize] += 1,
Expr::BinOp(_, l, r) => {
use_counts[l.0 as usize] += 1;
use_counts[r.0 as usize] += 1;
}
Expr::UnaryOp(_, i) | Expr::Load(i) | Expr::FieldAccess(i, _) => {
use_counts[i.0 as usize] += 1
}
Expr::Phi(inputs) => {
for i in inputs {
use_counts[i.0 as usize] += 1;
}
}
Expr::Ternary(c, t, e) => {
use_counts[c.0 as usize] += 1;
use_counts[t.0 as usize] += 1;
use_counts[e.0 as usize] += 1;
}
Expr::UserOp { inputs, .. } => {
for i in inputs {
use_counts[i.0 as usize] += 1;
}
}
Expr::Const(_, _) | Expr::Unknown => {}
}
}
for block in &ssa.blocks {
for stmt in &block.stmts {
match stmt {
Stmt::Store { addr, val } => {
use_counts[addr.0 as usize] += 1;
use_counts[val.0 as usize] += 1;
}
Stmt::Call { args, .. } => {
for a in args {
use_counts[a.0 as usize] += 1;
}
}
_ => {}
}
}
match &block.terminator {
SsaTerminator::CBranch { cond, .. } => use_counts[cond.0 as usize] += 1,
SsaTerminator::Return(Some(v)) | SsaTerminator::Indirect(v) => {
use_counts[v.0 as usize] += 1
}
SsaTerminator::Call { args, out, .. } => {
for a in args {
use_counts[a.0 as usize] += 1;
}
let _ = out; }
_ => {}
}
}
for (i, count) in use_counts.into_iter().enumerate() {
ssa.vars[i].use_count = count;
}
}
#[allow(dead_code)]
fn forward_substitute_block(ssa: &mut SsaCfg) {
for bi in 0..ssa.blocks.len() {
let mut reg_value: std::collections::HashMap<(u64, u32), VarId> =
std::collections::HashMap::new();
let mut alias_map: std::collections::HashMap<u32, VarId> = std::collections::HashMap::new();
let stmts = &ssa.blocks[bi].stmts;
let mut replacements: Vec<(u32, Expr)> = Vec::new();
for stmt in stmts {
match stmt {
Stmt::Assign(var_id) => {
let vdef = &ssa.vars[var_id.0 as usize];
if vdef.varnode.space == AddressSpaceId::Register {
match &vdef.expr {
Expr::Var(src_id) => {
let src = &ssa.vars[src_id.0 as usize];
if src.varnode.space == AddressSpaceId::Register {
let key = (src.varnode.offset, src.varnode.size);
if let Some(original) = reg_value.get(&key) {
replacements.push((var_id.0, Expr::Var(*original)));
let my_key = (vdef.varnode.offset, vdef.varnode.size);
reg_value.insert(my_key, *original);
} else {
let my_key = (vdef.varnode.offset, vdef.varnode.size);
reg_value.insert(my_key, *src_id);
}
} else {
if let Some(original) = alias_map.get(&src_id.0) {
replacements.push((var_id.0, Expr::Var(*original)));
let my_key = (vdef.varnode.offset, vdef.varnode.size);
reg_value.insert(my_key, *original);
} else {
let my_key = (vdef.varnode.offset, vdef.varnode.size);
reg_value.insert(my_key, *src_id);
}
}
}
Expr::Load(_) => {
let my_key = (vdef.varnode.offset, vdef.varnode.size);
reg_value.remove(&my_key);
}
_ => {
let my_key = (vdef.varnode.offset, vdef.varnode.size);
reg_value.remove(&my_key);
}
}
} else {
if let Expr::Var(src_id) = &vdef.expr {
let src = &ssa.vars[src_id.0 as usize];
if src.varnode.space == AddressSpaceId::Register {
let key = (src.varnode.offset, src.varnode.size);
if let Some(original) = reg_value.get(&key) {
replacements.push((var_id.0, Expr::Var(*original)));
alias_map.insert(var_id.0, *original);
} else {
alias_map.insert(var_id.0, *src_id);
}
}
}
}
}
Stmt::Store { .. } => {
}
Stmt::Call { .. } => {
reg_value.clear();
}
}
}
if matches!(&ssa.blocks[bi].terminator, SsaTerminator::Call { .. }) {
}
for (var_idx, new_expr) in replacements {
ssa.vars[var_idx as usize].expr = new_expr;
}
}
}
#[allow(dead_code)]
fn eliminate_save_restore(ssa: &mut SsaCfg) {
for v in 0..ssa.vars.len() {
let vdef = &ssa.vars[v];
if vdef.varnode.space != AddressSpaceId::Register {
continue;
}
let src_id = match &vdef.expr {
Expr::Var(id) => Some(*id),
_ => None,
};
let Some(src_id) = src_id else { continue };
let src = &ssa.vars[src_id.0 as usize];
if let Expr::Var(orig_id) = &src.expr {
let orig = &ssa.vars[orig_id.0 as usize];
if orig.varnode.space == AddressSpaceId::Register
&& orig.varnode.offset == vdef.varnode.offset
&& orig.varnode.size == vdef.varnode.size
{
}
}
}
let mut sr_replacements: Vec<(usize, VarId)> = Vec::new();
for v in 0..ssa.vars.len() {
let vdef = &ssa.vars[v];
if vdef.varnode.space != AddressSpaceId::Register {
continue;
}
if let Expr::Var(src_id) = &vdef.expr {
let src = &ssa.vars[src_id.0 as usize];
if let Expr::Var(orig_id) = &src.expr {
let orig = &ssa.vars[orig_id.0 as usize];
if orig.varnode.space == AddressSpaceId::Register
&& orig.varnode.offset == vdef.varnode.offset
&& orig.varnode.size == vdef.varnode.size
&& src.use_count <= 2
{
sr_replacements.push((v, *orig_id));
}
}
}
}
for bi in 0..ssa.blocks.len() {
let mut store_map: std::collections::HashMap<u64, VarId> = std::collections::HashMap::new();
for stmt in &ssa.blocks[bi].stmts {
if let Stmt::Store { addr, val } = stmt {
if let Some(offset) = compute_rbp_offset(*addr, &ssa.vars) {
let stored = &ssa.vars[val.0 as usize];
if stored.varnode.space == AddressSpaceId::Register {
store_map.insert(offset, *val);
}
}
}
}
if store_map.is_empty() {
continue;
}
let mut load_replacements: Vec<(u32, VarId)> = Vec::new();
for stmt in &ssa.blocks[bi].stmts {
if let Stmt::Assign(var_id) = stmt {
let vdef = &ssa.vars[var_id.0 as usize];
if vdef.varnode.space != AddressSpaceId::Register {
continue;
}
if let Expr::Load(addr_id) = &vdef.expr {
if let Some(offset) = compute_rbp_offset(*addr_id, &ssa.vars) {
if let Some(stored_val) = store_map.get(&offset) {
let stored = &ssa.vars[stored_val.0 as usize];
if stored.varnode.offset == vdef.varnode.offset {
load_replacements.push((var_id.0, *stored_val));
}
}
}
}
}
}
}
}
#[allow(dead_code)]
fn compute_rbp_offset(addr_id: VarId, vars: &[VarDef]) -> Option<u64> {
let v = &vars[addr_id.0 as usize];
match &v.expr {
Expr::BinOp(BinOpKind::Add, base_id, off_id) => {
let base = &vars[base_id.0 as usize];
if base.varnode.space == AddressSpaceId::Register && base.varnode.offset == 40 {
if let Expr::Const(val, _) = &vars[off_id.0 as usize].expr {
return Some(*val);
}
}
if let Expr::Var(inner) = &base.expr {
let inner_v = &vars[inner.0 as usize];
if inner_v.varnode.space == AddressSpaceId::Register && inner_v.varnode.offset == 40
{
if let Expr::Const(val, _) = &vars[off_id.0 as usize].expr {
return Some(*val);
}
}
}
None
}
Expr::Var(inner) => compute_rbp_offset(*inner, vars),
_ => None,
}
}
fn propagate_call_returns(ssa: &mut SsaCfg) {
for bi in 0..ssa.blocks.len() {
let has_call_term = matches!(&ssa.blocks[bi].terminator, SsaTerminator::Call { .. });
if has_call_term {
let fallthrough = match &ssa.blocks[bi].terminator {
SsaTerminator::Call { fallthrough, .. } => Some(*fallthrough),
_ => None,
};
let mut out_var: Option<VarId> = None;
let mut out_stmt_idx: Option<usize> = None;
for (idx, stmt) in ssa.blocks[bi].stmts.iter().enumerate().rev() {
if let Stmt::Assign(var_id) = stmt {
let vdef = &ssa.vars[var_id.0 as usize];
if vdef.call_return {
if vdef.use_count > 0 {
out_var = Some(*var_id);
out_stmt_idx = Some(idx);
}
break; }
}
}
if let (Some(var), Some(idx)) = (out_var, out_stmt_idx) {
if let SsaTerminator::Call { out, .. } = &mut ssa.blocks[bi].terminator {
*out = Some(var);
}
ssa.blocks[bi].stmts.remove(idx);
}
if let Some(ft) = fallthrough {
if ft.0 < ssa.blocks.len() {
for stmt in &ssa.blocks[ft.0].stmts {
if let Stmt::Assign(var_id) = stmt {
let vdef = &ssa.vars[var_id.0 as usize];
if vdef.call_return {
break;
}
if vdef.varnode.space == AddressSpaceId::Register
&& (vdef.varnode.offset == RAX_OFFSET)
&& matches!(&vdef.expr, Expr::Unknown)
{
ssa.vars[var_id.0 as usize].call_return = true;
break;
}
}
}
}
}
}
let mut call_idx: Option<usize> = None;
let mut to_remove: Vec<usize> = Vec::new();
for i in 0..ssa.blocks[bi].stmts.len() {
if matches!(&ssa.blocks[bi].stmts[i], Stmt::Call { .. }) {
call_idx = Some(i);
continue;
}
if let Some(cidx) = call_idx {
if let Stmt::Assign(var_id) = &ssa.blocks[bi].stmts[i] {
let var_id = *var_id;
let vdef = &ssa.vars[var_id.0 as usize];
if vdef.call_return {
let use_count = ssa.vars[var_id.0 as usize].use_count;
if use_count > 0 {
if let Stmt::Call { out, .. } = &mut ssa.blocks[bi].stmts[cidx] {
*out = Some(var_id);
to_remove.push(i);
}
}
call_idx = None;
continue;
}
if vdef.varnode.space == AddressSpaceId::Register
&& vdef.varnode.offset == RAX_OFFSET
{
ssa.vars[var_id.0 as usize].call_return = true;
let use_count = ssa.vars[var_id.0 as usize].use_count;
if use_count > 0 {
if let Stmt::Call { out, .. } = &mut ssa.blocks[bi].stmts[cidx] {
*out = Some(var_id);
to_remove.push(i);
}
}
call_idx = None;
}
} else {
call_idx = None;
}
}
}
for idx in to_remove.into_iter().rev() {
ssa.blocks[bi].stmts.remove(idx);
}
}
}
#[allow(dead_code)]
fn collapse_copy_chains(ssa: &mut SsaCfg) {
let copy_map: Vec<Option<VarId>> = (0..ssa.vars.len())
.map(|v| {
let vdef = &ssa.vars[v];
if vdef.call_return {
return None;
}
if vdef.use_count <= 1 && vdef.varnode.space == AddressSpaceId::Register {
if let Expr::Var(src) = &vdef.expr {
let src_def = &ssa.vars[src.0 as usize];
if src_def.call_return {
return None;
}
if src_def.varnode.space != AddressSpaceId::Register {
return Some(*src);
}
if src_def.param_name.is_some() {
return Some(*src);
}
}
}
None
})
.collect();
for v in 0..ssa.vars.len() {
let expr = ssa.vars[v].expr.clone();
ssa.vars[v].expr = substitute_copies(&expr, ©_map);
}
}
#[allow(dead_code)]
fn substitute_copies(expr: &Expr, copy_map: &[Option<VarId>]) -> Expr {
match expr {
Expr::Var(id) => {
if let Some(Some(src)) = copy_map.get(id.0 as usize) {
if let Some(Some(src2)) = copy_map.get(src.0 as usize) {
Expr::Var(*src2)
} else {
Expr::Var(*src)
}
} else {
expr.clone()
}
}
Expr::BinOp(kind, left, right) => {
let l = resolve_copy(*left, copy_map);
let r = resolve_copy(*right, copy_map);
Expr::BinOp(*kind, l, r)
}
Expr::UnaryOp(kind, input) => {
let i = resolve_copy(*input, copy_map);
Expr::UnaryOp(*kind, i)
}
Expr::Load(ptr) => {
let p = resolve_copy(*ptr, copy_map);
Expr::Load(p)
}
_ => expr.clone(),
}
}
#[allow(dead_code)]
fn resolve_copy(id: VarId, copy_map: &[Option<VarId>]) -> VarId {
if let Some(Some(src)) = copy_map.get(id.0 as usize) {
if let Some(Some(src2)) = copy_map.get(src.0 as usize) {
*src2
} else {
*src
}
} else {
id
}
}
fn name_parameters(ssa: &mut SsaCfg) {
name_parameters_with_cc(ssa, CallingConv::SysV)
}
fn name_parameters_with_cc(ssa: &mut SsaCfg, cc: CallingConv) {
if ssa.blocks.is_empty() {
return;
}
let entry = ssa.entry.0;
if entry >= ssa.blocks.len() {
return;
}
let mut param_idx = 0u32;
let mut named_offsets = std::collections::HashSet::new();
let mut to_name: Vec<(usize, String, u64)> = Vec::new();
let stmts: Vec<Stmt> = ssa.blocks[entry].stmts.clone();
for stmt in &stmts {
if let Stmt::Assign(var_id) = stmt {
let vdef = &ssa.vars[var_id.0 as usize];
if let Expr::Unknown = &vdef.expr {
if vdef.varnode.space == AddressSpaceId::Register
&& arg_reg_offsets().contains(&vdef.varnode.offset)
&& !named_offsets.contains(&vdef.varnode.offset)
{
to_name.push((
var_id.0 as usize,
format!("param_{}", param_idx),
vdef.varnode.offset,
));
named_offsets.insert(vdef.varnode.offset);
param_idx += 1;
}
}
}
if let Stmt::Store { val, .. } = stmt {
let vdef = &ssa.vars[val.0 as usize];
if vdef.param_name.is_none() {
if let Expr::Unknown = &vdef.expr {
if vdef.varnode.space == AddressSpaceId::Register
&& arg_reg_offsets().contains(&vdef.varnode.offset)
&& !named_offsets.contains(&vdef.varnode.offset)
{
to_name.push((
val.0 as usize,
format!("param_{}", param_idx),
vdef.varnode.offset,
));
named_offsets.insert(vdef.varnode.offset);
param_idx += 1;
}
}
}
}
}
for (v, name, _) in &to_name {
ssa.vars[*v].param_name = Some(name.clone());
}
{
let go_strict = matches!(cc, CallingConv::GoAmd64);
let early_vars: std::collections::HashSet<usize> = if go_strict {
let entry_idx = ssa.entry.0;
let mut early_blocks: std::collections::HashSet<usize> =
std::collections::HashSet::new();
let mut frontier: Vec<usize> = vec![entry_idx];
for _ in 0..3 {
let mut next = Vec::new();
for b in frontier.drain(..) {
if !early_blocks.insert(b) {
continue;
}
if b >= ssa.blocks.len() {
continue;
}
match &ssa.blocks[b].terminator {
SsaTerminator::Branch(t) | SsaTerminator::Fallthrough(t) => next.push(t.0),
SsaTerminator::CBranch {
taken, fallthrough, ..
} => {
next.push(taken.0);
next.push(fallthrough.0);
}
_ => {}
}
}
frontier = next;
}
let mut s: std::collections::HashSet<usize> = std::collections::HashSet::new();
for &b in &early_blocks {
if b < ssa.blocks.len() {
for stmt in &ssa.blocks[b].stmts {
if let Stmt::Assign(vid) = stmt {
s.insert(vid.0 as usize);
}
}
}
}
s
} else {
std::collections::HashSet::new()
};
let mut to_name: Vec<(usize, String)> = Vec::new();
for &offset in arg_reg_offsets().iter() {
if named_offsets.contains(&offset) {
continue;
}
let mut found: Option<usize> = None;
for v in 0..ssa.vars.len() {
let vdef = &ssa.vars[v];
if vdef.varnode.space != AddressSpaceId::Register {
continue;
}
if vdef.varnode.offset != offset {
continue;
}
if vdef.param_name.is_some() {
continue;
}
if !matches!(&vdef.expr, Expr::Unknown | Expr::Phi(_)) {
continue;
}
if go_strict && !early_vars.contains(&v) {
continue;
}
found = Some(v);
break;
}
if let Some(v) = found {
to_name.push((v, format!("param_{}", param_idx)));
named_offsets.insert(offset);
param_idx += 1;
} else if go_strict {
break;
}
}
for (v, name) in to_name {
ssa.vars[v].param_name = Some(name);
}
}
if arg_reg_offsets().is_empty() && param_idx == 0 {
const EBP_OFFSET_32: u64 = 20;
const RBP_OFFSET_64: u64 = 40;
let mut ebp_params: std::collections::BTreeMap<u64, Vec<usize>> =
std::collections::BTreeMap::new();
for v in 0..ssa.vars.len() {
let vdef = &ssa.vars[v];
if vdef.param_name.is_some() {
continue;
}
if let Expr::Load(ptr_id) = &vdef.expr {
let ptr = &ssa.vars[ptr_id.0 as usize];
if let Expr::BinOp(BinOpKind::Add, base_id, off_id) = &ptr.expr {
let base = &ssa.vars[base_id.0 as usize];
let off = &ssa.vars[off_id.0 as usize];
if base.varnode.space == AddressSpaceId::Register
&& (base.varnode.offset == EBP_OFFSET_32
|| base.varnode.offset == RBP_OFFSET_64)
{
if let Expr::Const(off_val, _) = &off.expr {
if *off_val >= 8 && *off_val < 0x80 && *off_val % 4 == 0 {
ebp_params.entry(*off_val).or_default().push(v);
}
}
}
}
}
}
for (off_val, var_indices) in &ebp_params {
let pidx = (off_val - 8) / 4;
let name = format!("param_{}", pidx);
for &vi in var_indices {
if ssa.vars[vi].param_name.is_none() {
ssa.vars[vi].param_name = Some(name.clone());
if let Expr::Load(ptr_id) = &ssa.vars[vi].expr {
let ptr_idx = ptr_id.0 as usize;
if ptr_idx < ssa.vars.len() {
}
}
}
}
}
}
if arg_reg_offsets().is_empty() {
const ECX_OFFSET: u64 = 8;
let has_ecx_param = ssa
.vars
.iter()
.any(|v| v.param_name.as_deref() == Some("this"));
if !has_ecx_param {
for v in 0..ssa.vars.len() {
let vdef = &ssa.vars[v];
if vdef.varnode.space == AddressSpaceId::Register
&& vdef.varnode.offset == ECX_OFFSET
&& vdef.varnode.size == 4
&& vdef.param_name.is_none()
&& matches!(&vdef.expr, Expr::Unknown)
&& vdef.use_count > 0
{
ssa.vars[v].param_name = Some("this".to_string());
break;
}
}
}
}
let float_offsets = float_arg_reg_offsets();
if !float_offsets.is_empty() {
let mut fparam_idx = 0u32;
let mut fnamed_offsets = std::collections::HashSet::new();
let stmts: Vec<Stmt> = ssa.blocks[entry].stmts.clone();
for stmt in &stmts {
if let Stmt::Assign(var_id) = stmt {
let idx = var_id.0 as usize;
let is_float_param = {
let vdef = &ssa.vars[idx];
matches!(&vdef.expr, Expr::Unknown)
&& vdef.varnode.space == AddressSpaceId::Register
&& float_offsets.contains(&vdef.varnode.offset)
&& !fnamed_offsets.contains(&vdef.varnode.offset)
};
if is_float_param {
let offset = ssa.vars[idx].varnode.offset;
ssa.vars[idx].param_name = Some(format!("fparam_{}", fparam_idx));
ssa.vars[idx].inferred_type = InferredType::Float;
fnamed_offsets.insert(offset);
fparam_idx += 1;
}
}
}
if fparam_idx == 0 {
for &offset in float_offsets.iter() {
for v in 0..ssa.vars.len() {
let vdef = &ssa.vars[v];
if vdef.varnode.space == AddressSpaceId::Register
&& vdef.varnode.offset == offset
&& vdef.param_name.is_none()
{
if matches!(&vdef.expr, Expr::Unknown | Expr::Phi(_)) {
ssa.vars[v].param_name = Some(format!("fparam_{}", fparam_idx));
ssa.vars[v].inferred_type = InferredType::Float;
fnamed_offsets.insert(offset);
fparam_idx += 1;
break;
}
}
}
}
}
}
}
fn recognize_field_access(ssa: &mut SsaCfg) {
let mut pointer_vars: std::collections::HashSet<VarId> = std::collections::HashSet::new();
for v in &ssa.vars {
if v.param_name.is_some() {
pointer_vars.insert(v.id);
}
if v.inferred_type == InferredType::Pointer {
pointer_vars.insert(v.id);
}
if matches!(&v.expr, Expr::Load(_)) {
pointer_vars.insert(v.id);
}
}
let mut replacements: Vec<(usize, VarId, u64)> = Vec::new();
for v in 0..ssa.vars.len() {
let vdef = &ssa.vars[v];
if let Expr::Load(ptr_id) = &vdef.expr {
let ptr_def = safe_var(&ssa.vars, *ptr_id);
if let Expr::BinOp(BinOpKind::Add, base, offset_var) = &ptr_def.expr {
let offset_def = safe_var(&ssa.vars, *offset_var);
if let Expr::Const(offset_val, _) = &offset_def.expr {
if *offset_val > 0 && *offset_val < 4096 {
let base_def = safe_var(&ssa.vars, *base);
let is_stack_frame = base_def.varnode.space == AddressSpaceId::Register
&& (base_def.varnode.offset == 20 || base_def.varnode.offset == 28)
&& base_def.varnode.size == 4;
if is_stack_frame {
continue;
}
let base_is_pointer = pointer_vars.contains(base)
|| base_def.param_name.is_some()
|| matches!(&base_def.expr, Expr::Load(_) | Expr::FieldAccess(_, _))
|| base_def.inferred_type == InferredType::Pointer;
let base_is_reg_param = base_def.varnode.space == AddressSpaceId::Register
&& (base_def.param_name.is_some()
|| matches!(&base_def.expr, Expr::Var(src) if safe_var(&ssa.vars, *src).param_name.is_some()));
if base_is_pointer || base_is_reg_param {
replacements.push((v, *base, *offset_val));
}
}
}
}
}
}
for (var_idx, base, offset) in replacements {
ssa.vars[var_idx].expr = Expr::FieldAccess(base, offset);
}
}
pub fn apply_signature_names(
ssa: &mut SsaCfg,
import_map: &std::collections::HashMap<u64, String>,
) {
let mut renames: Vec<(VarId, String, InferredType, Option<&'static str>)> = Vec::new();
for block in &ssa.blocks {
let mut process_call = |target: &CallTarget, args: &[VarId]| {
let addr = match target {
CallTarget::Direct(a) => *a,
CallTarget::Indirect(_) => return,
};
let sig = if let Some(name) = import_map.get(&addr) {
crate::signatures::lookup(name)
} else {
None
}
.or_else(|| crate::signatures::lookup_addr(addr));
let Some(sig) = sig else { return };
for (i, arg_id) in args.iter().enumerate() {
if let Some(param) = sig.params.get(i) {
let var = &ssa.vars[arg_id.0 as usize];
let ty = param.ty.to_inferred();
if ty != InferredType::Unknown && var.inferred_type == InferredType::Unknown {
renames.push((*arg_id, String::new(), ty, Some(param.ty.c_str())));
}
}
}
};
for stmt in &block.stmts {
if let Stmt::Call { target, args, .. } = stmt {
process_call(target, args);
}
}
if let SsaTerminator::Call { target, args, .. } = &block.terminator {
process_call(target, args);
}
}
for (var_id, _new_name, new_type, disp_type) in renames {
let var = &mut ssa.vars[var_id.0 as usize];
if new_type != InferredType::Unknown && var.inferred_type == InferredType::Unknown {
var.inferred_type = new_type;
}
if var.display_type.is_none() {
var.display_type = disp_type;
}
}
}
pub fn propagate_signature_return_types(
ssa: &mut SsaCfg,
import_map: &std::collections::HashMap<u64, String>,
) {
let mut type_updates: Vec<(VarId, InferredType, Option<&'static str>)> = Vec::new();
for block in &ssa.blocks {
for stmt in &block.stmts {
if let Stmt::Call {
target,
out: Some(out_id),
..
} = stmt
{
if let CallTarget::Direct(addr) = target {
let sig = import_map
.get(addr)
.and_then(|name| crate::signatures::lookup(name))
.or_else(|| crate::signatures::lookup_addr(*addr));
if let Some(sig) = sig {
let ret_ty = sig.ret.to_inferred();
let disp = sig.ret.c_str();
if ret_ty != InferredType::Unknown {
let var = &ssa.vars[out_id.0 as usize];
if var.inferred_type == InferredType::Unknown {
type_updates.push((*out_id, ret_ty, Some(disp)));
}
}
}
}
}
}
if let SsaTerminator::Call {
target,
fallthrough,
..
} = &block.terminator
{
if let CallTarget::Direct(addr) = target {
let sig = import_map
.get(addr)
.and_then(|name| crate::signatures::lookup(name))
.or_else(|| crate::signatures::lookup_addr(*addr));
if let Some(sig) = sig {
let ret_ty = sig.ret.to_inferred();
let disp = sig.ret.c_str();
if ret_ty != InferredType::Unknown {
let ft_idx = fallthrough.0;
if ft_idx < ssa.blocks.len() {
for stmt in &ssa.blocks[ft_idx].stmts {
if let Stmt::Assign(var_id) = stmt {
let var = &ssa.vars[var_id.0 as usize];
if var.call_return && var.inferred_type == InferredType::Unknown
{
type_updates.push((*var_id, ret_ty, Some(disp)));
break;
}
}
}
}
}
}
}
}
}
for (var_id, ty, disp) in type_updates {
let var = &mut ssa.vars[var_id.0 as usize];
var.inferred_type = ty;
if var.display_type.is_none() {
var.display_type = disp;
}
}
for _round in 0..3 {
let mut propagated = false;
for v in 0..ssa.vars.len() {
if let Expr::Var(src) = &ssa.vars[v].expr {
let src_idx = src.0 as usize;
if ssa.vars[v].inferred_type == InferredType::Unknown {
let src_ty = ssa.vars[src_idx].inferred_type;
if src_ty != InferredType::Unknown {
ssa.vars[v].inferred_type = src_ty;
propagated = true;
}
}
if ssa.vars[v].display_type.is_none() {
if let Some(disp) = ssa.vars[src_idx].display_type {
ssa.vars[v].display_type = Some(disp);
propagated = true;
}
}
}
}
if !propagated {
break;
}
}
for v in 0..ssa.vars.len() {
let disp = ssa.vars[v].display_type;
let Some(disp) = disp else { continue };
if let Expr::Load(ptr_id) = &ssa.vars[v].expr {
let ptr_idx = ptr_id.0 as usize;
if ptr_idx < ssa.vars.len() && ssa.vars[ptr_idx].display_type.is_none() {
if ssa.vars[ptr_idx].param_name.is_some() {
ssa.vars[ptr_idx].display_type = Some(disp);
if ssa.vars[ptr_idx].inferred_type == InferredType::Unknown {
ssa.vars[ptr_idx].inferred_type = ssa.vars[v].inferred_type;
}
}
}
}
}
}
fn name_loop_phis(ssa: &mut SsaCfg) {
let mut loop_phi_count = 0u32;
for vi in 0..ssa.vars.len() {
if ssa.vars[vi].param_name.is_some() {
continue;
}
if let Expr::Phi(ref inputs) = ssa.vars[vi].expr {
if inputs.len() < 2 {
continue;
}
let phi_id = VarId(vi as u32);
let is_self_ref = inputs
.iter()
.any(|input| refs_varid(*input, phi_id, &ssa.vars, 6));
if is_self_ref {
let vn = ssa.vars[vi].varnode;
let prefix = if vn.size <= 4 { "i" } else { "l" };
loop_phi_count += 1;
ssa.vars[vi].param_name = Some(format!("{}Var{}", prefix, loop_phi_count));
}
}
}
}
fn refs_varid(id: VarId, target: VarId, vars: &[VarDef], depth: u32) -> bool {
if depth == 0 {
return false;
}
if id == target {
return true;
}
let vdef = &vars[id.0 as usize];
match &vdef.expr {
Expr::Var(inner) => refs_varid(*inner, target, vars, depth - 1),
Expr::BinOp(_, l, r) => {
refs_varid(*l, target, vars, depth - 1) || refs_varid(*r, target, vars, depth - 1)
}
Expr::UnaryOp(_, i) => refs_varid(*i, target, vars, depth - 1),
Expr::Phi(inputs) => inputs
.iter()
.any(|i| refs_varid(*i, target, vars, depth - 1)),
_ => false,
}
}
use crate::dominators::compute_dominators;
pub fn rewrite_conditional_phi_to_ternary(ssa: &mut SsaCfg, cfg: &Cfg) {
if cfg.blocks.is_empty() {
return;
}
let dom = compute_dominators(cfg);
let preds = cfg.predecessors();
let n = cfg.blocks.len();
let mut is_back_target = vec![false; n];
for block in &cfg.blocks {
for succ in cfg.successors(block.id) {
if phi_dom_dominates(&dom, succ.0, block.id.0) {
is_back_target[succ.0] = true;
}
}
}
for merge_bid in 0..ssa.blocks.len() {
if merge_bid >= n {
break;
}
if is_back_target[merge_bid] {
continue;
}
let pred_list = match preds.get(merge_bid) {
Some(p) if p.len() >= 2 => p.clone(),
_ => continue,
};
let phi_stmts: Vec<(VarId, Vec<VarId>)> = ssa.blocks[merge_bid]
.stmts
.iter()
.filter_map(|s| match s {
Stmt::Assign(v) => match &ssa.vars[v.0 as usize].expr {
Expr::Phi(inputs) if inputs.len() == pred_list.len() => {
Some((*v, inputs.clone()))
}
_ => None,
},
_ => None,
})
.collect();
for (phi_v, inputs) in phi_stmts {
let first_leaf = phi_resolve_var_chain(inputs[0], &ssa.vars, 8);
let first_vn = ssa.vars[first_leaf.0 as usize].varnode;
let all_same_render = inputs.iter().all(|&inp| {
let leaf = phi_resolve_var_chain(inp, &ssa.vars, 8);
leaf == first_leaf || ssa.vars[leaf.0 as usize].varnode == first_vn
});
if all_same_render {
ssa.vars[phi_v.0 as usize].expr = Expr::Var(inputs[0]);
continue;
}
let mut groups: Vec<(VarId, Vec<BlockId>)> = Vec::new();
for (i, &p) in pred_list.iter().enumerate() {
let input = inputs[i];
if let Some(g) = groups.iter_mut().find(|(v, _)| *v == input) {
g.1.push(p);
} else {
groups.push((input, vec![p]));
}
}
if groups.len() != 2 {
continue;
}
let (val_a, preds_a) = groups[0].clone();
let (val_b, preds_b) = groups[1].clone();
let all_preds: Vec<BlockId> = preds_a.iter().chain(preds_b.iter()).copied().collect();
let Some(common_dom) = phi_nearest_common_dom(&dom, &all_preds) else {
continue;
};
if common_dom.0 >= ssa.blocks.len() {
continue;
}
let (cond, taken, fallthrough) = match &ssa.blocks[common_dom.0].terminator {
SsaTerminator::CBranch {
cond,
taken,
fallthrough,
} => (*cond, *taken, *fallthrough),
_ => continue,
};
let group_under = |ps: &[BlockId], arm: BlockId| -> bool {
ps.iter().all(|p| phi_dom_dominates(&dom, arm.0, p.0))
};
let (then_val, else_val) =
if group_under(&preds_a, taken) && group_under(&preds_b, fallthrough) {
(val_a, val_b)
} else if group_under(&preds_a, fallthrough) && group_under(&preds_b, taken) {
(val_b, val_a)
} else {
continue;
};
let t_leaf = phi_resolve_var_chain(then_val, &ssa.vars, 8);
let e_leaf = phi_resolve_var_chain(else_val, &ssa.vars, 8);
let same_leaf = t_leaf == e_leaf;
let same_location = {
let t_vn = ssa.vars[t_leaf.0 as usize].varnode;
let e_vn = ssa.vars[e_leaf.0 as usize].varnode;
t_vn == e_vn
};
if same_leaf || same_location {
ssa.vars[phi_v.0 as usize].expr = Expr::Var(then_val);
} else {
ssa.vars[phi_v.0 as usize].expr = Expr::Ternary(cond, then_val, else_val);
}
}
}
}
fn phi_resolve_var_chain(id: VarId, vars: &[VarDef], depth: u32) -> VarId {
if depth == 0 {
return id;
}
match &vars[id.0 as usize].expr {
Expr::Var(inner) => phi_resolve_var_chain(*inner, vars, depth - 1),
_ => id,
}
}
fn phi_dom_dominates(dom: &[BlockId], a: usize, b: usize) -> bool {
if a == b {
return true;
}
if a >= dom.len() || b >= dom.len() {
return false;
}
let mut cur = b;
for _ in 0..dom.len() {
let d = dom[cur].0;
if d == a {
return true;
}
if d == cur {
return false;
} cur = d;
}
false
}
fn phi_nearest_common_dom(dom: &[BlockId], blocks: &[BlockId]) -> Option<BlockId> {
if blocks.is_empty() {
return None;
}
let mut cd = blocks[0];
for &b in &blocks[1..] {
cd = phi_common_dom_pair(dom, cd, b)?;
}
Some(cd)
}
fn phi_common_dom_pair(dom: &[BlockId], a: BlockId, b: BlockId) -> Option<BlockId> {
let mut chain_a: std::collections::HashSet<BlockId> = Default::default();
let mut cur = a;
for _ in 0..dom.len() {
chain_a.insert(cur);
if cur.0 >= dom.len() {
break;
}
let d = dom[cur.0];
if d == cur {
break;
}
cur = d;
}
let mut cur = b;
for _ in 0..dom.len() {
if chain_a.contains(&cur) {
return Some(cur);
}
if cur.0 >= dom.len() {
return None;
}
let d = dom[cur.0];
if d == cur {
return if chain_a.contains(&cur) {
Some(cur)
} else {
None
};
}
cur = d;
}
None
}