diff --git a/Cargo.toml b/Cargo.toml index 78f3adc..f06d37f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,21 +6,22 @@ keywords = ["dyld", "dylib", "shared", "library", "dl_iterate_phdr"] license = "Apache-2.0/MIT" name = "findshlibs" readme = "./README.md" -repository = "https://github.com/fitzgen/findshlibs" -version = "0.3.2" +repository = "https://github.com/gimli-rs/findshlibs" +version = "0.4.0" + [badges.coveralls] -repository = "fitzgen/findshlibs" +repository = "gimli-rs/findshlibs" [badges.travis-ci] -repository = "fitzgen/findshlibs" +repository = "gimli-rs/findshlibs" [build-dependencies.bindgen] default-features = false -version = "0.29.0" +version = "0.36.0" [dependencies] -cfg-if = "0.1.0" -lazy_static = "0.2.2" +cfg-if = "0.1.2" +lazy_static = "1.0.0" [features] nightly = [] diff --git a/README.md b/README.md index 2517cf3..4cdb0e2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # `findshlibs` -[![](http://meritbadge.herokuapp.com/findshlibs) ![](https://img.shields.io/crates/d/findshlibs.png)](https://crates.io/crates/findshlibs) [![Build Status](https://travis-ci.org/fitzgen/findshlibs.png?branch=master)](https://travis-ci.org/fitzgen/findshlibs) [![Coverage Status](https://coveralls.io/repos/github/fitzgen/findshlibs/badge.svg?branch=master)](https://coveralls.io/github/fitzgen/findshlibs?branch=master) +[![](http://meritbadge.herokuapp.com/findshlibs) ![](https://img.shields.io/crates/d/findshlibs.png)](https://crates.io/crates/findshlibs) [![Build Status](https://travis-ci.org/gimli-rs/findshlibs.png?branch=master)](https://travis-ci.org/gimli-rs/findshlibs) [![Coverage Status](https://coveralls.io/repos/github/gimli-rs/findshlibs/badge.svg?branch=master)](https://coveralls.io/github/gimli-rs/findshlibs?branch=master) Find the shared libraries loaded in the current process with a cross platform API. @@ -39,4 +39,8 @@ These are the OSes that `findshlibs` currently supports: * Linux * macOS +If a platform is not supported then a fallback implementation is used that +does nothing. To see if your platform does something at runtime the +`TARGET_SUPPORTED` constant can be used. + Is your OS missing here? Send us a pull request! diff --git a/build.rs b/build.rs index b418b0a..f67fde7 100644 --- a/build.rs +++ b/build.rs @@ -8,17 +8,15 @@ fn main() { generate_linux_bindings(); } else if cfg!(target_os = "macos") { generate_macos_bindings(); - } else { - panic!("`findshlibs` does not support the target OS :("); } } fn generate_linux_bindings() { let bindings = bindgen::Builder::default() .header("./src/linux/bindings.h") - .whitelisted_function("dl_iterate_phdr") - .whitelisted_type(r#"Elf\d*.*"#) - .whitelisted_var("PT_.*") + .whitelist_function("dl_iterate_phdr") + .whitelist_type(r#"Elf\d*.*"#) + .whitelist_var("PT_.*") .generate() .expect("Should generate linux FFI bindings OK"); @@ -31,13 +29,15 @@ fn generate_linux_bindings() { fn generate_macos_bindings() { let bindings = bindgen::Builder::default() .header("./src/macos/bindings.h") - .whitelisted_function("_dyld_.*") - .whitelisted_type("mach_header.*") - .whitelisted_type("load_command.*") - .whitelisted_type("segment_command.*") - .whitelisted_type("section.*") - .whitelisted_var("MH_MAGIC.*") - .whitelisted_var("LC_SEGMENT.*") + .whitelist_function("_dyld_.*") + .whitelist_type("mach_header.*") + .whitelist_type("load_command.*") + .whitelist_type("uuid_command.*") + .whitelist_type("segment_command.*") + .whitelist_type("section.*") + .whitelist_var("MH_MAGIC.*") + .whitelist_var("LC_SEGMENT.*") + .whitelist_var("LC_UUID.*") .generate() .expect("Should generate macOS FFI bindings OK"); diff --git a/src/lib.rs b/src/lib.rs index a760f79..ff8659d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,6 +61,10 @@ //! * Linux //! * macOS //! +//! If a platform is not supported then a fallback implementation is used that +//! does nothing. To see if your platform does something at runtime the +//! `TARGET_SUPPORTED` constant can be used. +//! //! Is your OS missing here? Send us a pull request! //! //! ## Addresses @@ -108,6 +112,8 @@ use std::ffi::CStr; use std::fmt::{self, Debug}; use std::ptr; +pub mod unsupported; + cfg_if!( if #[cfg(target_os = "linux")] { @@ -117,6 +123,9 @@ cfg_if!( /// implementation for the target operating system. pub type TargetSharedLibrary<'a> = linux::SharedLibrary<'a>; + /// An indicator if this platform is supported. + pub const TARGET_SUPPORTED: bool = true; + } else if #[cfg(target_os = "macos")] { pub mod macos; @@ -125,9 +134,17 @@ cfg_if!( /// implementation for the target operating system. pub type TargetSharedLibrary<'a> = macos::SharedLibrary<'a>; + /// An indicator if this platform is supported. + pub const TARGET_SUPPORTED: bool = true; + } else { - // No implementation for this platform :( + /// The [`SharedLibrary` trait](./trait.SharedLibrary.html) + /// implementation for the target operating system. + pub type TargetSharedLibrary<'a> = unsupported::SharedLibrary<'a>; + + /// An indicator if this platform is supported. + pub const TARGET_SUPPORTED: bool = false; } ); @@ -285,6 +302,40 @@ pub trait Segment: NamedMemoryRange<::SharedLibrary> { type EhFrame: EhFrame; } +/// Represents an ID for a shared library. +#[derive(PartialEq, Eq, Hash)] +pub enum SharedLibraryId { + /// A UUID (used on mac) + Uuid([u8; 16]), +} + +impl fmt::Display for SharedLibraryId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SharedLibraryId::Uuid(ref bytes) => { + for (idx, byte) in bytes.iter().enumerate() { + if idx == 4 || idx == 6 || idx == 8 || idx == 10 { + try!(write!(f, "-")); + } + try!(write!(f, "{:02x}", byte)); + } + } + } + Ok(()) + } +} + +impl fmt::Debug for SharedLibraryId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + SharedLibraryId::Uuid(..) => { + write!(f, "Uuid(\"{}\")", self)?; + } + } + Ok(()) + } +} + /// A trait representing a shared library that is loaded in this process. pub trait SharedLibrary: Sized + Debug { /// The associated segment type for this shared library. @@ -302,6 +353,9 @@ pub trait SharedLibrary: Sized + Debug { /// Get the name of this shared library. fn name(&self) -> &CStr; + /// Get the debug-id of this shared library if available. + fn id(&self) -> Option; + /// Get the bias of this shared library. /// /// See the module documentation for details. @@ -318,6 +372,15 @@ pub trait SharedLibrary: Sized + Debug { /// exists. fn eh_frame(&self) -> Option; + /// Given an AVMA within this shared library, convert it back to an SVMA by + /// removing this shared library's bias. + #[inline] + fn avma_to_svma(&self, address: Avma) -> Svma { + let bias = self.virtual_memory_bias(); + let reverse_bias = -bias.0; + Svma(unsafe { address.0.offset(reverse_bias) }) + } + /// Iterate over the shared libraries mapped into this process and invoke /// `f` with each one. /// diff --git a/src/linux/mod.rs b/src/linux/mod.rs index 2891c0c..9dbc2ad 100644 --- a/src/linux/mod.rs +++ b/src/linux/mod.rs @@ -1,18 +1,16 @@ //! Linux-specific implementation of the `SharedLibrary` trait. -use super::{Bias, IterationControl, NamedMemoryRange, Svma}; +use super::{Bias, IterationControl, NamedMemoryRange, Svma, SharedLibraryId}; use super::EhFrameHdr as EhFrameHdrTrait; use super::Segment as SegmentTrait; use super::SharedLibrary as SharedLibraryTrait; use std::any::Any; -use std::cell::RefCell; use std::ffi::CStr; use std::isize; use std::marker::PhantomData; use std::os::raw; use std::panic; -use std::process; use std::slice; mod bindings; @@ -136,13 +134,13 @@ pub struct SharedLibrary<'a> { headers: &'a [Phdr], } -thread_local! { - static PANIC_VALUE: RefCell>> = RefCell::new(None); +struct IterState { + f: F, + panic: Option>, } const CONTINUE: raw::c_int = 0; const BREAK: raw::c_int = 1; -const PANIC: raw::c_int = 2; impl<'a> SharedLibrary<'a> { unsafe fn new(info: &'a bindings::dl_phdr_info, size: usize) -> Self { @@ -156,46 +154,24 @@ impl<'a> SharedLibrary<'a> { unsafe extern "C" fn callback(info: *mut bindings::dl_phdr_info, size: usize, - f: *mut raw::c_void) + state: *mut raw::c_void) -> raw::c_int where F: FnMut(&Self) -> C, C: Into { - // XXX: We must be very careful to avoid unwinding back into C code - // here, which is UB. We attempt to shepherd the panic across the C - // frames, by stashing it in the `PANIC_VALUE` thread local, and then - // resuming the panic after we exit the `dl_iterate_phdr` call. If, - // however, we panic *again* while stashing the panic value, then we are - // left with no choice but to abort. + let state = &mut *(state as *mut IterState); match panic::catch_unwind(panic::AssertUnwindSafe(|| { - let f = f as *mut F; - let f = f.as_mut().unwrap(); - let info = info.as_ref().unwrap(); let shlib = SharedLibrary::new(info, size); - f(&shlib).into() + (state.f)(&shlib).into() })) { Ok(IterationControl::Continue) => CONTINUE, Ok(IterationControl::Break) => BREAK, Err(panicked) => { - if let Err(_) = panic::catch_unwind(panic::AssertUnwindSafe(|| { - PANIC_VALUE.with(|p| { - *p.borrow_mut() = Some(panicked); - }); - })) { - // Try and print out a diagnostic message before aborting. - let _ = panic::catch_unwind(|| { - eprintln!( - "findshlibs: aborting due to double-panic when unwinding a panic \ - across C frames" - ); - }); - process::abort(); - } - - PANIC + state.panic = Some(panicked); + BREAK } } } @@ -211,6 +187,11 @@ impl<'a> SharedLibraryTrait for SharedLibrary<'a> { self.name } + #[inline] + fn id(&self) -> Option { + None + } + #[inline] fn segments(&self) -> Self::SegmentIter { SegmentIter { inner: self.headers.iter() } @@ -242,25 +223,21 @@ impl<'a> SharedLibraryTrait for SharedLibrary<'a> { } #[inline] - fn each(mut f: F) + fn each(f: F) where F: FnMut(&Self) -> C, C: Into { - match unsafe { - bindings::dl_iterate_phdr(Some(Self::callback::), &mut f as *mut _ as *mut _) - } { - r if r == BREAK || r == CONTINUE => return, - r if r == PANIC => { - panic::resume_unwind(PANIC_VALUE.with(|p| { - p.borrow_mut() - .take() - .expect("dl_iterate_phdr returned PANIC, but we don't have a PANIC_VALUE") - })) - } - otherwise => unreachable!( - "dl_iterate_phdr returned some value we never return from our callback: {}", - otherwise - ) + let mut state = IterState { + f: f, + panic: None, + }; + + unsafe { + bindings::dl_iterate_phdr(Some(Self::callback::), &mut state as *mut _ as *mut _); + } + + if let Some(panic) = state.panic { + panic::resume_unwind(panic); } } } diff --git a/src/macos/mod.rs b/src/macos/mod.rs index 73d1cef..06e6127 100644 --- a/src/macos/mod.rs +++ b/src/macos/mod.rs @@ -1,7 +1,7 @@ //! The MacOS implementation of the [SharedLibrary //! trait](../trait.SharedLibrary.html). -use super::{Bias, IterationControl, NamedMemoryRange, Svma}; +use super::{Bias, IterationControl, NamedMemoryRange, Svma, SharedLibraryId}; use super::EhFrameHdr as EhFrameHdrTrait; use super::EhFrame as EhFrameTrait; use super::Segment as SegmentTrait; @@ -247,6 +247,26 @@ pub struct SegmentIter<'a> { num_commands: usize, } +impl<'a> SegmentIter<'a> { + fn find_uuid(&self) -> Option<[u8; 16]> { + let mut num_commands = self.num_commands; + let mut commands = self.commands; + + while num_commands > 0 { + num_commands -= 1; + let this_command = unsafe { commands.as_ref().unwrap() }; + let command_size = this_command.cmdsize as isize; + if let bindings::LC_UUID = this_command.cmd { + let uuid_cmd = commands as *const bindings::uuid_command; + return Some(unsafe { (*uuid_cmd).uuid }); + } + commands = unsafe { (commands as *const u8).offset(command_size) as *const _ }; + } + + None + } +} + impl<'a> Iterator for SegmentIter<'a> { type Item = Segment<'a>; @@ -314,7 +334,8 @@ impl<'a> 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), + MachType::Mach64 => (header as *const bindings::mach_header_64) + .as_ref().map(MachHeader::Header64), } }) } @@ -390,6 +411,10 @@ impl<'a> SharedLibraryTrait for SharedLibrary<'a> { self.name } + fn id(&self) -> Option { + self.segments().find_uuid().map(SharedLibraryId::Uuid) + } + #[inline] fn segments(&self) -> Self::SegmentIter { match self.header { @@ -541,6 +566,13 @@ mod tests { }); } + #[test] + fn get_id() { + macos::SharedLibrary::each(|shlib| { + assert!(shlib.id().is_some()); + }); + } + #[test] fn have_text_or_pagezero() { macos::SharedLibrary::each(|shlib| { diff --git a/src/unsupported.rs b/src/unsupported.rs new file mode 100644 index 0000000..f559b1d --- /dev/null +++ b/src/unsupported.rs @@ -0,0 +1,151 @@ +//! The fallback implementation of the [SharedLibrary +//! trait](../trait.SharedLibrary.html) that does nothing. + +use super::Segment as SegmentTrait; +use super::SharedLibrary as SharedLibraryTrait; +use super::{Bias, IterationControl, SharedLibraryId, Svma, EhFrame, EhFrameHdr, NamedMemoryRange}; + +use std::ffi::CStr; +use std::marker::PhantomData; +use std::usize; + +/// An unsupported segment +#[derive(Debug)] +pub struct Segment<'a> { + phantom: PhantomData<&'a SharedLibrary<'a>>, +} + +impl <'a> SegmentTrait for Segment<'a> { + type SharedLibrary = SharedLibrary<'a>; + type EhFrameHdr = NoOpEhFrameHdr<'a>; + type EhFrame = NoOpEhFrame<'a>; +} + +impl <'a> NamedMemoryRange> for Segment<'a> { + fn name(&self) -> &'_ CStr { + unreachable!() + } + + fn stated_virtual_memory_address(&self) -> Svma { + unreachable!() + } + + fn len(&self) -> usize { + unreachable!() + } +} + +/// An iterator over Mach-O segments. +#[derive(Debug)] +pub struct SegmentIter<'a> { + phantom: PhantomData<&'a SharedLibrary<'a>>, +} + +impl<'a> Iterator for SegmentIter<'a> { + type Item = Segment<'a>; + + fn next(&mut self) -> Option { + None + } +} + +/// Unsuppoered EhFrame +#[derive(Debug)] +pub struct NoOpEhFrame<'a> { + phantom: PhantomData<&'a SharedLibrary<'a>>, +} + +impl<'a> EhFrame for NoOpEhFrame<'a> { + type Segment = Segment<'a>; + type SharedLibrary = SharedLibrary<'a>; + type EhFrameHdr = NoOpEhFrameHdr<'a>; +} + +impl <'a> NamedMemoryRange> for NoOpEhFrame<'a> { + fn name(&self) -> &'_ CStr { + unreachable!() + } + + fn stated_virtual_memory_address(&self) -> Svma { + unreachable!() + } + + fn len(&self) -> usize { + unreachable!() + } +} + +/// Unsupported EhFrameHdr +#[derive(Debug)] +pub struct NoOpEhFrameHdr<'a> { + phantom: PhantomData<&'a SharedLibrary<'a>>, +} + +impl <'a> EhFrameHdr for NoOpEhFrameHdr<'a> { + type Segment = Segment<'a>; + type SharedLibrary = SharedLibrary<'a>; + type EhFrame = NoOpEhFrame<'a>; +} + +impl <'a> NamedMemoryRange> for NoOpEhFrameHdr<'a> { + fn name(&self) -> &'_ CStr { + unreachable!() + } + + fn stated_virtual_memory_address(&self) -> Svma { + unreachable!() + } + + fn len(&self) -> usize { + unreachable!() + } +} + +/// The fallback implementation of the [SharedLibrary +/// trait](../trait.SharedLibrary.html). +#[derive(Debug)] +pub struct SharedLibrary<'a> { + phantom: PhantomData<&'a SharedLibrary<'a>>, +} + +impl<'a> SharedLibraryTrait for SharedLibrary<'a> { + type Segment = Segment<'a>; + type SegmentIter = SegmentIter<'a>; + type EhFrameHdr = NoOpEhFrameHdr<'a>; + type EhFrame = NoOpEhFrame<'a>; + + #[inline] + fn name(&self) -> &CStr { + unreachable!() + } + + fn id(&self) -> Option { + unreachable!() + } + + fn segments(&self) -> Self::SegmentIter { + SegmentIter { + phantom: PhantomData, + } + } + + #[inline] + fn virtual_memory_bias(&self) -> Bias { + unreachable!() + } + + fn each(_f: F) + where + F: FnMut(&Self) -> C, + C: Into, + { + } + + fn eh_frame_hdr(&self) -> Option { + unreachable!() + } + + fn eh_frame(&self) -> Option { + unreachable!() + } +}