#[derive(Debug, Clone, Copy)]
struct ParamCounts {
gpr: u32,
fp: u32,
}
impl ParamCounts {
fn total(self) -> u32 {
self.gpr + self.fp
}
}
type AnalysisResult = (
CppBodyKind,
CppReturnChannel,
bool,
Option<i64>,
Option<ParamCounts>,
Option<FunctionCfg>,
String,
);
#[derive(Debug)]
struct BasicBlock {
start: usize,
end: usize,
reachable: bool,
}
struct FunctionCfg {
insns: Vec<Insn>,
blocks: Vec<BasicBlock>,
entry_va: u64,
decode_gaps: Vec<macho_insn::DecodeGap>,
}
impl FunctionCfg {
fn insn_va(&self, insn: &Insn) -> u64 {
self.entry_va + insn.offset as u64
}
}
impl FunctionCfg {
fn build(bytes: &[u8], entry_va: u64, arch: Arch) -> Self {
let report = macho_insn::decode_lossy(bytes, entry_va, arch);
let insns: Vec<_> = report.instructions.into_iter().take(MAX_INSNS).collect();
let mut vas = Vec::new();
for insn in &insns {
vas.push(entry_va + insn.offset as u64);
}
if insns.is_empty() {
return Self {
insns,
blocks: Vec::new(),
entry_va,
decode_gaps: report.gaps,
};
}
let mut block_starts: BTreeSet<u64> = BTreeSet::new();
block_starts.insert(entry_va);
for (i, insn) in insns.iter().enumerate() {
let insn_va = vas[i];
let next_va = insn_va + insn.len as u64;
match &insn.kind {
InsnKind::Branch(bi) | InsnKind::Call(bi) | InsnKind::CondBranch(bi) => {
block_starts.insert(next_va);
if let BranchTarget::Direct(offset) = bi.target {
let target = insn_va.wrapping_add_signed(offset);
if target >= entry_va && target < entry_va + bytes.len() as u64 {
block_starts.insert(target);
}
}
}
InsnKind::Return => {
block_starts.insert(next_va);
}
_ => {}
}
}
let va_to_idx = |target_va: u64| -> Option<usize> { vas.binary_search(&target_va).ok() };
let mut blocks: Vec<BasicBlock> = Vec::new();
let mut current_start = 0usize;
for (i, va) in vas.iter().enumerate().take(insns.len()).skip(1) {
if block_starts.contains(va) {
blocks.push(BasicBlock {
start: current_start,
end: i,
reachable: false,
});
current_start = i;
}
}
blocks.push(BasicBlock {
start: current_start,
end: insns.len(),
reachable: false,
});
if !blocks.is_empty() {
let mut worklist = vec![0usize]; blocks[0].reachable = true;
while let Some(bi) = worklist.pop() {
let block = &blocks[bi];
if block.start >= block.end {
continue; }
let last_idx = block.end - 1;
let last_insn = &insns[last_idx];
let last_va = vas[last_idx];
let fallthrough_va = last_va + last_insn.len as u64;
let mut successor_vas = Vec::new();
match &last_insn.kind {
InsnKind::Return => {
}
InsnKind::Branch(bi_info) => {
if let BranchTarget::Direct(offset) = bi_info.target {
successor_vas.push(last_va.wrapping_add_signed(offset));
}
}
InsnKind::Call(_) => {
successor_vas.push(fallthrough_va);
}
InsnKind::CondBranch(bi_info) => {
successor_vas.push(fallthrough_va);
if let BranchTarget::Direct(offset) = bi_info.target {
successor_vas.push(last_va.wrapping_add_signed(offset));
}
}
_ => {
successor_vas.push(fallthrough_va);
}
}
for succ_va in successor_vas {
if let Some(insn_idx) = va_to_idx(succ_va) {
if let Ok(block_idx) = blocks.binary_search_by(|b| {
if insn_idx < b.start {
std::cmp::Ordering::Greater
} else if insn_idx >= b.end {
std::cmp::Ordering::Less
} else {
std::cmp::Ordering::Equal
}
}) {
if !blocks[block_idx].reachable {
blocks[block_idx].reachable = true;
worklist.push(block_idx);
}
}
}
}
}
}
Self {
insns,
blocks,
entry_va,
decode_gaps: report.gaps,
}
}
fn ret_positions(&self) -> Vec<usize> {
self.blocks
.iter()
.filter(|b| b.reachable)
.flat_map(|b| b.start..b.end)
.filter(|&i| matches!(self.insns[i].kind, InsnKind::Return))
.collect()
}
}