#[macro_use]
pub(crate) mod gnu_hash;
pub mod compression_header;
pub mod header;
pub mod program_header;
pub mod section_header;
#[macro_use]
pub mod sym;
pub mod dynamic;
#[macro_use]
pub mod reloc;
pub mod note;
macro_rules! if_sylvan {
($($i:item)*) => ($(
#[cfg(all(feature = "elf32", feature = "elf64", feature = "endian_fd"))]
$i
)*)
}
if_sylvan! {
use scroll::{ctx, Pread, Endian};
use crate::strtab::Strtab;
use crate::error;
use crate::container::{Container, Ctx};
use alloc::vec::Vec;
use core::cmp;
pub type Header = header::Header;
pub type ProgramHeader = program_header::ProgramHeader;
pub type SectionHeader = section_header::SectionHeader;
pub type Symtab<'a> = sym::Symtab<'a>;
pub type Sym = sym::Sym;
pub type Dyn = dynamic::Dyn;
pub type Dynamic = dynamic::Dynamic;
pub type Reloc = reloc::Reloc;
pub type RelocSection<'a> = reloc::RelocSection<'a>;
pub type ProgramHeaders = Vec<ProgramHeader>;
pub type SectionHeaders = Vec<SectionHeader>;
pub type ShdrIdx = usize;
#[derive(Debug)]
pub struct Elf<'a> {
pub header: Header,
pub program_headers: ProgramHeaders,
pub section_headers: SectionHeaders,
pub shdr_strtab: Strtab<'a>,
pub dynstrtab: Strtab<'a>,
pub dynsyms: Symtab<'a>,
pub syms: Symtab<'a>,
pub strtab: Strtab<'a>,
pub dynamic: Option<Dynamic>,
pub dynrelas: RelocSection<'a>,
pub dynrels: RelocSection<'a>,
pub pltrelocs: RelocSection<'a>,
pub shdr_relocs: Vec<(ShdrIdx, RelocSection<'a>)>,
pub soname: Option<&'a str>,
pub interpreter: Option<&'a str>,
pub libraries: Vec<&'a str>,
pub is_64: bool,
pub is_lib: bool,
pub entry: u64,
pub little_endian: bool,
ctx: Ctx,
}
impl<'a> Elf<'a> {
pub fn iter_note_headers(&self, data: &'a [u8]) -> Option<note::NoteIterator<'a>> {
let mut iters = vec![];
for phdr in &self.program_headers {
if phdr.p_type == program_header::PT_NOTE {
let offset = phdr.p_offset as usize;
let alignment = phdr.p_align as usize;
iters.push(note::NoteDataIterator {
data,
offset,
size: offset + phdr.p_filesz as usize,
ctx: (alignment, self.ctx)
});
}
}
if iters.is_empty() {
None
} else {
Some(note::NoteIterator {
iters: iters,
index: 0,
})
}
}
pub fn iter_note_sections(
&self,
data: &'a [u8],
section_name: Option<&str>,
) -> Option<note::NoteIterator<'a>> {
let mut iters = vec![];
for sect in &self.section_headers {
if sect.sh_type != section_header::SHT_NOTE {
continue;
}
if section_name.is_some() && !self.shdr_strtab
.get(sect.sh_name)
.map_or(false, |r| r.ok() == section_name) {
continue;
}
let offset = sect.sh_offset as usize;
let alignment = sect.sh_addralign as usize;
iters.push(note::NoteDataIterator {
data,
offset,
size: offset + sect.sh_size as usize,
ctx: (alignment, self.ctx)
});
}
if iters.is_empty() {
None
} else {
Some(note::NoteIterator {
iters: iters,
index: 0,
})
}
}
pub fn is_object_file(&self) -> bool {
self.header.e_type == header::ET_REL
}
pub fn parse_header(bytes: &'a [u8]) -> error::Result<Header> {
bytes.pread::<Header>(0)
}
pub fn lazy_parse(header: Header) -> error::Result<Self> {
let misc = parse_misc(&header)?;
Ok(Elf {
header,
program_headers: vec![],
section_headers: Default::default(),
shdr_strtab: Default::default(),
dynamic: None,
dynsyms: Default::default(),
dynstrtab: Strtab::default(),
syms: Default::default(),
strtab: Default::default(),
dynrelas: Default::default(),
dynrels: Default::default(),
pltrelocs: Default::default(),
shdr_relocs: Default::default(),
soname: None,
interpreter: None,
libraries: vec![],
is_64: misc.is_64,
is_lib: misc.is_lib,
entry: misc.entry,
little_endian: misc.little_endian,
ctx: misc.ctx,
})
}
pub fn parse(bytes: &'a [u8]) -> error::Result<Self> {
let header = Self::parse_header(bytes)?;
let misc = parse_misc(&header)?;
let ctx = misc.ctx;
let program_headers = ProgramHeader::parse(bytes, header.e_phoff as usize, header.e_phnum as usize, ctx)?;
let mut interpreter = None;
for ph in &program_headers {
if ph.p_type == program_header::PT_INTERP && ph.p_filesz != 0 {
let count = (ph.p_filesz - 1) as usize;
let offset = ph.p_offset as usize;
interpreter = bytes.pread_with::<&str>(offset, ::scroll::ctx::StrCtx::Length(count)).ok();
}
}
let section_headers = SectionHeader::parse(bytes, header.e_shoff as usize, header.e_shnum as usize, ctx)?;
let get_strtab = |section_headers: &[SectionHeader], section_idx: usize| {
if section_idx >= section_headers.len() {
Ok(Strtab::default())
} else {
let shdr = §ion_headers[section_idx];
shdr.check_size(bytes.len())?;
Strtab::parse(bytes, shdr.sh_offset as usize, shdr.sh_size as usize, 0x0)
}
};
let strtab_idx = header.e_shstrndx as usize;
let shdr_strtab = get_strtab(§ion_headers, strtab_idx)?;
let mut syms = Symtab::default();
let mut strtab = Strtab::default();
for shdr in §ion_headers {
if shdr.sh_type as u32 == section_header::SHT_SYMTAB {
let size = shdr.sh_entsize;
let count = if size == 0 { 0 } else { shdr.sh_size / size };
syms = Symtab::parse(bytes, shdr.sh_offset as usize, count as usize, ctx)?;
strtab = get_strtab(§ion_headers, shdr.sh_link as usize)?;
}
}
let mut soname = None;
let mut libraries = vec![];
let mut dynsyms = Symtab::default();
let mut dynrelas = RelocSection::default();
let mut dynrels = RelocSection::default();
let mut pltrelocs = RelocSection::default();
let mut dynstrtab = Strtab::default();
let dynamic = Dynamic::parse(bytes, &program_headers, ctx)?;
if let Some(ref dynamic) = dynamic {
let dyn_info = &dynamic.info;
dynstrtab = Strtab::parse(bytes,
dyn_info.strtab,
dyn_info.strsz,
0x0)?;
if dyn_info.soname != 0 {
soname = match dynstrtab.get(dyn_info.soname) { Some(Ok(soname)) => Some(soname), _ => None };
}
if dyn_info.needed_count > 0 {
libraries = dynamic.get_libraries(&dynstrtab);
}
dynrelas = RelocSection::parse(bytes, dyn_info.rela, dyn_info.relasz, true, ctx)?;
dynrels = RelocSection::parse(bytes, dyn_info.rel, dyn_info.relsz, false, ctx)?;
let is_rela = dyn_info.pltrel as u64 == dynamic::DT_RELA;
pltrelocs = RelocSection::parse(bytes, dyn_info.jmprel, dyn_info.pltrelsz, is_rela, ctx)?;
let mut num_syms = if let Some(gnu_hash) = dyn_info.gnu_hash {
gnu_hash_len(bytes, gnu_hash as usize, ctx)?
} else if let Some(hash) = dyn_info.hash {
hash_len(bytes, hash as usize, header.e_machine, ctx)?
} else {
0
};
let max_reloc_sym = dynrelas.iter()
.chain(dynrels.iter())
.chain(pltrelocs.iter())
.fold(0, |num, reloc| cmp::max(num, reloc.r_sym));
if max_reloc_sym != 0 {
num_syms = cmp::max(num_syms, max_reloc_sym + 1);
}
dynsyms = Symtab::parse(bytes, dyn_info.symtab, num_syms, ctx)?;
}
let mut shdr_relocs = vec![];
for (idx, section) in section_headers.iter().enumerate() {
let is_rela = section.sh_type == section_header::SHT_RELA;
if is_rela || section.sh_type == section_header::SHT_REL {
section.check_size(bytes.len())?;
let sh_relocs = RelocSection::parse(bytes, section.sh_offset as usize, section.sh_size as usize, is_rela, ctx)?;
shdr_relocs.push((idx, sh_relocs));
}
}
Ok(Elf {
header,
program_headers,
section_headers,
shdr_strtab,
dynamic,
dynsyms,
dynstrtab,
syms,
strtab,
dynrelas,
dynrels,
pltrelocs,
shdr_relocs,
soname,
interpreter,
libraries,
is_64: misc.is_64,
is_lib: misc.is_lib,
entry: misc.entry,
little_endian: misc.little_endian,
ctx: ctx,
})
}
}
impl<'a> ctx::TryFromCtx<'a, (usize, Endian)> for Elf<'a> {
type Error = crate::error::Error;
fn try_from_ctx(src: &'a [u8], (_, _): (usize, Endian)) -> Result<(Elf<'a>, usize), Self::Error> {
let elf = Elf::parse(src)?;
Ok((elf, src.len()))
}
}
fn gnu_hash_len(bytes: &[u8], offset: usize, ctx: Ctx) -> error::Result<usize> {
let buckets_num = bytes.pread_with::<u32>(offset, ctx.le)? as usize;
let min_chain = bytes.pread_with::<u32>(offset + 4, ctx.le)? as usize;
let bloom_size = bytes.pread_with::<u32>(offset + 8, ctx.le)? as usize;
if buckets_num == 0 || min_chain == 0 || bloom_size == 0 {
return Err(error::Error::Malformed(format!("Invalid DT_GNU_HASH: buckets_num={} min_chain={} bloom_size={}",
buckets_num, min_chain, bloom_size)));
}
let buckets_offset = offset + 16 + bloom_size * if ctx.container.is_big() { 8 } else { 4 };
let mut max_chain = 0;
for bucket in 0..buckets_num {
let chain = bytes.pread_with::<u32>(buckets_offset + bucket * 4, ctx.le)? as usize;
if max_chain < chain {
max_chain = chain;
}
}
if max_chain < min_chain {
return Ok(0);
}
let mut chain_offset = buckets_offset + buckets_num * 4 + (max_chain - min_chain) * 4;
loop {
let hash = bytes.pread_with::<u32>(chain_offset, ctx.le)?;
max_chain += 1;
chain_offset += 4;
if hash & 1 != 0 {
return Ok(max_chain);
}
}
}
fn hash_len(bytes: &[u8], offset: usize, machine: u16, ctx: Ctx) -> error::Result<usize> {
let nchain = if (machine == header::EM_FAKE_ALPHA || machine == header::EM_S390) && ctx.container.is_big() {
bytes.pread_with::<u64>(offset + 4, ctx.le)? as usize
} else {
bytes.pread_with::<u32>(offset + 4, ctx.le)? as usize
};
Ok(nchain)
}
struct Misc {
is_64: bool,
is_lib: bool,
entry: u64,
little_endian: bool,
ctx: Ctx,
}
fn parse_misc(header: &Header) -> error::Result<Misc> {
let entry = header.e_entry as usize;
let is_lib = header.e_type == header::ET_DYN;
let is_lsb = header.e_ident[header::EI_DATA] == header::ELFDATA2LSB;
let endianness = scroll::Endian::from(is_lsb);
let class = header.e_ident[header::EI_CLASS];
if class != header::ELFCLASS64 && class != header::ELFCLASS32 {
return Err(error::Error::Malformed(format!("Unknown values in ELF ident header: class: {} endianness: {}",
class,
header.e_ident[header::EI_DATA])));
}
let is_64 = class == header::ELFCLASS64;
let container = if is_64 { Container::Big } else { Container::Little };
let ctx = Ctx::new(container, endianness);
Ok(Misc{
is_64,
is_lib,
entry: entry as u64,
little_endian:is_lsb,
ctx,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_crt1_64bit() {
let crt1: Vec<u8> = include!("../../etc/crt1.rs");
match Elf::parse(&crt1) {
Ok(binary) => {
assert!(binary.is_64);
assert!(!binary.is_lib);
assert_eq!(binary.entry, 0);
assert!(binary.syms.get(1000).is_none());
assert!(binary.syms.get(5).is_some());
let syms = binary.syms.to_vec();
assert!(!binary.section_headers.is_empty());
for (i, sym) in syms.iter().enumerate() {
if i == 11 {
let symtab = binary.strtab;
println!("sym: {:?}", &sym);
assert_eq!(&symtab[sym.st_name], "_start");
break;
}
}
assert!(!syms.is_empty());
}
Err(err) => {
panic!("failed: {}", err);
}
}
}
#[test]
fn parse_crt1_32bit() {
let crt1: Vec<u8> = include!("../../etc/crt132.rs");
match Elf::parse(&crt1) {
Ok(binary) => {
assert!(!binary.is_64);
assert!(!binary.is_lib);
assert_eq!(binary.entry, 0);
assert!(binary.syms.get(1000).is_none());
assert!(binary.syms.get(5).is_some());
let syms = binary.syms.to_vec();
assert!(!binary.section_headers.is_empty());
for (i, sym) in syms.iter().enumerate() {
if i == 11 {
let symtab = binary.strtab;
println!("sym: {:?}", &sym);
assert_eq!(&symtab[sym.st_name], "__libc_csu_fini");
break;
}
}
assert!(!syms.is_empty());
}
Err(err) => {
panic!("failed: {}", err);
}
}
}
}