dlopen_rs/
dlopen.rs

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