use super::{ arch::ArchX86_64, unwind_rule::{OffsetOrPop, UnwindRuleX86_64}, unwindregs::Reg, }; use crate::arch::Arch; use crate::pe::{PeSections, PeUnwinderError, PeUnwinding}; use crate::unwind_result::UnwindResult; use core::ops::ControlFlow; use alloc::vec::Vec; use pe_unwind_info::x86_64::{ FunctionEpilogInstruction, FunctionTableEntries, Register, UnwindInfo, UnwindInfoTrailer, UnwindOperation, UnwindState, }; struct State<'a, F> { regs: &'a mut ::UnwindRegs, read_stack: &'a mut F, } impl UnwindState for State<'_, F> where F: FnMut(u64) -> Result, { fn read_register(&mut self, register: Register) -> u64 { self.regs.get(convert_pe_register(register)) } fn read_stack(&mut self, addr: u64) -> Option { (self.read_stack)(addr).ok() } fn write_register(&mut self, register: Register, value: u64) { self.regs.set(convert_pe_register(register), value) } fn write_xmm_register(&mut self, _register: pe_unwind_info::x86_64::XmmRegister, _value: u128) { // Ignore } } fn convert_pe_register(r: Register) -> Reg { match r { Register::RAX => Reg::RAX, Register::RCX => Reg::RCX, Register::RDX => Reg::RDX, Register::RBX => Reg::RBX, Register::RSP => Reg::RSP, Register::RBP => Reg::RBP, Register::RSI => Reg::RSI, Register::RDI => Reg::RDI, Register::R8 => Reg::R8, Register::R9 => Reg::R9, Register::R10 => Reg::R10, Register::R11 => Reg::R11, Register::R12 => Reg::R12, Register::R13 => Reg::R13, Register::R14 => Reg::R14, Register::R15 => Reg::R15, } } impl From<&'_ FunctionEpilogInstruction> for OffsetOrPop { fn from(value: &'_ FunctionEpilogInstruction) -> Self { match value { FunctionEpilogInstruction::AddSP(offset) => { if let Ok(v) = (offset / 8).try_into() { OffsetOrPop::OffsetBy8(v) } else { OffsetOrPop::None } } FunctionEpilogInstruction::Pop(reg) => OffsetOrPop::Pop(convert_pe_register(*reg)), _ => OffsetOrPop::None, } } } impl From<&'_ UnwindOperation> for OffsetOrPop { fn from(value: &'_ UnwindOperation) -> Self { match value { UnwindOperation::UnStackAlloc(offset) => { if let Ok(v) = (offset / 8).try_into() { OffsetOrPop::OffsetBy8(v) } else { OffsetOrPop::None } } UnwindOperation::PopNonVolatile(reg) => OffsetOrPop::Pop(convert_pe_register(*reg)), _ => OffsetOrPop::None, } } } impl PeUnwinding for ArchX86_64 { fn unwind_frame( sections: PeSections, address: u32, regs: &mut Self::UnwindRegs, is_first_frame: bool, read_stack: &mut F, ) -> Result, PeUnwinderError> where F: FnMut(u64) -> Result, D: core::ops::Deref, { let entries = FunctionTableEntries::parse(sections.pdata); let Some(function) = entries.lookup(address) else { return Ok(UnwindResult::ExecRule(UnwindRuleX86_64::JustReturn)); }; let read_stack_err = |read_stack: &mut F, addr| { read_stack(addr).map_err(|()| PeUnwinderError::MissingStackData(Some(addr))) }; let unwind_info_address = function.unwind_info_address.get(); let unwind_info = UnwindInfo::parse(sections.unwind_info_memory_at_rva(unwind_info_address)?) .ok_or(PeUnwinderError::UnwindInfoParseError)?; if is_first_frame { // 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). We only need to check this for the first unwind info (if // there are chained infos). let bytes = (function.end_address.get() - address) as usize; let instruction = §ions.text_memory_at_rva(address)?[..bytes]; if let Ok(epilog_instructions) = FunctionEpilogInstruction::parse_sequence(instruction, unwind_info.frame_register()) { // If the epilog is an optional AddSP followed by Pops, we can return a cache // rule. if let Some(rule) = UnwindRuleX86_64::for_sequence_of_offset_or_pop(epilog_instructions.iter()) { return Ok(UnwindResult::ExecRule(rule)); } for instruction in epilog_instructions.iter() { match instruction { FunctionEpilogInstruction::AddSP(offset) => { let rsp = regs.get(Reg::RSP); regs.set(Reg::RSP, rsp + *offset as u64); } FunctionEpilogInstruction::AddSPFromFP(offset) => { let fp = unwind_info .frame_register() .expect("invalid fp register offset"); let fp = convert_pe_register(fp); let fp = regs.get(fp); regs.set(Reg::RSP, fp + *offset as u64); } FunctionEpilogInstruction::Pop(reg) => { let rsp = regs.get(Reg::RSP); let val = read_stack_err(read_stack, rsp)?; regs.set(convert_pe_register(*reg), val); regs.set(Reg::RSP, rsp + 8); } } } let rsp = regs.get(Reg::RSP); let ra = read_stack_err(read_stack, rsp)?; regs.set(Reg::RSP, rsp + 8); return Ok(UnwindResult::Uncacheable(ra)); } } // Get all chained UnwindInfo and resolve errors when collecting. let chained_info = core::iter::successors(Some(Ok(unwind_info)), |info| { let Ok(info) = info else { return None; }; if let Some(UnwindInfoTrailer::ChainedUnwindInfo { chained }) = info.trailer() { let unwind_info_address = chained.unwind_info_address.get(); Some( sections .unwind_info_memory_at_rva(unwind_info_address) .and_then(|data| { UnwindInfo::parse(data).ok_or(PeUnwinderError::UnwindInfoParseError) }), ) } else { None } }) .collect::, _>>()?; // Get all operations across chained UnwindInfo. The first should be filtered to only those // operations which are before the offset in the function. let offset = address - function.begin_address.get(); let operations = chained_info.into_iter().enumerate().flat_map(|(i, info)| { info.unwind_operations() .skip_while(move |(o, _)| i == 0 && *o as u32 > offset) .map(|(_, op)| op) }); // We need to collect operations to first check (without losing ownership) whether an // unwind rule can be returned. let operations = operations.collect::>(); if let Some(rule) = UnwindRuleX86_64::for_sequence_of_offset_or_pop(operations.iter()) { return Ok(UnwindResult::ExecRule(rule)); } // Resolve operations to get the return address. let mut state = State { regs, read_stack }; for op in operations { if let ControlFlow::Break(ra) = unwind_info .resolve_operation(&mut state, &op) .ok_or(PeUnwinderError::MissingStackData(None))? { return Ok(UnwindResult::Uncacheable(ra)); } } let rsp = regs.get(Reg::RSP); let ra = read_stack_err(read_stack, rsp)?; regs.set(Reg::RSP, rsp + 8); Ok(UnwindResult::Uncacheable(ra)) } }