calltrace/dwarf_analyzer.rs
1//! DWARF Debug Information Analysis
2//!
3//! This module parses DWARF debugging information to extract function signatures,
4//! parameter types, and other metadata required for argument capture.
5
6use crate::error::{CallTraceError, Result};
7use std::collections::HashMap;
8use std::ffi::c_void;
9use std::fs;
10
11/// Function information extracted from DWARF
12#[derive(Debug, Clone)]
13pub struct FunctionInfo {
14 pub name: String,
15 pub address: u64,
16 pub size: Option<u64>,
17 pub parameters: Vec<ParameterInfo>,
18 pub return_type: Option<TypeInfo>,
19 pub source_file: Option<String>,
20 pub line_number: Option<u32>,
21}
22
23/// Parameter information for function arguments
24#[derive(Debug, Clone)]
25pub struct ParameterInfo {
26 pub name: String,
27 pub type_info: TypeInfo,
28 pub location: Option<String>, // DW_AT_location if available
29}
30
31/// Type information extracted from DWARF
32#[derive(Debug, Clone)]
33pub struct TypeInfo {
34 pub name: String,
35 pub size: Option<u64>,
36 pub is_pointer: bool,
37 pub is_const: bool,
38 pub is_struct: bool,
39 pub is_array: bool,
40 pub array_size: Option<u64>,
41 pub base_type: Option<Box<TypeInfo>>,
42}
43
44/// DWARF analyzer context that caches parsed information
45pub struct DwarfAnalyzer {
46 executable_path: String,
47 function_cache: HashMap<u64, FunctionInfo>,
48 type_cache: HashMap<u64, TypeInfo>,
49 symbol_cache: HashMap<u64, String>, // Cache for resolved symbols
50 base_address: Option<u64>, // Program base address
51 #[cfg(feature = "dwarf_support")]
52 dwarf_data: Option<DwarfData>,
53}
54
55/// Cached DWARF data
56#[cfg(feature = "dwarf_support")]
57struct DwarfData {
58 #[allow(dead_code)]
59 dwarf: (), // Simplified for now
60 #[allow(dead_code)]
61 file_data: Vec<u8>, // Keep file data alive
62}
63
64impl DwarfAnalyzer {
65 /// Create a new DWARF analyzer for the specified executable
66 pub fn new(executable_path: &str) -> Result<Self> {
67 // Try to get the real executable path
68 let real_exe_path = std::fs::read_link("/proc/self/exe")
69 .unwrap_or_else(|_| executable_path.into())
70 .to_string_lossy()
71 .to_string();
72
73 let mut analyzer = DwarfAnalyzer {
74 executable_path: real_exe_path,
75 function_cache: HashMap::new(),
76 type_cache: HashMap::new(),
77 symbol_cache: HashMap::new(),
78 base_address: None,
79 #[cfg(feature = "dwarf_support")]
80 dwarf_data: None,
81 };
82
83 // Try to load DWARF data
84 #[cfg(feature = "dwarf_support")]
85 if let Err(e) = analyzer.load_dwarf_data() {
86 // Log warning but continue - we can still work with limited functionality
87 eprintln!("Warning: Failed to load DWARF data: {}", e);
88 }
89
90 // Initialize base address
91 analyzer.base_address = analyzer.get_program_base_address();
92
93 Ok(analyzer)
94 }
95
96 /// Load DWARF debugging information from the executable
97 #[cfg(feature = "dwarf_support")]
98 fn load_dwarf_data(&mut self) -> Result<()> {
99 let file_data = fs::read(&self.executable_path)
100 .map_err(|e| CallTraceError::DwarfError(format!("Failed to read executable: {}", e)))?;
101
102 // TODO: Temporary simplified implementation
103 // In the future, this will parse object file and load DWARF sections:
104 /*
105 let object = object::File::parse(&file_data[..])?;
106 let load_section = |id: gimli::SectionId| -> std::result::Result<Cow<[u8]>, gimli::Error> {
107 match object.section_by_name(id.name()) {
108 Some(ref section) => Ok(section
109 .uncompressed_data()
110 .unwrap_or(Cow::Borrowed(&[][..]))),
111 None => Ok(Cow::Borrowed(&[][..])),
112 }
113 };
114 let dwarf_cow = Dwarf::load(&load_section)?;
115 */
116 // Keep file data alive for future use
117 self.dwarf_data = Some(DwarfData {
118 dwarf: (),
119 file_data,
120 });
121
122 Ok(())
123 }
124
125 /// Get function information for a given address
126 pub fn get_function_info(&mut self, address: u64) -> Result<FunctionInfo> {
127 // Check cache first
128 if let Some(cached) = self.function_cache.get(&address) {
129 return Ok(cached.clone());
130 }
131
132 // Try to extract from DWARF data
133 #[cfg(feature = "dwarf_support")]
134 if let Some(info) = self.extract_function_info_from_dwarf(address)? {
135 self.function_cache.insert(address, info.clone());
136 return Ok(info);
137 }
138
139 // Fallback: create minimal function info using dladdr
140 let fallback_info = self.create_fallback_function_info(address)?;
141 self.function_cache.insert(address, fallback_info.clone());
142 Ok(fallback_info)
143 }
144
145 /// Extract function information from DWARF data
146 #[cfg(feature = "dwarf_support")]
147 fn extract_function_info_from_dwarf(&mut self, _address: u64) -> Result<Option<FunctionInfo>> {
148 // TODO: Temporary simplified implementation due to gimli API changes
149 // Full implementation will be restored after updating to new gimli API
150 Ok(None)
151 }
152
153 /*
154 // TODO: Restore these functions with updated gimli API
155
156 /// Extract function name from DIE
157 #[cfg(feature = "dwarf_support")]
158 fn extract_function_name(
159 &self,
160 dwarf: &Dwarf<gimli::EndianSlice<'static, gimli::LittleEndian>>,
161 unit: &gimli::Unit<gimli::EndianSlice<'static, gimli::LittleEndian>>,
162 entry: &gimli::DebuggingInformationEntry<gimli::EndianSlice<'static, gimli::LittleEndian>>,
163 ) -> Result<Option<String>> {
164 // Try DW_AT_name first
165 if let Some(name_attr) = entry.attr_value(gimli::DW_AT_name)
166 .map_err(|e| CallTraceError::DwarfError(format!("Error reading name: {}", e)))?
167 {
168 if let gimli::AttributeValue::DebugStrRef(offset) = name_attr {
169 let name = dwarf.debug_str.get_str(offset)
170 .map_err(|e| CallTraceError::DwarfError(format!("Error reading string: {}", e)))?;
171 return Ok(Some(name.to_string_lossy().to_string()));
172 }
173 }
174
175 // Try DW_AT_linkage_name for mangled names
176 if let Some(linkage_attr) = entry.attr_value(gimli::DW_AT_linkage_name)
177 .map_err(|e| CallTraceError::DwarfError(format!("Error reading linkage name: {}", e)))?
178 {
179 if let gimli::AttributeValue::DebugStrRef(offset) = linkage_attr {
180 let name = dwarf.debug_str.get_str(offset)
181 .map_err(|e| CallTraceError::DwarfError(format!("Error reading string: {}", e)))?;
182 let demangled = demangle_function_name(&name.to_string_lossy());
183 return Ok(Some(demangled));
184 }
185 }
186
187 Ok(None)
188 }
189
190 /// Extract function parameters from DIE
191 #[cfg(feature = "dwarf_support")]
192 fn extract_function_parameters(
193 &mut self,
194 dwarf: &Dwarf<gimli::EndianSlice<'static, gimli::LittleEndian>>,
195 unit: &gimli::Unit<gimli::EndianSlice<'static, gimli::LittleEndian>>,
196 entry: &gimli::DebuggingInformationEntry<gimli::EndianSlice<'static, gimli::LittleEndian>>,
197 ) -> Result<Vec<ParameterInfo>> {
198 let mut parameters = Vec::new();
199 let mut children = entry.children();
200
201 while let Some(child) = children.next()
202 .map_err(|e| CallTraceError::DwarfError(format!("Error reading child: {}", e)))?
203 {
204 if child.tag() == gimli::DW_TAG_formal_parameter {
205 if let Some(param) = self.extract_parameter_info(dwarf, unit, &child)? {
206 parameters.push(param);
207 }
208 }
209 }
210
211 Ok(parameters)
212 }
213
214 /// Extract individual parameter information
215 #[cfg(feature = "dwarf_support")]
216 fn extract_parameter_info(
217 &mut self,
218 dwarf: &Dwarf<gimli::EndianSlice<'static, gimli::LittleEndian>>,
219 unit: &gimli::Unit<gimli::EndianSlice<'static, gimli::LittleEndian>>,
220 entry: &gimli::DebuggingInformationEntry<gimli::EndianSlice<'static, gimli::LittleEndian>>,
221 ) -> Result<Option<ParameterInfo>> {
222 // Extract parameter name
223 let name = if let Some(name_attr) = entry.attr_value(gimli::DW_AT_name)
224 .map_err(|e| CallTraceError::DwarfError(format!("Error reading param name: {}", e)))?
225 {
226 if let gimli::AttributeValue::DebugStrRef(offset) = name_attr {
227 dwarf.debug_str.get_str(offset)
228 .map_err(|e| CallTraceError::DwarfError(format!("Error reading param string: {}", e)))?
229 .to_string_lossy().to_string()
230 } else {
231 "unnamed".to_string()
232 }
233 } else {
234 "unnamed".to_string()
235 };
236
237 // Extract type information
238 let type_info = if let Some(type_attr) = entry.attr_value(gimli::DW_AT_type)
239 .map_err(|e| CallTraceError::DwarfError(format!("Error reading param type: {}", e)))?
240 {
241 if let gimli::AttributeValue::UnitRef(type_offset) = type_attr {
242 self.extract_type_info_from_offset(dwarf, unit, type_offset)?
243 } else {
244 TypeInfo {
245 name: "unknown".to_string(),
246 size: None,
247 is_pointer: false,
248 is_const: false,
249 is_struct: false,
250 is_array: false,
251 array_size: None,
252 base_type: None,
253 }
254 }
255 } else {
256 TypeInfo {
257 name: "unknown".to_string(),
258 size: None,
259 is_pointer: false,
260 is_const: false,
261 is_struct: false,
262 is_array: false,
263 array_size: None,
264 base_type: None,
265 }
266 };
267
268 // Extract location information (register, stack offset, etc.)
269 let location = if let Some(loc_attr) = entry.attr_value(gimli::DW_AT_location)
270 .map_err(|e| CallTraceError::DwarfError(format!("Error reading param location: {}", e)))?
271 {
272 // This would parse DWARF location expressions
273 // For now, we'll just indicate that location data exists
274 Some("dwarf_location".to_string())
275 } else {
276 None
277 };
278
279 Ok(Some(ParameterInfo {
280 name,
281 type_info,
282 location,
283 }))
284 }
285
286 /// Extract type information from a type offset
287 #[cfg(feature = "dwarf_support")]
288 fn extract_type_info_from_offset(
289 &mut self,
290 dwarf: &Dwarf<gimli::EndianSlice<'static, gimli::LittleEndian>>,
291 unit: &gimli::Unit<gimli::EndianSlice<'static, gimli::LittleEndian>>,
292 type_offset: gimli::UnitOffset,
293 ) -> Result<TypeInfo> {
294 // Check cache first
295 let cache_key = type_offset.0 as u64;
296 if let Some(cached_type) = self.type_cache.get(&cache_key) {
297 return Ok(cached_type.clone());
298 }
299
300 // Get the type DIE
301 let mut entries = unit.entries_at_offset(type_offset)
302 .map_err(|e| CallTraceError::DwarfError(format!("Error getting type entry: {}", e)))?;
303
304 let (_, type_entry) = entries.next_entry()
305 .map_err(|e| CallTraceError::DwarfError(format!("Error reading type entry: {}", e)))?
306 .ok_or_else(|| CallTraceError::DwarfError("Type entry not found".to_string()))?;
307
308 let type_info = self.extract_type_info_from_die(dwarf, unit, &type_entry)?;
309
310 // Cache the result
311 self.type_cache.insert(cache_key, type_info.clone());
312
313 Ok(type_info)
314 }
315
316 /// Extract detailed type information from a type DIE
317 #[cfg(feature = "dwarf_support")]
318 fn extract_type_info_from_die(
319 &mut self,
320 dwarf: &Dwarf<gimli::EndianSlice<'static, gimli::LittleEndian>>,
321 unit: &gimli::Unit<gimli::EndianSlice<'static, gimli::LittleEndian>>,
322 entry: &gimli::DebuggingInformationEntry<gimli::EndianSlice<'static, gimli::LittleEndian>>,
323 ) -> Result<TypeInfo> {
324 let tag = entry.tag();
325
326 match tag {
327 gimli::DW_TAG_base_type => {
328 self.extract_base_type_info(dwarf, unit, entry)
329 }
330 gimli::DW_TAG_pointer_type => {
331 self.extract_pointer_type_info(dwarf, unit, entry)
332 }
333 gimli::DW_TAG_array_type => {
334 self.extract_array_type_info(dwarf, unit, entry)
335 }
336 gimli::DW_TAG_structure_type | gimli::DW_TAG_class_type => {
337 self.extract_struct_type_info(dwarf, unit, entry)
338 }
339 gimli::DW_TAG_const_type => {
340 self.extract_const_type_info(dwarf, unit, entry)
341 }
342 gimli::DW_TAG_typedef => {
343 self.extract_typedef_info(dwarf, unit, entry)
344 }
345 _ => {
346 // Fallback for unknown types
347 Ok(TypeInfo {
348 name: format!("unknown_type_{:?}", tag),
349 size: entry.attr_value(gimli::DW_AT_byte_size)
350 .ok()
351 .flatten()
352 .and_then(|v| {
353 if let gimli::AttributeValue::Udata(size) = v {
354 Some(size)
355 } else {
356 None
357 }
358 }),
359 is_pointer: false,
360 is_const: false,
361 is_struct: false,
362 is_array: false,
363 array_size: None,
364 base_type: None,
365 })
366 }
367 }
368 }
369
370 /// Extract return type information
371 #[cfg(feature = "dwarf_support")]
372 fn extract_return_type(
373 &mut self,
374 dwarf: &Dwarf<gimli::EndianSlice<'static, gimli::LittleEndian>>,
375 unit: &gimli::Unit<gimli::EndianSlice<'static, gimli::LittleEndian>>,
376 entry: &gimli::DebuggingInformationEntry<gimli::EndianSlice<'static, gimli::LittleEndian>>,
377 ) -> Result<Option<TypeInfo>> {
378 if let Some(type_attr) = entry.attr_value(gimli::DW_AT_type)
379 .map_err(|e| CallTraceError::DwarfError(format!("Error reading return type: {}", e)))?
380 {
381 if let gimli::AttributeValue::UnitRef(type_offset) = type_attr {
382 let type_info = self.extract_type_info_from_offset(dwarf, unit, type_offset)?;
383 return Ok(Some(type_info));
384 }
385 }
386
387 // No return type means void
388 Ok(None)
389 }
390
391 /// Extract source file and line number information
392 #[cfg(feature = "dwarf_support")]
393 fn extract_source_location(
394 &self,
395 dwarf: &Dwarf<gimli::EndianSlice<'static, gimli::LittleEndian>>,
396 unit: &gimli::Unit<gimli::EndianSlice<'static, gimli::LittleEndian>>,
397 entry: &gimli::DebuggingInformationEntry<gimli::EndianSlice<'static, gimli::LittleEndian>>,
398 ) -> Result<(Option<String>, Option<u32>)> {
399 let mut source_file = None;
400 let mut line_number = None;
401
402 // Extract line number
403 if let Some(line_attr) = entry.attr_value(gimli::DW_AT_decl_line)
404 .map_err(|e| CallTraceError::DwarfError(format!("Error reading line: {}", e)))?
405 {
406 if let gimli::AttributeValue::Udata(line) = line_attr {
407 line_number = Some(line as u32);
408 }
409 }
410
411 // Extract source file
412 if let Some(file_attr) = entry.attr_value(gimli::DW_AT_decl_file)
413 .map_err(|e| CallTraceError::DwarfError(format!("Error reading file: {}", e)))?
414 {
415 if let gimli::AttributeValue::Udata(file_index) = file_attr {
416 // This would require parsing the line number table to get the actual filename
417 // For now, we'll just use the file index
418 source_file = Some(format!("file_{}", file_index));
419 }
420 }
421
422 Ok((source_file, line_number))
423 }
424
425 /// Extract base type information (int, char, float, etc.)
426 #[cfg(feature = "dwarf_support")]
427 fn extract_base_type_info(
428 &self,
429 dwarf: &Dwarf<gimli::EndianSlice<'static, gimli::LittleEndian>>,
430 _unit: &gimli::Unit<gimli::EndianSlice<'static, gimli::LittleEndian>>,
431 entry: &gimli::DebuggingInformationEntry<gimli::EndianSlice<'static, gimli::LittleEndian>>,
432 ) -> Result<TypeInfo> {
433 // Extract type name
434 let name = if let Some(name_attr) = entry.attr_value(gimli::DW_AT_name)
435 .map_err(|e| CallTraceError::DwarfError(format!("Error reading base type name: {}", e)))?
436 {
437 if let gimli::AttributeValue::DebugStrRef(offset) = name_attr {
438 dwarf.debug_str.get_str(offset)
439 .map_err(|e| CallTraceError::DwarfError(format!("Error reading base type string: {}", e)))?
440 .to_string_lossy().to_string()
441 } else {
442 "unknown_base_type".to_string()
443 }
444 } else {
445 "unknown_base_type".to_string()
446 };
447
448 // Extract size
449 let size = entry.attr_value(gimli::DW_AT_byte_size)
450 .map_err(|e| CallTraceError::DwarfError(format!("Error reading base type size: {}", e)))?
451 .and_then(|v| {
452 if let gimli::AttributeValue::Udata(size) = v {
453 Some(size)
454 } else {
455 None
456 }
457 });
458
459 Ok(TypeInfo {
460 name,
461 size,
462 is_pointer: false,
463 is_const: false,
464 is_struct: false,
465 is_array: false,
466 array_size: None,
467 base_type: None,
468 })
469 }
470
471 /// Extract pointer type information
472 #[cfg(feature = "dwarf_support")]
473 fn extract_pointer_type_info(
474 &mut self,
475 dwarf: &Dwarf<gimli::EndianSlice<'static, gimli::LittleEndian>>,
476 unit: &gimli::Unit<gimli::EndianSlice<'static, gimli::LittleEndian>>,
477 entry: &gimli::DebuggingInformationEntry<gimli::EndianSlice<'static, gimli::LittleEndian>>,
478 ) -> Result<TypeInfo> {
479 // Get the pointed-to type
480 let base_type = if let Some(type_attr) = entry.attr_value(gimli::DW_AT_type)
481 .map_err(|e| CallTraceError::DwarfError(format!("Error reading pointer target type: {}", e)))?
482 {
483 if let gimli::AttributeValue::UnitRef(type_offset) = type_attr {
484 Some(Box::new(self.extract_type_info_from_offset(dwarf, unit, type_offset)?))
485 } else {
486 None
487 }
488 } else {
489 // Void pointer
490 None
491 };
492
493 // Construct pointer type name
494 let name = if let Some(ref base) = base_type {
495 format!("{}*", base.name)
496 } else {
497 "void*".to_string()
498 };
499
500 // Pointer size (typically 8 bytes on 64-bit systems)
501 let size = entry.attr_value(gimli::DW_AT_byte_size)
502 .map_err(|e| CallTraceError::DwarfError(format!("Error reading pointer size: {}", e)))?
503 .and_then(|v| {
504 if let gimli::AttributeValue::Udata(size) = v {
505 Some(size)
506 } else {
507 None
508 }
509 })
510 .or(Some(8)); // Default to 8 bytes
511
512 Ok(TypeInfo {
513 name,
514 size,
515 is_pointer: true,
516 is_const: false,
517 is_struct: false,
518 is_array: false,
519 array_size: None,
520 base_type,
521 })
522 }
523
524 /// Extract array type information
525 #[cfg(feature = "dwarf_support")]
526 fn extract_array_type_info(
527 &mut self,
528 dwarf: &Dwarf<gimli::EndianSlice<'static, gimli::LittleEndian>>,
529 unit: &gimli::Unit<gimli::EndianSlice<'static, gimli::LittleEndian>>,
530 entry: &gimli::DebuggingInformationEntry<gimli::EndianSlice<'static, gimli::LittleEndian>>,
531 ) -> Result<TypeInfo> {
532 // Get the element type
533 let base_type = if let Some(type_attr) = entry.attr_value(gimli::DW_AT_type)
534 .map_err(|e| CallTraceError::DwarfError(format!("Error reading array element type: {}", e)))?
535 {
536 if let gimli::AttributeValue::UnitRef(type_offset) = type_attr {
537 Some(Box::new(self.extract_type_info_from_offset(dwarf, unit, type_offset)?))
538 } else {
539 None
540 }
541 } else {
542 None
543 };
544
545 // Extract array dimensions by looking at subrange_type children
546 let mut array_size = None;
547 let mut children = entry.children();
548
549 while let Some(child) = children.next()
550 .map_err(|e| CallTraceError::DwarfError(format!("Error reading array child: {}", e)))?
551 {
552 if child.tag() == gimli::DW_TAG_subrange_type {
553 // Try to get the count or upper bound
554 if let Some(count_attr) = child.attr_value(gimli::DW_AT_count)
555 .map_err(|e| CallTraceError::DwarfError(format!("Error reading array count: {}", e)))?
556 {
557 if let gimli::AttributeValue::Udata(count) = count_attr {
558 array_size = Some(count);
559 break;
560 }
561 } else if let Some(upper_attr) = child.attr_value(gimli::DW_AT_upper_bound)
562 .map_err(|e| CallTraceError::DwarfError(format!("Error reading array upper bound: {}", e)))?
563 {
564 if let gimli::AttributeValue::Udata(upper) = upper_attr {
565 // Upper bound is typically count - 1 for zero-based arrays
566 array_size = Some(upper + 1);
567 break;
568 }
569 }
570 }
571 }
572
573 // Construct array type name
574 let name = if let Some(ref base) = base_type {
575 if let Some(size) = array_size {
576 format!("{}[{}]", base.name, size)
577 } else {
578 format!("{}[]", base.name)
579 }
580 } else {
581 "unknown_array".to_string()
582 };
583
584 // Calculate total size
585 let total_size = if let (Some(ref base), Some(count)) = (&base_type, array_size) {
586 base.size.map(|element_size| element_size * count)
587 } else {
588 None
589 };
590
591 Ok(TypeInfo {
592 name,
593 size: total_size,
594 is_pointer: false,
595 is_const: false,
596 is_struct: false,
597 is_array: true,
598 array_size,
599 base_type,
600 })
601 }
602
603 /// Extract structure/class type information
604 #[cfg(feature = "dwarf_support")]
605 fn extract_struct_type_info(
606 &self,
607 dwarf: &Dwarf<gimli::EndianSlice<'static, gimli::LittleEndian>>,
608 _unit: &gimli::Unit<gimli::EndianSlice<'static, gimli::LittleEndian>>,
609 entry: &gimli::DebuggingInformationEntry<gimli::EndianSlice<'static, gimli::LittleEndian>>,
610 ) -> Result<TypeInfo> {
611 // Extract struct name
612 let name = if let Some(name_attr) = entry.attr_value(gimli::DW_AT_name)
613 .map_err(|e| CallTraceError::DwarfError(format!("Error reading struct name: {}", e)))?
614 {
615 if let gimli::AttributeValue::DebugStrRef(offset) = name_attr {
616 dwarf.debug_str.get_str(offset)
617 .map_err(|e| CallTraceError::DwarfError(format!("Error reading struct string: {}", e)))?
618 .to_string_lossy().to_string()
619 } else {
620 "anonymous_struct".to_string()
621 }
622 } else {
623 "anonymous_struct".to_string()
624 };
625
626 // Extract size
627 let size = entry.attr_value(gimli::DW_AT_byte_size)
628 .map_err(|e| CallTraceError::DwarfError(format!("Error reading struct size: {}", e)))?
629 .and_then(|v| {
630 if let gimli::AttributeValue::Udata(size) = v {
631 Some(size)
632 } else {
633 None
634 }
635 });
636
637 // Note: In a complete implementation, we would also extract member information
638 // This would involve iterating through DW_TAG_member children
639
640 Ok(TypeInfo {
641 name,
642 size,
643 is_pointer: false,
644 is_const: false,
645 is_struct: true,
646 is_array: false,
647 array_size: None,
648 base_type: None,
649 })
650 }
651
652 /// Extract const type information
653 #[cfg(feature = "dwarf_support")]
654 fn extract_const_type_info(
655 &mut self,
656 dwarf: &Dwarf<gimli::EndianSlice<'static, gimli::LittleEndian>>,
657 unit: &gimli::Unit<gimli::EndianSlice<'static, gimli::LittleEndian>>,
658 entry: &gimli::DebuggingInformationEntry<gimli::EndianSlice<'static, gimli::LittleEndian>>,
659 ) -> Result<TypeInfo> {
660 // Get the underlying type
661 let mut base_type_info = if let Some(type_attr) = entry.attr_value(gimli::DW_AT_type)
662 .map_err(|e| CallTraceError::DwarfError(format!("Error reading const target type: {}", e)))?
663 {
664 if let gimli::AttributeValue::UnitRef(type_offset) = type_attr {
665 self.extract_type_info_from_offset(dwarf, unit, type_offset)?
666 } else {
667 TypeInfo {
668 name: "unknown".to_string(),
669 size: None,
670 is_pointer: false,
671 is_const: false,
672 is_struct: false,
673 is_array: false,
674 array_size: None,
675 base_type: None,
676 }
677 }
678 } else {
679 TypeInfo {
680 name: "unknown".to_string(),
681 size: None,
682 is_pointer: false,
683 is_const: false,
684 is_struct: false,
685 is_array: false,
686 array_size: None,
687 base_type: None,
688 }
689 };
690
691 // Mark as const and update name
692 base_type_info.is_const = true;
693 base_type_info.name = format!("const {}", base_type_info.name);
694
695 Ok(base_type_info)
696 }
697
698 /// Extract typedef information
699 #[cfg(feature = "dwarf_support")]
700 fn extract_typedef_info(
701 &mut self,
702 dwarf: &Dwarf<gimli::EndianSlice<'static, gimli::LittleEndian>>,
703 unit: &gimli::Unit<gimli::EndianSlice<'static, gimli::LittleEndian>>,
704 entry: &gimli::DebuggingInformationEntry<gimli::EndianSlice<'static, gimli::LittleEndian>>,
705 ) -> Result<TypeInfo> {
706 // Extract typedef name
707 let typedef_name = if let Some(name_attr) = entry.attr_value(gimli::DW_AT_name)
708 .map_err(|e| CallTraceError::DwarfError(format!("Error reading typedef name: {}", e)))?
709 {
710 if let gimli::AttributeValue::DebugStrRef(offset) = name_attr {
711 dwarf.debug_str.get_str(offset)
712 .map_err(|e| CallTraceError::DwarfError(format!("Error reading typedef string: {}", e)))?
713 .to_string_lossy().to_string()
714 } else {
715 "unknown_typedef".to_string()
716 }
717 } else {
718 "unknown_typedef".to_string()
719 };
720
721 // Get the underlying type
722 let mut base_type_info = if let Some(type_attr) = entry.attr_value(gimli::DW_AT_type)
723 .map_err(|e| CallTraceError::DwarfError(format!("Error reading typedef target type: {}", e)))?
724 {
725 if let gimli::AttributeValue::UnitRef(type_offset) = type_attr {
726 self.extract_type_info_from_offset(dwarf, unit, type_offset)?
727 } else {
728 TypeInfo {
729 name: "unknown".to_string(),
730 size: None,
731 is_pointer: false,
732 is_const: false,
733 is_struct: false,
734 is_array: false,
735 array_size: None,
736 base_type: None,
737 }
738 }
739 } else {
740 TypeInfo {
741 name: "unknown".to_string(),
742 size: None,
743 is_pointer: false,
744 is_const: false,
745 is_struct: false,
746 is_array: false,
747 array_size: None,
748 base_type: None,
749 }
750 };
751
752 // Use the typedef name but preserve other type information
753 base_type_info.name = typedef_name;
754
755 Ok(base_type_info)
756 }
757 */
758
759 /// Get program base address from /proc/self/maps
760 fn get_program_base_address(&self) -> Option<u64> {
761 use std::fs;
762
763 let maps = fs::read_to_string("/proc/self/maps").ok()?;
764 for line in maps.lines() {
765 if line.contains(&self.executable_path) && line.contains("r-xp") {
766 let parts: Vec<&str> = line.split('-').collect();
767 if let Some(base_str) = parts.first() {
768 return u64::from_str_radix(base_str, 16).ok();
769 }
770 }
771 }
772 None
773 }
774
775 /// Create fallback function info using dladdr
776 fn create_fallback_function_info(&self, address: u64) -> Result<FunctionInfo> {
777 use libc::{dladdr, Dl_info};
778 use std::ffi::CStr;
779
780 let mut info: Dl_info = unsafe { std::mem::zeroed() };
781 let result = unsafe { dladdr(address as *const c_void, &mut info) };
782
783 let name = if result != 0 && !info.dli_sname.is_null() {
784 let raw_name = unsafe {
785 CStr::from_ptr(info.dli_sname)
786 .to_string_lossy()
787 .into_owned()
788 };
789
790 // Try to demangle C++ function names
791 demangle_function_name(&raw_name)
792 } else {
793 format!("0x{:x}", address)
794 };
795
796 Ok(FunctionInfo {
797 name,
798 address,
799 size: None,
800 parameters: Vec::new(), // No parameter info available
801 return_type: None,
802 source_file: None,
803 line_number: None,
804 })
805 }
806
807 /// Get the number of cached functions
808 pub fn cache_size(&self) -> usize {
809 self.function_cache.len()
810 }
811
812 /// Clear the function cache
813 pub fn clear_cache(&mut self) {
814 self.function_cache.clear();
815 self.type_cache.clear();
816 self.symbol_cache.clear();
817 }
818}
819
820impl Drop for DwarfAnalyzer {
821 fn drop(&mut self) {
822 // Clean up any resources
823 self.clear_cache();
824 }
825}
826
827/// Helper function to demangle C++ function names
828pub fn demangle_function_name(mangled: &str) -> String {
829 // Try multiple demangling approaches
830
831 // First try: Use cxa_demangle from libc++abi
832 if let Some(demangled) = try_cxa_demangle(mangled) {
833 return demangled;
834 }
835
836 // Second try: Use external c++filt tool
837 if let Some(demangled) = try_cppfilt_demangle(mangled) {
838 return demangled;
839 }
840
841 // Fallback: Return original mangled name
842 mangled.to_string()
843}
844
845/// Try to demangle using libc++abi's cxa_demangle function
846fn try_cxa_demangle(mangled: &str) -> Option<String> {
847 use std::ffi::{CStr, CString};
848 use std::ptr;
849
850 // Only process names that look like C++ mangled names
851 if !mangled.starts_with("_Z") && !mangled.starts_with("__Z") {
852 return None;
853 }
854
855 let c_mangled = CString::new(mangled).ok()?;
856 let mut status: libc::c_int = 0;
857
858 unsafe {
859 // Try to get __cxa_demangle dynamically to avoid linking issues
860 let cxa_demangle_fn = libc::dlsym(libc::RTLD_DEFAULT, c"__cxa_demangle".as_ptr());
861 if cxa_demangle_fn.is_null() {
862 // __cxa_demangle not available (e.g., in C programs without libstdc++)
863 return None;
864 }
865
866 // Cast to the correct function pointer type
867 type CxaDemangleFn = extern "C" fn(
868 *const libc::c_char,
869 *mut libc::c_char,
870 *mut libc::size_t,
871 *mut libc::c_int,
872 ) -> *mut libc::c_char;
873
874 let demangle_fn: CxaDemangleFn = std::mem::transmute(cxa_demangle_fn);
875
876 let result = demangle_fn(
877 c_mangled.as_ptr(),
878 ptr::null_mut(),
879 ptr::null_mut(),
880 &mut status,
881 );
882
883 if status == 0 && !result.is_null() {
884 let demangled_cstr = CStr::from_ptr(result);
885 let demangled = demangled_cstr.to_string_lossy().into_owned();
886
887 // Free the memory allocated by __cxa_demangle
888 libc::free(result as *mut libc::c_void);
889
890 Some(demangled)
891 } else {
892 None
893 }
894 }
895}
896
897/// Try to demangle using external c++filt tool
898fn try_cppfilt_demangle(mangled: &str) -> Option<String> {
899 use std::process::Command;
900
901 // Only process names that look like C++ mangled names
902 if !mangled.starts_with("_Z") && !mangled.starts_with("__Z") {
903 return None;
904 }
905
906 // Try to run c++filt command
907 let output = Command::new("c++filt").arg(mangled).output().ok()?;
908
909 if output.status.success() {
910 let demangled = String::from_utf8(output.stdout).ok()?;
911 let trimmed = demangled.trim();
912
913 // If c++filt actually demangled something, it should be different from input
914 if trimmed != mangled && !trimmed.is_empty() {
915 Some(trimmed.to_string())
916 } else {
917 None
918 }
919 } else {
920 None
921 }
922}
923
924/*
925/// Extract type information from a DWARF DIE
926#[cfg(feature = "dwarf_support")]
927#[allow(dead_code)]
928fn extract_type_info<R: Reader>(
929 _dwarf: &Dwarf<R>,
930 _unit: &gimli::Unit<R>,
931 _entry: &DebuggingInformationEntry<R>,
932) -> Result<TypeInfo> {
933 // This would extract detailed type information from DWARF
934 // Including struct layouts, array sizes, pointer levels, etc.
935
936 Ok(TypeInfo {
937 name: "unknown".to_string(),
938 size: None,
939 is_pointer: false,
940 is_const: false,
941 is_struct: false,
942 is_array: false,
943 array_size: None,
944 base_type: None,
945 })
946}
947*/