1use crate::{
4 core::{
5 mapping::ModuleMapping, CallerFrameRecovery, GlobalVariableInfo, ModuleAddress, Result,
6 SourceLocation,
7 },
8 objfile::LoadedObjfile,
9};
10use object::{Object, ObjectSection};
11use std::collections::HashMap;
12use std::path::{Path, PathBuf};
13
14#[derive(Debug, Clone)]
16pub enum ModuleLoadingEvent {
17 Discovered {
19 module_path: String,
20 current: usize,
21 total: usize,
22 },
23 LoadingStarted {
25 module_path: String,
26 current: usize,
27 total: usize,
28 },
29 LoadingCompleted {
31 module_path: String,
32 stats: ModuleLoadingStats,
33 current: usize,
34 total: usize,
35 },
36 LoadingFailed {
38 module_path: String,
39 error: String,
40 current: usize,
41 total: usize,
42 },
43}
44
45#[derive(Debug, Clone)]
47pub struct ModuleLoadingStats {
48 pub functions: usize,
49 pub variables: usize,
50 pub types: usize,
51 pub load_time_ms: u64,
52 pub parse_time_ms: u64,
53 pub index_time_ms: u64,
54 pub module_total_time_ms: u64,
55}
56
57#[derive(Debug, Clone)]
59pub struct AddressQueryResult {
60 pub module_path: PathBuf,
61 pub address: u64,
62 pub source_file: Option<String>,
63 pub source_line: Option<u32>,
64 pub source_column: Option<u32>,
65 pub function_name: Option<String>,
66 pub is_inline: Option<bool>,
67 pub variables: Vec<crate::VariableWithEvaluation>,
68 pub parameters: Vec<crate::VariableWithEvaluation>,
69}
70
71#[derive(Debug, Clone)]
73pub struct FunctionQueryResult {
74 pub function_name: String,
75 pub addresses: Vec<AddressQueryResult>,
76}
77
78#[derive(Debug)]
80pub struct DwarfAnalyzer {
81 pid: u32,
83 modules: HashMap<PathBuf, LoadedObjfile>,
85}
86
87impl DwarfAnalyzer {
88 fn resolve_type_shallow_by_name_in_module_with_tags<P: AsRef<Path>>(
89 &self,
90 module_path: P,
91 name: &str,
92 tags: &[gimli::DwTag],
93 ) -> Option<crate::TypeInfo> {
94 let path_buf = module_path.as_ref().to_path_buf();
95 self.modules
96 .get(&path_buf)
97 .and_then(|module_data| module_data.resolve_type_shallow_by_name_with_tags(name, tags))
98 }
99
100 fn resolve_type_shallow_by_name_with_tags(
101 &self,
102 name: &str,
103 tags: &[gimli::DwTag],
104 ) -> Option<crate::TypeInfo> {
105 self.modules
106 .values()
107 .find_map(|module_data| module_data.resolve_type_shallow_by_name_with_tags(name, tags))
108 }
109
110 fn build_address_query_result(
111 &self,
112 module_address: &ModuleAddress,
113 ) -> Result<AddressQueryResult> {
114 let mut variables = Vec::new();
115 let mut parameters = Vec::new();
116
117 for variable in self.get_all_variables_at_address(module_address)? {
118 if variable.is_parameter {
119 parameters.push(variable);
120 } else {
121 variables.push(variable);
122 }
123 }
124
125 let source_location = self.lookup_source_location(module_address);
126 let function_name = self.find_function_name_by_module_address(module_address);
127 let is_inline = self.is_inline_at(module_address);
128
129 Ok(AddressQueryResult {
130 module_path: module_address.module_path.clone(),
131 address: module_address.address,
132 source_file: source_location.as_ref().map(|sl| sl.file_path.clone()),
133 source_line: source_location.as_ref().map(|sl| sl.line_number),
134 source_column: source_location.as_ref().and_then(|sl| sl.column),
135 function_name,
136 is_inline,
137 variables,
138 parameters,
139 })
140 }
141
142 fn query_module_addresses(
143 &self,
144 module_addresses: Vec<ModuleAddress>,
145 ) -> Result<Vec<AddressQueryResult>> {
146 module_addresses
147 .iter()
148 .map(|module_address| self.build_address_query_result(module_address))
149 .collect()
150 }
151
152 fn query_module_addresses_best_effort(
153 &self,
154 module_addresses: Vec<ModuleAddress>,
155 query_label: &str,
156 ) -> Result<Vec<AddressQueryResult>> {
157 let mut results = Vec::new();
158 let mut first_error: Option<(ModuleAddress, String)> = None;
159
160 for module_address in &module_addresses {
161 match self.build_address_query_result(module_address) {
162 Ok(result) => results.push(result),
163 Err(error) => {
164 let error_string = error.to_string();
165 tracing::warn!(
166 "Skipping failed address query for {} at {}:0x{:x}: {}",
167 query_label,
168 module_address.module_display(),
169 module_address.address,
170 error_string
171 );
172
173 if first_error.is_none() {
174 first_error = Some((module_address.clone(), error_string));
175 }
176 }
177 }
178 }
179
180 if results.is_empty() {
181 if let Some((module_address, error)) = first_error {
182 return Err(anyhow::anyhow!(
183 "Failed to analyze any address for {} (first failure at {}:0x{:x}: {})",
184 query_label,
185 module_address.module_display(),
186 module_address.address,
187 error
188 ));
189 }
190 }
191
192 Ok(results)
193 }
194
195 fn find_function_name_by_module_address(
196 &self,
197 module_address: &ModuleAddress,
198 ) -> Option<String> {
199 self.modules
200 .get(&module_address.module_path)
201 .and_then(|module_data| {
202 module_data.find_function_name_by_address(module_address.address)
203 })
204 }
205
206 pub async fn from_pid(pid: u32) -> Result<Self> {
208 Self::from_pid_parallel(pid).await
209 }
210
211 pub fn is_inline_at(&self, module_address: &ModuleAddress) -> Option<bool> {
215 if let Some(module_data) = self.modules.get(&module_address.module_path) {
216 module_data.is_inline_at(module_address.address)
217 } else {
218 None
219 }
220 }
221
222 pub fn resolve_struct_type_shallow_by_name_in_module<P: AsRef<Path>>(
224 &self,
225 module_path: P,
226 name: &str,
227 ) -> Option<crate::TypeInfo> {
228 self.resolve_type_shallow_by_name_in_module_with_tags(
229 module_path,
230 name,
231 &[
232 gimli::constants::DW_TAG_structure_type,
233 gimli::constants::DW_TAG_class_type,
234 ],
235 )
236 }
237
238 pub fn resolve_struct_type_shallow_by_name(&self, name: &str) -> Option<crate::TypeInfo> {
240 self.resolve_type_shallow_by_name_with_tags(
241 name,
242 &[
243 gimli::constants::DW_TAG_structure_type,
244 gimli::constants::DW_TAG_class_type,
245 ],
246 )
247 }
248
249 pub fn resolve_union_type_shallow_by_name_in_module<P: AsRef<Path>>(
251 &self,
252 module_path: P,
253 name: &str,
254 ) -> Option<crate::TypeInfo> {
255 self.resolve_type_shallow_by_name_in_module_with_tags(
256 module_path,
257 name,
258 &[gimli::constants::DW_TAG_union_type],
259 )
260 }
261
262 pub fn resolve_union_type_shallow_by_name(&self, name: &str) -> Option<crate::TypeInfo> {
264 self.resolve_type_shallow_by_name_with_tags(name, &[gimli::constants::DW_TAG_union_type])
265 }
266
267 pub fn resolve_enum_type_shallow_by_name_in_module<P: AsRef<Path>>(
269 &self,
270 module_path: P,
271 name: &str,
272 ) -> Option<crate::TypeInfo> {
273 self.resolve_type_shallow_by_name_in_module_with_tags(
274 module_path,
275 name,
276 &[gimli::constants::DW_TAG_enumeration_type],
277 )
278 }
279
280 pub fn resolve_enum_type_shallow_by_name(&self, name: &str) -> Option<crate::TypeInfo> {
282 self.resolve_type_shallow_by_name_with_tags(
283 name,
284 &[gimli::constants::DW_TAG_enumeration_type],
285 )
286 }
287
288 pub async fn from_pid_parallel(pid: u32) -> Result<Self> {
290 Self::from_pid_parallel_with_config(pid, &[], false, |_event| {}).await
291 }
292
293 pub async fn from_pid_parallel_with_progress<F>(pid: u32, progress_callback: F) -> Result<Self>
295 where
296 F: Fn(ModuleLoadingEvent) + Send + Sync + 'static,
297 {
298 Self::from_pid_parallel_with_config(pid, &[], false, progress_callback).await
299 }
300
301 pub async fn from_pid_parallel_with_config<F>(
303 pid: u32,
304 debug_search_paths: &[String],
305 allow_loose_debug_match: bool,
306 progress_callback: F,
307 ) -> Result<Self>
308 where
309 F: Fn(ModuleLoadingEvent) + Send + Sync + 'static,
310 {
311 tracing::info!("Creating DWARF analyzer for PID {} (parallel)", pid);
312
313 let mut coord = ghostscope_process::ProcessManager::new();
315 coord.ensure_prefill_pid(pid)?;
316 let mut module_mappings: Vec<crate::core::mapping::ModuleMapping> = Vec::new();
317 if let Some(entries) = coord.cached_offsets_with_paths_for_pid(pid) {
318 use std::collections::HashSet;
319 let mut seen = HashSet::new();
320 for e in entries {
321 if seen.insert(e.module_path.clone()) {
322 let mut mm = crate::core::mapping::ModuleMapping::from_path(
323 std::path::PathBuf::from(&e.module_path),
324 );
325 mm.loaded_address = Some(e.base);
326 mm.size = e.size;
327 module_mappings.push(mm);
328 }
329 }
330 }
331
332 tracing::info!(
333 "Discovered {} modules for PID {}",
334 module_mappings.len(),
335 pid
336 );
337
338 for (index, mapping) in module_mappings.iter().enumerate() {
340 progress_callback(ModuleLoadingEvent::Discovered {
341 module_path: mapping.path.to_string_lossy().to_string(),
342 current: index + 1,
343 total: module_mappings.len(),
344 });
345 }
346
347 let mut loader = crate::loader::ModuleLoader::new(module_mappings).parallel();
349
350 if !debug_search_paths.is_empty() {
352 loader = loader.with_debug_search_paths(debug_search_paths.to_vec());
353 }
354 loader = loader.with_loose_debug_match(allow_loose_debug_match);
355
356 let modules = loader
357 .with_progress_callback(progress_callback)
358 .load()
359 .await?;
360
361 tracing::info!(
362 "Created DWARF analyzer for PID {} with {} modules (parallel)",
363 pid,
364 modules.len()
365 );
366
367 Ok(Self::from_modules(pid, modules))
368 }
369
370 pub async fn from_exec_path<P: AsRef<std::path::Path>>(exec_path: P) -> Result<Self> {
372 Self::from_exec_path_with_config(exec_path, &[], false).await
373 }
374
375 pub async fn from_exec_path_with_config<P: AsRef<std::path::Path>>(
377 exec_path: P,
378 debug_search_paths: &[String],
379 allow_loose_debug_match: bool,
380 ) -> Result<Self> {
381 Self::from_exec_path_with_config_and_progress(
382 exec_path,
383 debug_search_paths,
384 allow_loose_debug_match,
385 |_event| {},
386 )
387 .await
388 }
389
390 pub async fn from_exec_path_with_config_and_progress<P, F>(
392 exec_path: P,
393 debug_search_paths: &[String],
394 allow_loose_debug_match: bool,
395 progress_callback: F,
396 ) -> Result<Self>
397 where
398 P: AsRef<std::path::Path>,
399 F: Fn(ModuleLoadingEvent) + Send + Sync + 'static,
400 {
401 let exec_path = exec_path.as_ref().to_path_buf();
402 tracing::info!(
403 "Creating DWARF analyzer for executable: {}",
404 exec_path.display()
405 );
406
407 let mut analyzer = Self {
408 pid: 0, modules: HashMap::new(),
410 };
411
412 let module_mapping = ModuleMapping {
415 path: exec_path.clone(),
416 loaded_address: None, size: 0, };
419 let module_path = exec_path.to_string_lossy().to_string();
420
421 progress_callback(ModuleLoadingEvent::Discovered {
422 module_path: module_path.clone(),
423 current: 1,
424 total: 1,
425 });
426 progress_callback(ModuleLoadingEvent::LoadingStarted {
427 module_path: module_path.clone(),
428 current: 1,
429 total: 1,
430 });
431
432 let start_time = std::time::Instant::now();
434 match LoadedObjfile::load_parallel(
435 module_mapping,
436 debug_search_paths,
437 allow_loose_debug_match,
438 )
439 .await
440 {
441 Ok(module_data) => {
442 let (functions, variables, types) = module_data.get_lightweight_index().get_stats();
443 let (parse_time_ms, index_time_ms, module_total_time_ms) =
444 module_data.get_load_timing_ms();
445 progress_callback(ModuleLoadingEvent::LoadingCompleted {
446 module_path,
447 stats: ModuleLoadingStats {
448 functions,
449 variables,
450 types,
451 load_time_ms: start_time.elapsed().as_millis() as u64,
452 parse_time_ms,
453 index_time_ms,
454 module_total_time_ms,
455 },
456 current: 1,
457 total: 1,
458 });
459 analyzer.modules.insert(exec_path.clone(), module_data);
460 tracing::info!(
461 "Created DWARF analyzer for executable {} with 1 module",
462 exec_path.display()
463 );
464 }
465 Err(e) => {
466 progress_callback(ModuleLoadingEvent::LoadingFailed {
467 module_path,
468 error: e.to_string(),
469 current: 1,
470 total: 1,
471 });
472 return Err(crate::DwarfError::ModuleLoadError(format!(
473 "Failed to load executable {}: {}",
474 exec_path.display(),
475 e
476 ))
477 .into());
478 }
479 }
480
481 Ok(analyzer)
482 }
483
484 pub(crate) fn from_modules(pid: u32, modules: Vec<LoadedObjfile>) -> Self {
486 let mut analyzer = Self {
487 pid,
488 modules: HashMap::new(),
489 };
490
491 for module in modules {
492 let module_path = module.module_path().clone();
493 analyzer.modules.insert(module_path, module);
494 }
495
496 tracing::info!(
497 "Created DWARF analyzer for PID {} with {} pre-loaded modules",
498 pid,
499 analyzer.modules.len()
500 );
501
502 analyzer
503 }
504
505 pub fn lookup_function_addresses(&self, name: &str) -> Vec<ModuleAddress> {
508 let mut results = Vec::new();
509
510 for (module_path, module_data) in &self.modules {
511 let addresses = module_data.lookup_function_addresses_any(name);
512
513 for address in addresses {
515 tracing::debug!(
516 "Function '{}' found in module {} at address: 0x{:x}",
517 name,
518 module_path.display(),
519 address
520 );
521 results.push(ModuleAddress::new(module_path.clone(), address));
522 }
523 }
524
525 results.sort_by(|a, b| {
527 let pa = a.module_path.to_string_lossy();
528 let pb = b.module_path.to_string_lossy();
529 match pa.cmp(&pb) {
530 std::cmp::Ordering::Equal => a.address.cmp(&b.address),
531 other => other,
532 }
533 });
534 results
535 }
536
537 pub fn query_function(&self, name: &str) -> Result<FunctionQueryResult> {
539 let module_addresses = self.lookup_function_addresses(name);
540 let addresses = self.query_module_addresses(module_addresses)?;
541 Ok(FunctionQueryResult {
542 function_name: name.to_string(),
543 addresses,
544 })
545 }
546
547 pub fn query_function_best_effort(&self, name: &str) -> Result<FunctionQueryResult> {
550 let module_addresses = self.lookup_function_addresses(name);
551 let addresses = self
552 .query_module_addresses_best_effort(module_addresses, &format!("function '{name}'"))?;
553 Ok(FunctionQueryResult {
554 function_name: name.to_string(),
555 addresses,
556 })
557 }
558
559 pub fn vaddr_to_file_offset<P: AsRef<std::path::Path>>(
562 &self,
563 module_path: P,
564 vaddr: u64,
565 ) -> Option<u64> {
566 let path_buf = module_path.as_ref().to_path_buf();
567 if let Some(module_data) = self.modules.get(&path_buf) {
568 module_data.vaddr_to_file_offset(vaddr)
569 } else {
570 None
571 }
572 }
573
574 pub fn get_all_variables_at_address(
579 &self,
580 module_address: &ModuleAddress,
581 ) -> Result<Vec<crate::VariableWithEvaluation>> {
582 tracing::info!(
583 "Looking up variables at address 0x{:x} in module {}",
584 module_address.address,
585 module_address.module_display()
586 );
587
588 if let Some(module_data) = self.modules.get(&module_address.module_path) {
589 module_data.get_all_variables_at_address(module_address.address)
590 } else {
591 tracing::warn!(
592 "Module {} not found in loaded modules",
593 module_address.module_display()
594 );
595 Err(anyhow::anyhow!(
596 "Module {} not loaded",
597 module_address.module_display()
598 ))
599 }
600 }
601
602 pub fn plan_chain_access(
604 &self,
605 module_address: &ModuleAddress,
606 base_var: &str,
607 chain: &[String],
608 ) -> Result<Option<crate::VariableWithEvaluation>> {
609 if let Some(module_data) = self.modules.get(&module_address.module_path) {
610 module_data.plan_chain_access(module_address.address, base_var, chain)
611 } else {
612 Ok(None)
613 }
614 }
615
616 pub fn recover_caller_frame(
618 &self,
619 module_address: &ModuleAddress,
620 registers: &[u16],
621 ) -> Result<Option<CallerFrameRecovery>> {
622 if let Some(module_data) = self.modules.get(&module_address.module_path) {
623 module_data.recover_caller_frame(module_address.address, registers)
624 } else {
625 Ok(None)
626 }
627 }
628
629 pub fn get_loaded_modules(&self) -> Vec<&PathBuf> {
631 self.modules.keys().collect()
632 }
633
634 pub fn find_global_variables_by_name(&self, name: &str) -> Vec<(PathBuf, GlobalVariableInfo)> {
636 let mut results = Vec::new();
637 for (module_path, module_data) in &self.modules {
638 let vars = module_data.find_global_variables_by_name_any(name);
639 for v in vars {
640 results.push((module_path.clone(), v));
641 }
642 }
643 if !results.is_empty() {
644 return results;
645 }
646
647 for (module_path, module_data) in &self.modules {
649 let all = module_data.list_all_global_variables();
650 for v in all {
651 let leaf = v.name.rsplit("::").next().unwrap_or(&v.name).to_string();
652 if v.name == name || leaf == name {
653 results.push((module_path.clone(), v));
654 }
655 }
656 }
657
658 results
659 }
660
661 pub fn plan_global_chain_access(
669 &self,
670 prefer_module: &PathBuf,
671 base: &str,
672 fields: &[String],
673 ) -> Result<Option<(PathBuf, crate::VariableWithEvaluation)>> {
674 let matches = self.find_global_variables_by_name(base);
676 if matches.is_empty() {
677 return Ok(None);
679 }
680
681 let mut ordered: Vec<(PathBuf, GlobalVariableInfo)> = Vec::new();
683 for (mpath, info) in matches.iter() {
684 if *mpath == *prefer_module {
685 ordered.push((mpath.clone(), info.clone()));
686 }
687 }
688 for (mpath, info) in matches.into_iter() {
689 if mpath != *prefer_module {
690 ordered.push((mpath, info));
691 }
692 }
693
694 for (mpath, info) in ordered.into_iter() {
695 if let Some(link) = info.link_address {
697 if let Ok(Some((off, final_ty))) = self.compute_global_member_static_offset(
698 &mpath,
699 link,
700 info.unit_offset,
701 info.die_offset,
702 fields,
703 ) {
704 let name = if fields.is_empty() {
705 base.to_string()
706 } else {
707 format!("{base}.{}", fields.join("."))
708 };
709 let var = crate::VariableWithEvaluation {
710 name,
711 type_name: final_ty.type_name(),
712 dwarf_type: Some(final_ty),
713 evaluation_result: crate::core::EvaluationResult::MemoryLocation(
714 crate::core::LocationResult::Address(link + off),
715 ),
716 scope_depth: 0,
717 is_parameter: false,
718 is_artificial: false,
719 };
720 tracing::info!(
721 "plan_global_chain_access: resolved '{}' in module '{}' via static-offset",
722 base,
723 mpath.display()
724 );
725 return Ok(Some((mpath, var)));
726 }
727 }
728
729 let ma = ModuleAddress::new(mpath.clone(), 0);
731 match self.plan_chain_access(&ma, base, fields) {
732 Ok(Some(v)) => {
733 tracing::info!(
734 "plan_global_chain_access: resolved '{}' in module '{}' via planner",
735 base,
736 ma.module_display()
737 );
738 return Ok(Some((mpath, v)));
739 }
740 Ok(None) => {}
741 Err(e) => {
742 tracing::debug!(
743 "plan_global_chain_access: planner miss in module '{}': {}",
744 ma.module_display(),
745 e
746 );
747 }
748 }
749 }
750
751 Ok(None)
752 }
753
754 pub fn resolve_variable_by_offsets_in_module<P: AsRef<Path>>(
756 &self,
757 module_path: P,
758 cu_off: gimli::DebugInfoOffset,
759 die_off: gimli::UnitOffset,
760 ) -> Result<crate::VariableWithEvaluation> {
761 let path_buf = module_path.as_ref().to_path_buf();
762 if let Some(module_data) = self.modules.get(&path_buf) {
763 let items = vec![(cu_off, die_off)];
764 let vars = module_data.resolve_variables_by_offsets_at_address(0, &items)?;
765 let mut var = vars.into_iter().next().ok_or_else(|| {
766 anyhow::anyhow!(
767 "Failed to resolve variable at offsets {:?}/{:?} in module {}",
768 cu_off,
769 die_off,
770 path_buf.display()
771 )
772 })?;
773 if var.dwarf_type.is_none() {
774 if let Some(ti) = module_data.shallow_type_for_variable_offsets(cu_off, die_off) {
775 var.type_name = ti.type_name();
776 var.dwarf_type = Some(ti);
777 }
778 }
779 Ok(var)
780 } else {
781 Err(anyhow::anyhow!(
782 "Module {} not loaded",
783 module_path.as_ref().display()
784 ))
785 }
786 }
787
788 pub fn list_all_global_variables(&self) -> Vec<(PathBuf, GlobalVariableInfo)> {
790 let mut results = Vec::new();
791 for (module_path, module_data) in &self.modules {
792 for v in module_data.list_all_global_variables() {
793 results.push((module_path.clone(), v));
794 }
795 }
796 results
797 }
798
799 pub fn classify_section_for_address<P: AsRef<Path>>(
801 &self,
802 module_path: P,
803 vaddr: u64,
804 ) -> Option<crate::core::SectionType> {
805 let path = module_path.as_ref();
806 if let Some(module_data) = self.modules.get(path) {
807 module_data.classify_section_for_vaddr(vaddr)
808 } else {
809 None
810 }
811 }
812
813 pub fn compute_global_member_static_offset<P: AsRef<Path>>(
815 &self,
816 module_path: P,
817 link_address: u64,
818 cu_off: gimli::DebugInfoOffset,
819 var_die: gimli::UnitOffset,
820 fields: &[String],
821 ) -> Result<Option<(u64, crate::TypeInfo)>> {
822 let path_buf = module_path.as_ref().to_path_buf();
823 if let Some(module_data) = self.modules.get(&path_buf) {
824 module_data.compute_global_member_static_offset(cu_off, var_die, link_address, fields)
825 } else {
826 Err(anyhow::anyhow!(
827 "Module {} not loaded",
828 module_path.as_ref().display()
829 ))
830 }
831 }
832
833 pub fn lookup_function_address_by_name(&self, function_name: &str) -> Option<ModuleAddress> {
836 let module_addresses = self.lookup_function_addresses(function_name);
837
838 if let Some(first_module_address) = module_addresses.first() {
839 tracing::info!(
840 "Found function '{}' in module '{}' at address 0x{:x}",
841 function_name,
842 first_module_address.module_display(),
843 first_module_address.address
844 );
845 Some(first_module_address.clone())
846 } else {
847 tracing::warn!("Function '{}' not found in any module", function_name);
848 None
849 }
850 }
851
852 pub fn lookup_source_location(&self, module_address: &ModuleAddress) -> Option<SourceLocation> {
855 if let Some(module_data) = self.modules.get(&module_address.module_path) {
856 module_data.lookup_source_location(module_address.address)
857 } else {
858 tracing::warn!("Module {} not found", module_address.module_display());
859 None
860 }
861 }
862
863 pub fn lookup_addresses_by_source_line(
866 &self,
867 file_path: &str,
868 line_number: u32,
869 ) -> Vec<ModuleAddress> {
870 let mut results = Vec::new();
871
872 for (module_path, module_data) in &self.modules {
874 let addresses = module_data.lookup_addresses_by_source_line(file_path, line_number);
875
876 for address in addresses {
878 results.push(ModuleAddress::new(module_path.clone(), address));
879 }
880 }
881
882 if !results.is_empty() {
883 tracing::info!(
884 "Found {} addresses for {}:{} across {} modules",
885 results.len(),
886 file_path,
887 line_number,
888 self.modules.len()
889 );
890 }
891
892 results.sort_by(|a, b| {
893 let pa = a.module_path.to_string_lossy();
894 let pb = b.module_path.to_string_lossy();
895 match pa.cmp(&pb) {
896 std::cmp::Ordering::Equal => a.address.cmp(&b.address),
897 other => other,
898 }
899 });
900 results
901 }
902
903 pub fn query_source_line(
905 &self,
906 file_path: &str,
907 line_number: u32,
908 ) -> Result<Vec<AddressQueryResult>> {
909 let module_addresses = self.lookup_addresses_by_source_line(file_path, line_number);
910 self.query_module_addresses(module_addresses)
911 }
912
913 pub fn query_source_line_best_effort(
917 &self,
918 file_path: &str,
919 line_number: u32,
920 ) -> Result<Vec<AddressQueryResult>> {
921 let module_addresses = self.lookup_addresses_by_source_line(file_path, line_number);
922 self.query_module_addresses_best_effort(
923 module_addresses,
924 &format!("source line '{file_path}:{line_number}'"),
925 )
926 }
927
928 pub fn query_address<P: AsRef<Path>>(
930 &self,
931 module_path: P,
932 address: u64,
933 ) -> Result<AddressQueryResult> {
934 let module_address = ModuleAddress::new(module_path.as_ref().to_path_buf(), address);
935 self.build_address_query_result(&module_address)
936 }
937
938 pub fn get_all_function_names(&self) -> Vec<String> {
940 let mut all_names = std::collections::HashSet::new();
941 for module_data in self.modules.values() {
942 for name in module_data.get_function_names() {
943 all_names.insert(name.clone());
944 }
945 }
946 all_names.into_iter().collect()
947 }
948
949 pub fn get_stats(&self) -> AnalyzerStats {
951 let mut total_functions = 0;
952 let mut total_variables = 0;
953 let mut total_line_headers = 0;
954
955 for module_data in self.modules.values() {
956 total_functions += module_data.get_function_names().len();
957 total_variables += module_data.get_variable_names().len();
958 total_line_headers += module_data.get_line_header_count();
959 }
960
961 AnalyzerStats {
962 pid: self.pid,
963 module_count: self.modules.len(),
964 total_functions,
965 total_variables,
966 total_line_headers,
967 }
968 }
969
970 pub fn get_module_stats(&self) -> ModuleStats {
972 let mut total_symbols = 0;
973 let mut executable_modules = 0;
974 let mut library_modules = 0;
975
976 for (module_path, module_data) in &self.modules {
977 let function_names = module_data.get_function_names();
978 total_symbols += function_names.len();
979
980 if self.is_main_executable_module(module_path) {
982 executable_modules += 1;
983 } else {
984 library_modules += 1;
985 }
986 }
987
988 ModuleStats {
989 total_modules: self.modules.len(),
990 executable_modules,
991 library_modules,
992 total_symbols,
993 modules_with_debug_info: self.modules.len(), }
995 }
996
997 pub fn get_main_executable(&self) -> Option<MainExecutableInfo> {
999 for module_path in self.modules.keys() {
1001 if self.is_main_executable_module(module_path) {
1002 return Some(MainExecutableInfo {
1003 path: module_path.to_string_lossy().to_string(),
1004 });
1005 }
1006 }
1007 None
1008 }
1009
1010 fn is_main_executable_module(&self, module_path: &Path) -> bool {
1012 let filename = module_path
1014 .file_name()
1015 .and_then(|name| name.to_str())
1016 .unwrap_or("");
1017
1018 !filename.contains(".so") &&
1020 !module_path.to_string_lossy().starts_with("/lib") &&
1022 !module_path.to_string_lossy().starts_with("/usr/lib")
1023 }
1024
1025 pub fn list_functions(&self) -> Vec<String> {
1027 let mut all_functions = Vec::new();
1028
1029 for module_data in self.modules.values() {
1030 let function_names = module_data.get_function_names();
1031 for name in function_names {
1032 all_functions.push(name.clone());
1033 }
1034 }
1035
1036 all_functions.sort();
1038 all_functions.dedup();
1039
1040 tracing::debug!(
1041 "Listed {} unique functions across {} modules",
1042 all_functions.len(),
1043 self.modules.len()
1044 );
1045
1046 all_functions
1047 }
1048
1049 pub fn lookup_functions_by_pattern(&self, pattern: &str) -> Vec<String> {
1051 let all_functions = self.list_functions();
1052 all_functions
1053 .into_iter()
1054 .filter(|name| name.contains(pattern))
1055 .collect()
1056 }
1057
1058 pub fn lookup_all_function_names(&self) -> Vec<String> {
1060 self.list_functions()
1061 }
1062
1063 pub fn get_pid(&self) -> u32 {
1065 self.pid
1066 }
1067
1068 pub fn get_shared_library_info(&self) -> Vec<SharedLibraryInfo> {
1070 self.modules
1071 .iter()
1072 .filter(|(path, _)| self.is_shared_library(path))
1073 .map(|(path, module_data)| {
1074 let mapping = module_data.module_mapping();
1075 let debug_file_path = module_data
1076 .get_debug_file_path()
1077 .map(|p| p.to_string_lossy().to_string());
1078
1079 SharedLibraryInfo {
1080 from_address: mapping.loaded_address.unwrap_or(0),
1081 to_address: mapping.loaded_address.map_or(0, |addr| addr + mapping.size),
1082 symbols_read: !module_data.get_function_names().is_empty(),
1083 debug_info_available: module_data.has_dwarf_info(),
1085 library_path: path.to_string_lossy().to_string(),
1086 size: mapping.size,
1087 debug_file_path,
1088 }
1089 })
1090 .collect()
1091 }
1092
1093 pub fn get_executable_file_info(&self) -> Option<ExecutableFileInfo> {
1095 let executable = self
1097 .modules
1098 .iter()
1099 .find(|(path, _)| !self.is_shared_library(path))?;
1100
1101 let (exe_path, module_data) = executable;
1102 let file_path = exe_path.to_string_lossy().to_string();
1103
1104 let file_bytes = std::fs::read(exe_path).ok()?;
1106 let obj = object::File::parse(&file_bytes[..]).ok()?;
1107
1108 let file_type = match obj.format() {
1110 object::BinaryFormat::Elf => {
1111 if obj.is_64() {
1112 "ELF 64-bit executable"
1113 } else {
1114 "ELF 32-bit executable"
1115 }
1116 }
1117 _ => "Unknown format",
1118 }
1119 .to_string();
1120
1121 let has_symbols = !module_data.get_function_names().is_empty()
1123 || obj.symbols().count() > 0
1124 || obj.dynamic_symbols().count() > 0;
1125
1126 let has_debug_info = module_data.has_dwarf_info();
1129
1130 let debug_file_path = module_data.get_debug_file_path();
1132
1133 let load_bias = if self.pid != 0 {
1135 module_data.module_mapping().loaded_address.unwrap_or(0)
1136 } else {
1137 0
1138 };
1139
1140 let entry_point = Some(obj.entry() + load_bias);
1142
1143 let text_section = obj.section_by_name(".text").map(|section| {
1145 let addr = section.address() + load_bias;
1146 let size = section.size();
1147 SectionInfo {
1148 start_address: addr,
1149 end_address: addr + size,
1150 size,
1151 }
1152 });
1153
1154 let data_section = obj.section_by_name(".data").map(|section| {
1156 let addr = section.address() + load_bias;
1157 let size = section.size();
1158 SectionInfo {
1159 start_address: addr,
1160 end_address: addr + size,
1161 size,
1162 }
1163 });
1164
1165 let mode_description = if self.pid != 0 {
1167 format!("Attached to process {} (PID mode)", self.pid)
1168 } else {
1169 "Static analysis mode (target file specified with -t)".to_string()
1170 };
1171
1172 Some(ExecutableFileInfo {
1173 file_path,
1174 file_type,
1175 entry_point,
1176 has_symbols,
1177 has_debug_info,
1178 debug_file_path: debug_file_path.map(|p| p.to_string_lossy().to_string()),
1179 text_section,
1180 data_section,
1181 mode_description,
1182 })
1183 }
1184
1185 fn is_shared_library(&self, module_path: &Path) -> bool {
1189 let filename = module_path
1190 .file_name()
1191 .and_then(|name| name.to_str())
1192 .unwrap_or("");
1193
1194 filename.contains(".so")
1196 || module_path.to_string_lossy().starts_with("/lib")
1197 || module_path.to_string_lossy().starts_with("/usr/lib")
1198 }
1199
1200 pub fn get_grouped_file_info_by_module(&self) -> Result<Vec<(String, Vec<SimpleFileInfo>)>> {
1202 let mut grouped = Vec::new();
1203
1204 for (module_path, module_data) in &self.modules {
1205 let files = module_data.get_all_files();
1206 if !files.is_empty() {
1207 let simple_files: Vec<SimpleFileInfo> = files
1208 .into_iter()
1209 .map(|source_file| SimpleFileInfo {
1210 full_path: source_file.full_path,
1211 basename: source_file.filename,
1212 directory: source_file.directory_path,
1213 })
1214 .collect();
1215
1216 grouped.push((module_path.to_string_lossy().to_string(), simple_files));
1217 }
1218 }
1219
1220 Ok(grouped)
1221 }
1222}
1223
1224#[derive(Debug, Clone)]
1227pub struct ModuleStats {
1228 pub total_modules: usize,
1229 pub executable_modules: usize,
1230 pub library_modules: usize,
1231 pub total_symbols: usize,
1232 pub modules_with_debug_info: usize,
1233}
1234
1235#[derive(Debug, Clone)]
1237pub struct MainExecutableInfo {
1238 pub path: String,
1239}
1240
1241#[derive(Debug, Clone)]
1243pub struct AnalyzerStats {
1244 pub pid: u32,
1245 pub module_count: usize,
1246 pub total_functions: usize,
1247 pub total_variables: usize,
1248 pub total_line_headers: usize,
1249}
1250
1251#[derive(Debug, Clone)]
1253pub struct SharedLibraryInfo {
1254 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>, }
1262
1263#[derive(Debug, Clone)]
1265pub struct ExecutableFileInfo {
1266 pub file_path: String,
1267 pub file_type: String,
1268 pub entry_point: Option<u64>,
1269 pub has_symbols: bool,
1270 pub has_debug_info: bool,
1271 pub debug_file_path: Option<String>,
1272 pub text_section: Option<SectionInfo>,
1273 pub data_section: Option<SectionInfo>,
1274 pub mode_description: String,
1275}
1276
1277#[derive(Debug, Clone)]
1279pub struct SectionInfo {
1280 pub start_address: u64,
1281 pub end_address: u64,
1282 pub size: u64,
1283}
1284
1285#[derive(Debug, Clone)]
1287pub struct SimpleFileInfo {
1288 pub full_path: String,
1289 pub basename: String,
1290 pub directory: String,
1291}