elf_loader 0.17.0

A no_std-friendly ELF loader and runtime linker for Rust.
Documentation
use elf_loader::{
    Loader,
    arch::NativeArch,
    image::LoadedCore,
    input::ElfBinary,
    lazy::NativeLazyBinder,
    memory::{ImageMemoryExt, VmOffset},
    relocation::{RelocationArch, Relocator},
};
use object::{Object, ObjectSymbol, ObjectSymbolTable, RelocationFlags, RelocationTarget};

pub(crate) const FIRST: &str = "first";
pub(crate) const SECOND: &str = "second";
pub(crate) const CALL_FIRST: &str = "call_first";
pub(crate) const CALL_SECOND: &str = "call_second";
const JUMP_SLOT: u32 = <NativeArch as RelocationArch>::JUMP_SLOT.raw();
const LOADER: Loader = Loader::new();

type Image = LoadedCore<()>;
pub(crate) type Entry = extern "C" fn() -> i32;

pub(crate) fn load_provider() -> Image {
    Relocator::new()
        .run(
            LOADER
                .load_dylib(ElfBinary::new(
                    "provider.so",
                    &crate::fixture::fixtures().provider,
                ))
                .expect("failed to load lazy-binding provider"),
        )
        .relocate()
        .expect("failed to relocate lazy-binding provider")
}

pub(crate) fn load_lazy(provider: &Image) -> Image {
    Relocator::new()
        .lazy_binder(NativeLazyBinder::new())
        .run(
            LOADER
                .load_dylib(ElfBinary::new(
                    "consumer.so",
                    &crate::fixture::fixtures().consumer,
                ))
                .expect("failed to load lazy-binding consumer"),
        )
        .modules([provider])
        .relocate()
        .expect("failed to relocate lazy-binding consumer")
}

pub(crate) fn load_eager(provider: &Image) -> Image {
    Relocator::new()
        .run(
            LOADER
                .load_dylib(ElfBinary::new(
                    "consumer.so",
                    &crate::fixture::fixtures().now,
                ))
                .expect("failed to load bind-now consumer"),
        )
        .modules([provider])
        .relocate()
        .expect("failed to relocate bind-now consumer")
}

pub(crate) fn slot(image: &Image, bytes: &[u8], name: &str) -> u64 {
    let file = object::File::parse(bytes).expect("failed to parse lazy-binding fixture");
    let symbols = file
        .dynamic_symbol_table()
        .expect("lazy-binding fixture should have dynamic symbols");
    let offset = file
        .dynamic_relocations()
        .expect("lazy-binding fixture should have dynamic relocations")
        .find_map(|(offset, relocation)| {
            let RelocationTarget::Symbol(index) = relocation.target() else {
                return None;
            };
            let symbol = symbols.symbol_by_index(index).ok()?;
            let RelocationFlags::Elf { r_type } = relocation.flags() else {
                return None;
            };
            (r_type.0 == JUMP_SLOT && symbol.name_bytes().ok()? == name.as_bytes())
                .then_some(offset)
        })
        .unwrap_or_else(|| panic!("missing jump slot for {name}"));
    unsafe {
        image
            .segments()
            .read_value::<usize>(image.segments().base() + VmOffset::new(offset as usize))
            .expect("failed to read lazy-binding slot") as u64
    }
}

pub(crate) fn symbol(image: &Image, name: &str) -> u64 {
    unsafe {
        image
            .get::<u8>(name)
            .unwrap_or_else(|| panic!("missing symbol {name}"))
            .into_raw() as u64
    }
}

pub(crate) fn call(image: &Image, name: &str) -> i32 {
    let function: Entry = unsafe {
        core::mem::transmute(
            image
                .get::<*const ()>(name)
                .unwrap_or_else(|| panic!("missing entry symbol {name}"))
                .into_raw(),
        )
    };
    function()
}