1#![warn(clippy::all)]
2
3use anyhow::{anyhow, Error};
4use object::Object;
5use std::fmt::Write;
6use std::fs;
7use std::path::{Path, PathBuf};
8
9pub type Uuid = [u8; 16];
10
11mod pdb;
12use crate::pdb::locate_pdb;
13
14mod path_utils;
15use crate::path_utils::path_from_bytes;
16
17cfg_if::cfg_if! {
18 if #[cfg(target_os = "macos")] {
19 mod macos;
20 use crate::macos::locate_dsym_using_spotlight;
21 } else {
22 #[allow(clippy::unnecessary_wraps)]
23 fn locate_dsym_using_spotlight(_uuid: Uuid) -> Result<Option<PathBuf>, Error> {
24 Ok(None)
25 }
26 }
27}
28
29fn locate_dsym_fastpath(path: &Path, uuid: Uuid) -> Option<PathBuf> {
39 let path = path.canonicalize().ok()?;
42
43 let mut dsym = path.file_name()?.to_owned();
45 dsym.push(".dSYM");
46 let dsym_dir = path.with_file_name(&dsym);
47 if let Some(f) = try_match_dsym(&dsym_dir, uuid) {
48 return Some(f);
49 }
50
51 let mut target_channel_dir = &*path;
53 loop {
54 let parent = target_channel_dir.parent()?;
55 target_channel_dir = parent;
56
57 if target_channel_dir.parent().and_then(Path::file_name)
58 == Some(std::ffi::OsStr::new("target"))
59 {
60 break; }
62 }
63
64 let deps_dir = target_channel_dir.join("deps");
66 let examples_dir = target_channel_dir.join("examples");
67 try_match_dsym_in_dir(&deps_dir, uuid).or_else(|| try_match_dsym_in_dir(&examples_dir, uuid))
68}
69
70fn try_match_dsym_in_dir(dir: &Path, uuid: Uuid) -> Option<PathBuf> {
71 for entry in fs::read_dir(dir).ok()? {
72 let item = entry.ok()?.path();
73
74 if item.extension() != Some(std::ffi::OsStr::new("dSYM")) {
76 continue;
77 }
78
79 if let Some(debug_file_name) = try_match_dsym(&item, uuid) {
80 return Some(debug_file_name);
81 }
82 }
83
84 None
85}
86
87fn try_match_dsym(dsym_dir: &Path, uuid: Uuid) -> Option<PathBuf> {
88 let mut dir_iter = fs::read_dir(dsym_dir.join("Contents/Resources/DWARF")).ok()?;
90
91 let debug_file_name = dir_iter.next()?.ok()?.path();
92
93 if dir_iter.next().is_some() {
94 return None; }
96
97 let file = fs::read(&debug_file_name).ok()?;
99 let dsym = object::File::parse(&file[..]).ok()?;
100
101 if dsym.mach_uuid() == Ok(Some(uuid)) {
103 Some(debug_file_name)
104 } else {
105 None
106 }
107}
108
109pub fn locate_debug_symbols<'a, O, T>(object: &'a O, path: T) -> Result<Option<PathBuf>, Error>
116where
117 O: Object<'a, 'a>,
118 T: AsRef<Path>,
119{
120 if let Some(uuid) = object.mach_uuid()? {
121 return locate_dsym(path.as_ref(), uuid);
122 }
123 if let Some(pdbinfo) = object.pdb_info()? {
124 return locate_pdb(path.as_ref(), &pdbinfo);
125 }
126 if let Some(path) = object
127 .build_id()?
128 .and_then(locate_debug_build_id)
129 {
130 return Ok(Some(path));
131 }
133 if let Some((filename, crc)) = object.gnu_debuglink()? {
134 let filename = path_from_bytes(filename)?;
135 return locate_gnu_debuglink(path.as_ref(), filename, crc);
136 }
137 Ok(None)
138}
139
140pub fn locate_dsym<T>(path: T, uuid: Uuid) -> Result<Option<PathBuf>, Error>
143where
144 T: AsRef<Path>,
145{
146 if let Some(dsym_path) = locate_dsym_fastpath(path.as_ref(), uuid) {
147 return Ok(Some(dsym_path));
148 }
149 locate_dsym_using_spotlight(uuid)
150}
151
152pub fn locate_debug_build_id(id: &[u8]) -> Option<PathBuf> {
155 if id.len() < 2 {
156 return None;
157 }
158
159 let mut f = format!("/usr/lib/debug/.build-id/{:02x}/", id[0]);
161 for x in &id[1..] {
162 let _ = write!(&mut f, "{:02x}", x);
163 }
164 let _ = write!(&mut f, ".debug");
165 let f = PathBuf::from(f);
166 if f.exists() {
167 return Some(f);
168 }
169
170 None
171}
172
173pub fn locate_gnu_debuglink<T, U>(path: T, filename: U, _crc: u32) -> Result<Option<PathBuf>, Error>
176where
177 T: AsRef<Path>,
178 U: AsRef<Path>,
179{
180 let path = fs::canonicalize(path)?;
181 let parent = path.parent().ok_or_else(|| anyhow!("Bad path"))?;
182 let filename = filename.as_ref();
183
184 let f = parent.join(filename);
188 if f != path && f.exists() {
189 return Ok(Some(f));
190 }
191
192 let f = parent.join(".debug").join(filename);
194 if f.exists() {
195 return Ok(Some(f));
196 }
197
198 let parent = parent.strip_prefix("/").unwrap();
200 let f = Path::new("/usr/lib/debug").join(parent).join(filename);
201 if f.exists() {
202 return Ok(Some(f));
203 }
204
205 Ok(None)
206}