use core::cmp::Ordering;
use core::fmt;
use core::iter::FusedIterator;
use scroll::ctx::TryFromCtx;
use scroll::{self, Pread, Pwrite, SizeWith};
use crate::error;
use crate::pe::data_directories;
use crate::pe::options;
use crate::pe::section_table;
use crate::pe::utils;
#[allow(unused)]
const UNW_FLAG_NHANDLER: u8 = 0x00;
const UNW_FLAG_EHANDLER: u8 = 0x01;
const UNW_FLAG_UHANDLER: u8 = 0x02;
const UNW_FLAG_CHAININFO: u8 = 0x04;
const UWOP_PUSH_NONVOL: u8 = 0;
const UWOP_ALLOC_LARGE: u8 = 1;
const UWOP_ALLOC_SMALL: u8 = 2;
const UWOP_SET_FPREG: u8 = 3;
const UWOP_SAVE_NONVOL: u8 = 4;
const UWOP_SAVE_NONVOL_FAR: u8 = 5;
const UWOP_EPILOG: u8 = 6;
const UWOP_SPARE_CODE: u8 = 7;
const UWOP_SAVE_XMM128: u8 = 8;
const UWOP_SAVE_XMM128_FAR: u8 = 9;
const UWOP_PUSH_MACHFRAME: u8 = 10;
const RUNTIME_FUNCTION_SIZE: usize = 12;
const UNWIND_CODE_SIZE: usize = 2;
#[derive(Debug, Copy, Clone, Default, PartialEq, Hash, Pread, Pwrite, SizeWith)]
#[repr(C)]
pub struct ScopeTableEntry {
pub begin: u32,
pub end: u32,
pub handler: u32,
pub target: u32,
}
#[derive(Debug)]
pub struct ScopeTableIterator<'a> {
data: &'a [u8],
offset: usize,
}
impl Iterator for ScopeTableIterator<'_> {
type Item = ScopeTableEntry;
fn next(&mut self) -> Option<Self::Item> {
if self.offset >= self.data.len() {
return None;
}
Some(
self.data
.gread_with(&mut self.offset, scroll::LE)
.expect("Scope table is not aligned"),
)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.data.len() / core::mem::size_of::<ScopeTableEntry>();
(len, Some(len))
}
}
impl FusedIterator for ScopeTableIterator<'_> {}
impl ExactSizeIterator for ScopeTableIterator<'_> {}
#[repr(C)]
#[derive(Copy, Clone, PartialEq, Default, Pread, Pwrite)]
pub struct RuntimeFunction {
pub begin_address: u32,
pub end_address: u32,
pub unwind_info_address: u32,
}
impl fmt::Debug for RuntimeFunction {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("RuntimeFunction")
.field("begin_address", &format_args!("{:#x}", self.begin_address))
.field("end_address", &format_args!("{:#x}", self.end_address))
.field(
"unwind_info_address",
&format_args!("{:#x}", self.unwind_info_address),
)
.finish()
}
}
#[derive(Debug)]
pub struct RuntimeFunctionIterator<'a> {
data: &'a [u8],
}
impl Iterator for RuntimeFunctionIterator<'_> {
type Item = error::Result<RuntimeFunction>;
fn next(&mut self) -> Option<Self::Item> {
if self.data.is_empty() {
return None;
}
Some(match self.data.pread_with(0, scroll::LE) {
Ok(func) => {
self.data = &self.data[RUNTIME_FUNCTION_SIZE..];
Ok(func)
}
Err(error) => {
self.data = &[];
Err(error.into())
}
})
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.data.len() / RUNTIME_FUNCTION_SIZE;
(len, Some(len))
}
}
impl FusedIterator for RuntimeFunctionIterator<'_> {}
impl ExactSizeIterator for RuntimeFunctionIterator<'_> {}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct Register(pub u8);
impl Register {
fn xmm(number: u8) -> Self {
Register(number + 17)
}
pub fn name(self) -> &'static str {
match self.0 {
0 => "$rax",
1 => "$rcx",
2 => "$rdx",
3 => "$rbx",
4 => "$rsp",
5 => "$rbp",
6 => "$rsi",
7 => "$rdi",
8 => "$r8",
9 => "$r9",
10 => "$r10",
11 => "$r11",
12 => "$r12",
13 => "$r13",
14 => "$r14",
15 => "$r15",
16 => "$rip",
17 => "$xmm0",
18 => "$xmm1",
19 => "$xmm2",
20 => "$xmm3",
21 => "$xmm4",
22 => "$xmm5",
23 => "$xmm6",
24 => "$xmm7",
25 => "$xmm8",
26 => "$xmm9",
27 => "$xmm10",
28 => "$xmm11",
29 => "$xmm12",
30 => "$xmm13",
31 => "$xmm14",
32 => "$xmm15",
_ => "",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum StackFrameOffset {
RSP(u32),
FP(u32),
}
impl StackFrameOffset {
fn with_ctx(offset: u32, ctx: UnwindOpContext) -> Self {
match ctx.frame_register {
Register(0) => StackFrameOffset::RSP(offset),
Register(_) => StackFrameOffset::FP(offset),
}
}
}
impl fmt::Display for Register {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.name())
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum UnwindOperation {
PushNonVolatile(Register),
Alloc(u32),
SetFPRegister,
SaveNonVolatile(Register, StackFrameOffset),
SaveXMM(Register, StackFrameOffset),
Epilog {
offset_low_or_size: u8,
offset_high_or_flags: u8,
},
SaveXMM128(Register, StackFrameOffset),
PushMachineFrame(bool),
Noop,
}
#[derive(Clone, Copy, Debug, PartialEq)]
struct UnwindOpContext {
version: u8,
frame_register: Register,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct UnwindCode {
pub code_offset: u8,
pub operation: UnwindOperation,
}
impl<'a> TryFromCtx<'a, UnwindOpContext> for UnwindCode {
type Error = error::Error;
#[inline]
fn try_from_ctx(bytes: &'a [u8], ctx: UnwindOpContext) -> Result<(Self, usize), Self::Error> {
let mut read = 0;
let code_offset = bytes.gread_with::<u8>(&mut read, scroll::LE)?;
let operation = bytes.gread_with::<u8>(&mut read, scroll::LE)?;
let operation_code = operation & 0xf;
let operation_info = operation >> 4;
let operation = match operation_code {
self::UWOP_PUSH_NONVOL => {
let register = Register(operation_info);
UnwindOperation::PushNonVolatile(register)
}
self::UWOP_ALLOC_LARGE => {
let offset = match operation_info {
0 => u32::from(bytes.gread_with::<u16>(&mut read, scroll::LE)?) * 8,
1 => bytes.gread_with::<u32>(&mut read, scroll::LE)?,
i => {
let msg = format!("invalid op info ({}) for UWOP_ALLOC_LARGE", i);
return Err(error::Error::Malformed(msg));
}
};
UnwindOperation::Alloc(offset)
}
self::UWOP_ALLOC_SMALL => {
let offset = u32::from(operation_info) * 8 + 8;
UnwindOperation::Alloc(offset)
}
self::UWOP_SET_FPREG => UnwindOperation::SetFPRegister,
self::UWOP_SAVE_NONVOL => {
let register = Register(operation_info);
let offset = u32::from(bytes.gread_with::<u16>(&mut read, scroll::LE)?) * 8;
UnwindOperation::SaveNonVolatile(register, StackFrameOffset::with_ctx(offset, ctx))
}
self::UWOP_SAVE_NONVOL_FAR => {
let register = Register(operation_info);
let offset = bytes.gread_with::<u32>(&mut read, scroll::LE)?;
UnwindOperation::SaveNonVolatile(register, StackFrameOffset::with_ctx(offset, ctx))
}
self::UWOP_EPILOG => {
if ctx.version == 1 {
let data = u32::from(bytes.gread_with::<u16>(&mut read, scroll::LE)?) * 16;
let register = Register::xmm(operation_info);
UnwindOperation::SaveXMM(register, StackFrameOffset::with_ctx(data, ctx))
} else if ctx.version == 2 {
UnwindOperation::Epilog {
offset_low_or_size: code_offset,
offset_high_or_flags: operation_info,
}
} else {
let msg = format!(
"Unwind info version has to be either one of `1` or `2`: {}",
ctx.version
);
return Err(error::Error::Malformed(msg));
}
}
self::UWOP_SPARE_CODE => {
let data = bytes.gread_with::<u32>(&mut read, scroll::LE)?;
if ctx.version == 1 {
let register = Register::xmm(operation_info);
UnwindOperation::SaveXMM128(register, StackFrameOffset::with_ctx(data, ctx))
} else if ctx.version == 2 {
UnwindOperation::Noop
} else {
let msg = format!(
"Unwind info version has to be either one of `1` or `2`: {}",
ctx.version
);
return Err(error::Error::Malformed(msg));
}
}
self::UWOP_SAVE_XMM128 => {
let register = Register::xmm(operation_info);
let offset = u32::from(bytes.gread_with::<u16>(&mut read, scroll::LE)?) * 16;
UnwindOperation::SaveXMM128(register, StackFrameOffset::with_ctx(offset, ctx))
}
self::UWOP_SAVE_XMM128_FAR => {
let register = Register::xmm(operation_info);
let offset = bytes.gread_with::<u32>(&mut read, scroll::LE)?;
UnwindOperation::SaveXMM128(register, StackFrameOffset::with_ctx(offset, ctx))
}
self::UWOP_PUSH_MACHFRAME => {
let is_error = match operation_info {
0 => false,
1 => true,
i => {
let msg = format!("invalid op info ({}) for UWOP_PUSH_MACHFRAME", i);
return Err(error::Error::Malformed(msg));
}
};
UnwindOperation::PushMachineFrame(is_error)
}
op => {
let msg = format!("unknown unwind op code ({})", op);
return Err(error::Error::Malformed(msg));
}
};
let code = UnwindCode {
code_offset,
operation,
};
Ok((code, read))
}
}
#[derive(Clone, Debug)]
pub struct UnwindCodeIterator<'a> {
bytes: &'a [u8],
offset: usize,
context: UnwindOpContext,
}
impl Iterator for UnwindCodeIterator<'_> {
type Item = error::Result<UnwindCode>;
fn next(&mut self) -> Option<Self::Item> {
if self.offset >= self.bytes.len() {
return None;
}
Some(self.bytes.gread_with(&mut self.offset, self.context))
}
fn size_hint(&self) -> (usize, Option<usize>) {
let upper = (self.bytes.len() - self.offset) / UNWIND_CODE_SIZE;
let lower = (upper + 3 - (upper % 3)) / 3;
(lower, Some(upper))
}
}
impl FusedIterator for UnwindCodeIterator<'_> {}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum UnwindHandler<'a> {
ExceptionHandler(u32, &'a [u8]),
TerminationHandler(u32, &'a [u8]),
}
#[derive(Clone)]
pub struct UnwindInfo<'a> {
pub version: u8,
pub size_of_prolog: u8,
pub frame_register: Register,
pub frame_register_offset: u32,
pub chained_info: Option<RuntimeFunction>,
pub handler: Option<UnwindHandler<'a>>,
code_bytes: &'a [u8],
}
impl<'a> UnwindInfo<'a> {
pub fn parse(bytes: &'a [u8], mut offset: usize) -> error::Result<Self> {
let version_flags: u8 = bytes.gread_with(&mut offset, scroll::LE)?;
let version = version_flags & 0b111;
let flags = version_flags >> 3;
if version < 1 || version > 2 {
let msg = format!("unsupported unwind code version ({})", version);
return Err(error::Error::Malformed(msg));
}
let size_of_prolog = bytes.gread_with::<u8>(&mut offset, scroll::LE)?;
let count_of_codes = bytes.gread_with::<u8>(&mut offset, scroll::LE)?;
let frame_info = bytes.gread_with::<u8>(&mut offset, scroll::LE)?;
let frame_register = Register(frame_info & 0xf);
let frame_register_offset = u32::from((frame_info >> 4) * 16);
let codes_size = count_of_codes as usize * UNWIND_CODE_SIZE;
let code_bytes = bytes.gread_with(&mut offset, codes_size)?;
if count_of_codes % 2 != 0 {
offset += 2;
}
let mut chained_info = None;
let mut handler = None;
if flags & UNW_FLAG_CHAININFO != 0 {
chained_info = Some(bytes.gread_with(&mut offset, scroll::LE)?);
} else if flags & (UNW_FLAG_EHANDLER | UNW_FLAG_UHANDLER) != 0 {
let address = bytes.gread_with::<u32>(&mut offset, scroll::LE)?;
if offset > bytes.len() {
return Err(error::Error::Malformed(format!(
"Offset {offset:#x} is too big to contain unwind handlers",
)));
}
let data = &bytes[offset..];
handler = Some(if flags & UNW_FLAG_EHANDLER != 0 {
UnwindHandler::ExceptionHandler(address, data)
} else {
UnwindHandler::TerminationHandler(address, data)
});
}
Ok(UnwindInfo {
version,
size_of_prolog,
frame_register,
frame_register_offset,
chained_info,
handler,
code_bytes,
})
}
pub fn unwind_codes(&self) -> UnwindCodeIterator<'a> {
UnwindCodeIterator {
bytes: self.code_bytes,
offset: 0,
context: UnwindOpContext {
version: self.version,
frame_register: self.frame_register,
},
}
}
pub fn c_scope_table_entries(&self) -> Option<ScopeTableIterator<'a>> {
let UnwindHandler::ExceptionHandler(_, data) = self.handler? else {
return None;
};
let mut offset = 0;
let num_entries = data.gread_with::<u32>(&mut offset, scroll::LE).ok()?;
let table_size = num_entries * core::mem::size_of::<ScopeTableEntry>() as u32;
let data = data.pread_with::<&[u8]>(offset, table_size as usize).ok()?;
Some(ScopeTableIterator { data, offset: 0 })
}
}
impl fmt::Debug for UnwindInfo<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let count_of_codes = self.code_bytes.len() / UNWIND_CODE_SIZE;
f.debug_struct("UnwindInfo")
.field("version", &self.version)
.field("size_of_prolog", &self.size_of_prolog)
.field("frame_register", &self.frame_register)
.field("frame_register_offset", &self.frame_register_offset)
.field("count_of_codes", &count_of_codes)
.field("chained_info", &self.chained_info)
.field("handler", &self.handler)
.finish()
}
}
impl<'a> IntoIterator for &'_ UnwindInfo<'a> {
type Item = error::Result<UnwindCode>;
type IntoIter = UnwindCodeIterator<'a>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
self.unwind_codes()
}
}
pub struct ExceptionData<'a> {
bytes: &'a [u8],
offset: usize,
size: usize,
file_alignment: u32,
}
impl<'a> ExceptionData<'a> {
pub fn parse(
bytes: &'a [u8],
directory: data_directories::DataDirectory,
sections: &[section_table::SectionTable],
file_alignment: u32,
) -> error::Result<Self> {
Self::parse_with_opts(
bytes,
directory,
sections,
file_alignment,
&options::ParseOptions::default(),
)
}
pub fn parse_with_opts(
bytes: &'a [u8],
directory: data_directories::DataDirectory,
sections: &[section_table::SectionTable],
file_alignment: u32,
opts: &options::ParseOptions,
) -> error::Result<Self> {
let size = directory.size as usize;
if size % RUNTIME_FUNCTION_SIZE != 0 {
return Err(error::Error::from(scroll::Error::BadInput {
size,
msg: "invalid exception directory table size",
}));
}
Self::parse_inner(bytes, directory, sections, file_alignment, opts)
}
pub fn parse_arm64_with_opts(
bytes: &'a [u8],
directory: data_directories::DataDirectory,
sections: &[section_table::SectionTable],
file_alignment: u32,
opts: &options::ParseOptions,
) -> error::Result<Self> {
let size = directory.size as usize;
if size % size_of::<Arm64RuntimeFunction>() != 0 {
return Err(error::Error::from(scroll::Error::BadInput {
size,
msg: "invalid exception directory table size",
}));
}
Self::parse_inner(bytes, directory, sections, file_alignment, opts)
}
fn parse_inner(
bytes: &'a [u8],
directory: data_directories::DataDirectory,
sections: &[section_table::SectionTable],
file_alignment: u32,
opts: &options::ParseOptions,
) -> error::Result<Self> {
let size = directory.size as usize;
let rva = directory.virtual_address as usize;
let offset = utils::find_offset(rva, sections, file_alignment, opts).ok_or_else(|| {
error::Error::Malformed(format!("cannot map exception_rva ({:#x}) into offset", rva))
})?;
if offset % 4 != 0 {
return Err(error::Error::from(scroll::Error::BadOffset(offset)));
}
Ok(ExceptionData {
bytes,
offset,
size,
file_alignment,
})
}
pub fn len(&self) -> usize {
self.size / RUNTIME_FUNCTION_SIZE
}
pub fn len_arm64(&self) -> usize {
self.size / size_of::<Arm64RuntimeFunction>()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn functions(&self) -> RuntimeFunctionIterator<'a> {
RuntimeFunctionIterator {
data: &self.bytes[self.offset..self.offset + self.size],
}
}
pub fn get_function(&self, index: usize) -> error::Result<RuntimeFunction> {
self.get_function_by_offset(self.offset + index * RUNTIME_FUNCTION_SIZE)
}
pub fn functions_arm64(&self) -> Arm64RuntimeFunctionIterator<'a> {
Arm64RuntimeFunctionIterator {
data: &self.bytes[self.offset..self.offset + self.size],
}
}
pub fn get_function_arm64(&self, index: usize) -> error::Result<Arm64RuntimeFunction> {
self.get_function_by_offset_arm64(self.offset + index * size_of::<Arm64RuntimeFunction>())
}
pub fn find_function(&self, rva: u32) -> error::Result<Option<RuntimeFunction>> {
let mut size = self.len();
if size == 0 {
return Ok(None);
}
let mut base = 0;
while size > 1 {
let half = size / 2;
let mid = base + half;
let offset = self.offset + mid * RUNTIME_FUNCTION_SIZE;
let addr = self.bytes.pread_with::<u32>(offset, scroll::LE)?;
base = if addr > rva { base } else { mid };
size -= half;
}
let offset = self.offset + base * RUNTIME_FUNCTION_SIZE;
let addr = self.bytes.pread_with::<u32>(offset, scroll::LE)?;
let function = match addr.cmp(&rva) {
Ordering::Less | Ordering::Equal => self.get_function(base)?,
Ordering::Greater if base == 0 => return Ok(None),
Ordering::Greater => self.get_function(base - 1)?,
};
if function.end_address > rva {
Ok(Some(function))
} else {
Ok(None)
}
}
pub fn get_unwind_info(
&self,
function: RuntimeFunction,
sections: &[section_table::SectionTable],
) -> error::Result<UnwindInfo<'a>> {
self.get_unwind_info_with_opts(function, sections, &options::ParseOptions::default())
}
pub fn get_unwind_info_arm64(
&self,
function: Arm64RuntimeFunction,
sections: &[section_table::SectionTable],
) -> Option<error::Result<Arm64UnwindInfo<'a>>> {
self.get_unwind_info_arm64_with_opts(function, sections, &options::ParseOptions::default())
}
pub fn get_unwind_info_with_opts(
&self,
mut function: RuntimeFunction,
sections: &[section_table::SectionTable],
opts: &options::ParseOptions,
) -> error::Result<UnwindInfo<'a>> {
while function.unwind_info_address % 2 != 0 {
let rva = (function.unwind_info_address & !1) as usize;
function = self.get_function_by_rva_with_opts(rva, sections, opts)?;
}
let rva = function.unwind_info_address as usize;
let offset =
utils::find_offset(rva, sections, self.file_alignment, opts).ok_or_else(|| {
error::Error::Malformed(format!("cannot map unwind rva ({:#x}) into offset", rva))
})?;
UnwindInfo::parse(self.bytes, offset)
}
pub fn get_unwind_info_arm64_with_opts(
&self,
function: Arm64RuntimeFunction,
sections: &[section_table::SectionTable],
opts: &options::ParseOptions,
) -> Option<error::Result<Arm64UnwindInfo<'a>>> {
if function.flag() == ARM64_PDATA_REF_TO_FULL_XDATA {
let rva = function.unwind_data_rva() as usize;
let offset = match utils::find_offset(rva, sections, self.file_alignment, opts)
.ok_or_else(|| {
error::Error::Malformed(format!("cannot map unwind rva ({rva:#x}) into offset"))
}) {
Ok(v) => v,
Err(err) => return Some(Err(err)),
};
Some(Arm64UnwindInfo::parse(self.bytes, offset))
} else {
None
}
}
#[allow(dead_code)]
fn get_function_by_rva(
&self,
rva: usize,
sections: &[section_table::SectionTable],
) -> error::Result<RuntimeFunction> {
self.get_function_by_rva_with_opts(rva, sections, &options::ParseOptions::default())
}
fn get_function_by_rva_with_opts(
&self,
rva: usize,
sections: &[section_table::SectionTable],
opts: &options::ParseOptions,
) -> error::Result<RuntimeFunction> {
let offset =
utils::find_offset(rva, sections, self.file_alignment, opts).ok_or_else(|| {
error::Error::Malformed(format!(
"cannot map exception rva ({:#x}) into offset",
rva
))
})?;
self.get_function_by_offset(offset)
}
#[inline]
fn get_function_by_offset(&self, offset: usize) -> error::Result<RuntimeFunction> {
debug_assert!((offset - self.offset) % RUNTIME_FUNCTION_SIZE == 0);
debug_assert!(offset < self.offset + self.size);
Ok(self.bytes.pread_with(offset, scroll::LE)?)
}
#[inline]
fn get_function_by_offset_arm64(&self, offset: usize) -> error::Result<Arm64RuntimeFunction> {
debug_assert!((offset - self.offset) % size_of::<Arm64RuntimeFunction>() == 0);
debug_assert!(offset < self.offset + self.size);
Ok(self.bytes.pread_with(offset, scroll::LE)?)
}
}
impl fmt::Debug for ExceptionData<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("ExceptionData")
.field("file_alignment", &self.file_alignment)
.field("offset", &format_args!("{:#x}", self.offset))
.field("size", &format_args!("{:#x}", self.size))
.field("len", &self.len())
.finish()
}
}
impl<'a> IntoIterator for &'_ ExceptionData<'a> {
type Item = error::Result<RuntimeFunction>;
type IntoIter = RuntimeFunctionIterator<'a>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
self.functions()
}
}
pub const ARM64_PDATA_REF_TO_FULL_XDATA: u32 = 0;
pub const ARM64_PDATA_PACKED_UNWIND_FUNCTION: u32 = 1;
pub const ARM64_PDATA_PACKED_UNWIND_FRAGMENT: u32 = 2;
pub const ARM64_PDATA_CR_UNCHAINED: u32 = 0;
pub const ARM64_PDATA_CR_UNCHAINED_SAVED_LR: u32 = 1;
pub const ARM64_PDATA_CR_CHAINED_WITH_PAC: u32 = 2;
pub const ARM64_PDATA_CR_CHAINED: u32 = 3;
#[repr(C)]
#[derive(Copy, Clone, PartialEq, Default, Pread, Pwrite)]
pub struct Arm64RuntimeFunction {
pub begin_address: u32,
pub unwind_data: u32,
}
const _: () = assert!(size_of::<Arm64RuntimeFunction>() == 8);
impl Arm64RuntimeFunction {
const fn mask(bits: u32) -> u32 {
(1u32 << bits) - 1
}
const fn bits(v: u32, shr: u32, bits: u32) -> u32 {
(v >> shr) & Self::mask(bits)
}
#[inline]
pub const fn flag(&self) -> u32 {
Self::bits(self.unwind_data, 0, 2)
}
#[inline]
pub const fn function_length(&self) -> u32 {
Self::bits(self.unwind_data, 2, 11).wrapping_mul(4)
}
#[inline]
pub const fn frame_size(&self) -> u32 {
Self::bits(self.unwind_data, 23, 9).wrapping_mul(16)
}
#[inline]
pub const fn cr(&self) -> u32 {
Self::bits(self.unwind_data, 21, 2)
}
#[inline]
pub const fn h(&self) -> bool {
Self::bits(self.unwind_data, 20, 1) != 0
}
#[inline]
pub const fn reg_i(&self) -> u32 {
Self::bits(self.unwind_data, 16, 4)
}
#[inline]
pub const fn reg_f(&self) -> u32 {
Self::bits(self.unwind_data, 13, 3)
}
#[inline]
pub const fn is_packed(&self) -> bool {
self.flag() != ARM64_PDATA_REF_TO_FULL_XDATA
}
#[inline]
pub const fn unwind_data_rva(&self) -> u32 {
self.unwind_data & !Self::mask(2)
}
}
impl fmt::Debug for Arm64RuntimeFunction {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Arm64RuntimeFunction")
.field("begin_address", &format_args!("{:#x}", self.begin_address))
.field("unwind_data", &format_args!("{:#x}", self.unwind_data))
.finish()
}
}
#[derive(Debug)]
pub struct Arm64RuntimeFunctionIterator<'a> {
data: &'a [u8],
}
impl Iterator for Arm64RuntimeFunctionIterator<'_> {
type Item = error::Result<Arm64RuntimeFunction>;
fn next(&mut self) -> Option<Self::Item> {
if self.data.is_empty() {
return None;
}
Some(match self.data.pread_with(0, scroll::LE) {
Ok(func) => {
self.data = &self.data[size_of::<Arm64RuntimeFunction>()..];
Ok(func)
}
Err(error) => {
self.data = &[];
Err(error.into())
}
})
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.data.len() / size_of::<Arm64RuntimeFunction>();
(len, Some(len))
}
}
impl FusedIterator for Arm64RuntimeFunctionIterator<'_> {}
impl ExactSizeIterator for Arm64RuntimeFunctionIterator<'_> {}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Arm64UnwindHeader(pub u32);
const _: () = assert!(size_of::<Arm64UnwindHeader>() == 4);
impl Arm64UnwindHeader {
const fn mask(bits: u32) -> u32 {
(1u32 << bits) - 1
}
const fn bits(v: u32, shr: u32, bits: u32) -> u32 {
(v >> shr) & Self::mask(bits)
}
#[inline]
pub const fn function_length(&self) -> u32 {
Self::bits(self.0, 0, 18).wrapping_mul(4)
}
#[inline]
pub const fn version(&self) -> u32 {
Self::bits(self.0, 18, 2)
}
#[inline]
pub const fn exception_data_present(&self) -> bool {
Self::bits(self.0, 20, 1) != 0
}
#[inline]
pub const fn epilog_in_header(&self) -> bool {
Self::bits(self.0, 21, 1) != 0
}
#[inline]
pub const fn epilog_count(&self) -> u32 {
Self::bits(self.0, 22, 5)
}
#[inline]
pub const fn code_words(&self) -> u32 {
Self::bits(self.0, 27, 5)
}
#[inline]
pub const fn has_extension(&self) -> bool {
self.epilog_count() == 0 && self.code_words() == 0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Arm64UnwindExtension(pub u32);
const _: () = assert!(size_of::<Arm64UnwindExtension>() == 4);
impl Arm64UnwindExtension {
#[inline]
pub const fn epilog_count(&self) -> u32 {
self.0 & 0xFFFF
}
#[inline]
pub const fn code_words(&self) -> u32 {
(self.0 >> 16) & 0xFF
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Arm64EpilogScope(pub u32);
const _: () = assert!(size_of::<Arm64EpilogScope>() == 4);
impl Arm64EpilogScope {
const fn mask(bits: u32) -> u32 {
(1u32 << bits) - 1
}
const fn bits(v: u32, shr: u32, bits: u32) -> u32 {
(v >> shr) & Self::mask(bits)
}
#[inline]
pub const fn start_offset_words(&self) -> u32 {
Self::bits(self.0, 0, 18).wrapping_mul(4)
}
#[inline]
pub const fn condition(&self) -> u32 {
Self::bits(self.0, 18, 4)
}
#[inline]
pub const fn start_index(&self) -> u32 {
Self::bits(self.0, 22, 10)
}
}
#[derive(Debug)]
pub struct Arm64EpilogScopeIterator<'a> {
bytes: &'a [u8],
offset: usize,
}
impl Iterator for Arm64EpilogScopeIterator<'_> {
type Item = error::Result<Arm64EpilogScope>;
fn next(&mut self) -> Option<Self::Item> {
if self.offset >= self.bytes.len() {
return None;
}
Some(
self.bytes
.gread_with::<u32>(&mut self.offset, scroll::LE)
.map(Arm64EpilogScope)
.map_err(Into::into),
)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = (self.bytes.len().saturating_sub(self.offset)) / size_of_val(&0u32);
(len, Some(len))
}
}
impl FusedIterator for Arm64EpilogScopeIterator<'_> {}
impl ExactSizeIterator for Arm64EpilogScopeIterator<'_> {}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Arm64ExceptionHandler<'a> {
pub rva: u32,
pub data: &'a [u8],
}
#[derive(Clone, Debug)]
pub struct Arm64UnwindInfo<'a> {
pub header: Arm64UnwindHeader,
pub extension: Option<Arm64UnwindExtension>,
pub exception_handler: Option<Arm64ExceptionHandler<'a>>,
epilog_scope_bytes: &'a [u8],
unwind_code_bytes: &'a [u8],
}
impl<'a> Arm64UnwindInfo<'a> {
fn checked_mul4(words: u32) -> error::Result<usize> {
let bytes = words
.checked_mul(4)
.ok_or_else(|| error::Error::Malformed("arm64 unwind info size overflow".into()))?;
usize::try_from(bytes)
.map_err(|_| error::Error::Malformed("arm64 unwind info size overflow".into()))
}
fn checked_add(a: usize, b: usize) -> error::Result<usize> {
a.checked_add(b)
.ok_or_else(|| error::Error::Malformed("arm64 unwind info size overflow".into()))
}
pub fn size(bytes: &[u8]) -> error::Result<usize> {
let w0 = bytes.pread_with::<u32>(0, scroll::LE)?;
let (mut size_u32, epilog_scopes, unwind_words) = if (w0 >> 22) != 0 {
let size = 4u32;
let epilog_scopes = (w0 >> 22) & 0x1f;
let unwind_words = (w0 >> 27) & 0x1f;
(size, epilog_scopes, unwind_words)
} else {
let w1 = bytes.pread_with::<u32>(4, scroll::LE)?;
let size = 8u32;
let epilog_scopes = w1 & 0xffff;
let unwind_words = (w1 >> 16) & 0xff;
(size, epilog_scopes, unwind_words)
};
let epilog_in_header = (w0 & (1u32 << 21)) != 0;
if !epilog_in_header {
size_u32 = Self::checked_add(size_u32 as _, Self::checked_mul4(epilog_scopes)?)? as _;
}
size_u32 = Self::checked_add(size_u32 as _, Self::checked_mul4(unwind_words)?)? as _;
let exception_data_present = (w0 & (1u32 << 20)) != 0;
if exception_data_present {
size_u32 = Self::checked_add(size_u32 as _, 4)? as _;
}
Ok(size_u32 as usize)
}
pub fn parse(bytes: &'a [u8], mut offset: usize) -> error::Result<Self> {
let header_word = bytes.gread_with::<u32>(&mut offset, scroll::LE)?;
let header = Arm64UnwindHeader(header_word);
if header.version() != 0 {
return Err(error::Error::Malformed(format!(
"unsupported ARM64 .xdata version ({})",
header.version()
)));
}
let extension = if header.has_extension() {
let ext_word = bytes.gread_with::<u32>(&mut offset, scroll::LE)?;
Some(Arm64UnwindExtension(ext_word))
} else {
None
};
let epilog_scopes = match (header.epilog_in_header(), extension) {
(false, Some(ext)) => ext.epilog_count(),
(false, None) => header.epilog_count(),
(true, _) => 0,
};
let unwind_words = if let Some(ext) = extension {
ext.code_words()
} else {
header.code_words()
};
let scope_bytes_len = Self::checked_mul4(epilog_scopes)?;
let epilog_scope_bytes = bytes.gread_with(&mut offset, scope_bytes_len)?;
let unwind_bytes_len = Self::checked_mul4(unwind_words)?;
let unwind_code_bytes = bytes.gread_with(&mut offset, unwind_bytes_len)?;
let exception_handler = if header.exception_data_present() {
let handler_rva = bytes.gread_with::<u32>(&mut offset, scroll::LE)?;
let data = bytes.get(offset..).unwrap_or(&[]);
Some(Arm64ExceptionHandler {
rva: handler_rva,
data,
})
} else {
None
};
Ok(Self {
header,
extension,
epilog_scope_bytes,
unwind_code_bytes,
exception_handler,
})
}
pub fn epilog_scopes(&self) -> Option<Arm64EpilogScopeIterator<'a>> {
if self.header.epilog_in_header() || self.epilog_scope_bytes.is_empty() {
return None;
}
Some(Arm64EpilogScopeIterator {
bytes: self.epilog_scope_bytes,
offset: 0,
})
}
pub fn unwind_codes(&self, start_index: u16) -> error::Result<Arm64UnwindCodeIterator<'a>> {
Arm64UnwindCodeIterator::new(self.unwind_code_bytes, start_index as usize)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Arm64CustomStackCase {
TrapFrame,
MachineFrame,
Context,
EcContext,
ClearUnwoundToCall,
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Arm64UnwindCode {
AllocSmall {
size_bytes: u32,
},
SaveR19R20Preindexed {
offset_bytes: u32,
},
SaveFpLr {
offset_bytes: u32,
},
SaveFpLrPreindexed {
offset_bytes: u32,
},
AllocMedium {
size_bytes: u32,
},
SaveRegPair {
first_reg: u8,
offset_bytes: u32,
},
SaveRegPairPreindexed {
first_reg: u8,
offset_bytes: u32,
},
SaveReg {
reg: u8,
offset_bytes: u32,
},
SaveRegPreindexed {
reg: u8,
offset_bytes: u32,
},
SaveLrPair {
first_reg: u8,
offset_bytes: u32,
},
SaveFRegPair {
first_reg: u8,
offset_bytes: u32,
},
SaveFRegPairPreindexed {
first_reg: u8,
offset_bytes: u32,
},
SaveFReg {
reg: u8,
offset_bytes: u32,
},
SaveFRegPreindexed {
reg: u8,
offset_bytes: u32,
},
AllocZ {
count: u8,
},
AllocLarge {
size_bytes: u32,
},
SetFp,
AddFp {
imm8: u8,
offset_bytes: u32,
},
Nop,
End,
EndC,
SaveNext,
SaveAnyReg {
kind: u8,
pair: bool,
preindexed: bool,
reg: u8,
offset_bytes: u32,
},
SaveZReg {
r: u8,
o: u16,
},
SavePReg {
r: u8,
o: u16,
},
CustomStack(Arm64CustomStackCase),
PacSignLr,
Reserved {
opcode: u8,
data: u32,
data_len: u8,
},
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Arm64UnwindCodeData {
pub offset: u16,
pub code: Arm64UnwindCode,
pub len: u8,
}
pub struct Arm64UnwindCodeIterator<'a> {
bytes: &'a [u8],
offset: usize,
}
impl<'a> Arm64UnwindCodeIterator<'a> {
pub fn new(raw_unwind_bytes: &'a [u8], start: usize) -> error::Result<Self> {
if raw_unwind_bytes.len() % 4 != 0 {
return Err(error::Error::Malformed("length not a multiple of 4".into()));
}
if start > raw_unwind_bytes.len() {
return Err(error::Error::Malformed("start index out of range".into()));
}
Ok(Self {
bytes: raw_unwind_bytes,
offset: start,
})
}
}
impl<'a> Iterator for Arm64UnwindCodeIterator<'a> {
type Item = error::Result<Arm64UnwindCodeData>;
fn next(&mut self) -> Option<Self::Item> {
if self.offset >= self.bytes.len() {
return None;
}
macro_rules! fail {
($msg:expr) => {{
self.offset = self.bytes.len();
return Some(Err($crate::error::Error::Malformed(($msg).into())));
}};
($fmt:literal $(, $args:expr)* $(,)?) => {{
self.offset = self.bytes.len();
return Some(Err($crate::error::Error::Malformed(format!($fmt $(, $args)*))));
}};
}
macro_rules! try_read_u8 {
($at:expr) => {{
match self.bytes.pread::<u8>($at) {
Ok(v) => v,
Err(err) => fail!("{}", err),
}
}};
($at:expr, $msg:expr) => {{
match self.bytes.pread::<u8>($at) {
Ok(v) => v,
Err(_) => fail!($msg),
}
}};
($at:expr, $fmt:literal $(, $args:expr)* $(,)?) => {{
match self.bytes.pread::<u8>($at) {
Ok(v) => v,
Err(_) => fail!($fmt $(, $args)*),
}
}};
}
let start = self.offset;
let b0 = try_read_u8!(start);
let (code, len) = match b0 {
0x00..=0x1f => {
let x = (b0 & 0x1f) as u32;
(
Arm64UnwindCode::AllocSmall {
size_bytes: x.wrapping_mul(16),
},
1,
)
}
0x20..=0x3f => {
let z = (b0 & 0x1f) as u32;
(
Arm64UnwindCode::SaveR19R20Preindexed {
offset_bytes: z.wrapping_mul(8),
},
1,
)
}
0x40..=0x7f => {
let z = (b0 & 0x3f) as u32;
(
Arm64UnwindCode::SaveFpLr {
offset_bytes: z.wrapping_mul(8),
},
1,
)
}
0x80..=0xbf => {
let z = (b0 & 0x3f) as u32;
(
Arm64UnwindCode::SaveFpLrPreindexed {
offset_bytes: (z.wrapping_add(1)).wrapping_mul(8),
},
1,
)
}
0xc0..=0xc7 => {
let r = try_read_u8!(start + 1, "alloc_m is truncated");
let x = (((b0 & 0x07) as u32) << 8) | (r as u32);
(
Arm64UnwindCode::AllocMedium {
size_bytes: x.wrapping_mul(16),
},
2,
)
}
0xc8..=0xcb => {
let b1 = try_read_u8!(start + 1, "save_regp is truncated");
let x = (((b0 & 0x03) as u32) << 2) | (((b1 >> 6) & 0x03) as u32);
let z = (b1 & 0x3f) as u32;
let first_reg = 19u8.wrapping_add(x as u8);
(
Arm64UnwindCode::SaveRegPair {
first_reg,
offset_bytes: z.wrapping_mul(8),
},
2,
)
}
0xcc..=0xcf => {
let b1 = try_read_u8!(start + 1, "save_regp_x is truncated");
let x = (((b0 & 0x03) as u32) << 2) | (((b1 >> 6) & 0x03) as u32);
let z = (b1 & 0x3f) as u32;
let first_reg = 19u8.wrapping_add(x as u8);
(
Arm64UnwindCode::SaveRegPairPreindexed {
first_reg,
offset_bytes: (z.wrapping_add(1)).wrapping_mul(8),
},
2,
)
}
0xd0..=0xd3 => {
let b1 = try_read_u8!(start + 1, "save_reg is truncated");
let x = (((b0 & 0x03) as u32) << 2) | (((b1 >> 6) & 0x03) as u32);
let z = (b1 & 0x3f) as u32;
let reg = 19u8.wrapping_add(x as u8);
(
Arm64UnwindCode::SaveReg {
reg,
offset_bytes: z.wrapping_mul(8),
},
2,
)
}
0xd4..=0xd5 => {
let b1 = try_read_u8!(start + 1, "save_reg_x is truncated");
let x = (((b0 & 0x01) as u32) << 3) | (((b1 >> 5) & 0x07) as u32);
let z = (b1 & 0x1f) as u32;
let reg = 19u8.wrapping_add(x as u8);
(
Arm64UnwindCode::SaveRegPreindexed {
reg,
offset_bytes: (z.wrapping_add(1)).wrapping_mul(8),
},
2,
)
}
0xd6..=0xd7 => {
let b1 = try_read_u8!(start + 1, "save_lrpair is truncated");
let x = (((b0 & 0x01) as u32) << 2) | (((b1 >> 6) & 0x03) as u32);
let z = (b1 & 0x3f) as u32;
let first_reg = 19u8.wrapping_add((2 * x) as u8);
(
Arm64UnwindCode::SaveLrPair {
first_reg,
offset_bytes: z.wrapping_mul(8),
},
2,
)
}
0xd8..=0xd9 => {
let b1 = try_read_u8!(start + 1, "save_fregp is truncated");
let x = (((b0 & 0x01) as u32) << 2) | (((b1 >> 6) & 0x03) as u32);
let z = (b1 & 0x3f) as u32;
let first_reg = 8u8.wrapping_add(x as u8);
(
Arm64UnwindCode::SaveFRegPair {
first_reg,
offset_bytes: z.wrapping_mul(8),
},
2,
)
}
0xda..=0xdb => {
let b1 = try_read_u8!(start + 1, "save_fregp_x is truncated");
let x = (((b0 & 0x01) as u32) << 2) | (((b1 >> 6) & 0x03) as u32);
let z = (b1 & 0x3f) as u32;
let first_reg = 8u8.wrapping_add(x as u8);
(
Arm64UnwindCode::SaveFRegPairPreindexed {
first_reg,
offset_bytes: (z.wrapping_add(1)).wrapping_mul(8),
},
2,
)
}
0xdc..=0xdd => {
let b1 = try_read_u8!(start + 1, "save_freg is truncated");
let x = (((b0 & 0x01) as u32) << 2) | (((b1 >> 6) & 0x03) as u32);
let z = (b1 & 0x3f) as u32;
let reg = 8u8.wrapping_add(x as u8);
(
Arm64UnwindCode::SaveFReg {
reg,
offset_bytes: z.wrapping_mul(8),
},
2,
)
}
0xde => {
let b1 = try_read_u8!(start + 1, "save_freg_x is truncated");
let x = ((b1 >> 5) & 0x07) as u32;
let z = (b1 & 0x1f) as u32;
let reg = 8u8.wrapping_add(x as u8);
(
Arm64UnwindCode::SaveFRegPreindexed {
reg,
offset_bytes: (z.wrapping_add(1)).wrapping_mul(8),
},
2,
)
}
0xdf => {
let z = try_read_u8!(start + 1, "alloc_z is truncated");
(Arm64UnwindCode::AllocZ { count: z }, 2)
}
0xe0 => {
let imm = {
let b0 = try_read_u8!(start + 1) as u32;
let b1 = try_read_u8!(start + 2) as u32;
let b2 = try_read_u8!(start + 3) as u32;
(b0 << 16) | (b1 << 8) | b2
};
(
Arm64UnwindCode::AllocLarge {
size_bytes: imm.wrapping_mul(16),
},
4,
)
}
0xe1 => (Arm64UnwindCode::SetFp, 1),
0xe2 => {
let imm8 = try_read_u8!(start + 1, "add_fp is truncated");
(
Arm64UnwindCode::AddFp {
imm8,
offset_bytes: (imm8 as u32).wrapping_mul(8),
},
2,
)
}
0xe3 => (Arm64UnwindCode::Nop, 1),
0xe4 => (Arm64UnwindCode::End, 1),
0xe5 => (Arm64UnwindCode::EndC, 1),
0xe6 => (Arm64UnwindCode::SaveNext, 1),
0xe7 => {
let b1 = try_read_u8!(start + 1);
if (b1 & 0x80) != 0 {
let data = b1 as u32;
(
Arm64UnwindCode::Reserved {
opcode: 0xe7,
data,
data_len: 1,
},
2,
)
} else {
let b2 = try_read_u8!(start + 2, "save_any_? is truncated");
let cls = (b2 >> 6) & 0x03;
if cls != 3 {
let p = ((b1 >> 6) & 1) != 0;
let x = ((b1 >> 5) & 1) != 0;
let r = (b1 & 0x1f) as u8;
let o = (b2 & 0x3f) as u32;
let offset_bytes = match (cls, x || p) {
(0 | 1, true) => match o.checked_mul(16) {
Some(v) => v,
None => fail!("save_any_reg offset overflow"),
},
(0 | 1, false) => match o.checked_mul(8) {
Some(v) => v,
None => fail!("save_any_reg offset overflow"),
},
(2, _) => match o.checked_mul(16) {
Some(v) => v,
None => fail!("save_any_reg offset overflow"),
},
_ => 0,
};
let kind = cls;
(
Arm64UnwindCode::SaveAnyReg {
kind,
pair: p,
preindexed: x,
reg: r,
offset_bytes,
},
3,
)
} else {
let hi = ((b1 >> 5) & 0x03) as u16;
let r = (b1 & 0x0f) as u8;
let lo = (b2 & 0x3f) as u16;
let o = (hi << 6) | lo;
if (b1 & 0x10) != 0 {
(Arm64UnwindCode::SavePReg { r, o }, 3)
} else {
(Arm64UnwindCode::SaveZReg { r, o }, 3)
}
}
}
}
0xe8 => (
Arm64UnwindCode::CustomStack(Arm64CustomStackCase::TrapFrame),
1,
),
0xe9 => (
Arm64UnwindCode::CustomStack(Arm64CustomStackCase::MachineFrame),
1,
),
0xea => (
Arm64UnwindCode::CustomStack(Arm64CustomStackCase::Context),
1,
),
0xeb => (
Arm64UnwindCode::CustomStack(Arm64CustomStackCase::EcContext),
1,
),
0xec => (
Arm64UnwindCode::CustomStack(Arm64CustomStackCase::ClearUnwoundToCall),
1,
),
0xfc => (Arm64UnwindCode::PacSignLr, 1),
0xf8 => {
let b1 = try_read_u8!(start + 1);
(
Arm64UnwindCode::Reserved {
opcode: 0xf8,
data: b1 as u32,
data_len: 1,
},
2,
)
}
0xf9 => {
let b1 = try_read_u8!(start + 1);
let b2 = try_read_u8!(start + 2);
let data = ((b1 as u32) << 8) | (b2 as u32);
(
Arm64UnwindCode::Reserved {
opcode: 0xf9,
data,
data_len: 2,
},
3,
)
}
0xfa => {
let b1 = try_read_u8!(start + 1);
let b2 = try_read_u8!(start + 2);
let b3 = try_read_u8!(start + 3);
let data = ((b1 as u32) << 16) | ((b2 as u32) << 8) | (b3 as u32);
(
Arm64UnwindCode::Reserved {
opcode: 0xfa,
data,
data_len: 3,
},
4,
)
}
0xfb => {
let b1 = try_read_u8!(start + 1);
let b2 = try_read_u8!(start + 2);
let b3 = try_read_u8!(start + 3);
let b4 = try_read_u8!(start + 4);
let data =
((b1 as u32) << 24) | ((b2 as u32) << 16) | ((b3 as u32) << 8) | (b4 as u32);
(
Arm64UnwindCode::Reserved {
opcode: 0xfb,
data,
data_len: 4,
},
5,
)
}
other => (
Arm64UnwindCode::Reserved {
opcode: other,
data: 0,
data_len: 0,
},
1,
),
};
self.offset += len;
Some(Ok(Arm64UnwindCodeData {
offset: start as u16,
code,
len: len as u8,
}))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_size_of_runtime_function() {
assert_eq!(
std::mem::size_of::<RuntimeFunction>(),
RUNTIME_FUNCTION_SIZE
);
}
#[test]
fn test_unwind_codes_one_epilog() {
const BYTES: &[u8] = &[
0x02, 0x01, 0x03, 0x00, 0x02, 0x16, 0x00, 0x06, 0x01, 0x70, ];
let unwind_info = UnwindInfo::parse(BYTES, 0).unwrap();
let unwind_codes: Vec<UnwindCode> = unwind_info
.unwind_codes()
.map(|result| result.expect("Unable to parse unwind code"))
.collect();
assert_eq!(unwind_codes.len(), 3);
assert_eq!(
unwind_codes,
[
UnwindCode {
code_offset: 2,
operation: UnwindOperation::Epilog {
offset_low_or_size: 2,
offset_high_or_flags: 1,
},
},
UnwindCode {
code_offset: 0,
operation: UnwindOperation::Epilog {
offset_low_or_size: 0,
offset_high_or_flags: 0,
},
},
UnwindCode {
code_offset: 1,
operation: UnwindOperation::PushNonVolatile(Register(7)), },
]
);
}
#[test]
fn test_unwind_codes_two_epilog() {
const BYTES: &[u8] = &[
0x02, 0x02, 0x04, 0x00, 0x03, 0x16, 0x00, 0x06, 0x02, 0x60, 0x01, 0x70, ];
let unwind_info = UnwindInfo::parse(BYTES, 0).unwrap();
let unwind_codes: Vec<UnwindCode> = unwind_info
.unwind_codes()
.map(|result| result.expect("Unable to parse unwind code"))
.collect();
assert_eq!(unwind_codes.len(), 4);
assert_eq!(
unwind_codes,
[
UnwindCode {
code_offset: 3,
operation: UnwindOperation::Epilog {
offset_low_or_size: 3,
offset_high_or_flags: 1,
},
},
UnwindCode {
code_offset: 0,
operation: UnwindOperation::Epilog {
offset_low_or_size: 0,
offset_high_or_flags: 0,
},
},
UnwindCode {
code_offset: 2,
operation: UnwindOperation::PushNonVolatile(Register(6)), },
UnwindCode {
code_offset: 1,
operation: UnwindOperation::PushNonVolatile(Register(7)), },
]
);
}
#[test]
fn test_iter_unwind_codes() {
let unwind_info = UnwindInfo {
version: 1,
size_of_prolog: 4,
frame_register: Register(0),
frame_register_offset: 0,
chained_info: None,
handler: None,
code_bytes: &[4, 98],
};
let unwind_codes: Vec<UnwindCode> = unwind_info
.unwind_codes()
.map(|result| result.expect("parse unwind code"))
.collect();
assert_eq!(unwind_codes.len(), 1);
let expected = UnwindCode {
code_offset: 4,
operation: UnwindOperation::Alloc(56),
};
assert_eq!(unwind_codes[0], expected);
}
#[rustfmt::skip]
const UNWIND_INFO_C_SCOPE_TABLE: &[u8] = &[
0x09, 0x0F, 0x06, 0x00,
0x0F, 0x64, 0x09, 0x00,
0x0F, 0x34, 0x08, 0x00,
0x0F, 0x52, 0x0B, 0x70,
0xC0, 0x1F, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00,
0x01, 0x15, 0x00, 0x00, 0x06, 0x16, 0x00, 0x00, 0x76, 0x1F, 0x00, 0x00, 0x06, 0x16, 0x00, 0x00,
0x3A, 0x16, 0x00, 0x00, 0x4C, 0x16, 0x00, 0x00, 0x76, 0x1F, 0x00, 0x00, 0x06, 0x16, 0x00, 0x00, ];
#[rustfmt::skip]
const UNWIND_INFO_C_SCOPE_TABLE_INVALID: &[u8] = &[
0x09, 0x0F, 0x06, 0x00,
0x0F, 0x64, 0x09, 0x00,
0x0F, 0x34, 0x08, 0x00,
0x0F, 0x52, 0x0B, 0x70,
0xC0, 0x1F, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00,
0x01, 0x15, 0x00, 0x00, 0x06, 0x16, 0x00, 0x00, 0x76, 0x1F, 0x00, 0x00, 0x06, 0x16, 0x00, 0x00,
0x3A, 0x16, 0x00, 0x00, 0x4C, 0x16, 0x00, 0x00, 0x76, 0x1F, 0x00, 0x00, 0x06, ];
#[test]
fn parse_c_scope_table() {
let unwind_info = UnwindInfo::parse(UNWIND_INFO_C_SCOPE_TABLE, 0)
.expect("Failed to parse unwind info with C scope table");
let entries = unwind_info
.c_scope_table_entries()
.expect("C scope table should present");
let entries = entries.collect::<Vec<_>>();
assert_eq!(entries.len(), 2);
assert_eq!(
entries[0],
ScopeTableEntry {
begin: 0x00001501,
end: 0x00001606,
handler: 0x00001F76,
target: 0x00001606,
}
);
assert_eq!(
entries[1],
ScopeTableEntry {
begin: 0x0000163A,
end: 0x0000164C,
handler: 0x00001F76,
target: 0x00001606,
}
);
}
#[test]
#[should_panic(expected = "C scope table should present")]
fn malformed_scope_table_is_not_allowed() {
let unwind_info = UnwindInfo::parse(UNWIND_INFO_C_SCOPE_TABLE_INVALID, 0)
.expect("Failed to parse unwind info with C scope table");
unwind_info
.c_scope_table_entries()
.expect("C scope table should present");
}
#[test]
#[should_panic(expected = "Scope table is not aligned")]
fn unaligned_scope_table_is_not_allowed() {
let it = ScopeTableIterator {
data: &[0x00, 0x00, 0x00, 0x00, 0x00],
offset: 0,
};
let _ = it.collect::<Vec<_>>();
}
mod arm64 {
use crate::pe::exception::*;
#[test]
fn parse_unwind_info_packed() {
const DATA: &[u8] = &[
0x78, 0x11, 0x00, 0x00, 0x21, 0x00, 0x60, 0x80, ];
let func = DATA.pread::<Arm64RuntimeFunction>(0).unwrap();
assert_eq!(func.begin_address, 0x1178);
assert_eq!(func.is_packed(), true);
assert_eq!(func.flag(), ARM64_PDATA_PACKED_UNWIND_FUNCTION);
assert_eq!(func.function_length(), 32);
assert_eq!(func.frame_size(), 0x1000);
assert_eq!(func.cr(), ARM64_PDATA_CR_CHAINED);
assert_eq!(func.h(), false);
assert_eq!(func.reg_f(), 0);
assert_eq!(func.reg_i(), 0);
}
#[test]
fn parse_packed_all_fields_nonzero() {
const DATA: &[u8] = &[
0x00, 0x10, 0x00, 0x00, 0x41, 0x60, 0x55, 0x04, ];
let func = DATA.pread::<Arm64RuntimeFunction>(0).unwrap();
assert_eq!(func.flag(), ARM64_PDATA_PACKED_UNWIND_FUNCTION);
assert!(func.is_packed());
assert_eq!(func.function_length(), 64);
assert_eq!(func.frame_size(), 128);
assert_eq!(func.cr(), ARM64_PDATA_CR_CHAINED_WITH_PAC);
assert_eq!(func.h(), true);
assert_eq!(func.reg_i(), 5);
assert_eq!(func.reg_f(), 3);
}
#[test]
fn parse_packed_bit_positions() {
let func = Arm64RuntimeFunction {
begin_address: 0,
unwind_data: 0x02424281,
};
assert_eq!(func.flag(), ARM64_PDATA_PACKED_UNWIND_FUNCTION);
assert_eq!(func.function_length(), 640);
assert_eq!(func.reg_f(), 2);
assert_eq!(func.reg_i(), 2);
assert_eq!(func.h(), false);
assert_eq!(func.cr(), ARM64_PDATA_CR_CHAINED_WITH_PAC);
assert_eq!(func.frame_size(), 64);
}
#[test]
fn parse_packed_max_field_values() {
let func = Arm64RuntimeFunction {
begin_address: 0,
unwind_data: 0xFFFFFFFF,
};
assert_eq!(func.flag(), 3);
assert_eq!(func.function_length(), 0x7FF * 4);
assert_eq!(func.reg_f(), 0x7);
assert_eq!(func.reg_i(), 0xF);
assert_eq!(func.h(), true);
assert_eq!(func.cr(), 0x3);
assert_eq!(func.frame_size(), 0x1FF * 16);
}
#[test]
fn parse_packed_field_isolation() {
let only_frame = Arm64RuntimeFunction {
begin_address: 0,
unwind_data: 0x1 << 23,
};
assert_eq!(only_frame.frame_size(), 16);
assert_eq!(only_frame.cr(), 0);
assert_eq!(only_frame.h(), false);
assert_eq!(only_frame.reg_i(), 0);
assert_eq!(only_frame.reg_f(), 0);
let only_cr = Arm64RuntimeFunction {
begin_address: 0,
unwind_data: 0x3 << 21,
};
assert_eq!(only_cr.cr(), 3);
assert_eq!(only_cr.frame_size(), 0);
assert_eq!(only_cr.h(), false);
assert_eq!(only_cr.reg_i(), 0);
assert_eq!(only_cr.reg_f(), 0);
let only_h = Arm64RuntimeFunction {
begin_address: 0,
unwind_data: 0x1 << 20,
};
assert_eq!(only_h.h(), true);
assert_eq!(only_h.cr(), 0);
assert_eq!(only_h.frame_size(), 0);
assert_eq!(only_h.reg_i(), 0);
assert_eq!(only_h.reg_f(), 0);
let only_reg_i = Arm64RuntimeFunction {
begin_address: 0,
unwind_data: 0xF << 16,
};
assert_eq!(only_reg_i.reg_i(), 15);
assert_eq!(only_reg_i.h(), false);
assert_eq!(only_reg_i.cr(), 0);
assert_eq!(only_reg_i.frame_size(), 0);
assert_eq!(only_reg_i.reg_f(), 0);
let only_reg_f = Arm64RuntimeFunction {
begin_address: 0,
unwind_data: 0x7 << 13,
};
assert_eq!(only_reg_f.reg_f(), 7);
assert_eq!(only_reg_f.reg_i(), 0);
assert_eq!(only_reg_f.h(), false);
assert_eq!(only_reg_f.cr(), 0);
assert_eq!(only_reg_f.frame_size(), 0);
}
#[test]
fn parse_unpacked_xdata_rva() {
const DATA: &[u8] = &[
0x00, 0x10, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, ];
let func = DATA.pread::<Arm64RuntimeFunction>(0).unwrap();
assert_eq!(func.flag(), ARM64_PDATA_REF_TO_FULL_XDATA);
assert!(!func.is_packed());
assert_eq!(func.unwind_data_rva(), 0x00001234);
}
#[test]
fn arm64_runtime_function_iterator() {
#[rustfmt::skip]
const DATA: &[u8] = &[
0x00, 0x10, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00,
0x00, 0x20, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00,
];
let iter = Arm64RuntimeFunctionIterator { data: DATA };
let funcs: Vec<_> = iter.map(|r| r.unwrap()).collect();
assert_eq!(funcs.len(), 2);
assert_eq!(funcs[0].begin_address, 0x1000);
assert_eq!(funcs[0].unwind_data, 0x00001234);
assert_eq!(funcs[1].begin_address, 0x2000);
assert_eq!(funcs[1].unwind_data, 0x00005678);
}
#[test]
fn xdata_header_with_extension() {
#[rustfmt::skip]
const XDATA: &[u8] = &[
0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00,
0x0D, 0x00, 0x00, 0x00,
0x81, 0xE4, 0xE3, 0xE3,
];
let info = Arm64UnwindInfo::parse(XDATA, 0).unwrap();
assert!(info.extension.is_some());
let ext = info.extension.unwrap();
assert_eq!(ext.epilog_count(), 1);
assert_eq!(ext.code_words(), 1);
assert_eq!(info.epilog_scope_bytes.len(), 4);
assert_eq!(info.unwind_code_bytes.len(), 4);
}
#[test]
fn epilog_scope_field_extraction() {
let scope = Arm64EpilogScope(0x00B8000D);
assert_eq!(scope.start_offset_words(), 52);
assert_eq!(scope.condition(), 0x0E);
assert_eq!(scope.start_index(), 2);
}
#[test]
fn parse_unwind_info_0() {
#[rustfmt::skip]
const XDATA: &[u8] = &[
0x96, 0x00, 0x70, 0x10,
0xE1, 0x87, 0xD1, 0x04, 0xC8, 0x82, 0x26, 0xE4,
0x8C, 0x1E, 0x00, 0x00, 0xA0, 0x32, 0x00, 0x00, ];
let info = Arm64UnwindInfo::parse(XDATA, 0).unwrap();
assert_eq!(
Arm64UnwindInfo::size(XDATA).unwrap(),
XDATA.len() - size_of_val(&0u32)
);
assert_eq!(info.header.0, 0x10700096);
assert_eq!(info.header.function_length(), 600);
assert_eq!(info.header.version(), 0);
assert_eq!(info.header.exception_data_present(), true);
assert_eq!(info.header.epilog_in_header(), true);
assert_eq!(info.header.epilog_count(), 1);
assert_eq!(info.header.code_words(), 2);
assert_eq!(info.header.has_extension(), false);
assert_eq!(info.unwind_code_bytes, &XDATA[4..12]);
let codes = info.unwind_codes(0).unwrap();
let codes = codes.collect::<Vec<_>>();
assert_eq!(codes.len(), 6);
assert_eq!(
codes[0].as_ref().unwrap(),
&Arm64UnwindCodeData {
offset: 0,
code: Arm64UnwindCode::SetFp,
len: 1,
}
);
assert_eq!(
codes[1].as_ref().unwrap(),
&Arm64UnwindCodeData {
offset: 1,
code: Arm64UnwindCode::SaveFpLrPreindexed { offset_bytes: 64 },
len: 1,
}
);
assert_eq!(
codes[2].as_ref().unwrap(),
&Arm64UnwindCodeData {
offset: 2,
code: Arm64UnwindCode::SaveReg {
reg: 23,
offset_bytes: 32,
},
len: 2,
}
);
assert_eq!(
codes[3].as_ref().unwrap(),
&Arm64UnwindCodeData {
offset: 4,
code: Arm64UnwindCode::SaveRegPair {
first_reg: 21,
offset_bytes: 16,
},
len: 2,
}
);
assert_eq!(
codes[4].as_ref().unwrap(),
&Arm64UnwindCodeData {
offset: 6,
code: Arm64UnwindCode::SaveR19R20Preindexed { offset_bytes: 48 },
len: 1,
}
);
assert_eq!(
codes[5].as_ref().unwrap(),
&Arm64UnwindCodeData {
offset: 7,
code: Arm64UnwindCode::End,
len: 1,
}
);
}
#[test]
fn parse_unwind_info_1() {
#[rustfmt::skip]
const XDATA: &[u8] = &[
0x10, 0x00, 0x50, 0x08,
0x0D, 0x00, 0x00, 0x00,
0x81, 0xE4,
0xE3, 0xE3,
0x8C, 0x1E, 0x00, 0x00, 0xA0, 0x32, 0x00, 0x00, ];
let info = Arm64UnwindInfo::parse(XDATA, 0).unwrap();
assert_eq!(
Arm64UnwindInfo::size(XDATA).unwrap(),
XDATA.len() - size_of_val(&0u32)
);
assert_eq!(info.header.0, 0x08500010);
assert_eq!(info.header.version(), 0);
assert_eq!(info.header.exception_data_present(), true);
assert_eq!(info.header.epilog_in_header(), false);
assert_eq!(info.unwind_code_bytes, &XDATA[8..12]);
let scopes = info.epilog_scopes().unwrap();
let scopes = scopes.map(Result::unwrap).collect::<Vec<_>>();
assert_eq!(scopes.len(), 1);
assert_eq!(scopes[0], Arm64EpilogScope(0x0000000D));
let codes = info.unwind_codes(0).unwrap();
let codes = codes.collect::<Vec<_>>();
assert_eq!(codes.len(), 4);
assert_eq!(
codes[0].as_ref().unwrap(),
&Arm64UnwindCodeData {
offset: 0,
code: Arm64UnwindCode::SaveFpLrPreindexed { offset_bytes: 16 },
len: 1,
}
);
assert_eq!(
codes[1].as_ref().unwrap(),
&Arm64UnwindCodeData {
offset: 1,
code: Arm64UnwindCode::End,
len: 1,
}
);
assert_eq!(
codes[2].as_ref().unwrap(),
&Arm64UnwindCodeData {
offset: 2,
code: Arm64UnwindCode::Nop,
len: 1,
}
);
assert_eq!(
codes[3].as_ref().unwrap(),
&Arm64UnwindCodeData {
offset: 3,
code: Arm64UnwindCode::Nop,
len: 1,
}
);
assert_eq!(
info.exception_handler,
Some(Arm64ExceptionHandler {
rva: 0x00001E8C,
data: &0x000032A0u32.to_le_bytes()
})
);
}
#[test]
fn unwind_code_alloc_large_and_custom_stack() {
#[rustfmt::skip]
const CODES: &[u8] = &[
0xE0, 0x00, 0x01, 0x00, 0xFC, 0xE8, 0xE4, 0xE3, ];
let iter = Arm64UnwindCodeIterator::new(CODES, 0).unwrap();
let codes: Vec<_> = iter.map(|r| r.unwrap()).collect();
assert_eq!(codes.len(), 5);
assert_eq!(
codes[0].code,
Arm64UnwindCode::AllocLarge { size_bytes: 4096 }
);
assert_eq!(codes[0].len, 4);
assert_eq!(codes[1].code, Arm64UnwindCode::PacSignLr);
assert_eq!(
codes[2].code,
Arm64UnwindCode::CustomStack(Arm64CustomStackCase::TrapFrame)
);
assert_eq!(codes[3].code, Arm64UnwindCode::End);
assert_eq!(codes[4].code, Arm64UnwindCode::Nop);
}
#[test]
fn unwind_code_reserved_opcodes() {
#[rustfmt::skip]
const CODES: &[u8] = &[
0xF8, 0xAB, 0xF9, 0x12, 0x34, 0xE4, 0xE3, 0xE3, ];
let iter = Arm64UnwindCodeIterator::new(CODES, 0).unwrap();
let codes: Vec<_> = iter.map(|r| r.unwrap()).collect();
assert_eq!(codes.len(), 5);
assert_eq!(
codes[0].code,
Arm64UnwindCode::Reserved {
opcode: 0xF8,
data: 0xAB,
data_len: 1
}
);
assert_eq!(codes[0].len, 2);
assert_eq!(
codes[1].code,
Arm64UnwindCode::Reserved {
opcode: 0xF9,
data: 0x1234,
data_len: 2
}
);
assert_eq!(codes[1].len, 3);
assert_eq!(codes[2].code, Arm64UnwindCode::End);
}
#[test]
fn unwind_code_freg_operations() {
#[rustfmt::skip]
const CODES: &[u8] = &[
0xD8, 0x42, 0xDE, 0x65, ];
let iter = Arm64UnwindCodeIterator::new(CODES, 0).unwrap();
let codes: Vec<_> = iter.map(|r| r.unwrap()).collect();
assert_eq!(codes.len(), 2);
assert_eq!(
codes[0].code,
Arm64UnwindCode::SaveFRegPair {
first_reg: 9,
offset_bytes: 16
}
);
assert_eq!(
codes[1].code,
Arm64UnwindCode::SaveFRegPreindexed {
reg: 11,
offset_bytes: 48
}
);
}
#[test]
fn unwind_code_iterator_start_index() {
#[rustfmt::skip]
const CODES: &[u8] = &[
0xE1, 0x87, 0xE4, 0xE3, ];
let iter = Arm64UnwindCodeIterator::new(CODES, 2).unwrap();
let codes: Vec<_> = iter.map(|r| r.unwrap()).collect();
assert_eq!(codes.len(), 2);
assert_eq!(codes[0].code, Arm64UnwindCode::End);
assert_eq!(codes[0].offset, 2);
assert_eq!(codes[1].code, Arm64UnwindCode::Nop);
}
#[test]
fn unwind_code_iterator_bad_length() {
let result = Arm64UnwindCodeIterator::new(&[0xE4, 0xE3, 0xE3], 0);
assert!(result.is_err());
}
#[test]
fn xdata_no_exception_handler() {
#[rustfmt::skip]
const XDATA: &[u8] = &[
0x10, 0x00, 0x20, 0x08, 0x81, 0xE4, 0xE3, 0xE3, ];
let info = Arm64UnwindInfo::parse(XDATA, 0).unwrap();
assert!(!info.header.exception_data_present());
assert!(info.header.epilog_in_header());
assert!(info.exception_handler.is_none());
assert!(info.epilog_scopes().is_none());
}
#[test]
fn xdata_size_without_handler() {
#[rustfmt::skip]
const XDATA: &[u8] = &[
0x10, 0x00, 0x20, 0x08, 0x81, 0xE4, 0xE3, 0xE3, ];
assert_eq!(Arm64UnwindInfo::size(XDATA).unwrap(), 8);
}
#[test]
fn arm64_len_vs_x64_len() {
let ed = ExceptionData {
bytes: &[0u8; 64],
offset: 0,
size: 32,
file_alignment: 4,
};
assert_eq!(ed.len(), 2);
assert_eq!(ed.len_arm64(), 4);
}
}
}