elf_loader 0.15.0

A no_std-friendly ELF loader, runtime linker, and JIT linker for Rust.
#![allow(dead_code)]

use std::collections::HashMap;

use elf_loader::{
    Loader,
    arch::NativeArch,
    image::{LoadedCore, ModuleHandle},
    input::ElfBinary,
    relocation::RelocationArch,
};

const REL_COPY: u32 = <NativeArch as RelocationArch>::COPY.raw();
const REL_GOT: u32 = <NativeArch as RelocationArch>::GOT.raw();
const REL_IRELATIVE: u32 = <NativeArch as RelocationArch>::IRELATIVE.raw();
const REL_JUMP_SLOT: u32 = <NativeArch as RelocationArch>::JUMP_SLOT.raw();
const REL_RELATIVE: u32 = <NativeArch as RelocationArch>::RELATIVE.raw();
const REL_SYMBOLIC: u32 = <NativeArch as RelocationArch>::SYMBOLIC.raw();
#[cfg(feature = "tls")]
const REL_DTPMOD: u32 = <NativeArch as RelocationArch>::DTPMOD.raw();
#[cfg(feature = "tls")]
const REL_DTPOFF: u32 = <NativeArch as RelocationArch>::DTPOFF.raw();
use gen_elf::{
    Arch, DylibWriter, ElfWriteOutput, ElfWriterConfig, RelocEntry, RelocationInfo, SymbolDesc,
};

use super::BindingKind;
use crate::support::{
    dylib_relocation_checks,
    host_symbols::{
        COPY_VAR_NAME, EXTERNAL_FUNC_NAME, EXTERNAL_FUNC_NAME2, EXTERNAL_TLS_NAME,
        EXTERNAL_TLS_NAME2, EXTERNAL_VAR_NAME, F64Pair, LOCAL_VAR_NAME, TestHostSymbols,
    },
};

pub(super) const IFUNC_RESOLVER_OFFSET: u64 = 100;
pub(super) const FLOAT_TOLERANCE: f64 = 0.0001;

type HostExternalFn = extern "C" fn(
    i64,
    i64,
    i64,
    i64,
    i64,
    i64,
    i64,
    i64,
    F64Pair,
    f64,
    f64,
    f64,
    f64,
    f64,
    f64,
    f64,
) -> f64;

#[cfg(feature = "tls")]
type TlsHelperFn = extern "C" fn() -> *mut u32;

fn dynamic_relocation_entries() -> Vec<RelocEntry> {
    let relocations = vec![
        RelocEntry::with_name(EXTERNAL_FUNC_NAME, REL_JUMP_SLOT),
        RelocEntry::with_name(EXTERNAL_FUNC_NAME2, REL_JUMP_SLOT),
        RelocEntry::with_name(EXTERNAL_VAR_NAME, REL_GOT),
        RelocEntry::with_name(LOCAL_VAR_NAME, REL_SYMBOLIC),
        RelocEntry::with_name(COPY_VAR_NAME, REL_COPY),
        RelocEntry::new(REL_RELATIVE),
        RelocEntry::new(REL_IRELATIVE),
    ];
    #[cfg(feature = "tls")]
    let relocations = relocations.into_iter().chain([
        RelocEntry::with_name(EXTERNAL_TLS_NAME, REL_DTPMOD),
        RelocEntry::with_name(EXTERNAL_TLS_NAME, REL_DTPOFF),
        RelocEntry::with_name(EXTERNAL_TLS_NAME2, REL_DTPMOD),
        RelocEntry::with_name(EXTERNAL_TLS_NAME2, REL_DTPOFF),
    ]);
    relocations.into_iter().collect()
}

pub(crate) struct BindingFixture {
    helper_output: ElfWriteOutput,
    host_symbols: TestHostSymbols,
}

impl BindingFixture {
    pub(crate) fn new() -> Self {
        let arch = Arch::current();
        let config = ElfWriterConfig::default().with_ifunc_resolver_val(IFUNC_RESOLVER_OFFSET);

        let helper_symbols = vec![SymbolDesc::global_object(
            COPY_VAR_NAME,
            &[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88],
        )];
        #[cfg(feature = "tls")]
        let helper_symbols = helper_symbols.into_iter().chain([
            SymbolDesc::global_tls(EXTERNAL_TLS_NAME, &[0xAA, 0xBB, 0xCC, 0xDD]),
            SymbolDesc::global_tls(EXTERNAL_TLS_NAME2, &[0x11, 0x22, 0x33, 0x44]),
        ]);
        let helper_symbols: Vec<_> = helper_symbols.into_iter().collect();

        let helper_output = DylibWriter::with_config(arch, config.clone())
            .write(&[], &helper_symbols)
            .expect("failed to generate helper ELF");

        Self {
            helper_output,
            host_symbols: TestHostSymbols::new(),
        }
    }

    fn write_main_output(&self, binding: BindingKind) -> ElfWriteOutput {
        let arch = Arch::current();
        let config = ElfWriterConfig::default()
            .with_ifunc_resolver_val(IFUNC_RESOLVER_OFFSET)
            .with_bind_now(!binding.is_lazy());

        let main_symbols = vec![
            SymbolDesc::global_object(LOCAL_VAR_NAME, &[0u8; 8]),
            SymbolDesc::undefined_func(EXTERNAL_FUNC_NAME),
            SymbolDesc::undefined_func(EXTERNAL_FUNC_NAME2),
            SymbolDesc::undefined_object(EXTERNAL_VAR_NAME),
            SymbolDesc::undefined_object(COPY_VAR_NAME).with_size(8),
        ];
        #[cfg(feature = "tls")]
        let main_symbols = main_symbols.into_iter().chain([
            SymbolDesc::undefined_tls(EXTERNAL_TLS_NAME),
            SymbolDesc::undefined_tls(EXTERNAL_TLS_NAME2),
        ]);
        let main_symbols: Vec<_> = main_symbols.into_iter().collect();

        DylibWriter::with_config(arch, config)
            .write(&dynamic_relocation_entries(), &main_symbols)
            .expect("failed to generate main ELF")
    }

    pub(crate) fn load(self, binding: BindingKind) -> BindingScenario {
        let main_output = self.write_main_output(binding);
        let loader = Loader::new();
        #[cfg(feature = "tls")]
        let mut loader = loader.with_default_tls_resolver();
        #[cfg(not(feature = "tls"))]
        let mut loader = loader;

        let helper_dylib = loader
            .load_dylib(ElfBinary::new("libhelper.so", &self.helper_output.data))
            .expect("failed to load helper")
            .relocator()
            .relocate()
            .expect("failed to relocate helper");

        let pending_dylib = loader
            .load_dylib(ElfBinary::new("test_dynamic.so", &main_output.data))
            .expect("failed to load dylib");

        let prepared_relocator = pending_dylib.relocator().scope([
            ModuleHandle::from(self.host_symbols.source("__host")),
            ModuleHandle::from(&helper_dylib),
        ]);

        #[cfg(feature = "lazy-binding")]
        let loaded_dylib = if binding.is_lazy() {
            prepared_relocator.lazy().relocate()
        } else {
            prepared_relocator.relocate()
        }
        .expect("failed to relocate dylib");

        #[cfg(not(feature = "lazy-binding"))]
        let loaded_dylib = {
            prepared_relocator
                .relocate()
                .expect("failed to relocate dylib")
        };

        BindingScenario {
            binding,
            main_output,
            helper_dylib,
            loaded_dylib,
            host_symbol_addresses: self.host_symbols.addresses,
        }
    }
}

pub(crate) struct BindingScenario {
    binding: BindingKind,
    main_output: ElfWriteOutput,
    helper_dylib: LoadedCore<()>,
    loaded_dylib: LoadedCore<()>,
    host_symbol_addresses: HashMap<&'static str, usize>,
}

impl BindingScenario {
    pub(crate) fn binding_kind(&self) -> BindingKind {
        self.binding
    }

    pub(crate) fn loaded_dylib(&self) -> &LoadedCore<()> {
        &self.loaded_dylib
    }

    pub(crate) fn helper_dylib(&self) -> &LoadedCore<()> {
        &self.helper_dylib
    }

    pub(crate) fn host_symbol_address(&self, name: &str) -> u64 {
        self.host_symbol_addresses
            .get(name)
            .copied()
            .unwrap_or_else(|| panic!("missing host symbol {name}")) as u64
    }

    pub(crate) fn relocation_for_symbol(&self, r_type: u32, symbol_name: &str) -> &RelocationInfo {
        dylib_relocation_checks::relocation_for_symbol(&self.main_output, r_type, symbol_name)
    }

    pub(crate) fn anonymous_relocations(&self, r_type: u32) -> Vec<&RelocationInfo> {
        dylib_relocation_checks::anonymous_relocations(&self.main_output, r_type)
    }

    pub(crate) fn slot_address(&self, relocation: &RelocationInfo) -> usize {
        dylib_relocation_checks::slot_address(&self.loaded_dylib, relocation)
    }

    pub(crate) fn slot_word(&self, relocation: &RelocationInfo) -> u64 {
        dylib_relocation_checks::slot_word(&self.loaded_dylib, relocation)
    }

    pub(crate) fn loaded_symbol_address(&self, name: &str) -> Option<u64> {
        unsafe {
            self.loaded_dylib
                .get::<*const ()>(name)
                .map(|symbol| symbol.into_raw() as u64)
        }
    }

    pub(crate) fn expected_external_call_result(&self) -> (F64Pair, f64) {
        let vector = F64Pair([9.9, 10.10]);
        let expected = (1..9).sum::<i64>() as f64
            + (1..8).map(|i| i as f64 + i as f64 / 10.0).sum::<f64>()
            + vector.0[0]
            + vector.0[1];
        (vector, expected)
    }

    pub(crate) fn call_plt_helper(&self, symbol_name: &str) -> f64 {
        let helper_name = format!("{symbol_name}@plt_helper");
        let helper_fn: HostExternalFn = unsafe {
            core::mem::transmute(
                self.loaded_dylib
                    .get::<*const ()>(&helper_name)
                    .unwrap_or_else(|| panic!("missing helper symbol {helper_name}"))
                    .into_raw(),
            )
        };

        let (vector, _) = self.expected_external_call_result();
        helper_fn(
            1, 2, 3, 4, 5, 6, 7, 8, vector, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7,
        )
    }

    #[cfg(feature = "tls")]
    pub(crate) fn tls_helper(&self, symbol_name: &str) -> TlsHelperFn {
        let helper_name = format!("{symbol_name}@tls_helper");
        unsafe {
            core::mem::transmute(
                self.loaded_dylib
                    .get::<*const ()>(&helper_name)
                    .unwrap_or_else(|| panic!("missing TLS helper symbol {helper_name}"))
                    .into_raw(),
            )
        }
    }
}