1use crate::{
4 core::{mapping::ModuleMapping, GlobalVariableInfo, ModuleAddress, Result, SourceLocation},
5 module::ModuleData,
6};
7use object::{Object, ObjectSection};
8use std::collections::HashMap;
9use std::path::{Path, PathBuf};
10
11#[derive(Debug, Clone)]
13pub enum ModuleLoadingEvent {
14 Discovered {
16 module_path: String,
17 current: usize,
18 total: usize,
19 },
20 LoadingStarted {
22 module_path: String,
23 current: usize,
24 total: usize,
25 },
26 LoadingCompleted {
28 module_path: String,
29 stats: ModuleLoadingStats,
30 current: usize,
31 total: usize,
32 },
33 LoadingFailed {
35 module_path: String,
36 error: String,
37 current: usize,
38 total: usize,
39 },
40}
41
42#[derive(Debug, Clone)]
44pub struct ModuleLoadingStats {
45 pub functions: usize,
46 pub variables: usize,
47 pub types: usize,
48 pub load_time_ms: u64,
49}
50
51#[derive(Debug)]
53pub struct DwarfAnalyzer {
54 pid: u32,
56 modules: HashMap<PathBuf, ModuleData>,
58}
59
60impl DwarfAnalyzer {
61 pub async fn from_pid(pid: u32) -> Result<Self> {
63 Self::from_pid_parallel(pid).await
64 }
65
66 pub fn is_inline_at(&mut self, module_address: &ModuleAddress) -> Option<bool> {
70 if let Some(module_data) = self.modules.get_mut(&module_address.module_path) {
71 module_data.is_inline_at(module_address.address)
72 } else {
73 None
74 }
75 }
76
77 pub fn resolve_struct_type_shallow_by_name_in_module<P: AsRef<Path>>(
79 &mut self,
80 module_path: P,
81 name: &str,
82 ) -> Option<crate::TypeInfo> {
83 let path_buf = module_path.as_ref().to_path_buf();
84 if let Some(module_data) = self.modules.get_mut(&path_buf) {
85 return module_data.resolve_struct_type_shallow_by_name(name);
86 }
87 None
88 }
89
90 pub fn resolve_struct_type_shallow_by_name(&mut self, name: &str) -> Option<crate::TypeInfo> {
92 for module_data in self.modules.values_mut() {
93 if let Some(t) = module_data.resolve_struct_type_shallow_by_name(name) {
94 return Some(t);
95 }
96 }
97 None
98 }
99
100 pub fn resolve_union_type_shallow_by_name_in_module<P: AsRef<Path>>(
102 &mut self,
103 module_path: P,
104 name: &str,
105 ) -> Option<crate::TypeInfo> {
106 let path_buf = module_path.as_ref().to_path_buf();
107 if let Some(module_data) = self.modules.get_mut(&path_buf) {
108 return module_data.resolve_union_type_shallow_by_name(name);
109 }
110 None
111 }
112
113 pub fn resolve_union_type_shallow_by_name(&mut self, name: &str) -> Option<crate::TypeInfo> {
115 for module_data in self.modules.values_mut() {
116 if let Some(t) = module_data.resolve_union_type_shallow_by_name(name) {
117 return Some(t);
118 }
119 }
120 None
121 }
122
123 pub fn resolve_enum_type_shallow_by_name_in_module<P: AsRef<Path>>(
125 &mut self,
126 module_path: P,
127 name: &str,
128 ) -> Option<crate::TypeInfo> {
129 let path_buf = module_path.as_ref().to_path_buf();
130 if let Some(module_data) = self.modules.get_mut(&path_buf) {
131 return module_data.resolve_enum_type_shallow_by_name(name);
132 }
133 None
134 }
135
136 pub fn resolve_enum_type_shallow_by_name(&mut self, name: &str) -> Option<crate::TypeInfo> {
138 for module_data in self.modules.values_mut() {
139 if let Some(t) = module_data.resolve_enum_type_shallow_by_name(name) {
140 return Some(t);
141 }
142 }
143 None
144 }
145
146 pub async fn from_pid_parallel(pid: u32) -> Result<Self> {
148 Self::from_pid_parallel_with_config(pid, &[], false, |_event| {}).await
149 }
150
151 pub async fn from_pid_parallel_with_progress<F>(pid: u32, progress_callback: F) -> Result<Self>
153 where
154 F: Fn(ModuleLoadingEvent) + Send + Sync + 'static,
155 {
156 Self::from_pid_parallel_with_config(pid, &[], false, progress_callback).await
157 }
158
159 pub async fn from_pid_parallel_with_config<F>(
161 pid: u32,
162 debug_search_paths: &[String],
163 allow_loose_debug_match: bool,
164 progress_callback: F,
165 ) -> Result<Self>
166 where
167 F: Fn(ModuleLoadingEvent) + Send + Sync + 'static,
168 {
169 tracing::info!("Creating DWARF analyzer for PID {} (parallel)", pid);
170
171 let mut coord = ghostscope_process::ProcessManager::new();
173 coord.ensure_prefill_pid(pid)?;
174 let mut module_mappings: Vec<crate::core::mapping::ModuleMapping> = Vec::new();
175 if let Some(entries) = coord.cached_offsets_with_paths_for_pid(pid) {
176 use std::collections::HashSet;
177 let mut seen = HashSet::new();
178 for e in entries {
179 if seen.insert(e.module_path.clone()) {
180 let mut mm = crate::core::mapping::ModuleMapping::from_path(
181 std::path::PathBuf::from(&e.module_path),
182 );
183 mm.loaded_address = Some(e.base);
184 mm.size = e.size;
185 module_mappings.push(mm);
186 }
187 }
188 }
189
190 tracing::info!(
191 "Discovered {} modules for PID {}",
192 module_mappings.len(),
193 pid
194 );
195
196 for (index, mapping) in module_mappings.iter().enumerate() {
198 progress_callback(ModuleLoadingEvent::Discovered {
199 module_path: mapping.path.to_string_lossy().to_string(),
200 current: index + 1,
201 total: module_mappings.len(),
202 });
203 }
204
205 let mut loader = crate::loader::ModuleLoader::new(module_mappings).parallel();
207
208 if !debug_search_paths.is_empty() {
210 loader = loader.with_debug_search_paths(debug_search_paths.to_vec());
211 }
212 loader = loader.with_loose_debug_match(allow_loose_debug_match);
213
214 let modules = loader
215 .with_progress_callback(progress_callback)
216 .load()
217 .await?;
218
219 tracing::info!(
220 "Created DWARF analyzer for PID {} with {} modules (parallel)",
221 pid,
222 modules.len()
223 );
224
225 Ok(Self::from_modules(pid, modules))
226 }
227
228 pub async fn from_exec_path<P: AsRef<std::path::Path>>(exec_path: P) -> Result<Self> {
230 Self::from_exec_path_with_config(exec_path, &[], false).await
231 }
232
233 pub async fn from_exec_path_with_config<P: AsRef<std::path::Path>>(
235 exec_path: P,
236 debug_search_paths: &[String],
237 allow_loose_debug_match: bool,
238 ) -> Result<Self> {
239 let exec_path = exec_path.as_ref().to_path_buf();
240 tracing::info!(
241 "Creating DWARF analyzer for executable: {}",
242 exec_path.display()
243 );
244
245 let mut analyzer = Self {
246 pid: 0, modules: HashMap::new(),
248 };
249
250 let module_mapping = ModuleMapping {
253 path: exec_path.clone(),
254 loaded_address: None, size: 0, };
257
258 match ModuleData::load_parallel(module_mapping, debug_search_paths, allow_loose_debug_match)
260 .await
261 {
262 Ok(module_data) => {
263 analyzer.modules.insert(exec_path.clone(), module_data);
264 tracing::info!(
265 "Created DWARF analyzer for executable {} with 1 module",
266 exec_path.display()
267 );
268 }
269 Err(e) => {
270 return Err(crate::DwarfError::ModuleLoadError(format!(
271 "Failed to load executable {}: {}",
272 exec_path.display(),
273 e
274 ))
275 .into());
276 }
277 }
278
279 Ok(analyzer)
280 }
281
282 pub(crate) fn from_modules(pid: u32, modules: Vec<ModuleData>) -> Self {
284 let mut analyzer = Self {
285 pid,
286 modules: HashMap::new(),
287 };
288
289 for module in modules {
290 let module_path = module.module_path().clone();
291 analyzer.modules.insert(module_path, module);
292 }
293
294 tracing::info!(
295 "Created DWARF analyzer for PID {} with {} pre-loaded modules",
296 pid,
297 analyzer.modules.len()
298 );
299
300 analyzer
301 }
302
303 pub fn lookup_function_addresses(&self, name: &str) -> Vec<ModuleAddress> {
306 let mut results = Vec::new();
307
308 for (module_path, module_data) in &self.modules {
309 let addresses = module_data.lookup_function_addresses_any(name);
310
311 for address in addresses {
313 tracing::debug!(
314 "Function '{}' found in module {} at address: 0x{:x}",
315 name,
316 module_path.display(),
317 address
318 );
319 results.push(ModuleAddress::new(module_path.clone(), address));
320 }
321 }
322
323 results.sort_by(|a, b| {
325 let pa = a.module_path.to_string_lossy();
326 let pb = b.module_path.to_string_lossy();
327 match pa.cmp(&pb) {
328 std::cmp::Ordering::Equal => a.address.cmp(&b.address),
329 other => other,
330 }
331 });
332 results
333 }
334
335 pub fn vaddr_to_file_offset<P: AsRef<std::path::Path>>(
338 &self,
339 module_path: P,
340 vaddr: u64,
341 ) -> Option<u64> {
342 let path_buf = module_path.as_ref().to_path_buf();
343 if let Some(module_data) = self.modules.get(&path_buf) {
344 module_data.vaddr_to_file_offset(vaddr)
345 } else {
346 None
347 }
348 }
349
350 pub fn get_all_variables_at_address(
355 &mut self,
356 module_address: &ModuleAddress,
357 ) -> Result<Vec<crate::data::VariableWithEvaluation>> {
358 tracing::info!(
359 "Looking up variables at address 0x{:x} in module {}",
360 module_address.address,
361 module_address.module_display()
362 );
363
364 if let Some(module_data) = self.modules.get_mut(&module_address.module_path) {
365 module_data.get_all_variables_at_address(module_address.address)
366 } else {
367 tracing::warn!(
368 "Module {} not found in loaded modules",
369 module_address.module_display()
370 );
371 Err(anyhow::anyhow!(
372 "Module {} not loaded",
373 module_address.module_display()
374 ))
375 }
376 }
377
378 pub fn plan_chain_access(
380 &mut self,
381 module_address: &ModuleAddress,
382 base_var: &str,
383 chain: &[String],
384 ) -> Result<Option<crate::data::VariableWithEvaluation>> {
385 if let Some(module_data) = self.modules.get_mut(&module_address.module_path) {
386 module_data.plan_chain_access(module_address.address, base_var, chain)
387 } else {
388 Ok(None)
389 }
390 }
391
392 pub fn get_loaded_modules(&self) -> Vec<&PathBuf> {
394 self.modules.keys().collect()
395 }
396
397 pub fn find_global_variables_by_name(&self, name: &str) -> Vec<(PathBuf, GlobalVariableInfo)> {
399 let mut results = Vec::new();
400 for (module_path, module_data) in &self.modules {
401 let vars = module_data.find_global_variables_by_name_any(name);
402 for v in vars {
403 results.push((module_path.clone(), v));
404 }
405 }
406 if !results.is_empty() {
407 return results;
408 }
409
410 for (module_path, module_data) in &self.modules {
412 let all = module_data.list_all_global_variables();
413 for v in all {
414 let leaf = v.name.rsplit("::").next().unwrap_or(&v.name).to_string();
415 if v.name == name || leaf == name {
416 results.push((module_path.clone(), v));
417 }
418 }
419 }
420
421 results
422 }
423
424 pub fn plan_global_chain_access(
432 &mut self,
433 prefer_module: &PathBuf,
434 base: &str,
435 fields: &[String],
436 ) -> Result<Option<(PathBuf, crate::data::VariableWithEvaluation)>> {
437 let matches = self.find_global_variables_by_name(base);
439 if matches.is_empty() {
440 return Ok(None);
442 }
443
444 let mut ordered: Vec<(PathBuf, GlobalVariableInfo)> = Vec::new();
446 for (mpath, info) in matches.iter() {
447 if *mpath == *prefer_module {
448 ordered.push((mpath.clone(), info.clone()));
449 }
450 }
451 for (mpath, info) in matches.into_iter() {
452 if mpath != *prefer_module {
453 ordered.push((mpath, info));
454 }
455 }
456
457 for (mpath, info) in ordered.into_iter() {
458 if let Some(link) = info.link_address {
460 if let Ok(Some((off, final_ty))) = self.compute_global_member_static_offset(
461 &mpath,
462 link,
463 info.unit_offset,
464 info.die_offset,
465 fields,
466 ) {
467 let name = if fields.is_empty() {
468 base.to_string()
469 } else {
470 format!("{base}.{}", fields.join("."))
471 };
472 let var = crate::data::VariableWithEvaluation {
473 name,
474 type_name: final_ty.type_name(),
475 dwarf_type: Some(final_ty),
476 evaluation_result: crate::core::EvaluationResult::MemoryLocation(
477 crate::core::LocationResult::Address(link + off),
478 ),
479 scope_depth: 0,
480 is_parameter: false,
481 is_artificial: false,
482 };
483 tracing::info!(
484 "plan_global_chain_access: resolved '{}' in module '{}' via static-offset",
485 base,
486 mpath.display()
487 );
488 return Ok(Some((mpath, var)));
489 }
490 }
491
492 let ma = ModuleAddress::new(mpath.clone(), 0);
494 match self.plan_chain_access(&ma, base, fields) {
495 Ok(Some(v)) => {
496 tracing::info!(
497 "plan_global_chain_access: resolved '{}' in module '{}' via planner",
498 base,
499 ma.module_display()
500 );
501 return Ok(Some((mpath, v)));
502 }
503 Ok(None) => {}
504 Err(e) => {
505 tracing::debug!(
506 "plan_global_chain_access: planner miss in module '{}': {}",
507 ma.module_display(),
508 e
509 );
510 }
511 }
512 }
513
514 Ok(None)
515 }
516
517 pub fn resolve_variable_by_offsets_in_module<P: AsRef<Path>>(
519 &mut self,
520 module_path: P,
521 cu_off: gimli::DebugInfoOffset,
522 die_off: gimli::UnitOffset,
523 ) -> Result<crate::data::VariableWithEvaluation> {
524 let path_buf = module_path.as_ref().to_path_buf();
525 if let Some(module_data) = self.modules.get_mut(&path_buf) {
526 let items = vec![(cu_off, die_off)];
527 let vars = module_data.resolve_variables_by_offsets_at_address(0, &items)?;
528 let mut var = vars.into_iter().next().ok_or_else(|| {
529 anyhow::anyhow!(
530 "Failed to resolve variable at offsets {:?}/{:?} in module {}",
531 cu_off,
532 die_off,
533 path_buf.display()
534 )
535 })?;
536 if var.dwarf_type.is_none() {
537 if let Some(ti) = module_data.shallow_type_for_variable_offsets(cu_off, die_off) {
538 var.type_name = ti.type_name();
539 var.dwarf_type = Some(ti);
540 }
541 }
542 Ok(var)
543 } else {
544 Err(anyhow::anyhow!(
545 "Module {} not loaded",
546 module_path.as_ref().display()
547 ))
548 }
549 }
550
551 pub fn list_all_global_variables(&self) -> Vec<(PathBuf, GlobalVariableInfo)> {
553 let mut results = Vec::new();
554 for (module_path, module_data) in &self.modules {
555 for v in module_data.list_all_global_variables() {
556 results.push((module_path.clone(), v));
557 }
558 }
559 results
560 }
561
562 pub fn classify_section_for_address<P: AsRef<Path>>(
564 &self,
565 module_path: P,
566 vaddr: u64,
567 ) -> Option<crate::core::SectionType> {
568 let path = module_path.as_ref();
569 if let Some(module_data) = self.modules.get(path) {
570 module_data.classify_section_for_vaddr(vaddr)
571 } else {
572 None
573 }
574 }
575
576 pub fn compute_global_member_static_offset<P: AsRef<Path>>(
578 &mut self,
579 module_path: P,
580 link_address: u64,
581 cu_off: gimli::DebugInfoOffset,
582 var_die: gimli::UnitOffset,
583 fields: &[String],
584 ) -> Result<Option<(u64, crate::TypeInfo)>> {
585 let path_buf = module_path.as_ref().to_path_buf();
586 if let Some(module_data) = self.modules.get_mut(&path_buf) {
587 module_data.compute_global_member_static_offset(cu_off, var_die, link_address, fields)
588 } else {
589 Err(anyhow::anyhow!(
590 "Module {} not loaded",
591 module_path.as_ref().display()
592 ))
593 }
594 }
595
596 pub fn lookup_function_address_by_name(&self, function_name: &str) -> Option<ModuleAddress> {
599 let module_addresses = self.lookup_function_addresses(function_name);
600
601 if let Some(first_module_address) = module_addresses.first() {
602 tracing::info!(
603 "Found function '{}' in module '{}' at address 0x{:x}",
604 function_name,
605 first_module_address.module_display(),
606 first_module_address.address
607 );
608 Some(first_module_address.clone())
609 } else {
610 tracing::warn!("Function '{}' not found in any module", function_name);
611 None
612 }
613 }
614
615 pub fn lookup_source_location(
618 &mut self,
619 module_address: &ModuleAddress,
620 ) -> Option<SourceLocation> {
621 if let Some(module_data) = self.modules.get_mut(&module_address.module_path) {
622 module_data.lookup_source_location(module_address.address)
623 } else {
624 tracing::warn!("Module {} not found", module_address.module_display());
625 None
626 }
627 }
628
629 pub fn lookup_addresses_by_source_line(
632 &self,
633 file_path: &str,
634 line_number: u32,
635 ) -> Vec<ModuleAddress> {
636 let mut results = Vec::new();
637
638 for (module_path, module_data) in &self.modules {
640 let addresses = module_data.lookup_addresses_by_source_line(file_path, line_number);
641
642 for address in addresses {
644 results.push(ModuleAddress::new(module_path.clone(), address));
645 }
646 }
647
648 if !results.is_empty() {
649 tracing::info!(
650 "Found {} addresses for {}:{} across {} modules",
651 results.len(),
652 file_path,
653 line_number,
654 self.modules.len()
655 );
656 }
657
658 results.sort_by(|a, b| {
659 let pa = a.module_path.to_string_lossy();
660 let pb = b.module_path.to_string_lossy();
661 match pa.cmp(&pb) {
662 std::cmp::Ordering::Equal => a.address.cmp(&b.address),
663 other => other,
664 }
665 });
666 results
667 }
668
669 pub fn get_all_function_names(&self) -> Vec<String> {
671 let mut all_names = std::collections::HashSet::new();
672 for module_data in self.modules.values() {
673 for name in module_data.get_function_names() {
674 all_names.insert(name.clone());
675 }
676 }
677 all_names.into_iter().collect()
678 }
679
680 pub fn get_stats(&self) -> AnalyzerStats {
682 let mut total_functions = 0;
683 let mut total_variables = 0;
684 let mut total_line_headers = 0;
685 let mut total_cache_entries = 0;
686
687 for module_data in self.modules.values() {
688 total_functions += module_data.get_function_names().len();
689 total_variables += module_data.get_variable_names().len();
690 total_line_headers += module_data.get_line_header_count();
691 let (cache_entries, _) = module_data.get_cache_stats();
692 total_cache_entries += cache_entries;
693 }
694
695 AnalyzerStats {
696 pid: self.pid,
697 module_count: self.modules.len(),
698 total_functions,
699 total_variables,
700 total_line_headers,
701 total_cache_entries,
702 }
703 }
704
705 pub fn get_module_stats(&self) -> ModuleStats {
707 let mut total_symbols = 0;
708 let mut executable_modules = 0;
709 let mut library_modules = 0;
710
711 for (module_path, module_data) in &self.modules {
712 let function_names = module_data.get_function_names();
713 total_symbols += function_names.len();
714
715 if self.is_main_executable_module(module_path) {
717 executable_modules += 1;
718 } else {
719 library_modules += 1;
720 }
721 }
722
723 ModuleStats {
724 total_modules: self.modules.len(),
725 executable_modules,
726 library_modules,
727 total_symbols,
728 modules_with_debug_info: self.modules.len(), }
730 }
731
732 pub fn get_main_executable(&self) -> Option<MainExecutableInfo> {
734 for module_path in self.modules.keys() {
736 if self.is_main_executable_module(module_path) {
737 return Some(MainExecutableInfo {
738 path: module_path.to_string_lossy().to_string(),
739 });
740 }
741 }
742 None
743 }
744
745 fn is_main_executable_module(&self, module_path: &Path) -> bool {
747 let filename = module_path
749 .file_name()
750 .and_then(|name| name.to_str())
751 .unwrap_or("");
752
753 !filename.contains(".so") &&
755 !module_path.to_string_lossy().starts_with("/lib") &&
757 !module_path.to_string_lossy().starts_with("/usr/lib")
758 }
759
760 pub fn list_functions(&self) -> Vec<String> {
762 let mut all_functions = Vec::new();
763
764 for module_data in self.modules.values() {
765 let function_names = module_data.get_function_names();
766 for name in function_names {
767 all_functions.push(name.clone());
768 }
769 }
770
771 all_functions.sort();
773 all_functions.dedup();
774
775 tracing::debug!(
776 "Listed {} unique functions across {} modules",
777 all_functions.len(),
778 self.modules.len()
779 );
780
781 all_functions
782 }
783
784 pub fn lookup_functions_by_pattern(&self, pattern: &str) -> Vec<String> {
786 let all_functions = self.list_functions();
787 all_functions
788 .into_iter()
789 .filter(|name| name.contains(pattern))
790 .collect()
791 }
792
793 pub fn lookup_all_function_names(&self) -> Vec<String> {
795 self.list_functions()
796 }
797
798 pub fn get_pid(&self) -> u32 {
800 self.pid
801 }
802
803 pub fn get_shared_library_info(&self) -> Vec<SharedLibraryInfo> {
805 self.modules
806 .iter()
807 .filter(|(path, _)| self.is_shared_library(path))
808 .map(|(path, module_data)| {
809 let mapping = module_data.module_mapping();
810 let debug_file_path = module_data
811 .get_debug_file_path()
812 .map(|p| p.to_string_lossy().to_string());
813
814 SharedLibraryInfo {
815 from_address: mapping.loaded_address.unwrap_or(0),
816 to_address: mapping.loaded_address.map_or(0, |addr| addr + mapping.size),
817 symbols_read: !module_data.get_function_names().is_empty(),
818 debug_info_available: module_data.has_dwarf_info(),
820 library_path: path.to_string_lossy().to_string(),
821 size: mapping.size,
822 debug_file_path,
823 }
824 })
825 .collect()
826 }
827
828 pub fn get_executable_file_info(&self) -> Option<ExecutableFileInfo> {
830 let executable = self
832 .modules
833 .iter()
834 .find(|(path, _)| !self.is_shared_library(path))?;
835
836 let (exe_path, module_data) = executable;
837 let file_path = exe_path.to_string_lossy().to_string();
838
839 let file_bytes = std::fs::read(exe_path).ok()?;
841 let obj = object::File::parse(&file_bytes[..]).ok()?;
842
843 let file_type = match obj.format() {
845 object::BinaryFormat::Elf => {
846 if obj.is_64() {
847 "ELF 64-bit executable"
848 } else {
849 "ELF 32-bit executable"
850 }
851 }
852 _ => "Unknown format",
853 }
854 .to_string();
855
856 let has_symbols = !module_data.get_function_names().is_empty()
858 || obj.symbols().count() > 0
859 || obj.dynamic_symbols().count() > 0;
860
861 let has_debug_info = module_data.has_dwarf_info();
864
865 let debug_file_path = module_data.get_debug_file_path();
867
868 let load_bias = if self.pid != 0 {
870 module_data.module_mapping().loaded_address.unwrap_or(0)
871 } else {
872 0
873 };
874
875 let entry_point = Some(obj.entry() + load_bias);
877
878 let text_section = obj.section_by_name(".text").map(|section| {
880 let addr = section.address() + load_bias;
881 let size = section.size();
882 SectionInfo {
883 start_address: addr,
884 end_address: addr + size,
885 size,
886 }
887 });
888
889 let data_section = obj.section_by_name(".data").map(|section| {
891 let addr = section.address() + load_bias;
892 let size = section.size();
893 SectionInfo {
894 start_address: addr,
895 end_address: addr + size,
896 size,
897 }
898 });
899
900 let mode_description = if self.pid != 0 {
902 format!("Attached to process {} (PID mode)", self.pid)
903 } else {
904 "Static analysis mode (target file specified with -t)".to_string()
905 };
906
907 Some(ExecutableFileInfo {
908 file_path,
909 file_type,
910 entry_point,
911 has_symbols,
912 has_debug_info,
913 debug_file_path: debug_file_path.map(|p| p.to_string_lossy().to_string()),
914 text_section,
915 data_section,
916 mode_description,
917 })
918 }
919
920 fn is_shared_library(&self, module_path: &Path) -> bool {
924 let filename = module_path
925 .file_name()
926 .and_then(|name| name.to_str())
927 .unwrap_or("");
928
929 filename.contains(".so")
931 || module_path.to_string_lossy().starts_with("/lib")
932 || module_path.to_string_lossy().starts_with("/usr/lib")
933 }
934
935 pub fn find_symbol_by_module_address(&self, module_address: &ModuleAddress) -> Option<String> {
937 if let Some(module_data) = self.modules.get(&module_address.module_path) {
938 module_data.find_symbol_by_address(module_address.address)
939 } else {
940 None
941 }
942 }
943
944 pub fn get_grouped_file_info_by_module(&self) -> Result<Vec<(String, Vec<SimpleFileInfo>)>> {
946 let mut grouped = Vec::new();
947
948 for (module_path, module_data) in &self.modules {
949 let files = module_data.get_all_files();
950 if !files.is_empty() {
951 let simple_files: Vec<SimpleFileInfo> = files
952 .into_iter()
953 .map(|source_file| SimpleFileInfo {
954 full_path: source_file.full_path,
955 basename: source_file.filename,
956 directory: source_file.directory_path,
957 })
958 .collect();
959
960 grouped.push((module_path.to_string_lossy().to_string(), simple_files));
961 }
962 }
963
964 Ok(grouped)
965 }
966}
967
968#[derive(Debug, Clone)]
971pub struct ModuleStats {
972 pub total_modules: usize,
973 pub executable_modules: usize,
974 pub library_modules: usize,
975 pub total_symbols: usize,
976 pub modules_with_debug_info: usize,
977}
978
979#[derive(Debug, Clone)]
981pub struct MainExecutableInfo {
982 pub path: String,
983}
984
985#[derive(Debug, Clone)]
987pub struct AnalyzerStats {
988 pub pid: u32,
989 pub module_count: usize,
990 pub total_functions: usize,
991 pub total_variables: usize,
992 pub total_line_headers: usize,
993 pub total_cache_entries: usize,
994}
995
996#[derive(Debug, Clone)]
998pub struct SharedLibraryInfo {
999 pub from_address: u64, pub to_address: u64, pub symbols_read: bool, pub debug_info_available: bool, pub library_path: String, pub size: u64, pub debug_file_path: Option<String>, }
1007
1008#[derive(Debug, Clone)]
1010pub struct ExecutableFileInfo {
1011 pub file_path: String,
1012 pub file_type: String,
1013 pub entry_point: Option<u64>,
1014 pub has_symbols: bool,
1015 pub has_debug_info: bool,
1016 pub debug_file_path: Option<String>,
1017 pub text_section: Option<SectionInfo>,
1018 pub data_section: Option<SectionInfo>,
1019 pub mode_description: String,
1020}
1021
1022#[derive(Debug, Clone)]
1024pub struct SectionInfo {
1025 pub start_address: u64,
1026 pub end_address: u64,
1027 pub size: u64,
1028}
1029
1030#[derive(Debug, Clone)]
1032pub struct SimpleFileInfo {
1033 pub full_path: String,
1034 pub basename: String,
1035 pub directory: String,
1036}