#[cfg(feature = "breakpad")]
use std::fs::File;
use std::ops::ControlFlow;
use std::ops::Deref as _;
#[cfg(feature = "breakpad")]
use std::path::Path;
use std::path::PathBuf;
#[cfg(feature = "breakpad")]
use crate::breakpad::BreakpadResolver;
use crate::elf::ElfResolverData;
use crate::elf::DEFAULT_DEBUG_DIRS;
use crate::file_cache::FileCache;
use crate::inspect::ForEachFn;
use crate::util::OnceCellExt as _;
use crate::Result;
#[cfg(feature = "breakpad")]
use super::source::Breakpad;
use super::source::Elf;
use super::source::Source;
use super::FindAddrOpts;
use super::Inspect;
use super::SymInfo;
use super::SymType;
#[derive(Debug)]
pub struct Inspector {
#[cfg(feature = "breakpad")]
breakpad_cache: FileCache<Box<BreakpadResolver>>,
elf_cache: FileCache<ElfResolverData>,
}
impl Inspector {
pub fn new() -> Self {
Self {
#[cfg(feature = "breakpad")]
breakpad_cache: FileCache::builder().enable_auto_reload(true).build(),
elf_cache: FileCache::builder().enable_auto_reload(true).build(),
}
}
#[cfg(feature = "breakpad")]
fn create_breakpad_resolver(&self, path: &Path, file: &File) -> Result<Box<BreakpadResolver>> {
let resolver = BreakpadResolver::from_file(path.to_path_buf(), file)?;
Ok(Box::new(resolver))
}
#[cfg(feature = "breakpad")]
fn breakpad_resolver<'slf>(&'slf self, path: &Path) -> Result<&'slf BreakpadResolver> {
let (file, cell) = self.breakpad_cache.entry(path)?;
let resolver = cell.get_or_try_init_(|| self.create_breakpad_resolver(path, file))?;
Ok(resolver)
}
#[cfg_attr(feature = "tracing", crate::log::instrument(skip_all, fields(src = ?src, names = ?names), err))]
pub fn lookup<'slf>(
&'slf self,
src: &Source,
names: &[&str],
) -> Result<Vec<Vec<SymInfo<'slf>>>> {
let opts = FindAddrOpts {
file_offset: true,
sym_type: SymType::Undefined,
};
let resolver = match src {
#[cfg(feature = "breakpad")]
Source::Breakpad(Breakpad {
path,
_non_exhaustive: (),
}) => {
let resolver = self.breakpad_resolver(path)?;
resolver as &dyn Inspect
}
Source::Elf(Elf {
path,
debug_syms,
_non_exhaustive: (),
}) => {
let debug_dirs;
let resolver = self.elf_cache.elf_resolver(
path,
if *debug_syms {
debug_dirs = DEFAULT_DEBUG_DIRS
.iter()
.map(PathBuf::from)
.collect::<Vec<_>>();
Some(debug_dirs.as_slice())
} else {
None
},
)?;
resolver.deref() as &dyn Inspect
}
};
let syms = names
.iter()
.map(|name| {
resolver.find_addr(name, &opts).map(|syms| {
syms.into_iter().map(|sym| sym.to_owned()).collect()
})
})
.collect::<Result<Vec<_>>>()?;
Ok(syms)
}
#[cfg_attr(feature = "tracing", crate::log::instrument(skip_all, fields(src = ?src), err))]
pub fn for_each<F>(&self, src: &Source, mut f: F) -> Result<()>
where
F: FnMut(&SymInfo<'_>) -> ControlFlow<()>,
{
fn for_each_impl(slf: &Inspector, src: &Source, f: &mut ForEachFn<'_>) -> Result<()> {
let (resolver, opts) = match src {
#[cfg(feature = "breakpad")]
Source::Breakpad(Breakpad {
path,
_non_exhaustive: (),
}) => {
let opts = FindAddrOpts {
file_offset: false,
sym_type: SymType::Undefined,
};
let resolver = slf.breakpad_resolver(path)?;
(resolver as &dyn Inspect, opts)
}
Source::Elf(Elf {
path,
debug_syms,
_non_exhaustive: (),
}) => {
let opts = FindAddrOpts {
file_offset: true,
sym_type: SymType::Undefined,
};
let debug_dirs;
let resolver = slf.elf_cache.elf_resolver(
path,
if *debug_syms {
debug_dirs = DEFAULT_DEBUG_DIRS
.iter()
.map(PathBuf::from)
.collect::<Vec<_>>();
Some(debug_dirs.as_slice())
} else {
None
},
)?;
(resolver.deref() as &dyn Inspect, opts)
}
};
resolver.for_each(&opts, f)
}
for_each_impl(self, src, &mut f)
}
}
impl Default for Inspector {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(not(feature = "breakpad"))]
use std::path::Path;
use std::rc::Rc;
use crate::ErrorKind;
#[test]
fn debug_repr() {
let inspector = Inspector::default();
assert_ne!(format!("{inspector:?}"), "");
}
#[test]
fn non_present_file() {
fn test(src: &Source) {
let inspector = Inspector::new();
let err = inspector.lookup(src, &["factorial"]).unwrap_err();
assert_eq!(err.kind(), ErrorKind::NotFound);
}
let file = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("data")
.join("does-not-exist");
let src = Source::Elf(Elf::new(&file));
let () = test(&src);
let mut elf = Elf::new(file);
elf.debug_syms = !elf.debug_syms;
let src = Source::Elf(elf);
let () = test(&src);
}
#[test]
fn elf_resolver_caching() {
let test_elf = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("data")
.join("test-stable-addrs-no-dwarf.bin");
let mut elf = Elf::new(&test_elf);
assert!(elf.debug_syms);
let inspector = Inspector::new();
let data = || {
inspector
.elf_cache
.entry(&test_elf)
.unwrap()
.1
.get()
.unwrap()
.clone()
};
let _results = inspector.lookup(&Source::Elf(elf.clone()), &["factorial"]);
let data1 = data();
let _results = inspector.lookup(&Source::Elf(elf.clone()), &["factorial"]);
let data2 = data();
assert!(Rc::ptr_eq(
data1.dwarf.get().unwrap(),
data2.dwarf.get().unwrap()
));
elf.debug_syms = false;
let _results = inspector.lookup(&Source::Elf(elf.clone()), &["factorial"]);
let data3 = data();
assert!(!Rc::ptr_eq(
data1.dwarf.get().unwrap(),
data3.elf.get().unwrap()
));
}
}