Skip to content

Commit

Permalink
Implement iteration over DWARF symbols
Browse files Browse the repository at this point in the history
Add support for iterating over DWARF symbols in an ELF file. So far only
iteration over ELF symbols was supported. Unfortunately that subtly
changes the existing behavior: it was entirely allowed for users to set
debug_syms, but we would only iterate over ELF symbols. With this change
we will consult *only* DWARF symbols instead.

Signed-off-by: Daniel Müller <deso@posteo.net>
  • Loading branch information
d-e-s-o authored and danielocfb committed Oct 3, 2024
1 parent abf4e65 commit 85c3538
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 29 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
Unreleased
----------
- Added support for iteration over DWARF symbols to `inspect::Inspector`
- Adjusted normalization logic to use "symbolic path" for reading build
IDs when normalizing with `NormalizeOpts::map_files` equal to `false`
- Adjusted `inspect::Inspector::for_each` to accept callback returning
Expand Down
26 changes: 20 additions & 6 deletions src/dwarf/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::fmt::Formatter;
use std::fmt::Result as FmtResult;
use std::mem;
use std::mem::swap;
use std::ops::ControlFlow;
use std::ops::Deref as _;
use std::path::Path;
use std::path::PathBuf;
Expand Down Expand Up @@ -329,11 +330,24 @@ impl Inspect for DwarfResolver {
}
}

fn for_each(&self, _opts: &FindAddrOpts, _f: &mut ForEachFn<'_>) -> Result<()> {
// TODO: Implement this functionality.
Err(Error::with_unsupported(
"DWARF logic does not currently support symbol iteration",
))
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.offset_in_file);
match result {
Ok(Some(sym_info)) => f(&sym_info),
Ok(None) => ControlFlow::Continue(()),
Err(err) => {
overall_result = Err(err);
ControlFlow::Break(())
}
}
})?;
overall_result
}
}

Expand All @@ -349,7 +363,7 @@ impl Debug for DwarfResolver {
// directly.
impl<'dwarf> Units<'dwarf> {
/// Fill in source code information for an address to the provided
/// `IntSym`.
/// `ResolvedSym`.
///
/// `addr` is a normalized address.
fn fill_code_info<'slf>(
Expand Down
2 changes: 0 additions & 2 deletions src/dwarf/unit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,6 @@ impl<'dwarf> Unit<'dwarf> {
}
}

#[cfg(test)]
#[cfg(feature = "nightly")]
pub(super) fn parse_functions<'unit>(
&'unit self,
units: &Units<'dwarf>,
Expand Down
18 changes: 18 additions & 0 deletions src/dwarf/units.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
// > IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// > DEALINGS IN THE SOFTWARE.

use std::ops::ControlFlow;

use crate::log::warn;
use crate::once::OnceCell;
use crate::ErrorExt as _;
Expand Down Expand Up @@ -408,6 +410,22 @@ impl<'dwarf> Units<'dwarf> {
.filter_map(move |unit| unit.find_name(name, self).transpose())
}

pub(crate) fn for_each_function<F>(&self, mut f: F) -> Result<(), gimli::Error>
where
F: FnMut(&Function<'dwarf>) -> ControlFlow<()>,
{
for unit in self.units.iter() {
let functions = unit.parse_functions(self)?;

for function in functions.functions.iter() {
if let ControlFlow::Break(()) = f(function) {
return Ok(())
}
}
}
Ok(())
}

/// Retrieve a [`gimli::UnitRef`] for the provided `unit`.
#[inline]
pub(crate) fn unit_ref<'unit>(
Expand Down
8 changes: 5 additions & 3 deletions src/elf/resolver.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use std::fmt::Debug;
use std::fmt::Formatter;
use std::fmt::Result as FmtResult;
use std::ops::Deref as _;
use std::path::Path;
use std::path::PathBuf;
use std::rc::Rc;
Expand Down Expand Up @@ -203,8 +202,11 @@ impl Inspect for ElfResolver {
}

fn for_each(&self, opts: &FindAddrOpts, f: &mut ForEachFn<'_>) -> Result<()> {
let parser = self.parser();
parser.deref().for_each(opts, f)
match &self.backend {
#[cfg(feature = "dwarf")]
ElfBackend::Dwarf(dwarf) => dwarf.for_each(opts, f),
ElfBackend::Elf(parser) => parser.for_each(opts, f),
}
}
}

Expand Down
10 changes: 5 additions & 5 deletions src/inspect/inspector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,13 +154,13 @@ impl Inspector {
/// callback function.
///
/// # Notes
/// - no symbol name demangling is performed currently
/// - currently only function symbols (as opposed to variables) are reported
/// - no symbol name demangling is performed and data is reported as it
/// appears in the symbol source
/// - undefined symbols (such as ones referencing a different shared object)
/// are not reported
/// - for the [`Elf`](Source::Elf) source, at present DWARF symbols are
/// ignored (irrespective of the [`debug_syms`][Elf::debug_syms]
/// configuration)
/// - for the [`Elf`](Source::Elf) source:
/// - if `debug_syms` is set, *only* debug symbols are consulted and no
/// support for variables is present
/// - for the [`Breakpad`](Source::Breakpad) source:
/// - no variable support is present
/// - file offsets won't be reported
Expand Down
76 changes: 63 additions & 13 deletions tests/blazesym.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1121,11 +1121,18 @@ fn inspect_elf_file_offset() {
}


/// Check that we can iterate over all symbols in an ELF file.
/// Check that we can iterate over all symbols in a symbolization source.
#[test]
fn inspect_elf_breakpad_all_symbols() {
fn inspect_elf_dwarf_breakpad_all_symbols() {
fn test(src: &inspect::Source) {
let breakpad = matches!(src, inspect::Source::Breakpad(..));
let dwarf = matches!(
src,
inspect::Source::Elf(inspect::Elf {
debug_syms: true,
..
})
);
let inspector = Inspector::new();
let mut syms = HashMap::<String, inspect::SymInfo>::new();
let () = inspector
Expand All @@ -1135,8 +1142,8 @@ fn inspect_elf_breakpad_all_symbols() {
})
.unwrap();

// Breakpad doesn't contain any or any reasonable information for
// some symbols.
// Breakpad and DWARF don't contain any or any reasonable information
// for some symbols.
if !breakpad {
let sym = syms.get("main").unwrap();
assert_eq!(sym.sym_type, SymType::Function);
Expand All @@ -1151,7 +1158,7 @@ fn inspect_elf_breakpad_all_symbols() {
let sym = syms.get("factorial_inline_test").unwrap();
assert_eq!(sym.sym_type, SymType::Function);

if !breakpad {
if !breakpad && !dwarf {
let sym = syms.get("indirect_func").unwrap();
assert_eq!(sym.sym_type, SymType::Function);
}
Expand All @@ -1162,16 +1169,24 @@ fn inspect_elf_breakpad_all_symbols() {
let sym = syms.get("resolve_indirect_func").unwrap();
assert_eq!(sym.sym_type, SymType::Function);

if !breakpad {
if !breakpad && !dwarf {
let sym = syms.get("a_variable").unwrap();
assert_eq!(sym.sym_type, SymType::Variable);
}
}

let test_elf = Path::new(&env!("CARGO_MANIFEST_DIR"))
let path = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("data")
.join("test-stable-addrs-no-dwarf.bin");
let elf = inspect::Elf::new(test_elf);
let mut elf = inspect::Elf::new(path);
elf.debug_syms = false;
let src = inspect::Source::Elf(elf);
test(&src);

let path = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("data")
.join("test-stable-addrs-stripped-elf-with-dwarf.bin");
let elf = inspect::Elf::new(path);
let src = inspect::Source::Elf(elf);
test(&src);

Expand All @@ -1186,7 +1201,7 @@ fn inspect_elf_breakpad_all_symbols() {

/// Check that early stopping of symbol iteration works as expected.
#[test]
fn inspect_elf_breakpad_early_iter_stop() {
fn inspect_elf_dwarf_breakpad_early_iter_stop() {
fn test(src: &inspect::Source) {
let mut i = 0;
let inspector = Inspector::new();
Expand All @@ -1202,10 +1217,18 @@ fn inspect_elf_breakpad_early_iter_stop() {
.unwrap();
}

let test_elf = Path::new(&env!("CARGO_MANIFEST_DIR"))
let path = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("data")
.join("test-stable-addrs-no-dwarf.bin");
let elf = inspect::Elf::new(test_elf);
let mut elf = inspect::Elf::new(path);
elf.debug_syms = false;
let src = inspect::Source::Elf(elf);
test(&src);

let path = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("data")
.join("test-stable-addrs-stripped-elf-with-dwarf.bin");
let elf = inspect::Elf::new(path);
let src = inspect::Source::Elf(elf);
test(&src);

Expand All @@ -1218,14 +1241,41 @@ fn inspect_elf_breakpad_early_iter_stop() {
}


/// Make sure that the `debug_syms` flag is honored.
#[test]
fn inspect_debug_syms_flag() {
let path = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("data")
.join("test-stable-addrs-no-dwarf.bin");
let mut elf = inspect::Elf::new(path);
elf.debug_syms = true;
let src = inspect::Source::Elf(elf);
let inspector = Inspector::new();
// There aren't any debug symbols in the source (although there are ELF
// symbols).
let () = inspector.for_each(&src, |_sym| panic!()).unwrap();

let path = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("data")
.join("test-stable-addrs-stripped-elf-with-dwarf.bin");
let mut elf = inspect::Elf::new(path);
elf.debug_syms = false;
let src = inspect::Source::Elf(elf);
// There aren't any ELF symbols in the source (although there are DWARF
// symbols).
let () = inspector.for_each(&src, |_sym| panic!()).unwrap();
}


/// Check that we can iterate over all symbols in an ELF file, without
/// encountering duplicates caused by dynamic/static symbol overlap.
#[test]
fn inspect_elf_all_symbols_without_duplicates() {
let test_elf = Path::new(&env!("CARGO_MANIFEST_DIR"))
let path = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("data")
.join("libtest-so.so");
let elf = inspect::Elf::new(test_elf);
let mut elf = inspect::Elf::new(path);
elf.debug_syms = false;
let src = inspect::Source::Elf(elf);

let inspector = Inspector::new();
Expand Down

0 comments on commit 85c3538

Please sign in to comment.