#![allow(dead_code)]
use super::{encode_sleb128, encode_uleb128};
pub struct DwarfFrame;
pub mod cfa_ops {
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_nop: u8 = 0x00;
pub const DW_CFA_set_loc: u8 = 0x01;
pub const DW_CFA_advance_loc1: u8 = 0x02;
pub const DW_CFA_advance_loc2: u8 = 0x03;
pub const DW_CFA_advance_loc4: u8 = 0x04;
pub const DW_CFA_offset_extended: u8 = 0x05;
pub const DW_CFA_restore_extended: u8 = 0x06;
pub const DW_CFA_undefined: u8 = 0x07;
pub const DW_CFA_same_value: u8 = 0x08;
pub const DW_CFA_register: u8 = 0x09;
pub const DW_CFA_remember_state: u8 = 0x0A;
pub const DW_CFA_restore_state: u8 = 0x0B;
pub const DW_CFA_def_cfa: u8 = 0x0C;
pub const DW_CFA_def_cfa_register: u8 = 0x0D;
pub const DW_CFA_def_cfa_offset: u8 = 0x0E;
pub const DW_CFA_def_cfa_expression: u8 = 0x0F;
pub const DW_CFA_expression: u8 = 0x10;
pub const DW_CFA_offset_extended_sf: u8 = 0x11;
pub const DW_CFA_def_cfa_sf: u8 = 0x12;
pub const DW_CFA_def_cfa_offset_sf: u8 = 0x13;
pub const DW_CFA_val_offset: u8 = 0x14;
pub const DW_CFA_val_offset_sf: u8 = 0x15;
pub const DW_CFA_val_expression: u8 = 0x16;
pub const DW_CFA_lo_user: u8 = 0x1C;
pub const DW_CFA_hi_user: u8 = 0x3F;
pub const DW_CFA_GNU_args_size: u8 = 0x2E;
pub const DW_CFA_GNU_negative_offset_extended: u8 = 0x2F;
pub const DW_CFA_AARCH64_negate_ra_state: u8 = 0x2D;
pub const DW_CFA_MIPS_advance_loc8: u8 = 0x1D;
pub const DW_CFA_GNU_window_save: u8 = 0x2D;
}
pub mod ptr_encoding {
pub const DW_EH_PE_pcrel: u8 = 0x10;
pub const DW_EH_PE_textrel: u8 = 0x20;
pub const DW_EH_PE_datarel: u8 = 0x30;
pub const DW_EH_PE_funcrel: u8 = 0x40;
pub const DW_EH_PE_absptr: u8 = 0x00;
pub const DW_EH_PE_omit: u8 = 0xFF;
pub const DW_EH_PE_uleb128: u8 = 0x01;
pub const DW_EH_PE_udata2: u8 = 0x02;
pub const DW_EH_PE_udata4: u8 = 0x03;
pub const DW_EH_PE_udata8: u8 = 0x04;
pub const DW_EH_PE_sleb128: u8 = 0x09;
pub const DW_EH_PE_sdata2: u8 = 0x0A;
pub const DW_EH_PE_sdata4: u8 = 0x0B;
pub const DW_EH_PE_sdata8: u8 = 0x0C;
pub const DW_EH_PE_indirect: u8 = 0x80;
}
#[derive(Debug, Clone)]
pub struct CIE {
pub version: u8,
pub augmentation: String,
pub code_alignment_factor: u64,
pub data_alignment_factor: i64,
pub return_address_register: u64,
pub personality: Option<u64>,
pub lsda_encoding: u8,
pub fde_pointer_encoding: u8,
pub initial_instructions: Vec<u8>,
}
impl CIE {
pub fn new(version: u8, augmentation: &str) -> Self {
Self {
version,
augmentation: augmentation.to_string(),
code_alignment_factor: 1,
data_alignment_factor: -8,
return_address_register: 16,
personality: None,
lsda_encoding: ptr_encoding::DW_EH_PE_omit,
fde_pointer_encoding: ptr_encoding::DW_EH_PE_absptr,
initial_instructions: Vec::new(),
}
}
pub fn aarch64() -> Self {
let mut cie = CIE::new(4, "");
cie.code_alignment_factor = 1;
cie.data_alignment_factor = -8;
cie.return_address_register = 30; let mut instrs = Vec::new();
instrs.push(cfa_ops::DW_CFA_def_cfa);
encode_uleb128(&mut instrs, 31); encode_uleb128(&mut instrs, 0); cie.initial_instructions = instrs;
cie
}
pub fn x86_64() -> Self {
let mut cie = CIE::new(4, "");
cie.code_alignment_factor = 1;
cie.data_alignment_factor = -8;
cie.return_address_register = 16; let mut instrs = Vec::new();
instrs.push(cfa_ops::DW_CFA_def_cfa);
encode_uleb128(&mut instrs, 7); encode_uleb128(&mut instrs, 8); instrs.push(cfa_ops::DW_CFA_offset | 16); encode_uleb128(&mut instrs, 1); cie.initial_instructions = instrs;
cie
}
pub fn eh_frame_aarch64() -> Self {
let mut cie = CIE::aarch64();
cie.version = 1;
cie.augmentation = "zR".to_string();
cie.fde_pointer_encoding = ptr_encoding::DW_EH_PE_pcrel | ptr_encoding::DW_EH_PE_sdata4;
cie
}
pub fn eh_frame_x86_64() -> Self {
let mut cie = CIE::x86_64();
cie.version = 1;
cie.augmentation = "zR".to_string();
cie.fde_pointer_encoding = ptr_encoding::DW_EH_PE_pcrel | ptr_encoding::DW_EH_PE_sdata4;
cie
}
pub fn has_augmentation(&self, ch: char) -> bool {
self.augmentation.contains(ch)
}
}
impl Default for CIE {
fn default() -> Self {
CIE::x86_64()
}
}
#[derive(Debug, Clone)]
pub struct FDE {
pub cie_pointer: u32,
pub initial_location: u64,
pub address_range: u64,
pub augmentation_data: Vec<u8>,
pub instructions: Vec<u8>,
}
impl FDE {
pub fn new(cie_pointer: u32) -> Self {
Self {
cie_pointer,
initial_location: 0,
address_range: 0,
augmentation_data: Vec::new(),
instructions: Vec::new(),
}
}
pub fn for_function(cie_pointer: u32, start: u64, size: u64) -> Self {
Self {
cie_pointer,
initial_location: start,
address_range: size,
augmentation_data: Vec::new(),
instructions: Vec::new(),
}
}
}
pub struct CFIBuilder {
pub instructions: Vec<u8>,
pub cie: Option<CIE>,
pub is_eh_frame: bool,
}
impl CFIBuilder {
pub fn new(is_eh_frame: bool) -> Self {
Self {
instructions: Vec::new(),
cie: None,
is_eh_frame,
}
}
pub fn with_cie(is_eh_frame: bool, cie: CIE) -> Self {
Self {
instructions: Vec::new(),
cie: Some(cie),
is_eh_frame,
}
}
pub fn create_cie_aarch64() -> CIE {
if cfg!(target_arch = "aarch64") {
CIE::aarch64()
} else {
CIE::aarch64()
}
}
pub fn create_cie_x86_64() -> CIE {
CIE::x86_64()
}
pub fn def_cfa(&mut self, register: u64, offset: i64) {
if offset >= 0 {
self.instructions.push(cfa_ops::DW_CFA_def_cfa);
encode_uleb128(&mut self.instructions, register);
encode_uleb128(&mut self.instructions, offset as u64);
} else {
self.instructions.push(cfa_ops::DW_CFA_def_cfa_sf);
encode_uleb128(&mut self.instructions, register);
encode_sleb128(&mut self.instructions, offset);
}
}
pub fn def_cfa_register(&mut self, register: u64) {
self.instructions.push(cfa_ops::DW_CFA_def_cfa_register);
encode_uleb128(&mut self.instructions, register);
}
pub fn def_cfa_offset(&mut self, offset: u64) {
self.instructions.push(cfa_ops::DW_CFA_def_cfa_offset);
encode_uleb128(&mut self.instructions, offset);
}
pub fn def_cfa_offset_sf(&mut self, offset: i64) {
self.instructions.push(cfa_ops::DW_CFA_def_cfa_offset_sf);
encode_sleb128(&mut self.instructions, offset);
}
pub fn def_cfa_expression(&mut self, expr: &[u8]) {
self.instructions.push(cfa_ops::DW_CFA_def_cfa_expression);
encode_uleb128(&mut self.instructions, expr.len() as u64);
self.instructions.extend_from_slice(expr);
}
pub fn offset(&mut self, register: u64, offset: u64) {
if register < 64 {
self.instructions
.push(cfa_ops::DW_CFA_offset | (register as u8 & 0x3F));
encode_uleb128(&mut self.instructions, offset);
} else {
self.instructions.push(cfa_ops::DW_CFA_offset_extended);
encode_uleb128(&mut self.instructions, register);
encode_uleb128(&mut self.instructions, offset);
}
}
pub fn offset_extended_sf(&mut self, register: u64, offset: i64) {
self.instructions.push(cfa_ops::DW_CFA_offset_extended_sf);
encode_uleb128(&mut self.instructions, register);
encode_sleb128(&mut self.instructions, offset);
}
pub fn restore(&mut self, register: u64) {
if register < 64 {
self.instructions
.push(cfa_ops::DW_CFA_restore | (register as u8 & 0x3F));
} else {
self.instructions.push(cfa_ops::DW_CFA_restore_extended);
encode_uleb128(&mut self.instructions, register);
}
}
pub fn save_register(&mut self, reg1: u64, reg2: u64) {
self.instructions.push(cfa_ops::DW_CFA_register);
encode_uleb128(&mut self.instructions, reg1);
encode_uleb128(&mut self.instructions, reg2);
}
pub fn same_value(&mut self, register: u64) {
self.instructions.push(cfa_ops::DW_CFA_same_value);
encode_uleb128(&mut self.instructions, register);
}
pub fn undefined(&mut self, register: u64) {
self.instructions.push(cfa_ops::DW_CFA_undefined);
encode_uleb128(&mut self.instructions, register);
}
pub fn expression(&mut self, register: u64, expr: &[u8]) {
self.instructions.push(cfa_ops::DW_CFA_expression);
encode_uleb128(&mut self.instructions, register);
encode_uleb128(&mut self.instructions, expr.len() as u64);
self.instructions.extend_from_slice(expr);
}
pub fn val_offset(&mut self, register: u64, offset: u64) {
self.instructions.push(cfa_ops::DW_CFA_val_offset);
encode_uleb128(&mut self.instructions, register);
encode_uleb128(&mut self.instructions, offset);
}
pub fn val_offset_sf(&mut self, register: u64, offset: i64) {
self.instructions.push(cfa_ops::DW_CFA_val_offset_sf);
encode_uleb128(&mut self.instructions, register);
encode_sleb128(&mut self.instructions, offset);
}
pub fn val_expression(&mut self, register: u64, expr: &[u8]) {
self.instructions.push(cfa_ops::DW_CFA_val_expression);
encode_uleb128(&mut self.instructions, register);
encode_uleb128(&mut self.instructions, expr.len() as u64);
self.instructions.extend_from_slice(expr);
}
pub fn remember_state(&mut self) {
self.instructions.push(cfa_ops::DW_CFA_remember_state);
}
pub fn restore_state(&mut self) {
self.instructions.push(cfa_ops::DW_CFA_restore_state);
}
pub fn advance_loc(&mut self, delta: u64) {
if delta < 64 {
self.instructions
.push(cfa_ops::DW_CFA_advance_loc | (delta as u8 & 0x3F));
} else if delta <= 0xFF {
self.instructions.push(cfa_ops::DW_CFA_advance_loc1);
self.instructions.push(delta as u8);
} else if delta <= 0xFFFF {
self.instructions.push(cfa_ops::DW_CFA_advance_loc2);
self.instructions
.extend_from_slice(&(delta as u16).to_le_bytes());
} else {
self.instructions.push(cfa_ops::DW_CFA_advance_loc4);
self.instructions
.extend_from_slice(&(delta as u32).to_le_bytes());
}
}
pub fn set_loc(&mut self, address: u64, addr_size: u8) {
self.instructions.push(cfa_ops::DW_CFA_set_loc);
match addr_size {
4 => self
.instructions
.extend_from_slice(&(address as u32).to_le_bytes()),
_ => self.instructions.extend_from_slice(&address.to_le_bytes()),
}
}
pub fn nop(&mut self) {
self.instructions.push(cfa_ops::DW_CFA_nop);
}
pub fn pad_to_alignment(&mut self, alignment: usize) {
let rem = (self.instructions.len() + 1) % alignment; let padding = (alignment - rem) % alignment;
for _ in 0..padding {
self.nop();
}
}
pub fn emit_cie(&self, cie: &CIE, addr_size: u8) -> Vec<u8> {
let mut out = Vec::new();
if self.is_eh_frame {
self.encode_eh_frame_cie(&mut out, cie, addr_size);
} else {
self.encode_debug_frame_cie(&mut out, cie, addr_size);
}
let total_len = out.len() as u32;
let unit_len = total_len - 4;
out[0..4].copy_from_slice(&unit_len.to_le_bytes());
out
}
fn encode_debug_frame_cie(&self, out: &mut Vec<u8>, cie: &CIE, addr_size: u8) {
let len_pos = out.len();
out.extend_from_slice(&[0u8; 4]);
out.extend_from_slice(&0xFFFFFFFFu32.to_le_bytes());
out.push(cie.version);
out.extend_from_slice(cie.augmentation.as_bytes());
out.push(0);
out.push(addr_size);
out.push(0);
encode_uleb128(out, cie.code_alignment_factor);
encode_sleb128(out, cie.data_alignment_factor);
encode_uleb128(out, cie.return_address_register);
if cie.has_augmentation('z') {
let mut aug_data = Vec::new();
if cie.has_augmentation('R') {
aug_data.push(cie.fde_pointer_encoding);
}
if cie.has_augmentation('L') {
aug_data.push(cie.lsda_encoding);
}
if cie.has_augmentation('P') {
if let Some(pers) = cie.personality {
aug_data.push(cie.fde_pointer_encoding);
match addr_size {
4 => aug_data.extend_from_slice(&(pers as u32).to_le_bytes()),
_ => aug_data.extend_from_slice(&pers.to_le_bytes()),
}
}
}
encode_uleb128(out, aug_data.len() as u64);
out.extend_from_slice(&aug_data);
}
out.extend_from_slice(&cie.initial_instructions);
let _ = len_pos;
}
fn encode_eh_frame_cie(&self, out: &mut Vec<u8>, cie: &CIE, addr_size: u8) {
let len_pos = out.len();
out.extend_from_slice(&[0u8; 4]);
out.extend_from_slice(&0u32.to_le_bytes());
out.push(cie.version);
out.extend_from_slice(cie.augmentation.as_bytes());
out.push(0);
encode_uleb128(out, cie.code_alignment_factor);
encode_sleb128(out, cie.data_alignment_factor);
if cie.version >= 3 {
encode_uleb128(out, cie.return_address_register);
} else {
out.push(cie.return_address_register as u8);
}
if cie.has_augmentation('z') {
let aug_start = out.len();
out.push(0);
if cie.has_augmentation('R') {
out.push(cie.fde_pointer_encoding);
}
if cie.has_augmentation('L') {
out.push(cie.lsda_encoding);
}
if cie.has_augmentation('P') {
if let Some(pers) = cie.personality {
out.push(cie.fde_pointer_encoding);
match addr_size {
4 => out.extend_from_slice(&(pers as u32).to_le_bytes()),
_ => out.extend_from_slice(&pers.to_le_bytes()),
}
} else {
out.push(ptr_encoding::DW_EH_PE_omit);
}
}
let aug_len = (out.len() - aug_start - 1) as u8;
out[aug_start] = aug_len;
}
out.extend_from_slice(&cie.initial_instructions);
let _ = len_pos;
}
pub fn emit_fde(&self, fde: &FDE, cie: &CIE, addr_size: u8) -> Vec<u8> {
let mut out = Vec::new();
let len_pos = out.len();
out.extend_from_slice(&[0u8; 4]);
if self.is_eh_frame {
let cie_offset = fde.cie_pointer;
out.extend_from_slice(&cie_offset.to_le_bytes());
if cie.has_augmentation('R') {
let encoding = cie.fde_pointer_encoding & 0x0F;
match encoding {
ptr_encoding::DW_EH_PE_sdata4 | ptr_encoding::DW_EH_PE_udata4 => {
out.extend_from_slice(&(fde.initial_location as u32).to_le_bytes());
}
ptr_encoding::DW_EH_PE_sdata8 | ptr_encoding::DW_EH_PE_udata8 => {
out.extend_from_slice(&fde.initial_location.to_le_bytes());
}
_ => {
match addr_size {
4 => {
out.extend_from_slice(&(fde.initial_location as u32).to_le_bytes())
}
_ => out.extend_from_slice(&fde.initial_location.to_le_bytes()),
}
}
}
} else {
match addr_size {
4 => out.extend_from_slice(&(fde.initial_location as u32).to_le_bytes()),
_ => out.extend_from_slice(&fde.initial_location.to_le_bytes()),
}
}
match addr_size {
4 => out.extend_from_slice(&(fde.address_range as u32).to_le_bytes()),
_ => out.extend_from_slice(&fde.address_range.to_le_bytes()),
}
if cie.has_augmentation('z') {
encode_uleb128(&mut out, fde.augmentation_data.len() as u64);
out.extend_from_slice(&fde.augmentation_data);
}
} else {
out.extend_from_slice(&fde.cie_pointer.to_le_bytes());
match addr_size {
4 => out.extend_from_slice(&(fde.initial_location as u32).to_le_bytes()),
_ => out.extend_from_slice(&fde.initial_location.to_le_bytes()),
}
match addr_size {
4 => out.extend_from_slice(&(fde.address_range as u32).to_le_bytes()),
_ => out.extend_from_slice(&fde.address_range.to_le_bytes()),
}
if cie.has_augmentation('z') {
encode_uleb128(&mut out, fde.augmentation_data.len() as u64);
out.extend_from_slice(&fde.augmentation_data);
}
}
out.extend_from_slice(&fde.instructions);
let total_len = out.len() as u32 - len_pos as u32 - 4;
out[len_pos..len_pos + 4].copy_from_slice(&total_len.to_le_bytes());
out
}
pub fn emit_aarch64_prologue_cfi(&mut self) {
self.def_cfa_offset(32);
self.offset(29, 2);
self.offset(29, 4); self.offset(30, 3); }
pub fn emit_x86_64_prologue_cfi(&mut self) {
self.def_cfa_offset(16); self.offset(6, 2);
}
}
pub struct FrameEmitter {
pub cies: Vec<CIE>,
pub fdes: Vec<FDE>,
pub is_eh_frame: bool,
pub address_size: u8,
cie_offsets: Vec<u64>,
}
impl FrameEmitter {
pub fn new(is_eh_frame: bool, address_size: u8) -> Self {
Self {
cies: Vec::new(),
fdes: Vec::new(),
is_eh_frame,
address_size,
cie_offsets: Vec::new(),
}
}
pub fn add_cie(&mut self, cie: CIE) -> usize {
let idx = self.cies.len();
self.cies.push(cie);
idx
}
pub fn add_fde(&mut self, fde: FDE) {
self.fdes.push(fde);
}
pub fn add_function_fde(
&mut self,
cie_index: usize,
start: u64,
size: u64,
instructions: &[u8],
) {
let fde = FDE {
cie_pointer: 0, initial_location: start,
address_range: size,
augmentation_data: Vec::new(),
instructions: instructions.to_vec(),
};
self.fdes.push(fde);
let _ = cie_index;
}
pub fn emit(&mut self) -> Option<Vec<u8>> {
if self.cies.is_empty() {
return None;
}
let mut out = Vec::new();
let builder = CFIBuilder::new(self.is_eh_frame);
self.cie_offsets.clear();
for cie in &self.cies {
self.cie_offsets.push(out.len() as u64);
let cie_bytes = builder.emit_cie(cie, self.address_size);
out.extend_from_slice(&cie_bytes);
}
let mut current_offset = out.len() as u64;
for fde in &self.fdes {
let mut fde_bytes = Vec::new();
let len_pos = fde_bytes.len();
fde_bytes.extend_from_slice(&[0u8; 4]);
let cie_offset = if self.is_eh_frame {
let cie_pos = self
.cie_offsets
.get(fde.cie_pointer as usize)
.copied()
.unwrap_or(0);
let field_pos = current_offset + len_pos as u64 + 4;
(cie_pos as i64 - field_pos as i64) as u32
} else {
self.cie_offsets
.get(fde.cie_pointer as usize)
.copied()
.unwrap_or(0) as u32
};
fde_bytes.extend_from_slice(&cie_offset.to_le_bytes());
match self.address_size {
4 => fde_bytes.extend_from_slice(&(fde.initial_location as u32).to_le_bytes()),
_ => fde_bytes.extend_from_slice(&fde.initial_location.to_le_bytes()),
}
match self.address_size {
4 => fde_bytes.extend_from_slice(&(fde.address_range as u32).to_le_bytes()),
_ => fde_bytes.extend_from_slice(&fde.address_range.to_le_bytes()),
}
if let Some(cie) = self.cies.get(fde.cie_pointer as usize) {
if cie.has_augmentation('z') {
encode_uleb128(&mut fde_bytes, fde.augmentation_data.len() as u64);
fde_bytes.extend_from_slice(&fde.augmentation_data);
}
}
fde_bytes.extend_from_slice(&fde.instructions);
let fde_len = fde_bytes.len() as u32 - 4;
fde_bytes[0..4].copy_from_slice(&fde_len.to_le_bytes());
current_offset += fde_bytes.len() as u64;
out.extend_from_slice(&fde_bytes);
}
Some(out)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cie_new_defaults() {
let cie = CIE::new(4, "zR");
assert_eq!(cie.version, 4);
assert_eq!(cie.augmentation, "zR");
assert_eq!(cie.code_alignment_factor, 1);
assert_eq!(cie.data_alignment_factor, -8);
assert_eq!(cie.return_address_register, 16);
assert!(cie.personality.is_none());
assert!(cie.initial_instructions.is_empty());
}
#[test]
fn test_cie_aarch64() {
let cie = CIE::aarch64();
assert_eq!(cie.version, 4);
assert_eq!(cie.code_alignment_factor, 1);
assert_eq!(cie.data_alignment_factor, -8);
assert_eq!(cie.return_address_register, 30); assert!(!cie.initial_instructions.is_empty());
assert_eq!(cie.initial_instructions[0], cfa_ops::DW_CFA_def_cfa);
}
#[test]
fn test_cie_x86_64() {
let cie = CIE::x86_64();
assert_eq!(cie.version, 4);
assert_eq!(cie.code_alignment_factor, 1);
assert_eq!(cie.data_alignment_factor, -8);
assert_eq!(cie.return_address_register, 16); assert!(!cie.initial_instructions.is_empty());
assert_eq!(cie.initial_instructions[0], cfa_ops::DW_CFA_def_cfa);
}
#[test]
fn test_cie_eh_frame_aarch64() {
let cie = CIE::eh_frame_aarch64();
assert_eq!(cie.version, 1);
assert!(cie.augmentation.contains('z'));
assert!(cie.augmentation.contains('R'));
assert_eq!(
cie.fde_pointer_encoding,
ptr_encoding::DW_EH_PE_pcrel | ptr_encoding::DW_EH_PE_sdata4
);
}
#[test]
fn test_cie_eh_frame_x86_64() {
let cie = CIE::eh_frame_x86_64();
assert_eq!(cie.version, 1);
assert!(cie.augmentation.contains('z'));
assert!(cie.augmentation.contains('R'));
}
#[test]
fn test_cie_has_augmentation() {
let cie = CIE::new(1, "zPLR");
assert!(cie.has_augmentation('z'));
assert!(cie.has_augmentation('P'));
assert!(cie.has_augmentation('L'));
assert!(cie.has_augmentation('R'));
assert!(!cie.has_augmentation('X'));
}
#[test]
fn test_cie_default() {
let cie = CIE::default();
assert_eq!(cie.code_alignment_factor, 1);
assert_eq!(cie.return_address_register, 16);
}
#[test]
fn test_fde_new() {
let fde = FDE::new(0x20);
assert_eq!(fde.cie_pointer, 0x20);
assert_eq!(fde.initial_location, 0);
assert_eq!(fde.address_range, 0);
assert!(fde.instructions.is_empty());
}
#[test]
fn test_fde_for_function() {
let fde = FDE::for_function(0x10, 0x4000, 0x80);
assert_eq!(fde.cie_pointer, 0x10);
assert_eq!(fde.initial_location, 0x4000);
assert_eq!(fde.address_range, 0x80);
}
#[test]
fn test_cfi_builder_new() {
let b = CFIBuilder::new(false);
assert!(!b.is_eh_frame);
assert!(b.instructions.is_empty());
assert!(b.cie.is_none());
let b = CFIBuilder::new(true);
assert!(b.is_eh_frame);
}
#[test]
fn test_cfi_builder_with_cie() {
let cie = CIE::aarch64();
let b = CFIBuilder::with_cie(false, cie.clone());
assert!(b.cie.is_some());
assert_eq!(b.cie.as_ref().unwrap().version, 4);
}
#[test]
fn test_create_cie_aarch64() {
let cie = CFIBuilder::create_cie_aarch64();
assert_eq!(cie.return_address_register, 30);
}
#[test]
fn test_create_cie_x86_64() {
let cie = CFIBuilder::create_cie_x86_64();
assert_eq!(cie.return_address_register, 16);
}
#[test]
fn test_def_cfa_positive_offset() {
let mut b = CFIBuilder::new(false);
b.def_cfa(31, 0);
assert_eq!(b.instructions[0], cfa_ops::DW_CFA_def_cfa);
}
#[test]
fn test_def_cfa_negative_offset() {
let mut b = CFIBuilder::new(false);
b.def_cfa(7, -8);
assert_eq!(b.instructions[0], cfa_ops::DW_CFA_def_cfa_sf);
}
#[test]
fn test_def_cfa_register() {
let mut b = CFIBuilder::new(false);
b.def_cfa_register(29);
assert_eq!(b.instructions[0], cfa_ops::DW_CFA_def_cfa_register);
}
#[test]
fn test_def_cfa_offset() {
let mut b = CFIBuilder::new(false);
b.def_cfa_offset(16);
assert_eq!(b.instructions[0], cfa_ops::DW_CFA_def_cfa_offset);
}
#[test]
fn test_def_cfa_offset_sf() {
let mut b = CFIBuilder::new(false);
b.def_cfa_offset_sf(-16);
assert_eq!(b.instructions[0], cfa_ops::DW_CFA_def_cfa_offset_sf);
}
#[test]
fn test_offset_compact() {
let mut b = CFIBuilder::new(false);
b.offset(30, 2); assert_eq!(b.instructions[0], 0x80 | 0x1E);
}
#[test]
fn test_offset_extended() {
let mut b = CFIBuilder::new(false);
b.offset(64, 3); assert_eq!(b.instructions[0], cfa_ops::DW_CFA_offset_extended);
}
#[test]
fn test_restore_compact() {
let mut b = CFIBuilder::new(false);
b.restore(29);
assert_eq!(b.instructions[0], 0xC0 | 0x1D);
}
#[test]
fn test_restore_extended() {
let mut b = CFIBuilder::new(false);
b.restore(100);
assert_eq!(b.instructions[0], cfa_ops::DW_CFA_restore_extended);
}
#[test]
fn test_save_register() {
let mut b = CFIBuilder::new(false);
b.save_register(16, 6);
assert_eq!(b.instructions[0], cfa_ops::DW_CFA_register);
}
#[test]
fn test_same_value() {
let mut b = CFIBuilder::new(false);
b.same_value(3);
assert_eq!(b.instructions[0], cfa_ops::DW_CFA_same_value);
}
#[test]
fn test_undefined() {
let mut b = CFIBuilder::new(false);
b.undefined(5);
assert_eq!(b.instructions[0], cfa_ops::DW_CFA_undefined);
}
#[test]
fn test_remember_restore_state() {
let mut b = CFIBuilder::new(false);
b.remember_state();
assert_eq!(b.instructions[0], cfa_ops::DW_CFA_remember_state);
b.restore_state();
assert_eq!(b.instructions[1], cfa_ops::DW_CFA_restore_state);
}
#[test]
fn test_advance_loc_small() {
let mut b = CFIBuilder::new(false);
b.advance_loc(10);
assert_eq!(b.instructions[0], 0x40 | 0x0A);
}
#[test]
fn test_advance_loc_medium() {
let mut b = CFIBuilder::new(false);
b.advance_loc(200);
assert_eq!(b.instructions[0], cfa_ops::DW_CFA_advance_loc1);
assert_eq!(b.instructions[1], 200);
}
#[test]
fn test_advance_loc_large() {
let mut b = CFIBuilder::new(false);
b.advance_loc(70000);
assert_eq!(b.instructions[0], cfa_ops::DW_CFA_advance_loc4);
}
#[test]
fn test_set_loc_64() {
let mut b = CFIBuilder::new(false);
b.set_loc(0x400000, 8);
assert_eq!(b.instructions[0], cfa_ops::DW_CFA_set_loc);
assert_eq!(b.instructions.len(), 9);
}
#[test]
fn test_set_loc_32() {
let mut b = CFIBuilder::new(false);
b.set_loc(0x400000, 4);
assert_eq!(b.instructions[0], cfa_ops::DW_CFA_set_loc);
assert_eq!(b.instructions.len(), 5);
}
#[test]
fn test_nop() {
let mut b = CFIBuilder::new(false);
b.nop();
assert_eq!(b.instructions[0], cfa_ops::DW_CFA_nop);
}
#[test]
fn test_pad_to_alignment() {
let mut b = CFIBuilder::new(false);
let expected_padding = (8 - (b.instructions.len() + 1) % 8) % 8;
b.pad_to_alignment(8);
assert_eq!(b.instructions.len(), expected_padding);
for &byte in &b.instructions {
assert_eq!(byte, cfa_ops::DW_CFA_nop);
}
}
#[test]
fn test_emit_cie_debug_frame_aarch64() {
let cie = CIE::aarch64();
let b = CFIBuilder::new(false);
let bytes = b.emit_cie(&cie, 8);
assert!(!bytes.is_empty());
let cie_id = u32::from_le_bytes(bytes[4..8].try_into().unwrap());
assert_eq!(cie_id, 0xFFFFFFFF);
assert_eq!(bytes[8], 4);
}
#[test]
fn test_emit_cie_eh_frame() {
let cie = CIE::eh_frame_x86_64();
let b = CFIBuilder::new(true);
let bytes = b.emit_cie(&cie, 8);
assert!(!bytes.is_empty());
let cie_id = u32::from_le_bytes(bytes[4..8].try_into().unwrap());
assert_eq!(cie_id, 0);
assert_eq!(bytes[8], 1);
let all_bytes = String::from_utf8_lossy(&bytes);
assert!(
all_bytes.contains("zR"),
"Expected augmentation 'zR' in: {:?}",
all_bytes
);
}
#[test]
fn test_emit_fde_debug_frame() {
let cie = CIE::x86_64();
let b = CFIBuilder::new(false);
let fde = FDE::for_function(0x20, 0x1000, 0x50);
let bytes = b.emit_fde(&fde, &cie, 8);
assert!(!bytes.is_empty());
let loc = u64::from_le_bytes(bytes[8..16].try_into().unwrap());
assert_eq!(loc, 0x1000);
}
#[test]
fn test_emit_fde_debug_frame32() {
let cie = CIE::x86_64();
let b = CFIBuilder::new(false);
let fde = FDE::for_function(0x10, 0x4000, 0x30);
let bytes = b.emit_fde(&fde, &cie, 4);
assert!(!bytes.is_empty());
let loc = u32::from_le_bytes(bytes[8..12].try_into().unwrap());
assert_eq!(loc, 0x4000);
}
#[test]
fn test_cfa_ops_constants() {
assert_eq!(cfa_ops::DW_CFA_advance_loc, 0x40); assert_eq!(cfa_ops::DW_CFA_offset, 0x80); assert_eq!(cfa_ops::DW_CFA_restore, 0xC0); assert_eq!(cfa_ops::DW_CFA_nop, 0x00);
assert_eq!(cfa_ops::DW_CFA_set_loc, 0x01);
assert_eq!(cfa_ops::DW_CFA_advance_loc1, 0x02);
assert_eq!(cfa_ops::DW_CFA_advance_loc2, 0x03);
assert_eq!(cfa_ops::DW_CFA_advance_loc4, 0x04);
assert_eq!(cfa_ops::DW_CFA_def_cfa, 0x0C);
assert_eq!(cfa_ops::DW_CFA_def_cfa_offset, 0x0E);
assert_eq!(cfa_ops::DW_CFA_lo_user, 0x1C);
assert_eq!(cfa_ops::DW_CFA_hi_user, 0x3F);
}
#[test]
fn test_frame_emitter_new() {
let emitter = FrameEmitter::new(false, 8);
assert!(!emitter.is_eh_frame);
assert_eq!(emitter.address_size, 8);
assert!(emitter.cies.is_empty());
assert!(emitter.fdes.is_empty());
let emitter = FrameEmitter::new(true, 4);
assert!(emitter.is_eh_frame);
assert_eq!(emitter.address_size, 4);
}
#[test]
fn test_frame_emitter_add_cie() {
let mut emitter = FrameEmitter::new(false, 8);
let idx = emitter.add_cie(CIE::aarch64());
assert_eq!(idx, 0);
assert_eq!(emitter.cies.len(), 1);
}
#[test]
fn test_frame_emitter_add_fde() {
let mut emitter = FrameEmitter::new(false, 8);
emitter.add_cie(CIE::x86_64());
emitter.add_fde(FDE::for_function(0, 0x1000, 0x40));
assert_eq!(emitter.fdes.len(), 1);
}
#[test]
fn test_frame_emitter_emit_empty() {
let mut emitter = FrameEmitter::new(false, 8);
assert!(emitter.emit().is_none());
}
#[test]
fn test_frame_emitter_emit_debug_frame() {
let mut emitter = FrameEmitter::new(false, 8);
emitter.add_cie(CIE::x86_64());
emitter.add_fde(FDE::for_function(0, 0x4000, 0x80));
let output = emitter.emit().unwrap();
assert!(output.len() > 30);
let cie_len = u32::from_le_bytes(output[0..4].try_into().unwrap());
assert!(cie_len > 0);
let cie_id = u32::from_le_bytes(output[4..8].try_into().unwrap());
assert_eq!(cie_id, 0xFFFFFFFF);
}
#[test]
fn test_frame_emitter_emit_eh_frame() {
let mut emitter = FrameEmitter::new(true, 8);
emitter.add_cie(CIE::eh_frame_x86_64());
emitter.add_fde(FDE::for_function(0, 0x2000, 0x100));
let output = emitter.emit().unwrap();
assert!(output.len() > 20);
let cie_id = u32::from_le_bytes(output[4..8].try_into().unwrap());
assert_eq!(cie_id, 0);
}
#[test]
fn test_frame_emitter_multiple_cies() {
let mut emitter = FrameEmitter::new(false, 8);
emitter.add_cie(CIE::aarch64());
emitter.add_cie(CIE::x86_64());
emitter.add_fde(FDE::for_function(0, 0x1000, 0x30));
emitter.add_fde(FDE::for_function(1, 0x2000, 0x40));
let output = emitter.emit().unwrap();
assert!(output.len() > 60);
}
#[test]
fn test_prologue_aarch64() {
let mut b = CFIBuilder::new(false);
b.emit_aarch64_prologue_cfi();
assert!(!b.instructions.is_empty());
assert!(b.instructions.contains(&cfa_ops::DW_CFA_def_cfa_offset));
}
#[test]
fn test_prologue_x86_64() {
let mut b = CFIBuilder::new(false);
b.emit_x86_64_prologue_cfi();
assert!(!b.instructions.is_empty());
assert!(b.instructions.contains(&cfa_ops::DW_CFA_def_cfa_offset));
}
#[test]
fn test_val_offset() {
let mut b = CFIBuilder::new(false);
b.val_offset(16, 2);
assert_eq!(b.instructions[0], cfa_ops::DW_CFA_val_offset);
}
#[test]
fn test_val_offset_sf() {
let mut b = CFIBuilder::new(false);
b.val_offset_sf(16, -16);
assert_eq!(b.instructions[0], cfa_ops::DW_CFA_val_offset_sf);
}
#[test]
fn test_val_expression() {
let mut b = CFIBuilder::new(false);
b.val_expression(16, &[0x50, 0x30]); assert_eq!(b.instructions[0], cfa_ops::DW_CFA_val_expression);
}
#[test]
fn test_expression() {
let mut b = CFIBuilder::new(false);
b.expression(7, &[0x50]);
assert_eq!(b.instructions[0], cfa_ops::DW_CFA_expression);
}
#[test]
fn test_offset_extended_sf() {
let mut b = CFIBuilder::new(false);
b.offset_extended_sf(30, -2);
assert_eq!(b.instructions[0], cfa_ops::DW_CFA_offset_extended_sf);
}
#[test]
fn test_ptr_encoding_constants() {
assert_eq!(ptr_encoding::DW_EH_PE_pcrel, 0x10);
assert_eq!(ptr_encoding::DW_EH_PE_sdata4, 0x0B);
assert_eq!(ptr_encoding::DW_EH_PE_absptr, 0x00);
assert_eq!(ptr_encoding::DW_EH_PE_omit, 0xFF);
assert_eq!(ptr_encoding::DW_EH_PE_indirect, 0x80);
}
#[test]
fn test_emit_fde_with_instructions() {
let cie = CIE::x86_64();
let b = CFIBuilder::new(false);
let mut fde = FDE::for_function(0, 0x1000, 0x50);
let mut instr_builder = CFIBuilder::new(false);
instr_builder.advance_loc(4);
instr_builder.def_cfa_offset(32);
fde.instructions = instr_builder.instructions;
let bytes = b.emit_fde(&fde, &cie, 8);
assert!(bytes.len() > 20);
assert!(bytes.contains(&cfa_ops::DW_CFA_def_cfa_offset));
}
#[test]
fn test_def_cfa_expression() {
let mut b = CFIBuilder::new(false);
b.def_cfa_expression(&[0x12, 0x34]);
assert_eq!(b.instructions[0], cfa_ops::DW_CFA_def_cfa_expression);
}
#[test]
fn test_eh_frame_roundtrip_single_cie_fde() {
let mut emitter = FrameEmitter::new(true, 8);
let cie = CIE::eh_frame_x86_64();
emitter.add_cie(cie.clone());
emitter.add_fde(FDE::for_function(0, 0x400000, 0x200));
let output = emitter.emit().unwrap();
let cie_len = u32::from_le_bytes(output[0..4].try_into().unwrap());
assert!(cie_len >= 16);
let cie_end = 4 + cie_len as usize;
assert!(output.len() > cie_end + 4);
let fde_len = u32::from_le_bytes(output[cie_end..cie_end + 4].try_into().unwrap());
assert!(fde_len > 0);
}
#[test]
fn test_debug_frame_roundtrip_single_cie_fde() {
let mut emitter = FrameEmitter::new(false, 8);
let cie = CIE::aarch64();
emitter.add_cie(cie.clone());
emitter.add_fde(FDE::for_function(0, 0x400000, 0x100));
let output = emitter.emit().unwrap();
let cie_id = u32::from_le_bytes(output[4..8].try_into().unwrap());
assert_eq!(cie_id, 0xFFFFFFFF);
let cie_len = u32::from_le_bytes(output[0..4].try_into().unwrap());
let cie_end = 4 + cie_len as usize;
assert!(output.len() > cie_end);
}
#[test]
fn test_advance_loc_boundary() {
let mut b = CFIBuilder::new(false);
b.advance_loc(63);
assert_eq!(b.instructions[0], 0x40 | 63);
assert_eq!(b.instructions.len(), 1);
let mut b = CFIBuilder::new(false);
b.advance_loc(64);
assert_eq!(b.instructions[0], cfa_ops::DW_CFA_advance_loc1);
assert_eq!(b.instructions[1], 64);
}
}