Add no-std ElfStream support

This commit is contained in:
Mark Poliakov 2023-07-19 17:13:56 +03:00
parent b7067d3307
commit 1436aab797
5 changed files with 155 additions and 5 deletions

View File

@ -16,10 +16,12 @@ edition = "2021"
name = "elf"
[dependencies]
hashbrown = { version = "0.14.0", optional = true }
[features]
default = ["std", "to_str"]
std = []
to_str = []
no_std_stream = ["hashbrown"]
# Enable for nightly feature(error_in_core) to impl core::error::Error on ParseError
nightly = []

View File

@ -1,6 +1,9 @@
use core::ops::Range;
#[cfg(not(feature = "std"))]
use hashbrown::HashMap;
#[cfg(feature = "std")]
use std::collections::HashMap;
use std::io::{Read, Seek, SeekFrom};
use crate::abi;
use crate::compression::CompressionHeader;
@ -10,6 +13,7 @@ use crate::file::{parse_ident, Class};
use crate::gnu_symver::{
SymbolVersionTable, VerDefIterator, VerNeedIterator, VersionIndex, VersionIndexTable,
};
use crate::io_traits::{Read, Seek, SeekFrom};
use crate::note::NoteIterator;
use crate::parse::{ParseAt, ParseError};
use crate::relocation::{RelIterator, RelaIterator};
@ -24,7 +28,7 @@ use crate::file::FileHeader;
/// This type encapsulates the stream-oriented interface for parsing ELF objects from
/// a `Read + Seek`.
#[derive(Debug)]
pub struct ElfStream<E: EndianParse, S: std::io::Read + std::io::Seek> {
pub struct ElfStream<E: EndianParse, S: Read + Seek> {
pub ehdr: FileHeader<E>,
shdrs: Vec<SectionHeader>,
phdrs: Vec<ProgramHeader>,
@ -113,7 +117,7 @@ fn parse_program_headers<E: EndianParse, S: Read + Seek>(
Ok(phdrs_vec)
}
impl<E: EndianParse, S: std::io::Read + std::io::Seek> ElfStream<E, S> {
impl<E: EndianParse, S: Read + Seek> ElfStream<E, S> {
/// Do a minimal amount of parsing work to open an [ElfStream] handle from a Read+Seek containing an ELF object.
///
/// This parses the ELF [FileHeader], [SectionHeader] table, and [ProgramHeader] (segments) table.

141
src/io_traits.rs Normal file
View File

@ -0,0 +1,141 @@
#[cfg(feature = "std")]
pub type StreamError = std::io::Error;
#[cfg(not(feature = "std"))]
pub struct StreamError {
pub message: &'static str,
}
#[derive(Debug)]
pub enum SeekFrom {
Start(u64),
End(i64),
Current(i64),
}
pub trait Read {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, StreamError>;
fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), StreamError>;
}
pub trait Seek {
fn seek(&mut self, pos: SeekFrom) -> Result<u64, StreamError>;
}
#[cfg(feature = "std")]
impl<R: std::io::Read> Read for R {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> Result<usize, StreamError> {
std::io::Read::read(self, buf)
}
#[inline]
fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), StreamError> {
std::io::Read::read_exact(self, buf)
}
}
#[cfg(feature = "std")]
impl<S: std::io::Seek> Seek for S {
#[inline]
fn seek(&mut self, pos: SeekFrom) -> Result<u64, StreamError> {
std::io::Seek::seek(self, pos.into())
}
}
#[cfg(feature = "std")]
impl From<SeekFrom> for std::io::SeekFrom {
fn from(value: SeekFrom) -> Self {
match value {
SeekFrom::Current(v) => Self::Current(v),
SeekFrom::Start(v) => Self::Start(v),
SeekFrom::End(v) => Self::End(v),
}
}
}
#[cfg(not(feature = "std"))]
impl From<StreamError> for crate::ParseError {
fn from(e: StreamError) -> Self {
todo!()
}
}
#[cfg(test)]
mod no_std_stream_tests {
use crate::{abi, endian::AnyEndian, ElfStream};
use super::{Read, Seek, SeekFrom, StreamError};
pub struct NoStdStream {
pos: usize,
data: Vec<u8>,
}
impl NoStdStream {
pub fn read_from_path<P: AsRef<std::path::Path>>(path: P) -> Result<Self, std::io::Error> {
let data = std::fs::read(path)?;
Ok(Self { pos: 0, data })
}
}
impl Read for NoStdStream {
fn read(&mut self, _buf: &mut [u8]) -> Result<usize, StreamError> {
unimplemented!()
}
fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), StreamError> {
if self.pos + buf.len() > self.data.len() {
unimplemented!();
}
buf.copy_from_slice(&self.data[self.pos..self.pos + buf.len()]);
self.pos += buf.len();
Ok(())
}
}
impl Seek for NoStdStream {
fn seek(&mut self, pos: SeekFrom) -> Result<u64, StreamError> {
match pos {
SeekFrom::End(offset) => {
if offset == 0 {
self.pos = self.data.len();
} else {
unimplemented!();
}
}
SeekFrom::Start(offset) => {
if offset > self.data.len() as u64 {
unimplemented!()
} else {
self.pos = offset as usize;
}
}
SeekFrom::Current(_offset) => {
unimplemented!();
}
};
Ok(self.pos as u64)
}
}
#[test]
fn test_no_std_stream() {
let stream = NoStdStream::read_from_path("sample-objects/basic.x86_64").unwrap();
let mut file = ElfStream::<AnyEndian, _>::open_stream(stream).expect("Open test1");
let (shdrs, strtab) = file
.section_headers_with_strtab()
.expect("Failed to get shdrs");
let (shdrs, strtab) = (shdrs, strtab.unwrap());
let shdr_4 = &shdrs[4];
let name = strtab
.get(shdr_4.sh_name as usize)
.expect("Failed to get section name");
assert_eq!(name, ".gnu.hash");
assert_eq!(shdr_4.sh_type, abi::SHT_GNU_HASH);
}
}

View File

@ -144,6 +144,8 @@ pub mod segment;
pub mod string_table;
pub mod symbol;
pub mod io_traits;
#[cfg(feature = "to_str")]
pub mod to_str;
@ -154,9 +156,9 @@ mod elf_bytes;
pub use elf_bytes::CommonElfData;
pub use elf_bytes::ElfBytes;
#[cfg(feature = "std")]
#[cfg(any(feature = "std", feature = "no_std_stream"))]
mod elf_stream;
#[cfg(feature = "std")]
#[cfg(any(feature = "std", feature = "no_std_stream"))]
pub use elf_stream::ElfStream;
pub use parse::ParseError;

View File

@ -53,6 +53,7 @@ pub enum ParseError {
/// to represent in the native machine's usize type for in-memory processing.
/// This could be the case when processessing large 64-bit files on a 32-bit machine.
TryFromIntError(core::num::TryFromIntError),
#[cfg(feature = "std")]
/// Returned when parsing an ELF structure out of an io stream encountered
/// an io error.