use std::collections::HashMap;
use crate::arch::bpf::{DecodedInsn, InsnKind};
use crate::ir::Function;
use crate::ir::ssa::{DefSite, SsaInfo, Var};
use super::data_lookup::DataLookup;
const MAX_RESOLVE_DEPTH: usize = 8;
pub fn resolve_arg(
ssa: &SsaInfo,
insns_by_addr: &HashMap<u64, &DecodedInsn>,
call_ip: u64,
slot: usize,
data: Option<&dyn DataLookup>,
) -> Option<String> {
let reg = Var::Reg(format!("r{}", slot + 1));
let def = ssa.def_reaching(call_ip, ®)?;
resolve_def(ssa, insns_by_addr, def, data, 0)
}
fn resolve_def(
ssa: &SsaInfo,
insns_by_addr: &HashMap<u64, &DecodedInsn>,
def: crate::ir::ssa::DefId,
data: Option<&dyn DataLookup>,
depth: usize,
) -> Option<String> {
if depth >= MAX_RESOLVE_DEPTH {
return None;
}
let record = ssa.defs.get(def.0 as usize)?;
match &record.site {
DefSite::Insn(ip) => {
let insn = *insns_by_addr.get(ip)?;
resolve_insn(ssa, insns_by_addr, insn, &record.var, data, depth + 1)
}
DefSite::Phi { incoming, .. } => {
resolve_phi_consensus(ssa, insns_by_addr, incoming, data, depth)
}
DefSite::Entry => {
match &record.var {
Var::Reg(r) => {
let n: u8 = r.strip_prefix('r')?.parse().ok()?;
if (1..=5).contains(&n) {
Some(format!("arg_{}", n - 1))
} else if n == 10 {
Some("r10".into())
} else {
None
}
}
Var::Stack(off) => Some(format_stack_ref(*off, false)),
Var::Memory => None,
}
}
}
}
fn resolve_phi_consensus(
ssa: &SsaInfo,
insns_by_addr: &HashMap<u64, &DecodedInsn>,
incoming: &[crate::ir::ssa::DefId],
data: Option<&dyn DataLookup>,
depth: usize,
) -> Option<String> {
let mut consensus: Option<String> = None;
for &inc in incoming {
let record = ssa.defs.get(inc.0 as usize)?;
if matches!(record.site, DefSite::Phi { .. }) {
continue;
}
let resolved = resolve_def(ssa, insns_by_addr, inc, data, depth + 1)?;
match &consensus {
None => consensus = Some(resolved),
Some(prev) if *prev == resolved => {}
Some(_) => return None,
}
}
consensus
}
fn resolve_insn(
ssa: &SsaInfo,
insns_by_addr: &HashMap<u64, &DecodedInsn>,
insn: &DecodedInsn,
_target_var: &Var,
data: Option<&dyn DataLookup>,
depth: usize,
) -> Option<String> {
match insn.kind {
InsnKind::Lddw => {
let imm = insn.imm64?;
if let Some(d) = data
&& let Some(s) = super::bpf::read_inline_string(d, imm)
{
return Some(s);
}
Some(format!("0x{imm:x}"))
}
InsnKind::Alu32 | InsnKind::Alu64 => {
let op_nibble = (insn.opcode >> 4) & 0xf;
let is_reg_src = (insn.opcode & 0x08) != 0;
match op_nibble {
0xb => {
if is_reg_src {
let src_reg = Var::Reg(format!("r{}", insn.src));
let src_def = ssa.def_reaching(insn.addr.0, &src_reg)?;
resolve_def(ssa, insns_by_addr, src_def, data, depth)
} else {
#[allow(clippy::cast_sign_loss)]
Some(format!("0x{:x}", insn.imm as u32))
}
}
0x0 if !is_reg_src => {
let dst_reg = Var::Reg(format!("r{}", insn.dst));
let prior = ssa.def_reaching(insn.addr.0, &dst_reg)?;
let prior_val = resolve_def(ssa, insns_by_addr, prior, data, depth)?;
fold_stack_add(&prior_val, insn.imm)
}
_ => None,
}
}
InsnKind::Load => {
if insn.src == 10 {
Some(format_stack_ref(i64::from(insn.offset), false))
} else {
None
}
}
_ => None,
}
}
fn format_stack_ref(offset: i64, take_addr: bool) -> String {
let prefix = if take_addr { "&" } else { "[" };
let suffix = if take_addr { "" } else { "]" };
if offset >= 0 {
format!("{prefix}arg_{offset:x}{suffix}")
} else {
format!("{prefix}local_{:x}{suffix}", -offset)
}
}
fn fold_stack_add(base: &str, delta: i32) -> Option<String> {
if base == "r10" {
if delta < 0 {
return Some(format!("&local_{:x}", delta.unsigned_abs()));
}
return Some(format!("&arg_{delta:x}"));
}
if let Some(rest) = base.strip_prefix("&local_") {
let cur = i64::from_str_radix(rest, 16).ok()?;
let new = -cur + i64::from(delta);
if new < 0 {
return Some(format!("&local_{:x}", new.unsigned_abs()));
}
return Some(format!("&arg_{new:x}"));
}
if let Some(rest) = base.strip_prefix("&arg_") {
let cur = i64::from_str_radix(rest, 16).ok()?;
let new = cur + i64::from(delta);
if new < 0 {
return Some(format!("&local_{:x}", new.unsigned_abs()));
}
return Some(format!("&arg_{new:x}"));
}
None
}
#[must_use]
pub fn index_by_addr(f: &Function<DecodedInsn>) -> HashMap<u64, &DecodedInsn> {
let mut out = HashMap::new();
for block in &f.blocks {
for insn in &block.insns {
out.insert(insn.addr.0, insn);
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::arch::bpf::{BpfVariant, decode};
use crate::common::VAddr;
use crate::ir::{BasicBlock, Terminator};
fn lift_linear(bytes: &[u8]) -> Function<DecodedInsn> {
let insns = decode(bytes, 0x1000, BpfVariant::Sbfv1).expect("decode");
let term = match insns.last().map(|i| i.kind) {
Some(InsnKind::Exit) => Terminator::Return,
_ => Terminator::Fallthrough,
};
Function {
addr: VAddr(0x1000),
name: "test".into(),
blocks: vec![BasicBlock {
addr: VAddr(0x1000),
insns,
terminator: term,
}],
}
}
#[test]
fn mov_imm_resolves_at_following_call() {
let bytes = [
0xb7, 0x01, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x85, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
let f = lift_linear(&bytes);
let ssa = super::super::bpf_ssa::build_bpf_ssa(&f);
let ix = index_by_addr(&f);
let arg = resolve_arg(&ssa, &ix, 0x1008, 0, None);
assert_eq!(arg, Some("0x2a".into()));
}
#[test]
fn mov_chain_resolves_through_two_hops() {
let bytes = [
0xb7, 0x02, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xbf, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x85, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
let f = lift_linear(&bytes);
let ssa = super::super::bpf_ssa::build_bpf_ssa(&f);
let ix = index_by_addr(&f);
let arg = resolve_arg(&ssa, &ix, 0x1010, 0, None);
assert_eq!(arg, Some("0x7".into()));
}
#[test]
fn ldxdw_r10_stack_slot_resolves() {
let bytes = [
0x79, 0xa1, 0xf8, 0xff, 0x00, 0x00, 0x00, 0x00, 0x85, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
let f = lift_linear(&bytes);
let ssa = super::super::bpf_ssa::build_bpf_ssa(&f);
let ix = index_by_addr(&f);
let arg = resolve_arg(&ssa, &ix, 0x1008, 0, None);
assert_eq!(arg, Some("[local_8]".into()));
}
#[test]
fn r10_arith_resolves_to_pointer_to_local() {
let bytes = [
0xbf, 0xa1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x01, 0x00, 0x00, 0xb0, 0xff, 0xff, 0xff, 0x85, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
let f = lift_linear(&bytes);
let ssa = super::super::bpf_ssa::build_bpf_ssa(&f);
let ix = index_by_addr(&f);
let arg = resolve_arg(&ssa, &ix, 0x1010, 0, None);
assert_eq!(arg, Some("&local_50".into()));
}
#[test]
fn phi_with_unanimous_incoming_commits_to_value() {
let bytes = [
0x15, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb7, 0x01, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb7, 0x01, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x85, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
let insns = decode(&bytes, 0x1000, BpfVariant::Sbfv1).expect("decode");
let block_entry = BasicBlock {
addr: VAddr(0x1000),
insns: vec![insns[0].clone()],
terminator: Terminator::ConditionalBranch {
taken: VAddr(0x1018),
fallthrough: VAddr(0x1008),
},
};
let block_arm1 = BasicBlock {
addr: VAddr(0x1008),
insns: vec![insns[1].clone(), insns[2].clone()],
terminator: Terminator::UnconditionalBranch {
target: VAddr(0x1020),
},
};
let block_arm2 = BasicBlock {
addr: VAddr(0x1018),
insns: vec![insns[3].clone()],
terminator: Terminator::Fallthrough,
};
let block_merge = BasicBlock {
addr: VAddr(0x1020),
insns: vec![insns[4].clone(), insns[5].clone()],
terminator: Terminator::Return,
};
let f = Function {
addr: VAddr(0x1000),
name: "diamond".into(),
blocks: vec![block_entry, block_arm1, block_arm2, block_merge],
};
let ssa = super::super::bpf_ssa::build_bpf_ssa(&f);
let ix = index_by_addr(&f);
let arg = resolve_arg(&ssa, &ix, 0x1020, 0, None);
assert_eq!(arg, Some("0x2a".into()));
}
#[test]
fn phi_with_divergent_incoming_does_not_commit() {
let bytes = [
0x15, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb7, 0x01, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb7, 0x01, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x85, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
let insns = decode(&bytes, 0x1000, BpfVariant::Sbfv1).expect("decode");
let f = Function {
addr: VAddr(0x1000),
name: "diamond".into(),
blocks: vec![
BasicBlock {
addr: VAddr(0x1000),
insns: vec![insns[0].clone()],
terminator: Terminator::ConditionalBranch {
taken: VAddr(0x1018),
fallthrough: VAddr(0x1008),
},
},
BasicBlock {
addr: VAddr(0x1008),
insns: vec![insns[1].clone(), insns[2].clone()],
terminator: Terminator::UnconditionalBranch {
target: VAddr(0x1020),
},
},
BasicBlock {
addr: VAddr(0x1018),
insns: vec![insns[3].clone()],
terminator: Terminator::Fallthrough,
},
BasicBlock {
addr: VAddr(0x1020),
insns: vec![insns[4].clone(), insns[5].clone()],
terminator: Terminator::Return,
},
],
};
let ssa = super::super::bpf_ssa::build_bpf_ssa(&f);
let ix = index_by_addr(&f);
let arg = resolve_arg(&ssa, &ix, 0x1020, 0, None);
assert_eq!(arg, None);
}
#[test]
fn entry_register_resolves_to_arg_name() {
let bytes = [
0x85, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ];
let f = lift_linear(&bytes);
let ssa = super::super::bpf_ssa::build_bpf_ssa(&f);
let ix = index_by_addr(&f);
let arg = resolve_arg(&ssa, &ix, 0x1000, 0, None);
assert_eq!(arg, Some("arg_0".into()));
}
}