use std::borrow::Cow;
use std::cell::OnceCell;
use std::cell::RefCell;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::fmt::Debug;
use std::fmt::Formatter;
use std::fmt::Result as FmtResult;
use std::fs::File;
use std::mem::take;
use std::ops::Deref as _;
use std::ops::Range;
use std::path::Path;
use std::path::PathBuf;
use std::rc::Rc;
#[cfg(feature = "apk")]
use crate::apk::create_apk_elf_path;
#[cfg(feature = "breakpad")]
use crate::breakpad::BreakpadResolver;
use crate::elf::ElfParser;
use crate::elf::ElfResolver;
use crate::elf::ElfResolverData;
use crate::elf::StaticMem;
#[cfg(feature = "dwarf")]
use crate::elf::DEFAULT_DEBUG_DIRS;
use crate::file_cache::FileCache;
#[cfg(feature = "gsym")]
use crate::gsym::GsymResolver;
use crate::insert_map::InsertMap;
use crate::kernel::KernelCache;
use crate::kernel::KernelResolver;
use crate::log;
use crate::maps;
use crate::maps::EntryPath;
use crate::maps::MapsEntry;
use crate::maps::PathName;
use crate::mmap::Mmap;
use crate::normalize;
use crate::normalize::normalize_sorted_user_addrs_with_entries;
use crate::normalize::Handler as _;
#[cfg(feature = "apk")]
use crate::pathlike::PathLike;
use crate::perf_map::PerfMap;
use crate::symbolize::Resolve;
use crate::symbolize::TranslateFileOffset;
use crate::util;
use crate::util::Dbg;
#[cfg(feature = "tracing")]
use crate::util::Hexify;
use crate::util::OnceCellExt as _;
use crate::vdso::create_vdso_parser;
use crate::vdso::VDSO_MAPS_COMPONENT;
#[cfg(feature = "apk")]
use crate::zip;
use crate::Addr;
use crate::Error;
use crate::ErrorExt as _;
use crate::ErrorKind;
use crate::IntoError as _;
use crate::Pid;
use crate::Result;
use super::cache;
use super::cache::Cache;
#[cfg(feature = "apk")]
use super::source::Apk;
#[cfg(feature = "breakpad")]
use super::source::Breakpad;
use super::source::Elf;
#[cfg(feature = "gsym")]
use super::source::Gsym;
#[cfg(feature = "gsym")]
use super::source::GsymData;
#[cfg(feature = "gsym")]
use super::source::GsymFile;
use super::source::Kernel;
use super::source::Process;
use super::source::Source;
use super::FindSymOpts;
use super::Input;
use super::Reason;
use super::ResolvedSym;
use super::SrcLang;
use super::Sym;
use super::Symbolize;
use super::Symbolized;
#[cfg(feature = "tracing")]
struct DebugMapsEntry<'entry>(&'entry MapsEntry);
#[cfg(feature = "tracing")]
impl Debug for DebugMapsEntry<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let MapsEntry {
range,
offset,
path_name,
..
} = self.0;
let path = match path_name {
Some(PathName::Path(path)) => &path.symbolic_path,
Some(PathName::Component(component)) => Path::new(component),
None => Path::new("<no-path>"),
};
f.debug_struct(stringify!(MapsEntry))
.field(stringify!(range), &format_args!("{range:#x?}"))
.field(stringify!(offset), &format_args!("{offset:#x?}"))
.field(stringify!(path), &path.display())
.finish()
}
}
#[cfg(feature = "demangle")]
fn maybe_demangle_impl(name: Cow<'_, str>, language: SrcLang) -> Cow<'_, str> {
match language {
SrcLang::Rust => rustc_demangle::try_demangle(name.as_ref())
.ok()
.as_ref()
.map(|x| Cow::Owned(format!("{x:#}"))),
SrcLang::Cpp => cpp_demangle::Symbol::new(name.as_ref())
.ok()
.and_then(|x| x.demangle().ok().map(Cow::Owned)),
SrcLang::Unknown => rustc_demangle::try_demangle(name.as_ref())
.map(|x| Cow::Owned(format!("{x:#}")))
.ok()
.or_else(|| {
cpp_demangle::Symbol::new(name.as_ref())
.ok()
.and_then(|sym| sym.demangle().ok().map(Cow::Owned))
}),
}
.unwrap_or(name)
}
#[cfg(not(feature = "demangle"))]
fn maybe_demangle_impl(name: Cow<'_, str>, _language: SrcLang) -> Cow<'_, str> {
name
}
fn maybe_demangle(symbol: Cow<'_, str>, language: SrcLang, demangle: bool) -> Cow<'_, str> {
if demangle {
maybe_demangle_impl(symbol, language)
} else {
symbol
}
}
pub(crate) fn symbolize_with_resolver<'slf>(
addr: Addr,
resolver: &Resolver<'_, 'slf>,
find_sym_opts: &FindSymOpts,
demangle: bool,
) -> Result<Symbolized<'slf>> {
fn convert_sym<'sym>(addr: Addr, sym: ResolvedSym<'sym>, demangle: bool) -> Sym<'sym> {
let ResolvedSym {
name,
module,
addr: sym_addr,
size,
lang,
code_info,
mut inlined,
_non_exhaustive: (),
} = sym;
let () = inlined.iter_mut().for_each(|inlined_fn| {
let name = take(&mut inlined_fn.name);
inlined_fn.name = maybe_demangle(name, lang, demangle);
});
let sym = Sym {
name: maybe_demangle(Cow::Borrowed(name), lang, demangle),
module: module.map(Cow::Borrowed),
addr: sym_addr,
offset: (addr - sym_addr) as usize,
size,
code_info,
inlined,
_non_exhaustive: (),
};
sym
}
let sym = match resolver {
Resolver::Uncached(resolver) => match resolver.find_sym(addr, find_sym_opts)? {
Ok(sym) => convert_sym(addr, sym, demangle).into_owned(),
Err(reason) => return Ok(Symbolized::Unknown(reason)),
},
Resolver::Cached(resolver) => match resolver.find_sym(addr, find_sym_opts)? {
Ok(sym) => convert_sym(addr, sym, demangle),
Err(reason) => return Ok(Symbolized::Unknown(reason)),
},
};
Ok(Symbolized::Sym(sym))
}
#[cfg(feature = "apk")]
#[derive(Clone, Debug)]
pub struct ApkMemberInfo<'dat> {
pub apk_path: &'dat Path,
pub member_path: &'dat Path,
pub member_mmap: Mmap,
#[doc(hidden)]
pub _non_exhaustive: (),
}
#[cfg(feature = "apk")]
pub trait ApkDispatch: Fn(ApkMemberInfo<'_>) -> Result<Option<Box<dyn Resolve>>> {}
#[cfg(feature = "apk")]
impl<F> ApkDispatch for F where F: Fn(ApkMemberInfo<'_>) -> Result<Option<Box<dyn Resolve>>> {}
pub trait ProcessDispatch: Fn(ProcessMemberInfo<'_>) -> Result<Option<Box<dyn Resolve>>> {}
impl<F> ProcessDispatch for F where F: Fn(ProcessMemberInfo<'_>) -> Result<Option<Box<dyn Resolve>>> {}
#[cfg(feature = "apk")]
fn default_apk_dispatcher(
apk_path: &dyn PathLike,
info: ApkMemberInfo<'_>,
debug_dirs: Option<&[PathBuf]>,
) -> Result<Box<dyn Resolve>> {
let apk_elf_path = create_apk_elf_path(apk_path.represented_path(), info.member_path);
let parser = Rc::new(ElfParser::from_mmap(
info.member_mmap,
Some(apk_elf_path.into_os_string()),
));
let elf_cache = None;
let resolver = ElfResolver::from_parser(parser, debug_dirs, elf_cache)?;
let resolver = Box::new(resolver);
Ok(resolver)
}
#[derive(Clone, Debug)]
pub struct ProcessMemberInfo<'dat> {
pub range: Range<Addr>,
pub member_entry: &'dat PathName,
#[doc(hidden)]
pub _non_exhaustive: (),
}
#[derive(Debug)]
pub struct Builder {
auto_reload: bool,
code_info: bool,
inlined_fns: bool,
demangle: bool,
#[cfg(feature = "dwarf")]
debug_dirs: Rc<[PathBuf]>,
#[cfg(feature = "apk")]
apk_dispatch: Option<Dbg<Box<dyn ApkDispatch>>>,
process_dispatch: Option<Dbg<Box<dyn ProcessDispatch>>>,
}
impl Builder {
pub fn enable_auto_reload(mut self, enable: bool) -> Self {
self.auto_reload = enable;
self
}
pub fn enable_code_info(mut self, enable: bool) -> Self {
self.code_info = enable;
self
}
pub fn enable_inlined_fns(mut self, enable: bool) -> Self {
self.inlined_fns = enable;
self
}
pub fn enable_demangling(mut self, enable: bool) -> Self {
self.demangle = enable;
self
}
#[cfg(feature = "dwarf")]
#[cfg_attr(docsrs, doc(cfg(feature = "dwarf")))]
pub fn set_debug_dirs<D, P>(mut self, debug_dirs: Option<D>) -> Self
where
D: IntoIterator<Item = P>,
P: AsRef<Path>,
{
if let Some(debug_dirs) = debug_dirs {
self.debug_dirs = debug_dirs
.into_iter()
.map(|p| p.as_ref().to_path_buf())
.collect();
} else {
self.debug_dirs = DEFAULT_DEBUG_DIRS.iter().map(PathBuf::from).collect();
}
self
}
#[cfg(feature = "apk")]
#[cfg_attr(docsrs, doc(cfg(feature = "apk")))]
pub fn set_apk_dispatcher<D>(mut self, apk_dispatch: D) -> Self
where
D: ApkDispatch + 'static,
{
self.apk_dispatch = Some(Dbg(Box::new(apk_dispatch)));
self
}
pub fn set_process_dispatcher<D>(mut self, process_dispatch: D) -> Self
where
D: ProcessDispatch + 'static,
{
self.process_dispatch = Some(Dbg(Box::new(process_dispatch)));
self
}
pub fn build(self) -> Symbolizer {
let Self {
auto_reload,
code_info,
inlined_fns,
demangle,
#[cfg(feature = "dwarf")]
debug_dirs,
#[cfg(feature = "apk")]
apk_dispatch,
process_dispatch,
} = self;
let find_sym_opts = match (code_info, inlined_fns) {
(false, inlined_fns) => {
if inlined_fns {
log::warn!(
"inlined function reporting asked for but more general code information inquiry is disabled; flag is being ignored"
);
}
FindSymOpts::Basic
}
(true, false) => FindSymOpts::CodeInfo,
(true, true) => FindSymOpts::CodeInfoAndInlined,
};
Symbolizer {
#[cfg(feature = "apk")]
apk_cache: FileCache::builder().enable_auto_reload(auto_reload).build(),
#[cfg(feature = "breakpad")]
breakpad_cache: FileCache::builder().enable_auto_reload(auto_reload).build(),
elf_cache: FileCache::builder().enable_auto_reload(auto_reload).build(),
#[cfg(feature = "gsym")]
gsym_cache: FileCache::builder().enable_auto_reload(auto_reload).build(),
perf_map_cache: FileCache::builder().enable_auto_reload(auto_reload).build(),
process_vma_cache: RefCell::new(HashMap::new()),
process_cache: InsertMap::new(),
#[cfg(feature = "dwarf")]
kernel_cache: KernelCache::new(Rc::clone(&debug_dirs)),
#[cfg(not(feature = "dwarf"))]
kernel_cache: KernelCache::new(),
vdso_parser: OnceCell::new(),
find_sym_opts,
demangle,
#[cfg(feature = "dwarf")]
debug_dirs,
#[cfg(feature = "apk")]
apk_dispatch,
process_dispatch,
}
}
}
impl Default for Builder {
fn default() -> Self {
Self {
auto_reload: true,
code_info: true,
inlined_fns: true,
demangle: true,
#[cfg(feature = "dwarf")]
debug_dirs: DEFAULT_DEBUG_DIRS.iter().map(PathBuf::from).collect(),
#[cfg(feature = "apk")]
apk_dispatch: None,
process_dispatch: None,
}
}
}
struct SymbolizeHandler<'sym> {
symbolizer: &'sym Symbolizer,
pid: Pid,
debug_syms: bool,
perf_map: bool,
map_files: bool,
vdso: bool,
all_symbols: Vec<Symbolized<'sym>>,
}
impl SymbolizeHandler<'_> {
#[cfg(feature = "apk")]
fn handle_apk_addr(&mut self, addr: Addr, file_off: u64, entry_path: &EntryPath) -> Result<()> {
let result = if self.map_files {
self.symbolizer
.apk_resolver(entry_path, file_off, self.debug_syms)?
} else {
let path = &entry_path.symbolic_path;
self.symbolizer
.apk_resolver(path, file_off, self.debug_syms)?
};
match result {
Some((elf_resolver, elf_addr)) => {
let symbol = self.symbolizer.symbolize_with_resolver(
elf_addr,
&Resolver::Cached(elf_resolver.as_symbolize()),
)?;
let () = self.all_symbols.push(symbol);
}
None => self.handle_unknown_addr(addr, Reason::InvalidFileOffset),
}
Ok(())
}
fn handle_elf_addr(&mut self, addr: Addr, file_off: u64, entry_path: &EntryPath) -> Result<()> {
let resolver = if self.map_files {
self.symbolizer.elf_cache.elf_resolver(
entry_path,
self.symbolizer.maybe_debug_dirs(self.debug_syms),
)
} else {
let path = &entry_path.symbolic_path;
self.symbolizer
.elf_cache
.elf_resolver(path, self.symbolizer.maybe_debug_dirs(self.debug_syms))
}?;
match resolver.file_offset_to_virt_offset(file_off)? {
Some(addr) => {
let symbol = self
.symbolizer
.symbolize_with_resolver(addr, &Resolver::Cached(resolver.deref()))?;
let () = self.all_symbols.push(symbol);
}
None => self.handle_unknown_addr(addr, Reason::InvalidFileOffset),
}
Ok(())
}
fn handle_perf_map_addr(&mut self, addr: Addr) -> Result<()> {
if let Some(perf_map) = self.symbolizer.perf_map_resolver(self.pid)? {
let symbolized = self
.symbolizer
.symbolize_with_resolver(addr, &Resolver::Cached(perf_map))?;
let () = self.all_symbols.push(symbolized);
} else {
let () = self.handle_unknown_addr(addr, Reason::UnknownAddr);
}
Ok(())
}
fn handle_vdso_addr(
&mut self,
addr: Addr,
file_off: u64,
vdso_range: &Range<Addr>,
) -> Result<()> {
let parser = self.symbolizer.vdso_parser(self.pid, vdso_range)?;
match parser.file_offset_to_virt_offset(file_off)? {
Some(addr) => {
let symbol = self
.symbolizer
.symbolize_with_resolver(addr, &Resolver::Cached(parser))?;
let () = self.all_symbols.push(symbol);
}
None => self.handle_unknown_addr(addr, Reason::InvalidFileOffset),
}
Ok(())
}
}
impl normalize::Handler<Reason> for SymbolizeHandler<'_> {
#[cfg_attr(feature = "tracing", crate::log::instrument(skip_all, fields(addr = format_args!("{_addr:#x}"), ?reason)))]
fn handle_unknown_addr(&mut self, _addr: Addr, reason: Reason) {
let () = self.all_symbols.push(Symbolized::Unknown(reason));
}
#[cfg_attr(feature = "tracing", crate::log::instrument(skip_all, fields(addr = format_args!("{addr:#x}"), entry = ?DebugMapsEntry(entry))))]
fn handle_entry_addr(&mut self, addr: Addr, entry: &MapsEntry) -> Result<()> {
let file_off = addr - entry.range.start + entry.offset;
if let Some(path_name) = &entry.path_name {
if let Some(resolver) = self
.symbolizer
.process_dispatch_resolver(entry.range.clone(), path_name)?
{
let () = match resolver.file_offset_to_virt_offset(file_off)? {
Some(addr) => {
let symbol = self.symbolizer.symbolize_with_resolver(
addr,
&Resolver::Cached(resolver.as_symbolize()),
)?;
let () = self.all_symbols.push(symbol);
}
None => self.handle_unknown_addr(addr, Reason::InvalidFileOffset),
};
return Ok(())
}
}
match &entry.path_name {
Some(PathName::Path(entry_path)) => {
let ext = entry_path
.symbolic_path
.extension()
.unwrap_or_else(|| OsStr::new(""));
match ext.to_str() {
#[cfg(feature = "apk")]
Some("apk") | Some("zip") => self.handle_apk_addr(addr, file_off, entry_path),
_ => self.handle_elf_addr(addr, file_off, entry_path),
}
}
Some(PathName::Component(component)) => {
match component.as_str() {
component if self.vdso && component == VDSO_MAPS_COMPONENT => {
let () = self.handle_vdso_addr(addr, file_off, &entry.range)?;
}
_ => {
let () = self.handle_unknown_addr(addr, Reason::Unsupported);
}
}
Ok(())
}
None if self.perf_map => self.handle_perf_map_addr(addr),
None => {
let () = self.handle_unknown_addr(addr, Reason::UnknownAddr);
Ok(())
}
}
}
}
#[derive(Debug)]
pub(crate) enum Resolver<'tmp, 'slf> {
Uncached(&'tmp (dyn Symbolize + 'tmp)),
Cached(&'slf dyn Symbolize),
}
#[cfg(feature = "tracing")]
impl<'tmp, 'slf: 'tmp> Resolver<'tmp, 'slf> {
fn inner(&self) -> &(dyn Symbolize + '_) {
match self {
Self::Uncached(symbolize) | Self::Cached(symbolize) => *symbolize,
}
}
}
#[repr(transparent)]
struct Single<T>(T);
impl<A> FromIterator<A> for Single<A> {
fn from_iter<I>(i: I) -> Self
where
I: IntoIterator<Item = A>,
{
let mut iter = i.into_iter();
let slf = Single(iter.next().unwrap());
debug_assert!(iter.next().is_none());
slf
}
}
trait Addrs: AsRef<[Addr]> {
type OutTy<'slf>: FromIterator<Result<Symbolized<'slf>>>;
}
impl Addrs for &[Addr] {
type OutTy<'slf> = Result<Vec<Symbolized<'slf>>>;
}
impl Addrs for [Addr; 1] {
type OutTy<'slf> = Single<Result<Symbolized<'slf>>>;
}
#[derive(Debug)]
pub struct Symbolizer {
#[cfg(feature = "apk")]
apk_cache: FileCache<(zip::Archive, InsertMap<Range<u64>, Box<dyn Resolve>>)>,
#[cfg(feature = "breakpad")]
breakpad_cache: FileCache<BreakpadResolver>,
elf_cache: FileCache<ElfResolverData>,
#[cfg(feature = "gsym")]
gsym_cache: FileCache<GsymResolver<'static>>,
perf_map_cache: FileCache<PerfMap>,
process_vma_cache: RefCell<HashMap<Pid, Box<[maps::MapsEntry]>>>,
process_cache: InsertMap<PathName, Option<Box<dyn Resolve>>>,
kernel_cache: KernelCache,
vdso_parser: OnceCell<Box<ElfParser<StaticMem>>>,
find_sym_opts: FindSymOpts,
demangle: bool,
#[cfg(feature = "dwarf")]
debug_dirs: Rc<[PathBuf]>,
#[cfg(feature = "apk")]
apk_dispatch: Option<Dbg<Box<dyn ApkDispatch>>>,
process_dispatch: Option<Dbg<Box<dyn ProcessDispatch>>>,
}
impl Symbolizer {
pub fn new() -> Self {
Builder::default().build()
}
pub fn builder() -> Builder {
Builder::default()
}
pub fn register_elf_resolver(
&mut self,
path: &Path,
elf_resolver: Rc<ElfResolver>,
) -> Result<()> {
self.elf_cache.register(path, elf_resolver)
}
#[cfg_attr(feature = "tracing", crate::log::instrument(skip_all, fields(addr = format_args!("{addr:#x}"), resolver = ?resolver.inner())))]
fn symbolize_with_resolver<'slf>(
&'slf self,
addr: Addr,
resolver: &Resolver<'_, 'slf>,
) -> Result<Symbolized<'slf>> {
symbolize_with_resolver(addr, resolver, &self.find_sym_opts, self.demangle)
}
#[cfg(feature = "gsym")]
fn create_gsym_resolver(&self, path: &Path, file: &File) -> Result<GsymResolver<'static>> {
let resolver = GsymResolver::from_file(path.to_path_buf(), file)?;
Ok(resolver)
}
#[cfg(feature = "gsym")]
fn gsym_resolver<'slf>(&'slf self, path: &Path) -> Result<&'slf GsymResolver<'static>> {
let (file, cell) = self.gsym_cache.entry(path)?;
let resolver = cell.get_or_try_init_(|| self.create_gsym_resolver(path, file))?;
Ok(resolver)
}
#[cfg(feature = "apk")]
fn create_apk_resolver<'slf>(
&'slf self,
apk: &zip::Archive,
apk_path: &dyn PathLike,
file_off: u64,
debug_dirs: Option<&[PathBuf]>,
resolver_map: &'slf InsertMap<Range<u64>, Box<dyn Resolve>>,
) -> Result<Option<(&'slf dyn Resolve, Addr)>> {
let actual_path = apk_path.actual_path();
for apk_entry in apk.entries() {
let apk_entry = apk_entry.with_context(|| {
format!("failed to iterate `{}` members", actual_path.display())
})?;
let bounds = apk_entry.data_offset..apk_entry.data_offset + apk_entry.data.len() as u64;
if bounds.contains(&file_off) {
let resolver = resolver_map.get_or_try_insert(bounds.clone(), || {
let mmap = apk
.mmap()
.constrain(bounds.clone())
.ok_or_invalid_input(|| {
format!(
"invalid APK entry data bounds ({bounds:?}) in {}",
actual_path.display()
)
})?;
let info = ApkMemberInfo {
apk_path: actual_path,
member_path: apk_entry.path,
member_mmap: mmap,
_non_exhaustive: (),
};
let resolver = if let Some(Dbg(apk_dispatch)) = &self.apk_dispatch {
if let Some(resolver) = (apk_dispatch)(info.clone())? {
resolver
} else {
default_apk_dispatcher(apk_path, info, debug_dirs)?
}
} else {
default_apk_dispatcher(apk_path, info, debug_dirs)?
};
Ok(resolver)
})?;
let elf_off = file_off - apk_entry.data_offset;
if let Some(addr) = resolver.file_offset_to_virt_offset(elf_off)? {
return Ok(Some((resolver.deref(), addr)))
}
break
}
}
Ok(None)
}
#[cfg(feature = "apk")]
fn apk_resolver<'slf>(
&'slf self,
path: &dyn PathLike,
file_off: u64,
debug_syms: bool,
) -> Result<Option<(&'slf dyn Resolve, Addr)>> {
let actual_path = path.actual_path();
let (file, cell) = self.apk_cache.entry(actual_path)?;
let (apk, resolvers) = cell.get_or_try_init_(|| {
let mmap = Mmap::builder()
.map(file)
.with_context(|| format!("failed to memory map `{}`", actual_path.display()))?;
let apk = zip::Archive::with_mmap(mmap)
.with_context(|| format!("failed to open zip file `{}`", actual_path.display()))?;
let resolvers = InsertMap::new();
Result::<_, Error>::Ok((apk, resolvers))
})?;
let debug_dirs = self.maybe_debug_dirs(debug_syms);
let result = self.create_apk_resolver(apk, path, file_off, debug_dirs, resolvers);
result
}
#[cfg(feature = "breakpad")]
fn create_breakpad_resolver(&self, path: &Path, file: &File) -> Result<BreakpadResolver> {
let resolver = BreakpadResolver::from_file(path.to_path_buf(), file)?;
Ok(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)
}
fn create_perf_map_resolver(&self, path: &Path, file: &File) -> Result<PerfMap> {
let perf_map = PerfMap::from_file(path, file)?;
Ok(perf_map)
}
fn perf_map_resolver(&self, pid: Pid) -> Result<Option<&PerfMap>> {
let path = PerfMap::path(pid);
match self.perf_map_cache.entry(&path) {
Ok((file, cell)) => {
let perf_map =
cell.get_or_try_init_(|| self.create_perf_map_resolver(&path, file))?;
Ok(Some(perf_map))
}
Err(err) if err.kind() == ErrorKind::NotFound => Ok(None),
Err(err) => {
Err(err).with_context(|| format!("failed to open perf map `{}`", path.display()))
}
}
}
fn vdso_parser<'slf>(
&'slf self,
pid: Pid,
range: &Range<Addr>,
) -> Result<&'slf ElfParser<StaticMem>> {
let parser = self.vdso_parser.get_or_try_init_(|| {
let parser = create_vdso_parser(pid, range)?;
Result::<_, Error>::Ok(Box::new(parser))
})?;
Ok(parser)
}
fn process_dispatch_resolver<'slf>(
&'slf self,
range: Range<Addr>,
path_name: &PathName,
) -> Result<Option<&'slf dyn Resolve>> {
if let Some(Dbg(process_dispatch)) = &self.process_dispatch {
let resolver = self
.process_cache
.get_or_try_insert(path_name.clone(), || {
let info = ProcessMemberInfo {
range,
member_entry: path_name,
_non_exhaustive: (),
};
(process_dispatch)(info)
})?;
Ok(resolver.as_deref())
} else {
Ok(None)
}
}
fn symbolize_user_addrs(
&self,
addrs: &[Addr],
pid: Pid,
debug_syms: bool,
perf_map: bool,
map_files: bool,
vdso: bool,
) -> Result<Vec<Symbolized<'_>>> {
let mut handler = SymbolizeHandler {
symbolizer: self,
pid,
debug_syms,
perf_map,
map_files,
vdso,
all_symbols: Vec::with_capacity(addrs.len()),
};
let handler = util::with_ordered_elems(
addrs,
|handler: &mut SymbolizeHandler<'_>| handler.all_symbols.as_mut_slice(),
|sorted_addrs| -> Result<SymbolizeHandler<'_>> {
if let Some(cached) = self.process_vma_cache.borrow().get(&pid) {
let mut entry_iter = cached.iter().map(Ok);
let entries = |_addr| entry_iter.next();
let () = normalize_sorted_user_addrs_with_entries(
sorted_addrs,
entries,
&mut handler,
)?;
Ok(handler)
} else {
let mut entry_iter = maps::parse_filtered(pid)?;
let entries = |_addr| entry_iter.next();
let () = normalize_sorted_user_addrs_with_entries(
sorted_addrs,
entries,
&mut handler,
)?;
Ok(handler)
}
},
)?;
Ok(handler.all_symbols)
}
#[cfg(linux)]
fn create_kernel_resolver<'slf>(&'slf self, src: &Kernel) -> Result<KernelResolver<'slf>> {
let Kernel {
kallsyms,
vmlinux,
kaslr_offset,
debug_syms,
_non_exhaustive: (),
} = src;
KernelResolver::new(
kallsyms,
vmlinux,
*kaslr_offset,
*debug_syms,
&self.kernel_cache,
)
}
#[cfg(not(linux))]
fn create_kernel_resolver<'slf>(&'slf self, _src: &Kernel) -> Result<KernelResolver<'slf>> {
Err(Error::with_unsupported(
"kernel address symbolization is unsupported on operating systems other than Linux",
))
}
#[cfg_attr(feature = "tracing", crate::log::instrument(skip_all, fields(cache = ?cache), err))]
pub fn cache(&self, cache: &Cache) -> Result<()> {
match cache {
Cache::Elf(cache::Elf {
path,
_non_exhaustive: (),
}) => {
let _unpinned = self.elf_cache.unpin(path);
let result = self
.elf_cache
.elf_resolver(path, self.maybe_debug_dirs(false));
let _pinned = self.elf_cache.pin(path);
let resolver = result?;
let () = resolver.cache()?;
}
Cache::Process(cache::Process {
pid,
cache_vmas,
_non_exhaustive: (),
}) => {
if *cache_vmas {
let parsed = maps::parse_filtered(*pid)?.collect::<Result<Box<_>>>()?;
let _prev = self.process_vma_cache.borrow_mut().insert(*pid, parsed);
}
}
}
Ok(())
}
fn symbolize_impl<'in_, 'slf, A>(
&'slf self,
src: &Source,
input: Input<A>,
maybe_fold_error: fn(Error) -> Result<Symbolized<'slf>>,
) -> Result<A::OutTy<'slf>>
where
A: Copy + Addrs + 'in_,
{
match src {
#[cfg(feature = "apk")]
Source::Apk(Apk {
path,
debug_syms,
_non_exhaustive: (),
}) => {
let addrs = match input {
Input::VirtOffset(..) => {
return Err(Error::with_unsupported(
"APK symbolization does not support virtual offset inputs",
))
}
Input::AbsAddr(..) => {
return Err(Error::with_unsupported(
"APK symbolization does not support absolute address inputs",
))
}
Input::FileOffset(offsets) => offsets,
};
let symbols = addrs
.as_ref()
.iter()
.copied()
.map(
|offset| match self.apk_resolver(path, offset, *debug_syms)? {
Some((elf_resolver, elf_addr)) => self.symbolize_with_resolver(
elf_addr,
&Resolver::Cached(elf_resolver.as_symbolize()),
),
None => Ok(Symbolized::Unknown(Reason::InvalidFileOffset)),
},
)
.map(|result| result.or_else(maybe_fold_error))
.collect();
Ok(symbols)
}
#[cfg(feature = "breakpad")]
Source::Breakpad(Breakpad {
path,
_non_exhaustive: (),
}) => {
let addrs = match input {
Input::VirtOffset(..) => {
return Err(Error::with_unsupported(
"Breakpad symbolization does not support virtual offset inputs",
))
}
Input::AbsAddr(..) => {
return Err(Error::with_unsupported(
"Breakpad symbolization does not support absolute address inputs",
))
}
Input::FileOffset(addrs) => addrs,
};
let resolver = self.breakpad_resolver(path)?;
let symbols = addrs
.as_ref()
.iter()
.copied()
.map(|addr| self.symbolize_with_resolver(addr, &Resolver::Cached(resolver)))
.map(|result| result.or_else(maybe_fold_error))
.collect();
Ok(symbols)
}
Source::Elf(Elf {
path,
debug_syms,
_non_exhaustive: (),
}) => {
let resolver = self
.elf_cache
.elf_resolver(path, self.maybe_debug_dirs(*debug_syms))?;
match input {
Input::VirtOffset(addrs) => {
let symbols = addrs
.as_ref()
.iter()
.copied()
.map(|addr| {
self.symbolize_with_resolver(
addr,
&Resolver::Cached(resolver.deref()),
)
})
.map(|result| result.or_else(maybe_fold_error))
.collect();
Ok(symbols)
}
Input::AbsAddr(..) => Err(Error::with_unsupported(
"ELF symbolization does not support absolute address inputs",
)),
Input::FileOffset(offsets) => {
let symbols = offsets
.as_ref()
.iter()
.copied()
.map(
|offset| match resolver.file_offset_to_virt_offset(offset)? {
Some(addr) => self.symbolize_with_resolver(
addr,
&Resolver::Cached(resolver.deref()),
),
None => Ok(Symbolized::Unknown(Reason::InvalidFileOffset)),
},
)
.map(|result| result.or_else(maybe_fold_error))
.collect();
Ok(symbols)
}
}
}
Source::Kernel(kernel) => {
let addrs = match input {
Input::AbsAddr(addrs) => addrs,
Input::VirtOffset(..) => {
return Err(Error::with_unsupported(
"kernel symbolization does not support virtual offset inputs",
))
}
Input::FileOffset(..) => {
return Err(Error::with_unsupported(
"kernel symbolization does not support file offset inputs",
))
}
};
let resolver = self.create_kernel_resolver(kernel)?;
let symbols = addrs
.as_ref()
.iter()
.copied()
.map(|addr| self.symbolize_with_resolver(addr, &Resolver::Uncached(&resolver)))
.map(|result| result.or_else(maybe_fold_error))
.collect();
Ok(symbols)
}
Source::Process(Process {
pid,
debug_syms,
perf_map,
map_files,
vdso,
_non_exhaustive: (),
}) => {
let addrs = match input {
Input::AbsAddr(addrs) => addrs,
Input::VirtOffset(..) => {
return Err(Error::with_unsupported(
"process symbolization does not support virtual offset inputs",
))
}
Input::FileOffset(..) => {
return Err(Error::with_unsupported(
"process symbolization does not support file offset inputs",
))
}
};
let symbols = self.symbolize_user_addrs(
addrs.as_ref(),
*pid,
*debug_syms,
*perf_map,
*map_files,
*vdso,
)?;
Ok(symbols
.into_iter()
.map(Ok)
.map(|result| result.or_else(maybe_fold_error))
.collect())
}
#[cfg(feature = "gsym")]
Source::Gsym(Gsym::Data(GsymData {
data,
_non_exhaustive: (),
})) => {
let addrs = match input {
Input::VirtOffset(addrs) => addrs,
Input::AbsAddr(..) => {
return Err(Error::with_unsupported(
"Gsym symbolization does not support absolute address inputs",
))
}
Input::FileOffset(..) => {
return Err(Error::with_unsupported(
"Gsym symbolization does not support file offset inputs",
))
}
};
let resolver = Rc::new(GsymResolver::with_data(data)?);
let symbols = addrs
.as_ref()
.iter()
.copied()
.map(|addr| {
self.symbolize_with_resolver(addr, &Resolver::Uncached(resolver.deref()))
})
.map(|result| result.or_else(maybe_fold_error))
.collect();
Ok(symbols)
}
#[cfg(feature = "gsym")]
Source::Gsym(Gsym::File(GsymFile {
path,
_non_exhaustive: (),
})) => {
let addrs = match input {
Input::VirtOffset(addrs) => addrs,
Input::AbsAddr(..) => {
return Err(Error::with_unsupported(
"Gsym symbolization does not support absolute address inputs",
))
}
Input::FileOffset(..) => {
return Err(Error::with_unsupported(
"Gsym symbolization does not support file offset inputs",
))
}
};
let resolver = self.gsym_resolver(path)?;
let symbols = addrs
.as_ref()
.iter()
.copied()
.map(|addr| self.symbolize_with_resolver(addr, &Resolver::Cached(resolver)))
.map(|result| result.or_else(maybe_fold_error))
.collect();
Ok(symbols)
}
Source::Phantom(()) => unreachable!(),
}
}
#[cfg_attr(feature = "tracing", crate::log::instrument(skip_all, fields(src = ?src, addrs = ?input.map(Hexify)), err))]
pub fn symbolize<'slf>(
&'slf self,
src: &Source,
input: Input<&[u64]>,
) -> Result<Vec<Symbolized<'slf>>> {
let fold_error = |_err| Ok(Symbolized::Unknown(Reason::IgnoredError));
self.symbolize_impl(src, input, fold_error)
.and_then(|result| result)
}
#[cfg_attr(feature = "tracing", crate::log::instrument(skip_all, fields(src = ?src, input = format_args!("{input:#x?}")), err))]
pub fn symbolize_single<'slf>(
&'slf self,
src: &Source,
input: Input<u64>,
) -> Result<Symbolized<'slf>> {
let input = input.map(|addr| [addr; 1]);
let keep_error = |err| Err(err);
self.symbolize_impl(src, input, keep_error)?.0
}
fn maybe_debug_dirs(&self, debug_syms: bool) -> Option<&[PathBuf]> {
#[cfg(feature = "dwarf")]
let debug_dirs = &self.debug_dirs;
#[cfg(not(feature = "dwarf"))]
let debug_dirs = &[];
debug_syms.then_some(debug_dirs)
}
}
impl Default for Symbolizer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
#[allow(clippy::missing_transmute_annotations)]
mod tests {
use super::*;
use std::env::current_exe;
use std::io::Write as _;
use std::slice;
use tempfile::NamedTempFile;
use test_fork::fork;
use test_log::test;
use crate::elf::types::Elf64_Ehdr;
use crate::maps::Perm;
use crate::symbolize::CodeInfo;
#[test]
fn debug_repr() {
let builder = Symbolizer::builder();
assert_ne!(format!("{builder:?}"), "");
let symbolizer = builder.build();
assert_ne!(format!("{symbolizer:?}"), "");
let test_elf = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("data")
.join("test-stable-addrs.bin");
let parser = Rc::new(ElfParser::open(test_elf.as_path()).unwrap());
let debug_dirs = None;
let elf_cache = None;
let resolver = ElfResolver::from_parser(parser, debug_dirs, elf_cache).unwrap();
let resolver = Resolver::Cached(&resolver);
assert_ne!(format!("{resolver:?}"), "");
assert_ne!(format!("{:?}", resolver.inner()), "");
let entries = maps::parse(Pid::Slf).unwrap();
let () = entries.for_each(|entry| {
assert_ne!(format!("{:?}", DebugMapsEntry(&entry.unwrap())), "");
});
}
#[cfg(target_pointer_width = "64")]
#[test]
fn symbolizer_size() {
assert_eq!(size_of::<Symbolizer>(), 1168);
}
#[test]
fn symbol_source_code_path() {
let mut info = CodeInfo {
dir: None,
file: Cow::Borrowed(OsStr::new("source.c")),
line: Some(1),
column: Some(2),
_non_exhaustive: (),
};
assert_eq!(info.to_path(), Path::new("source.c"));
info.dir = Some(Cow::Borrowed(Path::new("/foobar")));
assert_eq!(info.to_path(), Path::new("/foobar/source.c"));
}
#[test]
fn demangle() {
let symbol = Cow::Borrowed("_ZN4core9panicking9panic_fmt17h5f1a6fd39197ad62E");
let name = maybe_demangle_impl(symbol, SrcLang::Rust);
assert_eq!(name, "core::panicking::panic_fmt");
let symbol = Cow::Borrowed("_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc");
let name = maybe_demangle_impl(symbol, SrcLang::Cpp);
assert_eq!(
name,
"std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)"
);
}
#[test]
fn unsupported_inputs() {
let test_elf = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("data")
.join("test-stable-addrs.bin");
let test_gsym = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("data")
.join("test-stable-addrs.gsym");
let test_sym = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("data")
.join("test-stable-addrs.sym");
let test_zip = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("data")
.join("test.zip");
let unsupported = [
(
Source::Apk(Apk::new(test_zip)),
&[
Input::VirtOffset([40].as_slice()),
Input::AbsAddr([41].as_slice()),
][..],
),
(
Source::Breakpad(Breakpad::new(test_sym)),
&[
Input::VirtOffset([50].as_slice()),
Input::AbsAddr([51].as_slice()),
][..],
),
(
Source::Process(Process::new(Pid::Slf)),
&[
Input::VirtOffset([42].as_slice()),
Input::FileOffset([43].as_slice()),
][..],
),
(
Source::Kernel(Kernel::default()),
&[
Input::VirtOffset([44].as_slice()),
Input::FileOffset([45].as_slice()),
][..],
),
(
Source::Elf(Elf::new(test_elf)),
&[Input::AbsAddr([46].as_slice())][..],
),
(
Source::Gsym(Gsym::File(GsymFile::new(test_gsym))),
&[
Input::AbsAddr([48].as_slice()),
Input::FileOffset([49].as_slice()),
][..],
),
];
let symbolizer = Symbolizer::new();
for (src, inputs) in unsupported {
for input in inputs {
let err = symbolizer.symbolize(&src, *input).unwrap_err();
assert_eq!(err.kind(), ErrorKind::Unsupported);
let input = input.try_to_single().unwrap();
let err = symbolizer.symbolize_single(&src, input).unwrap_err();
assert_eq!(err.kind(), ErrorKind::Unsupported);
}
}
}
#[test]
fn symbolize_entry_various() {
let addrs = [0x10000, 0x30000];
let mut entry_iter = [
Ok(MapsEntry {
range: 0x10000..0x20000,
perm: Perm::default(),
offset: 0,
path_name: Some(PathName::Component("a-component".to_string())),
build_id: None,
}),
Ok(MapsEntry {
range: 0x30000..0x40000,
perm: Perm::default(),
offset: 0,
path_name: None,
build_id: None,
}),
]
.into_iter();
let entries = |_addr| entry_iter.next();
let symbolizer = Symbolizer::new();
let mut handler = SymbolizeHandler {
symbolizer: &symbolizer,
pid: Pid::Slf,
debug_syms: false,
perf_map: false,
map_files: false,
vdso: false,
all_symbols: Vec::new(),
};
let () = normalize_sorted_user_addrs_with_entries(
addrs.as_slice().iter().copied(),
entries,
&mut handler,
)
.unwrap();
let syms = handler.all_symbols;
assert_eq!(syms.len(), 2);
assert!(
matches!(syms[0], Symbolized::Unknown(Reason::Unsupported)),
"{:?}",
syms[0]
);
}
#[fork]
#[test]
fn resolver_instantiation() {
let exe = current_exe().unwrap();
let addrs = maps::parse(Pid::Slf)
.unwrap()
.filter_map(|result| {
let entry = result.unwrap();
let path = entry.path_name.and_then(|path_name| {
path_name.as_path().map(|path| path.symbolic_path.clone())
});
if path == Some(exe.clone()) {
Some(entry.range.start)
} else {
None
}
})
.collect::<Box<[_]>>();
assert!(addrs.len() > 1, "{:x?}", addrs.as_ref());
let src = Source::Process(Process::new(Pid::Slf));
let symbolizer = Symbolizer::new();
let _result = symbolizer.symbolize(&src, Input::AbsAddr(&addrs)).unwrap();
assert_eq!(symbolizer.elf_cache.entry_count(), 1);
}
#[test]
fn symbolize_error_reporting() {
#[repr(C)]
struct ElfFile {
ehdr: Elf64_Ehdr,
}
let elf = ElfFile {
ehdr: Elf64_Ehdr {
e_ident: [127, 69, 76, 70, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
e_type: 3,
e_machine: 62,
e_version: 1,
e_entry: 4208,
e_phoff: size_of::<Elf64_Ehdr>() as _,
e_shoff: 0,
e_flags: 0,
e_ehsize: 64,
e_phentsize: 56,
e_phnum: 1000,
e_shentsize: 0,
e_shnum: 1000,
e_shstrndx: 0,
},
};
let mut file = NamedTempFile::new().unwrap();
let dump = unsafe {
slice::from_raw_parts((&elf as *const ElfFile).cast::<u8>(), size_of::<ElfFile>())
};
let () = file.write_all(dump).unwrap();
let path = file.path();
let module = path.as_os_str().to_os_string();
let parser = ElfParser::from_file(file.as_file(), module.clone()).unwrap();
let debug_dirs = None;
let elf_cache = None;
let resolver = ElfResolver::from_parser(Rc::new(parser), debug_dirs, elf_cache).unwrap();
let resolver = Rc::new(resolver);
for batch in [false, true] {
let mut symbolizer = Symbolizer::new();
let () = symbolizer
.register_elf_resolver(path, Rc::clone(&resolver))
.unwrap();
let mut elf = Elf::new(path);
elf.debug_syms = false;
let src = Source::from(elf);
if batch {
let symbolized = symbolizer
.symbolize(&src, Input::VirtOffset([0x1337].as_slice()))
.unwrap();
assert_eq!(symbolized.len(), 1);
let symbolized = symbolized.first().unwrap();
let Symbolized::Unknown(reason) = symbolized else {
panic!("unexpected symbolization result: {symbolized:?}");
};
assert_eq!(*reason, Reason::IgnoredError);
} else {
let _err = symbolizer
.symbolize_single(&src, Input::VirtOffset(0x1337))
.unwrap_err();
}
}
}
}