elf_loader 0.15.1

A no_std-friendly ELF loader, runtime linker, and JIT linker for Rust.
Documentation
use super::{KeyResolver, ResolvedKey};
use crate::{
    Error, IoError, LinkerError, ParseEhdrError, Result,
    input::{ElfFile, ElfReader, Path, PathBuf},
    linker::{DependencyRequest, RootRequest},
    relocation::RelocationArch,
    sync::Arc,
};
use alloc::{boxed::Box, vec::Vec};
use core::fmt;

fn expand_origin(value: &str, origin: &Path) -> PathBuf {
    PathBuf::from(
        value
            .replace("${ORIGIN}", origin.as_str())
            .replace("$ORIGIN", origin.as_str()),
    )
}

/// Runtime directory provider used by [`SearchDirSource::Dynamic`].
///
/// Implementations append directories to `out` in the order they should be
/// searched for `request.requested()`.
pub type SearchDirProvider =
    dyn for<'req> Fn(CandidateRequest<'req>, &mut Vec<PathBuf>) -> Result<()> + 'static;

/// One ordered source of search directories.
#[derive(Clone)]
pub enum SearchDirSource {
    /// A fixed directory joined with the requested value when it has no
    /// directory separators.
    Fixed(PathBuf),
    /// A runtime directory source that can inspect the current request.
    Dynamic(Arc<SearchDirProvider>),
}

impl SearchDirSource {
    /// Creates a fixed search directory source.
    #[inline]
    pub fn fixed(dir: impl Into<PathBuf>) -> Self {
        Self::Fixed(dir.into())
    }

    /// Creates a callback-backed search directory source.
    #[inline]
    pub fn dynamic<F>(resolver: F) -> Self
    where
        F: for<'req> Fn(CandidateRequest<'req>, &mut Vec<PathBuf>) -> Result<()> + 'static,
    {
        Self::Dynamic(Arc::from(Box::new(resolver) as Box<SearchDirProvider>))
    }
}

impl fmt::Debug for SearchDirSource {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Fixed(dir) => f.debug_tuple("Fixed").field(dir).finish(),
            Self::Dynamic(_) => f.write_str("Dynamic(..)"),
        }
    }
}

/// Context passed to dynamic search directory providers.
#[derive(Clone, Copy, Debug)]
pub enum CandidateRequest<'a> {
    /// Resolving the root key passed to [`KeyResolver::load_root`].
    Root {
        /// Path requested by the root load.
        requested: &'a Path,
    },
    /// Resolving one `DT_NEEDED` entry for an already-loaded owner.
    Dependency {
        /// Dependency name after applying `$ORIGIN` to the requested value.
        requested: &'a Path,
        /// Diagnostic name of the owner that requested this dependency.
        owner_name: &'a str,
        /// Loaded path/key of the owner that requested this dependency.
        owner_path: &'a Path,
        /// Raw `DT_RUNPATH` value, when present.
        runpath: Option<&'a str>,
        /// Raw `DT_RPATH` value, when present.
        rpath: Option<&'a str>,
    },
}

impl<'a> CandidateRequest<'a> {
    /// Creates a root candidate request.
    #[inline]
    pub const fn root(requested: &'a Path) -> Self {
        Self::Root { requested }
    }

    /// Creates a dependency candidate request.
    #[inline]
    pub const fn dependency(
        requested: &'a Path,
        owner_name: &'a str,
        owner_path: &'a Path,
        runpath: Option<&'a str>,
        rpath: Option<&'a str>,
    ) -> Self {
        Self::Dependency {
            requested,
            owner_name,
            owner_path,
            runpath,
            rpath,
        }
    }

    /// Returns the requested root path or dependency name/path.
    #[inline]
    pub const fn requested(&self) -> &'a Path {
        match self {
            Self::Root { requested } | Self::Dependency { requested, .. } => requested,
        }
    }

    /// Returns the owner name for dependency requests.
    #[inline]
    pub const fn owner_name(&self) -> Option<&'a str> {
        match self {
            Self::Root { .. } => None,
            Self::Dependency { owner_name, .. } => Some(owner_name),
        }
    }

    /// Returns the owner path for dependency requests.
    #[inline]
    pub const fn owner_path(&self) -> Option<&'a Path> {
        match self {
            Self::Root { .. } => None,
            Self::Dependency { owner_path, .. } => Some(owner_path),
        }
    }

    /// Returns the owner directory used for `$ORIGIN` expansion.
    #[inline]
    pub fn origin(&self) -> Option<&'a Path> {
        match self {
            Self::Root { .. } => None,
            Self::Dependency { owner_path, .. } => Some(owner_path.parent()),
        }
    }

    /// Returns expanded `DT_RUNPATH` directories for dependency requests.
    #[inline]
    pub fn runpath(&self) -> Option<Vec<PathBuf>> {
        match self {
            Self::Root { .. } => None,
            Self::Dependency { runpath, .. } => self.expand_dynamic_path_list(*runpath),
        }
    }

    /// Returns expanded `DT_RPATH` directories for dependency requests.
    #[inline]
    pub fn rpath(&self) -> Option<Vec<PathBuf>> {
        match self {
            Self::Root { .. } => None,
            Self::Dependency { rpath, .. } => self.expand_dynamic_path_list(*rpath),
        }
    }

    fn expand_dynamic_path_list(&self, path_list: Option<&str>) -> Option<Vec<PathBuf>> {
        let Self::Dependency {
            requested,
            owner_path,
            ..
        } = self
        else {
            return None;
        };

        if requested.has_dir_separator() {
            return None;
        }

        let origin = owner_path.parent();
        Some(
            path_list?
                .split(':')
                .filter(|dir| !dir.is_empty())
                .map(|dir| expand_origin(dir, origin))
                .collect(),
        )
    }
}

/// Filesystem-backed dependency resolver for [`Linker`](crate::linker::Linker).
///
/// `SearchPathResolver` is an opt-in convenience resolver for callers whose
/// linker keys can be viewed as loader paths and constructed from resolved
/// paths. Root requests and dependencies with directory separators are tried
/// directly. Plain-name searches walk the ordered
/// [`SearchDirSource`] list.
///
/// This resolver intentionally does not model the host dynamic linker's global
/// policy: it does not read `LD_LIBRARY_PATH`, system cache files, or default
/// system library directories unless callers add runtime directory providers
/// for them.
pub struct SearchPathResolver {
    search_dir_sources: Vec<SearchDirSource>,
}

impl Clone for SearchPathResolver {
    fn clone(&self) -> Self {
        Self {
            search_dir_sources: self.search_dir_sources.clone(),
        }
    }
}

impl Default for SearchPathResolver {
    fn default() -> Self {
        Self::new()
    }
}

impl fmt::Debug for SearchPathResolver {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("SearchPathResolver")
            .field("search_dir_sources", &self.search_dir_sources)
            .finish()
    }
}

impl SearchPathResolver {
    /// Creates an empty search-path resolver.
    #[inline]
    pub fn new() -> Self {
        Self {
            search_dir_sources: Vec::new(),
        }
    }

    /// Appends one search directory source.
    pub fn push_search_dir_source(&mut self, source: SearchDirSource) -> &mut Self {
        self.search_dir_sources.push(source);
        self
    }

    /// Appends a fixed search directory.
    pub fn push_fixed_dir(&mut self, dir: impl Into<PathBuf>) -> &mut Self {
        self.push_search_dir_source(SearchDirSource::Fixed(dir.into()))
    }

    /// Appends a callback that can provide search directories per request.
    pub fn push_search_dir_provider<F>(&mut self, provider: F) -> &mut Self
    where
        F: for<'req> Fn(CandidateRequest<'req>, &mut Vec<PathBuf>) -> Result<()> + 'static,
    {
        self.push_search_dir_source(SearchDirSource::Dynamic(Arc::from(
            Box::new(provider) as Box<SearchDirProvider>
        )))
    }

    /// Returns the configured search directory sources in lookup order.
    #[inline]
    pub fn search_dir_sources(&self) -> &[SearchDirSource] {
        &self.search_dir_sources
    }

    fn resolve_key(&self, request: CandidateRequest<'_>) -> Result<Option<(PathBuf, ElfFile)>> {
        let try_candidate = |candidate: PathBuf| -> Result<Option<(PathBuf, ElfFile)>> {
            let Some(file) = Self::open_elf(&candidate)? else {
                return Ok(None);
            };
            let key = match request {
                CandidateRequest::Root { requested } => PathBuf::from(requested),
                CandidateRequest::Dependency { .. } => candidate,
            };

            Ok(Some((key, file)))
        };

        let requested = request.requested();
        let has_dir_separator = requested.has_dir_separator();
        if matches!(request, CandidateRequest::Root { .. }) || has_dir_separator {
            if let Some(resolved) = try_candidate(PathBuf::from(requested))? {
                return Ok(Some(resolved));
            }
        }

        if has_dir_separator {
            return Ok(None);
        }

        let mut dynamic_dirs = Vec::new();
        for source in &self.search_dir_sources {
            match source {
                SearchDirSource::Fixed(dir) => {
                    if let Some(resolved) = try_candidate(dir.join(requested.as_str()))? {
                        return Ok(Some(resolved));
                    }
                }
                SearchDirSource::Dynamic(resolver) => {
                    dynamic_dirs.clear();
                    resolver(request, &mut dynamic_dirs)?;
                    for dir in &dynamic_dirs {
                        if let Some(resolved) = try_candidate(dir.join(requested.as_str()))? {
                            return Ok(Some(resolved));
                        }
                    }
                }
            }
        }

        Ok(None)
    }

    #[inline]
    fn resolved_key<'cfg, K, Arch: RelocationArch>(
        key: K,
        file: ElfFile,
        is_visible: bool,
    ) -> ResolvedKey<'cfg, K, Arch> {
        if is_visible {
            ResolvedKey::existing(key)
        } else {
            ResolvedKey::load(key, file)
        }
    }

    /// Open `path` if it exists, returning `Ok(None)` for ordinary open
    /// failures and propagating parse/read errors for files that were found.
    fn open_elf(path: &Path) -> Result<Option<ElfFile>> {
        let mut file = match ElfFile::from_path(path) {
            Ok(file) => file,
            Err(Error::Io(IoError::OpenFailed { .. })) => return Ok(None),
            Err(err) => return Err(err),
        };

        let mut magic = [0; 4];
        file.read(&mut magic, 0)?;
        if magic == *b"\x7fELF" {
            Ok(Some(file))
        } else {
            Err(ParseEhdrError::InvalidMagic.into())
        }
    }
}

impl<'cfg, K, Arch> KeyResolver<'cfg, K, Arch> for SearchPathResolver
where
    K: Clone + AsRef<Path> + From<PathBuf>,
    Arch: RelocationArch,
{
    fn load_root(&mut self, req: &RootRequest<'_, K>) -> Result<ResolvedKey<'cfg, K, Arch>> {
        if let Some((_, file)) = self.resolve_key(CandidateRequest::root(req.key().as_ref()))? {
            let key = req.key().clone();
            let is_visible = req.is_visible(&key);
            return Ok(Self::resolved_key(key, file, is_visible));
        }

        Err(LinkerError::resolver("root module was not found by SearchPathResolver").into())
    }

    fn resolve_dependency(
        &mut self,
        req: &DependencyRequest<'_, K>,
    ) -> Result<ResolvedKey<'cfg, K, Arch>> {
        let origin = req.owner_path().parent();
        let needed = expand_origin(req.needed(), origin);
        let request = CandidateRequest::dependency(
            needed.as_path(),
            req.owner_name(),
            req.owner_path(),
            req.runpath(),
            req.rpath(),
        );
        if let Some((key, file)) = self.resolve_key(request)? {
            let key = K::from(key);
            let is_visible = req.is_visible(&key);
            return Ok(Self::resolved_key(key, file, is_visible));
        }

        Err(req.unresolved())
    }
}