blob: 17703b88a88bed1348abbc573660baff12c66ef2 [file] [log] [blame]
#![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;
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 as *const macho::MachHeader32<NativeEndian>);
let data = core::slice::from_raw_parts(
header as *const _ as *const 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 as *const macho::MachHeader64<NativeEndian>);
let data = core::slice::from_raw_parts(
header as *const _ as *const 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 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,
})
}