use crate::{
formats::BinaryContext,
structures::{
abitype::AbiType, interfacetype::InterfaceTypeExtra, moduledata::Moduledata,
util::read_uintptr,
},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ItabPair {
pub iface_type_va: u64,
pub concrete_type_va: u64,
pub hash: u32,
pub itab_va: u64,
}
pub struct ItabIter<'a> {
ctx: &'a BinaryContext<'a>,
ps: usize,
source: ItabSource<'a>,
}
enum ItabSource<'a> {
Empty,
Pointers { array: &'a [u8], pos: usize },
Inline { va: u64, end_va: u64 },
}
impl<'a> ItabIter<'a> {
fn empty(ctx: &'a BinaryContext<'a>) -> Self {
Self {
ctx,
ps: 0,
source: ItabSource::Empty,
}
}
}
impl Iterator for ItabIter<'_> {
type Item = ItabPair;
fn next(&mut self) -> Option<ItabPair> {
let ps = self.ps;
if ps == 0 {
return None;
}
let ps_u8 = u8::try_from(ps).ok()?;
match &mut self.source {
ItabSource::Empty => None,
ItabSource::Pointers { array, pos } => loop {
let end = pos.checked_add(ps)?;
if end > array.len() {
return None;
}
let v = match ps {
4 => array
.get(*pos..end)
.and_then(|s| s.try_into().ok())
.map(|b: [u8; 4]| u32::from_le_bytes(b) as u64),
8 => array
.get(*pos..end)
.and_then(|s| s.try_into().ok())
.map(u64::from_le_bytes),
_ => return None,
};
*pos = end;
let itab_va = match v {
Some(v) if v != 0 => v,
_ => continue,
};
if let Some(pair) = parse_itab(self.ctx, itab_va, ps, ps_u8) {
return Some(pair);
}
},
ItabSource::Inline { va, end_va } => {
if *va >= *end_va {
return None;
}
let cur = *va;
let pair = parse_itab(self.ctx, cur, ps, ps_u8)?;
let stride = itab_stride(self.ctx, cur, ps, ps_u8)?;
let next = cur.checked_add(stride as u64)?;
if next <= cur {
return None;
}
*va = next;
Some(pair)
}
}
}
}
pub fn extract_iter<'a>(
ctx: &'a BinaryContext<'a>,
ptr_size: u8,
moduledata: Option<&Moduledata>,
) -> ItabIter<'a> {
let ps = ptr_size as usize;
if ps == 0 {
return ItabIter::empty(ctx);
}
let sections = ctx.sections();
if let Some(ref range) = sections.itablink
&& let Some(bytes) = ctx.section_data(range)
{
return ItabIter {
ctx,
ps,
source: ItabSource::Pointers {
array: bytes,
pos: 0,
},
};
}
if let Some(slice) = moduledata.and_then(|md| md.itablinks.as_ref())
&& let Some(rest) = ctx.slice_at_va(slice.ptr)
&& let Some(byte_len) = (slice.len as usize).checked_mul(ps)
&& let Some(s) = rest.get(..byte_len)
{
return ItabIter {
ctx,
ps,
source: ItabSource::Pointers { array: s, pos: 0 },
};
}
if let Some(md) = moduledata
&& let (Some(itaboffset), Some(itabsize)) = (md.itaboffset, md.itabsize)
&& itabsize > 0
&& let Some(start) = md.types.checked_add(itaboffset)
&& let Some(end_va) = start.checked_add(itabsize)
{
return ItabIter {
ctx,
ps,
source: ItabSource::Inline { va: start, end_va },
};
}
ItabIter::empty(ctx)
}
fn itab_stride(ctx: &BinaryContext<'_>, itab_va: u64, ps: usize, ps_u8: u8) -> Option<usize> {
let base = ps.checked_mul(4)?;
let buf = ctx.slice_at_va(itab_va)?;
let fun0_off = ps.checked_mul(3)?;
let fun0 = read_uintptr(buf, fun0_off, ps_u8)?;
if fun0 == 0 {
return Some(base);
}
let inter_va = read_uintptr(buf, 0, ps_u8)?;
let nmethods = interface_method_count(ctx, inter_va, ps_u8)?;
let extra = nmethods.checked_sub(1)?.checked_mul(ps)?;
base.checked_add(extra)
}
pub fn itab_methods(ctx: &BinaryContext<'_>, pair: &ItabPair, ptr_size: u8) -> Vec<u64> {
let ps = ptr_size as usize;
if ps == 0 {
return Vec::new();
}
let n = match interface_method_count(ctx, pair.iface_type_va, ptr_size) {
Some(n) if n > 0 && n < 100_000 => n,
_ => return Vec::new(),
};
let fun_off = match pair
.itab_va
.checked_add(ps.checked_mul(3).unwrap_or(0) as u64)
{
Some(v) => v,
None => return Vec::new(),
};
let buf = match ctx.slice_at_va(fun_off) {
Some(b) => b,
None => return Vec::new(),
};
let mut out = Vec::with_capacity(n);
for i in 0..n {
let off = match i.checked_mul(ps) {
Some(o) => o,
None => break,
};
match read_uintptr(buf, off, ptr_size) {
Some(v) => out.push(v),
None => break,
}
}
out
}
fn interface_method_count(ctx: &BinaryContext<'_>, inter_va: u64, ps_u8: u8) -> Option<usize> {
let buf = ctx.slice_at_va(inter_va)?;
let extra_off = AbiType::size(ps_u8);
let extra = InterfaceTypeExtra::parse(buf.get(extra_off..)?, ps_u8)?;
usize::try_from(extra.methods.len).ok()
}
fn parse_itab(ctx: &BinaryContext<'_>, itab_va: u64, ps: usize, ps_u8: u8) -> Option<ItabPair> {
let buf = ctx.slice_at_va(itab_va)?;
let hash_off = ps.checked_mul(2)?;
let needed = hash_off.checked_add(4)?;
if buf.len() < needed {
return None;
}
let iface_type_va = read_uintptr(buf, 0, ps_u8)?;
let concrete_type_va = read_uintptr(buf, ps, ps_u8)?;
let hash_end = hash_off.checked_add(4)?;
let hash_bytes: [u8; 4] = buf.get(hash_off..hash_end)?.try_into().ok()?;
let hash = u32::from_le_bytes(hash_bytes);
Some(ItabPair {
iface_type_va,
concrete_type_va,
hash,
itab_va,
})
}