struct_audit/dwarf/
types.rs

1use crate::error::{Error, Result};
2use crate::loader::DwarfSlice;
3use gimli::{AttributeValue, Dwarf, Unit, UnitOffset};
4use std::collections::HashMap;
5
6pub struct TypeResolver<'a, 'b> {
7    dwarf: &'b Dwarf<DwarfSlice<'a>>,
8    unit: &'b Unit<DwarfSlice<'a>>,
9    address_size: u8,
10    cache: HashMap<UnitOffset, (String, Option<u64>)>,
11}
12
13impl<'a, 'b> TypeResolver<'a, 'b> {
14    pub fn new(
15        dwarf: &'b Dwarf<DwarfSlice<'a>>,
16        unit: &'b Unit<DwarfSlice<'a>>,
17        address_size: u8,
18    ) -> Self {
19        Self { dwarf, unit, address_size, cache: HashMap::new() }
20    }
21
22    pub fn resolve_type(&mut self, offset: UnitOffset) -> Result<(String, Option<u64>)> {
23        if let Some(cached) = self.cache.get(&offset) {
24            return Ok(cached.clone());
25        }
26
27        let result = self.resolve_type_inner(offset, 0)?;
28        self.cache.insert(offset, result.clone());
29        Ok(result)
30    }
31
32    fn resolve_type_inner(
33        &mut self,
34        offset: UnitOffset,
35        depth: usize,
36    ) -> Result<(String, Option<u64>)> {
37        if depth > 20 {
38            return Ok(("...".to_string(), None));
39        }
40
41        let entry = self
42            .unit
43            .entry(offset)
44            .map_err(|e| Error::Dwarf(format!("Failed to get type entry: {}", e)))?;
45
46        let tag = entry.tag();
47
48        match tag {
49            gimli::DW_TAG_base_type => {
50                let name = self.get_type_name(&entry)?.unwrap_or_else(|| "?".to_string());
51                let size = self.get_byte_size(&entry)?;
52                Ok((name, size))
53            }
54
55            gimli::DW_TAG_pointer_type => {
56                let pointee = if let Some(type_offset) = self.get_type_ref(&entry)? {
57                    let (pointee_name, _) = self.resolve_type_inner(type_offset, depth + 1)?;
58                    pointee_name
59                } else {
60                    "void".to_string()
61                };
62                Ok((format!("*{}", pointee), Some(self.address_size as u64)))
63            }
64
65            gimli::DW_TAG_reference_type => {
66                let referee = if let Some(type_offset) = self.get_type_ref(&entry)? {
67                    let (referee_name, _) = self.resolve_type_inner(type_offset, depth + 1)?;
68                    referee_name
69                } else {
70                    "void".to_string()
71                };
72                Ok((format!("&{}", referee), Some(self.address_size as u64)))
73            }
74
75            gimli::DW_TAG_const_type
76            | gimli::DW_TAG_volatile_type
77            | gimli::DW_TAG_restrict_type => {
78                let prefix = match tag {
79                    gimli::DW_TAG_const_type => "const ",
80                    gimli::DW_TAG_volatile_type => "volatile ",
81                    gimli::DW_TAG_restrict_type => "restrict ",
82                    _ => "",
83                };
84                if let Some(type_offset) = self.get_type_ref(&entry)? {
85                    let (inner_name, size) = self.resolve_type_inner(type_offset, depth + 1)?;
86                    Ok((format!("{}{}", prefix, inner_name), size))
87                } else {
88                    Ok((format!("{}void", prefix), None))
89                }
90            }
91
92            gimli::DW_TAG_typedef => {
93                let name = self.get_type_name(&entry)?;
94                if let Some(type_offset) = self.get_type_ref(&entry)? {
95                    let (_, size) = self.resolve_type_inner(type_offset, depth + 1)?;
96                    Ok((name.unwrap_or_else(|| "typedef".to_string()), size))
97                } else {
98                    Ok((name.unwrap_or_else(|| "typedef".to_string()), None))
99                }
100            }
101
102            gimli::DW_TAG_array_type => {
103                let element_type = if let Some(type_offset) = self.get_type_ref(&entry)? {
104                    self.resolve_type_inner(type_offset, depth + 1)?
105                } else {
106                    ("?".to_string(), None)
107                };
108
109                let count = self.get_array_count(&entry)?;
110                let size = match (element_type.1, count) {
111                    (Some(elem_size), Some(c)) => Some(elem_size * c),
112                    _ => self.get_byte_size(&entry)?,
113                };
114
115                let count_str = count.map(|c| c.to_string()).unwrap_or_else(|| "?".to_string());
116                Ok((format!("[{}; {}]", element_type.0, count_str), size))
117            }
118
119            gimli::DW_TAG_structure_type | gimli::DW_TAG_class_type | gimli::DW_TAG_union_type => {
120                let name = self.get_type_name(&entry)?.unwrap_or_else(|| "<anonymous>".to_string());
121                let size = self.get_byte_size(&entry)?;
122                Ok((name, size))
123            }
124
125            gimli::DW_TAG_enumeration_type => {
126                let name = self.get_type_name(&entry)?.unwrap_or_else(|| "enum".to_string());
127                let size = self.get_byte_size(&entry)?;
128                Ok((name, size))
129            }
130
131            gimli::DW_TAG_subroutine_type => {
132                Ok(("fn(...)".to_string(), Some(self.address_size as u64)))
133            }
134
135            _ => {
136                let name = self.get_type_name(&entry)?.unwrap_or_else(|| format!("?<{:?}>", tag));
137                let size = self.get_byte_size(&entry)?;
138                Ok((name, size))
139            }
140        }
141    }
142
143    fn get_type_name(
144        &self,
145        entry: &gimli::DebuggingInformationEntry<DwarfSlice<'a>>,
146    ) -> Result<Option<String>> {
147        match entry.attr_value(gimli::DW_AT_name) {
148            Ok(Some(attr)) => {
149                let name = self
150                    .dwarf
151                    .attr_string(self.unit, attr)
152                    .map_err(|e| Error::Dwarf(format!("Failed to read type name: {}", e)))?;
153                Ok(Some(name.to_string_lossy().to_string()))
154            }
155            Ok(None) => Ok(None),
156            Err(e) => Err(Error::Dwarf(format!("Failed to read name attr: {}", e))),
157        }
158    }
159
160    fn get_byte_size(
161        &self,
162        entry: &gimli::DebuggingInformationEntry<DwarfSlice<'a>>,
163    ) -> Result<Option<u64>> {
164        match entry.attr_value(gimli::DW_AT_byte_size) {
165            Ok(Some(AttributeValue::Udata(s))) => Ok(Some(s)),
166            Ok(Some(AttributeValue::Data1(s))) => Ok(Some(s as u64)),
167            Ok(Some(AttributeValue::Data2(s))) => Ok(Some(s as u64)),
168            Ok(Some(AttributeValue::Data4(s))) => Ok(Some(s as u64)),
169            Ok(Some(AttributeValue::Data8(s))) => Ok(Some(s)),
170            _ => Ok(None),
171        }
172    }
173
174    fn get_type_ref(
175        &self,
176        entry: &gimli::DebuggingInformationEntry<DwarfSlice<'a>>,
177    ) -> Result<Option<UnitOffset>> {
178        match entry.attr_value(gimli::DW_AT_type) {
179            Ok(Some(AttributeValue::UnitRef(offset))) => Ok(Some(offset)),
180            Ok(Some(AttributeValue::DebugInfoRef(debug_info_offset))) => {
181                // Convert section offset to unit offset
182                if let Some(unit_debug_offset) = self.unit.header.offset().as_debug_info_offset() {
183                    let unit_offset =
184                        UnitOffset(debug_info_offset.0.saturating_sub(unit_debug_offset.0));
185                    Ok(Some(unit_offset))
186                } else {
187                    Ok(None)
188                }
189            }
190            _ => Ok(None),
191        }
192    }
193
194    fn get_array_count(
195        &self,
196        entry: &gimli::DebuggingInformationEntry<DwarfSlice<'a>>,
197    ) -> Result<Option<u64>> {
198        let mut tree = self
199            .unit
200            .entries_tree(Some(entry.offset()))
201            .map_err(|e| Error::Dwarf(format!("Failed to create tree: {}", e)))?;
202
203        let root = tree.root().map_err(|e| Error::Dwarf(format!("Failed to get root: {}", e)))?;
204
205        let mut children = root.children();
206        while let Some(child) =
207            children.next().map_err(|e| Error::Dwarf(format!("Failed to iterate: {}", e)))?
208        {
209            let child_entry = child.entry();
210            if child_entry.tag() == gimli::DW_TAG_subrange_type {
211                // Try DW_AT_count first (can be various data encodings)
212                if let Some(count) = self.extract_count_attr(child_entry, gimli::DW_AT_count)? {
213                    return Ok(Some(count));
214                }
215                // Fall back to DW_AT_upper_bound (0-indexed, so add 1)
216                if let Some(upper) =
217                    self.extract_count_attr(child_entry, gimli::DW_AT_upper_bound)?
218                {
219                    return Ok(Some(upper + 1));
220                }
221            }
222        }
223
224        Ok(None)
225    }
226
227    fn extract_count_attr(
228        &self,
229        entry: &gimli::DebuggingInformationEntry<DwarfSlice<'a>>,
230        attr: gimli::DwAt,
231    ) -> Result<Option<u64>> {
232        match entry.attr_value(attr) {
233            Ok(Some(AttributeValue::Udata(v))) => Ok(Some(v)),
234            Ok(Some(AttributeValue::Data1(v))) => Ok(Some(v as u64)),
235            Ok(Some(AttributeValue::Data2(v))) => Ok(Some(v as u64)),
236            Ok(Some(AttributeValue::Data4(v))) => Ok(Some(v as u64)),
237            Ok(Some(AttributeValue::Data8(v))) => Ok(Some(v)),
238            Ok(Some(AttributeValue::Sdata(v))) if v >= 0 => Ok(Some(v as u64)),
239            _ => Ok(None),
240        }
241    }
242}