Skip to content

Commit

Permalink
Add Breakpad support to inspect APIs
Browse files Browse the repository at this point in the history
This change adds inspection support for the Breakpad format.
Specifically, We support look up by name as well as iteration of
symbols. Note, though, that only some functions are contained in the
Breakpad file, and so these are all that we support as well. And even
there disclaimers abound, as GNU indirect functions, for example, are
not necessarily contained and public symbols may not carry sufficient
information to be reported either.

Signed-off-by: Daniel Müller <[email protected]>
  • Loading branch information
d-e-s-o committed Feb 23, 2024
1 parent 47212ba commit 1e54ae1
Show file tree
Hide file tree
Showing 8 changed files with 309 additions and 47 deletions.
2 changes: 2 additions & 0 deletions src/breakpad/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ use nom::Needed;
use super::types::*;

use crate::error::IntoCowStr;
use crate::once::OnceCell;
use crate::Error;
use crate::ErrorExt;
use crate::Result;
Expand Down Expand Up @@ -651,6 +652,7 @@ impl SymbolParser {
SymbolFile {
files: self.files,
functions: self.functions,
by_name_idx: OnceCell::new(),
inline_origins: self.inline_origins,
}
}
Expand Down
73 changes: 60 additions & 13 deletions src/breakpad/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,27 @@ use crate::ErrorExt as _;
use crate::IntoError as _;
use crate::Result;
use crate::SymResolver;
use crate::SymType;

use super::types::Function;
use super::types::SymbolFile;


impl<'func> From<&'func Function> for SymInfo<'func> {
#[inline]
fn from(func: &'func Function) -> Self {
Self {
name: Cow::Borrowed(&func.name),
addr: func.addr,
size: func.size as _,
sym_type: SymType::Function,
file_offset: None,
obj_file_name: None,
}
}
}


/// A symbol resolver for a single Breakpad file.
pub(crate) struct BreakpadResolver {
/// The parsed symbol file.
Expand Down Expand Up @@ -76,6 +93,24 @@ impl BreakpadResolver {

Ok(name)
}

/// Perform an operation on each symbol.
pub(crate) fn for_each_sym<F, R>(&self, opts: &FindAddrOpts, mut r: R, mut f: F) -> Result<R>
where
F: FnMut(R, &SymInfo<'_>) -> R,
{
if let SymType::Variable = opts.sym_type {
return Err(Error::with_unsupported(
"breakpad logic does not currently support variable iteration",
))
}

for func in &self.symbol_file.functions {
let sym = SymInfo::from(func);
r = f(r, &sym);
}
Ok(r)
}
}

impl SymResolver for BreakpadResolver {
Expand All @@ -99,14 +134,20 @@ impl SymResolver for BreakpadResolver {
}
}

fn find_addr<'slf>(
&'slf self,
_name: &str,
_opts: &FindAddrOpts,
) -> Result<Vec<SymInfo<'slf>>> {
Err(Error::with_unsupported(
"Breakpad resolver does not currently support lookup by name",
))
fn find_addr<'slf>(&'slf self, name: &str, opts: &FindAddrOpts) -> Result<Vec<SymInfo<'slf>>> {
if let SymType::Variable = opts.sym_type {
return Err(Error::with_unsupported(
"breakpad logic does not currently support variable lookup",
))
}

let syms = self
.symbol_file
.find_addr(name)
.map(SymInfo::from)
.collect::<Vec<_>>();

Ok(syms)
}

fn find_code_info(&self, addr: Addr, inlined_fns: bool) -> Result<Option<AddrCodeInfo<'_>>> {
Expand Down Expand Up @@ -198,18 +239,24 @@ mod tests {
assert!(dbg.ends_with("test-stable-addresses.sym"), "{dbg}");
}

/// Check that [`BreakpadResolver::find_addr`] behaves as expected.
/// Check that [`BreakpadResolver::find_addr`] and
/// [`BreakpadResolver::for_each_sym`] behave as expected.
#[test]
fn unsupported_find_addr() {
fn unsupported_ops() {
let sym_path = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("data")
.join("test-stable-addresses.sym");
let sym_file = File::open(&sym_path).unwrap();

let resolver = BreakpadResolver::from_file(sym_path, &sym_file).unwrap();
let err = resolver
.find_addr("factorial", &FindAddrOpts::default())
.unwrap_err();
let opts = FindAddrOpts {
sym_type: SymType::Variable,
..Default::default()
};
let err = resolver.find_addr("a_variable", &opts).unwrap_err();
assert_eq!(err.kind(), ErrorKind::Unsupported);

let err = resolver.for_each_sym(&opts, (), |(), _| ()).unwrap_err();
assert_eq!(err.kind(), ErrorKind::Unsupported);
}
}
52 changes: 51 additions & 1 deletion src/breakpad/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@

use std::collections::HashMap;

use crate::once::OnceCell;
use crate::util::find_lowest_match_by_key;
use crate::util::find_match_or_lower_bound_by_key;
use crate::util::Either;


/// A publicly visible linker symbol.
Expand Down Expand Up @@ -182,8 +185,10 @@ impl Function {
pub(crate) struct SymbolFile {
/// The set of source files involved in compilation.
pub files: HashMap<u32, String>,
/// Functions.
/// Functions, sorted by start address.
pub functions: Vec<Function>,
/// An index on top of `functions` sorted by name.
pub by_name_idx: OnceCell<Box<[usize]>>,
/// Function names for inlined functions.
pub inline_origins: HashMap<u32, String>,
}
Expand All @@ -204,6 +209,50 @@ impl SymbolFile {
}
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
}

/// Find a function symbol based on its name.
pub 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)
}
}


Expand All @@ -218,6 +267,7 @@ mod tests {
let file = SymbolFile {
files: HashMap::new(),
functions: Vec::new(),
by_name_idx: OnceCell::new(),
inline_origins: HashMap::new(),
};
assert_ne!(format!("{file:?}"), "");
Expand Down
56 changes: 56 additions & 0 deletions src/inspect/inspector.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
#[cfg(feature = "breakpad")]
use std::fs::File;
use std::ops::Deref as _;
#[cfg(feature = "breakpad")]
use std::path::Path;
#[cfg(feature = "breakpad")]
use std::rc::Rc;

#[cfg(feature = "breakpad")]
use crate::breakpad::BreakpadResolver;
use crate::elf::ElfResolverData;
use crate::file_cache::FileCache;
use crate::Result;
use crate::SymResolver;

#[cfg(feature = "breakpad")]
use super::source::Breakpad;
use super::source::Elf;
use super::source::Source;
use super::FindAddrOpts;
Expand All @@ -27,23 +37,44 @@ use super::SymType;
/// to consider creating a new `Inspector` instance regularly.
#[derive(Debug)]
pub struct Inspector {
#[cfg(feature = "breakpad")]
breakpad_cache: FileCache<Rc<BreakpadResolver>>,
elf_cache: FileCache<ElfResolverData>,
}

impl Inspector {
/// Create a new `Inspector`.
pub fn new() -> Self {
Self {
#[cfg(feature = "breakpad")]
breakpad_cache: FileCache::builder().enable_auto_reload(true).build(),
// TODO: Make auto reloading configurable by clients.
elf_cache: FileCache::builder().enable_auto_reload(true).build(),
}
}

#[cfg(feature = "breakpad")]
fn create_breakpad_resolver(&self, path: &Path, file: &File) -> Result<Rc<BreakpadResolver>> {
let resolver = BreakpadResolver::from_file(path.to_path_buf(), file)?;
Ok(Rc::new(resolver))
}

#[cfg(feature = "breakpad")]
fn breakpad_resolver<'slf>(&'slf self, path: &Path) -> Result<&'slf Rc<BreakpadResolver>> {
let (file, cell) = self.breakpad_cache.entry(path)?;
let resolver = cell.get_or_try_init(|| self.create_breakpad_resolver(path, file))?;
Ok(resolver)
}

/// Look up information (address etc.) about a list of symbols,
/// given their names.
///
/// # Notes
/// - no symbol name demangling is performed currently
/// - for the [`Breakpad`](Source::Breakpad) source:
/// - no variable support is present
/// - file offsets won't be reported
/// - addresses are reported as they appear in the symbol source
pub fn lookup<'slf>(
&'slf self,
src: &Source,
Expand All @@ -55,6 +86,14 @@ impl Inspector {
};

let resolver = match src {
#[cfg(feature = "breakpad")]
Source::Breakpad(Breakpad {
path,
_non_exhaustive: (),
}) => {
let resolver = self.breakpad_resolver(path)?;
resolver.deref() as &dyn SymResolver
}
Source::Elf(Elf {
path,
debug_syms,
Expand Down Expand Up @@ -95,11 +134,28 @@ impl Inspector {
/// - for the [`Elf`](Source::Elf) source, at present DWARF symbols are
/// ignored (irrespective of the [`debug_syms`][Elf::debug_syms]
/// configuration)
/// - for the [`Breakpad`](Source::Breakpad) source:
/// - no variable support is present
/// - file offsets won't be reported
/// - addresses are reported as they appear in the symbol source
pub fn for_each<F, R>(&self, src: &Source, r: R, f: F) -> Result<R>
where
F: FnMut(R, &SymInfo<'_>) -> R,
{
match src {
#[cfg(feature = "breakpad")]
Source::Breakpad(Breakpad {
path,
_non_exhaustive: (),
}) => {
let opts = FindAddrOpts {
// Breakpad logic doesn't support file offsets.
offset_in_file: false,
sym_type: SymType::Undefined,
};
let resolver = self.breakpad_resolver(path)?;
resolver.for_each_sym(&opts, r, f)
}
Source::Elf(Elf {
path,
debug_syms,
Expand Down
2 changes: 2 additions & 0 deletions src/inspect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ use crate::Addr;
use crate::SymType;

pub use inspector::Inspector;
#[cfg(feature = "breakpad")]
pub use source::Breakpad;
pub use source::Elf;
pub use source::Source;

Expand Down
35 changes: 35 additions & 0 deletions src/inspect/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,35 @@ use std::path::Path;
use std::path::PathBuf;


cfg_breakpad! {
/// A Breakpad file.
#[derive(Clone, Debug, PartialEq)]
pub struct Breakpad {
/// The path to the Breakpad (*.sym) file.
pub path: PathBuf,
/// The struct is non-exhaustive and open to extension.
#[doc(hidden)]
pub _non_exhaustive: (),
}

impl Breakpad {
/// Create a new [`Breakpad`] object, referencing the provided path.
pub fn new(path: impl Into<PathBuf>) -> Self {
Self {
path: path.into(),
_non_exhaustive: (),
}
}
}

impl From<Breakpad> for Source {
fn from(breakpad: Breakpad) -> Self {
Source::Breakpad(breakpad)
}
}
}


/// An ELF file.
#[derive(Clone, Debug, PartialEq)]
pub struct Elf {
Expand Down Expand Up @@ -39,6 +68,10 @@ impl From<Elf> for Source {
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum Source {
/// The source is a Breakpad file.
#[cfg(feature = "breakpad")]
#[cfg_attr(docsrs, doc(cfg(feature = "breakpad")))]
Breakpad(Breakpad),
/// The source is an ELF file.
Elf(Elf),
}
Expand All @@ -47,6 +80,8 @@ impl Source {
/// Retrieve the path to the source, if it has any.
pub fn path(&self) -> Option<&Path> {
match self {
#[cfg(feature = "breakpad")]
Self::Breakpad(breakpad) => Some(&breakpad.path),
Self::Elf(elf) => Some(&elf.path),
}
}
Expand Down
Loading

0 comments on commit 1e54ae1

Please sign in to comment.