use std::collections::HashMap;
use crate::arch::bpf::{BpfVariant, InsnKind, call_target, decode};
use crate::common::VAddr;
use crate::format::elf::{EM_BPF, EM_SBF, Elf64File, SHF_EXECINSTR};
use crate::analysis::function_map::{Function, FunctionSource};
#[derive(Debug, thiserror::Error)]
pub enum CallSiteError {
#[error(transparent)]
BpfDecode(crate::arch::bpf::Error),
}
#[allow(clippy::missing_errors_doc, clippy::implicit_hasher)]
pub fn discover_from_bpf_call_sites(
elf: &Elf64File,
syscall_calls: &HashMap<u64, String>,
) -> Result<Vec<Function>, CallSiteError> {
if !matches!(elf.ehdr.e_machine, EM_BPF | EM_SBF) {
return Ok(Vec::new());
}
let variant = if elf.ehdr.e_machine == EM_BPF {
BpfVariant::Linux
} else {
BpfVariant::Sbfv1
};
let exec_ranges: Vec<(u64, u64)> = elf
.sections()
.filter(|(_, sh, _)| sh.sh_flags & SHF_EXECINSTR != 0 && sh.sh_size > 0)
.map(|(_, sh, _)| (sh.sh_addr, sh.sh_addr.saturating_add(sh.sh_size)))
.collect();
let mut targets: std::collections::BTreeSet<u64> = std::collections::BTreeSet::new();
for (_, sh, data) in elf.sections() {
if sh.sh_flags & SHF_EXECINSTR == 0 || sh.sh_size == 0 {
continue;
}
if data.len() % crate::arch::bpf::INSN_SIZE != 0 {
continue;
}
let insns = decode(data, sh.sh_addr, variant).map_err(CallSiteError::BpfDecode)?;
for insn in &insns {
if insn.kind != InsnKind::Call {
continue;
}
if syscall_calls.contains_key(&insn.addr.0) {
continue;
}
let target = call_target(insn);
let in_text = exec_ranges
.iter()
.any(|&(start, end)| (start..end).contains(&target));
if !in_text {
continue;
}
if !target.is_multiple_of(crate::arch::bpf::INSN_SIZE as u64) {
continue;
}
targets.insert(target);
}
}
Ok(targets
.into_iter()
.map(|addr| Function {
addr: VAddr(addr),
size: 0,
name: format!("sub_{addr:x}"),
sources: vec![FunctionSource::CallSite],
})
.collect())
}