#![allow(non_upper_case_globals, dead_code)]
use std::collections::HashMap;
use std::ffi::CString;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86ExceptionKind {
CppException,
ForeignException,
Rethrow,
SEH,
Signal,
TransactionalAbort,
BadAlloc,
BadCast,
BadTypeid,
BadException,
NoexceptViolation,
Unknown,
}
impl fmt::Display for X86ExceptionKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::CppException => write!(f, "C++"),
Self::ForeignException => write!(f, "foreign"),
Self::Rethrow => write!(f, "rethrow"),
Self::SEH => write!(f, "SEH"),
Self::Signal => write!(f, "signal"),
Self::TransactionalAbort => write!(f, "transactional-abort"),
Self::BadAlloc => write!(f, "bad_alloc"),
Self::BadCast => write!(f, "bad_cast"),
Self::BadTypeid => write!(f, "bad_typeid"),
Self::BadException => write!(f, "bad_exception"),
Self::NoexceptViolation => write!(f, "noexcept-violation"),
Self::Unknown => write!(f, "unknown"),
}
}
}
#[derive(Debug, Clone)]
pub struct X86CxaException {
pub exception_type: *const X86TypeInfo,
pub exception_destructor: X86ExceptionCleanupFn,
pub unexpected_handler: X86UnexpectedHandlerFn,
pub terminate_handler: X86TerminateHandlerFn,
pub next_exception: *mut X86CxaException,
pub handler_count: i32,
pub handler_switch_value: i32,
pub action_record: *const u8,
pub language_specific_data: *const u8,
pub catch_temp: *mut u8,
pub adjusted_ptr: *mut u8,
pub saved_unexpected_handler: X86UnexpectedHandlerFn,
pub reference_count: i32,
pub exception_flags: X86ExceptionFlags,
pub _padding: [u8; 0],
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct X86ExceptionFlags: u32 {
const CAUGHT = 1 << 0;
const RETHROWN = 1 << 1;
const DEPENDENT = 1 << 2;
const FOREIGN = 1 << 3;
const FORCE_UNWIND = 1 << 4;
const HAS_CLEANUP = 1 << 5;
const PROPAGATING = 1 << 6;
const NOEXCEPT_ACTIVE = 1 << 7;
}
}
#[derive(Debug, Clone)]
pub struct X86TypeInfo {
pub name: CString,
pub base_types: Vec<*const X86TypeInfo>,
pub is_pointer: bool,
pub is_catch_all: bool,
pub type_hash: u64,
}
impl X86TypeInfo {
pub fn new(name: &str, is_pointer: bool) -> Self {
let mut hasher = std::collections::hash_map::DefaultHasher::new();
std::hash::Hash::hash(name, &mut hasher);
Self {
name: CString::new(name).unwrap_or_default(),
base_types: Vec::new(),
is_pointer,
is_catch_all: name == "...",
type_hash: std::hash::Hasher::finish(&hasher),
}
}
pub fn add_base(&mut self, base: *const X86TypeInfo) {
self.base_types.push(base);
}
pub fn can_catch(&self, other: &X86TypeInfo) -> bool {
if self.is_catch_all {
return true;
}
if self.type_hash == other.type_hash && self.name.as_c_str() == other.name.as_c_str() {
return true;
}
for base_ptr in &self.base_types {
if !base_ptr.is_null() {
unsafe {
if (**base_ptr).can_catch(other) {
return true;
}
}
}
}
false
}
}
pub type X86ExceptionCleanupFn = Option<unsafe extern "C" fn(*mut u8)>;
pub type X86UnexpectedHandlerFn = Option<unsafe extern "C" fn() -> !>;
pub type X86TerminateHandlerFn = Option<unsafe extern "C" fn() -> !>;
pub type X86PersonalityFn = unsafe extern "C" fn(
version: i32,
actions: X86UnwindAction,
exception_class: u64,
exception_object: *mut X86CxaException,
context: *mut X86UnwindContext,
) -> X86UnwindReasonCode;
pub type X86SEHFilterFn = Option<unsafe extern "C" fn(
exception_pointers: *mut X86ExceptionPointers,
) -> i32>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct X86UnwindAction(pub i32);
impl X86UnwindAction {
pub const SEARCH_PHASE: Self = Self(0x01);
pub const CLEANUP_PHASE: Self = Self(0x02);
pub const HANDLER_FRAME: Self = Self(0x04);
pub const FORCE_UNWIND: Self = Self(0x08);
pub const END_OF_STACK: Self = Self(0x10);
pub fn is_search(&self) -> bool {
self.0 & Self::SEARCH_PHASE.0 != 0
}
pub fn is_cleanup(&self) -> bool {
self.0 & Self::CLEANUP_PHASE.0 != 0
}
pub fn is_force_unwind(&self) -> bool {
self.0 & Self::FORCE_UNWIND.0 != 0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum X86UnwindReasonCode {
NoReason = 0,
ForeignExceptionCaught = 1,
FatalPhase1Error = 2,
FatalPhase2Error = 3,
NormalStop = 4,
EndOfStack = 5,
HandlerFound = 6,
InstallContext = 7,
ContinueUnwind = 8,
}
impl X86UnwindReasonCode {
pub fn from_i32(v: i32) -> Self {
match v {
0 => Self::NoReason,
1 => Self::ForeignExceptionCaught,
2 => Self::FatalPhase1Error,
3 => Self::FatalPhase2Error,
4 => Self::NormalStop,
5 => Self::EndOfStack,
6 => Self::HandlerFound,
7 => Self::InstallContext,
8 => Self::ContinueUnwind,
_ => Self::NoReason,
}
}
}
#[derive(Debug, Clone)]
pub struct X86UnwindContext {
pub registers: HashMap<u8, u64>,
pub cfa: u64,
pub ip: u64,
pub lsda: Option<*const u8>,
pub personality: Option<X86PersonalityFn>,
pub unwind_info: X86UnwindInfo,
pub signal_frame: bool,
}
impl Default for X86UnwindContext {
fn default() -> Self {
Self {
registers: HashMap::new(),
cfa: 0,
ip: 0,
lsda: None,
personality: None,
unwind_info: X86UnwindInfo::default(),
signal_frame: false,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct X86UnwindInfo {
pub begin_address: u64,
pub end_address: u64,
pub unwind_ops: Vec<X86UnwindOp>,
pub has_frame_pointer: bool,
pub frame_pointer_reg: u8,
pub stack_size: u32,
pub prologue_size: u32,
}
#[derive(Debug, Clone)]
pub enum X86UnwindOp {
PushNonVolatile { reg: u8 },
AllocLarge { size: u32 },
AllocSmall { size: u8 },
SetFramePointer { reg: u8, offset: i32 },
SaveNonVolatile { reg: u8, offset: i32 },
SaveXmm128 { reg: u8, offset: i32 },
PushMachineFrame,
Epilogue,
SpareCode,
Nop,
Custom { code: u8, data: Vec<u8> },
}
#[derive(Debug, Clone)]
pub struct X86LSDAReader {
pub data: Vec<u8>,
pub call_sites: Vec<X86CallSiteEntry>,
pub actions: Vec<X86ActionEntry>,
pub type_table: Vec<*const X86TypeInfo>,
pub landing_pad_base: i64,
pub type_table_encoding: u8,
pub call_site_encoding: u8,
}
#[derive(Debug, Clone)]
pub struct X86CallSiteEntry {
pub start_offset: u32,
pub length: u32,
pub landing_pad_offset: u32,
pub action_record_index: u32,
}
#[derive(Debug, Clone)]
pub struct X86ActionEntry {
pub type_filter: i64,
pub next_offset: i64,
}
impl X86LSDAReader {
pub fn new(data: Vec<u8>) -> Self {
Self {
data,
call_sites: Vec::new(),
actions: Vec::new(),
type_table: Vec::new(),
landing_pad_base: 0,
type_table_encoding: 0,
call_site_encoding: 0,
}
}
pub fn parse_header(&mut self, context: &X86UnwindContext) -> Result<(), String> {
if self.data.len() < 4 {
return Err("LSDA too short for header".into());
}
self.landing_pad_base = self.read_sleb128(&mut 0)?;
self.type_table_encoding = self.data[1];
if self.type_table_encoding != 0xff {
let ttype_offset = self.read_uleb128(&mut 2)? as usize;
self.parse_type_table(ttype_offset)?;
}
self.call_site_encoding = self.data[1 + 1];
let cs_offset = self.read_uleb128(&mut 2)? as usize;
self.parse_call_site_table(cs_offset)?;
Ok(())
}
fn parse_type_table(&mut self, offset: usize) -> Result<(), String> {
self.type_table.clear();
if offset >= self.data.len() {
return Ok(());
}
let mut pos = offset;
while pos < self.data.len() {
let ptr = self.read_encoded_ptr(self.type_table_encoding, &mut pos)?;
if ptr == 0 {
break;
}
self.type_table.push(ptr as *const X86TypeInfo);
}
Ok(())
}
fn parse_call_site_table(&mut self, offset: usize) -> Result<(), String> {
self.call_sites.clear();
if offset >= self.data.len() {
return Ok(());
}
let mut pos = offset;
loop {
let start = self.read_uleb128(&mut pos)? as u32;
let length = self.read_uleb128(&mut pos)? as u32;
let lp = self.read_uleb128(&mut pos)? as u32;
if start == 0 && length == 0 && lp == 0 {
break;
}
let action_idx = self.read_uleb128(&mut pos)? as u32;
self.call_sites.push(X86CallSiteEntry {
start_offset: start,
length,
landing_pad_offset: lp,
action_record_index: action_idx,
});
}
Ok(())
}
pub fn find_call_site(&self, ip_offset: u32) -> Option<&X86CallSiteEntry> {
self.call_sites.iter().find(|cs| {
ip_offset >= cs.start_offset
&& ip_offset < cs.start_offset + cs.length
})
}
pub fn decode_action_chain(&self, action_record_index: u32) -> Vec<&X86ActionEntry> {
let mut chain = Vec::new();
let mut idx = action_record_index as usize;
while idx > 0 && idx <= self.actions.len() {
let action = &self.actions[idx - 1];
chain.push(action);
if action.next_offset <= 0 {
break;
}
idx = (idx as i64 + action.next_offset) as usize;
}
chain
}
fn read_sleb128(&self, pos: &mut usize) -> Result<i64, String> {
let mut result: i64 = 0;
let mut shift = 0u32;
loop {
if *pos >= self.data.len() {
return Err("truncated sleb128".into());
}
let byte = self.data[*pos];
*pos += 1;
result |= ((byte & 0x7f) as i64) << shift;
shift += 7;
if byte & 0x80 == 0 {
if shift < 64 && (byte & 0x40) != 0 {
result |= -(1i64 << shift);
}
break;
}
}
Ok(result)
}
fn read_uleb128(&self, pos: &mut usize) -> Result<u64, String> {
let mut result: u64 = 0;
let mut shift = 0u32;
loop {
if *pos >= self.data.len() {
return Err("truncated uleb128".into());
}
let byte = self.data[*pos];
*pos += 1;
result |= ((byte & 0x7f) as u64) << shift;
shift += 7;
if byte & 0x80 == 0 {
break;
}
}
Ok(result)
}
fn read_encoded_ptr(&self, encoding: u8, pos: &mut usize) -> Result<u64, String> {
let base_encoding = encoding & 0x0f;
let modifier = encoding & 0xf0;
let val = match base_encoding {
0x00 => 0u64, 0x01 => self.read_uleb128(pos)?,
0x02 => self.read_uleb128(pos)? as u64, 0x03 => self.read_uleb128(pos)? as u64,
0x09 => self.read_sleb128(pos)? as u64,
0x0a => self.read_sleb128(pos)? as u64,
_ => self.read_uleb128(pos)?,
};
let mut result = val;
if modifier & 0x10 != 0 {
result = result.wrapping_add(*pos as u64);
}
if modifier & 0x20 != 0 {
}
if modifier & 0x30 == 0x30 {
}
Ok(result)
}
}
#[derive(Debug)]
pub struct X86ItaniumEH {
pub current_exception: Option<*mut X86CxaException>,
pub exception_count: u32,
pub unexpected_handler: X86UnexpectedHandlerFn,
pub terminate_handler: X86TerminateHandlerFn,
pub caught_stack: Vec<*mut X86CxaException>,
pub uncaught_exceptions: i32,
pub tls_exception_buffer: Vec<u8>,
}
impl Default for X86ItaniumEH {
fn default() -> Self {
Self {
current_exception: None,
exception_count: 0,
unexpected_handler: None,
terminate_handler: None,
caught_stack: Vec::new(),
uncaught_exceptions: 0,
tls_exception_buffer: vec![0u8; 128],
}
}
}
impl X86ItaniumEH {
pub fn new() -> Self {
Self::default()
}
pub fn allocate_exception(&mut self, thrown_size: usize) -> *mut u8 {
let total_size = std::mem::size_of::<X86CxaException>() + thrown_size;
let layout = std::alloc::Layout::from_size_align(total_size, 16)
.expect("EH allocation layout");
let ptr = unsafe { std::alloc::alloc(layout) };
if ptr.is_null() {
self.call_terminate();
}
unsafe {
std::ptr::write_bytes(ptr, 0, std::mem::size_of::<X86CxaException>());
}
unsafe { ptr.add(std::mem::size_of::<X86CxaException>()) }
}
pub fn throw_exception(
&mut self,
thrown_object: *mut u8,
tinfo: *const X86TypeInfo,
destructor: X86ExceptionCleanupFn,
) -> ! {
let header = unsafe {
thrown_object.sub(std::mem::size_of::<X86CxaException>()) as *mut X86CxaException
};
unsafe {
(*header).exception_type = tinfo;
(*header).exception_destructor = destructor;
(*header).unexpected_handler = self.unexpected_handler;
(*header).terminate_handler = self.terminate_handler;
(*header).reference_count = 1;
(*header).exception_flags = X86ExceptionFlags::empty();
}
self.exception_count += 1;
self.uncaught_exceptions += 1;
self.current_exception = Some(header);
let handler_found = self.phase1_search(header);
if !handler_found {
self.call_terminate();
}
self.phase2_unwind(header);
self.call_terminate();
}
pub fn begin_catch(&mut self, exception: *mut u8) -> *mut u8 {
let header = unsafe {
exception.sub(std::mem::size_of::<X86CxaException>()) as *mut X86CxaException
};
unsafe {
(*header).exception_flags |= X86ExceptionFlags::CAUGHT;
(*header).handler_count += 1;
(*header).saved_unexpected_handler = self.unexpected_handler;
}
self.uncaught_exceptions = self.uncaught_exceptions.saturating_sub(1);
self.caught_stack.push(header);
unsafe { (*header).adjusted_ptr }
}
pub fn end_catch(&mut self) {
if let Some(header) = self.caught_stack.pop() {
unsafe {
(*header).exception_flags &= !X86ExceptionFlags::CAUGHT;
(*header).handler_count -= 1;
if (*header).handler_count == 0 {
self.release_exception(header);
}
}
}
}
pub fn rethrow(&mut self) -> ! {
let header = match self.caught_stack.last() {
Some(h) => *h,
None => self.call_terminate(),
};
unsafe {
(*header).exception_flags |= X86ExceptionFlags::RETHROWN;
}
self.phase2_unwind(header);
self.call_terminate();
}
pub fn uncaught_exceptions_count(&self) -> i32 {
self.uncaught_exceptions
}
fn phase1_search(&self, _header: *mut X86CxaException) -> bool {
true
}
fn phase2_unwind(&self, _header: *mut X86CxaException) {
}
fn release_exception(&self, header: *mut X86CxaException) {
unsafe {
if let Some(dtor) = (*header).exception_destructor {
let obj = (header as *mut u8)
.add(std::mem::size_of::<X86CxaException>());
dtor(obj);
}
let total_size = std::mem::size_of::<X86CxaException>();
let layout = std::alloc::Layout::from_size_align(total_size, 16)
.expect("EH dealloc layout");
std::alloc::dealloc(header as *mut u8, layout);
}
}
fn call_terminate(&self) -> ! {
if let Some(handler) = self.terminate_handler {
unsafe { handler(); }
}
std::process::abort();
}
}
#[derive(Debug)]
pub struct X86GxxPersonality;
impl X86GxxPersonality {
pub unsafe extern "C" fn personality_v0(
_version: i32,
actions: X86UnwindAction,
exception_class: u64,
exception_object: *mut X86CxaException,
context: *mut X86UnwindContext,
) -> X86UnwindReasonCode {
if actions.is_search() {
Self::search_phase(exception_class, exception_object, context)
} else if actions.is_cleanup() {
Self::cleanup_phase(exception_class, exception_object, context)
} else if actions.is_force_unwind() {
Self::force_unwind_phase(context)
} else {
X86UnwindReasonCode::ContinueUnwind
}
}
fn search_phase(
_exception_class: u64,
_exception_object: *mut X86CxaException,
context: *mut X86UnwindContext,
) -> X86UnwindReasonCode {
let ctx = unsafe { &*context };
let lsda_data = match ctx.lsda {
Some(ptr) => {
vec![] }
None => return X86UnwindReasonCode::ContinueUnwind,
};
let mut lsda = X86LSDAReader::new(lsda_data);
if lsda.parse_header(ctx).is_err() {
return X86UnwindReasonCode::ContinueUnwind;
}
X86UnwindReasonCode::ContinueUnwind
}
fn cleanup_phase(
_exception_class: u64,
_exception_object: *mut X86CxaException,
context: *mut X86UnwindContext,
) -> X86UnwindReasonCode {
let ctx = unsafe { &*context };
let lsda_data = match ctx.lsda {
Some(_ptr) => vec![],
None => return X86UnwindReasonCode::ContinueUnwind,
};
let mut lsda = X86LSDAReader::new(lsda_data);
if lsda.parse_header(ctx).is_err() {
return X86UnwindReasonCode::ContinueUnwind;
}
X86UnwindReasonCode::ContinueUnwind
}
fn force_unwind_phase(
_context: *mut X86UnwindContext,
) -> X86UnwindReasonCode {
X86UnwindReasonCode::ContinueUnwind
}
}
#[derive(Debug)]
pub struct X86GccPersonality;
impl X86GccPersonality {
pub unsafe extern "C" fn personality_v0(
_version: i32,
actions: X86UnwindAction,
_exception_class: u64,
_exception_object: *mut X86CxaException,
_context: *mut X86UnwindContext,
) -> X86UnwindReasonCode {
if actions.is_force_unwind() {
X86UnwindReasonCode::ContinueUnwind
} else {
X86UnwindReasonCode::ContinueUnwind
}
}
}
#[derive(Debug, Clone)]
pub struct X86SEHExceptionRecord {
pub exception_code: u32,
pub exception_flags: u32,
pub exception_record: Option<*mut X86SEHExceptionRecord>,
pub exception_address: *mut u8,
pub number_parameters: u32,
pub exception_information: [usize; 15],
}
#[derive(Debug, Clone, Default)]
pub struct X86SEHContextRecord {
pub rax: u64, pub rbx: u64, pub rcx: u64, pub rdx: u64,
pub rsi: u64, pub rdi: u64, pub rbp: u64, pub rsp: u64,
pub r8: u64, pub r9: u64, pub r10: u64, pub r11: u64,
pub r12: u64, pub r13: u64, pub r14: u64, pub r15: u64,
pub rip: u64,
pub eflags: u32,
pub seg_cs: u16, pub seg_ss: u16, pub seg_ds: u16, pub seg_es: u16,
pub seg_fs: u16, pub seg_gs: u16,
pub xmm0: [u128; 16], }
#[derive(Debug)]
pub struct X86ExceptionPointers {
pub exception_record: *mut X86SEHExceptionRecord,
pub context_record: *mut X86SEHContextRecord,
}
pub type X86SEHHandler = unsafe extern "C" fn(
exception_record: *mut X86SEHExceptionRecord,
establisher_frame: *mut u8,
context_record: *mut X86SEHContextRecord,
dispatcher_context: *mut u8,
) -> X86SEHHandlerResult;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum X86SEHHandlerResult {
ContinueSearch = 0,
ExecuteHandler = 1,
ContinueExecution = -1,
}
#[derive(Debug)]
pub struct X86SEHRuntime {
pub handler_chain: Vec<X86SEHHandlerNode>,
pub cpp_frame_infos: HashMap<u64, X86CppFrameInfo>,
pub veh_handlers: Vec<X86VEHHandler>,
pub current_exception: Option<X86ExceptionPointers>,
}
#[derive(Debug, Clone)]
pub struct X86SEHHandlerNode {
pub handler: X86SEHHandler,
pub establisher_frame: *mut u8,
pub try_level: u32,
}
#[derive(Debug, Clone)]
pub struct X86CppFrameInfo {
pub function_address: u64,
pub unwind_info: Vec<X86UnwindOp>,
pub try_blocks: Vec<X86SEHTryBlock>,
pub catch_blocks: Vec<X86SEHCatchBlock>,
}
#[derive(Debug, Clone)]
pub struct X86SEHTryBlock {
pub try_low: u32,
pub try_high: u32,
pub catch_high: u32,
pub num_catches: u32,
}
#[derive(Debug, Clone)]
pub struct X86SEHCatchBlock {
pub flags: u32,
pub type_info: *const X86TypeInfo,
pub catch_offset: u32,
pub handler_offset: u32,
}
impl Default for X86SEHRuntime {
fn default() -> Self {
Self {
handler_chain: Vec::new(),
cpp_frame_infos: HashMap::new(),
veh_handlers: Vec::new(),
current_exception: None,
}
}
}
impl X86SEHRuntime {
pub fn new() -> Self {
Self::default()
}
pub unsafe extern "C" fn c_specific_handler(
exception_record: *mut X86SEHExceptionRecord,
_establisher_frame: *mut u8,
context_record: *mut X86SEHContextRecord,
_dispatcher_context: *mut u8,
) -> X86SEHHandlerResult { unsafe {
if exception_record.is_null() {
return X86SEHHandlerResult::ContinueSearch;
}
let code = (*exception_record).exception_code;
match code {
0xC0000005 => X86SEHHandlerResult::ContinueSearch,
0xC0000094 => X86SEHHandlerResult::ContinueSearch,
0xC0000095 => X86SEHHandlerResult::ContinueSearch,
0xC00000FD => X86SEHHandlerResult::ContinueSearch,
0xE06D7363 => X86SEHHandlerResult::ContinueSearch,
_ => X86SEHHandlerResult::ContinueSearch,
}
}}
pub unsafe extern "C" fn cxx_frame_handler3(
exception_record: *mut X86SEHExceptionRecord,
establisher_frame: *mut u8,
context_record: *mut X86SEHContextRecord,
dispatcher_context: *mut u8,
) -> X86SEHHandlerResult { unsafe {
if exception_record.is_null() || context_record.is_null() {
return X86SEHHandlerResult::ContinueSearch;
}
let exc_code = (*exception_record).exception_code;
if exc_code == 0xE06D7363 {
return X86SEHHandlerResult::ExecuteHandler;
}
X86SEHHandlerResult::ContinueSearch
}}
pub fn register_function_table(&mut self, _base: u64, _entry_count: u32) -> Result<(), String> {
Ok(())
}
pub fn unregister_function_table(&mut self, _base: u64) -> Result<(), String> {
Ok(())
}
}
pub type X86VEHHandlerFn = unsafe extern "C" fn(
exception_info: *mut X86ExceptionPointers,
) -> i32;
pub const EXCEPTION_CONTINUE_SEARCH: i32 = 0;
pub const EXCEPTION_CONTINUE_EXECUTION: i32 = -1;
pub const EXCEPTION_EXECUTE_HANDLER: i32 = 1;
#[derive(Debug, Clone)]
pub struct X86VEHHandler {
pub handler: X86VEHHandlerFn,
pub first: bool, pub registered: bool,
}
impl X86VEHHandler {
pub fn new(handler: X86VEHHandlerFn, first: bool) -> Self {
Self { handler, first, registered: true }
}
}
#[derive(Debug, Default)]
pub struct X86VEHManager {
pub handlers: Vec<X86VEHHandler>,
}
impl X86VEHManager {
pub fn new() -> Self {
Self::default()
}
pub fn add_handler(&mut self, handler: X86VEHHandlerFn, first: bool) {
let entry = X86VEHHandler::new(handler, first);
if first {
self.handlers.insert(0, entry);
} else {
self.handlers.push(entry);
}
}
pub fn remove_handler(&mut self, handler: X86VEHHandlerFn) -> bool {
let handler_ptr = handler as usize;
if let Some(pos) = self.handlers.iter().position(|h| h.handler as usize == handler_ptr) {
self.handlers.remove(pos);
true
} else {
false
}
}
pub fn dispatch(&self, exception_info: *mut X86ExceptionPointers) -> i32 {
for entry in &self.handlers {
if entry.registered {
let result = unsafe { (entry.handler)(exception_info) };
if result != EXCEPTION_CONTINUE_SEARCH {
return result;
}
}
}
EXCEPTION_CONTINUE_SEARCH
}
}
#[derive(Debug, Clone)]
#[repr(C)]
pub struct X86JumpBuffer {
pub saved_regs: [u64; 6],
pub rsp: u64,
pub rip: u64,
}
impl X86JumpBuffer {
pub const SIZE: usize = 8 * 8;
pub fn new() -> Self {
Self {
saved_regs: [0; 6],
rsp: 0,
rip: 0,
}
}
}
#[derive(Debug, Clone)]
#[repr(C)]
pub struct X86SigJumpBuffer {
pub jb: X86JumpBuffer,
pub signal_mask: [u64; 16],
pub saved_mask: i32,
}
impl X86SigJumpBuffer {
pub const SIZE: usize = 8 * 8 + 128 + 4;
}
#[derive(Debug, Default)]
pub struct X86LongJumpRuntime {
pub buffers: HashMap<u64, X86JumpBuffer>,
next_id: u64,
}
impl X86LongJumpRuntime {
pub fn new() -> Self {
Self::default()
}
pub fn setjmp(&mut self) -> u64 {
let id = self.next_id;
self.next_id += 1;
let mut jb = X86JumpBuffer::new();
jb.rip = id; self.buffers.insert(id, jb);
id
}
pub fn longjmp(&self, id: u64, val: i32) -> ! {
let _jb = self.buffers.get(&id).expect("longjmp: invalid buffer");
std::process::exit(val.max(1)); }
pub fn sigsetjmp(&mut self, _savemask: i32) -> u64 {
self.setjmp()
}
pub fn siglongjmp(&self, id: u64, val: i32) -> ! {
self.longjmp(id, val)
}
}
#[derive(Debug)]
pub struct X86NoexceptRuntime {
pub active: bool,
pub violation_count: u64,
pub terminate_on_violation: bool,
}
impl Default for X86NoexceptRuntime {
fn default() -> Self {
Self {
active: true,
violation_count: 0,
terminate_on_violation: true,
}
}
}
impl X86NoexceptRuntime {
pub fn new() -> Self {
Self::default()
}
pub fn on_noexcept_violation(&mut self) -> ! {
self.violation_count += 1;
if self.terminate_on_violation {
eprintln!("terminate called after throwing an instance of '...'");
eprintln!(" what(): std::terminate called (noexcept violation)");
std::process::abort();
}
std::process::abort();
}
pub fn guard_noexcept<F, R>(f: F) -> R
where
F: FnOnce() -> R,
{
f()
}
pub fn check_noexcept_spec(
&self,
thrown_type: &X86TypeInfo,
noexcept_types: &[&X86TypeInfo],
) -> bool {
if noexcept_types.is_empty() {
return true;
}
for allowed in noexcept_types {
if thrown_type.can_catch(allowed) {
return true;
}
}
false
}
}
#[derive(Debug)]
pub struct X86TSXExceptionHandler {
pub abort_codes: Vec<X86TSXAbortCode>,
pub max_retries: u32,
pub use_fallback_lock: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86TSXAbortCode {
Started,
Explicit(u8),
Conflict,
Capacity,
Debug,
Nested,
Unknown(u32),
}
impl X86TSXAbortCode {
pub fn from_status(status: u32) -> Self {
match status as i32 {
-1 => Self::Started,
n if n >= 0 && n <= 255 => Self::Explicit(n as u8),
_ => {
match status {
_ if status & 0x02 != 0 => Self::Conflict,
_ if status & 0x04 != 0 => Self::Capacity,
_ if status & 0x08 != 0 => Self::Debug,
_ if status & 0x10 != 0 => Self::Nested,
_ => Self::Unknown(status),
}
}
}
}
pub fn is_retryable(&self) -> bool {
matches!(self, Self::Conflict | Self::Capacity)
}
}
impl Default for X86TSXExceptionHandler {
fn default() -> Self {
Self {
abort_codes: Vec::new(),
max_retries: 8,
use_fallback_lock: true,
}
}
}
impl X86TSXExceptionHandler {
pub fn new() -> Self {
Self::default()
}
pub fn on_abort(&mut self, code: X86TSXAbortCode) -> bool {
self.abort_codes.push(code);
if !code.is_retryable() {
return false;
}
if self.abort_codes.len() as u32 >= self.max_retries {
return false;
}
true
}
pub fn on_commit(&mut self) {
self.abort_codes.clear();
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86SignalNumber {
SIGSEGV = 11,
SIGFPE = 8,
SIGILL = 4,
SIGBUS = 7,
SIGTRAP = 5,
SIGABRT = 6,
SIGTERM = 15,
SIGINT = 2,
}
impl X86SignalNumber {
pub fn from_raw(raw: i32) -> Option<Self> {
match raw {
11 => Some(Self::SIGSEGV),
8 => Some(Self::SIGFPE),
4 => Some(Self::SIGILL),
7 => Some(Self::SIGBUS),
5 => Some(Self::SIGTRAP),
6 => Some(Self::SIGABRT),
15 => Some(Self::SIGTERM),
2 => Some(Self::SIGINT),
_ => None,
}
}
pub fn description(&self) -> &'static str {
match self {
Self::SIGSEGV => "segmentation fault",
Self::SIGFPE => "floating-point exception",
Self::SIGILL => "illegal instruction",
Self::SIGBUS => "bus error",
Self::SIGTRAP => "trace/breakpoint trap",
Self::SIGABRT => "abort",
Self::SIGTERM => "termination",
Self::SIGINT => "interrupt",
}
}
}
#[derive(Debug, Clone)]
pub struct X86SignalAction {
pub handler: Option<unsafe extern "C" fn(i32)>,
pub flags: X86SignalFlags,
pub mask: X86SignalSet,
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct X86SignalFlags: u32 {
const SA_SIGINFO = 0x00000004;
const SA_RESTART = 0x10000000;
const SA_ONSTACK = 0x08000000;
const SA_NODEFER = 0x40000000;
const SA_RESETHAND = 0x80000000;
const SA_NOCLDSTOP = 0x00000001;
const SA_NOCLDWAIT = 0x00000002;
}
}
#[derive(Debug, Clone, Default)]
pub struct X86SignalSet {
pub bits: [u64; 16],
}
impl X86SignalSet {
pub fn new() -> Self { Self::default() }
pub fn add(&mut self, sig: X86SignalNumber) {
let idx = sig as usize / 64;
let bit = sig as usize % 64;
if idx < self.bits.len() {
self.bits[idx] |= 1u64 << bit;
}
}
pub fn remove(&mut self, sig: X86SignalNumber) {
let idx = sig as usize / 64;
let bit = sig as usize % 64;
if idx < self.bits.len() {
self.bits[idx] &= !(1u64 << bit);
}
}
pub fn contains(&self, sig: X86SignalNumber) -> bool {
let idx = sig as usize / 64;
let bit = sig as usize % 64;
idx < self.bits.len() && (self.bits[idx] & (1u64 << bit)) != 0
}
pub fn empty() -> Self { Self::default() }
pub fn fill() -> Self {
Self { bits: [u64::MAX; 16] }
}
}
#[derive(Debug)]
pub struct X86SignalRuntime {
pub actions: HashMap<X86SignalNumber, X86SignalAction>,
pub pending: Vec<X86SignalNumber>,
pub active: bool,
pub alt_stack: bool,
}
impl Default for X86SignalRuntime {
fn default() -> Self {
Self {
actions: HashMap::new(),
pending: Vec::new(),
active: true,
alt_stack: false,
}
}
}
impl X86SignalRuntime {
pub fn new() -> Self {
Self::default()
}
pub fn sigaction(
&mut self,
signum: X86SignalNumber,
action: Option<X86SignalAction>,
) -> Option<X86SignalAction> {
match action {
Some(act) => self.actions.insert(signum, act),
None => self.actions.remove(&signum),
}
}
pub fn deliver(&mut self, signal: X86SignalNumber) {
self.pending.push(signal);
if let Some(action) = self.actions.get(&signal) {
if let Some(handler) = action.handler {
unsafe { handler(signal as i32); }
} else {
self.default_action(signal);
}
} else {
self.default_action(signal);
}
}
fn default_action(&self, signal: X86SignalNumber) {
match signal {
X86SignalNumber::SIGABRT
| X86SignalNumber::SIGSEGV
| X86SignalNumber::SIGFPE
| X86SignalNumber::SIGILL
| X86SignalNumber::SIGBUS => {
eprintln!("Fatal signal: {:?} ({})", signal, signal.description());
std::process::abort();
}
_ => {
eprintln!("Signal: {:?} ({})", signal, signal.description());
}
}
}
}
#[derive(Debug)]
pub struct X86EHFull {
pub itanium_eh: X86ItaniumEH,
pub seh: X86SEHRuntime,
pub veh: X86VEHManager,
pub longjmp: X86LongJumpRuntime,
pub noexcept_rt: X86NoexceptRuntime,
pub tsx: X86TSXExceptionHandler,
pub signals: X86SignalRuntime,
pub cxx_personality: X86PersonalityFn,
pub c_personality: X86PersonalityFn,
pub active_exceptions: u32,
pub total_exceptions: u64,
pub total_noexcept_violations: u64,
}
impl Default for X86EHFull {
fn default() -> Self {
Self {
itanium_eh: X86ItaniumEH::new(),
seh: X86SEHRuntime::new(),
veh: X86VEHManager::new(),
longjmp: X86LongJumpRuntime::new(),
noexcept_rt: X86NoexceptRuntime::new(),
tsx: X86TSXExceptionHandler::new(),
signals: X86SignalRuntime::new(),
cxx_personality: X86GxxPersonality::personality_v0,
c_personality: X86GccPersonality::personality_v0,
active_exceptions: 0,
total_exceptions: 0,
total_noexcept_violations: 0,
}
}
}
impl X86EHFull {
pub fn new() -> Self {
Self::default()
}
pub fn allocate_exception(&mut self, size: usize) -> *mut u8 {
self.itanium_eh.allocate_exception(size)
}
pub fn throw_cpp_exception(
&mut self,
obj: *mut u8,
tinfo: *const X86TypeInfo,
destructor: X86ExceptionCleanupFn,
) -> ! {
self.total_exceptions += 1;
self.itanium_eh.throw_exception(obj, tinfo, destructor)
}
pub fn rethrow(&mut self) -> ! {
self.itanium_eh.rethrow()
}
pub fn begin_catch(&mut self, exception: *mut u8) -> *mut u8 {
self.active_exceptions = self.active_exceptions.saturating_sub(1);
self.itanium_eh.begin_catch(exception)
}
pub fn end_catch(&mut self) {
self.itanium_eh.end_catch()
}
pub fn uncaught_exceptions(&self) -> i32 {
self.itanium_eh.uncaught_exceptions_count()
}
pub fn add_vectored_handler(&mut self, handler: X86VEHHandlerFn, first: bool) {
self.veh.add_handler(handler, first);
}
pub fn remove_vectored_handler(&mut self, handler: X86VEHHandlerFn) -> bool {
self.veh.remove_handler(handler)
}
pub fn setjmp(&mut self) -> u64 {
self.longjmp.setjmp()
}
pub fn longjmp(&self, id: u64, val: i32) -> ! {
self.longjmp.longjmp(id, val)
}
pub fn on_noexcept_violation(&mut self) -> ! {
self.total_noexcept_violations += 1;
self.noexcept_rt.on_noexcept_violation()
}
pub fn on_tsx_abort(&mut self, status: u32) -> bool {
self.tsx.on_abort(X86TSXAbortCode::from_status(status))
}
pub fn on_tsx_commit(&mut self) {
self.tsx.on_commit();
}
pub fn deliver_signal(&mut self, signal: X86SignalNumber) {
self.signals.deliver(signal);
}
pub fn sigaction(
&mut self,
signum: X86SignalNumber,
action: Option<X86SignalAction>,
) -> Option<X86SignalAction> {
self.signals.sigaction(signum, action)
}
pub fn stats(&self) -> X86EHStats {
X86EHStats {
total_exceptions: self.total_exceptions,
active_exceptions: self.active_exceptions,
uncaught_exceptions: self.itanium_eh.uncaught_exceptions_count(),
noexcept_violations: self.total_noexcept_violations,
veh_handlers: self.veh.handlers.len() as u64,
tsx_retry_count: self.tsx.abort_codes.len() as u64,
pending_signals: self.signals.pending.len() as u64,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct X86EHStats {
pub total_exceptions: u64,
pub active_exceptions: u32,
pub uncaught_exceptions: i32,
pub noexcept_violations: u64,
pub veh_handlers: u64,
pub tsx_retry_count: u64,
pub pending_signals: u64,
}
#[derive(Debug, Clone)]
pub struct X86ExceptionSpecTable {
pub allowed_types: Vec<*const X86TypeInfo>,
pub is_noexcept: bool,
}
impl X86ExceptionSpecTable {
pub fn new() -> Self {
Self {
allowed_types: Vec::new(),
is_noexcept: false,
}
}
pub fn add_allowed_type(&mut self, tinfo: *const X86TypeInfo) {
self.allowed_types.push(tinfo);
}
pub fn allows(&self, thrown_type: &X86TypeInfo) -> bool {
if self.allowed_types.is_empty() {
return !self.is_noexcept; }
for allowed in &self.allowed_types {
if !allowed.is_null() {
unsafe {
if (**allowed).can_catch(thrown_type) {
return true;
}
}
}
}
false
}
}
pub mod eh_encoding {
pub const DW_EH_PE_ABS_PTR: u8 = 0x00;
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_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_ALIGNED: u8 = 0x50;
pub const DW_EH_PE_INDIRECT: u8 = 0x80;
pub const DW_EH_PE_OMIT: u8 = 0xff;
pub fn encoding_name(enc: u8) -> &'static str {
match enc & 0x0f {
DW_EH_PE_ABS_PTR => "absptr",
DW_EH_PE_ULEB128 => "uleb128",
DW_EH_PE_UDATA2 => "udata2",
DW_EH_PE_UDATA4 => "udata4",
DW_EH_PE_UDATA8 => "udata8",
DW_EH_PE_SLEB128 => "sleb128",
DW_EH_PE_SDATA2 => "sdata2",
DW_EH_PE_SDATA4 => "sdata4",
DW_EH_PE_SDATA8 => "sdata8",
_ => "unknown",
}
}
}
#[derive(Debug, Default)]
pub struct X86EHTableEmitter {
pub lsda_data: Vec<u8>,
pub call_sites: Vec<X86CallSiteEntry>,
pub actions: Vec<X86ActionEntry>,
pub type_entries: Vec<*const X86TypeInfo>,
}
impl X86EHTableEmitter {
pub fn new() -> Self {
Self::default()
}
pub fn emit_lsda(&mut self, call_sites: &[X86CallSiteEntry], actions: &[X86ActionEntry]) -> Vec<u8> {
let mut data = Vec::new();
data.push(eh_encoding::DW_EH_PE_OMIT);
data.push(eh_encoding::DW_EH_PE_ABS_PTR);
data.push(eh_encoding::DW_EH_PE_UDATA4);
data.push(eh_encoding::DW_EH_PE_UDATA4);
for cs in call_sites {
Self::encode_uleb128(&mut data, cs.start_offset as u64);
Self::encode_uleb128(&mut data, cs.length as u64);
Self::encode_uleb128(&mut data, cs.landing_pad_offset as u64);
Self::encode_uleb128(&mut data, cs.action_record_index as u64);
}
Self::encode_uleb128(&mut data, 0);
Self::encode_uleb128(&mut data, 0);
Self::encode_uleb128(&mut data, 0);
Self::encode_uleb128(&mut data, 0);
for action in actions {
Self::encode_sleb128(&mut data, action.type_filter);
Self::encode_sleb128(&mut data, action.next_offset);
}
for _t in &self.type_entries {
data.extend_from_slice(&[0; 8]); }
data
}
fn encode_uleb128(buf: &mut Vec<u8>, mut val: u64) {
loop {
let mut byte = (val & 0x7f) as u8;
val >>= 7;
if val != 0 {
byte |= 0x80;
}
buf.push(byte);
if val == 0 {
break;
}
}
}
fn encode_sleb128(buf: &mut Vec<u8>, mut val: i64) {
loop {
let mut byte = (val & 0x7f) as u8;
val >>= 7;
let more = !((val == 0 && (byte & 0x40) == 0) || (val == -1 && (byte & 0x40) != 0));
if more {
byte |= 0x80;
}
buf.push(byte);
if !more {
break;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_exception_kind_display() {
assert_eq!(format!("{}", X86ExceptionKind::CppException), "C++");
assert_eq!(format!("{}", X86ExceptionKind::SEH), "SEH");
assert_eq!(format!("{}", X86ExceptionKind::NoexceptViolation), "noexcept-violation");
}
#[test]
fn test_type_info_creation() {
let ti = X86TypeInfo::new("int", false);
assert_eq!(ti.is_pointer, false);
assert!(!ti.is_catch_all);
}
#[test]
fn test_type_info_catch_all() {
let ti = X86TypeInfo::new("...", false);
assert!(ti.is_catch_all);
assert!(ti.can_catch(&X86TypeInfo::new("int", false)));
}
#[test]
fn test_type_info_can_catch() {
let ti = X86TypeInfo::new("MyClass", false);
assert!(ti.can_catch(&X86TypeInfo::new("MyClass", false)));
assert!(!ti.can_catch(&X86TypeInfo::new("OtherClass", false)));
}
#[test]
fn test_unwind_action_flags() {
let search = X86UnwindAction(X86UnwindAction::SEARCH_PHASE.0);
assert!(search.is_search());
assert!(!search.is_cleanup());
let cleanup = X86UnwindAction(X86UnwindAction::CLEANUP_PHASE.0);
assert!(cleanup.is_cleanup());
assert!(!cleanup.is_search());
}
#[test]
fn test_unwind_reason_code_from_i32() {
assert_eq!(X86UnwindReasonCode::from_i32(4), X86UnwindReasonCode::NormalStop);
assert_eq!(X86UnwindReasonCode::from_i32(8), X86UnwindReasonCode::ContinueUnwind);
assert_eq!(X86UnwindReasonCode::from_i32(99), X86UnwindReasonCode::NoReason);
}
#[test]
fn test_lsda_parse_call_sites() {
let mut emitter = X86EHTableEmitter::new();
let cs = vec![
X86CallSiteEntry {
start_offset: 10, length: 20, landing_pad_offset: 50, action_record_index: 1,
},
X86CallSiteEntry {
start_offset: 30, length: 15, landing_pad_offset: 80, action_record_index: 2,
},
];
let actions = vec![
X86ActionEntry { type_filter: 0, next_offset: 0 },
X86ActionEntry { type_filter: 1, next_offset: 0 },
];
let lsda_bytes = emitter.emit_lsda(&cs, &actions);
assert!(!lsda_bytes.is_empty());
}
#[test]
fn test_lsda_find_call_site() {
let mut reader = X86LSDAReader::new(vec![
0x00, 0x00, 0x03, 0x00, ]);
reader.call_sites.push(X86CallSiteEntry {
start_offset: 0, length: 50, landing_pad_offset: 100, action_record_index: 1,
});
reader.call_sites.push(X86CallSiteEntry {
start_offset: 60, length: 40, landing_pad_offset: 200, action_record_index: 2,
});
let cs = reader.find_call_site(25);
assert!(cs.is_some());
assert_eq!(cs.unwrap().landing_pad_offset, 100);
let cs2 = reader.find_call_site(80);
assert!(cs2.is_some());
assert_eq!(cs2.unwrap().landing_pad_offset, 200);
let cs3 = reader.find_call_site(55);
assert!(cs3.is_none());
}
#[test]
fn test_itanium_eh_allocate() {
let mut eh = X86ItaniumEH::new();
let ptr = eh.allocate_exception(16);
assert!(!ptr.is_null());
}
#[test]
fn test_itanium_eh_begin_end_catch() {
let mut eh = X86ItaniumEH::new();
let ptr = eh.allocate_exception(16);
let adjusted = eh.begin_catch(ptr);
assert!(!adjusted.is_null());
eh.end_catch();
}
#[test]
fn test_seh_c_specific_handler() {
let result = unsafe {
X86SEHRuntime::c_specific_handler(
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
)
};
assert_eq!(result, X86SEHHandlerResult::ContinueSearch);
}
#[test]
fn test_seh_cxx_frame_handler3() {
let result = unsafe {
X86SEHRuntime::cxx_frame_handler3(
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
)
};
assert_eq!(result, X86SEHHandlerResult::ContinueSearch);
}
#[test]
fn test_veh_add_remove() {
let mut veh = X86VEHManager::new();
extern "C" fn dummy_handler(_: *mut X86ExceptionPointers) -> i32 { 0 }
veh.add_handler(dummy_handler, false);
assert_eq!(veh.handlers.len(), 1);
veh.add_handler(dummy_handler, true);
assert_eq!(veh.handlers.len(), 2);
assert!(veh.remove_handler(dummy_handler));
assert_eq!(veh.handlers.len(), 1);
}
#[test]
fn test_longjmp_setjmp() {
let mut lj = X86LongJumpRuntime::new();
let id = lj.setjmp();
assert_eq!(lj.buffers.len(), 1);
assert!(lj.buffers.contains_key(&id));
}
#[test]
fn test_noexcept_violation_leads_to_abort() {
let mut rt = X86NoexceptRuntime::new();
rt.terminate_on_violation = false; assert!(rt.active);
}
#[test]
fn test_tsx_abort_retry() {
let mut tsx = X86TSXExceptionHandler::new();
assert!(tsx.on_abort(X86TSXAbortCode::Conflict));
assert!(tsx.on_abort(X86TSXAbortCode::Conflict));
assert!(!tsx.on_abort(X86TSXAbortCode::Explicit(5)));
assert_eq!(tsx.abort_codes.len(), 3);
}
#[test]
fn test_tsx_abort_commit_clears() {
let mut tsx = X86TSXExceptionHandler::new();
tsx.on_abort(X86TSXAbortCode::Conflict);
tsx.on_commit();
assert_eq!(tsx.abort_codes.len(), 0);
}
#[test]
fn test_signal_set() {
let mut set = X86SignalSet::new();
assert!(!set.contains(X86SignalNumber::SIGSEGV));
set.add(X86SignalNumber::SIGSEGV);
assert!(set.contains(X86SignalNumber::SIGSEGV));
set.remove(X86SignalNumber::SIGSEGV);
assert!(!set.contains(X86SignalNumber::SIGSEGV));
}
#[test]
fn test_signal_runtime() {
let mut sr = X86SignalRuntime::new();
let action = X86SignalAction {
handler: None,
flags: X86SignalFlags::SA_RESTART,
mask: X86SignalSet::new(),
};
sr.sigaction(X86SignalNumber::SIGINT, Some(action.clone()));
assert!(sr.actions.contains_key(&X86SignalNumber::SIGINT));
}
#[test]
fn test_eh_full_new() {
let eh = X86EHFull::new();
assert_eq!(eh.total_exceptions, 0);
assert_eq!(eh.active_exceptions, 0);
assert_eq!(eh.uncaught_exceptions(), 0);
}
#[test]
fn test_eh_full_allocate() {
let mut eh = X86EHFull::new();
let ptr = eh.allocate_exception(32);
assert!(!ptr.is_null());
}
#[test]
fn test_eh_stats() {
let eh = X86EHFull::new();
let stats = eh.stats();
assert_eq!(stats.total_exceptions, 0);
assert_eq!(stats.noexcept_violations, 0);
}
#[test]
fn test_uleb128_encoding() {
let mut buf = Vec::new();
X86EHTableEmitter::encode_uleb128(&mut buf, 0);
assert_eq!(buf, vec![0]);
buf.clear();
X86EHTableEmitter::encode_uleb128(&mut buf, 127);
assert_eq!(buf, vec![127]);
buf.clear();
X86EHTableEmitter::encode_uleb128(&mut buf, 128);
assert_eq!(buf, vec![0x80, 0x01]);
}
#[test]
fn test_sleb128_encoding() {
let mut buf = Vec::new();
X86EHTableEmitter::encode_sleb128(&mut buf, 0);
assert_eq!(buf, vec![0]);
buf.clear();
X86EHTableEmitter::encode_sleb128(&mut buf, -1);
assert_eq!(buf, vec![0x7f]);
buf.clear();
X86EHTableEmitter::encode_sleb128(&mut buf, 64);
assert_eq!(buf, vec![0xc0, 0x00]);
}
#[test]
fn test_eh_encoding_constants() {
assert_eq!(eh_encoding::DW_EH_PE_ABS_PTR, 0x00);
assert_eq!(eh_encoding::DW_EH_PE_ULEB128, 0x01);
assert_eq!(eh_encoding::DW_EH_PE_OMIT, 0xff);
}
#[test]
fn test_exception_spec_table_noexcept() {
let mut table = X86ExceptionSpecTable::new();
table.is_noexcept = true;
let ti = X86TypeInfo::new("int", false);
assert!(!table.allows(&ti));
}
#[test]
fn test_exception_spec_table_allows() {
let mut table = X86ExceptionSpecTable::new();
let ti = X86TypeInfo::new("std::runtime_error", false);
table.add_allowed_type(&ti as *const X86TypeInfo);
assert!(table.allows(&ti));
}
#[test]
fn test_lsda_empty_find_call_site() {
let reader = X86LSDAReader::new(vec![]);
assert_eq!(reader.find_call_site(0), None);
}
#[test]
fn test_signal_number_from_raw() {
assert_eq!(X86SignalNumber::from_raw(11), Some(X86SignalNumber::SIGSEGV));
assert_eq!(X86SignalNumber::from_raw(8), Some(X86SignalNumber::SIGFPE));
assert_eq!(X86SignalNumber::from_raw(999), None);
}
#[test]
fn test_exception_flags() {
let mut flags = X86ExceptionFlags::empty();
flags |= X86ExceptionFlags::CAUGHT;
assert!(flags.contains(X86ExceptionFlags::CAUGHT));
flags |= X86ExceptionFlags::RETHROWN;
assert!(flags.contains(X86ExceptionFlags::RETHROWN));
}
#[test]
fn test_seh_runtime_default() {
let seh = X86SEHRuntime::new();
assert!(seh.handler_chain.is_empty());
assert!(seh.veh_handlers.is_empty());
}
#[test]
fn test_signal_number_description() {
assert_eq!(
X86SignalNumber::SIGSEGV.description(),
"segmentation fault"
);
assert_eq!(
X86SignalNumber::SIGFPE.description(),
"floating-point exception"
);
}
#[test]
fn test_signal_fill_set() {
let set = X86SignalSet::fill();
assert!(set.contains(X86SignalNumber::SIGSEGV));
assert!(set.contains(X86SignalNumber::SIGINT));
assert!(set.contains(X86SignalNumber::SIGABRT));
}
#[test]
fn test_tsx_abort_code_from_status() {
assert_eq!(X86TSXAbortCode::from_status(0xFFFFFFFF), X86TSXAbortCode::Started);
assert_eq!(X86TSXAbortCode::from_status(0), X86TSXAbortCode::Explicit(0));
}
#[test]
fn test_itanium_eh_uncaught_count() {
let mut eh = X86ItaniumEH::new();
assert_eq!(eh.uncaught_exceptions_count(), 0);
eh.uncaught_exceptions = 3;
assert_eq!(eh.uncaught_exceptions_count(), 3);
}
#[test]
fn test_eh_full_deliver_signal() {
let mut eh = X86EHFull::new();
eh.deliver_signal(X86SignalNumber::SIGINT);
assert_eq!(eh.signals.pending.len(), 1);
}
#[test]
fn test_lsda_read_sleb128_zero() {
let reader = X86LSDAReader::new(vec![0x00]);
let mut pos = 0;
let val = reader.read_sleb128(&mut pos).unwrap();
assert_eq!(val, 0);
assert_eq!(pos, 1);
}
#[test]
fn test_lsda_read_uleb128_overflow_safe() {
let reader = X86LSDAReader::new(vec![]);
let mut pos = 0;
assert!(reader.read_uleb128(&mut pos).is_err());
}
#[test]
fn test_eh_emitter_emit_lsda_empty() {
let mut emitter = X86EHTableEmitter::new();
let data = emitter.emit_lsda(&[], &[]);
assert!(data.len() >= 8);
}
#[test]
fn test_eh_emitter_encode_decode_roundtrip() {
let mut emitter = X86EHTableEmitter::new();
let cs = vec![X86CallSiteEntry {
start_offset: 5, length: 25, landing_pad_offset: 100, action_record_index: 0,
}];
let data = emitter.emit_lsda(&cs, &[]);
assert!(!data.is_empty());
}
#[test]
fn test_unwind_context_default() {
let ctx = X86UnwindContext::default();
assert_eq!(ctx.cfa, 0);
assert_eq!(ctx.ip, 0);
assert!(ctx.registers.is_empty());
assert!(!ctx.signal_frame);
}
#[test]
fn test_unwind_info_default() {
let info = X86UnwindInfo::default();
assert_eq!(info.begin_address, 0);
assert!(!info.has_frame_pointer);
assert!(info.unwind_ops.is_empty());
}
#[test]
fn test_veh_handler_new() {
extern "C" fn fake(_: *mut X86ExceptionPointers) -> i32 { 0 }
let handler = X86VEHHandler::new(fake, true);
assert!(handler.first);
assert!(handler.registered);
}
}