elf_loader 0.15.0

A no_std-friendly ELF loader, runtime linker, and JIT linker for Rust.
#[cfg(feature = "lazy-binding")]
mod enabled {
    use crate::image::{
        CoreInner, DynamicInfo, Module, ModuleScope, RawDylib, RawDynamic, RawElf, RawExec,
    };
    use crate::{
        RelocationError, Result,
        arch::{NativeArch, prepare_lazy_bind},
        elf::{ElfRelEntry, ElfRelType, SymbolInfo},
        relocation::{BindingMode, RelocAddr, RelocationArch, SupportLazy, SymDef, unlikely},
        sync::Arc,
        tls::lookup_tls_get_addr,
    };
    use core::ptr::NonNull;

    impl<D: 'static, Arch: RelocationArch> SupportLazy for RawDynamic<D, Arch> {}

    impl<D: 'static, Arch: RelocationArch> SupportLazy for RawDylib<D, Arch> {}

    impl<D: 'static, Arch: RelocationArch> SupportLazy for RawExec<D, Arch> {}

    impl<D: 'static, Arch: RelocationArch> SupportLazy for RawElf<D, Arch> {}

    fn lookup_addr<Arch: RelocationArch>(
        source: &dyn Module<Arch>,
        name: &str,
    ) -> Option<RelocAddr> {
        let syminfo = SymbolInfo::from_str(name, None);
        let mut precompute = syminfo.precompute();
        let sym = source.lookup_symbol(&syminfo, &mut precompute)?;
        Some(SymDef::<(), Arch>::new(Some(sym), source).convert())
    }

    pub(crate) enum ResolvedBinding {
        Eager,
        Lazy,
    }

    impl ResolvedBinding {
        #[inline]
        pub(crate) fn is_lazy(&self) -> bool {
            matches!(self, Self::Lazy)
        }

        pub(crate) fn prepare_plt<D, Arch: RelocationArch>(
            &self,
            image: &RawDynamic<D, Arch>,
        ) -> Result<()>
        where
            D: 'static,
        {
            if self.is_lazy() {
                let pltrel = image.relocation().pltrel;
                if pltrel.is_empty() {
                    return Ok(());
                }

                let got = lazy_binding_got(image)?;
                let core = image.core_ref();
                prepare_lazy_bind(got.as_ptr(), RelocAddr::from_ptr(Arc::as_ptr(&core.inner)));
            }
            Ok(())
        }

        pub(crate) fn relocate_jump_slot<Arch: RelocationArch>(
            &self,
            base: RelocAddr,
            rel: &ElfRelType<Arch>,
        ) -> bool {
            if !self.is_lazy() {
                return false;
            }

            let addr = base.offset(rel.r_offset());
            let ptr = addr.as_mut_ptr::<usize>();
            unsafe {
                let origin_val = ptr.read();
                let new_val = base.offset(origin_val).into_inner();
                ptr.write(new_val);
            }
            true
        }
    }

    impl<D, Arch: RelocationArch> RawDynamic<D, Arch> {
        pub(crate) fn resolve_binding(&self, binding: BindingMode) -> ResolvedBinding {
            match binding {
                BindingMode::Default => {
                    if self.is_lazy() {
                        ResolvedBinding::Lazy
                    } else {
                        ResolvedBinding::Eager
                    }
                }
                BindingMode::Eager => ResolvedBinding::Eager,
                BindingMode::Lazy => ResolvedBinding::Lazy,
            }
        }

        pub(crate) fn install_lazy_lookup(
            &self,
            binding: ResolvedBinding,
            scope: ModuleScope<Arch>,
        ) -> Result<()>
        where
            D: 'static,
        {
            if let ResolvedBinding::Lazy = binding {
                if self.relocation().pltrel.is_empty() {
                    return Ok(());
                }

                let dynamic_info = self.core_ref().inner.dynamic_info.as_ref().ok_or(
                    RelocationError::LazyBindingSetup {
                        detail: "lazy binding requires dynamic metadata",
                    },
                )?;
                let info = unsafe { &mut *(Arc::as_ptr(dynamic_info) as *mut DynamicInfo<Arch>) };
                info.lazy.scope = Some(scope);
            }
            Ok(())
        }
    }

    fn lazy_binding_got<D, Arch: RelocationArch>(
        image: &RawDynamic<D, Arch>,
    ) -> Result<NonNull<usize>>
    where
        D: 'static,
    {
        image.got().ok_or(
            RelocationError::LazyBindingSetup {
                detail: "lazy binding requires a GOT/PLTGOT entry",
            }
            .into(),
        )
    }

    #[cold]
    #[inline(never)]
    fn unresolved_symbol(name: &str, symbol: &str) -> ! {
        panic!("lazy binding failed for {name}: unresolved symbol {symbol}");
    }

    #[cold]
    #[inline(never)]
    fn invalid_state(name: &str, reason: &str) -> ! {
        panic!("lazy binding failed for {name}: {reason}");
    }

    #[cold]
    #[inline(never)]
    fn invalid_relocation(name: &str, rela_idx: usize, rel: &ElfRelType) -> ! {
        panic!(
            "lazy binding failed for {name}: invalid PLT relocation {rela_idx} (type {}, sym {})",
            rel.r_type(),
            rel.r_symbol()
        );
    }

    #[cold]
    #[inline(never)]
    fn invalid_relocation_index(name: &str, rela_idx: usize, len: usize) -> ! {
        panic!(
            "lazy binding failed for {name}: relocation index {rela_idx} out of range (len {len})"
        );
    }

    #[cold]
    #[inline(never)]
    fn invalid_symbol_index(name: &str, r_sym: usize, sym_count: usize) -> ! {
        panic!(
            "lazy binding failed for {name}: symbol index {r_sym} out of range (len {})",
            sym_count
        );
    }

    pub(crate) unsafe extern "C" fn dl_fixup(dylib: &CoreInner, rela_idx: usize) -> usize {
        let Some(dynamic_info) = dylib.dynamic_info.as_ref() else {
            invalid_state(dylib.path.as_str(), "missing dynamic metadata")
        };
        let pltrel = dynamic_info.lazy.pltrel;

        let Some(rela) = pltrel.get(rela_idx) else {
            invalid_relocation_index(dylib.path.as_str(), rela_idx, pltrel.len())
        };
        let r_type = rela.r_type();
        let r_sym = rela.r_symbol();
        let segments = &dylib.segments;

        if unlikely(r_type != <NativeArch as RelocationArch>::JUMP_SLOT || r_sym == 0) {
            invalid_relocation(dylib.path.as_str(), rela_idx, rela);
        }

        let sym_count = dylib.symtab.count_syms();
        if unlikely(r_sym >= sym_count) {
            invalid_symbol_index(dylib.path.as_str(), r_sym, sym_count);
        }

        let (_, syminfo) = dylib.symtab.symbol_idx(r_sym);

        let Some(scope) = dynamic_info.lazy.scope.as_ref() else {
            invalid_state(dylib.path.as_str(), "missing lazy lookup")
        };
        let symbol = lookup_tls_get_addr(syminfo.name(), dylib.tls.tls_get_addr())
            .map(RelocAddr::from_ptr)
            .or_else(|| {
                scope
                    .iter()
                    .find_map(|source| lookup_addr::<NativeArch>(&**source, syminfo.name()))
            })
            .unwrap_or_else(|| unresolved_symbol(dylib.path.as_str(), syminfo.name()));

        segments.write(rela.r_offset(), symbol);
        symbol.into_inner()
    }
}

#[cfg(not(feature = "lazy-binding"))]
mod disabled {
    use crate::{
        elf::ElfRelType,
        image::{ModuleScope, RawDynamic},
        relocation::{BindingMode, RelocationArch},
    };

    pub(crate) enum ResolvedBinding {
        Eager,
    }

    impl ResolvedBinding {
        #[inline]
        pub(crate) const fn is_lazy(&self) -> bool {
            false
        }

        pub(crate) fn prepare_plt<D, Arch: RelocationArch>(
            &self,
            _image: &RawDynamic<D, Arch>,
        ) -> crate::Result<()>
        where
            D: 'static,
        {
            Ok(())
        }

        pub(crate) fn relocate_jump_slot<Arch: RelocationArch>(
            &self,
            _base: crate::relocation::RelocAddr,
            _rel: &ElfRelType<Arch>,
        ) -> bool {
            false
        }
    }

    impl<D, Arch: RelocationArch> RawDynamic<D, Arch> {
        pub(crate) fn resolve_binding(&self, _binding: BindingMode) -> ResolvedBinding {
            ResolvedBinding::Eager
        }

        pub(crate) fn install_lazy_lookup(
            &self,
            _binding: ResolvedBinding,
            _scope: ModuleScope<Arch>,
        ) -> crate::Result<()>
        where
            D: 'static,
        {
            Ok(())
        }
    }
}

#[cfg(not(feature = "lazy-binding"))]
pub(crate) use disabled::ResolvedBinding;
#[cfg(feature = "lazy-binding")]
pub(crate) use enabled::{ResolvedBinding, dl_fixup};