ghostscope_dwarf/analyzer/
plan_global.rs1use super::DwarfAnalyzer;
2use crate::{
3 core::{GlobalVariableInfo, Provenance, Result},
4 semantics::{VariableAccessPath, VariableReadPlan},
5};
6use std::path::{Path, PathBuf};
7
8impl DwarfAnalyzer {
9 pub(super) fn select_unambiguous_global_plan(
10 base: &str,
11 mut candidates: Vec<(PathBuf, VariableReadPlan)>,
12 ) -> Result<Option<(PathBuf, VariableReadPlan)>> {
13 match candidates.len() {
14 0 => Ok(None),
15 1 => Ok(candidates.pop()),
16 count => {
17 let details = candidates
18 .iter()
19 .map(|(module_path, plan)| {
20 let declaration = plan
21 .declaration
22 .map(|die| format!(" cu={} die=0x{:x}", die.cu.0, die.offset))
23 .unwrap_or_default();
24 format!("{}{}", module_path.display(), declaration)
25 })
26 .collect::<Vec<_>>()
27 .join(", ");
28 Err(anyhow::anyhow!(
29 "Ambiguous global '{base}': {count} matches [{details}]"
30 ))
31 }
32 }
33 }
34
35 pub(super) fn select_global_plan_with_preferred_module(
36 base: &str,
37 prefer_module: &Path,
38 candidates: Vec<(PathBuf, VariableReadPlan)>,
39 ) -> Result<Option<(PathBuf, VariableReadPlan)>> {
40 let (preferred, fallback): (Vec<_>, Vec<_>) = candidates
41 .into_iter()
42 .partition(|(module_path, _)| module_path == prefer_module);
43 if !preferred.is_empty() {
44 return Self::select_unambiguous_global_plan(base, preferred);
45 }
46
47 Self::select_unambiguous_global_plan(base, fallback)
48 }
49
50 pub fn find_global_variables_by_name(&self, name: &str) -> Vec<(PathBuf, GlobalVariableInfo)> {
52 let mut results = Vec::new();
53 for (module_path, module_data) in &self.modules {
54 let vars = module_data.find_global_variables_by_name_any(name);
55 for v in vars {
56 results.push((module_path.clone(), v));
57 }
58 }
59 if !results.is_empty() {
60 return results;
61 }
62
63 for (module_path, module_data) in &self.modules {
65 let all = module_data.list_all_global_variables();
66 for v in all {
67 let leaf = v.name.rsplit("::").next().unwrap_or(&v.name).to_string();
68 if v.name == name || leaf == name {
69 results.push((module_path.clone(), v));
70 }
71 }
72 }
73
74 results
75 }
76
77 pub fn plan_global_access_read_plan(
79 &self,
80 prefer_module: &PathBuf,
81 base: &str,
82 path: &VariableAccessPath,
83 ) -> Result<Option<(PathBuf, VariableReadPlan)>> {
84 let matches = self.find_global_variables_by_name(base);
85 if matches.is_empty() {
86 return Ok(None);
87 }
88
89 let mut ordered: Vec<(PathBuf, GlobalVariableInfo)> = Vec::new();
90 for (module_path, info) in matches.iter() {
91 if *module_path == *prefer_module {
92 ordered.push((module_path.clone(), info.clone()));
93 }
94 }
95 for (module_path, info) in matches.into_iter() {
96 if module_path != *prefer_module {
97 ordered.push((module_path, info));
98 }
99 }
100
101 let mut direct_matches = Vec::new();
102 let mut last_error = None;
103 for (module_path, info) in ordered {
104 let base_plan = match self.resolve_variable_read_plan_by_offsets_in_module(
105 &module_path,
106 info.unit_offset,
107 info.die_offset,
108 Provenance::Synthesized {
109 detail: "global access".to_string(),
110 },
111 ) {
112 Ok(plan) => plan,
113 Err(err) => {
114 last_error = Some(err);
115 continue;
116 }
117 };
118
119 match self.plan_access_path_with_type_completion(&module_path, base_plan, path) {
120 Ok(plan) => direct_matches.push((module_path, plan)),
121 Err(primary_error) => {
122 if Self::is_value_backed_aggregate_access_error(&primary_error) {
123 return Err(primary_error);
124 }
125 last_error = Some(primary_error);
126 }
127 }
128 }
129
130 if !direct_matches.is_empty() {
131 return Self::select_global_plan_with_preferred_module(
132 base,
133 prefer_module,
134 direct_matches,
135 );
136 }
137
138 if let Some(err) = last_error {
139 return Err(err);
140 }
141 Ok(None)
142 }
143
144 fn resolve_variable_read_plan_by_offsets_in_module<P: AsRef<Path>>(
145 &self,
146 module_path: P,
147 cu_off: gimli::DebugInfoOffset,
148 die_off: gimli::UnitOffset,
149 provenance: Provenance,
150 ) -> Result<VariableReadPlan> {
151 let path_buf = module_path.as_ref().to_path_buf();
152 if let Some(module_data) = self.modules.get(&path_buf) {
153 let items = vec![(cu_off, die_off)];
154 let vars = module_data.resolve_variables_by_offsets_at_address(0, &items)?;
155 let mut var = vars.into_iter().next().ok_or_else(|| {
156 anyhow::anyhow!(
157 "Failed to resolve variable at offsets {:?}/{:?} in module {}",
158 cu_off,
159 die_off,
160 path_buf.display()
161 )
162 })?;
163 if var.dwarf_type.is_none() {
164 if let Some(ti) = module_data.shallow_type_for_variable_offsets(cu_off, die_off) {
165 var.type_name = ti.type_name();
166 var.dwarf_type = Some(ti);
167 }
168 }
169 let mut plan = Self::read_plan_from_variable(var, provenance);
170 plan.module_path = Some(path_buf);
171 Ok(plan)
172 } else {
173 Err(anyhow::anyhow!(
174 "Module {} not loaded",
175 module_path.as_ref().display()
176 ))
177 }
178 }
179
180 pub fn list_all_global_variables(&self) -> Vec<(PathBuf, GlobalVariableInfo)> {
182 let mut results = Vec::new();
183 for (module_path, module_data) in &self.modules {
184 for v in module_data.list_all_global_variables() {
185 results.push((module_path.clone(), v));
186 }
187 }
188 results
189 }
190}