1use std::{
2 fmt::{self, Debug},
3 path::PathBuf,
4 sync::Arc,
5};
6
7use memmap2::Mmap;
8use object::{read::archive::ArchiveFile, Object};
9
10pub(crate) mod index;
11pub(crate) mod loader;
12
13pub use index::index_debug_file_sources;
14pub(crate) use loader::{Dwarf, DwarfReader, RawDie};
15
16use crate::DwarfDb;
17
18pub type Expression = gimli::Expression<DwarfReader>;
19
20#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd, salsa::Update)]
21pub struct SourceLocation {
22 pub file: SourceFile,
23 pub line: u64,
24 pub column: Option<u64>,
25}
26
27impl SourceLocation {
28 pub fn new(file: SourceFile, line: u64, column: Option<u64>) -> Self {
29 Self { file, line, column }
30 }
31}
32
33pub fn detect_cargo_root() -> Option<PathBuf> {
34 fn cargo_detect_workspace() -> Option<PathBuf> {
35 let output = std::process::Command::new(env!("CARGO"))
36 .arg("locate-project")
37 .arg("--workspace")
38 .arg("--message-format=plain")
39 .output()
40 .ok()?
41 .stdout;
42 let cargo_path = PathBuf::from(std::str::from_utf8(&output).ok()?.trim());
43 Some(cargo_path.parent()?.to_path_buf())
44 }
45
46 fn cwd_detect_workspace() -> Option<PathBuf> {
47 let mut path = std::env::current_dir().ok()?;
48 while !path.join("Cargo.toml").exists() {
49 path = path.parent()?.to_path_buf();
50 }
51 Some(path)
52 }
53
54 cargo_detect_workspace().or_else(cwd_detect_workspace)
57}
58
59#[salsa::input(debug)]
60pub struct File {
61 #[returns(ref)]
62 pub path: PathBuf,
63 #[returns(ref)]
64 pub member_file: Option<String>,
65 pub mtime: std::time::SystemTime,
66 pub size: u64,
67}
68
69impl File {
70 pub fn build(
72 db: &dyn DwarfDb,
73 path: PathBuf,
74 member_file: Option<String>,
75 ) -> anyhow::Result<File> {
76 let path = db.remap_path(&path);
78
79 let file = std::fs::File::open(&path).inspect_err(|_| {
80 tracing::warn!("Failed to open file: {}:", path.display());
81 })?;
82 let metadata = file.metadata()?;
83 let mtime = metadata.modified()?;
84 let size = metadata.len();
85
86 Ok(Self::new(db, path, member_file, mtime, size))
87 }
88
89 pub fn name(&self, db: &dyn salsa::Database) -> String {
90 self.path(db).display().to_string()
91 }
92}
93
94#[salsa::input(debug)]
95pub struct Binary {
96 pub file: File,
97}
98
99impl Binary {
100 pub fn name(&self, db: &dyn DwarfDb) -> String {
101 self.file(db).name(db)
102 }
103}
104
105#[salsa::input(debug)]
106#[derive(PartialOrd, Ord)]
107pub struct DebugFile {
108 pub file: File,
110 pub relocatable: bool,
113}
114
115impl DebugFile {
116 pub fn name(&self, db: &dyn salsa::Database) -> String {
117 let file = self.file(db);
118 if let Some(member) = file.member_file(db) {
119 format!("{}({})", file.name(db), member)
120 } else {
121 file.name(db)
122 }
123 }
124}
125
126pub struct LoadedFile {
127 filepath: String,
128 file: File,
129 mapped_file: Mmap,
130 pub object: object::File<'static>,
131 pub dwarf: Dwarf,
132}
133
134impl PartialEq for LoadedFile {
135 fn eq(&self, other: &Self) -> bool {
136 self.file == other.file
137 }
138}
139
140impl fmt::Debug for LoadedFile {
141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 f.debug_struct("LoadedFile")
143 .field("filepath", &self.filepath.to_string())
144 .field("size", &self.mapped_file.len())
145 .field("has_debug_sections", &self.object.has_debug_symbols())
146 .finish()
147 }
148}
149
150#[salsa::tracked(returns(ref))]
151pub fn load<'db>(db: &'db dyn DwarfDb, file: File) -> Result<LoadedFile, Error> {
152 let path = file.path(db);
153 let path = db.remap_path(path);
154 let member = file.member_file(db);
155
156 let file_handle = std::fs::File::open(path)
157 .inspect_err(|_| tracing::warn!("Failed to open file: {}", file.name(db)))?;
158 let mmap = unsafe { memmap2::Mmap::map(&file_handle) }?;
159 let mmap_static_ref = unsafe {
160 std::mem::transmute::<&[u8], &'static [u8]>(&*mmap)
163 };
164 let object = if let Some(member) = &member {
165 let archive = ArchiveFile::parse(mmap_static_ref)?;
167 if let Some(file) = archive.members().find_map(|file| {
168 let Ok(file) = file else {
169 return None;
170 };
171 if file.name() == member.as_bytes() {
172 Some(file)
173 } else {
174 None
175 }
176 }) {
177 object::File::parse(file.data(mmap_static_ref)?)?
180 } else {
181 return Err(Error::MemberFileNotFound(format!(
182 "object file {member} not found in archive {}",
183 file.name(db)
184 )));
185 }
186 } else {
187 object::read::File::parse(mmap_static_ref)?
188 };
189 let dwarf = self::loader::load(&object)?;
190
191 Ok(LoadedFile {
192 filepath: if let Some(member) = member {
193 format!("{}({member}", file.name(db))
194 } else {
195 file.name(db)
196 },
197 file,
198 mapped_file: mmap,
199 object,
200 dwarf,
201 })
202}
203
204#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd, salsa::Update)]
205pub struct SourceFile {
206 pub path: PathBuf,
207}
208
209impl SourceFile {
210 pub fn new(path: PathBuf) -> Self {
211 Self { path }
212 }
213
214 pub fn path_str(&self) -> std::borrow::Cow<'_, str> {
215 self.path.to_string_lossy()
216 }
217
218 pub fn is_external(&self) -> bool {
219 let current_dir = std::env::current_dir()
221 .unwrap_or_default()
222 .to_string_lossy()
223 .to_string();
224 !self.path.starts_with(¤t_dir)
225 }
226}
227
228#[derive(Debug, Clone)]
229pub enum Error {
230 Gimli(gimli::Error),
231 Io(Arc<std::io::Error>),
232 ObjectParseFailure(object::read::Error),
233 MemberFileNotFound(String),
234}
235
236impl fmt::Display for Error {
237 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
238 match self {
239 Error::Gimli(error) => write!(f, "Gimli error: {error}"),
240 Error::Io(error) => write!(f, "IO Error: {error}",),
241 Error::MemberFileNotFound(e) => write!(f, "Member file not found: {e}"),
242 Error::ObjectParseFailure(error) => write!(f, "Object parse error: {error}"),
243 }
244 }
245}
246
247impl PartialEq for Error {
248 fn eq(&self, _other: &Self) -> bool {
249 true
252 }
253}
254
255impl std::error::Error for Error {
256 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
257 match self {
258 Error::Gimli(error) => Some(error),
259 Error::Io(error) => Some(error.as_ref()),
260 Error::ObjectParseFailure(error) => Some(error),
261 Error::MemberFileNotFound(_) => None,
262 }
263 }
264}
265
266impl From<std::io::Error> for Error {
267 fn from(err: std::io::Error) -> Self {
268 Error::Io(Arc::new(err))
269 }
270}
271
272impl From<object::read::Error> for Error {
273 fn from(err: object::read::Error) -> Self {
274 Error::ObjectParseFailure(err)
275 }
276}
277
278impl From<gimli::Error> for Error {
279 fn from(err: gimli::Error) -> Self {
280 Error::Gimli(err)
281 }
282}