#![allow(non_upper_case_globals, dead_code)]
use std::collections::HashMap;
use std::fmt;
pub mod unwind_x86_64_mode {
pub const UNWIND_X86_64_MODE_RBP_FRAME: u32 = 0;
pub const UNWIND_X86_64_MODE_STACK_IMMD: u32 = 1;
pub const UNWIND_X86_64_MODE_STACK_IND: u32 = 2;
pub const UNWIND_X86_64_MODE_DWARF: u32 = 3;
}
pub mod unwind_x86_64_encoding {
pub const REG_COUNT_SHIFT: u32 = 16;
pub const REG_COUNT_MASK: u32 = 0x00FF_0000;
pub const REG_PERMUTATION_SHIFT: u32 = 12;
pub const REG_PERMUTATION_MASK: u32 = 0x0000_F000;
pub const MODE_SHIFT: u32 = 24;
pub const MODE_MASK: u32 = 0x0F00_0000;
pub const STACK_SIZE_SHIFT: u32 = 28;
pub const STACK_SIZE_MASK: u32 = 0xF000_0000;
}
pub mod unwind_x86_mode {
pub const UNWIND_X86_MODE_EBP_FRAME: u32 = 0;
pub const UNWIND_X86_MODE_STACK_IMMD: u32 = 1;
pub const UNWIND_X86_MODE_STACK_IND: u32 = 2;
pub const UNWIND_X86_MODE_DWARF: u32 = 3;
}
pub mod unwind_x86_encoding {
pub const REG_COUNT_SHIFT: u32 = 16;
pub const REG_COUNT_MASK: u32 = 0x00FF_0000;
pub const REG_PERMUTATION_SHIFT: u32 = 12;
pub const REG_PERMUTATION_MASK: u32 = 0x0000_F000;
pub const MODE_SHIFT: u32 = 24;
pub const MODE_MASK: u32 = 0x0F00_0000;
pub const STACK_SIZE_SHIFT: u32 = 28;
pub const STACK_SIZE_MASK: u32 = 0xF000_0000;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PersonalityEncoding {
None,
Direct(u64),
Indirect(u32),
Gnu(u32),
}
impl PersonalityEncoding {
pub fn encode(&self) -> u32 {
match self {
Self::None => 0,
Self::Direct(ptr) => {
(ptr & 0x3FFF_FFFF) as u32
}
Self::Indirect(offset) => {
0x4000_0000 | (offset & 0x3FFF_FFFF)
}
Self::Gnu(offset) => {
0x8000_0000 | (offset & 0x3FFF_FFFF)
}
}
}
pub fn decode(raw: u32) -> Self {
if raw == 0 {
return Self::None;
}
match (raw >> 30) & 3 {
0b00 => Self::Direct(raw as u64 & 0x3FFF_FFFF),
0b01 => Self::Indirect(raw & 0x3FFF_FFFF),
0b10 => Self::Gnu(raw & 0x3FFF_FFFF),
_ => Self::None, }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CompactUnwindEntry {
pub function_start: u32,
pub function_length: u32,
pub encoding: u32,
pub personality: u32,
pub lsda: u32,
}
impl CompactUnwindEntry {
pub const ENTRY_SIZE: usize = 20;
pub fn new(start: u32, length: u32, encoding: u32, personality: u32, lsda: u32) -> Self {
Self {
function_start: start,
function_length: length,
encoding,
personality,
lsda,
}
}
pub fn simple(start: u32, length: u32, encoding: u32) -> Self {
Self {
function_start: start,
function_length: length,
encoding,
personality: 0,
lsda: 0,
}
}
pub fn encode_to_bytes(&self) -> [u8; 20] {
let mut buf = [0u8; 20];
buf[0..4].copy_from_slice(&self.function_start.to_le_bytes());
buf[4..8].copy_from_slice(&self.function_length.to_le_bytes());
buf[8..12].copy_from_slice(&self.encoding.to_le_bytes());
buf[12..16].copy_from_slice(&self.personality.to_le_bytes());
buf[16..20].copy_from_slice(&self.lsda.to_le_bytes());
buf
}
pub fn decode_from_bytes(bytes: &[u8; 20]) -> Self {
let start = u32::from_le_bytes(bytes[0..4].try_into().unwrap());
let length = u32::from_le_bytes(bytes[4..8].try_into().unwrap());
let encoding = u32::from_le_bytes(bytes[8..12].try_into().unwrap());
let personality = u32::from_le_bytes(bytes[12..16].try_into().unwrap());
let lsda = u32::from_le_bytes(bytes[16..20].try_into().unwrap());
Self {
function_start: start,
function_length: length,
encoding,
personality,
lsda,
}
}
pub fn mode_64(&self) -> u32 {
(self.encoding >> unwind_x86_64_encoding::MODE_SHIFT) & 0xF
}
pub fn mode_32(&self) -> u32 {
(self.encoding >> unwind_x86_encoding::MODE_SHIFT) & 0xF
}
pub fn reg_count_64(&self) -> u32 {
(self.encoding >> unwind_x86_64_encoding::REG_COUNT_SHIFT) & 0xFF
}
pub fn stack_size_64(&self) -> u32 {
let mode = self.mode_64();
let stack_field = (self.encoding >> unwind_x86_64_encoding::STACK_SIZE_SHIFT) & 0xF;
match mode {
unwind_x86_64_mode::UNWIND_X86_64_MODE_RBP_FRAME => stack_field * 8,
unwind_x86_64_mode::UNWIND_X86_64_MODE_STACK_IMMD => (stack_field + 16) * 8,
_ => 0,
}
}
pub fn set_personality(&mut self, enc: PersonalityEncoding) {
self.personality = enc.encode();
}
}
impl fmt::Display for CompactUnwindEntry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"compact_unwind_entry [start={:#x}, len={}, encoding={:#010x}]",
self.function_start, self.function_length, self.encoding
)
}
}
pub mod x86_64_reg_permutations {
pub static PERMUTATION_TABLE: &[&[u8]] = &[
&[], &[0], &[1], &[2], &[3], &[4], &[5], &[0, 1], &[0, 2], &[0, 3], &[0, 4], &[0, 5], &[1, 2], &[1, 3], &[1, 4], &[1, 5], &[2, 3], &[2, 4], &[2, 5], &[3, 4], &[3, 5], &[4, 5], &[0, 1, 2], &[0, 1, 3], &[0, 1, 4], &[0, 1, 5], &[0, 2, 3], &[0, 2, 4], &[0, 2, 5], &[1, 2, 3], &[1, 2, 4], &[0, 3, 4], &[0, 3, 5], &[1, 3, 4], &[1, 3, 5], &[2, 3, 4], ];
pub const TABLE_LENGTH: usize = 36;
pub fn find_permutation(regs: &[u8]) -> Option<usize> {
if regs.len() > 3 {
return None;
}
PERMUTATION_TABLE.iter().position(|entry| entry == ®s)
}
pub fn get_registers(index: usize) -> Option<&'static [u8]> {
PERMUTATION_TABLE.get(index).copied()
}
}
pub fn encode_x86_64_rbp_frame(stack_size: u32, saved_regs: &[u8]) -> Result<u32, String> {
if stack_size > 128 || stack_size % 8 != 0 {
return Err(format!(
"RBP_FRAME stack size must be ≤128 and multiple of 8, got {stack_size}"
));
}
let stack_field = stack_size / 8;
if stack_field > 15 {
return Err(format!(
"RBP_FRAME stack size field overflows: {stack_size}"
));
}
let reg_count = saved_regs.len() as u32;
if reg_count > 6 {
return Err(format!("RBP_FRAME max 6 saved regs, got {reg_count}"));
}
let perm_idx = if reg_count > 0 {
x86_64_reg_permutations::find_permutation(saved_regs)
.ok_or_else(|| format!("Unsupported register permutation: {saved_regs:?}"))?
} else {
0
} as u32;
let encoding = (unwind_x86_64_mode::UNWIND_X86_64_MODE_RBP_FRAME
<< unwind_x86_64_encoding::MODE_SHIFT)
| (stack_field << unwind_x86_64_encoding::STACK_SIZE_SHIFT)
| (reg_count << unwind_x86_64_encoding::REG_COUNT_SHIFT)
| (perm_idx << unwind_x86_64_encoding::REG_PERMUTATION_SHIFT);
Ok(encoding)
}
pub fn encode_x86_64_stack_immd(stack_size: u32, saved_regs: &[u8]) -> Result<u32, String> {
if stack_size % 8 != 0 {
return Err(format!(
"STACK_IMMD stack size must be multiple of 8, got {stack_size}"
));
}
let scaled = stack_size / 8;
if scaled < 16 || scaled > 527 {
return Err(format!(
"STACK_IMMD stack size scaled value {scaled} out of range (16..527)"
));
}
let stack_field = scaled - 16; if stack_field > 511 {
return Err(format!("STACK_IMMD stack field overflow: {stack_size}"));
}
let reg_count = saved_regs.len() as u32;
if reg_count > 6 {
return Err(format!("STACK_IMMD max 6 saved regs, got {reg_count}"));
}
let perm_idx = if reg_count > 0 {
x86_64_reg_permutations::find_permutation(saved_regs)
.ok_or_else(|| format!("Unsupported register permutation: {saved_regs:?}"))?
} else {
0
} as u32;
let stack_high = ((stack_field >> 4) & 0xF) << unwind_x86_64_encoding::STACK_SIZE_SHIFT;
let stack_low = (stack_field & 0xF) << 8;
let encoding = (unwind_x86_64_mode::UNWIND_X86_64_MODE_STACK_IMMD
<< unwind_x86_64_encoding::MODE_SHIFT)
| stack_high
| stack_low
| (reg_count << unwind_x86_64_encoding::REG_COUNT_SHIFT)
| (perm_idx << unwind_x86_64_encoding::REG_PERMUTATION_SHIFT);
Ok(encoding)
}
pub fn encode_x86_64_stack_ind(saved_regs: &[u8]) -> Result<u32, String> {
let reg_count = saved_regs.len() as u32;
if reg_count > 6 {
return Err(format!("STACK_IND max 6 saved regs, got {reg_count}"));
}
let perm_idx = if reg_count > 0 {
x86_64_reg_permutations::find_permutation(saved_regs)
.ok_or_else(|| format!("Unsupported register permutation: {saved_regs:?}"))?
} else {
0
} as u32;
let encoding = (unwind_x86_64_mode::UNWIND_X86_64_MODE_STACK_IND
<< unwind_x86_64_encoding::MODE_SHIFT)
| (reg_count << unwind_x86_64_encoding::REG_COUNT_SHIFT)
| (perm_idx << unwind_x86_64_encoding::REG_PERMUTATION_SHIFT);
Ok(encoding)
}
pub fn encode_x86_64_dwarf() -> u32 {
(unwind_x86_64_mode::UNWIND_X86_64_MODE_DWARF << unwind_x86_64_encoding::MODE_SHIFT)
}
pub mod x86_reg_permutations {
pub static PERMUTATION_TABLE: &[&[u8]] = &[
&[], &[0], &[4], &[3], &[5], &[0, 4], &[0, 3], &[0, 5], &[4, 3], &[4, 5], &[3, 5], &[0, 4, 3], &[0, 4, 5], &[0, 3, 5], &[4, 3, 5], &[0, 4, 3, 5], ];
pub fn find_permutation(regs: &[u8]) -> Option<usize> {
PERMUTATION_TABLE.iter().position(|entry| entry == ®s)
}
pub fn get_registers(index: usize) -> Option<&'static [u8]> {
PERMUTATION_TABLE.get(index).copied()
}
}
pub fn encode_x86_ebp_frame(stack_size: u32, saved_regs: &[u8]) -> Result<u32, String> {
if stack_size % 4 != 0 || stack_size > 60 {
return Err(format!(
"EBP_FRAME stack size must be ≤60 and multiple of 4, got {stack_size}"
));
}
let stack_field = stack_size / 4;
let reg_count = saved_regs.len() as u32;
if reg_count > 4 {
return Err(format!("EBP_FRAME max 4 saved regs, got {reg_count}"));
}
let perm_idx = x86_reg_permutations::find_permutation(saved_regs)
.ok_or_else(|| format!("Unsupported register permutation: {saved_regs:?}"))?
as u32;
let encoding = (unwind_x86_mode::UNWIND_X86_MODE_EBP_FRAME << unwind_x86_encoding::MODE_SHIFT)
| (stack_field << unwind_x86_encoding::STACK_SIZE_SHIFT)
| (reg_count << unwind_x86_encoding::REG_COUNT_SHIFT)
| (perm_idx << unwind_x86_encoding::REG_PERMUTATION_SHIFT);
Ok(encoding)
}
pub fn encode_x86_stack_immd(stack_size: u32, saved_regs: &[u8]) -> Result<u32, String> {
if stack_size % 4 != 0 {
return Err(format!(
"STACK_IMMD stack size must be multiple of 4, got {stack_size}"
));
}
let scaled = stack_size / 4;
let stack_field = scaled.saturating_sub(16); if stack_field > 15 {
return Err(format!(
"STACK_IMMD stack size field overflow: {stack_size} (max 124)"
));
}
let reg_count = saved_regs.len() as u32;
if reg_count > 4 {
return Err(format!("STACK_IMMD max 4 saved regs, got {reg_count}"));
}
let perm_idx = x86_reg_permutations::find_permutation(saved_regs)
.ok_or_else(|| format!("Unsupported register permutation: {saved_regs:?}"))?
as u32;
let encoding = (unwind_x86_mode::UNWIND_X86_MODE_STACK_IMMD << unwind_x86_encoding::MODE_SHIFT)
| (stack_field << unwind_x86_encoding::STACK_SIZE_SHIFT)
| (reg_count << unwind_x86_encoding::REG_COUNT_SHIFT)
| (perm_idx << unwind_x86_encoding::REG_PERMUTATION_SHIFT);
Ok(encoding)
}
pub fn encode_x86_stack_ind(saved_regs: &[u8]) -> Result<u32, String> {
let reg_count = saved_regs.len() as u32;
if reg_count > 4 {
return Err(format!("STACK_IND max 4 saved regs, got {reg_count}"));
}
let perm_idx = x86_reg_permutations::find_permutation(saved_regs)
.ok_or_else(|| format!("Unsupported register permutation: {saved_regs:?}"))?
as u32;
let encoding = (unwind_x86_mode::UNWIND_X86_MODE_STACK_IND << unwind_x86_encoding::MODE_SHIFT)
| (reg_count << unwind_x86_encoding::REG_COUNT_SHIFT)
| (perm_idx << unwind_x86_encoding::REG_PERMUTATION_SHIFT);
Ok(encoding)
}
pub fn encode_x86_dwarf() -> u32 {
(unwind_x86_mode::UNWIND_X86_MODE_DWARF << unwind_x86_encoding::MODE_SHIFT)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FrameAnalysis {
pub stack_size: u32,
pub uses_frame_pointer: bool,
pub saved_regs: Vec<u8>,
pub has_calls: bool,
pub has_var_sized_objects: bool,
pub is_64bit: bool,
}
impl Default for FrameAnalysis {
fn default() -> Self {
Self {
stack_size: 0,
uses_frame_pointer: false,
saved_regs: Vec::new(),
has_calls: false,
has_var_sized_objects: false,
is_64bit: true,
}
}
}
impl FrameAnalysis {
pub fn select_encoding(&self) -> (u32, u32) {
if self.has_var_sized_objects {
if self.is_64bit {
return (
encode_x86_64_dwarf(),
unwind_x86_64_mode::UNWIND_X86_64_MODE_DWARF,
);
} else {
return (encode_x86_dwarf(), unwind_x86_mode::UNWIND_X86_MODE_DWARF);
}
}
if self.is_64bit {
self.select_encoding_64()
} else {
self.select_encoding_32()
}
}
fn select_encoding_64(&self) -> (u32, u32) {
if self.uses_frame_pointer && self.stack_size <= 128 && self.stack_size % 8 == 0 {
if let Ok(encoding) = encode_x86_64_rbp_frame(self.stack_size, &self.saved_regs) {
return (encoding, unwind_x86_64_mode::UNWIND_X86_64_MODE_RBP_FRAME);
}
}
let min_immd = 128u32;
if self.stack_size >= min_immd && self.stack_size % 8 == 0 {
let scaled = self.stack_size / 8;
if scaled <= 527 {
if let Ok(encoding) = encode_x86_64_stack_immd(self.stack_size, &self.saved_regs) {
return (encoding, unwind_x86_64_mode::UNWIND_X86_64_MODE_STACK_IMMD);
}
}
}
if self.stack_size > 0 {
if let Ok(encoding) = encode_x86_64_stack_ind(&self.saved_regs) {
return (encoding, unwind_x86_64_mode::UNWIND_X86_64_MODE_STACK_IND);
}
}
(
encode_x86_64_dwarf(),
unwind_x86_64_mode::UNWIND_X86_64_MODE_DWARF,
)
}
fn select_encoding_32(&self) -> (u32, u32) {
if self.uses_frame_pointer && self.stack_size <= 60 && self.stack_size % 4 == 0 {
if let Ok(encoding) = encode_x86_ebp_frame(self.stack_size, &self.saved_regs) {
return (encoding, unwind_x86_mode::UNWIND_X86_MODE_EBP_FRAME);
}
}
if self.stack_size >= 64 && self.stack_size <= 124 && self.stack_size % 4 == 0 {
if let Ok(encoding) = encode_x86_stack_immd(self.stack_size, &self.saved_regs) {
return (encoding, unwind_x86_mode::UNWIND_X86_MODE_STACK_IMMD);
}
}
if self.stack_size > 0 {
if let Ok(encoding) = encode_x86_stack_ind(&self.saved_regs) {
return (encoding, unwind_x86_mode::UNWIND_X86_MODE_STACK_IND);
}
}
(encode_x86_dwarf(), unwind_x86_mode::UNWIND_X86_MODE_DWARF)
}
}
pub mod dwarf_cfi {
pub const DW_CFA_nop: u8 = 0x00;
pub const DW_CFA_advance_loc: u8 = 0x40; pub const DW_CFA_offset: u8 = 0x80; pub const DW_CFA_restore: u8 = 0xC0; pub const DW_CFA_def_cfa: u8 = 0x0C;
pub const DW_CFA_def_cfa_offset: u8 = 0x0E;
pub const DW_CFA_def_cfa_register: u8 = 0x0D;
pub const DW_CFA_remember_state: u8 = 0x0A;
pub const DW_CFA_restore_state: u8 = 0x0B;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CfiInstruction {
DefCfa { register: u8, offset: u32 },
DefCfaOffset(u32),
DefCfaRegister(u8),
Offset { register: u8, offset: u32 },
Restore(u8),
RememberState,
RestoreState,
AdvanceLoc(u32),
Nop,
}
pub mod dwarf_regs_x86_64 {
pub const RAX: u8 = 0;
pub const RDX: u8 = 1;
pub const RCX: u8 = 2;
pub const RBX: u8 = 3;
pub const RSI: u8 = 4;
pub const RDI: u8 = 5;
pub const RBP: u8 = 6;
pub const RSP: u8 = 7;
pub const R8: u8 = 8;
pub const R9: u8 = 9;
pub const R10: u8 = 10;
pub const R11: u8 = 11;
pub const R12: u8 = 12;
pub const R13: u8 = 13;
pub const R14: u8 = 14;
pub const R15: u8 = 15;
pub const RIP: u8 = 16;
}
pub mod dwarf_regs_x86 {
pub const EAX: u8 = 0;
pub const ECX: u8 = 1;
pub const EDX: u8 = 2;
pub const EBX: u8 = 3;
pub const ESP: u8 = 4;
pub const EBP: u8 = 5;
pub const ESI: u8 = 6;
pub const EDI: u8 = 7;
pub const EIP: u8 = 8;
}
#[derive(Debug, Clone, Default)]
pub struct DwarfCfiProgram {
pub instructions: Vec<CfiInstruction>,
pub initial_location: u64,
pub address_range: u64,
pub cie_pointer: u32,
pub lsda: Option<u64>,
pub personality: Option<u64>,
}
impl DwarfCfiProgram {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, inst: CfiInstruction) {
self.instructions.push(inst);
}
pub fn build_rbp_frame(stack_size: u32, saved_regs: &[u8]) -> Self {
let mut program = Self::new();
program.add(CfiInstruction::DefCfaOffset(16));
program.add(CfiInstruction::Offset {
register: dwarf_regs_x86_64::RBP,
offset: 16,
});
program.add(CfiInstruction::DefCfaRegister(dwarf_regs_x86_64::RBP));
let mut current_offset = stack_size;
for (i, ®) in saved_regs.iter().enumerate() {
current_offset = current_offset.saturating_sub(8);
program.add(CfiInstruction::Offset {
register: reg,
offset: current_offset, });
}
program
}
pub fn encode_uleb128(value: u32) -> Vec<u8> {
let mut buf = Vec::new();
let mut v = value;
loop {
let mut byte = (v & 0x7F) as u8;
v >>= 7;
if v != 0 {
byte |= 0x80;
}
buf.push(byte);
if v == 0 {
break;
}
}
buf
}
pub fn encode_sleb128(value: i32) -> Vec<u8> {
let mut buf = Vec::new();
let mut v = value;
loop {
let mut byte = (v & 0x7F) as u8;
v >>= 7;
if (v == 0 && (byte & 0x40) == 0) || (v == -1 && (byte & 0x40) != 0) {
buf.push(byte);
break;
}
byte |= 0x80;
buf.push(byte);
}
buf
}
pub fn encode_instructions(&self) -> Vec<u8> {
let mut buf = Vec::new();
for inst in &self.instructions {
match inst {
CfiInstruction::DefCfa { register, offset } => {
buf.push(dwarf_cfi::DW_CFA_def_cfa);
buf.extend_from_slice(&Self::encode_uleb128(*register as u32));
buf.extend_from_slice(&Self::encode_uleb128(*offset));
}
CfiInstruction::DefCfaOffset(offset) => {
buf.push(dwarf_cfi::DW_CFA_def_cfa_offset);
buf.extend_from_slice(&Self::encode_uleb128(*offset));
}
CfiInstruction::DefCfaRegister(reg) => {
buf.push(dwarf_cfi::DW_CFA_def_cfa_register);
buf.extend_from_slice(&Self::encode_uleb128(*reg as u32));
}
CfiInstruction::Offset { register, offset } => {
buf.push(dwarf_cfi::DW_CFA_offset | (register & 0x3F));
buf.extend_from_slice(&Self::encode_uleb128(*offset));
}
CfiInstruction::Restore(reg) => {
buf.push(dwarf_cfi::DW_CFA_restore | (reg & 0x3F));
}
CfiInstruction::RememberState => {
buf.push(dwarf_cfi::DW_CFA_remember_state);
}
CfiInstruction::RestoreState => {
buf.push(dwarf_cfi::DW_CFA_restore_state);
}
CfiInstruction::AdvanceLoc(delta) => {
if *delta < 0x40 {
buf.push(dwarf_cfi::DW_CFA_advance_loc | (*delta as u8));
}
}
CfiInstruction::Nop => {
buf.push(dwarf_cfi::DW_CFA_nop);
}
}
}
buf
}
}
#[derive(Debug, Clone, Default)]
pub struct CompactUnwindSection {
pub entries: Vec<CompactUnwindEntry>,
by_start: HashMap<u32, usize>,
}
impl CompactUnwindSection {
pub fn new() -> Self {
Self {
entries: Vec::new(),
by_start: HashMap::new(),
}
}
pub fn add_entry(&mut self, entry: CompactUnwindEntry) -> usize {
let idx = self.entries.len();
self.by_start.insert(entry.function_start, idx);
self.entries.push(entry);
idx
}
pub fn find(&self, start: u32) -> Option<&CompactUnwindEntry> {
self.by_start.get(&start).map(|&idx| &self.entries[idx])
}
pub fn encode_to_bytes(&self) -> Vec<u8> {
let mut sorted: Vec<&CompactUnwindEntry> = self.entries.iter().collect();
sorted.sort_by_key(|e| e.function_start);
let mut buf = Vec::with_capacity(sorted.len() * CompactUnwindEntry::ENTRY_SIZE);
for entry in &sorted {
buf.extend_from_slice(&entry.encode_to_bytes());
}
buf
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
#[derive(Debug, Clone)]
pub struct X86CompactUnwind {
pub section: CompactUnwindSection,
pub dwarf_fallbacks: HashMap<u32, DwarfCfiProgram>,
pub personalities: HashMap<u32, PersonalityEncoding>,
pub lsdas: HashMap<u32, u32>,
pub is_64bit: bool,
}
impl Default for X86CompactUnwind {
fn default() -> Self {
Self {
section: CompactUnwindSection::new(),
dwarf_fallbacks: HashMap::new(),
personalities: HashMap::new(),
lsdas: HashMap::new(),
is_64bit: true,
}
}
}
impl X86CompactUnwind {
pub fn new_x86_64() -> Self {
Self {
is_64bit: true,
..Default::default()
}
}
pub fn new_x86_32() -> Self {
Self {
is_64bit: false,
..Default::default()
}
}
pub fn register_function(
&mut self,
start: u32,
length: u32,
analysis: &FrameAnalysis,
) -> (u32, u32) {
let (encoding, mode) = analysis.select_encoding();
let entry = CompactUnwindEntry::new(start, length, encoding, 0, 0);
self.section.add_entry(entry);
if self.is_64bit && mode == unwind_x86_64_mode::UNWIND_X86_64_MODE_DWARF
|| !self.is_64bit && mode == unwind_x86_mode::UNWIND_X86_MODE_DWARF
{
let cfi = DwarfCfiProgram::build_rbp_frame(analysis.stack_size, &analysis.saved_regs);
self.dwarf_fallbacks.insert(start, cfi);
}
(encoding, mode)
}
pub fn register_encoded(
&mut self,
start: u32,
length: u32,
encoding: u32,
personality: Option<PersonalityEncoding>,
lsda: Option<u32>,
) {
let personality_val = personality.map(|p| p.encode()).unwrap_or(0);
let lsda_val = lsda.unwrap_or(0);
let entry = CompactUnwindEntry::new(start, length, encoding, personality_val, lsda_val);
self.section.add_entry(entry);
if let Some(p) = personality {
self.personalities.insert(p.encode(), p);
}
if let Some(l) = lsda {
self.lsdas.insert(start, l);
}
}
pub fn register_personality(&mut self, enc: PersonalityEncoding) -> u32 {
let encoded = enc.encode();
self.personalities.insert(encoded, enc);
encoded
}
pub fn set_lsda(&mut self, function_start: u32, lsda: u32) {
self.lsdas.insert(function_start, lsda);
}
pub fn find_entry(&self, start: u32) -> Option<&CompactUnwindEntry> {
self.section.find(start)
}
pub fn get_dwarf_fallback(&self, start: u32) -> Option<&DwarfCfiProgram> {
self.dwarf_fallbacks.get(&start)
}
pub fn encode_section(&self) -> Vec<u8> {
self.section.encode_to_bytes()
}
pub fn entry_count(&self) -> usize {
self.section.len()
}
}
#[derive(Debug, Clone)]
pub struct PrologueDesc {
pub word_size: u8,
pub pushed_regs: Vec<u8>,
pub uses_fp: bool,
pub extra_stack: u32,
pub is_64bit: bool,
}
impl Default for PrologueDesc {
fn default() -> Self {
Self {
word_size: 8,
pushed_regs: Vec::new(),
uses_fp: false,
extra_stack: 0,
is_64bit: true,
}
}
}
impl PrologueDesc {
pub fn x86_64() -> Self {
Self {
word_size: 8,
is_64bit: true,
..Default::default()
}
}
pub fn x86_32() -> Self {
Self {
word_size: 4,
is_64bit: false,
..Default::default()
}
}
pub fn push(&mut self, reg: u8) {
self.pushed_regs.push(reg);
}
pub fn set_frame_pointer(&mut self, used: bool) {
self.uses_fp = used;
}
pub fn set_extra_stack(&mut self, size: u32) {
self.extra_stack = size;
}
pub fn total_stack(&self) -> u32 {
let push_size = self.pushed_regs.len() as u32 * self.word_size as u32;
push_size + self.extra_stack
}
pub fn to_analysis(&self) -> FrameAnalysis {
FrameAnalysis {
stack_size: self.total_stack(),
uses_frame_pointer: self.uses_fp,
saved_regs: self.pushed_regs.clone(),
has_calls: false,
has_var_sized_objects: false,
is_64bit: self.is_64bit,
}
}
pub fn derive_encoding(&self) -> (u32, u32) {
self.to_analysis().select_encoding()
}
}
#[derive(Debug, Clone)]
pub struct EncodingInfo {
pub raw: u32,
pub mode_name: String,
pub stack_size: Option<u32>,
pub reg_count: u32,
pub saved_regs: Vec<u8>,
pub perm_index: u32,
pub is_64bit: bool,
}
pub fn decode_x86_64_encoding(encoding: u32) -> EncodingInfo {
let mode = (encoding >> unwind_x86_64_encoding::MODE_SHIFT) & 0xF;
let stack_field = (encoding >> unwind_x86_64_encoding::STACK_SIZE_SHIFT) & 0xF;
let reg_count = (encoding >> unwind_x86_64_encoding::REG_COUNT_SHIFT) & 0xFF;
let perm_idx = (encoding >> unwind_x86_64_encoding::REG_PERMUTATION_SHIFT) & 0xF;
let (mode_name, stack_size) = match mode {
unwind_x86_64_mode::UNWIND_X86_64_MODE_RBP_FRAME => {
("RBP_FRAME".to_string(), Some(stack_field * 8))
}
unwind_x86_64_mode::UNWIND_X86_64_MODE_STACK_IMMD => {
let stack_low = (encoding >> 8) & 0xF;
let full = (stack_field << 4) | stack_low;
("STACK_IMMD".to_string(), Some((full + 16) * 8))
}
unwind_x86_64_mode::UNWIND_X86_64_MODE_STACK_IND => ("STACK_IND".to_string(), None),
unwind_x86_64_mode::UNWIND_X86_64_MODE_DWARF => ("DWARF".to_string(), None),
_ => ("UNKNOWN".to_string(), None),
};
let saved_regs = x86_64_reg_permutations::get_registers(perm_idx as usize)
.unwrap_or(&[])
.to_vec();
EncodingInfo {
raw: encoding,
mode_name,
stack_size,
reg_count,
saved_regs,
perm_index: perm_idx,
is_64bit: true,
}
}
pub fn decode_x86_encoding(encoding: u32) -> EncodingInfo {
let mode = (encoding >> unwind_x86_encoding::MODE_SHIFT) & 0xF;
let stack_field = (encoding >> unwind_x86_encoding::STACK_SIZE_SHIFT) & 0xF;
let reg_count = (encoding >> unwind_x86_encoding::REG_COUNT_SHIFT) & 0xFF;
let perm_idx = (encoding >> unwind_x86_encoding::REG_PERMUTATION_SHIFT) & 0xF;
let (mode_name, stack_size) = match mode {
unwind_x86_mode::UNWIND_X86_MODE_EBP_FRAME => {
("EBP_FRAME".to_string(), Some(stack_field * 4))
}
unwind_x86_mode::UNWIND_X86_MODE_STACK_IMMD => {
("STACK_IMMD".to_string(), Some((stack_field + 16) * 4))
}
unwind_x86_mode::UNWIND_X86_MODE_STACK_IND => ("STACK_IND".to_string(), None),
unwind_x86_mode::UNWIND_X86_MODE_DWARF => ("DWARF".to_string(), None),
_ => ("UNKNOWN".to_string(), None),
};
let saved_regs = x86_reg_permutations::get_registers(perm_idx as usize)
.unwrap_or(&[])
.to_vec();
EncodingInfo {
raw: encoding,
mode_name,
stack_size,
reg_count,
saved_regs,
perm_index: perm_idx,
is_64bit: false,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompactUnwindRelocType {
X86_64RelocUnsigned,
X86_64RelocBranch,
X86_64RelocSubtractor,
}
#[derive(Debug, Clone)]
pub struct CompactUnwindReloc {
pub section_offset: u32,
pub reloc_type: CompactUnwindRelocType,
pub symbol_index: u32,
pub addend: i64,
pub is_pcrel: bool,
pub is_extern: bool,
}
impl CompactUnwindReloc {
pub fn new(
offset: u32,
reloc_type: CompactUnwindRelocType,
symbol_index: u32,
addend: i64,
) -> Self {
Self {
section_offset: offset,
reloc_type,
symbol_index,
addend,
is_pcrel: false,
is_extern: true,
}
}
pub fn pcrel(offset: u32, symbol_index: u32, addend: i64) -> Self {
Self {
section_offset: offset,
reloc_type: CompactUnwindRelocType::X86_64RelocBranch,
symbol_index,
addend,
is_pcrel: true,
is_extern: true,
}
}
pub fn encode_macho(&self) -> [u8; 16] {
let mut buf = [0u8; 16];
buf[0..4].copy_from_slice(&self.section_offset.to_le_bytes());
let mut info: u32 = (self.symbol_index & 0x00FF_FFFF);
if self.is_pcrel {
info |= 0x0100_0000;
}
if self.is_extern {
info |= 0x0200_0000;
}
info |= (self.reloc_type as u32) << 28;
buf[4..8].copy_from_slice(&info.to_le_bytes());
buf[8..16].copy_from_slice(&self.addend.to_le_bytes());
buf
}
}
#[derive(Debug, Clone)]
pub struct CompactUnwindSectionHeader {
pub section_name: String,
pub segment_name: String,
pub address: u64,
pub size: u64,
pub offset: u32,
pub alignment: u32,
pub reloc_offset: u32,
pub num_relocs: u32,
pub flags: u32,
pub reserved1: u32,
pub reserved2: u32,
pub reserved3: u32,
}
impl Default for CompactUnwindSectionHeader {
fn default() -> Self {
Self {
section_name: "__compact_unwind".to_string(),
segment_name: "__LD".to_string(),
address: 0,
size: 0,
offset: 0,
alignment: 3, reloc_offset: 0,
num_relocs: 0,
flags: 0,
reserved1: 0,
reserved2: 0,
reserved3: 0,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct CompactUnwindArchive {
pub sections: HashMap<String, CompactUnwindSection>,
pub common_personalities: Vec<PersonalityEncoding>,
pub common_lsdas: HashMap<u64, Vec<u8>>,
}
impl CompactUnwindArchive {
pub fn new() -> Self {
Self::default()
}
pub fn add_section(&mut self, arch: &str, section: CompactUnwindSection) {
self.sections.insert(arch.to_string(), section);
}
pub fn get_section(&self, arch: &str) -> Option<&CompactUnwindSection> {
self.sections.get(arch)
}
pub fn total_entries(&self) -> usize {
self.sections.values().map(|s| s.len()).sum()
}
pub fn encode_archive(&self) -> HashMap<String, Vec<u8>> {
self.sections
.iter()
.map(|(arch, section)| (arch.clone(), section.encode_to_bytes()))
.collect()
}
}
#[derive(Debug, Clone)]
pub struct ValidationResult {
pub valid: bool,
pub errors: Vec<String>,
pub warnings: Vec<String>,
}
impl ValidationResult {
pub fn ok() -> Self {
Self {
valid: true,
errors: Vec::new(),
warnings: Vec::new(),
}
}
pub fn error(&mut self, msg: String) {
self.valid = false;
self.errors.push(msg);
}
pub fn warn(&mut self, msg: String) {
self.warnings.push(msg);
}
}
pub fn validate_x86_64_entry(entry: &CompactUnwindEntry) -> ValidationResult {
let mut result = ValidationResult::ok();
let mode = entry.mode_64();
match mode {
unwind_x86_64_mode::UNWIND_X86_64_MODE_RBP_FRAME => {
let stack = entry.stack_size_64();
if stack > 128 {
result.error(format!("RBP_FRAME stack size {stack} exceeds 128 bytes"));
}
if stack % 8 != 0 {
result.warn(format!("RBP_FRAME stack size {stack} not multiple of 8"));
}
let reg_count = entry.reg_count_64();
if reg_count > 6 {
result.error(format!("RBP_FRAME reg count {reg_count} exceeds 6"));
}
}
unwind_x86_64_mode::UNWIND_X86_64_MODE_STACK_IMMD => {
let stack = entry.stack_size_64();
if stack < 128 {
result.warn(format!(
"STACK_IMMD stack size {stack} below 128; consider RBP_FRAME"
));
}
if stack % 8 != 0 {
result.error(format!("STACK_IMMD stack size {stack} not multiple of 8"));
}
}
unwind_x86_64_mode::UNWIND_X86_64_MODE_STACK_IND => {
}
unwind_x86_64_mode::UNWIND_X86_64_MODE_DWARF => {
}
_ => {
result.error(format!("Unknown unwind mode: {mode}"));
}
}
if entry.personality != 0 {
match PersonalityEncoding::decode(entry.personality) {
PersonalityEncoding::None => {
result.warn("Personality field set but decodes to None".to_string());
}
_ => {}
}
}
result
}
pub fn validate_x86_entry(entry: &CompactUnwindEntry) -> ValidationResult {
let mut result = ValidationResult::ok();
let mode = entry.mode_32();
match mode {
unwind_x86_mode::UNWIND_X86_MODE_EBP_FRAME => {
let stack_size = (entry.encoding >> unwind_x86_encoding::STACK_SIZE_SHIFT) & 0xF;
if stack_size > 15 {
result.error(format!("EBP_FRAME stack field {stack_size} exceeds 15"));
}
}
unwind_x86_mode::UNWIND_X86_MODE_STACK_IMMD => {
let stack_field = (entry.encoding >> unwind_x86_encoding::STACK_SIZE_SHIFT) & 0xF;
if stack_field > 15 {
result.error(format!("STACK_IMMD stack field {stack_field} exceeds 15"));
}
}
unwind_x86_mode::UNWIND_X86_MODE_STACK_IND => {}
unwind_x86_mode::UNWIND_X86_MODE_DWARF => {}
_ => {
result.error(format!("Unknown unwind mode: {mode}"));
}
}
result
}
#[derive(Debug, Clone, Default)]
pub struct LsdaBuilder {
pub landing_pad_base: u32,
pub type_table: Vec<u32>,
pub call_sites: Vec<LsdaCallSite>,
pub actions: Vec<LsdaAction>,
pub type_encoding: u8,
pub call_site_encoding: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LsdaCallSite {
pub start_offset: u32,
pub length: u32,
pub landing_pad: u32,
pub action_index: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LsdaAction {
pub type_filter: i32,
pub next_offset: i32,
}
impl LsdaBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn add_type(&mut self, type_rva: u32) -> usize {
let idx = self.type_table.len();
self.type_table.push(type_rva);
idx
}
pub fn add_call_site(&mut self, start: u32, length: u32, landing_pad: u32, action_index: u32) {
self.call_sites.push(LsdaCallSite {
start_offset: start,
length,
landing_pad,
action_index,
});
}
pub fn add_action(&mut self, type_filter: i32, next_offset: i32) {
self.actions.push(LsdaAction {
type_filter,
next_offset,
});
}
pub fn encode(&self) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(&DwarfCfiProgram::encode_uleb128(self.landing_pad_base));
buf.push(self.type_encoding);
buf.extend_from_slice(&DwarfCfiProgram::encode_uleb128(
self.type_table.len() as u32
));
for &type_rva in &self.type_table {
buf.extend_from_slice(&type_rva.to_le_bytes());
}
buf.push(self.call_site_encoding);
buf.extend_from_slice(&DwarfCfiProgram::encode_uleb128(
self.call_sites.len() as u32
));
for site in &self.call_sites {
buf.extend_from_slice(&DwarfCfiProgram::encode_uleb128(site.start_offset));
buf.extend_from_slice(&DwarfCfiProgram::encode_uleb128(site.length));
buf.extend_from_slice(&DwarfCfiProgram::encode_uleb128(site.landing_pad));
buf.extend_from_slice(&DwarfCfiProgram::encode_uleb128(site.action_index));
}
for action in &self.actions {
buf.extend_from_slice(&DwarfCfiProgram::encode_sleb128(action.type_filter));
buf.extend_from_slice(&DwarfCfiProgram::encode_sleb128(action.next_offset));
}
buf
}
}
#[derive(Debug, Clone, Default)]
pub struct FrameAnalysisCache {
entries: HashMap<u64, FrameAnalysis>,
pub hits: u64,
pub misses: u64,
}
impl FrameAnalysisCache {
pub fn new() -> Self {
Self::default()
}
fn hash_frame(
stack_size: u32,
uses_fp: bool,
saved_regs: &[u8],
has_vla: bool,
is_64bit: bool,
) -> u64 {
let mut h: u64 = stack_size as u64;
h ^= (uses_fp as u64) << 32;
h ^= (has_vla as u64) << 33;
h ^= (is_64bit as u64) << 34;
for (i, &r) in saved_regs.iter().enumerate() {
h ^= (r as u64) << (40 + i * 8);
}
h
}
pub fn get_or_insert(
&mut self,
stack_size: u32,
uses_fp: bool,
saved_regs: &[u8],
has_vla: bool,
is_64bit: bool,
) -> &FrameAnalysis {
let hash = Self::hash_frame(stack_size, uses_fp, saved_regs, has_vla, is_64bit);
if self.entries.contains_key(&hash) {
self.hits += 1;
} else {
self.misses += 1;
let analysis = FrameAnalysis {
stack_size,
uses_frame_pointer: uses_fp,
saved_regs: saved_regs.to_vec(),
has_calls: false,
has_var_sized_objects: has_vla,
is_64bit,
};
self.entries.insert(hash, analysis);
}
self.entries.get(&hash).unwrap()
}
pub fn stats(&self) -> (u64, u64) {
(self.hits, self.misses)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_personality_none() {
let enc = PersonalityEncoding::None;
assert_eq!(enc.encode(), 0);
let decoded = PersonalityEncoding::decode(0);
assert_eq!(decoded, PersonalityEncoding::None);
}
#[test]
fn test_personality_direct() {
let enc = PersonalityEncoding::Direct(0x12345678);
let raw = enc.encode();
assert_eq!(raw & 0xC000_0000, 0);
assert_eq!(raw & 0x3FFF_FFFF, 0x1234_5678);
let decoded = PersonalityEncoding::decode(raw);
match decoded {
PersonalityEncoding::Direct(v) => assert_eq!(v, 0x1234_5678),
_ => panic!("Expected Direct"),
}
}
#[test]
fn test_personality_indirect() {
let enc = PersonalityEncoding::Indirect(0x1000);
let raw = enc.encode();
assert_eq!((raw >> 30) & 3, 1);
let decoded = PersonalityEncoding::decode(raw);
match decoded {
PersonalityEncoding::Indirect(v) => assert_eq!(v, 0x1000),
_ => panic!("Expected Indirect"),
}
}
#[test]
fn test_personality_gnu() {
let enc = PersonalityEncoding::Gnu(0x2000);
let raw = enc.encode();
assert_eq!((raw >> 30) & 3, 2);
let decoded = PersonalityEncoding::decode(raw);
match decoded {
PersonalityEncoding::Gnu(v) => assert_eq!(v, 0x2000),
_ => panic!("Expected Gnu"),
}
}
#[test]
fn test_entry_encode_decode() {
let entry = CompactUnwindEntry::new(0x1000, 0x200, 0x01020001, 0, 0x5000);
let bytes = entry.encode_to_bytes();
let decoded = CompactUnwindEntry::decode_from_bytes(&bytes);
assert_eq!(decoded.function_start, 0x1000);
assert_eq!(decoded.function_length, 0x200);
assert_eq!(decoded.encoding, 0x01020001);
assert_eq!(decoded.lsda, 0x5000);
}
#[test]
fn test_entry_simple() {
let entry = CompactUnwindEntry::simple(0x1000, 0x100, 0x02000000);
assert_eq!(entry.personality, 0);
assert_eq!(entry.lsda, 0);
}
#[test]
fn test_entry_mode_64() {
let encoding = encode_x86_64_rbp_frame(16, &[]).unwrap();
let entry = CompactUnwindEntry::simple(0, 0, encoding);
assert_eq!(
entry.mode_64(),
unwind_x86_64_mode::UNWIND_X86_64_MODE_RBP_FRAME
);
}
#[test]
fn test_entry_stack_size_64() {
let encoding = encode_x86_64_rbp_frame(48, &[]).unwrap();
let entry = CompactUnwindEntry::simple(0, 0, encoding);
assert_eq!(entry.stack_size_64(), 48);
}
#[test]
fn test_entry_display() {
let entry = CompactUnwindEntry::new(0x1000, 0x50, 0, 0, 0);
let s = format!("{entry}");
assert!(s.contains("compact_unwind_entry"));
assert!(s.contains("0x1000"));
}
#[test]
fn test_encode_rbp_frame_no_regs() {
let encoding = encode_x86_64_rbp_frame(32, &[]).unwrap();
let info = decode_x86_64_encoding(encoding);
assert_eq!(info.mode_name, "RBP_FRAME");
assert_eq!(info.stack_size, Some(32));
assert_eq!(info.reg_count, 0);
}
#[test]
fn test_encode_rbp_frame_with_regs() {
let encoding = encode_x86_64_rbp_frame(64, &[0, 1]).unwrap();
let info = decode_x86_64_encoding(encoding);
assert_eq!(info.mode_name, "RBP_FRAME");
assert_eq!(info.stack_size, Some(64));
assert_eq!(info.reg_count, 2);
assert_eq!(info.saved_regs, vec![0, 1]);
}
#[test]
fn test_encode_rbp_frame_invalid_stack() {
assert!(encode_x86_64_rbp_frame(200, &[]).is_err());
assert!(encode_x86_64_rbp_frame(13, &[]).is_err()); }
#[test]
fn test_encode_stack_immd() {
let encoding = encode_x86_64_stack_immd(256, &[0, 5]).unwrap();
let info = decode_x86_64_encoding(encoding);
assert_eq!(info.mode_name, "STACK_IMMD");
assert_eq!(info.stack_size, Some(256));
}
#[test]
fn test_encode_stack_immd_min_size() {
let encoding = encode_x86_64_stack_immd(128, &[]).unwrap();
assert_ne!(encoding, 0);
}
#[test]
fn test_encode_stack_immd_invalid() {
assert!(encode_x86_64_stack_immd(64, &[]).is_err()); assert!(encode_x86_64_stack_immd(13, &[]).is_err()); }
#[test]
fn test_encode_stack_ind() {
let encoding = encode_x86_64_stack_ind(&[0, 1, 2]).unwrap();
let info = decode_x86_64_encoding(encoding);
assert_eq!(info.mode_name, "STACK_IND");
assert_eq!(info.stack_size, None);
}
#[test]
fn test_encode_dwarf() {
let encoding = encode_x86_64_dwarf();
let info = decode_x86_64_encoding(encoding);
assert_eq!(info.mode_name, "DWARF");
}
#[test]
fn test_encode_ebp_frame() {
let encoding = encode_x86_ebp_frame(20, &[0, 4]).unwrap();
let info = decode_x86_encoding(encoding);
assert_eq!(info.mode_name, "EBP_FRAME");
assert_eq!(info.stack_size, Some(20));
}
#[test]
fn test_encode_ebp_frame_invalid() {
assert!(encode_x86_ebp_frame(100, &[]).is_err());
assert!(encode_x86_ebp_frame(3, &[]).is_err()); }
#[test]
fn test_encode_x86_stack_immd() {
let encoding = encode_x86_stack_immd(80, &[0, 4]).unwrap();
let info = decode_x86_encoding(encoding);
assert_eq!(info.mode_name, "STACK_IMMD");
assert_eq!(info.stack_size, Some(80));
}
#[test]
fn test_encode_x86_stack_ind() {
let encoding = encode_x86_stack_ind(&[0, 4, 3]).unwrap();
let info = decode_x86_encoding(encoding);
assert_eq!(info.mode_name, "STACK_IND");
}
#[test]
fn test_encode_x86_dwarf() {
let encoding = encode_x86_dwarf();
let info = decode_x86_encoding(encoding);
assert_eq!(info.mode_name, "DWARF");
}
#[test]
fn test_find_permutation_64() {
assert_eq!(x86_64_reg_permutations::find_permutation(&[]), Some(0));
assert_eq!(x86_64_reg_permutations::find_permutation(&[0]), Some(1));
assert_eq!(x86_64_reg_permutations::find_permutation(&[5]), Some(6));
assert_eq!(
x86_64_reg_permutations::find_permutation(&[0, 1, 2]),
Some(22)
);
}
#[test]
fn test_find_permutation_64_not_found() {
assert!(x86_64_reg_permutations::find_permutation(&[0, 1, 2, 3]).is_none());
}
#[test]
fn test_find_permutation_32() {
assert_eq!(x86_reg_permutations::find_permutation(&[]), Some(0));
assert_eq!(x86_reg_permutations::find_permutation(&[0]), Some(1));
assert_eq!(
x86_reg_permutations::find_permutation(&[0, 4, 3, 5]),
Some(15)
);
}
#[test]
fn test_frame_analysis_select_rbp_frame() {
let analysis = FrameAnalysis {
stack_size: 32,
uses_frame_pointer: true,
saved_regs: vec![0], has_calls: false,
has_var_sized_objects: false,
is_64bit: true,
};
let (_enc, mode) = analysis.select_encoding();
assert_eq!(mode, unwind_x86_64_mode::UNWIND_X86_64_MODE_RBP_FRAME);
}
#[test]
fn test_frame_analysis_select_stack_immd() {
let analysis = FrameAnalysis {
stack_size: 256,
uses_frame_pointer: false,
saved_regs: vec![],
has_calls: false,
has_var_sized_objects: false,
is_64bit: true,
};
let (_enc, mode) = analysis.select_encoding();
assert_eq!(mode, unwind_x86_64_mode::UNWIND_X86_64_MODE_STACK_IMMD);
}
#[test]
fn test_frame_analysis_select_dwarf_for_vla() {
let analysis = FrameAnalysis {
stack_size: 32,
uses_frame_pointer: true,
saved_regs: vec![],
has_calls: false,
has_var_sized_objects: true, is_64bit: true,
};
let (_enc, mode) = analysis.select_encoding();
assert_eq!(mode, unwind_x86_64_mode::UNWIND_X86_64_MODE_DWARF);
}
#[test]
fn test_frame_analysis_select_32_ebp() {
let analysis = FrameAnalysis {
stack_size: 24,
uses_frame_pointer: true,
saved_regs: vec![0],
has_calls: false,
has_var_sized_objects: false,
is_64bit: false,
};
let (_enc, mode) = analysis.select_encoding();
assert_eq!(mode, unwind_x86_mode::UNWIND_X86_MODE_EBP_FRAME);
}
#[test]
fn test_frame_analysis_select_32_dwarf_for_vla() {
let analysis = FrameAnalysis {
stack_size: 20,
uses_frame_pointer: true,
saved_regs: vec![],
has_calls: false,
has_var_sized_objects: true,
is_64bit: false,
};
let (_enc, mode) = analysis.select_encoding();
assert_eq!(mode, unwind_x86_mode::UNWIND_X86_MODE_DWARF);
}
#[test]
fn test_section_add_and_find() {
let mut section = CompactUnwindSection::new();
let entry = CompactUnwindEntry::simple(0x1000, 0x100, 0);
section.add_entry(entry);
let found = section.find(0x1000).unwrap();
assert_eq!(found.function_start, 0x1000);
assert!(section.find(0x2000).is_none());
}
#[test]
fn test_section_encode_sorted() {
let mut section = CompactUnwindSection::new();
section.add_entry(CompactUnwindEntry::simple(0x3000, 0x100, 0));
section.add_entry(CompactUnwindEntry::simple(0x1000, 0x100, 0));
section.add_entry(CompactUnwindEntry::simple(0x2000, 0x100, 0));
let bytes = section.encode_to_bytes();
assert_eq!(bytes.len(), 60);
let first_start = u32::from_le_bytes(bytes[0..4].try_into().unwrap());
assert_eq!(first_start, 0x1000);
}
#[test]
fn test_cfi_build_rbp_frame() {
let cfi = DwarfCfiProgram::build_rbp_frame(32, &[3]); assert!(!cfi.instructions.is_empty());
assert_eq!(cfi.instructions[0], CfiInstruction::DefCfaOffset(16));
}
#[test]
fn test_cfi_encode_uleb128() {
let encoded = DwarfCfiProgram::encode_uleb128(0);
assert_eq!(encoded, vec![0]);
let encoded = DwarfCfiProgram::encode_uleb128(127);
assert_eq!(encoded, vec![127]);
let encoded = DwarfCfiProgram::encode_uleb128(128);
assert_eq!(encoded, vec![0x80, 0x01]);
let encoded = DwarfCfiProgram::encode_uleb128(300);
assert_eq!(encoded, vec![0xAC, 0x02]);
}
#[test]
fn test_cfi_encode_sleb128() {
let encoded = DwarfCfiProgram::encode_sleb128(0);
assert_eq!(encoded, vec![0]);
let encoded = DwarfCfiProgram::encode_sleb128(-1);
assert_eq!(encoded, vec![0x7F]);
let encoded = DwarfCfiProgram::encode_sleb128(64);
assert_eq!(encoded, vec![0xC0, 0x00]);
let encoded = DwarfCfiProgram::encode_sleb128(-64);
assert_eq!(encoded, vec![0x40]);
}
#[test]
fn test_cfi_encode_instructions() {
let mut cfi = DwarfCfiProgram::new();
cfi.add(CfiInstruction::DefCfa {
register: 7,
offset: 8,
});
cfi.add(CfiInstruction::Offset {
register: 16,
offset: 8,
});
let bytes = cfi.encode_instructions();
assert!(!bytes.is_empty());
assert_eq!(bytes[0], dwarf_cfi::DW_CFA_def_cfa);
}
#[test]
fn test_compact_unwind_register_64_rbp() {
let mut cu = X86CompactUnwind::new_x86_64();
let analysis = FrameAnalysis {
stack_size: 32,
uses_frame_pointer: true,
saved_regs: vec![0],
has_calls: false,
has_var_sized_objects: false,
is_64bit: true,
};
let (_enc, mode) = cu.register_function(0x1000, 0x200, &analysis);
assert_eq!(mode, unwind_x86_64_mode::UNWIND_X86_64_MODE_RBP_FRAME);
assert_eq!(cu.entry_count(), 1);
}
#[test]
fn test_compact_unwind_register_64_dwarf_fallback() {
let mut cu = X86CompactUnwind::new_x86_64();
let analysis = FrameAnalysis {
stack_size: 32,
uses_frame_pointer: true,
saved_regs: vec![],
has_calls: false,
has_var_sized_objects: true,
is_64bit: true,
};
let (_enc, mode) = cu.register_function(0x1000, 0x200, &analysis);
assert_eq!(mode, unwind_x86_64_mode::UNWIND_X86_64_MODE_DWARF);
assert!(cu.dwarf_fallbacks.contains_key(&0x1000));
}
#[test]
fn test_compact_unwind_register_32() {
let mut cu = X86CompactUnwind::new_x86_32();
let analysis = FrameAnalysis {
stack_size: 24,
uses_frame_pointer: true,
saved_regs: vec![0],
has_calls: false,
has_var_sized_objects: false,
is_64bit: false,
};
let (_enc, mode) = cu.register_function(0x1000, 0x200, &analysis);
assert_eq!(mode, unwind_x86_mode::UNWIND_X86_MODE_EBP_FRAME);
assert_eq!(cu.entry_count(), 1);
}
#[test]
fn test_compact_unwind_register_encoded() {
let mut cu = X86CompactUnwind::new_x86_64();
let encoding = encode_x86_64_rbp_frame(48, &[0, 5]).unwrap();
cu.register_encoded(
0x1000,
0x200,
encoding,
Some(PersonalityEncoding::Direct(0x4000)),
Some(0x5000),
);
let entry = cu.find_entry(0x1000).unwrap();
assert_eq!(
entry.personality,
PersonalityEncoding::Direct(0x4000).encode()
);
assert_eq!(entry.lsda, 0x5000);
}
#[test]
fn test_compact_unwind_encode_section_empty() {
let cu = X86CompactUnwind::new_x86_64();
let bytes = cu.encode_section();
assert!(bytes.is_empty());
}
#[test]
fn test_prologue_desc_total_stack() {
let mut desc = PrologueDesc::x86_64();
desc.push(0); desc.push(5); desc.set_extra_stack(64);
assert_eq!(desc.total_stack(), 80); }
#[test]
fn test_prologue_desc_derive_encoding() {
let mut desc = PrologueDesc::x86_64();
desc.push(5); desc.set_frame_pointer(true);
desc.set_extra_stack(16);
let (_enc, mode) = desc.derive_encoding();
assert_eq!(mode, unwind_x86_64_mode::UNWIND_X86_64_MODE_RBP_FRAME);
}
#[test]
fn test_prologue_desc_32_total_stack() {
let mut desc = PrologueDesc::x86_32();
desc.push(0); desc.push(4); desc.set_extra_stack(20);
assert_eq!(desc.total_stack(), 28); }
#[test]
fn test_decode_x86_64_encoding() {
let encoding = encode_x86_64_rbp_frame(32, &[0, 1]).unwrap();
let info = decode_x86_64_encoding(encoding);
assert!(info.is_64bit);
assert_eq!(info.mode_name, "RBP_FRAME");
}
#[test]
fn test_decode_x86_encoding() {
let encoding = encode_x86_ebp_frame(20, &[0]).unwrap();
let info = decode_x86_encoding(encoding);
assert!(!info.is_64bit);
assert_eq!(info.mode_name, "EBP_FRAME");
}
#[test]
fn test_rbp_frame_max_stack() {
assert!(encode_x86_64_rbp_frame(128, &[]).is_ok());
}
#[test]
fn test_ebp_frame_max_stack() {
assert!(encode_x86_ebp_frame(60, &[]).is_ok());
}
#[test]
fn test_stack_immd_max_stack() {
assert!(encode_x86_64_stack_immd(4216, &[]).is_ok());
}
#[test]
fn test_stack_immd_too_large() {
assert!(encode_x86_64_stack_immd(5000, &[]).is_err());
}
#[test]
fn test_roundtrip_rbp_frame() {
let encoding = encode_x86_64_rbp_frame(48, &[0, 5]).unwrap();
let info = decode_x86_64_encoding(encoding);
assert_eq!(info.stack_size, Some(48));
assert_eq!(info.reg_count, 2);
assert_eq!(info.saved_regs, vec![0, 5]);
}
#[test]
fn test_roundtrip_ebp_frame() {
let encoding = encode_x86_ebp_frame(20, &[0, 4]).unwrap();
let info = decode_x86_encoding(encoding);
assert_eq!(info.stack_size, Some(20));
assert_eq!(info.saved_regs, vec![0, 4]);
}
#[test]
fn test_roundtrip_stack_immd_64() {
let encoding = encode_x86_64_stack_immd(256, &[1, 2]).unwrap();
let info = decode_x86_64_encoding(encoding);
assert_eq!(info.stack_size, Some(256));
}
#[test]
fn test_zero_regs_permutation() {
let encoding = encode_x86_64_rbp_frame(8, &[]).unwrap();
let info = decode_x86_64_encoding(encoding);
assert_eq!(info.perm_index, 0);
assert!(info.saved_regs.is_empty());
}
#[test]
fn test_max_regs_rbp_frame() {
let encoding = encode_x86_64_rbp_frame(128, &[0, 1, 2, 3, 4, 5]).unwrap();
let info = decode_x86_64_encoding(encoding);
assert_eq!(info.reg_count, 6);
}
#[test]
fn test_too_many_regs() {
assert!(encode_x86_64_rbp_frame(128, &[0, 1, 2, 3, 4, 5, 0]).is_err());
}
#[test]
fn test_lsda_association() {
let mut cu = X86CompactUnwind::new_x86_64();
cu.set_lsda(0x1000, 0x6000);
assert_eq!(cu.lsdas.get(&0x1000), Some(&0x6000));
}
#[test]
fn test_register_personality() {
let mut cu = X86CompactUnwind::new_x86_64();
let encoded = cu.register_personality(PersonalityEncoding::Indirect(0x3000));
assert_ne!(encoded, 0);
assert!(cu.personalities.contains_key(&encoded));
}
#[test]
fn test_reloc_new() {
let reloc =
CompactUnwindReloc::new(0x100, CompactUnwindRelocType::X86_64RelocUnsigned, 5, 0x10);
assert_eq!(reloc.section_offset, 0x100);
assert_eq!(reloc.symbol_index, 5);
assert_eq!(reloc.addend, 0x10);
assert!(!reloc.is_pcrel);
}
#[test]
fn test_reloc_pcrel() {
let reloc = CompactUnwindReloc::pcrel(0x200, 3, -4);
assert!(reloc.is_pcrel);
assert_eq!(reloc.addend, -4);
}
#[test]
fn test_reloc_encode_macho() {
let reloc =
CompactUnwindReloc::new(0x50, CompactUnwindRelocType::X86_64RelocUnsigned, 7, 0);
let bytes = reloc.encode_macho();
assert_eq!(bytes.len(), 16);
let addr = u32::from_le_bytes(bytes[0..4].try_into().unwrap());
assert_eq!(addr, 0x50);
}
#[test]
fn test_archive_add_section() {
let mut archive = CompactUnwindArchive::new();
let section = CompactUnwindSection::new();
archive.add_section("x86_64", section);
assert!(archive.get_section("x86_64").is_some());
assert!(archive.get_section("arm64").is_none());
}
#[test]
fn test_archive_total_entries() {
let mut archive = CompactUnwindArchive::new();
let mut sec1 = CompactUnwindSection::new();
sec1.add_entry(CompactUnwindEntry::simple(0x1000, 0x100, 0));
let mut sec2 = CompactUnwindSection::new();
sec2.add_entry(CompactUnwindEntry::simple(0x2000, 0x100, 0));
sec2.add_entry(CompactUnwindEntry::simple(0x3000, 0x100, 0));
archive.add_section("x86_64", sec1);
archive.add_section("i386", sec2);
assert_eq!(archive.total_entries(), 3);
}
#[test]
fn test_archive_encode() {
let mut archive = CompactUnwindArchive::new();
let section = CompactUnwindSection::new();
archive.add_section("x86_64", section);
let encoded = archive.encode_archive();
assert!(encoded.contains_key("x86_64"));
}
#[test]
fn test_validate_x86_64_rbp_frame_ok() {
let encoding = encode_x86_64_rbp_frame(48, &[0]).unwrap();
let entry = CompactUnwindEntry::simple(0, 0, encoding);
let result = validate_x86_64_entry(&entry);
assert!(result.valid);
assert!(result.errors.is_empty());
}
#[test]
fn test_validate_x86_64_rbp_frame_stack_too_large() {
let mut entry = CompactUnwindEntry::simple(0, 0, 0);
entry.encoding = (unwind_x86_64_mode::UNWIND_X86_64_MODE_RBP_FRAME
<< unwind_x86_64_encoding::MODE_SHIFT)
| (15 << unwind_x86_64_encoding::STACK_SIZE_SHIFT);
let result = validate_x86_64_entry(&entry);
assert!(result.valid);
}
#[test]
fn test_validate_x86_64_unknown_mode() {
let mut entry = CompactUnwindEntry::simple(0, 0, 0);
entry.encoding = 0xF << unwind_x86_64_encoding::MODE_SHIFT;
let result = validate_x86_64_entry(&entry);
assert!(!result.valid);
assert!(!result.errors.is_empty());
}
#[test]
fn test_validate_x86_ebpf_frame_ok() {
let encoding = encode_x86_ebp_frame(20, &[0]).unwrap();
let entry = CompactUnwindEntry::simple(0, 0, encoding);
let result = validate_x86_entry(&entry);
assert!(result.valid);
}
#[test]
fn test_lsda_builder_empty() {
let builder = LsdaBuilder::new();
let bytes = builder.encode();
assert!(!bytes.is_empty());
}
#[test]
fn test_lsda_builder_with_types() {
let mut builder = LsdaBuilder::new();
builder.add_type(0x4000);
builder.add_type(0x5000);
let bytes = builder.encode();
assert!(!bytes.is_empty());
}
#[test]
fn test_lsda_builder_with_call_sites() {
let mut builder = LsdaBuilder::new();
builder.add_call_site(0x10, 0x20, 0x100, 1);
builder.add_call_site(0x30, 0x40, 0x200, 2);
let bytes = builder.encode();
assert!(!bytes.is_empty());
}
#[test]
fn test_lsda_builder_full() {
let mut builder = LsdaBuilder::new();
builder.landing_pad_base = 0x1000;
builder.type_encoding = 0x9B; builder.call_site_encoding = 0x01; builder.add_type(0x4000);
builder.add_call_site(0x10, 0x30, 0x200, 1);
builder.add_action(1, 0);
let bytes = builder.encode();
assert!(!bytes.is_empty());
}
#[test]
fn test_cache_miss_then_hit() {
let mut cache = FrameAnalysisCache::new();
assert_eq!(cache.stats(), (0, 0));
cache.get_or_insert(32, true, &[0], false, true);
assert_eq!(cache.stats(), (0, 1));
cache.get_or_insert(32, true, &[0], false, true);
assert_eq!(cache.stats(), (1, 1));
}
#[test]
fn test_cache_different_frames() {
let mut cache = FrameAnalysisCache::new();
cache.get_or_insert(32, true, &[0], false, true);
cache.get_or_insert(64, false, &[], false, true);
cache.get_or_insert(32, false, &[0], false, false);
assert_eq!(cache.stats(), (0, 3));
}
#[test]
fn test_section_header_default() {
let header = CompactUnwindSectionHeader::default();
assert_eq!(header.section_name, "__compact_unwind");
assert_eq!(header.segment_name, "__LD");
assert_eq!(header.alignment, 3); }
#[test]
fn test_roundtrip_all_64_modes() {
let enc1 = encode_x86_64_rbp_frame(32, &[0]).unwrap();
let info1 = decode_x86_64_encoding(enc1);
assert_eq!(info1.mode_name, "RBP_FRAME");
let enc2 = encode_x86_64_stack_immd(256, &[1]).unwrap();
let info2 = decode_x86_64_encoding(enc2);
assert_eq!(info2.mode_name, "STACK_IMMD");
let enc3 = encode_x86_64_stack_ind(&[0, 1]).unwrap();
let info3 = decode_x86_64_encoding(enc3);
assert_eq!(info3.mode_name, "STACK_IND");
let enc4 = encode_x86_64_dwarf();
let info4 = decode_x86_64_encoding(enc4);
assert_eq!(info4.mode_name, "DWARF");
}
#[test]
fn test_roundtrip_all_32_modes() {
let enc1 = encode_x86_ebp_frame(20, &[0]).unwrap();
assert_eq!(decode_x86_encoding(enc1).mode_name, "EBP_FRAME");
let enc2 = encode_x86_stack_immd(80, &[0]).unwrap();
assert_eq!(decode_x86_encoding(enc2).mode_name, "STACK_IMMD");
let enc3 = encode_x86_stack_ind(&[0, 4]).unwrap();
assert_eq!(decode_x86_encoding(enc3).mode_name, "STACK_IND");
let enc4 = encode_x86_dwarf();
assert_eq!(decode_x86_encoding(enc4).mode_name, "DWARF");
}
#[test]
fn test_prologue_desc_zero_stack() {
let desc = PrologueDesc::x86_64();
assert_eq!(desc.total_stack(), 0);
let (enc, mode) = desc.derive_encoding();
assert!(enc != 0 || mode == unwind_x86_64_mode::UNWIND_X86_64_MODE_DWARF);
}
#[test]
fn test_prologue_desc_all_regs_32() {
let mut desc = PrologueDesc::x86_32();
desc.push(0); desc.push(4); desc.push(3); desc.push(5); desc.set_frame_pointer(true);
desc.set_extra_stack(0);
assert_eq!(desc.total_stack(), 16); let (_enc, mode) = desc.derive_encoding();
assert_eq!(mode, unwind_x86_mode::UNWIND_X86_MODE_EBP_FRAME);
}
#[test]
fn test_cfi_program_empty() {
let cfi = DwarfCfiProgram::new();
assert!(cfi.instructions.is_empty());
let bytes = cfi.encode_instructions();
assert!(bytes.is_empty());
}
#[test]
fn test_cfi_program_remember_restore() {
let mut cfi = DwarfCfiProgram::new();
cfi.add(CfiInstruction::RememberState);
cfi.add(CfiInstruction::AdvanceLoc(10));
cfi.add(CfiInstruction::Restore(3)); cfi.add(CfiInstruction::RestoreState);
let bytes = cfi.encode_instructions();
assert!(!bytes.is_empty());
assert_eq!(bytes[0], dwarf_cfi::DW_CFA_remember_state);
assert_eq!(bytes[1], dwarf_cfi::DW_CFA_advance_loc | 10);
assert_eq!(bytes[2], dwarf_cfi::DW_CFA_restore | 3);
assert_eq!(bytes[3], dwarf_cfi::DW_CFA_restore_state);
}
#[test]
fn test_cfi_nop() {
let mut cfi = DwarfCfiProgram::new();
cfi.add(CfiInstruction::Nop);
let bytes = cfi.encode_instructions();
assert_eq!(bytes, vec![0x00]);
}
#[test]
fn test_validation_result_ok() {
let result = ValidationResult::ok();
assert!(result.valid);
assert!(result.errors.is_empty());
assert!(result.warnings.is_empty());
}
#[test]
fn test_validation_result_error() {
let mut result = ValidationResult::ok();
result.error("bad encoding".to_string());
assert!(!result.valid);
assert_eq!(result.errors.len(), 1);
}
#[test]
fn test_validation_result_warning() {
let mut result = ValidationResult::ok();
result.warn("odd alignment".to_string());
assert!(result.valid); assert_eq!(result.warnings.len(), 1);
}
#[test]
fn test_personality_reserved_bits() {
let raw = 0xC000_0000u32;
let decoded = PersonalityEncoding::decode(raw);
assert_eq!(decoded, PersonalityEncoding::None);
}
#[test]
fn test_many_entries() {
let mut section = CompactUnwindSection::new();
for i in 0..100 {
let enc = encode_x86_64_rbp_frame(16, &[]).unwrap();
section.add_entry(CompactUnwindEntry::simple(i * 0x100, 0x50, enc));
}
assert_eq!(section.len(), 100);
let bytes = section.encode_to_bytes();
assert_eq!(bytes.len(), 100 * 20);
}
#[test]
fn test_lsda_chain_of_actions() {
let mut builder = LsdaBuilder::new();
builder.add_type(0x1000);
builder.add_call_site(0x10, 0x20, 0x200, 1);
for i in 0..10 {
builder.add_action(i + 1, if i < 9 { i + 2 } else { 0 });
}
let bytes = builder.encode();
assert!(!bytes.is_empty());
}
#[test]
fn test_uleb128_large_values() {
assert_eq!(DwarfCfiProgram::encode_uleb128(0xFFFFFFFF).len(), 5);
assert_eq!(DwarfCfiProgram::encode_uleb128(0x1FFFFF).len(), 3);
}
#[test]
fn test_sleb128_negative_large() {
let encoded = DwarfCfiProgram::encode_sleb128(-123456);
assert!(!encoded.is_empty());
}
#[test]
fn test_sleb128_boundary() {
assert_eq!(DwarfCfiProgram::encode_sleb128(0), vec![0]);
assert_eq!(DwarfCfiProgram::encode_sleb128(63), vec![63]);
assert_eq!(DwarfCfiProgram::encode_sleb128(64), vec![0xC0, 0x00]);
assert_eq!(DwarfCfiProgram::encode_sleb128(-1), vec![0x7F]);
assert_eq!(DwarfCfiProgram::encode_sleb128(-64), vec![0x40]);
assert_eq!(DwarfCfiProgram::encode_sleb128(-65), vec![0xBF, 0x7F]);
}
#[test]
fn test_frame_analysis_select_32_stack_ind_large() {
let analysis = FrameAnalysis {
stack_size: 200,
uses_frame_pointer: false,
saved_regs: vec![0, 4],
has_calls: false,
has_var_sized_objects: false,
is_64bit: false,
};
let (_enc, mode) = analysis.select_encoding();
assert_eq!(mode, unwind_x86_mode::UNWIND_X86_MODE_STACK_IND);
}
#[test]
fn test_frame_analysis_select_64_stack_ind_large() {
let analysis = FrameAnalysis {
stack_size: 10000,
uses_frame_pointer: false,
saved_regs: vec![0, 1, 2, 3, 4, 5],
has_calls: false,
has_var_sized_objects: false,
is_64bit: true,
};
let (_enc, mode) = analysis.select_encoding();
assert_eq!(mode, unwind_x86_64_mode::UNWIND_X86_64_MODE_STACK_IND);
}
#[test]
fn test_compact_unwind_find_nonexistent() {
let cu = X86CompactUnwind::new_x86_64();
assert!(cu.find_entry(0xDEAD).is_none());
assert!(cu.get_dwarf_fallback(0xDEAD).is_none());
}
#[test]
fn test_personality_roundtrip_all() {
let variants = vec![
PersonalityEncoding::None,
PersonalityEncoding::Direct(0xDEADBEEF),
PersonalityEncoding::Indirect(0x12345),
PersonalityEncoding::Gnu(0x54321),
];
for v in &variants {
let encoded = v.encode();
let decoded = PersonalityEncoding::decode(encoded);
match (v, &decoded) {
(PersonalityEncoding::None, PersonalityEncoding::None) => {}
(PersonalityEncoding::Direct(a), PersonalityEncoding::Direct(b)) => {
assert_eq!(a, b)
}
(PersonalityEncoding::Indirect(a), PersonalityEncoding::Indirect(b)) => {
assert_eq!(a, b)
}
(PersonalityEncoding::Gnu(a), PersonalityEncoding::Gnu(b)) => assert_eq!(a, b),
_ => panic!("Mismatch: {v:?} vs {decoded:?}"),
}
}
}
#[test]
fn test_cfi_def_cfa_register() {
let mut cfi = DwarfCfiProgram::new();
cfi.add(CfiInstruction::DefCfaRegister(dwarf_regs_x86_64::RBP));
let bytes = cfi.encode_instructions();
assert_eq!(bytes[0], dwarf_cfi::DW_CFA_def_cfa_register);
assert_eq!(bytes[1], dwarf_regs_x86_64::RBP); }
#[test]
fn test_cfi_def_cfa_offset() {
let mut cfi = DwarfCfiProgram::new();
cfi.add(CfiInstruction::DefCfaOffset(32));
let bytes = cfi.encode_instructions();
assert_eq!(bytes[0], dwarf_cfi::DW_CFA_def_cfa_offset);
}
#[test]
fn test_lsda_builder_multiple_types_and_call_sites() {
let mut builder = LsdaBuilder::new();
builder.landing_pad_base = 0x2000;
builder.add_type(0x4000);
builder.add_type(0x5000);
builder.add_type(0x6000);
builder.add_call_site(0x10, 0x20, 0x100, 1);
builder.add_call_site(0x30, 0x40, 0x200, 2);
builder.add_call_site(0x50, 0x60, 0x0, 0); builder.add_action(1, 2);
builder.add_action(0, 0); let bytes = builder.encode();
assert!(!bytes.is_empty());
assert!(bytes.len() > 10);
}
#[test]
fn test_cache_stress_many_frames() {
let mut cache = FrameAnalysisCache::new();
for i in 0..50 {
cache.get_or_insert(i * 8, i % 2 == 0, &[(i % 6) as u8], false, true);
}
let (hits, misses) = cache.stats();
assert_eq!(misses, 50);
assert_eq!(hits, 0);
for i in 0..50 {
cache.get_or_insert(i * 8, i % 2 == 0, &[(i % 6) as u8], false, true);
}
let (hits2, _) = cache.stats();
assert_eq!(hits2, 50);
}
#[test]
fn test_reloc_pcrel_encode() {
let reloc = CompactUnwindReloc::pcrel(0x40, 5, -8);
let bytes = reloc.encode_macho();
assert_eq!(bytes.len(), 16);
}
#[test]
fn test_validate_x86_64_stack_immd_below_min() {
let mut entry = CompactUnwindEntry::simple(0, 0, 0);
entry.encoding = (unwind_x86_64_mode::UNWIND_X86_64_MODE_STACK_IMMD
<< unwind_x86_64_encoding::MODE_SHIFT)
| (0 << unwind_x86_64_encoding::STACK_SIZE_SHIFT)
| (0 << 8); let result = validate_x86_64_entry(&entry);
assert!(result.valid);
assert!(!result.warnings.is_empty());
}
#[test]
fn test_archive_multi_arch_encode() {
let mut archive = CompactUnwindArchive::new();
let mut sec64 = CompactUnwindSection::new();
sec64.add_entry(CompactUnwindEntry::simple(0x1000, 0x100, 1));
archive.add_section("x86_64", sec64);
let mut sec32 = CompactUnwindSection::new();
sec32.add_entry(CompactUnwindEntry::simple(0x2000, 0x100, 2));
archive.add_section("i386", sec32);
let encoded = archive.encode_archive();
assert_eq!(encoded.len(), 2);
assert_eq!(encoded["x86_64"].len(), 20);
assert_eq!(encoded["i386"].len(), 20);
}
#[test]
fn test_validation_warning_and_error_together() {
let mut result = ValidationResult::ok();
result.warn("minor issue".to_string());
result.error("major issue".to_string());
assert!(!result.valid);
assert_eq!(result.errors.len(), 1);
assert_eq!(result.warnings.len(), 1);
}
#[test]
fn test_prologue_desc_to_analysis_roundtrip() {
let mut desc = PrologueDesc::x86_64();
desc.push(0); desc.push(5); desc.set_frame_pointer(true);
desc.set_extra_stack(32);
let analysis = desc.to_analysis();
assert_eq!(analysis.stack_size, 48); assert!(analysis.uses_frame_pointer);
assert_eq!(analysis.saved_regs, vec![0, 5]);
}
#[test]
fn test_section_header_non_default() {
let mut header = CompactUnwindSectionHeader::default();
header.alignment = 4; header.num_relocs = 42;
assert_eq!(header.alignment, 4);
assert_eq!(header.num_relocs, 42);
}
#[test]
fn test_permutation_table_64_all_entries() {
for (i, entry) in x86_64_reg_permutations::PERMUTATION_TABLE
.iter()
.enumerate()
{
let regs = x86_64_reg_permutations::get_registers(i);
assert!(regs.is_some());
assert_eq!(regs.unwrap(), *entry);
}
}
#[test]
fn test_permutation_table_32_bounds() {
assert!(x86_reg_permutations::get_registers(0).is_some());
assert!(x86_reg_permutations::get_registers(15).is_some()); assert!(x86_reg_permutations::get_registers(16).is_none()); }
#[test]
fn test_frame_analysis_hash_uniqueness() {
let mut cache = FrameAnalysisCache::new();
cache.get_or_insert(32, true, &[0], false, true);
cache.get_or_insert(32, false, &[0], false, true);
assert_eq!(cache.misses, 2);
}
#[test]
fn test_compact_unwind_section_find_miss() {
let section = CompactUnwindSection::new();
assert!(section.find(0x1234).is_none());
}
#[test]
fn test_entry_set_personality() {
let mut entry = CompactUnwindEntry::simple(0, 0, 0);
entry.set_personality(PersonalityEncoding::Indirect(0x2000));
assert_ne!(entry.personality, 0);
}
#[test]
fn test_lsda_action_entry_values() {
let action = LsdaAction {
type_filter: 1,
next_offset: 3,
};
assert_eq!(action.type_filter, 1);
assert_eq!(action.next_offset, 3);
}
#[test]
fn test_lsda_call_site_values() {
let cs = LsdaCallSite {
start_offset: 10,
length: 20,
landing_pad: 30,
action_index: 1,
};
assert_eq!(cs.start_offset, 10);
assert_eq!(cs.length, 20);
assert_eq!(cs.landing_pad, 30);
assert_eq!(cs.action_index, 1);
}
#[test]
fn test_cfi_advance_loc_large_uses_no_encoding() {
let mut cfi = DwarfCfiProgram::new();
cfi.add(CfiInstruction::AdvanceLoc(0x100));
let bytes = cfi.encode_instructions();
assert!(bytes.is_empty());
}
}