Skip to main content

ghostscope_dwarf/analyzer/
plan_global.rs

1use 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    /// Find global/static variables by name across all loaded modules
51    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        // Fallback: scan all globals in each module and match by exact or leaf name
64        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    /// Plan a global/static source-level access path as a neutral read plan.
78    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    /// List all global/static variables with usable addresses across all loaded modules
181    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}