elf_loader 0.17.0

A no_std-friendly ELF loader and runtime linker for Rust.
Documentation
#[cfg(target_arch = "riscv64")]
use std::env;
use std::{fs, path::PathBuf, sync::OnceLock};

use crate::fixture_build::FixtureBuild;

#[cfg(any(
    feature = "use-syscall",
    all(any(target_os = "linux", target_os = "android"), feature = "libc")
))]
pub(crate) struct BindingFixtures {
    pub(crate) provider: Vec<u8>,
    pub(crate) consumer: Vec<u8>,
}

pub(crate) struct ScopeFixtures {
    pub(crate) provider: Vec<u8>,
    pub(crate) consumer: Vec<u8>,
    pub(crate) defining: Vec<u8>,
    pub(crate) symbolic: Vec<u8>,
}

#[cfg(all(
    not(target_arch = "loongarch64"),
    any(
        feature = "use-syscall",
        all(any(target_os = "linux", target_os = "android"), feature = "libc")
    )
))]
pub(crate) struct CopyFixtures {
    pub(crate) provider: Vec<u8>,
    pub(crate) executable: Vec<u8>,
}

pub(crate) struct DynamicFixtures {
    #[cfg(any(
        feature = "use-syscall",
        all(any(target_os = "linux", target_os = "android"), feature = "libc")
    ))]
    pub(crate) binding: BindingFixtures,
    pub(crate) scope: ScopeFixtures,
    #[cfg(any(
        feature = "use-syscall",
        all(any(target_os = "linux", target_os = "android"), feature = "libc")
    ))]
    pub(crate) relative: Vec<u8>,
    pub(crate) weak: Vec<u8>,
    #[cfg(all(
        not(target_arch = "loongarch64"),
        any(
            feature = "use-syscall",
            all(any(target_os = "linux", target_os = "android"), feature = "libc")
        )
    ))]
    pub(crate) copy: CopyFixtures,
    #[cfg(any(
        feature = "use-syscall",
        all(any(target_os = "linux", target_os = "android"), feature = "libc")
    ))]
    pub(crate) ifunc: Vec<u8>,
}

#[cfg(target_arch = "riscv64")]
pub(crate) struct Riscv64Fixtures {
    pub(crate) a: PathBuf,
    pub(crate) b: PathBuf,
    pub(crate) call: PathBuf,
    pub(crate) globals: PathBuf,
    pub(crate) hi_lo: PathBuf,
    pub(crate) pointers: PathBuf,
}

pub(crate) fn dynamic() -> &'static DynamicFixtures {
    static FIXTURES: OnceLock<DynamicFixtures> = OnceLock::new();
    FIXTURES.get_or_init(build_dynamic)
}

#[cfg(target_arch = "riscv64")]
pub(crate) fn riscv64() -> &'static Riscv64Fixtures {
    static FIXTURES: OnceLock<Riscv64Fixtures> = OnceLock::new();
    FIXTURES.get_or_init(build_riscv64)
}

fn read(path: PathBuf) -> Vec<u8> {
    fs::read(&path).unwrap_or_else(|_| panic!("failed to read fixture {}", path.display()))
}

fn build_dynamic() -> DynamicFixtures {
    let build = FixtureBuild::c("tests/relocator/fixtures/dynamic", "relocator/dynamic");

    #[cfg(any(
        feature = "use-syscall",
        all(any(target_os = "linux", target_os = "android"), feature = "libc")
    ))]
    let binding = {
        let provider = build.c_shared(
            "binding/provider.c",
            "libbinding_provider.so",
            &[],
            |command| {
                command.arg("-Wl,-soname,libbinding_provider.so");
            },
        );
        let consumer = build.c_shared(
            "binding/consumer.c",
            "libbinding_consumer.so",
            &[provider.as_path()],
            |command| {
                command
                    .arg("-Wl,-z,now")
                    .arg("-Wl,-rpath,$ORIGIN")
                    .arg("-L")
                    .arg(build.output(""))
                    .arg("-lbinding_provider");
            },
        );
        BindingFixtures {
            provider: read(provider),
            consumer: read(consumer),
        }
    };

    let scope_provider = build.c_shared("scope/provider.c", "libscope_provider.so", &[], |_| {});
    let scope_consumer =
        build.c_shared("scope/consumer.c", "libscope_consumer.so", &[], |command| {
            command.arg("-Wl,-z,now");
        });
    let scope_defining =
        build.c_shared("scope/defining.c", "libscope_defining.so", &[], |command| {
            command.arg("-Wl,-z,now");
        });
    let scope_symbolic =
        build.c_shared("scope/symbolic.c", "libscope_symbolic.so", &[], |command| {
            command.arg("-Wl,-z,now").arg("-Wl,-Bsymbolic");
        });

    #[cfg(any(
        feature = "use-syscall",
        all(any(target_os = "linux", target_os = "android"), feature = "libc")
    ))]
    let relative = read(
        build.c_shared("relative.c", "librelative.so", &[], |command| {
            command.arg("-Wl,-z,now");
        }),
    );
    let weak = build.c_shared("weak.c", "libweak.so", &[], |command| {
        command.arg("-Wl,-z,now");
    });

    #[cfg(any(
        feature = "use-syscall",
        all(any(target_os = "linux", target_os = "android"), feature = "libc")
    ))]
    let ifunc = build.c_shared("ifunc.c", "libifunc.so", &[], |command| {
        command.arg("-Wl,-z,now");
    });

    #[cfg(all(
        not(target_arch = "loongarch64"),
        any(
            feature = "use-syscall",
            all(any(target_os = "linux", target_os = "android"), feature = "libc")
        )
    ))]
    let copy = {
        let provider = build.c_shared("copy/provider.c", "libcopy_provider.so", &[], |command| {
            command.arg("-Wl,-soname,libcopy_provider.so");
        });
        let executable = build.compile(
            "copy/executable.c",
            "copy_relocations",
            &[provider.as_path()],
            |command| {
                command
                    .arg("-fno-pic")
                    .arg("-fno-pie")
                    .arg("-no-pie")
                    .arg("-fno-stack-protector")
                    .arg("-nostdlib")
                    .arg("-Wl,-e,_start")
                    .arg("-Wl,-z,now")
                    .arg("-Wl,-rpath,$ORIGIN")
                    .arg("-L")
                    .arg(build.output(""))
                    .arg("-lcopy_provider");
            },
        );
        CopyFixtures {
            provider: read(provider),
            executable: read(executable),
        }
    };

    DynamicFixtures {
        #[cfg(any(
            feature = "use-syscall",
            all(any(target_os = "linux", target_os = "android"), feature = "libc")
        ))]
        binding,
        scope: ScopeFixtures {
            provider: read(scope_provider),
            consumer: read(scope_consumer),
            defining: read(scope_defining),
            symbolic: read(scope_symbolic),
        },
        #[cfg(any(
            feature = "use-syscall",
            all(any(target_os = "linux", target_os = "android"), feature = "libc")
        ))]
        relative,
        weak: read(weak),
        #[cfg(all(
            not(target_arch = "loongarch64"),
            any(
                feature = "use-syscall",
                all(any(target_os = "linux", target_os = "android"), feature = "libc")
            )
        ))]
        copy,
        #[cfg(any(
            feature = "use-syscall",
            all(any(target_os = "linux", target_os = "android"), feature = "libc")
        ))]
        ifunc: read(ifunc),
    }
}

#[cfg(target_arch = "riscv64")]
fn build_riscv64() -> Riscv64Fixtures {
    const NAMES: [&str; 6] = ["a", "b", "call", "globals", "hi_lo", "pointers"];

    let build = FixtureBuild::c("tests/relocator/fixtures/riscv64", "relocator/riscv64");
    let cflags =
        env::var("RISCV64_CFLAGS").unwrap_or_else(|_| "-march=rv64gc -mabi=lp64d".to_owned());
    for name in NAMES {
        build.compile(format!("{name}.c"), format!("{name}.o"), &[], |command| {
            command
                .args(cflags.split_whitespace())
                .arg("-fPIC")
                .arg("-c");
        });
    }

    Riscv64Fixtures {
        a: build.output("a.o"),
        b: build.output("b.o"),
        call: build.output("call.o"),
        globals: build.output("globals.o"),
        hi_lo: build.output("hi_lo.o"),
        pointers: build.output("pointers.o"),
    }
}