std/backtrace/src/symbolize/gimli/libs_macos.rs
1#![allow(deprecated)]
2
3use super::mystd::ffi::OsStr;
4use super::mystd::os::unix::prelude::*;
5use super::mystd::prelude::v1::*;
6use super::{Library, LibrarySegment};
7use core::convert::TryInto;
8use core::ffi::CStr;
9use core::mem;
10
11// FIXME: replace with ptr::from_ref once MSRV is high enough
12#[inline(always)]
13#[must_use]
14const fn ptr_from_ref<T: ?Sized>(r: &T) -> *const T {
15 r
16}
17
18pub(super) fn native_libraries() -> Vec<Library> {
19 let mut ret = Vec::new();
20 let images = unsafe { libc::_dyld_image_count() };
21 for i in 0..images {
22 ret.extend(native_library(i));
23 }
24 return ret;
25}
26
27fn native_library(i: u32) -> Option<Library> {
28 use object::macho;
29 use object::read::macho::{MachHeader, Segment};
30 use object::NativeEndian;
31
32 // Fetch the name of this library which corresponds to the path of
33 // where to load it as well.
34 let name = unsafe {
35 let name = libc::_dyld_get_image_name(i);
36 if name.is_null() {
37 return None;
38 }
39 CStr::from_ptr(name)
40 };
41
42 // Load the image header of this library and delegate to `object` to
43 // parse all the load commands so we can figure out all the segments
44 // involved here.
45 let (mut load_commands, endian) = unsafe {
46 let header = libc::_dyld_get_image_header(i);
47 if header.is_null() {
48 return None;
49 }
50 match (*header).magic {
51 macho::MH_MAGIC => {
52 let endian = NativeEndian;
53 let header = &*header.cast::<macho::MachHeader32<NativeEndian>>();
54 let data = core::slice::from_raw_parts(
55 ptr_from_ref(header).cast::<u8>(),
56 mem::size_of_val(header) + header.sizeofcmds.get(endian) as usize,
57 );
58 (header.load_commands(endian, data, 0).ok()?, endian)
59 }
60 macho::MH_MAGIC_64 => {
61 let endian = NativeEndian;
62 let header = &*header.cast::<macho::MachHeader64<NativeEndian>>();
63 let data = core::slice::from_raw_parts(
64 ptr_from_ref(header).cast::<u8>(),
65 mem::size_of_val(header) + header.sizeofcmds.get(endian) as usize,
66 );
67 (header.load_commands(endian, data, 0).ok()?, endian)
68 }
69 _ => return None,
70 }
71 };
72
73 // Iterate over the segments and register known regions for segments
74 // that we find. Additionally record information bout text segments
75 // for processing later, see comments below.
76 let mut segments = Vec::new();
77 let mut first_text = 0;
78 let mut text_fileoff_zero = false;
79 while let Some(cmd) = load_commands.next().ok()? {
80 if let Some((seg, _)) = cmd.segment_32().ok()? {
81 if seg.name() == b"__TEXT" {
82 first_text = segments.len();
83 if seg.fileoff(endian) == 0 && seg.filesize(endian) > 0 {
84 text_fileoff_zero = true;
85 }
86 }
87 segments.push(LibrarySegment {
88 len: seg.vmsize(endian).try_into().ok()?,
89 stated_virtual_memory_address: seg.vmaddr(endian).try_into().ok()?,
90 });
91 }
92 if let Some((seg, _)) = cmd.segment_64().ok()? {
93 if seg.name() == b"__TEXT" {
94 first_text = segments.len();
95 if seg.fileoff(endian) == 0 && seg.filesize(endian) > 0 {
96 text_fileoff_zero = true;
97 }
98 }
99 segments.push(LibrarySegment {
100 len: seg.vmsize(endian).try_into().ok()?,
101 stated_virtual_memory_address: seg.vmaddr(endian).try_into().ok()?,
102 });
103 }
104 }
105
106 // Determine the "slide" for this library which ends up being the
107 // bias we use to figure out where in memory objects are loaded.
108 // This is a bit of a weird computation though and is the result of
109 // trying a few things in the wild and seeing what sticks.
110 //
111 // The general idea is that the `bias` plus a segment's
112 // `stated_virtual_memory_address` is going to be where in the
113 // actual address space the segment resides. The other thing we rely
114 // on though is that a real address minus the `bias` is the index to
115 // look up in the symbol table and debuginfo.
116 //
117 // It turns out, though, that for system loaded libraries these
118 // calculations are incorrect. For native executables, however, it
119 // appears correct. Lifting some logic from LLDB's source it has
120 // some special-casing for the first `__TEXT` section loaded from
121 // file offset 0 with a nonzero size. For whatever reason when this
122 // is present it appears to mean that the symbol table is relative
123 // to just the vmaddr slide for the library. If it's *not* present
124 // then the symbol table is relative to the vmaddr slide plus the
125 // segment's stated address.
126 //
127 // To handle this situation if we *don't* find a text section at
128 // file offset zero then we increase the bias by the first text
129 // sections's stated address and decrease all stated addresses by
130 // that amount as well. That way the symbol table is always appears
131 // relative to the library's bias amount. This appears to have the
132 // right results for symbolizing via the symbol table.
133 //
134 // Honestly I'm not entirely sure whether this is right or if
135 // there's something else that should indicate how to do this. For
136 // now though this seems to work well enough (?) and we should
137 // always be able to tweak this over time if necessary.
138 //
139 // For some more information see #318
140 let mut slide = unsafe { libc::_dyld_get_image_vmaddr_slide(i) as usize };
141 if !text_fileoff_zero {
142 let adjust = segments[first_text].stated_virtual_memory_address;
143 for segment in segments.iter_mut() {
144 segment.stated_virtual_memory_address -= adjust;
145 }
146 slide += adjust;
147 }
148
149 Some(Library {
150 name: OsStr::from_bytes(name.to_bytes()).to_owned(),
151 segments,
152 bias: slide,
153 })
154}