use crate::{
Result,
elf::{DT_RELR, DT_RELRSZ, ElfDyn, ElfRel, ElfRelType, ElfRela, ElfRelr},
parse_dynamic_error,
segment::ElfSegments,
};
use alloc::vec::Vec;
use core::fmt::Debug;
use core::{
num::NonZeroUsize,
ops::{Add, AddAssign, Sub, SubAssign},
ptr::{NonNull, null_mut},
};
use elf::abi::*;
impl ElfDynamic {
pub fn new(dynamic_ptr: *const ElfDyn, segments: &ElfSegments) -> Result<Self> {
let mut symtab_off = 0; let mut strtab_off = 0; let mut elf_hash_off = None; let mut gnu_hash_off = None; let mut got_off = None; let mut pltrel_size = None; let mut pltrel_off = None; let mut rel_off = None; let mut rel_size = None; let mut rel_count = None; let mut relr_off = None; let mut relr_size = None; let mut init_off = None; let mut fini_off = None; let mut init_array_off = None; let mut init_array_size = None; let mut fini_array_off = None; let mut fini_array_size = None; let mut version_ids_off = None; let mut verneed_off = None; let mut verneed_num = None; let mut verdef_off = None; let mut verdef_num = None; let mut rpath_off = None; let mut runpath_off = None; let mut flags = 0; let mut flags_1 = 0; let mut is_rela = None; let mut needed_libs = Vec::new();
let mut cur_dyn_ptr = dynamic_ptr;
let mut dynamic = unsafe { &*cur_dyn_ptr };
let base = segments.base();
unsafe {
loop {
match dynamic.d_tag as _ {
DT_FLAGS => flags = dynamic.d_un as usize,
DT_FLAGS_1 => flags_1 = dynamic.d_un as usize,
DT_PLTGOT => got_off = NonZeroUsize::new(dynamic.d_un as usize),
DT_NEEDED => {
if let Some(val) = NonZeroUsize::new(dynamic.d_un as usize) {
needed_libs.push(val);
}
}
DT_HASH => elf_hash_off = Some(dynamic.d_un as usize),
DT_GNU_HASH => gnu_hash_off = Some(dynamic.d_un as usize),
DT_SYMTAB => symtab_off = dynamic.d_un as usize,
DT_STRTAB => strtab_off = dynamic.d_un as usize,
DT_PLTRELSZ => pltrel_size = NonZeroUsize::new(dynamic.d_un as usize),
DT_PLTREL => {
is_rela = Some(dynamic.d_un as i64 == DT_RELA);
}
DT_JMPREL => pltrel_off = NonZeroUsize::new(dynamic.d_un as usize),
DT_RELR => relr_off = NonZeroUsize::new(dynamic.d_un as usize),
DT_RELA | DT_REL => {
is_rela = Some(dynamic.d_tag as i64 == DT_RELA);
rel_off = NonZeroUsize::new(dynamic.d_un as usize)
}
DT_RELASZ | DT_RELSZ => rel_size = NonZeroUsize::new(dynamic.d_un as usize),
DT_RELRSZ => relr_size = NonZeroUsize::new(dynamic.d_un as usize),
DT_RELACOUNT | DT_RELCOUNT => {
rel_count = NonZeroUsize::new(dynamic.d_un as usize)
}
DT_INIT => init_off = NonZeroUsize::new(dynamic.d_un as usize),
DT_FINI => fini_off = NonZeroUsize::new(dynamic.d_un as usize),
DT_INIT_ARRAY => init_array_off = NonZeroUsize::new(dynamic.d_un as usize),
DT_INIT_ARRAYSZ => init_array_size = NonZeroUsize::new(dynamic.d_un as usize),
DT_FINI_ARRAY => fini_array_off = NonZeroUsize::new(dynamic.d_un as usize),
DT_FINI_ARRAYSZ => fini_array_size = NonZeroUsize::new(dynamic.d_un as usize),
DT_VERSYM => {
version_ids_off = Some(NonZeroUsize::new_unchecked(dynamic.d_un as usize))
}
DT_VERNEED => {
verneed_off = Some(NonZeroUsize::new_unchecked(dynamic.d_un as usize))
}
DT_VERNEEDNUM => {
verneed_num = Some(NonZeroUsize::new_unchecked(dynamic.d_un as usize))
}
DT_VERDEF => {
verdef_off = Some(NonZeroUsize::new_unchecked(dynamic.d_un as usize))
}
DT_VERDEFNUM => {
verdef_num = Some(NonZeroUsize::new_unchecked(dynamic.d_un as usize))
}
DT_RPATH => {
rpath_off = Some(NonZeroUsize::new_unchecked(dynamic.d_un as usize))
}
DT_RUNPATH => {
runpath_off = Some(NonZeroUsize::new_unchecked(dynamic.d_un as usize))
}
DT_NULL => break,
_ => {}
}
cur_dyn_ptr = cur_dyn_ptr.add(1);
dynamic = &*cur_dyn_ptr;
}
}
if let Some(is_rela) = is_rela {
assert!(
is_rela && size_of::<ElfRelType>() == size_of::<ElfRela>()
|| !is_rela && size_of::<ElfRelType>() == size_of::<ElfRel>()
);
}
let hash_off = if let Some(off) = gnu_hash_off {
ElfDynamicHashTab::Gnu(off)
} else if let Some(off) = elf_hash_off {
ElfDynamicHashTab::Elf(off)
} else {
return Err(parse_dynamic_error(
"dynamic section does not have DT_GNU_HASH nor DT_HASH",
));
};
let pltrel = pltrel_off.map(|pltrel_off| {
segments.get_slice(pltrel_off.get(), pltrel_size.map(|s| s.get()).unwrap_or(0))
});
let dynrel = rel_off.map(|rel_off| {
segments.get_slice(rel_off.get(), rel_size.map(|s| s.get()).unwrap_or(0))
});
let relr = relr_off.map(|relr_off| {
segments.get_slice(relr_off.get(), relr_size.map(|s| s.get()).unwrap_or(0))
});
let init_fn = init_off
.map(|val| unsafe { core::mem::transmute(segments.get_ptr::<fn()>(val.get())) });
let init_array_fn = init_array_off.map(|init_array_off| {
segments.get_slice(
init_array_off.get(),
init_array_size.map(|s| s.get()).unwrap_or(0),
)
});
let fini_fn = fini_off.map(|fini_off| unsafe {
core::mem::transmute(segments.get_ptr::<fn()>(fini_off.get()))
});
let fini_array_fn = fini_array_off.map(|fini_array_off| {
segments.get_slice(
fini_array_off.get(),
fini_array_size.map(|s| s.get()).unwrap_or(0),
)
});
let verneed = verneed_off
.map(|verneed_off| (verneed_off.checked_add(base).unwrap(), verneed_num.unwrap()));
let verdef = verdef_off
.map(|verdef_off| (verdef_off.checked_add(base).unwrap(), verdef_num.unwrap()));
let version_idx = version_ids_off.map(|off| off.checked_add(base).unwrap());
Ok(ElfDynamic {
dyn_ptr: dynamic_ptr,
hashtab: hash_off + base,
symtab: symtab_off + base,
strtab: strtab_off + base,
bind_now: flags & DF_BIND_NOW as usize != 0 || flags_1 & DF_1_NOW as usize != 0,
static_tls: flags & DF_STATIC_TLS as usize != 0,
got_plt: NonNull::new(
got_off
.map(|off| (base + off.get()) as *mut usize)
.unwrap_or(null_mut()),
),
needed_libs,
pltrel,
dynrel,
relr,
init_fn,
init_array_fn,
fini_fn,
fini_array_fn,
rel_count,
rpath_off,
runpath_off,
version_idx,
verneed,
verdef,
})
}
}
pub enum ElfDynamicHashTab {
Gnu(usize),
Elf(usize),
}
impl Into<usize> for ElfDynamicHashTab {
fn into(self) -> usize {
match self {
ElfDynamicHashTab::Gnu(off) => off,
ElfDynamicHashTab::Elf(off) => off,
}
}
}
impl Add<usize> for ElfDynamicHashTab {
type Output = ElfDynamicHashTab;
fn add(self, rhs: usize) -> Self::Output {
match self {
ElfDynamicHashTab::Gnu(off) => ElfDynamicHashTab::Gnu(off + rhs),
ElfDynamicHashTab::Elf(off) => ElfDynamicHashTab::Elf(off + rhs),
}
}
}
impl Sub<usize> for ElfDynamicHashTab {
type Output = ElfDynamicHashTab;
fn sub(self, rhs: usize) -> Self::Output {
match self {
ElfDynamicHashTab::Gnu(off) => ElfDynamicHashTab::Gnu(off - rhs),
ElfDynamicHashTab::Elf(off) => ElfDynamicHashTab::Elf(off - rhs),
}
}
}
impl AddAssign<usize> for ElfDynamicHashTab {
fn add_assign(&mut self, rhs: usize) {
match self {
ElfDynamicHashTab::Gnu(off) => *off += rhs,
ElfDynamicHashTab::Elf(off) => *off += rhs,
}
}
}
impl SubAssign<usize> for ElfDynamicHashTab {
fn sub_assign(&mut self, rhs: usize) {
match self {
ElfDynamicHashTab::Gnu(off) => *off -= rhs,
ElfDynamicHashTab::Elf(off) => *off -= rhs,
}
}
}
#[allow(unused)]
pub(crate) struct ElfDynamic {
pub dyn_ptr: *const ElfDyn,
pub hashtab: ElfDynamicHashTab,
pub symtab: usize,
pub strtab: usize,
pub bind_now: bool,
pub static_tls: bool,
pub got_plt: Option<NonNull<usize>>,
pub init_fn: Option<fn()>,
pub init_array_fn: Option<&'static [fn()]>,
pub fini_fn: Option<fn()>,
pub fini_array_fn: Option<&'static [fn()]>,
pub pltrel: Option<&'static [ElfRelType]>,
pub dynrel: Option<&'static [ElfRelType]>,
pub relr: Option<&'static [ElfRelr]>,
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 rpath_off: Option<NonZeroUsize>,
pub runpath_off: Option<NonZeroUsize>,
}
impl Debug for ElfDynamic {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ElfDynamic")
.field("dyn_ptr", &self.dyn_ptr)
.field("symtab", &format_args!("0x{:x}", self.symtab))
.field("strtab", &format_args!("0x{:x}", self.strtab))
.field("bind_now", &self.bind_now)
.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.map(|r| r.len()).unwrap_or(0))
.field("dynrel_count", &self.dynrel.map(|r| r.len()).unwrap_or(0))
.field("relr_count", &self.relr.map(|r| r.len()).unwrap_or(0))
.finish()
}
}