rudy-dwarf 0.2.0

DWARF debug information parsing and querying for Rust debugging tools
Documentation
//! DWARF parsing utilities and helper functions

use std::path::PathBuf;

use anyhow::{Context, Result};
use gimli::Reader;
use itertools::Itertools;

use super::unit::UnitRef;
use crate::file::{
    loader::{Dwarf, DwarfReader, Offset, RawDie},
    File,
};

pub fn get_dwarf(db: &dyn crate::DwarfDb, file: File) -> Option<&Dwarf> {
    let loaded = crate::file::load(db, file).as_ref().ok()?;
    Some(&loaded.dwarf)
}

pub fn get_string_attr_raw<'a>(
    die: &RawDie<'a>,
    attr: gimli::DwAt,
    unit_ref: &UnitRef<'a>,
) -> Result<Option<DwarfReader>> {
    let attr = die.attr(attr)?;
    let Some(attr) = attr else { return Ok(None) };
    let value = attr.value();
    let value = unit_ref.attr_string(value)?;
    Ok(Some(value))
}

pub fn get_lang_attr<'a>(
    die: &RawDie<'a>,
    _unit_ref: &UnitRef<'a>,
) -> Result<Option<gimli::DwLang>> {
    let attr = die.attr(gimli::DW_AT_language)?;
    let Some(attr) = attr else { return Ok(None) };
    let value = attr.value();
    let gimli::AttributeValue::Language(lang) = value else {
        return Ok(None);
    };
    Ok(Some(lang))
}

pub fn get_string_attr<'a>(
    die: &RawDie<'a>,
    attr: gimli::DwAt,
    unit_ref: &UnitRef<'a>,
) -> Result<Option<String>> {
    let value = get_string_attr_raw(die, attr, unit_ref)?;
    let Some(value) = value else { return Ok(None) };
    let value = value.to_string()?;
    Ok(Some(value.into_owned()))
}

pub fn get_unit_ref_attr<'a>(die: &RawDie<'a>, attr: gimli::DwAt) -> Result<Offset> {
    let value = die
        .attr_value(attr)
        .with_context(|| format!("Failed to get `{attr}` attribute"))?
        .with_context(|| format!("attribute `{attr}` not found"))?;
    if let gimli::AttributeValue::UnitRef(offset) = value {
        Ok(offset)
    } else {
        Err(anyhow::anyhow!(
            "Expected UnitRef attribute for {:#x}, got {value:?}",
            die.offset().0,
        ))
    }
}

pub fn parse_die_string_attribute<'a>(
    die: &RawDie<'a>,
    attr: gimli::DwAt,
    unit_ref: &UnitRef<'a>,
) -> Result<String> {
    let attr = die
        .attr(attr)?
        .with_context(|| format!("Failed to get string attribute `{attr}`"))?;
    let value = attr.value();
    let value = unit_ref.attr_string(value)?;
    Ok(value.to_string()?.into_owned())
}

pub fn file_entry_to_path<'db>(
    db: &'db dyn crate::DwarfDb,
    f: &gimli::FileEntry<DwarfReader>,
    unit_ref: &UnitRef<'db>,
) -> Option<PathBuf> {
    let lp = unit_ref
        .line_program
        .as_ref()
        .expect("Line program should be present");
    let header = lp.header();
    let dir = f
        .directory(header)
        .and_then(|d| unit_ref.attr_string(d).ok());
    let dir = dir.as_ref().and_then(|d| d.to_string().ok());
    let path = unit_ref
        .attr_string(f.path_name())
        .inspect_err(|e| {
            tracing::debug!("Failed to convert path to string: {e}");
        })
        .ok()?;
    let path = path
        .to_string()
        .inspect_err(|e| {
            tracing::debug!("Failed to convert path to string: {e}");
        })
        .ok()?;

    let path = if let Some(d) = dir {
        if !d.starts_with("/") {
            // this is a relative path, so we need to prepend the current working directory
            let compilation_dir = unit_ref.comp_dir.as_ref().map_or_else(
                || "/".to_string(),
                |d| {
                    d.to_string()
                        .ok()
                        .map_or_else(|| "/".to_string(), |d| d.into_owned())
                },
            );
            format!("{compilation_dir}/{d}/{path}")
        } else {
            format!("{d}/{path}")
        }
    } else {
        path.to_string()
    };

    // adjust for source map
    let path = PathBuf::from(path);
    Some(db.remap_path(&path))
}

pub fn to_range(mut iter: gimli::RangeIter<DwarfReader>) -> Result<Option<(u64, u64)>> {
    let mut lowest_pc = None;
    let mut highest_pc = None;

    while let Some(range) = iter.next()? {
        if lowest_pc.is_none_or(|lowest_pc| range.begin < lowest_pc) {
            lowest_pc = Some(range.begin);
        }
        if highest_pc.is_none_or(|highest_pc| range.end > highest_pc) {
            highest_pc = Some(range.end);
        }
    }
    Ok(lowest_pc.zip(highest_pc))
}

const MAX_DIE_ATTR_LENGTH: usize = 512;

pub fn pretty_print_die_entry(die: &RawDie<'_>, unit_ref: &UnitRef<'_>) -> String {
    let mut attrs = die.attrs();
    let attrs_iter = std::iter::from_fn(|| attrs.next().ok().flatten());
    let attrs = attrs_iter
        .map(|a| {
            let name = a.name();
            let value = a.value();

            if let Ok(v) = unit_ref.attr_string(a.value()) {
                format!(
                    "{name}: {}",
                    v.to_string_lossy()
                        .map_or_else(|_| format!("{value:?}"), |v| v.into_owned())
                )
            } else {
                let string_value = format!("{value:?}");
                if string_value.len() > MAX_DIE_ATTR_LENGTH {
                    format!("{name}: {}..", &string_value[..MAX_DIE_ATTR_LENGTH])
                } else {
                    format!("{name}: {string_value}")
                }
            }
        })
        .join(",\n\t");
    format!("{} {{\n\t{}\n}}", die.tag(), attrs)
}