use crate::alloc::borrow::Cow;
use crate::alloc::fmt;
use crate::alloc::vec::Vec;
use std::iter;
use std::slice;
#[cfg(feature = "compression")]
use flate2::{Decompress, FlushDecompress};
#[cfg(feature = "compression")]
use goblin::container;
use goblin::{elf, strtab};
#[cfg(feature = "compression")]
use scroll::ctx::TryFromCtx;
use scroll::{self, Pread};
use crate::{
Machine, Object, ObjectSection, ObjectSegment, Relocation, RelocationKind, SectionIndex,
SectionKind, Symbol, SymbolIndex, SymbolKind, SymbolMap,
};
#[derive(Debug)]
pub struct ElfFile<'data> {
elf: elf::Elf<'data>,
data: &'data [u8],
}
#[derive(Debug)]
pub struct ElfSegmentIterator<'data, 'file>
where
'data: 'file,
{
file: &'file ElfFile<'data>,
iter: slice::Iter<'file, elf::ProgramHeader>,
}
#[derive(Debug)]
pub struct ElfSegment<'data, 'file>
where
'data: 'file,
{
file: &'file ElfFile<'data>,
segment: &'file elf::ProgramHeader,
}
#[derive(Debug)]
pub struct ElfSectionIterator<'data, 'file>
where
'data: 'file,
{
file: &'file ElfFile<'data>,
iter: iter::Enumerate<slice::Iter<'file, elf::SectionHeader>>,
}
#[derive(Debug)]
pub struct ElfSection<'data, 'file>
where
'data: 'file,
{
file: &'file ElfFile<'data>,
index: SectionIndex,
section: &'file elf::SectionHeader,
}
pub struct ElfSymbolIterator<'data, 'file>
where
'data: 'file,
{
strtab: &'file strtab::Strtab<'data>,
symbols: iter::Enumerate<elf::sym::SymIterator<'data>>,
}
pub struct ElfRelocationIterator<'data, 'file>
where
'data: 'file,
{
section_index: SectionIndex,
file: &'file ElfFile<'data>,
sections: slice::Iter<'file, (elf::ShdrIdx, elf::RelocSection<'data>)>,
relocations: Option<elf::reloc::RelocIterator<'data>>,
}
impl<'data> ElfFile<'data> {
#[inline]
pub fn elf(&self) -> &elf::Elf<'data> {
&self.elf
}
pub fn parse(data: &'data [u8]) -> Result<Self, &'static str> {
let elf = elf::Elf::parse(data).map_err(|_| "Could not parse ELF header")?;
Ok(ElfFile { elf, data })
}
#[inline]
pub fn is_64(&self) -> bool {
self.elf.is_64
}
fn raw_section_by_name<'file>(
&'file self,
section_name: &str,
) -> Option<ElfSection<'data, 'file>> {
for (index, section) in self.elf.section_headers.iter().enumerate() {
if let Some(Ok(name)) = self.elf.shdr_strtab.get(section.sh_name) {
if name == section_name {
return Some(ElfSection {
file: self,
index: SectionIndex(index),
section,
});
}
}
}
None
}
#[cfg(feature = "compression")]
fn zdebug_section_by_name<'file>(
&'file self,
section_name: &str,
) -> Option<ElfSection<'data, 'file>> {
if !section_name.starts_with(".debug_") {
return None;
}
self.raw_section_by_name(&format!(".zdebug_{}", §ion_name[7..]))
}
#[cfg(not(feature = "compression"))]
fn zdebug_section_by_name<'file>(
&'file self,
_section_name: &str,
) -> Option<ElfSection<'data, 'file>> {
None
}
}
impl<'data, 'file> Object<'data, 'file> for ElfFile<'data>
where
'data: 'file,
{
type Segment = ElfSegment<'data, 'file>;
type SegmentIterator = ElfSegmentIterator<'data, 'file>;
type Section = ElfSection<'data, 'file>;
type SectionIterator = ElfSectionIterator<'data, 'file>;
type SymbolIterator = ElfSymbolIterator<'data, 'file>;
fn machine(&self) -> Machine {
match self.elf.header.e_machine {
elf::header::EM_ARM => Machine::Arm,
elf::header::EM_AARCH64 => Machine::Arm64,
elf::header::EM_386 => Machine::X86,
elf::header::EM_X86_64 => Machine::X86_64,
elf::header::EM_MIPS => Machine::Mips,
_ => Machine::Other,
}
}
fn segments(&'file self) -> ElfSegmentIterator<'data, 'file> {
ElfSegmentIterator {
file: self,
iter: self.elf.program_headers.iter(),
}
}
fn section_by_name(&'file self, section_name: &str) -> Option<ElfSection<'data, 'file>> {
self.raw_section_by_name(section_name)
.or_else(|| self.zdebug_section_by_name(section_name))
}
fn section_by_index(&'file self, index: SectionIndex) -> Option<ElfSection<'data, 'file>> {
self.elf
.section_headers
.get(index.0)
.map(|section| ElfSection {
file: self,
index,
section,
})
}
fn sections(&'file self) -> ElfSectionIterator<'data, 'file> {
ElfSectionIterator {
file: self,
iter: self.elf.section_headers.iter().enumerate(),
}
}
fn symbol_by_index(&self, index: SymbolIndex) -> Option<Symbol<'data>> {
self.elf
.syms
.get(index.0)
.map(|symbol| parse_symbol(index.0, &symbol, &self.elf.strtab))
}
fn symbols(&'file self) -> ElfSymbolIterator<'data, 'file> {
ElfSymbolIterator {
strtab: &self.elf.strtab,
symbols: self.elf.syms.iter().enumerate(),
}
}
fn dynamic_symbols(&'file self) -> ElfSymbolIterator<'data, 'file> {
ElfSymbolIterator {
strtab: &self.elf.dynstrtab,
symbols: self.elf.dynsyms.iter().enumerate(),
}
}
fn symbol_map(&self) -> SymbolMap<'data> {
let mut symbols: Vec<_> = self
.symbols()
.map(|(_, s)| s)
.filter(SymbolMap::filter)
.collect();
symbols.sort_by_key(|x| x.address);
SymbolMap { symbols }
}
#[inline]
fn is_little_endian(&self) -> bool {
self.elf.little_endian
}
fn has_debug_symbols(&self) -> bool {
for header in &self.elf.section_headers {
if let Some(Ok(name)) = self.elf.shdr_strtab.get(header.sh_name) {
if name == ".debug_info" || name == ".zdebug_info" {
return true;
}
}
}
false
}
fn build_id(&self) -> Option<&'data [u8]> {
if let Some(mut notes) = self.elf.iter_note_headers(self.data) {
while let Some(Ok(note)) = notes.next() {
if note.n_type == elf::note::NT_GNU_BUILD_ID {
return Some(note.desc);
}
}
}
if let Some(mut notes) = self
.elf
.iter_note_sections(self.data, Some(".note.gnu.build-id"))
{
while let Some(Ok(note)) = notes.next() {
if note.n_type == elf::note::NT_GNU_BUILD_ID {
return Some(note.desc);
}
}
}
None
}
fn gnu_debuglink(&self) -> Option<(&'data [u8], u32)> {
if let Some(Cow::Borrowed(data)) = self.section_data_by_name(".gnu_debuglink") {
if let Some(filename_len) = data.iter().position(|x| *x == 0) {
let filename = &data[..filename_len];
let offset = (filename_len + 1 + 3) & !3;
if offset + 4 <= data.len() {
let endian = if self.is_little_endian() {
scroll::LE
} else {
scroll::BE
};
let crc: u32 = data.pread_with(offset, endian).unwrap();
return Some((filename, crc));
}
}
}
None
}
fn entry(&self) -> u64 {
self.elf.entry
}
}
impl<'data, 'file> Iterator for ElfSegmentIterator<'data, 'file> {
type Item = ElfSegment<'data, 'file>;
fn next(&mut self) -> Option<Self::Item> {
while let Some(segment) = self.iter.next() {
if segment.p_type == elf::program_header::PT_LOAD {
return Some(ElfSegment {
file: self.file,
segment,
});
}
}
None
}
}
impl<'data, 'file> ObjectSegment<'data> for ElfSegment<'data, 'file> {
#[inline]
fn address(&self) -> u64 {
self.segment.p_vaddr
}
#[inline]
fn size(&self) -> u64 {
self.segment.p_memsz
}
#[inline]
fn align(&self) -> u64 {
self.segment.p_align
}
fn data(&self) -> &'data [u8] {
&self.file.data[self.segment.p_offset as usize..][..self.segment.p_filesz as usize]
}
fn data_range(&self, address: u64, size: u64) -> Option<&'data [u8]> {
crate::data_range(self.data(), self.address(), address, size)
}
#[inline]
fn name(&self) -> Option<&str> {
None
}
}
impl<'data, 'file> Iterator for ElfSectionIterator<'data, 'file> {
type Item = ElfSection<'data, 'file>;
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|(index, section)| ElfSection {
index: SectionIndex(index),
file: self.file,
section,
})
}
}
impl<'data, 'file> ElfSection<'data, 'file> {
fn raw_data(&self) -> &'data [u8] {
if self.section.sh_type == elf::section_header::SHT_NOBITS {
&[]
} else {
&self.file.data[self.section.sh_offset as usize..][..self.section.sh_size as usize]
}
}
#[cfg(feature = "compression")]
fn maybe_decompress_data(&self) -> Option<Cow<'data, [u8]>> {
if (self.section.sh_flags & u64::from(elf::section_header::SHF_COMPRESSED)) == 0 {
return None;
}
let container = match self.file.elf.header.container() {
Ok(c) => c,
Err(_) => return None,
};
let endianness = match self.file.elf.header.endianness() {
Ok(e) => e,
Err(_) => return None,
};
let ctx = container::Ctx::new(container, endianness);
let data = self.raw_data();
let (compression_type, uncompressed_size, compressed_data) =
match elf::compression_header::CompressionHeader::try_from_ctx(&data, ctx) {
Ok((chdr, size)) => (chdr.ch_type, chdr.ch_size, &data[size..]),
Err(_) => return None,
};
if compression_type != elf::compression_header::ELFCOMPRESS_ZLIB {
return None;
}
let mut decompressed = Vec::with_capacity(uncompressed_size as usize);
let mut decompress = Decompress::new(true);
if decompress
.decompress_vec(compressed_data, &mut decompressed, FlushDecompress::Finish)
.is_err()
{
return None;
}
Some(Cow::Owned(decompressed))
}
#[cfg(feature = "compression")]
fn maybe_decompress_data_gnu(&self) -> Option<Cow<'data, [u8]>> {
let name = match self.name() {
Some(name) => name,
None => return None,
};
if !name.starts_with(".zdebug_") {
return None;
}
let data = self.raw_data();
if data.len() < 12 || &data[..8] != b"ZLIB\0\0\0\0" {
return None;
}
let uncompressed_size: u32 = data.pread_with(8, scroll::BE).unwrap();
let mut decompressed = Vec::with_capacity(uncompressed_size as usize);
let mut decompress = Decompress::new(true);
if decompress
.decompress_vec(&data[12..], &mut decompressed, FlushDecompress::Finish)
.is_err()
{
return None;
}
Some(Cow::Owned(decompressed))
}
}
impl<'data, 'file> ObjectSection<'data> for ElfSection<'data, 'file> {
type RelocationIterator = ElfRelocationIterator<'data, 'file>;
#[inline]
fn index(&self) -> SectionIndex {
self.index
}
#[inline]
fn address(&self) -> u64 {
self.section.sh_addr
}
#[inline]
fn size(&self) -> u64 {
self.section.sh_size
}
#[inline]
fn align(&self) -> u64 {
self.section.sh_addralign
}
#[inline]
fn data(&self) -> Cow<'data, [u8]> {
Cow::from(self.raw_data())
}
fn data_range(&self, address: u64, size: u64) -> Option<&'data [u8]> {
crate::data_range(self.raw_data(), self.address(), address, size)
}
#[cfg(feature = "compression")]
fn uncompressed_data(&self) -> Cow<'data, [u8]> {
self.maybe_decompress_data()
.or_else(|| self.maybe_decompress_data_gnu())
.unwrap_or_else(|| self.data())
}
#[cfg(not(feature = "compression"))]
#[inline]
fn uncompressed_data(&self) -> Cow<'data, [u8]> {
self.data()
}
fn name(&self) -> Option<&str> {
self.file
.elf
.shdr_strtab
.get(self.section.sh_name)
.and_then(Result::ok)
}
#[inline]
fn segment_name(&self) -> Option<&str> {
None
}
fn kind(&self) -> SectionKind {
match self.section.sh_type {
elf::section_header::SHT_PROGBITS => {
if self.section.sh_flags & u64::from(elf::section_header::SHF_ALLOC) != 0 {
if self.section.sh_flags & u64::from(elf::section_header::SHF_EXECINSTR) != 0 {
SectionKind::Text
} else if self.section.sh_flags & u64::from(elf::section_header::SHF_TLS) != 0 {
SectionKind::Tls
} else if self.section.sh_flags & u64::from(elf::section_header::SHF_WRITE) != 0
{
SectionKind::Data
} else if self.section.sh_flags & u64::from(elf::section_header::SHF_STRINGS)
!= 0
{
SectionKind::ReadOnlyString
} else {
SectionKind::ReadOnlyData
}
} else if self.section.sh_flags & u64::from(elf::section_header::SHF_STRINGS) != 0 {
SectionKind::OtherString
} else {
SectionKind::Other
}
}
elf::section_header::SHT_NOBITS => {
if self.section.sh_flags & u64::from(elf::section_header::SHF_TLS) != 0 {
SectionKind::UninitializedTls
} else {
SectionKind::UninitializedData
}
}
elf::section_header::SHT_NULL
| elf::section_header::SHT_SYMTAB
| elf::section_header::SHT_STRTAB
| elf::section_header::SHT_RELA
| elf::section_header::SHT_HASH
| elf::section_header::SHT_DYNAMIC
| elf::section_header::SHT_REL
| elf::section_header::SHT_DYNSYM => SectionKind::Metadata,
_ => {
SectionKind::Unknown
}
}
}
fn relocations(&self) -> ElfRelocationIterator<'data, 'file> {
ElfRelocationIterator {
section_index: self.index,
file: self.file,
sections: self.file.elf.shdr_relocs.iter(),
relocations: None,
}
}
}
impl<'data, 'file> fmt::Debug for ElfSymbolIterator<'data, 'file> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ElfSymbolIterator").finish()
}
}
impl<'data, 'file> Iterator for ElfSymbolIterator<'data, 'file> {
type Item = (SymbolIndex, Symbol<'data>);
fn next(&mut self) -> Option<Self::Item> {
self.symbols.next().map(|(index, symbol)| {
(
SymbolIndex(index),
parse_symbol(index, &symbol, self.strtab),
)
})
}
}
fn parse_symbol<'data>(
index: usize,
symbol: &elf::sym::Sym,
strtab: &strtab::Strtab<'data>,
) -> Symbol<'data> {
let name = strtab.get(symbol.st_name).and_then(Result::ok);
let kind = match elf::sym::st_type(symbol.st_info) {
elf::sym::STT_NOTYPE if index == 0 => SymbolKind::Null,
elf::sym::STT_OBJECT => SymbolKind::Data,
elf::sym::STT_FUNC => SymbolKind::Text,
elf::sym::STT_SECTION => SymbolKind::Section,
elf::sym::STT_FILE => SymbolKind::File,
elf::sym::STT_COMMON => SymbolKind::Common,
elf::sym::STT_TLS => SymbolKind::Tls,
_ => SymbolKind::Unknown,
};
let undefined = symbol.st_shndx == elf::section_header::SHN_UNDEF as usize;
let section_index =
if undefined || symbol.st_shndx >= elf::section_header::SHN_LORESERVE as usize {
None
} else {
Some(SectionIndex(symbol.st_shndx))
};
Symbol {
name,
address: symbol.st_value,
size: symbol.st_size,
kind,
section_index,
undefined,
global: elf::sym::st_bind(symbol.st_info) != elf::sym::STB_LOCAL,
}
}
impl<'data, 'file> Iterator for ElfRelocationIterator<'data, 'file> {
type Item = (u64, Relocation);
fn next(&mut self) -> Option<Self::Item> {
loop {
if let Some(ref mut relocations) = self.relocations {
if let Some(reloc) = relocations.next() {
let (kind, size) = match self.file.elf.header.e_machine {
elf::header::EM_ARM => match reloc.r_type {
elf::reloc::R_ARM_ABS32 => (RelocationKind::Absolute, 32),
_ => (RelocationKind::Other(reloc.r_type), 0),
},
elf::header::EM_AARCH64 => match reloc.r_type {
elf::reloc::R_AARCH64_ABS64 => (RelocationKind::Absolute, 64),
elf::reloc::R_AARCH64_ABS32 => (RelocationKind::Absolute, 32),
elf::reloc::R_AARCH64_ABS16 => (RelocationKind::Absolute, 16),
elf::reloc::R_AARCH64_PREL64 => (RelocationKind::Relative, 64),
elf::reloc::R_AARCH64_PREL32 => (RelocationKind::Relative, 32),
elf::reloc::R_AARCH64_PREL16 => (RelocationKind::Relative, 16),
_ => (RelocationKind::Other(reloc.r_type), 0),
},
elf::header::EM_386 => match reloc.r_type {
elf::reloc::R_386_32 => (RelocationKind::Absolute, 32),
elf::reloc::R_386_PC32 => (RelocationKind::Relative, 32),
elf::reloc::R_386_GOT32 => (RelocationKind::GotRelative, 32),
elf::reloc::R_386_PLT32 => (RelocationKind::PltRelative, 32),
_ => (RelocationKind::Other(reloc.r_type), 0),
},
elf::header::EM_X86_64 => match reloc.r_type {
elf::reloc::R_X86_64_64 => (RelocationKind::Absolute, 64),
elf::reloc::R_X86_64_PC32 => (RelocationKind::Relative, 32),
elf::reloc::R_X86_64_GOT32 => (RelocationKind::GotOffset, 32),
elf::reloc::R_X86_64_PLT32 => (RelocationKind::PltRelative, 32),
elf::reloc::R_X86_64_GOTPCREL => (RelocationKind::GotRelative, 32),
elf::reloc::R_X86_64_32 => (RelocationKind::Absolute, 32),
elf::reloc::R_X86_64_32S => (RelocationKind::AbsoluteSigned, 32),
elf::reloc::R_X86_64_16 => (RelocationKind::Absolute, 16),
elf::reloc::R_X86_64_PC16 => (RelocationKind::Relative, 16),
elf::reloc::R_X86_64_8 => (RelocationKind::Absolute, 8),
elf::reloc::R_X86_64_PC8 => (RelocationKind::Relative, 8),
_ => (RelocationKind::Other(reloc.r_type), 0),
},
_ => (RelocationKind::Other(reloc.r_type), 0),
};
return Some((
reloc.r_offset,
Relocation {
kind,
size,
symbol: SymbolIndex(reloc.r_sym as usize),
addend: reloc.r_addend.unwrap_or(0),
implicit_addend: reloc.r_addend.is_none(),
},
));
}
}
match self.sections.next() {
None => return None,
Some((index, relocs)) => {
let section = &self.file.elf.section_headers[*index];
if section.sh_info as usize == self.section_index.0 {
self.relocations = Some(relocs.into_iter());
}
}
}
}
}
}
impl<'data, 'file> fmt::Debug for ElfRelocationIterator<'data, 'file> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ElfRelocationIterator").finish()
}
}