use alloc::boxed::Box;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::cmp;
use crate::lazy::LazyResult;
use crate::{
Context, DebugFile, Error, Function, Functions, LazyFunctions, LazyLines,
LineLocationRangeIter, Lines, Location, LookupContinuation, LookupResult, RangeAttributes,
SimpleLookup, SplitDwarfLoad,
};
pub(crate) struct UnitRange {
unit_id: usize,
min_begin: u64,
range: gimli::Range,
}
pub(crate) struct ResUnit<R: gimli::Reader> {
offset: gimli::DebugInfoOffset<R::Offset>,
dw_unit: gimli::Unit<R>,
pub(crate) lang: Option<gimli::DwLang>,
lines: LazyLines,
functions: LazyFunctions<R>,
dwo: LazyResult<Option<Box<DwoUnit<R>>>>,
}
type UnitRef<'unit, R> = (DebugFile, gimli::UnitRef<'unit, R>);
impl<R: gimli::Reader> ResUnit<R> {
pub(crate) fn unit_ref<'a>(&'a self, sections: &'a gimli::Dwarf<R>) -> gimli::UnitRef<'a, R> {
gimli::UnitRef::new(sections, &self.dw_unit)
}
pub(crate) fn dwarf_and_unit<'unit, 'ctx: 'unit>(
&'unit self,
ctx: &'ctx Context<R>,
) -> LookupResult<
SimpleLookup<
Result<UnitRef<'unit, R>, Error>,
R,
impl FnOnce(Option<Arc<gimli::Dwarf<R>>>) -> Result<UnitRef<'unit, R>, Error>,
>,
> {
let map_dwo = move |dwo: &'unit Result<Option<Box<DwoUnit<R>>>, Error>| match dwo {
Ok(Some(dwo)) => Ok((DebugFile::Dwo, dwo.unit_ref())),
Ok(None) => Ok((DebugFile::Primary, self.unit_ref(&*ctx.sections))),
Err(e) => Err(*e),
};
let complete = |dwo| SimpleLookup::new_complete(map_dwo(dwo));
if let Some(dwo) = self.dwo.borrow() {
return complete(dwo);
}
let dwo_id = match self.dw_unit.dwo_id {
None => {
return complete(self.dwo.borrow_with(|| Ok(None)));
}
Some(dwo_id) => dwo_id,
};
let comp_dir = self.dw_unit.comp_dir.clone();
let dwo_name = self.dw_unit.dwo_name().and_then(|s| {
if let Some(s) = s {
Ok(Some(ctx.sections.attr_string(&self.dw_unit, s)?))
} else {
Ok(None)
}
});
let path = match dwo_name {
Ok(v) => v,
Err(e) => {
return complete(self.dwo.borrow_with(|| Err(e)));
}
};
let process_dwo = move |dwo_dwarf: Option<Arc<gimli::Dwarf<R>>>| {
let dwo_dwarf = match dwo_dwarf {
None => return Ok(None),
Some(dwo_dwarf) => dwo_dwarf,
};
let mut dwo_units = dwo_dwarf.units();
let dwo_header = match dwo_units.next()? {
Some(dwo_header) => dwo_header,
None => return Ok(None),
};
let mut dwo_unit = dwo_dwarf.unit(dwo_header)?;
dwo_unit.copy_relocated_attributes(&self.dw_unit);
Ok(Some(Box::new(DwoUnit {
sections: dwo_dwarf,
dw_unit: dwo_unit,
})))
};
SimpleLookup::new_needs_load(
SplitDwarfLoad {
dwo_id,
comp_dir,
path,
parent: ctx.sections.clone(),
},
move |dwo_dwarf| map_dwo(self.dwo.borrow_with(|| process_dwo(dwo_dwarf))),
)
}
pub(crate) fn parse_lines(&self, sections: &gimli::Dwarf<R>) -> Result<Option<&Lines>, Error> {
let ilnp = match self.dw_unit.line_program {
Some(ref ilnp) => ilnp,
None => return Ok(None),
};
self.lines.borrow(self.unit_ref(sections), ilnp).map(Some)
}
pub(crate) fn parse_functions<'unit, 'ctx: 'unit>(
&'unit self,
ctx: &'ctx Context<R>,
) -> LookupResult<impl LookupContinuation<Output = Result<&'unit Functions<R>, Error>, Buf = R>>
{
self.dwarf_and_unit(ctx).map(move |r| {
let (_file, unit) = r?;
self.functions.borrow(unit)
})
}
pub(crate) fn parse_inlined_functions<'unit, 'ctx: 'unit>(
&'unit self,
ctx: &'ctx Context<R>,
) -> LookupResult<impl LookupContinuation<Output = Result<(), Error>, Buf = R> + 'unit> {
self.dwarf_and_unit(ctx).map(move |r| {
let (file, unit) = r?;
self.functions
.borrow(unit)?
.parse_inlined_functions(file, unit, ctx)
})
}
pub(crate) fn find_location(
&self,
probe: u64,
sections: &gimli::Dwarf<R>,
) -> Result<Option<Location<'_>>, Error> {
let Some(lines) = self.parse_lines(sections)? else {
return Ok(None);
};
lines.find_location(probe)
}
#[inline]
pub(crate) fn find_location_range(
&self,
probe_low: u64,
probe_high: u64,
sections: &gimli::Dwarf<R>,
) -> Result<Option<LineLocationRangeIter<'_>>, Error> {
let Some(lines) = self.parse_lines(sections)? else {
return Ok(None);
};
lines.find_location_range(probe_low, probe_high).map(Some)
}
pub(crate) fn find_function_or_location<'unit, 'ctx: 'unit>(
&'unit self,
probe: u64,
ctx: &'ctx Context<R>,
) -> LookupResult<
impl LookupContinuation<
Output = Result<(Option<&'unit Function<R>>, Option<Location<'unit>>), Error>,
Buf = R,
>,
> {
self.dwarf_and_unit(ctx).map(move |r| {
let (file, unit) = r?;
let functions = self.functions.borrow(unit)?;
let function = match functions.find_address(probe) {
Some(address) => {
let function_index = functions.addresses[address].function;
let function = &functions.functions[function_index];
Some(function.borrow(file, unit, ctx)?)
}
None => None,
};
let location = self.find_location(probe, unit.dwarf)?;
Ok((function, location))
})
}
}
pub(crate) struct ResUnits<R: gimli::Reader> {
ranges: Box<[UnitRange]>,
units: Box<[ResUnit<R>]>,
}
impl<R: gimli::Reader> ResUnits<R> {
pub(crate) fn parse(sections: &gimli::Dwarf<R>) -> Result<Self, Error> {
let mut aranges = Vec::new();
let mut headers = sections.debug_aranges.headers();
while let Some(header) = headers.next()? {
aranges.push((header.debug_info_offset(), header.offset()));
}
aranges.sort_by_key(|i| i.0);
let mut unit_ranges = Vec::new();
let mut res_units = Vec::new();
let mut units = sections.units();
while let Some(header) = units.next()? {
let unit_id = res_units.len();
let offset = match header.offset().as_debug_info_offset() {
Some(offset) => offset,
None => continue,
};
let mut need_unit_range = match header.type_() {
gimli::UnitType::Type { .. } | gimli::UnitType::SplitType { .. } => continue,
gimli::UnitType::Partial => {
false
}
_ => true,
};
let dw_unit = match sections.unit(header) {
Ok(dw_unit) => dw_unit,
Err(_) => continue,
};
let dw_unit_ref = gimli::UnitRef::new(sections, &dw_unit);
let mut lang = None;
if need_unit_range {
let mut entries = dw_unit_ref.entries_raw(None)?;
let abbrev = match entries.read_abbreviation()? {
Some(abbrev) => abbrev,
None => continue,
};
let mut ranges = RangeAttributes::default();
for spec in abbrev.attributes() {
let attr = entries.read_attribute(*spec)?;
match attr.name() {
gimli::DW_AT_low_pc => match attr.value() {
gimli::AttributeValue::Addr(val) => ranges.low_pc = Some(val),
gimli::AttributeValue::DebugAddrIndex(index) => {
ranges.low_pc = Some(dw_unit_ref.address(index)?);
}
_ => {}
},
gimli::DW_AT_high_pc => match attr.value() {
gimli::AttributeValue::Addr(val) => ranges.high_pc = Some(val),
gimli::AttributeValue::DebugAddrIndex(index) => {
ranges.high_pc = Some(dw_unit_ref.address(index)?);
}
gimli::AttributeValue::Udata(val) => ranges.size = Some(val),
_ => {}
},
gimli::DW_AT_ranges => {
ranges.ranges_offset = dw_unit_ref.attr_ranges_offset(attr.value())?;
}
gimli::DW_AT_language => {
if let gimli::AttributeValue::Language(val) = attr.value() {
lang = Some(val);
}
}
_ => {}
}
}
let i = match ranges.ranges_offset {
Some(_) => None,
None => aranges.binary_search_by_key(&offset, |x| x.0).ok(),
};
if let Some(mut i) = i {
while i > 0 && aranges[i - 1].0 == offset {
i -= 1;
}
for (_, aranges_offset) in aranges[i..].iter().take_while(|x| x.0 == offset) {
let aranges_header = sections.debug_aranges.header(*aranges_offset)?;
let mut aranges = aranges_header.entries();
while let Some(arange) = aranges.next()? {
if arange.length() != 0 {
unit_ranges.push(UnitRange {
range: arange.range(),
unit_id,
min_begin: 0,
});
need_unit_range = false;
}
}
}
} else {
need_unit_range &= !ranges.for_each_range(dw_unit_ref, |range| {
unit_ranges.push(UnitRange {
range,
unit_id,
min_begin: 0,
});
})?;
}
}
let lines = LazyLines::new();
if need_unit_range {
if let Some(ref ilnp) = dw_unit_ref.line_program {
if let Ok(lines) = lines.borrow(dw_unit_ref, ilnp) {
for range in lines.ranges() {
unit_ranges.push(UnitRange {
range,
unit_id,
min_begin: 0,
})
}
}
}
}
res_units.push(ResUnit {
offset,
dw_unit,
lang,
lines,
functions: LazyFunctions::new(),
dwo: LazyResult::new(),
});
}
unit_ranges.sort_by_key(|i| i.range.end);
let mut min = !0;
for i in unit_ranges.iter_mut().rev() {
min = min.min(i.range.begin);
i.min_begin = min;
}
Ok(ResUnits {
ranges: unit_ranges.into_boxed_slice(),
units: res_units.into_boxed_slice(),
})
}
pub(crate) fn iter(&self) -> impl Iterator<Item = &ResUnit<R>> {
self.units.iter()
}
pub(crate) fn find_offset(
&self,
offset: gimli::DebugInfoOffset<R::Offset>,
) -> Result<&gimli::Unit<R>, Error> {
match self
.units
.binary_search_by_key(&offset.0, |unit| unit.offset.0)
{
Ok(_) | Err(0) => Err(gimli::Error::NoEntryAtGivenOffset),
Err(i) => Ok(&self.units[i - 1].dw_unit),
}
}
pub(crate) fn find(&self, probe: u64) -> impl Iterator<Item = &ResUnit<R>> {
self.find_range(probe, probe + 1).map(|(unit, _range)| unit)
}
#[inline]
pub(crate) fn find_range(
&self,
probe_low: u64,
probe_high: u64,
) -> impl Iterator<Item = (&ResUnit<R>, &gimli::Range)> {
let pos = match self
.ranges
.binary_search_by_key(&probe_low, |i| i.range.end)
{
Ok(i) => i + 1, Err(i) => i, };
self.ranges[pos..]
.iter()
.take_while(move |i| {
debug_assert!(i.range.end >= probe_low);
probe_high > i.min_begin
})
.filter_map(move |i| {
if probe_low >= i.range.end || probe_high <= i.range.begin {
return None;
}
Some((&self.units[i.unit_id], &i.range))
})
}
pub(crate) fn find_location_range<'a>(
&'a self,
probe_low: u64,
probe_high: u64,
sections: &'a gimli::Dwarf<R>,
) -> Result<LocationRangeIter<'a, R>, Error> {
let unit_iter = Box::new(self.find_range(probe_low, probe_high));
Ok(LocationRangeIter {
unit_iter,
iter: None,
probe_low,
probe_high,
sections,
})
}
}
struct DwoUnit<R: gimli::Reader> {
sections: Arc<gimli::Dwarf<R>>,
dw_unit: gimli::Unit<R>,
}
impl<R: gimli::Reader> DwoUnit<R> {
fn unit_ref(&self) -> gimli::UnitRef<R> {
gimli::UnitRef::new(&self.sections, &self.dw_unit)
}
}
pub(crate) struct SupUnit<R: gimli::Reader> {
offset: gimli::DebugInfoOffset<R::Offset>,
dw_unit: gimli::Unit<R>,
}
pub(crate) struct SupUnits<R: gimli::Reader> {
units: Box<[SupUnit<R>]>,
}
impl<R: gimli::Reader> Default for SupUnits<R> {
fn default() -> Self {
SupUnits {
units: Box::default(),
}
}
}
impl<R: gimli::Reader> SupUnits<R> {
pub(crate) fn parse(sections: &gimli::Dwarf<R>) -> Result<Self, Error> {
let mut sup_units = Vec::new();
let mut units = sections.units();
while let Some(header) = units.next()? {
let offset = match header.offset().as_debug_info_offset() {
Some(offset) => offset,
None => continue,
};
let dw_unit = match sections.unit(header) {
Ok(dw_unit) => dw_unit,
Err(_) => continue,
};
sup_units.push(SupUnit { dw_unit, offset });
}
Ok(SupUnits {
units: sup_units.into_boxed_slice(),
})
}
pub(crate) fn find_offset(
&self,
offset: gimli::DebugInfoOffset<R::Offset>,
) -> Result<&gimli::Unit<R>, Error> {
match self
.units
.binary_search_by_key(&offset.0, |unit| unit.offset.0)
{
Ok(_) | Err(0) => Err(gimli::Error::NoEntryAtGivenOffset),
Err(i) => Ok(&self.units[i - 1].dw_unit),
}
}
}
pub struct LocationRangeIter<'ctx, R: gimli::Reader> {
unit_iter: Box<dyn Iterator<Item = (&'ctx ResUnit<R>, &'ctx gimli::Range)> + 'ctx>,
iter: Option<LineLocationRangeIter<'ctx>>,
probe_low: u64,
probe_high: u64,
sections: &'ctx gimli::Dwarf<R>,
}
impl<'ctx, R: gimli::Reader> LocationRangeIter<'ctx, R> {
fn next_loc(&mut self) -> Result<Option<(u64, u64, Location<'ctx>)>, Error> {
loop {
let iter = self.iter.take();
match iter {
None => match self.unit_iter.next() {
Some((unit, range)) => {
self.iter = unit.find_location_range(
cmp::max(self.probe_low, range.begin),
cmp::min(self.probe_high, range.end),
self.sections,
)?;
}
None => return Ok(None),
},
Some(mut iter) => {
if let item @ Some(_) = iter.next() {
self.iter = Some(iter);
return Ok(item);
}
}
}
}
}
}
impl<'ctx, R> Iterator for LocationRangeIter<'ctx, R>
where
R: gimli::Reader + 'ctx,
{
type Item = (u64, u64, Location<'ctx>);
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.next_loc().unwrap_or_default()
}
}
#[cfg(feature = "fallible-iterator")]
impl<'ctx, R> fallible_iterator::FallibleIterator for LocationRangeIter<'ctx, R>
where
R: gimli::Reader + 'ctx,
{
type Item = (u64, u64, Location<'ctx>);
type Error = Error;
#[inline]
fn next(&mut self) -> Result<Option<Self::Item>, Self::Error> {
self.next_loc()
}
}