use alloc::borrow::Cow;
use core::{fmt, result};
#[cfg(not(feature = "std"))]
use alloc::collections::btree_map::BTreeMap as Map;
#[cfg(feature = "std")]
use std::collections::hash_map::HashMap as Map;
pub use crate::common::*;
mod read_ref;
pub use read_ref::*;
mod read_cache;
pub use read_cache::*;
mod symbol_map;
pub use symbol_map::*;
mod util;
pub use util::*;
#[cfg(any(feature = "elf", feature = "macho"))]
mod gnu_compression;
#[cfg(any(
feature = "coff",
feature = "elf",
feature = "macho",
feature = "pe",
feature = "wasm",
feature = "xcoff"
))]
mod any;
#[cfg(any(
feature = "coff",
feature = "elf",
feature = "macho",
feature = "pe",
feature = "wasm",
feature = "xcoff"
))]
pub use any::*;
#[cfg(feature = "archive")]
pub mod archive;
#[cfg(feature = "coff")]
pub mod coff;
#[cfg(feature = "elf")]
pub mod elf;
#[cfg(feature = "macho")]
pub mod macho;
#[cfg(feature = "pe")]
pub mod pe;
#[cfg(feature = "wasm")]
pub mod wasm;
#[cfg(feature = "xcoff")]
pub mod xcoff;
mod traits;
pub use traits::*;
mod private {
pub trait Sealed {}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Error(pub(crate) &'static str);
impl fmt::Display for Error {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.0)
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {}
#[cfg(all(not(feature = "std"), core_error))]
impl core::error::Error for Error {}
pub type Result<T> = result::Result<T, Error>;
trait ReadError<T> {
fn read_error(self, error: &'static str) -> Result<T>;
}
impl<T> ReadError<T> for result::Result<T, ()> {
fn read_error(self, error: &'static str) -> Result<T> {
self.map_err(|()| Error(error))
}
}
impl<T> ReadError<T> for result::Result<T, Error> {
fn read_error(self, error: &'static str) -> Result<T> {
self.map_err(|_| Error(error))
}
}
impl<T> ReadError<T> for Option<T> {
fn read_error(self, error: &'static str) -> Result<T> {
self.ok_or(Error(error))
}
}
#[cfg(all(
unix,
not(target_vendor = "apple"),
not(target_os = "aix"),
feature = "elf"
))]
pub type NativeFile<'data, R = &'data [u8]> = elf::NativeElfFile<'data, R>;
#[cfg(all(target_vendor = "apple", feature = "macho"))]
pub type NativeFile<'data, R = &'data [u8]> = macho::NativeMachOFile<'data, R>;
#[cfg(all(target_os = "windows", feature = "pe"))]
pub type NativeFile<'data, R = &'data [u8]> = pe::NativePeFile<'data, R>;
#[cfg(all(target_family = "wasm", feature = "wasm"))]
pub type NativeFile<'data, R = &'data [u8]> = wasm::WasmFile<'data, R>;
#[cfg(all(target_os = "aix", feature = "xcoff"))]
pub type NativeFile<'data, R = &'data [u8]> = xcoff::NativeXcoffFile<'data, R>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum FileKind {
#[cfg(feature = "archive")]
Archive,
#[cfg(feature = "coff")]
Coff,
#[cfg(feature = "coff")]
CoffBig,
#[cfg(feature = "coff")]
CoffImport,
#[cfg(feature = "macho")]
DyldCache,
#[cfg(feature = "elf")]
Elf32,
#[cfg(feature = "elf")]
Elf64,
#[cfg(feature = "macho")]
MachO32,
#[cfg(feature = "macho")]
MachO64,
#[cfg(feature = "macho")]
MachOFat32,
#[cfg(feature = "macho")]
MachOFat64,
#[cfg(feature = "pe")]
Pe32,
#[cfg(feature = "pe")]
Pe64,
#[cfg(feature = "wasm")]
Wasm,
#[cfg(feature = "xcoff")]
Xcoff32,
#[cfg(feature = "xcoff")]
Xcoff64,
}
impl FileKind {
pub fn parse<'data, R: ReadRef<'data>>(data: R) -> Result<FileKind> {
Self::parse_at(data, 0)
}
pub fn parse_at<'data, R: ReadRef<'data>>(data: R, offset: u64) -> Result<FileKind> {
let magic = data
.read_bytes_at(offset, 16)
.read_error("Could not read file magic")?;
if magic.len() < 16 {
return Err(Error("File too short"));
}
let kind = match [magic[0], magic[1], magic[2], magic[3], magic[4], magic[5], magic[6], magic[7]] {
#[cfg(feature = "archive")]
[b'!', b'<', b'a', b'r', b'c', b'h', b'>', b'\n']
| [b'!', b'<', b't', b'h', b'i', b'n', b'>', b'\n'] => FileKind::Archive,
#[cfg(feature = "macho")]
[b'd', b'y', b'l', b'd', b'_', b'v', b'1', b' '] => FileKind::DyldCache,
#[cfg(feature = "elf")]
[0x7f, b'E', b'L', b'F', 1, ..] => FileKind::Elf32,
#[cfg(feature = "elf")]
[0x7f, b'E', b'L', b'F', 2, ..] => FileKind::Elf64,
#[cfg(feature = "macho")]
[0xfe, 0xed, 0xfa, 0xce, ..]
| [0xce, 0xfa, 0xed, 0xfe, ..] => FileKind::MachO32,
#[cfg(feature = "macho")]
| [0xfe, 0xed, 0xfa, 0xcf, ..]
| [0xcf, 0xfa, 0xed, 0xfe, ..] => FileKind::MachO64,
#[cfg(feature = "macho")]
[0xca, 0xfe, 0xba, 0xbe, ..] => FileKind::MachOFat32,
#[cfg(feature = "macho")]
[0xca, 0xfe, 0xba, 0xbf, ..] => FileKind::MachOFat64,
#[cfg(feature = "wasm")]
[0x00, b'a', b's', b'm', _, _, 0x00, 0x00] => FileKind::Wasm,
#[cfg(feature = "pe")]
[b'M', b'Z', ..] if offset == 0 => {
match pe::optional_header_magic(data) {
Ok(crate::pe::IMAGE_NT_OPTIONAL_HDR32_MAGIC) => {
FileKind::Pe32
}
Ok(crate::pe::IMAGE_NT_OPTIONAL_HDR64_MAGIC) => {
FileKind::Pe64
}
_ => return Err(Error("Unknown MS-DOS file")),
}
}
#[cfg(feature = "coff")]
[0xc4, 0x01, ..]
| [0x64, 0xaa, ..]
| [0x41, 0xa6, ..]
| [0xf0, 0x01, ..]
| [0xf1, 0x01, ..]
| [0xf2, 0x01, ..]
| [0x4c, 0x01, ..]
| [0x64, 0x86, ..] => FileKind::Coff,
#[cfg(feature = "coff")]
[0x00, 0x00, 0xff, 0xff, 0x00, 0x00, ..] => FileKind::CoffImport,
#[cfg(feature = "coff")]
[0x00, 0x00, 0xff, 0xff, 0x02, 0x00, ..] if offset == 0 => {
match coff::anon_object_class_id(data) {
Ok(crate::pe::ANON_OBJECT_HEADER_BIGOBJ_CLASS_ID) => FileKind::CoffBig,
_ => return Err(Error("Unknown anon object file")),
}
}
#[cfg(feature = "xcoff")]
[0x01, 0xdf, ..] => FileKind::Xcoff32,
#[cfg(feature = "xcoff")]
[0x01, 0xf7, ..] => FileKind::Xcoff64,
_ => return Err(Error("Unknown file magic")),
};
Ok(kind)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ObjectKind {
Unknown,
Relocatable,
Executable,
Dynamic,
Core,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SectionIndex(pub usize);
impl fmt::Display for SectionIndex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SymbolIndex(pub usize);
impl fmt::Display for SymbolIndex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum SymbolSection {
Unknown,
None,
Undefined,
Absolute,
Common,
Section(SectionIndex),
}
impl SymbolSection {
#[inline]
pub fn index(self) -> Option<SectionIndex> {
if let SymbolSection::Section(index) = self {
Some(index)
} else {
None
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Import<'data> {
library: ByteString<'data>,
name: ByteString<'data>,
}
impl<'data> Import<'data> {
#[inline]
pub fn name(&self) -> &'data [u8] {
self.name.0
}
#[inline]
pub fn library(&self) -> &'data [u8] {
self.library.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Export<'data> {
name: ByteString<'data>,
address: u64,
}
impl<'data> Export<'data> {
#[inline]
pub fn name(&self) -> &'data [u8] {
self.name.0
}
#[inline]
pub fn address(&self) -> u64 {
self.address
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CodeView<'data> {
guid: [u8; 16],
path: ByteString<'data>,
age: u32,
}
impl<'data> CodeView<'data> {
#[inline]
pub fn path(&self) -> &'data [u8] {
self.path.0
}
#[inline]
pub fn age(&self) -> u32 {
self.age
}
#[inline]
pub fn guid(&self) -> [u8; 16] {
self.guid
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum RelocationTarget {
Symbol(SymbolIndex),
Section(SectionIndex),
Absolute,
}
pub struct Relocation {
kind: RelocationKind,
encoding: RelocationEncoding,
size: u8,
target: RelocationTarget,
subtractor: Option<SymbolIndex>,
addend: i64,
implicit_addend: bool,
flags: RelocationFlags,
}
impl fmt::Debug for Relocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("Relocation");
s.field("kind", &self.kind)
.field("encoding", &self.encoding)
.field("size", &self.size)
.field("target", &self.target);
if let Some(subtractor) = self.subtractor {
s.field("subtractor", &subtractor);
}
s.field("addend", &self.addend)
.field("implicit_addend", &self.implicit_addend)
.field("flags", &self.flags)
.finish()
}
}
impl Relocation {
#[inline]
pub fn kind(&self) -> RelocationKind {
self.kind
}
#[inline]
pub fn encoding(&self) -> RelocationEncoding {
self.encoding
}
#[inline]
pub fn size(&self) -> u8 {
self.size
}
#[inline]
pub fn target(&self) -> RelocationTarget {
self.target
}
#[inline]
pub fn subtractor(&self) -> Option<SymbolIndex> {
self.subtractor
}
#[inline]
pub fn addend(&self) -> i64 {
self.addend
}
#[inline]
pub fn set_addend(&mut self, addend: i64) {
self.addend = addend;
}
#[inline]
pub fn has_implicit_addend(&self) -> bool {
self.implicit_addend
}
#[inline]
pub fn flags(&self) -> RelocationFlags {
self.flags
}
}
#[derive(Debug, Default)]
pub struct RelocationMap(Map<u64, RelocationMapEntry>);
impl RelocationMap {
pub fn new<'data, 'file, T>(file: &'file T, section: &T::Section<'file>) -> Result<Self>
where
T: Object<'data>,
{
let mut map = RelocationMap(Map::new());
for (offset, relocation) in section.relocations() {
map.add(file, offset, relocation)?;
}
Ok(map)
}
pub fn add<'data: 'file, 'file, T>(
&mut self,
file: &'file T,
offset: u64,
relocation: Relocation,
) -> Result<()>
where
T: Object<'data>,
{
let mut entry = RelocationMapEntry {
implicit_addend: relocation.has_implicit_addend(),
addend: relocation.addend() as u64,
};
match relocation.kind() {
RelocationKind::None => return Ok(()),
RelocationKind::Absolute => match relocation.target() {
RelocationTarget::Symbol(symbol_idx) => {
let symbol = file
.symbol_by_index(symbol_idx)
.read_error("Relocation with invalid symbol")?;
entry.addend = symbol.address().wrapping_add(entry.addend);
}
RelocationTarget::Section(section_idx) => {
let section = file
.section_by_index(section_idx)
.read_error("Relocation with invalid section")?;
if section.kind() != SectionKind::Debug {
entry.addend = section.address().wrapping_add(entry.addend);
}
}
_ => {
return Err(Error("Unsupported relocation target"));
}
},
_ => {
return Err(Error("Unsupported relocation type"));
}
}
if relocation.encoding() != RelocationEncoding::Generic {
return Err(Error("Unsupported relocation encoding"));
}
if self.0.insert(offset, entry).is_some() {
return Err(Error("Multiple relocations for offset"));
}
Ok(())
}
pub fn relocate(&self, offset: u64, value: u64) -> u64 {
if let Some(relocation) = self.0.get(&offset) {
if relocation.implicit_addend {
value.wrapping_add(relocation.addend)
} else {
relocation.addend
}
} else {
value
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct RelocationMapEntry {
implicit_addend: bool,
addend: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum CompressionFormat {
None,
Unknown,
Zlib,
Zstandard,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CompressedFileRange {
pub format: CompressionFormat,
pub offset: u64,
pub compressed_size: u64,
pub uncompressed_size: u64,
}
impl CompressedFileRange {
#[inline]
pub fn none(range: Option<(u64, u64)>) -> Self {
if let Some((offset, size)) = range {
CompressedFileRange {
format: CompressionFormat::None,
offset,
compressed_size: size,
uncompressed_size: size,
}
} else {
CompressedFileRange {
format: CompressionFormat::None,
offset: 0,
compressed_size: 0,
uncompressed_size: 0,
}
}
}
pub fn data<'data, R: ReadRef<'data>>(self, file: R) -> Result<CompressedData<'data>> {
let data = file
.read_bytes_at(self.offset, self.compressed_size)
.read_error("Invalid compressed data size or offset")?;
Ok(CompressedData {
format: self.format,
data,
uncompressed_size: self.uncompressed_size,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CompressedData<'data> {
pub format: CompressionFormat,
pub data: &'data [u8],
pub uncompressed_size: u64,
}
impl<'data> CompressedData<'data> {
#[inline]
pub fn none(data: &'data [u8]) -> Self {
CompressedData {
format: CompressionFormat::None,
data,
uncompressed_size: data.len() as u64,
}
}
pub fn decompress(self) -> Result<Cow<'data, [u8]>> {
match self.format {
CompressionFormat::None => Ok(Cow::Borrowed(self.data)),
#[cfg(feature = "compression")]
CompressionFormat::Zlib | CompressionFormat::Zstandard => {
use alloc::vec::Vec;
use core::convert::TryInto;
use std::io::Read;
let size = self
.uncompressed_size
.try_into()
.ok()
.read_error("Uncompressed data size is too large.")?;
let mut decompressed = Vec::new();
decompressed
.try_reserve_exact(size)
.ok()
.read_error("Uncompressed data allocation failed")?;
match self.format {
CompressionFormat::Zlib => {
let mut decompress = flate2::Decompress::new(true);
decompress
.decompress_vec(
self.data,
&mut decompressed,
flate2::FlushDecompress::Finish,
)
.ok()
.read_error("Invalid zlib compressed data")?;
}
CompressionFormat::Zstandard => {
let mut input = self.data;
while !input.is_empty() {
let mut decoder = match ruzstd::decoding::StreamingDecoder::new(&mut input) {
Ok(decoder) => decoder,
Err(
ruzstd::decoding::errors::FrameDecoderError::ReadFrameHeaderError(
ruzstd::decoding::errors::ReadFrameHeaderError::SkipFrame {
length,
..
},
),
) => {
input = input
.get(length as usize..)
.read_error("Invalid zstd compressed data")?;
continue;
}
x => x.ok().read_error("Invalid zstd compressed data")?,
};
decoder
.read_to_end(&mut decompressed)
.ok()
.read_error("Invalid zstd compressed data")?;
}
}
_ => unreachable!(),
}
if size != decompressed.len() {
return Err(Error(
"Uncompressed data size does not match compression header",
));
}
Ok(Cow::Owned(decompressed))
}
_ => Err(Error("Unsupported compressed data.")),
}
}
}