├── .gitignore ├── cdylib ├── Cargo.toml └── src │ └── lib.rs ├── example-dylib ├── Cargo.toml └── src │ └── lib.rs ├── benches ├── dlopen_llvm.rs ├── dlopen.rs ├── dlsym.rs └── dl_iterate_phdr.rs ├── check.sh ├── examples ├── preload.rs └── dlopen.rs ├── ci └── run.sh ├── src ├── abi.rs ├── dlsym.rs ├── find.rs ├── arch.rs ├── dladdr.rs ├── debug.rs ├── dl_iterate_phdr.rs ├── cache.rs ├── lib.rs ├── register.rs ├── init.rs ├── loader.rs ├── dlopen.rs └── tls.rs ├── .github └── workflows │ └── rust.yml ├── TroubleshootingGdb.md ├── Cargo.toml ├── README-zh_cn.md ├── tests └── all.rs ├── README.md ├── scripts └── reload_sos_on_r_brk.py ├── LICENSE └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .vscode -------------------------------------------------------------------------------- /cdylib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cdylib" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies.dlopen-rs] 7 | path = "../" 8 | default-features = false 9 | features = ["fs", "debug"] 10 | 11 | [dependencies] 12 | ctor = "0.4.1" 13 | env_logger = "0.11.6" 14 | 15 | [lib] 16 | name = "dlopen" 17 | crate-type = ["cdylib"] 18 | -------------------------------------------------------------------------------- /example-dylib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example_dylib" 3 | version = "0.1.0" 4 | authors = ["wzhao <1207410841@qq.com>"] 5 | description = "Example dynamic link library for executing tests of libraries that load and operate on dynamic link libraries" 6 | license = "Apache-2.0" 7 | edition = "2021" 8 | 9 | [lib] 10 | name = "example" 11 | crate-type = ["cdylib"] 12 | -------------------------------------------------------------------------------- /cdylib/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::{c_char, c_int, c_void}, 3 | ptr::null, 4 | }; 5 | 6 | #[ctor::ctor] 7 | fn init() { 8 | env_logger::init(); 9 | dlopen_rs::init(); 10 | } 11 | 12 | #[no_mangle] 13 | unsafe extern "C" fn dlinfo(_handle: *const c_void, _request: c_int, _info: *mut c_void) { 14 | todo!() 15 | } 16 | 17 | #[no_mangle] 18 | unsafe extern "C" fn dlerror() -> *const c_char { 19 | null() 20 | } 21 | -------------------------------------------------------------------------------- /benches/dlopen_llvm.rs: -------------------------------------------------------------------------------- 1 | use criterion::{Criterion, criterion_group, criterion_main}; 2 | use dlopen_rs::{ElfLibrary, OpenFlags}; 3 | 4 | fn load(c: &mut Criterion) { 5 | dlopen_rs::init(); 6 | let path = "/usr/lib/llvm-18/lib/libLLVM-18.so"; 7 | c.bench_function("dlopen-rs:dlopen", |b| { 8 | b.iter(|| { 9 | let _libexample = ElfLibrary::dlopen(path, OpenFlags::RTLD_GLOBAL).unwrap(); 10 | }) 11 | }); 12 | } 13 | 14 | criterion_group!(benches, load); 15 | criterion_main!(benches); 16 | -------------------------------------------------------------------------------- /check.sh: -------------------------------------------------------------------------------- 1 | cargo check -p dlopen-rs --no-default-features --features="" 2 | cargo check -p dlopen-rs --no-default-features --features="std" 3 | cargo check -p dlopen-rs --no-default-features --features="tls" 4 | # 检查其余的feature 5 | cargo check -p dlopen-rs --no-default-features --features="debug" 6 | cargo check -p dlopen-rs --no-default-features --features="version" 7 | # 检查常规组合 8 | cargo check -p dlopen-rs --no-default-features --features="mmap,tls,debug" 9 | cargo check -p dlopen-rs --no-default-features --features="tls,debug,version" -------------------------------------------------------------------------------- /benches/dlopen.rs: -------------------------------------------------------------------------------- 1 | use criterion::{Criterion, criterion_group, criterion_main}; 2 | use dlopen_rs::{ElfLibrary, OpenFlags}; 3 | use libloading::Library; 4 | 5 | fn load(c: &mut Criterion) { 6 | dlopen_rs::init(); 7 | let path = "./target/release/libexample.so"; 8 | c.bench_function("dlopen-rs:dlopen", |b| { 9 | b.iter(|| { 10 | let _libexample = ElfLibrary::dlopen(path, OpenFlags::RTLD_GLOBAL).unwrap(); 11 | }) 12 | }); 13 | c.bench_function("libloading:new", |b| { 14 | b.iter(|| { 15 | unsafe { Library::new(path).unwrap() }; 16 | }) 17 | }); 18 | } 19 | 20 | criterion_group!(benches, load); 21 | criterion_main!(benches); 22 | -------------------------------------------------------------------------------- /benches/dlsym.rs: -------------------------------------------------------------------------------- 1 | use criterion::{Criterion, criterion_group, criterion_main}; 2 | use dlopen_rs::{ElfLibrary, OpenFlags}; 3 | use libloading::Library; 4 | 5 | fn get_symbol(c: &mut Criterion) { 6 | dlopen_rs::init(); 7 | let path = "./target/release/libexample.so"; 8 | let lib1 = ElfLibrary::dlopen(path, OpenFlags::CUSTOM_NOT_REGISTER).unwrap(); 9 | let lib2 = unsafe { Library::new(path).unwrap() }; 10 | c.bench_function("dlopen-rs:get", |b| { 11 | b.iter(|| unsafe { lib1.get:: i32>("add").unwrap() }) 12 | }); 13 | c.bench_function("libloading:get", |b| { 14 | b.iter(|| { 15 | unsafe { lib2.get:: i32>("add".as_bytes()).unwrap() }; 16 | }) 17 | }); 18 | } 19 | 20 | criterion_group!(benches, get_symbol); 21 | criterion_main!(benches); 22 | -------------------------------------------------------------------------------- /examples/preload.rs: -------------------------------------------------------------------------------- 1 | use libloading::Library; 2 | 3 | fn main() { 4 | let libexample = unsafe { Library::new("./target/release/libexample.so").unwrap() }; 5 | let add = unsafe { 6 | libexample 7 | .get:: i32>("add".as_bytes()) 8 | .unwrap() 9 | }; 10 | println!("{}", add(1, 1)); 11 | 12 | let print = unsafe { libexample.get::("print".as_bytes()).unwrap() }; 13 | print("dlopen-rs: hello world"); 14 | 15 | let thread_local = unsafe { libexample.get::("thread_local".as_bytes()).unwrap() }; 16 | thread_local(); 17 | 18 | let panic = unsafe { libexample.get::("panic".as_bytes()).unwrap() }; 19 | panic(); 20 | 21 | let backtrace = unsafe { libexample.get::("backtrace".as_bytes()).unwrap() }; 22 | backtrace(); 23 | } 24 | -------------------------------------------------------------------------------- /ci/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -ex 4 | 5 | : "${TARGET?The TARGET environment variable must be set.}" 6 | 7 | if [ "${TARGET}" = "x86_64-unknown-linux-gnu"]; then 8 | CROSS=0 9 | else 10 | CROSS=1 11 | fi 12 | 13 | CARGO=cargo 14 | if [ "${CROSS}" = "1" ]; then 15 | export CARGO_NET_RETRY=5 16 | export CARGO_NET_TIMEOUT=10 17 | 18 | cargo install --locked cross 19 | CARGO=cross 20 | fi 21 | 22 | if [ "${OP}" = "build" ]; then 23 | "${CARGO}" -vv ${OP} --target="${TARGET}" --no-default-features 24 | "${CARGO}" -vv ${OP} --target="${TARGET}" --no-default-features --features "${FEATURES}" 25 | elif [ "${OP}" = "test" ]; then 26 | cargo -vv ${OP} --target="${TARGET}" --no-default-features --features "${FEATURES}" -- --nocapture 27 | else 28 | "${CARGO}" -vv ${OP} --target="${TARGET}" --no-default-features --features "${FEATURES}" 29 | fi 30 | 31 | -------------------------------------------------------------------------------- /benches/dl_iterate_phdr.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | use dlopen_rs::ElfLibrary; 3 | use libc::{dl_iterate_phdr, size_t}; 4 | use std::ptr::null_mut; 5 | 6 | fn iterate_phdr(c: &mut Criterion) { 7 | dlopen_rs::init(); 8 | unsafe extern "C" fn callback( 9 | _info: *mut libc::dl_phdr_info, 10 | _size: size_t, 11 | _data: *mut libc::c_void, 12 | ) -> libc::c_int { 13 | 0 14 | } 15 | 16 | c.bench_function("dlopen-rs:dl_iterate_phdr", |b| { 17 | b.iter(|| ElfLibrary::dl_iterate_phdr(|_info| Ok(()))) 18 | }); 19 | c.bench_function("libc:dl_iterate_phdr", |b| { 20 | b.iter(|| { 21 | unsafe { dl_iterate_phdr(Some(callback), null_mut()) }; 22 | }) 23 | }); 24 | } 25 | 26 | criterion_group!(benches, iterate_phdr); 27 | criterion_main!(benches); 28 | -------------------------------------------------------------------------------- /src/abi.rs: -------------------------------------------------------------------------------- 1 | //! c interface 2 | 3 | use crate::register::MANAGER; 4 | use alloc::boxed::Box; 5 | use alloc::sync::Arc; 6 | use core::ffi::{c_int, c_void}; 7 | use elf_loader::RelocatedDylib; 8 | 9 | pub use crate::dl_iterate_phdr::{CDlPhdrInfo, dl_iterate_phdr}; 10 | pub use crate::dladdr::{CDlinfo, dladdr}; 11 | pub use crate::dlopen::dlopen; 12 | pub use crate::dlsym::dlsym; 13 | 14 | /// # Safety 15 | /// It is the same as `dlclose`. 16 | #[unsafe(no_mangle)] 17 | pub unsafe extern "C" fn dlclose(handle: *const c_void) -> c_int { 18 | let deps = unsafe { Arc::from_raw(handle as *const Box<[RelocatedDylib<'static>]>) }; 19 | let dylib = MANAGER 20 | .read() 21 | .all 22 | .get(deps[0].shortname()) 23 | .unwrap() 24 | .get_dylib(); 25 | drop(deps); 26 | log::info!("dlclose: Closing [{}]", dylib.name()); 27 | 0 28 | } 29 | -------------------------------------------------------------------------------- /example-dylib/src/lib.rs: -------------------------------------------------------------------------------- 1 | //!An example dynamically loadable library. 2 | //! 3 | //! This crate creates a dynamic library that can be used for testing purposes. 4 | 5 | use std::{backtrace::Backtrace, cell::Cell, thread}; 6 | 7 | #[no_mangle] 8 | pub fn panic() { 9 | let res = std::panic::catch_unwind(|| { 10 | panic!("panic!"); 11 | }); 12 | assert!(res.is_err()); 13 | println!("catch panic!") 14 | } 15 | 16 | thread_local! { 17 | static NUM:Cell=Cell::new(0) 18 | } 19 | 20 | #[no_mangle] 21 | pub fn backtrace() { 22 | println!("{}", Backtrace::force_capture()); 23 | } 24 | 25 | #[no_mangle] 26 | pub fn thread_local() { 27 | println!("{}", HELLO); 28 | let handle = thread::spawn(|| { 29 | NUM.set(NUM.get() + 1); 30 | println!("thread1:{}", NUM.get()); 31 | }); 32 | handle.join().unwrap(); 33 | NUM.set(NUM.get() + 2); 34 | println!("thread2:{}", NUM.get()); 35 | } 36 | 37 | #[no_mangle] 38 | pub fn print(str: &str) { 39 | println!("{}", str); 40 | } 41 | 42 | #[no_mangle] 43 | pub fn add(a: i32, b: i32) -> i32 { 44 | a + b 45 | } 46 | 47 | #[no_mangle] 48 | fn args() { 49 | let args = std::env::args(); 50 | println!("{:?}", args); 51 | } 52 | 53 | #[no_mangle] 54 | pub static HELLO: &str = "Hello!"; 55 | -------------------------------------------------------------------------------- /src/dlsym.rs: -------------------------------------------------------------------------------- 1 | use crate::{loader::find_symbol, register::MANAGER}; 2 | use alloc::{boxed::Box, sync::Arc}; 3 | use core::{ 4 | ffi::{CStr, c_char, c_void}, 5 | mem::forget, 6 | ptr::null, 7 | }; 8 | use elf_loader::RelocatedDylib; 9 | 10 | /// # Safety 11 | /// It is the same as `dlsym`. 12 | #[unsafe(no_mangle)] 13 | pub unsafe extern "C" fn dlsym(handle: *const c_void, symbol_name: *const c_char) -> *const c_void { 14 | const RTLD_DEFAULT: usize = 0; 15 | const RTLD_NEXT: usize = usize::MAX; 16 | let value = handle as usize; 17 | let name = unsafe { CStr::from_ptr(symbol_name).to_str().unwrap_unchecked() }; 18 | let sym = if value == RTLD_DEFAULT { 19 | log::info!("dlsym: Use RTLD_DEFAULT flag to find symbol [{}]", name); 20 | MANAGER 21 | .read() 22 | .global 23 | .values() 24 | .find_map(|lib| unsafe { lib.get::<()>(name).map(|v| v.into_raw()) }) 25 | } else if value == RTLD_NEXT { 26 | todo!("RTLD_NEXT is not supported") 27 | } else { 28 | let libs = unsafe { Arc::from_raw(handle as *const Box<[RelocatedDylib<'static>]>) }; 29 | let symbol = find_symbol::<()>(&libs, name) 30 | .ok() 31 | .map(|sym| sym.into_raw()); 32 | forget(libs); 33 | symbol 34 | }; 35 | sym.unwrap_or(null()).cast() 36 | } 37 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: dtolnay/rust-toolchain@master 18 | with: 19 | toolchain: ${{ matrix.channel }} 20 | - env: 21 | TARGET: ${{ matrix.target }} 22 | CHANNEL: ${{ matrix.channel }} 23 | FEATURES: ${{ matrix.features }} 24 | OP: build 25 | run: sh ci/run.sh 26 | strategy: 27 | matrix: 28 | target: [ x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu, riscv64gc-unknown-linux-gnu ] 29 | channel: [ 1.88.0, stable ] 30 | features: [ "tls,debug,version", "fs" ] 31 | 32 | test: 33 | runs-on: ubuntu-latest 34 | steps: 35 | - uses: actions/checkout@v4 36 | - uses: dtolnay/rust-toolchain@master 37 | with: 38 | toolchain: ${{ matrix.channel }} 39 | - env: 40 | TARGET: ${{ matrix.target }} 41 | CHANNEL: ${{ matrix.channel }} 42 | FEATURES: ${{ matrix.features }} 43 | OP: test 44 | run: sh ci/run.sh 45 | strategy: 46 | matrix: 47 | target: [ x86_64-unknown-linux-gnu ] 48 | channel: [ 1.88.0, stable ] 49 | features: [ "fs, tls, debug"] -------------------------------------------------------------------------------- /TroubleshootingGdb.md: -------------------------------------------------------------------------------- 1 | ## Symbols are not loading when when using the dynamic loader 2 | Modern `gdb` variants will place internal breakpoints to detect a shared library being loaded. The location of these breakpoints 3 | depend on weather `gdb` was able to pinpoint a set of static probes placed on `ld.so`. If detecting the probes was successful, 4 | the breakpoints are going to be set on a `glibc` internal functions. Since `dlopen-rs` does not use `glibc` to load shared libraries, 5 | loading new objects will go unnoticed by `gdb`. For more details, see [Issue #10](https://github.com/weizhiao/rust-dlopen/issues/10). 6 | 7 | We provide a `gdb` script that can be used when starting up `gdb` to manually add debug symbols: 8 | 9 | ```sh 10 | gdb -x scripts/reload_sos_on_r_brk.py /path/to/binary 11 | ``` 12 | Make sure that you have `elftools` installed via `pip` as well as the debug information for `glibc` either through the official packages, 13 | or `debuginfod`. 14 | 15 | #### Packages providing debugging symbols 16 | 17 | | Distro family | Package name | 18 | | :-------------- | :----------: | 19 | | Ubuntu/Debian | libc6-dbg | 20 | | RHEL/Fedora | glibc-debug | 21 | | Arch vaiants | glibc-debug | 22 | 23 | #### Notes on ubuntu 24 | 25 | After installing the debug symbols you may need to modify the directory where GDB searches for debug files: 26 | ```sh 27 | set debug-file-directory /usr/lib/debug 28 | ``` 29 | -------------------------------------------------------------------------------- /examples/dlopen.rs: -------------------------------------------------------------------------------- 1 | use dlopen_rs::{ElfLibrary, OpenFlags}; 2 | use std::path::Path; 3 | 4 | fn main() { 5 | unsafe { std::env::set_var("RUST_LOG", "trace") }; 6 | env_logger::init(); 7 | dlopen_rs::init(); 8 | let path = Path::new("./target/release/libexample.so"); 9 | let libexample1 = ElfLibrary::dlopen( 10 | path.as_os_str().to_str().unwrap(), 11 | OpenFlags::CUSTOM_NOT_REGISTER | OpenFlags::RTLD_LAZY, 12 | ) 13 | .unwrap(); 14 | let add = unsafe { libexample1.get:: i32>("add").unwrap() }; 15 | println!("{}", add(1, 1)); 16 | 17 | let print = unsafe { libexample1.get::("print").unwrap() }; 18 | print("dlopen-rs: hello world"); 19 | 20 | let args = unsafe { libexample1.get::("args") }.unwrap(); 21 | args(); 22 | 23 | drop(libexample1); 24 | 25 | let bytes = std::fs::read(path).unwrap(); 26 | let libexample2 = ElfLibrary::dlopen_from_binary( 27 | &bytes, 28 | "./target/release/libexample.so", 29 | OpenFlags::RTLD_GLOBAL, 30 | ) 31 | .unwrap(); 32 | 33 | let backtrace = unsafe { libexample2.get::("backtrace").unwrap() }; 34 | backtrace(); 35 | 36 | let panic = unsafe { libexample2.get::("panic").unwrap() }; 37 | panic(); 38 | 39 | let thread_local = unsafe { libexample2.get::("thread_local").unwrap() }; 40 | thread_local(); 41 | 42 | let dl_info = ElfLibrary::dladdr(backtrace.into_raw() as usize).unwrap(); 43 | println!("{:?}", dl_info); 44 | 45 | ElfLibrary::dl_iterate_phdr(|info| { 46 | println!("iterate dynamic library: {}", info.name()); 47 | Ok(()) 48 | }) 49 | .unwrap(); 50 | } 51 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dlopen-rs" 3 | version = "0.7.3" 4 | edition = "2024" 5 | rust-version = "1.88.0" 6 | authors = ["wzhao <1207410841@qq.com>"] 7 | readme = "README.md" 8 | repository = "https://github.com/weizhiao/dlopen-rs" 9 | keywords = ["dlopen", "elf", "unix", "loader"] 10 | categories = ["no-std", "os", "embedded"] 11 | license = "Apache-2.0" 12 | description = "A dynamic linker fully implemented in Rust." 13 | exclude = [".gitignore", "/example-dylib", "check.sh"] 14 | 15 | [workspace] 16 | members = ["example-dylib", "cdylib"] 17 | resolver = "2" 18 | 19 | [dependencies.spin] 20 | version = "0.10.0" 21 | default-features = false 22 | features = ["rwlock", "lazy", "mutex", "spin_mutex"] 23 | 24 | [dependencies.indexmap] 25 | version = '2.9.0' 26 | 27 | [dependencies.elf_loader] 28 | version = "0.13.0" 29 | default-features = false 30 | features = ["use-syscall", "log"] 31 | 32 | [dependencies] 33 | bitflags = "2.6.0" 34 | log = "0.4.22" 35 | syscalls = "0.7.0" 36 | 37 | [features] 38 | default = ["fs", "debug"] 39 | use-ldso = ["tls"] 40 | # enable this when you want to use gdb/lldb to debug the loaded dynamic libraries 41 | debug = ["use-ldso"] 42 | # enable std 43 | fs = [] 44 | # enable this when you need to use thread loca 45 | tls = [] 46 | # activate specific versions of symbols for dynamic library loading 47 | version = ["elf_loader/version"] 48 | 49 | [dev-dependencies] 50 | criterion = "0.5.1" 51 | libloading = "0.8.5" 52 | env_logger = "0.11.6" 53 | libc = "0.2.149" 54 | 55 | [[bench]] 56 | name = "dlopen" 57 | harness = false 58 | required-features = ["fs", "tls"] 59 | 60 | [[bench]] 61 | name = "dlopen_llvm" 62 | harness = false 63 | required-features = ["fs", "tls"] 64 | 65 | [[bench]] 66 | name = "dlsym" 67 | harness = false 68 | required-features = ["fs", "tls"] 69 | 70 | [[bench]] 71 | name = "dl_iterate_phdr" 72 | harness = false 73 | required-features = ["fs", "tls"] 74 | 75 | [[example]] 76 | name = "dlopen" 77 | required-features = ["tls"] 78 | -------------------------------------------------------------------------------- /src/find.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | ElfLibrary, LinkMap, 3 | loader::{EH_FRAME_ID, EhFrame}, 4 | register::MANAGER, 5 | }; 6 | use core::{ 7 | ffi::{c_int, c_void}, 8 | ptr::null, 9 | }; 10 | 11 | #[repr(C)] 12 | pub struct DlFindObject { 13 | dlfo_flags: u64, 14 | dlfo_map_start: *mut c_void, 15 | dlfo_map_end: *mut c_void, 16 | dlfo_link_map: *mut c_void, 17 | dlfo_eh_frame: *mut c_void, 18 | __dflo_reserved: [u64; 7], 19 | } 20 | 21 | pub(crate) fn addr2dso(addr: usize) -> Option { 22 | log::trace!("addr2dso: addr [{:#x}]", addr); 23 | MANAGER.read().all.values().find_map(|v| { 24 | let start = v.relocated_dylib_ref().base(); 25 | let end = start + v.relocated_dylib_ref().map_len(); 26 | log::trace!("addr2dso: [{}] [{:#x}]-[{:#x}]", v.shortname(), start, end); 27 | if (start..end).contains(&addr) { 28 | Some(v.get_dylib()) 29 | } else { 30 | None 31 | } 32 | }) 33 | } 34 | 35 | #[unsafe(no_mangle)] 36 | extern "C" fn _dl_find_object(pc: *const c_void, dlfo: *mut DlFindObject) -> c_int { 37 | addr2dso(pc as usize) 38 | .map(|dylib| { 39 | let dlfo = unsafe { &mut *dlfo }; 40 | dlfo.dlfo_flags = 0; 41 | dlfo.dlfo_map_start = dylib.base() as *mut c_void; 42 | dlfo.dlfo_map_end = (dylib.base() + dylib.map_len()) as *mut c_void; 43 | dlfo.dlfo_eh_frame = dylib 44 | .inner 45 | .user_data() 46 | .get(EH_FRAME_ID) 47 | .unwrap() 48 | .downcast_ref::() 49 | .unwrap() 50 | .0 as *mut c_void; 51 | 0 52 | }) 53 | .unwrap_or(-1) 54 | } 55 | 56 | // 本函数的作用是解决__cxa_thread_atexit_impl导致的内存泄露问题 57 | // 现在在程序退出时会调用__cxa_thread_atexit_impl注册的tls dtor 58 | #[unsafe(no_mangle)] 59 | extern "C" fn _dl_find_dso_for_object(_addr: usize) -> *const LinkMap { 60 | null() 61 | } 62 | -------------------------------------------------------------------------------- /src/arch.rs: -------------------------------------------------------------------------------- 1 | pub(crate) trait ModifyRegister { 2 | /// Get thread register base 3 | fn base() -> usize; 4 | unsafe fn get(offset: usize) -> T; 5 | unsafe fn set(offset: usize, value: usize); 6 | } 7 | 8 | pub(crate) struct ThreadRegister; 9 | 10 | #[cfg(target_arch = "x86_64")] 11 | impl ModifyRegister for ThreadRegister { 12 | fn base() -> usize { 13 | let val; 14 | unsafe { 15 | core::arch::asm!("rdfsbase {}", out(reg) val); 16 | } 17 | val 18 | } 19 | 20 | unsafe fn get(offset: usize) -> T { 21 | let size = core::mem::size_of::(); 22 | match size { 23 | 2 => { 24 | let val: u16; 25 | unsafe { 26 | core::arch::asm!("mov rax, {1}","mov {0:x}, fs:[rax]", out(reg) val, in(reg) offset); 27 | } 28 | unsafe { core::mem::transmute_copy(&val) } 29 | } 30 | 4 => { 31 | let val: u32; 32 | unsafe { 33 | core::arch::asm!("mov rax, {1}","mov {0:e}, fs:[rax]", out(reg) val, in(reg) offset); 34 | } 35 | unsafe { core::mem::transmute_copy(&val) } 36 | } 37 | 8 => { 38 | let val: u64; 39 | unsafe { 40 | core::arch::asm!("mov rax, {1}","mov {0}, fs:[rax]", out(reg) val, in(reg) offset); 41 | } 42 | unsafe { core::mem::transmute_copy(&val) } 43 | } 44 | _ => panic!("Unsupported type size"), 45 | } 46 | } 47 | 48 | unsafe fn set(offset: usize, value: usize) { 49 | unsafe { 50 | core::arch::asm!("mov rax, {1}","mov fs:[rax],{0}",in(reg) value, in(reg) offset); 51 | } 52 | } 53 | } 54 | 55 | #[cfg(target_arch = "aarch64")] 56 | impl ModifyRegister for ThreadRegister { 57 | fn get() -> usize { 58 | let val: usize; 59 | unsafe { 60 | core::arch::asm!("mrs {},tpidr_el0",out(reg) val); 61 | } 62 | val 63 | } 64 | 65 | unsafe fn set(value: usize) { 66 | unsafe { 67 | core::arch::asm!("msr tpidr_el0,{}",in(reg) value); 68 | } 69 | } 70 | } 71 | 72 | #[cfg(any(target_arch = "riscv64", target_arch = "riscv32"))] 73 | impl ModifyRegister for ThreadRegister { 74 | fn get() -> usize { 75 | let val: usize; 76 | unsafe { 77 | core::arch::asm!("mv {}, tp",out(reg) val); 78 | } 79 | val 80 | } 81 | 82 | unsafe fn set(value: usize) { 83 | unsafe { 84 | core::arch::asm!("mv tp,{}",in(reg) value); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /README-zh_cn.md: -------------------------------------------------------------------------------- 1 | [![](https://img.shields.io/crates/v/dlopen-rs.svg)](https://crates.io/crates/dlopen-rs) 2 | [![](https://img.shields.io/crates/d/dlopen-rs.svg)](https://crates.io/crates/dlopen-rs) 3 | [![license](https://img.shields.io/crates/l/dlopen-rs.svg)](https://crates.io/crates/dlopen-rs) 4 | [![dlopen-rs on docs.rs](https://docs.rs/dlopen-rs/badge.svg)](https://docs.rs/dlopen-rs) 5 | [![Rust](https://img.shields.io/badge/rust-1.88.0%2B-blue.svg?maxAge=3600)](https://github.com/weizhiao/dlopen_rs) 6 | [![Build Status](https://github.com/weizhiao/dlopen-rs/actions/workflows/rust.yml/badge.svg)](https://github.com/weizhiao/dlopen-rs/actions) 7 | # dlopen-rs 8 | 9 | [文档](https://docs.rs/dlopen-rs/) 10 | 11 | `dlopen-rs`是一个完全使用Rust实现的动态链接器,提供了一组对Rust友好的操作动态库的接口,也提供了一组与libc中行为一致的C接口。 12 | 13 | ## 用法 14 | 你可以使用`dlopen-rs`替换`libloading`来加载动态库,也可以在不修改任何代码的情况下,利用`LD_PRELOAD`将libc中的`dlopen`,`dlsym`,`dl_iterate_phdr`等函数替换为`dlopen-rs`中的实现。 15 | 16 | ```shell 17 | # 将本库编译成动态库形式 18 | cargo build -r -p cdylib 19 | # 编译测试用例 20 | cargo build -r -p dlopen-rs --example preload 21 | # 使用本库中的实现替换libc中的实现 22 | RUST_LOG=trace LD_PRELOAD=./target/release/libdlopen.so ./target/release/examples/preload 23 | ``` 24 | 25 | ## 优势 26 | 1. 能够为 #![no_std] 目标提供加载 `ELF` 动态库的支持。 27 | 2. 能够轻松地在运行时用自己的自定义符号替换共享库中的符号。 28 | 3. 大多数情况下有比`ld.so`更快的速度。(加载动态库和获取符号) 29 | 4. 提供了对Rust友好的接口。 30 | 5. 允许重复加载某个动态库到内存中。你可以使用OpenFlags中的`CUSTOM_NOT_REGISTER`标识来做到这一点,使用该功能时,程序的内存空间中可以同时存在一个动态库的多个副本(这些副本可能完全相同,也可能有所差异),你可以利用该功能在运行时动态更新动态库。 31 | 32 | ## 特性 33 | 34 | | 特性 | 是否默认开启 | 描述 | 35 | | ------- | ------------ | -------------------------------------------- | 36 | | std | 是 | 启用Rust标准库 | 37 | | debug | 是 | 启用后可以使用 gdb/lldb 调试已加载的动态库。 | 38 | | mmap | 是 | 启用在有mmap的平台上的默认实现 | 39 | | version | 否 | 在寻找符号时使用符号的版本号 | 40 | | tls | 是 | 启用后动态库中可以使用线程本地存储。 | 41 | 42 | ## 指令集支持 43 | 44 | | 指令集 | 支持 | 延迟绑定 | 测试 | 45 | | ----------- | ---- | -------- | --------- | 46 | | x86_64 | ✅ | ✅ | ✅(CI) | 47 | | aarch64 | ✅ | ✅ | ✅(Manual) | 48 | | riscv64 | ✅ | ✅ | ✅(Manual) | 49 | | loongarch64 | ✅ | ❌ | ❌ | 50 | 51 | ## 示例 52 | 53 | 使用`dlopen`接口加载动态库,使用`dl_iterate_phdr`接口遍历已经加载的动态库。此外本库使用了`log`库,你可以使用自己喜欢的库输出日志信息,来查看dlopen-rs的工作流程,本库的例子中使用的是`env_logger`库。 54 | ```rust 55 | use dlopen_rs::{ElfLibrary, OpenFlags}; 56 | use std::path::Path; 57 | 58 | fn main() { 59 | std::env::set_var("RUST_LOG", "trace"); 60 | env_logger::init(); 61 | dlopen_rs::init(); 62 | let path = Path::new("./target/release/libexample.so"); 63 | let libexample = 64 | ElfLibrary::dlopen(path, OpenFlags::RTLD_LOCAL | OpenFlags::RTLD_LAZY).unwrap(); 65 | let add = unsafe { libexample.get:: i32>("add").unwrap() }; 66 | println!("{}", add(1, 1)); 67 | 68 | let print = unsafe { libexample.get::("print").unwrap() }; 69 | print("dlopen-rs: hello world"); 70 | 71 | let dl_info = ElfLibrary::dladdr(print.into_raw() as usize).unwrap(); 72 | println!("{:?}", dl_info); 73 | 74 | ElfLibrary::dl_iterate_phdr(|info| { 75 | println!( 76 | "iterate dynamic library: {}", 77 | unsafe { CStr::from_ptr(info.dlpi_name).to_str().unwrap() } 78 | ); 79 | Ok(()) 80 | }) 81 | .unwrap(); 82 | } 83 | ``` 84 | 85 | ## 最低编译器版本支持 86 | Rust 1.85.0及以上 87 | 88 | ## 未完成 89 | * dlinfo还未实现。dlerror目前只会返回NULL。 90 | * dlsym的RTLD_NEXT还未实现。 91 | * 在调用dlopen失败时,新加载的动态库虽然会被销毁但没有调用.fini中的函数。 92 | ## 补充 93 | 如果在使用过程中遇到问题可以在 GitHub 上提出问题,十分欢迎大家为本库提交代码一起完善dlopen-rs的功能。😊 94 | -------------------------------------------------------------------------------- /src/dladdr.rs: -------------------------------------------------------------------------------- 1 | use crate::{ElfLibrary, find::addr2dso}; 2 | use core::{ 3 | ffi::{CStr, c_char, c_int, c_void}, 4 | fmt::Debug, 5 | ptr::null, 6 | }; 7 | 8 | #[repr(C)] 9 | pub struct CDlinfo { 10 | pub dli_fname: *const c_char, 11 | pub dli_fbase: *mut c_void, 12 | pub dli_sname: *const c_char, 13 | pub dli_saddr: *mut c_void, 14 | } 15 | 16 | pub struct DlInfo { 17 | /// dylib 18 | dylib: ElfLibrary, 19 | /// Name of symbol whose definition overlaps addr 20 | sname: Option<&'static CStr>, 21 | /// Exact address of symbol named in dli_sname 22 | saddr: usize, 23 | } 24 | 25 | impl DlInfo { 26 | #[inline] 27 | pub fn dylib(&self) -> &ElfLibrary { 28 | &self.dylib 29 | } 30 | 31 | /// Name of symbol whose definition overlaps addr 32 | #[inline] 33 | pub fn symbol_name(&self) -> Option<&str> { 34 | self.sname.map(|s| s.to_str().unwrap()) 35 | } 36 | 37 | /// Exact address of symbol 38 | #[inline] 39 | pub fn symbol_addr(&self) -> Option { 40 | if self.saddr == 0 { 41 | None 42 | } else { 43 | Some(self.saddr) 44 | } 45 | } 46 | } 47 | 48 | impl Debug for DlInfo { 49 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 50 | f.debug_struct("DlInfo") 51 | .field("dylib", &self.dylib) 52 | .field("sname", &self.sname) 53 | .field("saddr", &format_args!("{:#x}", self.saddr)) 54 | .finish() 55 | } 56 | } 57 | 58 | impl ElfLibrary { 59 | /// determines whether the address specified in addr is located in one of the shared objects loaded by the calling 60 | /// application. If it is, then `dladdr` returns information about the shared object and 61 | /// symbol that overlaps addr. 62 | pub fn dladdr(addr: usize) -> Option { 63 | log::info!( 64 | "dladdr: Try to find the symbol information corresponding to [{:#x}]", 65 | addr 66 | ); 67 | addr2dso(addr).map(|dylib| { 68 | let mut dl_info = DlInfo { 69 | dylib, 70 | sname: None, 71 | saddr: 0, 72 | }; 73 | let symtab = dl_info.dylib.inner.symtab(); 74 | for i in 0..symtab.count_syms() { 75 | let (sym, syminfo) = symtab.symbol_idx(i); 76 | let start = dl_info.dylib.base() + sym.st_value(); 77 | let end = start + sym.st_size(); 78 | if sym.st_value() != 0 79 | && sym.is_ok_bind() 80 | && sym.is_ok_type() 81 | && (start..end).contains(&addr) 82 | { 83 | dl_info.sname = Some(unsafe { 84 | core::mem::transmute::<&CStr, &CStr>(syminfo.cname().unwrap()) 85 | }); 86 | dl_info.saddr = start; 87 | } 88 | } 89 | dl_info 90 | }) 91 | } 92 | } 93 | 94 | /// # Safety 95 | /// It is the same as `dladdr`. 96 | #[unsafe(no_mangle)] 97 | pub unsafe extern "C" fn dladdr(addr: *const c_void, info: *mut CDlinfo) -> c_int { 98 | if let Some(dl_info) = ElfLibrary::dladdr(addr as usize) { 99 | let info = unsafe { &mut *info }; 100 | info.dli_fbase = dl_info.dylib().base() as _; 101 | info.dli_fname = dl_info.dylib().cname().as_ptr(); 102 | info.dli_saddr = dl_info.symbol_addr().unwrap_or(0) as _; 103 | info.dli_sname = dl_info.sname.map_or(null(), |s| s.as_ptr()); 104 | 1 105 | } else { 106 | 0 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/debug.rs: -------------------------------------------------------------------------------- 1 | use crate::LinkMap; 2 | use alloc::boxed::Box; 3 | use spin::Mutex; 4 | 5 | use crate::init::GDBDebug; 6 | use core::{ 7 | ffi::{CStr, c_char, c_int}, 8 | ptr::null_mut, 9 | }; 10 | const RT_ADD: c_int = 1; 11 | const RT_CONSISTENT: c_int = 0; 12 | const RT_DELETE: c_int = 2; 13 | 14 | pub(crate) struct CustomDebug { 15 | pub debug: *mut GDBDebug, 16 | pub tail: *mut LinkMap, 17 | } 18 | 19 | unsafe impl Sync for CustomDebug {} 20 | unsafe impl Send for CustomDebug {} 21 | 22 | pub(crate) struct DebugInfo { 23 | link_map: Box, 24 | } 25 | 26 | impl Drop for DebugInfo { 27 | fn drop(&mut self) { 28 | unsafe { 29 | let mut custom_debug = DEBUG.lock(); 30 | let tail = custom_debug.tail; 31 | let debug = &mut *custom_debug.debug; 32 | debug.state = RT_DELETE; 33 | (debug.brk)(); 34 | match ( 35 | debug.map == self.link_map.as_mut(), 36 | tail == self.link_map.as_mut(), 37 | ) { 38 | (true, true) => { 39 | debug.map = null_mut(); 40 | custom_debug.tail = null_mut(); 41 | } 42 | (true, false) => { 43 | debug.map = self.link_map.l_next; 44 | (*self.link_map.l_next).l_prev = null_mut(); 45 | } 46 | (false, true) => { 47 | let prev = &mut *self.link_map.l_prev; 48 | prev.l_next = null_mut(); 49 | custom_debug.tail = prev; 50 | } 51 | (false, false) => { 52 | let prev = &mut *self.link_map.l_prev; 53 | let next = &mut *self.link_map.l_next; 54 | prev.l_next = next; 55 | next.l_prev = prev; 56 | } 57 | } 58 | debug.state = RT_CONSISTENT; 59 | (debug.brk)(); 60 | } 61 | } 62 | } 63 | 64 | pub(crate) static DEBUG: Mutex = Mutex::new(CustomDebug { 65 | debug: null_mut(), 66 | tail: null_mut(), 67 | }); 68 | 69 | impl DebugInfo { 70 | pub(crate) unsafe fn new(base: usize, name: *const c_char, dynamic: usize) -> DebugInfo { 71 | let mut custom_debug = DEBUG.lock(); 72 | let tail = custom_debug.tail; 73 | assert!( 74 | !custom_debug.debug.is_null(), 75 | "Please call init function first" 76 | ); 77 | let debug = unsafe { &mut *custom_debug.debug }; 78 | let link_map = Box::leak(Box::new(LinkMap { 79 | l_addr: base as _, 80 | l_name: name as _, 81 | l_ld: dynamic as _, 82 | l_next: null_mut(), 83 | l_prev: tail, 84 | })); 85 | if tail.is_null() { 86 | debug.map = link_map; 87 | } else { 88 | unsafe { 89 | (*tail).l_next = link_map; 90 | } 91 | } 92 | custom_debug.tail = link_map; 93 | debug.state = RT_ADD; 94 | (debug.brk)(); 95 | debug.state = RT_CONSISTENT; 96 | (debug.brk)(); 97 | log::trace!("Add debugging information for [{}]", unsafe { 98 | CStr::from_ptr(name).to_str().unwrap() 99 | }); 100 | DebugInfo { 101 | link_map: unsafe { Box::from_raw(link_map) }, 102 | } 103 | } 104 | } 105 | 106 | #[inline] 107 | pub(crate) fn init_debug(debug: &mut GDBDebug) { 108 | debug.map = null_mut(); 109 | let mut custom = DEBUG.lock(); 110 | custom.debug = debug; 111 | custom.tail = null_mut(); 112 | } 113 | -------------------------------------------------------------------------------- /src/dl_iterate_phdr.rs: -------------------------------------------------------------------------------- 1 | use crate::{ElfLibrary, Error, Result, register::MANAGER}; 2 | use alloc::boxed::Box; 3 | use core::{ 4 | ffi::{CStr, c_char, c_int, c_ulonglong, c_void}, 5 | ptr::null_mut, 6 | }; 7 | use elf_loader::arch::ElfPhdr; 8 | 9 | /// same as dl_phdr_info in libc 10 | #[repr(C)] 11 | pub struct CDlPhdrInfo { 12 | pub dlpi_addr: usize, 13 | pub dlpi_name: *const c_char, 14 | pub dlpi_phdr: *const ElfPhdr, 15 | pub dlpi_phnum: u16, 16 | pub dlpi_adds: c_ulonglong, 17 | pub dlpi_subs: c_ulonglong, 18 | pub dlpi_tls_modid: usize, 19 | pub dlpi_tls_data: *mut c_void, 20 | } 21 | 22 | pub struct DlPhdrInfo<'lib> { 23 | lib_base: usize, 24 | lib_name: &'lib CStr, 25 | phdrs: &'lib [ElfPhdr], 26 | dlpi_adds: c_ulonglong, 27 | dlpi_subs: c_ulonglong, 28 | tls_modid: usize, 29 | tls_data: Option<&'lib [u8]>, 30 | } 31 | 32 | impl DlPhdrInfo<'_> { 33 | /// Get the name of the dynamic library. 34 | #[inline] 35 | pub fn name(&self) -> &str { 36 | self.lib_name.to_str().unwrap() 37 | } 38 | 39 | /// Get the C-style name of the dynamic library. 40 | #[inline] 41 | pub fn cname(&self) -> &CStr { 42 | self.lib_name 43 | } 44 | 45 | /// Get the base address of the dynamic library. 46 | #[inline] 47 | pub fn base(&self) -> usize { 48 | self.lib_base 49 | } 50 | 51 | /// Get the program headers of the dynamic library. 52 | #[inline] 53 | pub fn phdrs(&self) -> &[ElfPhdr] { 54 | self.phdrs 55 | } 56 | } 57 | 58 | impl ElfLibrary { 59 | /// Iterate over the program headers of all dynamic libraries. 60 | pub fn dl_iterate_phdr(mut callback: F) -> Result<()> 61 | where 62 | F: FnMut(&DlPhdrInfo) -> Result<()>, 63 | { 64 | let reader = MANAGER.read(); 65 | for lib in reader.all.values() { 66 | let phdrs = lib.relocated_dylib_ref().phdrs(); 67 | if phdrs.is_empty() { 68 | continue; 69 | } 70 | let info = DlPhdrInfo { 71 | lib_base: lib.relocated_dylib_ref().base(), 72 | lib_name: lib.relocated_dylib_ref().cname(), 73 | phdrs, 74 | dlpi_adds: reader.all.len() as _, 75 | dlpi_subs: 0, 76 | tls_modid: 0, 77 | tls_data: None, 78 | }; 79 | callback(&info)?; 80 | } 81 | Ok(()) 82 | } 83 | } 84 | 85 | pub(crate) type CallBack = 86 | unsafe extern "C" fn(info: *mut CDlPhdrInfo, size: usize, data: *mut c_void) -> c_int; 87 | 88 | /// # Safety 89 | /// It is the same as `dl_iterate_phdr`. 90 | #[unsafe(no_mangle)] 91 | pub unsafe extern "C" fn dl_iterate_phdr(callback: Option, data: *mut c_void) -> c_int { 92 | let f = |info: &DlPhdrInfo| { 93 | if let Some(callback) = callback { 94 | let mut c_info = CDlPhdrInfo { 95 | dlpi_addr: info.lib_base, 96 | dlpi_name: info.lib_name.as_ptr(), 97 | dlpi_phdr: info.phdrs.as_ptr(), 98 | dlpi_phnum: info.phdrs.len() as _, 99 | dlpi_adds: info.dlpi_adds, 100 | dlpi_subs: info.dlpi_subs, 101 | dlpi_tls_modid: info.tls_modid, 102 | dlpi_tls_data: info 103 | .tls_data 104 | .map(|data| data.as_ptr() as _) 105 | .unwrap_or(null_mut()), 106 | }; 107 | unsafe { 108 | let ret = callback(&mut c_info, size_of::(), data); 109 | if ret != 0 { 110 | return Err(Error::IteratorPhdrError { err: Box::new(ret) }); 111 | } 112 | }; 113 | } 114 | Ok(()) 115 | }; 116 | if let Err(Error::IteratorPhdrError { err }) = ElfLibrary::dl_iterate_phdr(f) { 117 | *err.downcast::().unwrap() 118 | } else { 119 | 0 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /tests/all.rs: -------------------------------------------------------------------------------- 1 | use dlopen_rs::{ElfLibrary, OpenFlags}; 2 | use std::env::consts; 3 | use std::path::PathBuf; 4 | use std::sync::OnceLock; 5 | 6 | const TARGET_DIR: Option<&'static str> = option_env!("CARGO_TARGET_DIR"); 7 | static TARGET_TRIPLE: OnceLock = OnceLock::new(); 8 | 9 | fn lib_path(file_name: &str) -> String { 10 | let path: PathBuf = TARGET_DIR.unwrap_or("target").into(); 11 | path.join(TARGET_TRIPLE.get().unwrap()) 12 | .join("release") 13 | .join(file_name) 14 | .to_str() 15 | .unwrap() 16 | .to_string() 17 | } 18 | 19 | const PACKAGE_NAME: [&str; 1] = ["example_dylib"]; 20 | 21 | fn compile() { 22 | static ONCE: ::std::sync::Once = ::std::sync::Once::new(); 23 | ONCE.call_once(|| { 24 | unsafe { std::env::set_var("RUST_LOG", "trace") }; 25 | env_logger::init(); 26 | dlopen_rs::init(); 27 | let arch = consts::ARCH; 28 | if arch.contains("x86_64") { 29 | TARGET_TRIPLE 30 | .set("x86_64-unknown-linux-gnu".to_string()) 31 | .unwrap(); 32 | } else if arch.contains("riscv64") { 33 | TARGET_TRIPLE 34 | .set("riscv64gc-unknown-linux-gnu".to_string()) 35 | .unwrap(); 36 | } else if arch.contains("aarch64") { 37 | TARGET_TRIPLE 38 | .set("aarch64-unknown-linux-gnu".to_string()) 39 | .unwrap(); 40 | } else if arch.contains("loongarch64") { 41 | TARGET_TRIPLE 42 | .set("loongarch64-unknown-linux-musl".to_string()) 43 | .unwrap(); 44 | } else { 45 | unimplemented!() 46 | } 47 | 48 | for name in PACKAGE_NAME { 49 | let mut cmd = ::std::process::Command::new("cargo"); 50 | cmd.arg("build") 51 | .arg("-r") 52 | .arg("-p") 53 | .arg(name) 54 | .arg("--target") 55 | .arg(TARGET_TRIPLE.get().unwrap().as_str()); 56 | assert!( 57 | cmd.status() 58 | .expect("could not compile the test helpers!") 59 | .success() 60 | ); 61 | } 62 | }); 63 | } 64 | 65 | #[test] 66 | fn dlopen() { 67 | compile(); 68 | let path = lib_path("libexample.so"); 69 | assert!(ElfLibrary::dlopen(path, OpenFlags::RTLD_GLOBAL).is_ok()); 70 | } 71 | 72 | #[test] 73 | fn dlsym() { 74 | compile(); 75 | let path = lib_path("libexample.so"); 76 | let lib = ElfLibrary::dlopen(path, OpenFlags::RTLD_GLOBAL).unwrap(); 77 | let print = unsafe { lib.get::("print").unwrap() }; 78 | print("dlopen-rs: hello world"); 79 | } 80 | 81 | #[test] 82 | fn dl_iterate_phdr() { 83 | compile(); 84 | let path = lib_path("libexample.so"); 85 | let _lib = ElfLibrary::dlopen(path, OpenFlags::RTLD_GLOBAL).unwrap(); 86 | ElfLibrary::dl_iterate_phdr(|info| { 87 | println!("iterate dynamic library: {}", info.name()); 88 | Ok(()) 89 | }) 90 | .unwrap(); 91 | } 92 | 93 | #[test] 94 | fn panic() { 95 | compile(); 96 | let path = lib_path("libexample.so"); 97 | let lib = ElfLibrary::dlopen(path, OpenFlags::RTLD_GLOBAL).unwrap(); 98 | let panic = unsafe { lib.get::("panic").unwrap() }; 99 | panic(); 100 | } 101 | 102 | #[test] 103 | fn dladdr() { 104 | compile(); 105 | let path = lib_path("libexample.so"); 106 | let lib = ElfLibrary::dlopen(path, OpenFlags::RTLD_GLOBAL).unwrap(); 107 | let print = unsafe { lib.get::("print").unwrap() }; 108 | let find = ElfLibrary::dladdr(print.into_raw() as usize).unwrap(); 109 | assert!(find.dylib().name() == lib.name()); 110 | } 111 | 112 | #[test] 113 | fn thread_local() { 114 | compile(); 115 | let path = lib_path("libexample.so"); 116 | let lib = ElfLibrary::dlopen(path, OpenFlags::RTLD_GLOBAL).unwrap(); 117 | let thread_local = unsafe { lib.get::("thread_local").unwrap() }; 118 | thread_local(); 119 | } 120 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![](https://img.shields.io/crates/v/dlopen-rs.svg)](https://crates.io/crates/dlopen-rs) 2 | [![](https://img.shields.io/crates/d/dlopen-rs.svg)](https://crates.io/crates/dlopen-rs) 3 | [![license](https://img.shields.io/crates/l/dlopen-rs.svg)](https://crates.io/crates/dlopen-rs) 4 | [![dlopen-rs on docs.rs](https://docs.rs/dlopen-rs/badge.svg)](https://docs.rs/dlopen-rs) 5 | [![Rust](https://img.shields.io/badge/rust-1.88.0%2B-blue.svg?maxAge=3600)](https://github.com/weizhiao/dlopen_rs) 6 | [![Build Status](https://github.com/weizhiao/dlopen-rs/actions/workflows/rust.yml/badge.svg)](https://github.com/weizhiao/dlopen-rs/actions) 7 | # dlopen-rs 8 | 9 | English | [中文](README-zh_cn.md) 10 | 11 | [[Documentation]](https://docs.rs/dlopen-rs/) 12 | 13 | `dlopen-rs` is a dynamic linker fully implemented in Rust, providing a set of Rust-friendly interfaces for manipulating dynamic libraries, as well as C-compatible interfaces consistent with `libc` behavior. 14 | 15 | ## Usage 16 | You can use `dlopen-rs` as a replacement for `libloading` to load dynamic libraries. It also allows replacing libc's `dlopen`, `dlsym`, `dl_iterate_phdr` and other functions with implementations from `dlopen-rs` using `LD_PRELOAD` without code modifications. 17 | ```shell 18 | # Compile the library as a dynamic library 19 | $ cargo build -r -p cdylib 20 | # Compile test cases 21 | $ cargo build -r -p dlopen-rs --example preload 22 | # Replace libc implementations with ours 23 | $ RUST_LOG=trace LD_PRELOAD=./target/release/libdlopen.so ./target/release/examples/preload 24 | ``` 25 | 26 | ## Advantages 27 | 1. Provides support for loading ELF dynamic libraries to #![no_std] targets. 28 | 2. Enables easy runtime replacement of symbols in shared libraries with custom implementations. 29 | 3. Typically faster than `ld.so` for dynamic library loading and symbol resolution. 30 | 4. Offers Rust-friendly interfaces with ergonomic design. 31 | 5. Allows repeated loading of the same dynamic library into memory. Using the `CUSTOM_NOT_REGISTER` flag in OpenFlags enables multiple coexisting copies of a library (identical or modified) in memory, facilitating runtime dynamic library `hot-swapping`. 32 | 33 | ## Feature 34 | | Feature | Default | Description | 35 | | -------- | ------- | ------------------------------------------------------------------ | 36 | | debug | Yes | Enable this to use gdb/lldb for debugging loaded dynamic libraries | 37 | | fs | Yes | Enable this to use the file system. | 38 | | version | No | Activate specific versions of symbols for dynamic library loading | 39 | | tls | Yes | Enable this to use thread local storage. | 40 | | use-ldso | No | Enable this to work with the libc ld.so. | 41 | 42 | ## Architecture Support 43 | 44 | | Arch | Support | Lazy Binding | Test | 45 | | ----------- | ------- | ------------ | --------- | 46 | | x86_64 | ✅ | ✅ | ✅(CI) | 47 | | aarch64 | ✅ | ✅ | ✅(Manual) | 48 | | riscv64 | ✅ | ✅ | ✅(Manual) | 49 | | loongarch64 | ✅ | ❌ | ❌ | 50 | 51 | ## Examples 52 | 53 | The `dlopen` interface is used to load dynamic libraries, and the `dl_iterate_phdr` interface is used to iterate through the already loaded dynamic libraries. Additionally, this library uses the `log` crate, and you can use your preferred library to output log information to view the workflow of `dlopen-rs`. In the examples of this library, the `env_logger` crate is used. 54 | ```rust 55 | use dlopen_rs::ELFLibrary; 56 | use std::path::Path; 57 | 58 | fn main() { 59 | std::env::set_var("RUST_LOG", "trace"); 60 | env_logger::init(); 61 | dlopen_rs::init(); 62 | let path = Path::new("./target/release/libexample.so"); 63 | let libexample = 64 | ElfLibrary::dlopen(path, OpenFlags::RTLD_LOCAL | OpenFlags::RTLD_LAZY).unwrap(); 65 | let add = unsafe { libexample.get:: i32>("add").unwrap() }; 66 | println!("{}", add(1, 1)); 67 | 68 | let print = unsafe { libexample.get::("print").unwrap() }; 69 | print("dlopen-rs: hello world"); 70 | 71 | let dl_info = ElfLibrary::dladdr(print.into_raw() as usize).unwrap(); 72 | println!("{:?}", dl_info); 73 | 74 | ElfLibrary::dl_iterate_phdr(|info| { 75 | println!( 76 | "iterate dynamic library: {}", 77 | unsafe { CStr::from_ptr(info.dlpi_name).to_str().unwrap() } 78 | ); 79 | Ok(()) 80 | }) 81 | .unwrap(); 82 | } 83 | ``` 84 | 85 | ## Minimum Supported Rust Version 86 | Rust 1.88 or higher. 87 | 88 | ## TODO 89 | * dlinfo have not been implemented yet. dlerror currently only returns NULL. 90 | * RTLD_NEXT for dlsym has not been implemented. 91 | * When dlopen fails, the newly loaded dynamic library is destroyed, but the functions in .fini are not called. 92 | * Fix multi-threading bugs. 93 | 94 | ## Supplement 95 | If you encounter any issues during use, feel free to raise them on GitHub. We warmly welcome everyone to contribute code to help improve the functionality of dlopen-rs. 😊 96 | 97 | ## Troubleshooting GDB 98 | [See the dedicated page.](TroubleshootingGdb.md) 99 | -------------------------------------------------------------------------------- /scripts/reload_sos_on_r_brk.py: -------------------------------------------------------------------------------- 1 | from gdb import ( 2 | NewObjFileEvent, 3 | Value, 4 | execute, 5 | objfiles, 6 | BP_BREAKPOINT, 7 | Breakpoint, 8 | parse_and_eval, 9 | events, 10 | ) 11 | from elftools.elf.elffile import ELFFile 12 | 13 | # Global flag to track whether the breakpoint has been set 14 | BRK_INIT = False 15 | 16 | 17 | def try_manually_adding_symbols(lib_name, l_addr): 18 | """Attempts to manually load debug symbols for a shared library. 19 | 20 | This function reads the ELF file to locate the `.text` section and 21 | calculates its virtual memory address (VMA). It then instructs GDB 22 | to add symbol information based on the loaded address. 23 | 24 | Args: 25 | lib_name (str): Path to the shared library. 26 | l_addr (int): Base address where the library is loaded. 27 | """ 28 | try: 29 | with open(lib_name, "rb") as elf_file: 30 | elf = ELFFile(elf_file) 31 | text_section = elf.get_section_by_name(".text") 32 | if text_section is None: 33 | return 34 | text_section_vma = text_section["sh_addr"] 35 | command = f"add-symbol-file {lib_name} {l_addr + text_section_vma}" 36 | execute(command) 37 | except Exception: 38 | pass 39 | 40 | 41 | def ensure_shared_library(lib_path: str, lib_addr: int): 42 | """Ensures that the specified shared library has its symbols loaded. 43 | 44 | This function checks whether the shared object (SO) file has already been 45 | loaded in GDB. It does so by comparing only the file names (not full paths) 46 | to avoid false positives. If the library isn't recognized, it attempts to 47 | manually add its symbols. 48 | 49 | Args: 50 | lib_path (str): Full path to the shared library. 51 | lib_addr (int): Base address where the library is loaded. 52 | """ 53 | loaded_lib_names = list( 54 | set( 55 | so.filename.split("/")[-1] 56 | for so in objfiles()[1:] # Skip the main executable 57 | if so.filename is not None and " " not in so.filename 58 | ) 59 | ) 60 | lib_name = lib_path.split("/")[-1] 61 | 62 | if lib_name not in loaded_lib_names: 63 | try_manually_adding_symbols(lib_path, lib_addr) 64 | 65 | 66 | class BrkReloadAllSharedLib(Breakpoint): 67 | """Breakpoint that ensures symbols are loaded for all shared libraries. 68 | 69 | This breakpoint is placed at `_r_debug.r_brk`, which is triggered when 70 | shared libraries are loaded or unloaded. It iterates over the linked list 71 | of loaded libraries (`r_map`), checking for any that are missing symbols 72 | and loading them if necessary. 73 | """ 74 | 75 | def __init__(self, debug: Value, brk: Value): 76 | super().__init__(f"*{int(brk)}", BP_BREAKPOINT, internal=False) 77 | self.debug = debug 78 | 79 | def stop(self) -> bool: 80 | """Handles the breakpoint hit by processing the shared library list. 81 | 82 | This function traverses the `r_map` linked list to identify newly 83 | loaded shared libraries that may be missing symbols. It then loads 84 | the missing symbols into GDB. 85 | 86 | Returns: 87 | bool: Always returns `False` to continue execution after the breakpoint. 88 | """ 89 | execute("set language c") 90 | link = self.debug.dereference()["r_map"] 91 | 92 | # Traverse the linked list of loaded shared objects 93 | while link != 0: 94 | # Extract and clean up the library path from the C string 95 | lib_path = str(link.dereference()["l_name"]).split(" ")[1][1:-1].strip() 96 | if lib_path == "" or lib_path == "linux-vdso.so.1": 97 | link = link.dereference()["l_next"] 98 | continue 99 | 100 | # Get the memory address where the shared library is loaded 101 | l_addr = int(link.dereference()["l_addr"]) 102 | 103 | ensure_shared_library(lib_path, l_addr) 104 | link = link.dereference()["l_next"] 105 | 106 | execute("set language auto") 107 | return False 108 | 109 | 110 | def object_event_hook(event: NewObjFileEvent) -> object: 111 | """Handles new shared object file events in GDB. 112 | 113 | When a new shared object file is loaded, this function checks if `_r_debug` 114 | is properly set up and places a breakpoint at `_r_debug.r_brk` if needed. 115 | 116 | Args: 117 | event (NewObjFileEvent): GDB event triggered by loading a new object file. 118 | """ 119 | global BRK_INIT 120 | execute("set language c") 121 | debug = parse_and_eval("(struct r_debug *) &_r_debug") 122 | r_brk = debug.dereference()["r_brk"] 123 | 124 | # Ensure `_r_debug.r_brk` is set before placing the breakpoint 125 | if not BRK_INIT and r_brk != 0: 126 | BrkReloadAllSharedLib(debug, r_brk) 127 | BRK_INIT = True 128 | execute("set language auto") 129 | 130 | 131 | # Register the event hook to track shared library loading. 132 | # The `_r_debug` structure is not exposed initially in GDB, but can be accessed 133 | # once the program is in the correct execution state. This hook ensures that 134 | # when a new shared object is loaded, we properly set up the breakpoint to handle it. 135 | events.new_objfile.connect(object_event_hook) 136 | -------------------------------------------------------------------------------- /src/cache.rs: -------------------------------------------------------------------------------- 1 | use crate::{Error, parse_ld_cache_error}; 2 | use crate::{Result, dlopen::ElfPath}; 3 | use alloc::boxed::Box; 4 | use alloc::{string::String, vec::Vec}; 5 | 6 | impl From for Error { 7 | fn from(value: syscalls::Errno) -> Self { 8 | parse_ld_cache_error(value) 9 | } 10 | } 11 | 12 | pub(super) fn build_ld_cache() -> Result> { 13 | // 尝试读取 /etc/ld.so.cache 文件 14 | let path = b"/etc/ld.so.cache\0"; // C字符串需要以null结尾 15 | 16 | const O_RDONLY: usize = 0; 17 | // 使用系统调用打开文件 18 | let fd = 19 | unsafe { syscalls::syscall2(syscalls::Sysno::open, path.as_ptr() as usize, O_RDONLY)? }; 20 | 21 | const SEEK_END: usize = 2; 22 | const SEEK_SET: usize = 0; 23 | // 获取文件大小 24 | let file_size = 25 | unsafe { syscalls::syscall3(syscalls::Sysno::lseek, fd as usize, 0, SEEK_END)? }; 26 | 27 | // 重置文件指针到开头 28 | unsafe { syscalls::syscall3(syscalls::Sysno::lseek, fd as usize, 0, SEEK_SET)? }; 29 | 30 | // 分配内存并读取文件内容 31 | let mut buffer = Vec::with_capacity(file_size); 32 | unsafe { 33 | buffer.set_len(file_size); 34 | } 35 | 36 | let bytes_read = unsafe { 37 | syscalls::syscall3( 38 | syscalls::Sysno::read, 39 | fd as usize, 40 | buffer.as_mut_ptr() as usize, 41 | file_size, 42 | )? 43 | }; 44 | 45 | // 关闭文件 46 | unsafe { syscalls::syscall1(syscalls::Sysno::close, fd as usize)? }; 47 | 48 | if bytes_read != file_size { 49 | return Err(parse_ld_cache_error( 50 | "Failed to read complete ld.so.cache file", 51 | )); 52 | } 53 | 54 | // 解析 ld.so.cache 内容 55 | parse_ld_cache(&buffer) 56 | } 57 | 58 | fn parse_ld_cache(data: &[u8]) -> Result> { 59 | // 检查新格式的魔数 60 | let new_cache_magic = b"glibc-ld.so.cache"; 61 | let new_cache_version = b"1.1"; 62 | 63 | if data.len() > 20 64 | && data[0..new_cache_magic.len()] == *new_cache_magic 65 | && data[17..20] == *new_cache_version 66 | { 67 | parse_new_format(data) 68 | } else { 69 | // 不支持的格式或旧格式 70 | return Err(parse_ld_cache_error("Unsupported ld.so.cache format")); 71 | } 72 | } 73 | 74 | fn parse_new_format(data: &[u8]) -> Result> { 75 | if data.len() < 32 { 76 | return Err(parse_ld_cache_error("ld.so.cache file is too small")); 77 | } 78 | 79 | // 解析头部信息 80 | let header_size = 32; 81 | let nlibs = u32::from_le_bytes([data[20], data[21], data[22], data[23]]); 82 | let len_strings = u32::from_le_bytes([data[24], data[25], data[26], data[27]]) as usize; 83 | let _flags = data[28]; 84 | let _extension_offset = u32::from_le_bytes([data[29], data[30], data[31], data[32]]) as usize; 85 | 86 | // 计算字符串表的偏移量 87 | let string_table_offset = header_size + (nlibs as usize) * 32; // 每个条目32字节 88 | 89 | // 修正边界检查逻辑,确保字符串表在文件范围内 90 | if string_table_offset > data.len() { 91 | return Err(parse_ld_cache_error("Invalid ld.so.cache format: entries exceed file size")); 92 | } 93 | 94 | // 如果字符串表长度为0,或者超出文件范围,则使用从string_table_offset到文件末尾的所有数据 95 | let string_table_end = if len_strings == 0 || string_table_offset + len_strings > data.len() { 96 | data.len() 97 | } else { 98 | string_table_offset + len_strings 99 | }; 100 | 101 | // 提取字符串表 102 | let string_table = &data[string_table_offset..string_table_end]; 103 | 104 | // 解析条目并提取路径 105 | let mut paths = Vec::new(); 106 | let mut offset = header_size; 107 | let mut entry_count = 0; 108 | 109 | while offset + 32 <= string_table_offset && entry_count < nlibs as usize { 110 | // 解析条目中的value字段(库文件路径的偏移量) 111 | if offset + 16 > data.len() { 112 | break; 113 | } 114 | 115 | let value_offset = u32::from_le_bytes([ 116 | data[offset + 8], 117 | data[offset + 9], 118 | data[offset + 10], 119 | data[offset + 11], 120 | ]) as usize; 121 | 122 | // 从字符串表中提取路径 123 | if value_offset < string_table.len() { 124 | if let Some(path_str) = extract_string(string_table, value_offset) { 125 | // 获取目录部分 126 | if let Some(parent_path) = get_parent_path(&path_str) { 127 | if let Ok(elf_path) = ElfPath::from_str(&parent_path) { 128 | // 避免重复添加相同的路径 129 | if !paths.contains(&elf_path) { 130 | paths.push(elf_path); 131 | } 132 | } 133 | } 134 | } 135 | } 136 | 137 | offset += 32; // 下一个条目 138 | entry_count += 1; 139 | } 140 | 141 | Ok(paths.into_boxed_slice()) 142 | } 143 | 144 | fn extract_string(data: &[u8], offset: usize) -> Option { 145 | if offset >= data.len() { 146 | return None; 147 | } 148 | 149 | let mut end = offset; 150 | while end < data.len() && data[end] != 0 { 151 | end += 1; 152 | } 153 | 154 | if end > offset { 155 | match core::str::from_utf8(&data[offset..end]) { 156 | Ok(s) => Some(String::from(s)), 157 | Err(_) => None, 158 | } 159 | } else { 160 | None 161 | } 162 | } 163 | 164 | fn get_parent_path(path: &str) -> Option { 165 | // 查找最后一个 '/' 字符 166 | if let Some(last_slash) = path.rfind('/') { 167 | if last_slash > 0 { 168 | Some(String::from(&path[..last_slash])) 169 | } else { 170 | Some(String::from("/")) 171 | } 172 | } else { 173 | None 174 | } 175 | } -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //!A Rust library that implements a series of interfaces such as `dlopen` and `dlsym`, consistent with the behavior of libc, 2 | //!providing robust support for dynamic library loading and symbol resolution. 3 | //! 4 | //!This library serves four purposes: 5 | //!1. Provide a pure Rust alternative to musl ld.so or glibc ld.so. 6 | //!2. Provide loading ELF dynamic libraries support for `#![no_std]` targets. 7 | //!3. Easily swap out symbols in shared libraries with your own custom symbols at runtime 8 | //!4. Faster than `ld.so` in most cases (loading dynamic libraries and getting symbols) 9 | //! 10 | //!Currently, it supports `x86_64`, `RV64`, and `AArch64` architectures. 11 | //! 12 | //! # Examples 13 | //! ```no_run 14 | //! # use dlopen_rs::{ElfLibrary, OpenFlags}; 15 | //! use std::path::Path; 16 | //! 17 | //! fn main(){ 18 | //! dlopen_rs::init(); 19 | //! let path = Path::new("./target/release/libexample.so"); 20 | //! let libexample = ElfLibrary::dlopen(path, OpenFlags::RTLD_LOCAL | OpenFlags::RTLD_LAZY).unwrap(); 21 | //! 22 | //! let add = unsafe { 23 | //! libexample.get:: i32>("add").unwrap() 24 | //! }; 25 | //! println!("{}", add(1,1)); 26 | //! } 27 | //! ``` 28 | #![allow(clippy::type_complexity)] 29 | #![warn( 30 | clippy::unnecessary_lazy_evaluations, 31 | clippy::collapsible_if, 32 | clippy::explicit_iter_loop, 33 | clippy::manual_assert, 34 | clippy::needless_question_mark, 35 | clippy::needless_return, 36 | clippy::needless_update, 37 | clippy::redundant_clone, 38 | clippy::redundant_else, 39 | clippy::redundant_static_lifetimes 40 | )] 41 | #![no_std] 42 | 43 | extern crate alloc; 44 | 45 | pub mod abi; 46 | mod arch; 47 | #[cfg(feature = "fs")] 48 | mod cache; 49 | #[cfg(feature = "debug")] 50 | mod debug; 51 | mod dl_iterate_phdr; 52 | mod dladdr; 53 | mod dlopen; 54 | mod dlsym; 55 | mod find; 56 | #[cfg(feature = "use-ldso")] 57 | mod init; 58 | mod loader; 59 | mod register; 60 | #[cfg(feature = "tls")] 61 | mod tls; 62 | use alloc::{ 63 | boxed::Box, 64 | string::{String, ToString}, 65 | }; 66 | use bitflags::bitflags; 67 | use core::{ 68 | any::Any, 69 | ffi::{c_char, c_void}, 70 | fmt::Display, 71 | }; 72 | use elf_loader::arch::Dyn; 73 | 74 | pub use elf_loader::{Symbol, mmap::Mmap}; 75 | #[cfg(feature = "use-ldso")] 76 | pub use init::init; 77 | pub use loader::{Builder, ElfLibrary}; 78 | 79 | #[cfg(not(any( 80 | target_arch = "x86_64", 81 | target_arch = "aarch64", 82 | target_arch = "riscv64", 83 | )))] 84 | compile_error!("unsupport arch"); 85 | 86 | bitflags! { 87 | /// Control how dynamic libraries are loaded. 88 | #[derive(Clone, Copy, Debug)] 89 | pub struct OpenFlags:u32{ 90 | /// This is the converse of RTLD_GLOBAL, and the default if neither flag is specified. 91 | /// Symbols defined in this shared object are not made available to resolve references in subsequently loaded shared objects. 92 | const RTLD_LOCAL = 0; 93 | /// Perform lazy binding. Resolve symbols only as the code that references them is executed. 94 | /// If the symbol is never referenced, then it is never resolved. 95 | const RTLD_LAZY = 1; 96 | /// If this value is specified, or the environment variable LD_BIND_NOW is set to a nonempty string, 97 | /// all undefined symbols in the shared object are resolved before dlopen() returns. 98 | const RTLD_NOW= 2; 99 | /// Not supported 100 | const RTLD_NOLOAD = 4; 101 | /// Not supported 102 | const RTLD_DEEPBIND =8; 103 | /// The symbols defined by this shared object will be made available for symbol resolution of subsequently loaded shared objects. 104 | const RTLD_GLOBAL = 256; 105 | /// Do not unload the shared object during dlclose(). Consequently, 106 | /// the object's static and global variables are not reinitialized if the object is reloaded with dlopen() at a later time. 107 | const RTLD_NODELETE = 4096; 108 | /// dlopen-rs custom flag, true local loading, does not involve any global variable operations, no lock, and has the fastest loading speed. 109 | const CUSTOM_NOT_REGISTER = 1024; 110 | } 111 | } 112 | 113 | /// dlopen-rs error type 114 | #[derive(Debug)] 115 | pub enum Error { 116 | /// Returned when encountered a loader error. 117 | LoaderError { err: elf_loader::Error }, 118 | /// Returned when failed to find a library. 119 | FindLibError { msg: String }, 120 | /// Returned when failed to find a symbol. 121 | FindSymbolError { msg: String }, 122 | /// Returned when failed to iterate phdr. 123 | IteratorPhdrError { err: Box }, 124 | /// Returned when failed to parse ld.so.cache. 125 | ParseLdCacheError { msg: String }, 126 | } 127 | 128 | impl Display for Error { 129 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 130 | match self { 131 | Error::LoaderError { err } => write!(f, "{err}"), 132 | Error::FindLibError { msg } => write!(f, "{msg}"), 133 | Error::FindSymbolError { msg } => write!(f, "{msg}"), 134 | Error::IteratorPhdrError { err } => write!(f, "{:?}", err), 135 | Error::ParseLdCacheError { msg } => write!(f, "{msg}"), 136 | } 137 | } 138 | } 139 | 140 | impl From for Error { 141 | #[cold] 142 | fn from(value: elf_loader::Error) -> Self { 143 | Error::LoaderError { err: value } 144 | } 145 | } 146 | 147 | #[cold] 148 | #[inline(never)] 149 | fn find_lib_error(msg: impl ToString) -> Error { 150 | Error::FindLibError { 151 | msg: msg.to_string(), 152 | } 153 | } 154 | 155 | #[cold] 156 | #[inline(never)] 157 | fn find_symbol_error(msg: impl ToString) -> Error { 158 | Error::FindSymbolError { 159 | msg: msg.to_string(), 160 | } 161 | } 162 | 163 | #[cold] 164 | #[inline(never)] 165 | fn parse_ld_cache_error(msg: impl ToString) -> Error { 166 | Error::ParseLdCacheError { 167 | msg: msg.to_string(), 168 | } 169 | } 170 | 171 | #[repr(C)] 172 | pub(crate) struct LinkMap { 173 | pub l_addr: *mut c_void, 174 | pub l_name: *const c_char, 175 | pub l_ld: *mut Dyn, 176 | pub l_next: *mut LinkMap, 177 | pub l_prev: *mut LinkMap, 178 | } 179 | 180 | pub type Result = core::result::Result; 181 | -------------------------------------------------------------------------------- /src/register.rs: -------------------------------------------------------------------------------- 1 | use crate::{ElfLibrary, OpenFlags}; 2 | use alloc::{borrow::ToOwned, boxed::Box, string::String, sync::Arc}; 3 | use elf_loader::RelocatedDylib; 4 | use indexmap::IndexMap; 5 | use spin::{Lazy, RwLock}; 6 | 7 | impl Drop for ElfLibrary { 8 | fn drop(&mut self) { 9 | if self.flags.contains(OpenFlags::RTLD_NODELETE) 10 | | self.flags.contains(OpenFlags::CUSTOM_NOT_REGISTER) 11 | { 12 | return; 13 | } 14 | let mut lock = MANAGER.write(); 15 | let ref_count = self.inner.strong_count(); 16 | // Dylib本身 + 全局 17 | let threshold = 18 | 2 + self.deps.is_some() as usize + self.flags.contains(OpenFlags::RTLD_GLOBAL) as usize; 19 | if ref_count == threshold { 20 | #[cfg(feature = "tls")] 21 | crate::tls::update_generation(); 22 | log::info!("Destroying dylib [{}]", self.inner.shortname()); 23 | lock.all.shift_remove(self.inner.shortname()); 24 | if self.flags.contains(OpenFlags::RTLD_GLOBAL) { 25 | lock.global.shift_remove(self.inner.shortname()); 26 | } 27 | for dep in self.deps.as_ref().unwrap().iter().skip(1) { 28 | let dep_threshold = if let Some(lib) = lock.all.get(dep.shortname()) { 29 | if lib.flags.contains(OpenFlags::RTLD_NODELETE) { 30 | continue; 31 | } 32 | 2 + lib.deps.is_some() as usize 33 | + lib.flags.contains(OpenFlags::RTLD_GLOBAL) as usize 34 | } else { 35 | continue; 36 | }; 37 | if dep.strong_count() == dep_threshold { 38 | log::info!("Destroying dylib [{}]", dep.shortname()); 39 | lock.all.shift_remove(dep.shortname()); 40 | lock.global.shift_remove(self.inner.shortname()); 41 | } 42 | } 43 | } 44 | } 45 | } 46 | 47 | #[derive(Clone, Copy, Default)] 48 | pub(crate) struct DylibState(u8); 49 | 50 | impl DylibState { 51 | const USED_MASK: u8 = 0b10000000; 52 | const RELOCATED: u8 = 0b01111111; 53 | 54 | #[inline] 55 | pub(crate) fn set_unused(&mut self) -> &mut Self { 56 | self.0 &= !Self::USED_MASK; 57 | self 58 | } 59 | 60 | #[inline] 61 | pub(crate) fn set_used(&mut self) -> &mut Self { 62 | self.0 |= Self::USED_MASK; 63 | self 64 | } 65 | 66 | #[inline] 67 | pub(crate) fn is_used(&self) -> bool { 68 | self.0 & Self::USED_MASK != 0 69 | } 70 | 71 | #[inline] 72 | pub(crate) fn get_new_idx(&self) -> Option { 73 | let remove_used_bit = self.0 & !Self::USED_MASK; 74 | if remove_used_bit == Self::RELOCATED { 75 | None 76 | } else { 77 | Some(remove_used_bit) 78 | } 79 | } 80 | 81 | #[inline] 82 | pub(crate) fn set_relocated(&mut self) -> &mut Self { 83 | self.0 |= Self::RELOCATED; 84 | self 85 | } 86 | 87 | #[allow(unused)] 88 | #[inline] 89 | pub(crate) fn set_new_idx(&mut self, idx: u8) -> &mut Self { 90 | assert!(idx < Self::RELOCATED); 91 | self.0 |= idx; 92 | self 93 | } 94 | } 95 | 96 | #[derive(Clone)] 97 | pub(crate) struct GlobalDylib { 98 | inner: RelocatedDylib<'static>, 99 | flags: OpenFlags, 100 | deps: Option]>>>, 101 | pub(crate) state: DylibState, 102 | } 103 | 104 | unsafe impl Send for GlobalDylib {} 105 | unsafe impl Sync for GlobalDylib {} 106 | 107 | impl GlobalDylib { 108 | #[inline] 109 | pub(crate) fn get_dylib(&self) -> ElfLibrary { 110 | debug_assert!(self.deps.is_some()); 111 | ElfLibrary { 112 | inner: self.inner.clone(), 113 | flags: self.flags, 114 | deps: self.deps.clone(), 115 | } 116 | } 117 | 118 | #[inline] 119 | pub(crate) fn set_flags(&mut self, flags: OpenFlags) { 120 | self.flags = flags; 121 | } 122 | 123 | #[inline] 124 | pub(crate) fn flags(&self) -> OpenFlags { 125 | self.flags 126 | } 127 | 128 | #[inline] 129 | pub(crate) fn relocated_dylib(&self) -> RelocatedDylib<'static> { 130 | self.inner.clone() 131 | } 132 | 133 | #[inline] 134 | pub(crate) fn relocated_dylib_ref(&self) -> &RelocatedDylib<'static> { 135 | &self.inner 136 | } 137 | 138 | #[inline] 139 | pub(crate) fn shortname(&self) -> &str { 140 | self.inner.shortname() 141 | } 142 | 143 | #[inline] 144 | pub(crate) fn deps(&self) -> Option<&Arc]>>> { 145 | self.deps.as_ref() 146 | } 147 | } 148 | 149 | pub(crate) struct Manager { 150 | pub(crate) all: IndexMap, 151 | pub(crate) global: IndexMap>, 152 | } 153 | 154 | pub(crate) static MANAGER: Lazy> = Lazy::new(|| { 155 | RwLock::new(Manager { 156 | all: IndexMap::new(), 157 | global: IndexMap::new(), 158 | }) 159 | }); 160 | 161 | pub(crate) fn register( 162 | lib: RelocatedDylib<'static>, 163 | flags: OpenFlags, 164 | deps: Option]>>>, 165 | manager: &mut Manager, 166 | state: DylibState, 167 | ) { 168 | let shortname = lib.shortname().to_owned(); 169 | log::debug!( 170 | "Trying to register a library. Name: [{}] flags:[{:?}]", 171 | shortname, 172 | flags 173 | ); 174 | manager.all.insert( 175 | shortname.to_owned(), 176 | GlobalDylib { 177 | state, 178 | inner: lib.clone(), 179 | flags, 180 | deps, 181 | }, 182 | ); 183 | if flags.contains(OpenFlags::RTLD_GLOBAL) { 184 | manager.global.insert(shortname, lib); 185 | } 186 | } 187 | 188 | pub(crate) fn global_find(name: &str) -> Option<*const ()> { 189 | log::debug!("Lazy Binding: [{}]", name); 190 | MANAGER.read().global.values().find_map(|lib| unsafe { 191 | lib.get::<()>(name).map(|sym| { 192 | log::trace!( 193 | "Lazy Binding: find symbol [{}] from [{}] in global scope ", 194 | name, 195 | lib.name() 196 | ); 197 | let val = sym.into_raw(); 198 | assert!(lib.base() != val as usize); 199 | val 200 | }) 201 | }) 202 | } 203 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2024 wzhao 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/init.rs: -------------------------------------------------------------------------------- 1 | use crate::LinkMap; 2 | use crate::arch::{ModifyRegister, ThreadRegister}; 3 | use crate::tls::{ 4 | DTV_OFFSET, TLS_GENERATION, TLS_STATIC_ALIGN, TLS_STATIC_SIZE, TlsState, add_tls, init_tls, 5 | }; 6 | use crate::{ 7 | OpenFlags, Result, 8 | abi::CDlPhdrInfo, 9 | dl_iterate_phdr::CallBack, 10 | loader::{EH_FRAME_ID, EhFrame}, 11 | register::{DylibState, MANAGER, global_find, register}, 12 | }; 13 | use alloc::{borrow::ToOwned, boxed::Box, ffi::CString, sync::Arc, vec::Vec}; 14 | use core::{ 15 | ffi::{CStr, c_char, c_int, c_void}, 16 | mem::ManuallyDrop, 17 | num::NonZero, 18 | ptr::{NonNull, addr_of_mut}, 19 | }; 20 | use elf_loader::{ 21 | RelocatedDylib, Symbol, UserData, 22 | abi::{PT_DYNAMIC, PT_GNU_EH_FRAME, PT_LOAD, PT_TLS}, 23 | arch::{Dyn, ElfPhdr}, 24 | dynamic::ElfDynamic, 25 | segment::{ElfSegments, MASK, PAGE_SIZE}, 26 | set_global_scope, 27 | }; 28 | use spin::Once; 29 | 30 | #[repr(C)] 31 | pub(crate) struct GDBDebug { 32 | pub version: c_int, 33 | pub map: *mut LinkMap, 34 | pub brk: extern "C" fn(), 35 | pub state: c_int, 36 | pub ldbase: *mut c_void, 37 | } 38 | 39 | #[cfg(target_env = "gnu")] 40 | #[inline] 41 | fn get_debug_struct() -> &'static mut GDBDebug { 42 | unsafe extern "C" { 43 | static mut _r_debug: GDBDebug; 44 | } 45 | unsafe { &mut *addr_of_mut!(_r_debug) } 46 | } 47 | 48 | // 静态链接的musl中没有_dl_debug_addr这个符号,无法通过编译,因此需要生成dyn格式的可执行文件 49 | #[cfg(target_env = "musl")] 50 | #[inline] 51 | fn get_debug_struct() -> &'static mut GDBDebug { 52 | unsafe extern "C" { 53 | static mut _dl_debug_addr: GDBDebug; 54 | } 55 | unsafe { &mut *addr_of_mut!(_dl_debug_addr) } 56 | } 57 | 58 | static ONCE: Once = Once::new(); 59 | //static mut PROGRAM_NAME: Option = None; 60 | 61 | pub(crate) static mut ARGC: usize = 0; 62 | pub(crate) static mut ARGV: Vec<*mut c_char> = Vec::new(); 63 | pub(crate) static mut ENVP: usize = 0; 64 | 65 | fn create_segments(base: usize, len: usize) -> Option { 66 | let memory = if let Some(memory) = NonNull::new(base as _) { 67 | memory 68 | } else { 69 | // 如果程序本身不是Shared object file,那么它的这个字段为0,此时无法使用程序本身的符号进行重定位 70 | // log::warn!( 71 | // "Failed to initialize an existing library: [{:?}], Because it's not a Shared object file", 72 | // unsafe { (*addr_of!(PROGRAM_NAME)).as_ref().unwrap() } 73 | // ); 74 | return None; 75 | }; 76 | unsafe fn drop_handle(_handle: NonNull, _len: usize) -> elf_loader::Result<()> { 77 | Ok(()) 78 | } 79 | Some(ElfSegments::new(memory, len, drop_handle)) 80 | } 81 | 82 | unsafe fn from_raw( 83 | name: CString, 84 | segments: ElfSegments, 85 | dynamic_ptr: *const Dyn, 86 | extra: Option<(&'static [ElfPhdr], &StaticTlsInfo, usize)>, 87 | ) -> Result>> { 88 | #[allow(unused_mut)] 89 | let mut dynamic = ElfDynamic::new(dynamic_ptr, &segments)?; 90 | 91 | // 因为glibc会修改dynamic段中的信息,所以这里需要手动恢复一下。 92 | if !name.to_str().unwrap().contains("linux-vdso.so.1") { 93 | let base = segments.base(); 94 | if dynamic.strtab > 2 * base { 95 | dynamic.strtab -= base; 96 | dynamic.symtab -= base; 97 | dynamic.hashtab -= base; 98 | dynamic.version_idx = dynamic 99 | .version_idx 100 | .map(|v| NonZero::new(v.get() - base).unwrap()); 101 | } 102 | } 103 | 104 | #[allow(unused_mut)] 105 | let mut user_data = UserData::empty(); 106 | #[cfg(feature = "debug")] 107 | unsafe { 108 | if extra.is_some() { 109 | use super::debug::*; 110 | user_data.insert( 111 | crate::loader::DEBUG_INFO_ID, 112 | Box::new(DebugInfo::new( 113 | segments.base(), 114 | name.as_ptr() as _, 115 | dynamic_ptr as usize, 116 | )), 117 | ); 118 | } 119 | }; 120 | let mut use_phdrs: &[ElfPhdr] = &[]; 121 | let len = if let Some((phdrs, tls, modid)) = extra { 122 | let mut min_vaddr = usize::MAX; 123 | let mut max_vaddr = 0; 124 | phdrs.iter().for_each(|phdr| { 125 | if phdr.p_type == PT_LOAD { 126 | min_vaddr = min_vaddr.min(phdr.p_vaddr as usize & MASK); 127 | max_vaddr = max_vaddr 128 | .max((phdr.p_vaddr as usize + phdr.p_memsz as usize + PAGE_SIZE - 1) & MASK); 129 | } else if phdr.p_type == PT_GNU_EH_FRAME { 130 | user_data.insert( 131 | EH_FRAME_ID, 132 | Box::new(EhFrame::new(phdr.p_vaddr as usize + segments.base())), 133 | ); 134 | } else if phdr.p_type == PT_TLS { 135 | add_tls( 136 | &segments, 137 | phdr, 138 | &mut user_data, 139 | TlsState::Initialized(tls.get_offset(modid - 1)), 140 | ); 141 | } 142 | }); 143 | use_phdrs = phdrs; 144 | max_vaddr - min_vaddr 145 | } else { 146 | usize::MAX 147 | }; 148 | let new_segments: ElfSegments = create_segments(segments.base(), len).unwrap(); 149 | let lib = unsafe { 150 | RelocatedDylib::new_uncheck( 151 | name, 152 | new_segments.base(), 153 | dynamic, 154 | use_phdrs, 155 | new_segments, 156 | user_data, 157 | ) 158 | }; 159 | Ok(Some(lib)) 160 | } 161 | 162 | type IterPhdr = extern "C" fn(callback: Option, data: *mut c_void) -> c_int; 163 | 164 | // 寻找libc中的dl_iterate_phdr函数 165 | fn iterate_phdr(start: *const LinkMap, mut f: impl FnMut(Symbol)) { 166 | let mut cur_map_ptr = start; 167 | let mut needed_libs = Vec::new(); 168 | while !cur_map_ptr.is_null() { 169 | let cur_map = unsafe { &*cur_map_ptr }; 170 | let name = unsafe { CStr::from_ptr(cur_map.l_name).to_owned() }; 171 | let Some(segments) = create_segments(cur_map.l_addr as usize, usize::MAX) else { 172 | cur_map_ptr = cur_map.l_next; 173 | continue; 174 | }; 175 | if let Some(lib) = unsafe { from_raw(name, segments, cur_map.l_ld, None).unwrap() } { 176 | let lib_name = lib.name(); 177 | if lib_name.contains("libc.so") || lib_name.contains("ld-") { 178 | needed_libs.push(lib); 179 | // 目前只要用这两个 180 | if needed_libs.len() >= 2 { 181 | break; 182 | } 183 | } 184 | }; 185 | cur_map_ptr = cur_map.l_next; 186 | } 187 | assert!(needed_libs.len() == 2); 188 | for lib in needed_libs { 189 | if lib.name().contains("libc.so") { 190 | f(unsafe { lib.get::("dl_iterate_phdr").unwrap() }); 191 | } else if lib.name().contains("ld-") { 192 | let mut tls_static_size: usize = 0; 193 | let mut tls_static_align: usize = 0; 194 | unsafe { 195 | lib.get::("_dl_get_tls_static_info") 196 | .unwrap()(&mut tls_static_size, &mut tls_static_align); 197 | } 198 | unsafe { TLS_STATIC_SIZE = tls_static_size }; 199 | unsafe { TLS_STATIC_ALIGN = tls_static_align }; 200 | log::debug!( 201 | "tls static info: size: {}, align: {}", 202 | tls_static_size, 203 | tls_static_align 204 | ) 205 | } 206 | } 207 | } 208 | 209 | fn init_argv() { 210 | // let mut argv = Vec::new(); 211 | // for arg in env::args_os() { 212 | // argv.push(CString::new(arg.into_vec()).unwrap().into_raw()); 213 | // } 214 | // argv.push(null_mut()); 215 | // unsafe { 216 | // ARGC = argv.len(); 217 | // ARGV = argv; 218 | // ENVP = environ; 219 | // } 220 | } 221 | 222 | #[repr(C)] 223 | struct DtvPointer { 224 | val: *const c_void, 225 | free: *const c_void, 226 | } 227 | 228 | #[repr(C)] 229 | union Dtv { 230 | counter: usize, 231 | pointer: ManuallyDrop, 232 | } 233 | 234 | struct StaticTlsInfo { 235 | dtv: &'static [Dtv], 236 | tcb: usize, 237 | } 238 | 239 | impl StaticTlsInfo { 240 | fn new() -> StaticTlsInfo { 241 | let tcb = ThreadRegister::base(); 242 | let dtv = unsafe { ThreadRegister::get::<*const Dtv>(DTV_OFFSET) }; 243 | let count = unsafe { dtv.sub(1).read().counter }; 244 | let slice = unsafe { core::slice::from_raw_parts(dtv.add(1), count) }; 245 | StaticTlsInfo { dtv: slice, tcb } 246 | } 247 | 248 | fn get_offset(&self, modid: usize) -> usize { 249 | unsafe { self.tcb.wrapping_sub(self.dtv[modid].pointer.val as usize) } 250 | } 251 | } 252 | 253 | unsafe extern "C" fn callback(info: *mut CDlPhdrInfo, _size: usize, data: *mut c_void) -> c_int { 254 | let info = unsafe { &*info }; 255 | let static_tls_info = unsafe { &mut *(data as *mut StaticTlsInfo) }; 256 | let base = info.dlpi_addr; 257 | let phdrs = unsafe { core::slice::from_raw_parts(info.dlpi_phdr, info.dlpi_phnum as usize) }; 258 | let dynamic_ptr = phdrs 259 | .iter() 260 | .find_map(|phdr| { 261 | if phdr.p_type == PT_DYNAMIC { 262 | Some(base + phdr.p_vaddr as usize) 263 | } else { 264 | None 265 | } 266 | }) 267 | .unwrap() as _; 268 | let Some(segments) = create_segments(base, usize::MAX) else { 269 | return 0; 270 | }; 271 | let Some(lib) = unsafe { 272 | from_raw( 273 | CStr::from_ptr(info.dlpi_name).to_owned(), 274 | segments, 275 | dynamic_ptr, 276 | Some((phdrs, static_tls_info, info.dlpi_tls_modid)), 277 | ) 278 | } 279 | .unwrap() else { 280 | return 0; 281 | }; 282 | let flags = OpenFlags::RTLD_NODELETE | OpenFlags::RTLD_GLOBAL; 283 | let mut temp = Vec::new(); 284 | temp.push(lib.clone()); 285 | let deps = Some(Arc::new(temp.into_boxed_slice())); 286 | let start = lib.base(); 287 | let end = start + lib.map_len(); 288 | let shortname = lib.shortname(); 289 | let name = if shortname.is_empty() { 290 | // unsafe { 291 | // (*addr_of!(PROGRAM_NAME)) 292 | // .as_ref() 293 | // .unwrap() 294 | // .to_str() 295 | // .unwrap() 296 | // } 297 | "" 298 | } else { 299 | shortname 300 | }; 301 | log::info!( 302 | "Initialize an existing library: [{:?}] [{:#x}]-[{:#x}]", 303 | name, 304 | start, 305 | end, 306 | ); 307 | 308 | register( 309 | lib, 310 | flags, 311 | deps, 312 | &mut MANAGER.write(), 313 | *DylibState::default().set_relocated(), 314 | ); 315 | 0 316 | } 317 | /// `init` is responsible for the initialization of dlopen_rs, If you want to use the dynamic library that the program itself depends on, 318 | /// or want to use the debug function, please call it at the beginning. This is usually necessary. 319 | pub fn init() { 320 | ONCE.call_once(|| { 321 | init_argv(); 322 | // let program_self = env::current_exe().unwrap(); 323 | // unsafe { PROGRAM_NAME = Some(program_self) }; 324 | let debug = get_debug_struct(); 325 | iterate_phdr(debug.map, |iter| { 326 | #[cfg(feature = "debug")] 327 | crate::debug::init_debug(debug); 328 | let mut tls_info = StaticTlsInfo::new(); 329 | iter(Some(callback), &mut tls_info as *const _ as *mut _); 330 | TLS_GENERATION.fetch_add(1, core::sync::atomic::Ordering::Relaxed); 331 | }); 332 | init_tls(); 333 | unsafe { set_global_scope(global_find as _) }; 334 | log::info!("Initialization is complete"); 335 | }); 336 | } 337 | -------------------------------------------------------------------------------- /src/loader.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "debug")] 2 | use super::debug::DebugInfo; 3 | use crate::{OpenFlags, Result, find_symbol_error}; 4 | use alloc::{boxed::Box, format, sync::Arc, vec::Vec}; 5 | use core::{any::Any, ffi::CStr, fmt::Debug}; 6 | use elf_loader::{ 7 | CoreComponent, CoreComponentRef, ElfDylib, Loader, RelocatedDylib, Symbol, UserData, 8 | abi::PT_GNU_EH_FRAME, 9 | arch::{ElfPhdr, ElfRela}, 10 | mmap::{Mmap, MmapImpl}, 11 | object::{ElfBinary, ElfObject}, 12 | segment::ElfSegments, 13 | }; 14 | 15 | pub(crate) const EH_FRAME_ID: u8 = 0; 16 | #[cfg(feature = "debug")] 17 | pub(crate) const DEBUG_INFO_ID: u8 = 1; 18 | 19 | pub(crate) struct EhFrame(pub usize); 20 | 21 | impl EhFrame { 22 | pub(crate) fn new(eh_frame_hdr: usize) -> Self { 23 | EhFrame(eh_frame_hdr) 24 | } 25 | } 26 | 27 | #[inline] 28 | pub(crate) fn find_symbol<'lib, T>( 29 | libs: &'lib [RelocatedDylib<'static>], 30 | name: &str, 31 | ) -> Result> { 32 | log::info!("Get the symbol [{}] in [{}]", name, libs[0].shortname()); 33 | libs.iter() 34 | .find_map(|lib| unsafe { lib.get::(name) }) 35 | .ok_or(find_symbol_error(format!("can not find symbol:{}", name))) 36 | } 37 | 38 | pub trait Builder { 39 | fn create_object(path: &str) -> Result; 40 | } 41 | 42 | #[inline(always)] 43 | #[allow(unused)] 44 | fn parse_phdr( 45 | cname: &CStr, 46 | phdr: &ElfPhdr, 47 | segments: &ElfSegments, 48 | data: &mut UserData, 49 | ) -> core::result::Result<(), Box> { 50 | match phdr.p_type { 51 | PT_GNU_EH_FRAME => { 52 | data.insert( 53 | EH_FRAME_ID, 54 | Box::new(EhFrame::new(phdr.p_vaddr as usize + segments.base())), 55 | ); 56 | } 57 | #[cfg(feature = "debug")] 58 | elf_loader::abi::PT_DYNAMIC => { 59 | data.insert( 60 | DEBUG_INFO_ID, 61 | Box::new(unsafe { 62 | DebugInfo::new( 63 | segments.base(), 64 | cname.as_ptr() as _, 65 | segments.base() + phdr.p_vaddr as usize, 66 | ) 67 | }), 68 | ); 69 | } 70 | #[cfg(feature = "tls")] 71 | elf_loader::abi::PT_TLS => { 72 | crate::tls::add_tls(segments, phdr, data, crate::tls::TlsState::Dynamic); 73 | } 74 | _ => {} 75 | } 76 | Ok(()) 77 | } 78 | 79 | #[cfg(feature = "tls")] 80 | #[allow(unused)] 81 | pub(crate) fn deal_unknown( 82 | rela: &ElfRela, 83 | lib: &CoreComponent, 84 | deps: &[&RelocatedDylib], 85 | ) -> core::result::Result<(), Box> { 86 | use crate::tls; 87 | 88 | fn find_tls_info(core: &elf_loader::CoreComponent) -> &crate::tls::TlsInfo { 89 | core.user_data() 90 | .get(crate::tls::TLS_INFO_ID) 91 | .unwrap() 92 | .downcast_ref::() 93 | .unwrap() 94 | } 95 | 96 | match rela.r_type() as _ { 97 | elf_loader::arch::REL_DTPMOD => { 98 | let r_sym = rela.r_symbol(); 99 | let r_off = rela.r_offset(); 100 | let ptr = (lib.base() + r_off) as *mut usize; 101 | if r_sym != 0 { 102 | let symdef = elf_loader::find_symdef(lib, deps, r_sym).unwrap(); 103 | unsafe { 104 | ptr.write(find_tls_info(symdef.lib).modid); 105 | } 106 | return Ok(()); 107 | } else { 108 | unsafe { ptr.write(find_tls_info(lib).modid) }; 109 | return Ok(()); 110 | } 111 | } 112 | elf_loader::arch::REL_TPOFF => { 113 | let r_sym = rela.r_symbol(); 114 | let r_off = rela.r_offset(); 115 | let r_addend = rela.r_addend(lib.base()); 116 | if r_sym != 0 { 117 | let symdef = elf_loader::find_symdef(lib, deps, r_sym).unwrap(); 118 | let ptr = (lib.base() + r_off) as *mut usize; 119 | let tls_info = find_tls_info(symdef.lib); 120 | unsafe { 121 | ptr.write( 122 | (symdef.sym.unwrap().st_value() + r_addend) 123 | .wrapping_sub(tls_info.static_tls_offset.unwrap()), 124 | ) 125 | }; 126 | return Ok(()); 127 | } 128 | } 129 | _ => {} 130 | } 131 | log::error!("Relocating dylib [{}] failed!", lib.name()); 132 | Err(Box::new(())) 133 | } 134 | 135 | #[cfg(not(feature = "tls"))] 136 | #[allow(unused)] 137 | pub(crate) fn deal_unknown( 138 | rela: &ElfRela, 139 | lib: &CoreComponent, 140 | deps: &[&RelocatedDylib], 141 | ) -> core::result::Result<(), Box> { 142 | log::error!("Relocating dylib [{}] failed!", lib.name()); 143 | Err(Box::new(())) 144 | } 145 | 146 | #[inline] 147 | pub(crate) fn create_lazy_scope( 148 | deps: &[RelocatedDylib], 149 | ) -> Arc Fn(&'a str) -> Option<*const ()>> { 150 | let deps_weak: Vec = deps 151 | .iter() 152 | .map(|dep| unsafe { dep.core_component_ref().downgrade() }) 153 | .collect(); 154 | Arc::new(move |name: &str| { 155 | deps_weak.iter().find_map(|dep| unsafe { 156 | let lib = RelocatedDylib::from_core_component(dep.upgrade().unwrap()); 157 | lib.get::<()>(name).map(|sym| { 158 | log::trace!( 159 | "Lazy Binding: find symbol [{}] from [{}] in local scope ", 160 | name, 161 | lib.name() 162 | ); 163 | sym.into_raw() 164 | }) 165 | }) 166 | }) 167 | } 168 | 169 | fn from_impl(object: impl ElfObject, flags: OpenFlags) -> Result { 170 | let mut loader = Loader::::new(); 171 | loader.set_hook(Box::new(parse_phdr)); 172 | #[cfg(all(feature = "fs", feature = "use-ldso"))] 173 | unsafe { 174 | loader.set_init(Arc::new( 175 | |func: Option, func_array: Option<&[fn()]>| { 176 | func.iter() 177 | .chain(func_array.unwrap_or(&[]).iter()) 178 | .for_each(|init| { 179 | core::mem::transmute::<_, &fn(usize, usize, usize)>(init)( 180 | crate::init::ARGC, 181 | (*core::ptr::addr_of!(crate::init::ARGV)).as_ptr() as usize, 182 | crate::init::ENVP, 183 | ) 184 | }); 185 | }, 186 | )) 187 | }; 188 | let lazy_bind = if flags.contains(OpenFlags::RTLD_LAZY) { 189 | Some(true) 190 | } else if flags.contains(OpenFlags::RTLD_NOW) { 191 | Some(false) 192 | } else { 193 | None 194 | }; 195 | let dylib = loader.load_dylib(object, lazy_bind)?; 196 | log::debug!( 197 | "Loading dylib [{}] at address [0x{:x}-0x{:x}]", 198 | dylib.name(), 199 | dylib.base(), 200 | dylib.base() + dylib.map_len() 201 | ); 202 | Ok(dylib) 203 | } 204 | 205 | pub(super) struct FileBuilder; 206 | 207 | #[cfg(feature = "fs")] 208 | impl Builder for FileBuilder { 209 | fn create_object(path: &str) -> Result { 210 | use elf_loader::object::ElfFile; 211 | Ok(ElfFile::from_path(path)?) 212 | } 213 | } 214 | 215 | #[cfg(not(feature = "fs"))] 216 | impl Builder for FileBuilder { 217 | fn create_object(path: &str) -> Result { 218 | Err::(crate::find_lib_error(path)) 219 | } 220 | } 221 | 222 | /// An relocated dynamic library 223 | #[derive(Clone)] 224 | pub struct ElfLibrary { 225 | pub(crate) inner: RelocatedDylib<'static>, 226 | pub(crate) flags: OpenFlags, 227 | pub(crate) deps: Option]>>>, 228 | } 229 | 230 | impl Debug for ElfLibrary { 231 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 232 | f.debug_struct("Dylib") 233 | .field("inner", &self.inner) 234 | .field("flags", &self.flags) 235 | .finish() 236 | } 237 | } 238 | 239 | impl ElfLibrary { 240 | /// Find and load a elf dynamic library from path. 241 | #[cfg(feature = "fs")] 242 | #[inline] 243 | pub(crate) fn from_file(path: impl AsRef, flags: OpenFlags) -> Result { 244 | ElfLibrary::from_builder::(path.as_ref(), flags) 245 | } 246 | 247 | #[inline] 248 | pub(crate) fn from_builder(path: &str, flags: OpenFlags) -> Result 249 | where 250 | B: Builder, 251 | M: Mmap, 252 | { 253 | from_impl(B::create_object(path)?, flags) 254 | } 255 | 256 | /// Load a elf dynamic library from bytes. 257 | #[inline] 258 | pub(crate) fn from_binary( 259 | bytes: impl AsRef<[u8]>, 260 | path: impl AsRef, 261 | flags: OpenFlags, 262 | ) -> Result { 263 | let file = ElfBinary::new(path.as_ref(), bytes.as_ref()); 264 | from_impl(file, flags) 265 | } 266 | 267 | /// Get the name of the dynamic library. 268 | #[inline] 269 | pub fn name(&self) -> &str { 270 | self.inner.name() 271 | } 272 | 273 | /// Get the C-style name of the dynamic library. 274 | #[inline] 275 | pub fn cname(&self) -> &CStr { 276 | self.inner.cname() 277 | } 278 | 279 | /// Get the base address of the dynamic library. 280 | #[inline] 281 | pub fn base(&self) -> usize { 282 | self.inner.base() 283 | } 284 | 285 | /// Gets the memory length of the elf object map. 286 | #[inline] 287 | pub fn map_len(&self) -> usize { 288 | self.inner.map_len() 289 | } 290 | 291 | /// Get the program headers of the dynamic library. 292 | #[inline] 293 | pub fn phdrs(&self) -> &[ElfPhdr] { 294 | self.inner.phdrs() 295 | } 296 | 297 | /// Get the needed libs' name of the elf object. 298 | #[inline] 299 | pub fn needed_libs(&self) -> &[&str] { 300 | self.inner.needed_libs() 301 | } 302 | 303 | /// Get a pointer to a function or static variable by symbol name. 304 | /// 305 | /// The symbol is interpreted as-is; no mangling is done. This means that symbols like `x::y` are 306 | /// most likely invalid. 307 | /// 308 | /// # Safety 309 | /// Users of this API must specify the correct type of the function or variable loaded. 310 | /// 311 | /// # Examples 312 | /// ```no_run 313 | /// # use dlopen_rs::{Symbol, ElfLibrary ,OpenFlags}; 314 | /// # let lib = ElfLibrary::dlopen("awesome.so", OpenFlags::RTLD_NOW).unwrap(); 315 | /// unsafe { 316 | /// let awesome_function: Symbol f64> = 317 | /// lib.get("awesome_function").unwrap(); 318 | /// awesome_function(0.42); 319 | /// } 320 | /// ``` 321 | /// A static variable may also be loaded and inspected: 322 | /// ```no_run 323 | /// # use dlopen_rs::{Symbol, ElfLibrary ,OpenFlags}; 324 | /// # let lib = ElfLibrary::dlopen("awesome.so", OpenFlags::RTLD_NOW).unwrap(); 325 | /// unsafe { 326 | /// let awesome_variable: Symbol<*mut f64> = lib.get("awesome_variable").unwrap(); 327 | /// **awesome_variable = 42.0; 328 | /// }; 329 | /// ``` 330 | #[inline] 331 | pub unsafe fn get<'lib, T>(&'lib self, name: &str) -> Result> { 332 | find_symbol(self.deps.as_ref().unwrap(), name) 333 | } 334 | 335 | /// Load a versioned symbol from the dynamic library. 336 | /// 337 | /// # Examples 338 | /// ```no_run 339 | /// # use dlopen_rs::{Symbol, ElfLibrary ,OpenFlags}; 340 | /// # let lib = ElfLibrary::dlopen("awesome.so", OpenFlags::RTLD_NOW).unwrap(); 341 | /// let symbol = unsafe { lib.get_version::("function_name", "1.0").unwrap() }; 342 | /// ``` 343 | #[cfg(feature = "version")] 344 | #[inline] 345 | pub unsafe fn get_version<'lib, T>( 346 | &'lib self, 347 | name: &str, 348 | version: &str, 349 | ) -> Result> { 350 | unsafe { 351 | self.inner 352 | .get_version(name, version) 353 | .ok_or(find_symbol_error(format!("can not find symbol:{}", name))) 354 | } 355 | } 356 | } 357 | -------------------------------------------------------------------------------- /src/dlopen.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | OpenFlags, Result, find_lib_error, 3 | loader::{Builder, ElfLibrary, FileBuilder, create_lazy_scope, deal_unknown}, 4 | register::{DylibState, MANAGER, register}, 5 | }; 6 | use alloc::{ 7 | borrow::ToOwned, 8 | boxed::Box, 9 | format, 10 | string::{String, ToString}, 11 | sync::Arc, 12 | vec, 13 | vec::Vec, 14 | }; 15 | use core::ffi::{c_char, c_int, c_void}; 16 | use elf_loader::{ 17 | ElfDylib, RelocatedDylib, 18 | mmap::{Mmap, MmapImpl}, 19 | }; 20 | use spin::Lazy; 21 | 22 | #[derive(Debug, PartialEq, Eq, Hash)] 23 | pub(crate) struct ElfPath { 24 | path: String, 25 | } 26 | 27 | impl ElfPath { 28 | pub(crate) fn from_str(path: &str) -> Result { 29 | Ok(ElfPath { 30 | path: path.to_owned(), 31 | }) 32 | } 33 | 34 | fn join(&self, file_name: &str) -> ElfPath { 35 | let mut new = self.path.clone(); 36 | new.push('/'); 37 | new.push_str(file_name); 38 | ElfPath { path: new } 39 | } 40 | 41 | fn as_str(&self) -> &str { 42 | &self.path 43 | } 44 | } 45 | 46 | impl ElfLibrary { 47 | /// Load a shared library from a specified path. It is the same as dlopen. 48 | /// 49 | /// # Example 50 | /// ```no_run 51 | /// # use std::path::Path; 52 | /// # use dlopen_rs::{ElfLibrary, OpenFlags}; 53 | /// 54 | /// let path = Path::new("/path/to/library.so"); 55 | /// let lib = ElfLibrary::dlopen(path, OpenFlags::RTLD_LOCAL).expect("Failed to load library"); 56 | /// ``` 57 | #[cfg(feature = "fs")] 58 | #[inline] 59 | pub fn dlopen(path: impl AsRef, flags: OpenFlags) -> Result { 60 | dlopen_impl::(path.as_ref(), flags, || { 61 | ElfLibrary::from_file(path.as_ref(), flags) 62 | }) 63 | } 64 | 65 | #[inline] 66 | pub fn dlopen_from_builder( 67 | path: &str, 68 | bytes: Option<&[u8]>, 69 | flags: OpenFlags, 70 | ) -> Result 71 | where 72 | B: Builder, 73 | M: Mmap, 74 | { 75 | if let Some(bytes) = bytes { 76 | dlopen_impl::(path, flags, || ElfLibrary::from_binary(bytes, path, flags)) 77 | } else { 78 | dlopen_impl::(path, flags, || { 79 | ElfLibrary::from_builder::(path, flags) 80 | }) 81 | } 82 | } 83 | 84 | /// Load a shared library from bytes. It is the same as dlopen. However, it can also be used in the no_std environment, 85 | /// and it will look for dependent libraries in those manually opened dynamic libraries. 86 | #[inline] 87 | pub fn dlopen_from_binary( 88 | bytes: &[u8], 89 | path: impl AsRef, 90 | flags: OpenFlags, 91 | ) -> Result { 92 | dlopen_impl::(path.as_ref(), flags, || { 93 | ElfLibrary::from_binary(bytes, path.as_ref(), flags) 94 | }) 95 | } 96 | } 97 | 98 | struct Recycler { 99 | is_recycler: bool, 100 | old_all_len: usize, 101 | old_global_len: usize, 102 | } 103 | 104 | impl Drop for Recycler { 105 | fn drop(&mut self) { 106 | if self.is_recycler { 107 | log::debug!("Destroying newly added dynamic libraries from the global"); 108 | let mut lock = MANAGER.write(); 109 | lock.all.truncate(self.old_all_len); 110 | lock.global.truncate(self.old_global_len); 111 | } 112 | } 113 | } 114 | 115 | fn dlopen_impl( 116 | path: &str, 117 | flags: OpenFlags, 118 | f: impl Fn() -> Result, 119 | ) -> Result 120 | where 121 | B: Builder, 122 | M: Mmap, 123 | { 124 | let shortname = path.split('/').next_back().unwrap(); 125 | log::info!("dlopen: Try to open [{}] with [{:?}] ", path, flags); 126 | let mut lock = MANAGER.write(); 127 | // 新加载的动态库 128 | let mut new_libs = Vec::new(); 129 | let core = if flags.contains(OpenFlags::CUSTOM_NOT_REGISTER) { 130 | let lib = f()?; 131 | let core = lib.core_component(); 132 | new_libs.push(Some(lib)); 133 | unsafe { RelocatedDylib::from_core_component(core) } 134 | } else { 135 | // 检查是否是已经加载的库 136 | if let Some(lib) = lock.all.get(shortname) { 137 | if lib.deps().is_some() 138 | && !flags 139 | .difference(lib.flags()) 140 | .contains(OpenFlags::RTLD_GLOBAL) 141 | { 142 | return Ok(lib.get_dylib()); 143 | } 144 | lib.relocated_dylib() 145 | } else { 146 | let lib = f()?; 147 | let core = lib.core_component(); 148 | new_libs.push(Some(lib)); 149 | unsafe { RelocatedDylib::from_core_component(core) } 150 | } 151 | }; 152 | 153 | let mut recycler = Recycler { 154 | is_recycler: true, 155 | old_all_len: usize::MAX, 156 | old_global_len: usize::MAX, 157 | }; 158 | 159 | // 用于保存所有的依赖库 160 | let mut dep_libs = Vec::new(); 161 | let mut cur_pos = 0; 162 | dep_libs.push(core.clone()); 163 | recycler.old_all_len = lock.all.len(); 164 | recycler.old_global_len = lock.global.len(); 165 | 166 | let mut cur_newlib_pos = 0; 167 | // 广度优先搜索,这是规范的要求,这个循环里会加载所有需要的动态库,无论是直接依赖还是间接依赖的 168 | while cur_pos < dep_libs.len() { 169 | let lib_names: &[&str] = unsafe { core::mem::transmute(dep_libs[cur_pos].needed_libs()) }; 170 | let mut cur_rpath = None; 171 | for lib_name in lib_names { 172 | if let Some(lib) = lock.all.get_mut(*lib_name) { 173 | if !lib.state.is_used() { 174 | lib.state.set_used(); 175 | dep_libs.push(lib.relocated_dylib()); 176 | log::debug!("Use an existing dylib: [{}]", lib.shortname()); 177 | if flags 178 | .difference(lib.flags()) 179 | .contains(OpenFlags::RTLD_GLOBAL) 180 | { 181 | let shortname = lib.shortname().to_owned(); 182 | log::debug!( 183 | "Trying to update a library. Name: [{}] Old flags:[{:?}] New flags:[{:?}]", 184 | shortname, 185 | lib.flags(), 186 | flags 187 | ); 188 | lib.set_flags(flags); 189 | let core = lib.relocated_dylib(); 190 | lock.global.insert(shortname, core); 191 | } 192 | } 193 | continue; 194 | } 195 | 196 | let rpath = if let Some(rpath) = &cur_rpath { 197 | rpath 198 | } else { 199 | let parent_lib = new_libs[cur_newlib_pos].as_ref().unwrap(); 200 | cur_rpath = Some( 201 | parent_lib 202 | .rpath() 203 | .map(|rpath| fixup_rpath(parent_lib.name(), rpath)) 204 | .unwrap_or(Box::new([])), 205 | ); 206 | cur_newlib_pos += 1; 207 | unsafe { cur_rpath.as_ref().unwrap_unchecked() } 208 | }; 209 | 210 | find_library(rpath, lib_name, |path| { 211 | let new_lib = ElfLibrary::from_builder::(path.as_str(), flags)?; 212 | let inner = new_lib.core_component(); 213 | register( 214 | unsafe { RelocatedDylib::from_core_component(inner.clone()) }, 215 | flags, 216 | None, 217 | &mut lock, 218 | *DylibState::default() 219 | .set_used() 220 | .set_new_idx(new_libs.len() as _), 221 | ); 222 | dep_libs.push(unsafe { RelocatedDylib::from_core_component(inner) }); 223 | new_libs.push(Some(new_lib)); 224 | Ok(()) 225 | })?; 226 | } 227 | cur_pos += 1; 228 | } 229 | 230 | #[derive(Clone, Copy)] 231 | struct Item { 232 | idx: usize, 233 | next: usize, 234 | } 235 | // 保存new_libs的索引 236 | let mut stack = Vec::new(); 237 | stack.push(Item { idx: 0, next: 0 }); 238 | // 记录新加载的动态库进行重定位的顺序 239 | let mut order = Vec::new(); 240 | 241 | 'start: while let Some(mut item) = stack.pop() { 242 | let names = new_libs[item.idx].as_ref().unwrap().needed_libs(); 243 | for name in names.iter().skip(item.next) { 244 | let lib = lock.all.get_mut(*name).unwrap(); 245 | lib.state.set_unused(); 246 | // 判断当前依赖库是否是新加载的,如果不是则跳过本轮操作,因为它已经被重定位过了 247 | let Some(idx) = lib.state.get_new_idx() else { 248 | continue; 249 | }; 250 | // 将当前依赖库的状态设置为已经重定位 251 | lib.state.set_relocated(); 252 | item.next += 1; 253 | stack.push(item); 254 | stack.push(Item { 255 | idx: idx as usize, 256 | next: 0, 257 | }); 258 | continue 'start; 259 | } 260 | order.push(item.idx); 261 | } 262 | 263 | #[cfg(feature = "tls")] 264 | crate::tls::update_generation(); 265 | 266 | let deps = Arc::new(dep_libs.into_boxed_slice()); 267 | let core = deps[0].clone(); 268 | let res = ElfLibrary { 269 | inner: core.clone(), 270 | flags, 271 | deps: Some(deps.clone()), 272 | }; 273 | //重新注册因为更新了deps 274 | register( 275 | core, 276 | flags, 277 | Some(deps.clone()), 278 | &mut lock, 279 | *DylibState::default().set_relocated(), 280 | ); 281 | let read_lock = lock.downgrade(); 282 | let lazy_scope = create_lazy_scope(&deps); 283 | let iter: Vec<&RelocatedDylib<'_>> = read_lock.global.values().chain(deps.iter()).collect(); 284 | for idx in order { 285 | let lib = core::mem::take(&mut new_libs[idx]).unwrap(); 286 | log::debug!("Relocating dylib [{}]", lib.name()); 287 | let is_lazy = lib.is_lazy(); 288 | lib.relocate( 289 | &iter, 290 | &|_| None, 291 | &mut deal_unknown, 292 | if is_lazy { 293 | Some(lazy_scope.clone()) 294 | } else { 295 | None 296 | }, 297 | )?; 298 | } 299 | if !flags.contains(OpenFlags::CUSTOM_NOT_REGISTER) { 300 | recycler.is_recycler = false; 301 | } 302 | Ok(res) 303 | } 304 | 305 | static LD_LIBRARY_PATH: Lazy> = Lazy::new(|| { 306 | //TODO 307 | Box::new([]) 308 | }); 309 | static DEFAULT_PATH: spin::Lazy> = Lazy::new(|| unsafe { 310 | let v = vec![ElfPath::from_str("/usr/lib").unwrap_unchecked()]; 311 | v.into_boxed_slice() 312 | }); 313 | static LD_CACHE: Lazy> = Lazy::new(|| { 314 | #[cfg(feature = "fs")] 315 | return crate::cache::build_ld_cache().unwrap(); 316 | #[cfg(not(feature = "fs"))] 317 | Box::new([]) 318 | }); 319 | 320 | #[inline] 321 | fn fixup_rpath(lib_path: &str, rpath: &str) -> Box<[ElfPath]> { 322 | if !rpath.contains('$') { 323 | return deal_path(rpath); 324 | } 325 | for s in rpath.split('$').skip(1) { 326 | if !s.starts_with("ORIGIN") && !s.starts_with("{ORIGIN}") { 327 | log::warn!("DT_RUNPATH format is incorrect: [{}]", rpath); 328 | return Box::new([]); 329 | } 330 | } 331 | let dir = if let Some((path, _)) = lib_path.rsplit_once('/') { 332 | path 333 | } else { 334 | "." 335 | }; 336 | deal_path(&rpath.to_string().replace("$ORIGIN", dir)) 337 | } 338 | 339 | #[inline] 340 | fn deal_path(s: &str) -> Box<[ElfPath]> { 341 | s.split(":") 342 | .map(|str| ElfPath::from_str(str).unwrap()) 343 | .collect() 344 | } 345 | 346 | #[inline] 347 | fn find_library( 348 | cur_rpath: &[ElfPath], 349 | lib_name: &str, 350 | mut f: impl FnMut(&ElfPath) -> Result<()>, 351 | ) -> Result<()> { 352 | // Search order: DT_RPATH(deprecated) -> LD_LIBRARY_PATH -> DT_RUNPATH -> /etc/ld.so.cache -> /lib:/usr/lib. 353 | let search_paths = LD_LIBRARY_PATH 354 | .iter() 355 | .chain(cur_rpath.iter()) 356 | .chain(LD_CACHE.iter()) 357 | .chain(DEFAULT_PATH.iter()); 358 | 359 | for path in search_paths { 360 | let file_path = path.join(lib_name); 361 | log::trace!("Try to open dependency shared object: [{:?}]", file_path); 362 | if f(&file_path).is_ok() { 363 | return Ok(()); 364 | } 365 | } 366 | Err(find_lib_error(format!("can not find file: {}", lib_name))) 367 | } 368 | 369 | /// # Safety 370 | /// It is the same as `dlopen`. 371 | #[allow(unused_variables)] 372 | #[unsafe(no_mangle)] 373 | pub unsafe extern "C" fn dlopen(filename: *const c_char, flags: c_int) -> *const c_void { 374 | let mut lib = if filename.is_null() { 375 | MANAGER.read().all.get_index(0).unwrap().1.get_dylib() 376 | } else { 377 | #[cfg(feature = "fs")] 378 | { 379 | let flags = OpenFlags::from_bits_retain(flags as _); 380 | let filename = unsafe { core::ffi::CStr::from_ptr(filename) }; 381 | let path = filename.to_str().unwrap(); 382 | if let Ok(lib) = ElfLibrary::dlopen(path, flags) { 383 | lib 384 | } else { 385 | return core::ptr::null(); 386 | } 387 | } 388 | #[cfg(not(feature = "fs"))] 389 | return core::ptr::null(); 390 | }; 391 | Arc::into_raw(core::mem::take(&mut lib.deps).unwrap()) as _ 392 | } 393 | -------------------------------------------------------------------------------- /src/tls.rs: -------------------------------------------------------------------------------- 1 | use alloc::{ 2 | alloc::{dealloc, handle_alloc_error}, 3 | boxed::Box, 4 | vec::Vec, 5 | }; 6 | use core::{ 7 | alloc::Layout, 8 | ffi::{c_int, c_void}, 9 | mem::ManuallyDrop, 10 | ptr::{null, null_mut}, 11 | sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering}, 12 | }; 13 | use elf_loader::{UserData, arch::ElfPhdr, segment::ElfSegments}; 14 | use spin::Lazy; 15 | 16 | use crate::arch::{ModifyRegister, ThreadRegister}; 17 | 18 | #[repr(C)] 19 | pub(crate) struct TlsIndex { 20 | ti_module: usize, 21 | ti_offset: usize, 22 | } 23 | 24 | pub(crate) const DTV_OFFSET: usize = 8; 25 | const TLS_TCB_SIZE: usize = 2368; 26 | pub(crate) const TLS_INFO_ID: u8 = 2; 27 | 28 | // struct StaticTlsInfo { 29 | // size: usize, 30 | // align: usize, 31 | // nelem: usize, 32 | // } 33 | 34 | // static STATIC_TLS_INFO: Once = Once::new(); 35 | pub(crate) static mut TLS_STATIC_SIZE: usize = 0; 36 | pub(crate) static mut TLS_STATIC_ALIGN: usize = 0; 37 | static TLS_NEXT_DTV_IDX: AtomicUsize = AtomicUsize::new(1); 38 | 39 | pub(crate) static TLS_GENERATION: AtomicUsize = AtomicUsize::new(0); 40 | static HAS_SLOT_GAPS: AtomicBool = AtomicBool::new(false); 41 | static DTV_SLOT_LIST: Lazy = Lazy::new(|| DtvSlotList::new()); 42 | 43 | const SLOT_SIZE: usize = 20; 44 | 45 | pub(crate) struct TlsInfo { 46 | image: &'static [u8], 47 | pub(crate) modid: usize, 48 | pub(crate) static_tls_offset: Option, 49 | memsz: usize, 50 | align: usize, 51 | } 52 | 53 | struct DtvSlot { 54 | generation: AtomicUsize, 55 | tls_info: AtomicPtr, 56 | } 57 | 58 | impl DtvSlot { 59 | fn tls_info(&self) -> *const TlsInfo { 60 | self.tls_info.load(Ordering::Relaxed) 61 | } 62 | } 63 | 64 | impl Default for DtvSlot { 65 | fn default() -> Self { 66 | Self { 67 | generation: Default::default(), 68 | tls_info: AtomicPtr::new(null_mut()), 69 | } 70 | } 71 | } 72 | 73 | struct DtvSlotList { 74 | next: AtomicPtr, 75 | slots: [DtvSlot; SLOT_SIZE], 76 | } 77 | 78 | impl DtvSlotList { 79 | fn new() -> Self { 80 | DtvSlotList { 81 | next: AtomicPtr::new(null_mut()), 82 | slots: core::array::from_fn(|_| DtvSlot::default()), 83 | } 84 | } 85 | 86 | fn add_slot(&self, tls_info: *const TlsInfo) { 87 | let info = unsafe { &*tls_info }; 88 | let mut idx = info.modid; 89 | let mut prev: *const DtvSlotList; 90 | let mut cur = self; 91 | loop { 92 | if idx < SLOT_SIZE { 93 | break; 94 | } 95 | idx -= SLOT_SIZE; 96 | prev = cur; 97 | let next = cur.next.load(Ordering::Relaxed); 98 | if next.is_null() { 99 | assert!(idx == 0); 100 | let new_slot = Box::leak(Box::new(DtvSlotList::new())); 101 | let ptr = new_slot as *mut DtvSlotList; 102 | cur = new_slot; 103 | (unsafe { &*prev }).next.store(ptr, Ordering::Release); 104 | break; 105 | } 106 | cur = unsafe { &mut *next }; 107 | } 108 | cur.slots[idx] 109 | .generation 110 | .store(TLS_GENERATION.load(Ordering::Relaxed), Ordering::Relaxed); 111 | cur.slots[idx] 112 | .tls_info 113 | .store(tls_info as *mut TlsInfo, Ordering::Relaxed); 114 | } 115 | 116 | fn find_slot(&self, mut idx: usize) -> &DtvSlot { 117 | let mut node = self; 118 | loop { 119 | if idx < SLOT_SIZE { 120 | break; 121 | } 122 | idx -= SLOT_SIZE; 123 | node = unsafe { &*node.next.load(Ordering::Relaxed) }; 124 | } 125 | &node.slots[idx] 126 | } 127 | 128 | fn find_free_slot(&self) -> Option<(usize, &DtvSlot)> { 129 | let mut node = self; 130 | let mut result = 0; 131 | loop { 132 | for slot in node.slots.iter() { 133 | if slot.tls_info.load(Ordering::Relaxed).is_null() { 134 | return Some((result, slot)); 135 | } 136 | result += 1; 137 | } 138 | let next = node.next.load(Ordering::Relaxed); 139 | if next.is_null() { 140 | return None; 141 | } 142 | node = unsafe { &mut *next }; 143 | } 144 | } 145 | 146 | fn update_slotinfo(&self, dtv: &mut DtvHeader, req_modid: usize) -> *const TlsInfo { 147 | let slot = self.find_slot(req_modid); 148 | let dtv_gen = dtv.get_gen(); 149 | let new_gen = slot.generation.load(Ordering::Relaxed); 150 | let mut tls_info = null(); 151 | if dtv_gen != new_gen { 152 | let max_modid = TLS_NEXT_DTV_IDX.load(Ordering::Relaxed) - 1; 153 | let mut cur_node = self; 154 | let mut total = 0; 155 | loop { 156 | for (cnt, slot) in cur_node.slots.iter().enumerate() { 157 | let cur_modid = cnt + if total == 0 { 1 } else { total }; 158 | if cur_modid > max_modid { 159 | break; 160 | } 161 | let cur_gen = slot.generation.load(Ordering::Relaxed); 162 | if cur_gen > new_gen && cur_gen <= dtv_gen { 163 | continue; 164 | } 165 | let cur_tls_info = slot.tls_info.load(Ordering::Relaxed); 166 | if dtv.dtv_cnt() < cur_modid + 1 { 167 | if cur_tls_info.is_null() { 168 | continue; 169 | } 170 | dtv.resize(max_modid); 171 | } 172 | if cur_modid == req_modid { 173 | tls_info = cur_tls_info; 174 | } 175 | dtv.try_free_dtv_entry(cur_modid); 176 | } 177 | total += SLOT_SIZE; 178 | if total >= max_modid { 179 | break; 180 | } 181 | if let Some(node) = cur_node.next_node() { 182 | cur_node = node; 183 | } else { 184 | break; 185 | } 186 | } 187 | dtv.set_gen(new_gen); 188 | } 189 | return tls_info; 190 | } 191 | 192 | fn next_node(&self) -> Option<&mut DtvSlotList> { 193 | let next = self.next.load(Ordering::Relaxed); 194 | if next.is_null() { 195 | return None; 196 | } 197 | Some(unsafe { &mut *next }) 198 | } 199 | } 200 | 201 | struct DtvPointer { 202 | ptr: *mut u8, 203 | layout: Option, 204 | } 205 | 206 | pub(crate) union DtvElem { 207 | ptr: ManuallyDrop, 208 | generation: usize, 209 | } 210 | 211 | impl Default for DtvElem { 212 | fn default() -> Self { 213 | Self { 214 | ptr: ManuallyDrop::new(DtvPointer { 215 | ptr: null_mut(), 216 | layout: None, 217 | }), 218 | } 219 | } 220 | } 221 | 222 | impl DtvElem { 223 | fn new_dynamic(tls_info: &TlsInfo) -> Self { 224 | let layout = Layout::from_size_align(tls_info.memsz, tls_info.align).unwrap(); 225 | let ptr = unsafe { alloc::alloc::alloc(layout) }; 226 | if ptr.is_null() { 227 | handle_alloc_error(layout); 228 | } 229 | let slice = unsafe { core::slice::from_raw_parts_mut(ptr as *mut u8, tls_info.memsz) }; 230 | let filesz = tls_info.image.len(); 231 | slice[..filesz].copy_from_slice(tls_info.image); 232 | slice[filesz..].fill(0); 233 | Self { 234 | ptr: ManuallyDrop::new(DtvPointer { 235 | ptr, 236 | layout: Some(layout), 237 | }), 238 | } 239 | } 240 | 241 | fn new_static(tls_info: &TlsInfo, dest: *mut u8) -> Self { 242 | let slice = unsafe { core::slice::from_raw_parts_mut(dest as *mut u8, tls_info.memsz) }; 243 | let filesz = tls_info.image.len(); 244 | slice[..filesz].copy_from_slice(tls_info.image); 245 | slice[filesz..].fill(0); 246 | Self { 247 | ptr: ManuallyDrop::new(DtvPointer { 248 | ptr: dest as _, 249 | layout: None, 250 | }), 251 | } 252 | } 253 | 254 | fn dtv_ptr(&self) -> *const u8 { 255 | return unsafe { self.ptr.ptr }; 256 | } 257 | } 258 | 259 | struct DtvHeader { 260 | dtv: Vec, 261 | } 262 | 263 | impl DtvHeader { 264 | fn new() -> Self { 265 | let mut dtv = Vec::new(); 266 | dtv.push(DtvElem { generation: 0 }); 267 | Self { dtv } 268 | } 269 | 270 | fn with_capicity(capacity: usize) -> Self { 271 | let mut dtv: Vec = Vec::with_capacity(capacity); 272 | dtv.push(DtvElem { generation: 0 }); 273 | Self { dtv } 274 | } 275 | 276 | fn set_dtv_header(dtv: &DtvHeader) { 277 | unsafe { 278 | ThreadRegister::set(DTV_OFFSET, dtv as *const DtvHeader as usize); 279 | } 280 | } 281 | 282 | fn get_dtv_header() -> &'static mut DtvHeader { 283 | let dtv = unsafe { ThreadRegister::get(DTV_OFFSET) }; 284 | dtv 285 | } 286 | 287 | fn get_gen(&self) -> usize { 288 | unsafe { self.dtv[0].generation } 289 | } 290 | 291 | fn set_gen(&mut self, generation: usize) { 292 | self.dtv[0].generation = generation; 293 | } 294 | 295 | fn dtv_cnt(&self) -> usize { 296 | self.dtv.len() - 1 297 | } 298 | 299 | fn try_free_dtv_entry(&mut self, modid: usize) { 300 | if modid >= self.dtv.len() { 301 | return; 302 | } 303 | let entry = &mut self.dtv[modid]; 304 | let ptr = unsafe { entry.ptr.ptr }; 305 | if ptr.is_null() { 306 | return; 307 | } 308 | if let Some(layout) = unsafe { entry.ptr.layout } { 309 | unsafe { 310 | dealloc(ptr, layout); 311 | entry.ptr.ptr = null_mut(); 312 | } 313 | } 314 | } 315 | 316 | fn resize(&mut self, max_modid: usize) { 317 | assert!(max_modid + 2 > self.dtv.len()); 318 | self.dtv.resize_with(max_modid + 2, || DtvElem::default()); 319 | } 320 | } 321 | 322 | fn get_slot_list() -> &'static DtvSlotList { 323 | &DTV_SLOT_LIST 324 | } 325 | 326 | pub(crate) fn update_generation() { 327 | TLS_GENERATION.fetch_add(1, Ordering::Relaxed); 328 | } 329 | 330 | pub(crate) enum TlsState { 331 | Dynamic, 332 | Static, 333 | Initialized(usize), 334 | } 335 | 336 | pub(crate) fn add_tls( 337 | segments: &ElfSegments, 338 | phdr: &ElfPhdr, 339 | data: &mut UserData, 340 | state: TlsState, 341 | ) { 342 | let memsz = phdr.p_memsz as usize; 343 | if memsz == 0 { 344 | return; 345 | } 346 | let image = unsafe { 347 | core::slice::from_raw_parts( 348 | (segments.base() + phdr.p_vaddr as usize) as *const u8, 349 | phdr.p_filesz as usize, 350 | ) 351 | }; 352 | let align = phdr.p_align as usize; 353 | let list = get_slot_list(); 354 | let static_tls_offset = match state { 355 | TlsState::Dynamic => None, 356 | TlsState::Static => { 357 | let mut tls_offset = unsafe { TLS_STATIC_SIZE }; 358 | tls_offset += memsz + align - 1; 359 | tls_offset -= (tls_offset + phdr.p_vaddr as usize) & (align - 1); 360 | unsafe { TLS_STATIC_SIZE = tls_offset }; 361 | unsafe { 362 | TLS_STATIC_ALIGN = TLS_STATIC_ALIGN.max(align); 363 | } 364 | Some(tls_offset) 365 | } 366 | TlsState::Initialized(static_tls_offset) => Some(static_tls_offset), 367 | }; 368 | if HAS_SLOT_GAPS.load(Ordering::Relaxed) { 369 | if let Some((modid, slot)) = list.find_free_slot() { 370 | let tls_info = Box::new(TlsInfo { 371 | image, 372 | modid, 373 | memsz, 374 | align, 375 | static_tls_offset, 376 | }); 377 | let ptr = tls_info.as_ref() as *const _ as _; 378 | slot.tls_info.store(ptr, Ordering::Release); 379 | data.insert(TLS_INFO_ID, tls_info); 380 | return; 381 | } 382 | HAS_SLOT_GAPS.store(false, Ordering::Relaxed); 383 | } 384 | let modid = TLS_NEXT_DTV_IDX.fetch_add(1, Ordering::Relaxed); 385 | let tls_info = Box::new(TlsInfo { 386 | image, 387 | modid, 388 | memsz, 389 | align, 390 | static_tls_offset, 391 | }); 392 | let ptr = tls_info.as_ref() as *const _ as _; 393 | list.add_slot(ptr); 394 | data.insert(TLS_INFO_ID, tls_info); 395 | } 396 | 397 | fn tls_get_addr_tail( 398 | tls_index: &TlsIndex, 399 | header: &mut DtvHeader, 400 | tls_info: *const TlsInfo, 401 | ) -> *const u8 { 402 | let tls_info = if tls_info.is_null() { 403 | let slot_list = get_slot_list(); 404 | unsafe { &*slot_list.find_slot(tls_index.ti_module).tls_info() } 405 | } else { 406 | unsafe { &*tls_info } 407 | }; 408 | let new_entry = DtvElem::new_dynamic(tls_info); 409 | let ptr = new_entry.dtv_ptr(); 410 | header.dtv[tls_index.ti_module] = new_entry; 411 | unsafe { ptr.add(tls_index.ti_offset) } 412 | } 413 | 414 | fn update_get_addr(tls_index: &TlsIndex, header: &mut DtvHeader) -> *const u8 { 415 | let tls_info = get_slot_list().update_slotinfo(header, tls_index.ti_module); 416 | let ptr = header.dtv[tls_index.ti_module].dtv_ptr(); 417 | if ptr.is_null() { 418 | return tls_get_addr_tail(tls_index, header, tls_info); 419 | } 420 | unsafe { ptr.add(tls_index.ti_offset) } 421 | } 422 | 423 | #[unsafe(no_mangle)] 424 | pub(crate) unsafe extern "C" fn __tls_get_addr(tls_index: &TlsIndex) -> *const u8 { 425 | let header = DtvHeader::get_dtv_header(); 426 | let generation = TLS_GENERATION.load(Ordering::Relaxed); 427 | if header.get_gen() != generation { 428 | return update_get_addr(tls_index, header); 429 | } 430 | let ptr = header.dtv[tls_index.ti_module].dtv_ptr(); 431 | if ptr.is_null() { 432 | return tls_get_addr_tail(tls_index, header, null()); 433 | } 434 | return unsafe { ptr.add(tls_index.ti_offset) }; 435 | } 436 | 437 | pub(crate) fn init_tls() { 438 | let header = Box::leak(Box::new(DtvHeader::new())); 439 | DtvHeader::set_dtv_header(header); 440 | } 441 | 442 | fn get_header_from_tcb(tcb: *mut u8) -> *mut *mut DtvHeader { 443 | unsafe { tcb.add(DTV_OFFSET).cast::<*mut DtvHeader>() } 444 | } 445 | 446 | fn allocate_dtv(tcb: *mut u8) -> *mut u8 { 447 | let len = TLS_NEXT_DTV_IDX.load(Ordering::Relaxed); 448 | unsafe { get_header_from_tcb(tcb).write(Box::leak(Box::new(DtvHeader::with_capicity(len)))) }; 449 | tcb 450 | } 451 | 452 | fn allocate_tls_storage() -> *mut u8 { 453 | let size = unsafe { TLS_STATIC_SIZE }; 454 | let align = unsafe { TLS_STATIC_ALIGN }; 455 | let layout = Layout::from_size_align(size, align).unwrap(); 456 | let allocated = unsafe { alloc::alloc::alloc(layout) }; 457 | if allocated.is_null() { 458 | handle_alloc_error(layout); 459 | } 460 | let tcb = unsafe { allocated.add(size - TLS_TCB_SIZE) }; 461 | unsafe { core::slice::from_raw_parts_mut(tcb, TLS_TCB_SIZE).fill(0) }; 462 | allocate_dtv(tcb) 463 | } 464 | 465 | fn init_tls_storage(tcb: *mut u8) -> *const c_void { 466 | if tcb.is_null() { 467 | return null(); 468 | } 469 | let header = unsafe { &mut **get_header_from_tcb(tcb) }; 470 | let count = TLS_NEXT_DTV_IDX.load(Ordering::Relaxed); 471 | if header.dtv.len() < count { 472 | header.resize(count); 473 | } 474 | let mut cur_node = get_slot_list(); 475 | let max_modid = count - 1; 476 | let mut max_gen = 0; 477 | let mut total = 0; 478 | loop { 479 | for (cnt, slot) in cur_node.slots.iter().enumerate() { 480 | let cur_modid = cnt + total; 481 | if cur_modid == max_modid { 482 | break; 483 | } 484 | let cur_tls_info = slot.tls_info.load(Ordering::Relaxed); 485 | if cur_tls_info.is_null() { 486 | continue; 487 | } 488 | let cur_tls_info = unsafe { &*cur_tls_info }; 489 | max_gen = max_gen.max(slot.generation.load(Ordering::Relaxed)); 490 | if let Some(static_tls_offset) = cur_tls_info.static_tls_offset { 491 | let dest = unsafe { tcb.sub(static_tls_offset) }; 492 | header.dtv[cur_tls_info.modid] = DtvElem::new_static(cur_tls_info, dest); 493 | } 494 | } 495 | total += SLOT_SIZE; 496 | if total >= max_modid { 497 | break; 498 | } 499 | if let Some(node) = cur_node.next_node() { 500 | cur_node = node; 501 | } else { 502 | break; 503 | } 504 | } 505 | header.set_gen(max_gen); 506 | tcb as _ 507 | } 508 | 509 | #[unsafe(no_mangle)] 510 | extern "C" fn _dl_allocate_tls(mem: *const c_void) -> *const c_void { 511 | let tcb = if mem.is_null() { 512 | allocate_tls_storage() 513 | } else { 514 | allocate_dtv(mem as _) 515 | }; 516 | init_tls_storage(tcb) 517 | } 518 | 519 | #[unsafe(no_mangle)] 520 | // FIXME: 有内存泄漏 521 | extern "C" fn __cxa_thread_atexit_impl() -> c_int { 522 | 0 523 | } 524 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anes" 16 | version = "0.1.6" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" 19 | 20 | [[package]] 21 | name = "anstream" 22 | version = "0.6.21" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" 25 | dependencies = [ 26 | "anstyle", 27 | "anstyle-parse", 28 | "anstyle-query", 29 | "anstyle-wincon", 30 | "colorchoice", 31 | "is_terminal_polyfill", 32 | "utf8parse", 33 | ] 34 | 35 | [[package]] 36 | name = "anstyle" 37 | version = "1.0.13" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" 40 | 41 | [[package]] 42 | name = "anstyle-parse" 43 | version = "0.2.7" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" 46 | dependencies = [ 47 | "utf8parse", 48 | ] 49 | 50 | [[package]] 51 | name = "anstyle-query" 52 | version = "1.1.4" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" 55 | dependencies = [ 56 | "windows-sys 0.60.2", 57 | ] 58 | 59 | [[package]] 60 | name = "anstyle-wincon" 61 | version = "3.0.10" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" 64 | dependencies = [ 65 | "anstyle", 66 | "once_cell_polyfill", 67 | "windows-sys 0.60.2", 68 | ] 69 | 70 | [[package]] 71 | name = "autocfg" 72 | version = "1.5.0" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 75 | 76 | [[package]] 77 | name = "bitflags" 78 | version = "2.9.4" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" 81 | 82 | [[package]] 83 | name = "bumpalo" 84 | version = "3.19.0" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" 87 | 88 | [[package]] 89 | name = "cast" 90 | version = "0.3.0" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" 93 | 94 | [[package]] 95 | name = "cdylib" 96 | version = "0.1.0" 97 | dependencies = [ 98 | "ctor", 99 | "dlopen-rs", 100 | "env_logger", 101 | ] 102 | 103 | [[package]] 104 | name = "cfg-if" 105 | version = "1.0.3" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" 108 | 109 | [[package]] 110 | name = "ciborium" 111 | version = "0.2.2" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" 114 | dependencies = [ 115 | "ciborium-io", 116 | "ciborium-ll", 117 | "serde", 118 | ] 119 | 120 | [[package]] 121 | name = "ciborium-io" 122 | version = "0.2.2" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" 125 | 126 | [[package]] 127 | name = "ciborium-ll" 128 | version = "0.2.2" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" 131 | dependencies = [ 132 | "ciborium-io", 133 | "half", 134 | ] 135 | 136 | [[package]] 137 | name = "clap" 138 | version = "4.5.48" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" 141 | dependencies = [ 142 | "clap_builder", 143 | ] 144 | 145 | [[package]] 146 | name = "clap_builder" 147 | version = "4.5.48" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" 150 | dependencies = [ 151 | "anstyle", 152 | "clap_lex", 153 | ] 154 | 155 | [[package]] 156 | name = "clap_lex" 157 | version = "0.7.5" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" 160 | 161 | [[package]] 162 | name = "colorchoice" 163 | version = "1.0.4" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" 166 | 167 | [[package]] 168 | name = "criterion" 169 | version = "0.5.1" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" 172 | dependencies = [ 173 | "anes", 174 | "cast", 175 | "ciborium", 176 | "clap", 177 | "criterion-plot", 178 | "is-terminal", 179 | "itertools", 180 | "num-traits", 181 | "once_cell", 182 | "oorandom", 183 | "plotters", 184 | "rayon", 185 | "regex", 186 | "serde", 187 | "serde_derive", 188 | "serde_json", 189 | "tinytemplate", 190 | "walkdir", 191 | ] 192 | 193 | [[package]] 194 | name = "criterion-plot" 195 | version = "0.5.0" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" 198 | dependencies = [ 199 | "cast", 200 | "itertools", 201 | ] 202 | 203 | [[package]] 204 | name = "crossbeam-deque" 205 | version = "0.8.6" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" 208 | dependencies = [ 209 | "crossbeam-epoch", 210 | "crossbeam-utils", 211 | ] 212 | 213 | [[package]] 214 | name = "crossbeam-epoch" 215 | version = "0.9.18" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 218 | dependencies = [ 219 | "crossbeam-utils", 220 | ] 221 | 222 | [[package]] 223 | name = "crossbeam-utils" 224 | version = "0.8.21" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 227 | 228 | [[package]] 229 | name = "crunchy" 230 | version = "0.2.4" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" 233 | 234 | [[package]] 235 | name = "ctor" 236 | version = "0.4.3" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "ec09e802f5081de6157da9a75701d6c713d8dc3ba52571fd4bd25f412644e8a6" 239 | dependencies = [ 240 | "ctor-proc-macro", 241 | "dtor", 242 | ] 243 | 244 | [[package]] 245 | name = "ctor-proc-macro" 246 | version = "0.0.6" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "e2931af7e13dc045d8e9d26afccc6fa115d64e115c9c84b1166288b46f6782c2" 249 | 250 | [[package]] 251 | name = "delegate" 252 | version = "0.13.4" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "6178a82cf56c836a3ba61a7935cdb1c49bfaa6fa4327cd5bf554a503087de26b" 255 | dependencies = [ 256 | "proc-macro2", 257 | "quote", 258 | "syn", 259 | ] 260 | 261 | [[package]] 262 | name = "dlopen-rs" 263 | version = "0.7.3" 264 | dependencies = [ 265 | "bitflags", 266 | "criterion", 267 | "elf_loader", 268 | "env_logger", 269 | "indexmap", 270 | "libc", 271 | "libloading", 272 | "log", 273 | "spin", 274 | "syscalls", 275 | ] 276 | 277 | [[package]] 278 | name = "dtor" 279 | version = "0.0.6" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "97cbdf2ad6846025e8e25df05171abfb30e3ababa12ee0a0e44b9bbe570633a8" 282 | dependencies = [ 283 | "dtor-proc-macro", 284 | ] 285 | 286 | [[package]] 287 | name = "dtor-proc-macro" 288 | version = "0.0.5" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "7454e41ff9012c00d53cf7f475c5e3afa3b91b7c90568495495e8d9bf47a1055" 291 | 292 | [[package]] 293 | name = "either" 294 | version = "1.15.0" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 297 | 298 | [[package]] 299 | name = "elf" 300 | version = "0.8.0" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "55dd888a213fc57e957abf2aa305ee3e8a28dbe05687a251f33b637cd46b0070" 303 | 304 | [[package]] 305 | name = "elf_loader" 306 | version = "0.13.0" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "badabb9d4c402c863dfd966f60201c192de7992f901400a6f5e0b4a9adcd50ed" 309 | dependencies = [ 310 | "bitflags", 311 | "cfg-if", 312 | "delegate", 313 | "elf", 314 | "libc", 315 | "log", 316 | "syscalls", 317 | "windows-sys 0.59.0", 318 | ] 319 | 320 | [[package]] 321 | name = "env_filter" 322 | version = "0.1.3" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" 325 | dependencies = [ 326 | "log", 327 | "regex", 328 | ] 329 | 330 | [[package]] 331 | name = "env_logger" 332 | version = "0.11.8" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" 335 | dependencies = [ 336 | "anstream", 337 | "anstyle", 338 | "env_filter", 339 | "jiff", 340 | "log", 341 | ] 342 | 343 | [[package]] 344 | name = "equivalent" 345 | version = "1.0.2" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 348 | 349 | [[package]] 350 | name = "example_dylib" 351 | version = "0.1.0" 352 | 353 | [[package]] 354 | name = "half" 355 | version = "2.6.0" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" 358 | dependencies = [ 359 | "cfg-if", 360 | "crunchy", 361 | ] 362 | 363 | [[package]] 364 | name = "hashbrown" 365 | version = "0.16.0" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" 368 | 369 | [[package]] 370 | name = "hermit-abi" 371 | version = "0.5.2" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" 374 | 375 | [[package]] 376 | name = "indexmap" 377 | version = "2.11.4" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" 380 | dependencies = [ 381 | "equivalent", 382 | "hashbrown", 383 | ] 384 | 385 | [[package]] 386 | name = "is-terminal" 387 | version = "0.4.16" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" 390 | dependencies = [ 391 | "hermit-abi", 392 | "libc", 393 | "windows-sys 0.59.0", 394 | ] 395 | 396 | [[package]] 397 | name = "is_terminal_polyfill" 398 | version = "1.70.1" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 401 | 402 | [[package]] 403 | name = "itertools" 404 | version = "0.10.5" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 407 | dependencies = [ 408 | "either", 409 | ] 410 | 411 | [[package]] 412 | name = "itoa" 413 | version = "1.0.15" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 416 | 417 | [[package]] 418 | name = "jiff" 419 | version = "0.2.15" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" 422 | dependencies = [ 423 | "jiff-static", 424 | "log", 425 | "portable-atomic", 426 | "portable-atomic-util", 427 | "serde", 428 | ] 429 | 430 | [[package]] 431 | name = "jiff-static" 432 | version = "0.2.15" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" 435 | dependencies = [ 436 | "proc-macro2", 437 | "quote", 438 | "syn", 439 | ] 440 | 441 | [[package]] 442 | name = "js-sys" 443 | version = "0.3.81" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" 446 | dependencies = [ 447 | "once_cell", 448 | "wasm-bindgen", 449 | ] 450 | 451 | [[package]] 452 | name = "libc" 453 | version = "0.2.176" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" 456 | 457 | [[package]] 458 | name = "libloading" 459 | version = "0.8.9" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" 462 | dependencies = [ 463 | "cfg-if", 464 | "windows-link", 465 | ] 466 | 467 | [[package]] 468 | name = "log" 469 | version = "0.4.28" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" 472 | 473 | [[package]] 474 | name = "memchr" 475 | version = "2.7.6" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" 478 | 479 | [[package]] 480 | name = "num-traits" 481 | version = "0.2.19" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 484 | dependencies = [ 485 | "autocfg", 486 | ] 487 | 488 | [[package]] 489 | name = "once_cell" 490 | version = "1.21.3" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 493 | 494 | [[package]] 495 | name = "once_cell_polyfill" 496 | version = "1.70.1" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" 499 | 500 | [[package]] 501 | name = "oorandom" 502 | version = "11.1.5" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" 505 | 506 | [[package]] 507 | name = "plotters" 508 | version = "0.3.7" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" 511 | dependencies = [ 512 | "num-traits", 513 | "plotters-backend", 514 | "plotters-svg", 515 | "wasm-bindgen", 516 | "web-sys", 517 | ] 518 | 519 | [[package]] 520 | name = "plotters-backend" 521 | version = "0.3.7" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" 524 | 525 | [[package]] 526 | name = "plotters-svg" 527 | version = "0.3.7" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" 530 | dependencies = [ 531 | "plotters-backend", 532 | ] 533 | 534 | [[package]] 535 | name = "portable-atomic" 536 | version = "1.11.1" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" 539 | 540 | [[package]] 541 | name = "portable-atomic-util" 542 | version = "0.2.4" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" 545 | dependencies = [ 546 | "portable-atomic", 547 | ] 548 | 549 | [[package]] 550 | name = "proc-macro2" 551 | version = "1.0.101" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" 554 | dependencies = [ 555 | "unicode-ident", 556 | ] 557 | 558 | [[package]] 559 | name = "quote" 560 | version = "1.0.41" 561 | source = "registry+https://github.com/rust-lang/crates.io-index" 562 | checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" 563 | dependencies = [ 564 | "proc-macro2", 565 | ] 566 | 567 | [[package]] 568 | name = "rayon" 569 | version = "1.11.0" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" 572 | dependencies = [ 573 | "either", 574 | "rayon-core", 575 | ] 576 | 577 | [[package]] 578 | name = "rayon-core" 579 | version = "1.13.0" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" 582 | dependencies = [ 583 | "crossbeam-deque", 584 | "crossbeam-utils", 585 | ] 586 | 587 | [[package]] 588 | name = "regex" 589 | version = "1.11.3" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" 592 | dependencies = [ 593 | "aho-corasick", 594 | "memchr", 595 | "regex-automata", 596 | "regex-syntax", 597 | ] 598 | 599 | [[package]] 600 | name = "regex-automata" 601 | version = "0.4.11" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" 604 | dependencies = [ 605 | "aho-corasick", 606 | "memchr", 607 | "regex-syntax", 608 | ] 609 | 610 | [[package]] 611 | name = "regex-syntax" 612 | version = "0.8.6" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" 615 | 616 | [[package]] 617 | name = "rustversion" 618 | version = "1.0.22" 619 | source = "registry+https://github.com/rust-lang/crates.io-index" 620 | checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" 621 | 622 | [[package]] 623 | name = "ryu" 624 | version = "1.0.20" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 627 | 628 | [[package]] 629 | name = "same-file" 630 | version = "1.0.6" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 633 | dependencies = [ 634 | "winapi-util", 635 | ] 636 | 637 | [[package]] 638 | name = "serde" 639 | version = "1.0.228" 640 | source = "registry+https://github.com/rust-lang/crates.io-index" 641 | checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 642 | dependencies = [ 643 | "serde_core", 644 | "serde_derive", 645 | ] 646 | 647 | [[package]] 648 | name = "serde_core" 649 | version = "1.0.228" 650 | source = "registry+https://github.com/rust-lang/crates.io-index" 651 | checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 652 | dependencies = [ 653 | "serde_derive", 654 | ] 655 | 656 | [[package]] 657 | name = "serde_derive" 658 | version = "1.0.228" 659 | source = "registry+https://github.com/rust-lang/crates.io-index" 660 | checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 661 | dependencies = [ 662 | "proc-macro2", 663 | "quote", 664 | "syn", 665 | ] 666 | 667 | [[package]] 668 | name = "serde_json" 669 | version = "1.0.145" 670 | source = "registry+https://github.com/rust-lang/crates.io-index" 671 | checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" 672 | dependencies = [ 673 | "itoa", 674 | "memchr", 675 | "ryu", 676 | "serde", 677 | "serde_core", 678 | ] 679 | 680 | [[package]] 681 | name = "serde_repr" 682 | version = "0.1.20" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" 685 | dependencies = [ 686 | "proc-macro2", 687 | "quote", 688 | "syn", 689 | ] 690 | 691 | [[package]] 692 | name = "spin" 693 | version = "0.10.0" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" 696 | 697 | [[package]] 698 | name = "syn" 699 | version = "2.0.106" 700 | source = "registry+https://github.com/rust-lang/crates.io-index" 701 | checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" 702 | dependencies = [ 703 | "proc-macro2", 704 | "quote", 705 | "unicode-ident", 706 | ] 707 | 708 | [[package]] 709 | name = "syscalls" 710 | version = "0.7.0" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "90db46b5b4962319605d435986c775ea45a0ad2561c09e1d5372b89afeb49cf4" 713 | dependencies = [ 714 | "serde", 715 | "serde_repr", 716 | ] 717 | 718 | [[package]] 719 | name = "tinytemplate" 720 | version = "1.2.1" 721 | source = "registry+https://github.com/rust-lang/crates.io-index" 722 | checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" 723 | dependencies = [ 724 | "serde", 725 | "serde_json", 726 | ] 727 | 728 | [[package]] 729 | name = "unicode-ident" 730 | version = "1.0.19" 731 | source = "registry+https://github.com/rust-lang/crates.io-index" 732 | checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" 733 | 734 | [[package]] 735 | name = "utf8parse" 736 | version = "0.2.2" 737 | source = "registry+https://github.com/rust-lang/crates.io-index" 738 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 739 | 740 | [[package]] 741 | name = "walkdir" 742 | version = "2.5.0" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 745 | dependencies = [ 746 | "same-file", 747 | "winapi-util", 748 | ] 749 | 750 | [[package]] 751 | name = "wasm-bindgen" 752 | version = "0.2.104" 753 | source = "registry+https://github.com/rust-lang/crates.io-index" 754 | checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" 755 | dependencies = [ 756 | "cfg-if", 757 | "once_cell", 758 | "rustversion", 759 | "wasm-bindgen-macro", 760 | "wasm-bindgen-shared", 761 | ] 762 | 763 | [[package]] 764 | name = "wasm-bindgen-backend" 765 | version = "0.2.104" 766 | source = "registry+https://github.com/rust-lang/crates.io-index" 767 | checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" 768 | dependencies = [ 769 | "bumpalo", 770 | "log", 771 | "proc-macro2", 772 | "quote", 773 | "syn", 774 | "wasm-bindgen-shared", 775 | ] 776 | 777 | [[package]] 778 | name = "wasm-bindgen-macro" 779 | version = "0.2.104" 780 | source = "registry+https://github.com/rust-lang/crates.io-index" 781 | checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" 782 | dependencies = [ 783 | "quote", 784 | "wasm-bindgen-macro-support", 785 | ] 786 | 787 | [[package]] 788 | name = "wasm-bindgen-macro-support" 789 | version = "0.2.104" 790 | source = "registry+https://github.com/rust-lang/crates.io-index" 791 | checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" 792 | dependencies = [ 793 | "proc-macro2", 794 | "quote", 795 | "syn", 796 | "wasm-bindgen-backend", 797 | "wasm-bindgen-shared", 798 | ] 799 | 800 | [[package]] 801 | name = "wasm-bindgen-shared" 802 | version = "0.2.104" 803 | source = "registry+https://github.com/rust-lang/crates.io-index" 804 | checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" 805 | dependencies = [ 806 | "unicode-ident", 807 | ] 808 | 809 | [[package]] 810 | name = "web-sys" 811 | version = "0.3.81" 812 | source = "registry+https://github.com/rust-lang/crates.io-index" 813 | checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" 814 | dependencies = [ 815 | "js-sys", 816 | "wasm-bindgen", 817 | ] 818 | 819 | [[package]] 820 | name = "winapi-util" 821 | version = "0.1.11" 822 | source = "registry+https://github.com/rust-lang/crates.io-index" 823 | checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" 824 | dependencies = [ 825 | "windows-sys 0.61.1", 826 | ] 827 | 828 | [[package]] 829 | name = "windows-link" 830 | version = "0.2.0" 831 | source = "registry+https://github.com/rust-lang/crates.io-index" 832 | checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" 833 | 834 | [[package]] 835 | name = "windows-sys" 836 | version = "0.59.0" 837 | source = "registry+https://github.com/rust-lang/crates.io-index" 838 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 839 | dependencies = [ 840 | "windows-targets 0.52.6", 841 | ] 842 | 843 | [[package]] 844 | name = "windows-sys" 845 | version = "0.60.2" 846 | source = "registry+https://github.com/rust-lang/crates.io-index" 847 | checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" 848 | dependencies = [ 849 | "windows-targets 0.53.4", 850 | ] 851 | 852 | [[package]] 853 | name = "windows-sys" 854 | version = "0.61.1" 855 | source = "registry+https://github.com/rust-lang/crates.io-index" 856 | checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" 857 | dependencies = [ 858 | "windows-link", 859 | ] 860 | 861 | [[package]] 862 | name = "windows-targets" 863 | version = "0.52.6" 864 | source = "registry+https://github.com/rust-lang/crates.io-index" 865 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 866 | dependencies = [ 867 | "windows_aarch64_gnullvm 0.52.6", 868 | "windows_aarch64_msvc 0.52.6", 869 | "windows_i686_gnu 0.52.6", 870 | "windows_i686_gnullvm 0.52.6", 871 | "windows_i686_msvc 0.52.6", 872 | "windows_x86_64_gnu 0.52.6", 873 | "windows_x86_64_gnullvm 0.52.6", 874 | "windows_x86_64_msvc 0.52.6", 875 | ] 876 | 877 | [[package]] 878 | name = "windows-targets" 879 | version = "0.53.4" 880 | source = "registry+https://github.com/rust-lang/crates.io-index" 881 | checksum = "2d42b7b7f66d2a06854650af09cfdf8713e427a439c97ad65a6375318033ac4b" 882 | dependencies = [ 883 | "windows-link", 884 | "windows_aarch64_gnullvm 0.53.0", 885 | "windows_aarch64_msvc 0.53.0", 886 | "windows_i686_gnu 0.53.0", 887 | "windows_i686_gnullvm 0.53.0", 888 | "windows_i686_msvc 0.53.0", 889 | "windows_x86_64_gnu 0.53.0", 890 | "windows_x86_64_gnullvm 0.53.0", 891 | "windows_x86_64_msvc 0.53.0", 892 | ] 893 | 894 | [[package]] 895 | name = "windows_aarch64_gnullvm" 896 | version = "0.52.6" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 899 | 900 | [[package]] 901 | name = "windows_aarch64_gnullvm" 902 | version = "0.53.0" 903 | source = "registry+https://github.com/rust-lang/crates.io-index" 904 | checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" 905 | 906 | [[package]] 907 | name = "windows_aarch64_msvc" 908 | version = "0.52.6" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 911 | 912 | [[package]] 913 | name = "windows_aarch64_msvc" 914 | version = "0.53.0" 915 | source = "registry+https://github.com/rust-lang/crates.io-index" 916 | checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" 917 | 918 | [[package]] 919 | name = "windows_i686_gnu" 920 | version = "0.52.6" 921 | source = "registry+https://github.com/rust-lang/crates.io-index" 922 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 923 | 924 | [[package]] 925 | name = "windows_i686_gnu" 926 | version = "0.53.0" 927 | source = "registry+https://github.com/rust-lang/crates.io-index" 928 | checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" 929 | 930 | [[package]] 931 | name = "windows_i686_gnullvm" 932 | version = "0.52.6" 933 | source = "registry+https://github.com/rust-lang/crates.io-index" 934 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 935 | 936 | [[package]] 937 | name = "windows_i686_gnullvm" 938 | version = "0.53.0" 939 | source = "registry+https://github.com/rust-lang/crates.io-index" 940 | checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" 941 | 942 | [[package]] 943 | name = "windows_i686_msvc" 944 | version = "0.52.6" 945 | source = "registry+https://github.com/rust-lang/crates.io-index" 946 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 947 | 948 | [[package]] 949 | name = "windows_i686_msvc" 950 | version = "0.53.0" 951 | source = "registry+https://github.com/rust-lang/crates.io-index" 952 | checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" 953 | 954 | [[package]] 955 | name = "windows_x86_64_gnu" 956 | version = "0.52.6" 957 | source = "registry+https://github.com/rust-lang/crates.io-index" 958 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 959 | 960 | [[package]] 961 | name = "windows_x86_64_gnu" 962 | version = "0.53.0" 963 | source = "registry+https://github.com/rust-lang/crates.io-index" 964 | checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" 965 | 966 | [[package]] 967 | name = "windows_x86_64_gnullvm" 968 | version = "0.52.6" 969 | source = "registry+https://github.com/rust-lang/crates.io-index" 970 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 971 | 972 | [[package]] 973 | name = "windows_x86_64_gnullvm" 974 | version = "0.53.0" 975 | source = "registry+https://github.com/rust-lang/crates.io-index" 976 | checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" 977 | 978 | [[package]] 979 | name = "windows_x86_64_msvc" 980 | version = "0.52.6" 981 | source = "registry+https://github.com/rust-lang/crates.io-index" 982 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 983 | 984 | [[package]] 985 | name = "windows_x86_64_msvc" 986 | version = "0.53.0" 987 | source = "registry+https://github.com/rust-lang/crates.io-index" 988 | checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" 989 | --------------------------------------------------------------------------------