mod serde;
use serde::*;
mod structs;
pub use structs::*;
#[derive(Clone, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Ident {
pub class: Class,
pub byte_order: ByteOrder,
pub abi: ABI,
pub abi_version: u8,
}
impl Ident {
pub fn new(
class: Class,
byte_order: impl Into<ByteOrder>,
abi: impl Into<ABI>,
abi_version: u8,
) -> Self {
Self {
class,
byte_order: byte_order.into(),
abi: abi.into(),
abi_version,
}
}
#[rustfmt::skip]
pub fn write<W: std::io::Write>(&self, file: &mut W) -> Result<()> {
file.write_all(&[
0x7f, 0x45, 0x4c, 0x46,
self.class.int_value(), self.byte_order.int_value(), 1,
self.abi.int_value(), self.abi_version,
0, 0, 0, 0, 0, 0, 0,
]).map_err(|err|Error::io(err, "e_ident"))
}
pub fn read<R: std::io::Read>(file: &mut R) -> Result<Self> {
let mut buffer = [0_u8; 4];
file.read_exact(&mut buffer).map_err(|err| {
Error::Signature(format!("failed to read ELF signature from a file: {err}"))
})?;
if buffer != [0x7f, 0x45, 0x4c, 0x46] {
return Err(Error::Signature(format!(
"incorrect ELF signature: {buffer:02X?}, expected: [0x7f, 0x45, 0x4c, 0x46]"
)));
}
let mut buffer = [0_u8; 12];
file.read_exact(&mut buffer)
.map_err(|err| Error::io(err, "e_ident"))?;
let [class, byte_order, version, abi, abi_version] = buffer[..5] else {
panic!("Internal error: buffer size is not compatible with e_ident size!");
};
if version != 1 {
return Err(Error::parse("version should be always 1", "version"));
}
let class = Class::from_int(class);
let byte_order = ByteOrder::from_int(byte_order)?;
let abi = ABI::from_int(abi);
Ok(Self {
class,
byte_order,
abi,
abi_version,
})
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct SegmentTemplate<T: SizeT> {
pub p_type: SegmentType,
pub data: Vec<u8>,
pub size: T,
pub flags: u32,
}
impl<T: SizeT> SegmentTemplate<T> {
pub fn new(p_type: SegmentType, data: Vec<u8>, size: T, flags: u32) -> Self {
Self {
p_type,
data,
size,
flags,
}
}
}
impl Machine {
pub fn text_region_address(self) -> u64 {
match self {
Self::X86 => 0x08048000,
Self::X86_64 => 0x400000,
_ => 100, }
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct ELF<T: SizeT> {
pub ident: Ident,
pub e_type: Type,
pub machine: Machine,
pub entry_point: T,
pub segments: Vec<Segment<T>>,
pub sections: Vec<Section<T>>,
pub flags: u32,
pub section_header_string_table_index: u16,
}
macro_rules! rw_enum {
(read $type: ident, $name: ident, $storage: ident, $ident: ident, $file: ident) => {
$type::from_int($storage::read(
$file,
$ident.byte_order,
stringify!($display_part),
)?)
};
(write $self: ident, $part: ident, $file: ident) => {
$self
.$part
.int_value()
.write($file, $self.ident.byte_order, stringify!($part))?
};
}
impl<T: SizeT> ELF<T> {
pub fn new(
ident: Ident,
e_type: Type,
machine: impl Into<Machine>,
has_entry_point: bool,
segments: Vec<SegmentTemplate<T>>,
sections: Vec<Section<T>>,
) -> Result<Self> {
let machine = machine.into();
if ident.class != T::CLASS {
return Err(Error::Error(format!(
"Expected ELF class {:?}, got class {:?}",
T::CLASS,
ident.class
)));
}
let section_header_string_table_index = sections.len().saturating_sub(1) as _;
let segments = {
let align = 0x1000;
let mut offset = T::ELF_HEADER_SIZE as u64
+ T::SEGMENT_HEADER_SIZE as u64 * segments.len() as u64
+ T::SECTION_HEADER_SIZE as u64 * sections.len() as u64;
let mut virtual_address = machine.text_region_address() + (offset % align);
segments
.into_iter()
.map(|segment| {
let segment_offset = offset;
let segment_virtual_address = virtual_address;
offset += segment.data.len() as u64;
virtual_address += segment.size.value();
Segment {
p_type: segment.p_type,
data: segment.data,
offset: T::new(segment_offset),
virtual_address: T::new(segment_virtual_address),
physical_address: T::new(0),
size: segment.size,
flags: segment.flags,
align: T::new(align),
}
})
.collect::<Vec<_>>()
};
Ok(Self {
ident,
e_type,
machine,
entry_point: if has_entry_point {
T::new(
segments
.get(0)
.ok_or(Error::Error(String::from(
"Can't have an entry point with no segments",
)))?
.virtual_address
.value(),
)
} else {
T::new(0)
},
segments,
sections,
flags: 0,
section_header_string_table_index,
})
}
pub fn write<W: std::io::Write + std::io::Seek>(&self, file: &mut W) -> Result<()> {
let segment_headers_offset = T::ELF_HEADER_SIZE as u64;
let section_headers_offset =
segment_headers_offset + T::SEGMENT_HEADER_SIZE as u64 * self.segments.len() as u64;
self.ident.write(file)?;
rw_enum!(write self, e_type, file);
rw_enum!(write self, machine, file);
1_u32.write(file, self.ident.byte_order, "e_version")?;
self.entry_point
.write(file, self.ident.byte_order, "e_entry")?;
T::new(if self.segments.is_empty() {
0
} else {
segment_headers_offset
})
.write(file, self.ident.byte_order, "e_phoff")?;
T::new(if self.sections.is_empty() {
0
} else {
section_headers_offset
})
.write(file, self.ident.byte_order, "e_shoff")?;
self.flags.write(file, self.ident.byte_order, "e_flags")?;
T::ELF_HEADER_SIZE.write(file, self.ident.byte_order, "e_ehsize")?;
T::SEGMENT_HEADER_SIZE.write(file, self.ident.byte_order, "e_phentsize")?;
(self.segments.len() as u16).write(file, self.ident.byte_order, "e_phnum")?;
T::SECTION_HEADER_SIZE.write(file, self.ident.byte_order, "e_shentsize")?;
(self.sections.len() as u16).write(file, self.ident.byte_order, "e_shnum")?;
self.section_header_string_table_index
.write(file, self.ident.byte_order, "e_shstrndx")?;
let mut cursor =
section_headers_offset + T::SECTION_HEADER_SIZE as u64 * self.sections.len() as u64;
for segment in &self.segments {
segment.write_header(file, &mut cursor, self.ident.byte_order)?;
}
for segment in &self.segments {
file.seek(std::io::SeekFrom::Start(segment.offset.value()))
.map_err(|err| Error::io(err, "segment data"))?;
file.write_all(&segment.data)
.map_err(|err| Error::io(err, "segment data"))?;
}
Ok(())
}
pub fn read<R: std::io::Read + std::io::Seek>(file: &mut R) -> Result<Self> {
let ident = Ident::read(file)?;
if ident.class != T::CLASS {
return Err(Error::Error(format!(
"Expected ELF class {:?}, got class {:?}",
T::CLASS,
ident.class
)));
}
Self::read_remainder(file, ident)
}
pub fn read_remainder<R: std::io::Read>(file: &mut R, ident: Ident) -> Result<Self> {
let e_type = rw_enum!(read Type, e_type, u16, ident, file);
let machine = rw_enum!(read Machine, machine, u16, ident, file);
if u32::read(file, ident.byte_order, "e_version")? != 1 {
return Err(Error::parse("ELF version should be always 1", "e_version"));
}
let entry_point = T::read(file, ident.byte_order, "e_entry")?;
let program_headers_offset = T::read(file, ident.byte_order, "e_phoff")?;
let section_headers_offset = T::read(file, ident.byte_order, "e_shoff")?;
let flags = u32::read(file, ident.byte_order, "e_flags")?;
let elf_header_size = u16::read(file, ident.byte_order, "e_ehsize")?;
if elf_header_size != T::ELF_HEADER_SIZE {
return Err(Error::Error(format!(
"Invalid elf header size, expected {}, got {elf_header_size}!",
T::ELF_HEADER_SIZE
)));
}
let segment_header_size = u16::read(file, ident.byte_order, "e_phentsize")?;
if segment_header_size != T::SEGMENT_HEADER_SIZE {
return Err(Error::Error(format!(
"Invalid segment header size, expected {}, got {segment_header_size}!",
T::SEGMENT_HEADER_SIZE
)));
}
let num_segments = u16::read(file, ident.byte_order, "e_phnum")?;
let section_header_size = u16::read(file, ident.byte_order, "e_shentsize")?;
if section_header_size != T::SECTION_HEADER_SIZE {
return Err(Error::Error(format!(
"Invalid section header size, expected {}, got {section_header_size}!",
T::SECTION_HEADER_SIZE
)));
}
let num_sections = u16::read(file, ident.byte_order, "e_shnum")?;
let section_header_string_table_index = u16::read(file, ident.byte_order, "e_shstrndx")?;
Ok(Self {
ident,
e_type,
machine,
entry_point,
flags,
segments: Vec::new(),
sections: Vec::new(),
section_header_string_table_index,
})
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Segment<T: SizeT> {
pub p_type: SegmentType,
pub data: Vec<u8>,
pub offset: T,
pub virtual_address: T,
pub physical_address: T,
pub size: T,
pub flags: u32,
pub align: T,
}
impl<T: SizeT> Segment<T> {
fn write_header<W: std::io::Write>(
&self,
file: &mut W,
cursor: &mut u64,
byte_order: ByteOrder,
) -> Result<()> {
self.p_type.int_value().write(file, byte_order, "p_type")?;
if T::MOVE_PFLAGS {
self.flags.write(file, byte_order, "p_flags")?;
}
T::new(*cursor).write(file, byte_order, "p_offset")?;
self.virtual_address.write(file, byte_order, "p_vaddr")?;
self.physical_address.write(file, byte_order, "p_paddr")?;
T::new(self.data.len() as _).write(file, byte_order, "p_filesz")?;
self.size.write(file, byte_order, "p_memsz")?;
if !T::MOVE_PFLAGS {
self.flags.write(file, byte_order, "p_flags")?;
}
self.align.write(file, byte_order, "p_align")?;
*cursor += self.data.len() as u64;
Ok(())
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Section<T: SizeT> {
__: T,
}
impl SizeT for u32 {
fn new(value: u64) -> Self {
value as _
}
fn value(&self) -> u64 {
*self as _
}
const CLASS: Class = Class::ELF32;
const ELF_HEADER_SIZE: u16 = 52;
const SEGMENT_HEADER_SIZE: u16 = 32;
const SECTION_HEADER_SIZE: u16 = 40;
const MOVE_PFLAGS: bool = false;
}
impl SizeT for u64 {
fn new(value: u64) -> Self {
value
}
fn value(&self) -> u64 {
*self
}
const CLASS: Class = Class::ELF64;
const ELF_HEADER_SIZE: u16 = 64;
const SEGMENT_HEADER_SIZE: u16 = 56;
const SECTION_HEADER_SIZE: u16 = 64;
const MOVE_PFLAGS: bool = true;
}
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Clone, Debug)]
pub enum Error {
Signature(String),
Error(String),
}
impl Error {
fn io(err: std::io::Error, part: &str) -> Self {
Self::Error(format!("failed to read/write {part} to/from a file: {err}"))
}
fn parse(err: impl ToString, part: &str) -> Self {
Self::Error(format!("failed to parse {part}: {}", err.to_string()))
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::Signature(msg) => f.write_str(msg),
Error::Error(msg) => f.write_str(msg),
}
}
}
#[cfg(test)]
mod tests;