struct_audit/dwarf/
context.rs

1use crate::error::{Error, Result};
2use crate::loader::{DwarfSlice, LoadedDwarf};
3use crate::types::{MemberLayout, SourceLocation, StructLayout};
4use gimli::{AttributeValue, DebuggingInformationEntry, Dwarf, Unit};
5
6use super::TypeResolver;
7use super::expr::{evaluate_member_offset, try_simple_offset};
8
9pub struct DwarfContext<'a> {
10    dwarf: &'a Dwarf<DwarfSlice<'a>>,
11    address_size: u8,
12    endian: gimli::RunTimeEndian,
13}
14
15impl<'a> DwarfContext<'a> {
16    pub fn new(loaded: &'a LoadedDwarf<'a>) -> Self {
17        Self { dwarf: &loaded.dwarf, address_size: loaded.address_size, endian: loaded.endian }
18    }
19
20    pub fn find_structs(&self, filter: Option<&str>) -> Result<Vec<StructLayout>> {
21        let mut structs = Vec::new();
22        let mut units = self.dwarf.units();
23
24        while let Some(header) =
25            units.next().map_err(|e| Error::Dwarf(format!("Failed to read unit header: {}", e)))?
26        {
27            let unit = self
28                .dwarf
29                .unit(header)
30                .map_err(|e| Error::Dwarf(format!("Failed to parse unit: {}", e)))?;
31
32            self.process_unit(&unit, filter, &mut structs)?;
33        }
34
35        Ok(structs)
36    }
37
38    fn process_unit(
39        &self,
40        unit: &Unit<DwarfSlice<'a>>,
41        filter: Option<&str>,
42        structs: &mut Vec<StructLayout>,
43    ) -> Result<()> {
44        let mut type_resolver = TypeResolver::new(self.dwarf, unit, self.address_size);
45        let mut entries = unit.entries();
46
47        while let Some((_, entry)) =
48            entries.next_dfs().map_err(|e| Error::Dwarf(format!("Failed to read DIE: {}", e)))?
49        {
50            if !matches!(entry.tag(), gimli::DW_TAG_structure_type | gimli::DW_TAG_class_type) {
51                continue;
52            }
53
54            if let Some(layout) =
55                self.process_struct_entry(unit, entry, filter, &mut type_resolver)?
56            {
57                structs.push(layout);
58            }
59        }
60
61        Ok(())
62    }
63
64    fn process_struct_entry(
65        &self,
66        unit: &Unit<DwarfSlice<'a>>,
67        entry: &DebuggingInformationEntry<DwarfSlice<'a>>,
68        filter: Option<&str>,
69        type_resolver: &mut TypeResolver<'a, '_>,
70    ) -> Result<Option<StructLayout>> {
71        let size = match entry.attr_value(gimli::DW_AT_byte_size) {
72            Ok(Some(AttributeValue::Udata(s))) => s,
73            Ok(Some(AttributeValue::Data1(s))) => s as u64,
74            Ok(Some(AttributeValue::Data2(s))) => s as u64,
75            Ok(Some(AttributeValue::Data4(s))) => s as u64,
76            Ok(Some(AttributeValue::Data8(s))) => s,
77            _ => return Ok(None), // Forward declaration or no size
78        };
79
80        let name = self.get_die_name(unit, entry)?;
81        let name = match name {
82            Some(n) if !n.starts_with("__") => n, // Skip compiler-generated
83            None => return Ok(None),              // Anonymous struct
84            _ => return Ok(None),
85        };
86
87        if filter.is_some_and(|f| !name.contains(f)) {
88            return Ok(None);
89        }
90
91        let alignment = match entry.attr_value(gimli::DW_AT_alignment) {
92            Ok(Some(AttributeValue::Udata(a))) => Some(a),
93            _ => None,
94        };
95
96        let mut layout = StructLayout::new(name, size, alignment);
97        layout.source_location = self.get_source_location(unit, entry)?;
98        layout.members = self.extract_members(unit, entry, type_resolver)?;
99
100        Ok(Some(layout))
101    }
102
103    fn extract_members(
104        &self,
105        unit: &Unit<DwarfSlice<'a>>,
106        struct_entry: &DebuggingInformationEntry<DwarfSlice<'a>>,
107        type_resolver: &mut TypeResolver<'a, '_>,
108    ) -> Result<Vec<MemberLayout>> {
109        let mut members = Vec::new();
110        let mut tree = unit
111            .entries_tree(Some(struct_entry.offset()))
112            .map_err(|e| Error::Dwarf(format!("Failed to create entries tree: {}", e)))?;
113
114        let root =
115            tree.root().map_err(|e| Error::Dwarf(format!("Failed to get tree root: {}", e)))?;
116
117        let mut children = root.children();
118        while let Some(child) = children
119            .next()
120            .map_err(|e| Error::Dwarf(format!("Failed to iterate children: {}", e)))?
121        {
122            let entry = child.entry();
123            match entry.tag() {
124                gimli::DW_TAG_member => {
125                    if let Some(member) = self.process_member(unit, entry, type_resolver)? {
126                        members.push(member);
127                    }
128                }
129                gimli::DW_TAG_inheritance => {
130                    if let Some(member) = self.process_inheritance(unit, entry, type_resolver)? {
131                        members.push(member);
132                    }
133                }
134                _ => {}
135            }
136        }
137
138        members.sort_by_key(|m| m.offset.unwrap_or(u64::MAX));
139        Ok(members)
140    }
141
142    fn process_inheritance(
143        &self,
144        unit: &Unit<DwarfSlice<'a>>,
145        entry: &DebuggingInformationEntry<DwarfSlice<'a>>,
146        type_resolver: &mut TypeResolver<'a, '_>,
147    ) -> Result<Option<MemberLayout>> {
148        let offset = self.get_member_offset(unit, entry)?;
149
150        let (type_name, size) = match entry.attr_value(gimli::DW_AT_type) {
151            Ok(Some(AttributeValue::UnitRef(type_offset))) => {
152                type_resolver.resolve_type(type_offset)?
153            }
154            Ok(Some(AttributeValue::DebugInfoRef(debug_info_offset))) => {
155                if let Some(unit_debug_offset) = unit.header.offset().as_debug_info_offset() {
156                    let unit_offset =
157                        gimli::UnitOffset(debug_info_offset.0.saturating_sub(unit_debug_offset.0));
158                    type_resolver.resolve_type(unit_offset)?
159                } else {
160                    ("unknown".to_string(), None)
161                }
162            }
163            _ => ("unknown".to_string(), None),
164        };
165
166        let name = format!("<base: {}>", type_name);
167        Ok(Some(MemberLayout::new(name, type_name, offset, size)))
168    }
169
170    fn process_member(
171        &self,
172        unit: &Unit<DwarfSlice<'a>>,
173        entry: &DebuggingInformationEntry<DwarfSlice<'a>>,
174        type_resolver: &mut TypeResolver<'a, '_>,
175    ) -> Result<Option<MemberLayout>> {
176        let name = self.get_die_name(unit, entry)?.unwrap_or_else(|| "<anonymous>".to_string());
177
178        let (type_name, size) = match entry.attr_value(gimli::DW_AT_type) {
179            Ok(Some(AttributeValue::UnitRef(type_offset))) => {
180                type_resolver.resolve_type(type_offset)?
181            }
182            Ok(Some(AttributeValue::DebugInfoRef(debug_info_offset))) => {
183                // Convert section offset to unit offset
184                if let Some(unit_debug_offset) = unit.header.offset().as_debug_info_offset() {
185                    let unit_offset =
186                        gimli::UnitOffset(debug_info_offset.0.saturating_sub(unit_debug_offset.0));
187                    type_resolver.resolve_type(unit_offset)?
188                } else {
189                    ("unknown".to_string(), None)
190                }
191            }
192            _ => ("unknown".to_string(), None),
193        };
194
195        let offset = self.get_member_offset(unit, entry)?;
196
197        let mut member = MemberLayout::new(name, type_name, offset, size);
198
199        let read_u64_attr = |attr: gimli::DwAt| -> Option<u64> {
200            match entry.attr_value(attr).ok().flatten()? {
201                AttributeValue::Udata(v) => Some(v),
202                AttributeValue::Data1(v) => Some(v as u64),
203                AttributeValue::Data2(v) => Some(v as u64),
204                AttributeValue::Data4(v) => Some(v as u64),
205                AttributeValue::Data8(v) => Some(v),
206                AttributeValue::Sdata(v) if v >= 0 => Some(v as u64),
207                _ => None,
208            }
209        };
210
211        let bit_size = read_u64_attr(gimli::DW_AT_bit_size);
212        let dwarf5_data_bit_offset = read_u64_attr(gimli::DW_AT_data_bit_offset);
213        let dwarf4_bit_offset = read_u64_attr(gimli::DW_AT_bit_offset);
214
215        member.bit_size = bit_size;
216
217        if let Some(bit_size) = bit_size
218            && let Some(storage_bytes) = member.size
219            && storage_bytes > 0
220        {
221            let storage_bits = storage_bytes.saturating_mul(8);
222
223            // Determine the containing storage unit byte offset for this bitfield.
224            // Prefer DW_AT_data_member_location when present. If absent, infer the
225            // storage unit start by aligning the absolute DW_AT_data_bit_offset down
226            // to the storage unit size.
227            let container_offset = member.offset.or_else(|| {
228                let data_bit_offset = dwarf5_data_bit_offset?;
229                let start_byte = data_bit_offset / 8;
230                Some((start_byte / storage_bytes) * storage_bytes)
231            });
232
233            if member.offset.is_none() {
234                member.offset = container_offset;
235            }
236
237            // Compute bit offset within the containing storage unit.
238            if let Some(container_offset) = container_offset {
239                if let Some(data_bit_offset) = dwarf5_data_bit_offset {
240                    member.bit_offset = Some(data_bit_offset.saturating_sub(container_offset * 8));
241                } else if let Some(raw_bit_offset) = dwarf4_bit_offset {
242                    if raw_bit_offset + bit_size <= storage_bits {
243                        let bit_offset = match self.endian {
244                            gimli::RunTimeEndian::Little => {
245                                storage_bits - raw_bit_offset - bit_size
246                            }
247                            gimli::RunTimeEndian::Big => raw_bit_offset,
248                        };
249                        member.bit_offset = Some(bit_offset);
250                    }
251                }
252            }
253        }
254
255        Ok(Some(member))
256    }
257
258    fn get_member_offset(
259        &self,
260        unit: &Unit<DwarfSlice<'a>>,
261        entry: &DebuggingInformationEntry<DwarfSlice<'a>>,
262    ) -> Result<Option<u64>> {
263        match entry.attr_value(gimli::DW_AT_data_member_location) {
264            Ok(Some(AttributeValue::Udata(offset))) => Ok(Some(offset)),
265            Ok(Some(AttributeValue::Data1(offset))) => Ok(Some(offset as u64)),
266            Ok(Some(AttributeValue::Data2(offset))) => Ok(Some(offset as u64)),
267            Ok(Some(AttributeValue::Data4(offset))) => Ok(Some(offset as u64)),
268            Ok(Some(AttributeValue::Data8(offset))) => Ok(Some(offset)),
269            Ok(Some(AttributeValue::Sdata(offset))) if offset >= 0 => Ok(Some(offset as u64)),
270            Ok(Some(AttributeValue::Exprloc(expr))) => {
271                // Try simple constant extraction first (fast path)
272                if let Some(offset) = try_simple_offset(expr, unit.encoding()) {
273                    return Ok(Some(offset));
274                }
275                // Fall back to full expression evaluation
276                evaluate_member_offset(expr, unit.encoding())
277            }
278            Ok(None) => Ok(None), // Missing offset - don't assume 0 (bitfields, packed structs)
279            _ => Ok(None),
280        }
281    }
282
283    fn get_die_name(
284        &self,
285        unit: &Unit<DwarfSlice<'a>>,
286        entry: &DebuggingInformationEntry<DwarfSlice<'a>>,
287    ) -> Result<Option<String>> {
288        match entry.attr_value(gimli::DW_AT_name) {
289            Ok(Some(attr)) => {
290                let name = self
291                    .dwarf
292                    .attr_string(unit, attr)
293                    .map_err(|e| Error::Dwarf(format!("Failed to read name: {}", e)))?;
294                Ok(Some(name.to_string_lossy().to_string()))
295            }
296            Ok(None) => Ok(None),
297            Err(e) => Err(Error::Dwarf(format!("Failed to read name attribute: {}", e))),
298        }
299    }
300
301    fn get_source_location(
302        &self,
303        _unit: &Unit<DwarfSlice<'a>>,
304        entry: &DebuggingInformationEntry<DwarfSlice<'a>>,
305    ) -> Result<Option<SourceLocation>> {
306        let file_index = match entry.attr_value(gimli::DW_AT_decl_file) {
307            Ok(Some(AttributeValue::FileIndex(idx))) => idx,
308            Ok(Some(AttributeValue::Udata(idx))) => idx,
309            _ => return Ok(None),
310        };
311
312        let line = match entry.attr_value(gimli::DW_AT_decl_line) {
313            Ok(Some(AttributeValue::Udata(l))) => l,
314            _ => return Ok(None),
315        };
316
317        // File name resolution would require parsing .debug_line
318        // For MVP, just return the file index
319        Ok(Some(SourceLocation { file: format!("file#{}", file_index), line }))
320    }
321}