use crate::util::read_u32_le_at;
const HEADER_BYTES: usize = 28;
const INDEX_ENTRY_BYTES: usize = 12;
const LSDA_ENTRY_BYTES: usize = 8;
const REGULAR_PAGE: u32 = 2;
const COMPRESSED_PAGE: u32 = 3;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UnwindFunction {
pub address: u64,
pub encoding: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LsdaRef {
pub function: u64,
pub lsda: u64,
}
#[derive(Debug, Clone, Copy)]
pub struct UnwindInfo<'a> {
bytes: &'a [u8],
image_base: u64,
}
impl<'a> UnwindInfo<'a> {
#[must_use]
pub fn parse(bytes: &'a [u8], image_base: u64) -> Option<Self> {
if bytes.len() < HEADER_BYTES || read_u32_le_at(bytes, 0)? != 1 {
return None;
}
Some(Self { bytes, image_base })
}
fn word(&self, offset: usize) -> Option<u32> {
read_u32_le_at(self.bytes, offset)
}
fn half(&self, offset: usize) -> Option<u16> {
Some(u16::from_le_bytes(
self.bytes
.get(offset..offset.checked_add(2)?)?
.try_into()
.ok()?,
))
}
fn index(&self) -> Vec<(u32, u32, u32)> {
let (Some(at), Some(count)) = (self.word(20), self.word(24)) else {
return Vec::new();
};
(0..count as usize)
.map_while(|index| {
let entry = (at as usize).checked_add(index.checked_mul(INDEX_ENTRY_BYTES)?)?;
Some((
self.word(entry)?,
self.word(entry.checked_add(4)?)?,
self.word(entry.checked_add(8)?)?,
))
})
.collect()
}
fn address(&self, offset: u32) -> u64 {
self.image_base.wrapping_add(u64::from(offset))
}
#[must_use]
pub fn functions(&self) -> Vec<UnwindFunction> {
let common_at = self.word(4).unwrap_or(0) as usize;
let common_count = self.word(8).unwrap_or(0);
let common = |index: u32| -> Option<u32> {
(index < common_count)
.then(|| self.word(common_at.checked_add((index as usize).checked_mul(4)?)?))
.flatten()
};
let mut functions = Vec::new();
for (base, page, _) in self.index() {
if page == 0 {
continue;
}
let page = page as usize;
match self.word(page) {
Some(REGULAR_PAGE) => {
let (Some(at), Some(count)) = (
page.checked_add(4).and_then(|at| self.half(at)),
page.checked_add(6).and_then(|at| self.half(at)),
) else {
continue;
};
for index in 0..usize::from(count) {
let Some(entry) = page
.checked_add(usize::from(at))
.and_then(|entries| entries.checked_add(index.checked_mul(8)?))
else {
break;
};
let (Some(function), Some(encoding)) = (
self.word(entry),
entry.checked_add(4).and_then(|at| self.word(at)),
) else {
break;
};
functions.push(UnwindFunction {
address: self.address(function),
encoding,
});
}
}
Some(COMPRESSED_PAGE) => {
let header =
|offset: usize| page.checked_add(offset).and_then(|at| self.half(at));
let (Some(at), Some(count), Some(local_at), Some(local_count)) =
(header(4), header(6), header(8), header(10))
else {
continue;
};
for index in 0..usize::from(count) {
let Some(packed) = page
.checked_add(usize::from(at))
.and_then(|entries| entries.checked_add(index.checked_mul(4)?))
.and_then(|entry| self.word(entry))
else {
break;
};
let encoding_index = packed >> 24;
let encoding = common(encoding_index).or_else(|| {
let local = encoding_index.checked_sub(common_count)?;
(local < u32::from(local_count))
.then(|| {
self.word(
page.checked_add(usize::from(local_at))?
.checked_add((local as usize).checked_mul(4)?)?,
)
})
.flatten()
});
let Some(encoding) = encoding else {
continue;
};
functions.push(UnwindFunction {
address: self.address(base.wrapping_add(packed & 0x00FF_FFFF)),
encoding,
});
}
}
_ => {}
}
}
functions.sort_by_key(|function| function.address);
functions.dedup_by_key(|function| function.address);
functions
}
#[must_use]
pub fn end(&self) -> Option<u64> {
self.index().last().map(|&(end, _, _)| self.address(end))
}
#[must_use]
pub fn lsdas(&self) -> Vec<LsdaRef> {
let index = self.index();
let (Some(&(_, _, first)), Some(&(_, _, last))) = (index.first(), index.last()) else {
return Vec::new();
};
(first as usize..last as usize)
.step_by(LSDA_ENTRY_BYTES)
.map_while(|entry| {
Some(LsdaRef {
function: self.address(self.word(entry)?),
lsda: self.address(self.word(entry.checked_add(4)?)?),
})
})
.collect()
}
}