use std::collections::HashMap;
use crate::format::elf::{Elf64File, R_BPF_64_32, SHT_DYNSYM, SHT_REL};
const REL_SIZE: usize = 16;
const SYM_SIZE: usize = 24;
#[derive(Debug, thiserror::Error)]
pub enum BpfRelocError {
#[error("`.rel.dyn` size {size} is not a multiple of {entry}")]
BadRelSize { size: usize, entry: usize },
}
#[allow(clippy::missing_errors_doc)]
pub fn build_call_site_names(elf: &Elf64File) -> Result<HashMap<u64, String>, BpfRelocError> {
let mut out: HashMap<u64, String> = HashMap::new();
for (_idx, shdr, data) in elf.sections() {
if shdr.sh_type != SHT_REL {
continue;
}
let dynsym_idx = shdr.sh_link as usize;
let dynsym_shdr = match elf.shdrs.get(dynsym_idx) {
Some(s) if s.sh_type == SHT_DYNSYM => s,
_ => continue,
};
let dynsym_data = elf.section_data(dynsym_idx).unwrap_or(&[]);
let strtab_idx = dynsym_shdr.sh_link as usize;
let Some(strtab) = elf.section_data(strtab_idx) else {
continue;
};
if data.len() % REL_SIZE != 0 {
return Err(BpfRelocError::BadRelSize {
size: data.len(),
entry: REL_SIZE,
});
}
for chunk in data.as_chunks::<REL_SIZE>().0 {
let r_offset = u64::from_le_bytes(chunk[0..8].try_into().unwrap());
let r_info = u64::from_le_bytes(chunk[8..16].try_into().unwrap());
#[allow(clippy::cast_possible_truncation)]
let r_type = r_info as u32;
let sym_idx = (r_info >> 32) as usize;
if r_type != R_BPF_64_32 {
continue;
}
let Some(name) = read_dynsym_name(dynsym_data, sym_idx, strtab) else {
continue;
};
out.insert(r_offset, name);
}
}
Ok(out)
}
fn read_dynsym_name(dynsym_data: &[u8], idx: usize, strtab: &[u8]) -> Option<String> {
let off = idx.checked_mul(SYM_SIZE)?;
let chunk = dynsym_data.get(off..off + SYM_SIZE)?;
let st_name = u32::from_le_bytes(chunk[0..4].try_into().unwrap()) as usize;
let tail = strtab.get(st_name..)?;
let nul = tail.iter().position(|&b| b == 0)?;
let s = std::str::from_utf8(&tail[..nul]).ok()?;
if s.is_empty() {
None
} else {
Some(s.to_string())
}
}