elf_loader 0.16.0

A no_std-friendly ELF loader and runtime linker for Rust.
Documentation
use super::{ElfReader, IntoElfReader, Path, PathBuf};
use crate::{Result, logging, os::RawFile};
use alloc::{borrow::Cow, boxed::Box, string::String, vec::Vec};
use core::ops::Range;

fn checked_read_range(offset: usize, len: usize, available: usize) -> Result<Range<usize>> {
    let Some(end) = offset.checked_add(len).filter(|&end| end <= available) else {
        return Err(
            crate::IoError::ReadOutOfBounds(Box::new(crate::ReadBoundsError::new(
                offset, len, available,
            )))
            .into(),
        );
    };
    Ok(offset..end)
}

/// An ELF object source backed by an in-memory byte slice.
///
/// This is useful for loading ELF files that are already in memory, such as
/// those embedded in the binary or received over a network.
#[derive(Debug)]
pub struct ElfBinary<'bytes> {
    /// Loader source path or caller-provided source identifier.
    path: PathBuf,
    /// The raw ELF data.
    bytes: Cow<'bytes, [u8]>,
}

impl<'bytes> ElfBinary<'bytes> {
    /// Creates a new memory-based ELF object from a byte slice.
    ///
    /// # Examples
    /// ```rust
    /// use elf_loader::input::ElfBinary;
    ///
    /// let data = &[]; // In practice, this would be the bytes of an ELF file
    /// let binary = ElfBinary::new("liba.so", data);
    /// ```
    pub fn new(path: impl Into<PathBuf>, bytes: &'bytes [u8]) -> Self {
        Self {
            path: path.into(),
            bytes: Cow::Borrowed(bytes),
        }
    }

    /// Creates a new memory-based ELF object that owns its bytes.
    ///
    /// This is useful for scanned images, which retain their reader until the
    /// later mapping phase.
    pub fn owned(path: impl Into<PathBuf>, bytes: impl Into<Vec<u8>>) -> Self {
        Self {
            path: path.into(),
            bytes: Cow::Owned(bytes.into()),
        }
    }
}

impl<'bytes> ElfReader for ElfBinary<'bytes> {
    /// Returns the source path or caller-provided identifier.
    fn path(&self) -> &Path {
        &self.path
    }

    /// Returns the byte length of the in-memory ELF object.
    fn len(&self) -> usize {
        self.bytes.as_ref().len()
    }

    /// Reads data from the memory-based ELF object.
    fn read(&self, buf: &mut [u8], offset: usize) -> crate::Result<()> {
        let bytes = self.bytes.as_ref();
        let range = checked_read_range(offset, buf.len(), bytes.len())?;
        buf.copy_from_slice(&bytes[range]);
        Ok(())
    }

    /// Borrows data directly from the memory-based ELF object.
    fn borrow_bytes(&self, offset: usize, len: usize) -> Result<Option<&[u8]>> {
        let bytes = self.bytes.as_ref();
        let range = checked_read_range(offset, len, bytes.len())?;
        Ok(Some(&bytes[range]))
    }

    /// Returns `None` since memory-based objects don't have file descriptors.
    fn as_fd(&self) -> Option<isize> {
        None
    }
}

/// An ELF object source backed by a file on the filesystem.
///
/// This implementation uses standard file I/O to read ELF data. It also
/// provides access to the underlying file descriptor for memory mapping.
pub struct ElfFile {
    /// The underlying OS-specific file handle.
    inner: RawFile,
}

impl ElfFile {
    /// Creates a new file-based ELF object from an owned file descriptor.
    ///
    /// # Safety
    /// The caller must ensure that `raw_fd` is valid and owned by this object.
    pub unsafe fn from_owned_fd(path: impl AsRef<Path>, raw_fd: i32) -> Result<Self> {
        let path = path.as_ref();
        Ok(ElfFile {
            inner: RawFile::from_owned_fd(path, raw_fd)?,
        })
    }

    /// Creates a new file-based ELF object by opening a file at the given path.
    pub fn from_path(path: impl AsRef<Path>) -> Result<Self> {
        let path = path.as_ref();
        logging::debug!("Opening ELF file: {}", path);

        let inner = RawFile::from_path(path)?;
        Ok(ElfFile { inner })
    }
}

impl ElfReader for ElfFile {
    /// Returns the path of the ELF file.
    fn path(&self) -> &Path {
        self.inner.path()
    }

    /// Returns the byte length of the file-backed ELF object.
    fn len(&self) -> usize {
        self.inner.len()
    }

    /// Reads data from the file-based ELF object.
    fn read(&self, buf: &mut [u8], offset: usize) -> Result<()> {
        self.inner.read(buf, offset)
    }

    /// Returns the raw file descriptor for the underlying file.
    fn as_fd(&self) -> Option<isize> {
        self.inner.as_fd()
    }
}

// Implementation of `ElfReader` for byte slices.
//
// This allows users to pass a byte slice directly to loading functions
// for in-memory ELF data.
impl ElfReader for &[u8] {
    /// Returns a generic name for memory-based data.
    fn path(&self) -> &Path {
        Path::new("<memory>")
    }

    /// Returns the byte length of the slice-backed ELF object.
    fn len(&self) -> usize {
        (**self).len()
    }

    /// Reads data from the byte slice at the specified offset.
    fn read(&self, buf: &mut [u8], offset: usize) -> Result<()> {
        let bytes = *self;
        let range = checked_read_range(offset, buf.len(), bytes.len())?;
        buf.copy_from_slice(&bytes[range]);
        Ok(())
    }

    /// Borrows data directly from the byte slice.
    fn borrow_bytes(&self, offset: usize, len: usize) -> Result<Option<&[u8]>> {
        let bytes = *self;
        let range = checked_read_range(offset, len, bytes.len())?;
        Ok(Some(&bytes[range]))
    }

    /// Memory-based readers do not have file descriptors.
    fn as_fd(&self) -> Option<isize> {
        None
    }
}

// Implementation for string slices (file paths)
impl<'a> IntoElfReader<'a> for &'a str {
    type Reader = ElfFile;

    fn into_reader(self) -> Result<Self::Reader> {
        ElfFile::from_path(self)
    }
}

// Implementation for owned strings (file paths)
impl<'a> IntoElfReader<'a> for String {
    type Reader = ElfFile;

    fn into_reader(self) -> Result<Self::Reader> {
        ElfFile::from_path(self)
    }
}

impl<'a> IntoElfReader<'a> for &'a Path {
    type Reader = ElfFile;

    fn into_reader(self) -> Result<Self::Reader> {
        ElfFile::from_path(self)
    }
}

impl<'a> IntoElfReader<'a> for PathBuf {
    type Reader = ElfFile;

    fn into_reader(self) -> Result<Self::Reader> {
        ElfFile::from_path(self)
    }
}

impl<'a> IntoElfReader<'a> for &'a PathBuf {
    type Reader = ElfFile;

    fn into_reader(self) -> Result<Self::Reader> {
        ElfFile::from_path(self)
    }
}

// Implementation for byte slices (in-memory ELF data)
impl<'a> IntoElfReader<'a> for &'a [u8] {
    type Reader = ElfBinary<'a>;

    fn into_reader(self) -> Result<Self::Reader> {
        Ok(ElfBinary::new("<memory>", self))
    }
}

impl<'a> IntoElfReader<'a> for &'a Vec<u8> {
    type Reader = ElfBinary<'a>;

    fn into_reader(self) -> Result<Self::Reader> {
        Ok(ElfBinary::new("<memory>", self.as_slice()))
    }
}

impl<'a> IntoElfReader<'a> for Vec<u8> {
    type Reader = ElfBinary<'static>;

    fn into_reader(self) -> Result<Self::Reader> {
        Ok(ElfBinary::owned("<memory>", self))
    }
}

impl<'a> IntoElfReader<'a> for Box<[u8]> {
    type Reader = ElfBinary<'static>;

    fn into_reader(self) -> Result<Self::Reader> {
        Ok(ElfBinary::owned("<memory>", self.into_vec()))
    }
}

// Implementation for already constructed ElfFile (pass-through)
impl<'a> IntoElfReader<'a> for ElfFile {
    type Reader = ElfFile;

    fn into_reader(self) -> Result<Self::Reader> {
        Ok(self)
    }
}

// Implementation for already constructed ElfBinary (pass-through)
impl<'a, 'b> IntoElfReader<'a> for ElfBinary<'b>
where
    'b: 'a,
{
    type Reader = ElfBinary<'b>;

    fn into_reader(self) -> Result<Self::Reader> {
        Ok(self)
    }
}