elf_loader 0.17.0

A no_std-friendly ELF loader and runtime linker for Rust.
Documentation
use std::sync::OnceLock;

use elf_loader::{
    Loader,
    arch::NativeArch,
    image::LoadedCore,
    input::ElfBinary,
    memory::{HostRegion, ImageMemoryExt, VmOffset},
    relocation::{RelocationArch, Relocator},
    tls::DefaultTlsResolver,
};
use gen_elf::{
    Arch, DylibWriter, ElfWriteOutput, ElfWriterConfig, RelocEntry, RelocationInfo, SymbolDesc,
};

pub(crate) const FIRST: &str = "first";
pub(crate) const SECOND: &str = "second";
pub(crate) const DTPMOD: u32 = <NativeArch as RelocationArch>::DTPMOD.unwrap().raw();
pub(crate) const DTPOFF: u32 = <NativeArch as RelocationArch>::DTPOFF.raw();

type TlsImage = LoadedCore<(), NativeArch, HostRegion, DefaultTlsResolver>;
pub(crate) type TlsHelper = extern "C" fn() -> *mut u32;

pub(crate) struct TlsScenario {
    output: ElfWriteOutput,
    provider: TlsImage,
    consumer: TlsImage,
}

impl TlsScenario {
    pub(crate) fn provider(&self) -> &TlsImage {
        &self.provider
    }

    pub(crate) fn relocation(&self, r_type: u32, name: &str) -> &RelocationInfo {
        self.output
            .find_relocation(r_type, name)
            .unwrap_or_else(|| panic!("missing relocation type {r_type} for symbol {name}"))
    }

    pub(crate) fn slot(&self, relocation: &RelocationInfo) -> u64 {
        let addr = self.consumer.segments().base()
            + VmOffset::new(
                relocation
                    .vaddr
                    .expect("dynamic relocation should have a virtual address")
                    as usize,
            );
        unsafe {
            self.consumer
                .segments()
                .read_value::<usize>(addr)
                .expect("failed to read TLS relocation slot") as u64
        }
    }

    pub(crate) fn helper(&self, name: &str) -> TlsHelper {
        let name = format!("{name}@tls_helper");
        unsafe {
            core::mem::transmute(
                self.consumer
                    .get::<*const ()>(&name)
                    .unwrap_or_else(|| panic!("missing TLS helper symbol {name}"))
                    .into_raw(),
            )
        }
    }
}

pub(crate) fn scenario() -> &'static TlsScenario {
    static SCENARIO: OnceLock<TlsScenario> = OnceLock::new();
    SCENARIO.get_or_init(load)
}

fn load() -> TlsScenario {
    let arch = Arch::current();
    let config = || ElfWriterConfig::default().with_bind_now(true);
    let provider = DylibWriter::with_config(arch, config())
        .write(
            &[],
            &[
                SymbolDesc::global_tls(FIRST, &[0xAA, 0xBB, 0xCC, 0xDD]),
                SymbolDesc::global_tls(SECOND, &[0x11, 0x22, 0x33, 0x44]),
            ],
        )
        .expect("failed to generate TLS provider");
    let output = DylibWriter::with_config(arch, config())
        .write(
            &[
                RelocEntry::with_name(FIRST, DTPMOD),
                RelocEntry::with_name(FIRST, DTPOFF),
                RelocEntry::with_name(SECOND, DTPMOD),
                RelocEntry::with_name(SECOND, DTPOFF),
            ],
            &[
                SymbolDesc::undefined_tls(FIRST),
                SymbolDesc::undefined_tls(SECOND),
            ],
        )
        .expect("failed to generate TLS consumer");
    let loader = Loader::new().with_default_tls_resolver();
    let provider = Relocator::new()
        .run(
            loader
                .load_dylib(ElfBinary::new("provider.so", &provider.data))
                .expect("failed to load TLS provider"),
        )
        .relocate()
        .expect("failed to relocate TLS provider");
    let consumer = Relocator::new()
        .run(
            loader
                .load_dylib(ElfBinary::new("consumer.so", &output.data))
                .expect("failed to load TLS consumer"),
        )
        .modules([&provider])
        .relocate()
        .expect("failed to relocate TLS consumer");

    TlsScenario {
        output,
        provider,
        consumer,
    }
}