use std::cell::OnceCell;
use std::collections::HashMap;
use std::ffi::OsString;
use crate::util::find_lowest_match_by_key;
use crate::util::find_match_or_lower_bound_by_key;
use crate::util::Either;
#[derive(Debug, Eq, PartialEq)]
pub(crate) struct PublicSymbol {
pub addr: u64,
pub name: String,
pub parameter_size: u32,
}
#[derive(Debug, Eq, PartialEq)]
pub(crate) struct SourceLine {
pub addr: u64,
pub size: u32,
pub file: u32,
pub line: u32,
}
#[derive(Debug, PartialEq, Eq)]
pub(crate) struct Inlinee {
pub depth: u32,
pub addr: u64,
pub size: u32,
pub call_file: u32,
pub call_line: u32,
pub origin_id: u32,
}
#[derive(Debug, Eq, PartialEq)]
pub(crate) struct Function {
pub addr: u64,
pub size: u32,
pub parameter_size: u32,
pub name: String,
pub lines: Vec<SourceLine>,
pub inlinees: Vec<Inlinee>,
}
impl Function {
pub(super) fn find_line(&self, addr: u64) -> Option<&SourceLine> {
let idx = find_match_or_lower_bound_by_key(&self.lines, addr, |l| l.addr)?;
for line in &self.lines[idx..] {
if line.addr > addr {
break
}
if (line.addr == addr && line.size == 0)
|| (line.addr <= addr && addr < line.addr + u64::from(line.size))
{
return Some(line)
}
}
None
}
#[cfg(test)]
pub(super) fn get_inlinee_at_depth(
&self,
depth: u32,
addr: u64,
) -> Option<(u32, u32, u64, u32)> {
self.find_inlinee_at_depth(depth, addr).map(|inlinee| {
(
inlinee.call_file,
inlinee.call_line,
inlinee.addr,
inlinee.origin_id,
)
})
}
pub(crate) fn find_inlinee_at_depth(&self, depth: u32, addr: u64) -> Option<&Inlinee> {
let inlinee = match self
.inlinees
.binary_search_by_key(&(depth, addr), |inlinee| (inlinee.depth, inlinee.addr))
{
Ok(index) => &self.inlinees[index],
Err(0) => return None,
Err(index) => &self.inlinees[index - 1],
};
if inlinee.depth != depth {
return None
}
let end_address = inlinee.addr.checked_add(inlinee.size as u64)?;
if addr < end_address {
Some(inlinee)
} else {
None
}
}
pub(crate) fn find_inlinees(&self, addr: u64) -> Vec<&Inlinee> {
let mut inlinees = Vec::new();
while let Some(inlinee) = self.find_inlinee_at_depth(inlinees.len() as _, addr) {
let () = inlinees.push(inlinee);
}
inlinees
}
}
#[derive(Debug)]
pub(crate) struct SymbolFile {
pub module: Option<OsString>,
pub files: HashMap<u32, String>,
pub functions: Vec<Function>,
pub by_name_idx: OnceCell<Box<[usize]>>,
pub inline_origins: HashMap<u32, String>,
}
impl SymbolFile {
pub(crate) fn find_function(&self, addr: u64) -> Option<&Function> {
let idx = find_match_or_lower_bound_by_key(&self.functions, addr, |f| f.addr)?;
for func in &self.functions[idx..] {
if func.addr > addr {
break
}
if (func.addr == addr && func.size == 0)
|| (func.addr <= addr && addr < func.addr + u64::from(func.size))
{
return Some(func)
}
}
None
}
fn create_by_name_idx(funcs: &[Function]) -> Vec<usize> {
let mut by_name_idx = (0..funcs.len()).collect::<Vec<_>>();
let () = by_name_idx.sort_by(|idx1, idx2| {
let sym1 = &funcs[*idx1];
let sym2 = &funcs[*idx2];
sym1.name
.cmp(&sym2.name)
.then_with(|| sym1.addr.cmp(&sym2.addr))
});
by_name_idx
}
pub(crate) fn find_addr<'s, 'slf: 's>(
&'slf self,
name: &'s str,
) -> impl Iterator<Item = &'slf Function> + 's {
let by_name_idx = self.by_name_idx.get_or_init(|| {
let by_name_idx = Self::create_by_name_idx(&self.functions);
let by_name_idx = by_name_idx.into_boxed_slice();
by_name_idx
});
let idx = if let Some(idx) =
find_lowest_match_by_key(by_name_idx, &name, |idx| &self.functions[*idx].name)
{
idx
} else {
return Either::A([].into_iter())
};
let funcs = by_name_idx[idx..].iter().map_while(move |idx| {
let sym = &self.functions[*idx];
if sym.name == name {
Some(sym)
} else {
None
}
});
Either::B(funcs)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn debug_repr() {
let file = SymbolFile {
module: None,
files: HashMap::new(),
functions: Vec::new(),
by_name_idx: OnceCell::new(),
inline_origins: HashMap::new(),
};
assert_ne!(format!("{file:?}"), "");
}
}