use std::collections::BTreeSet;
use anyhow::Result;
use crate::{
analysis::{
cfg,
cfg::{flow::Flow, ChangeBatch, CFG},
dis,
dis::Target,
},
aspace::AddressSpace,
module::Module,
VA,
};
pub fn cfg_mark_noret(module: &Module, cfg: &mut CFG, va: VA) -> Result<BTreeSet<VA>> {
log::debug!("mark noret: {:#x}", va);
let mut ret: BTreeSet<VA> = Default::default();
let mut batch: ChangeBatch = Default::default();
let mut callers: Vec<VA> = Default::default();
let flows_to = cfg
.flows
.flows_by_dst
.get(&va)
.unwrap_or(&Default::default())
.clone()
.into_iter();
for flow in flows_to {
let src = match flow {
Flow::Call(Target::Direct(src)) => src,
Flow::Call(Target::Indirect(src)) => src,
Flow::UnconditionalJump(Target::Direct(src)) => src,
Flow::UnconditionalJump(Target::Indirect(src)) => src,
_ => continue,
};
log::debug!("mark noret: {:#x}: caller: {:#x}", va, src);
callers.push(src);
}
for &src in &callers {
batch.prune_noret_call(src);
}
cfg.commit(batch);
let decoder = dis::get_disassembler(module).expect("invalid disassembler");
for call_insn in callers.into_iter() {
if !cfg.insns.insns_by_address.contains_key(&call_insn) {
continue;
}
let leaf_block = cfg.basic_blocks.blocks_by_last_address[&call_insn];
for head in cfg.get_reaches_to(leaf_block) {
if cfg.flows.flows_by_dst[&head.address]
.iter()
.any(|flow| matches!(flow, Flow::Call(_)))
{
let is_ret = cfg
.get_reaches_from(head.address)
.filter(|block| cfg::empty(cfg::edges(&cfg.flows.flows_by_src[&block.address_of_last_insn])))
.any(|block| {
let mut insn_buf = [0u8; 16];
module
.address_space
.read_into(block.address_of_last_insn, &mut insn_buf)
.unwrap();
let insn = dis::decode(&decoder, &insn_buf)
.expect("invalid instruction")
.expect("missing instruction");
matches!(insn.mnemonic, zydis::Mnemonic::RET)
});
if !is_ret {
log::debug!("noret function: {:#x}", head.address);
ret.insert(head.address);
}
}
}
}
for &caller in ret.clone().iter() {
ret.extend(cfg_mark_noret(module, cfg, caller)?);
}
ret.insert(va);
Ok(ret)
}