rudy_dwarf/die/
mod.rs

1//! Core DWARF entity types and their navigation methods
2
3pub(crate) mod cu;
4pub(crate) mod navigation;
5pub(crate) mod unit;
6pub(crate) mod utils;
7
8use std::fmt;
9
10use anyhow::Context;
11pub(crate) use cu::CompilationUnitId;
12use gimli::UnitSectionOffset;
13pub(crate) use unit::UnitRef;
14pub(crate) use utils::{file_entry_to_path, get_unit_ref_attr, parse_die_string_attribute};
15
16use crate::{
17    die::utils::pretty_print_die_entry,
18    file::{
19        loader::{DwarfReader, Offset, RawDie},
20        DebugFile, SourceFile, SourceLocation,
21    },
22    DwarfDb,
23};
24
25/// References a specific DWARF debugging information entry
26#[derive(Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd, salsa::Update)]
27pub struct Die {
28    pub(crate) file: DebugFile,
29    pub(crate) cu_offset: UnitSectionOffset<usize>,
30    pub(crate) die_offset: Offset,
31}
32
33impl fmt::Debug for Die {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        salsa::with_attached_database(|db| write!(f, "Die {}", self.location(db)))
36            .unwrap_or_else(|| write!(f, "Die at {:?} {:#010x}", self.file, self.offset()))
37    }
38}
39
40impl Die {
41    pub(crate) fn new(
42        file: DebugFile,
43        cu_offset: UnitSectionOffset<usize>,
44        die_offset: Offset,
45    ) -> Self {
46        Self {
47            file,
48            cu_offset,
49            die_offset,
50        }
51    }
52}
53
54struct DieLocation {
55    path: String,
56    die_offset: usize,
57}
58
59pub struct DieAccessError {
60    inner: anyhow::Error,
61    location: DieLocation,
62}
63
64trait ResExt {
65    type V;
66    fn into_die_result(self, db: &dyn DwarfDb, die: &Die) -> Result<Self::V>;
67}
68
69impl<V> ResExt for anyhow::Result<V> {
70    type V = V;
71    fn into_die_result(self, db: &dyn DwarfDb, die: &Die) -> Result<V> {
72        self.map_err(|e| die.make_error(db, e))
73    }
74}
75
76type Result<T> = std::result::Result<T, DieAccessError>;
77
78impl fmt::Debug for DieAccessError {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        write!(f, "{self}")
81    }
82}
83
84impl fmt::Display for DieAccessError {
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        let Self {
87            inner,
88            location: DieLocation { path, die_offset },
89        } = self;
90        write!(
91            f,
92            "Die access error at {path} {die_offset:#010x}: {inner:?}",
93        )
94    }
95}
96
97impl std::error::Error for DieAccessError {
98    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
99        self.inner.source()
100    }
101
102    fn cause(&self) -> Option<&dyn std::error::Error> {
103        Some(self.inner.as_ref())
104    }
105}
106
107impl Die {
108    // GROUP 1: Core Identity (Keep - no dependencies)
109
110    pub(crate) fn cu(&self) -> CompilationUnitId {
111        CompilationUnitId::new(self.file, self.cu_offset)
112    }
113
114    // GROUP 2: High-Cohesion Navigation + Basic Attributes (Keep - used together 90% of time)
115    pub(crate) fn children(&self, db: &dyn DwarfDb) -> Result<Vec<Die>> {
116        let mut children = vec![];
117
118        let unit_ref = self.unit_ref(db)?;
119
120        let mut tree = unit_ref
121            .entries_tree(Some(self.die_offset))
122            .context("Failed to get children nodes")
123            .into_die_result(db, self)?;
124        let tree_root = tree
125            .root()
126            .context("Failed to get children nodes")
127            .into_die_result(db, self)?;
128
129        let mut child_nodes = tree_root.children();
130
131        while let Some(child) = child_nodes
132            .next()
133            .context("Failed to parse child nodes")
134            .into_die_result(db, self)?
135        {
136            let child_offset = child.entry().offset();
137            let child_die = Die::new(self.file, self.cu_offset, child_offset);
138            children.push(child_die);
139        }
140
141        Ok(children)
142    }
143
144    pub(crate) fn make_error<E: Into<anyhow::Error>>(
145        &self,
146        db: &dyn DwarfDb,
147        error: E,
148    ) -> DieAccessError {
149        DieAccessError {
150            inner: error.into(),
151            location: DieLocation {
152                path: self.file.name(db),
153                die_offset: self.die_offset.0,
154            },
155        }
156    }
157
158    /// Get the offset of this DIE within the entire debug file
159    pub(crate) fn offset(&self) -> usize {
160        self.cu_offset.as_debug_info_offset().unwrap().0 + self.die_offset.0
161    }
162
163    pub fn location(&self, db: &dyn salsa::Database) -> String {
164        format!("{} {:#010x}", self.file.name(db), self.offset())
165    }
166
167    pub(crate) fn format_with_location<T: AsRef<str>>(
168        &self,
169        db: &dyn DwarfDb,
170        message: T,
171    ) -> String {
172        format!(
173            "{} for {} {:#010x}",
174            message.as_ref(),
175            self.file.name(db),
176            self.offset(),
177        )
178    }
179
180    pub(crate) fn get_referenced_entry(&self, db: &dyn DwarfDb, attr: gimli::DwAt) -> Result<Die> {
181        self.with_entry_and_unit(db, |entry, _| {
182            get_unit_ref_attr(entry, attr)
183                .map(|unit_offset| Die::new(self.file, self.cu_offset, unit_offset))
184                .into_die_result(db, self)
185        })?
186    }
187
188    pub(crate) fn tag(&self, db: &dyn DwarfDb) -> gimli::DwTag {
189        self.with_entry(db, |entry| entry.tag())
190            .unwrap_or(gimli::DW_TAG_null)
191    }
192
193    pub fn name(&self, db: &dyn DwarfDb) -> Result<String> {
194        self.string_attr(db, gimli::DW_AT_name)
195    }
196
197    // GROUP 3: Attribute Access (Keep - building blocks for other operations)
198
199    pub(crate) fn get_member(&self, db: &dyn DwarfDb, name: &str) -> Result<Die> {
200        self.children(db)?
201            .into_iter()
202            .find(|child| child.name(db).is_ok_and(|n| n == name))
203            .with_context(|| format!("Failed to find member `{name}`"))
204            .into_die_result(db, self)
205    }
206
207    pub(crate) fn get_member_by_tag(&self, db: &dyn DwarfDb, tag: gimli::DwTag) -> Result<Die> {
208        self.children(db)?
209            .into_iter()
210            .find(|child| child.tag(db) == tag)
211            .with_context(|| format!("Failed to find member with tag `{tag:?}`"))
212            .into_die_result(db, self)
213    }
214
215    pub(crate) fn get_udata_member_attribute(
216        &self,
217        db: &dyn DwarfDb,
218        name: &str,
219        attr: gimli::DwAt,
220    ) -> Result<usize> {
221        self.get_member(db, name)?.udata_attr(db, attr)
222    }
223
224    pub(crate) fn get_generic_type_entry(&self, db: &dyn DwarfDb, name: &str) -> Result<Die> {
225        self.children(db)?
226            .into_iter()
227            .find(|child| {
228                child.tag(db) == gimli::DW_TAG_template_type_parameter
229                    && child.name(db).is_ok_and(|n| n == name)
230            })
231            .with_context(|| format!("Failed to find generic type entry `{name}`"))
232            .into_die_result(db, self)
233            .and_then(|member| member.get_referenced_entry(db, gimli::DW_AT_type))
234    }
235
236    pub(crate) fn get_attr(
237        &self,
238        db: &dyn DwarfDb,
239        attr: gimli::DwAt,
240    ) -> Result<gimli::AttributeValue<DwarfReader>> {
241        Ok(self
242            .with_entry(db, |entry| entry.attr(attr))?
243            .with_context(|| format!("error fetching attribute {attr}"))
244            .into_die_result(db, self)?
245            .with_context(|| format!("attribute {attr} not found"))
246            .into_die_result(db, self)?
247            .value())
248    }
249
250    pub(crate) fn string_attr(&self, db: &dyn DwarfDb, attr: gimli::DwAt) -> Result<String> {
251        self.with_entry_and_unit(db, |entry, unit_ref| {
252            parse_die_string_attribute(entry, attr, unit_ref).into_die_result(db, self)
253        })?
254    }
255
256    pub(crate) fn udata_attr(&self, db: &dyn DwarfDb, attr: gimli::DwAt) -> Result<usize> {
257        let v = self.get_attr(db, attr)?;
258
259        v.udata_value()
260            .with_context(|| format!("attr {attr} is not a udata value, got: {v:?}"))
261            .map(|v| v as usize)
262            .into_die_result(db, self)
263    }
264
265    pub(crate) fn print(&self, db: &dyn DwarfDb) -> String {
266        self.with_entry_and_unit(db, |entry, unit_ref| {
267            self.format_with_location(db, pretty_print_die_entry(entry, unit_ref))
268        })
269        .unwrap_or_else(|e| {
270            tracing::error!("Failed to print DIE entry: {e}");
271            "entry not found".to_string()
272        })
273    }
274
275    // GROUP 5: Low-Level Access (Make private - implementation details)
276
277    pub(super) fn with_entry_and_unit<F: FnOnce(&RawDie<'_>, &UnitRef<'_>) -> T, T>(
278        &self,
279        db: &dyn DwarfDb,
280        f: F,
281    ) -> Result<T> {
282        let unit_ref = self.unit_ref(db)?;
283        let entry = self.entry(db, &unit_ref)?;
284        Ok(f(&entry, &unit_ref))
285    }
286
287    pub(crate) fn unit_ref<'db>(&self, db: &'db dyn DwarfDb) -> Result<UnitRef<'db>> {
288        self.cu()
289            .unit_ref(db)
290            .context("Failed to get unit reference")
291            .into_die_result(db, self)
292    }
293
294    fn with_entry<F: FnOnce(&RawDie<'_>) -> T, T>(&self, db: &dyn DwarfDb, f: F) -> Result<T> {
295        let unit_ref = self.unit_ref(db)?;
296        let entry = self.entry(db, &unit_ref)?;
297        Ok(f(&entry))
298    }
299
300    fn entry<'a>(&self, db: &dyn DwarfDb, unit_ref: &'a UnitRef<'_>) -> Result<RawDie<'a>> {
301        unit_ref
302            .entry(self.die_offset)
303            .context("Failed to get DIE entry")
304            .into_die_result(db, self)
305    }
306}
307
308/// Get the position for a DIE entry
309pub(crate) fn position(db: &dyn DwarfDb, entry: Die) -> Result<Option<SourceLocation>> {
310    let Ok(decl_file_attr) = entry.get_attr(db, gimli::DW_AT_decl_file) else {
311        return Ok(None);
312    };
313    let Ok(decl_line) = entry.udata_attr(db, gimli::DW_AT_decl_line) else {
314        return Ok(None);
315    };
316    let gimli::AttributeValue::FileIndex(file_idx) = decl_file_attr else {
317        return Err(entry.make_error(
318            db,
319            anyhow::anyhow!("Expected DW_AT_decl_file to be a FileIndex, got: {decl_file_attr:?}"),
320        ));
321    };
322
323    let unit_ref = entry.unit_ref(db)?;
324
325    // Get the file from the line program
326    let Some(line_program) = unit_ref.line_program.clone() else {
327        return Err(entry.make_error(db, anyhow::anyhow!("Failed to parse line program")));
328    };
329    let header = line_program.header();
330    let Some(file) = header.file(file_idx) else {
331        return Err(entry.make_error(
332            db,
333            anyhow::anyhow!("Failed to parse file index: {:#?}", header.file_names()),
334        ));
335    };
336
337    let Some(path) = file_entry_to_path(db, file, &unit_ref) else {
338        return Err(entry.make_error(db, anyhow::anyhow!("Failed to convert file entry to path")));
339    };
340
341    let source_file = SourceFile::new(path);
342    Ok(Some(SourceLocation::new(
343        source_file,
344        decl_line as u64,
345        None,
346    )))
347}