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}