use std::borrow::Cow;
#[cfg(test)]
use std::env;
use std::ffi::OsStr;
use std::fmt::Debug;
use std::fmt::Formatter;
use std::fmt::Result as FmtResult;
use std::io;
use std::mem;
use std::mem::swap;
use std::ops::ControlFlow;
use std::ops::Deref as _;
use std::path::Path;
use std::path::PathBuf;
use std::rc::Rc;
use gimli::AbbreviationsCacheStrategy;
use gimli::Dwarf;
use crate::dwarf::reader::Endianess;
use crate::dwarf::reader::R;
use crate::elf::ElfParser;
use crate::elf::ElfResolverData;
#[cfg(test)]
use crate::elf::DEFAULT_DEBUG_DIRS;
use crate::error::IntoCowStr;
use crate::file_cache::FileCache;
use crate::helper::read_elf_build_id;
use crate::inspect::FindAddrOpts;
use crate::inspect::ForEachFn;
use crate::inspect::Inspect;
use crate::inspect::SymInfo;
use crate::log;
use crate::log::debug;
use crate::log::warn;
use crate::symbolize::CodeInfo;
use crate::symbolize::FindSymOpts;
use crate::symbolize::InlinedFn;
use crate::symbolize::Reason;
use crate::symbolize::ResolvedSym;
use crate::symbolize::SrcLang;
use crate::symbolize::Symbolize;
use crate::Addr;
use crate::Error;
use crate::ErrorExt;
use crate::ErrorKind;
use crate::Result;
use crate::SymType;
use super::debug_link::debug_link_crc32;
use super::debug_link::read_debug_link;
use super::debug_link::DebugFileIter;
use super::function::Function;
use super::location::Location;
use super::reader;
use super::unit::Unit;
use super::units::Units;
impl ErrorExt for gimli::Error {
type Output = Error;
fn context<C>(self, context: C) -> Self::Output
where
C: IntoCowStr,
{
Error::from(self).context(context)
}
fn with_context<C, F>(self, f: F) -> Self::Output
where
C: IntoCowStr,
F: FnOnce() -> C,
{
Error::from(self).with_context(f)
}
}
impl From<Option<gimli::DwLang>> for SrcLang {
fn from(other: Option<gimli::DwLang>) -> Self {
match other {
Some(gimli::DW_LANG_Rust) => SrcLang::Rust,
Some(
gimli::DW_LANG_C_plus_plus
| gimli::DW_LANG_C_plus_plus_03
| gimli::DW_LANG_C_plus_plus_11
| gimli::DW_LANG_C_plus_plus_14
| gimli::DW_LANG_C_plus_plus_17
| gimli::DW_LANG_C_plus_plus_20,
) => SrcLang::Cpp,
_ => SrcLang::Unknown,
}
}
}
fn try_canonicalize(path: &Path) -> io::Result<Cow<'_, Path>> {
match path.canonicalize() {
Ok(path) => Ok(Cow::Owned(path)),
Err(err) if err.kind() == io::ErrorKind::NotFound => {
Ok(Cow::Borrowed(path))
}
Err(err) => Err(err),
}
}
fn find_debug_file(file: &OsStr, linker: Option<&Path>, debug_dirs: &[PathBuf]) -> Option<PathBuf> {
let canonical_linker = linker.and_then(|linker| try_canonicalize(linker).ok());
let build_id = canonical_linker
.as_ref()
.and_then(|linker| read_elf_build_id(linker).unwrap_or_default());
let it = DebugFileIter::new(debug_dirs, canonical_linker.as_deref(), file, build_id);
for path in it {
if path.exists() {
debug!("found debug info at `{}`", path.display());
return Some(path)
}
}
warn!(
"debug link references destination `{}` which was not found in any known location",
Path::new(file).display(),
);
None
}
fn try_deref_debug_link(
parser: &ElfParser,
debug_dirs: &[PathBuf],
elf_cache: Option<&FileCache<ElfResolverData>>,
) -> Result<Option<Rc<ElfParser>>> {
if let Some((file, checksum)) = read_debug_link(parser)? {
let linker = parser.module().map(OsStr::as_ref);
match find_debug_file(file, linker, debug_dirs) {
Some(path) => {
let tmp_parser;
let dst_parser = if let Some(elf_cache) = elf_cache {
elf_cache
.elf_resolver(&path, None)
.with_context(|| {
format!("failed to open debug link destination `{}`", path.display())
})?
.parser()
} else {
let parser = ElfParser::open(&path).with_context(|| {
format!("failed to open debug link destination `{}`", path.display())
})?;
tmp_parser = Rc::new(parser);
&tmp_parser
};
let mmap = dst_parser.backend();
let crc = debug_link_crc32(mmap);
if crc != checksum {
return Err(Error::with_invalid_data(format!(
"debug link destination `{}` checksum does not match \
expected one: {crc:x} (actual) != {checksum:x} (expected)",
path.display()
)))
}
Ok(Some(Rc::clone(dst_parser)))
}
None => Ok(None),
}
} else {
Ok(None)
}
}
fn try_find_dwp(
parser: &ElfParser,
elf_cache: Option<&FileCache<ElfResolverData>>,
) -> Result<Option<Rc<ElfParser>>> {
if let Some(path) = parser.module() {
let mut dwp_path = path.to_os_string();
let () = dwp_path.push(".dwp");
let dwp_path = PathBuf::from(dwp_path);
let result = if let Some(elf_cache) = elf_cache {
elf_cache
.elf_resolver(&dwp_path, None)
.map(|resolver| Rc::clone(resolver.parser()))
} else {
ElfParser::open(&dwp_path).map(Rc::new)
};
match result {
Ok(parser) => {
log::debug!("using DWARF package `{}`", dwp_path.display());
Ok(Some(parser))
}
Err(err) if err.kind() == ErrorKind::NotFound => Ok(None),
Err(err) => Err(err),
}
} else {
Ok(None)
}
}
pub(crate) struct DwarfResolver {
units: Units<'static>,
parser: Rc<ElfParser>,
linkee_parser: Option<Rc<ElfParser>>,
_dwp_parser: Option<Rc<ElfParser>>,
}
impl DwarfResolver {
pub(crate) fn parser(&self) -> &Rc<ElfParser> {
&self.parser
}
pub(crate) fn from_parser(
parser: Rc<ElfParser>,
debug_dirs: &[PathBuf],
elf_cache: Option<&FileCache<ElfResolverData>>,
) -> Result<Self> {
let linkee_parser = try_deref_debug_link(&parser, debug_dirs, elf_cache)?;
let dwp_parser = try_find_dwp(&parser, elf_cache)?;
let static_linkee_parser = unsafe {
mem::transmute::<&ElfParser, &'static ElfParser>(
linkee_parser.as_ref().unwrap_or(&parser).deref(),
)
};
let mut load_section = |section| reader::load_section(static_linkee_parser, section);
let mut dwarf = Dwarf::load(&mut load_section)?;
let () = dwarf.populate_abbreviations_cache(AbbreviationsCacheStrategy::Duplicates);
let dwp = dwp_parser
.as_deref()
.map(|dwp_parser| {
let empty = R::new(&[], Endianess::default());
let static_dwp_parser =
unsafe { mem::transmute::<&ElfParser, &'static ElfParser>(dwp_parser) };
let load_dwo_section =
|section| reader::load_dwo_section(static_dwp_parser, section);
let dwp = gimli::DwarfPackage::load(load_dwo_section, empty)?;
Result::<_, Error>::Ok(dwp)
})
.transpose()?;
let units = Units::parse(dwarf, dwp)?;
let slf = Self {
units,
parser,
linkee_parser,
_dwp_parser: dwp_parser,
};
Ok(slf)
}
#[cfg(test)]
fn open(path: &Path) -> Result<Self> {
let parser = ElfParser::open(path)?;
let debug_dirs = DEFAULT_DEBUG_DIRS
.iter()
.map(PathBuf::from)
.collect::<Vec<_>>();
Self::from_parser(Rc::new(parser), debug_dirs.as_slice(), None)
}
fn function_to_sym_info<'slf>(
&'slf self,
function: &'slf Function,
offset_in_file: bool,
) -> Result<Option<SymInfo<'slf>>> {
let name = if let Some(name) = function.name {
name.to_string().unwrap()
} else {
return Ok(None)
};
let addr = function
.range
.as_ref()
.map(|range| range.begin)
.unwrap_or(0);
let size = function
.range
.as_ref()
.and_then(|range| range.end.checked_sub(range.begin))
.map(|size| usize::try_from(size).unwrap_or(usize::MAX))
.unwrap_or(0);
let info = SymInfo {
name: Cow::Borrowed(name),
addr,
size: Some(size),
sym_type: SymType::Function,
file_offset: offset_in_file
.then(|| self.parser.find_file_offset(addr, size))
.transpose()?
.flatten(),
module: self.parser.module().map(Cow::Borrowed),
_non_exhaustive: (),
};
Ok(Some(info))
}
}
impl Symbolize for DwarfResolver {
fn find_sym(&self, addr: Addr, opts: &FindSymOpts) -> Result<Result<ResolvedSym<'_>, Reason>> {
let data = self.units.find_function(addr)?;
let mut sym = if let Some((function, unit)) = data {
let name = function
.name
.map(|name| name.to_string())
.transpose()?
.unwrap_or("");
let fn_addr = function.range.map(|range| range.begin).unwrap_or(0);
let size = function
.range
.map(|range| usize::try_from(range.end - range.begin).unwrap_or(usize::MAX));
ResolvedSym {
name,
module: self.parser.module(),
addr: fn_addr,
size,
lang: unit.language().into(),
code_info: None,
inlined: Box::new([]),
_non_exhaustive: (),
}
} else {
let parser = self.linkee_parser.as_ref().unwrap_or(&self.parser).deref();
match parser.find_sym(addr, opts)? {
Ok(sym) => sym,
Err(reason) => return Ok(Err(reason)),
}
};
let () = self.units.fill_code_info(&mut sym, addr, opts, data)?;
Ok(Ok(sym))
}
}
impl Inspect for DwarfResolver {
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("not implemented"))
}
let syms = self
.units
.find_name(name)
.map(|result| {
match result {
Ok(function) => {
let info = self
.function_to_sym_info(function, opts.file_offset)?
.unwrap();
Ok(info)
}
Err(err) => Err(Error::from(err)),
}
})
.collect::<Result<Vec<_>>>()?;
if syms.is_empty() {
let parser = self.linkee_parser.as_ref().unwrap_or(&self.parser).deref();
parser.find_addr(name, opts)
} else {
Ok(syms)
}
}
fn for_each(&self, opts: &FindAddrOpts, f: &mut ForEachFn<'_>) -> Result<()> {
if let SymType::Variable = opts.sym_type {
return Err(Error::with_unsupported("not implemented"))
}
let mut overall_result = Ok(());
let () = self.units.for_each_function(|func| {
let result = self.function_to_sym_info(func, opts.file_offset);
match result {
Ok(Some(sym_info)) => f(&sym_info),
Ok(None) => ControlFlow::Continue(()),
Err(err) => {
overall_result = Err(err);
ControlFlow::Break(())
}
}
})?;
overall_result
}
}
impl Debug for DwarfResolver {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let module = self
.parser()
.module()
.unwrap_or_else(|| OsStr::new("<unknown>"));
write!(f, "DwarfResolver({module:?})")
}
}
impl<'dwarf> Units<'dwarf> {
fn fill_code_info<'slf>(
&'slf self,
sym: &mut ResolvedSym<'slf>,
addr: Addr,
opts: &FindSymOpts,
data: Option<(&'slf Function<'dwarf>, &'slf Unit<'dwarf>)>,
) -> Result<()> {
if !opts.code_info() {
return Ok(())
}
let direct_location = if let Some(direct_location) = self.find_location(addr)? {
direct_location
} else {
return Ok(())
};
let Location {
dir,
file,
line,
column,
} = direct_location;
let mut direct_code_info = CodeInfo {
dir: Some(Cow::Borrowed(dir)),
file: Cow::Borrowed(file),
line,
column: column.map(|col| col.try_into().unwrap_or(u16::MAX)),
_non_exhaustive: (),
};
let inlined = if opts.inlined_fns() {
if let Some((function, unit)) = data {
if let Some(inline_stack) = self.find_inlined_functions(addr, function, unit)? {
let mut inlined = Vec::<InlinedFn>::with_capacity(inline_stack.len());
for result in inline_stack {
let (name, location) = result?;
let mut code_info = location.map(|location| {
let Location {
dir,
file,
line,
column,
} = location;
CodeInfo {
dir: Some(Cow::Borrowed(dir)),
file: Cow::Borrowed(file),
line,
column: column.map(|col| col.try_into().unwrap_or(u16::MAX)),
_non_exhaustive: (),
}
});
if let Some(ref mut last_code_info) =
inlined.last_mut().map(|f| &mut f.code_info)
{
let () = swap(&mut code_info, last_code_info);
} else if let Some(code_info) = &mut code_info {
let () = swap(code_info, &mut direct_code_info);
}
let inlined_fn = InlinedFn {
name: Cow::Borrowed(name),
code_info,
_non_exhaustive: (),
};
let () = inlined.push(inlined_fn);
}
inlined
} else {
Vec::new()
}
} else {
Vec::new()
}
} else {
Vec::new()
};
sym.code_info = Some(Box::new(direct_code_info));
sym.inlined = inlined.into_boxed_slice();
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::env::current_exe;
use std::ffi::OsStr;
use std::ops::ControlFlow;
use std::path::PathBuf;
use tempfile::NamedTempFile;
use test_log::test;
use crate::ErrorKind;
#[test]
fn debug_repr() {
let bin_name = current_exe().unwrap();
let resolver = DwarfResolver::open(&bin_name).unwrap();
assert_ne!(format!("{resolver:?}"), "");
}
#[test]
fn error_conversion() {
let inner = gimli::Error::Io;
let err = Result::<(), _>::Err(inner)
.context("failed to read")
.unwrap_err();
assert_eq!(format!("{err:#}"), format!("failed to read: {inner}"));
let err = Result::<(), _>::Err(inner)
.with_context(|| "failed to read")
.unwrap_err();
assert_eq!(format!("{err:#}"), format!("failed to read: {inner}"));
}
#[test]
fn canonicalization_attempt() {
let file = NamedTempFile::new().unwrap();
let path = file.path().to_path_buf();
let expected = path.clone();
let canonical = try_canonicalize(&path).unwrap();
assert_eq!(canonical, expected);
drop(file);
let canonical = try_canonicalize(&path).unwrap();
assert_eq!(canonical, expected);
}
#[test]
fn debug_link_resolution() {
let path = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("data")
.join("test-stable-addrs-stripped-with-link.bin");
let resolver = DwarfResolver::open(&path).unwrap();
assert!(resolver.linkee_parser.is_some());
let linkee_path = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("data")
.join("test-stable-addrs-dwarf-only.dbg");
assert_eq!(
resolver.linkee_parser.as_ref().unwrap().module(),
Some(linkee_path.as_os_str())
);
}
#[test]
fn dwp_discovery() {
let path = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("data")
.join("test-rs-split-dwarf.bin");
let parser = ElfParser::open(&path).unwrap();
let dwp_parser = try_find_dwp(&parser, None).unwrap();
assert!(dwp_parser.is_some());
let path = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("data")
.join("test-rs.bin");
let parser = ElfParser::open(&path).unwrap();
let dwp_parser = try_find_dwp(&parser, None).unwrap();
assert!(dwp_parser.is_none());
}
#[test]
fn source_location_finding() {
let bin_name = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("data")
.join("test-stable-addrs.bin");
let resolver = DwarfResolver::open(bin_name.as_ref()).unwrap();
let info = resolver
.find_sym(0x2000200, &FindSymOpts::CodeInfo)
.unwrap()
.unwrap()
.code_info
.unwrap();
assert_ne!(info.dir, Some(Cow::Owned(PathBuf::new())));
assert_eq!(info.file, OsStr::new("test-stable-addrs.c"));
assert_eq!(info.line, Some(10));
assert!(info.column.is_some());
}
#[test]
fn lookup_symbol() {
let test_dwarf = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("data")
.join("test-stable-addrs-stripped-elf-with-dwarf.bin");
let opts = FindAddrOpts {
file_offset: false,
sym_type: SymType::Function,
};
let resolver = DwarfResolver::open(test_dwarf.as_ref()).unwrap();
let symbols = resolver.find_addr("factorial", &opts).unwrap();
assert_eq!(symbols.len(), 1);
let symbol = symbols.first().unwrap();
assert_eq!(symbol.addr, 0x2000200);
}
#[test]
fn unsupported_ops() {
let test_dwarf = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("data")
.join("test-stable-addrs-stripped-elf-with-dwarf.bin");
let opts = FindAddrOpts {
file_offset: false,
sym_type: SymType::Variable,
};
let resolver = DwarfResolver::open(test_dwarf.as_ref()).unwrap();
let err = resolver.find_addr("factorial", &opts).unwrap_err();
assert_eq!(err.kind(), ErrorKind::Unsupported);
let err = resolver
.for_each(&opts, &mut |_| ControlFlow::Continue(()))
.unwrap_err();
assert_eq!(err.kind(), ErrorKind::Unsupported);
}
}