diff options
Diffstat (limited to 'third_party/rust/goblin/src/pe/exception.rs')
-rw-r--r-- | third_party/rust/goblin/src/pe/exception.rs | 1058 |
1 files changed, 1058 insertions, 0 deletions
diff --git a/third_party/rust/goblin/src/pe/exception.rs b/third_party/rust/goblin/src/pe/exception.rs new file mode 100644 index 0000000000..d26854d3e9 --- /dev/null +++ b/third_party/rust/goblin/src/pe/exception.rs @@ -0,0 +1,1058 @@ +//! Exception handling and stack unwinding for x64. +//! +//! Exception information is exposed via the [`ExceptionData`] structure. If present in a PE file, +//! it contains a list of [`RuntimeFunction`] entries that can be used to get [`UnwindInfo`] for a +//! particular code location. +//! +//! Unwind information contains a list of unwind codes which specify the operations that are +//! necessary to restore registers (including the stack pointer RSP) when unwinding out of a +//! function. +//! +//! Depending on where the instruction pointer lies, there are three strategies to unwind: +//! +//! 1. If the RIP is within an epilog, then control is leaving the function, there can be no +//! exception handler associated with this exception for this function, and the effects of the +//! epilog must be continued to compute the context of the caller function. To determine if the +//! RIP is within an epilog, the code stream from RIP on is examined. If that code stream can be +//! matched to the trailing portion of a legitimate epilog, then it's in an epilog, and the +//! remaining portion of the epilog is simulated, with the context record updated as each +//! instruction is processed. After this, step 1 is repeated. +//! +//! 2. Case b) If the RIP lies within the prologue, then control has not entered the function, +//! there can be no exception handler associated with this exception for this function, and the +//! effects of the prolog must be undone to compute the context of the caller function. The RIP +//! is within the prolog if the distance from the function start to the RIP is less than or +//! equal to the prolog size encoded in the unwind info. The effects of the prolog are unwound +//! by scanning forward through the unwind codes array for the first entry with an offset less +//! than or equal to the offset of the RIP from the function start, then undoing the effect of +//! all remaining items in the unwind code array. Step 1 is then repeated. +//! +//! 3. If the RIP is not within a prolog or epilog and the function has an exception handler, then +//! the language-specific handler is called. The handler scans its data and calls filter +//! functions as appropriate. The language-specific handler can return that the exception was +//! handled or that the search is to be continued. It can also initiate an unwind directly. +//! +//! For more information, see [x64 exception handling]. +//! +//! [`ExceptionData`]: struct.ExceptionData.html +//! [`RuntimeFunction`]: struct.RuntimeFunction.html +//! [`UnwindInfo`]: struct.UnwindInfo.html +//! [x64 exception handling]: https://docs.microsoft.com/en-us/cpp/build/exception-handling-x64?view=vs-2017 + +use core::cmp::Ordering; +use core::fmt; +use core::iter::FusedIterator; + +use scroll::ctx::TryFromCtx; +use scroll::{self, Pread, Pwrite}; + +use crate::error; + +use crate::pe::data_directories; +use crate::pe::options; +use crate::pe::section_table; +use crate::pe::utils; + +/// The function has an exception handler that should be called when looking for functions that need +/// to examine exceptions. +const UNW_FLAG_EHANDLER: u8 = 0x01; +/// The function has a termination handler that should be called when unwinding an exception. +const UNW_FLAG_UHANDLER: u8 = 0x02; +/// This unwind info structure is not the primary one for the procedure. Instead, the chained unwind +/// info entry is the contents of a previous `RUNTIME_FUNCTION` entry. If this flag is set, then the +/// `UNW_FLAG_EHANDLER` and `UNW_FLAG_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 UNW_FLAG_CHAININFO: u8 = 0x04; + +/// info == register number +const UWOP_PUSH_NONVOL: u8 = 0; +/// no info, alloc size in next 2 slots +const UWOP_ALLOC_LARGE: u8 = 1; +/// info == size of allocation / 8 - 1 +const UWOP_ALLOC_SMALL: u8 = 2; +/// no info, FP = RSP + UNWIND_INFO.FPRegOffset*16 +const UWOP_SET_FPREG: u8 = 3; +/// info == register number, offset in next slot +const UWOP_SAVE_NONVOL: u8 = 4; +/// info == register number, offset in next 2 slots +const UWOP_SAVE_NONVOL_FAR: u8 = 5; +/// changes the structure of unwind codes to `struct Epilogue`. +/// (was UWOP_SAVE_XMM in version 1, but deprecated and removed) +const UWOP_EPILOG: u8 = 6; +/// reserved +/// (was UWOP_SAVE_XMM_FAR in version 1, but deprecated and removed) +const UWOP_SPARE_CODE: u8 = 7; +/// info == XMM reg number, offset in next slot +const UWOP_SAVE_XMM128: u8 = 8; +/// info == XMM reg number, offset in next 2 slots +const UWOP_SAVE_XMM128_FAR: u8 = 9; +/// info == 0: no error-code, 1: error-code +const UWOP_PUSH_MACHFRAME: u8 = 10; + +/// Size of `RuntimeFunction` entries. +const RUNTIME_FUNCTION_SIZE: usize = 12; +/// Size of unwind code slots. Codes take 1 - 3 slots. +const UNWIND_CODE_SIZE: usize = 2; + +/// An unwind entry for a range of a function. +/// +/// Unwind information for this function can be loaded with [`ExceptionData::get_unwind_info`]. +/// +/// [`ExceptionData::get_unwind_info`]: struct.ExceptionData.html#method.get_unwind_info +#[repr(C)] +#[derive(Copy, Clone, PartialEq, Default, Pread, Pwrite)] +pub struct RuntimeFunction { + /// Function start address. + pub begin_address: u32, + /// Function end address. + pub end_address: u32, + /// Unwind info address. + 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() + } +} + +/// Iterator over runtime function entries in [`ExceptionData`](struct.ExceptionData.html). +#[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<'_> {} + +/// An x64 register used during unwinding. +/// +/// - `0` - `15`: General purpose registers +/// - `17` - `32`: XMM registers +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] +pub struct Register(pub u8); + +impl Register { + fn xmm(number: u8) -> Self { + Register(number + 17) + } + + /// Returns the x64 register name. + 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", + _ => "", + } + } +} + +/// An unsigned offset to a value in the local stack frame. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum StackFrameOffset { + /// Offset from the current RSP, that is, the lowest address of the fixed stack allocation. + /// + /// To restore this register, read the value at the given offset from the RSP. + RSP(u32), + + /// Offset from the value of the frame pointer register. + /// + /// To restore this register, read the value at the given offset from the FP register, reduced + /// by the `frame_register_offset` value specified in the `UnwindInfo` structure. By definition, + /// the frame pointer register is any register other than RAX (`0`). + 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()) + } +} + +/// An unwind operation corresponding to code in the function prolog. +/// +/// Unwind operations can be used to reverse the effects of the function prolog and restore register +/// values of parent stack frames that have been saved to the stack. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum UnwindOperation { + /// Push a nonvolatile integer register, decrementing `RSP` by 8. + PushNonVolatile(Register), + + /// Allocate a fixed-size area on the stack. + Alloc(u32), + + /// Establish the frame pointer register by setting the register to some offset of the current + /// RSP. The use of an offset permits establishing a frame pointer that points to the middle of + /// the fixed stack allocation, helping code density by allowing more accesses to use short + /// instruction forms. + SetFPRegister, + + /// Save a nonvolatile integer register on the stack using a MOV instead of a PUSH. This code is + /// primarily used for shrink-wrapping, where a nonvolatile register is saved to the stack in a + /// position that was previously allocated. + SaveNonVolatile(Register, StackFrameOffset), + + /// Save the lower 64 bits of a nonvolatile XMM register on the stack. + SaveXMM(Register, StackFrameOffset), + + /// Describes the function epilog. + /// + /// This operation has been introduced with unwind info version 2 and is not implemented yet. + Epilog, + + /// Save all 128 bits of a nonvolatile XMM register on the stack. + SaveXMM128(Register, StackFrameOffset), + + /// Push a machine frame. This is used to record the effect of a hardware interrupt or + /// exception. Depending on the error flag, this frame has two different layouts. + /// + /// This unwind code always appears in a dummy prolog, which is never actually executed but + /// instead appears before the real entry point of an interrupt routine, and exists only to + /// provide a place to simulate the push of a machine frame. This operation records that + /// simulation, which indicates the machine has conceptually done this: + /// + /// 1. Pop RIP return address from top of stack into `temp` + /// 2. `$ss`, Push old `$rsp`, `$rflags`, `$cs`, `temp` + /// 3. If error flag is `true`, push the error code + /// + /// Without an error code, RSP was incremented by `40` and the following was frame pushed: + /// + /// Offset | Value + /// ---------|-------- + /// RSP + 32 | `$ss` + /// RSP + 24 | old `$rsp` + /// RSP + 16 | `$rflags` + /// RSP + 8 | `$cs` + /// RSP + 0 | `$rip` + /// + /// With an error code, RSP was incremented by `48` and the following was frame pushed: + /// + /// Offset | Value + /// ---------|-------- + /// RSP + 40 | `$ss` + /// RSP + 32 | old `$rsp` + /// RSP + 24 | `$rflags` + /// RSP + 16 | `$cs` + /// RSP + 8 | `$rip` + /// RSP + 0 | error code + PushMachineFrame(bool), + + /// A reserved operation without effect. + Noop, +} + +/// Context used to parse unwind operation. +#[derive(Clone, Copy, Debug, PartialEq)] +struct UnwindOpContext { + /// Version of the unwind info. + version: u8, + + /// The nonvolatile register used as the frame pointer of this function. + /// + /// If this register is non-zero, all stack frame offsets used in unwind operations are of type + /// `StackFrameOffset::FP`. When loading these offsets, they have to be based off the value of + /// this frame register instead of the conventional RSP. This allows the RSP to be modified. + frame_register: Register, +} + +/// An unwind operation that is executed at a particular place in the function prolog. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct UnwindCode { + /// Offset of the corresponding instruction in the function prolog. + /// + /// To be precise, this is the offset from the beginning of the prolog of the end of the + /// instruction that performs this operation, plus 1 (that is, the offset of the start of the + /// next instruction). + /// + /// Unwind codes are ordered by this offset in reverse order, suitable for unwinding. + pub code_offset: u8, + + /// The operation that was performed by the code in the prolog. + 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 => { + let data = u32::from(bytes.gread_with::<u16>(&mut read, scroll::LE)?) * 16; + if ctx.version == 1 { + let register = Register::xmm(operation_info); + UnwindOperation::SaveXMM(register, StackFrameOffset::with_ctx(data, ctx)) + } else { + // TODO: See https://weekly-geekly.github.io/articles/322956/index.html + UnwindOperation::Epilog + } + } + 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 { + UnwindOperation::Noop + } + } + 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)) + } +} + +/// An iterator over unwind codes for a function or part of a function, returned from +/// [`UnwindInfo`]. +/// +/// [`UnwindInfo`]: struct.UnwindInfo.html +#[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; + // the largest codes take up three slots + let lower = (upper + 3 - (upper % 3)) / 3; + (lower, Some(upper)) + } +} + +impl FusedIterator for UnwindCodeIterator<'_> {} + +/// A language-specific handler that is called as part of the search for an exception handler or as +/// part of an unwind. +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum UnwindHandler<'a> { + /// The image-relative address of an exception handler and its implementation-defined data. + ExceptionHandler(u32, &'a [u8]), + /// The image-relative address of a termination handler and its implementation-defined data. + TerminationHandler(u32, &'a [u8]), +} + +/// Unwind information for a function or portion of a function. +/// +/// The unwind info structure is used to record the effects a function has on the stack pointer and +/// where the nonvolatile registers are saved on the stack. The unwind codes can be enumerated with +/// [`unwind_codes`]. +/// +/// This unwind info might only be secondary information, and link to a [chained unwind handler]. +/// For unwinding, this link shall be followed until the root unwind info record has been resolved. +/// +/// [`unwind_codes`]: struct.UnwindInfo.html#method.unwind_codes +/// [chained unwind handler]: struct.UnwindInfo.html#structfield.chained_info +#[derive(Clone)] +pub struct UnwindInfo<'a> { + /// Version of this unwind info. + pub version: u8, + + /// Length of the function prolog in bytes. + pub size_of_prolog: u8, + + /// The nonvolatile register used as the frame pointer of this function. + /// + /// If this register is non-zero, all stack frame offsets used in unwind operations are of type + /// `StackFrameOffset::FP`. When loading these offsets, they have to be based off the value of + /// this frame register instead of the conventional RSP. This allows the RSP to be modified. + pub frame_register: Register, + + /// Offset from RSP that is applied to the FP register when it is established. + /// + /// When loading offsets of type `StackFrameOffset::FP` from the stack, this offset has to be + /// subtracted before loading the value since the actual RSP was lower by that amount in the + /// prolog. + pub frame_register_offset: u32, + + /// A record pointing to chained unwind information. + /// + /// If chained unwind info is present, then this unwind info is a secondary one and the linked + /// unwind info contains primary information. Chained info is useful in two situations. First, + /// it is used for noncontiguous code segments. Second, this mechanism is sometimes used to + /// group volatile register saves. + /// + /// The referenced unwind info can itself specify chained unwind information, until it arrives + /// at the root unwind info. Generally, the entire chain should be considered when unwinding. + pub chained_info: Option<RuntimeFunction>, + + /// An exception or termination handler called as part of the unwind. + pub handler: Option<UnwindHandler<'a>>, + + /// A list of unwind codes, sorted descending by code offset. + code_bytes: &'a [u8], +} + +impl<'a> UnwindInfo<'a> { + /// Parses unwind information from the image at the given offset. + pub fn parse(bytes: &'a [u8], mut offset: usize) -> error::Result<Self> { + // Read the version and flags fields, which are combined into a single byte. + 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)?; + + // Parse the frame register and frame register offset values, that are combined into a + // single byte. + let frame_info = bytes.gread_with::<u8>(&mut offset, scroll::LE)?; + // If nonzero, then the function uses a frame pointer (FP), and this field is the number + // of the nonvolatile register used as the frame pointer. The zero register value does + // not need special casing since it will not be referenced by the unwind operations. + let frame_register = Register(frame_info & 0xf); + // The the scaled offset from RSP that is applied to the FP register when it's + // established. The actual FP register is set to RSP + 16 * this number, allowing + // offsets from 0 to 240. + let frame_register_offset = u32::from((frame_info >> 4) * 16); + + // An array of items that explains the effect of the prolog on the nonvolatile registers and + // RSP. Some unwind codes require more than one slot in the array. + let codes_size = count_of_codes as usize * UNWIND_CODE_SIZE; + let code_bytes = bytes.gread_with(&mut offset, codes_size)?; + + // For alignment purposes, the codes array always has an even number of entries, and the + // final entry is potentially unused. In that case, the array is one longer than indicated + // by the count of unwind codes field. + if count_of_codes % 2 != 0 { + offset += 2; + } + debug_assert!(offset % 4 == 0); + + let mut chained_info = None; + let mut handler = None; + + // If flag UNW_FLAG_CHAININFO is set then the UNWIND_INFO structure ends with three UWORDs. + // These UWORDs represent the RUNTIME_FUNCTION information for the function of the chained + // unwind. + if flags & UNW_FLAG_CHAININFO != 0 { + chained_info = Some(bytes.gread_with(&mut offset, scroll::LE)?); + + // The relative address of the language-specific handler is present in the UNWIND_INFO + // whenever flags UNW_FLAG_EHANDLER or UNW_FLAG_UHANDLER are set. The language-specific + // handler is called as part of the search for an exception handler or as part of an unwind. + } else if flags & (UNW_FLAG_EHANDLER | UNW_FLAG_UHANDLER) != 0 { + let address = bytes.gread_with::<u32>(&mut offset, scroll::LE)?; + 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, + }) + } + + /// Returns an iterator over unwind codes in this unwind info. + /// + /// Unwind codes are iterated in descending `code_offset` order suitable for unwinding. If the + /// optional [`chained_info`] is present, codes of that unwind info should be interpreted + /// immediately afterwards. + pub fn unwind_codes(&self) -> UnwindCodeIterator<'a> { + UnwindCodeIterator { + bytes: self.code_bytes, + offset: 0, + context: UnwindOpContext { + version: self.version, + frame_register: self.frame_register, + }, + } + } +} + +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() + } +} + +/// Exception handling and stack unwind information for functions in the image. +pub struct ExceptionData<'a> { + bytes: &'a [u8], + offset: usize, + size: usize, + file_alignment: u32, +} + +impl<'a> ExceptionData<'a> { + /// Parses exception data from the image at the given offset. + 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(), + ) + } + + /// Parses exception data from the image at the given offset. + 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", + })); + } + + 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, + }) + } + + /// The number of function entries described by this exception data. + pub fn len(&self) -> usize { + self.size / RUNTIME_FUNCTION_SIZE + } + + /// Indicating whether there are functions in this entry. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Iterates all function entries in order of their code offset. + /// + /// To search for a function by relative instruction address, use [`find_function`]. To resolve + /// unwind information, use [`get_unwind_info`]. + /// + /// [`find_function`]: struct.ExceptionData.html#method.find_function + /// [`get_unwind_info`]: struct.ExceptionData.html#method.get_unwind_info + pub fn functions(&self) -> RuntimeFunctionIterator<'a> { + RuntimeFunctionIterator { + data: &self.bytes[self.offset..self.offset + self.size], + } + } + + /// Returns the function at the given index. + pub fn get_function(&self, index: usize) -> error::Result<RuntimeFunction> { + self.get_function_by_offset(self.offset + index * RUNTIME_FUNCTION_SIZE) + } + + /// Performs a binary search to find a function entry covering the given RVA relative to the + /// image. + pub fn find_function(&self, rva: u32) -> error::Result<Option<RuntimeFunction>> { + // NB: Binary search implementation copied from std::slice::binary_search_by and adapted. + // Theoretically, there should be nothing that causes parsing runtime functions to fail and + // all access to the bytes buffer is guaranteed to be in range. However, since all other + // functions also return Results, this is much more ergonomic here. + + 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) + } + } + + /// Resolves unwind information for the given function entry. + 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()) + } + + /// Resolves unwind information for the given function entry. + 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) + } + + #[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)?) + } +} + +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() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_size_of_runtime_function() { + assert_eq!( + std::mem::size_of::<RuntimeFunction>(), + RUNTIME_FUNCTION_SIZE + ); + } + + // Tests disabled until there is a solution for handling binary test data + // See https://github.com/m4b/goblin/issues/185 + + // macro_rules! microsoft_symbol { + // ($name:literal, $id:literal) => {{ + // use std::fs::File; + // use std::path::Path; + + // let path = Path::new(concat!("cache/", $name)); + // if !path.exists() { + // let url = format!( + // "https://msdl.microsoft.com/download/symbols/{}/{}/{}", + // $name, $id, $name + // ); + + // let mut response = reqwest::get(&url).expect(concat!("get ", $name)); + // let mut target = File::create(path).expect(concat!("create ", $name)); + // response + // .copy_to(&mut target) + // .expect(concat!("download ", $name)); + // } + + // std::fs::read(path).expect(concat!("open ", $name)) + // }}; + // } + + // lazy_static::lazy_static! { + // static ref PE_DATA: Vec<u8> = microsoft_symbol!("WSHTCPIP.DLL", "4a5be0b77000"); + // } + + // #[test] + // fn test_parse() { + // let pe = PE::parse(&PE_DATA).expect("parse PE"); + // let exception_data = pe.exception_data.expect("get exception data"); + + // assert_eq!(exception_data.len(), 19); + // assert!(!exception_data.is_empty()); + // } + + // #[test] + // fn test_iter_functions() { + // let pe = PE::parse(&PE_DATA).expect("parse PE"); + // let exception_data = pe.exception_data.expect("get exception data"); + + // let functions: Vec<RuntimeFunction> = exception_data + // .functions() + // .map(|result| result.expect("parse runtime function")) + // .collect(); + + // assert_eq!(functions.len(), 19); + + // let expected = RuntimeFunction { + // begin_address: 0x1355, + // end_address: 0x1420, + // unwind_info_address: 0x4019, + // }; + + // assert_eq!(functions[4], expected); + // } + + // #[test] + // fn test_get_function() { + // let pe = PE::parse(&PE_DATA).expect("parse PE"); + // let exception_data = pe.exception_data.expect("get exception data"); + + // let expected = RuntimeFunction { + // begin_address: 0x1355, + // end_address: 0x1420, + // unwind_info_address: 0x4019, + // }; + + // assert_eq!( + // exception_data.get_function(4).expect("find function"), + // expected + // ); + // } + + // #[test] + // fn test_find_function() { + // let pe = PE::parse(&PE_DATA).expect("parse PE"); + // let exception_data = pe.exception_data.expect("get exception data"); + + // let expected = RuntimeFunction { + // begin_address: 0x1355, + // end_address: 0x1420, + // unwind_info_address: 0x4019, + // }; + + // assert_eq!( + // exception_data.find_function(0x1400).expect("find function"), + // Some(expected) + // ); + // } + + // #[test] + // fn test_find_function_none() { + // let pe = PE::parse(&PE_DATA).expect("parse PE"); + // let exception_data = pe.exception_data.expect("get exception data"); + + // // 0x1d00 is the end address of the last function. + + // assert_eq!( + // exception_data.find_function(0x1d00).expect("find function"), + // None + // ); + // } + + // #[test] + // fn test_get_unwind_info() { + // let pe = PE::parse(&PE_DATA).expect("parse PE"); + // let exception_data = pe.exception_data.expect("get exception data"); + + // // runtime function #0 directly refers to unwind info + // let rt_function = RuntimeFunction { + // begin_address: 0x1010, + // end_address: 0x1090, + // unwind_info_address: 0x25d8, + // }; + + // let unwind_info = exception_data + // .get_unwind_info(rt_function, &pe.sections) + // .expect("get unwind info"); + + // // Unwind codes just used to assert that the right unwind info was resolved + // let expected = &[4, 98]; + + // assert_eq!(unwind_info.code_bytes, expected); + // } + + // #[test] + // fn test_get_unwind_info_redirect() { + // let pe = PE::parse(&PE_DATA).expect("parse PE"); + // let exception_data = pe.exception_data.expect("get exception data"); + + // // runtime function #4 has a redirect (unwind_info_address & 1). + // let rt_function = RuntimeFunction { + // begin_address: 0x1355, + // end_address: 0x1420, + // unwind_info_address: 0x4019, + // }; + + // let unwind_info = exception_data + // .get_unwind_info(rt_function, &pe.sections) + // .expect("get unwind info"); + + // // Unwind codes just used to assert that the right unwind info was resolved + // let expected = &[ + // 28, 100, 15, 0, 28, 84, 14, 0, 28, 52, 12, 0, 28, 82, 24, 240, 22, 224, 20, 208, 18, + // 192, 16, 112, + // ]; + + // assert_eq!(unwind_info.code_bytes, expected); + // } + + #[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); + } +} |