pub mod eval;
mod loader;
mod location;
mod symbol;
pub mod r#type;
pub mod unit;
pub mod unwind;
mod utils;
pub use self::unwind::DwarfUnwinder;
use crate::debugger::ExplorationContext;
use crate::debugger::address::{GlobalAddress, RelocatedAddress};
use crate::debugger::context::gcx;
use crate::debugger::debugee::dwarf::eval::AddressKind;
use crate::debugger::debugee::dwarf::symbol::SymbolTab;
use crate::debugger::debugee::dwarf::unit::die::{DerefContext, Die};
use crate::debugger::debugee::dwarf::unit::die_ref::{FatDieRef, Function, Variable};
use crate::debugger::debugee::dwarf::unit::{
BsUnit, DwarfUnitParser, FunctionInfo, PlaceDescriptorOwned,
};
use crate::debugger::debugee::dwarf::utils::PathSearchIndex;
use crate::debugger::debugee::{Debugee, Location};
use crate::debugger::error::Error;
use crate::debugger::error::Error::{DebugIDFormat, UnitNotFound};
use crate::debugger::register::{DwarfRegisterMap, RegisterMap};
use crate::{muted_error, resolve_unit_call, version_switch, weak_error};
use gimli::CfaRule::RegisterAndOffset;
use gimli::{
BaseAddresses, CfaRule, DebugAddr, DebugFrame, DebugInfoOffset, DebugPubTypes, Dwarf, EhFrame,
LocationLists, Range, Reader, RunTimeEndian, Section, UnitOffset, UnwindContext, UnwindSection,
UnwindTableRow,
};
use indexmap::IndexMap;
use log::debug;
use memmap2::Mmap;
use object::{Object, ObjectSection};
use rayon::prelude::*;
use regex::Regex;
use std::collections::{HashMap, HashSet};
use std::ops::Add;
use std::path::{Path, PathBuf};
use std::{fs, path};
pub use symbol::Symbol;
use trie_rs::Trie;
use unit::PlaceDescriptor;
use walkdir::WalkDir;
pub type EndianArcSlice = gimli::EndianArcSlice<gimli::RunTimeEndian>;
pub struct DebugInformation<R: gimli::Reader = EndianArcSlice> {
file: PathBuf,
inner: Dwarf<R>,
eh_frame: EhFrame<R>,
debug_frame: Option<DebugFrame<R>>,
bases: BaseAddresses,
units: Option<Vec<BsUnit>>,
symbol_table: Option<SymbolTab>,
pub_names: Option<Trie<u8>>,
pub_types: HashMap<String, (DebugInfoOffset, UnitOffset)>,
files_index: PathSearchIndex<(usize, Vec<usize>)>,
}
impl Clone for DebugInformation {
fn clone(&self) -> Self {
Self {
file: self.file.clone(),
inner: Dwarf {
debug_abbrev: self.inner.debug_abbrev.clone(),
debug_addr: self.inner.debug_addr.clone(),
debug_aranges: self.inner.debug_aranges.clone(),
debug_info: self.inner.debug_info.clone(),
debug_line: self.inner.debug_line.clone(),
debug_line_str: self.inner.debug_line_str.clone(),
debug_macro: self.inner.debug_macro.clone(),
debug_macinfo: self.inner.debug_macinfo.clone(),
debug_names: self.inner.debug_names.clone(),
debug_str: self.inner.debug_str.clone(),
debug_str_offsets: self.inner.debug_str_offsets.clone(),
debug_types: self.inner.debug_types.clone(),
locations: self.inner.locations.clone(),
ranges: self.inner.ranges.clone(),
file_type: self.inner.file_type,
sup: self.inner.sup.clone(),
abbreviations_cache: Default::default(),
},
eh_frame: self.eh_frame.clone(),
debug_frame: self.debug_frame.clone(),
bases: self.bases.clone(),
units: self
.units
.as_ref()
.map(|units| units.iter().map(|u| u.clone(self.dwarf())).collect()),
symbol_table: self.symbol_table.clone(),
pub_names: None,
pub_types: self.pub_types.clone(),
files_index: self.files_index.clone(),
}
}
}
#[macro_export]
macro_rules! debug_info_exists {
($expr: expr) => {
$expr.expect("unreachable: debug information must exists")
};
}
impl DebugInformation {
pub fn pathname(&self) -> &Path {
self.file.as_path()
}
pub fn locations(&self) -> &LocationLists<EndianArcSlice> {
&self.inner.locations
}
fn get_units(&self) -> Result<&[BsUnit], Error> {
self.units
.as_deref()
.ok_or(Error::NoDebugInformation("file"))
}
pub fn has_debug_info(&self) -> bool {
self.units.is_some()
}
pub fn unit_ensure(&self, idx: usize) -> &BsUnit {
&debug_info_exists!(self.get_units())[idx]
}
#[inline(always)]
pub fn unit_count(&self) -> usize {
self.units
.as_ref()
.map(|units| units.len())
.unwrap_or_default()
}
pub fn tpl_in_pub_names(&self, tpl: &str) -> Option<bool> {
debug_assert!(tpl.split("::").count() > 0);
let needle = tpl.split("::").last().expect("at least one exists");
self.pub_names.as_ref().map(|pub_names| {
let found = pub_names.predictive_search(needle);
!found.is_empty()
})
}
fn evaluate_cfa(
&self,
debugee: &Debugee,
registers: &DwarfRegisterMap,
utr: &UnwindTableRow<usize>,
ecx: &ExplorationContext,
) -> Result<RelocatedAddress, Error> {
let rule = utr.cfa();
match rule {
RegisterAndOffset { register, offset } => {
let ra = registers.value(*register)?;
Ok(RelocatedAddress::from(ra as usize).offset(*offset as isize))
}
CfaRule::Expression(expr) => {
let unit = debug_info_exists!(self.find_unit_by_pc(ecx.location().global_pc))
.ok_or(UnitNotFound(ecx.location().global_pc))?;
let evaluator =
resolve_unit_call!(&self.inner, unit, evaluator, debugee, self.dwarf());
let expr_result = evaluator.evaluate(ecx, expr.get(&self.eh_frame)?)?;
Ok((expr_result.into_scalar::<usize>(AddressKind::Value)?).into())
}
}
}
pub fn get_cfa(
&self,
debugee: &Debugee,
ecx: &ExplorationContext,
) -> Result<RelocatedAddress, Error> {
let mut ucx = Box::new(UnwindContext::new());
let row = self.eh_frame.unwind_info_for_address(
&self.bases,
&mut ucx,
ecx.location().global_pc.into(),
EhFrame::cie_from_offset,
)?;
self.evaluate_cfa(
debugee,
&DwarfRegisterMap::from(RegisterMap::current(ecx.pid_on_focus())?),
row,
ecx,
)
}
pub fn debug_addr(&self) -> &DebugAddr<EndianArcSlice> {
&self.inner.debug_addr
}
pub fn known_files(&self) -> Result<impl Iterator<Item = &PathBuf>, Error> {
Ok(self.get_units()?.iter().flat_map(|unit| unit.files()))
}
fn find_unit_by_pc(&self, pc: GlobalAddress) -> Result<Option<&BsUnit>, Error> {
Ok(self.get_units()?.iter().find(|&unit| {
match unit
.ranges()
.binary_search_by_key(&(pc.into()), |r| r.begin)
{
Ok(_) => true,
Err(pos) => unit.ranges()[..pos]
.iter()
.rev()
.any(|range| pc.in_range(range)),
}
}))
}
pub fn find_place_from_pc(
&self,
pc: GlobalAddress,
) -> Result<Option<PlaceDescriptor<'_>>, Error> {
let mb_unit = self.find_unit_by_pc(pc)?;
Ok(mb_unit.and_then(|u| u.find_place_by_pc(pc)))
}
pub fn find_exact_place_from_pc(
&self,
pc: GlobalAddress,
) -> Result<Option<PlaceDescriptor<'_>>, Error> {
let mb_unit = self.find_unit_by_pc(pc)?;
Ok(mb_unit.and_then(|u| u.find_exact_place_by_pc(pc)))
}
pub fn find_function_by_pc(
&'_ self,
pc: GlobalAddress,
) -> Result<Option<(FatDieRef<'_, Function>, &'_ FunctionInfo)>, Error> {
let mb_unit = self.find_unit_by_pc(pc)?;
Ok(mb_unit.and_then(|unit| {
let pc = u64::from(pc);
let die_ranges = resolve_unit_call!(self.dwarf(), unit, fn_ranges);
let find_pos = match die_ranges.binary_search_by_key(&pc, |dr| dr.range.begin) {
Ok(pos) => {
let mut idx = pos + 1;
while idx < die_ranges.len() && die_ranges[idx].range.begin == pc {
idx += 1;
}
idx
}
Err(pos) => pos,
};
die_ranges[..find_pos].iter().rev().find_map(|dr| {
let mb_fn_info = resolve_unit_call!(&self.inner, unit, fn_info, dr.die_off);
if let Some(fn_info) = mb_fn_info
&& dr.range.begin <= pc
&& pc < dr.range.end
{
return Some((FatDieRef::new_func(self, unit.idx(), dr.die_off), fn_info));
};
None
})
}))
}
pub fn search_functions(
&self,
template: &str,
) -> Result<Vec<(FatDieRef<'_, Function>, &FunctionInfo)>, Error> {
let units = self.get_units()?;
let result: Vec<_> = units
.par_iter()
.flat_map(|unit| {
let fn_infos = resolve_unit_call!(self.dwarf(), unit, search_functions, template);
fn_infos
.into_iter()
.map(|(offset, info)| (FatDieRef::new_func(self, unit.idx(), offset), info))
.collect::<Vec<_>>()
})
.collect();
Ok(result)
}
pub fn find_closest_place(
&self,
file_tpl: &str,
line: u64,
) -> Result<Vec<PlaceDescriptor<'_>>, Error> {
let files = self.files_index.get(file_tpl);
#[derive(PartialEq, Hash, Eq)]
struct Key {
name: Option<String>,
range: Box<[Range]>,
}
let mut unique_subprograms = HashSet::new();
let mut result = vec![];
let possible_lines = &[line, line + 1];
for &needle_line in possible_lines {
if !result.is_empty() {
break;
}
for (unit_idx, file_lines) in &files {
let unit = self.unit_ensure(*unit_idx);
let mut suitable_places_in_unit = vec![];
let mut i = 0;
while i < file_lines.len() {
let mut line_idx = file_lines[i];
let next_line_row = unit.line(line_idx);
if suitable_places_in_unit.is_empty() {
if next_line_row.line != needle_line || !next_line_row.is_stmt() {
i += 1;
continue;
}
let mut ahead_idx = i + 1;
loop {
let Some(&ahead_line_idx) = file_lines.get(ahead_idx) else {
break;
};
let line_row = unit.line(ahead_line_idx);
if line_row.line != next_line_row.line || !line_row.is_stmt() {
break;
}
if line_row.prolog_end() {
line_idx = ahead_line_idx;
i = ahead_idx;
break;
}
ahead_idx += 1;
}
if let Some(place) = unit.find_place_by_idx(line_idx) {
suitable_places_in_unit.push(place);
}
} else {
let line = suitable_places_in_unit[0].line_number;
let col = suitable_places_in_unit[0].column_number;
let pe = suitable_places_in_unit[0].prolog_end;
let eb = suitable_places_in_unit[0].epilog_begin;
let es = suitable_places_in_unit[0].end_sequence;
if next_line_row.line != line
|| next_line_row.column != col
|| next_line_row.prolog_end() != pe
|| next_line_row.epilog_begin() != eb
|| next_line_row.end_sequence() != es
|| !next_line_row.is_stmt()
{
i += 1;
continue;
}
if let Some(place) = unit.find_place_by_idx(line_idx) {
suitable_places_in_unit.push(place);
}
}
i += 1;
}
for suitable_place in suitable_places_in_unit {
if let Some((func, info)) = self.find_function_by_pc(suitable_place.address)? {
let key = Key {
name: info.name.clone(),
range: func.ranges(),
};
if !unique_subprograms.contains(&key) {
unique_subprograms.insert(key);
result.push(suitable_place);
}
} else {
result.push(suitable_place);
}
}
}
}
Ok(result)
}
pub fn find_places_in_line_range(
&self,
file_tpl: &str,
start_line: u64,
end_line: u64,
) -> Result<Vec<PlaceDescriptor<'_>>, Error> {
let files = self.files_index.get(file_tpl);
let (start_line, end_line) = if start_line <= end_line {
(start_line, end_line)
} else {
(end_line, start_line)
};
let mut result = Vec::new();
let mut seen = HashSet::new();
for (unit_idx, file_lines) in &files {
let unit = self.unit_ensure(*unit_idx);
for &line_idx in file_lines {
let line_row = unit.line(line_idx);
if !line_row.is_stmt() {
continue;
}
let line = line_row.line;
if line < start_line || line > end_line {
continue;
}
if let Some(place) = unit.find_place_by_idx(line_idx) {
let key = (place.address, place.line_number, place.column_number);
if seen.insert(key) {
result.push(place);
}
}
}
}
Ok(result)
}
pub fn search_places_for_fn_tpl(
&self,
template: &str,
) -> Result<Vec<PlaceDescriptorOwned>, Error> {
Ok(self
.search_functions(template)?
.into_iter()
.filter_map(|(fn_ref, _)| {
weak_error!(fn_ref.prolog_end_place()).map(|place| place.to_owned())
})
.collect())
}
pub fn find_symbols(&'_ self, regex: &Regex) -> Vec<Symbol<'_>> {
self.symbol_table
.as_ref()
.map(|table| table.find(regex))
.unwrap_or_default()
}
pub fn find_variables(
&self,
location: Location,
name: &str,
) -> Result<Vec<FatDieRef<'_, Variable>>, Error> {
let units = self.get_units()?;
let mut found = vec![];
for unit in units {
let mb_var_locations = resolve_unit_call!(self.dwarf(), unit, locate_var_die, name);
if let Some(vars) = mb_var_locations {
vars.iter().for_each(|(_, offset)| {
let fref = FatDieRef::new_no_hint(self, unit.idx(), *offset);
if let Some(die) = weak_error!(fref.deref())
&& die.tag() == gimli::DW_TAG_variable
{
let variable = fref.with_new_hint::<Variable>();
if variable.valid_at(location.global_pc) {
found.push(variable);
}
}
});
}
}
for unit in units {
let rustc_version = unit.rustc_version().unwrap_or_default();
let tls_ns_part = version_switch!(
rustc_version,
.. (1 . 80) => {
vec![name, "__getit"]
},
(1 . 80) .. => {
vec![name]
},
);
let tls_ns_part = tls_ns_part.expect("infallible: all rustc versions are covered");
let mut tls_collector = |(namespaces, offset): &(NamespaceHierarchy, UnitOffset)| {
if namespaces.contains(&tls_ns_part) {
let die_ref: FatDieRef<'_, _> =
FatDieRef::new_no_hint(self, unit.idx(), *offset);
if let Some(die) = weak_error!(die_ref.deref())
&& die.tag() == gimli::DW_TAG_variable
{
found.push(die_ref.with_new_hint::<Variable>());
}
}
};
if let Some(vars) = resolve_unit_call!(self.dwarf(), unit, locate_var_die, "__KEY") {
vars.iter().for_each(&mut tls_collector);
};
if let Some(vars) = resolve_unit_call!(self.dwarf(), unit, locate_var_die, "VAL") {
vars.iter().for_each(&mut tls_collector);
};
if let Some(vars) = resolve_unit_call!(
self.dwarf(),
unit,
locate_var_die,
"__RUST_STD_INTERNAL_VAL"
) {
vars.iter().for_each(&mut tls_collector);
};
}
Ok(found)
}
pub fn find_type_die_ref(&self, name: &str) -> Option<(DebugInfoOffset, UnitOffset)> {
if self.pub_types.is_empty() {
self.get_units().ok()?.iter().find_map(|u| {
u.offset().and_then(|u_offset| {
let type_ref_in_unit = resolve_unit_call!(&self.inner, u, locate_type, name)?;
Some((u_offset, type_ref_in_unit))
})
})
} else {
self.pub_types.get(name).copied()
}
}
pub fn find_type_die_ref_all(&self, name: &str) -> Vec<(DebugInfoOffset, UnitOffset)> {
self.get_units()
.unwrap_or_default()
.iter()
.filter_map(|u| {
u.offset().and_then(|u_offset| {
let type_ref_in_unit = resolve_unit_call!(&self.inner, u, locate_type, name)?;
Some((u_offset, type_ref_in_unit))
})
})
.collect()
}
#[inline(always)]
pub fn find_unit(&self, offset: DebugInfoOffset) -> Option<&BsUnit> {
let mb_unit = debug_info_exists!(self.get_units())
.binary_search_by_key(&Some(offset), |u| u.offset());
match mb_unit {
Ok(_) | Err(0) => None,
Err(pos) => Some(self.unit_ensure(pos - 1)),
}
}
pub fn dwarf(&self) -> &Dwarf<EndianArcSlice> {
&self.inner
}
pub fn range(&self) -> Option<Range> {
let units = self.get_units().ok()?;
let begin = units
.iter()
.filter_map(|u| u.ranges().first().map(|r| r.begin))
.min()?;
let end = units
.iter()
.map(|u| {
u.ranges().iter().fold(
begin,
|end, range| if range.end > end { range.end } else { end },
)
})
.max()?;
Some(Range { begin, end })
}
}
#[derive(Default)]
pub struct DebugInformationBuilder;
impl DebugInformationBuilder {
const DEBUG_FILES_DIR: &'static str = "/usr/lib/debug";
fn get_dwarf_from_separate_debug_file<'a, 'b, OBJ>(
&self,
obj_file: &'a OBJ,
) -> Result<Option<(PathBuf, Mmap)>, Error>
where
'a: 'b,
OBJ: Object<'a, 'b>,
{
let debug_id_sect = obj_file.section_by_name(".note.gnu.build-id");
if let Some(build_id) = debug_id_sect {
let data = build_id.data()?;
let note = &data[16..];
if note.len() < 2 {
return Err(DebugIDFormat);
}
let dir = format!("{:02x}", note[0]);
let file = note[1..]
.iter()
.map(|&b| format!("{b:02x}"))
.collect::<Vec<String>>()
.join("")
.add(".debug");
let path = PathBuf::from(Self::DEBUG_FILES_DIR)
.join(".build-id")
.join(dir)
.join(file);
let file = fs::File::open(path.as_path())?;
let mmap = unsafe { memmap2::Mmap::map(&file)? };
return Ok(Some((path, mmap)));
}
let debug_link_sect = obj_file.section_by_name(".gnu_debuglink");
if let Some(sect) = debug_link_sect {
let data = sect.data()?;
let data: Vec<u8> = data.iter().take_while(|&&b| b != 0).copied().collect();
let debug_link = std::str::from_utf8(&data)?;
for entry in WalkDir::new(Self::DEBUG_FILES_DIR)
.into_iter()
.filter_map(|e| e.ok())
{
let name = entry.file_name().to_string_lossy();
if name.contains(debug_link) {
let file = fs::File::open(entry.path())?;
let mmap = unsafe { memmap2::Mmap::map(&file)? };
return Ok(Some((entry.path().to_path_buf(), mmap)));
}
}
}
Ok(None)
}
pub fn build(&self, obj_path: &Path, file: &object::File) -> Result<DebugInformation, Error> {
let endian = if file.is_little_endian() {
RunTimeEndian::Little
} else {
RunTimeEndian::Big
};
let eh_frame = EhFrame::load(|id| -> Result<EndianArcSlice, Error> {
loader::load_section(id, file, endian)
})?;
let section_addr = |name: &str| -> Option<u64> {
file.sections().find_map(|section| {
if section.name().ok()? == name {
Some(section.address())
} else {
None
}
})
};
let mut bases = BaseAddresses::default();
if let Some(got) = section_addr(".got") {
bases = bases.set_got(got);
}
if let Some(text) = section_addr(".text") {
bases = bases.set_text(text);
}
if let Some(eh) = section_addr(".eh_frame") {
bases = bases.set_eh_frame(eh);
}
if let Some(eh_frame_hdr) = section_addr(".eh_frame_hdr") {
bases = bases.set_eh_frame_hdr(eh_frame_hdr);
}
let debug_split_file_data;
let debug_split_file;
let debug_info_file =
if let Ok(Some((path, debug_file))) = self.get_dwarf_from_separate_debug_file(file) {
debug!(target: "dwarf-loader", "{obj_path:?} has separate debug information file");
debug!(target: "dwarf-loader", "load debug information from {path:?}");
debug_split_file_data = debug_file;
debug_split_file = object::File::parse(&*debug_split_file_data)?;
&debug_split_file
} else {
debug!(target: "dwarf-loader", "load debug information from {obj_path:?}");
file
};
let dwarf = loader::load_par(debug_info_file, endian)?;
let debug_frame = if debug_info_file.section_by_name(".debug_frame").is_some() {
Some(DebugFrame::load(|id| -> Result<EndianArcSlice, Error> {
loader::load_section(id, debug_info_file, endian)
})?)
} else {
None
};
let symbol_table = SymbolTab::new(debug_info_file);
let pub_names = None;
let mb_pub_types_sect = muted_error!(DebugPubTypes::load(|id| {
loader::load_section(id, debug_info_file, endian)
}));
let pub_types = mb_pub_types_sect.and_then(|pub_types_sect| {
pub_types_sect
.items()
.map(|e| match e {
Ok(e) => {
let type_name = e.name().to_string_lossy()?.to_string();
let unit_offset = e.unit_header_offset();
Ok((type_name, (unit_offset, e.die_offset())))
}
Err(e) => Err(e),
})
.collect::<Result<_, _>>()
.ok()
});
let parser = DwarfUnitParser::new(&dwarf);
let headers = dwarf.units().collect::<Result<Vec<_>, _>>()?;
if headers.is_empty() {
debug!(target: "dwarf-loader", "no debug information for {obj_path:?}");
return Ok(DebugInformation {
file: obj_path.to_path_buf(),
inner: dwarf,
eh_frame,
debug_frame,
bases,
units: None,
symbol_table,
pub_names,
pub_types: pub_types.unwrap_or_default(),
files_index: PathSearchIndex::new(""),
});
}
let headers_len = headers.len();
let mut units = headers
.into_par_iter()
.map(|header| -> gimli::Result<BsUnit> {
let unit = parser.parse(header)?;
Ok(unit)
})
.collect::<gimli::Result<Vec<_>>>()?;
debug_assert!(units.capacity() == headers_len);
units.sort_unstable_by_key(|u| u.offset());
units.iter_mut().enumerate().for_each(|(i, u)| u.set_idx(i));
let mut files_index = PathSearchIndex::new(path::MAIN_SEPARATOR_STR);
units.iter().for_each(|unit| {
unit.file_path_with_lines_pairs()
.for_each(|(file_path, lines)| {
files_index.insert(file_path, (unit.idx(), lines));
});
});
files_index.shrink_to_fit();
Ok(DebugInformation {
file: obj_path.to_path_buf(),
inner: dwarf,
eh_frame,
debug_frame,
bases,
units: Some(units),
symbol_table,
pub_names,
pub_types: pub_types.unwrap_or_default(),
files_index,
})
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct NamespaceHierarchy(Vec<string_interner::DefaultSymbol>);
impl NamespaceHierarchy {
pub fn new(parts: impl IntoIterator<Item = impl AsRef<str>>) -> Self {
let inner = parts
.into_iter()
.map(|s| gcx().with_interner(|i| i.get_or_intern(s)));
Self(inner.collect())
}
pub fn as_parts(&self) -> Vec<String> {
self.0
.iter()
.map(|s| {
gcx().with_interner(|i| {
i.resolve(*s)
.expect("symbol should be resolved")
.to_string()
})
})
.collect()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn for_die(
dcx: DerefContext,
die_offset: gimli::UnitOffset,
parent_index: &IndexMap<UnitOffset, UnitOffset>,
) -> Self {
let mut ns_chain = vec![];
let mut p_idx = parent_index.get(&die_offset).copied();
let mut next_parent = || -> Option<_> {
let parent = weak_error!(Die::new(dcx.clone(), p_idx?))?;
p_idx = parent_index.get(&parent.offset()).copied();
Some(parent)
};
use gimli::DW_TAG_namespace as NS_TAG;
while let Some((NS_TAG, next_die)) = next_parent().map(|die| (die.tag(), die)) {
ns_chain.push(next_die.name().unwrap_or_default());
}
ns_chain.reverse();
NamespaceHierarchy::new(ns_chain)
}
pub fn contains(&self, needle: &[&str]) -> bool {
let needle_symbols = needle
.iter()
.map(|n| gcx().with_interner(|i| i.get_or_intern(n)))
.collect::<Vec<_>>();
self.0
.windows(needle.len())
.any(|slice| slice == needle_symbols)
}
#[inline(always)]
pub fn from_mangled(linkage_name: &str) -> (Self, String) {
let demangled = rustc_demangle::demangle(linkage_name);
let demangled = format!("{demangled:#}");
let mut parts: Vec<_> = demangled.split("::").map(ToString::to_string).collect();
debug_assert!(!parts.is_empty());
let fn_name = parts.pop().expect("function name must exists");
(NamespaceHierarchy::new(parts), fn_name)
}
}
#[cfg(test)]
mod test {
use crate::debugger::debugee::dwarf::NamespaceHierarchy;
#[test]
fn test_namespace_from_mangled() {
struct TestCase {
mangled: &'static str,
expected_ns: Vec<String>,
expected_fn: &'static str,
}
let test_cases = vec![
TestCase {
mangled: "_ZN5tokio7runtime4task3raw7RawTask4poll17h7b89afb116da4cf2E",
expected_ns: vec![
"tokio".to_string(),
"runtime".to_string(),
"task".to_string(),
"raw".to_string(),
"RawTask".to_string(),
],
expected_fn: "poll",
},
TestCase {
mangled: "poll",
expected_ns: vec![],
expected_fn: "poll",
},
];
for tc in test_cases {
let (ns, name) = NamespaceHierarchy::from_mangled(tc.mangled);
assert_eq!(ns.as_parts(), tc.expected_ns);
assert_eq!(name, tc.expected_fn);
}
}
}