use super::{Bias, IterationControl, Svma};
use super::Segment as SegmentTrait;
use super::SharedLibrary as SharedLibraryTrait;
use std::ffi::CStr;
use std::marker::PhantomData;
use std::ptr;
use std::sync::Mutex;
use std::usize;
mod bindings;
lazy_static! {
pub static ref DYLD_LOCK: Mutex<()> = Mutex::new(());
}
#[derive(Debug)]
pub enum Segment<'a> {
Segment32(&'a bindings::segment_command),
Segment64(&'a bindings::segment_command_64),
}
impl<'a> SegmentTrait for Segment<'a> {
type SharedLibrary = ::macos::SharedLibrary<'a>;
#[inline]
fn name(&self) -> &CStr {
match *self {
Segment::Segment32(seg) => unsafe { CStr::from_ptr(seg.segname.as_ptr()) },
Segment::Segment64(seg) => unsafe { CStr::from_ptr(seg.segname.as_ptr()) },
}
}
#[inline]
fn stated_virtual_memory_address(&self) -> Svma {
match *self {
Segment::Segment32(seg) => Svma(seg.vmaddr as usize as *const u8),
Segment::Segment64(seg) => {
assert!(seg.vmaddr <= (usize::MAX as u64));
Svma(seg.vmaddr as usize as *const u8)
}
}
}
#[inline]
fn len(&self) -> usize {
match *self {
Segment::Segment32(seg) => seg.vmsize as usize,
Segment::Segment64(seg) => {
assert!(seg.vmsize <= (usize::MAX as u64));
seg.vmsize as usize
}
}
}
}
#[derive(Debug)]
pub struct SegmentIter<'a> {
phantom: PhantomData<&'a SharedLibrary<'a>>,
commands: *const bindings::load_command,
num_commands: usize,
}
impl<'a> Iterator for SegmentIter<'a> {
type Item = Segment<'a>;
fn next(&mut self) -> Option<Self::Item> {
while self.num_commands > 0 {
self.num_commands -= 1;
let this_command = unsafe { self.commands.as_ref().unwrap() };
let command_size = this_command.cmdsize as isize;
match this_command.cmd {
bindings::LC_SEGMENT => {
let segment = self.commands as *const bindings::segment_command;
let segment = unsafe { segment.as_ref().unwrap() };
self.commands =
unsafe { (self.commands as *const u8).offset(command_size) as *const _ };
return Some(Segment::Segment32(segment));
}
bindings::LC_SEGMENT_64 => {
let segment = self.commands as *const bindings::segment_command_64;
let segment = unsafe { segment.as_ref().unwrap() };
self.commands =
unsafe { (self.commands as *const u8).offset(command_size) as *const _ };
return Some(Segment::Segment64(segment));
}
_ => {
self.commands =
unsafe { (self.commands as *const u8).offset(command_size) as *const _ };
continue;
}
}
}
None
}
}
#[derive(Debug)]
enum MachType {
Mach32,
Mach64,
}
impl MachType {
unsafe fn from_header_ptr(header: *const bindings::mach_header) -> Option<MachType> {
header.as_ref().and_then(|header| {
match header.magic {
bindings::MH_MAGIC => Some(MachType::Mach32),
bindings::MH_MAGIC_64 => Some(MachType::Mach64),
_ => None,
}
})
}
}
#[derive(Debug)]
enum MachHeader<'a> {
Header32(&'a bindings::mach_header),
Header64(&'a bindings::mach_header_64),
}
impl<'a> MachHeader<'a> {
unsafe fn from_header_ptr(header: *const bindings::mach_header) -> Option<MachHeader<'a>> {
MachType::from_header_ptr(header).and_then(|ty| {
match ty {
MachType::Mach32 => header.as_ref().map(MachHeader::Header32),
MachType::Mach64 => (header as *const _).as_ref().map(MachHeader::Header64),
}
})
}
}
#[derive(Debug)]
pub struct SharedLibrary<'a> {
header: MachHeader<'a>,
slide: isize,
name: &'a CStr,
}
impl<'a> SharedLibrary<'a> {
fn new(header: MachHeader<'a>, slide: isize, name: &'a CStr) -> Self {
SharedLibrary {
header: header,
slide: slide,
name: name,
}
}
}
impl<'a> SharedLibraryTrait for SharedLibrary<'a> {
type Segment = Segment<'a>;
type SegmentIter = SegmentIter<'a>;
#[inline]
fn name(&self) -> &CStr {
self.name
}
fn segments(&self) -> Self::SegmentIter {
match self.header {
MachHeader::Header32(header) => {
let num_commands = header.ncmds;
let header = header as *const bindings::mach_header;
let commands = unsafe { header.offset(1) as *const bindings::load_command };
SegmentIter {
phantom: PhantomData,
commands: commands,
num_commands: num_commands as usize,
}
}
MachHeader::Header64(header) => {
let num_commands = header.ncmds;
let header = header as *const bindings::mach_header_64;
let commands = unsafe { header.offset(1) as *const bindings::load_command };
SegmentIter {
phantom: PhantomData,
commands: commands,
num_commands: num_commands as usize,
}
}
}
}
#[inline]
fn virtual_memory_bias(&self) -> Bias {
Bias(self.slide)
}
fn each<F, C>(mut f: F)
where F: FnMut(&Self) -> C,
C: Into<IterationControl>
{
let _dyld_lock = DYLD_LOCK.lock();
let count = unsafe { bindings::_dyld_image_count() };
for image_idx in 0..count {
let (header, slide, name) = unsafe {
(bindings::_dyld_get_image_header(image_idx),
bindings::_dyld_get_image_vmaddr_slide(image_idx),
bindings::_dyld_get_image_name(image_idx))
};
if let Some(header) = unsafe { MachHeader::from_header_ptr(header) } {
assert!(slide != 0,
"If we have a header pointer, slide should be valid");
assert!(name != ptr::null(),
"If we have a header pointer, name should be valid");
let name = unsafe { CStr::from_ptr(name) };
let shlib = SharedLibrary::new(header, slide, name);
match f(&shlib).into() {
IterationControl::Break => break,
IterationControl::Continue => continue,
}
}
}
}
}
#[cfg(test)]
mod tests {
use macos;
use super::super::{IterationControl, SharedLibrary, Segment};
#[test]
fn have_libdyld() {
let mut found_dyld = false;
macos::SharedLibrary::each(|shlib| {
found_dyld |= shlib.name
.to_bytes()
.split(|c| *c == b'.' || *c == b'/')
.find(|s| s == b"libdyld")
.is_some();
});
assert!(found_dyld);
}
#[test]
fn can_break() {
let mut first_count = 0;
macos::SharedLibrary::each(|_| {
first_count += 1;
});
assert!(first_count > 2);
let mut second_count = 0;
macos::SharedLibrary::each(|_| {
second_count += 1;
if second_count == first_count - 1 {
IterationControl::Break
} else {
IterationControl::Continue
}
});
assert_eq!(second_count, first_count - 1);
}
#[test]
fn get_name() {
macos::SharedLibrary::each(|shlib| {
let _ = shlib.name();
});
}
#[test]
fn have_text_or_pagezero() {
macos::SharedLibrary::each(|shlib| {
println!("shlib = {:?}", shlib.name());
let mut found_text_or_pagezero = false;
for seg in shlib.segments() {
println!(" segment = {:?}", seg.name());
found_text_or_pagezero |= seg.name().to_bytes() == b"__TEXT";
found_text_or_pagezero |= seg.name().to_bytes() == b"__PAGEZERO";
}
assert!(found_text_or_pagezero);
});
}
}