Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 70 additions & 2 deletions one_collect/src/helpers/exporting/mappings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,20 @@ impl ExportMapping {
unique_ips.sort();
sym_reader.reset();

// When more than one symbol source is unioned onto the same mapping
// (e.g. ELF symtab/dynsym followed by the Go gopclntab), a function
// present in multiple sources must not be added twice. We detect this
// transparently: if the mapping already holds symbols from an earlier
// source, we de-duplicate by start address against them (and against
// symbols added during this pass); the first source to add a given
// start wins. Single-source callers add onto an empty mapping and take
// this cost only when the scenario actually requires it.
let mut seen: Option<std::collections::HashSet<u64>> = if initial_symbol_count > 0 {
Some(self.symbols.iter().map(|s| s.start()).collect())
} else {
None
};

loop {
if !sym_reader.next() {
break;
Expand All @@ -230,6 +244,16 @@ impl ExportMapping {
}
}

// Skip duplicates (by start address) when de-duplicating across
// unioned symbol sources.
if add_sym {
if let Some(seen) = seen.as_mut() {
if !seen.insert(start_ip) {
add_sym = false;
}
}
}

if add_sym {
let demangled_name = sym_reader.demangle();
let demangled_name = match &demangled_name {
Expand Down Expand Up @@ -585,8 +609,52 @@ mod tests {
}

#[test]
fn lookup() {
let mappings = vec!(
fn add_matching_symbols_dedup_test() {
// Unions two symbol sources (e.g. ELF symtab then Go pclntab) into one
// mapping. Functions that appear in both (same start) must not be
// duplicated; the first source wins. Distinct functions are all kept.
// Dedup is transparent: calling add_matching_symbols a second time on a
// mapping that already has symbols de-duplicates automatically.
let start = 4096;
let end = start + 4096;
let file_offset = 1024;

let mut mapping = ExportMapping::new(0, 0, start, end, file_offset, false, 0, UnwindType::Prolog);
let mut strings = InternedStrings::new(32);

// VA = file - 1024 + 4096. IPs chosen to hit each symbol's start.
let mut unique_ips = vec![4096u64, 4222, 4372];

// Source A (e.g. symtab): two symbols.
let source_a = vec![
(1024u64, 1100u64, "shared_func".to_string()), // VA 4096-4172
(1150, 1250, "only_in_a".to_string()), // VA 4222-4322
];
let mut reader_a = MockSymbolReader::new(source_a);
mapping.add_matching_symbols(&mut unique_ips, &mut reader_a, &mut strings);
assert_eq!(2, mapping.symbols().len(), "source A adds 2");

// Source B (e.g. pclntab): re-lists shared_func (same start) plus a new one.
let source_b = vec![
(1024u64, 1100u64, "shared_func".to_string()), // duplicate start -> skipped
(1300, 1400, "only_in_b".to_string()), // VA 4372-4472 -> added
];
let mut reader_b = MockSymbolReader::new(source_b);
mapping.add_matching_symbols(&mut unique_ips, &mut reader_b, &mut strings);

// Total should be 3 (shared_func once, only_in_a, only_in_b).
assert_eq!(3, mapping.symbols().len(), "duplicate start must be deduped");

let names: Vec<&str> = mapping.symbols().iter()
.map(|s| strings.from_id(s.name_id()).unwrap())
.collect();
assert!(names.contains(&"shared_func"));
assert!(names.contains(&"only_in_a"));
assert!(names.contains(&"only_in_b"));
}

#[test]
fn lookup() { let mappings = vec!(
new_map(0, 0, 1023, 1),
new_map(0, 2048, 3071, 3),
new_map(0, 1024, 2047, 2),
Expand Down
120 changes: 87 additions & 33 deletions one_collect/src/helpers/exporting/os/linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use crate::os::system_page_size;

use crate::ruwind::elf::*;
use crate::ruwind::{ModuleAccessor, UnwindType};
use symbols::{ElfSymbolReader, R2RLoadedLayoutSymbolTransformer, R2RMapSymbolReader};
use symbols::{ElfSymbolReader, GoPclnTabSymbolReader, R2RLoadedLayoutSymbolTransformer, R2RMapSymbolReader};
use self::symbols::PerfMapSymbolReader;

/* OS Specific Session Type */
Expand Down Expand Up @@ -68,7 +68,7 @@ trait ExportProcessLinuxExt {
bin_path: &str,
metadata: &ElfModuleMetadata,
sym_types_requested: u32,
strings: &InternedStrings) -> Vec<File>;
strings: &InternedStrings) -> Vec<(File, u32)>;

fn check_candidate_symbol_file(
&self,
Expand Down Expand Up @@ -173,27 +173,56 @@ impl ExportProcessLinuxExt for ExportProcess {
let sym_files = self.find_symbol_files(
filename,
metadata,
SYMBOL_TYPE_ELF_SYMTAB | SYMBOL_TYPE_ELF_DYNSYM,
SYMBOL_TYPE_ELF_SYMTAB | SYMBOL_TYPE_ELF_DYNSYM | SYMBOL_TYPE_GO_PCLNTAB,
strings);

if sym_files.is_empty() {
debug!("add_matching_elf_symbols: no symbol files found for file={}", filename);
}

for sym_file in sym_files {
// De-duplication across the ELF and Go passes below is handled
// transparently by add_matching_symbols (a function already added
// from one source is not re-added from another).
for (sym_file, sym_types) in sym_files {

// Page align the values from the load header.
let p_offset = metadata.p_offset() & page_mask;
let p_vaddr = metadata.p_vaddr() & page_mask;

let load_header = ElfLoadHeader::new(p_offset, p_vaddr);
let mut sym_reader = ElfSymbolReader::new(sym_file, load_header, page_size);
let has_elf = sym_types & (SYMBOL_TYPE_ELF_SYMTAB | SYMBOL_TYPE_ELF_DYNSYM) != 0;
let has_go = sym_types & SYMBOL_TYPE_GO_PCLNTAB != 0;

// Give each reader its own independent file handle. Only clone
// when both passes will run; otherwise move the handle directly.
let (elf_file, go_file): (Option<File>, Option<File>) = match (has_elf, has_go) {
(true, true) => match sym_file.try_clone() {
Ok(clone) => (Some(sym_file), Some(clone)),
Err(_) => (Some(sym_file), None),
},
(true, false) => (Some(sym_file), None),
(false, true) => (None, Some(sym_file)),
(false, false) => (None, None),
};

let map_mut = self.mappings_mut().get_mut(map_index).unwrap();

map_mut.add_matching_symbols(
frames,
&mut sym_reader,
strings);
// ELF symtab/dynsym pass.
if let Some(elf_file) = elf_file {
let load_header = ElfLoadHeader::new(p_offset, p_vaddr);
let mut sym_reader = ElfSymbolReader::new(elf_file, load_header, page_size);
map_mut.add_matching_symbols(frames, &mut sym_reader, strings);
}

// Go pclntab pass. Unioned with the ELF symbols above; pclntab
// fills functions the ELF symbol table lacks (or is the only
// source when the symbol table has been stripped). Functions
// shared with the ELF pass are dropped automatically.
if let Some(go_file) = go_file {
let load_header = ElfLoadHeader::new(p_offset, p_vaddr);
if let Some(mut go_reader) = GoPclnTabSymbolReader::new(go_file, load_header, page_size) {
map_mut.add_matching_symbols(frames, &mut go_reader, strings);
}
}
}
}
}
Expand All @@ -204,10 +233,31 @@ impl ExportProcessLinuxExt for ExportProcess {
bin_path: &str,
metadata: &ElfModuleMetadata,
sym_types_requested: u32,
strings: &InternedStrings) -> Vec<File> {
strings: &InternedStrings) -> Vec<(File, u32)> {
let mut symbol_files = Vec::new();
let mut sym_types_found = 0u32;

// ELF symbol tables (symtab/dynsym) can be split into separate debug
// files, so a still-missing ELF type justifies continuing to walk the
// .dbg/.debug/debug-link chain. The Go pclntab is only ever carried in
// the binary itself (the first candidate), so it never justifies
// continuing — this keeps non-Go binaries from incurring extra
// symbol-file lookups.
let searchable_requested = sym_types_requested
& (SYMBOL_TYPE_ELF_SYMTAB | SYMBOL_TYPE_ELF_DYNSYM);

// The search is complete once everything requested is found, or once
// every *searchable* requested type is found (any remaining requested
// type can't appear in a later file). The `searchable_requested != 0`
// guard prevents a request with no searchable types (e.g. Go-only) from
// stopping after an arbitrary first hit — it then stops only when the
// requested type is actually found.
let search_complete = |found: u32| -> bool {
(found & sym_types_requested) == sym_types_requested
|| (searchable_requested != 0
&& (found & searchable_requested) == searchable_requested)
};

// Keep evaluating symbol files until we find a matching one with a symtab.
let mut path_buf = PathBuf::new();

Expand All @@ -216,9 +266,9 @@ impl ExportProcessLinuxExt for ExportProcess {
if let Some((sym_file, types_found)) = self.check_candidate_symbol_file(
metadata.build_id(),
&path_buf) {
symbol_files.push(sym_file);
symbol_files.push((sym_file, types_found));
sym_types_found |= types_found;
if sym_types_found == sym_types_requested {
if search_complete(sym_types_found) {
return symbol_files
}
}
Expand All @@ -230,10 +280,10 @@ impl ExportProcessLinuxExt for ExportProcess {
if let Some((sym_file, types_found)) = self.check_candidate_symbol_file(
metadata.build_id(),
&path_buf) {
symbol_files.push(sym_file);
symbol_files.push((sym_file, types_found));
sym_types_found |= types_found;

if sym_types_found == sym_types_requested {
if search_complete(sym_types_found) {
return symbol_files
}
}
Expand All @@ -244,9 +294,9 @@ impl ExportProcessLinuxExt for ExportProcess {
if let Some((sym_file, types_found)) = self.check_candidate_symbol_file(
metadata.build_id(),
&path_buf) {
symbol_files.push(sym_file);
symbol_files.push((sym_file, types_found));
sym_types_found |= types_found;
if sym_types_found == sym_types_requested {
if search_complete(sym_types_found) {
return symbol_files
}
}
Expand All @@ -260,9 +310,9 @@ impl ExportProcessLinuxExt for ExportProcess {
if let Some((sym_file, types_found)) = self.check_candidate_symbol_file(
metadata.build_id(),
&path_buf) {
symbol_files.push(sym_file);
symbol_files.push((sym_file, types_found));
sym_types_found |= types_found;
if sym_types_found == sym_types_requested {
if search_complete(sym_types_found) {
return symbol_files
}
}
Expand All @@ -281,9 +331,9 @@ impl ExportProcessLinuxExt for ExportProcess {
if let Some((sym_file, types_found)) = self.check_candidate_symbol_file(
metadata.build_id(),
&path_buf) {
symbol_files.push(sym_file);
symbol_files.push((sym_file, types_found));
sym_types_found |= types_found;
if sym_types_found == sym_types_requested {
if search_complete(sym_types_found) {
return symbol_files
}
}
Expand All @@ -297,9 +347,9 @@ impl ExportProcessLinuxExt for ExportProcess {
if let Some((sym_file, types_found)) = self.check_candidate_symbol_file(
metadata.build_id(),
&path_buf) {
symbol_files.push(sym_file);
symbol_files.push((sym_file, types_found));
sym_types_found |= types_found;
if sym_types_found == sym_types_requested {
if search_complete(sym_types_found) {
return symbol_files
}
}
Expand All @@ -313,9 +363,9 @@ impl ExportProcessLinuxExt for ExportProcess {
if let Some((sym_file, types_found)) = self.check_candidate_symbol_file(
metadata.build_id(),
&path_buf) {
symbol_files.push(sym_file);
symbol_files.push((sym_file, types_found));
sym_types_found |= types_found;
if sym_types_found == sym_types_requested {
if search_complete(sym_types_found) {
return symbol_files
}
}
Expand All @@ -341,9 +391,9 @@ impl ExportProcessLinuxExt for ExportProcess {
if let Some((sym_file, types_found)) = self.check_candidate_symbol_file(
metadata.build_id(),
&path_buf) {
symbol_files.push(sym_file);
symbol_files.push((sym_file, types_found));
sym_types_found |= types_found;
if sym_types_found == sym_types_requested {
if search_complete(sym_types_found) {
return symbol_files
}
}
Expand All @@ -358,9 +408,9 @@ impl ExportProcessLinuxExt for ExportProcess {
if let Some((sym_file, types_found)) = self.check_candidate_symbol_file(
metadata.build_id(),
&path_buf) {
symbol_files.push(sym_file);
symbol_files.push((sym_file, types_found));
sym_types_found |= types_found;
if sym_types_found == sym_types_requested {
if search_complete(sym_types_found) {
return symbol_files
}
}
Expand All @@ -374,9 +424,9 @@ impl ExportProcessLinuxExt for ExportProcess {
if let Some((sym_file, types_found)) = self.check_candidate_symbol_file(
metadata.build_id(),
&path_buf) {
symbol_files.push(sym_file);
symbol_files.push((sym_file, types_found));
sym_types_found |= types_found;
if sym_types_found == sym_types_requested {
if search_complete(sym_types_found) {
return symbol_files
}
}
Expand All @@ -391,9 +441,9 @@ impl ExportProcessLinuxExt for ExportProcess {
if let Some((sym_file, types_found)) = self.check_candidate_symbol_file(
metadata.build_id(),
&path_buf) {
symbol_files.push(sym_file);
symbol_files.push((sym_file, types_found));
sym_types_found |= types_found;
if sym_types_found == sym_types_requested {
if search_complete(sym_types_found) {
return symbol_files
}
}
Expand Down Expand Up @@ -467,6 +517,10 @@ impl ExportProcessLinuxExt for ExportProcess {
sym_flags |= SYMBOL_TYPE_ELF_DYNSYM;
}

if has_go_pclntab(&mut reader) || has_go_build_info(&mut reader) {
sym_flags |= SYMBOL_TYPE_GO_PCLNTAB;
}

if sym_flags != 0 {
return Some((reader, sym_flags));
}
Expand Down
Loading
Loading