#![allow(non_upper_case_globals, dead_code)]
use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt;
use std::mem;
use crate::codegen::MachineInstr;
pub const X86_EH_MAX_LANDINGPADS: usize = 128;
pub const X86_EH_MAX_CATCH_CLAUSES: usize = 64;
pub const X86_EH_MAX_ACTIONS: usize = 256;
pub const X86_EH_LSDA_ENCODING: u8 = 0xFF;
pub const X86_EH_FDE_ENCODING: u8 = 0x00;
pub const X86_EH_PERSONALITY_ENCODING: u8 = 0x9B;
pub const X86_EH_CALLSITE_ENCODING: u8 = 0x01;
pub const X86_EH_CXA_HEADER_SIZE: usize = 128;
pub const X86_EH_UNWIND_CODE_SIZE: usize = 2;
pub const X86_EH_MAX_UNWIND_CODES: usize = 256;
pub const X86_EH_MAX_FILTERS: usize = 16;
pub const X86_EH_TYPEINFO_ENCODING: u8 = 0x00;
pub const X86_EH_LSDA_ALIGNMENT: u32 = 4;
pub const X86_EH_CALLSITE_ALIGNMENT: u32 = 1;
const DW_RAX: u16 = 0;
const DW_RDX: u16 = 1;
const DW_RCX: u16 = 2;
const DW_RBX: u16 = 3;
const DW_RSI: u16 = 4;
const DW_RDI: u16 = 5;
const DW_RBP: u16 = 6;
const DW_RSP: u16 = 7;
const DW_R8: u16 = 8;
const DW_R9: u16 = 9;
const DW_R10: u16 = 10;
const DW_R11: u16 = 11;
const DW_R12: u16 = 12;
const DW_R13: u16 = 13;
const DW_R14: u16 = 14;
const DW_R15: u16 = 15;
const DW_RIP: u16 = 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86PersonalityKind {
GxxPersonalityV0,
GccPersonalityV0,
CxxFrameHandler3,
CSpecificHandler,
ObjcPersonalityV0,
GxxPersonalitySeh0,
GnatPersonalityV0,
GoPersonalityV0,
}
impl X86PersonalityKind {
pub fn symbol_name(&self) -> &'static str {
match self {
Self::GxxPersonalityV0 => "__gxx_personality_v0",
Self::GccPersonalityV0 => "__gcc_personality_v0",
Self::CxxFrameHandler3 => "__CxxFrameHandler3",
Self::CSpecificHandler => "__C_specific_handler",
Self::ObjcPersonalityV0 => "__objc_personality_v0",
Self::GxxPersonalitySeh0 => "__gxx_personality_seh0",
Self::GnatPersonalityV0 => "__gnat_personality_v0",
Self::GoPersonalityV0 => "__go_personality_v0",
}
}
pub fn uses_lsda(&self) -> bool {
matches!(
self,
Self::GxxPersonalityV0
| Self::GccPersonalityV0
| Self::ObjcPersonalityV0
| Self::GxxPersonalitySeh0
| Self::GnatPersonalityV0
)
}
pub fn uses_xdata(&self) -> bool {
matches!(self, Self::CxxFrameHandler3 | Self::CSpecificHandler)
}
pub fn encoding(&self) -> u8 {
match self {
Self::CxxFrameHandler3 | Self::CSpecificHandler => 0, _ => X86_EH_PERSONALITY_ENCODING,
}
}
}
impl fmt::Display for X86PersonalityKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.symbol_name())
}
}
#[derive(Debug, Clone)]
pub struct X86PersonalityConfig {
pub kind: X86PersonalityKind,
pub supports_cleanup: bool,
pub supports_catch: bool,
pub supports_filter: bool,
pub type_matching_by_pointer: bool,
pub catch_foreign_exceptions: bool,
pub exception_header_size: usize,
}
impl Default for X86PersonalityConfig {
fn default() -> Self {
Self {
kind: X86PersonalityKind::GxxPersonalityV0,
supports_cleanup: true,
supports_catch: true,
supports_filter: true,
type_matching_by_pointer: false,
catch_foreign_exceptions: false,
exception_header_size: X86_EH_CXA_HEADER_SIZE,
}
}
}
impl X86PersonalityConfig {
pub fn gxx() -> Self {
Self {
kind: X86PersonalityKind::GxxPersonalityV0,
supports_cleanup: true,
supports_catch: true,
supports_filter: true,
type_matching_by_pointer: false,
catch_foreign_exceptions: true,
exception_header_size: X86_EH_CXA_HEADER_SIZE,
}
}
pub fn cxx_frame_handler() -> Self {
Self {
kind: X86PersonalityKind::CxxFrameHandler3,
supports_cleanup: true,
supports_catch: true,
supports_filter: false,
type_matching_by_pointer: true,
catch_foreign_exceptions: true,
exception_header_size: 160, }
}
pub fn c_specific_handler() -> Self {
Self {
kind: X86PersonalityKind::CSpecificHandler,
supports_cleanup: false,
supports_catch: false,
supports_filter: true, type_matching_by_pointer: false,
catch_foreign_exceptions: false,
exception_header_size: 0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86EHIntrinsicKind {
LandingPad,
Resume,
CleanupRet,
CatchRet,
CatchSwitch,
CatchPad,
CleanupPad,
BeginCatch,
EndCatch,
EHTypeidFor,
}
impl fmt::Display for X86EHIntrinsicKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
Self::LandingPad => "landingpad",
Self::Resume => "resume",
Self::CleanupRet => "cleanupret",
Self::CatchRet => "catchret",
Self::CatchSwitch => "catchswitch",
Self::CatchPad => "catchpad",
Self::CleanupPad => "cleanuppad",
Self::BeginCatch => "begin.catch",
Self::EndCatch => "end.catch",
Self::EHTypeidFor => "eh.typeid.for",
};
write!(f, "{}", s)
}
}
#[derive(Debug, Clone)]
pub struct X86LSDAHeader {
pub lp_start_encoding: u8,
pub ttype_encoding: u8,
pub callsite_encoding: u8,
pub lp_is_func_start: bool,
}
#[derive(Debug, Clone)]
pub struct X86TypeInfoEntry {
pub type_index: u32,
pub type_info_addr: u64,
pub encoding: u8,
pub filter_value: Option<i32>,
pub type_name: String,
}
#[derive(Debug, Clone)]
pub struct X86CallSiteEntry {
pub cs_start: u32,
pub cs_length: u32,
pub cs_landing_pad: u32,
pub cs_action: u32,
}
#[derive(Debug, Clone)]
pub struct X86ActionRecord {
pub type_filter: i32,
pub next_action_offset: i32,
}
#[derive(Debug, Clone)]
pub struct X86LSDA {
pub header: X86LSDAHeader,
pub lp_base: u64,
pub type_table: Vec<X86TypeInfoEntry>,
pub call_site_table: Vec<X86CallSiteEntry>,
pub action_table: Vec<X86ActionRecord>,
pub encoded_size: u32,
}
impl X86LSDA {
pub fn new() -> Self {
Self {
header: X86LSDAHeader {
lp_start_encoding: X86_EH_LSDA_ENCODING,
ttype_encoding: X86_EH_TYPEINFO_ENCODING,
callsite_encoding: X86_EH_CALLSITE_ENCODING,
lp_is_func_start: true,
},
lp_base: 0,
type_table: Vec::new(),
call_site_table: Vec::new(),
action_table: Vec::new(),
encoded_size: 0,
}
}
pub fn set_lp_base(&mut self, base: u64) {
self.lp_base = base;
self.header.lp_is_func_start = false;
}
pub fn add_type(&mut self, type_info_addr: u64, type_name: &str) -> u32 {
let idx = self.type_table.len() as u32;
self.type_table.push(X86TypeInfoEntry {
type_index: idx,
type_info_addr,
encoding: X86_EH_TYPEINFO_ENCODING,
filter_value: None,
type_name: type_name.to_string(),
});
idx
}
pub fn add_call_site(&mut self, start: u32, length: u32, landing_pad: u32, action_index: u32) {
self.call_site_table.push(X86CallSiteEntry {
cs_start: start,
cs_length: length,
cs_landing_pad: landing_pad,
cs_action: action_index,
});
}
pub fn add_catch_action(&mut self, type_index: u32, next: Option<u32>) -> u32 {
let idx = self.action_table.len() as u32 + 1; self.action_table.push(X86ActionRecord {
type_filter: type_index as i32 + 1, next_action_offset: next.map(|n| n as i32).unwrap_or(0),
});
idx
}
pub fn add_cleanup_action(&mut self, next: Option<u32>) -> u32 {
let idx = self.action_table.len() as u32 + 1;
self.action_table.push(X86ActionRecord {
type_filter: 0, next_action_offset: next.map(|n| n as i32).unwrap_or(0),
});
idx
}
pub fn add_filter_action(&mut self, filter_idx: u32, next: Option<u32>) -> u32 {
let idx = self.action_table.len() as u32 + 1;
self.action_table.push(X86ActionRecord {
type_filter: -((filter_idx as i32) + 1), next_action_offset: next.map(|n| n as i32).unwrap_or(0),
});
idx
}
pub fn encode(&self) -> Vec<u8> {
let mut buf = Vec::new();
buf.push(self.header.lp_start_encoding);
if !self.header.lp_is_func_start {
buf.extend_from_slice(&self.lp_base.to_le_bytes());
}
buf.push(self.header.ttype_encoding);
buf.push(1);
buf.push(self.header.callsite_encoding);
let cs_len = self.call_site_table.len() as u32 * 4 * 4; Self::encode_uleb128(&mut buf, cs_len as u64);
for cs in &self.call_site_table {
Self::encode_uleb128(&mut buf, cs.cs_start as u64);
Self::encode_uleb128(&mut buf, cs.cs_length as u64);
Self::encode_uleb128(&mut buf, cs.cs_landing_pad as u64);
Self::encode_uleb128(&mut buf, cs.cs_action as u64);
}
for action in &self.action_table {
Self::encode_sleb128(&mut buf, action.type_filter as i64);
Self::encode_sleb128(&mut buf, action.next_action_offset as i64);
}
for ti in &self.type_table {
buf.extend_from_slice(&ti.type_info_addr.to_le_bytes());
}
while buf.len() % 4 != 0 {
buf.push(0);
}
buf
}
fn encode_uleb128(buf: &mut Vec<u8>, mut value: u64) {
loop {
let mut byte = (value & 0x7F) as u8;
value >>= 7;
if value != 0 {
byte |= 0x80;
}
buf.push(byte);
if value == 0 {
break;
}
}
}
fn encode_sleb128(buf: &mut Vec<u8>, mut value: i64) {
loop {
let mut byte = (value & 0x7F) as u8;
value >>= 7;
if (value == 0 && (byte & 0x40) == 0) || (value == -1 && (byte & 0x40) != 0) {
buf.push(byte);
break;
}
byte |= 0x80;
buf.push(byte);
}
}
}
#[derive(Debug, Clone)]
pub struct X86XData {
pub flags: u8,
pub prologue_size: u8,
pub unwind_code_count: u8,
pub frame_register: u8,
pub frame_register_offset: u8,
pub unwind_codes: Vec<X86UnwindCode>,
pub handler_address: Option<u64>,
pub handler_data: Option<Vec<u8>>,
}
#[derive(Debug, Clone)]
pub enum X86UnwindCode {
PushNonVolatile {
op_reg: u8,
},
AllocLarge {
alloc_size: u32,
},
AllocSmall {
alloc_size: u8,
},
SetFPReg,
SaveNonVolatile {
op_reg: u8,
offset: u32,
},
SaveNonVolatileFar { op_reg: u8, offset: u32 },
SaveXmm128 { op_reg: u8, offset: u32 },
SaveXmm128Far { op_reg: u8, offset: u32 },
PushMachineFrame,
}
impl X86UnwindCode {
pub fn op_code(&self) -> u8 {
match self {
Self::PushNonVolatile { .. } => 0,
Self::AllocLarge { .. } => 1,
Self::AllocSmall { .. } => 2,
Self::SetFPReg => 3,
Self::SaveNonVolatile { .. } => 4,
Self::SaveNonVolatileFar { .. } => 5,
Self::SaveXmm128 { .. } => 8,
Self::SaveXmm128Far { .. } => 9,
Self::PushMachineFrame => 10,
}
}
pub fn slot_count(&self) -> u8 {
match self {
Self::PushNonVolatile { .. } => 1,
Self::AllocLarge { .. } => {
if self.alloc_size() > 0xFFFF {
3
} else {
2
}
}
Self::AllocSmall { .. } => 1,
Self::SetFPReg => 1,
Self::SaveNonVolatile { .. } => 2,
Self::SaveNonVolatileFar { .. } => 3,
Self::SaveXmm128 { .. } => 2,
Self::SaveXmm128Far { .. } => 3,
Self::PushMachineFrame => 1,
}
}
fn alloc_size(&self) -> u32 {
match self {
Self::AllocLarge { alloc_size } => *alloc_size,
_ => 0,
}
}
}
impl X86XData {
pub fn new() -> Self {
Self {
flags: 0,
prologue_size: 0,
unwind_code_count: 0,
frame_register: 0,
frame_register_offset: 0,
unwind_codes: Vec::new(),
handler_address: None,
handler_data: None,
}
}
pub fn set_handler(&mut self, handler_addr: u64) {
self.handler_address = Some(handler_addr);
self.flags |= 0x01; }
pub fn set_termination_handler(&mut self, handler_addr: u64) {
self.handler_address = Some(handler_addr);
self.flags |= 0x02; }
pub fn clear_handler(&mut self) {
self.handler_address = None;
self.handler_data = None;
self.flags &= !0x03;
}
pub fn add_unwind_code(&mut self, code: X86UnwindCode) {
self.unwind_codes.push(code);
self.unwind_code_count = self.unwind_codes.len() as u8;
}
pub fn encode(&self) -> Vec<u8> {
let mut buf = Vec::new();
let version_plus_flags = 1 | (self.flags << 3);
buf.push(version_plus_flags);
buf.push(self.prologue_size);
buf.push(self.unwind_code_count);
let frame_info = (self.frame_register & 0x0F) | ((self.frame_register_offset & 0x0F) << 4);
buf.push(frame_info);
let mut slot_buf = Vec::new();
for code in &self.unwind_codes {
let op = code.op_code();
match code {
X86UnwindCode::PushNonVolatile { op_reg } => {
slot_buf.push(*op_reg);
slot_buf.push(0);
}
X86UnwindCode::AllocLarge { alloc_size } => {
let sz = *alloc_size;
if sz <= 0xFFFF {
slot_buf.push((sz & 0xFF) as u8);
slot_buf.push(((sz >> 8) & 0xFF) as u8);
} else {
slot_buf.push((sz & 0xFF) as u8);
slot_buf.push(((sz >> 8) & 0xFF) as u8);
slot_buf.push(((sz >> 16) & 0xFF) as u8);
slot_buf.push(((sz >> 24) & 0xFF) as u8);
}
}
X86UnwindCode::AllocSmall { alloc_size } => {
slot_buf.push(*alloc_size);
slot_buf.push(0);
}
X86UnwindCode::SetFPReg => {
slot_buf.push(0);
slot_buf.push(0);
}
X86UnwindCode::SaveNonVolatile { op_reg, offset } => {
slot_buf.push(*op_reg);
let off = *offset / 8;
slot_buf.push((off & 0xFF) as u8);
slot_buf.push(((off >> 8) & 0xFF) as u8);
}
X86UnwindCode::SaveNonVolatileFar { op_reg, offset } => {
slot_buf.push(*op_reg);
slot_buf.extend_from_slice(&offset.to_le_bytes());
}
X86UnwindCode::SaveXmm128 { op_reg, offset } => {
slot_buf.push(*op_reg);
let off = *offset / 16;
slot_buf.push((off & 0xFF) as u8);
slot_buf.push(((off >> 8) & 0xFF) as u8);
}
X86UnwindCode::SaveXmm128Far { op_reg, offset } => {
slot_buf.push(*op_reg);
slot_buf.extend_from_slice(&offset.to_le_bytes());
}
X86UnwindCode::PushMachineFrame => {
slot_buf.push(0);
slot_buf.push(0);
}
}
}
slot_buf.reverse();
buf.extend_from_slice(&slot_buf);
while buf.len() % 4 != 0 {
buf.push(0);
}
if self.flags & 0x03 != 0 {
if let Some(addr) = self.handler_address {
buf.extend_from_slice(&(addr as u32).to_le_bytes());
} else {
buf.extend_from_slice(&0u32.to_le_bytes());
}
}
if let Some(ref data) = self.handler_data {
buf.extend_from_slice(data);
}
buf
}
}
#[derive(Debug, Clone)]
pub struct X86LandingPad {
pub id: u32,
pub offset: u32,
pub clauses: Vec<X86LandingPadClause>,
pub has_cleanup: bool,
pub dispatch_block: Option<u64>,
}
#[derive(Debug, Clone)]
pub enum X86LandingPadClause {
Catch {
type_index: u32,
type_name: String,
handler_block: Option<u64>,
is_catch_all: bool,
},
Filter {
filter_index: u32,
allowed_types: Vec<u32>,
},
Cleanup {
cleanup_block: Option<u64>,
},
}
impl X86LandingPad {
pub fn new(id: u32, offset: u32) -> Self {
Self {
id,
offset,
clauses: Vec::new(),
has_cleanup: false,
dispatch_block: None,
}
}
pub fn add_catch(&mut self, type_index: u32, type_name: &str, is_catch_all: bool) {
self.clauses.push(X86LandingPadClause::Catch {
type_index,
type_name: type_name.to_string(),
handler_block: None,
is_catch_all,
});
}
pub fn add_cleanup(&mut self) {
self.clauses.push(X86LandingPadClause::Cleanup {
cleanup_block: None,
});
self.has_cleanup = true;
}
pub fn add_filter(&mut self, filter_index: u32, allowed_types: Vec<u32>) {
self.clauses.push(X86LandingPadClause::Filter {
filter_index,
allowed_types,
});
}
pub fn num_catches(&self) -> usize {
self.clauses
.iter()
.filter(|c| matches!(c, X86LandingPadClause::Catch { .. }))
.count()
}
pub fn num_filters(&self) -> usize {
self.clauses
.iter()
.filter(|c| matches!(c, X86LandingPadClause::Filter { .. }))
.count()
}
}
pub struct X86CatchDispatch {
landing_pads: Vec<X86LandingPad>,
type_table: Vec<X86TypeInfoEntry>,
}
impl X86CatchDispatch {
pub fn new() -> Self {
Self {
landing_pads: Vec::new(),
type_table: Vec::new(),
}
}
pub fn register_landing_pad(&mut self, lp: X86LandingPad) {
self.landing_pads.push(lp);
}
pub fn register_type(&mut self, type_info: X86TypeInfoEntry) {
self.type_table.push(type_info);
}
pub fn generate_dispatch_sequence(&self, lp: &X86LandingPad) -> Vec<u8> {
let mut seq = Vec::new();
seq.push(0x49);
seq.push(0x89);
seq.push(0xC4);
seq.push(0x83); seq.push(0xFA);
seq.push(0x01);
seq.push(0x74); seq.push(0x10);
seq.push(0x85); seq.push(0xD2);
seq.push(0x74); seq.push(0x20);
seq.push(0x83); seq.push(0xEA);
seq.push(0x02);
let num_types = lp.num_catches() as u8;
seq.push(0x83); seq.push(0xFA);
seq.push(num_types);
seq.push(0x77); seq.push(0x30);
for (i, clause) in lp.clauses.iter().enumerate() {
if let X86LandingPadClause::Catch { .. } = clause {
seq.push(0x83); seq.push(0xFA);
seq.push(i as u8);
seq.push(0x74); seq.push(0x08); seq.push(0xEB);
seq.push(0x06);
}
}
seq.push(0x48); seq.push(0x89);
seq.push(0xE7);
seq.push(0xE8); seq.extend_from_slice(&0u32.to_le_bytes());
seq
}
pub fn generate_pointer_adjustment(base_offset: i32, result_reg: u16) -> Vec<u8> {
let mut seq = Vec::new();
if base_offset != 0 {
seq.push(0x48);
seq.push(0x8D); let modrm = (0x80u16 | (result_reg & 0x07)) as u8; seq.push(modrm);
seq.extend_from_slice(&base_offset.to_le_bytes());
}
seq
}
}
pub struct X86CleanupGenerator {
destructors: Vec<X86CleanupDestructor>,
}
#[derive(Debug, Clone)]
pub struct X86CleanupDestructor {
pub object_ptr_offset: i32,
pub destructor_fn: Option<u64>,
pub is_array: bool,
pub unconditional: bool,
}
impl X86CleanupGenerator {
pub fn new() -> Self {
Self {
destructors: Vec::new(),
}
}
pub fn register_destructor(&mut self, dtor: X86CleanupDestructor) {
self.destructors.push(dtor);
}
pub fn generate_cleanup_sequence(&self) -> Vec<u8> {
let mut seq = Vec::new();
for dtor in self.destructors.iter().rev() {
seq.push(0x48);
seq.push(0x8D); seq.push(0xBD); seq.extend_from_slice(&(dtor.object_ptr_offset as u32).to_le_bytes());
seq.push(0xE8); seq.extend_from_slice(&0u32.to_le_bytes()); }
seq
}
pub fn generate_cleanuppad_prologue(&self) -> Vec<u8> {
let mut seq = Vec::new();
seq.push(0x55);
seq.push(0x48);
seq.push(0x89);
seq.push(0xE5);
seq.push(0x53); seq
}
pub fn generate_cleanupret() -> Vec<u8> {
vec![0x5B, 0x5D, 0xC3] }
}
#[derive(Debug, Clone)]
pub struct X86TerminateHandler {
pub terminate_fn_addr: u64,
pub unexpected_fn_addr: u64,
pub noexcept_calls_terminate: bool,
}
impl Default for X86TerminateHandler {
fn default() -> Self {
Self {
terminate_fn_addr: 0,
unexpected_fn_addr: 0,
noexcept_calls_terminate: true,
}
}
}
impl X86TerminateHandler {
pub fn generate_terminate_call(&self) -> Vec<u8> {
let mut seq = Vec::new();
seq.push(0xFF); seq.push(0x15); seq.extend_from_slice(&0u32.to_le_bytes()); seq
}
pub fn generate_noexcept_check(&self) -> Vec<u8> {
let mut seq = Vec::new();
seq.push(0x64); seq.push(0x48);
seq.push(0x8B); seq.push(0x04); seq.push(0x25); seq.extend_from_slice(&0u32.to_le_bytes());
seq.push(0x83); seq.push(0x78); seq.push(0x04); seq.push(0x00);
seq.push(0x74); seq.push(0x05);
seq.push(0xE8);
seq.extend_from_slice(&0u32.to_le_bytes());
seq
}
}
#[derive(Debug, Clone)]
pub struct X86TMEHStub {
pub xbegin_fallback: u64,
pub abort_code: u32,
pub explicit_abort: bool,
}
impl X86TMEHStub {
pub fn new(fallback_addr: u64) -> Self {
Self {
xbegin_fallback: fallback_addr,
abort_code: 0,
explicit_abort: false,
}
}
pub fn generate_xbegin_sequence(&self) -> Vec<u8> {
let mut seq = Vec::new();
seq.push(0xC7); seq.push(0xF8);
let rel = (self.xbegin_fallback as i32).wrapping_sub(0);
seq.extend_from_slice(&rel.to_le_bytes());
seq
}
pub fn generate_xend() -> Vec<u8> {
vec![0x0F, 0x01, 0xD5] }
pub fn generate_xabort(abort_code: u8) -> Vec<u8> {
vec![0xC6, 0xF8, abort_code] }
pub fn generate_abort_dispatch(&self) -> Vec<u8> {
let mut seq = Vec::new();
seq.push(0xA9); seq.extend_from_slice(&1u32.to_le_bytes());
seq.push(0x75); seq.push(0x10);
seq.push(0xA9);
seq.extend_from_slice(&2u32.to_le_bytes());
seq.push(0x75); seq.push(0x10);
seq.push(0xA9);
seq.extend_from_slice(&4u32.to_le_bytes());
seq.push(0x75); seq.push(0x10);
seq.push(0xEB); seq.push(0x0A);
seq
}
}
#[derive(Debug, Clone)]
pub struct X86EHLoweringConfig {
pub personality: X86PersonalityKind,
pub emit_lsda: bool,
pub emit_xdata: bool,
pub emit_eh_frame: bool,
pub outline_landing_pads: bool,
pub merge_landing_pads: bool,
pub prune_unreachable_handlers: bool,
pub noexcept_terminate: bool,
pub target_triple: String,
}
impl Default for X86EHLoweringConfig {
fn default() -> Self {
Self {
personality: X86PersonalityKind::GxxPersonalityV0,
emit_lsda: true,
emit_xdata: false,
emit_eh_frame: true,
outline_landing_pads: false,
merge_landing_pads: true,
prune_unreachable_handlers: true,
noexcept_terminate: true,
target_triple: "x86_64-unknown-linux-gnu".to_string(),
}
}
}
pub struct X86EHLowering {
pub config: X86EHLoweringConfig,
personality_config: X86PersonalityConfig,
lsda: X86LSDA,
xdata: X86XData,
landing_pads: Vec<X86LandingPad>,
catch_dispatch: X86CatchDispatch,
cleanup_generator: X86CleanupGenerator,
terminate_handler: X86TerminateHandler,
tm_stubs: Vec<X86TMEHStub>,
current_function: Option<String>,
next_lp_id: u32,
}
impl X86EHLowering {
pub fn new(config: X86EHLoweringConfig) -> Self {
let personality_config = match config.personality {
X86PersonalityKind::GxxPersonalityV0 => X86PersonalityConfig::gxx(),
X86PersonalityKind::CxxFrameHandler3 => X86PersonalityConfig::cxx_frame_handler(),
X86PersonalityKind::CSpecificHandler => X86PersonalityConfig::c_specific_handler(),
_ => X86PersonalityConfig::default(),
};
Self {
config,
personality_config,
lsda: X86LSDA::new(),
xdata: X86XData::new(),
landing_pads: Vec::new(),
catch_dispatch: X86CatchDispatch::new(),
cleanup_generator: X86CleanupGenerator::new(),
terminate_handler: X86TerminateHandler::default(),
tm_stubs: Vec::new(),
current_function: None,
next_lp_id: 0,
}
}
pub fn begin_function(&mut self, name: &str, _address: u64) {
self.current_function = Some(name.to_string());
self.landing_pads.clear();
self.lsda = X86LSDA::new();
self.xdata = X86XData::new();
self.next_lp_id = 0;
}
pub fn add_landing_pad(&mut self, offset: u32) -> u32 {
let id = self.next_lp_id;
self.next_lp_id += 1;
self.landing_pads.push(X86LandingPad::new(id, offset));
id
}
pub fn add_catch_clause(
&mut self,
lp_id: u32,
type_info_addr: u64,
type_name: &str,
is_catch_all: bool,
) -> Option<u32> {
let type_idx = if is_catch_all {
u32::MAX } else {
self.lsda.add_type(type_info_addr, type_name)
};
if let Some(lp) = self.landing_pads.iter_mut().find(|lp| lp.id == lp_id) {
lp.add_catch(type_idx, type_name, is_catch_all);
Some(type_idx)
} else {
None
}
}
pub fn add_cleanup_clause(&mut self, lp_id: u32) -> bool {
if let Some(lp) = self.landing_pads.iter_mut().find(|lp| lp.id == lp_id) {
lp.add_cleanup();
true
} else {
false
}
}
pub fn add_filter_clause(
&mut self,
lp_id: u32,
allowed_type_addrs: &[(u64, String)],
) -> Option<u32> {
let filter_idx = self.lsda.type_table.len() as u32;
let mut allowed_indices = Vec::new();
for (addr, name) in allowed_type_addrs {
let idx = self.lsda.add_type(*addr, name);
allowed_indices.push(idx);
}
if let Some(lp) = self.landing_pads.iter_mut().find(|lp| lp.id == lp_id) {
lp.add_filter(filter_idx, allowed_indices);
Some(filter_idx)
} else {
None
}
}
pub fn register_cleanup_destructor(
&mut self,
object_ptr_offset: i32,
destructor_fn: Option<u64>,
) {
self.cleanup_generator
.register_destructor(X86CleanupDestructor {
object_ptr_offset,
destructor_fn,
is_array: false,
unconditional: true,
});
}
pub fn set_terminate_handler(&mut self, addr: u64) {
self.terminate_handler.terminate_fn_addr = addr;
}
pub fn set_unexpected_handler(&mut self, addr: u64) {
self.terminate_handler.unexpected_fn_addr = addr;
}
pub fn add_tm_stub(&mut self, stub: X86TMEHStub) {
self.tm_stubs.push(stub);
}
pub fn merge_landing_pads(&mut self) -> usize {
if !self.config.merge_landing_pads {
return 0;
}
let mut removed = 0;
let mut i = 0;
while i < self.landing_pads.len() {
let mut j = i + 1;
while j < self.landing_pads.len() {
if self.landing_pads[i].clauses.len() == self.landing_pads[j].clauses.len()
&& self.landing_pads[i].has_cleanup == self.landing_pads[j].has_cleanup
{
self.landing_pads.remove(j);
removed += 1;
} else {
j += 1;
}
}
i += 1;
}
removed
}
pub fn prune_unreachable_handlers(&mut self, _reachable_lps: &HashSet<u32>) -> usize {
if !self.config.prune_unreachable_handlers {
return 0;
}
0
}
pub fn build_lsda(&mut self, func_start: u64) -> &X86LSDA {
for lp in &self.landing_pads {
let mut prev_action: Option<u32> = None;
for clause in lp.clauses.iter().rev() {
match clause {
X86LandingPadClause::Catch {
type_index,
is_catch_all,
..
} => {
if *is_catch_all {
let action_idx = self.lsda.add_cleanup_action(prev_action);
prev_action = Some(action_idx);
} else {
let action_idx = self.lsda.add_catch_action(*type_index, prev_action);
prev_action = Some(action_idx);
}
}
X86LandingPadClause::Cleanup { .. } => {
let action_idx = self.lsda.add_cleanup_action(prev_action);
prev_action = Some(action_idx);
}
X86LandingPadClause::Filter { filter_index, .. } => {
let action_idx = self.lsda.add_filter_action(*filter_index, prev_action);
prev_action = Some(action_idx);
}
}
}
let cs_action = prev_action.unwrap_or(0);
self.lsda.add_call_site(
0, 0, lp.offset, cs_action,
);
}
&self.lsda
}
pub fn build_xdata(&mut self) -> &X86XData {
if let Some(handler) = self.xdata.handler_address {
if handler != 0 {
self.xdata.set_handler(handler);
}
}
&self.xdata
}
pub fn generate_landing_pad_dispatch(&self, lp_id: u32) -> Option<Vec<u8>> {
let lp = self.landing_pads.iter().find(|lp| lp.id == lp_id)?;
Some(self.catch_dispatch.generate_dispatch_sequence(lp))
}
pub fn generate_resume_sequence() -> Vec<u8> {
let mut seq = Vec::new();
seq.push(0x4C);
seq.push(0x89);
seq.push(0xE7);
seq.push(0xE8);
seq.extend_from_slice(&0u32.to_le_bytes());
seq
}
pub fn end_function(&mut self, func_start: u64) -> X86EHLoweringResult {
self.build_lsda(func_start);
self.build_xdata();
X86EHLoweringResult {
function_name: self.current_function.take().unwrap_or_default(),
personality: self.config.personality,
lsda: self.lsda.clone(),
xdata: self.xdata.clone(),
landing_pads: self.landing_pads.clone(),
lsda_encoded: self.lsda.encode(),
xdata_encoded: self.xdata.encode(),
has_cleanup: self.landing_pads.iter().any(|lp| lp.has_cleanup),
num_landing_pads: self.landing_pads.len(),
num_types: self.lsda.type_table.len(),
}
}
pub fn personality_symbol(&self) -> &'static str {
self.personality_config.kind.symbol_name()
}
}
#[derive(Debug, Clone)]
pub struct X86EHLoweringResult {
pub function_name: String,
pub personality: X86PersonalityKind,
pub lsda: X86LSDA,
pub xdata: X86XData,
pub landing_pads: Vec<X86LandingPad>,
pub lsda_encoded: Vec<u8>,
pub xdata_encoded: Vec<u8>,
pub has_cleanup: bool,
pub num_landing_pads: usize,
pub num_types: usize,
}
impl X86EHLoweringResult {
pub fn needs_lsda(&self) -> bool {
self.personality.uses_lsda() && !self.landing_pads.is_empty()
}
pub fn needs_xdata(&self) -> bool {
self.personality.uses_xdata() && !self.landing_pads.is_empty()
}
pub fn eh_data_size(&self) -> usize {
self.lsda_encoded.len() + self.xdata_encoded.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_personality_kinds() {
assert_eq!(
X86PersonalityKind::GxxPersonalityV0.symbol_name(),
"__gxx_personality_v0"
);
assert!(X86PersonalityKind::GxxPersonalityV0.uses_lsda());
assert!(!X86PersonalityKind::GxxPersonalityV0.uses_xdata());
assert_eq!(
X86PersonalityKind::CxxFrameHandler3.symbol_name(),
"__CxxFrameHandler3"
);
assert!(!X86PersonalityKind::CxxFrameHandler3.uses_lsda());
assert!(X86PersonalityKind::CxxFrameHandler3.uses_xdata());
}
#[test]
fn test_lsda_encoding() {
let mut lsda = X86LSDA::new();
let ti0 = lsda.add_type(0x401000, "std::exception");
let ti1 = lsda.add_type(0x402000, "MyException");
lsda.add_call_site(0x10, 0x20, 0x100, 0);
let catch_idx = lsda.add_catch_action(ti0, None);
lsda.add_call_site(0x40, 0x30, 0x200, catch_idx);
let encoded = lsda.encode();
assert!(!encoded.is_empty());
assert_eq!(encoded[0], X86_EH_LSDA_ENCODING);
}
#[test]
fn test_lsda_action_table() {
let mut lsda = X86LSDA::new();
let ti0 = lsda.add_type(0x401000, "TypeA");
let ti1 = lsda.add_type(0x402000, "TypeB");
let action_b = lsda.add_catch_action(ti1, None);
let action_a = lsda.add_catch_action(ti0, Some(action_b));
assert_eq!(lsda.action_table.len(), 2);
assert_eq!(lsda.action_table[0].type_filter, ti0 as i32 + 1);
assert_eq!(lsda.action_table[0].next_action_offset, action_b as i32);
}
#[test]
fn test_lsda_cleanup_action() {
let mut lsda = X86LSDA::new();
let cleanup_idx = lsda.add_cleanup_action(None);
assert_eq!(lsda.action_table.len(), 1);
assert_eq!(lsda.action_table[0].type_filter, 0); }
#[test]
fn test_lsda_filter_action() {
let mut lsda = X86LSDA::new();
lsda.add_type(0x401000, "TypeA");
lsda.add_type(0x402000, "TypeB");
let filter_idx = lsda.add_filter_action(0, None);
assert_eq!(lsda.action_table.len(), 1);
assert_eq!(lsda.action_table[0].type_filter, -1); }
#[test]
fn test_landing_pad_creation() {
let mut lp = X86LandingPad::new(0, 0x100);
lp.add_catch(0, "std::exception", false);
lp.add_cleanup();
lp.add_catch(1, "MyException", false);
assert_eq!(lp.num_catches(), 2);
assert!(lp.has_cleanup);
assert_eq!(lp.num_filters(), 0);
}
#[test]
fn test_xdata_encoding() {
let mut xdata = X86XData::new();
xdata.prologue_size = 16;
xdata.frame_register = DW_RBP as u8;
xdata.frame_register_offset = 2;
xdata.add_unwind_code(X86UnwindCode::PushNonVolatile {
op_reg: DW_RBP as u8,
});
xdata.add_unwind_code(X86UnwindCode::AllocSmall { alloc_size: 2 });
let encoded = xdata.encode();
assert!(!encoded.is_empty());
assert_eq!(encoded[0] & 0x07, 1); }
#[test]
fn test_xdata_with_handler() {
let mut xdata = X86XData::new();
xdata.set_handler(0x401000);
let encoded = xdata.encode();
assert!(!encoded.is_empty());
assert!(encoded[0] & 0x08 != 0); }
#[test]
fn test_catch_dispatch_generation() {
let mut lp = X86LandingPad::new(0, 0x100);
lp.add_catch(0, "std::runtime_error", false);
let dispatch = X86CatchDispatch::new();
let seq = dispatch.generate_dispatch_sequence(&lp);
assert!(seq.len() > 10);
}
#[test]
fn test_cleanup_sequence_generation() {
let mut r#gen = X86CleanupGenerator::new();
r#gen.register_destructor(X86CleanupDestructor {
object_ptr_offset: -8,
destructor_fn: Some(0x401000),
is_array: false,
unconditional: true,
});
let seq = r#gen.generate_cleanup_sequence();
assert!(seq.len() > 5);
}
#[test]
fn test_terminate_handler() {
let handler = X86TerminateHandler {
terminate_fn_addr: 0x405000,
unexpected_fn_addr: 0x406000,
noexcept_calls_terminate: true,
};
let seq = handler.generate_terminate_call();
assert_eq!(seq[0], 0xFF); assert_eq!(seq[1], 0x15);
let noex = handler.generate_noexcept_check();
assert!(noex.len() > 10);
}
#[test]
fn test_tm_stubs() {
let stub = X86TMEHStub::new(0x401000);
let xbegin = stub.generate_xbegin_sequence();
assert_eq!(xbegin[0], 0xC7); assert_eq!(xbegin[1], 0xF8);
let xend = X86TMEHStub::generate_xend();
assert_eq!(xend, vec![0x0F, 0x01, 0xD5]);
let xabort = X86TMEHStub::generate_xabort(42);
assert_eq!(xabort[0], 0xC6);
assert_eq!(xabort[1], 0xF8);
assert_eq!(xabort[2], 42);
}
#[test]
fn test_eh_lowering_full_pipeline() {
let config = X86EHLoweringConfig::default();
let mut lowering = X86EHLowering::new(config);
lowering.begin_function("test_eh", 0x400000);
let lp_id = lowering.add_landing_pad(0x120);
lowering.add_catch_clause(lp_id, 0x500000, "std::exception", false);
lowering.add_cleanup_clause(lp_id);
let lp2_id = lowering.add_landing_pad(0x200);
lowering.add_catch_clause(lp2_id, 0x501000, "MyException", false);
lowering.add_catch_clause(lp2_id, 0, "...", true);
lowering.register_cleanup_destructor(-16, Some(0x502000));
let merged = lowering.merge_landing_pads();
let _ = merged;
let result = lowering.end_function(0x400000);
assert_eq!(result.function_name, "test_eh");
assert_eq!(result.personality, X86PersonalityKind::GxxPersonalityV0);
assert!(result.has_cleanup);
assert!(result.num_landing_pads > 0);
assert!(!result.lsda_encoded.is_empty());
}
#[test]
fn test_pointer_adjustment() {
let seq = X86CatchDispatch::generate_pointer_adjustment(16, 0); assert!(seq.len() > 0);
let seq2 = X86CatchDispatch::generate_pointer_adjustment(0, 0);
assert!(seq2.is_empty());
}
#[test]
fn test_resume_sequence() {
let seq = X86EHLowering::generate_resume_sequence();
assert!(seq.len() > 0);
}
#[test]
fn test_unwind_code_op_codes() {
assert_eq!(X86UnwindCode::PushNonVolatile { op_reg: 0 }.op_code(), 0);
assert_eq!(X86UnwindCode::AllocLarge { alloc_size: 0 }.op_code(), 1);
assert_eq!(X86UnwindCode::AllocSmall { alloc_size: 0 }.op_code(), 2);
assert_eq!(X86UnwindCode::SetFPReg.op_code(), 3);
}
#[test]
fn test_unwind_code_slot_counts() {
assert_eq!(X86UnwindCode::PushNonVolatile { op_reg: 0 }.slot_count(), 1);
assert_eq!(
X86UnwindCode::AllocLarge { alloc_size: 0x100 }.slot_count(),
2
);
assert_eq!(
X86UnwindCode::AllocLarge {
alloc_size: 0x10000
}
.slot_count(),
3
);
assert_eq!(X86UnwindCode::AllocSmall { alloc_size: 0 }.slot_count(), 1);
}
#[test]
fn test_landing_pad_clause_filter() {
let mut lp = X86LandingPad::new(0, 0x100);
lp.add_filter(0, vec![0, 1, 2]);
assert_eq!(lp.num_filters(), 1);
lp.add_filter(1, vec![3]);
assert_eq!(lp.num_filters(), 2);
}
#[test]
fn test_lsda_uleb128_encoding() {
let mut buf = Vec::new();
X86LSDA::encode_uleb128(&mut buf, 0);
assert_eq!(buf.len(), 1);
assert_eq!(buf[0], 0);
buf.clear();
X86LSDA::encode_uleb128(&mut buf, 127);
assert_eq!(buf.len(), 1);
assert_eq!(buf[0], 127);
buf.clear();
X86LSDA::encode_uleb128(&mut buf, 128);
assert_eq!(buf.len(), 2);
assert_eq!(buf[0], 0x80);
assert_eq!(buf[1], 0x01);
}
#[test]
fn test_lsda_sleb128_encoding() {
let mut buf = Vec::new();
X86LSDA::encode_sleb128(&mut buf, 0);
assert_eq!(buf.len(), 1);
assert_eq!(buf[0], 0);
buf.clear();
X86LSDA::encode_sleb128(&mut buf, -1);
assert_eq!(buf.len(), 1);
assert_eq!(buf[0], 0x7F);
buf.clear();
X86LSDA::encode_sleb128(&mut buf, 64);
assert_eq!(buf.len(), 1);
assert_eq!(buf[0], 0x40);
}
#[test]
fn test_eh_result_needs_lsda() {
let config = X86EHLoweringConfig::default();
let mut lowering = X86EHLowering::new(config);
lowering.begin_function("test", 0x400000);
let lp_id = lowering.add_landing_pad(0x100);
lowering.add_catch_clause(lp_id, 0x500000, "std::exception", false);
let result = lowering.end_function(0x400000);
assert!(result.needs_lsda());
assert!(!result.needs_xdata());
assert!(result.eh_data_size() > 0);
}
#[test]
fn test_seh_xdata_encoding_empty() {
let xdata = X86XData::new();
let encoded = xdata.encode();
assert!(!encoded.is_empty());
assert_eq!(encoded[0] & 0x07, 1); }
#[test]
fn test_seh_xdata_with_termination_handler() {
let mut xdata = X86XData::new();
xdata.set_termination_handler(0x500000);
assert!(xdata.flags & 0x02 != 0);
let encoded = xdata.encode();
assert!(!encoded.is_empty());
}
#[test]
fn test_seh_xdata_push_nonvolatile() {
let mut xdata = X86XData::new();
xdata.add_unwind_code(X86UnwindCode::PushNonVolatile {
op_reg: DW_RBP as u8,
});
xdata.add_unwind_code(X86UnwindCode::PushNonVolatile {
op_reg: DW_RBX as u8,
});
assert_eq!(xdata.unwind_code_count, 2);
let encoded = xdata.encode();
assert!(encoded.len() > 4);
}
#[test]
fn test_seh_xdata_alloc_small() {
let mut xdata = X86XData::new();
xdata.prologue_size = 8;
xdata.add_unwind_code(X86UnwindCode::AllocSmall { alloc_size: 1 });
let encoded = xdata.encode();
assert!(encoded.len() > 0);
}
#[test]
fn test_landing_pad_catch_all() {
let mut lp = X86LandingPad::new(0, 0x100);
lp.add_catch(0xFFFFFFFF, "...", true);
lp.add_cleanup();
assert_eq!(lp.num_catches(), 1);
assert!(lp.has_cleanup);
let has_catch_all = lp.clauses.iter().any(|c| {
matches!(
c,
X86LandingPadClause::Catch {
is_catch_all: true,
..
}
)
});
assert!(has_catch_all);
}
#[test]
fn test_lsda_multiple_call_sites() {
let mut lsda = X86LSDA::new();
lsda.add_type(0x401000, "TypeA");
lsda.add_type(0x402000, "TypeB");
lsda.add_call_site(0x10, 0x20, 0x100, 0);
let action = lsda.add_catch_action(0, None);
lsda.add_call_site(0x40, 0x30, 0x200, action);
let action_b = lsda.add_catch_action(1, None);
let action_a = lsda.add_catch_action(0, Some(action_b));
lsda.add_call_site(0x80, 0x20, 0x300, action_a);
assert_eq!(lsda.call_site_table.len(), 3);
assert_eq!(lsda.action_table.len(), 3);
}
#[test]
fn test_personality_config_gxx() {
let config = X86PersonalityConfig::gxx();
assert_eq!(config.kind, X86PersonalityKind::GxxPersonalityV0);
assert!(config.supports_cleanup);
assert!(config.supports_catch);
assert!(config.supports_filter);
assert!(config.catch_foreign_exceptions);
}
#[test]
fn test_personality_config_cxx() {
let config = X86PersonalityConfig::cxx_frame_handler();
assert_eq!(config.kind, X86PersonalityKind::CxxFrameHandler3);
assert!(config.type_matching_by_pointer);
assert!(!config.supports_filter);
}
#[test]
fn test_cleanuppad_prologue() {
let r#gen = X86CleanupGenerator::new();
let seq = r#gen.generate_cleanuppad_prologue();
assert_eq!(seq[0], 0x55); assert!(seq.len() >= 3);
}
#[test]
fn test_cleanupret_sequence() {
let seq = X86CleanupGenerator::generate_cleanupret();
assert_eq!(seq, vec![0x5B, 0x5D, 0xC3]); }
#[test]
fn test_tm_abort_dispatch() {
let stub = X86TMEHStub::new(0x401000);
let seq = stub.generate_abort_dispatch();
assert!(seq.len() > 10);
assert!(seq.contains(&0xA9));
}
#[test]
fn test_eh_lowering_with_seh_personality() {
let config = X86EHLoweringConfig {
personality: X86PersonalityKind::CxxFrameHandler3,
emit_xdata: true,
emit_lsda: false,
..Default::default()
};
let mut lowering = X86EHLowering::new(config);
lowering.begin_function("seh_func", 0x400000);
let lp_id = lowering.add_landing_pad(0x100);
lowering.add_catch_clause(lp_id, 0x500000, "std::exception", false);
let result = lowering.end_function(0x400000);
assert!(!result.needs_lsda());
assert!(result.personality.uses_xdata());
}
#[test]
fn test_eh_lowering_noexcept() {
let handler = X86TerminateHandler {
terminate_fn_addr: 0x405000,
unexpected_fn_addr: 0x406000,
noexcept_calls_terminate: true,
};
let seq = handler.generate_noexcept_check();
assert!(seq.len() > 10);
assert!(seq.contains(&0x64));
}
#[test]
fn test_lsda_all_clause_types() {
let mut lowering = X86EHLowering::new(X86EHLoweringConfig::default());
lowering.begin_function("all_clauses", 0x400000);
let lp_id = lowering.add_landing_pad(0x200);
lowering.add_catch_clause(lp_id, 0x501000, "TypeA", false);
lowering.add_cleanup_clause(lp_id);
lowering.add_catch_clause(lp_id, 0, "...", true);
lowering.add_filter_clause(
lp_id,
&[
(0x502000, "TypeB".to_string()),
(0x503000, "TypeC".to_string()),
],
);
let result = lowering.end_function(0x400000);
assert!(result.num_landing_pads == 1);
assert!(result.num_types >= 2);
assert!(result.has_cleanup);
}
#[test]
fn test_eh_intrinsic_display() {
assert_eq!(X86EHIntrinsicKind::LandingPad.to_string(), "landingpad");
assert_eq!(X86EHIntrinsicKind::Resume.to_string(), "resume");
assert_eq!(X86EHIntrinsicKind::CatchRet.to_string(), "catchret");
assert_eq!(X86EHIntrinsicKind::CleanupRet.to_string(), "cleanupret");
assert_eq!(X86EHIntrinsicKind::CatchSwitch.to_string(), "catchswitch");
assert_eq!(X86EHIntrinsicKind::CatchPad.to_string(), "catchpad");
assert_eq!(X86EHIntrinsicKind::CleanupPad.to_string(), "cleanuppad");
assert_eq!(X86EHIntrinsicKind::BeginCatch.to_string(), "begin.catch");
assert_eq!(X86EHIntrinsicKind::EndCatch.to_string(), "end.catch");
assert_eq!(X86EHIntrinsicKind::EHTypeidFor.to_string(), "eh.typeid.for");
}
#[test]
fn test_personality_kind_display() {
assert_eq!(
X86PersonalityKind::GxxPersonalityV0.to_string(),
"__gxx_personality_v0"
);
assert_eq!(
X86PersonalityKind::CxxFrameHandler3.to_string(),
"__CxxFrameHandler3"
);
assert_eq!(
X86PersonalityKind::CSpecificHandler.to_string(),
"__C_specific_handler"
);
}
#[test]
fn test_lsda_sleb128_negative_values() {
let mut buf = Vec::new();
X86LSDA::encode_sleb128(&mut buf, -2);
assert_eq!(buf.len(), 1);
assert_eq!(buf[0], 0x7E);
}
#[test]
fn test_frame_info_combination() {
let frame_info = (DW_RBP as u8 & 0x0F) | ((2u8 & 0x0F) << 4);
assert_eq!(frame_info & 0x0F, DW_RBP as u8); assert_eq!((frame_info >> 4) & 0x0F, 2); }
}