dlopen_rs/loader/
mod.rs

1pub(crate) mod builtin;
2pub(crate) mod ehframe;
3#[cfg(feature = "tls")]
4pub(crate) mod tls;
5
6#[cfg(feature = "debug")]
7use super::debug::DebugInfo;
8use crate::{
9    OpenFlags, Result, find_lib_error, find_symbol_error,
10    register::{DylibState, MANAGER, register},
11};
12use alloc::{boxed::Box, format, sync::Arc, vec::Vec};
13use core::{any::Any, ffi::CStr, fmt::Debug};
14use ehframe::EhFrame;
15use elf_loader::{
16    CoreComponent, CoreComponentRef, ElfDylib, Loader, RelocatedDylib, Symbol, UserData,
17    abi::PT_GNU_EH_FRAME,
18    arch::{ElfPhdr, ElfRela},
19    mmap::MmapImpl,
20    object::{ElfBinary, ElfObject},
21    segment::ElfSegments,
22};
23
24pub(crate) const EH_FRAME_ID: u8 = 0;
25#[cfg(feature = "debug")]
26pub(crate) const DEBUG_INFO_ID: u8 = 1;
27#[cfg(feature = "tls")]
28const TLS_ID: u8 = 2;
29const CLOSURE: u8 = 3;
30
31#[inline]
32pub(crate) fn find_symbol<'lib, T>(
33    libs: &'lib [RelocatedDylib<'static>],
34    name: &str,
35) -> Result<Symbol<'lib, T>> {
36    log::info!("Get the symbol [{}] in [{}]", name, libs[0].shortname());
37    libs.iter()
38        .find_map(|lib| unsafe { lib.get::<T>(name) })
39        .ok_or(find_symbol_error(format!("can not find symbol:{}", name)))
40}
41
42/// An unrelocated dynamic library
43pub struct ElfLibrary {
44    pub(crate) dylib: ElfDylib,
45    pub(crate) flags: OpenFlags,
46}
47
48impl Debug for ElfLibrary {
49    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
50        self.dylib.fmt(f)
51    }
52}
53
54#[inline(always)]
55#[allow(unused)]
56fn parse_phdr(
57    cname: &CStr,
58    phdr: &ElfPhdr,
59    segments: &ElfSegments,
60    data: &mut UserData,
61) -> core::result::Result<(), Box<dyn Any>> {
62    match phdr.p_type {
63        PT_GNU_EH_FRAME => {
64            data.insert(
65                EH_FRAME_ID,
66                Box::new(EhFrame::new(
67                    phdr,
68                    segments.base()..segments.base() + segments.len(),
69                )),
70            );
71        }
72        #[cfg(feature = "debug")]
73        elf_loader::abi::PT_DYNAMIC => {
74            data.insert(
75                DEBUG_INFO_ID,
76                Box::new(unsafe {
77                    DebugInfo::new(
78                        segments.base(),
79                        cname.as_ptr() as _,
80                        segments.base() + phdr.p_vaddr as usize,
81                    )
82                }),
83            );
84        }
85        #[cfg(feature = "tls")]
86        elf_loader::abi::PT_TLS => {
87            data.insert(TLS_ID, Box::new(tls::ElfTls::new(phdr, segments.base())));
88        }
89        _ => {}
90    }
91    Ok(())
92}
93
94#[inline(always)]
95#[allow(unused)]
96pub(crate) fn deal_unknown<'scope>(
97    rela: &ElfRela,
98    lib: &CoreComponent,
99    mut deps: impl Iterator<Item = &'scope RelocatedDylib<'static>> + Clone,
100) -> core::result::Result<(), Box<dyn Any>> {
101    #[cfg(feature = "tls")]
102    match rela.r_type() as _ {
103        elf_loader::arch::REL_DTPMOD => {
104            let r_sym = rela.r_symbol();
105            let r_off = rela.r_offset();
106            let ptr = (lib.base() + r_off) as *mut usize;
107            let cast = |core: &elf_loader::CoreComponent| unsafe {
108                core.user_data()
109                    .get(TLS_ID)
110                    .unwrap()
111                    .downcast_ref::<tls::ElfTls>()
112                    .unwrap_unchecked()
113                    .module_id()
114            };
115            if r_sym != 0 {
116                let (dynsym, syminfo) = lib.symtab().unwrap().symbol_idx(r_sym);
117                if dynsym.is_local() {
118                    unsafe { ptr.write(cast(lib)) };
119                    return Ok(());
120                } else {
121                    if let Some(id) = deps.find_map(|lib| unsafe {
122                        lib.symtab()
123                            .lookup_filter(&syminfo)
124                            .map(|_| cast(lib.core_component_ref()))
125                    }) {
126                        unsafe { ptr.write(id) };
127                        return Ok(());
128                    };
129                };
130            } else {
131                unsafe { ptr.write(cast(lib)) };
132                return Ok(());
133            }
134        }
135        elf_loader::arch::REL_TPOFF => {
136            let r_sym = rela.r_symbol();
137            let r_off = rela.r_offset();
138            if r_sym != 0 {
139                let (dynsym, syminfo) = lib.symtab().unwrap().symbol_idx(r_sym);
140                if let Some(val) = tls::get_libc_tls_offset(syminfo.name()) {
141                    let ptr = (lib.base() + r_off) as *mut usize;
142                    unsafe { ptr.write(val) };
143                    return Ok(());
144                }
145            }
146        }
147        _ => {}
148    }
149    log::error!("Relocating dylib [{}] failed!", lib.name());
150    Err(Box::new(()))
151}
152
153#[inline]
154pub(crate) fn create_lazy_scope(
155    deps: &[RelocatedDylib],
156    is_lazy: bool,
157) -> Option<Box<dyn for<'a> Fn(&'a str) -> Option<*const ()>>> {
158    if is_lazy {
159        let deps_weak: Vec<CoreComponentRef> = deps
160            .iter()
161            .map(|dep| unsafe { dep.core_component_ref().downgrade() })
162            .collect();
163        Some(Box::new(move |name: &str| {
164            deps_weak.iter().find_map(|dep| unsafe {
165                let lib = RelocatedDylib::from_core_component(dep.upgrade().unwrap());
166                lib.get::<()>(name).map(|sym| {
167                    log::trace!(
168                        "Lazy Binding: find symbol [{}] from [{}] in local scope ",
169                        name,
170                        lib.name()
171                    );
172                    sym.into_raw()
173                })
174            })
175        })
176            as Box<dyn Fn(&str) -> Option<*const ()> + 'static>)
177    } else {
178        None
179    }
180}
181
182fn from_impl(object: impl ElfObject, flags: OpenFlags) -> Result<ElfLibrary> {
183    let mut loader = Loader::<MmapImpl>::new();
184    #[cfg(feature = "std")]
185    unsafe {
186        loader.set_init_params(
187            crate::init::ARGC,
188            (*core::ptr::addr_of!(crate::init::ARGV)).as_ptr() as usize,
189            crate::init::ENVP,
190        )
191    };
192    let lazy_bind = if flags.contains(OpenFlags::RTLD_LAZY) {
193        Some(true)
194    } else if flags.contains(OpenFlags::RTLD_NOW) {
195        Some(false)
196    } else {
197        None
198    };
199    let dylib = loader.load_dylib(object, lazy_bind, parse_phdr)?;
200    log::debug!(
201        "Loading dylib [{}] at address [0x{:x}-0x{:x}]",
202        dylib.name(),
203        dylib.base(),
204        dylib.base() + dylib.map_len()
205    );
206    let lib = ElfLibrary { dylib, flags };
207    Ok(lib)
208}
209
210impl ElfLibrary {
211    /// Find and load a elf dynamic library from path.
212    ///
213    /// The `path` argument may be either:
214    /// * The absolute path to the library;
215    /// * A relative (to the current working directory) path to the library.   
216    ///
217    /// The `flags` argument can control how dynamic libraries are loaded.
218    ///
219    /// # Examples
220    /// ```no_run
221    /// # use dlopen_rs::{ElfLibrary, OpenFlags};
222    /// let lib = ElfLibrary::from_file("/path/to/awesome.module", OpenFlags::RTLD_LOCAL)
223    ///		.unwrap();
224    /// ```
225    ///
226    #[cfg(feature = "std")]
227    #[inline]
228    pub fn from_file(path: impl AsRef<std::ffi::OsStr>, flags: OpenFlags) -> Result<Self> {
229        let path = path.as_ref().to_str().unwrap();
230        let file = std::fs::File::open(path)?;
231        Self::from_open_file(file, path, flags)
232    }
233
234    /// Creates a new `ElfLibrary` instance from an open file handle.
235    /// The `flags` argument can control how dynamic libraries are loaded.
236    /// # Examples
237    /// ```no_run
238    /// # use std::fs::File;
239    /// # use dlopen_rs::{ElfLibrary ,OpenFlags};
240    /// let file = File::open("/path/to/awesome.module").unwrap();
241    /// let lib = ElfLibrary::from_open_file(file, "/path/to/awesome.module", OpenFlags::RTLD_LOCAL).unwrap();
242    /// ```
243    #[cfg(feature = "std")]
244    #[inline]
245    pub fn from_open_file(
246        file: std::fs::File,
247        path: impl AsRef<str>,
248        flags: OpenFlags,
249    ) -> Result<ElfLibrary> {
250        use std::os::fd::IntoRawFd;
251
252        use elf_loader::object;
253        let file = unsafe { object::ElfFile::from_owned_fd(path.as_ref(), file.into_raw_fd()) };
254        from_impl(file, flags)
255    }
256
257    /// Load a elf dynamic library from bytes.
258    /// The `flags` argument can control how dynamic libraries are loaded.
259    /// # Examples
260    ///
261    /// ```no_run
262    /// # use ::dlopen_rs::{ElfLibrary, OpenFlags};
263    /// # use std::path::Path;
264    /// let path = Path::new("/path/to/awesome.module");
265    /// let bytes = std::fs::read(path).unwrap();
266    /// let lib = ElfLibrary::from_binary(&bytes, "/path/to/awesome.module", OpenFlags::RTLD_LOCAL).unwrap();
267    /// ```
268    #[inline]
269    pub fn from_binary(
270        bytes: impl AsRef<[u8]>,
271        path: impl AsRef<str>,
272        flags: OpenFlags,
273    ) -> Result<Self> {
274        let file = ElfBinary::new(path.as_ref(), bytes.as_ref());
275        from_impl(file, flags)
276    }
277
278    /// Load an existing dynamic library using the shortname of the library
279    /// # Examples
280    /// ```no_run
281    /// # use ::dlopen_rs::ElfLibrary;
282    /// let libc = ElfLibrary::load_existing("libc.so.6").unwrap();
283    /// ```
284    pub fn load_existing(shortname: &str) -> Result<Dylib> {
285        MANAGER
286            .read()
287            .all
288            .get(shortname)
289            .filter(|lib| lib.deps().is_some())
290            .map(|lib| lib.get_dylib())
291            .ok_or(find_lib_error(format!("{}: load fail", shortname)))
292    }
293
294    /// Gets the name of the dependent libraries
295    pub fn needed_libs(&self) -> &[&str] {
296        self.dylib.needed_libs()
297    }
298
299    /// Gets the name of the dynamic library.
300    pub fn name(&self) -> &str {
301        self.dylib.name()
302    }
303
304    fn relocate_impl<F>(self, libs: &[Dylib], find: &F) -> Result<Dylib>
305    where
306        F: for<'b> Fn(&'b str) -> Option<*const ()>,
307    {
308        let mut deps = Vec::new();
309        deps.push(unsafe { RelocatedDylib::from_core_component(self.dylib.core_component()) });
310        deps.extend(libs.iter().map(|lib| lib.inner.clone()));
311        let deps = Arc::new(deps.into_boxed_slice());
312        let lazy_scope = create_lazy_scope(&deps, self.dylib.is_lazy());
313        let cur_lib: RelocatedDylib<'static> = unsafe {
314            core::mem::transmute(self.dylib.relocate(
315                deps.iter(),
316                find,
317                deal_unknown,
318                lazy_scope,
319            )?)
320        };
321        if !self.flags.contains(OpenFlags::CUSTOM_NOT_REGISTER) {
322            register(
323                cur_lib.clone(),
324                self.flags,
325                Some(deps.clone()),
326                &mut MANAGER.write(),
327                *DylibState::default().set_relocated(),
328            );
329            Ok(Dylib {
330                inner: cur_lib,
331                flags: self.flags,
332                deps: Some(deps),
333            })
334        } else {
335            Ok(Dylib {
336                inner: cur_lib,
337                flags: self.flags,
338                deps: Some(deps),
339            })
340        }
341    }
342
343    /// Use libraries to relocate the current library.
344    /// # Examples
345    /// ```no_run
346    /// # use ::dlopen_rs::{ElfLibrary, OpenFlags};
347    /// let libc = ElfLibrary::load_existing("libc").unwrap();
348    /// let libgcc = ElfLibrary::load_existing("libgcc").unwrap();
349    /// let lib = ElfLibrary::from_file("/path/to/awesome.module", OpenFlags::RTLD_LOCAL)
350    /// 	.unwrap()
351    /// 	.relocate(&[libgcc, libc]);
352    /// ```
353    #[inline]
354    pub fn relocate(self, libs: impl AsRef<[Dylib]>) -> Result<Dylib> {
355        self.relocate_impl(libs.as_ref(), &|name| builtin::BUILTIN.get(name).copied())
356    }
357
358    /// Use libraries and function closure to relocate the current library.
359    /// # Examples
360    ///
361    /// ```no_run
362    /// # use ::dlopen_rs::{ElfLibrary, OpenFlags};
363    /// # use core::ffi::c_void;
364    /// # use libc::size_t;
365    /// extern "C" fn mymalloc(size: size_t) -> *mut c_void {
366    ///     println!("malloc:{}bytes", size);
367    ///     unsafe { libc::malloc(size) }
368    /// }
369    /// let libc = ElfLibrary::load_existing("libc").unwrap();
370    /// let libgcc = ElfLibrary::load_existing("libgcc").unwrap();
371    /// let lib = ElfLibrary::from_file("/path/to/awesome.module", OpenFlags::RTLD_LOCAL)
372    /// 	.unwrap()
373    /// 	.relocate_with(&[libc, libgcc], |name| {
374    ///         if name == "malloc" {
375    ///	             return Some(mymalloc as _);
376    ///         } else {
377    ///	             return None;
378    ///         }
379    ///     })
380    ///		.unwrap();
381    /// ```
382    /// # Note
383    /// It will use function closure to relocate current lib firstly.
384    #[inline]
385    pub fn relocate_with<F>(mut self, libs: impl AsRef<[Dylib]>, func: F) -> Result<Dylib>
386    where
387        F: for<'b> Fn(&'b str) -> Option<*const ()> + 'static,
388    {
389        type Closure = Box<dyn Fn(&str) -> Option<*const ()> + 'static>;
390
391        self.dylib.user_data_mut().unwrap().insert(
392            CLOSURE,
393            Box::new(
394                Box::new(move |name: &str| func(name).or(builtin::BUILTIN.get(name).copied()))
395                    as Closure,
396            ),
397        );
398        let func_ref: &Closure = unsafe {
399            core::mem::transmute(
400                self.dylib
401                    .user_data()
402                    .get(CLOSURE)
403                    .unwrap()
404                    .downcast_ref::<Closure>()
405                    .unwrap(),
406            )
407        };
408        self.relocate_impl(libs.as_ref(), func_ref)
409    }
410}
411
412/// An relocated dynamic library
413#[derive(Clone)]
414pub struct Dylib {
415    pub(crate) inner: RelocatedDylib<'static>,
416    pub(crate) flags: OpenFlags,
417    pub(crate) deps: Option<Arc<Box<[RelocatedDylib<'static>]>>>,
418}
419
420impl Debug for Dylib {
421    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
422        f.debug_struct("Dylib")
423            .field("inner", &self.inner)
424            .field("flags", &self.flags)
425            .finish()
426    }
427}
428
429impl Dylib {
430    /// Get the name of the dynamic library.
431    #[inline]
432    pub fn name(&self) -> &str {
433        self.inner.name()
434    }
435
436    /// Get the C-style name of the dynamic library.
437    #[inline]
438    pub fn cname(&self) -> &CStr {
439        self.inner.cname()
440    }
441
442    /// Get the base address of the dynamic library.
443    #[inline]
444    pub fn base(&self) -> usize {
445        self.inner.base()
446    }
447
448    /// Get the program headers of the dynamic library.
449    #[inline]
450    pub fn phdrs(&self) -> &[ElfPhdr] {
451        self.inner.phdrs()
452    }
453
454    /// Get the needed libs' name of the elf object.
455    #[inline]
456    pub fn needed_libs(&self) -> &[&str] {
457        self.inner.needed_libs()
458    }
459
460    /// Get a pointer to a function or static variable by symbol name.
461    ///
462    /// The symbol is interpreted as-is; no mangling is done. This means that symbols like `x::y` are
463    /// most likely invalid.
464    ///
465    /// # Safety
466    /// Users of this API must specify the correct type of the function or variable loaded.
467    ///
468    /// # Examples
469    /// ```no_run
470    /// # use dlopen_rs::{Symbol, ElfLibrary ,OpenFlags};
471    /// # let lib = ElfLibrary::dlopen("awesome.so", OpenFlags::RTLD_NOW).unwrap();
472    /// unsafe {
473    ///     let awesome_function: Symbol<unsafe extern fn(f64) -> f64> =
474    ///         lib.get("awesome_function").unwrap();
475    ///     awesome_function(0.42);
476    /// }
477    /// ```
478    /// A static variable may also be loaded and inspected:
479    /// ```no_run
480    /// # use dlopen_rs::{Symbol, ElfLibrary ,OpenFlags};
481    /// # let lib = ElfLibrary::dlopen("awesome.so", OpenFlags::RTLD_NOW).unwrap();
482    /// unsafe {
483    ///     let awesome_variable: Symbol<*mut f64> = lib.get("awesome_variable").unwrap();
484    ///     **awesome_variable = 42.0;
485    /// };
486    /// ```
487    #[inline]
488    pub unsafe fn get<'lib, T>(&'lib self, name: &str) -> Result<Symbol<'lib, T>> {
489        find_symbol(self.deps.as_ref().unwrap(), name)
490    }
491
492    /// Load a versioned symbol from the dynamic library.
493    ///
494    /// # Examples
495    /// ```
496    /// let symbol = unsafe { lib.get_version::<fn()>>("function_name", "1.0").unwrap() };
497    /// ```
498    #[cfg(feature = "version")]
499    #[inline]
500    pub unsafe fn get_version<'lib, T>(
501        &'lib self,
502        name: &str,
503        version: &str,
504    ) -> Result<Symbol<'lib, T>> {
505        unsafe {
506            self.inner
507                .get_version(name, version)
508                .ok_or(find_symbol_error(format!("can not find symbol:{}", name)))
509        }
510    }
511}