elf_loader 0.16.0

A no_std-friendly ELF loader and runtime linker for Rust.
Documentation
mod support;

use elf_loader::{
    Loader,
    elf::ElfProgramType,
    image::ScannedElf,
    input::ElfBinary,
    observer::{BeforeLoadEvent, LoadObserver},
};
use gen_elf::{Arch, ElfWriterConfig, SymbolDesc};
use support::{generated_dylib::return_42_stub, test_dylib::write_test_dylib_with_config};

#[derive(Default)]
struct ScanData {
    value: usize,
}

struct ScanObserver;

impl LoadObserver<ScanData> for ScanObserver {
    fn on_before_load(
        &mut self,
        mut event: BeforeLoadEvent<'_, ScanData>,
    ) -> elf_loader::Result<()> {
        assert!(
            event
                .phdrs()
                .iter()
                .any(|phdr| phdr.program_type() == ElfProgramType::DYNAMIC)
        );
        event.user_data_mut().value = 42;
        Ok(())
    }
}

#[test]
fn borrowed_dynamic_reuses_existing_mapping() {
    let arch = Arch::current();
    let output = write_test_dylib_with_config(
        ElfWriterConfig::default()
            .with_bind_now(true)
            .with_soname("libowner.so"),
        &[],
        &[SymbolDesc::global_func("answer", &return_42_stub(arch))],
    );

    let owner_loader = Loader::new();
    let owner = owner_loader
        .load_dylib(ElfBinary::new("owner.so", &output.data))
        .expect("failed to map owner dylib");

    let borrowed_loader = Loader::new();
    let borrowed = unsafe {
        borrowed_loader.load_mapped_dynamic(
            "borrowed-main",
            owner.base(),
            owner.phdrs().to_vec(),
            owner.entry(),
        )
    }
    .expect("failed to wrap borrowed mapping");

    assert_eq!(borrowed.path().as_str(), "borrowed-main");
    assert_eq!(borrowed.name(), "libowner.so");
    assert_eq!(borrowed.base(), owner.base());
    assert_eq!(borrowed.entry(), owner.entry());
    assert!(borrowed.segments().contains_addr(owner.base()));
    assert_eq!(borrowed.phdrs().len(), owner.phdrs().len());
    assert!(
        borrowed
            .phdrs()
            .iter()
            .zip(owner.phdrs())
            .all(
                |(borrowed, owner)| borrowed.program_type() == owner.program_type()
                    && borrowed.p_vaddr() == owner.p_vaddr()
                    && borrowed.p_memsz() == owner.p_memsz()
            )
    );
    assert_eq!(borrowed.needed_libs(), owner.needed_libs());
    assert_eq!(owner.soname(), Some("libowner.so"));
    assert_eq!(borrowed.soname(), owner.soname());
}

#[test]
fn scanned_dynamic_load_reuses_scanned_metadata() {
    let arch = Arch::current();
    let output = write_test_dylib_with_config(
        ElfWriterConfig::default()
            .with_bind_now(true)
            .with_soname("libscanned.so"),
        &[],
        &[SymbolDesc::global_func("answer", &return_42_stub(arch))],
    );
    let bytes = output.data;

    let loader = Loader::new().with_data::<ScanData>();
    let mut run = loader.run().with_observer(ScanObserver);
    let ScannedElf::Dynamic(scanned) = run
        .scan(ElfBinary::owned("scanned.so", bytes))
        .expect("failed to scan dylib")
    else {
        panic!("generated dylib should scan as dynamic");
    };
    assert_eq!(scanned.soname(), Some("libscanned.so"));

    let raw = run
        .load_scanned_dynamic(scanned)
        .expect("failed to load scanned dynamic image");

    assert_eq!(raw.path().as_str(), "scanned.so");
    assert_eq!(raw.name(), "libscanned.so");
    assert_eq!(raw.soname(), Some("libscanned.so"));
    assert_eq!(raw.user_data().value, 42);
    assert!(
        raw.phdrs()
            .iter()
            .any(|phdr| phdr.program_type() == ElfProgramType::DYNAMIC)
    );
}