use std::borrow::Cow;
use std::fmt;
use std::io::Cursor;
use failure::Fail;
use flate2::{Decompress, FlushDecompress};
use goblin::elf::compression_header::{CompressionHeader, ELFCOMPRESS_ZLIB};
use goblin::{container::Ctx, elf, error::Error as GoblinError, strtab};
use symbolic_common::{Arch, AsSelf, CodeId, DebugId, Uuid};
use crate::base::*;
use crate::dwarf::{Dwarf, DwarfDebugSession, DwarfError, DwarfSection, Endian};
use crate::private::Parse;
const UUID_SIZE: usize = 16;
const PAGE_SIZE: usize = 4096;
const SHN_UNDEF: usize = elf::section_header::SHN_UNDEF as usize;
const SHF_COMPRESSED: u64 = elf::section_header::SHF_COMPRESSED as u64;
#[allow(unused)]
const EF_MIPS_ABI_O32: u32 = 0x0000_1000;
const EF_MIPS_ABI_O64: u32 = 0x0000_2000;
#[allow(unused)]
const EF_MIPS_ABI_EABI32: u32 = 0x0000_3000;
const EF_MIPS_ABI_EABI64: u32 = 0x0000_4000;
const MIPS_64_FLAGS: u32 = EF_MIPS_ABI_O64 | EF_MIPS_ABI_EABI64;
#[non_exhaustive]
#[derive(Debug, Fail)]
pub enum ElfError {
#[fail(display = "invalid ELF file")]
BadObject(#[fail(cause)] GoblinError),
}
pub struct ElfObject<'d> {
elf: elf::Elf<'d>,
data: &'d [u8],
}
impl<'d> ElfObject<'d> {
pub fn test(data: &[u8]) -> bool {
match goblin::peek(&mut Cursor::new(data)) {
Ok(goblin::Hint::Elf(_)) => true,
_ => false,
}
}
pub fn parse(data: &'d [u8]) -> Result<Self, ElfError> {
elf::Elf::parse(data)
.map(|elf| ElfObject { elf, data })
.map_err(ElfError::BadObject)
}
pub fn file_format(&self) -> FileFormat {
FileFormat::Elf
}
pub fn code_id(&self) -> Option<CodeId> {
self.find_build_id()
.filter(|slice| !slice.is_empty())
.map(|slice| CodeId::from_binary(slice))
}
pub fn name(&self) -> Option<&'d str> {
self.elf.soname
}
pub fn debug_id(&self) -> DebugId {
if let Some(identifier) = self.find_build_id() {
return self.compute_debug_id(identifier);
}
if let Some(section) = self.raw_section("text") {
let mut hash = [0; UUID_SIZE];
for i in 0..std::cmp::min(section.data.len(), PAGE_SIZE) {
hash[i % UUID_SIZE] ^= section.data[i];
}
return self.compute_debug_id(&hash);
}
DebugId::default()
}
pub fn arch(&self) -> Arch {
match self.elf.header.e_machine {
goblin::elf::header::EM_386 => Arch::X86,
goblin::elf::header::EM_X86_64 => Arch::Amd64,
goblin::elf::header::EM_AARCH64 => Arch::Arm64,
goblin::elf::header::EM_ARM => Arch::Arm,
goblin::elf::header::EM_PPC => Arch::Ppc,
goblin::elf::header::EM_PPC64 => Arch::Ppc64,
goblin::elf::header::EM_MIPS | goblin::elf::header::EM_MIPS_RS3_LE => {
if self.elf.header.e_flags & MIPS_64_FLAGS != 0 {
Arch::Mips64
} else {
Arch::Mips
}
}
_ => Arch::Unknown,
}
}
pub fn kind(&self) -> ObjectKind {
let kind = match self.elf.header.e_type {
goblin::elf::header::ET_NONE => ObjectKind::None,
goblin::elf::header::ET_REL => ObjectKind::Relocatable,
goblin::elf::header::ET_EXEC => ObjectKind::Executable,
goblin::elf::header::ET_DYN => ObjectKind::Library,
goblin::elf::header::ET_CORE => ObjectKind::Dump,
_ => ObjectKind::Other,
};
if kind == ObjectKind::Executable && self.elf.interpreter.is_none() {
return ObjectKind::Debug;
}
if kind == ObjectKind::Library && self.raw_section("text").is_none() {
return ObjectKind::Debug;
}
kind
}
pub fn load_address(&self) -> u64 {
for phdr in &self.elf.program_headers {
if phdr.p_type == elf::program_header::PT_LOAD {
return phdr.p_vaddr;
}
}
0
}
pub fn has_symbols(&self) -> bool {
!self.elf.syms.is_empty()
}
pub fn symbols(&self) -> ElfSymbolIterator<'d, '_> {
ElfSymbolIterator {
symbols: self.elf.syms.iter(),
strtab: &self.elf.strtab,
sections: &self.elf.section_headers,
load_addr: self.load_address(),
}
}
pub fn symbol_map(&self) -> SymbolMap<'d> {
self.symbols().collect()
}
pub fn has_debug_info(&self) -> bool {
self.has_section("debug_info")
}
pub fn debug_session(&self) -> Result<DwarfDebugSession<'d>, DwarfError> {
let symbols = self.symbol_map();
DwarfDebugSession::parse(self, symbols, self.load_address(), self.kind())
}
pub fn has_unwind_info(&self) -> bool {
self.has_section("eh_frame") || self.has_section("debug_frame")
}
pub fn has_sources(&self) -> bool {
false
}
pub fn data(&self) -> &'d [u8] {
self.data
}
fn decompress_section(&self, section_data: &[u8]) -> Option<Vec<u8>> {
let (size, compressed) = if section_data.starts_with(b"ZLIB") {
if section_data.len() < 12 {
return None;
}
let mut size_bytes = [0; 8];
size_bytes.copy_from_slice(§ion_data[4..12]);
(u64::from_be_bytes(size_bytes), §ion_data[12..])
} else {
let container = self.elf.header.container().ok()?;
let endianness = self.elf.header.endianness().ok()?;
let context = Ctx::new(container, endianness);
let compression = CompressionHeader::parse(§ion_data, 0, context).ok()?;
if compression.ch_type != ELFCOMPRESS_ZLIB {
return None;
}
let compressed = §ion_data[CompressionHeader::size(context)..];
(compression.ch_size, compressed)
};
let mut decompressed = Vec::with_capacity(size as usize);
Decompress::new(true)
.decompress_vec(compressed, &mut decompressed, FlushDecompress::Finish)
.ok()?;
Some(decompressed)
}
fn find_section(&self, name: &str) -> Option<(bool, DwarfSection<'d>)> {
for header in &self.elf.section_headers {
if header.sh_type != elf::section_header::SHT_PROGBITS {
continue;
}
if let Some(Ok(section_name)) = self.elf.shdr_strtab.get(header.sh_name) {
let offset = header.sh_offset as usize;
if offset == 0 {
return None;
}
if section_name.is_empty() {
continue;
}
let (compressed, section_name) = if section_name.starts_with(".z") {
(true, §ion_name[2..])
} else {
(header.sh_flags & SHF_COMPRESSED != 0, §ion_name[1..])
};
if section_name != name {
continue;
}
let size = header.sh_size as usize;
let data = &self.data[offset..][..size];
let section = DwarfSection {
data: Cow::Borrowed(data),
address: header.sh_addr,
offset: header.sh_offset,
align: header.sh_addralign,
};
return Some((compressed, section));
}
}
None
}
fn find_build_id(&self) -> Option<&'d [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 compute_debug_id(&self, identifier: &[u8]) -> DebugId {
let mut data = [0 as u8; UUID_SIZE];
let len = std::cmp::min(identifier.len(), UUID_SIZE);
data[0..len].copy_from_slice(&identifier[0..len]);
if self.elf.little_endian {
data[0..4].reverse();
data[4..6].reverse();
data[6..8].reverse();
}
Uuid::from_slice(&data)
.map(DebugId::from_uuid)
.unwrap_or_default()
}
}
impl fmt::Debug for ElfObject<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ElfObject")
.field("code_id", &self.code_id())
.field("debug_id", &self.debug_id())
.field("arch", &self.arch())
.field("kind", &self.kind())
.field("load_address", &format_args!("{:#x}", self.load_address()))
.field("has_symbols", &self.has_symbols())
.field("has_debug_info", &self.has_debug_info())
.field("has_unwind_info", &self.has_unwind_info())
.finish()
}
}
impl<'slf, 'd: 'slf> AsSelf<'slf> for ElfObject<'d> {
type Ref = ElfObject<'slf>;
fn as_self(&'slf self) -> &Self::Ref {
self
}
}
impl<'d> Parse<'d> for ElfObject<'d> {
type Error = ElfError;
fn test(data: &[u8]) -> bool {
Self::test(data)
}
fn parse(data: &'d [u8]) -> Result<Self, ElfError> {
Self::parse(data)
}
}
impl<'d> ObjectLike for ElfObject<'d> {
type Error = DwarfError;
type Session = DwarfDebugSession<'d>;
fn file_format(&self) -> FileFormat {
self.file_format()
}
fn code_id(&self) -> Option<CodeId> {
self.code_id()
}
fn debug_id(&self) -> DebugId {
self.debug_id()
}
fn arch(&self) -> Arch {
self.arch()
}
fn kind(&self) -> ObjectKind {
self.kind()
}
fn load_address(&self) -> u64 {
self.load_address()
}
fn has_symbols(&self) -> bool {
self.has_symbols()
}
fn symbols(&self) -> DynIterator<'_, Symbol<'_>> {
Box::new(self.symbols())
}
fn symbol_map(&self) -> SymbolMap<'_> {
self.symbol_map()
}
fn has_debug_info(&self) -> bool {
self.has_debug_info()
}
fn debug_session(&self) -> Result<Self::Session, Self::Error> {
self.debug_session()
}
fn has_unwind_info(&self) -> bool {
self.has_unwind_info()
}
fn has_sources(&self) -> bool {
self.has_sources()
}
}
impl<'d> Dwarf<'d> for ElfObject<'d> {
fn endianity(&self) -> Endian {
if self.elf.little_endian {
Endian::Little
} else {
Endian::Big
}
}
fn raw_section(&self, name: &str) -> Option<DwarfSection<'d>> {
let (_, section) = self.find_section(name)?;
Some(section)
}
fn section(&self, name: &str) -> Option<DwarfSection<'d>> {
let (compressed, mut section) = self.find_section(name)?;
if compressed {
let decompressed = self.decompress_section(§ion.data)?;
section.data = Cow::Owned(decompressed);
}
Some(section)
}
}
pub struct ElfSymbolIterator<'d, 'o> {
symbols: elf::sym::SymIterator<'d>,
strtab: &'o strtab::Strtab<'d>,
sections: &'o [elf::SectionHeader],
load_addr: u64,
}
impl<'d, 'o> Iterator for ElfSymbolIterator<'d, 'o> {
type Item = Symbol<'d>;
fn next(&mut self) -> Option<Self::Item> {
while let Some(symbol) = self.symbols.next() {
if symbol.st_type() != elf::sym::STT_FUNC {
continue;
}
if symbol.st_value < self.load_addr {
continue;
}
let section = match symbol.st_shndx {
self::SHN_UNDEF => None,
index => self.sections.get(index),
};
if !section.map_or(false, |header| header.is_executable()) {
continue;
}
let name = self
.strtab
.get(symbol.st_name)
.and_then(Result::ok)
.map(Cow::Borrowed);
return Some(Symbol {
name,
address: symbol.st_value - self.load_addr,
size: symbol.st_size,
});
}
None
}
}