//! Unwind information for x86_64 (usually called x64 in microsoft documentation). //! //! The high-level API is accessed through `FunctionTableEntries::unwind_frame`. This function //! allows you to unwind a frame to get the return address and all updated contextual registers.
use arrayvec::ArrayVec; use core::ops::ControlFlow; use thiserror::Error; use zerocopy::{FromBytes, Immutable, KnownLayout, Ref, LE}; use zerocopy_derive::*;
/// A view over function table entries in the `.pdata` section. #[derive(Debug, Clone, Copy)] pubstruct FunctionTableEntries<'a> {
data: &'a [u8],
}
/// A runtime function record in the function table. #[derive(Unaligned, FromBytes, KnownLayout, Immutable, Debug, Clone, Copy)] #[repr(C)] pubstruct RuntimeFunction { /// The start relative virtual address of the function. pub begin_address: U32, /// The end relative virtual address of the function. pub end_address: U32, /// The relative virtual address of the unwind information related to the function. pub unwind_info_address: U32,
}
impl<'a> FunctionTableEntries<'a> { /// Parse function table entries from the given `.pdata` section contents. pubfn parse(data: &'a [u8]) -> Self {
FunctionTableEntries { data }
}
/// Get the number of `RuntimeFunction` stored in the function table. pubfn functions_len(&self) -> usize { self.data.len() / core::mem::size_of::<RuntimeFunction>()
}
/// Get the `RuntimeFunction`s in the function table, if the parsed data is well-aligned and /// sized. pubfn functions(&self) -> Option<&'a [RuntimeFunction]> { Ref::from_bytes(self.data).ok().map(Ref::into_ref)
}
/// Lookup the runtime function that contains the given relative virtual address. pubfn lookup(&self, address: u32) -> Option<&'a RuntimeFunction> { let functions = self.functions()?; match functions.binary_search_by_key(&address, |f| f.begin_address.get()) {
Ok(i) => Some(&functions[i]),
Err(i) if i > 0 && address < functions[i - 1].end_address.get() => {
Some(&functions[i - 1])
}
_ => None,
}
}
pubfn unwind_frame<'m, S: UnwindState, M>(
&self,
state: &mut S, mut memory_at_rva: M,
address: u32,
) -> Option<u64> where
M: FnMut(u32) -> Option<&'m [u8]> + 'm,
{ // This implements the procedure found // [here](https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64?view=msvc-170#unwind-procedure). iflet Some(mut function) = self.lookup(address) { let offset = address - function.begin_address.get(); letmut is_chained = false; loop { let unwind_info =
UnwindInfo::parse(memory_at_rva(function.unwind_info_address.get())?)?;
if !is_chained
&& should_check_for_epilog(address, function.end_address.get(), &mut unwind_ops)
{ // Check whether the address is in the function epilog. If so, we need to // simulate the remaining epilog instructions (unwind codes don't account for // unwinding from the epilog).
let bytes = (function.end_address.get() - address) as usize; let instruction = &memory_at_rva(address)?[..bytes]; iflet Ok(epilog_instructions) = FunctionEpilogInstruction::parse_sequence(
instruction,
unwind_info.frame_register(),
) { for instruction in epilog_instructions.iter() { match instruction {
FunctionEpilogInstruction::AddSP(offset) => { let rsp = state.read_register(Register::RSP);
state.write_register(Register::RSP, rsp + *offset as u64);
}
FunctionEpilogInstruction::AddSPFromFP(offset) => { let fp = unwind_info
.frame_register()
.expect("invalid fp register offset"); let fp = state.read_register(fp);
state.write_register(Register::RSP, fp + *offset as u64);
}
FunctionEpilogInstruction::Pop(reg) => { let rsp = state.read_register(Register::RSP); let val = state.read_stack(rsp)?;
state.write_register(*reg, val);
state.write_register(Register::RSP, rsp + 8);
}
}
} break;
}
}
for (_, op) in unwind_ops.skip_while(|(o, _)| !is_chained && *o as u32 > offset) { iflet ControlFlow::Break(rip) = unwind_info.resolve_operation(state, &op)? { return Some(rip);
}
} iflet Some(UnwindInfoTrailer::ChainedUnwindInfo { chained }) =
unwind_info.trailer()
{
is_chained = true;
function = chained;
} else { break;
}
}
} let rsp = state.read_register(Register::RSP); let rip = state.read_stack(rsp)?;
state.write_register(Register::RSP, rsp + 8);
Some(rip)
}
/// Unwind a single frame at the given relative virtual address. /// /// This does not attempt to invoke any exception or termination handlers. /// /// Returns `None` if `UnwindInfo` could not be parsed, a stack value could not be read, or a /// memory offset in the binary could not be read (whether when parsing the section table or /// when reading memory pointed to by the section table). pubfn unwind_frame_with_image<S: UnwindState>(
&self,
state: &mut S,
image: &[u8],
address: u32,
) -> Option<u64> { let sections = Sections::parse(image)?; self.unwind_frame(state, |addr| sections.memory_at_rva(addr), address)
}
}
impl<'a> Iterator for FunctionTableEntries<'a> { type Item = &'a RuntimeFunction;
/// Returns whether we are potentially in a function epilog or not. /// /// This must be called on the unwind operations before any values operations are consumed, since /// the epilog operations will be before any others. fn should_check_for_epilog(
address: u32,
function_end_address: u32,
unwind_ops: &mut core::iter::Peekable<UnwindOperations<'_>>,
) -> bool { // Check whether there is epilog offset information in the unwind ops // (added in version 2). All epilog information should be at the head of the // set of operations. If it is present and we can confirm we're not in the // epilog, we can skip trying to parse the instructions. iflet Some((_, UnwindOperation::EpilogInformation(info))) = unwind_ops.peek() { struct InEpilog {
value: bool,
epilog_size: u32,
address: u32,
function_end: u32,
}
if header.single_epilog() {
in_epilog.epilog_at(header.epilog_size() as u32);
}
unwind_ops.next();
// Always consume remaining epilog infos (which will be additional offsets, or padding // entries with 0 offsets). whilelet Some((_, UnwindOperation::EpilogInformation(info))) = unwind_ops.peek() { // Only continue checking if we don't know we're in an epilog yet. if !in_epilog.value { let epilog_offset = info.as_offset() as u32; if epilog_offset != 0 {
in_epilog.epilog_at(epilog_offset);
}
}
unwind_ops.next();
}
/// A general-purpose register. /// /// If converted to a u8, the resulting value matches those in the x86_64 spec for register /// operands as well as the operation info bits in unwind codes. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[repr(u8)] pubenum Register {
RAX,
RCX,
RDX,
RBX,
RSP,
RBP,
RSI,
RDI,
R8,
R9,
R10,
R11,
R12,
R13,
R14,
R15,
}
/// Fixed data at the start of [PE UnwindInfo][unwindinfo]. /// /// The version 2 details are not documented formally, but they add `UWOP_EPILOG` and /// `UWOP_SPARE_CODE` where `UWOP_SAVE_XMM` and `UWOP_SAVE_XMM_FAR` were previously deprecated and /// removed. The behavior of `UWOP_EPILOG` was derived from the [coreclr implementation][coreclr]. /// /// [unwindinfo]: https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64?view=msvc-170#struct-unwind_info /// [coreclr]: https://github.com/dotnet/runtime/blob/e11a3d4c0604a2bd1f5b2aee27a32d4a0cbcbac8/src/coreclr/unwinder/amd64/unwinder.cpp #[derive(Unaligned, FromBytes, KnownLayout, Immutable, Debug, Clone, Copy)] #[repr(C)] pubstruct UnwindInfoHeader { /// The unwind information version and flags. pub version_and_flags: u8, /// The length of the function prolog, in bytes. pub prolog_size: u8, /// The number of u16 slots in the unwind codes array. pub unwind_codes_len: u8, /// The frame register and offset. pub frame_register_and_offset: u8,
}
bitflags::bitflags! { /// The unwind info bit flags. /// /// Note that while they are individual bits, it seems as if they can only be /// mutually-exclusive. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pubstruct UnwindInfoFlags: u8 { /// The function has an exception handler that should be called when looking for functions /// that need to examine exceptions. const EHANDLER = 0x1; /// The function has a termination handler that should be called when unwinding an /// exception. const UHANDLER = 0x2; /// This unwind info structure is not the primary one for the procedure. Instead, the /// chained unwind info entry is the contents of a previous RuntimeFunction entry. If this /// flag is set, then the EHANDLER and UHANDLER flags must be cleared. Also, the frame /// register and fixed-stack allocation fields must have the same values as in the primary /// unwind info. const CHAININFO = 0x4;
}
}
impl UnwindInfoHeader { /// The UnwindInfo version. Should be `1` or `2`. #[inline] pubfn version(&self) -> u8 { self.version_and_flags & 0x7
}
/// The raw flags bits. #[inline] pubfn flags_raw(&self) -> u8 { self.version_and_flags >> 3
}
/// The raw frame register value. #[inline] pubfn frame_register_raw(&self) -> u8 { self.frame_register_and_offset & 0xf
}
/// The raw frame register offset value. #[inline] pubfn frame_register_offset_raw(&self) -> u8 { self.frame_register_and_offset >> 4
}
/// The unwind info flags. pubfn flags(&self) -> UnwindInfoFlags {
UnwindInfoFlags::from_bits_truncate(self.flags_raw())
}
/// The frame register, if any. pubfn frame_register(&self) -> Option<Register> { let reg = self.frame_register_raw();
(reg != 0).then_some(
reg.try_into()
.expect("reg is <= 15 so this always succeeds"),
)
}
/// The scaled frame register offset. pubfn frame_register_offset(&self) -> u8 { // u8 is appropriate as the maximum value is 15 * 16 = 240 self.frame_register_offset_raw() * 16
}
/// Get an absolute address from the given StackFrameOffset. pubfn resolve_offset<F>(&self, read_register: F, offset: StackFrameOffset) -> u64 where
F: FnOnce(Register) -> u64,
{ matchself.frame_register() {
Some(reg) => read_register(reg) - self.frame_register_offset() as u64 + offset.0as u64,
None => read_register(Register::RSP) + offset.0as u64,
}
}
/// Perform the given UnwindOperation, changing `state` appropriately. /// /// Returns `None` when reading the stack fails. pubfn resolve_operation<S: UnwindState>(
&self,
state: &mut S,
op: &UnwindOperation,
) -> Option<ControlFlow<u64>> { match op {
UnwindOperation::PopNonVolatile(reg) => { let rsp = state.read_register(Register::RSP); let value = state.read_stack(rsp)?;
state.write_register(*reg, value);
state.write_register(Register::RSP, rsp + 8);
}
UnwindOperation::UnStackAlloc(bytes) => { let rsp = state.read_register(Register::RSP);
state.write_register(Register::RSP, rsp + *bytes as u64);
}
UnwindOperation::RestoreSPFromFP => { iflet Some(reg) = self.frame_register() { let value = state.read_register(reg) - self.frame_register_offset() as u64;
state.write_register(Register::RSP, value);
}
}
UnwindOperation::ReadNonVolatile(reg, offset) => { let addr = self.resolve_offset(|reg| state.read_register(reg), *offset); let value = state.read_stack(addr)?;
state.write_register(*reg, value);
}
UnwindOperation::EpilogInformation(_) => { // These should be fully consumed before this point, but ignore unexpected // operations.
}
UnwindOperation::ReadXMM(reg, offset) => { let addr = self.resolve_offset(|reg| state.read_register(reg), *offset); let value =
state.read_stack(addr)? as u128 | ((state.read_stack(addr + 8)? as u128) << 64);
state.write_xmm_register(*reg, value);
}
UnwindOperation::PopMachineFrame { error_code } => { let offset = if *error_code { 8 } else { 0 }; let rsp = state.read_register(Register::RSP); let return_address = state.read_stack(rsp + offset)?; let rsp = state.read_stack(rsp + offset + 24)?;
state.write_register(Register::RSP, rsp); return Some(ControlFlow::Break(return_address));
}
}
Some(ControlFlow::Continue(()))
}
}
/// A virtual instruction in the function epilog, interpreted from x86_64 instructions. #[derive(Debug, Clone, Copy)] pubenum FunctionEpilogInstruction { /// Add the given offset to the stack pointer.
AddSP(u32), /// Add the given offset to the frame pointer to recover the stack pointer.
AddSPFromFP(u32), /// Pop a value from the stack into the given register.
Pop(Register),
}
/// An error resulting from an attempt at parsing an epilog instruction. #[derive(Error, Debug)] pubenum InstructionParseError { #[error("not enough data")]
NotEnoughData, #[error("invalid instruction found")]
InvalidInstruction, #[error("too many instructions for epilog")]
TooManyInstructions,
}
/// The maximum number of instructions to allow when parsing a function epilog. /// /// There is at most one AddSP/AddSPFromFP, and only 8 caller-saved registers (disregarding the /// implicit RSP). We give a bit of extra space just in case, but it shouldn't be necessary. pubconst FUNCTION_EPILOG_LIMIT: usize = 12;
impl FunctionEpilogInstruction { /// Parse a function epilog instruction. /// /// Returns Ok(None) if the instruction is an epilog terminator (`ret` or `jmp`). /// /// `allow_add_sp` should only be true for the (potential) first instruction in an epilog. pubfn parse(
ip: &[u8],
fpreg: Option<Register>,
allow_add_sp: bool,
) -> Result<Option<(Self, &[u8])>, InstructionParseError> { if ip.is_empty() { return Err(InstructionParseError::NotEnoughData);
}
// Read REX instruction byte if present. let (rex, ip) = if ip[0] & 0xf0 == 0x40 {
(ip[0] & 0x0f, &ip[1..])
} else {
(0, &ip[0..])
};
// Both add and lea need at least 3 bytes after REX if allow_add_sp && ip.len() >= 3 { // add RSP,imm32 if rex & 0x8 != 0 && ip[0] == 0x81 && ip[1] == 0xc4 { let (val, rest) = Ref::<_, U32>::from_prefix(&ip[2..])
.map_err(|_| InstructionParseError::NotEnoughData)?; return Ok(Some((FunctionEpilogInstruction::AddSP(val.get()), rest)));
} // add RSP,imm8 if rex & 0x8 != 0 && ip[0] == 0x83 && ip[1] == 0xc4 { return Ok(Some((
FunctionEpilogInstruction::AddSP(ip[2] as u32),
&ip[3..],
)));
}
// pop r/m64 if ip.len() >= 2 && ip[0] == 0x8f && ip[1] & 0xf8 == 0xc0 { let reg = ip[1] & 0x7 | ((rex & 1) << 3); return Ok(Some((
FunctionEpilogInstruction::Pop(
reg.try_into().expect( "`reg` is between 0 and 15, which are defined values of `Register`.",
),
),
&ip[2..],
)));
} // pop r64 if !ip.is_empty() && ip[0] & 0xf8 == 0x58 { let reg = ip[0] & 0x7 | ((rex & 1) << 3);
debug_assert!(reg <= 15); return Ok(Some((
FunctionEpilogInstruction::Pop(
reg.try_into().expect( "`reg` is between 0 and 15, which are defined values of `Register`.",
),
),
&ip[1..],
)));
}
// ret if !ip.is_empty() && ip[0] == 0xc3 { return Ok(None);
}
if ip.len() >= 2 { // jmp with relative displacements // // The MS docs say epilogs only have jmp instructions with a ModRM byte, but I've seen // relative displacement jmps too (tail calls). if ip[0] == 0xeb || ip[0] == 0xe9 { return Ok(None);
} // jmp with ModRM and mod bits as 00 if ip[0] == 0xff { let mod_opcode = ip[1] & 0xf8; if mod_opcode == 0x20 || mod_opcode == 0x28 { return Ok(None);
} else { return Err(InstructionParseError::InvalidInstruction);
}
}
}
// not a valid epilog instruction
Err(InstructionParseError::InvalidInstruction)
}
/// Check whether a series of instructions are a tail of a function epilog /// and parse them into a limited sequence of epilog instructions. /// /// This function does not allocate memory; the result is stored in a /// fixed-capacity `ArrayVec`. /// /// Returns `Err` if too many instructions were encountered or if the /// instructions do not appear to be a function epilog. /// /// [Epilogs][] look like: /// * `add RSP,<constant>` or `lea RSP,constant[FPReg]` /// * zero or more `pop <GPREG>` /// * `ret` or `jmp` with a ModRM argument with mod field 00 /// /// [Epilogs]: https://learn.microsoft.com/en-us/cpp/build/prolog-and-epilog?view=msvc-170#epilog-code pubfn parse_sequence(
ip: &[u8],
frame_register: Option<Register>,
) -> Result<ArrayVec<Self, FUNCTION_EPILOG_LIMIT>, InstructionParseError> { letmut buffer = ArrayVec::new(); letmut instruction_and_rest = Self::parse(ip, frame_register, true)?;
/// An interface over state needed for unwinding stack frames. pubtrait UnwindState { /// Return the value of the given register. fn read_register(&mutself, register: Register) -> u64; /// Return the 8-byte value at the given address on the stack, if any. fn read_stack(&mutself, addr: u64) -> Option<u64>; /// Write a new value to the given register, updating the unwind context. fn write_register(&mutself, register: Register, value: u64); /// Write a new value to the given xmm register, updating the unwind context. fn write_xmm_register(&mutself, register: XmmRegister, value: u128);
}
/// Optional information at the end of UnwindInfo. pubenum UnwindInfoTrailer<'a> { /// There is an exception handler associated with this unwind info.
ExceptionHandler {
handler_address: &'a U32,
handler_data: &'a [u8],
}, /// There is a termination handler associated with this unwind info.
TerminationHandler {
handler_address: &'a U32,
handler_data: &'a [u8],
}, /// There is a chained unwind info entry associated with this unwind info.
ChainedUnwindInfo { chained: &'a RuntimeFunction },
}
impl<'a> UnwindInfo<'a> { /// Read the unwind info from the given buffer. /// /// Returns None if there aren't enough bytes or the alignment is incorrect. pubfn parse(data: &'a [u8]) -> Option<Self> { let (header, rest) = Ref::<_, UnwindInfoHeader>::from_prefix(data).ok()?; if !(1..=2).contains(&header.version()) { return None;
} let (unwind_codes, rest) = Ref::from_prefix_with_elems(rest, header.unwind_codes_len as usize * 2).ok()?;
Some(UnwindInfo {
header: Ref::into_ref(header),
unwind_codes: Ref::into_ref(unwind_codes),
rest,
})
}
/// Get an iterator over the unwind operations. pubfn unwind_operations(&self) -> UnwindOperations<'a> {
UnwindOperations {
version: self.header.version(),
unwind_codes: self.unwind_codes,
}
}
/// Get the trailing information of the unwind info, if any. pubfn trailer(&self) -> Option<UnwindInfoTrailer<'a>> { let flags = self.flags(); if flags.contains(UnwindInfoFlags::EHANDLER) { let (handler_address, handler_data) = Ref::<_, U32>::from_prefix(self.rest).ok()?;
Some(UnwindInfoTrailer::ExceptionHandler {
handler_address: Ref::into_ref(handler_address),
handler_data,
})
} elseif flags.contains(UnwindInfoFlags::UHANDLER) { let (handler_address, handler_data) = Ref::<_, U32>::from_prefix(self.rest).ok()?;
Some(UnwindInfoTrailer::TerminationHandler {
handler_address: Ref::into_ref(handler_address),
handler_data,
})
} elseif flags.contains(UnwindInfoFlags::CHAININFO) {
Some(UnwindInfoTrailer::ChainedUnwindInfo {
chained: Ref::into_ref(Ref::<_, RuntimeFunction>::from_bytes(self.rest).ok()?),
})
} else {
None
}
}
}
impl core::ops::Deref for UnwindInfo<'_> { type Target = UnwindInfoHeader;
/// An iterator over `UnwindOperation`s. /// /// This iterator parses the operations as it iterates, since it needs to parse them to know how /// many slots each takes up. #[derive(Clone, Copy, Debug)] pubstruct UnwindOperations<'a> {
version: u8,
unwind_codes: &'a [u8],
}
impl<'a> UnwindOperations<'a> { /// Get the current `UnwindCode`. pubfn unwind_code(&self) -> Option<&'a UnwindCode> { letmut c = *self;
c.read::<UnwindCode>()
}
impl<'a> Iterator for UnwindOperations<'a> { type Item = (u8, UnwindOperation);
fn next(&mutself) -> Option<Self::Item> { let unwind_code = self.read::<UnwindCode>()?; let op = match unwind_code.operation_code(self.version)? {
UnwindOperationCode::PushNonvol => {
UnwindOperation::PopNonVolatile(unwind_code.operation_info_as_register())
}
UnwindOperationCode::AllocLarge => match unwind_code.operation_info() { 0 => UnwindOperation::UnStackAlloc(self.read::<U16>()?.get() as u32 * 8), 1 => UnwindOperation::UnStackAlloc(self.read::<U32>()?.get()),
_ => return None,
},
UnwindOperationCode::AllocSmall => {
UnwindOperation::UnStackAlloc((unwind_code.operation_info() as u32 + 1) * 8)
}
UnwindOperationCode::SetFPReg => UnwindOperation::RestoreSPFromFP,
UnwindOperationCode::SaveNonvol => UnwindOperation::ReadNonVolatile(
unwind_code.operation_info_as_register(),
StackFrameOffset(self.read::<U16>()?.get() as u32 * 8),
),
UnwindOperationCode::SaveNonvolFar => UnwindOperation::ReadNonVolatile(
unwind_code.operation_info_as_register(),
StackFrameOffset(self.read::<U32>()?.get()),
),
UnwindOperationCode::Epilog => UnwindOperation::EpilogInformation(EpilogInformation {
size_or_offset_low: unwind_code.prolog_offset,
single_or_offset_high: unwind_code.operation_code_raw(),
}), // We don't expect to ever see a `UWOP_SPARE_CODE` (so much so that other // implementations assert it), but we'll try to keep going anyway.
UnwindOperationCode::Spare => { // The spare has an extra 2 16-bit "slots" (to be size-compatible with the // [deprecated] version 1 code, UWOP_SAVE_XMM_FAR). let _ = self.read::<U32>()?; // Skip this operation. returnself.next();
}
UnwindOperationCode::SaveXmm128 => UnwindOperation::ReadXMM(
unwind_code.operation_info_as_xmm(),
StackFrameOffset(self.read::<U16>()?.get() as u32 * 16),
),
UnwindOperationCode::SaveXmm128Far => UnwindOperation::ReadXMM(
unwind_code.operation_info_as_xmm(),
StackFrameOffset(self.read::<U32>()?.get()),
),
UnwindOperationCode::PushMachframe => UnwindOperation::PopMachineFrame {
error_code: unwind_code.operation_info() == 1,
},
};
Some((unwind_code.prolog_offset, op))
}
}
/// Epilog information must be the first unwind operations in the stream. The first entry gives the /// epilog size and indicates whether there is one epilog or many. Subsequent entries give offsets /// for other epilogs, if necessary, and there are always a multiple of 2 epilog codes (to match /// the 2 slots that the version 1 unwind code used, even though it was deprecated without ever /// being used in practice). #[derive(Debug, Clone, Copy)] pubstruct EpilogInformation {
size_or_offset_low: u8,
single_or_offset_high: u8,
}
/// An offset relative to the local stack frame. #[derive(Debug, Clone, Copy)] pubstruct StackFrameOffset(u32);
/// An unwind operation to perform. /// /// These generally correspond to `UnwindOperationCode`s, however they are named based on the /// operation that needs to be done to unwind. #[derive(Debug, Clone, Copy)] pubenum UnwindOperation { /// Provide information about the function's epilog.
EpilogInformation(EpilogInformation), /// Restore a register's value by popping from the stack (incrementing RSP by 8).
PopNonVolatile(Register), /// Undo a stack allocation of the given size (incrementing RSP).
UnStackAlloc(u32), /// Use the frame pointer register to restore RSP. The stack pointer should be restored from /// the frame pointer minus UnwindInfo::frame_register_offset().
RestoreSPFromFP, /// Restore a register's value from the given stack frame offset.
ReadNonVolatile(Register, StackFrameOffset), /// Restore an XMM register's value from the given stack frame offset.
ReadXMM(XmmRegister, StackFrameOffset), /// Pop a machine frame. This restores from the stack an optional error code, and then (in /// order from lowest to highest addresses) IP, CS, EFLAGS, the old SP, and SS.
PopMachineFrame { error_code: bool },
}
/// An operation represented by an `UnwindCode`. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[repr(u8)] pubenum UnwindOperationCode {
PushNonvol,
AllocLarge,
AllocSmall,
SetFPReg,
SaveNonvol,
SaveNonvolFar,
Epilog,
Spare,
SaveXmm128,
SaveXmm128Far,
PushMachframe,
}
/// A single step to unwind operations done in a frame's prolog. #[derive(Unaligned, FromBytes, KnownLayout, Immutable, Debug, Clone, Copy)] #[repr(C)] pubstruct UnwindCode { /// The byte offset into the prolog where the operation was done. pub prolog_offset: u8, /// The operation code and info. pub opcode_and_opinfo: u8,
}
impl UnwindCode { /// Get the raw operation code. #[inline] pubfn operation_code_raw(&self) -> u8 { self.opcode_and_opinfo & 0xf
}
/// Get the operation information bits. #[inline] pubfn operation_info(&self) -> u8 { self.opcode_and_opinfo >> 4
}
/// Interpret the operation info as a register. #[inline] fn operation_info_as_register(&self) -> Register { let op_info = self.operation_info();
op_info
.try_into()
.expect("`op_info` is between 0 and 15, which are defined values of `Register`.")
}
/// Interpret the operation info as an Xmm register. #[inline] fn operation_info_as_xmm(&self) -> XmmRegister { let op_info = self.operation_info();
op_info
.try_into()
.expect("`op_info` is between 0 and 15, which are defined values of `XmmRegister`.")
}
}
#[cfg(test)] mod tests { usesuper::*;
use hex_literal::hex; use memmap2::Mmap; use object::read::{File, Object, ObjectSection}; use std::sync::OnceLock;
fn assert_fixture_unwind(mut context: FrameContext, ra: u64, changes: RegisterChanges) { let file = File::parse(get_fixture()).unwrap(); let pdata_section = file.section_by_name(".pdata").unwrap(); let entries = FunctionTableEntries::parse(pdata_section.data().unwrap()); let ip_offset = (context.ip - FIXTURE_ADDRESS) as u32; let result = entries.unwind_frame_with_image(&mut context, get_fixture(), ip_offset);
assert_eq!(result, Some(ra), "mismatched return address");
assert_eq!(context.changes, changes, "mismatched register changes");
}
fn assert_fixture_frames(mut context: FrameContext, return_addrs: &[u64]) { let file = File::parse(get_fixture()).unwrap(); let pdata_section = file.section_by_name(".pdata").unwrap(); let entries = FunctionTableEntries::parse(pdata_section.data().unwrap());
letmut ip = context.ip; for ra in return_addrs { let ip_offset = (ip - FIXTURE_ADDRESS) as u32; let result = entries.unwind_frame_with_image(&mut context, get_fixture(), ip_offset);
assert_eq!(result, Some(*ra), "mismatched return address");
ip = ra - 1;
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.