| #![allow(deprecated)] |
| |
| use super::mystd::ffi::{CStr, OsStr}; |
| use super::mystd::os::unix::prelude::*; |
| use super::mystd::prelude::v1::*; |
| use super::{Library, LibrarySegment}; |
| use core::convert::TryInto; |
| use core::mem; |
| |
| // FIXME: replace with ptr::from_ref once MSRV is high enough |
| #[inline(always)] |
| #[must_use] |
| const fn ptr_from_ref<T: ?Sized>(r: &T) -> *const T { |
| r |
| } |
| |
| pub(super) fn native_libraries() -> Vec<Library> { |
| let mut ret = Vec::new(); |
| let images = unsafe { libc::_dyld_image_count() }; |
| for i in 0..images { |
| ret.extend(native_library(i)); |
| } |
| return ret; |
| } |
| |
| fn native_library(i: u32) -> Option<Library> { |
| use object::macho; |
| use object::read::macho::{MachHeader, Segment}; |
| use object::NativeEndian; |
| |
| // Fetch the name of this library which corresponds to the path of |
| // where to load it as well. |
| let name = unsafe { |
| let name = libc::_dyld_get_image_name(i); |
| if name.is_null() { |
| return None; |
| } |
| CStr::from_ptr(name) |
| }; |
| |
| // Load the image header of this library and delegate to `object` to |
| // parse all the load commands so we can figure out all the segments |
| // involved here. |
| let (mut load_commands, endian) = unsafe { |
| let header = libc::_dyld_get_image_header(i); |
| if header.is_null() { |
| return None; |
| } |
| match (*header).magic { |
| macho::MH_MAGIC => { |
| let endian = NativeEndian; |
| let header = &*header.cast::<macho::MachHeader32<NativeEndian>>(); |
| let data = core::slice::from_raw_parts( |
| ptr_from_ref(header).cast::<u8>(), |
| mem::size_of_val(header) + header.sizeofcmds.get(endian) as usize, |
| ); |
| (header.load_commands(endian, data, 0).ok()?, endian) |
| } |
| macho::MH_MAGIC_64 => { |
| let endian = NativeEndian; |
| let header = &*header.cast::<macho::MachHeader64<NativeEndian>>(); |
| let data = core::slice::from_raw_parts( |
| ptr_from_ref(header).cast::<u8>(), |
| mem::size_of_val(header) + header.sizeofcmds.get(endian) as usize, |
| ); |
| (header.load_commands(endian, data, 0).ok()?, endian) |
| } |
| _ => return None, |
| } |
| }; |
| |
| // Iterate over the segments and register known regions for segments |
| // that we find. Additionally record information bout text segments |
| // for processing later, see comments below. |
| let mut segments = Vec::new(); |
| let mut first_text = 0; |
| let mut text_fileoff_zero = false; |
| while let Some(cmd) = load_commands.next().ok()? { |
| if let Some((seg, _)) = cmd.segment_32().ok()? { |
| if seg.name() == b"__TEXT" { |
| first_text = segments.len(); |
| if seg.fileoff(endian) == 0 && seg.filesize(endian) > 0 { |
| text_fileoff_zero = true; |
| } |
| } |
| segments.push(LibrarySegment { |
| len: seg.vmsize(endian).try_into().ok()?, |
| stated_virtual_memory_address: seg.vmaddr(endian).try_into().ok()?, |
| }); |
| } |
| if let Some((seg, _)) = cmd.segment_64().ok()? { |
| if seg.name() == b"__TEXT" { |
| first_text = segments.len(); |
| if seg.fileoff(endian) == 0 && seg.filesize(endian) > 0 { |
| text_fileoff_zero = true; |
| } |
| } |
| segments.push(LibrarySegment { |
| len: seg.vmsize(endian).try_into().ok()?, |
| stated_virtual_memory_address: seg.vmaddr(endian).try_into().ok()?, |
| }); |
| } |
| } |
| |
| // Determine the "slide" for this library which ends up being the |
| // bias we use to figure out where in memory objects are loaded. |
| // This is a bit of a weird computation though and is the result of |
| // trying a few things in the wild and seeing what sticks. |
| // |
| // The general idea is that the `bias` plus a segment's |
| // `stated_virtual_memory_address` is going to be where in the |
| // actual address space the segment resides. The other thing we rely |
| // on though is that a real address minus the `bias` is the index to |
| // look up in the symbol table and debuginfo. |
| // |
| // It turns out, though, that for system loaded libraries these |
| // calculations are incorrect. For native executables, however, it |
| // appears correct. Lifting some logic from LLDB's source it has |
| // some special-casing for the first `__TEXT` section loaded from |
| // file offset 0 with a nonzero size. For whatever reason when this |
| // is present it appears to mean that the symbol table is relative |
| // to just the vmaddr slide for the library. If it's *not* present |
| // then the symbol table is relative to the vmaddr slide plus the |
| // segment's stated address. |
| // |
| // To handle this situation if we *don't* find a text section at |
| // file offset zero then we increase the bias by the first text |
| // sections's stated address and decrease all stated addresses by |
| // that amount as well. That way the symbol table is always appears |
| // relative to the library's bias amount. This appears to have the |
| // right results for symbolizing via the symbol table. |
| // |
| // Honestly I'm not entirely sure whether this is right or if |
| // there's something else that should indicate how to do this. For |
| // now though this seems to work well enough (?) and we should |
| // always be able to tweak this over time if necessary. |
| // |
| // For some more information see #318 |
| let mut slide = unsafe { libc::_dyld_get_image_vmaddr_slide(i) as usize }; |
| if !text_fileoff_zero { |
| let adjust = segments[first_text].stated_virtual_memory_address; |
| for segment in segments.iter_mut() { |
| segment.stated_virtual_memory_address -= adjust; |
| } |
| slide += adjust; |
| } |
| |
| Some(Library { |
| name: OsStr::from_bytes(name.to_bytes()).to_owned(), |
| segments, |
| bias: slide, |
| }) |
| } |