rudy_dwarf/parser/
primitives.rs

1//! DWARF-specific primitive parsers and utilities
2
3use std::convert::Infallible;
4
5use anyhow::Context as _;
6
7use super::{Parser, Result};
8use crate::{
9    die::utils::get_string_attr, file::DwarfReader, types::DieTypeDefinition, Die, DwarfDb,
10};
11
12/// Parser for getting offset values
13pub fn data_offset() -> DataOffset {
14    DataOffset
15}
16
17pub struct DataOffset;
18
19impl Parser<usize> for DataOffset {
20    fn parse(&self, db: &dyn DwarfDb, entry: Die) -> Result<usize> {
21        Ok(entry.udata_attr(db, gimli::DW_AT_data_member_location)?)
22    }
23}
24
25/// Generic attribute parser
26pub fn attr<T>(attr: gimli::DwAt) -> Attr<T> {
27    Attr::new(attr)
28}
29
30pub fn entry_type() -> Attr<Die> {
31    Attr::new(gimli::DW_AT_type)
32}
33
34pub struct Attr<T> {
35    attr: gimli::DwAt,
36    _marker: std::marker::PhantomData<T>,
37}
38
39impl<T> Attr<T> {
40    pub fn new(attr: gimli::DwAt) -> Self {
41        Attr {
42            attr,
43            _marker: std::marker::PhantomData,
44        }
45    }
46}
47
48impl Parser<usize> for Attr<usize> {
49    fn parse(&self, db: &dyn DwarfDb, entry: Die) -> Result<usize> {
50        Ok(entry.udata_attr(db, self.attr)?)
51    }
52}
53
54impl Parser<u64> for Attr<u64> {
55    fn parse(&self, db: &dyn DwarfDb, entry: Die) -> Result<u64> {
56        let value = entry.get_attr(db, self.attr)?;
57        if let Some(v) = value.udata_value() {
58            Ok(v)
59        } else if let Some(v) = value.sdata_value() {
60            if v < 0 {
61                return Err(anyhow::anyhow!(
62                    "Value {} is negative, cannot fit in u64",
63                    v
64                ));
65            }
66            Ok(v as u64)
67        } else {
68            Err(anyhow::anyhow!(
69                "Expected {} to be a signed or unsigned data attribute, found: {value:?}",
70                self.attr,
71            ))
72        }
73    }
74}
75
76impl Parser<i64> for Attr<i64> {
77    fn parse(&self, db: &dyn DwarfDb, entry: Die) -> Result<i64> {
78        let value = entry.get_attr(db, self.attr)?;
79        if let Some(v) = value.udata_value() {
80            if v > i64::MAX as u64 {
81                return Err(anyhow::anyhow!("Value {} exceeds i64 maximum", v));
82            }
83            Ok(v as i64)
84        } else if let Some(v) = value.sdata_value() {
85            Ok(v)
86        } else {
87            Err(anyhow::anyhow!(
88                "Expected {} to be a signed or unsigned data attribute, found: {value:?}",
89                self.attr,
90            ))
91        }
92    }
93}
94
95impl Parser<i128> for Attr<i128> {
96    fn parse(&self, db: &dyn DwarfDb, entry: Die) -> Result<i128> {
97        let value = entry.get_attr(db, self.attr)?;
98        if let Some(v) = value.udata_value() {
99            Ok(v as i128)
100        } else if let Some(v) = value.sdata_value() {
101            Ok(v as i128)
102        } else {
103            Err(anyhow::anyhow!(
104                "Expected {} to be a signed or unsigned data attribute, found: {value:?}",
105                self.attr,
106            ))
107        }
108    }
109}
110
111impl Parser<String> for Attr<String> {
112    fn parse(&self, db: &dyn DwarfDb, entry: Die) -> Result<String> {
113        Ok(entry.string_attr(db, self.attr)?)
114    }
115}
116
117impl Parser<Option<String>> for Attr<Option<String>> {
118    fn parse(&self, db: &dyn DwarfDb, entry: Die) -> Result<Option<String>> {
119        entry.with_entry_and_unit(db, |entry, unit_ref| {
120            get_string_attr(entry, self.attr, unit_ref)
121        })?
122    }
123}
124
125impl Parser<gimli::AttributeValue<DwarfReader>> for Attr<gimli::AttributeValue<DwarfReader>> {
126    fn parse(&self, db: &dyn DwarfDb, entry: Die) -> Result<gimli::AttributeValue<DwarfReader>> {
127        Ok(entry.get_attr(db, self.attr)?)
128    }
129}
130
131impl Parser<gimli::DwLang> for Attr<gimli::DwLang> {
132    fn parse(&self, db: &dyn DwarfDb, entry: Die) -> Result<gimli::DwLang> {
133        let value = entry.get_attr(db, self.attr)?;
134        if let gimli::AttributeValue::Language(lang) = value {
135            Ok(lang)
136        } else {
137            Err(anyhow::anyhow!(
138                "Expected {} to be a language attribute, found: {value:?}",
139                self.attr,
140            ))
141        }
142    }
143}
144
145impl Parser<Die> for Attr<Die> {
146    fn parse(&self, db: &dyn DwarfDb, entry: Die) -> Result<Die> {
147        Ok(entry.get_referenced_entry(db, self.attr)?)
148    }
149}
150
151/// Parser that gets an optional attribute
152pub struct OptionalAttr<T> {
153    attr: gimli::DwAt,
154    _marker: std::marker::PhantomData<T>,
155}
156
157impl<T> Parser<Option<T>> for OptionalAttr<T>
158where
159    Attr<T>: Parser<T>,
160{
161    fn parse(&self, db: &dyn DwarfDb, entry: Die) -> Result<Option<T>> {
162        Ok(attr(self.attr).parse(db, entry).ok())
163    }
164}
165
166pub fn optional_attr<T>(attr: gimli::DwAt) -> OptionalAttr<T> {
167    OptionalAttr {
168        attr,
169        _marker: std::marker::PhantomData,
170    }
171}
172
173/// Find a member by name and return its Die
174pub fn member(name: &str) -> Member {
175    Member {
176        name: name.to_string(),
177    }
178}
179
180pub struct Member {
181    name: String,
182}
183
184impl Parser<Die> for Member {
185    fn parse(&self, db: &dyn DwarfDb, entry: Die) -> Result<Die> {
186        Ok(entry.get_member(db, &self.name)?)
187    }
188}
189
190/// Check if current entry has expected name and return it
191pub struct IsMember {
192    pub(super) expected_name: String,
193}
194
195impl Parser<Die> for IsMember {
196    fn parse(&self, db: &dyn DwarfDb, entry: Die) -> Result<Die> {
197        let entry_name = entry.name(db).context("Failed to get entry name")?;
198        if entry_name == self.expected_name {
199            Ok(entry)
200        } else {
201            Err(anyhow::anyhow!(
202                "Expected field '{}', found '{entry_name}'",
203                self.expected_name,
204            ))
205        }
206    }
207}
208
209pub fn is_member(name: &str) -> IsMember {
210    IsMember {
211        expected_name: name.to_string(),
212    }
213}
214
215/// Check if current entry has expected name and get its offset
216pub struct IsMemberOffset {
217    expected_name: String,
218}
219
220impl Parser<usize> for IsMemberOffset {
221    fn parse(&self, db: &dyn DwarfDb, entry: Die) -> Result<usize> {
222        let entry_name = entry
223            .name(db)
224            .map_err(|e| crate::Error::from(anyhow::anyhow!("Failed to get entry name: {}", e)))?;
225        if entry_name == self.expected_name {
226            entry
227                .udata_attr(db, gimli::DW_AT_data_member_location)
228                .with_context(|| format!("Failed to get offset for field '{}'", self.expected_name))
229        } else {
230            Err(anyhow::anyhow!(
231                "Expected field '{}', found '{entry_name}'",
232                self.expected_name,
233            ))
234        }
235    }
236}
237
238pub fn is_member_offset(name: &str) -> IsMemberOffset {
239    IsMemberOffset {
240        expected_name: name.to_string(),
241    }
242}
243
244/// Find a member by tag and return its Die
245pub fn member_by_tag(tag: gimli::DwTag) -> MemberByTag {
246    MemberByTag { tag }
247}
248
249pub struct MemberByTag {
250    tag: gimli::DwTag,
251}
252
253impl Parser<Die> for MemberByTag {
254    fn parse(&self, db: &dyn DwarfDb, entry: Die) -> Result<Die> {
255        entry
256            .get_member_by_tag(db, self.tag)
257            .with_context(|| format!("Failed to find member with tag '{}'", self.tag,))
258    }
259}
260
261/// Check if current entry has expected tag and return it
262pub fn is_member_tag(tag: gimli::DwTag) -> IsMemberTag {
263    IsMemberTag { expected_tag: tag }
264}
265
266pub struct IsMemberTag {
267    expected_tag: gimli::DwTag,
268}
269
270impl Parser<Die> for IsMemberTag {
271    fn parse(&self, db: &dyn DwarfDb, entry: Die) -> Result<Die> {
272        let entry_tag = entry.tag(db);
273        if entry_tag == self.expected_tag {
274            Ok(entry)
275        } else {
276            Err(anyhow::anyhow!(
277                "Expected entry to have tag '{}', found '{entry_tag}'",
278                self.expected_tag,
279            ))
280        }
281    }
282}
283
284/// Check if current entry matches expected generic name
285pub struct Generic {
286    expected_name: String,
287}
288
289impl Parser<Die> for Generic {
290    fn parse(&self, db: &dyn DwarfDb, entry: Die) -> Result<Die> {
291        if entry.tag(db) != gimli::DW_TAG_template_type_parameter {
292            return Err(anyhow::anyhow!(
293                "Expected generic type parameter, found tag {}",
294                entry.tag(db)
295            ));
296        }
297        let entry_name = entry
298            .name(db)
299            .map_err(|e| crate::Error::from(anyhow::anyhow!("Failed to get entry name: {}", e)))?;
300        if entry_name == self.expected_name {
301            Ok(entry.get_referenced_entry(db, gimli::DW_AT_type)?)
302        } else {
303            Err(anyhow::anyhow!(
304                "Expected generic '{}', found '{entry_name}'",
305                self.expected_name,
306            ))
307        }
308    }
309}
310
311pub fn generic(name: &str) -> Generic {
312    Generic {
313        expected_name: name.to_string(),
314    }
315}
316
317pub fn resolved_generic(name: &str) -> impl Parser<DieTypeDefinition> {
318    generic(name).then(resolve_type_shallow())
319}
320
321/// Combinator that resolves a Die into a type definition
322pub struct ResolveType;
323
324impl Parser<DieTypeDefinition> for ResolveType {
325    fn parse(&self, db: &dyn DwarfDb, entry: Die) -> Result<DieTypeDefinition> {
326        Ok(crate::types::resolve_type_offset(db, entry)?)
327    }
328}
329
330/// Parser that does nothing, just returns the entry as is
331pub fn identity() -> Identity {
332    Identity
333}
334
335pub struct Identity;
336
337impl Parser<Die> for Identity {
338    fn parse(&self, _db: &dyn DwarfDb, entry: Die) -> Result<Die> {
339        Ok(entry)
340    }
341}
342
343#[allow(dead_code)]
344pub fn resolve_type() -> ResolveType {
345    ResolveType
346}
347
348/// Combinator that resolves a Die into a type definition (shallow)
349pub struct ResolveTypeShallow;
350
351impl Parser<DieTypeDefinition> for ResolveTypeShallow {
352    fn parse(&self, db: &dyn DwarfDb, entry: Die) -> Result<DieTypeDefinition> {
353        Ok(crate::types::shallow_resolve_type(db, entry)?)
354    }
355}
356
357pub fn resolve_type_shallow() -> ResolveTypeShallow {
358    ResolveTypeShallow
359}
360
361/// A parser that follows a chain of fields, accumulating offsets
362pub struct FieldPath {
363    path: Vec<String>,
364}
365
366impl FieldPath {
367    pub fn new(path: Vec<String>) -> Self {
368        Self { path }
369    }
370}
371
372impl Parser<(Die, usize)> for FieldPath {
373    fn parse(&self, db: &dyn DwarfDb, mut entry: Die) -> Result<(Die, usize)> {
374        let mut path_iter = self.path.iter();
375
376        let mut offset = 0;
377        // Start with the first field
378        let Some(first_field) = path_iter.next() else {
379            // or error?
380            return Ok((entry, 0));
381        };
382
383        if &entry.name(db)? != first_field {
384            return Err(anyhow::anyhow!(
385                "Expected entry name '{}', found '{}'",
386                first_field,
387                entry.name(db)?
388            ));
389        }
390        offset += entry
391            .udata_attr(db, gimli::DW_AT_data_member_location)
392            .with_context(|| format!("Failed to get offset for field '{first_field}'"))?;
393        entry = entry
394            .get_referenced_entry(db, gimli::DW_AT_type)
395            .with_context(|| format!("Failed to resolve type for field '{first_field}'"))?;
396        for field_name in path_iter {
397            entry = entry
398                .get_member(db, field_name)
399                .with_context(|| format!("Failed to navigate to field '{field_name}'"))?;
400            offset += entry
401                .udata_attr(db, gimli::DW_AT_data_member_location)
402                .with_context(|| format!("Failed to get offset for field '{field_name}'"))?;
403            entry = entry
404                .get_referenced_entry(db, gimli::DW_AT_type)
405                .with_context(|| format!("Failed to resolve type for field '{field_name}'"))?;
406        }
407
408        Ok((entry, offset))
409    }
410}
411
412/// Parse a field path and return the final offset
413pub fn field_path_offset(path: Vec<&str>) -> impl Parser<usize> {
414    FieldPath::new(path.into_iter().map(|s| s.to_string()).collect()).map(|(_, offset)| offset)
415}
416
417pub fn rust_cu() -> impl Parser<bool> {
418    attr(gimli::DW_AT_language).map(|lang| matches!(lang, gimli::DW_LANG_Rust))
419}
420
421pub fn name() -> impl Parser<Option<String>> {
422    attr(gimli::DW_AT_name)
423}
424
425pub fn tag() -> impl Parser<gimli::DwTag> {
426    super::from_fn(|db: &dyn DwarfDb, entry: Die| Ok::<_, Infallible>(entry.tag(db)))
427}
428
429pub fn offset() -> impl Parser<usize> {
430    super::from_fn(|_: &dyn DwarfDb, entry: Die| Ok::<_, Infallible>(entry.offset()))
431}