Skip to main content

dlopen_rs/api/
dlopen.rs

1use crate::{
2    OpenFlags, Result,
3    core_impl::{
4        loader::{DylibExt, ElfDylib, ElfLibrary, LoadResult, LoadedDylib, create_lazy_scope},
5        register::{DylibState, GlobalDylib, MANAGER, Manager, register},
6        traits::AsFilename,
7    },
8    error::find_lib_error,
9    utils::ld_cache::LdCache,
10};
11use alloc::{
12    borrow::ToOwned,
13    boxed::Box,
14    format,
15    string::{String, ToString},
16    sync::Arc,
17    vec,
18    vec::Vec,
19};
20use core::ffi::{CStr, c_char, c_int, c_void};
21use spin::{Lazy, RwLockWriteGuard};
22
23#[derive(Debug, PartialEq, Eq, Hash)]
24pub(crate) struct ElfPath {
25    path: String,
26}
27
28impl ElfPath {
29    pub(crate) fn from_str(path: &str) -> Result<Self> {
30        Ok(ElfPath {
31            path: path.to_owned(),
32        })
33    }
34
35    /// Appends a file name to the path, ensuring a separator is present.
36    fn join(&self, file_name: &str) -> ElfPath {
37        let mut new = self.path.clone();
38        if !new.is_empty() && !new.ends_with('/') {
39            new.push('/');
40        }
41        new.push_str(file_name);
42        ElfPath { path: new }
43    }
44
45    fn as_str(&self) -> &str {
46        &self.path
47    }
48}
49
50fn get_env(name: &str) -> Option<&'static str> {
51    unsafe {
52        let mut cur = crate::core_impl::types::ENVP;
53        if cur.is_null() {
54            return None;
55        }
56        while !(*cur).is_null() {
57            if let Ok(env) = CStr::from_ptr(*cur).to_str() {
58                if let Some((k, v)) = env.split_once('=') {
59                    if k == name {
60                        return Some(v);
61                    }
62                }
63            }
64            cur = cur.add(1);
65        }
66    }
67    None
68}
69
70impl ElfLibrary {
71    /// Get the main executable as an `ElfLibrary`. It is the same as `dlopen(NULL, RTLD_NOW)`.
72    pub fn this() -> ElfLibrary {
73        let reader = crate::lock_read!(MANAGER);
74        reader
75            .get_index(0)
76            .expect("Main executable must be initialized")
77            .1
78            .get_lib()
79    }
80
81    /// Load a shared library from a specified path. It is the same as dlopen.
82    ///
83    /// # Example
84    /// ```no_run
85    /// # use dlopen_rs::{ElfLibrary, OpenFlags};
86    ///
87    /// let path = "/path/to/library.so";
88    /// let lib = ElfLibrary::dlopen(path, OpenFlags::RTLD_LOCAL).expect("Failed to load library");
89    /// ```
90    pub fn dlopen(path: impl AsFilename, flags: OpenFlags) -> Result<ElfLibrary> {
91        dlopen_impl(path.as_filename(), flags, None)
92    }
93
94    /// Load a shared library from bytes. It is the same as dlopen. However, it can also be used in the no_std environment,
95    /// and it will look for dependent libraries in those manually opened dynamic libraries.
96    pub fn dlopen_from_binary(
97        bytes: &[u8],
98        path: impl AsFilename,
99        flags: OpenFlags,
100    ) -> Result<ElfLibrary> {
101        dlopen_impl(path.as_filename(), flags, Some(bytes))
102    }
103}
104
105/// The context for a `dlopen` operation.
106///
107/// Manages the acquisition of the global lock, tracking of newly loaded libraries,
108/// and handling resource cleanup if the operation fails.
109struct OpenContext<'a> {
110    /// The write lock guard for the global library manager.
111    /// Can be temporarily dropped to avoid deadlocks during relocation.
112    lock: Option<RwLockWriteGuard<'a, Manager>>,
113    /// Metadata needed for each newly loaded library in the current operation.
114    new_libs: Vec<Option<ElfDylib>>,
115    /// Names of libraries that were added to the global registry in this operation.
116    added_names: Vec<String>,
117    /// The flattened set of all dependencies involved in this load operation.
118    dep_libs: Vec<LoadedDylib>,
119    /// Loading flags for this operation.
120    flags: OpenFlags,
121    /// Indicates if the operation was successfully committed.
122    committed: bool,
123}
124
125impl<'a> Drop for OpenContext<'a> {
126    fn drop(&mut self) {
127        // If not committed, roll back changes to the global registry.
128        if !self.committed {
129            log::debug!("Destroying newly added dynamic libraries from the global");
130            let mut lock = self
131                .lock
132                .take()
133                .unwrap_or_else(|| crate::lock_write!(MANAGER));
134            for name in &self.added_names {
135                lock.remove(name);
136            }
137        }
138    }
139}
140
141impl<'a> OpenContext<'a> {
142    fn new(mut flags: OpenFlags) -> Self {
143        if get_env("LD_BIND_NOW").is_some() {
144            flags |= OpenFlags::RTLD_NOW;
145        }
146        let lock = crate::lock_write!(MANAGER);
147        Self {
148            lock: Some(lock),
149            new_libs: Vec::new(),
150            added_names: Vec::new(),
151            dep_libs: Vec::new(),
152            flags,
153            committed: false,
154        }
155    }
156
157    fn try_existing(&mut self, path: &str) -> Option<ElfLibrary> {
158        let shortname = path.rsplit_once('/').map_or(path, |(_, name)| name);
159        if let Some(lib) = self.wait_for_library(shortname) {
160            let elf_lib = lib.get_lib();
161            log::info!("dlopen: Found existing library [{}]", path);
162            self.lock
163                .as_mut()
164                .expect("Lock must be held")
165                .promote(shortname, self.flags);
166            self.committed = true;
167            return Some(elf_lib);
168        }
169        None
170    }
171
172    fn wait_for_library(&mut self, shortname: &str) -> Option<GlobalDylib> {
173        loop {
174            let entry = {
175                let lock = self.lock.as_ref().expect("Lock must be held");
176                lock.get(shortname).cloned()
177            };
178
179            match entry {
180                Some(lib) if lib.state.is_relocated() => return Some(lib),
181                Some(lib) if self.added_names.iter().any(|n| n == shortname) => {
182                    // It's a library being loaded by the current thread in this dlopen session.
183                    // We must not wait, otherwise we deadlock.
184                    return Some(lib);
185                }
186                Some(_) => {
187                    // It's being loaded or relocated by another thread
188                    drop(self.lock.take());
189                    core::hint::spin_loop();
190                    self.lock = Some(crate::lock_write!(MANAGER));
191                }
192                None => return None,
193            }
194        }
195    }
196
197    fn register_new(&mut self, lib: ElfDylib) -> LoadedDylib {
198        let core = lib.core();
199        let relocated = unsafe { LoadedDylib::from_core(core.clone()) };
200        let new_idx = self.new_libs.len();
201
202        let shortname = relocated.shortname().to_owned();
203        register(
204            relocated.clone(),
205            self.flags,
206            self.lock.as_mut().expect("Lock must be held"),
207            *DylibState::default().set_new_idx(new_idx as _),
208        );
209
210        self.dep_libs.push(relocated.clone());
211        self.added_names.push(shortname);
212        self.new_libs.push(Some(lib));
213
214        relocated
215    }
216
217    fn try_use_existing(&mut self, shortname: &str) -> bool {
218        // If it's already in our current dependency chain, we don't need to wait or promote.
219        // It might be one of our 'added_names' (newly loaded) or an existing one we already found.
220        if self.dep_libs.iter().any(|d| d.shortname() == shortname) {
221            return true;
222        }
223
224        if let Some(lib) = self.wait_for_library(shortname) {
225            // Found an existing library from a PREVIOUS dlopen (already relocated)
226            // or one being loaded by another thread (we waited for it).
227            self.dep_libs.push(lib.dylib());
228            log::debug!("Use an existing dylib: [{}]", lib.shortname());
229            self.lock
230                .as_mut()
231                .expect("Lock must be held")
232                .promote(shortname, self.flags);
233            return true;
234        }
235        false
236    }
237
238    fn load_and_register(
239        &mut self,
240        p: &ElfPath,
241        bytes: Option<&[u8]>,
242    ) -> Result<Option<Vec<String>>> {
243        match ElfLibrary::load(p.as_str(), bytes)? {
244            LoadResult::Dylib(lib) => {
245                self.register_new(lib);
246                Ok(None)
247            }
248            LoadResult::Script(libs) => Ok(Some(libs)),
249        }
250    }
251
252    fn load_deps(&mut self) -> Result<()> {
253        let mut cur_pos = 0;
254        while cur_pos < self.dep_libs.len() {
255            let lib_names = self.dep_libs[cur_pos].needed_libs().to_vec();
256            let mut cur_paths: Option<(Box<[ElfPath]>, Box<[ElfPath]>)> = None;
257
258            // Should we look up RPATH/RUNPATH? Only if the current parent is a NEW library.
259            let parent_new_idx = {
260                let lock = self.lock.as_mut().expect("Lock must be held");
261                lock.get(self.dep_libs[cur_pos].shortname())
262                    .expect("Library must be registered")
263                    .state
264                    .get_new_idx()
265                    .map(|idx| idx as usize)
266            };
267
268            for lib_name in lib_names {
269                let (rpath, runpath): (&[ElfPath], &[ElfPath]) = if let Some((r, ru)) = &cur_paths {
270                    (&**r, &**ru)
271                } else if let Some(idx) = parent_new_idx {
272                    let parent_lib: &ElfDylib = self.new_libs[idx]
273                        .as_ref()
274                        .expect("New library must be available");
275                    let new_rpath = parent_lib
276                        .rpath()
277                        .map(|r| fixup_rpath(parent_lib.name(), r))
278                        .unwrap_or_default();
279                    let new_runpath = parent_lib
280                        .runpath()
281                        .map(|r| fixup_rpath(parent_lib.name(), r))
282                        .unwrap_or_default();
283                    cur_paths = Some((new_rpath, new_runpath));
284                    let (r, ru) = unsafe { cur_paths.as_ref().unwrap_unchecked() };
285                    (&**r, &**ru)
286                } else {
287                    (&[], &[])
288                };
289
290                self.find_library(rpath, runpath, &lib_name, None)?;
291            }
292            cur_pos += 1;
293        }
294        Ok(())
295    }
296
297    fn compute_order(&mut self) -> Vec<usize> {
298        if self.new_libs.is_empty() {
299            return Vec::new();
300        }
301
302        #[derive(Clone, Copy)]
303        struct Item {
304            idx: usize,
305            next: usize,
306        }
307        // Start from the root library if it's new (index 0).
308        // If the root is existing, we shouldn't be here unless we support partial new deps,
309        // which isn't fully supported by this topological sort rooting strategy.
310        // However, for standard dlopen usage, new libs only appear if root is new.
311        let mut stack = vec![Item { idx: 0, next: 0 }];
312        let mut order = Vec::new();
313
314        'start: while let Some(mut item) = stack.pop() {
315            let names = self.new_libs[item.idx]
316                .as_ref()
317                .expect("New library must be available")
318                .needed_libs();
319            for name in names.iter().skip(item.next) {
320                let lib = self
321                    .lock
322                    .as_mut()
323                    .expect("Lock must be held")
324                    .get_mut(*name)
325                    .expect("Library must be registered");
326
327                if let Some(idx) = lib.state.get_new_idx() {
328                    lib.state.set_relocated();
329                    item.next += 1;
330                    stack.push(item);
331                    stack.push(Item {
332                        idx: idx as usize,
333                        next: 0,
334                    });
335                    continue 'start;
336                }
337            }
338            order.push(item.idx);
339        }
340        order
341    }
342
343    fn update_dependency_scopes(&mut self) {
344        log::debug!("Updating dependency scopes for new libraries");
345
346        let lock = self.lock.as_mut().expect("Lock must be held");
347        // Retrieve names of newly loaded libraries.
348        let new_lib_names = self
349            .new_libs
350            .iter()
351            .filter_map(|lib_opt| lib_opt.as_ref())
352            .map(|lib| lib.short_name());
353
354        crate::core_impl::register::update_dependency_scopes(lock, new_lib_names);
355    }
356
357    /// Sets the state of all involved libraries to `RELOCATING`.
358    fn set_relocating(&mut self) {
359        let lock = self.lock.as_mut().expect("Lock must be held");
360        for lib in &self.dep_libs {
361            lock.get_mut(lib.shortname())
362                .expect("Library must be registered")
363                .state
364                .set_relocating();
365        }
366
367        // Release write lock to avoid deadlock in dl_iterate_phdr during relocation
368        drop(self.lock.take());
369    }
370
371    /// Sets the state of all involved libraries to `RELOCATED`.
372    /// Note: This acquires a new write lock as the context's lock might have been dropped.
373    fn set_relocated(&self) {
374        let mut lock = crate::lock_write!(MANAGER);
375        for lib in &self.dep_libs {
376            lock.get_mut(lib.shortname())
377                .expect("Library must be registered")
378                .state
379                .set_relocated();
380        }
381    }
382
383    /// Performs the relocation for all new libraries in the specified order.
384    fn relocate(&mut self, order: &[usize], deps: &Arc<[LoadedDylib]>) -> Result<()> {
385        // Set state to RELOCATING for all deps before dropping lock
386        self.set_relocating();
387
388        let lazy_scope = create_lazy_scope(deps, self.flags);
389        let global_libs = {
390            let lock = crate::lock_read!(MANAGER);
391            lock.global_values().cloned().collect::<Vec<_>>()
392        };
393
394        for &idx in order {
395            let lib = core::mem::take(&mut self.new_libs[idx]).expect("Library missing");
396            log::debug!("Relocating dylib [{}]", lib.name());
397            let is_lazy = if self.flags.is_now() {
398                false
399            } else if self.flags.is_lazy() {
400                true
401            } else {
402                lib.is_lazy()
403            };
404
405            let scope = if self.flags.is_deepbind() {
406                deps.iter().chain(global_libs.iter())
407            } else {
408                global_libs.iter().chain(deps.iter())
409            };
410
411            lib.relocator()
412                .scope(scope.cloned())
413                .lazy(is_lazy)
414                .lazy_scope(lazy_scope.clone())
415                .relocate()?;
416        }
417
418        // Set state to RELOCATED
419        self.set_relocated();
420
421        Ok(())
422    }
423
424    /// Returns an `Arc` slice of all dependencies.
425    fn get_deps(&self) -> Arc<[LoadedDylib]> {
426        let lock = self.lock.as_ref().expect("Lock must be held");
427        let shortname = self.dep_libs[0].shortname();
428        lock.get(shortname)
429            .expect("Root library must be registered")
430            .deps
431            .clone()
432            .expect("Dependency scope must be computed")
433    }
434
435    /// Finalizes the operation and returns the `ElfLibrary`.
436    fn finish(mut self, deps: Arc<[LoadedDylib]>) -> ElfLibrary {
437        self.committed = true;
438        let core = deps[0].clone();
439        ElfLibrary {
440            inner: core,
441            deps: Some(deps),
442        }
443    }
444
445    fn load_root(&mut self, path: &str, bytes: Option<&[u8]>) -> Result<Option<ElfLibrary>> {
446        if let Some(lib) = self.try_existing(path) {
447            return Ok(Some(lib));
448        }
449
450        if self.flags.is_noload() {
451            return Err(find_lib_error(format!("can not find file: {}", path)));
452        }
453
454        self.find_library(&[], &[], path, bytes)?;
455        Ok(None)
456    }
457
458    fn find_library(
459        &mut self,
460        rpath: &[ElfPath],
461        runpath: &[ElfPath],
462        lib_name: &str,
463        bytes: Option<&[u8]>,
464    ) -> Result<()> {
465        let shortname = lib_name.rsplit_once('/').map_or(lib_name, |(_, name)| name);
466        if self.try_use_existing(shortname) {
467            return Ok(());
468        }
469
470        // 1. Absolute or relative path (contains '/')
471        if lib_name.contains('/') {
472            if let Ok(path) = ElfPath::from_str(lib_name) {
473                return self.try_load_internal(rpath, runpath, &path, bytes);
474            }
475        }
476
477        // Search order: DT_RPATH -> LD_LIBRARY_PATH -> DT_RUNPATH -> LD_CACHE -> DEFAULT_PATH
478        let rpath_dirs = if runpath.is_empty() { rpath } else { &[] };
479        for dir in rpath_dirs
480            .iter()
481            .chain(LD_LIBRARY_PATH.iter())
482            .chain(runpath.iter())
483        {
484            if self
485                .try_load_internal(rpath, runpath, &dir.join(lib_name), bytes)
486                .is_ok()
487            {
488                return Ok(());
489            }
490        }
491
492        // 4. LD_CACHE
493        if let Some(cache) = &*LD_CACHE {
494            if let Some(path) = cache.lookup(lib_name) {
495                if let Ok(path) = ElfPath::from_str(&path) {
496                    if self.try_load_internal(rpath, runpath, &path, bytes).is_ok() {
497                        return Ok(());
498                    }
499                }
500            }
501        }
502
503        // 5. DEFAULT_PATH
504        for dir in DEFAULT_PATH.iter() {
505            if self
506                .try_load_internal(rpath, runpath, &dir.join(lib_name), bytes)
507                .is_ok()
508            {
509                return Ok(());
510            }
511        }
512
513        Err(find_lib_error(format!(
514            "can not find library: {}",
515            lib_name
516        )))
517    }
518
519    fn try_load_internal(
520        &mut self,
521        rpath: &[ElfPath],
522        runpath: &[ElfPath],
523        path: &ElfPath,
524        bytes: Option<&[u8]>,
525    ) -> Result<()> {
526        let Some(libs) = self.load_and_register(path, bytes)? else {
527            return Ok(());
528        };
529        for lib in libs {
530            self.find_library(rpath, runpath, &lib, None)?;
531        }
532        Ok(())
533    }
534}
535
536fn dlopen_impl(path: &str, flags: OpenFlags, bytes: Option<&[u8]>) -> Result<ElfLibrary> {
537    let mut ctx = OpenContext::new(flags);
538
539    // 1. Initial Check / Load
540    log::info!("dlopen: Try to open [{}] with [{:?}] ", path, ctx.flags);
541
542    if let Some(lib) = ctx.load_root(path, bytes)? {
543        return Ok(lib);
544    }
545
546    // 2. Resolve Dependencies
547    ctx.load_deps()?;
548
549    // 3. Update Dependency Scopes
550    ctx.update_dependency_scopes();
551
552    // 4. Relocation Order
553    let order = ctx.compute_order();
554
555    // 5. Relocation
556    let deps = ctx.get_deps();
557    ctx.relocate(&order, &deps)?;
558
559    // 6. Finalize
560    Ok(ctx.finish(deps))
561}
562
563static LD_LIBRARY_PATH: Lazy<Box<[ElfPath]>> = Lazy::new(|| {
564    if let Some(path) = get_env("LD_LIBRARY_PATH") {
565        parse_path_list(path)
566    } else {
567        Box::new([])
568    }
569});
570static DEFAULT_PATH: Lazy<Box<[ElfPath]>> = Lazy::new(|| unsafe {
571    let v = vec![
572        ElfPath::from_str("/lib").unwrap_unchecked(),
573        ElfPath::from_str("/usr/lib").unwrap_unchecked(),
574        ElfPath::from_str("/lib64").unwrap_unchecked(),
575        ElfPath::from_str("/usr/lib64").unwrap_unchecked(),
576    ];
577    v.into_boxed_slice()
578});
579static LD_CACHE: Lazy<Option<LdCache>> = Lazy::new(|| LdCache::new().ok());
580
581#[inline]
582fn fixup_rpath(lib_path: &str, rpath: &str) -> Box<[ElfPath]> {
583    if !rpath.contains('$') {
584        return parse_path_list(rpath);
585    }
586    for s in rpath.split('$').skip(1) {
587        if !s.starts_with("ORIGIN") && !s.starts_with("{ORIGIN}") {
588            log::warn!("DT_RUNPATH format is incorrect: [{}]", rpath);
589            return Box::new([]);
590        }
591    }
592    let dir = if let Some((path, _)) = lib_path.rsplit_once('/') {
593        path
594    } else {
595        "."
596    };
597    parse_path_list(&rpath.to_string().replace("$ORIGIN", dir))
598}
599
600/// Parses a colon-separated list of paths into a boxed slice of ElfPath.
601#[inline]
602fn parse_path_list(s: &str) -> Box<[ElfPath]> {
603    s.split(':')
604        .filter(|str| !str.is_empty())
605        .map(|str| ElfPath::from_str(str).unwrap())
606        .collect()
607}
608
609/// # Safety
610/// It is the same as `dlopen`.
611#[unsafe(no_mangle)]
612pub unsafe extern "C" fn dlopen(filename: *const c_char, flags: c_int) -> *const c_void {
613    let lib = if filename.is_null() {
614        ElfLibrary::this()
615    } else {
616        let flags = OpenFlags::from_bits_retain(flags as _);
617        let filename = unsafe { CStr::from_ptr(filename) };
618        let Ok(path) = filename.to_str() else {
619            return core::ptr::null();
620        };
621        if let Ok(lib) = ElfLibrary::dlopen(path, flags) {
622            lib
623        } else {
624            return core::ptr::null();
625        }
626    };
627    Box::into_raw(Box::new(lib)) as _
628}