dlopen_rs/
dlopen.rs

1use crate::{
2    OpenFlags, Result,
3    loader::{Dylib, ElfLibrary, builtin, create_lazy_scope, deal_unknown},
4    register::{DylibState, MANAGER, register},
5};
6use alloc::{borrow::ToOwned, sync::Arc, vec::Vec};
7use core::ffi::{c_char, c_int, c_void};
8use elf_loader::RelocatedDylib;
9
10impl ElfLibrary {
11    /// Load a shared library from a specified path. It is the same as dlopen.
12    ///
13    /// # Example
14    /// ```no_run
15    /// # use std::path::Path;
16    /// # use dlopen_rs::{ElfLibrary, OpenFlags};
17    ///
18    /// let path = Path::new("/path/to/library.so");
19    /// let lib = ElfLibrary::dlopen(path, OpenFlags::RTLD_LOCAL).expect("Failed to load library");
20    /// ```
21    #[cfg(feature = "std")]
22    #[inline]
23    pub fn dlopen(path: impl AsRef<std::ffi::OsStr>, flags: OpenFlags) -> Result<Dylib> {
24        dlopen_impl(path.as_ref().to_str().unwrap(), flags, || {
25            ElfLibrary::from_file(path.as_ref(), flags)
26        })
27    }
28
29    /// Load a shared library from bytes. It is the same as dlopen. However, it can also be used in the no_std environment,
30    /// and it will look for dependent libraries in those manually opened dynamic libraries.
31    #[inline]
32    pub fn dlopen_from_binary(
33        bytes: &[u8],
34        path: impl AsRef<str>,
35        flags: OpenFlags,
36    ) -> Result<Dylib> {
37        dlopen_impl(path.as_ref(), flags, || {
38            ElfLibrary::from_binary(bytes, path.as_ref(), flags)
39        })
40    }
41}
42
43struct Recycler {
44    is_recycler: bool,
45    old_all_len: usize,
46    old_global_len: usize,
47}
48
49impl Drop for Recycler {
50    fn drop(&mut self) {
51        if self.is_recycler {
52            log::debug!("Destroying newly added dynamic libraries from the global");
53            let mut lock = MANAGER.write();
54            lock.all.truncate(self.old_all_len);
55            lock.global.truncate(self.old_global_len);
56        }
57    }
58}
59
60fn dlopen_impl(path: &str, flags: OpenFlags, f: impl Fn() -> Result<ElfLibrary>) -> Result<Dylib> {
61    let shortname = path.split('/').last().unwrap();
62    log::info!("dlopen: Try to open [{}] with [{:?}] ", path, flags);
63    let reader = MANAGER.read();
64    // 新加载的动态库
65    let mut new_libs = Vec::new();
66    let core = if flags.contains(OpenFlags::CUSTOM_NOT_REGISTER) {
67        let lib = f()?;
68        let core = lib.dylib.core_component().clone();
69        new_libs.push(Some(lib));
70        unsafe { RelocatedDylib::from_core_component(core) }
71    } else {
72        // 检查是否是已经加载的库
73        if let Some(lib) = reader.all.get(shortname) {
74            if lib.deps().is_some()
75                && !flags
76                    .difference(lib.flags())
77                    .contains(OpenFlags::RTLD_GLOBAL)
78            {
79                return Ok(lib.get_dylib());
80            }
81            lib.relocated_dylib()
82        } else {
83            let lib = f()?;
84            let core = lib.dylib.core_component().clone();
85            new_libs.push(Some(lib));
86            unsafe { RelocatedDylib::from_core_component(core) }
87        }
88    };
89
90    drop(reader);
91
92    let mut recycler = Recycler {
93        is_recycler: true,
94        old_all_len: usize::MAX,
95        old_global_len: usize::MAX,
96    };
97
98    // 用于保存所有的依赖库
99    let mut dep_libs = Vec::new();
100    let mut cur_pos = 0;
101    dep_libs.push(core.clone());
102    let mut lock = MANAGER.write();
103    recycler.old_all_len = lock.all.len();
104    recycler.old_global_len = lock.global.len();
105
106    register(core, flags, None, &mut lock, DylibState::default());
107
108    #[cfg(feature = "std")]
109    let mut cur_newlib_pos = 0;
110    // 广度优先搜索,这是规范的要求,这个循环里会加载所有需要的动态库,无论是直接依赖还是间接依赖的
111    while cur_pos < dep_libs.len() {
112        let lib_names: &[&str] = unsafe { core::mem::transmute(dep_libs[cur_pos].needed_libs()) };
113        #[cfg(feature = "std")]
114        let mut cur_rpath = None;
115        for lib_name in lib_names {
116            if let Some(lib) = lock.all.get_mut(*lib_name) {
117                if !lib.state.is_used() {
118                    lib.state.set_used();
119                    dep_libs.push(lib.relocated_dylib());
120                    log::debug!("Use an existing dylib: [{}]", lib.shortname());
121                    if flags
122                        .difference(lib.flags())
123                        .contains(OpenFlags::RTLD_GLOBAL)
124                    {
125                        let shortname = lib.shortname().to_owned();
126                        log::debug!(
127                            "Trying to update a library. Name: [{}] Old flags:[{:?}] New flags:[{:?}]",
128                            shortname,
129                            lib.flags(),
130                            flags
131                        );
132                        lib.set_flags(flags);
133                        let core = lib.relocated_dylib();
134                        lock.global.insert(shortname, core);
135                    }
136                }
137                continue;
138            }
139
140            #[cfg(feature = "std")]
141            {
142                let rpath = if let Some(rpath) = &cur_rpath {
143                    rpath
144                } else {
145                    let parent_lib = new_libs[cur_newlib_pos].as_ref().unwrap();
146                    cur_rpath = Some(
147                        parent_lib
148                            .dylib
149                            .rpath()
150                            .map(|rpath| imp::fixup_rpath(parent_lib.name(), rpath))
151                            .unwrap_or(Box::new([])),
152                    );
153                    cur_newlib_pos += 1;
154                    unsafe { cur_rpath.as_ref().unwrap_unchecked() }
155                };
156
157                imp::find_library(rpath, lib_name, |file, file_path| {
158                    let new_lib =
159                        ElfLibrary::from_open_file(file, file_path.to_str().unwrap(), flags)?;
160                    let inner = new_lib.dylib.core_component().clone();
161                    register(
162                        unsafe { RelocatedDylib::from_core_component(inner.clone()) },
163                        flags,
164                        None,
165                        &mut lock,
166                        *DylibState::default()
167                            .set_used()
168                            .set_new_idx(new_libs.len() as _),
169                    );
170                    dep_libs.push(unsafe { RelocatedDylib::from_core_component(inner) });
171                    new_libs.push(Some(new_lib));
172                    Ok(())
173                })?;
174            }
175
176            #[cfg(not(feature = "std"))]
177            return Err(crate::find_lib_error(alloc::format!(
178                "can not find file: {}",
179                lib_name
180            )));
181        }
182        cur_pos += 1;
183    }
184
185    #[derive(Clone, Copy)]
186    struct Item {
187        idx: usize,
188        next: usize,
189    }
190    // 保存new_libs的索引
191    let mut stack = Vec::new();
192    stack.push(Item { idx: 0, next: 0 });
193    // 记录新加载的动态库进行重定位的顺序
194    let mut order = Vec::new();
195
196    'start: while let Some(mut item) = stack.pop() {
197        let names = new_libs[item.idx].as_ref().unwrap().needed_libs();
198        for name in names.iter().skip(item.next) {
199            let lib = lock.all.get_mut(*name).unwrap();
200            lib.state.set_unused();
201            // 判断当前依赖库是否是新加载的,如果不是则跳过本轮操作,因为它已经被重定位过了
202            let Some(idx) = lib.state.get_new_idx() else {
203                continue;
204            };
205            // 将当前依赖库的状态设置为已经重定位
206            lib.state.set_relocated();
207            item.next += 1;
208            stack.push(item);
209            stack.push(Item {
210                idx: idx as usize,
211                next: 0,
212            });
213            continue 'start;
214        }
215        order.push(item.idx);
216    }
217
218    let read_lock = lock.downgrade();
219    for idx in order {
220        let lib = core::mem::take(&mut new_libs[idx]).unwrap();
221        let lazy_scope = create_lazy_scope(&dep_libs, lib.dylib.is_lazy());
222        log::debug!("Relocating dylib [{}]", lib.name());
223        let iter = read_lock.global.values().chain(dep_libs.iter());
224        lib.dylib.relocate(
225            iter,
226            &|name| builtin::BUILTIN.get(name).copied(),
227            deal_unknown,
228            lazy_scope,
229        )?;
230    }
231    drop(read_lock);
232    let deps = Arc::new(dep_libs.into_boxed_slice());
233    if !flags.contains(OpenFlags::CUSTOM_NOT_REGISTER) {
234        recycler.is_recycler = false;
235    }
236    let core = deps[0].clone();
237
238    let res = Dylib {
239        inner: core.clone(),
240        flags,
241        deps: Some(deps.clone()),
242    };
243    //重新注册因为更新了deps
244    register(
245        core,
246        flags,
247        Some(deps),
248        &mut MANAGER.write(),
249        *DylibState::default().set_relocated(),
250    );
251    Ok(res)
252}
253
254#[cfg(feature = "std")]
255pub mod imp {
256    use crate::{Result, find_lib_error};
257    use core::str::FromStr;
258    use dynamic_loader_cache::{Cache as LdCache, Result as LdResult};
259    use spin::Lazy;
260    use std::path::PathBuf;
261
262    static LD_LIBRARY_PATH: Lazy<Box<[PathBuf]>> = Lazy::new(|| {
263        let library_path = std::env::var("LD_LIBRARY_PATH").unwrap_or(String::new());
264        deal_path(&library_path)
265    });
266    static DEFAULT_PATH: spin::Lazy<Box<[PathBuf]>> = Lazy::new(|| unsafe {
267        vec![
268            PathBuf::from_str("/lib").unwrap_unchecked(),
269            PathBuf::from_str("/usr/lib").unwrap_unchecked(),
270        ]
271        .into_boxed_slice()
272    });
273    static LD_CACHE: Lazy<Box<[PathBuf]>> = Lazy::new(|| {
274        build_ld_cache().unwrap_or_else(|err| {
275            log::warn!("Build ld cache failed: {}", err);
276            Box::new([])
277        })
278    });
279
280    #[inline]
281    fn build_ld_cache() -> LdResult<Box<[PathBuf]>> {
282        use std::collections::HashSet;
283
284        let cache = LdCache::load()?;
285        let unique_ld_foders = cache
286            .iter()?
287            .filter_map(LdResult::ok)
288            .map(|entry| {
289                // Since the `full_path` is always a file, we can always unwrap it
290                entry.full_path.parent().unwrap().to_owned()
291            })
292            .collect::<HashSet<_>>();
293        Ok(Vec::from_iter(unique_ld_foders).into_boxed_slice())
294    }
295
296    #[inline]
297    pub(crate) fn fixup_rpath(lib_path: &str, rpath: &str) -> Box<[PathBuf]> {
298        if !rpath.contains('$') {
299            return deal_path(rpath);
300        }
301        for s in rpath.split('$').skip(1) {
302            if !s.starts_with("ORIGIN") && !s.starts_with("{ORIGIN}") {
303                log::warn!("DT_RUNPATH format is incorrect: [{}]", rpath);
304                return Box::new([]);
305            }
306        }
307        let dir = if let Some((path, _)) = lib_path.rsplit_once('/') {
308            path
309        } else {
310            "."
311        };
312        deal_path(&rpath.to_string().replace("$ORIGIN", dir))
313    }
314
315    #[inline]
316    fn deal_path(s: &str) -> Box<[PathBuf]> {
317        s.split(":")
318            .map(|str| std::path::PathBuf::try_from(str).unwrap())
319            .collect()
320    }
321
322    #[inline]
323    pub(crate) fn find_library(
324        cur_rpath: &Box<[PathBuf]>,
325        lib_name: &str,
326        mut f: impl FnMut(std::fs::File, &std::path::PathBuf) -> Result<()>,
327    ) -> Result<()> {
328        // Search order: DT_RPATH(deprecated) -> LD_LIBRARY_PATH -> DT_RUNPATH -> /etc/ld.so.cache -> /lib:/usr/lib.
329        let search_paths = LD_LIBRARY_PATH
330            .iter()
331            .chain(cur_rpath.iter())
332            .chain(LD_CACHE.iter())
333            .chain(DEFAULT_PATH.iter());
334
335        for path in search_paths {
336            let file_path = path.join(lib_name);
337            log::trace!("Try to open dependency shared object: [{:?}]", file_path);
338            if let Ok(file) = std::fs::File::open(&file_path) {
339                match f(file, &file_path) {
340                    Ok(_) => return Ok(()),
341                    Err(err) => {
342                        log::debug!("Cannot load dylib: [{:?}] reason: [{:?}]", file_path, err)
343                    }
344                }
345            }
346        }
347        Err(find_lib_error(format!("can not find file: {}", lib_name)))
348    }
349}
350
351#[allow(unused_variables)]
352/// It is the same as `dlopen`.
353pub unsafe extern "C" fn dlopen(filename: *const c_char, flags: c_int) -> *const c_void {
354    let mut lib = if filename.is_null() {
355        MANAGER.read().all.get_index(0).unwrap().1.get_dylib()
356    } else {
357        #[cfg(feature = "std")]
358        {
359            let flags = OpenFlags::from_bits_retain(flags as _);
360            let filename = unsafe { core::ffi::CStr::from_ptr(filename) };
361            let path = filename.to_str().unwrap();
362            if let Ok(lib) = ElfLibrary::dlopen(path, flags) {
363                lib
364            } else {
365                return core::ptr::null();
366            }
367        }
368        #[cfg(not(feature = "std"))]
369        return core::ptr::null();
370    };
371    Arc::into_raw(core::mem::take(&mut lib.deps).unwrap()) as _
372}