use crate::{
ParseDynamicError, Result,
elf::{ElfLayout, ElfSymbol, SymbolLookup, SymbolTableView},
memory::{ImageMemory, MappedView, RegionAccess, VmAddr},
segment::ElfSegments,
};
use core::mem::size_of;
#[repr(C)]
#[derive(Clone, Copy)]
struct ElfHashHeader {
nbucket: u32,
nchain: u32,
}
impl ElfHashHeader {
#[inline]
fn from_bytes(bytes: [u8; size_of::<Self>()]) -> Self {
let [n0, n1, n2, n3, c0, c1, c2, c3] = bytes;
Self {
nbucket: u32::from_ne_bytes([n0, n1, n2, n3]),
nchain: u32::from_ne_bytes([c0, c1, c2, c3]),
}
}
}
#[derive(Clone)]
pub(crate) struct ElfHash {
header: ElfHashHeader,
buckets: MappedView<u32>,
chains: MappedView<u32>,
}
impl ElfHash {
#[inline]
pub(crate) fn hash(name: &[u8]) -> u64 {
let mut hash = 0u32;
#[allow(unused_assignments)]
let mut g = 0u32;
for byte in name {
hash = (hash << 4) + u32::from(*byte);
g = hash & 0xf0000000;
if g != 0 {
hash ^= g >> 24;
}
hash &= !g;
}
u64::from(hash)
}
#[inline]
pub(crate) fn parse<R: RegionAccess>(
segments: &ElfSegments<R>,
addr: VmAddr,
) -> Result<ElfHash> {
const HEADER_SIZE: usize = size_of::<ElfHashHeader>();
let start = addr
.checked_offset_from(segments.base())
.ok_or(ParseDynamicError::AddressOverflow)?;
let mut bytes = [0u8; HEADER_SIZE];
segments.read_bytes(addr, &mut bytes)?;
let header = ElfHashHeader::from_bytes(bytes);
if header.nbucket == 0 {
return Err(ParseDynamicError::EmptyHashTable {
table: "DT_HASH bucket table",
}
.into());
}
let bucket_size = (header.nbucket as usize)
.checked_mul(size_of::<u32>())
.ok_or(ParseDynamicError::AddressOverflow)?;
let buckets_off = start
.checked_add(HEADER_SIZE)
.ok_or(ParseDynamicError::AddressOverflow)?;
let chains_off = buckets_off
.checked_add(bucket_size)
.ok_or(ParseDynamicError::AddressOverflow)?;
let buckets = segments.read_view(buckets_off, bucket_size).ok_or(
ParseDynamicError::MalformedHashTable {
detail: "DT_HASH bucket table size is malformed",
},
)?;
let chain_size = (header.nchain as usize)
.checked_mul(size_of::<u32>())
.ok_or(ParseDynamicError::AddressOverflow)?;
let chains = segments.read_view(chains_off, chain_size).ok_or(
ParseDynamicError::MalformedHashTable {
detail: "DT_HASH chain table size is malformed",
},
)?;
Ok(ElfHash {
header,
buckets,
chains,
})
}
}
impl ElfHash {
#[inline]
pub(crate) fn count_syms(&self) -> usize {
self.header.nchain as usize
}
pub(crate) fn lookup<'sym, L: ElfLayout, H>(
&self,
table: SymbolTableView<'sym, L, H>,
lookup: &mut SymbolLookup<'_>,
) -> Option<&'sym ElfSymbol<L>> {
let hash = lookup.sysv_hash(|name| Self::hash(name.as_bytes()) as u32);
let buckets = self.buckets.as_slice();
let chains = self.chains.as_slice();
let bucket_idx = (hash as usize) % self.header.nbucket as usize;
let mut chain_idx = *buckets.get(bucket_idx)? as usize;
loop {
if chain_idx == 0 {
return None;
}
let next_chain = *chains.get(chain_idx)? as usize;
let cur_symbol = table.symbols().get(chain_idx)?;
let sym_name = table.strtab.get_str(cur_symbol.st_name());
#[cfg(feature = "version")]
if sym_name == lookup.name() && table.check_match(chain_idx, lookup.version()) {
return Some(cur_symbol);
}
#[cfg(not(feature = "version"))]
if sym_name == lookup.name() {
return Some(cur_symbol);
}
chain_idx = next_chain;
}
}
}