1 //! System V ABI unwind information. 2 3 use alloc::vec::Vec; 4 use byteorder::{ByteOrder, LittleEndian}; 5 #[cfg(feature = "enable-serde")] 6 use serde::{Deserialize, Serialize}; 7 8 /// Maximum (inclusive) size of a "small" stack allocation 9 const SMALL_ALLOC_MAX_SIZE: u32 = 128; 10 /// Maximum (inclusive) size of a "large" stack allocation that can represented in 16-bits 11 const LARGE_ALLOC_16BIT_MAX_SIZE: u32 = 524280; 12 13 struct Writer<'a> { 14 buf: &'a mut [u8], 15 offset: usize, 16 } 17 18 impl<'a> Writer<'a> { new(buf: &'a mut [u8]) -> Self19 pub fn new(buf: &'a mut [u8]) -> Self { 20 Self { buf, offset: 0 } 21 } 22 write_u8(&mut self, v: u8)23 fn write_u8(&mut self, v: u8) { 24 self.buf[self.offset] = v; 25 self.offset += 1; 26 } 27 write_u16<T: ByteOrder>(&mut self, v: u16)28 fn write_u16<T: ByteOrder>(&mut self, v: u16) { 29 T::write_u16(&mut self.buf[self.offset..(self.offset + 2)], v); 30 self.offset += 2; 31 } 32 write_u32<T: ByteOrder>(&mut self, v: u32)33 fn write_u32<T: ByteOrder>(&mut self, v: u32) { 34 T::write_u32(&mut self.buf[self.offset..(self.offset + 4)], v); 35 self.offset += 4; 36 } 37 } 38 39 /// The supported unwind codes for the x64 Windows ABI. 40 /// 41 /// See: https://docs.microsoft.com/en-us/cpp/build/exception-handling-x64 42 /// Only what is needed to describe the prologues generated by the Cranelift x86 ISA are represented here. 43 /// Note: the Cranelift x86 ISA RU enum matches the Windows unwind GPR encoding values. 44 #[derive(Clone, Debug, PartialEq, Eq)] 45 #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] 46 pub(crate) enum UnwindCode { 47 PushRegister { 48 offset: u8, 49 reg: u8, 50 }, 51 SaveXmm { 52 offset: u8, 53 reg: u8, 54 stack_offset: u32, 55 }, 56 StackAlloc { 57 offset: u8, 58 size: u32, 59 }, 60 SetFramePointer { 61 offset: u8, 62 sp_offset: u8, 63 }, 64 } 65 66 impl UnwindCode { emit(&self, writer: &mut Writer)67 fn emit(&self, writer: &mut Writer) { 68 enum UnwindOperation { 69 PushNonvolatileRegister = 0, 70 LargeStackAlloc = 1, 71 SmallStackAlloc = 2, 72 SetFramePointer = 3, 73 SaveXmm128 = 8, 74 SaveXmm128Far = 9, 75 } 76 77 match self { 78 Self::PushRegister { offset, reg } => { 79 writer.write_u8(*offset); 80 writer.write_u8((*reg << 4) | (UnwindOperation::PushNonvolatileRegister as u8)); 81 } 82 Self::SaveXmm { 83 offset, 84 reg, 85 stack_offset, 86 } => { 87 writer.write_u8(*offset); 88 let stack_offset = stack_offset / 16; 89 if stack_offset <= core::u16::MAX as u32 { 90 writer.write_u8((*reg << 4) | (UnwindOperation::SaveXmm128 as u8)); 91 writer.write_u16::<LittleEndian>(stack_offset as u16); 92 } else { 93 writer.write_u8((*reg << 4) | (UnwindOperation::SaveXmm128Far as u8)); 94 writer.write_u16::<LittleEndian>(stack_offset as u16); 95 writer.write_u16::<LittleEndian>((stack_offset >> 16) as u16); 96 } 97 } 98 Self::StackAlloc { offset, size } => { 99 // Stack allocations on Windows must be a multiple of 8 and be at least 1 slot 100 assert!(*size >= 8); 101 assert!((*size % 8) == 0); 102 103 writer.write_u8(*offset); 104 if *size <= SMALL_ALLOC_MAX_SIZE { 105 writer.write_u8( 106 ((((*size - 8) / 8) as u8) << 4) | UnwindOperation::SmallStackAlloc as u8, 107 ); 108 } else if *size <= LARGE_ALLOC_16BIT_MAX_SIZE { 109 writer.write_u8(UnwindOperation::LargeStackAlloc as u8); 110 writer.write_u16::<LittleEndian>((*size / 8) as u16); 111 } else { 112 writer.write_u8((1 << 4) | (UnwindOperation::LargeStackAlloc as u8)); 113 writer.write_u32::<LittleEndian>(*size); 114 } 115 } 116 Self::SetFramePointer { offset, sp_offset } => { 117 writer.write_u8(*offset); 118 writer.write_u8((*sp_offset << 4) | (UnwindOperation::SetFramePointer as u8)); 119 } 120 }; 121 } 122 node_count(&self) -> usize123 fn node_count(&self) -> usize { 124 match self { 125 Self::StackAlloc { size, .. } => { 126 if *size <= SMALL_ALLOC_MAX_SIZE { 127 1 128 } else if *size <= LARGE_ALLOC_16BIT_MAX_SIZE { 129 2 130 } else { 131 3 132 } 133 } 134 Self::SaveXmm { stack_offset, .. } => { 135 if *stack_offset <= core::u16::MAX as u32 { 136 2 137 } else { 138 3 139 } 140 } 141 _ => 1, 142 } 143 } 144 } 145 146 /// Represents Windows x64 unwind information. 147 /// 148 /// For information about Windows x64 unwind info, see: 149 /// https://docs.microsoft.com/en-us/cpp/build/exception-handling-x64 150 #[derive(Clone, Debug, PartialEq, Eq)] 151 #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] 152 pub struct UnwindInfo { 153 pub(crate) flags: u8, 154 pub(crate) prologue_size: u8, 155 pub(crate) frame_register: Option<u8>, 156 pub(crate) frame_register_offset: u8, 157 pub(crate) unwind_codes: Vec<UnwindCode>, 158 } 159 160 impl UnwindInfo { 161 /// Gets the emit size of the unwind information, in bytes. emit_size(&self) -> usize162 pub fn emit_size(&self) -> usize { 163 let node_count = self.node_count(); 164 165 // Calculation of the size requires no SEH handler or chained info 166 assert!(self.flags == 0); 167 168 // Size of fixed part of UNWIND_INFO is 4 bytes 169 // Then comes the UNWIND_CODE nodes (2 bytes each) 170 // Then comes 2 bytes of padding for the unwind codes if necessary 171 // Next would come the SEH data, but we assert above that the function doesn't have SEH data 172 173 4 + (node_count * 2) + if (node_count & 1) == 1 { 2 } else { 0 } 174 } 175 176 /// Emits the unwind information into the given mutable byte slice. 177 /// 178 /// This function will panic if the slice is not at least `emit_size` in length. emit(&self, buf: &mut [u8])179 pub fn emit(&self, buf: &mut [u8]) { 180 const UNWIND_INFO_VERSION: u8 = 1; 181 182 let node_count = self.node_count(); 183 assert!(node_count <= 256); 184 185 let mut writer = Writer::new(buf); 186 187 writer.write_u8((self.flags << 3) | UNWIND_INFO_VERSION); 188 writer.write_u8(self.prologue_size); 189 writer.write_u8(node_count as u8); 190 191 if let Some(reg) = self.frame_register { 192 writer.write_u8((self.frame_register_offset << 4) | reg); 193 } else { 194 writer.write_u8(0); 195 } 196 197 // Unwind codes are written in reverse order (prologue offset descending) 198 for code in self.unwind_codes.iter().rev() { 199 code.emit(&mut writer); 200 } 201 202 // To keep a 32-bit alignment, emit 2 bytes of padding if there's an odd number of 16-bit nodes 203 if (node_count & 1) == 1 { 204 writer.write_u16::<LittleEndian>(0); 205 } 206 207 // Ensure the correct number of bytes was emitted 208 assert_eq!(writer.offset, self.emit_size()); 209 } 210 node_count(&self) -> usize211 fn node_count(&self) -> usize { 212 self.unwind_codes 213 .iter() 214 .fold(0, |nodes, c| nodes + c.node_count()) 215 } 216 } 217