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), };
79
80 let name = self.get_die_name(unit, entry)?;
81 let name = match name {
82 Some(n) if !n.starts_with("__") => n, None => return Ok(None), _ => 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 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 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 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 if let Some(offset) = try_simple_offset(expr, unit.encoding()) {
273 return Ok(Some(offset));
274 }
275 evaluate_member_offset(expr, unit.encoding())
277 }
278 Ok(None) => Ok(None), _ => 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 Ok(Some(SourceLocation { file: format!("file#{}", file_index), line }))
320 }
321}