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};
8use super::{debug_info_ref_to_unit_offset, read_u64_from_attr};
9
10#[cfg(test)]
15const GO_INTERNAL_PREFIXES: &[&str] = &[
16 "runtime.",
18 "runtime/",
19 "internal/",
20 "reflect.",
21 "sync.",
22 "sync/",
23 "syscall.",
24 "unsafe.",
25 "go.",
27 "go:",
28 "type:",
30 "type..",
31 "type.*[",
32 "hash<",
34 "bucket<",
35 "hmap",
36 "hchan",
37 "waitq<",
38 "sudog",
39 "itab",
41 "iface",
42 "eface",
43 "funcval",
45 "go.shape.",
47 "groupReference<",
48 "stackObject",
50 "stackScan",
51 "stackfreelist",
52 "stkframe",
53 "[]",
55 "[]*",
56 "noalg.",
58];
59
60pub fn is_go_internal_type(name: &str) -> bool {
63 let Some(&first) = name.as_bytes().first() else {
65 return false; };
67
68 let matches_prefix = match first {
70 b'r' => {
71 name.starts_with("runtime.")
72 || name.starts_with("runtime/")
73 || name.starts_with("reflect.")
74 }
75 b's' => {
76 name.starts_with("sync.")
77 || name.starts_with("sync/")
78 || name.starts_with("syscall.")
79 || name.starts_with("sudog")
80 || name.starts_with("stackObject")
81 || name.starts_with("stackScan")
82 || name.starts_with("stackfreelist")
83 || name.starts_with("stkframe")
84 }
85 b'i' => {
86 name.starts_with("internal/") || name.starts_with("itab") || name.starts_with("iface")
87 }
88 b'g' => {
89 name.starts_with("go.")
90 || name.starts_with("go:")
91 || name.starts_with("groupReference<")
92 }
93 b't' => {
94 name.starts_with("type:") || name.starts_with("type..") || name.starts_with("type.*[")
95 }
96 b'u' => name.starts_with("unsafe."),
97 b'h' => name.starts_with("hash<") || name.starts_with("hmap") || name.starts_with("hchan"),
98 b'b' => name.starts_with("bucket<"),
99 b'w' => name.starts_with("waitq<"),
100 b'e' => name.starts_with("eface"),
101 b'f' => name.starts_with("funcval"),
102 b'n' => name.starts_with("noalg."),
103 b'[' => name.starts_with("[]") || name.starts_with("[]*"),
104 _ => false,
105 };
106
107 matches_prefix || name.contains('\u{00B7}')
109}
110
111#[cfg(test)]
113pub(crate) fn go_internal_prefixes() -> &'static [&'static str] {
114 GO_INTERNAL_PREFIXES
115}
116
117pub struct DwarfContext<'a> {
118 dwarf: &'a Dwarf<DwarfSlice<'a>>,
119 address_size: u8,
120 endian: gimli::RunTimeEndian,
121}
122
123impl<'a> DwarfContext<'a> {
124 pub fn new(loaded: &'a LoadedDwarf<'a>) -> Self {
125 Self { dwarf: &loaded.dwarf, address_size: loaded.address_size, endian: loaded.endian }
126 }
127
128 pub fn find_structs(
133 &self,
134 filter: Option<&str>,
135 include_go_runtime: bool,
136 ) -> Result<Vec<StructLayout>> {
137 let mut structs = Vec::new();
138 let mut units = self.dwarf.units();
139
140 while let Some(header) =
141 units.next().map_err(|e| Error::Dwarf(format!("Failed to read unit header: {}", e)))?
142 {
143 let unit = self
144 .dwarf
145 .unit(header)
146 .map_err(|e| Error::Dwarf(format!("Failed to parse unit: {}", e)))?;
147
148 self.process_unit(&unit, filter, include_go_runtime, &mut structs)?;
149 }
150
151 let mut with_fp: Vec<(StructFingerprint, usize, StructLayout)> =
156 structs.into_iter().enumerate().map(|(i, s)| (struct_fingerprint(&s), i, s)).collect();
157 with_fp.sort_by(|a, b| a.0.cmp(&b.0).then_with(|| a.1.cmp(&b.1)));
158 with_fp.dedup_by(|a, b| a.0 == b.0);
159
160 Ok(with_fp.into_iter().map(|(_, _, s)| s).collect())
161 }
162
163 fn process_unit(
164 &self,
165 unit: &Unit<DwarfSlice<'a>>,
166 filter: Option<&str>,
167 include_go_runtime: bool,
168 structs: &mut Vec<StructLayout>,
169 ) -> Result<()> {
170 let mut type_resolver = TypeResolver::new(self.dwarf, unit, self.address_size);
171 let mut entries = unit.entries();
172
173 while let Some((_, entry)) =
174 entries.next_dfs().map_err(|e| Error::Dwarf(format!("Failed to read DIE: {}", e)))?
175 {
176 if !matches!(entry.tag(), gimli::DW_TAG_structure_type | gimli::DW_TAG_class_type) {
177 continue;
178 }
179
180 if let Some(layout) = self.process_struct_entry(
181 unit,
182 entry,
183 filter,
184 include_go_runtime,
185 &mut type_resolver,
186 )? {
187 structs.push(layout);
188 }
189 }
190
191 Ok(())
192 }
193
194 fn process_struct_entry(
195 &self,
196 unit: &Unit<DwarfSlice<'a>>,
197 entry: &DebuggingInformationEntry<DwarfSlice<'a>>,
198 filter: Option<&str>,
199 include_go_runtime: bool,
200 type_resolver: &mut TypeResolver<'a, '_>,
201 ) -> Result<Option<StructLayout>> {
202 let Some(size) =
204 read_u64_from_attr(entry.attr_value(gimli::DW_AT_byte_size).ok().flatten())
205 else {
206 return Ok(None); };
208
209 let name = self.get_die_name(unit, entry)?;
210 let name = match name {
211 Some(n) if !n.starts_with("__") => n, None => return Ok(None), _ => return Ok(None),
214 };
215
216 if !include_go_runtime && is_go_internal_type(&name) {
218 return Ok(None);
219 }
220
221 if filter.is_some_and(|f| !name.contains(f)) {
222 return Ok(None);
223 }
224
225 let alignment = read_u64_from_attr(entry.attr_value(gimli::DW_AT_alignment).ok().flatten());
226
227 let mut layout = StructLayout::new(name, size, alignment);
228 layout.source_location = self.get_source_location(unit, entry)?;
229 layout.members = self.extract_members(unit, entry, type_resolver)?;
230
231 Ok(Some(layout))
232 }
233
234 fn extract_members(
235 &self,
236 unit: &Unit<DwarfSlice<'a>>,
237 struct_entry: &DebuggingInformationEntry<DwarfSlice<'a>>,
238 type_resolver: &mut TypeResolver<'a, '_>,
239 ) -> Result<Vec<MemberLayout>> {
240 let mut members = Vec::new();
241 let mut tree = unit
242 .entries_tree(Some(struct_entry.offset()))
243 .map_err(|e| Error::Dwarf(format!("Failed to create entries tree: {}", e)))?;
244
245 let root =
246 tree.root().map_err(|e| Error::Dwarf(format!("Failed to get tree root: {}", e)))?;
247
248 let mut children = root.children();
249 while let Some(child) = children
250 .next()
251 .map_err(|e| Error::Dwarf(format!("Failed to iterate children: {}", e)))?
252 {
253 let entry = child.entry();
254 match entry.tag() {
255 gimli::DW_TAG_member => {
256 if let Some(member) = self.process_member(unit, entry, type_resolver)? {
257 members.push(member);
258 }
259 }
260 gimli::DW_TAG_inheritance => {
261 if let Some(member) = self.process_inheritance(unit, entry, type_resolver)? {
262 members.push(member);
263 }
264 }
265 _ => {}
266 }
267 }
268
269 members.sort_by_key(|m| m.offset.unwrap_or(u64::MAX));
270 Ok(members)
271 }
272
273 fn resolve_type_attr(
276 &self,
277 unit: &Unit<DwarfSlice<'a>>,
278 entry: &DebuggingInformationEntry<DwarfSlice<'a>>,
279 type_resolver: &mut TypeResolver<'a, '_>,
280 ) -> Result<(String, Option<u64>, bool)> {
281 match entry.attr_value(gimli::DW_AT_type) {
282 Ok(Some(AttributeValue::UnitRef(type_offset))) => {
283 type_resolver.resolve_type(type_offset)
284 }
285 Ok(Some(AttributeValue::DebugInfoRef(debug_info_offset))) => {
286 if let Some(unit_offset) =
288 debug_info_ref_to_unit_offset(debug_info_offset, &unit.header)
289 {
290 type_resolver.resolve_type(unit_offset)
291 } else {
292 Ok(("unknown".to_string(), None, false))
293 }
294 }
295 _ => Ok(("unknown".to_string(), None, false)),
296 }
297 }
298
299 fn process_inheritance(
300 &self,
301 unit: &Unit<DwarfSlice<'a>>,
302 entry: &DebuggingInformationEntry<DwarfSlice<'a>>,
303 type_resolver: &mut TypeResolver<'a, '_>,
304 ) -> Result<Option<MemberLayout>> {
305 let offset = self.get_member_offset(unit, entry)?;
306 let (type_name, size, is_atomic) = self.resolve_type_attr(unit, entry, type_resolver)?;
307
308 let name = format!("<base: {}>", type_name);
309 Ok(Some(MemberLayout::new(name, type_name, offset, size).with_atomic(is_atomic)))
310 }
311
312 fn process_member(
313 &self,
314 unit: &Unit<DwarfSlice<'a>>,
315 entry: &DebuggingInformationEntry<DwarfSlice<'a>>,
316 type_resolver: &mut TypeResolver<'a, '_>,
317 ) -> Result<Option<MemberLayout>> {
318 let name = self.get_die_name(unit, entry)?.unwrap_or_else(|| "<anonymous>".to_string());
319 let (type_name, size, is_atomic) = self.resolve_type_attr(unit, entry, type_resolver)?;
320
321 let offset = self.get_member_offset(unit, entry)?;
322
323 let mut member = MemberLayout::new(name, type_name, offset, size).with_atomic(is_atomic);
324
325 let bit_size = read_u64_from_attr(entry.attr_value(gimli::DW_AT_bit_size).ok().flatten());
326 let dwarf5_data_bit_offset =
327 read_u64_from_attr(entry.attr_value(gimli::DW_AT_data_bit_offset).ok().flatten());
328 let dwarf4_bit_offset =
329 read_u64_from_attr(entry.attr_value(gimli::DW_AT_bit_offset).ok().flatten());
330
331 member.bit_size = bit_size;
332
333 if let Some(bit_size) = bit_size
334 && let Some(storage_bytes) = member.size
335 && storage_bytes > 0
336 {
337 let storage_bits = storage_bytes.saturating_mul(8);
338
339 let container_offset = member.offset.or_else(|| {
344 let data_bit_offset = dwarf5_data_bit_offset?;
345 let start_byte = data_bit_offset.checked_div(8)?;
346 start_byte.checked_div(storage_bytes)?.checked_mul(storage_bytes)
348 });
349
350 if member.offset.is_none() {
351 member.offset = container_offset;
352 }
353
354 if let Some(container_offset) = container_offset {
356 if let Some(data_bit_offset) = dwarf5_data_bit_offset {
357 if let Some(container_bits) = container_offset.checked_mul(8) {
359 member.bit_offset = Some(data_bit_offset.saturating_sub(container_bits));
360 }
361 } else if let Some(raw_bit_offset) = dwarf4_bit_offset {
362 if let Some(end_bit) = raw_bit_offset.checked_add(bit_size) {
364 if end_bit <= storage_bits {
365 let bit_offset = match self.endian {
366 gimli::RunTimeEndian::Little => {
367 storage_bits - raw_bit_offset - bit_size
368 }
369 gimli::RunTimeEndian::Big => raw_bit_offset,
370 };
371 member.bit_offset = Some(bit_offset);
372 }
373 }
374 }
375 }
376 }
377
378 Ok(Some(member))
379 }
380
381 fn get_member_offset(
382 &self,
383 unit: &Unit<DwarfSlice<'a>>,
384 entry: &DebuggingInformationEntry<DwarfSlice<'a>>,
385 ) -> Result<Option<u64>> {
386 match entry.attr_value(gimli::DW_AT_data_member_location) {
387 Ok(Some(AttributeValue::Udata(offset))) => Ok(Some(offset)),
388 Ok(Some(AttributeValue::Data1(offset))) => Ok(Some(offset as u64)),
389 Ok(Some(AttributeValue::Data2(offset))) => Ok(Some(offset as u64)),
390 Ok(Some(AttributeValue::Data4(offset))) => Ok(Some(offset as u64)),
391 Ok(Some(AttributeValue::Data8(offset))) => Ok(Some(offset)),
392 Ok(Some(AttributeValue::Sdata(offset))) if offset >= 0 => Ok(Some(offset as u64)),
393 Ok(Some(AttributeValue::Exprloc(expr))) => {
394 if let Some(offset) = try_simple_offset(expr, unit.encoding()) {
396 return Ok(Some(offset));
397 }
398 evaluate_member_offset(expr, unit.encoding())
400 }
401 Ok(None) => Ok(None), _ => Ok(None),
403 }
404 }
405
406 fn get_die_name(
407 &self,
408 unit: &Unit<DwarfSlice<'a>>,
409 entry: &DebuggingInformationEntry<DwarfSlice<'a>>,
410 ) -> Result<Option<String>> {
411 match entry.attr_value(gimli::DW_AT_name) {
412 Ok(Some(attr)) => {
413 let name = self
414 .dwarf
415 .attr_string(unit, attr)
416 .map_err(|e| Error::Dwarf(format!("Failed to read name: {}", e)))?;
417 Ok(Some(name.to_string_lossy().into_owned()))
418 }
419 Ok(None) => Ok(None),
420 Err(e) => Err(Error::Dwarf(format!("Failed to read name attribute: {}", e))),
421 }
422 }
423
424 fn get_source_location(
425 &self,
426 unit: &Unit<DwarfSlice<'a>>,
427 entry: &DebuggingInformationEntry<DwarfSlice<'a>>,
428 ) -> Result<Option<SourceLocation>> {
429 let Some(file_index) =
430 read_u64_from_attr(entry.attr_value(gimli::DW_AT_decl_file).ok().flatten())
431 else {
432 return Ok(None);
433 };
434 let Some(line) =
435 read_u64_from_attr(entry.attr_value(gimli::DW_AT_decl_line).ok().flatten())
436 else {
437 return Ok(None);
438 };
439
440 let file_name = self.resolve_file_name(unit, file_index).unwrap_or_else(|| {
442 format!("file#{}", file_index)
444 });
445
446 Ok(Some(SourceLocation { file: file_name, line }))
447 }
448
449 fn resolve_file_name(&self, unit: &Unit<DwarfSlice<'a>>, file_index: u64) -> Option<String> {
451 let line_program = unit.line_program.as_ref()?;
453
454 let header = line_program.header();
455
456 let file = header.file(file_index)?;
459
460 let file_name =
462 self.dwarf.attr_string(unit, file.path_name()).ok()?.to_string_lossy().into_owned();
463
464 if let Some(dir) = file.directory(header) {
466 if let Ok(dir_str) = self.dwarf.attr_string(unit, dir) {
467 let dir_name = dir_str.to_string_lossy();
468 if !dir_name.is_empty() {
469 return Some(format!("{}/{}", dir_name, file_name));
471 }
472 }
473 }
474
475 Some(file_name)
476 }
477}
478
479#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
480struct StructFingerprint {
481 name: String,
482 size: u64,
483 alignment: Option<u64>,
484 source: Option<(String, u64)>,
485 members: Vec<MemberFingerprint>,
486}
487
488#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
489struct MemberFingerprint {
490 name: String,
491 type_name: String,
492 offset: Option<u64>,
493 size: Option<u64>,
494 bit_offset: Option<u64>,
495 bit_size: Option<u64>,
496 is_atomic: bool,
497}
498
499fn struct_fingerprint(s: &StructLayout) -> StructFingerprint {
500 StructFingerprint {
501 name: s.name.clone(),
502 size: s.size,
503 alignment: s.alignment,
504 source: s.source_location.as_ref().map(|l| (l.file.clone(), l.line)),
505 members: s
506 .members
507 .iter()
508 .map(|m| MemberFingerprint {
509 name: m.name.clone(),
510 type_name: m.type_name.clone(),
511 offset: m.offset,
512 size: m.size,
513 bit_offset: m.bit_offset,
514 bit_size: m.bit_size,
515 is_atomic: m.is_atomic,
516 })
517 .collect(),
518 }
519}
520
521#[cfg(test)]
522mod tests {
523 use super::*;
524
525 #[test]
526 fn test_is_go_internal_type() {
527 assert!(is_go_internal_type("runtime.g"));
529 assert!(is_go_internal_type("runtime.m"));
530 assert!(is_go_internal_type("runtime.stack"));
531 assert!(is_go_internal_type("runtime/internal/atomic.Uint64"));
532 assert!(is_go_internal_type("internal/abi.Type"));
533 assert!(is_go_internal_type("reflect.Value"));
534 assert!(is_go_internal_type("sync.Mutex"));
535 assert!(is_go_internal_type("sync/atomic.Int64"));
536 assert!(is_go_internal_type("syscall.Stat_t"));
537 assert!(is_go_internal_type("unsafe.Pointer"));
538
539 assert!(is_go_internal_type("type:main.MyStruct"));
541 assert!(is_go_internal_type("type..hash.main.MyStruct"));
542 assert!(is_go_internal_type("type.*[10]int"));
543
544 assert!(is_go_internal_type("hmap"));
546 assert!(is_go_internal_type("hchan"));
547 assert!(is_go_internal_type("hchan<int>"));
548 assert!(is_go_internal_type("waitq<bool>"));
549 assert!(is_go_internal_type("hash<string,int>"));
550 assert!(is_go_internal_type("bucket<string,int>"));
551
552 assert!(is_go_internal_type("itab"));
554 assert!(is_go_internal_type("iface"));
555 assert!(is_go_internal_type("eface"));
556
557 assert!(is_go_internal_type("stackObject"));
559 assert!(is_go_internal_type("stackScan"));
560 assert!(is_go_internal_type("stkframe"));
561
562 assert!(is_go_internal_type("go.shape.string"));
564 assert!(is_go_internal_type("groupReference<int32,unsafe.Pointer>"));
565
566 assert!(is_go_internal_type("[]int"));
568 assert!(is_go_internal_type("[]*runtime.g"));
569
570 assert!(is_go_internal_type("noalg.map.group[string]bool"));
572
573 assert!(is_go_internal_type("go:itab.*os.File,io.Reader"));
575
576 assert!(is_go_internal_type("pkg\u{00B7}unexported"));
578 assert!(is_go_internal_type("(*T)\u{00B7}method"));
579 assert!(is_go_internal_type("main\u{00B7}init"));
580
581 assert!(!is_go_internal_type("main.Order"));
583 assert!(!is_go_internal_type("main.Config"));
584 assert!(!is_go_internal_type("main.PoorlyAligned"));
585 assert!(!is_go_internal_type("mypackage.MyStruct"));
586 assert!(!is_go_internal_type("github.com/user/pkg.Type"));
587 assert!(!is_go_internal_type("Order"));
588 assert!(!is_go_internal_type("Config"));
589 assert!(!is_go_internal_type("MyStack"));
591 assert!(!is_go_internal_type("StackFrame"));
592 assert!(!is_go_internal_type("HashMap"));
593 assert!(!is_go_internal_type("runtimeConfig")); assert!(!is_go_internal_type("syncronizer")); assert!(!is_go_internal_type("go_util")); assert!(!is_go_internal_type("MyStackFrame")); assert!(!is_go_internal_type("reflector")); assert!(!is_go_internal_type("internalConfig")); assert!(!is_go_internal_type("[5]int")); assert!(!is_go_internal_type("[10]MyStruct")); assert!(!is_go_internal_type("[3]*MyStruct")); assert!(!is_go_internal_type("groupReference")); assert!(!is_go_internal_type("")); }
612
613 #[test]
614 fn test_go_internal_prefixes_consistency() {
615 let prefixes = go_internal_prefixes();
617 assert!(prefixes.len() >= 30, "Should have comprehensive prefix list");
618
619 for prefix in prefixes {
622 let test_name = format!("{}TestType", prefix);
623 assert!(
624 is_go_internal_type(&test_name),
625 "Prefix '{}' should filter '{}' - check match statement coverage",
626 prefix,
627 test_name
628 );
629 }
630 }
631
632 #[test]
633 fn test_go_internal_type_edge_cases() {
634 assert!(!is_go_internal_type("r")); assert!(!is_go_internal_type("s")); assert!(!is_go_internal_type("i")); assert!(!is_go_internal_type("g")); assert!(!is_go_internal_type("t")); assert!(!is_go_internal_type("[")); assert!(is_go_internal_type("[][]int")); assert!(is_go_internal_type("[]*[]int")); assert!(is_go_internal_type("type\u{00B7}inner\u{00B7}func"));
648
649 assert!(is_go_internal_type("runtime\u{00B7}internal"));
651 }
652}