dlopen_rs/
dlopen.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
use crate::{find_lib_error, loader::ElfLibrary, register::REGISTER_LIBS, Result};
use alloc::{format, vec::Vec};
use elf_loader::RelocatedDylib;
use spin::Mutex;

static LOCK: Mutex<()> = Mutex::new(());
#[cfg(feature = "std")]
static LD_LIBRARY_PATH: std::sync::OnceLock<Vec<std::path::PathBuf>> = std::sync::OnceLock::new();
#[cfg(feature = "std")]
static LAZY_BIND: std::sync::OnceLock<Option<bool>> = std::sync::OnceLock::new();
#[cfg(feature = "ldso")]
const SYS_LIBS: [&'static str; 4] = ["libc.so", "libgcc_s.so", "libstdc++", "libm"];

impl ElfLibrary {
    /// Load a shared library from a specified path
    ///
    /// # Note
    /// * Please set the `LD_LIBRARY_PATH` environment variable before calling this function.
    /// dlopen-rs will look for dependent dynamic libraries in the paths saved in `LD_LIBRARY_PATH`. In addition, dlopen-rs also looks for dependent dynamic libraries in the registered dynamic library  
    /// * Lazy binding is forcibly turned on when `LD_BIND_NOW = 0`, forcibly turned off when `LD_BIND_NOW = 1`,
    /// and determined by the compilation parameter of the dynamic library itself when `LD_BIND_NOW` environment variable is not set.
    ///
    /// # Example
    /// ```no_run
    /// use std::path::Path;
    /// use dlopen_rs::ELFLibrary;
    ///
    /// let path = Path::new("/path/to/library.so");
    /// let lib = ELFLibrary::dlopen(path).expect("Failed to load library");
    /// ```
    #[cfg(feature = "std")]
    pub fn dlopen(path: impl AsRef<std::ffi::OsStr>) -> Result<RelocatedDylib> {
        let lazy_bind = LAZY_BIND.get_or_init(|| {
            std::env::var("LD_BIND_NOW")
                .ok()
                .map(|val| {
                    val.parse::<u8>()
                        .expect("set LD_BIND_NOW = 0 or set LD_BIND_NOW = 1")
                })
                .map(|val| val == 0)
        });
        // 检查是否是已经加载的库
        if let Some(Some(lib)) = REGISTER_LIBS
            .read()
            .get(path.as_ref().to_str().unwrap())
            .map(|lib| lib.inner.upgrade())
        {
            return Ok(RelocatedDylib { inner: lib });
        };

        let lock = LOCK.lock();
        let lib = ElfLibrary::from_file(path, lazy_bind.clone())?
            .register()
            .dlopen_impl()?;
        drop(lock);
        Ok(lib)
    }

    /// Load a shared library from bytes
    /// # Note
    /// Please set the `LD_LIBRARY_PATH` environment variable before calling this function.
    /// dlopen-rs will look for dependent dynamic libraries in the paths saved in `LD_LIBRARY_PATH`.
    /// In addition, dlopen-rs also looks for dependent dynamic libraries in the registered dynamic library
    pub fn dlopen_from_binary(bytes: &[u8], name: impl AsRef<str>) -> Result<RelocatedDylib> {
        #[cfg(feature = "std")]
        let lazy_bind = LAZY_BIND.get_or_init(|| {
            std::env::var("LD_BIND_NOW")
                .ok()
                .map(|val| {
                    val.parse::<u8>()
                        .expect("set LD_BIND_NOW = 0 or set LD_BIND_NOW = 1")
                })
                .map(|val| val == 0)
        });
        #[cfg(not(feature = "std"))]
        let lazy_bind = None;
        // 检查是否是已经加载的库
        if let Some(Some(lib)) = REGISTER_LIBS
            .read()
            .get(name.as_ref())
            .map(|lib| lib.inner.upgrade())
        {
            return Ok(RelocatedDylib { inner: lib });
        };
        let lock = LOCK.lock();
        let lib = ElfLibrary::from_binary(bytes, name, lazy_bind.clone())?
            .register()
            .dlopen_impl()?;
        drop(lock);
        Ok(lib)
    }

    fn dlopen_impl(self) -> Result<RelocatedDylib> {
        #[cfg(feature = "std")]
        LD_LIBRARY_PATH.get_or_init(|| {
            let ld_library_path = std::env::var("LD_LIBRARY_PATH").expect("env LD_LIBRARY_PATH");
            ld_library_path
                .split(":")
                .map(|str| std::path::PathBuf::try_from(str).unwrap())
                .collect()
        });
        let needed_libs_name = self.needed_libs();
        let mut needed_libs = Vec::new();
        for needed_lib_name in needed_libs_name {
            // 不需要加载其他动态链接器
            if needed_lib_name.contains("ld-") {
                continue;
            }

            let read_lock = REGISTER_LIBS.read();
            if let Some(Some(needed_lib)) = read_lock
                .get(*needed_lib_name)
                .map(|lib| lib.inner.upgrade())
            {
                needed_libs.push(RelocatedDylib { inner: needed_lib });
            } else {
                #[cfg(feature = "std")]
                let needed_lib = {
                    drop(read_lock);
                    let mut needed_lib = None;
                    // 像libc这种系统库需要使用系统的动态链接器加载,前提是开启ldso feature
                    #[cfg(feature = "ldso")]
                    for sys_lib_name in SYS_LIBS {
                        if needed_lib_name.contains(sys_lib_name) {
                            needed_lib = Some(ElfLibrary::sys_load(needed_lib_name)?);
                            break;
                        }
                    }

                    if needed_lib.is_none() {
                        // 按LD_LIBRARY_PATH指定的路径依次寻找
                        for sys_path in LD_LIBRARY_PATH.get().unwrap() {
                            let file_path = sys_path.join(needed_lib_name);
                            match std::fs::File::open(&file_path) {
                                Ok(file) => {
                                    let new_lib = ElfLibrary::from_open_file(
                                        file,
                                        file_path.to_str().unwrap(),
                                        None,
                                    )?
                                    .register();
                                    needed_lib = Some(Self::dlopen_impl(new_lib)?);
                                }
                                Err(_) => continue,
                            }
                        }
                    }
                    needed_lib
                };
                #[cfg(not(feature = "std"))]
                let needed_lib = None;
                if let Some(needed_lib) = needed_lib {
                    needed_libs.push(needed_lib);
                } else {
                    return Err(find_lib_error(format!(
                        "can not find file: {}",
                        needed_lib_name
                    )));
                }
            }
        }
        self.relocate(needed_libs).finish()
    }
}