std/backtrace/src/symbolize/gimli/
macho.rs1use super::mystd::path::Path;
2use super::{gimli, Context, Endian, EndianSlice, Mapping, Stash};
3use alloc::boxed::Box;
4use alloc::sync::Arc;
5use alloc::vec::Vec;
6use core::convert::TryInto;
7use object::macho;
8use object::read::macho::{MachHeader, Nlist, Section, Segment as _};
9use object::{Bytes, NativeEndian};
10
11#[cfg(target_pointer_width = "32")]
12type Mach = object::macho::MachHeader32<NativeEndian>;
13#[cfg(target_pointer_width = "64")]
14type Mach = object::macho::MachHeader64<NativeEndian>;
15type MachSegment = <Mach as MachHeader>::Segment;
16type MachSection = <Mach as MachHeader>::Section;
17type MachNlist = <Mach as MachHeader>::Nlist;
18
19impl Mapping {
20 pub fn new(path: &Path) -> Option<Mapping> {
24 let map = super::mmap(path)?;
27 let (macho, data) = find_header(&map)?;
28 let endian = macho.endian().ok()?;
29 let uuid = macho.uuid(endian, data, 0).ok()?;
30
31 if let Some(uuid) = uuid {
38 if let Some(parent) = path.parent() {
39 if let Some(mapping) = Mapping::load_dsym(parent, uuid) {
40 return Some(mapping);
41 }
42 }
43 }
44
45 Mapping::mk(map, |data, stash| {
49 let (macho, data) = find_header(data)?;
50 let endian = macho.endian().ok()?;
51 let obj = Object::parse(macho, endian, data)?;
52 Context::new(stash, obj, None, None)
53 })
54 }
55
56 fn load_dsym(dir: &Path, uuid: [u8; 16]) -> Option<Mapping> {
57 for entry in dir.read_dir().ok()? {
58 let entry = entry.ok()?;
59 let filename = match entry.file_name().into_string() {
60 Ok(name) => name,
61 Err(_) => continue,
62 };
63 if !filename.ends_with(".dSYM") {
64 continue;
65 }
66 let candidates = entry.path().join("Contents/Resources/DWARF");
67 if let Some(mapping) = Mapping::try_dsym_candidate(&candidates, uuid) {
68 return Some(mapping);
69 }
70 }
71 None
72 }
73
74 fn try_dsym_candidate(dir: &Path, uuid: [u8; 16]) -> Option<Mapping> {
75 for entry in dir.read_dir().ok()? {
79 let entry = entry.ok()?;
80 let map = super::mmap(&entry.path())?;
81 let candidate = Mapping::mk(map, |data, stash| {
82 let (macho, data) = find_header(data)?;
83 let endian = macho.endian().ok()?;
84 let entry_uuid = macho.uuid(endian, data, 0).ok()??;
85 if entry_uuid != uuid {
86 return None;
87 }
88 let obj = Object::parse(macho, endian, data)?;
89 Context::new(stash, obj, None, None)
90 });
91 if let Some(candidate) = candidate {
92 return Some(candidate);
93 }
94 }
95
96 None
97 }
98}
99
100fn find_header(data: &'_ [u8]) -> Option<(&'_ Mach, &'_ [u8])> {
101 use object::endian::BigEndian;
102
103 let desired_cpu = || {
104 if cfg!(target_arch = "x86") {
105 Some(macho::CPU_TYPE_X86)
106 } else if cfg!(target_arch = "x86_64") {
107 Some(macho::CPU_TYPE_X86_64)
108 } else if cfg!(target_arch = "arm") {
109 Some(macho::CPU_TYPE_ARM)
110 } else if cfg!(target_arch = "aarch64") {
111 Some(macho::CPU_TYPE_ARM64)
112 } else {
113 None
114 }
115 };
116
117 let mut data = Bytes(data);
118 match data
119 .clone()
120 .read::<object::endian::U32<NativeEndian>>()
121 .ok()?
122 .get(NativeEndian)
123 {
124 macho::MH_MAGIC_64 | macho::MH_CIGAM_64 | macho::MH_MAGIC | macho::MH_CIGAM => {}
125
126 macho::FAT_MAGIC | macho::FAT_CIGAM => {
127 let mut header_data = data;
128 let endian = BigEndian;
129 let header = header_data.read::<macho::FatHeader>().ok()?;
130 let nfat = header.nfat_arch.get(endian);
131 let arch = (0..nfat)
132 .filter_map(|_| header_data.read::<macho::FatArch32>().ok())
133 .find(|arch| desired_cpu() == Some(arch.cputype.get(endian)))?;
134 let offset = arch.offset.get(endian);
135 let size = arch.size.get(endian);
136 data = data
137 .read_bytes_at(offset.try_into().ok()?, size.try_into().ok()?)
138 .ok()?;
139 }
140
141 macho::FAT_MAGIC_64 | macho::FAT_CIGAM_64 => {
142 let mut header_data = data;
143 let endian = BigEndian;
144 let header = header_data.read::<macho::FatHeader>().ok()?;
145 let nfat = header.nfat_arch.get(endian);
146 let arch = (0..nfat)
147 .filter_map(|_| header_data.read::<macho::FatArch64>().ok())
148 .find(|arch| desired_cpu() == Some(arch.cputype.get(endian)))?;
149 let offset = arch.offset.get(endian);
150 let size = arch.size.get(endian);
151 data = data
152 .read_bytes_at(offset.try_into().ok()?, size.try_into().ok()?)
153 .ok()?;
154 }
155
156 _ => return None,
157 }
158
159 Mach::parse(data.0, 0).ok().map(|h| (h, data.0))
160}
161
162pub struct Object<'a> {
164 endian: NativeEndian,
165 data: &'a [u8],
166 dwarf: Option<&'a [MachSection]>,
167 syms: Vec<(&'a [u8], u64)>,
168 syms_sort_by_name: bool,
169 object_map: Option<object::ObjectMap<'a>>,
171 object_mappings: Box<[Option<Option<Mapping>>]>,
173}
174
175impl<'a> Object<'a> {
176 fn parse(mach: &'a Mach, endian: NativeEndian, data: &'a [u8]) -> Option<Object<'a>> {
177 let is_object = mach.filetype(endian) == object::macho::MH_OBJECT;
178 let mut dwarf = None;
179 let mut syms = Vec::new();
180 let mut syms_sort_by_name = false;
181 let mut commands = mach.load_commands(endian, data, 0).ok()?;
182 let mut object_map = None;
183 let mut object_mappings = Vec::new();
184 while let Ok(Some(command)) = commands.next() {
185 if let Some((segment, section_data)) = MachSegment::from_command(command).ok()? {
186 if segment.name() == b"__DWARF" || (is_object && segment.name() == b"") {
188 dwarf = segment.sections(endian, section_data).ok();
189 }
190 } else if let Some(symtab) = command.symtab().ok()? {
191 let symbols = symtab.symbols::<Mach, _>(endian, data).ok()?;
192 syms = symbols
193 .iter()
194 .filter_map(|nlist: &MachNlist| {
195 let name = nlist.name(endian, symbols.strings()).ok()?;
196 if name.len() > 0 && nlist.is_definition() {
197 Some((name, u64::from(nlist.n_value(endian))))
198 } else {
199 None
200 }
201 })
202 .collect();
203 if is_object {
204 syms.sort_unstable_by_key(|(name, _)| *name);
208 syms_sort_by_name = true;
209 } else {
210 syms.sort_unstable_by_key(|(_, addr)| *addr);
211 let map = symbols.object_map(endian);
212 object_mappings.resize_with(map.objects().len(), || None);
213 object_map = Some(map);
214 }
215 }
216 }
217
218 Some(Object {
219 endian,
220 data,
221 dwarf,
222 syms,
223 syms_sort_by_name,
224 object_map,
225 object_mappings: object_mappings.into_boxed_slice(),
226 })
227 }
228
229 pub fn section(&self, _: &Stash, name: &str) -> Option<&'a [u8]> {
230 let name = name.as_bytes();
231 let dwarf = self.dwarf?;
232 let section = dwarf.into_iter().find(|section| {
233 let section_name = section.name();
234 section_name == name || {
235 section_name.starts_with(b"__")
236 && name.starts_with(b".")
237 && §ion_name[2..] == &name[1..]
238 }
239 })?;
240 Some(section.data(self.endian, self.data).ok()?)
241 }
242
243 pub fn search_symtab<'b>(&'b self, addr: u64) -> Option<&'b [u8]> {
244 debug_assert!(!self.syms_sort_by_name);
245 let i = match self.syms.binary_search_by_key(&addr, |(_, addr)| *addr) {
246 Ok(i) => i,
247 Err(i) => i.checked_sub(1)?,
248 };
249 let (sym, _addr) = self.syms.get(i)?;
250 Some(sym)
251 }
252
253 pub(super) fn search_object_map<'b>(&'b mut self, addr: u64) -> Option<(&'b Context<'b>, u64)> {
257 let object_map = self.object_map.as_ref()?;
260 let symbol = object_map.get(addr)?;
261 let object_index = symbol.object_index();
262 let mapping = self.object_mappings.get_mut(object_index)?;
263 if mapping.is_none() {
264 *mapping = Some(object_mapping(object_map.objects().get(object_index)?));
266 }
267 let cx: &'b Context<'static> = &mapping.as_ref()?.as_ref()?.cx;
268 let cx = unsafe { core::mem::transmute::<&'b Context<'static>, &'b Context<'b>>(cx) };
270
271 debug_assert!(cx.object.syms.is_empty() || cx.object.syms_sort_by_name);
274 let i = cx
275 .object
276 .syms
277 .binary_search_by_key(&symbol.name(), |(name, _)| *name)
278 .ok()?;
279 let object_symbol = cx.object.syms.get(i)?;
280 let object_addr = addr
281 .wrapping_sub(symbol.address())
282 .wrapping_add(object_symbol.1);
283 Some((cx, object_addr))
284 }
285}
286
287fn object_mapping(file: &object::read::ObjectMapFile<'_>) -> Option<Mapping> {
288 use super::mystd::ffi::OsStr;
289 use super::mystd::os::unix::prelude::*;
290
291 let map = super::mmap(Path::new(OsStr::from_bytes(file.path())))?;
292 let member_name = file.member();
293 Mapping::mk(map, |data, stash| {
294 let data = match member_name {
295 Some(member_name) => {
296 let archive = object::read::archive::ArchiveFile::parse(data).ok()?;
297 let member = archive
298 .members()
299 .filter_map(Result::ok)
300 .find(|m| m.name() == member_name)?;
301 member.data(data).ok()?
302 }
303 None => data,
304 };
305 let (macho, data) = find_header(data)?;
306 let endian = macho.endian().ok()?;
307 let obj = Object::parse(macho, endian, data)?;
308 Context::new(stash, obj, None, None)
309 })
310}
311
312pub(super) fn handle_split_dwarf<'data>(
313 _package: Option<&gimli::DwarfPackage<EndianSlice<'data, Endian>>>,
314 _stash: &'data Stash,
315 _load: addr2line::SplitDwarfLoad<EndianSlice<'data, Endian>>,
316) -> Option<Arc<gimli::Dwarf<EndianSlice<'data, Endian>>>> {
317 None
318}