elf_loader 0.17.0

A no_std-friendly ELF loader and runtime linker for Rust.
Documentation
use std::{fs, path::Path, sync::OnceLock};

#[cfg(any(feature = "libc", feature = "use-syscall"))]
use elf_loader::linker::SearchPathResolver;
#[cfg(any(feature = "libc", feature = "use-syscall"))]
use std::path::PathBuf;

use crate::fixture_build::FixtureBuild;

pub(crate) struct Fixtures {
    pub(crate) provider: Vec<u8>,
    pub(crate) dependent: Vec<u8>,
    pub(crate) global: Vec<u8>,
    pub(crate) plain: Vec<u8>,
    #[cfg(any(
        feature = "use-syscall",
        all(any(target_os = "linux", target_os = "android"), feature = "libc")
    ))]
    pub(crate) exec: Vec<u8>,
    #[cfg(any(feature = "libc", feature = "use-syscall"))]
    pub(crate) provider_path: PathBuf,
    #[cfg(any(feature = "libc", feature = "use-syscall"))]
    pub(crate) root_path: PathBuf,
    #[cfg(any(feature = "libc", feature = "use-syscall"))]
    pub(crate) rpath_root_path: PathBuf,
    #[cfg(any(feature = "libc", feature = "use-syscall"))]
    pub(crate) rpath_caller_path: PathBuf,
    #[cfg(any(feature = "libc", feature = "use-syscall"))]
    pub(crate) rpath_middle_path: PathBuf,
    #[cfg(any(feature = "libc", feature = "use-syscall"))]
    pub(crate) rpath_leaf_path: PathBuf,
}

pub(crate) fn fixtures() -> &'static Fixtures {
    static FIXTURES: OnceLock<Fixtures> = OnceLock::new();
    FIXTURES.get_or_init(build)
}

#[cfg(any(feature = "libc", feature = "use-syscall"))]
pub(crate) fn search_path_resolver() -> SearchPathResolver {
    let mut resolver = SearchPathResolver::new();
    resolver.push_rpath();
    resolver.push_runpath();
    resolver
}

fn build() -> Fixtures {
    let build = FixtureBuild::rust("tests/linker/fixtures", "linker");
    let dylib = |name: &str, dependency: Option<(&str, &Path)>| {
        let dependencies = dependency.iter().map(|(_, path)| *path).collect::<Vec<_>>();
        build.rust_cdylib(format!("{name}.rs"), name, &dependencies, |command| {
            command
                .arg("-C")
                .arg("link-arg=--emit-relocs")
                .arg("-C")
                .arg("link-arg=-rpath")
                .arg("-C")
                .arg("link-arg=$ORIGIN");
            if let Some((dependency, _)) = dependency {
                command
                    .arg("-L")
                    .arg(format!("native={}", build.output("").display()))
                    .arg("-l")
                    .arg(format!("dylib={dependency}"));
            }
        })
    };
    let provider_path = dylib("provider", None);
    let dependent_path = dylib("dependent", Some(("provider", &provider_path)));
    let global_path = dylib("global", None);
    #[cfg(any(feature = "libc", feature = "use-syscall"))]
    let root_path = dylib("root", Some(("dependent", &dependent_path)));

    #[cfg(any(feature = "libc", feature = "use-syscall"))]
    let (rpath_root_path, rpath_caller_path, rpath_middle_path, rpath_leaf_path) = {
        let build = FixtureBuild::c("tests/linker/fixtures", "linker");
        let dir = build.output("rpath");
        fs::create_dir_all(&dir).expect("failed to create RPATH fixture directory");
        let leaf = build.c_shared("rpath_leaf.c", "rpath/libleaf.so", &[], |command| {
            command.arg("-Wl,-soname,libleaf.so");
        });
        let middle = build.c_shared(
            "rpath_middle.c",
            "rpath/libmiddle.so",
            &[leaf.as_path()],
            |command| {
                command
                    .arg("-Wl,-soname,libmiddle.so")
                    .arg(format!("-L{}", dir.display()))
                    .arg("-lleaf");
            },
        );
        let root = build.c_shared(
            "rpath_root.c",
            "librpath_root.so",
            &[middle.as_path()],
            |command| {
                command
                    .arg(format!("-L{}", dir.display()))
                    .arg("-lmiddle")
                    .arg("-Wl,--disable-new-dtags,-rpath,$ORIGIN/rpath");
            },
        );
        let caller = build.c_shared("rpath_caller.c", "librpath_caller.so", &[], |command| {
            command.arg("-Wl,--disable-new-dtags,-rpath,$ORIGIN/rpath");
        });
        (root, caller, middle, leaf)
    };

    let plain = build.rust_cdylib("plain.rs", "plain", &[], |_| {});

    #[cfg(any(
        feature = "use-syscall",
        all(any(target_os = "linux", target_os = "android"), feature = "libc")
    ))]
    let exec = build.compile(
        "exec.rs",
        "dynamic_exec",
        &[provider_path.as_path()],
        |command| {
            command
                .arg("--crate-type=bin")
                .arg("--crate-name")
                .arg("dynamic_exec")
                .arg("-O")
                .arg("-C")
                .arg("panic=abort")
                .arg("-C")
                .arg("linker=rust-lld")
                .arg("-C")
                .arg("link-arg=-no-pie")
                .arg("-C")
                .arg("link-arg=-e")
                .arg("-C")
                .arg("link-arg=_start")
                .arg("-C")
                .arg("link-arg=-rpath")
                .arg("-C")
                .arg("link-arg=$ORIGIN")
                .arg("-L")
                .arg(format!("native={}", build.output("").display()))
                .arg("-l")
                .arg("dylib=provider");
        },
    );

    Fixtures {
        provider: fs::read(&provider_path).expect("failed to read linker provider fixture"),
        dependent: fs::read(dependent_path).expect("failed to read linker dependent fixture"),
        global: fs::read(global_path).expect("failed to read linker global fixture"),
        plain: fs::read(plain).expect("failed to read linker plain fixture"),
        #[cfg(any(
            feature = "use-syscall",
            all(any(target_os = "linux", target_os = "android"), feature = "libc")
        ))]
        exec: fs::read(exec).expect("failed to read linker executable fixture"),
        #[cfg(any(feature = "libc", feature = "use-syscall"))]
        provider_path,
        #[cfg(any(feature = "libc", feature = "use-syscall"))]
        root_path,
        #[cfg(any(feature = "libc", feature = "use-syscall"))]
        rpath_root_path,
        #[cfg(any(feature = "libc", feature = "use-syscall"))]
        rpath_caller_path,
        #[cfg(any(feature = "libc", feature = "use-syscall"))]
        rpath_middle_path,
        #[cfg(any(feature = "libc", feature = "use-syscall"))]
        rpath_leaf_path,
    }
}