layout_audit/dwarf/
types.rs

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