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