elf_loader 0.12.0

A pure-rust library for loading all ELF format files from both memory and files.
Documentation
#[cfg(all(feature = "fs", feature = "mmap"))]
mod fs {
    use elf_loader::{Elf, load, load_dylib, load_exec};
    use std::env::consts;
    use std::path::PathBuf;
    use std::sync::OnceLock;
    use std::{collections::HashMap, fs::File, io::Read};

    const TARGET_DIR: Option<&'static str> = option_env!("CARGO_TARGET_DIR");
    static TARGET_TRIPLE: OnceLock<String> = OnceLock::new();

    fn lib_path(file_name: &str) -> String {
        let path: PathBuf = TARGET_DIR.unwrap_or("target").into();
        path.join(TARGET_TRIPLE.get().unwrap())
            .join("release")
            .join(file_name)
            .to_str()
            .unwrap()
            .to_string()
    }

    const PACKAGE_NAME: [&str; 3] = ["a", "b", "c"];

    fn compile() {
        static ONCE: ::std::sync::Once = ::std::sync::Once::new();
        ONCE.call_once(|| {
            let arch = consts::ARCH;
            if arch.contains("x86_64") {
                TARGET_TRIPLE
                    .set("x86_64-unknown-linux-gnu".to_string())
                    .unwrap();
            } else if arch.contains("x86") {
                TARGET_TRIPLE
                    .set("i586-unknown-linux-gnu".to_string())
                    .unwrap();
            } else if arch.contains("arm") {
                TARGET_TRIPLE
                    .set("arm-unknown-linux-gnueabihf".to_string())
                    .unwrap();
            } else if arch.contains("riscv64") {
                TARGET_TRIPLE
                    .set("riscv64gc-unknown-linux-gnu".to_string())
                    .unwrap();
            } else if arch.contains("aarch64") {
                TARGET_TRIPLE
                    .set("aarch64-unknown-linux-gnu".to_string())
                    .unwrap();
            } else if arch.contains("loongarch64") {
                TARGET_TRIPLE
                    .set("loongarch64-unknown-linux-musl".to_string())
                    .unwrap();
            } else {
                unimplemented!()
            }

            for name in PACKAGE_NAME {
                let mut cmd = ::std::process::Command::new("cargo");
                cmd.arg("rustc")
                    .arg("-r")
                    .arg("-p")
                    .arg(name)
                    .arg("--target")
                    .arg(TARGET_TRIPLE.get().unwrap().as_str())
                    .arg("--")
                    .arg("-C")
                    .arg("panic=abort");
                assert!(
                    cmd.status()
                        .expect("could not compile the test helpers!")
                        .success()
                );
            }
        });
    }

    #[test]
    fn relocate_dylib() {
        compile();
        fn print(s: &str) {
            println!("{}", s);
        }

        let mut map = HashMap::new();
        map.insert("print", print as _);
        let pre_find = |name: &str| -> Option<*const ()> { map.get(name).copied() };
        let liba = load_dylib!(&lib_path("liba.so")).unwrap();
        let libb = load_dylib!(&lib_path("libb.so")).unwrap();
        let libc = load_dylib!(&lib_path("libc.so")).unwrap();
        let a = liba.easy_relocate([].iter(), &pre_find).unwrap();
        let f = unsafe { a.get::<fn() -> i32>("a").unwrap() };
        assert!(f() == 1);
        let b = libb.easy_relocate([&a].into_iter(), &pre_find).unwrap();
        let f = unsafe { b.get::<fn() -> i32>("b").unwrap() };
        assert!(f() == 2);
        let c = libc.easy_relocate([&b].into_iter(), &pre_find).unwrap();
        let f = unsafe { c.get::<fn() -> i32>("c").unwrap() };
        assert!(f() == 3);
    }

    #[test]
    fn lazy_binding() {
        use std::sync::Arc;
        compile();
        fn print(s: &str) {
            println!("{}", s);
        }
        let mut map = HashMap::new();
        map.insert("print", print as _);
        let pre_find = |name: &str| -> Option<*const ()> { map.get(name).copied() };
        let liba = load_dylib!(&lib_path("liba.so")).unwrap();
        let libb = load_dylib!(&lib_path("libb.so"), lazy : true).unwrap();
        let a = liba.easy_relocate([].iter(), &pre_find).unwrap();
        let b = libb
            .relocate(
                [&a],
                &pre_find,
                &mut |_, _, _| Err(Box::new(())),
                Some(Arc::new(|name| unsafe {
                    a.get::<()>(name)
                        .map(|sym| sym.into_raw())
                        .or_else(|| pre_find(name))
                })),
            )
            .unwrap();
        let f = unsafe { b.get::<fn() -> i32>("b").unwrap() };
        assert!(f() == 2);
    }

    #[test]
    fn load_from_memory() {
        compile();
        let mut file = File::open(&lib_path("liba.so")).unwrap();
        let mut bytes = Vec::new();
        file.read_to_end(&mut bytes).unwrap();
        let liba = load_dylib!("liba.so", &bytes).unwrap();
        let a = liba.easy_relocate([].iter(), &|_| None).unwrap();
        let f = unsafe { a.get::<fn() -> i32>("a").unwrap() };
        assert!(f() == 1);
    }

    #[test]
    fn wrong_name_fails() {
        compile();
        let _ = load_dylib!("target/this_location_is_definitely_non existent:^~")
            .err()
            .unwrap();
    }

    #[test]
    fn type_mismatch() {
        compile();
        let _ = load_exec!(&lib_path("liba.so")).err().unwrap();
    }

    #[test]
    fn load_elf() {
        compile();
        let liba = load!(&lib_path("liba.so")).unwrap();
        assert!(matches!(liba, Elf::Dylib(_)));
        let a = liba
            .easy_relocate([].into_iter(), &|_| None)
            .unwrap()
            .into_dylib()
            .unwrap();
        let f = unsafe { a.get::<fn() -> i32>("a").unwrap() };
        assert!(f() == 1);
    }

    #[test]
    fn missing_symbol_fails() {
        compile();
        let lib = load_dylib!(&lib_path("liba.so"))
            .unwrap()
            .easy_relocate([].into_iter(), &|_| None)
            .unwrap();
        unsafe {
            assert!(lib.get::<*mut ()>("test_does_not_exist").is_none());
        }
    }

    #[repr(C)]
    #[derive(Clone, Copy, PartialEq, Debug)]
    struct S {
        a: u64,
        b: u32,
        c: u16,
        d: u8,
    }

    #[test]
    fn test_id_struct() {
        compile();
        let lib = load_dylib!(&lib_path("liba.so"))
            .unwrap()
            .easy_relocate([].into_iter(), &|_| None)
            .unwrap();
        unsafe {
            let f = lib
                .get::<unsafe extern "C" fn(S) -> S>("test_identity_struct")
                .unwrap();
            assert_eq!(
                S {
                    a: 1,
                    b: 2,
                    c: 3,
                    d: 4
                },
                f(S {
                    a: 1,
                    b: 2,
                    c: 3,
                    d: 4
                })
            );
        }
    }
}