gsym-rs 0.1.5

Pure-Rust reader, writer, and Linux ELF/DWARF converter for LLVM GSYM
Documentation
use std::borrow::Cow;
use std::collections::{HashMap, HashSet};

use gimli::{AttributeValue, DebuggingInformationEntry, Reader, Unit};

use super::lines::intern_header_files;
use super::{file_index_attribute, gimli_error, unsigned_attribute};
use crate::convert::ConversionWarning;
use crate::model::{FileIndex, LineEntry};
use crate::{Error, GsymBuilder, Result};

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
enum DebugSource {
    Main,
    Supplementary,
}

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub(super) struct DieKey {
    source: DebugSource,
    offset: usize,
}

pub(super) fn resolve_declaration_line<R: Reader<Offset = usize>>(
    dwarf: &gimli::Dwarf<R>,
    unit: &Unit<R>,
    entry: &DebuggingInformationEntry<R>,
    files: &HashMap<u64, FileIndex>,
    builder: &mut GsymBuilder,
    warnings: &mut Vec<ConversionWarning>,
) -> Result<Option<LineEntry>> {
    DeclarationResolver::new(builder, warnings).resolve(
        dwarf,
        unit,
        entry,
        files,
        DebugSource::Main,
        0,
    )
}

struct DeclarationResolver<'a> {
    builder: &'a mut GsymBuilder,
    warnings: &'a mut Vec<ConversionWarning>,
    visited: HashSet<DieKey>,
}

impl<'a> DeclarationResolver<'a> {
    fn new(builder: &'a mut GsymBuilder, warnings: &'a mut Vec<ConversionWarning>) -> Self {
        Self {
            builder,
            warnings,
            visited: HashSet::new(),
        }
    }

    fn resolve<R: Reader<Offset = usize>>(
        &mut self,
        dwarf: &gimli::Dwarf<R>,
        unit: &Unit<R>,
        entry: &DebuggingInformationEntry<R>,
        files: &HashMap<u64, FileIndex>,
        source: DebugSource,
        depth: u8,
    ) -> Result<Option<LineEntry>> {
        if depth >= 64 {
            return Ok(None);
        }
        if let (Some(file_index), Some(line_number)) = (
            file_index_attribute(entry, gimli::constants::DW_AT_decl_file),
            unsigned_attribute(entry, gimli::constants::DW_AT_decl_line),
        ) {
            match (files.get(&file_index).copied(), u32::try_from(line_number)) {
                (Some(file), Ok(line)) => {
                    return Ok(Some(LineEntry {
                        address: 0,
                        file,
                        line,
                    }));
                }
                (None, _) => self
                    .warnings
                    .push(ConversionWarning::InvalidDeclarationFile {
                        die_offset: absolute_entry_offset(unit, entry.offset())? as u64,
                        index: file_index,
                    }),
                (_, Err(_)) => self
                    .warnings
                    .push(ConversionWarning::InvalidDeclarationLine {
                        die_offset: absolute_entry_offset(unit, entry.offset())? as u64,
                        line: line_number,
                    }),
            }
        }
        for attribute in [
            gimli::constants::DW_AT_abstract_origin,
            gimli::constants::DW_AT_specification,
        ] {
            match entry.attr_value(attribute) {
                Some(AttributeValue::UnitRef(offset)) => {
                    let key = DieKey {
                        source,
                        offset: absolute_entry_offset(unit, offset)?,
                    };
                    if self.visited.insert(key) {
                        let referenced = unit.entry(offset).map_err(gimli_error)?;
                        if let Some(line) = self.resolve(
                            dwarf,
                            unit,
                            &referenced,
                            files,
                            source,
                            depth.saturating_add(1),
                        )? {
                            return Ok(Some(line));
                        }
                    }
                }
                Some(AttributeValue::DebugInfoRef(offset)) => {
                    if let Some(line) = self.resolve_absolute(
                        dwarf,
                        offset.0,
                        DebugSource::Main,
                        depth.saturating_add(1),
                    )? {
                        return Ok(Some(line));
                    }
                }
                Some(AttributeValue::DebugInfoRefSup(offset)) => {
                    if let Some(sup) = dwarf.sup()
                        && let Some(line) = self.resolve_absolute(
                            sup,
                            offset.0,
                            DebugSource::Supplementary,
                            depth.saturating_add(1),
                        )?
                    {
                        return Ok(Some(line));
                    }
                }
                _ => {}
            }
        }
        Ok(None)
    }

    fn resolve_absolute<R: Reader<Offset = usize>>(
        &mut self,
        dwarf: &gimli::Dwarf<R>,
        target: usize,
        source: DebugSource,
        depth: u8,
    ) -> Result<Option<LineEntry>> {
        if !self.visited.insert(DieKey {
            source,
            offset: target,
        }) {
            return Ok(None);
        }
        let Some((unit, offset)) = unit_containing_offset(dwarf, target)? else {
            return Ok(None);
        };
        let referenced = unit.entry(offset).map_err(gimli_error)?;
        let files = unit.line_program.as_ref().map_or_else(
            || Ok(HashMap::new()),
            |program| intern_header_files(dwarf, &unit, program.header(), self.builder),
        )?;
        self.resolve(dwarf, &unit, &referenced, &files, source, depth)
    }
}

pub(super) fn resolve_name<R: Reader<Offset = usize>>(
    dwarf: &gimli::Dwarf<R>,
    unit: &Unit<R>,
    entry: &DebuggingInformationEntry<R>,
    depth: u8,
) -> Result<Option<Vec<u8>>> {
    let mut visited = HashSet::new();
    resolve_name_inner(dwarf, unit, entry, DebugSource::Main, depth, &mut visited)
}

fn resolve_name_inner<R: Reader<Offset = usize>>(
    dwarf: &gimli::Dwarf<R>,
    unit: &Unit<R>,
    entry: &DebuggingInformationEntry<R>,
    source: DebugSource,
    depth: u8,
    visited: &mut HashSet<DieKey>,
) -> Result<Option<Vec<u8>>> {
    if depth >= 64 {
        return Ok(None);
    }
    for attribute in [
        gimli::constants::DW_AT_linkage_name,
        gimli::constants::DW_AT_MIPS_linkage_name,
        gimli::constants::DW_AT_name,
    ] {
        if let Some(value) = entry.attr_value(attribute) {
            let bytes = attribute_bytes(dwarf, unit, value)?;
            if !bytes.is_empty() {
                return Ok(Some(bytes));
            }
        }
    }
    for attribute in [
        gimli::constants::DW_AT_abstract_origin,
        gimli::constants::DW_AT_specification,
    ] {
        match entry.attr_value(attribute) {
            Some(AttributeValue::UnitRef(offset)) => {
                let key = DieKey {
                    source,
                    offset: absolute_entry_offset(unit, offset)?,
                };
                if visited.insert(key) {
                    let referenced = unit.entry(offset).map_err(gimli_error)?;
                    if let Some(name) = resolve_name_inner(
                        dwarf,
                        unit,
                        &referenced,
                        source,
                        depth.saturating_add(1),
                        visited,
                    )? {
                        return Ok(Some(name));
                    }
                }
            }
            Some(AttributeValue::DebugInfoRef(offset)) => {
                if let Some(name) = resolve_absolute_name(
                    dwarf,
                    offset.0,
                    DebugSource::Main,
                    depth.saturating_add(1),
                    visited,
                )? {
                    return Ok(Some(name));
                }
            }
            Some(AttributeValue::DebugInfoRefSup(offset)) => {
                if let Some(sup) = dwarf.sup()
                    && let Some(name) = resolve_absolute_name(
                        sup,
                        offset.0,
                        DebugSource::Supplementary,
                        depth.saturating_add(1),
                        visited,
                    )?
                {
                    return Ok(Some(name));
                }
            }
            _ => {}
        }
    }
    Ok(None)
}

pub(super) fn resolve_reference_name<R: Reader<Offset = usize>>(
    dwarf: &gimli::Dwarf<R>,
    unit: &Unit<R>,
    reference: &AttributeValue<R>,
    depth: u8,
    visited: &mut HashSet<DieKey>,
) -> Result<Option<Vec<u8>>> {
    let next = depth.saturating_add(1);
    if let AttributeValue::UnitRef(offset) = reference {
        let key = DieKey {
            source: DebugSource::Main,
            offset: absolute_entry_offset(unit, *offset)?,
        };
        if !visited.insert(key) {
            return Ok(None);
        }
        let entry = unit.entry(*offset).map_err(gimli_error)?;
        resolve_name_inner(dwarf, unit, &entry, DebugSource::Main, next, visited)
    } else if let AttributeValue::DebugInfoRef(offset) = reference {
        resolve_absolute_name(dwarf, offset.0, DebugSource::Main, next, visited)
    } else if let AttributeValue::DebugInfoRefSup(offset) = reference {
        dwarf.sup().map_or(Ok(None), |sup| {
            resolve_absolute_name(sup, offset.0, DebugSource::Supplementary, next, visited)
        })
    } else {
        Ok(None)
    }
}

pub(super) fn absolute_entry_offset<R: Reader<Offset = usize>>(
    unit: &Unit<R>,
    offset: gimli::UnitOffset<usize>,
) -> Result<usize> {
    unit.header
        .debug_info_offset()
        .and_then(|base| base.0.checked_add(offset.0))
        .ok_or(Error::Overflow("DWARF entry offset"))
}

fn resolve_absolute_name<R: Reader<Offset = usize>>(
    dwarf: &gimli::Dwarf<R>,
    target: usize,
    source: DebugSource,
    depth: u8,
    visited: &mut HashSet<DieKey>,
) -> Result<Option<Vec<u8>>> {
    if !visited.insert(DieKey {
        source,
        offset: target,
    }) {
        return Ok(None);
    }
    let Some((unit, offset)) = unit_containing_offset(dwarf, target)? else {
        return Ok(None);
    };
    let referenced = unit.entry(offset).map_err(gimli_error)?;
    resolve_name_inner(dwarf, &unit, &referenced, source, depth, visited)
}

fn unit_containing_offset<R: Reader<Offset = usize>>(
    dwarf: &gimli::Dwarf<R>,
    target: usize,
) -> Result<Option<(Unit<R>, gimli::UnitOffset<usize>)>> {
    let mut units = dwarf.units();
    while let Some(header) = units.next().map_err(gimli_error)? {
        let Some(start) = header.debug_info_offset().map(|offset| offset.0) else {
            continue;
        };
        let end = start
            .checked_add(header.length_including_self())
            .ok_or(Error::Overflow("DWARF unit end"))?;
        if start <= target && target < end {
            return Ok(Some((
                dwarf.unit(header).map_err(gimli_error)?,
                gimli::UnitOffset(target.saturating_sub(start)),
            )));
        }
    }
    Ok(None)
}

pub(super) fn attribute_bytes<R: Reader<Offset = usize>>(
    dwarf: &gimli::Dwarf<R>,
    unit: &Unit<R>,
    value: AttributeValue<R>,
) -> Result<Vec<u8>> {
    if matches!(value, AttributeValue::DebugStrRefSup(_)) && dwarf.sup().is_none() {
        return Ok(Vec::new());
    }
    let value = dwarf.attr_string(unit, value).map_err(gimli_error)?;
    value.to_slice().map(Cow::into_owned).map_err(gimli_error)
}