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}
13
14impl<'a> DwarfContext<'a> {
15    pub fn new(loaded: &'a LoadedDwarf<'a>) -> Self {
16        Self { dwarf: &loaded.dwarf, address_size: loaded.address_size }
17    }
18
19    pub fn find_structs(&self, filter: Option<&str>) -> Result<Vec<StructLayout>> {
20        let mut structs = Vec::new();
21        let mut units = self.dwarf.units();
22
23        while let Some(header) =
24            units.next().map_err(|e| Error::Dwarf(format!("Failed to read unit header: {}", e)))?
25        {
26            let unit = self
27                .dwarf
28                .unit(header)
29                .map_err(|e| Error::Dwarf(format!("Failed to parse unit: {}", e)))?;
30
31            self.process_unit(&unit, filter, &mut structs)?;
32        }
33
34        Ok(structs)
35    }
36
37    fn process_unit(
38        &self,
39        unit: &Unit<DwarfSlice<'a>>,
40        filter: Option<&str>,
41        structs: &mut Vec<StructLayout>,
42    ) -> Result<()> {
43        let mut type_resolver = TypeResolver::new(self.dwarf, unit, self.address_size);
44        let mut entries = unit.entries();
45
46        while let Some((_, entry)) =
47            entries.next_dfs().map_err(|e| Error::Dwarf(format!("Failed to read DIE: {}", e)))?
48        {
49            if !matches!(entry.tag(), gimli::DW_TAG_structure_type | gimli::DW_TAG_class_type) {
50                continue;
51            }
52
53            if let Some(layout) =
54                self.process_struct_entry(unit, entry, filter, &mut type_resolver)?
55            {
56                structs.push(layout);
57            }
58        }
59
60        Ok(())
61    }
62
63    fn process_struct_entry(
64        &self,
65        unit: &Unit<DwarfSlice<'a>>,
66        entry: &DebuggingInformationEntry<DwarfSlice<'a>>,
67        filter: Option<&str>,
68        type_resolver: &mut TypeResolver<'a, '_>,
69    ) -> Result<Option<StructLayout>> {
70        let size = match entry.attr_value(gimli::DW_AT_byte_size) {
71            Ok(Some(AttributeValue::Udata(s))) => s,
72            Ok(Some(AttributeValue::Data1(s))) => s as u64,
73            Ok(Some(AttributeValue::Data2(s))) => s as u64,
74            Ok(Some(AttributeValue::Data4(s))) => s as u64,
75            Ok(Some(AttributeValue::Data8(s))) => s,
76            _ => return Ok(None), // Forward declaration or no size
77        };
78
79        let name = self.get_die_name(unit, entry)?;
80        let name = match name {
81            Some(n) if !n.starts_with("__") => n, // Skip compiler-generated
82            None => return Ok(None),              // Anonymous struct
83            _ => return Ok(None),
84        };
85
86        if filter.is_some_and(|f| !name.contains(f)) {
87            return Ok(None);
88        }
89
90        let alignment = match entry.attr_value(gimli::DW_AT_alignment) {
91            Ok(Some(AttributeValue::Udata(a))) => Some(a),
92            _ => None,
93        };
94
95        let mut layout = StructLayout::new(name, size, alignment);
96        layout.source_location = self.get_source_location(unit, entry)?;
97        layout.members = self.extract_members(unit, entry, type_resolver)?;
98
99        Ok(Some(layout))
100    }
101
102    fn extract_members(
103        &self,
104        unit: &Unit<DwarfSlice<'a>>,
105        struct_entry: &DebuggingInformationEntry<DwarfSlice<'a>>,
106        type_resolver: &mut TypeResolver<'a, '_>,
107    ) -> Result<Vec<MemberLayout>> {
108        let mut members = Vec::new();
109        let mut tree = unit
110            .entries_tree(Some(struct_entry.offset()))
111            .map_err(|e| Error::Dwarf(format!("Failed to create entries tree: {}", e)))?;
112
113        let root =
114            tree.root().map_err(|e| Error::Dwarf(format!("Failed to get tree root: {}", e)))?;
115
116        let mut children = root.children();
117        while let Some(child) = children
118            .next()
119            .map_err(|e| Error::Dwarf(format!("Failed to iterate children: {}", e)))?
120        {
121            let entry = child.entry();
122            if entry.tag() != gimli::DW_TAG_member {
123                continue;
124            }
125
126            if let Some(member) = self.process_member(unit, entry, type_resolver)? {
127                members.push(member);
128            }
129        }
130
131        members.sort_by_key(|m| m.offset.unwrap_or(u64::MAX));
132        Ok(members)
133    }
134
135    fn process_member(
136        &self,
137        unit: &Unit<DwarfSlice<'a>>,
138        entry: &DebuggingInformationEntry<DwarfSlice<'a>>,
139        type_resolver: &mut TypeResolver<'a, '_>,
140    ) -> Result<Option<MemberLayout>> {
141        let name = self.get_die_name(unit, entry)?.unwrap_or_else(|| "<anonymous>".to_string());
142
143        let offset = self.get_member_offset(unit, entry)?;
144
145        let (type_name, size) = match entry.attr_value(gimli::DW_AT_type) {
146            Ok(Some(AttributeValue::UnitRef(type_offset))) => {
147                type_resolver.resolve_type(type_offset)?
148            }
149            Ok(Some(AttributeValue::DebugInfoRef(debug_info_offset))) => {
150                // Convert section offset to unit offset
151                if let Some(unit_debug_offset) = unit.header.offset().as_debug_info_offset() {
152                    let unit_offset =
153                        gimli::UnitOffset(debug_info_offset.0.saturating_sub(unit_debug_offset.0));
154                    type_resolver.resolve_type(unit_offset)?
155                } else {
156                    ("unknown".to_string(), None)
157                }
158            }
159            _ => ("unknown".to_string(), None),
160        };
161
162        let mut member = MemberLayout::new(name, type_name, offset, size);
163
164        // Parse bitfield attributes (DWARF 4 style)
165        if let Ok(Some(AttributeValue::Udata(bit_offset))) =
166            entry.attr_value(gimli::DW_AT_bit_offset)
167        {
168            member.bit_offset = Some(bit_offset);
169        }
170
171        // DWARF 5 style: DW_AT_data_bit_offset (offset from start of containing entity)
172        if let Ok(Some(AttributeValue::Udata(data_bit_offset))) =
173            entry.attr_value(gimli::DW_AT_data_bit_offset)
174        {
175            member.bit_offset = Some(data_bit_offset);
176        }
177
178        if let Ok(Some(AttributeValue::Udata(bit_size))) = entry.attr_value(gimli::DW_AT_bit_size) {
179            member.bit_size = Some(bit_size);
180        }
181
182        Ok(Some(member))
183    }
184
185    fn get_member_offset(
186        &self,
187        unit: &Unit<DwarfSlice<'a>>,
188        entry: &DebuggingInformationEntry<DwarfSlice<'a>>,
189    ) -> Result<Option<u64>> {
190        match entry.attr_value(gimli::DW_AT_data_member_location) {
191            Ok(Some(AttributeValue::Udata(offset))) => Ok(Some(offset)),
192            Ok(Some(AttributeValue::Data1(offset))) => Ok(Some(offset as u64)),
193            Ok(Some(AttributeValue::Data2(offset))) => Ok(Some(offset as u64)),
194            Ok(Some(AttributeValue::Data4(offset))) => Ok(Some(offset as u64)),
195            Ok(Some(AttributeValue::Data8(offset))) => Ok(Some(offset)),
196            Ok(Some(AttributeValue::Sdata(offset))) if offset >= 0 => Ok(Some(offset as u64)),
197            Ok(Some(AttributeValue::Exprloc(expr))) => {
198                // Try simple constant extraction first (fast path)
199                if let Some(offset) = try_simple_offset(expr, unit.encoding()) {
200                    return Ok(Some(offset));
201                }
202                // Fall back to full expression evaluation
203                evaluate_member_offset(expr, unit.encoding())
204            }
205            Ok(None) => Ok(None), // Missing offset - don't assume 0 (bitfields, packed structs)
206            _ => Ok(None),
207        }
208    }
209
210    fn get_die_name(
211        &self,
212        unit: &Unit<DwarfSlice<'a>>,
213        entry: &DebuggingInformationEntry<DwarfSlice<'a>>,
214    ) -> Result<Option<String>> {
215        match entry.attr_value(gimli::DW_AT_name) {
216            Ok(Some(attr)) => {
217                let name = self
218                    .dwarf
219                    .attr_string(unit, attr)
220                    .map_err(|e| Error::Dwarf(format!("Failed to read name: {}", e)))?;
221                Ok(Some(name.to_string_lossy().to_string()))
222            }
223            Ok(None) => Ok(None),
224            Err(e) => Err(Error::Dwarf(format!("Failed to read name attribute: {}", e))),
225        }
226    }
227
228    fn get_source_location(
229        &self,
230        _unit: &Unit<DwarfSlice<'a>>,
231        entry: &DebuggingInformationEntry<DwarfSlice<'a>>,
232    ) -> Result<Option<SourceLocation>> {
233        let file_index = match entry.attr_value(gimli::DW_AT_decl_file) {
234            Ok(Some(AttributeValue::FileIndex(idx))) => idx,
235            Ok(Some(AttributeValue::Udata(idx))) => idx,
236            _ => return Ok(None),
237        };
238
239        let line = match entry.attr_value(gimli::DW_AT_decl_line) {
240            Ok(Some(AttributeValue::Udata(l))) => l,
241            _ => return Ok(None),
242        };
243
244        // File name resolution would require parsing .debug_line
245        // For MVP, just return the file index
246        Ok(Some(SourceLocation { file: format!("file#{}", file_index), line }))
247    }
248}