#![allow(non_upper_case_globals, dead_code)]
use std::collections::{BTreeMap, HashMap};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum UnwindOpcode {
PushNonVol = 0,
AllocLarge = 1,
AllocSmall = 2,
SetFpReg = 3,
SaveNonVol = 4,
SaveNonVolFar = 5,
Epilog = 6,
SpareCode = 7,
SaveXmm128 = 8,
SaveXmm128Far = 9,
PushMachFrame = 10,
}
impl UnwindOpcode {
pub fn from_u8(v: u8) -> Option<Self> {
match v {
0 => Some(Self::PushNonVol),
1 => Some(Self::AllocLarge),
2 => Some(Self::AllocSmall),
3 => Some(Self::SetFpReg),
4 => Some(Self::SaveNonVol),
5 => Some(Self::SaveNonVolFar),
6 => Some(Self::Epilog),
7 => Some(Self::SpareCode),
8 => Some(Self::SaveXmm128),
9 => Some(Self::SaveXmm128Far),
10 => Some(Self::PushMachFrame),
_ => None,
}
}
pub fn requires_extra_slot(self) -> bool {
matches!(
self,
Self::AllocLarge | Self::SaveNonVolFar | Self::SaveXmm128Far | Self::SetFpReg
)
}
pub fn name(self) -> &'static str {
match self {
Self::PushNonVol => "UWOP_PUSH_NONVOL",
Self::AllocLarge => "UWOP_ALLOC_LARGE",
Self::AllocSmall => "UWOP_ALLOC_SMALL",
Self::SetFpReg => "UWOP_SET_FPREG",
Self::SaveNonVol => "UWOP_SAVE_NONVOL",
Self::SaveNonVolFar => "UWOP_SAVE_NONVOL_FAR",
Self::Epilog => "UWOP_EPILOG",
Self::SpareCode => "UWOP_SPARE_CODE",
Self::SaveXmm128 => "UWOP_SAVE_XMM128",
Self::SaveXmm128Far => "UWOP_SAVE_XMM128_FAR",
Self::PushMachFrame => "UWOP_PUSH_MACHFRAME",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UnwindCode {
pub code_offset: u8,
pub unwind_op: UnwindOpcode,
pub op_info: u8,
}
impl UnwindCode {
pub fn new(code_offset: u8, unwind_op: UnwindOpcode, op_info: u8) -> Self {
Self {
code_offset,
unwind_op,
op_info,
}
}
pub fn encode(&self) -> u16 {
let op = self.unwind_op as u16;
let info = self.op_info as u16;
let offset = self.code_offset as u16;
(offset & 0xF) | ((op & 0xF) << 4) | ((info & 0xFF) << 8)
}
pub fn decode(raw: u16) -> Option<Self> {
let code_offset = (raw & 0xF) as u8;
let unwind_op = UnwindOpcode::from_u8(((raw >> 4) & 0xF) as u8)?;
let op_info = ((raw >> 8) & 0xFF) as u8;
Some(Self {
code_offset,
unwind_op,
op_info,
})
}
pub fn push_nonvol(offset: u8, reg: u8) -> Self {
Self::new(offset, UnwindOpcode::PushNonVol, reg)
}
pub fn alloc_large(offset: u8, size_info: u8) -> Self {
Self::new(offset, UnwindOpcode::AllocLarge, size_info)
}
pub fn alloc_small(offset: u8, size_div8_minus1: u8) -> Self {
Self::new(offset, UnwindOpcode::AllocSmall, size_div8_minus1)
}
pub fn set_fpreg(offset: u8) -> Self {
Self::new(offset, UnwindOpcode::SetFpReg, 0)
}
pub fn save_nonvol(offset: u8, reg: u8) -> Self {
Self::new(offset, UnwindOpcode::SaveNonVol, reg)
}
pub fn save_nonvol_far(offset: u8, reg: u8) -> Self {
Self::new(offset, UnwindOpcode::SaveNonVolFar, reg)
}
pub fn save_xmm128(offset: u8, reg: u8) -> Self {
Self::new(offset, UnwindOpcode::SaveXmm128, reg)
}
pub fn save_xmm128_far(offset: u8, reg: u8) -> Self {
Self::new(offset, UnwindOpcode::SaveXmm128Far, reg)
}
pub fn epilog(offset: u8, epilog_start_offset: u8) -> Self {
Self::new(offset, UnwindOpcode::Epilog, epilog_start_offset)
}
pub fn push_machframe(offset: u8, has_error_code: bool) -> Self {
Self::new(
offset,
UnwindOpcode::PushMachFrame,
if has_error_code { 1 } else { 0 },
)
}
pub fn spare(offset: u8, info: u8) -> Self {
Self::new(offset, UnwindOpcode::SpareCode, info)
}
}
impl fmt::Display for UnwindCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} off={:#x} info={}",
self.unwind_op.name(),
self.code_offset,
self.op_info
)
}
}
pub mod unwind_flags {
pub const UNW_FLAG_EHANDLER: u8 = 0x01;
pub const UNW_FLAG_UHANDLER: u8 = 0x02;
pub const UNW_FLAG_CHAININFO: u8 = 0x04;
}
pub const MAX_UNWIND_CODES: usize = 256;
pub const UNWIND_INFO_HEADER_SIZE: usize = 4;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UnwindInfo {
pub version: u8,
pub flags: u8,
pub prologue_size: u8,
pub count_of_codes: u8,
pub frame_register: u8,
pub frame_register_offset: u8,
pub unwind_codes: Vec<UnwindCode>,
pub exception_handler: Option<u32>,
pub handler_data: Vec<u8>,
pub chained_info: Option<Box<UnwindInfo>>,
}
impl Default for UnwindInfo {
fn default() -> Self {
Self {
version: 1,
flags: 0,
prologue_size: 0,
count_of_codes: 0,
frame_register: 0,
frame_register_offset: 0,
unwind_codes: Vec::new(),
exception_handler: None,
handler_data: Vec::new(),
chained_info: None,
}
}
}
impl UnwindInfo {
pub fn new() -> Self {
Self::default()
}
pub fn new_v2() -> Self {
Self {
version: 2,
..Default::default()
}
}
pub fn add_unwind_code(&mut self, code: UnwindCode) -> usize {
let slots = if code.unwind_op.requires_extra_slot() {
2
} else {
1
};
self.unwind_codes.push(code);
self.count_of_codes += slots as u8;
slots
}
pub fn push_nonvol(&mut self, offset: u8, reg: u8) {
self.add_unwind_code(UnwindCode::push_nonvol(offset, reg));
}
pub fn alloc_small(&mut self, offset: u8, size: u32) -> Result<(), String> {
if size == 0 || size % 8 != 0 {
return Err(format!(
"ALLOC_SMALL size must be positive multiple of 8, got {size}"
));
}
let scaled = (size / 8) as u8;
if scaled < 1 || scaled > 7 {
return Err(format!("ALLOC_SMALL size {size} out of range (8..64)"));
}
self.add_unwind_code(UnwindCode::alloc_small(offset, scaled - 1));
Ok(())
}
pub fn alloc_large(&mut self, offset: u8, size: u32) -> Result<(), String> {
if size % 8 != 0 {
return Err(format!(
"ALLOC_LARGE size must be multiple of 8, got {size}"
));
}
let scaled = size / 8;
if scaled <= u16::MAX as u32 {
self.add_unwind_code(UnwindCode::alloc_large(offset, 0));
self.unwind_codes.push(UnwindCode::new(
(scaled & 0xFF) as u8,
UnwindOpcode::PushNonVol,
((scaled >> 8) & 0xFF) as u8,
));
self.count_of_codes += 1;
} else {
self.add_unwind_code(UnwindCode::alloc_large(offset, 1));
self.unwind_codes.push(UnwindCode::new(
(scaled & 0xFF) as u8,
UnwindOpcode::PushNonVol,
((scaled >> 8) & 0xFF) as u8,
));
self.unwind_codes.push(UnwindCode::new(
((scaled >> 16) & 0xFF) as u8,
UnwindOpcode::PushNonVol,
((scaled >> 24) & 0xFF) as u8,
));
self.count_of_codes += 2;
}
Ok(())
}
pub fn set_fpreg(&mut self, offset: u8, reg: u8, frame_offset: u32) -> Result<(), String> {
if frame_offset % 16 != 0 || frame_offset > 240 {
return Err(format!(
"SET_FPREG offset {frame_offset} must be multiple of 16 and <= 240"
));
}
self.add_unwind_code(UnwindCode::set_fpreg(offset));
let extra = (reg as u16) | ((frame_offset / 16) as u16) << 4;
self.unwind_codes.push(UnwindCode::new(
(extra & 0xFF) as u8,
UnwindOpcode::PushNonVol,
((extra >> 8) & 0xFF) as u8,
));
self.count_of_codes += 1;
self.frame_register = reg;
self.frame_register_offset = (frame_offset / 16) as u8;
Ok(())
}
pub fn save_nonvol(&mut self, offset: u8, reg: u8, save_offset: u32) -> Result<(), String> {
if save_offset % 8 != 0 || save_offset > 0x7FFF8 {
return Err(format!(
"SAVE_NONVOL offset {save_offset} must be multiple of 8"
));
}
let scaled = save_offset / 8;
if scaled <= u16::MAX as u32 {
self.add_unwind_code(UnwindCode::save_nonvol(offset, reg));
self.unwind_codes.push(UnwindCode::new(
(scaled & 0xFF) as u8,
UnwindOpcode::PushNonVol,
((scaled >> 8) & 0xFF) as u8,
));
self.count_of_codes += 1;
} else {
self.add_unwind_code(UnwindCode::save_nonvol_far(offset, reg));
let word = save_offset;
self.unwind_codes.push(UnwindCode::new(
(word & 0xFF) as u8,
UnwindOpcode::PushNonVol,
((word >> 8) & 0xFF) as u8,
));
self.unwind_codes.push(UnwindCode::new(
((word >> 16) & 0xFF) as u8,
UnwindOpcode::PushNonVol,
((word >> 24) & 0xFF) as u8,
));
self.count_of_codes += 2;
}
Ok(())
}
pub fn save_xmm128(&mut self, offset: u8, reg: u8, save_offset: u32) -> Result<(), String> {
if save_offset % 16 != 0 {
return Err(format!(
"SAVE_XMM128 offset {save_offset} must be multiple of 16"
));
}
let scaled = save_offset / 16;
if scaled <= u16::MAX as u32 {
self.add_unwind_code(UnwindCode::save_xmm128(offset, reg));
self.unwind_codes.push(UnwindCode::new(
(scaled & 0xFF) as u8,
UnwindOpcode::PushNonVol,
((scaled >> 8) & 0xFF) as u8,
));
self.count_of_codes += 1;
} else {
self.add_unwind_code(UnwindCode::save_xmm128_far(offset, reg));
let word = save_offset;
self.unwind_codes.push(UnwindCode::new(
(word & 0xFF) as u8,
UnwindOpcode::PushNonVol,
((word >> 8) & 0xFF) as u8,
));
self.unwind_codes.push(UnwindCode::new(
((word >> 16) & 0xFF) as u8,
UnwindOpcode::PushNonVol,
((word >> 24) & 0xFF) as u8,
));
self.count_of_codes += 2;
}
Ok(())
}
pub fn add_epilog(&mut self, offset: u8, epilog_start: u8) -> Result<(), String> {
if self.version < 2 {
return Err("EPILOG codes require version 2 UNWIND_INFO".into());
}
self.add_unwind_code(UnwindCode::epilog(offset, epilog_start));
Ok(())
}
pub fn push_machframe(&mut self, offset: u8, has_error_code: bool) {
self.add_unwind_code(UnwindCode::push_machframe(offset, has_error_code));
}
pub fn set_exception_handler(&mut self, handler_rva: u32, data: Vec<u8>) {
self.exception_handler = Some(handler_rva);
self.handler_data = data;
self.flags |= unwind_flags::UNW_FLAG_EHANDLER;
}
pub fn set_termination_handler(&mut self, handler_rva: u32) {
self.exception_handler = Some(handler_rva);
self.flags |= unwind_flags::UNW_FLAG_UHANDLER;
}
pub fn set_chained_info(&mut self, info: UnwindInfo) {
self.chained_info = Some(Box::new(info));
self.flags |= unwind_flags::UNW_FLAG_CHAININFO;
}
pub fn encoded_size(&self) -> usize {
let mut size = UNWIND_INFO_HEADER_SIZE
+ (self.count_of_codes as usize * 2)
+ if self.exception_handler.is_some() {
4
} else {
0
}
+ self.handler_data.len();
size = (size + 3) & !3;
if let Some(ref chained) = self.chained_info {
size += chained.encoded_size();
}
size
}
pub fn encode_header(&self) -> [u8; 4] {
let mut header = [0u8; 4];
header[0] = (self.version & 0x07) | ((self.flags & 0x1F) << 3);
header[1] = self.prologue_size;
header[2] = self.count_of_codes;
header[3] = (self.frame_register & 0x0F) | ((self.frame_register_offset & 0x0F) << 4);
header
}
pub fn encode_codes(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(self.unwind_codes.len() * 2);
for code in &self.unwind_codes {
let encoded = code.encode();
buf.push(encoded as u8);
buf.push((encoded >> 8) as u8);
}
buf
}
pub fn encode_to_bytes(&self) -> Vec<u8> {
let mut buf = Vec::new();
self.encode_into(&mut buf);
buf
}
fn encode_into(&self, buf: &mut Vec<u8>) {
buf.extend_from_slice(&self.encode_header());
buf.extend_from_slice(&self.encode_codes());
if let Some(rva) = self.exception_handler {
buf.push(rva as u8);
buf.push((rva >> 8) as u8);
buf.push((rva >> 16) as u8);
buf.push((rva >> 24) as u8);
buf.extend_from_slice(&self.handler_data);
}
while buf.len() & 3 != 0 {
buf.push(0);
}
if let Some(ref chained) = self.chained_info {
chained.encode_into(buf);
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RuntimeFunction {
pub begin_address: u32,
pub end_address: u32,
pub unwind_info_address: u32,
}
impl RuntimeFunction {
pub fn new(begin: u32, end: u32, unwind_info_rva: u32) -> Self {
Self {
begin_address: begin,
end_address: end,
unwind_info_address: unwind_info_rva,
}
}
pub fn encode_to_bytes(&self) -> [u8; 12] {
let mut buf = [0u8; 12];
buf[0..4].copy_from_slice(&self.begin_address.to_le_bytes());
buf[4..8].copy_from_slice(&self.end_address.to_le_bytes());
buf[8..12].copy_from_slice(&self.unwind_info_address.to_le_bytes());
buf
}
pub fn decode_from_bytes(bytes: &[u8; 12]) -> Self {
let begin = u32::from_le_bytes(bytes[0..4].try_into().unwrap());
let end = u32::from_le_bytes(bytes[4..8].try_into().unwrap());
let unwind = u32::from_le_bytes(bytes[8..12].try_into().unwrap());
Self {
begin_address: begin,
end_address: end,
unwind_info_address: unwind,
}
}
}
impl fmt::Display for RuntimeFunction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"RUNTIME_FUNCTION [begin={:#010x}, end={:#010x}, unwind={:#010x}]",
self.begin_address, self.end_address, self.unwind_info_address
)
}
}
#[derive(Debug, Clone, Default)]
pub struct PdataBuilder {
pub entries: Vec<RuntimeFunction>,
by_begin: BTreeMap<u32, usize>,
}
impl PdataBuilder {
pub fn new() -> Self {
Self {
entries: Vec::new(),
by_begin: BTreeMap::new(),
}
}
pub fn add_function(&mut self, begin: u32, end: u32, unwind_rva: u32) -> Result<(), String> {
if end <= begin {
return Err(format!(
"Invalid function range: begin {begin:#x} >= end {end:#x}"
));
}
if let Some(_) = self.by_begin.range(..=end).next_back().filter(|&(&k, _)| {
let entry = &self.entries[self.by_begin[&k]];
entry.end_address > begin
}) {
}
let entry = RuntimeFunction::new(begin, end, unwind_rva);
let idx = self.entries.len();
self.entries.push(entry);
self.by_begin.insert(begin, idx);
Ok(())
}
pub fn find(&self, ip: u32) -> Option<&RuntimeFunction> {
self.by_begin
.range(..=ip)
.next_back()
.map(|(_, &idx)| &self.entries[idx])
.filter(|entry| ip >= entry.begin_address && ip < entry.end_address)
}
pub fn encode_to_bytes(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(self.entries.len() * 12);
let mut sorted: Vec<&RuntimeFunction> = self.entries.iter().collect();
sorted.sort_by_key(|e| e.begin_address);
for entry in &sorted {
buf.extend_from_slice(&entry.encode_to_bytes());
}
buf
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
#[derive(Debug, Clone, Default)]
pub struct XdataBuilder {
pub entries: Vec<(u32, UnwindInfo)>,
current_offset: u32,
}
impl XdataBuilder {
pub fn new() -> Self {
Self {
entries: Vec::new(),
current_offset: 0,
}
}
pub fn add_unwind_info(&mut self, info: UnwindInfo) -> u32 {
let rva = self.current_offset;
let size = info.encoded_size() as u32;
self.entries.push((rva, info));
self.current_offset += size;
rva
}
pub fn encode_to_bytes(&self) -> Vec<u8> {
let mut buf = Vec::new();
for (_rva, info) in &self.entries {
info.encode_into(&mut buf);
}
buf
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
pub type CSpecificHandler = unsafe extern "system" fn(
exception_record: *mut u8,
establisher_frame: u64,
context_record: *mut u8,
dispatcher_context: *mut u8,
) -> i32;
pub type CxxFrameHandler3 = unsafe extern "system" fn(
exception_record: *mut u8,
establisher_frame: u64,
context_record: *mut u8,
dispatcher_context: *mut u8,
) -> i32;
pub mod seh_result {
pub const EXCEPTION_CONTINUE_SEARCH: i32 = 0;
pub const EXCEPTION_EXECUTE_HANDLER: i32 = 1;
pub const EXCEPTION_CONTINUE_EXECUTION: i32 = -1;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CScopeTableEntry {
pub try_begin: u32,
pub try_end: u32,
pub target: u32,
pub handler: u32,
pub jump_target: u32,
}
impl CScopeTableEntry {
pub fn new(try_begin: u32, try_end: u32, filter: u32, handler: u32, jump: u32) -> Self {
Self {
try_begin,
try_end,
target: filter,
handler,
jump_target: jump,
}
}
pub fn encode(&self) -> [u8; 20] {
let mut buf = [0u8; 20];
buf[0..4].copy_from_slice(&self.try_begin.to_le_bytes());
buf[4..8].copy_from_slice(&self.try_end.to_le_bytes());
buf[8..12].copy_from_slice(&self.target.to_le_bytes());
buf[12..16].copy_from_slice(&self.handler.to_le_bytes());
buf[16..20].copy_from_slice(&self.jump_target.to_le_bytes());
buf
}
}
#[derive(Debug, Clone)]
pub struct CScopeTable {
pub entries: Vec<CScopeTableEntry>,
}
impl CScopeTable {
pub fn new() -> Self {
Self {
entries: Vec::new(),
}
}
pub fn add_scope(
&mut self,
try_begin: u32,
try_end: u32,
filter: u32,
handler: u32,
jump: u32,
) {
self.entries.push(CScopeTableEntry::new(
try_begin, try_end, filter, handler, jump,
));
}
pub fn encode(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(4 + self.entries.len() * 20);
buf.extend_from_slice(&(self.entries.len() as u32).to_le_bytes());
for entry in &self.entries {
buf.extend_from_slice(&entry.encode());
}
buf
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CppHandlerKind {
Catch,
Cleanup,
Finally,
}
#[derive(Debug, Clone)]
pub struct CppTryRegion {
pub start_rva: u32,
pub end_rva: u32,
pub handler_kind: CppHandlerKind,
pub catch_type: u32,
pub handler_rva: u32,
}
impl CppTryRegion {
pub fn new(
start: u32,
end: u32,
kind: CppHandlerKind,
catch_type: u32,
handler_rva: u32,
) -> Self {
Self {
start_rva: start,
end_rva: end,
handler_kind: kind,
catch_type,
handler_rva,
}
}
}
#[derive(Debug, Clone)]
pub struct CppFuncInfo {
pub magic: u32,
pub max_state: u32,
pub unwind_map: Vec<CppUnwindMapEntry>,
pub try_blocks: Vec<CppTryBlockDesc>,
pub ip_to_state_map: Vec<IpToStateEntry>,
pub es_type_list: Vec<u32>,
pub eh_flags: u32,
}
impl Default for CppFuncInfo {
fn default() -> Self {
Self {
magic: 0x1993_0522,
max_state: 0,
unwind_map: Vec::new(),
try_blocks: Vec::new(),
ip_to_state_map: Vec::new(),
es_type_list: Vec::new(),
eh_flags: 1,
}
}
}
impl CppFuncInfo {
pub fn new() -> Self {
Self::default()
}
pub fn encoded_size(&self) -> usize {
36 + self.unwind_map.len() * 8
+ self.try_blocks.len() * 20
+ self.ip_to_state_map.len() * 8
+ self.es_type_list.len() * 4
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CppUnwindMapEntry {
pub to_state: i32,
pub action: u32,
}
impl CppUnwindMapEntry {
pub fn new(to_state: i32, action: u32) -> Self {
Self { to_state, action }
}
}
#[derive(Debug, Clone)]
pub struct CppTryBlockDesc {
pub try_low: u32,
pub try_high: u32,
pub catch_high: u32,
pub num_catches: u32,
pub catch_handlers_rva: u32,
pub catch_handlers: Vec<CppCatchHandlerDesc>,
}
impl CppTryBlockDesc {
pub fn new(try_low: u32, try_high: u32, catch_high: u32) -> Self {
Self {
try_low,
try_high,
catch_high,
num_catches: 0,
catch_handlers_rva: 0,
catch_handlers: Vec::new(),
}
}
pub fn add_catch(&mut self, handler: CppCatchHandlerDesc) {
self.catch_handlers.push(handler);
self.num_catches = self.catch_handlers.len() as u32;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CppCatchHandlerDesc {
pub adjectives: u32,
pub type_rva: u32,
pub disp_frame: i32,
pub handler_rva: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct IpToStateEntry {
pub ip_rva: u32,
pub state: u32,
}
impl IpToStateEntry {
pub fn new(ip_rva: u32, state: u32) -> Self {
Self { ip_rva, state }
}
}
pub type VEHHandlerFn = unsafe extern "system" fn(exception_info: *mut u8) -> i32;
#[derive(Debug, Clone)]
pub struct VEHHandler {
pub handler: VEHHandlerFn,
pub first: bool,
pub id: u64,
}
impl VEHHandler {
pub fn new(handler: VEHHandlerFn, first: bool, id: u64) -> Self {
Self { handler, first, id }
}
}
#[derive(Debug, Clone, Default)]
pub struct VEHChain {
pub handlers: Vec<VEHHandler>,
next_id: u64,
}
impl VEHChain {
pub fn new() -> Self {
Self {
handlers: Vec::new(),
next_id: 1,
}
}
pub fn add_handler(&mut self, handler: VEHHandlerFn, first: bool) -> u64 {
let id = self.next_id;
self.next_id += 1;
let entry = VEHHandler::new(handler, first, id);
if first {
self.handlers.insert(0, entry);
} else {
self.handlers.push(entry);
}
id
}
pub fn remove_handler(&mut self, id: u64) -> bool {
let len_before = self.handlers.len();
self.handlers.retain(|h| h.id != id);
self.handlers.len() < len_before
}
pub fn dispatch(&self, exception_info: *mut u8) -> i32 {
for handler in &self.handlers {
let result = unsafe { (handler.handler)(exception_info) };
if result != seh_result::EXCEPTION_CONTINUE_SEARCH {
return result;
}
}
seh_result::EXCEPTION_CONTINUE_SEARCH
}
pub fn len(&self) -> usize {
self.handlers.len()
}
pub fn is_empty(&self) -> bool {
self.handlers.is_empty()
}
}
#[derive(Debug, Clone)]
pub struct X86Win64EH {
pub pdata: PdataBuilder,
pub xdata: XdataBuilder,
pub unwind_map: BTreeMap<u32, u32>,
pub c_handler_tables: HashMap<u32, CScopeTable>,
pub cpp_handler_infos: HashMap<u32, CppFuncInfo>,
pub veh_chain: VEHChain,
pub use_cxx_frame_handler3: bool,
}
impl Default for X86Win64EH {
fn default() -> Self {
Self {
pdata: PdataBuilder::new(),
xdata: XdataBuilder::new(),
unwind_map: BTreeMap::new(),
c_handler_tables: HashMap::new(),
cpp_handler_infos: HashMap::new(),
veh_chain: VEHChain::new(),
use_cxx_frame_handler3: true,
}
}
}
impl X86Win64EH {
pub fn new() -> Self {
Self::default()
}
pub fn register_function(
&mut self,
begin: u32,
end: u32,
unwind_info: UnwindInfo,
) -> Result<usize, String> {
let unwind_rva = self.xdata.add_unwind_info(unwind_info);
self.pdata.add_function(begin, end, unwind_rva)?;
self.unwind_map.insert(begin, unwind_rva);
Ok(self.pdata.len() - 1)
}
pub fn register_chained_function(
&mut self,
begin: u32,
end: u32,
primary_info: UnwindInfo,
chained_info: UnwindInfo,
) -> Result<usize, String> {
let mut primary = primary_info;
primary.set_chained_info(chained_info);
self.register_function(begin, end, primary)
}
pub fn build_standard_unwind_info(
prologue_ops: &[(u8, UnwindOpcode, u8)],
prologue_size: u8,
frame_reg: u8,
frame_offset: u8,
handler_rva: Option<u32>,
) -> UnwindInfo {
let mut info = UnwindInfo::new();
info.prologue_size = prologue_size;
info.frame_register = frame_reg;
info.frame_register_offset = frame_offset;
for &(offset, op, op_info) in prologue_ops.iter().rev() {
info.add_unwind_code(UnwindCode::new(offset, op, op_info));
}
if let Some(rva) = handler_rva {
info.set_exception_handler(rva, Vec::new());
}
info
}
pub fn register_c_handler(
&mut self,
begin: u32,
end: u32,
handler_rva: u32,
scope_table: &CScopeTable,
prologue_ops: &[(u8, UnwindOpcode, u8)],
prologue_size: u8,
frame_reg: u8,
frame_offset: u8,
) -> Result<usize, String> {
let handler_data = scope_table.encode();
let mut info = Self::build_standard_unwind_info(
prologue_ops,
prologue_size,
frame_reg,
frame_offset,
Some(handler_rva),
);
info.handler_data = handler_data;
self.c_handler_tables
.insert(handler_rva, scope_table.clone());
self.register_function(begin, end, info)
}
pub fn create_c_scope_table(regions: &[(u32, u32, u32, u32, u32)]) -> CScopeTable {
let mut table = CScopeTable::new();
for &(try_begin, try_end, filter, handler, jump) in regions {
table.add_scope(try_begin, try_end, filter, handler, jump);
}
table
}
pub fn register_cpp_function(
&mut self,
begin: u32,
end: u32,
handler_rva: u32,
func_info: CppFuncInfo,
prologue_ops: &[(u8, UnwindOpcode, u8)],
prologue_size: u8,
frame_reg: u8,
frame_offset: u8,
) -> Result<usize, String> {
let handler_data = func_info.encode_to_bytes();
let mut info = Self::build_standard_unwind_info(
prologue_ops,
prologue_size,
frame_reg,
frame_offset,
Some(handler_rva),
);
info.handler_data = handler_data;
self.cpp_handler_infos.insert(handler_rva, func_info);
self.register_function(begin, end, info)
}
pub fn find_function(&self, ip: u32) -> Option<&RuntimeFunction> {
self.pdata.find(ip)
}
pub fn find_unwind_info(&self, begin: u32) -> Option<&UnwindInfo> {
let unwind_rva = self.unwind_map.get(&begin)?;
self.xdata
.entries
.iter()
.find(|(rva, _)| rva == unwind_rva)
.map(|(_, info)| info)
}
pub fn find_c_scope_table(&self, handler_rva: u32) -> Option<&CScopeTable> {
self.c_handler_tables.get(&handler_rva)
}
pub fn find_cpp_func_info(&self, handler_rva: u32) -> Option<&CppFuncInfo> {
self.cpp_handler_infos.get(&handler_rva)
}
pub fn add_veh_handler(&mut self, handler: VEHHandlerFn, first: bool) -> u64 {
self.veh_chain.add_handler(handler, first)
}
pub fn remove_veh_handler(&mut self, id: u64) -> bool {
self.veh_chain.remove_handler(id)
}
pub fn dispatch_veh(&self, exception_info: *mut u8) -> i32 {
self.veh_chain.dispatch(exception_info)
}
pub fn encode_pdata(&self) -> Vec<u8> {
self.pdata.encode_to_bytes()
}
pub fn encode_xdata(&self) -> Vec<u8> {
self.xdata.encode_to_bytes()
}
pub fn build_simple_func_info(
try_start: u32,
try_end: u32,
catch_type_rva: u32,
handler_rva: u32,
) -> CppFuncInfo {
let mut info = CppFuncInfo::new();
info.unwind_map.push(CppUnwindMapEntry::new(0, 0));
let mut try_block = CppTryBlockDesc::new(1, 1, 2);
try_block.add_catch(CppCatchHandlerDesc {
adjectives: 0,
type_rva: catch_type_rva,
disp_frame: 0,
handler_rva,
});
info.try_blocks.push(try_block);
info.ip_to_state_map.push(IpToStateEntry::new(try_start, 1));
info.ip_to_state_map.push(IpToStateEntry::new(try_end, 0));
info.max_state = 1;
info
}
pub fn build_cleanup_func_info(try_start: u32, try_end: u32, cleanup_rva: u32) -> CppFuncInfo {
let mut info = CppFuncInfo::new();
info.unwind_map.push(CppUnwindMapEntry::new(0, cleanup_rva));
let try_block = CppTryBlockDesc::new(1, 1, 1);
info.try_blocks.push(try_block);
info.ip_to_state_map.push(IpToStateEntry::new(try_start, 1));
info.ip_to_state_map.push(IpToStateEntry::new(try_end, 0));
info.max_state = 1;
info
}
pub fn build_nested_func_info(
regions: &[(u32, u32, u32, u32)], ) -> CppFuncInfo {
let mut info = CppFuncInfo::new();
info.unwind_map.push(CppUnwindMapEntry::new(0, 0));
let mut state = 1u32;
for &(try_start, try_end, catch_type, handler_rva) in regions {
let catch_high = state + 1;
let mut try_block = CppTryBlockDesc::new(state, state, catch_high);
try_block.add_catch(CppCatchHandlerDesc {
adjectives: 0,
type_rva: catch_type,
disp_frame: 0,
handler_rva,
});
info.try_blocks.push(try_block);
info.unwind_map
.push(CppUnwindMapEntry::new(state.saturating_sub(1) as i32, 0));
info.ip_to_state_map
.push(IpToStateEntry::new(try_start, state));
info.ip_to_state_map
.push(IpToStateEntry::new(try_end, state.saturating_sub(1)));
state += 1;
}
info.max_state = state.saturating_sub(1);
info
}
}
impl CppFuncInfo {
pub fn encode_to_bytes(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(self.encoded_size());
buf.extend_from_slice(&self.magic.to_le_bytes());
buf.extend_from_slice(&self.max_state.to_le_bytes());
let unwind_map_offset = 36u32; buf.extend_from_slice(&unwind_map_offset.to_le_bytes());
buf.extend_from_slice(&(self.try_blocks.len() as u32).to_le_bytes());
let try_block_offset = unwind_map_offset + (self.unwind_map.len() as u32 * 8);
buf.extend_from_slice(&try_block_offset.to_le_bytes());
buf.extend_from_slice(&(self.ip_to_state_map.len() as u32).to_le_bytes());
let ip_map_offset = try_block_offset + (self.try_blocks.len() as u32 * 20);
buf.extend_from_slice(&ip_map_offset.to_le_bytes());
let es_offset = ip_map_offset + (self.ip_to_state_map.len() as u32 * 8);
buf.extend_from_slice(&es_offset.to_le_bytes());
buf.extend_from_slice(&self.eh_flags.to_le_bytes());
for entry in &self.unwind_map {
buf.extend_from_slice(&(entry.to_state as u32).to_le_bytes());
buf.extend_from_slice(&entry.action.to_le_bytes());
}
for block in &self.try_blocks {
buf.extend_from_slice(&block.try_low.to_le_bytes());
buf.extend_from_slice(&block.try_high.to_le_bytes());
buf.extend_from_slice(&block.catch_high.to_le_bytes());
buf.extend_from_slice(&block.num_catches.to_le_bytes());
buf.extend_from_slice(&0u32.to_le_bytes());
}
for entry in &self.ip_to_state_map {
buf.extend_from_slice(&entry.ip_rva.to_le_bytes());
buf.extend_from_slice(&entry.state.to_le_bytes());
}
for &rva in &self.es_type_list {
buf.extend_from_slice(&rva.to_le_bytes());
}
buf
}
}
#[derive(Debug, Clone)]
pub struct PrologueAnalysis {
pub unwind_info: UnwindInfo,
pub prologue_size: u8,
pub has_frame_pointer: bool,
pub frame_register: u8,
pub pushed_regs: Vec<u8>,
pub saved_regs: Vec<(u8, u32)>,
pub saved_xmm: Vec<(u8, u32)>,
pub stack_size: u32,
}
impl Default for PrologueAnalysis {
fn default() -> Self {
Self {
unwind_info: UnwindInfo::new(),
prologue_size: 0,
has_frame_pointer: false,
frame_register: 0,
pushed_regs: Vec::new(),
saved_regs: Vec::new(),
saved_xmm: Vec::new(),
stack_size: 0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PrologueInst {
Push(u8),
SubRsp(u32),
SaveReg(u8, u32),
SaveXmm(u8, u32),
SetFramePointer(u8),
LeaRsp(i32),
PushImm(u32),
MovSpFp,
Other(u8),
}
const PUSH_REG_SIZE: u8 = 1; const PUSH_IMM_SIZE: u8 = 5;
const SUB_RSP_IMM8_SIZE: u8 = 4;
const SUB_RSP_IMM32_SIZE: u8 = 7;
const MOV_RBP_RSP_SIZE: u8 = 3;
const MOV_REG_MEM_SIZE: u8 = 5; const MOVAPS_SIZE: u8 = 5;
impl PrologueInst {
pub fn size(&self) -> u8 {
match self {
Self::Push(_) => PUSH_REG_SIZE,
Self::PushImm(_) => PUSH_IMM_SIZE,
Self::SubRsp(imm) => {
if *imm <= 127 {
SUB_RSP_IMM8_SIZE
} else {
SUB_RSP_IMM32_SIZE
}
}
Self::SaveReg(_, _) => MOV_REG_MEM_SIZE + 2, Self::SaveXmm(_, _) => MOVAPS_SIZE + 2,
Self::SetFramePointer(_) => MOV_RBP_RSP_SIZE,
Self::LeaRsp(_) => SUB_RSP_IMM32_SIZE,
Self::MovSpFp => MOV_RBP_RSP_SIZE,
Self::Other(n) => *n,
}
}
}
pub fn analyze_prologue(insts: &[PrologueInst]) -> Result<PrologueAnalysis, String> {
let mut analysis = PrologueAnalysis::default();
let mut current_offset: u8 = 0;
let mut unwind_codes: Vec<UnwindCode> = Vec::new();
let mut reg_push_count = 0u8;
for &inst in insts {
let inst_size = inst.size();
match inst {
PrologueInst::Push(reg) => {
unwind_codes.push(UnwindCode::push_nonvol(current_offset, reg));
analysis.pushed_regs.push(reg);
reg_push_count += 1;
}
PrologueInst::PushImm(_) => {
unwind_codes.push(UnwindCode::alloc_small(current_offset, 0)); analysis.stack_size += 8;
}
PrologueInst::SubRsp(imm) => {
analysis.stack_size += imm;
let scaled = imm / 8;
if scaled < 8 {
unwind_codes.push(UnwindCode::alloc_small(
current_offset,
(scaled as u8).saturating_sub(1),
));
} else if scaled <= u16::MAX as u32 {
unwind_codes.push(UnwindCode::alloc_large(current_offset, 0));
unwind_codes.push(UnwindCode::new(
(scaled & 0xFF) as u8,
UnwindOpcode::PushNonVol,
((scaled >> 8) & 0xFF) as u8,
));
} else {
unwind_codes.push(UnwindCode::alloc_large(current_offset, 1));
unwind_codes.push(UnwindCode::new(
(scaled & 0xFF) as u8,
UnwindOpcode::PushNonVol,
((scaled >> 8) & 0xFF) as u8,
));
unwind_codes.push(UnwindCode::new(
((scaled >> 16) & 0xFF) as u8,
UnwindOpcode::PushNonVol,
((scaled >> 24) & 0xFF) as u8,
));
}
}
PrologueInst::SaveReg(reg, offset) => {
analysis.saved_regs.push((reg, offset));
let scaled = offset / 8;
if offset <= 0x7FFF8 {
unwind_codes.push(UnwindCode::save_nonvol(current_offset, reg));
unwind_codes.push(UnwindCode::new(
(scaled & 0xFF) as u8,
UnwindOpcode::PushNonVol,
((scaled >> 8) & 0xFF) as u8,
));
} else {
unwind_codes.push(UnwindCode::save_nonvol_far(current_offset, reg));
let word = offset;
unwind_codes.push(UnwindCode::new(
(word & 0xFF) as u8,
UnwindOpcode::PushNonVol,
((word >> 8) & 0xFF) as u8,
));
unwind_codes.push(UnwindCode::new(
((word >> 16) & 0xFF) as u8,
UnwindOpcode::PushNonVol,
((word >> 24) & 0xFF) as u8,
));
}
}
PrologueInst::SaveXmm(reg, offset) => {
analysis.saved_xmm.push((reg, offset));
let scaled = offset / 16;
if offset <= 0x7FFF0 {
unwind_codes.push(UnwindCode::save_xmm128(current_offset, reg));
unwind_codes.push(UnwindCode::new(
(scaled & 0xFF) as u8,
UnwindOpcode::PushNonVol,
((scaled >> 8) & 0xFF) as u8,
));
} else {
unwind_codes.push(UnwindCode::save_xmm128_far(current_offset, reg));
let word = offset;
unwind_codes.push(UnwindCode::new(
(word & 0xFF) as u8,
UnwindOpcode::PushNonVol,
((word >> 8) & 0xFF) as u8,
));
unwind_codes.push(UnwindCode::new(
((word >> 16) & 0xFF) as u8,
UnwindOpcode::PushNonVol,
((word >> 24) & 0xFF) as u8,
));
}
}
PrologueInst::SetFramePointer(reg) => {
analysis.has_frame_pointer = true;
analysis.frame_register = reg;
let fp_offset = reg_push_count * 8;
unwind_codes.push(UnwindCode::set_fpreg(current_offset));
unwind_codes.push(UnwindCode::new(
(reg | ((fp_offset / 16) << 4)) & 0xFF,
UnwindOpcode::PushNonVol,
(((reg as u16 | ((fp_offset / 16) as u16) << 4) >> 8) & 0xFF) as u8,
));
}
PrologueInst::LeaRsp(_) | PrologueInst::MovSpFp => {
}
PrologueInst::Other(_n) => {
}
}
current_offset += inst_size;
}
let mut info = UnwindInfo::new();
info.prologue_size = current_offset;
info.frame_register = analysis.frame_register;
info.frame_register_offset = (reg_push_count * 8 / 16) as u8;
info.unwind_codes = unwind_codes.into_iter().rev().collect();
info.count_of_codes = info
.unwind_codes
.iter()
.map(|c| {
if c.unwind_op.requires_extra_slot() {
2
} else {
1
}
})
.sum::<usize>() as u8;
analysis.unwind_info = info;
analysis.prologue_size = current_offset;
Ok(analysis)
}
pub fn create_chained_unwind(
all_codes: &[UnwindCode],
prologue_size: u8,
frame_reg: u8,
frame_offset: u8,
max_codes_per_info: usize,
) -> Result<(UnwindInfo, UnwindInfo), String> {
if all_codes.len() <= max_codes_per_info {
let mut info = UnwindInfo::new();
info.prologue_size = prologue_size;
info.frame_register = frame_reg;
info.frame_register_offset = frame_offset;
info.unwind_codes = all_codes.to_vec();
info.count_of_codes = info
.unwind_codes
.iter()
.map(|c| {
if c.unwind_op.requires_extra_slot() {
2
} else {
1
}
})
.sum::<usize>() as u8;
return Ok((info, UnwindInfo::new()));
}
let (primary_codes, remaining_codes) = all_codes.split_at(max_codes_per_info);
let mut primary = UnwindInfo::new();
primary.prologue_size = prologue_size;
primary.frame_register = frame_reg;
primary.frame_register_offset = frame_offset;
primary.unwind_codes = primary_codes.to_vec();
primary.count_of_codes = primary
.unwind_codes
.iter()
.map(|c| {
if c.unwind_op.requires_extra_slot() {
2
} else {
1
}
})
.sum::<usize>() as u8;
let mut secondary = UnwindInfo::new();
secondary.prologue_size = 0; secondary.frame_register = frame_reg;
secondary.frame_register_offset = frame_offset;
secondary.unwind_codes = remaining_codes.to_vec();
secondary.count_of_codes = secondary
.unwind_codes
.iter()
.map(|c| {
if c.unwind_op.requires_extra_slot() {
2
} else {
1
}
})
.sum::<usize>() as u8;
primary.set_chained_info(secondary.clone());
Ok((primary, secondary))
}
pub fn build_rbp_frame_unwind(
stack_alloc: u32,
pushed_regs: &[u8],
saved_xmm: &[(u8, u32)],
) -> Result<UnwindInfo, String> {
let mut info = UnwindInfo::new();
let mut offset: u8 = 0;
for ® in pushed_regs {
info.push_nonvol(offset, reg);
offset += PUSH_REG_SIZE;
}
if stack_alloc > 0 {
if stack_alloc < 128 {
info.alloc_small(offset, stack_alloc)?;
} else {
info.alloc_large(offset, stack_alloc)?;
}
offset += if stack_alloc <= 127 {
SUB_RSP_IMM8_SIZE
} else {
SUB_RSP_IMM32_SIZE
};
}
for &(reg, save_off) in saved_xmm {
info.save_xmm128(offset, reg, save_off)?;
offset += MOVAPS_SIZE + 2;
}
if !pushed_regs.is_empty() || stack_alloc > 0 {
let fp_reg = 5u8; let fp_off = pushed_regs.len() as u32 * 8;
info.set_fpreg(offset, fp_reg, fp_off)?;
offset += MOV_RBP_RSP_SIZE;
}
info.prologue_size = offset;
Ok(info)
}
pub fn build_win64_prologue_unwind(
stack_alloc: u32,
pushed_regs: &[u8],
saved_xmm: &[(u8, u32)],
) -> Result<UnwindInfo, String> {
let mut info = UnwindInfo::new();
let mut offset: u8 = 0;
for ® in pushed_regs {
info.push_nonvol(offset, reg);
offset += PUSH_REG_SIZE;
}
if stack_alloc > 0 {
if stack_alloc < 128 {
info.alloc_small(offset, stack_alloc)?;
} else {
info.alloc_large(offset, stack_alloc)?;
}
offset += if stack_alloc <= 127 {
SUB_RSP_IMM8_SIZE
} else {
SUB_RSP_IMM32_SIZE
};
}
for &(reg, save_off) in saved_xmm {
info.save_xmm128(offset, reg, save_off)?;
offset += MOVAPS_SIZE + 2;
}
info.prologue_size = offset;
Ok(info)
}
pub fn build_machframe_unwind(
has_error_code: bool,
additional_stack: u32,
) -> Result<UnwindInfo, String> {
let mut info = UnwindInfo::new();
let mut offset: u8 = 0;
info.push_machframe(offset, has_error_code);
if additional_stack > 0 {
offset += if has_error_code { 6 } else { 5 };
if additional_stack < 128 {
info.alloc_small(offset, additional_stack)?;
} else {
info.alloc_large(offset, additional_stack)?;
}
}
if has_error_code {
info.prologue_size = 5 + if additional_stack > 0 {
if additional_stack <= 127 {
SUB_RSP_IMM8_SIZE
} else {
SUB_RSP_IMM32_SIZE
}
} else {
0
};
} else {
info.prologue_size = 4 + if additional_stack > 0 {
if additional_stack <= 127 {
SUB_RSP_IMM8_SIZE
} else {
SUB_RSP_IMM32_SIZE
}
} else {
0
};
}
Ok(info)
}
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct ExceptionRegistration32 {
pub prev: u32,
pub handler: u32,
}
impl ExceptionRegistration32 {
pub const SIZE: usize = 8;
pub fn to_bytes(&self) -> [u8; 8] {
let mut buf = [0u8; 8];
buf[0..4].copy_from_slice(&self.prev.to_le_bytes());
buf[4..8].copy_from_slice(&self.handler.to_le_bytes());
buf
}
}
#[derive(Debug, Clone)]
#[repr(C)]
pub struct ExtendedExceptionRegistration32 {
pub prev: u32,
pub handler: u32,
pub scope_table: u32,
pub try_level: i32,
pub ebp: u32,
}
impl ExtendedExceptionRegistration32 {
pub const SIZE: usize = 20;
pub fn to_bytes(&self) -> [u8; 20] {
let mut buf = [0u8; 20];
buf[0..4].copy_from_slice(&self.prev.to_le_bytes());
buf[4..8].copy_from_slice(&self.handler.to_le_bytes());
buf[8..12].copy_from_slice(&self.scope_table.to_le_bytes());
buf[12..16].copy_from_slice(&(self.try_level as u32).to_le_bytes());
buf[16..20].copy_from_slice(&self.ebp.to_le_bytes());
buf
}
}
#[derive(Debug, Clone)]
#[repr(C)]
pub struct ExceptionRecord {
pub exception_code: u32,
pub exception_flags: u32,
pub exception_record: u64,
pub exception_address: u64,
pub number_parameters: u32,
pub exception_information: [u64; 15],
}
impl Default for ExceptionRecord {
fn default() -> Self {
Self {
exception_code: 0,
exception_flags: 0,
exception_record: 0,
exception_address: 0,
number_parameters: 0,
exception_information: [0; 15],
}
}
}
pub mod exception_codes {
pub const EXCEPTION_ACCESS_VIOLATION: u32 = 0xC000_0005;
pub const EXCEPTION_INT_DIVIDE_BY_ZERO: u32 = 0xC000_0094;
pub const EXCEPTION_INT_OVERFLOW: u32 = 0xC000_0095;
pub const EXCEPTION_STACK_OVERFLOW: u32 = 0xC000_00FD;
pub const EXCEPTION_ILLEGAL_INSTRUCTION: u32 = 0xC000_001D;
pub const EXCEPTION_BREAKPOINT: u32 = 0x8000_0003;
pub const EXCEPTION_SINGLE_STEP: u32 = 0x8000_0004;
pub const CXX_EXCEPTION: u32 = 0xE06D_7363; pub const CLR_EXCEPTION: u32 = 0xE043_4F4D;
pub const RPC_S_SERVER_UNAVAILABLE: u32 = 0x006B_AAB0;
}
pub mod exception_flags {
pub const EXCEPTION_NONCONTINUABLE: u32 = 0x01;
pub const EXCEPTION_UNWINDING: u32 = 0x02;
pub const EXCEPTION_EXIT_UNWIND: u32 = 0x04;
pub const EXCEPTION_STACK_INVALID: u32 = 0x08;
pub const EXCEPTION_NESTED_CALL: u32 = 0x10;
pub const EXCEPTION_TARGET_UNREACHABLE: u32 = 0x20;
pub const EXCEPTION_COLLIDED_UNWIND: u32 = 0x40;
}
#[derive(Debug, Clone)]
#[repr(C)]
pub struct DispatcherContext {
pub control_pc: u64,
pub image_base: u64,
pub function_entry: RuntimeFunction,
pub establisher_frame: u64,
pub target_ip: u64,
pub context_record: u64,
pub language_handler: u64,
pub handler_data: u64,
pub history_table: u64,
pub scope_index: u32,
pub unwind_flags: u32,
}
impl DispatcherContext {
pub fn new() -> Self {
Self {
control_pc: 0,
image_base: 0,
function_entry: RuntimeFunction::new(0, 0, 0),
establisher_frame: 0,
target_ip: 0,
context_record: 0,
language_handler: 0,
handler_data: 0,
history_table: 0,
scope_index: 0,
unwind_flags: 0,
}
}
}
pub type FunctionTableCallback =
unsafe extern "system" fn(control_pc: u64, context: *mut u8) -> *mut RuntimeFunction;
#[derive(Debug, Clone)]
pub struct FunctionTable {
pub base_address: u64,
pub entry_count: u32,
pub callback: Option<FunctionTableCallback>,
pub context: u64,
pub entries: Vec<RuntimeFunction>,
}
impl FunctionTable {
pub fn new_static(base: u64, entries: Vec<RuntimeFunction>) -> Self {
let count = entries.len() as u32;
Self {
base_address: base,
entry_count: count,
callback: None,
context: 0,
entries,
}
}
pub fn new_dynamic(
base: u64,
count: u32,
callback: FunctionTableCallback,
context: u64,
) -> Self {
Self {
base_address: base,
entry_count: count,
callback: Some(callback),
context,
entries: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct X64ExceptionDispatcher {
pub function_tables: Vec<FunctionTable>,
pub veh: VEHChain,
pub current_exception: Option<ExceptionRecord>,
pub exception_count: u64,
}
impl Default for X64ExceptionDispatcher {
fn default() -> Self {
Self {
function_tables: Vec::new(),
veh: VEHChain::new(),
current_exception: None,
exception_count: 0,
}
}
}
impl X64ExceptionDispatcher {
pub fn new() -> Self {
Self::default()
}
pub fn add_function_table(&mut self, table: FunctionTable) {
self.function_tables.push(table);
}
pub fn lookup_runtime_function(&self, control_pc: u64) -> Option<&RuntimeFunction> {
for table in &self.function_tables {
if control_pc >= table.base_address {
if let Some(ref callback) = table.callback {
let rf = unsafe { callback(control_pc, table.context as *mut u8) };
if !rf.is_null() {
return Some(unsafe { &*rf });
}
} else {
for entry in &table.entries {
let func_begin = table.base_address + entry.begin_address as u64;
let func_end = table.base_address + entry.end_address as u64;
if control_pc >= func_begin && control_pc < func_end {
return Some(entry);
}
}
}
}
}
None
}
pub fn dispatch(&mut self, record: ExceptionRecord) -> bool {
self.exception_count += 1;
self.current_exception = Some(record.clone());
let veh_info = &record as *const _ as *mut u8;
self.veh.dispatch(veh_info);
let func_rf = self.lookup_runtime_function(record.exception_address);
if func_rf.is_some() {
}
false
}
pub fn clear_exception(&mut self) {
self.current_exception = None;
}
pub fn stats(&self) -> X64ExceptionStats {
X64ExceptionStats {
exception_count: self.exception_count,
function_tables: self.function_tables.len() as u64,
veh_handlers: self.veh.len() as u64,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct X64ExceptionStats {
pub exception_count: u64,
pub function_tables: u64,
pub veh_handlers: u64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_unwind_code_encode_decode() {
let code = UnwindCode::push_nonvol(5, 3); let raw = code.encode();
let decoded = UnwindCode::decode(raw).unwrap();
assert_eq!(decoded.code_offset, 5);
assert_eq!(decoded.unwind_op, UnwindOpcode::PushNonVol);
assert_eq!(decoded.op_info, 3);
}
#[test]
fn test_all_opcodes_encode_decode() {
let test_cases = vec![
UnwindCode::push_nonvol(0, 0),
UnwindCode::alloc_large(1, 0),
UnwindCode::alloc_small(2, 3),
UnwindCode::save_nonvol(3, 4),
UnwindCode::save_nonvol_far(4, 5),
UnwindCode::save_xmm128(5, 6),
UnwindCode::save_xmm128_far(6, 7),
UnwindCode::epilog(7, 8),
UnwindCode::push_machframe(8, true),
UnwindCode::spare(9, 42),
];
for code in &test_cases {
let raw = code.encode();
let decoded = UnwindCode::decode(raw).unwrap();
assert_eq!(
decoded.code_offset, code.code_offset,
"offset mismatch for {:?}",
code.unwind_op
);
assert_eq!(decoded.unwind_op, code.unwind_op, "opcode mismatch");
assert_eq!(
decoded.op_info, code.op_info,
"op_info mismatch for {:?}",
code.unwind_op
);
}
}
#[test]
fn test_invalid_opcode() {
let raw: u16 = 0x00F0;
assert!(UnwindCode::decode(raw).is_none());
}
#[test]
fn test_display() {
let code = UnwindCode::push_nonvol(5, 3);
let s = format!("{code}");
assert!(s.contains("UWOP_PUSH_NONVOL"));
assert!(s.contains("0x5"));
}
#[test]
fn test_unwind_info_default() {
let info = UnwindInfo::default();
assert_eq!(info.version, 1);
assert_eq!(info.flags, 0);
assert_eq!(info.prologue_size, 0);
assert_eq!(info.count_of_codes, 0);
assert!(info.unwind_codes.is_empty());
assert!(info.exception_handler.is_none());
}
#[test]
fn test_unwind_info_push_nonvol() {
let mut info = UnwindInfo::new();
info.push_nonvol(0, 5); assert_eq!(info.unwind_codes.len(), 1);
}
#[test]
fn test_unwind_info_alloc_small() {
let mut info = UnwindInfo::new();
info.alloc_small(1, 16).unwrap();
assert_eq!(info.unwind_codes.len(), 1);
assert_eq!(info.unwind_codes[0].unwind_op, UnwindOpcode::AllocSmall);
assert_eq!(info.unwind_codes[0].op_info, 1); }
#[test]
fn test_unwind_info_alloc_small_invalid() {
let mut info = UnwindInfo::new();
assert!(info.alloc_small(1, 72).is_err()); assert!(info.alloc_small(1, 0).is_err()); assert!(info.alloc_small(1, 3).is_err()); }
#[test]
fn test_unwind_info_alloc_large() {
let mut info = UnwindInfo::new();
info.alloc_large(2, 1024).unwrap();
assert_eq!(info.unwind_codes.len(), 2); assert_eq!(info.unwind_codes[0].unwind_op, UnwindOpcode::AllocLarge);
}
#[test]
fn test_unwind_info_set_fpreg() {
let mut info = UnwindInfo::new();
info.push_nonvol(0, 5); info.set_fpreg(1, 5, 16).unwrap();
assert_eq!(info.frame_register, 5);
assert_eq!(info.frame_register_offset, 1); assert_eq!(info.unwind_codes.len(), 2); }
#[test]
fn test_unwind_info_save_nonvol() {
let mut info = UnwindInfo::new();
info.save_nonvol(3, 3, 64).unwrap(); assert_eq!(info.unwind_codes.len(), 2); }
#[test]
fn test_unwind_info_save_nonvol_far() {
let mut info = UnwindInfo::new();
info.save_nonvol(3, 3, 0x10000).unwrap();
assert_eq!(info.unwind_codes[0].unwind_op, UnwindOpcode::SaveNonVolFar);
assert_eq!(info.unwind_codes.len(), 3); }
#[test]
fn test_unwind_info_save_xmm128() {
let mut info = UnwindInfo::new();
info.save_xmm128(4, 6, 32).unwrap(); assert_eq!(info.unwind_codes.len(), 2);
}
#[test]
fn test_unwind_info_exception_handler() {
let mut info = UnwindInfo::new();
info.set_exception_handler(0x1234, vec![1, 2, 3, 4]);
assert!(info.exception_handler.is_some());
assert_eq!(
info.flags & unwind_flags::UNW_FLAG_EHANDLER,
unwind_flags::UNW_FLAG_EHANDLER
);
assert_eq!(info.handler_data, vec![1, 2, 3, 4]);
}
#[test]
fn test_unwind_info_chained() {
let mut primary = UnwindInfo::new();
primary.push_nonvol(0, 5);
let secondary = UnwindInfo::new();
primary.set_chained_info(secondary);
assert!(primary.chained_info.is_some());
assert_eq!(
primary.flags & unwind_flags::UNW_FLAG_CHAININFO,
unwind_flags::UNW_FLAG_CHAININFO
);
}
#[test]
fn test_unwind_info_encode_header() {
let mut info = UnwindInfo::new();
info.version = 1;
info.flags = unwind_flags::UNW_FLAG_EHANDLER;
info.prologue_size = 10;
info.count_of_codes = 3;
info.frame_register = 5;
info.frame_register_offset = 1;
let header = info.encode_header();
assert_eq!(header[0], 0x01 | (0x01 << 3)); assert_eq!(header[1], 10);
assert_eq!(header[2], 3);
assert_eq!(header[3], 5 | (1 << 4));
}
#[test]
fn test_unwind_info_encoded_size() {
let mut info = UnwindInfo::new();
info.push_nonvol(0, 5);
info.alloc_small(1, 16).unwrap();
let size = info.encoded_size();
assert_eq!(size, 8);
info.set_exception_handler(0x1000, vec![1, 2]);
let size_with_handler = info.encoded_size();
assert_eq!(size_with_handler, 16);
}
#[test]
fn test_unwind_info_encode_to_bytes() {
let mut info = UnwindInfo::new();
info.version = 1;
info.push_nonvol(0, 5);
info.alloc_small(1, 8).unwrap();
let bytes = info.encode_to_bytes();
assert_eq!(bytes.len(), 8);
assert_eq!(bytes[0], 0x01); assert_eq!(bytes[1], 0); assert_eq!(bytes[2], 2); }
#[test]
fn test_runtime_function_encode_decode() {
let rf = RuntimeFunction::new(0x1000, 0x1100, 0x2000);
let bytes = rf.encode_to_bytes();
let decoded = RuntimeFunction::decode_from_bytes(&bytes);
assert_eq!(decoded.begin_address, 0x1000);
assert_eq!(decoded.end_address, 0x1100);
assert_eq!(decoded.unwind_info_address, 0x2000);
}
#[test]
fn test_runtime_function_display() {
let rf = RuntimeFunction::new(0x1000, 0x1100, 0x2000);
let s = format!("{rf}");
assert!(s.contains("RUNTIME_FUNCTION"));
assert!(s.contains("0x00001000"));
}
#[test]
fn test_pdata_add_and_find() {
let mut pdata = PdataBuilder::new();
pdata.add_function(0x1000, 0x1100, 0x2000).unwrap();
pdata.add_function(0x2000, 0x2100, 0x3000).unwrap();
let rf = pdata.find(0x1050).unwrap();
assert_eq!(rf.begin_address, 0x1000);
let rf2 = pdata.find(0x2050).unwrap();
assert_eq!(rf2.begin_address, 0x2000);
assert!(pdata.find(0x500).is_none());
assert!(pdata.find(0x3000).is_none());
}
#[test]
fn test_pdata_detect_overlap() {
let mut pdata = PdataBuilder::new();
pdata.add_function(0x1000, 0x1100, 0x2000).unwrap();
pdata.add_function(0x1080, 0x1180, 0x2008).unwrap();
assert_eq!(pdata.len(), 2);
}
#[test]
fn test_pdata_encode_bytes() {
let mut pdata = PdataBuilder::new();
pdata.add_function(0x2000, 0x2100, 0x3000).unwrap();
pdata.add_function(0x1000, 0x1100, 0x2000).unwrap();
let bytes = pdata.encode_to_bytes();
assert_eq!(bytes.len(), 24);
let first_begin = u32::from_le_bytes(bytes[0..4].try_into().unwrap());
assert_eq!(first_begin, 0x1000);
}
#[test]
fn test_xdata_add_and_encode() {
let mut xdata = XdataBuilder::new();
let mut info = UnwindInfo::new();
info.push_nonvol(0, 5);
let rva1 = xdata.add_unwind_info(info);
let mut info2 = UnwindInfo::new();
info2.push_nonvol(0, 3);
let rva2 = xdata.add_unwind_info(info2);
assert_eq!(rva1, 0);
assert_ne!(rva2, 0); assert_eq!(xdata.len(), 2);
let bytes = xdata.encode_to_bytes();
assert!(!bytes.is_empty());
}
#[test]
fn test_c_scope_table_encode() {
let mut table = CScopeTable::new();
table.add_scope(0x100, 0x200, 0x300, 0x400, 0x500);
table.add_scope(0x600, 0x700, 0x800, 0x900, 0xA00);
let bytes = table.encode();
assert_eq!(bytes.len(), 44);
let count = u32::from_le_bytes(bytes[0..4].try_into().unwrap());
assert_eq!(count, 2);
}
#[test]
fn test_cpp_func_info_default_magic() {
let info = CppFuncInfo::default();
assert_eq!(info.magic, 0x1993_0522);
assert_eq!(info.eh_flags, 1);
}
#[test]
fn test_cpp_func_info_encode() {
let info = CppFuncInfo::new();
let bytes = info.encode_to_bytes();
assert_eq!(bytes.len(), 36);
let magic = u32::from_le_bytes(bytes[0..4].try_into().unwrap());
assert_eq!(magic, 0x1993_0522);
}
#[test]
fn test_build_simple_func_info() {
let info = X86Win64EH::build_simple_func_info(0x10, 0x50, 0x200, 0x300);
assert_eq!(info.max_state, 1);
assert_eq!(info.try_blocks.len(), 1);
assert_eq!(info.ip_to_state_map.len(), 2);
}
#[test]
fn test_build_nested_func_info() {
let regions = vec![(0x10, 0x20, 0x100, 0x200), (0x30, 0x40, 0x300, 0x400)];
let info = X86Win64EH::build_nested_func_info(®ions);
assert_eq!(info.max_state, 2);
assert_eq!(info.try_blocks.len(), 2);
}
#[test]
fn test_analyze_prologue_push_rbp_sub_rsp() {
let insts = vec![
PrologueInst::Push(5), PrologueInst::SetFramePointer(5), PrologueInst::SubRsp(32), ];
let analysis = analyze_prologue(&insts).unwrap();
assert!(analysis.has_frame_pointer);
assert_eq!(analysis.frame_register, 5);
assert_eq!(analysis.pushed_regs.len(), 1);
assert_eq!(analysis.stack_size, 32);
}
#[test]
fn test_analyze_prologue_save_xmm() {
let insts = vec![
PrologueInst::Push(3), PrologueInst::SubRsp(128), PrologueInst::SaveXmm(6, 16), PrologueInst::SaveXmm(7, 32), ];
let analysis = analyze_prologue(&insts).unwrap();
assert_eq!(analysis.saved_xmm.len(), 2);
assert_eq!(analysis.saved_xmm[0], (6, 16));
assert_eq!(analysis.saved_xmm[1], (7, 32));
}
#[test]
fn test_create_chained_unwind_fits() {
let codes: Vec<UnwindCode> = (0..5).map(|i| UnwindCode::push_nonvol(i, i)).collect();
let (primary, secondary) = create_chained_unwind(&codes, 20, 5, 1, 10).unwrap();
assert_eq!(primary.unwind_codes.len(), 5);
assert!(primary.chained_info.is_none());
assert!(secondary.unwind_codes.is_empty());
}
#[test]
fn test_create_chained_unwind_overflow() {
let codes: Vec<UnwindCode> = (0..15)
.map(|i| UnwindCode::push_nonvol(i % 256, i))
.collect();
let (primary, secondary) = create_chained_unwind(&codes, 40, 5, 1, 10).unwrap();
assert_eq!(primary.unwind_codes.len(), 10);
assert_eq!(secondary.unwind_codes.len(), 5);
assert!(primary.chained_info.is_some());
}
#[test]
fn test_build_rbp_frame() {
let info = build_rbp_frame_unwind(32, &[5], &[]).unwrap();
assert_eq!(info.frame_register, 5); assert!(!info.unwind_codes.is_empty());
}
#[test]
fn test_build_win64_prologue() {
let info = build_win64_prologue_unwind(64, &[3, 6], &[(6, 16)]).unwrap();
assert!(info.unwind_codes.len() >= 3);
}
#[test]
fn test_build_machframe() {
let info = build_machframe_unwind(true, 0).unwrap();
assert!(!info.unwind_codes.is_empty());
assert_eq!(info.unwind_codes[0].unwind_op, UnwindOpcode::PushMachFrame);
}
#[test]
fn test_win64eh_register_function() {
let mut eh = X86Win64EH::new();
let mut info = UnwindInfo::new();
info.push_nonvol(0, 5);
let idx = eh.register_function(0x1000, 0x1100, info).unwrap();
assert_eq!(idx, 0);
assert_eq!(eh.pdata.len(), 1);
assert_eq!(eh.xdata.len(), 1);
let rf = eh.find_function(0x1050).unwrap();
assert_eq!(rf.begin_address, 0x1000);
}
#[test]
fn test_win64eh_register_chained() {
let mut eh = X86Win64EH::new();
let mut primary = UnwindInfo::new();
primary.push_nonvol(0, 5);
let secondary = UnwindInfo::new();
let idx = eh
.register_chained_function(0x1000, 0x1100, primary, secondary)
.unwrap();
assert_eq!(idx, 0);
let found = eh.find_unwind_info(0x1000).unwrap();
assert!(found.chained_info.is_some());
}
#[test]
fn test_win64eh_register_c_handler() {
let mut eh = X86Win64EH::new();
let scope = X86Win64EH::create_c_scope_table(&[(0x10, 0x20, 0x100, 0x200, 0x300)]);
let prologue: Vec<(u8, UnwindOpcode, u8)> = vec![(0, UnwindOpcode::PushNonVol, 5)];
let idx = eh
.register_c_handler(0x1000, 0x1100, 0x2000, &scope, &prologue, 1, 5, 1)
.unwrap();
assert_eq!(idx, 0);
}
#[test]
fn test_win64eh_veh_chain() {
let mut eh = X86Win64EH::new();
let id = eh.add_veh_handler(veh_continue_search as VEHHandlerFn, true);
assert_eq!(eh.veh_chain.len(), 1);
assert!(eh.remove_veh_handler(id));
assert_eq!(eh.veh_chain.len(), 0);
}
#[test]
fn test_win64eh_encode_pdata_empty() {
let eh = X86Win64EH::new();
let bytes = eh.encode_pdata();
assert!(bytes.is_empty());
}
#[test]
fn test_win64eh_encode_pdata_with_function() {
let mut eh = X86Win64EH::new();
let mut info = UnwindInfo::new();
info.push_nonvol(0, 5);
eh.register_function(0x1000, 0x1100, info).unwrap();
let bytes = eh.encode_pdata();
assert_eq!(bytes.len(), 12);
}
#[test]
fn test_veh_chain_add_remove() {
let mut chain = VEHChain::new();
let id1 = chain.add_handler(veh_continue_search as VEHHandlerFn, true);
let id2 = chain.add_handler(veh_continue_search as VEHHandlerFn, false);
assert_eq!(chain.len(), 2);
assert!(chain.remove_handler(id1));
assert_eq!(chain.len(), 1);
assert!(!chain.remove_handler(id1)); }
#[test]
fn test_veh_chain_dispatch() {
let chain = VEHChain::new();
let result = chain.dispatch(std::ptr::null_mut());
assert_eq!(result, seh_result::EXCEPTION_CONTINUE_SEARCH);
}
#[test]
fn test_exception_record_default() {
let rec = ExceptionRecord::default();
assert_eq!(rec.exception_code, 0);
assert_eq!(rec.number_parameters, 0);
}
#[test]
fn test_prologue_inst_sizes() {
assert_eq!(PrologueInst::Push(5).size(), PUSH_REG_SIZE);
assert_eq!(PrologueInst::SubRsp(32).size(), SUB_RSP_IMM8_SIZE);
assert_eq!(PrologueInst::SubRsp(0x1000).size(), SUB_RSP_IMM32_SIZE);
assert_eq!(PrologueInst::SetFramePointer(5).size(), MOV_RBP_RSP_SIZE);
}
#[test]
fn test_function_table_static() {
let entries = vec![RuntimeFunction::new(0x100, 0x200, 0x300)];
let table = FunctionTable::new_static(0x1000, entries);
assert_eq!(table.entry_count, 1);
assert!(table.callback.is_none());
}
unsafe extern "system" fn dyn_table_callback(
_control_pc: u64,
_ctx: *mut u8,
) -> *mut RuntimeFunction {
std::ptr::null_mut()
}
#[test]
fn test_function_table_dynamic() {
let table =
FunctionTable::new_dynamic(0x1000, 10, dyn_table_callback as FunctionTableCallback, 0);
assert_eq!(table.entry_count, 10);
assert!(table.callback.is_some());
}
#[test]
fn test_dispatcher_lookup_static() {
let mut disp = X64ExceptionDispatcher::new();
let entries = vec![RuntimeFunction::new(0x100, 0x200, 0x300)];
let table = FunctionTable::new_static(0x1000, entries);
disp.add_function_table(table);
let rf = disp.lookup_runtime_function(0x1100);
assert!(rf.is_some());
assert_eq!(rf.unwrap().begin_address, 0x100);
let rf2 = disp.lookup_runtime_function(0x900);
assert!(rf2.is_none());
}
#[test]
fn test_dispatcher_dispatch() {
let mut disp = X64ExceptionDispatcher::new();
let rec = ExceptionRecord::default();
let result = disp.dispatch(rec);
assert!(!result);
assert_eq!(disp.exception_count, 1);
}
#[test]
fn test_build_standard_unwind_info() {
let ops: Vec<(u8, UnwindOpcode, u8)> = vec![
(0, UnwindOpcode::PushNonVol, 5),
(1, UnwindOpcode::AllocSmall, 1),
];
let info = X86Win64EH::build_standard_unwind_info(&ops, 5, 5, 1, Some(0x1000));
assert_eq!(info.prologue_size, 5);
assert_eq!(info.frame_register, 5);
assert!(info.exception_handler.is_some());
}
#[test]
fn test_exception_registration_32_encode() {
let reg = ExceptionRegistration32 {
prev: 0xFFFFFFFF,
handler: 0x1000,
};
let bytes = reg.to_bytes();
assert_eq!(bytes.len(), 8);
let prev = u32::from_le_bytes(bytes[0..4].try_into().unwrap());
assert_eq!(prev, 0xFFFFFFFF);
}
#[test]
fn test_extended_exception_registration_32_encode() {
let reg = ExtendedExceptionRegistration32 {
prev: 0xFFFFFFFF,
handler: 0x1000,
scope_table: 0x2000,
try_level: -1,
ebp: 0x3000,
};
let bytes = reg.to_bytes();
assert_eq!(bytes.len(), 20);
}
#[test]
fn test_exception_codes_constants() {
assert_eq!(exception_codes::EXCEPTION_ACCESS_VIOLATION, 0xC000_0005);
assert_eq!(exception_codes::CXX_EXCEPTION, 0xE06D_7363);
}
#[test]
fn test_dispatcher_context_new() {
let ctx = DispatcherContext::new();
assert_eq!(ctx.control_pc, 0);
assert_eq!(ctx.scope_index, 0);
}
#[test]
fn test_dispatcher_stats() {
let disp = X64ExceptionDispatcher::new();
let stats = disp.stats();
assert_eq!(stats.exception_count, 0);
assert_eq!(stats.function_tables, 0);
}
#[test]
fn test_create_c_scope_table() {
let table = X86Win64EH::create_c_scope_table(&[(0x10, 0x20, 0x100, 0x200, 0x300)]);
assert_eq!(table.len(), 1);
}
#[test]
fn test_requires_extra_slot() {
assert!(UnwindOpcode::AllocLarge.requires_extra_slot());
assert!(UnwindOpcode::SaveNonVolFar.requires_extra_slot());
assert!(UnwindOpcode::SaveXmm128Far.requires_extra_slot());
assert!(UnwindOpcode::SetFpReg.requires_extra_slot());
assert!(!UnwindOpcode::PushNonVol.requires_extra_slot());
assert!(!UnwindOpcode::AllocSmall.requires_extra_slot());
assert!(!UnwindOpcode::SaveNonVol.requires_extra_slot());
assert!(!UnwindOpcode::SaveXmm128.requires_extra_slot());
assert!(!UnwindOpcode::Epilog.requires_extra_slot());
assert!(!UnwindOpcode::PushMachFrame.requires_extra_slot());
}
#[test]
fn test_alloc_small_min_and_max() {
let mut info = UnwindInfo::new();
info.alloc_small(0, 8).unwrap();
assert_eq!(info.unwind_codes[0].op_info, 0);
let mut info2 = UnwindInfo::new();
info2.alloc_small(0, 64).unwrap();
assert_eq!(info2.unwind_codes[0].op_info, 7);
}
#[test]
fn test_version2_epilog() {
let mut info = UnwindInfo::new_v2();
assert_eq!(info.version, 2);
info.add_epilog(10, 5).unwrap();
assert_eq!(info.unwind_codes[0].unwind_op, UnwindOpcode::Epilog);
}
#[test]
fn test_version1_epilog_rejected() {
let mut info = UnwindInfo::new();
assert!(info.add_epilog(10, 5).is_err());
}
#[test]
fn test_pdata_invalid_range() {
let mut pdata = PdataBuilder::new();
assert!(pdata.add_function(0x2000, 0x1000, 0x3000).is_err());
assert!(pdata.add_function(0x1000, 0x1000, 0x3000).is_err());
}
#[test]
fn test_many_unwind_codes() {
let mut info = UnwindInfo::new();
for i in 0..20 {
info.push_nonvol(i, (i % 8) as u8);
}
assert_eq!(info.unwind_codes.len(), 20);
let bytes = info.encode_to_bytes();
assert!(!bytes.is_empty());
}
#[test]
fn test_deeply_chained_unwind() {
let mut level3 = UnwindInfo::new();
level3.push_nonvol(0, 12);
let mut level2 = UnwindInfo::new();
level2.push_nonvol(0, 13);
level2.set_chained_info(level3);
let mut level1 = UnwindInfo::new();
level1.push_nonvol(0, 14);
level1.set_chained_info(level2);
let size = level1.encoded_size();
assert!(size > 8);
let bytes = level1.encode_to_bytes();
assert!(!bytes.is_empty());
}
}
unsafe extern "system" fn veh_continue_search(_info: *mut u8) -> i32 {
seh_result::EXCEPTION_CONTINUE_SEARCH
}