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