use super::bytes::to_u64;
use super::consts::{
CPU_TYPE_ARM64, CPU_TYPE_X86_64, UNWIND_ARM64_MODE_DWARF, UNWIND_HAS_LSDA, UNWIND_MODE_MASK,
UNWIND_PERSONALITY_MASK, UNWIND_X86_64_MODE_DWARF,
};
use super::object::ObjectFile;
use super::reloc::PairedRelocation;
use crate::error::Result;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UnwindField {
pub value: u64,
pub relocation: Option<PairedRelocation>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CompactUnwindEntry {
pub offset: u64,
pub function: UnwindField,
pub length: u32,
pub encoding: u32,
pub personality: UnwindField,
pub lsda: UnwindField,
}
impl CompactUnwindEntry {
#[must_use]
pub fn needs_dwarf(&self, cpu_type: u32) -> bool {
let mode = self.encoding & UNWIND_MODE_MASK;
match cpu_type {
CPU_TYPE_ARM64 => mode == UNWIND_ARM64_MODE_DWARF,
CPU_TYPE_X86_64 => mode == UNWIND_X86_64_MODE_DWARF,
_ => false,
}
}
#[must_use]
pub fn has_lsda(&self) -> bool {
self.encoding & UNWIND_HAS_LSDA != 0
}
#[must_use]
pub fn personality_index(&self) -> u32 {
(self.encoding & UNWIND_PERSONALITY_MASK) >> 28
}
}
pub fn compact_unwind_entries(object: &ObjectFile<'_>) -> Result<Vec<CompactUnwindEntry>> {
let Some((index, section)) = object.find_section(b"__LD", b"__compact_unwind") else {
return Ok(Vec::new());
};
let data = object.section_data(index)?;
let is64 = object.file().is64();
let endian = object.file().endian();
let (record, word, personality_at, lsda_at) = if is64 {
(32usize, 8usize, 16usize, 24usize)
} else {
(20, 4, 12, 16)
};
let source = object.source();
if data.len().checked_rem(record) != Some(0) {
return Err(source.malformed(
u64::from(section.offset),
format!(
"__compact_unwind (size {:#x} is not a multiple of {record})",
data.len()
),
));
}
let mut entries = Vec::with_capacity(data.len().checked_div(record).unwrap_or(0));
for (i, chunk) in data.chunks_exact(record).enumerate() {
let read_word = |at: usize| endian.word(chunk, at, is64).unwrap_or(0);
let field = |at: usize| UnwindField {
value: read_word(at),
relocation: None,
};
entries.push(CompactUnwindEntry {
offset: to_u64(i.saturating_mul(record)),
function: field(0),
length: endian.u32(chunk, word).unwrap_or(0),
encoding: endian.u32(chunk, word.saturating_add(4)).unwrap_or(0),
personality: field(personality_at),
lsda: field(lsda_at),
});
}
let table = object.relocations(index)?;
for relocation in table.paired(source) {
let relocation = relocation?;
let address = usize::try_from(relocation.relocation.address).unwrap_or(usize::MAX);
let (entry, within) = (
address.checked_div(record).unwrap_or(usize::MAX),
address.checked_rem(record).unwrap_or(usize::MAX),
);
let slot = entries.get_mut(entry).and_then(|e| match within {
0 => Some(&mut e.function),
w if w == personality_at => Some(&mut e.personality),
w if w == lsda_at => Some(&mut e.lsda),
_ => None,
});
match slot {
Some(field) if field.relocation.is_none() => field.relocation = Some(relocation),
_ => {
return Err(source.malformed(
table
.file_offset()
.saturating_add(to_u64(relocation.index).saturating_mul(8)),
format!(
"__compact_unwind relocation (address {address:#x} is not a relocatable field)"
),
));
}
}
}
Ok(entries)
}