anchor_cli/debugger/
source.rs1use {
15 super::model::SrcLoc,
16 addr2line::Loader,
17 object::{Object, ObjectSection},
18 std::{
19 collections::BTreeSet,
20 path::{Path, PathBuf},
21 },
22};
23
24const INSN_SIZE: u64 = 8;
25
26pub struct SourceResolver {
28 inner: Option<Inner>,
29}
30
31struct Inner {
32 loader: Loader,
33 text_addr: u64,
34 text_size: u64,
35}
36
37impl SourceResolver {
38 pub fn from_elf_path(path: &Path) -> Self {
41 Self {
42 inner: build(path).ok(),
43 }
44 }
45
46 pub fn resolve(&self, pc: u64) -> Option<SrcLoc> {
49 let inner = self.inner.as_ref()?;
50 let vaddr = inner.text_addr.checked_add(pc.checked_mul(INSN_SIZE)?)?;
51 let loc = inner.loader.find_location(vaddr).ok().flatten()?;
52 Some(SrcLoc {
53 file: PathBuf::from(loc.file?),
54 line: loc.line?,
55 })
56 }
57
58 pub fn resolve_frames(&self, pc: u64) -> Vec<SrcLoc> {
71 let Some(inner) = self.inner.as_ref() else {
72 return Vec::<SrcLoc>::new();
73 };
74 let Some(vaddr) = inner
75 .text_addr
76 .checked_add(match pc.checked_mul(INSN_SIZE) {
77 Some(v) => v,
78 None => return Vec::<SrcLoc>::new(),
79 })
80 else {
81 return Vec::<SrcLoc>::new();
82 };
83 let mut out: Vec<SrcLoc> = Vec::new();
84 let Ok(mut frames) = inner.loader.find_frames(vaddr) else {
85 return out;
86 };
87 while let Ok(Some(frame)) = frames.next() {
88 if let Some(loc) = frame.location {
89 if let (Some(file), Some(line)) = (loc.file, loc.line) {
90 out.push(SrcLoc {
91 file: PathBuf::from(file),
92 line,
93 });
94 }
95 }
96 }
97 out
98 }
99
100 pub fn executable_lines(&self) -> Vec<SrcLoc> {
104 let Some(inner) = self.inner.as_ref() else {
105 return Vec::new();
106 };
107 let Some(text_end) = inner.text_addr.checked_add(inner.text_size) else {
108 return Vec::new();
109 };
110 let Ok(iter) = inner.loader.find_location_range(inner.text_addr, text_end) else {
111 return Vec::new();
112 };
113
114 let mut lines = BTreeSet::new();
115 for (_, _, loc) in iter {
116 if let (Some(file), Some(line)) = (loc.file, loc.line) {
117 if line != 0 {
118 lines.insert((PathBuf::from(file), line));
119 }
120 }
121 }
122
123 lines
124 .into_iter()
125 .map(|(file, line)| SrcLoc { file, line })
126 .collect()
127 }
128
129 pub fn is_empty(&self) -> bool {
132 self.inner.is_none()
133 }
134}
135
136#[cfg(target_os = "macos")]
139pub const CI_PLATFORM_TOOLS_PREFIX: &str =
140 "/Users/runner/work/platform-tools/platform-tools/out/rust/library/";
141#[cfg(target_os = "linux")]
142pub const CI_PLATFORM_TOOLS_PREFIX: &str =
143 "/home/runner/work/platform-tools/platform-tools/out/rust/library/";
144#[cfg(not(any(target_os = "macos", target_os = "linux")))]
145pub const CI_PLATFORM_TOOLS_PREFIX: &str = compile_error!("Current platform is not supported");
146
147pub fn discover_platform_tools_stdlib_roots() -> Vec<PathBuf> {
154 let Some(home) = dirs::home_dir() else {
155 return Vec::new();
156 };
157 let base = home.join(".cache/solana");
158 let Ok(entries) = std::fs::read_dir(&base) else {
159 return Vec::new();
160 };
161 let mut versions: Vec<(String, PathBuf)> = entries
162 .flatten()
163 .filter_map(|e| {
164 let name = e.file_name().to_str()?.to_owned();
165 let candidate = e
166 .path()
167 .join("platform-tools/rust/lib/rustlib/src/rust/library");
168 candidate.is_dir().then_some((name, candidate))
169 })
170 .collect();
171 versions.sort_by(|a, b| b.0.cmp(&a.0));
175 versions.into_iter().map(|(_, p)| p).collect()
176}
177
178fn build(path: &Path) -> anyhow::Result<Inner> {
179 let bytes = std::fs::read(path)?;
181 let file = object::File::parse(&*bytes)?;
182 let text = file
183 .sections()
184 .find(|s| s.name().ok() == Some(".text"))
185 .or_else(|| {
186 file.sections()
187 .find(|s| s.kind() == object::SectionKind::Text)
188 })
189 .ok_or_else(|| anyhow::anyhow!("no .text section"))?;
190 let text_addr = text.address();
191 let text_size = text.size();
192
193 let loader = Loader::new(path).map_err(|e| anyhow::anyhow!("load DWARF: {e}"))?;
194 Ok(Inner {
195 loader,
196 text_addr,
197 text_size,
198 })
199}