ghostscope_dwarf/analyzer/
plan_pc.rs1use super::DwarfAnalyzer;
2use crate::{
3 core::{ModuleAddress, Provenance, Result},
4 semantics::{
5 AddressSpaceInfo, FunctionParameter, PcContext, PcLineInfo, PcRange, PlanError,
6 VariableAccessPath, VariableAccessSegment, VariableReadPlan, VisibleVariable,
7 VisibleVariablesResult,
8 },
9};
10use std::path::Path;
11
12impl DwarfAnalyzer {
13 pub fn resolve_pc(&self, module_address: &ModuleAddress) -> Result<PcContext> {
19 if let Some(context) = self
20 .pc_context_cache
21 .read()
22 .expect("PC context cache lock poisoned")
23 .get(&module_address.module_path, module_address.address)
24 {
25 return Ok(context);
26 }
27
28 let context = self.resolve_pc_uncached(module_address)?;
29 self.pc_context_cache
30 .write()
31 .expect("PC context cache lock poisoned")
32 .insert(
33 module_address.module_path.clone(),
34 module_address.address,
35 context.clone(),
36 );
37 Ok(context)
38 }
39
40 fn resolve_pc_uncached(&self, module_address: &ModuleAddress) -> Result<PcContext> {
41 let module_path = self
42 .loaded_module_path_for(&module_address.module_path)
43 .ok_or_else(|| {
44 anyhow::anyhow!("Module {} not loaded", module_address.module_display())
45 })?;
46 let module_data = self.modules.get(module_path).ok_or_else(|| {
47 anyhow::anyhow!("Module {} not loaded", module_address.module_display())
48 })?;
49 let module = self.module_id_for_path(module_path).ok_or_else(|| {
50 anyhow::anyhow!(
51 "Module {} has no semantic module id",
52 module_address.module_display()
53 )
54 })?;
55
56 let (cu, function, lexical_scopes, inline_chain) = module_data
57 .resolve_pc_scopes(module, module_address.address)
58 .unwrap_or_else(|error| {
59 tracing::debug!(
60 "Failed to resolve semantic PC scopes for {}:0x{:x}: {}",
61 module_address.module_display(),
62 module_address.address,
63 error
64 );
65 (None, None, Vec::new(), Vec::new())
66 });
67 let source_location = module_data.lookup_source_location(module_address.address);
68 let line = source_location.map(|location| PcLineInfo {
69 file_path: location.file_path,
70 line_number: location.line_number,
71 column: location.column,
72 address: location.address,
73 });
74 let function_name = module_data.find_function_name_by_address(module_address.address);
75 let is_inline = module_data.is_inline_at(module_address.address);
76 let mapping = module_data.module_mapping();
77
78 Ok(PcContext {
79 module,
80 pc: module_address.address,
81 normalized_pc: module_address.address,
82 cu,
83 function,
84 function_name,
85 lexical_scopes,
86 inline_chain,
87 is_inline,
88 line,
89 address_space: AddressSpaceInfo {
90 module_path: Some(mapping.path.clone()),
91 runtime_base: mapping.loaded_address,
92 link_base: None,
93 },
94 })
95 }
96
97 pub fn visible_variables(&self, ctx: &PcContext) -> Result<Vec<VisibleVariable>> {
99 Ok(self.visible_variables_with_diagnostics(ctx)?.variables)
100 }
101
102 pub fn function_parameters(&self, ctx: &PcContext) -> Result<Vec<FunctionParameter>> {
108 let Some(function) = ctx.function else {
109 return Ok(Vec::new());
110 };
111 let module_address = self.module_address_for_context(ctx)?;
112
113 self.modules
114 .get(&module_address.module_path)
115 .ok_or_else(|| {
116 anyhow::anyhow!("Module {} not loaded", module_address.module_display())
117 })?
118 .function_parameters(function)
119 }
120
121 pub fn visible_variables_with_diagnostics(
123 &self,
124 ctx: &PcContext,
125 ) -> Result<VisibleVariablesResult> {
126 let module_address = self.module_address_for_context(ctx)?;
127
128 let (variables, diagnostics) = self
129 .modules
130 .get(&module_address.module_path)
131 .ok_or_else(|| {
132 anyhow::anyhow!("Module {} not loaded", module_address.module_display())
133 })?
134 .get_visible_variables_at_address_best_effort_with_diagnostics(
135 ctx.module,
136 module_address.address,
137 )?;
138 let mut variables: Vec<VisibleVariable> = variables
139 .into_iter()
140 .map(|variable| variable.visible_variable())
141 .collect();
142
143 variables.sort_by(|a, b| {
144 a.scope_depth
145 .cmp(&b.scope_depth)
146 .then_with(|| b.is_parameter.cmp(&a.is_parameter))
147 .then_with(|| a.name.cmp(&b.name))
148 });
149 Ok(VisibleVariablesResult {
150 variables,
151 diagnostics,
152 })
153 }
154
155 pub(super) fn module_address_for_context(&self, ctx: &PcContext) -> Result<ModuleAddress> {
156 let module_path = match ctx.address_space.module_path.as_deref() {
157 Some(path) => path,
158 None => self.module_path_for_id(ctx.module).ok_or_else(|| {
159 anyhow::anyhow!("Semantic module id {:?} is not loaded", ctx.module)
160 })?,
161 };
162 Ok(ModuleAddress::new(
163 module_path.to_path_buf(),
164 ctx.normalized_pc,
165 ))
166 }
167
168 pub(super) fn is_value_backed_aggregate_access_error(err: &anyhow::Error) -> bool {
169 err.downcast_ref::<PlanError>()
170 .is_some_and(PlanError::is_value_backed_aggregate_access)
171 }
172
173 pub(super) fn read_plan_from_variable(
174 variable: crate::parser::ParsedVariable,
175 provenance: Provenance,
176 ) -> VariableReadPlan {
177 VariableReadPlan::from_visible_variable(variable.visible_variable(), provenance)
178 }
179
180 fn attach_pc_context(ctx: &PcContext, mut plan: VariableReadPlan) -> VariableReadPlan {
181 plan.pc_range = Some(PcRange {
182 start: ctx.normalized_pc,
183 end: ctx.normalized_pc,
184 });
185 plan.inline_context = ctx.inline_chain.last().and_then(|frame| frame.context);
186 plan.module_path = ctx.address_space.module_path.clone();
187 plan
188 }
189
190 pub(super) fn plan_access_path_with_type_completion(
191 &self,
192 module_path: &Path,
193 mut plan: VariableReadPlan,
194 path: &VariableAccessPath,
195 ) -> Result<VariableReadPlan> {
196 for segment in &path.segments {
197 let pointer_type_name = plan.type_name.clone();
198 self.complete_unknown_pointer_target_type(module_path, &mut plan, &pointer_type_name);
199 plan = plan.plan_access_path(&VariableAccessPath::new(vec![segment.clone()]))?;
200 if matches!(segment, VariableAccessSegment::Dereference) {
201 self.complete_unknown_pointer_target_type(
202 module_path,
203 &mut plan,
204 &pointer_type_name,
205 );
206 }
207 }
208
209 Ok(plan)
210 }
211
212 pub fn plan_variable_by_name(
216 &self,
217 ctx: &PcContext,
218 name: &str,
219 ) -> Result<Option<VariableReadPlan>> {
220 let VisibleVariablesResult {
221 variables: visible_variables,
222 diagnostics,
223 } = self.visible_variables_with_diagnostics(ctx)?;
224
225 Self::select_visible_variable_by_name(
226 ctx.normalized_pc,
227 name,
228 visible_variables,
229 &diagnostics,
230 )
231 .map(|variable| {
232 variable.map(|variable| {
233 Self::attach_pc_context(
234 ctx,
235 VariableReadPlan::from_visible_variable(variable, Provenance::DirectDie),
236 )
237 })
238 })
239 }
240
241 pub(super) fn select_visible_variable_by_name(
242 pc: u64,
243 name: &str,
244 visible_variables: Vec<VisibleVariable>,
245 diagnostics: &[crate::semantics::VariableQueryDiagnostic],
246 ) -> Result<Option<VisibleVariable>> {
247 let synthesized_prefix = format!("{name}@");
248 let matching_diagnostics = diagnostics
249 .iter()
250 .filter(|diagnostic| {
251 diagnostic.name.as_deref().is_some_and(|diagnostic_name| {
252 diagnostic_name == name || diagnostic_name.starts_with(&synthesized_prefix)
253 })
254 })
255 .collect::<Vec<_>>();
256
257 let exact_matches = visible_variables
258 .iter()
259 .filter(|variable| variable.name == name)
260 .cloned()
261 .collect::<Vec<_>>();
262
263 let mut candidates = if exact_matches.is_empty() {
264 visible_variables
265 .into_iter()
266 .filter(|variable| variable.name.starts_with(&synthesized_prefix))
267 .collect::<Vec<_>>()
268 } else {
269 exact_matches
270 };
271
272 if candidates.is_empty() {
273 if let Some(diagnostic) = matching_diagnostics
274 .iter()
275 .max_by_key(|diagnostic| diagnostic.scope_depth)
276 {
277 return Err(anyhow::anyhow!(
278 "Unavailable variable '{name}' at PC 0x{:x}: {}",
279 pc,
280 diagnostic.detail
281 ));
282 }
283 return Ok(None);
284 }
285
286 let max_scope_depth = candidates
287 .iter()
288 .map(|variable| variable.scope_depth)
289 .max()
290 .unwrap_or(0);
291 if let Some(diagnostic) = matching_diagnostics
292 .iter()
293 .filter(|diagnostic| diagnostic.scope_depth > max_scope_depth)
294 .max_by_key(|diagnostic| diagnostic.scope_depth)
295 {
296 return Err(anyhow::anyhow!(
297 "Unavailable variable '{name}' at PC 0x{:x}: {}",
298 pc,
299 diagnostic.detail
300 ));
301 }
302 candidates.retain(|variable| variable.scope_depth == max_scope_depth);
303
304 if candidates.iter().any(|variable| !variable.is_artificial) {
305 candidates.retain(|variable| !variable.is_artificial);
306 }
307
308 candidates.dedup();
309 if candidates.len() > 1 {
310 let names = candidates
311 .iter()
312 .map(|variable| variable.name.as_str())
313 .collect::<Vec<_>>()
314 .join(", ");
315 return Err(anyhow::anyhow!(
316 "Ambiguous variable '{name}' at PC 0x{:x}: candidates [{}]",
317 pc,
318 names
319 ));
320 }
321
322 Ok(candidates.into_iter().next())
323 }
324
325 pub fn plan_variable(
331 &self,
332 ctx: &PcContext,
333 variable_id: crate::VariableId,
334 ) -> Result<Option<VariableReadPlan>> {
335 if variable_id.declaration.module != ctx.module {
336 return Err(anyhow::anyhow!(
337 "VariableId module {:?} does not match PcContext module {:?}",
338 variable_id.declaration.module,
339 ctx.module
340 ));
341 }
342
343 let matches = self
344 .visible_variables(ctx)?
345 .into_iter()
346 .filter(|variable| variable.declaration == Some(variable_id.declaration))
347 .collect::<Vec<_>>();
348
349 match matches.as_slice() {
350 [] => Ok(None),
351 [variable] => Ok(Some(Self::attach_pc_context(
352 ctx,
353 VariableReadPlan::from_visible_variable(variable.clone(), Provenance::DirectDie),
354 ))),
355 _ => Err(anyhow::anyhow!(
356 "Ambiguous VariableId {:?} at PC 0x{:x}: {} visible matches",
357 variable_id,
358 ctx.normalized_pc,
359 matches.len()
360 )),
361 }
362 }
363
364 pub fn plan_variable_access(
366 &self,
367 ctx: &PcContext,
368 variable_id: crate::VariableId,
369 path: &VariableAccessPath,
370 ) -> Result<Option<VariableReadPlan>> {
371 let Some(plan) = self.plan_variable(ctx, variable_id)? else {
372 return Ok(None);
373 };
374 let module_path = self
375 .module_path_for_id(ctx.module)
376 .ok_or_else(|| anyhow::anyhow!("Semantic module id {:?} is not loaded", ctx.module))?
377 .to_path_buf();
378
379 self.plan_access_path_with_type_completion(&module_path, plan, path)
380 .map(Some)
381 }
382
383 pub fn plan_variable_access_by_name(
385 &self,
386 ctx: &PcContext,
387 name: &str,
388 path: &VariableAccessPath,
389 ) -> Result<Option<VariableReadPlan>> {
390 let Some(plan) = self.plan_variable_by_name(ctx, name)? else {
391 return Ok(None);
392 };
393 let module_path = self
394 .module_path_for_id(ctx.module)
395 .ok_or_else(|| anyhow::anyhow!("Semantic module id {:?} is not loaded", ctx.module))?
396 .to_path_buf();
397
398 self.plan_access_path_with_type_completion(&module_path, plan, path)
399 .map(Some)
400 }
401
402 pub(super) fn visible_variables_at_address(
407 &self,
408 module_address: &ModuleAddress,
409 ) -> Result<Vec<VisibleVariable>> {
410 tracing::info!(
411 "Looking up variables at address 0x{:x} in module {}",
412 module_address.address,
413 module_address.module_display()
414 );
415 let ctx = self.resolve_pc(module_address)?;
416 self.visible_variables(&ctx)
417 }
418}