std/backtrace/src/symbolize/gimli/
macho.rs

1use 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    // The loading path for macOS is so different we just have a completely
21    // different implementation of the function here. On macOS we need to go
22    // probing the filesystem for a bunch of files.
23    pub fn new(path: &Path) -> Option<Mapping> {
24        // First up we need to load the unique UUID which is stored in the macho
25        // header of the file we're reading, specified at `path`.
26        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        // Next we need to look for a `*.dSYM` file. For now we just probe the
32        // containing directory and look around for something that matches
33        // `*.dSYM`. Once it's found we root through the dwarf resources that it
34        // contains and try to find a macho file which has a matching UUID as
35        // the one of our own file. If we find a match that's the dwarf file we
36        // want to return.
37        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        // Looks like nothing matched our UUID, so let's at least return our own
46        // file. This should have the symbol table for at least some
47        // symbolication purposes.
48        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        // Look for files in the `DWARF` directory which have a matching uuid to
76        // the original object file. If we find one then we found the debug
77        // information.
78        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
162// This is used both for executables/libraries and source object files.
163pub 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    // Only set for executables/libraries, and not the source object files.
170    object_map: Option<object::ObjectMap<'a>>,
171    // The outer Option is for lazy loading, and the inner Option allows load errors to be cached.
172    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                // Object files should have all sections in a single unnamed segment load command.
187                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                    // We never search object file symbols by address.
205                    // Instead, we already know the symbol name from the executable, and we
206                    // need to search by name to find the matching symbol in the object file.
207                    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                    && &section_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    /// Try to load a context for an object file.
254    ///
255    /// If dsymutil was not run, then the DWARF may be found in the source object files.
256    pub(super) fn search_object_map<'b>(&'b mut self, addr: u64) -> Option<(&'b Context<'b>, u64)> {
257        // `object_map` contains a map from addresses to symbols and object paths.
258        // Look up the address and get a mapping for the object.
259        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            // No cached mapping, so create it.
265            *mapping = Some(object_mapping(object_map.objects().get(object_index)?));
266        }
267        let cx: &'b Context<'static> = &mapping.as_ref()?.as_ref()?.cx;
268        // Don't leak the `'static` lifetime, make sure it's scoped to just ourselves.
269        let cx = unsafe { core::mem::transmute::<&'b Context<'static>, &'b Context<'b>>(cx) };
270
271        // We must translate the address in order to be able to look it up
272        // in the DWARF in the object file.
273        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}