use crate::{
ParseDynamicError, RelocTableError, Result,
arch::NativeArch,
elf::{
ElfDynRaw, ElfDynamicTag, ElfLayout, ElfRel, ElfRelType, ElfRela, ElfRelr, ElfWord,
Lifecycle, NativeElfLayout,
},
memory::{MappedView, RegionAccess, VmAddr, VmOffset},
relocation::RelocationArch,
segment::ElfSegments,
};
use alloc::vec::Vec;
use core::fmt::Debug;
use core::num::NonZeroUsize;
use elf::abi::*;
#[derive(Debug)]
#[repr(transparent)]
pub struct ElfDyn<L: ElfLayout = NativeElfLayout> {
dyn_: L::Dyn,
}
impl<L: ElfLayout> ElfDyn<L> {
#[inline]
pub fn new(tag: ElfDynamicTag, value: usize) -> Self {
let mut dyn_: L::Dyn = unsafe { core::mem::zeroed() };
dyn_.set_d_tag(tag.raw());
dyn_.set_d_un(value);
Self { dyn_ }
}
#[inline]
pub fn tag(&self) -> ElfDynamicTag {
ElfDynamicTag::new(self.dyn_.d_tag())
}
#[inline]
pub fn value(&self) -> usize {
self.dyn_.d_un()
}
#[inline]
pub fn set_tag(&mut self, tag: ElfDynamicTag) {
self.dyn_.set_d_tag(tag.raw());
}
#[inline]
pub fn set_value(&mut self, value: usize) {
self.dyn_.set_d_un(value);
}
}
#[derive(Debug, Default)]
pub(crate) struct ParsedDynamic {
pub(crate) symtab_off: usize,
pub(crate) strtab_off: usize,
pub(crate) strtab_size: Option<NonZeroUsize>,
pub(crate) elf_hash_off: Option<usize>,
pub(crate) gnu_hash_off: Option<usize>,
pub(crate) got_off: Option<NonZeroUsize>,
pub(crate) pltrel_size: Option<NonZeroUsize>,
pub(crate) pltrel_off: Option<NonZeroUsize>,
pub(crate) rel_off: Option<NonZeroUsize>,
pub(crate) rel_size: Option<NonZeroUsize>,
pub(crate) rel_entry_size: Option<usize>,
pub(crate) rela_entry_size: Option<usize>,
pub(crate) rel_count: Option<NonZeroUsize>,
pub(crate) relr_off: Option<NonZeroUsize>,
pub(crate) relr_size: Option<NonZeroUsize>,
pub(crate) relr_entry_size: Option<usize>,
pub(crate) init_off: Option<NonZeroUsize>,
pub(crate) fini_off: Option<NonZeroUsize>,
pub(crate) init_array_off: Option<NonZeroUsize>,
pub(crate) init_array_size: Option<NonZeroUsize>,
pub(crate) fini_array_off: Option<NonZeroUsize>,
pub(crate) fini_array_size: Option<NonZeroUsize>,
pub(crate) version_ids_off: Option<NonZeroUsize>,
pub(crate) verneed_off: Option<NonZeroUsize>,
pub(crate) verneed_num: Option<NonZeroUsize>,
pub(crate) verdef_off: Option<NonZeroUsize>,
pub(crate) verdef_num: Option<NonZeroUsize>,
pub(crate) soname_off: Option<NonZeroUsize>,
pub(crate) rpath_off: Option<NonZeroUsize>,
pub(crate) runpath_off: Option<NonZeroUsize>,
pub(crate) dt_debug_idx: Option<usize>,
pub(crate) bind_now: bool,
pub(crate) symbolic: bool,
pub(crate) flags: usize,
pub(crate) flags_1: usize,
pub(crate) is_rela: Option<bool>,
pub(crate) needed_libs: Vec<NonZeroUsize>,
}
#[inline]
fn dynamic_table_end(offset: Option<NonZeroUsize>, size: Option<NonZeroUsize>) -> Option<usize> {
offset?.get().checked_add(size.map_or(0, NonZeroUsize::get))
}
impl ParsedDynamic {
#[inline]
fn apply(&mut self, idx: usize, tag: ElfDynamicTag, value: usize) -> bool {
match tag {
ElfDynamicTag::FLAGS => self.flags = value,
ElfDynamicTag::FLAGS_1 => self.flags_1 = value,
ElfDynamicTag::PLTGOT => self.got_off = NonZeroUsize::new(value),
ElfDynamicTag::NEEDED => {
if let Some(val) = NonZeroUsize::new(value) {
self.needed_libs.push(val);
}
}
ElfDynamicTag::HASH => self.elf_hash_off = Some(value),
ElfDynamicTag::GNU_HASH => self.gnu_hash_off = Some(value),
ElfDynamicTag::BIND_NOW => self.bind_now = true,
ElfDynamicTag::SYMBOLIC => self.symbolic = true,
ElfDynamicTag::SONAME => self.soname_off = NonZeroUsize::new(value),
ElfDynamicTag::SYMTAB => self.symtab_off = value,
ElfDynamicTag::STRTAB => self.strtab_off = value,
ElfDynamicTag::PLTRELSZ => self.pltrel_size = NonZeroUsize::new(value),
ElfDynamicTag::PLTREL => {
self.is_rela = Some(ElfDynamicTag::new(value as i64) == ElfDynamicTag::RELA);
}
ElfDynamicTag::JMPREL => self.pltrel_off = NonZeroUsize::new(value),
ElfDynamicTag::RELR => self.relr_off = NonZeroUsize::new(value),
ElfDynamicTag::RELA | ElfDynamicTag::REL => {
self.is_rela = Some(tag == ElfDynamicTag::RELA);
self.rel_off = NonZeroUsize::new(value)
}
ElfDynamicTag::RELASZ | ElfDynamicTag::RELSZ => {
self.rel_size = NonZeroUsize::new(value)
}
ElfDynamicTag::RELAENT => self.rela_entry_size = Some(value),
ElfDynamicTag::RELENT => self.rel_entry_size = Some(value),
ElfDynamicTag::RELRSZ => self.relr_size = NonZeroUsize::new(value),
ElfDynamicTag::RELRENT => self.relr_entry_size = Some(value),
ElfDynamicTag::RELACOUNT | ElfDynamicTag::RELCOUNT => {
self.rel_count = NonZeroUsize::new(value)
}
ElfDynamicTag::INIT => self.init_off = NonZeroUsize::new(value),
ElfDynamicTag::FINI => self.fini_off = NonZeroUsize::new(value),
ElfDynamicTag::INIT_ARRAY => self.init_array_off = NonZeroUsize::new(value),
ElfDynamicTag::INIT_ARRAYSZ => self.init_array_size = NonZeroUsize::new(value),
ElfDynamicTag::FINI_ARRAY => self.fini_array_off = NonZeroUsize::new(value),
ElfDynamicTag::FINI_ARRAYSZ => self.fini_array_size = NonZeroUsize::new(value),
ElfDynamicTag::VERSYM => self.version_ids_off = NonZeroUsize::new(value),
ElfDynamicTag::VERNEED => self.verneed_off = NonZeroUsize::new(value),
ElfDynamicTag::VERNEEDNUM => self.verneed_num = NonZeroUsize::new(value),
ElfDynamicTag::VERDEF => self.verdef_off = NonZeroUsize::new(value),
ElfDynamicTag::VERDEFNUM => self.verdef_num = NonZeroUsize::new(value),
ElfDynamicTag::RPATH => self.rpath_off = NonZeroUsize::new(value),
ElfDynamicTag::RUNPATH => self.runpath_off = NonZeroUsize::new(value),
ElfDynamicTag::STRSZ => self.strtab_size = NonZeroUsize::new(value),
ElfDynamicTag::DEBUG => self.dt_debug_idx = Some(idx),
ElfDynamicTag::NULL => return true,
_ => {}
}
false
}
}
#[inline]
pub(crate) fn parse_dynamic_entries<I>(entries: I) -> ParsedDynamic
where
I: IntoIterator<Item = (ElfDynamicTag, usize)>,
{
let mut parsed = ParsedDynamic::default();
for (idx, (tag, value)) in entries.into_iter().enumerate() {
if parsed.apply(idx, tag, value) {
break;
}
}
parsed
}
impl<Arch> ElfDynamic<Arch>
where
Arch: RelocationArch,
{
pub fn new<R: RegionAccess>(
dynamic_entries: MappedView<ElfDyn<Arch::Layout>>,
dynamic_addr: VmAddr,
segments: &ElfSegments<R>,
) -> Result<Self> {
dynamic_entries
.as_slice()
.first()
.ok_or(ParseDynamicError::MissingRequiredTag {
tag: ElfDynamicTag::NULL,
})?;
let parsed = parse_dynamic_entries(
dynamic_entries
.as_slice()
.iter()
.map(|entry| (entry.tag(), entry.value())),
);
let dt_debug_addr = parsed
.dt_debug_idx
.map(|idx| -> Result<_> {
let offset = idx
.checked_mul(size_of::<ElfDyn<Arch::Layout>>())
.ok_or(ParseDynamicError::AddressOverflow)?;
dynamic_addr
.checked_add(VmOffset::new(offset))
.ok_or_else(|| ParseDynamicError::AddressOverflow.into())
})
.transpose()?;
let base = segments.base();
if let Some(is_rela) = parsed.is_rela {
assert!(
is_rela && size_of::<Arch::Relocation>() == size_of::<ElfRela<Arch::Layout>>()
|| !is_rela
&& size_of::<Arch::Relocation>() == size_of::<ElfRel<Arch::Layout>>()
);
}
let add_base = |offset: usize| -> Result<VmAddr> {
base.checked_add(VmOffset::new(offset))
.ok_or(ParseDynamicError::AddressOverflow.into())
};
let add_base_nonzero = |offset: NonZeroUsize| -> Result<NonZeroUsize> {
NonZeroUsize::new(add_base(offset.get())?.get())
.ok_or_else(|| ParseDynamicError::AddressOverflow.into())
};
let hash_off = if let Some(off) = parsed.gnu_hash_off {
ElfDynamicHashTab::Gnu(add_base(off)?)
} else if let Some(off) = parsed.elf_hash_off {
ElfDynamicHashTab::Elf(add_base(off)?)
} else {
return Err(ParseDynamicError::MissingRequiredTag {
tag: ElfDynamicTag::GNU_HASH,
}
.into());
};
let pltrel = parsed
.pltrel_off
.map(|pltrel_off| -> Result<_> {
let view = segments
.read_view::<ElfRelType<Arch>>(
VmOffset::new(pltrel_off.get()),
parsed.pltrel_size.map(|len| len.get()).unwrap_or(0),
)
.ok_or(ParseDynamicError::InvalidRelocTable {
reason: RelocTableError::JmpRelSize,
})?;
Ok(view)
})
.transpose()?;
let dynrel = parsed
.rel_off
.map(|rel_off| -> Result<_> {
if parsed.is_rela.unwrap_or(false) {
let entry_size =
parsed
.rela_entry_size
.ok_or(ParseDynamicError::MissingRequiredTag {
tag: ElfDynamicTag::RELAENT,
})?;
let expected = size_of::<ElfRela<Arch::Layout>>();
if entry_size != expected {
return Err(ParseDynamicError::InvalidRelocTable {
reason: RelocTableError::RelaEntrySize {
expected,
actual: entry_size,
},
}
.into());
}
} else {
let entry_size =
parsed
.rel_entry_size
.ok_or(ParseDynamicError::MissingRequiredTag {
tag: ElfDynamicTag::RELENT,
})?;
let expected = size_of::<ElfRel<Arch::Layout>>();
if entry_size != expected {
return Err(ParseDynamicError::InvalidRelocTable {
reason: RelocTableError::RelEntrySize {
expected,
actual: entry_size,
},
}
.into());
}
}
let view = segments
.read_view::<ElfRelType<Arch>>(
VmOffset::new(rel_off.get()),
parsed.rel_size.map(|len| len.get()).unwrap_or(0),
)
.ok_or(ParseDynamicError::InvalidRelocTable {
reason: RelocTableError::DynRelSize,
})?;
Ok(view)
})
.transpose()?;
let relr = parsed
.relr_off
.map(|relr_off| -> Result<_> {
let entry_size =
parsed
.relr_entry_size
.ok_or(ParseDynamicError::MissingRequiredTag {
tag: ElfDynamicTag::RELRENT,
})?;
let expected = size_of::<ElfRelr<Arch::Layout>>();
if entry_size != expected {
return Err(ParseDynamicError::InvalidRelocTable {
reason: RelocTableError::RelrEntrySize {
expected,
actual: entry_size,
},
}
.into());
}
let view = segments
.read_view::<ElfRelr<Arch::Layout>>(
VmOffset::new(relr_off.get()),
parsed.relr_size.map(|len| len.get()).unwrap_or(0),
)
.ok_or(ParseDynamicError::InvalidRelocTable {
reason: RelocTableError::RelrSize,
})?;
Ok(view)
})
.transpose()?;
let pltrel_is_dynrel_tail = matches!(
(
dynamic_table_end(parsed.rel_off, parsed.rel_size),
dynamic_table_end(parsed.pltrel_off, parsed.pltrel_size),
),
(Some(dynrel_end), Some(pltrel_end)) if dynrel_end == pltrel_end
);
let init_fn = parsed
.init_off
.map(|init_off| add_base(init_off.get()))
.transpose()?;
let init_array_size = parsed.init_array_size.map(|len| len.get()).unwrap_or(0);
let fini_fn = parsed
.fini_off
.map(|fini_off| add_base(fini_off.get()))
.transpose()?;
let fini_array_size = parsed.fini_array_size.map(|len| len.get()).unwrap_or(0);
let verneed = parsed
.verneed_off
.map(|verneed_off| -> Result<_> {
Ok((
add_base_nonzero(verneed_off)?,
parsed
.verneed_num
.ok_or(ParseDynamicError::MissingRequiredTag {
tag: ElfDynamicTag::VERNEEDNUM,
})?,
))
})
.transpose()?;
let verdef = parsed
.verdef_off
.map(|verdef_off| -> Result<_> {
Ok((
add_base_nonzero(verdef_off)?,
parsed
.verdef_num
.ok_or(ParseDynamicError::MissingRequiredTag {
tag: ElfDynamicTag::VERDEFNUM,
})?,
))
})
.transpose()?;
let version_idx = parsed.version_ids_off.map(add_base_nonzero).transpose()?;
Ok(ElfDynamic {
dt_debug_addr,
hashtab: hash_off,
symtab: add_base(parsed.symtab_off)?,
strtab: add_base(parsed.strtab_off)?,
strtab_size: parsed.strtab_size,
bind_now: parsed.bind_now
|| parsed.flags & DF_BIND_NOW as usize != 0
|| parsed.flags_1 & DF_1_NOW as usize != 0,
symbolic: parsed.symbolic || parsed.flags & DF_SYMBOLIC as usize != 0,
static_tls: parsed.flags & DF_STATIC_TLS as usize != 0,
got_plt: parsed.got_off.map(|off| add_base(off.get())).transpose()?,
needed_libs: parsed.needed_libs,
pltrel,
dynrel,
relr,
pltrel_is_dynrel_tail,
init: LifecycleSpec::new(init_fn, parsed.init_array_off, init_array_size),
fini: LifecycleSpec::new(fini_fn, parsed.fini_array_off, fini_array_size),
rel_count: parsed.rel_count,
soname_off: parsed.soname_off,
rpath_off: parsed.rpath_off,
runpath_off: parsed.runpath_off,
version_idx,
verneed,
verdef,
})
}
}
#[derive(Clone, Copy)]
pub(crate) struct LifecycleSpec {
func: Option<VmAddr>,
array_offset: Option<NonZeroUsize>,
array_byte_len: usize,
}
impl LifecycleSpec {
#[inline]
const fn new(
func: Option<VmAddr>,
array_offset: Option<NonZeroUsize>,
array_byte_len: usize,
) -> Self {
Self {
func,
array_offset,
array_byte_len,
}
}
pub(crate) fn resolve<L: ElfLayout, R: RegionAccess>(
self,
segments: &ElfSegments<R>,
malformed: &'static str,
) -> Result<Lifecycle> {
let array = self
.array_offset
.map(|offset| -> Result<_> {
let words = segments
.read_view::<L::Word>(VmOffset::new(offset.get()), self.array_byte_len)
.ok_or(ParseDynamicError::MalformedLifecycleTable { detail: malformed })?;
Ok(words
.as_slice()
.iter()
.copied()
.map(|addr| VmAddr::new(addr.to_usize()))
.collect())
})
.transpose()?;
Ok(Lifecycle::new(self.func, array))
}
}
pub enum ElfDynamicHashTab {
Gnu(VmAddr),
Elf(VmAddr),
}
#[allow(unused)]
pub(crate) struct ElfDynamic<Arch: RelocationArch = NativeArch> {
pub dt_debug_addr: Option<VmAddr>,
pub hashtab: ElfDynamicHashTab,
pub symtab: VmAddr,
pub strtab: VmAddr,
pub strtab_size: Option<NonZeroUsize>,
pub bind_now: bool,
pub symbolic: bool,
pub static_tls: bool,
pub got_plt: Option<VmAddr>,
pub init: LifecycleSpec,
pub fini: LifecycleSpec,
pub pltrel: Option<MappedView<ElfRelType<Arch>>>,
pub dynrel: Option<MappedView<ElfRelType<Arch>>>,
pub relr: Option<MappedView<ElfRelr<Arch::Layout>>>,
pub pltrel_is_dynrel_tail: bool,
pub rel_count: Option<NonZeroUsize>,
pub needed_libs: Vec<NonZeroUsize>,
pub version_idx: Option<NonZeroUsize>,
pub verneed: Option<(NonZeroUsize, NonZeroUsize)>,
pub verdef: Option<(NonZeroUsize, NonZeroUsize)>,
pub soname_off: Option<NonZeroUsize>,
pub rpath_off: Option<NonZeroUsize>,
pub runpath_off: Option<NonZeroUsize>,
}
impl<Arch: RelocationArch> Debug for ElfDynamic<Arch> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ElfDynamic")
.field("dt_debug_addr", &self.dt_debug_addr)
.field("symtab", &format_args!("0x{:x}", self.symtab.get()))
.field("strtab", &format_args!("0x{:x}", self.strtab.get()))
.field("bind_now", &self.bind_now)
.field("symbolic", &self.symbolic)
.field("static_tls", &self.static_tls)
.field("got_plt", &self.got_plt)
.field("needed_libs_count", &self.needed_libs.len())
.field(
"pltrel_count",
&self.pltrel.as_ref().map(|r| r.len()).unwrap_or(0),
)
.field(
"dynrel_count",
&self.dynrel.as_ref().map(|r| r.len()).unwrap_or(0),
)
.field(
"relr_count",
&self.relr.as_ref().map(|r| r.len()).unwrap_or(0),
)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::{ElfDyn, ElfDynamicTag, parse_dynamic_entries};
use core::num::NonZeroUsize;
use elf::abi::DF_SYMBOLIC;
#[test]
fn owned_dyn_round_trips_and_mutates() {
let mut dyn_: ElfDyn = ElfDyn::new(ElfDynamicTag::STRTAB, 0x1234);
assert_eq!(dyn_.tag(), ElfDynamicTag::STRTAB);
assert_eq!(dyn_.value(), 0x1234);
dyn_.set_tag(ElfDynamicTag::NULL);
dyn_.set_value(0x5678);
assert_eq!(dyn_.tag(), ElfDynamicTag::NULL);
assert_eq!(dyn_.value(), 0x5678);
}
#[test]
fn parses_metadata_only_dynamic_tags() {
let parsed = parse_dynamic_entries([
(ElfDynamicTag::SONAME, 0x24),
(ElfDynamicTag::BIND_NOW, 0),
(ElfDynamicTag::NULL, 0),
]);
assert_eq!(parsed.soname_off, NonZeroUsize::new(0x24));
assert!(parsed.bind_now);
}
#[test]
fn parses_symbolic_dynamic_flags() {
let parsed = parse_dynamic_entries([
(ElfDynamicTag::SYMBOLIC, 0),
(ElfDynamicTag::FLAGS, DF_SYMBOLIC as usize),
(ElfDynamicTag::NULL, 0),
]);
assert!(parsed.symbolic);
assert_eq!(parsed.flags & DF_SYMBOLIC as usize, DF_SYMBOLIC as usize);
}
#[test]
fn parses_relocation_entry_size_tags() {
let parsed = parse_dynamic_entries([
(ElfDynamicTag::RELAENT, 24),
(ElfDynamicTag::RELENT, 16),
(ElfDynamicTag::RELRENT, 8),
(ElfDynamicTag::NULL, 0),
]);
assert_eq!(parsed.rela_entry_size, Some(24));
assert_eq!(parsed.rel_entry_size, Some(16));
assert_eq!(parsed.relr_entry_size, Some(8));
}
}