findshlibs/
lib.rs

1//! # `findshlibs`
2//!
3//! Find the set of shared libraries currently loaded in this process with a
4//! cross platform API.
5//!
6//! The API entry point is the `TargetSharedLibrary` type and its
7//! `SharedLibrary::each` trait method implementation.
8//!
9//! ## Example
10//!
11//! Here is an example program that prints out each shared library that is
12//! loaded in the process and the addresses where each of its segments are
13//! mapped into memory.
14//!
15//! ```
16//! extern crate findshlibs;
17//! use findshlibs::{Segment, SharedLibrary, TargetSharedLibrary};
18//!
19//! fn main() {
20//!     TargetSharedLibrary::each(|shlib| {
21//!         println!("{}", shlib.name().to_string_lossy());
22//!
23//!         for seg in shlib.segments() {
24//!             println!("    {}: segment {}",
25//!                      seg.actual_virtual_memory_address(shlib),
26//!                      seg.name());
27//!         }
28//!     });
29//! }
30//! ```
31//!
32//! ## Supported OSes
33//!
34//! These are the OSes that `findshlibs` currently supports:
35//!
36//! * Linux
37//! * macOS
38//! * Windows
39//! * Android
40//! * iOS
41//!
42//! If a platform is not supported then a fallback implementation is used that
43//! does nothing.  To see if your platform does something at runtime the
44//! `TARGET_SUPPORTED` constant can be used.
45//!
46//! Is your OS missing here? Send us a pull request!
47//!
48//! ## Addresses
49//!
50//! Shared libraries' addresses can be confusing. They are loaded somewhere in
51//! physical memory, but we generally don't care about physical memory
52//! addresses, because only the OS can see that address and in userspace we can
53//! only access memory through its virtual memory address. But even "virtual
54//! memory address" is ambiguous because it isn't clear whether this is the
55//! address before or after the loader maps the shared library into memory and
56//! performs relocation.
57//!
58//! To clarify between these different kinds of addresses, we borrow some
59//! terminology from [LUL][]:
60//!
61//! > * **SVMA** ("Stated Virtual Memory Address"): this is an address of a
62//! >   symbol (etc) as it is stated in the symbol table, or other
63//! >   metadata, of an object.  Such values are typically small and
64//! >   start from zero or thereabouts, unless the object has been
65//! >   prelinked.
66//! >
67//! > * **AVMA** ("Actual Virtual Memory Address"): this is the address of a
68//! >   symbol (etc) in a running process, that is, once the associated
69//! >   object has been mapped into a process.  Such values are typically
70//! >   much larger than SVMAs, since objects can get mapped arbitrarily
71//! >   far along the address space.
72//! >
73//! > * **"Bias"**: the difference between AVMA and SVMA for a given symbol
74//! >   (specifically, AVMA - SVMA).  The bias is always an integral
75//! >   number of pages.  Once we know the bias for a given object's
76//! >   text section (for example), we can compute the AVMAs of all of
77//! >   its text symbols by adding the bias to their SVMAs.
78//!
79//! [LUL]: https://searchfox.org/mozilla-central/rev/13148faaa91a1c823a7d68563d9995480e714979/tools/profiler/lul/LulMain.h#17-51
80//!
81//! ## Names and IDs
82//!
83//! `findshlibs` also gives access to module names and IDs.  Since this is also
84//! not consistent across operating systems the following general rules apply:
85//!
86//! > * `id` refers to the ID of the object file itself.  This is generally
87//! >   available on all platforms however it might still not be compiled into
88//! >   the binary in all case.  For instance on Linux the `gnu.build-id` note
89//! >   needs to be compiled in (which Rust does automatically).
90//! > * `debug_id` refers to the ID of the debug file.  This only plays a role
91//! >   on Windows where the executable and the debug file (PDB) have a different
92//! >   ID.
93//! > * `name` is the name of the executable.  On most operating systems (and
94//! >   all systems implemented currently) this is not just the name but in fact
95//! >   the entire path to the executable.
96//! > * `debug_name` is the name of the debug file if known.  This is again
97//! >   the case on windows where this will be the path to the PDB file.
98#![deny(missing_docs)]
99
100#[cfg(any(target_os = "macos", target_os = "ios"))]
101pub mod macos;
102
103#[cfg(any(
104    target_os = "linux",
105    all(target_os = "android", feature = "dl_iterate_phdr")
106))]
107pub mod linux;
108
109#[cfg(target_os = "windows")]
110pub mod windows;
111
112use std::ffi::OsStr;
113use std::fmt::{self, Debug};
114use std::usize;
115
116pub mod unsupported;
117
118#[cfg(any(
119    target_os = "linux",
120    all(target_os = "android", feature = "dl_iterate_phdr")
121))]
122use crate::linux as native_mod;
123
124#[cfg(any(target_os = "macos", target_os = "ios"))]
125use crate::macos as native_mod;
126
127#[cfg(target_os = "windows")]
128use crate::windows as native_mod;
129
130#[cfg(not(any(
131    target_os = "macos",
132    target_os = "ios",
133    target_os = "linux",
134    all(target_os = "android", feature = "dl_iterate_phdr"),
135    target_os = "windows"
136)))]
137use unsupported as native_mod;
138
139/// The [`SharedLibrary` trait](./trait.SharedLibrary.html)
140/// implementation for the target operating system.
141pub type TargetSharedLibrary<'a> = native_mod::SharedLibrary<'a>;
142
143/// An indicator if this platform is supported.
144pub const TARGET_SUPPORTED: bool = cfg!(any(
145    target_os = "macos",
146    target_os = "ios",
147    target_os = "linux",
148    all(target_os = "android", feature = "dl_iterate_phdr"),
149    target_os = "windows"
150));
151
152macro_rules! simple_newtypes {
153    (
154        $(
155            $(#[$attr:meta])*
156            type $name:ident = $oldty:ty
157            where
158                default = $default:expr ,
159                display = $format:expr ;
160        )*
161    ) => {
162        $(
163            $(#[$attr])*
164            #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
165            pub struct $name(pub $oldty);
166
167            impl Default for $name {
168                #[inline]
169                fn default() -> Self {
170                    $name( $default )
171                }
172            }
173
174            impl From<$oldty> for $name {
175                fn from(x: $oldty) -> $name {
176                    $name(x)
177                }
178            }
179
180            impl From<$name> for $oldty {
181                fn from($name(x): $name) -> $oldty {
182                    x
183                }
184            }
185
186            impl fmt::Display for $name {
187                fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
188                    write!(f, $format, self.0)
189                }
190            }
191        )*
192    }
193}
194
195simple_newtypes! {
196    /// Stated virtual memory address.
197    ///
198    /// See the module documentation for details.
199    type Svma = usize
200    where
201        default = 0,
202        display = "{:#x}";
203
204    /// Actual virtual memory address.
205    ///
206    /// See the module documentation for details.
207    type Avma = usize
208    where
209        default = 0,
210        display = "{:#x}";
211
212    /// Virtual memory bias.
213    ///
214    /// See the module documentation for details.
215    type Bias = usize
216    where
217        default = 0,
218        display = "{:#x}";
219}
220
221/// A mapped segment in a shared library.
222#[allow(clippy::len_without_is_empty)]
223pub trait Segment: Sized + Debug {
224    /// The associated shared library type for this segment.
225    type SharedLibrary: SharedLibrary<Segment = Self>;
226
227    /// Get this segment's name.
228    fn name(&self) -> &str;
229
230    /// Returns `true` if this is a code segment.
231    #[inline]
232    fn is_code(&self) -> bool {
233        false
234    }
235
236    /// Returns `true` if this is a segment loaded into memory.
237    #[inline]
238    fn is_load(&self) -> bool {
239        self.is_code()
240    }
241
242    /// Get this segment's stated virtual address of this segment.
243    ///
244    /// This is the virtual memory address without the bias applied. See the
245    /// module documentation for details.
246    fn stated_virtual_memory_address(&self) -> Svma;
247
248    /// Get the length of this segment in memory (in bytes).
249    fn len(&self) -> usize;
250
251    // Provided methods.
252
253    /// Get this segment's actual virtual memory address.
254    ///
255    /// This is the virtual memory address with the bias applied. See the module
256    /// documentation for details.
257    #[inline]
258    fn actual_virtual_memory_address(&self, shlib: &Self::SharedLibrary) -> Avma {
259        let svma = self.stated_virtual_memory_address();
260        let bias = shlib.virtual_memory_bias();
261        Avma(svma.0 + bias.0)
262    }
263
264    /// Does this segment contain the given address?
265    #[inline]
266    fn contains_svma(&self, address: Svma) -> bool {
267        let start = self.stated_virtual_memory_address().0;
268        let end = start + self.len();
269        let address = address.0;
270        start <= address && address < end
271    }
272
273    /// Does this segment contain the given address?
274    #[inline]
275    fn contains_avma(&self, shlib: &Self::SharedLibrary, address: Avma) -> bool {
276        let start = self.actual_virtual_memory_address(shlib).0;
277        let end = start + self.len();
278        let address = address.0;
279        start <= address && address < end
280    }
281}
282
283/// Represents an ID for a shared library.
284#[derive(PartialEq, Eq, Hash)]
285pub enum SharedLibraryId {
286    /// A UUID (used on mac)
287    Uuid([u8; 16]),
288    /// A GNU build ID
289    GnuBuildId(Vec<u8>),
290    /// The PE timestamp and size
291    PeSignature(u32, u32),
292    /// A PDB GUID and age,
293    PdbSignature([u8; 16], u32),
294}
295
296impl SharedLibraryId {
297    /// Returns the raw bytes of the shared library ID.
298    pub fn as_bytes(&self) -> &[u8] {
299        match *self {
300            SharedLibraryId::Uuid(ref bytes) => &*bytes,
301            SharedLibraryId::GnuBuildId(ref bytes) => bytes,
302            SharedLibraryId::PeSignature(_, _) => &[][..],
303            SharedLibraryId::PdbSignature(ref bytes, _) => &*bytes,
304        }
305    }
306}
307
308impl fmt::Display for SharedLibraryId {
309    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
310        match *self {
311            SharedLibraryId::Uuid(ref bytes) => {
312                for (idx, byte) in bytes.iter().enumerate() {
313                    if idx == 4 || idx == 6 || idx == 8 || idx == 10 {
314                        write!(f, "-")?;
315                    }
316                    write!(f, "{:02x}", byte)?;
317                }
318            }
319            SharedLibraryId::GnuBuildId(ref bytes) => {
320                for byte in bytes {
321                    write!(f, "{:02x}", byte)?;
322                }
323            }
324            SharedLibraryId::PeSignature(timestamp, size_of_image) => {
325                write!(f, "{:08X}{:x}", timestamp, size_of_image)?;
326            }
327            SharedLibraryId::PdbSignature(ref bytes, age) => {
328                for (idx, byte) in bytes.iter().enumerate() {
329                    if idx == 4 || idx == 6 || idx == 8 || idx == 10 {
330                        write!(f, "-")?;
331                    }
332                    write!(f, "{:02X}", byte)?;
333                }
334                write!(f, "{:x}", age)?;
335            }
336        }
337        Ok(())
338    }
339}
340
341impl fmt::Debug for SharedLibraryId {
342    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
343        let name = match *self {
344            SharedLibraryId::Uuid(..) => "Uuid",
345            SharedLibraryId::GnuBuildId(..) => "GnuBuildId",
346            SharedLibraryId::PeSignature(..) => "PeSignature",
347            SharedLibraryId::PdbSignature(..) => "PdbSignature",
348        };
349        write!(f, "{}(\"{}\")", name, self)
350    }
351}
352
353/// A trait representing a shared library that is loaded in this process.
354#[allow(clippy::len_without_is_empty)]
355pub trait SharedLibrary: Sized + Debug {
356    /// The associated segment type for this shared library.
357    type Segment: Segment<SharedLibrary = Self>;
358
359    /// An iterator over a shared library's segments.
360    type SegmentIter: Debug + Iterator<Item = Self::Segment>;
361
362    /// Get the name of this shared library.
363    fn name(&self) -> &OsStr;
364
365    /// Get the name of the debug file with this shared library if there is one.
366    fn debug_name(&self) -> Option<&OsStr> {
367        None
368    }
369
370    /// Get the code-id of this shared library if available.
371    fn id(&self) -> Option<SharedLibraryId>;
372
373    /// Get the debug-id of this shared library if available.
374    fn debug_id(&self) -> Option<SharedLibraryId> {
375        self.id()
376    }
377
378    /// Returns the address of where the library is loaded into virtual
379    /// memory.
380    ///
381    /// This address maps to the `Avma` of the first segment loaded into
382    /// memory. Depending on the platform, this segment may not contain code.
383    fn actual_load_addr(&self) -> Avma {
384        self.segments()
385            .find(|x| x.is_load())
386            .map(|x| x.actual_virtual_memory_address(self))
387            .unwrap_or(Avma(usize::MAX))
388    }
389
390    #[inline]
391    #[doc(hidden)]
392    #[deprecated(note = "use stated_load_address() instead")]
393    fn load_addr(&self) -> Svma {
394        self.stated_load_addr()
395    }
396
397    /// Returns the address of where the library prefers to be loaded into
398    /// virtual memory.
399    ///
400    /// This address maps to the `Svma` of the first segment loaded into
401    /// memory. Depending on the platform, this segment may not contain code.
402    fn stated_load_addr(&self) -> Svma {
403        self.segments()
404            .find(|x| x.is_load())
405            .map(|x| x.stated_virtual_memory_address())
406            .unwrap_or(Svma(usize::MAX))
407    }
408
409    /// Returns the size of the image.
410    ///
411    /// This typically is the size of the executable code segment.  This is
412    /// normally used by server side symbolication systems to determine when
413    /// an IP no longer falls into an image.
414    fn len(&self) -> usize {
415        let end_address = self
416            .segments()
417            .filter(|x| x.is_load())
418            .map(|x| x.actual_virtual_memory_address(self).0 + x.len())
419            .max()
420            .unwrap_or(usize::MAX);
421
422        end_address - self.actual_load_addr().0
423    }
424
425    /// Iterate over this shared library's segments.
426    fn segments(&self) -> Self::SegmentIter;
427
428    /// Get the bias of this shared library.
429    ///
430    /// See the module documentation for details.
431    fn virtual_memory_bias(&self) -> Bias;
432
433    /// Given an AVMA within this shared library, convert it back to an SVMA by
434    /// removing this shared library's bias.
435    #[inline]
436    fn avma_to_svma(&self, address: Avma) -> Svma {
437        let bias = self.virtual_memory_bias();
438        Svma(address.0 - bias.0)
439    }
440
441    /// Find all shared libraries in this process and invoke `f` with each one.
442    fn each<F, C>(f: F)
443    where
444        F: FnMut(&Self) -> C,
445        C: Into<IterationControl>;
446}
447
448/// Control whether iteration over shared libraries should continue or stop.
449#[derive(Clone, Copy, Debug, PartialEq, Eq)]
450pub enum IterationControl {
451    /// Stop iteration.
452    Break,
453    /// Continue iteration.
454    Continue,
455}
456
457impl From<()> for IterationControl {
458    #[inline]
459    fn from(_: ()) -> Self {
460        IterationControl::Continue
461    }
462}
463
464#[cfg(test)]
465mod tests {
466    use super::*;
467
468    #[test]
469    fn panic_in_each() {
470        use std::panic;
471
472        match panic::catch_unwind(|| {
473            TargetSharedLibrary::each(|_| panic!("uh oh"));
474        }) {
475            Ok(()) => panic!("Expected a panic, but didn't get one"),
476            Err(any) => {
477                assert!(
478                    any.is::<&'static str>(),
479                    "panic value should be a &'static str"
480                );
481                assert_eq!(*any.downcast_ref::<&'static str>().unwrap(), "uh oh");
482            }
483        }
484    }
485
486    #[test]
487    fn test_load_address_bias() {
488        TargetSharedLibrary::each(|lib| {
489            let svma = lib.stated_load_addr();
490            let avma = lib.actual_load_addr();
491            assert_eq!(lib.avma_to_svma(avma), svma);
492        });
493    }
494}