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, Endian};
use crate::private::{HexFmt, 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;
#[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().map(|slice| CodeId::from_binary(slice))
}
pub fn debug_id(&self) -> DebugId {
if let Some(identifier) = self.find_build_id() {
return self.compute_debug_id(identifier);
}
if let Some((_, data)) = self.raw_data("text") {
let mut hash = [0; UUID_SIZE];
for i in 0..std::cmp::min(data.len(), PAGE_SIZE) {
hash[i % UUID_SIZE] ^= 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,
_ => 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() {
ObjectKind::Debug
} else {
kind
}
}
pub fn load_address(&self) -> u64 {
for phdr in &self.elf.program_headers {
if phdr.p_type == elf::program_header::PT_LOAD && phdr.is_executable() {
return phdr.p_vaddr;
}
}
0
}
pub fn has_symbols(&self) -> bool {
self.elf.syms.len() > 0
}
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())
}
pub fn has_unwind_info(&self) -> bool {
self.has_section("eh_frame") || self.has_section("debug_frame")
}
pub fn data(&self) -> &'d [u8] {
self.data
}
fn decompress_section(&self, section_data: &[u8]) -> Option<Vec<u8>> {
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)..];
let mut decompressed = Vec::with_capacity(compression.ch_size as usize);
Decompress::new(true)
.decompress_vec(compressed, &mut decompressed, FlushDecompress::Finish)
.ok()?;
Some(decompressed)
}
fn find_section(&self, name: &str) -> Option<(&elf::SectionHeader, bool, &'d [u8])> {
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];
return Some((header, compressed, data));
}
}
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", &HexFmt(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()
}
}
impl<'d> Dwarf<'d> for ElfObject<'d> {
fn endianity(&self) -> Endian {
if self.elf.little_endian {
Endian::Little
} else {
Endian::Big
}
}
fn raw_data(&self, section: &str) -> Option<(u64, &'d [u8])> {
let (header, _, data) = self.find_section(section)?;
Some((header.sh_offset, data))
}
fn section_data(&self, section: &str) -> Option<(u64, Cow<'d, [u8]>)> {
let (header, compressed, data) = self.find_section(section)?;
if compressed {
Some((header.sh_offset, Cow::Owned(self.decompress_section(data)?)))
} else {
Some((header.sh_offset, Cow::Borrowed(data)))
}
}
}
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),
};
let is_valid_section = section.map_or(false, |header| {
header.sh_type == elf::section_header::SHT_PROGBITS && header.is_executable()
});
if !is_valid_section {
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
}
}