elfy 0.2.2

A Rust crate for reading ELF files
Documentation
#![warn(missing_docs)]

//! A rusty crate for reading data from ELF files quickly and simply
//! 
//! Currently Elfy is focused on reading data important to statically compiled ARM
//! executables, in the future it will support more architectures and ELF features

use std::error::Error;
use std::fmt::{ Display, Formatter };
use std::io::{ Read, Seek, SeekFrom };
use std::collections::HashMap;

#[macro_use]
mod macros;
pub mod types;
pub mod numeric;

pub mod constants;

use crate::types::*;

/// The result type for Elfy, wraps a `ParseElfError`
pub type ParseElfResult<T> = std::result::Result<T, ParseElfError>;

/// The Elfy error type
/// 
/// Various errors may occur while parsing, including IO errors. This type captures all of them
#[derive(Debug)]
pub enum ParseElfError {
    /// Captures an `std::io::Error`, this error is generated by the Rust STL
    IoError{
        /// The captured `std::io::Error`
        inner: std::io::Error
    },

    #[allow(missing_docs)]
    InvalidSectionType(u32),

    #[allow(missing_docs)]
    InvalidProgramFlags(u32),

    #[allow(missing_docs)]
    InvalidProgramHeader(u32),
    
    #[allow(missing_docs)]
    InvalidVersion(u32),
    
    #[allow(missing_docs)]
    InvalidMachine(u16),
    
    #[allow(missing_docs)]
    InvalidElfType(u16),
    
    #[allow(missing_docs)]
    InvalidOsAbi(u8),
    
    #[allow(missing_docs)]
    InvalidIdentVersion(u8),
    
    #[allow(missing_docs)]
    InvalidDataFormat(u8),
    
    #[allow(missing_docs)]
    InvalidDataClass(u8),

    /// Indicates that the `Descriptor` used to parse a given item within an ELF file is invalid in the parsers current state
    /// 
    /// Generally, this should never be seen under normal use cases. It may signal the presence of a bug within Elfy.
    InvalidParsingDescriptor,
}

impl Error for ParseElfError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            ParseElfError::IoError{ inner } => Some(inner),
            _ => None,
        }
    }
}

impl Display for ParseElfError {
    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
        write!(f, "{:?}", self)
    }
}

impl From<std::io::Error> for ParseElfError {
    fn from(err: std::io::Error) -> ParseElfError {
        ParseElfError::IoError{ inner: err }
    }
}

/// Trait used to describe how individual parts of an ELF file are parsed
trait Parslet {
    fn parse<R: Read + Seek>(reader: &mut R, descriptor: &mut Descriptor) -> ParseElfResult<Self> where Self: Sized;
}

/// Describes context necessary to decode a given section of an ELF file
/// 
/// Correctly reading and interpretting ELF files is an incremental process, meaning
/// not all of the necessary context is available immediately and is revealed
/// as the file is processed. This type is used to maintain that context as we gather it.
enum Descriptor {
    None,
    Data{ format: DataFormat, class: DataClass },
}

impl Descriptor {
    fn data_class(&self) -> ParseElfResult<DataClass> {
        match self {
            Descriptor::Data{ class, .. } => Ok(*class), 
            Descriptor::None => Err(ParseElfError::InvalidParsingDescriptor),
        }
    }

    fn data_format(&self) -> ParseElfResult<DataFormat> {
        match self {
            Descriptor::Data{ format, .. } => Ok(*format),
            Descriptor::None => Err(ParseElfError::InvalidParsingDescriptor)
        }
    }
}


/// Represents a parsed ELF (Executable and Linkable Format) file.
/// 
/// The ELF format is a common standard file format for executable files, object code,
/// shared libraries, and core dumps.
#[derive(Debug)]
pub struct Elf {
    header: ElfHeader,
    sections: Vec<Section>,
    segments: Vec<Segment>,
    section_map: HashMap<String, usize>,
}

impl Elf {
    /// Loads an ELF file from disk and parses it
    /// 
    /// # Errors
    /// 
    /// Returns 'Err' if the file can not be loaded or if parsing fails, with a description of the failure
    /// 
    /// # Examples
    /// ```
    /// # use crate::elfy::*;    
    /// let elf = Elf::load("examples/example-binary").unwrap();
    /// ```
    pub fn load<P: AsRef<std::path::Path>>(path: P) -> ParseElfResult<Elf> {
        let file = std::fs::File::open(path)?;
        let mut buf = std::io::BufReader::new(file);
        
        Ok(Elf::parse(&mut buf)?)
    }

    /// Parses an ELF file from a reader source
    /// 
    /// 'reader' can be anything that implements both 'Read' and 'Seek'
    /// 
    /// # Errors
    /// 
    /// Returns 'Err' if parsing fails, with a description of what caused the failure
    /// 
    /// # Examples
    /// ```
    /// # use crate::elfy::*;    
    /// # let file = std::fs::File::open("examples/example-binary").unwrap();
    /// let mut buf = std::io::BufReader::new(file);
    /// let elf = Elf::parse(&mut buf).unwrap();
    /// ```
    pub fn parse<R: Read + Seek>(reader: &mut R) -> ParseElfResult<Elf> {
        let mut descriptor = Descriptor::None;
        
        let header = ElfHeader::parse(reader, &mut descriptor)?;
        let sections = parse_sections(reader, &mut descriptor, &header)?;
        let segments = parse_segments(reader, &mut descriptor, &header)?;
        let mut section_map = HashMap::new();

        associate_string_table(&mut section_map, &sections, &header);

        Ok(Elf{
            header,
            sections,
            segments,
            section_map
        })
    }

    /// Tries to retrieve a section by name, returns 'None' if the section does not exist
    /// 
    /// # Examples
    /// ```
    /// # use crate::elfy::prelude::*;
    /// 
    /// let elf = Elf::load("examples/example-binary").unwrap();
    /// let text = elf.try_get_section(".text").unwrap();
    /// 
    /// assert_eq!(SectionFlags::AllocExecute, text.header().flags());
    /// ```
    pub fn try_get_section(&self, section_name: &str) -> Option<&Section> {
        self.sections.get(*self.section_map.get(section_name)?)
    }

    /// Returns the ELF files header
    pub fn header(&self) -> &ElfHeader {
        &self.header
    }

    /// Returns an `Iterator` that iterates over references of sections contained in this `Elf` file
    pub fn sections(&self) -> SectionIter {
        SectionIter {
            elf: &self,
            idx: 0
        }
    }

    /// Returns an `Iterator` that iterates over references of program headers contained in this `Elf` file
    pub fn segments(&self) -> SegmentIter {
        SegmentIter {
            elf: &self,
            idx: 0
        }
    }
}

// TODO: Iterators for headers and sections
fn parse_sections<R: Read + Seek>(reader: &mut R, descriptor: &mut Descriptor, header: &ElfHeader) -> ParseElfResult<Vec<Section>> {
    reader.seek(SeekFrom::Start(header.section_headers_offset()))?;
    let mut sections = Vec::new();
    for _ in 0..header.section_header_count() {
        sections.push(Section::parse(reader, descriptor)?)
    }
    Ok(sections)
}

fn parse_segments<R: Read + Seek>(reader: &mut R, descriptor: &mut Descriptor, header: &ElfHeader) -> ParseElfResult<Vec<Segment>> {
    reader.seek(SeekFrom::Start(header.program_headers_offset()))?;
    let mut segments = Vec::new();
    for _ in 0..header.program_header_count() {
        segments.push(Segment::parse(reader, descriptor)?)
    }
    Ok(segments)
}

fn associate_string_table(section_map: &mut HashMap<String, usize>, sections: &[Section], header: &ElfHeader) {
    if let Some(idx) = header.section_name_table_index() {
        if let SectionData::Strings(table) = &sections[idx].data() {
            for (i, _section) in sections.iter().enumerate() {
                let name = table[i].clone();
                section_map.insert(name, i);
            }
        }
    }
}

/// Iterator over the sections in an ELF file
pub struct SectionIter<'a> {
    elf: &'a Elf,
    idx: usize,
}

impl<'a> Iterator for SectionIter<'a> {
    type Item = &'a Section;

    fn next(&mut self) -> Option<Self::Item> {
        let item = self.elf.sections.get(self.idx)?;
        self.idx += 1;
        Some(item)
    }
}

/// Iterator over the program headers in an ELF file
pub struct SegmentIter<'a> {
    elf: &'a Elf,
    idx: usize,
}

impl<'a> Iterator for SegmentIter<'a> {
    type Item = &'a Segment;

    fn next(&mut self) -> Option<Self::Item> {
        let item = self.elf.segments.get(self.idx)?;
        self.idx += 1;
        Some(item)
    }
}

/// The Elfy prelude
pub mod prelude {
    pub use crate::numeric::*;
    pub use crate::types::*;
    pub use crate::Elf;
}

#[cfg(test)]
mod test {
    use super::*;

    fn _load_example_binary() -> Elf {
        let elf = Elf::load("examples/example-binary").unwrap();
        elf
    }
    
    #[test]
    fn get_section_bytes() {
        let elf = _load_example_binary();        
        let text = elf.try_get_section(".text").unwrap();
        
        if let SectionData::Bytes(_bytes) = text.data() {
            // do something with bytes
        }
    }

    #[test]
    fn section_iters() {
        let elf = _load_example_binary();

        for (i, s) in elf.sections().enumerate() {
            match i {
                0 => assert_eq!(s.header().section_type(), SectionType::Null),
                2 => assert_eq!(s.header().section_type(), SectionType::ProgramData),
                5 => assert_eq!(s.header().section_type(), SectionType::SymbolTable),
                6 => assert_eq!(s.header().section_type(), SectionType::StringTable),
                _ => continue
            }
        }
    }

    #[test]
    fn segment_iters() {
        let elf = _load_example_binary();

        println!("{:#?}", elf);

        for (i, h) in elf.segments().enumerate() {
            match i {
                0 => assert_eq!(h.header().program_header_type(), ProgramHeaderType::Phdr),
                1 => assert_eq!(h.header().program_header_type(), ProgramHeaderType::Loadable),
                2 => assert_eq!(h.header().program_header_type(), ProgramHeaderType::Loadable),
                3 => assert_eq!(h.header().program_header_type(), ProgramHeaderType::GnuStack),
                4 => assert_eq!(h.header().program_header_type(), ProgramHeaderType::ArmExidx),
                _ => continue
            }
        }
    }
}