/rust/registry/src/index.crates.io-1949cf8c6b5b557f/goblin-0.10.7/src/pe/exception.rs
Line | Count | Source |
1 | | //! Exception handling and stack unwinding for x64 and ARM64. |
2 | | //! |
3 | | //! Exception information is exposed via the [`ExceptionData`] structure. If present in a PE file, |
4 | | //! it contains a list of [`RuntimeFunction`] entries that can be used to get [`UnwindInfo`] for a |
5 | | //! particular code location. |
6 | | //! |
7 | | //! Unwind information contains a list of unwind codes which specify the operations that are |
8 | | //! necessary to restore registers (including the stack pointer RSP) when unwinding out of a |
9 | | //! function. |
10 | | //! |
11 | | //! Depending on where the instruction pointer lies, there are three strategies to unwind: |
12 | | //! |
13 | | //! 1. If the RIP is within an epilog, then control is leaving the function, there can be no |
14 | | //! exception handler associated with this exception for this function, and the effects of the |
15 | | //! epilog must be continued to compute the context of the caller function. To determine if the |
16 | | //! RIP is within an epilog, the code stream from RIP on is examined. If that code stream can be |
17 | | //! matched to the trailing portion of a legitimate epilog, then it's in an epilog, and the |
18 | | //! remaining portion of the epilog is simulated, with the context record updated as each |
19 | | //! instruction is processed. After this, step 1 is repeated. |
20 | | //! |
21 | | //! 2. Case b) If the RIP lies within the prologue, then control has not entered the function, |
22 | | //! there can be no exception handler associated with this exception for this function, and the |
23 | | //! effects of the prolog must be undone to compute the context of the caller function. The RIP |
24 | | //! is within the prolog if the distance from the function start to the RIP is less than or |
25 | | //! equal to the prolog size encoded in the unwind info. The effects of the prolog are unwound |
26 | | //! by scanning forward through the unwind codes array for the first entry with an offset less |
27 | | //! than or equal to the offset of the RIP from the function start, then undoing the effect of |
28 | | //! all remaining items in the unwind code array. Step 1 is then repeated. |
29 | | //! |
30 | | //! 3. If the RIP is not within a prolog or epilog and the function has an exception handler, then |
31 | | //! the language-specific handler is called. The handler scans its data and calls filter |
32 | | //! functions as appropriate. The language-specific handler can return that the exception was |
33 | | //! handled or that the search is to be continued. It can also initiate an unwind directly. |
34 | | //! |
35 | | //! For more information, see [x64 exception handling]. |
36 | | //! |
37 | | //! [`ExceptionData`]: struct.ExceptionData.html |
38 | | //! [`RuntimeFunction`]: struct.RuntimeFunction.html |
39 | | //! [`UnwindInfo`]: struct.UnwindInfo.html |
40 | | //! [x64 exception handling]: https://docs.microsoft.com/en-us/cpp/build/exception-handling-x64?view=vs-2017 |
41 | | |
42 | | use core::cmp::Ordering; |
43 | | use core::fmt; |
44 | | use core::iter::FusedIterator; |
45 | | |
46 | | use scroll::ctx::TryFromCtx; |
47 | | use scroll::{self, Pread, Pwrite, SizeWith}; |
48 | | |
49 | | use crate::error; |
50 | | |
51 | | use crate::pe::data_directories; |
52 | | use crate::pe::options; |
53 | | use crate::pe::section_table; |
54 | | use crate::pe::utils; |
55 | | |
56 | | /// **N**o handlers. |
57 | | #[allow(unused)] |
58 | | const UNW_FLAG_NHANDLER: u8 = 0x00; |
59 | | /// The function has an exception handler that should be called when looking for functions that need |
60 | | /// to examine exceptions. |
61 | | const UNW_FLAG_EHANDLER: u8 = 0x01; |
62 | | /// The function has a termination handler that should be called when unwinding an exception. |
63 | | const UNW_FLAG_UHANDLER: u8 = 0x02; |
64 | | /// This unwind info structure is not the primary one for the procedure. Instead, the chained unwind |
65 | | /// info entry is the contents of a previous `RUNTIME_FUNCTION` entry. If this flag is set, then the |
66 | | /// `UNW_FLAG_EHANDLER` and `UNW_FLAG_UHANDLER` flags must be cleared. Also, the frame register and |
67 | | /// fixed-stack allocation fields must have the same values as in the primary unwind info. |
68 | | const UNW_FLAG_CHAININFO: u8 = 0x04; |
69 | | |
70 | | /// info == register number |
71 | | const UWOP_PUSH_NONVOL: u8 = 0; |
72 | | /// no info, alloc size in next 2 slots |
73 | | const UWOP_ALLOC_LARGE: u8 = 1; |
74 | | /// info == size of allocation / 8 - 1 |
75 | | const UWOP_ALLOC_SMALL: u8 = 2; |
76 | | /// no info, FP = RSP + UNWIND_INFO.FPRegOffset*16 |
77 | | const UWOP_SET_FPREG: u8 = 3; |
78 | | /// info == register number, offset in next slot |
79 | | const UWOP_SAVE_NONVOL: u8 = 4; |
80 | | /// info == register number, offset in next 2 slots |
81 | | const UWOP_SAVE_NONVOL_FAR: u8 = 5; |
82 | | /// changes the structure of unwind codes to `struct Epilogue`. |
83 | | /// (was UWOP_SAVE_XMM in version 1, but deprecated and removed) |
84 | | const UWOP_EPILOG: u8 = 6; |
85 | | /// reserved |
86 | | /// (was UWOP_SAVE_XMM_FAR in version 1, but deprecated and removed) |
87 | | const UWOP_SPARE_CODE: u8 = 7; |
88 | | /// info == XMM reg number, offset in next slot |
89 | | const UWOP_SAVE_XMM128: u8 = 8; |
90 | | /// info == XMM reg number, offset in next 2 slots |
91 | | const UWOP_SAVE_XMM128_FAR: u8 = 9; |
92 | | /// info == 0: no error-code, 1: error-code |
93 | | const UWOP_PUSH_MACHFRAME: u8 = 10; |
94 | | |
95 | | /// Size of `RuntimeFunction` entries. |
96 | | const RUNTIME_FUNCTION_SIZE: usize = 12; |
97 | | /// Size of unwind code slots. Codes take 1 - 3 slots. |
98 | | const UNWIND_CODE_SIZE: usize = 2; |
99 | | |
100 | | /// Represents a single entry in a Windows PE exception handling scope table `C_SCOPE_TABLE_ENTRY`. |
101 | | /// |
102 | | /// Each entry defines a protected range of code and its associated exception handler. |
103 | | /// These entries are typically found in the scope table associated with `UNWIND_INFO` |
104 | | /// structures in Windows x64 exception handling. |
105 | | #[derive(Debug, Copy, Clone, Default, PartialEq, Hash, Pread, Pwrite, SizeWith)] |
106 | | #[repr(C)] |
107 | | pub struct ScopeTableEntry { |
108 | | /// The starting RVA (relative virtual address) of the protected code region. |
109 | | /// |
110 | | /// This marks the beginning of a `try` block. |
111 | | pub begin: u32, |
112 | | |
113 | | /// The ending RVA (exclusive) of the protected code region. |
114 | | /// |
115 | | /// This marks the end of the `try` block. |
116 | | pub end: u32, |
117 | | |
118 | | /// The RVA of the exception handler function. |
119 | | /// |
120 | | /// e.g., be invoked when an exception occurs in the associated code range. |
121 | | pub handler: u32, |
122 | | |
123 | | /// The RVA of the continuation target after the handler is executed. |
124 | | /// |
125 | | /// This is used for control transfer (e.g., continuation blocks, to resume execution after `finally`). |
126 | | pub target: u32, |
127 | | } |
128 | | |
129 | | /// Iterator over [ScopeTableEntry] entries in `C_SCOPE_TABLE`. |
130 | | #[derive(Debug)] |
131 | | pub struct ScopeTableIterator<'a> { |
132 | | data: &'a [u8], |
133 | | offset: usize, |
134 | | } |
135 | | |
136 | | impl Iterator for ScopeTableIterator<'_> { |
137 | | type Item = ScopeTableEntry; |
138 | | |
139 | 0 | fn next(&mut self) -> Option<Self::Item> { |
140 | 0 | if self.offset >= self.data.len() { |
141 | 0 | return None; |
142 | 0 | } |
143 | | |
144 | | // It is guaranteed that .expect here is really a unreachable. |
145 | | // See: that we do `num_entries * core::mem::size_of::<ScopeTableEntry>() as u32;` |
146 | 0 | Some( |
147 | 0 | self.data |
148 | 0 | .gread_with(&mut self.offset, scroll::LE) |
149 | 0 | .expect("Scope table is not aligned"), |
150 | 0 | ) |
151 | 0 | } |
152 | | |
153 | 0 | fn size_hint(&self) -> (usize, Option<usize>) { |
154 | 0 | let len = self.data.len() / core::mem::size_of::<ScopeTableEntry>(); |
155 | 0 | (len, Some(len)) |
156 | 0 | } |
157 | | } |
158 | | |
159 | | impl FusedIterator for ScopeTableIterator<'_> {} |
160 | | impl ExactSizeIterator for ScopeTableIterator<'_> {} |
161 | | |
162 | | /// An unwind entry for a range of a function. |
163 | | /// |
164 | | /// Unwind information for this function can be loaded with [`ExceptionData::get_unwind_info`]. |
165 | | /// |
166 | | /// [`ExceptionData::get_unwind_info`]: struct.ExceptionData.html#method.get_unwind_info |
167 | | #[repr(C)] |
168 | | #[derive(Copy, Clone, PartialEq, Default, Pread, Pwrite)] |
169 | | pub struct RuntimeFunction { |
170 | | /// Function start address. |
171 | | pub begin_address: u32, |
172 | | /// Function end address. |
173 | | pub end_address: u32, |
174 | | /// Unwind info address. |
175 | | pub unwind_info_address: u32, |
176 | | } |
177 | | |
178 | | impl fmt::Debug for RuntimeFunction { |
179 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
180 | 0 | f.debug_struct("RuntimeFunction") |
181 | 0 | .field("begin_address", &format_args!("{:#x}", self.begin_address)) |
182 | 0 | .field("end_address", &format_args!("{:#x}", self.end_address)) |
183 | 0 | .field( |
184 | 0 | "unwind_info_address", |
185 | 0 | &format_args!("{:#x}", self.unwind_info_address), |
186 | 0 | ) |
187 | 0 | .finish() |
188 | 0 | } |
189 | | } |
190 | | |
191 | | /// Iterator over runtime function entries in [`ExceptionData`](struct.ExceptionData.html). |
192 | | #[derive(Debug)] |
193 | | pub struct RuntimeFunctionIterator<'a> { |
194 | | data: &'a [u8], |
195 | | } |
196 | | |
197 | | impl Iterator for RuntimeFunctionIterator<'_> { |
198 | | type Item = error::Result<RuntimeFunction>; |
199 | | |
200 | 0 | fn next(&mut self) -> Option<Self::Item> { |
201 | 0 | if self.data.is_empty() { |
202 | 0 | return None; |
203 | 0 | } |
204 | | |
205 | 0 | Some(match self.data.pread_with(0, scroll::LE) { |
206 | 0 | Ok(func) => { |
207 | 0 | self.data = &self.data[RUNTIME_FUNCTION_SIZE..]; |
208 | 0 | Ok(func) |
209 | | } |
210 | 0 | Err(error) => { |
211 | 0 | self.data = &[]; |
212 | 0 | Err(error.into()) |
213 | | } |
214 | | }) |
215 | 0 | } |
216 | | |
217 | 0 | fn size_hint(&self) -> (usize, Option<usize>) { |
218 | 0 | let len = self.data.len() / RUNTIME_FUNCTION_SIZE; |
219 | 0 | (len, Some(len)) |
220 | 0 | } |
221 | | } |
222 | | |
223 | | impl FusedIterator for RuntimeFunctionIterator<'_> {} |
224 | | impl ExactSizeIterator for RuntimeFunctionIterator<'_> {} |
225 | | |
226 | | /// An x64 register used during unwinding. |
227 | | /// |
228 | | /// - `0` - `15`: General purpose registers |
229 | | /// - `17` - `32`: XMM registers |
230 | | #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] |
231 | | pub struct Register(pub u8); |
232 | | |
233 | | impl Register { |
234 | 0 | fn xmm(number: u8) -> Self { |
235 | 0 | Register(number + 17) |
236 | 0 | } |
237 | | |
238 | | /// Returns the x64 register name. |
239 | 0 | pub fn name(self) -> &'static str { |
240 | 0 | match self.0 { |
241 | 0 | 0 => "$rax", |
242 | 0 | 1 => "$rcx", |
243 | 0 | 2 => "$rdx", |
244 | 0 | 3 => "$rbx", |
245 | 0 | 4 => "$rsp", |
246 | 0 | 5 => "$rbp", |
247 | 0 | 6 => "$rsi", |
248 | 0 | 7 => "$rdi", |
249 | 0 | 8 => "$r8", |
250 | 0 | 9 => "$r9", |
251 | 0 | 10 => "$r10", |
252 | 0 | 11 => "$r11", |
253 | 0 | 12 => "$r12", |
254 | 0 | 13 => "$r13", |
255 | 0 | 14 => "$r14", |
256 | 0 | 15 => "$r15", |
257 | 0 | 16 => "$rip", |
258 | 0 | 17 => "$xmm0", |
259 | 0 | 18 => "$xmm1", |
260 | 0 | 19 => "$xmm2", |
261 | 0 | 20 => "$xmm3", |
262 | 0 | 21 => "$xmm4", |
263 | 0 | 22 => "$xmm5", |
264 | 0 | 23 => "$xmm6", |
265 | 0 | 24 => "$xmm7", |
266 | 0 | 25 => "$xmm8", |
267 | 0 | 26 => "$xmm9", |
268 | 0 | 27 => "$xmm10", |
269 | 0 | 28 => "$xmm11", |
270 | 0 | 29 => "$xmm12", |
271 | 0 | 30 => "$xmm13", |
272 | 0 | 31 => "$xmm14", |
273 | 0 | 32 => "$xmm15", |
274 | 0 | _ => "", |
275 | | } |
276 | 0 | } |
277 | | } |
278 | | |
279 | | /// An unsigned offset to a value in the local stack frame. |
280 | | #[derive(Clone, Copy, Debug, Eq, PartialEq)] |
281 | | pub enum StackFrameOffset { |
282 | | /// Offset from the current RSP, that is, the lowest address of the fixed stack allocation. |
283 | | /// |
284 | | /// To restore this register, read the value at the given offset from the RSP. |
285 | | RSP(u32), |
286 | | |
287 | | /// Offset from the value of the frame pointer register. |
288 | | /// |
289 | | /// To restore this register, read the value at the given offset from the FP register, reduced |
290 | | /// by the `frame_register_offset` value specified in the `UnwindInfo` structure. By definition, |
291 | | /// the frame pointer register is any register other than RAX (`0`). |
292 | | FP(u32), |
293 | | } |
294 | | |
295 | | impl StackFrameOffset { |
296 | 0 | fn with_ctx(offset: u32, ctx: UnwindOpContext) -> Self { |
297 | 0 | match ctx.frame_register { |
298 | 0 | Register(0) => StackFrameOffset::RSP(offset), |
299 | 0 | Register(_) => StackFrameOffset::FP(offset), |
300 | | } |
301 | 0 | } |
302 | | } |
303 | | |
304 | | impl fmt::Display for Register { |
305 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
306 | 0 | f.write_str(self.name()) |
307 | 0 | } |
308 | | } |
309 | | |
310 | | /// An unwind operation corresponding to code in the function prolog. |
311 | | /// |
312 | | /// Unwind operations can be used to reverse the effects of the function prolog and restore register |
313 | | /// values of parent stack frames that have been saved to the stack. |
314 | | #[derive(Clone, Copy, Debug, Eq, PartialEq)] |
315 | | #[non_exhaustive] |
316 | | pub enum UnwindOperation { |
317 | | /// Push a nonvolatile integer register, decrementing `RSP` by 8. |
318 | | PushNonVolatile(Register), |
319 | | |
320 | | /// Allocate a fixed-size area on the stack. |
321 | | Alloc(u32), |
322 | | |
323 | | /// Establish the frame pointer register by setting the register to some offset of the current |
324 | | /// RSP. The use of an offset permits establishing a frame pointer that points to the middle of |
325 | | /// the fixed stack allocation, helping code density by allowing more accesses to use short |
326 | | /// instruction forms. |
327 | | SetFPRegister, |
328 | | |
329 | | /// Save a nonvolatile integer register on the stack using a MOV instead of a PUSH. This code is |
330 | | /// primarily used for shrink-wrapping, where a nonvolatile register is saved to the stack in a |
331 | | /// position that was previously allocated. |
332 | | SaveNonVolatile(Register, StackFrameOffset), |
333 | | |
334 | | /// Save the lower 64 bits of a nonvolatile XMM register on the stack. |
335 | | SaveXMM(Register, StackFrameOffset), |
336 | | |
337 | | /// Describes the function epilog location and size (Version 2). |
338 | | /// |
339 | | /// [UWOP_EPILOG] entries work together to describe where epilogues are located in the function, |
340 | | /// but not what operations they perform. The actual operations performed during epilogue |
341 | | /// execution are the reverse of the prolog operations. |
342 | | /// |
343 | | /// These entries always appear at the beginning of the [UnwindCode] array, with a minimum of |
344 | | /// two entries required for alignment purposes, even when only one epilogue exists. The first |
345 | | /// [UWOP_EPILOG] entry is special as it contains the size of the epilogue in its `offset_low_or_size` |
346 | | /// field. If bit `0` of its `offset_high_or_flags` field is set, this first entry also describes |
347 | | /// an epilogue located at the function's end, with `offset_low_or_size` serving dual purpose as |
348 | | /// both size and offset. When bit `0` is not set, the first entry contains only the size, and |
349 | | /// subsequent [UWOP_EPILOG] entries provide the epilogue locations. |
350 | | /// |
351 | | /// Each subsequent [UWOP_EPILOG] entry describes an additional epilogue location using a 12-bit |
352 | | /// offset from the function's end address. The offset is formed by combining `offset_low_or_size` |
353 | | /// (lower 8 bits) with the lower 4 bits of `offset_high_or_flags` (upper 4 bits). The epilogue's |
354 | | /// starting address is calculated by subtracting this offset from the function's `EndAddress`. |
355 | | Epilog { |
356 | | /// For the first [UWOP_EPILOG] entry: |
357 | | /// * Size of the epilogue in bytes |
358 | | /// * If `offset_high_or_flags` bit 0 is set, also serves as offset |
359 | | /// |
360 | | /// For subsequent entries: |
361 | | /// * Lower 8 bits of the offset from function end |
362 | | offset_low_or_size: u8, |
363 | | |
364 | | /// For the first [UWOP_EPILOG] entry: |
365 | | /// * Bit 0: If set, epilogue is at function end and `offset_low_or_size` |
366 | | /// is also the offset |
367 | | /// * Bits 1-3: Reserved/unused |
368 | | /// |
369 | | /// For subsequent entries: |
370 | | /// * Upper 4 bits of the offset from function end (bits 0-3) |
371 | | /// |
372 | | /// The complete offset is computed as: |
373 | | /// `EndAddress` - (`offset_high_or_flags` << 8 | `offset_low_or_size`) |
374 | | offset_high_or_flags: u8, |
375 | | }, |
376 | | |
377 | | /// Save all 128 bits of a nonvolatile XMM register on the stack. |
378 | | SaveXMM128(Register, StackFrameOffset), |
379 | | |
380 | | /// Push a machine frame. This is used to record the effect of a hardware interrupt or |
381 | | /// exception. Depending on the error flag, this frame has two different layouts. |
382 | | /// |
383 | | /// This unwind code always appears in a dummy prolog, which is never actually executed but |
384 | | /// instead appears before the real entry point of an interrupt routine, and exists only to |
385 | | /// provide a place to simulate the push of a machine frame. This operation records that |
386 | | /// simulation, which indicates the machine has conceptually done this: |
387 | | /// |
388 | | /// 1. Pop RIP return address from top of stack into `temp` |
389 | | /// 2. `$ss`, Push old `$rsp`, `$rflags`, `$cs`, `temp` |
390 | | /// 3. If error flag is `true`, push the error code |
391 | | /// |
392 | | /// Without an error code, RSP was incremented by `40` and the following was frame pushed: |
393 | | /// |
394 | | /// Offset | Value |
395 | | /// ---------|-------- |
396 | | /// RSP + 32 | `$ss` |
397 | | /// RSP + 24 | old `$rsp` |
398 | | /// RSP + 16 | `$rflags` |
399 | | /// RSP + 8 | `$cs` |
400 | | /// RSP + 0 | `$rip` |
401 | | /// |
402 | | /// With an error code, RSP was incremented by `48` and the following was frame pushed: |
403 | | /// |
404 | | /// Offset | Value |
405 | | /// ---------|-------- |
406 | | /// RSP + 40 | `$ss` |
407 | | /// RSP + 32 | old `$rsp` |
408 | | /// RSP + 24 | `$rflags` |
409 | | /// RSP + 16 | `$cs` |
410 | | /// RSP + 8 | `$rip` |
411 | | /// RSP + 0 | error code |
412 | | PushMachineFrame(bool), |
413 | | |
414 | | /// A reserved operation without effect. |
415 | | Noop, |
416 | | } |
417 | | |
418 | | /// Context used to parse unwind operation. |
419 | | #[derive(Clone, Copy, Debug, PartialEq)] |
420 | | struct UnwindOpContext { |
421 | | /// Version of the unwind info. |
422 | | version: u8, |
423 | | |
424 | | /// The nonvolatile register used as the frame pointer of this function. |
425 | | /// |
426 | | /// If this register is non-zero, all stack frame offsets used in unwind operations are of type |
427 | | /// `StackFrameOffset::FP`. When loading these offsets, they have to be based off the value of |
428 | | /// this frame register instead of the conventional RSP. This allows the RSP to be modified. |
429 | | frame_register: Register, |
430 | | } |
431 | | |
432 | | /// An unwind operation that is executed at a particular place in the function prolog. |
433 | | #[derive(Clone, Copy, Debug, Eq, PartialEq)] |
434 | | pub struct UnwindCode { |
435 | | /// Offset of the corresponding instruction in the function prolog. |
436 | | /// |
437 | | /// To be precise, this is the offset from the beginning of the prolog of the end of the |
438 | | /// instruction that performs this operation, plus 1 (that is, the offset of the start of the |
439 | | /// next instruction). |
440 | | /// |
441 | | /// Unwind codes are ordered by this offset in reverse order, suitable for unwinding. |
442 | | pub code_offset: u8, |
443 | | |
444 | | /// The operation that was performed by the code in the prolog. |
445 | | pub operation: UnwindOperation, |
446 | | } |
447 | | |
448 | | impl<'a> TryFromCtx<'a, UnwindOpContext> for UnwindCode { |
449 | | type Error = error::Error; |
450 | | #[inline] |
451 | 0 | fn try_from_ctx(bytes: &'a [u8], ctx: UnwindOpContext) -> Result<(Self, usize), Self::Error> { |
452 | 0 | let mut read = 0; |
453 | 0 | let code_offset = bytes.gread_with::<u8>(&mut read, scroll::LE)?; |
454 | 0 | let operation = bytes.gread_with::<u8>(&mut read, scroll::LE)?; |
455 | | |
456 | 0 | let operation_code = operation & 0xf; |
457 | 0 | let operation_info = operation >> 4; |
458 | | |
459 | 0 | let operation = match operation_code { |
460 | | self::UWOP_PUSH_NONVOL => { |
461 | 0 | let register = Register(operation_info); |
462 | 0 | UnwindOperation::PushNonVolatile(register) |
463 | | } |
464 | | self::UWOP_ALLOC_LARGE => { |
465 | 0 | let offset = match operation_info { |
466 | 0 | 0 => u32::from(bytes.gread_with::<u16>(&mut read, scroll::LE)?) * 8, |
467 | 0 | 1 => bytes.gread_with::<u32>(&mut read, scroll::LE)?, |
468 | 0 | i => { |
469 | 0 | let msg = format!("invalid op info ({}) for UWOP_ALLOC_LARGE", i); |
470 | 0 | return Err(error::Error::Malformed(msg)); |
471 | | } |
472 | | }; |
473 | 0 | UnwindOperation::Alloc(offset) |
474 | | } |
475 | | self::UWOP_ALLOC_SMALL => { |
476 | 0 | let offset = u32::from(operation_info) * 8 + 8; |
477 | 0 | UnwindOperation::Alloc(offset) |
478 | | } |
479 | 0 | self::UWOP_SET_FPREG => UnwindOperation::SetFPRegister, |
480 | | self::UWOP_SAVE_NONVOL => { |
481 | 0 | let register = Register(operation_info); |
482 | 0 | let offset = u32::from(bytes.gread_with::<u16>(&mut read, scroll::LE)?) * 8; |
483 | 0 | UnwindOperation::SaveNonVolatile(register, StackFrameOffset::with_ctx(offset, ctx)) |
484 | | } |
485 | | self::UWOP_SAVE_NONVOL_FAR => { |
486 | 0 | let register = Register(operation_info); |
487 | 0 | let offset = bytes.gread_with::<u32>(&mut read, scroll::LE)?; |
488 | 0 | UnwindOperation::SaveNonVolatile(register, StackFrameOffset::with_ctx(offset, ctx)) |
489 | | } |
490 | | self::UWOP_EPILOG => { |
491 | 0 | if ctx.version == 1 { |
492 | | // Version 1: This was UWOP_SAVE_XMM |
493 | 0 | let data = u32::from(bytes.gread_with::<u16>(&mut read, scroll::LE)?) * 16; |
494 | 0 | let register = Register::xmm(operation_info); |
495 | 0 | UnwindOperation::SaveXMM(register, StackFrameOffset::with_ctx(data, ctx)) |
496 | 0 | } else if ctx.version == 2 { |
497 | | // Version 2: UWOP_EPILOG - describes epilogue locations |
498 | | // See https://github.com/BlancLoup/weekly-geekly.github.io/blob/1cbdf1c6127fcdaeda1f01bcbb006febd17d5a95/articles/322956/index.html |
499 | | // See https://github.com/Montura/cpp/blob/4393c678ee8e44dd98feb7a198c3983a6108cef8/src/exception_handling/msvc/eh_msvc_cxx_EH_x64.md?plain=1#L119 |
500 | 0 | UnwindOperation::Epilog { |
501 | 0 | offset_low_or_size: code_offset, |
502 | 0 | offset_high_or_flags: operation_info, |
503 | 0 | } |
504 | | } else { |
505 | 0 | let msg = format!( |
506 | 0 | "Unwind info version has to be either one of `1` or `2`: {}", |
507 | | ctx.version |
508 | | ); |
509 | 0 | return Err(error::Error::Malformed(msg)); |
510 | | } |
511 | | } |
512 | | self::UWOP_SPARE_CODE => { |
513 | 0 | let data = bytes.gread_with::<u32>(&mut read, scroll::LE)?; |
514 | 0 | if ctx.version == 1 { |
515 | 0 | let register = Register::xmm(operation_info); |
516 | 0 | UnwindOperation::SaveXMM128(register, StackFrameOffset::with_ctx(data, ctx)) |
517 | 0 | } else if ctx.version == 2 { |
518 | 0 | UnwindOperation::Noop |
519 | | } else { |
520 | 0 | let msg = format!( |
521 | 0 | "Unwind info version has to be either one of `1` or `2`: {}", |
522 | | ctx.version |
523 | | ); |
524 | 0 | return Err(error::Error::Malformed(msg)); |
525 | | } |
526 | | } |
527 | | self::UWOP_SAVE_XMM128 => { |
528 | 0 | let register = Register::xmm(operation_info); |
529 | 0 | let offset = u32::from(bytes.gread_with::<u16>(&mut read, scroll::LE)?) * 16; |
530 | 0 | UnwindOperation::SaveXMM128(register, StackFrameOffset::with_ctx(offset, ctx)) |
531 | | } |
532 | | self::UWOP_SAVE_XMM128_FAR => { |
533 | 0 | let register = Register::xmm(operation_info); |
534 | 0 | let offset = bytes.gread_with::<u32>(&mut read, scroll::LE)?; |
535 | 0 | UnwindOperation::SaveXMM128(register, StackFrameOffset::with_ctx(offset, ctx)) |
536 | | } |
537 | | self::UWOP_PUSH_MACHFRAME => { |
538 | 0 | let is_error = match operation_info { |
539 | 0 | 0 => false, |
540 | 0 | 1 => true, |
541 | 0 | i => { |
542 | 0 | let msg = format!("invalid op info ({}) for UWOP_PUSH_MACHFRAME", i); |
543 | 0 | return Err(error::Error::Malformed(msg)); |
544 | | } |
545 | | }; |
546 | 0 | UnwindOperation::PushMachineFrame(is_error) |
547 | | } |
548 | 0 | op => { |
549 | 0 | let msg = format!("unknown unwind op code ({})", op); |
550 | 0 | return Err(error::Error::Malformed(msg)); |
551 | | } |
552 | | }; |
553 | | |
554 | 0 | let code = UnwindCode { |
555 | 0 | code_offset, |
556 | 0 | operation, |
557 | 0 | }; |
558 | | |
559 | 0 | Ok((code, read)) |
560 | 0 | } |
561 | | } |
562 | | |
563 | | /// An iterator over unwind codes for a function or part of a function, returned from |
564 | | /// [`UnwindInfo`]. |
565 | | /// |
566 | | /// [`UnwindInfo`]: struct.UnwindInfo.html |
567 | | #[derive(Clone, Debug)] |
568 | | pub struct UnwindCodeIterator<'a> { |
569 | | bytes: &'a [u8], |
570 | | offset: usize, |
571 | | context: UnwindOpContext, |
572 | | } |
573 | | |
574 | | impl Iterator for UnwindCodeIterator<'_> { |
575 | | type Item = error::Result<UnwindCode>; |
576 | | |
577 | 0 | fn next(&mut self) -> Option<Self::Item> { |
578 | 0 | if self.offset >= self.bytes.len() { |
579 | 0 | return None; |
580 | 0 | } |
581 | | |
582 | 0 | Some(self.bytes.gread_with(&mut self.offset, self.context)) |
583 | 0 | } |
584 | | |
585 | 0 | fn size_hint(&self) -> (usize, Option<usize>) { |
586 | 0 | let upper = (self.bytes.len() - self.offset) / UNWIND_CODE_SIZE; |
587 | | // the largest codes take up three slots |
588 | 0 | let lower = (upper + 3 - (upper % 3)) / 3; |
589 | 0 | (lower, Some(upper)) |
590 | 0 | } |
591 | | } |
592 | | |
593 | | impl FusedIterator for UnwindCodeIterator<'_> {} |
594 | | |
595 | | /// A language-specific handler that is called as part of the search for an exception handler or as |
596 | | /// part of an unwind. |
597 | | #[derive(Copy, Clone, Debug, PartialEq)] |
598 | | pub enum UnwindHandler<'a> { |
599 | | /// The image-relative address of an exception handler and its implementation-defined data. |
600 | | ExceptionHandler(u32, &'a [u8]), |
601 | | /// The image-relative address of a termination handler and its implementation-defined data. |
602 | | TerminationHandler(u32, &'a [u8]), |
603 | | } |
604 | | |
605 | | /// Unwind information for a function or portion of a function. |
606 | | /// |
607 | | /// The unwind info structure is used to record the effects a function has on the stack pointer and |
608 | | /// where the nonvolatile registers are saved on the stack. The unwind codes can be enumerated with |
609 | | /// [`unwind_codes`]. |
610 | | /// |
611 | | /// This unwind info might only be secondary information, and link to a [chained unwind handler]. |
612 | | /// For unwinding, this link shall be followed until the root unwind info record has been resolved. |
613 | | /// |
614 | | /// [`unwind_codes`]: struct.UnwindInfo.html#method.unwind_codes |
615 | | /// [chained unwind handler]: struct.UnwindInfo.html#structfield.chained_info |
616 | | #[derive(Clone)] |
617 | | pub struct UnwindInfo<'a> { |
618 | | /// Version of this unwind info. |
619 | | pub version: u8, |
620 | | |
621 | | /// Length of the function prolog in bytes. |
622 | | pub size_of_prolog: u8, |
623 | | |
624 | | /// The nonvolatile register used as the frame pointer of this function. |
625 | | /// |
626 | | /// If this register is non-zero, all stack frame offsets used in unwind operations are of type |
627 | | /// `StackFrameOffset::FP`. When loading these offsets, they have to be based off the value of |
628 | | /// this frame register instead of the conventional RSP. This allows the RSP to be modified. |
629 | | pub frame_register: Register, |
630 | | |
631 | | /// Offset from RSP that is applied to the FP register when it is established. |
632 | | /// |
633 | | /// When loading offsets of type `StackFrameOffset::FP` from the stack, this offset has to be |
634 | | /// subtracted before loading the value since the actual RSP was lower by that amount in the |
635 | | /// prolog. |
636 | | pub frame_register_offset: u32, |
637 | | |
638 | | /// A record pointing to chained unwind information. |
639 | | /// |
640 | | /// If chained unwind info is present, then this unwind info is a secondary one and the linked |
641 | | /// unwind info contains primary information. Chained info is useful in two situations. First, |
642 | | /// it is used for noncontiguous code segments. Second, this mechanism is sometimes used to |
643 | | /// group volatile register saves. |
644 | | /// |
645 | | /// The referenced unwind info can itself specify chained unwind information, until it arrives |
646 | | /// at the root unwind info. Generally, the entire chain should be considered when unwinding. |
647 | | pub chained_info: Option<RuntimeFunction>, |
648 | | |
649 | | /// An exception or termination handler called as part of the unwind. |
650 | | pub handler: Option<UnwindHandler<'a>>, |
651 | | |
652 | | /// A list of unwind codes, sorted descending by code offset. |
653 | | code_bytes: &'a [u8], |
654 | | } |
655 | | |
656 | | impl<'a> UnwindInfo<'a> { |
657 | | /// Parses unwind information from the image at the given offset. |
658 | 0 | pub fn parse(bytes: &'a [u8], mut offset: usize) -> error::Result<Self> { |
659 | | // Read the version and flags fields, which are combined into a single byte. |
660 | 0 | let version_flags: u8 = bytes.gread_with(&mut offset, scroll::LE)?; |
661 | 0 | let version = version_flags & 0b111; |
662 | 0 | let flags = version_flags >> 3; |
663 | | |
664 | 0 | if version < 1 || version > 2 { |
665 | 0 | let msg = format!("unsupported unwind code version ({})", version); |
666 | 0 | return Err(error::Error::Malformed(msg)); |
667 | 0 | } |
668 | | |
669 | 0 | let size_of_prolog = bytes.gread_with::<u8>(&mut offset, scroll::LE)?; |
670 | 0 | let count_of_codes = bytes.gread_with::<u8>(&mut offset, scroll::LE)?; |
671 | | |
672 | | // Parse the frame register and frame register offset values, that are combined into a |
673 | | // single byte. |
674 | 0 | let frame_info = bytes.gread_with::<u8>(&mut offset, scroll::LE)?; |
675 | | // If nonzero, then the function uses a frame pointer (FP), and this field is the number |
676 | | // of the nonvolatile register used as the frame pointer. The zero register value does |
677 | | // not need special casing since it will not be referenced by the unwind operations. |
678 | 0 | let frame_register = Register(frame_info & 0xf); |
679 | | // The the scaled offset from RSP that is applied to the FP register when it's |
680 | | // established. The actual FP register is set to RSP + 16 * this number, allowing |
681 | | // offsets from 0 to 240. |
682 | 0 | let frame_register_offset = u32::from((frame_info >> 4) * 16); |
683 | | |
684 | | // An array of items that explains the effect of the prolog on the nonvolatile registers and |
685 | | // RSP. Some unwind codes require more than one slot in the array. |
686 | 0 | let codes_size = count_of_codes as usize * UNWIND_CODE_SIZE; |
687 | 0 | let code_bytes = bytes.gread_with(&mut offset, codes_size)?; |
688 | | |
689 | | // For alignment purposes, the codes array typically has an even number of entries, and the |
690 | | // final entry is potentially unused. In that case, the array is one longer than indicated |
691 | | // by the count of unwind codes field. |
692 | | // |
693 | | // Sometimes, developers decided to handy craft unwind info in their assembly forget to align |
694 | | // unwind infos. This is not really desirable behavior for such cases anyway. |
695 | 0 | if count_of_codes % 2 != 0 { |
696 | 0 | offset += 2; |
697 | 0 | } |
698 | | |
699 | 0 | let mut chained_info = None; |
700 | 0 | let mut handler = None; |
701 | | |
702 | | // If flag UNW_FLAG_CHAININFO is set then the UNWIND_INFO structure ends with three UWORDs. |
703 | | // These UWORDs represent the RUNTIME_FUNCTION information for the function of the chained |
704 | | // unwind. |
705 | 0 | if flags & UNW_FLAG_CHAININFO != 0 { |
706 | 0 | chained_info = Some(bytes.gread_with(&mut offset, scroll::LE)?); |
707 | | |
708 | | // The relative address of the language-specific handler is present in the UNWIND_INFO |
709 | | // whenever flags UNW_FLAG_EHANDLER or UNW_FLAG_UHANDLER are set. The language-specific |
710 | | // handler is called as part of the search for an exception handler or as part of an unwind. |
711 | 0 | } else if flags & (UNW_FLAG_EHANDLER | UNW_FLAG_UHANDLER) != 0 { |
712 | 0 | let address = bytes.gread_with::<u32>(&mut offset, scroll::LE)?; |
713 | 0 | if offset > bytes.len() { |
714 | 0 | return Err(error::Error::Malformed(format!( |
715 | 0 | "Offset {offset:#x} is too big to contain unwind handlers", |
716 | 0 | ))); |
717 | 0 | } |
718 | 0 | let data = &bytes[offset..]; |
719 | | |
720 | 0 | handler = Some(if flags & UNW_FLAG_EHANDLER != 0 { |
721 | 0 | UnwindHandler::ExceptionHandler(address, data) |
722 | | } else { |
723 | 0 | UnwindHandler::TerminationHandler(address, data) |
724 | | }); |
725 | 0 | } |
726 | | |
727 | 0 | Ok(UnwindInfo { |
728 | 0 | version, |
729 | 0 | size_of_prolog, |
730 | 0 | frame_register, |
731 | 0 | frame_register_offset, |
732 | 0 | chained_info, |
733 | 0 | handler, |
734 | 0 | code_bytes, |
735 | 0 | }) |
736 | 0 | } |
737 | | |
738 | | /// Returns an iterator over unwind codes in this unwind info. |
739 | | /// |
740 | | /// Unwind codes are iterated in descending `code_offset` order suitable for unwinding. If the |
741 | | /// optional [`chained_info`](Self::chained_info) is present, codes of that unwind info should be interpreted |
742 | | /// immediately afterwards. |
743 | 0 | pub fn unwind_codes(&self) -> UnwindCodeIterator<'a> { |
744 | 0 | UnwindCodeIterator { |
745 | 0 | bytes: self.code_bytes, |
746 | 0 | offset: 0, |
747 | 0 | context: UnwindOpContext { |
748 | 0 | version: self.version, |
749 | 0 | frame_register: self.frame_register, |
750 | 0 | }, |
751 | 0 | } |
752 | 0 | } |
753 | | |
754 | | /// Returns an iterator over C scope table entries in this unwind info. |
755 | | /// |
756 | | /// If this unwind info has no [UnwindHandler::ExceptionHandler], this will always return `None`. |
757 | 0 | pub fn c_scope_table_entries(&self) -> Option<ScopeTableIterator<'a>> { |
758 | 0 | let UnwindHandler::ExceptionHandler(_, data) = self.handler? else { |
759 | 0 | return None; |
760 | | }; |
761 | 0 | let mut offset = 0; |
762 | 0 | let num_entries = data.gread_with::<u32>(&mut offset, scroll::LE).ok()?; |
763 | 0 | let table_size = num_entries * core::mem::size_of::<ScopeTableEntry>() as u32; |
764 | 0 | let data = data.pread_with::<&[u8]>(offset, table_size as usize).ok()?; |
765 | 0 | Some(ScopeTableIterator { data, offset: 0 }) |
766 | 0 | } |
767 | | } |
768 | | |
769 | | impl fmt::Debug for UnwindInfo<'_> { |
770 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
771 | 0 | let count_of_codes = self.code_bytes.len() / UNWIND_CODE_SIZE; |
772 | | |
773 | 0 | f.debug_struct("UnwindInfo") |
774 | 0 | .field("version", &self.version) |
775 | 0 | .field("size_of_prolog", &self.size_of_prolog) |
776 | 0 | .field("frame_register", &self.frame_register) |
777 | 0 | .field("frame_register_offset", &self.frame_register_offset) |
778 | 0 | .field("count_of_codes", &count_of_codes) |
779 | 0 | .field("chained_info", &self.chained_info) |
780 | 0 | .field("handler", &self.handler) |
781 | 0 | .finish() |
782 | 0 | } |
783 | | } |
784 | | |
785 | | impl<'a> IntoIterator for &'_ UnwindInfo<'a> { |
786 | | type Item = error::Result<UnwindCode>; |
787 | | type IntoIter = UnwindCodeIterator<'a>; |
788 | | |
789 | | #[inline] |
790 | 0 | fn into_iter(self) -> Self::IntoIter { |
791 | 0 | self.unwind_codes() |
792 | 0 | } |
793 | | } |
794 | | |
795 | | /// Exception handling and stack unwind information for functions in the image. |
796 | | pub struct ExceptionData<'a> { |
797 | | bytes: &'a [u8], |
798 | | offset: usize, |
799 | | size: usize, |
800 | | file_alignment: u32, |
801 | | } |
802 | | |
803 | | impl<'a> ExceptionData<'a> { |
804 | | /// Parses exception data from the image at the given offset. |
805 | 0 | pub fn parse( |
806 | 0 | bytes: &'a [u8], |
807 | 0 | directory: data_directories::DataDirectory, |
808 | 0 | sections: &[section_table::SectionTable], |
809 | 0 | file_alignment: u32, |
810 | 0 | ) -> error::Result<Self> { |
811 | 0 | Self::parse_with_opts( |
812 | 0 | bytes, |
813 | 0 | directory, |
814 | 0 | sections, |
815 | 0 | file_alignment, |
816 | 0 | &options::ParseOptions::default(), |
817 | | ) |
818 | 0 | } |
819 | | |
820 | | /// Parses exception data from the image at the given offset. |
821 | 295 | pub fn parse_with_opts( |
822 | 295 | bytes: &'a [u8], |
823 | 295 | directory: data_directories::DataDirectory, |
824 | 295 | sections: &[section_table::SectionTable], |
825 | 295 | file_alignment: u32, |
826 | 295 | opts: &options::ParseOptions, |
827 | 295 | ) -> error::Result<Self> { |
828 | 295 | let size = directory.size as usize; |
829 | | |
830 | 295 | if size % RUNTIME_FUNCTION_SIZE != 0 { |
831 | 74 | return Err(error::Error::from(scroll::Error::BadInput { |
832 | 74 | size, |
833 | 74 | msg: "invalid exception directory table size", |
834 | 74 | })); |
835 | 221 | } |
836 | | |
837 | 221 | Self::parse_inner(bytes, directory, sections, file_alignment, opts) |
838 | 295 | } |
839 | | |
840 | | /// Parses ARM64 exception data from the image at the given offset. |
841 | 189 | pub fn parse_arm64_with_opts( |
842 | 189 | bytes: &'a [u8], |
843 | 189 | directory: data_directories::DataDirectory, |
844 | 189 | sections: &[section_table::SectionTable], |
845 | 189 | file_alignment: u32, |
846 | 189 | opts: &options::ParseOptions, |
847 | 189 | ) -> error::Result<Self> { |
848 | 189 | let size = directory.size as usize; |
849 | | |
850 | 189 | if size % size_of::<Arm64RuntimeFunction>() != 0 { |
851 | 162 | return Err(error::Error::from(scroll::Error::BadInput { |
852 | 162 | size, |
853 | 162 | msg: "invalid exception directory table size", |
854 | 162 | })); |
855 | 27 | } |
856 | | |
857 | 27 | Self::parse_inner(bytes, directory, sections, file_alignment, opts) |
858 | 189 | } |
859 | | |
860 | 248 | fn parse_inner( |
861 | 248 | bytes: &'a [u8], |
862 | 248 | directory: data_directories::DataDirectory, |
863 | 248 | sections: &[section_table::SectionTable], |
864 | 248 | file_alignment: u32, |
865 | 248 | opts: &options::ParseOptions, |
866 | 248 | ) -> error::Result<Self> { |
867 | 248 | let size = directory.size as usize; |
868 | 248 | let rva = directory.virtual_address as usize; |
869 | 248 | let offset = utils::find_offset(rva, sections, file_alignment, opts).ok_or_else(|| { |
870 | 30 | error::Error::Malformed(format!("cannot map exception_rva ({:#x}) into offset", rva)) |
871 | 30 | })?; |
872 | | |
873 | 218 | if offset % 4 != 0 { |
874 | 38 | return Err(error::Error::from(scroll::Error::BadOffset(offset))); |
875 | 180 | } |
876 | | |
877 | 180 | Ok(ExceptionData { |
878 | 180 | bytes, |
879 | 180 | offset, |
880 | 180 | size, |
881 | 180 | file_alignment, |
882 | 180 | }) |
883 | 248 | } |
884 | | |
885 | | /// The number of x64 function entries described by this exception data. |
886 | 176 | pub fn len(&self) -> usize { |
887 | 176 | self.size / RUNTIME_FUNCTION_SIZE |
888 | 176 | } |
889 | | |
890 | | /// The number of ARM64 function entries described by this exception data. |
891 | 0 | pub fn len_arm64(&self) -> usize { |
892 | 0 | self.size / size_of::<Arm64RuntimeFunction>() |
893 | 0 | } |
894 | | |
895 | | /// Indicating whether there are functions in this entry. |
896 | 176 | pub fn is_empty(&self) -> bool { |
897 | 176 | self.len() == 0 |
898 | 176 | } |
899 | | |
900 | | /// Iterates all function entries in order of their code offset. |
901 | | /// |
902 | | /// To search for a function by relative instruction address, use [`find_function`]. To resolve |
903 | | /// unwind information, use [`get_unwind_info`]. |
904 | | /// |
905 | | /// [`find_function`]: struct.ExceptionData.html#method.find_function |
906 | | /// [`get_unwind_info`]: struct.ExceptionData.html#method.get_unwind_info |
907 | 0 | pub fn functions(&self) -> RuntimeFunctionIterator<'a> { |
908 | 0 | RuntimeFunctionIterator { |
909 | 0 | data: &self.bytes[self.offset..self.offset + self.size], |
910 | 0 | } |
911 | 0 | } |
912 | | |
913 | | /// Returns the function at the given index. |
914 | 0 | pub fn get_function(&self, index: usize) -> error::Result<RuntimeFunction> { |
915 | 0 | self.get_function_by_offset(self.offset + index * RUNTIME_FUNCTION_SIZE) |
916 | 0 | } |
917 | | |
918 | | /// Returns an iterator over ARM64 runtime function entries. |
919 | 0 | pub fn functions_arm64(&self) -> Arm64RuntimeFunctionIterator<'a> { |
920 | 0 | Arm64RuntimeFunctionIterator { |
921 | 0 | data: &self.bytes[self.offset..self.offset + self.size], |
922 | 0 | } |
923 | 0 | } |
924 | | |
925 | | /// Returns an ARM64 runtime function at the given index. |
926 | 0 | pub fn get_function_arm64(&self, index: usize) -> error::Result<Arm64RuntimeFunction> { |
927 | 0 | self.get_function_by_offset_arm64(self.offset + index * size_of::<Arm64RuntimeFunction>()) |
928 | 0 | } |
929 | | |
930 | | /// Performs a binary search to find a function entry covering the given RVA relative to the |
931 | | /// image. |
932 | 0 | pub fn find_function(&self, rva: u32) -> error::Result<Option<RuntimeFunction>> { |
933 | | // NB: Binary search implementation copied from std::slice::binary_search_by and adapted. |
934 | | // Theoretically, there should be nothing that causes parsing runtime functions to fail and |
935 | | // all access to the bytes buffer is guaranteed to be in range. However, since all other |
936 | | // functions also return Results, this is much more ergonomic here. |
937 | | |
938 | 0 | let mut size = self.len(); |
939 | 0 | if size == 0 { |
940 | 0 | return Ok(None); |
941 | 0 | } |
942 | | |
943 | 0 | let mut base = 0; |
944 | 0 | while size > 1 { |
945 | 0 | let half = size / 2; |
946 | 0 | let mid = base + half; |
947 | 0 | let offset = self.offset + mid * RUNTIME_FUNCTION_SIZE; |
948 | 0 | let addr = self.bytes.pread_with::<u32>(offset, scroll::LE)?; |
949 | 0 | base = if addr > rva { base } else { mid }; |
950 | 0 | size -= half; |
951 | | } |
952 | | |
953 | 0 | let offset = self.offset + base * RUNTIME_FUNCTION_SIZE; |
954 | 0 | let addr = self.bytes.pread_with::<u32>(offset, scroll::LE)?; |
955 | 0 | let function = match addr.cmp(&rva) { |
956 | 0 | Ordering::Less | Ordering::Equal => self.get_function(base)?, |
957 | 0 | Ordering::Greater if base == 0 => return Ok(None), |
958 | 0 | Ordering::Greater => self.get_function(base - 1)?, |
959 | | }; |
960 | | |
961 | 0 | if function.end_address > rva { |
962 | 0 | Ok(Some(function)) |
963 | | } else { |
964 | 0 | Ok(None) |
965 | | } |
966 | 0 | } |
967 | | |
968 | | /// Resolves unwind information for the given function entry. |
969 | 0 | pub fn get_unwind_info( |
970 | 0 | &self, |
971 | 0 | function: RuntimeFunction, |
972 | 0 | sections: &[section_table::SectionTable], |
973 | 0 | ) -> error::Result<UnwindInfo<'a>> { |
974 | 0 | self.get_unwind_info_with_opts(function, sections, &options::ParseOptions::default()) |
975 | 0 | } |
976 | | |
977 | | /// Resolves unwind information for the given ARM64 function entry. |
978 | 0 | pub fn get_unwind_info_arm64( |
979 | 0 | &self, |
980 | 0 | function: Arm64RuntimeFunction, |
981 | 0 | sections: &[section_table::SectionTable], |
982 | 0 | ) -> Option<error::Result<Arm64UnwindInfo<'a>>> { |
983 | 0 | self.get_unwind_info_arm64_with_opts(function, sections, &options::ParseOptions::default()) |
984 | 0 | } |
985 | | |
986 | | /// Resolves unwind information for the given function entry. |
987 | 0 | pub fn get_unwind_info_with_opts( |
988 | 0 | &self, |
989 | 0 | mut function: RuntimeFunction, |
990 | 0 | sections: &[section_table::SectionTable], |
991 | 0 | opts: &options::ParseOptions, |
992 | 0 | ) -> error::Result<UnwindInfo<'a>> { |
993 | 0 | while function.unwind_info_address % 2 != 0 { |
994 | 0 | let rva = (function.unwind_info_address & !1) as usize; |
995 | 0 | function = self.get_function_by_rva_with_opts(rva, sections, opts)?; |
996 | | } |
997 | | |
998 | 0 | let rva = function.unwind_info_address as usize; |
999 | 0 | let offset = |
1000 | 0 | utils::find_offset(rva, sections, self.file_alignment, opts).ok_or_else(|| { |
1001 | 0 | error::Error::Malformed(format!("cannot map unwind rva ({:#x}) into offset", rva)) |
1002 | 0 | })?; |
1003 | | |
1004 | 0 | UnwindInfo::parse(self.bytes, offset) |
1005 | 0 | } |
1006 | | |
1007 | | /// Resolves unwind information for the given ARM64 function entry. |
1008 | 0 | pub fn get_unwind_info_arm64_with_opts( |
1009 | 0 | &self, |
1010 | 0 | function: Arm64RuntimeFunction, |
1011 | 0 | sections: &[section_table::SectionTable], |
1012 | 0 | opts: &options::ParseOptions, |
1013 | 0 | ) -> Option<error::Result<Arm64UnwindInfo<'a>>> { |
1014 | 0 | if function.flag() == ARM64_PDATA_REF_TO_FULL_XDATA { |
1015 | 0 | let rva = function.unwind_data_rva() as usize; |
1016 | 0 | let offset = match utils::find_offset(rva, sections, self.file_alignment, opts) |
1017 | 0 | .ok_or_else(|| { |
1018 | 0 | error::Error::Malformed(format!("cannot map unwind rva ({rva:#x}) into offset")) |
1019 | 0 | }) { |
1020 | 0 | Ok(v) => v, |
1021 | 0 | Err(err) => return Some(Err(err)), |
1022 | | }; |
1023 | 0 | Some(Arm64UnwindInfo::parse(self.bytes, offset)) |
1024 | | } else { |
1025 | | // Unwind info is packed into the runtime function entry. |
1026 | 0 | None |
1027 | | } |
1028 | 0 | } |
1029 | | |
1030 | | #[allow(dead_code)] |
1031 | 0 | fn get_function_by_rva( |
1032 | 0 | &self, |
1033 | 0 | rva: usize, |
1034 | 0 | sections: &[section_table::SectionTable], |
1035 | 0 | ) -> error::Result<RuntimeFunction> { |
1036 | 0 | self.get_function_by_rva_with_opts(rva, sections, &options::ParseOptions::default()) |
1037 | 0 | } |
1038 | | |
1039 | 0 | fn get_function_by_rva_with_opts( |
1040 | 0 | &self, |
1041 | 0 | rva: usize, |
1042 | 0 | sections: &[section_table::SectionTable], |
1043 | 0 | opts: &options::ParseOptions, |
1044 | 0 | ) -> error::Result<RuntimeFunction> { |
1045 | 0 | let offset = |
1046 | 0 | utils::find_offset(rva, sections, self.file_alignment, opts).ok_or_else(|| { |
1047 | 0 | error::Error::Malformed(format!( |
1048 | 0 | "cannot map exception rva ({:#x}) into offset", |
1049 | 0 | rva |
1050 | 0 | )) |
1051 | 0 | })?; |
1052 | | |
1053 | 0 | self.get_function_by_offset(offset) |
1054 | 0 | } |
1055 | | |
1056 | | #[inline] |
1057 | 0 | fn get_function_by_offset(&self, offset: usize) -> error::Result<RuntimeFunction> { |
1058 | 0 | debug_assert!((offset - self.offset) % RUNTIME_FUNCTION_SIZE == 0); |
1059 | 0 | debug_assert!(offset < self.offset + self.size); |
1060 | | |
1061 | 0 | Ok(self.bytes.pread_with(offset, scroll::LE)?) |
1062 | 0 | } |
1063 | | |
1064 | | #[inline] |
1065 | 0 | fn get_function_by_offset_arm64(&self, offset: usize) -> error::Result<Arm64RuntimeFunction> { |
1066 | 0 | debug_assert!((offset - self.offset) % size_of::<Arm64RuntimeFunction>() == 0); |
1067 | 0 | debug_assert!(offset < self.offset + self.size); |
1068 | | |
1069 | 0 | Ok(self.bytes.pread_with(offset, scroll::LE)?) |
1070 | 0 | } |
1071 | | } |
1072 | | |
1073 | | impl fmt::Debug for ExceptionData<'_> { |
1074 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
1075 | 0 | f.debug_struct("ExceptionData") |
1076 | 0 | .field("file_alignment", &self.file_alignment) |
1077 | 0 | .field("offset", &format_args!("{:#x}", self.offset)) |
1078 | 0 | .field("size", &format_args!("{:#x}", self.size)) |
1079 | 0 | .field("len", &self.len()) |
1080 | 0 | .finish() |
1081 | 0 | } |
1082 | | } |
1083 | | |
1084 | | impl<'a> IntoIterator for &'_ ExceptionData<'a> { |
1085 | | type Item = error::Result<RuntimeFunction>; |
1086 | | type IntoIter = RuntimeFunctionIterator<'a>; |
1087 | | |
1088 | | #[inline] |
1089 | 0 | fn into_iter(self) -> Self::IntoIter { |
1090 | 0 | self.functions() |
1091 | 0 | } |
1092 | | } |
1093 | | |
1094 | | // Arm64RuntimeFunction::flag |
1095 | | |
1096 | | /// `unwind_data` is an RVA to a full unwind record. |
1097 | | pub const ARM64_PDATA_REF_TO_FULL_XDATA: u32 = 0; |
1098 | | /// `unwind_data` uses the packed unwind data for a function. |
1099 | | pub const ARM64_PDATA_PACKED_UNWIND_FUNCTION: u32 = 1; |
1100 | | /// `unwind_data` uses the packed unwind data for a fragment. |
1101 | | pub const ARM64_PDATA_PACKED_UNWIND_FRAGMENT: u32 = 2; |
1102 | | |
1103 | | // Arm64RuntimeFunction::cr |
1104 | | |
1105 | | /// Unchained function `<x29, lr>` pair is not saved on the stack. |
1106 | | pub const ARM64_PDATA_CR_UNCHAINED: u32 = 0; |
1107 | | /// Unchained function but `lr` is saved on the stack. |
1108 | | pub const ARM64_PDATA_CR_UNCHAINED_SAVED_LR: u32 = 1; |
1109 | | /// Chained function with PAC (Pointer Authentication Code). |
1110 | | pub const ARM64_PDATA_CR_CHAINED_WITH_PAC: u32 = 2; |
1111 | | /// Chained function. |
1112 | | pub const ARM64_PDATA_CR_CHAINED: u32 = 3; |
1113 | | |
1114 | | /// An ARM64 unwind entry for a range. |
1115 | | #[repr(C)] |
1116 | | #[derive(Copy, Clone, PartialEq, Default, Pread, Pwrite)] |
1117 | | pub struct Arm64RuntimeFunction { |
1118 | | /// Function start RVA. |
1119 | | pub begin_address: u32, |
1120 | | /// Unwind data for this function entry. |
1121 | | pub unwind_data: u32, |
1122 | | } |
1123 | | |
1124 | | const _: () = assert!(size_of::<Arm64RuntimeFunction>() == 8); |
1125 | | |
1126 | | impl Arm64RuntimeFunction { |
1127 | 0 | const fn mask(bits: u32) -> u32 { |
1128 | 0 | (1u32 << bits) - 1 |
1129 | 0 | } |
1130 | | |
1131 | 0 | const fn bits(v: u32, shr: u32, bits: u32) -> u32 { |
1132 | 0 | (v >> shr) & Self::mask(bits) |
1133 | 0 | } |
1134 | | |
1135 | | /// If zero, unwind data is an RVA, packed into the runtime function entry otherwise. |
1136 | | /// |
1137 | | /// Must be one of: |
1138 | | /// - [ARM64_PDATA_REF_TO_FULL_XDATA] |
1139 | | /// - [ARM64_PDATA_PACKED_UNWIND_FUNCTION] |
1140 | | /// - [ARM64_PDATA_PACKED_UNWIND_FRAGMENT] |
1141 | | #[inline] |
1142 | 0 | pub const fn flag(&self) -> u32 { |
1143 | 0 | Self::bits(self.unwind_data, 0, 2) |
1144 | 0 | } |
1145 | | |
1146 | | /// Returns the length of the function in bytes. |
1147 | | #[inline] |
1148 | 0 | pub const fn function_length(&self) -> u32 { |
1149 | 0 | Self::bits(self.unwind_data, 2, 11).wrapping_mul(4) |
1150 | 0 | } |
1151 | | |
1152 | | /// Returns the frame size in bytes (bits [31:23], multiplied by 16). |
1153 | | #[inline] |
1154 | 0 | pub const fn frame_size(&self) -> u32 { |
1155 | 0 | Self::bits(self.unwind_data, 23, 9).wrapping_mul(16) |
1156 | 0 | } |
1157 | | |
1158 | | /// Returns the CR (chain/return) field (bits [22:21]). |
1159 | | /// |
1160 | | /// Must be one of: |
1161 | | /// - [ARM64_PDATA_CR_UNCHAINED] |
1162 | | /// - [ARM64_PDATA_CR_UNCHAINED_SAVED_LR] |
1163 | | /// - [ARM64_PDATA_CR_CHAINED_WITH_PAC] |
1164 | | /// - [ARM64_PDATA_CR_CHAINED] |
1165 | | #[inline] |
1166 | 0 | pub const fn cr(&self) -> u32 { |
1167 | 0 | Self::bits(self.unwind_data, 21, 2) |
1168 | 0 | } |
1169 | | |
1170 | | /// Returns `true` if the function homes integer parameter registers x0-x7 (bit [20]). |
1171 | | #[inline] |
1172 | 0 | pub const fn h(&self) -> bool { |
1173 | 0 | Self::bits(self.unwind_data, 20, 1) != 0 |
1174 | 0 | } |
1175 | | |
1176 | | /// Returns the number of saved non-volatile INT registers (x19-x28) (bits [19:16]). |
1177 | | #[inline] |
1178 | 0 | pub const fn reg_i(&self) -> u32 { |
1179 | 0 | Self::bits(self.unwind_data, 16, 4) |
1180 | 0 | } |
1181 | | |
1182 | | /// Returns the number of saved non-volatile FP registers (d8-d15) (bits [15:13]). |
1183 | | /// |
1184 | | /// 0 means no FP registers saved. Values 1-7 mean `reg_f + 1` registers saved. |
1185 | | #[inline] |
1186 | 0 | pub const fn reg_f(&self) -> u32 { |
1187 | 0 | Self::bits(self.unwind_data, 13, 3) |
1188 | 0 | } |
1189 | | |
1190 | | /// Returns `true` if this unwind data is packed, `false` otherwise. |
1191 | | #[inline] |
1192 | 0 | pub const fn is_packed(&self) -> bool { |
1193 | 0 | self.flag() != ARM64_PDATA_REF_TO_FULL_XDATA |
1194 | 0 | } |
1195 | | |
1196 | | /// Returns an RVA of the full unwind data. |
1197 | | #[inline] |
1198 | 0 | pub const fn unwind_data_rva(&self) -> u32 { |
1199 | 0 | self.unwind_data & !Self::mask(2) |
1200 | 0 | } |
1201 | | } |
1202 | | |
1203 | | impl fmt::Debug for Arm64RuntimeFunction { |
1204 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
1205 | 0 | f.debug_struct("Arm64RuntimeFunction") |
1206 | 0 | .field("begin_address", &format_args!("{:#x}", self.begin_address)) |
1207 | 0 | .field("unwind_data", &format_args!("{:#x}", self.unwind_data)) |
1208 | 0 | .finish() |
1209 | 0 | } |
1210 | | } |
1211 | | |
1212 | | /// Iterator over [Arm64RuntimeFunction] ARM64 runtime function entries. |
1213 | | #[derive(Debug)] |
1214 | | pub struct Arm64RuntimeFunctionIterator<'a> { |
1215 | | data: &'a [u8], |
1216 | | } |
1217 | | |
1218 | | impl Iterator for Arm64RuntimeFunctionIterator<'_> { |
1219 | | type Item = error::Result<Arm64RuntimeFunction>; |
1220 | | |
1221 | 0 | fn next(&mut self) -> Option<Self::Item> { |
1222 | 0 | if self.data.is_empty() { |
1223 | 0 | return None; |
1224 | 0 | } |
1225 | | |
1226 | 0 | Some(match self.data.pread_with(0, scroll::LE) { |
1227 | 0 | Ok(func) => { |
1228 | 0 | self.data = &self.data[size_of::<Arm64RuntimeFunction>()..]; |
1229 | 0 | Ok(func) |
1230 | | } |
1231 | 0 | Err(error) => { |
1232 | 0 | self.data = &[]; |
1233 | 0 | Err(error.into()) |
1234 | | } |
1235 | | }) |
1236 | 0 | } |
1237 | | |
1238 | 0 | fn size_hint(&self) -> (usize, Option<usize>) { |
1239 | 0 | let len = self.data.len() / size_of::<Arm64RuntimeFunction>(); |
1240 | 0 | (len, Some(len)) |
1241 | 0 | } |
1242 | | } |
1243 | | |
1244 | | impl FusedIterator for Arm64RuntimeFunctionIterator<'_> {} |
1245 | | impl ExactSizeIterator for Arm64RuntimeFunctionIterator<'_> {} |
1246 | | |
1247 | | /// ARM64 unwind info header. |
1248 | | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
1249 | | pub struct Arm64UnwindHeader(pub u32); |
1250 | | |
1251 | | const _: () = assert!(size_of::<Arm64UnwindHeader>() == 4); |
1252 | | |
1253 | | impl Arm64UnwindHeader { |
1254 | 0 | const fn mask(bits: u32) -> u32 { |
1255 | 0 | (1u32 << bits) - 1 |
1256 | 0 | } |
1257 | | |
1258 | 0 | const fn bits(v: u32, shr: u32, bits: u32) -> u32 { |
1259 | 0 | (v >> shr) & Self::mask(bits) |
1260 | 0 | } |
1261 | | |
1262 | | /// Returns a function length in bytes. |
1263 | | #[inline] |
1264 | 0 | pub const fn function_length(&self) -> u32 { |
1265 | 0 | Self::bits(self.0, 0, 18).wrapping_mul(4) |
1266 | 0 | } |
1267 | | |
1268 | | /// Returns an unwind info version. |
1269 | | #[inline] |
1270 | 0 | pub const fn version(&self) -> u32 { |
1271 | 0 | Self::bits(self.0, 18, 2) |
1272 | 0 | } |
1273 | | |
1274 | | /// Returns `true` if the unwind info has exception handler. |
1275 | | #[inline] |
1276 | 0 | pub const fn exception_data_present(&self) -> bool { |
1277 | 0 | Self::bits(self.0, 20, 1) != 0 |
1278 | 0 | } |
1279 | | |
1280 | | /// Returns `true` if an epilog is in the header. |
1281 | | #[inline] |
1282 | 0 | pub const fn epilog_in_header(&self) -> bool { |
1283 | 0 | Self::bits(self.0, 21, 1) != 0 |
1284 | 0 | } |
1285 | | |
1286 | | /// Returns bits indicates either an number of epilog scope or |
1287 | | /// the index of the first unwind code that describes the epilog. |
1288 | | #[inline] |
1289 | 0 | pub const fn epilog_count(&self) -> u32 { |
1290 | 0 | Self::bits(self.0, 22, 5) |
1291 | 0 | } |
1292 | | |
1293 | | /// Returns the number of u32's with unwind codes. |
1294 | | #[inline] |
1295 | 0 | pub const fn code_words(&self) -> u32 { |
1296 | 0 | Self::bits(self.0, 27, 5) |
1297 | 0 | } |
1298 | | |
1299 | | /// Returns `true` if "extension" bytes are in addition. |
1300 | | #[inline] |
1301 | 0 | pub const fn has_extension(&self) -> bool { |
1302 | 0 | self.epilog_count() == 0 && self.code_words() == 0 |
1303 | 0 | } |
1304 | | } |
1305 | | |
1306 | | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
1307 | | pub struct Arm64UnwindExtension(pub u32); |
1308 | | |
1309 | | const _: () = assert!(size_of::<Arm64UnwindExtension>() == 4); |
1310 | | |
1311 | | impl Arm64UnwindExtension { |
1312 | | /// Returns the number of epilog. |
1313 | | #[inline] |
1314 | 0 | pub const fn epilog_count(&self) -> u32 { |
1315 | 0 | self.0 & 0xFFFF |
1316 | 0 | } |
1317 | | |
1318 | | /// Returns the number of u32's with unwind codes. |
1319 | | #[inline] |
1320 | 0 | pub const fn code_words(&self) -> u32 { |
1321 | 0 | (self.0 >> 16) & 0xFF |
1322 | 0 | } |
1323 | | } |
1324 | | |
1325 | | /// ARM64 epilog scope info. |
1326 | | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
1327 | | pub struct Arm64EpilogScope(pub u32); |
1328 | | |
1329 | | const _: () = assert!(size_of::<Arm64EpilogScope>() == 4); |
1330 | | |
1331 | | impl Arm64EpilogScope { |
1332 | 0 | const fn mask(bits: u32) -> u32 { |
1333 | 0 | (1u32 << bits) - 1 |
1334 | 0 | } |
1335 | | |
1336 | 0 | const fn bits(v: u32, shr: u32, bits: u32) -> u32 { |
1337 | 0 | (v >> shr) & Self::mask(bits) |
1338 | 0 | } |
1339 | | |
1340 | | /// Returns an epulog start offset in bytes. |
1341 | | #[inline] |
1342 | 0 | pub const fn start_offset_words(&self) -> u32 { |
1343 | 0 | Self::bits(self.0, 0, 18).wrapping_mul(4) |
1344 | 0 | } |
1345 | | |
1346 | | /// Returns the bits indicate condition. |
1347 | | #[inline] |
1348 | 0 | pub const fn condition(&self) -> u32 { |
1349 | 0 | Self::bits(self.0, 18, 4) |
1350 | 0 | } |
1351 | | |
1352 | | /// Returns the index of the unwind code. |
1353 | | #[inline] |
1354 | 0 | pub const fn start_index(&self) -> u32 { |
1355 | 0 | Self::bits(self.0, 22, 10) |
1356 | 0 | } |
1357 | | } |
1358 | | |
1359 | | /// Iterator over epilog scopes. |
1360 | | #[derive(Debug)] |
1361 | | pub struct Arm64EpilogScopeIterator<'a> { |
1362 | | bytes: &'a [u8], |
1363 | | offset: usize, |
1364 | | } |
1365 | | |
1366 | | impl Iterator for Arm64EpilogScopeIterator<'_> { |
1367 | | type Item = error::Result<Arm64EpilogScope>; |
1368 | | |
1369 | 0 | fn next(&mut self) -> Option<Self::Item> { |
1370 | 0 | if self.offset >= self.bytes.len() { |
1371 | 0 | return None; |
1372 | 0 | } |
1373 | 0 | Some( |
1374 | 0 | self.bytes |
1375 | 0 | .gread_with::<u32>(&mut self.offset, scroll::LE) |
1376 | 0 | .map(Arm64EpilogScope) |
1377 | 0 | .map_err(Into::into), |
1378 | 0 | ) |
1379 | 0 | } |
1380 | | |
1381 | 0 | fn size_hint(&self) -> (usize, Option<usize>) { |
1382 | 0 | let len = (self.bytes.len().saturating_sub(self.offset)) / size_of_val(&0u32); |
1383 | 0 | (len, Some(len)) |
1384 | 0 | } |
1385 | | } |
1386 | | |
1387 | | impl FusedIterator for Arm64EpilogScopeIterator<'_> {} |
1388 | | impl ExactSizeIterator for Arm64EpilogScopeIterator<'_> {} |
1389 | | |
1390 | | /// ARM64 exception handler. |
1391 | | #[derive(Clone, Debug, Eq, PartialEq)] |
1392 | | pub struct Arm64ExceptionHandler<'a> { |
1393 | | /// RVA of the exception handler. |
1394 | | pub rva: u32, |
1395 | | /// Handler-specific data. |
1396 | | pub data: &'a [u8], |
1397 | | } |
1398 | | |
1399 | | /// ARM64 unwind info. |
1400 | | #[derive(Clone, Debug)] |
1401 | | pub struct Arm64UnwindInfo<'a> { |
1402 | | /// Unwind info header. |
1403 | | pub header: Arm64UnwindHeader, |
1404 | | /// Extension info if present. |
1405 | | pub extension: Option<Arm64UnwindExtension>, |
1406 | | /// Exception handler and bytes of handler specific data if present. |
1407 | | pub exception_handler: Option<Arm64ExceptionHandler<'a>>, |
1408 | | /// Bytes of epilog scope. |
1409 | | epilog_scope_bytes: &'a [u8], |
1410 | | /// Bytes of unwind codes. |
1411 | | unwind_code_bytes: &'a [u8], |
1412 | | } |
1413 | | |
1414 | | impl<'a> Arm64UnwindInfo<'a> { |
1415 | 0 | fn checked_mul4(words: u32) -> error::Result<usize> { |
1416 | 0 | let bytes = words |
1417 | 0 | .checked_mul(4) |
1418 | 0 | .ok_or_else(|| error::Error::Malformed("arm64 unwind info size overflow".into()))?; |
1419 | 0 | usize::try_from(bytes) |
1420 | 0 | .map_err(|_| error::Error::Malformed("arm64 unwind info size overflow".into())) |
1421 | 0 | } |
1422 | | |
1423 | 0 | fn checked_add(a: usize, b: usize) -> error::Result<usize> { |
1424 | 0 | a.checked_add(b) |
1425 | 0 | .ok_or_else(|| error::Error::Malformed("arm64 unwind info size overflow".into())) |
1426 | 0 | } |
1427 | | |
1428 | | /// Precomputes the size of this xdata. This does _not_ count for handler-specific bytes after handler RVA. |
1429 | | /// <https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling?view=msvc-170#xdata-records> |
1430 | 0 | pub fn size(bytes: &[u8]) -> error::Result<usize> { |
1431 | 0 | let w0 = bytes.pread_with::<u32>(0, scroll::LE)?; |
1432 | | |
1433 | 0 | let (mut size_u32, epilog_scopes, unwind_words) = if (w0 >> 22) != 0 { |
1434 | 0 | let size = 4u32; |
1435 | 0 | let epilog_scopes = (w0 >> 22) & 0x1f; |
1436 | 0 | let unwind_words = (w0 >> 27) & 0x1f; |
1437 | 0 | (size, epilog_scopes, unwind_words) |
1438 | | } else { |
1439 | 0 | let w1 = bytes.pread_with::<u32>(4, scroll::LE)?; |
1440 | 0 | let size = 8u32; |
1441 | 0 | let epilog_scopes = w1 & 0xffff; |
1442 | 0 | let unwind_words = (w1 >> 16) & 0xff; |
1443 | 0 | (size, epilog_scopes, unwind_words) |
1444 | | }; |
1445 | | |
1446 | 0 | let epilog_in_header = (w0 & (1u32 << 21)) != 0; |
1447 | 0 | if !epilog_in_header { |
1448 | 0 | size_u32 = Self::checked_add(size_u32 as _, Self::checked_mul4(epilog_scopes)?)? as _; |
1449 | 0 | } |
1450 | | |
1451 | 0 | size_u32 = Self::checked_add(size_u32 as _, Self::checked_mul4(unwind_words)?)? as _; |
1452 | | |
1453 | 0 | let exception_data_present = (w0 & (1u32 << 20)) != 0; |
1454 | 0 | if exception_data_present { |
1455 | 0 | size_u32 = Self::checked_add(size_u32 as _, 4)? as _; |
1456 | 0 | } |
1457 | | |
1458 | 0 | Ok(size_u32 as usize) |
1459 | 0 | } |
1460 | | |
1461 | | /// Parses a full ARM64 unwind xdata. |
1462 | 0 | pub fn parse(bytes: &'a [u8], mut offset: usize) -> error::Result<Self> { |
1463 | 0 | let header_word = bytes.gread_with::<u32>(&mut offset, scroll::LE)?; |
1464 | 0 | let header = Arm64UnwindHeader(header_word); |
1465 | | |
1466 | 0 | if header.version() != 0 { |
1467 | 0 | return Err(error::Error::Malformed(format!( |
1468 | 0 | "unsupported ARM64 .xdata version ({})", |
1469 | 0 | header.version() |
1470 | 0 | ))); |
1471 | 0 | } |
1472 | | |
1473 | 0 | let extension = if header.has_extension() { |
1474 | 0 | let ext_word = bytes.gread_with::<u32>(&mut offset, scroll::LE)?; |
1475 | 0 | Some(Arm64UnwindExtension(ext_word)) |
1476 | | } else { |
1477 | 0 | None |
1478 | | }; |
1479 | | |
1480 | 0 | let epilog_scopes = match (header.epilog_in_header(), extension) { |
1481 | 0 | (false, Some(ext)) => ext.epilog_count(), |
1482 | 0 | (false, None) => header.epilog_count(), |
1483 | 0 | (true, _) => 0, |
1484 | | }; |
1485 | | |
1486 | 0 | let unwind_words = if let Some(ext) = extension { |
1487 | 0 | ext.code_words() |
1488 | | } else { |
1489 | 0 | header.code_words() |
1490 | | }; |
1491 | | |
1492 | 0 | let scope_bytes_len = Self::checked_mul4(epilog_scopes)?; |
1493 | 0 | let epilog_scope_bytes = bytes.gread_with(&mut offset, scope_bytes_len)?; |
1494 | | |
1495 | 0 | let unwind_bytes_len = Self::checked_mul4(unwind_words)?; |
1496 | 0 | let unwind_code_bytes = bytes.gread_with(&mut offset, unwind_bytes_len)?; |
1497 | | |
1498 | 0 | let exception_handler = if header.exception_data_present() { |
1499 | 0 | let handler_rva = bytes.gread_with::<u32>(&mut offset, scroll::LE)?; |
1500 | 0 | let data = bytes.get(offset..).unwrap_or(&[]); |
1501 | 0 | Some(Arm64ExceptionHandler { |
1502 | 0 | rva: handler_rva, |
1503 | 0 | data, |
1504 | 0 | }) |
1505 | | } else { |
1506 | 0 | None |
1507 | | }; |
1508 | | |
1509 | 0 | Ok(Self { |
1510 | 0 | header, |
1511 | 0 | extension, |
1512 | 0 | epilog_scope_bytes, |
1513 | 0 | unwind_code_bytes, |
1514 | 0 | exception_handler, |
1515 | 0 | }) |
1516 | 0 | } |
1517 | | |
1518 | | /// Returns an iterator over epilog scope words. |
1519 | 0 | pub fn epilog_scopes(&self) -> Option<Arm64EpilogScopeIterator<'a>> { |
1520 | 0 | if self.header.epilog_in_header() || self.epilog_scope_bytes.is_empty() { |
1521 | 0 | return None; |
1522 | 0 | } |
1523 | 0 | Some(Arm64EpilogScopeIterator { |
1524 | 0 | bytes: self.epilog_scope_bytes, |
1525 | 0 | offset: 0, |
1526 | 0 | }) |
1527 | 0 | } |
1528 | | |
1529 | | /// Returns an iterator over unwind codes. |
1530 | 0 | pub fn unwind_codes(&self, start_index: u16) -> error::Result<Arm64UnwindCodeIterator<'a>> { |
1531 | 0 | Arm64UnwindCodeIterator::new(self.unwind_code_bytes, start_index as usize) |
1532 | 0 | } |
1533 | | } |
1534 | | |
1535 | | /// Custom stack cases reserved for specific Microsoft unwind opcodes. |
1536 | | /// |
1537 | | /// For more info: <https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling?view=msvc-170#unwind-codes> |
1538 | | #[derive(Clone, Copy, Debug, Eq, PartialEq)] |
1539 | | pub enum Arm64CustomStackCase { |
1540 | | TrapFrame, |
1541 | | MachineFrame, |
1542 | | Context, |
1543 | | EcContext, |
1544 | | ClearUnwoundToCall, |
1545 | | } |
1546 | | |
1547 | | /// ARM64 unwind codes. |
1548 | | #[non_exhaustive] |
1549 | | #[derive(Clone, Copy, Debug, Eq, PartialEq)] |
1550 | | pub enum Arm64UnwindCode { |
1551 | | AllocSmall { |
1552 | | size_bytes: u32, |
1553 | | }, |
1554 | | SaveR19R20Preindexed { |
1555 | | offset_bytes: u32, |
1556 | | }, |
1557 | | SaveFpLr { |
1558 | | offset_bytes: u32, |
1559 | | }, |
1560 | | SaveFpLrPreindexed { |
1561 | | offset_bytes: u32, |
1562 | | }, |
1563 | | AllocMedium { |
1564 | | size_bytes: u32, |
1565 | | }, |
1566 | | SaveRegPair { |
1567 | | first_reg: u8, |
1568 | | offset_bytes: u32, |
1569 | | }, |
1570 | | SaveRegPairPreindexed { |
1571 | | first_reg: u8, |
1572 | | offset_bytes: u32, |
1573 | | }, |
1574 | | SaveReg { |
1575 | | reg: u8, |
1576 | | offset_bytes: u32, |
1577 | | }, |
1578 | | SaveRegPreindexed { |
1579 | | reg: u8, |
1580 | | offset_bytes: u32, |
1581 | | }, |
1582 | | SaveLrPair { |
1583 | | first_reg: u8, |
1584 | | offset_bytes: u32, |
1585 | | }, |
1586 | | SaveFRegPair { |
1587 | | first_reg: u8, |
1588 | | offset_bytes: u32, |
1589 | | }, |
1590 | | SaveFRegPairPreindexed { |
1591 | | first_reg: u8, |
1592 | | offset_bytes: u32, |
1593 | | }, |
1594 | | SaveFReg { |
1595 | | reg: u8, |
1596 | | offset_bytes: u32, |
1597 | | }, |
1598 | | SaveFRegPreindexed { |
1599 | | reg: u8, |
1600 | | offset_bytes: u32, |
1601 | | }, |
1602 | | |
1603 | | AllocZ { |
1604 | | count: u8, |
1605 | | }, |
1606 | | |
1607 | | AllocLarge { |
1608 | | size_bytes: u32, |
1609 | | }, |
1610 | | SetFp, |
1611 | | AddFp { |
1612 | | imm8: u8, |
1613 | | offset_bytes: u32, |
1614 | | }, |
1615 | | Nop, |
1616 | | End, |
1617 | | EndC, |
1618 | | SaveNext, |
1619 | | |
1620 | | SaveAnyReg { |
1621 | | kind: u8, |
1622 | | pair: bool, |
1623 | | preindexed: bool, |
1624 | | reg: u8, |
1625 | | offset_bytes: u32, |
1626 | | }, |
1627 | | |
1628 | | SaveZReg { |
1629 | | r: u8, |
1630 | | o: u16, |
1631 | | }, |
1632 | | |
1633 | | SavePReg { |
1634 | | r: u8, |
1635 | | o: u16, |
1636 | | }, |
1637 | | |
1638 | | CustomStack(Arm64CustomStackCase), |
1639 | | |
1640 | | PacSignLr, |
1641 | | |
1642 | | Reserved { |
1643 | | opcode: u8, |
1644 | | data: u32, |
1645 | | data_len: u8, |
1646 | | }, |
1647 | | } |
1648 | | |
1649 | | /// Intermediate data to store offset and length of the unwind code. |
1650 | | #[derive(Clone, Copy, Debug, Eq, PartialEq)] |
1651 | | pub struct Arm64UnwindCodeData { |
1652 | | /// Offset of this unwind code. |
1653 | | pub offset: u16, |
1654 | | /// The unwind code. |
1655 | | pub code: Arm64UnwindCode, |
1656 | | /// Length of this unwind code in bytes. |
1657 | | pub len: u8, |
1658 | | } |
1659 | | |
1660 | | /// Iterator over ARM64 unwind codes. |
1661 | | pub struct Arm64UnwindCodeIterator<'a> { |
1662 | | bytes: &'a [u8], |
1663 | | offset: usize, |
1664 | | } |
1665 | | |
1666 | | impl<'a> Arm64UnwindCodeIterator<'a> { |
1667 | | /// Create a new iterator starting at `start` (byte index in the MSB-first unwind-byte stream). |
1668 | 0 | pub fn new(raw_unwind_bytes: &'a [u8], start: usize) -> error::Result<Self> { |
1669 | 0 | if raw_unwind_bytes.len() % 4 != 0 { |
1670 | 0 | return Err(error::Error::Malformed("length not a multiple of 4".into())); |
1671 | 0 | } |
1672 | 0 | if start > raw_unwind_bytes.len() { |
1673 | 0 | return Err(error::Error::Malformed("start index out of range".into())); |
1674 | 0 | } |
1675 | | |
1676 | 0 | Ok(Self { |
1677 | 0 | bytes: raw_unwind_bytes, |
1678 | 0 | offset: start, |
1679 | 0 | }) |
1680 | 0 | } |
1681 | | } |
1682 | | |
1683 | | impl<'a> Iterator for Arm64UnwindCodeIterator<'a> { |
1684 | | type Item = error::Result<Arm64UnwindCodeData>; |
1685 | | |
1686 | 0 | fn next(&mut self) -> Option<Self::Item> { |
1687 | 0 | if self.offset >= self.bytes.len() { |
1688 | 0 | return None; |
1689 | 0 | } |
1690 | | |
1691 | | macro_rules! fail { |
1692 | | ($msg:expr) => {{ |
1693 | | self.offset = self.bytes.len(); |
1694 | | return Some(Err($crate::error::Error::Malformed(($msg).into()))); |
1695 | | }}; |
1696 | | ($fmt:literal $(, $args:expr)* $(,)?) => {{ |
1697 | | self.offset = self.bytes.len(); |
1698 | | return Some(Err($crate::error::Error::Malformed(format!($fmt $(, $args)*)))); |
1699 | | }}; |
1700 | | } |
1701 | | |
1702 | | macro_rules! try_read_u8 { |
1703 | | ($at:expr) => {{ |
1704 | | match self.bytes.pread::<u8>($at) { |
1705 | | Ok(v) => v, |
1706 | | Err(err) => fail!("{}", err), |
1707 | | } |
1708 | | }}; |
1709 | | ($at:expr, $msg:expr) => {{ |
1710 | | match self.bytes.pread::<u8>($at) { |
1711 | | Ok(v) => v, |
1712 | | Err(_) => fail!($msg), |
1713 | | } |
1714 | | }}; |
1715 | | ($at:expr, $fmt:literal $(, $args:expr)* $(,)?) => {{ |
1716 | | match self.bytes.pread::<u8>($at) { |
1717 | | Ok(v) => v, |
1718 | | Err(_) => fail!($fmt $(, $args)*), |
1719 | | } |
1720 | | }}; |
1721 | | } |
1722 | | |
1723 | | // https://learn.microsoft.com/en-us/cpp/build/arm64-exception-handling?view=msvc-170#unwind-codes |
1724 | | |
1725 | 0 | let start = self.offset; |
1726 | 0 | let b0 = try_read_u8!(start); |
1727 | | |
1728 | 0 | let (code, len) = match b0 { |
1729 | 0 | 0x00..=0x1f => { |
1730 | 0 | let x = (b0 & 0x1f) as u32; |
1731 | 0 | ( |
1732 | 0 | Arm64UnwindCode::AllocSmall { |
1733 | 0 | size_bytes: x.wrapping_mul(16), |
1734 | 0 | }, |
1735 | 0 | 1, |
1736 | 0 | ) |
1737 | | } |
1738 | | |
1739 | 0 | 0x20..=0x3f => { |
1740 | 0 | let z = (b0 & 0x1f) as u32; |
1741 | 0 | ( |
1742 | 0 | Arm64UnwindCode::SaveR19R20Preindexed { |
1743 | 0 | offset_bytes: z.wrapping_mul(8), |
1744 | 0 | }, |
1745 | 0 | 1, |
1746 | 0 | ) |
1747 | | } |
1748 | | |
1749 | 0 | 0x40..=0x7f => { |
1750 | 0 | let z = (b0 & 0x3f) as u32; |
1751 | 0 | ( |
1752 | 0 | Arm64UnwindCode::SaveFpLr { |
1753 | 0 | offset_bytes: z.wrapping_mul(8), |
1754 | 0 | }, |
1755 | 0 | 1, |
1756 | 0 | ) |
1757 | | } |
1758 | | |
1759 | 0 | 0x80..=0xbf => { |
1760 | 0 | let z = (b0 & 0x3f) as u32; |
1761 | 0 | ( |
1762 | 0 | Arm64UnwindCode::SaveFpLrPreindexed { |
1763 | 0 | offset_bytes: (z.wrapping_add(1)).wrapping_mul(8), |
1764 | 0 | }, |
1765 | 0 | 1, |
1766 | 0 | ) |
1767 | | } |
1768 | | |
1769 | 0 | 0xc0..=0xc7 => { |
1770 | 0 | let r = try_read_u8!(start + 1, "alloc_m is truncated"); |
1771 | 0 | let x = (((b0 & 0x07) as u32) << 8) | (r as u32); |
1772 | 0 | ( |
1773 | 0 | Arm64UnwindCode::AllocMedium { |
1774 | 0 | size_bytes: x.wrapping_mul(16), |
1775 | 0 | }, |
1776 | 0 | 2, |
1777 | 0 | ) |
1778 | | } |
1779 | | |
1780 | 0 | 0xc8..=0xcb => { |
1781 | 0 | let b1 = try_read_u8!(start + 1, "save_regp is truncated"); |
1782 | 0 | let x = (((b0 & 0x03) as u32) << 2) | (((b1 >> 6) & 0x03) as u32); |
1783 | 0 | let z = (b1 & 0x3f) as u32; |
1784 | 0 | let first_reg = 19u8.wrapping_add(x as u8); |
1785 | 0 | ( |
1786 | 0 | Arm64UnwindCode::SaveRegPair { |
1787 | 0 | first_reg, |
1788 | 0 | offset_bytes: z.wrapping_mul(8), |
1789 | 0 | }, |
1790 | 0 | 2, |
1791 | 0 | ) |
1792 | | } |
1793 | | |
1794 | 0 | 0xcc..=0xcf => { |
1795 | 0 | let b1 = try_read_u8!(start + 1, "save_regp_x is truncated"); |
1796 | 0 | let x = (((b0 & 0x03) as u32) << 2) | (((b1 >> 6) & 0x03) as u32); |
1797 | 0 | let z = (b1 & 0x3f) as u32; |
1798 | 0 | let first_reg = 19u8.wrapping_add(x as u8); |
1799 | 0 | ( |
1800 | 0 | Arm64UnwindCode::SaveRegPairPreindexed { |
1801 | 0 | first_reg, |
1802 | 0 | offset_bytes: (z.wrapping_add(1)).wrapping_mul(8), |
1803 | 0 | }, |
1804 | 0 | 2, |
1805 | 0 | ) |
1806 | | } |
1807 | | |
1808 | 0 | 0xd0..=0xd3 => { |
1809 | 0 | let b1 = try_read_u8!(start + 1, "save_reg is truncated"); |
1810 | 0 | let x = (((b0 & 0x03) as u32) << 2) | (((b1 >> 6) & 0x03) as u32); |
1811 | 0 | let z = (b1 & 0x3f) as u32; |
1812 | 0 | let reg = 19u8.wrapping_add(x as u8); |
1813 | 0 | ( |
1814 | 0 | Arm64UnwindCode::SaveReg { |
1815 | 0 | reg, |
1816 | 0 | offset_bytes: z.wrapping_mul(8), |
1817 | 0 | }, |
1818 | 0 | 2, |
1819 | 0 | ) |
1820 | | } |
1821 | | |
1822 | 0 | 0xd4..=0xd5 => { |
1823 | 0 | let b1 = try_read_u8!(start + 1, "save_reg_x is truncated"); |
1824 | 0 | let x = (((b0 & 0x01) as u32) << 3) | (((b1 >> 5) & 0x07) as u32); |
1825 | 0 | let z = (b1 & 0x1f) as u32; |
1826 | 0 | let reg = 19u8.wrapping_add(x as u8); |
1827 | 0 | ( |
1828 | 0 | Arm64UnwindCode::SaveRegPreindexed { |
1829 | 0 | reg, |
1830 | 0 | offset_bytes: (z.wrapping_add(1)).wrapping_mul(8), |
1831 | 0 | }, |
1832 | 0 | 2, |
1833 | 0 | ) |
1834 | | } |
1835 | | |
1836 | 0 | 0xd6..=0xd7 => { |
1837 | 0 | let b1 = try_read_u8!(start + 1, "save_lrpair is truncated"); |
1838 | 0 | let x = (((b0 & 0x01) as u32) << 2) | (((b1 >> 6) & 0x03) as u32); |
1839 | 0 | let z = (b1 & 0x3f) as u32; |
1840 | 0 | let first_reg = 19u8.wrapping_add((2 * x) as u8); |
1841 | 0 | ( |
1842 | 0 | Arm64UnwindCode::SaveLrPair { |
1843 | 0 | first_reg, |
1844 | 0 | offset_bytes: z.wrapping_mul(8), |
1845 | 0 | }, |
1846 | 0 | 2, |
1847 | 0 | ) |
1848 | | } |
1849 | | |
1850 | 0 | 0xd8..=0xd9 => { |
1851 | 0 | let b1 = try_read_u8!(start + 1, "save_fregp is truncated"); |
1852 | 0 | let x = (((b0 & 0x01) as u32) << 2) | (((b1 >> 6) & 0x03) as u32); |
1853 | 0 | let z = (b1 & 0x3f) as u32; |
1854 | 0 | let first_reg = 8u8.wrapping_add(x as u8); |
1855 | 0 | ( |
1856 | 0 | Arm64UnwindCode::SaveFRegPair { |
1857 | 0 | first_reg, |
1858 | 0 | offset_bytes: z.wrapping_mul(8), |
1859 | 0 | }, |
1860 | 0 | 2, |
1861 | 0 | ) |
1862 | | } |
1863 | | |
1864 | 0 | 0xda..=0xdb => { |
1865 | 0 | let b1 = try_read_u8!(start + 1, "save_fregp_x is truncated"); |
1866 | 0 | let x = (((b0 & 0x01) as u32) << 2) | (((b1 >> 6) & 0x03) as u32); |
1867 | 0 | let z = (b1 & 0x3f) as u32; |
1868 | 0 | let first_reg = 8u8.wrapping_add(x as u8); |
1869 | 0 | ( |
1870 | 0 | Arm64UnwindCode::SaveFRegPairPreindexed { |
1871 | 0 | first_reg, |
1872 | 0 | offset_bytes: (z.wrapping_add(1)).wrapping_mul(8), |
1873 | 0 | }, |
1874 | 0 | 2, |
1875 | 0 | ) |
1876 | | } |
1877 | | |
1878 | 0 | 0xdc..=0xdd => { |
1879 | 0 | let b1 = try_read_u8!(start + 1, "save_freg is truncated"); |
1880 | 0 | let x = (((b0 & 0x01) as u32) << 2) | (((b1 >> 6) & 0x03) as u32); |
1881 | 0 | let z = (b1 & 0x3f) as u32; |
1882 | 0 | let reg = 8u8.wrapping_add(x as u8); |
1883 | 0 | ( |
1884 | 0 | Arm64UnwindCode::SaveFReg { |
1885 | 0 | reg, |
1886 | 0 | offset_bytes: z.wrapping_mul(8), |
1887 | 0 | }, |
1888 | 0 | 2, |
1889 | 0 | ) |
1890 | | } |
1891 | | |
1892 | | 0xde => { |
1893 | 0 | let b1 = try_read_u8!(start + 1, "save_freg_x is truncated"); |
1894 | 0 | let x = ((b1 >> 5) & 0x07) as u32; |
1895 | 0 | let z = (b1 & 0x1f) as u32; |
1896 | 0 | let reg = 8u8.wrapping_add(x as u8); |
1897 | 0 | ( |
1898 | 0 | Arm64UnwindCode::SaveFRegPreindexed { |
1899 | 0 | reg, |
1900 | 0 | offset_bytes: (z.wrapping_add(1)).wrapping_mul(8), |
1901 | 0 | }, |
1902 | 0 | 2, |
1903 | 0 | ) |
1904 | | } |
1905 | | |
1906 | | 0xdf => { |
1907 | 0 | let z = try_read_u8!(start + 1, "alloc_z is truncated"); |
1908 | 0 | (Arm64UnwindCode::AllocZ { count: z }, 2) |
1909 | | } |
1910 | | |
1911 | | 0xe0 => { |
1912 | 0 | let imm = { |
1913 | 0 | let b0 = try_read_u8!(start + 1) as u32; |
1914 | 0 | let b1 = try_read_u8!(start + 2) as u32; |
1915 | 0 | let b2 = try_read_u8!(start + 3) as u32; |
1916 | | |
1917 | 0 | (b0 << 16) | (b1 << 8) | b2 |
1918 | | }; |
1919 | 0 | ( |
1920 | 0 | Arm64UnwindCode::AllocLarge { |
1921 | 0 | size_bytes: imm.wrapping_mul(16), |
1922 | 0 | }, |
1923 | 0 | 4, |
1924 | 0 | ) |
1925 | | } |
1926 | | |
1927 | 0 | 0xe1 => (Arm64UnwindCode::SetFp, 1), |
1928 | | |
1929 | | 0xe2 => { |
1930 | 0 | let imm8 = try_read_u8!(start + 1, "add_fp is truncated"); |
1931 | 0 | ( |
1932 | 0 | Arm64UnwindCode::AddFp { |
1933 | 0 | imm8, |
1934 | 0 | offset_bytes: (imm8 as u32).wrapping_mul(8), |
1935 | 0 | }, |
1936 | 0 | 2, |
1937 | 0 | ) |
1938 | | } |
1939 | | |
1940 | 0 | 0xe3 => (Arm64UnwindCode::Nop, 1), |
1941 | 0 | 0xe4 => (Arm64UnwindCode::End, 1), |
1942 | 0 | 0xe5 => (Arm64UnwindCode::EndC, 1), |
1943 | 0 | 0xe6 => (Arm64UnwindCode::SaveNext, 1), |
1944 | | |
1945 | | 0xe7 => { |
1946 | 0 | let b1 = try_read_u8!(start + 1); |
1947 | | |
1948 | 0 | if (b1 & 0x80) != 0 { |
1949 | 0 | let data = b1 as u32; |
1950 | 0 | ( |
1951 | 0 | Arm64UnwindCode::Reserved { |
1952 | 0 | opcode: 0xe7, |
1953 | 0 | data, |
1954 | 0 | data_len: 1, |
1955 | 0 | }, |
1956 | 0 | 2, |
1957 | 0 | ) |
1958 | | } else { |
1959 | 0 | let b2 = try_read_u8!(start + 2, "save_any_? is truncated"); |
1960 | | |
1961 | 0 | let cls = (b2 >> 6) & 0x03; |
1962 | 0 | if cls != 3 { |
1963 | 0 | let p = ((b1 >> 6) & 1) != 0; |
1964 | 0 | let x = ((b1 >> 5) & 1) != 0; |
1965 | 0 | let r = (b1 & 0x1f) as u8; |
1966 | 0 | let o = (b2 & 0x3f) as u32; |
1967 | | |
1968 | 0 | let offset_bytes = match (cls, x || p) { |
1969 | 0 | (0 | 1, true) => match o.checked_mul(16) { |
1970 | 0 | Some(v) => v, |
1971 | 0 | None => fail!("save_any_reg offset overflow"), |
1972 | | }, |
1973 | 0 | (0 | 1, false) => match o.checked_mul(8) { |
1974 | 0 | Some(v) => v, |
1975 | 0 | None => fail!("save_any_reg offset overflow"), |
1976 | | }, |
1977 | 0 | (2, _) => match o.checked_mul(16) { |
1978 | 0 | Some(v) => v, |
1979 | 0 | None => fail!("save_any_reg offset overflow"), |
1980 | | }, |
1981 | 0 | _ => 0, |
1982 | | }; |
1983 | | |
1984 | 0 | let kind = cls; |
1985 | 0 | ( |
1986 | 0 | Arm64UnwindCode::SaveAnyReg { |
1987 | 0 | kind, |
1988 | 0 | pair: p, |
1989 | 0 | preindexed: x, |
1990 | 0 | reg: r, |
1991 | 0 | offset_bytes, |
1992 | 0 | }, |
1993 | 0 | 3, |
1994 | 0 | ) |
1995 | | } else { |
1996 | 0 | let hi = ((b1 >> 5) & 0x03) as u16; |
1997 | 0 | let r = (b1 & 0x0f) as u8; |
1998 | 0 | let lo = (b2 & 0x3f) as u16; |
1999 | 0 | let o = (hi << 6) | lo; |
2000 | | |
2001 | 0 | if (b1 & 0x10) != 0 { |
2002 | 0 | (Arm64UnwindCode::SavePReg { r, o }, 3) |
2003 | | } else { |
2004 | 0 | (Arm64UnwindCode::SaveZReg { r, o }, 3) |
2005 | | } |
2006 | | } |
2007 | | } |
2008 | | } |
2009 | | |
2010 | 0 | 0xe8 => ( |
2011 | 0 | Arm64UnwindCode::CustomStack(Arm64CustomStackCase::TrapFrame), |
2012 | 0 | 1, |
2013 | 0 | ), |
2014 | 0 | 0xe9 => ( |
2015 | 0 | Arm64UnwindCode::CustomStack(Arm64CustomStackCase::MachineFrame), |
2016 | 0 | 1, |
2017 | 0 | ), |
2018 | 0 | 0xea => ( |
2019 | 0 | Arm64UnwindCode::CustomStack(Arm64CustomStackCase::Context), |
2020 | 0 | 1, |
2021 | 0 | ), |
2022 | 0 | 0xeb => ( |
2023 | 0 | Arm64UnwindCode::CustomStack(Arm64CustomStackCase::EcContext), |
2024 | 0 | 1, |
2025 | 0 | ), |
2026 | 0 | 0xec => ( |
2027 | 0 | Arm64UnwindCode::CustomStack(Arm64CustomStackCase::ClearUnwoundToCall), |
2028 | 0 | 1, |
2029 | 0 | ), |
2030 | | |
2031 | 0 | 0xfc => (Arm64UnwindCode::PacSignLr, 1), |
2032 | | |
2033 | | 0xf8 => { |
2034 | 0 | let b1 = try_read_u8!(start + 1); |
2035 | 0 | ( |
2036 | 0 | Arm64UnwindCode::Reserved { |
2037 | 0 | opcode: 0xf8, |
2038 | 0 | data: b1 as u32, |
2039 | 0 | data_len: 1, |
2040 | 0 | }, |
2041 | 0 | 2, |
2042 | 0 | ) |
2043 | | } |
2044 | | 0xf9 => { |
2045 | 0 | let b1 = try_read_u8!(start + 1); |
2046 | 0 | let b2 = try_read_u8!(start + 2); |
2047 | 0 | let data = ((b1 as u32) << 8) | (b2 as u32); |
2048 | 0 | ( |
2049 | 0 | Arm64UnwindCode::Reserved { |
2050 | 0 | opcode: 0xf9, |
2051 | 0 | data, |
2052 | 0 | data_len: 2, |
2053 | 0 | }, |
2054 | 0 | 3, |
2055 | 0 | ) |
2056 | | } |
2057 | | 0xfa => { |
2058 | 0 | let b1 = try_read_u8!(start + 1); |
2059 | 0 | let b2 = try_read_u8!(start + 2); |
2060 | 0 | let b3 = try_read_u8!(start + 3); |
2061 | 0 | let data = ((b1 as u32) << 16) | ((b2 as u32) << 8) | (b3 as u32); |
2062 | 0 | ( |
2063 | 0 | Arm64UnwindCode::Reserved { |
2064 | 0 | opcode: 0xfa, |
2065 | 0 | data, |
2066 | 0 | data_len: 3, |
2067 | 0 | }, |
2068 | 0 | 4, |
2069 | 0 | ) |
2070 | | } |
2071 | | 0xfb => { |
2072 | 0 | let b1 = try_read_u8!(start + 1); |
2073 | 0 | let b2 = try_read_u8!(start + 2); |
2074 | 0 | let b3 = try_read_u8!(start + 3); |
2075 | 0 | let b4 = try_read_u8!(start + 4); |
2076 | 0 | let data = |
2077 | 0 | ((b1 as u32) << 24) | ((b2 as u32) << 16) | ((b3 as u32) << 8) | (b4 as u32); |
2078 | 0 | ( |
2079 | 0 | Arm64UnwindCode::Reserved { |
2080 | 0 | opcode: 0xfb, |
2081 | 0 | data, |
2082 | 0 | data_len: 4, |
2083 | 0 | }, |
2084 | 0 | 5, |
2085 | 0 | ) |
2086 | | } |
2087 | | |
2088 | 0 | other => ( |
2089 | 0 | Arm64UnwindCode::Reserved { |
2090 | 0 | opcode: other, |
2091 | 0 | data: 0, |
2092 | 0 | data_len: 0, |
2093 | 0 | }, |
2094 | 0 | 1, |
2095 | 0 | ), |
2096 | | }; |
2097 | | |
2098 | 0 | self.offset += len; |
2099 | | |
2100 | 0 | Some(Ok(Arm64UnwindCodeData { |
2101 | 0 | offset: start as u16, |
2102 | 0 | code, |
2103 | 0 | len: len as u8, |
2104 | 0 | })) |
2105 | 0 | } |
2106 | | } |
2107 | | |
2108 | | #[cfg(test)] |
2109 | | mod tests { |
2110 | | use super::*; |
2111 | | |
2112 | | #[test] |
2113 | | fn test_size_of_runtime_function() { |
2114 | | assert_eq!( |
2115 | | std::mem::size_of::<RuntimeFunction>(), |
2116 | | RUNTIME_FUNCTION_SIZE |
2117 | | ); |
2118 | | } |
2119 | | |
2120 | | // Tests disabled until there is a solution for handling binary test data |
2121 | | // See https://github.com/m4b/goblin/issues/185 |
2122 | | |
2123 | | // macro_rules! microsoft_symbol { |
2124 | | // ($name:literal, $id:literal) => {{ |
2125 | | // use std::fs::File; |
2126 | | // use std::path::Path; |
2127 | | |
2128 | | // let path = Path::new(concat!("cache/", $name)); |
2129 | | // if !path.exists() { |
2130 | | // let url = format!( |
2131 | | // "https://msdl.microsoft.com/download/symbols/{}/{}/{}", |
2132 | | // $name, $id, $name |
2133 | | // ); |
2134 | | |
2135 | | // let mut response = reqwest::get(&url).expect(concat!("get ", $name)); |
2136 | | // let mut target = File::create(path).expect(concat!("create ", $name)); |
2137 | | // response |
2138 | | // .copy_to(&mut target) |
2139 | | // .expect(concat!("download ", $name)); |
2140 | | // } |
2141 | | |
2142 | | // std::fs::read(path).expect(concat!("open ", $name)) |
2143 | | // }}; |
2144 | | // } |
2145 | | |
2146 | | // lazy_static::lazy_static! { |
2147 | | // static ref PE_DATA: Vec<u8> = microsoft_symbol!("WSHTCPIP.DLL", "4a5be0b77000"); |
2148 | | // } |
2149 | | |
2150 | | // #[test] |
2151 | | // fn test_parse() { |
2152 | | // let pe = PE::parse(&PE_DATA).expect("parse PE"); |
2153 | | // let exception_data = pe.exception_data.expect("get exception data"); |
2154 | | |
2155 | | // assert_eq!(exception_data.len(), 19); |
2156 | | // assert!(!exception_data.is_empty()); |
2157 | | // } |
2158 | | |
2159 | | // #[test] |
2160 | | // fn test_iter_functions() { |
2161 | | // let pe = PE::parse(&PE_DATA).expect("parse PE"); |
2162 | | // let exception_data = pe.exception_data.expect("get exception data"); |
2163 | | |
2164 | | // let functions: Vec<RuntimeFunction> = exception_data |
2165 | | // .functions() |
2166 | | // .map(|result| result.expect("parse runtime function")) |
2167 | | // .collect(); |
2168 | | |
2169 | | // assert_eq!(functions.len(), 19); |
2170 | | |
2171 | | // let expected = RuntimeFunction { |
2172 | | // begin_address: 0x1355, |
2173 | | // end_address: 0x1420, |
2174 | | // unwind_info_address: 0x4019, |
2175 | | // }; |
2176 | | |
2177 | | // assert_eq!(functions[4], expected); |
2178 | | // } |
2179 | | |
2180 | | // #[test] |
2181 | | // fn test_get_function() { |
2182 | | // let pe = PE::parse(&PE_DATA).expect("parse PE"); |
2183 | | // let exception_data = pe.exception_data.expect("get exception data"); |
2184 | | |
2185 | | // let expected = RuntimeFunction { |
2186 | | // begin_address: 0x1355, |
2187 | | // end_address: 0x1420, |
2188 | | // unwind_info_address: 0x4019, |
2189 | | // }; |
2190 | | |
2191 | | // assert_eq!( |
2192 | | // exception_data.get_function(4).expect("find function"), |
2193 | | // expected |
2194 | | // ); |
2195 | | // } |
2196 | | |
2197 | | // #[test] |
2198 | | // fn test_find_function() { |
2199 | | // let pe = PE::parse(&PE_DATA).expect("parse PE"); |
2200 | | // let exception_data = pe.exception_data.expect("get exception data"); |
2201 | | |
2202 | | // let expected = RuntimeFunction { |
2203 | | // begin_address: 0x1355, |
2204 | | // end_address: 0x1420, |
2205 | | // unwind_info_address: 0x4019, |
2206 | | // }; |
2207 | | |
2208 | | // assert_eq!( |
2209 | | // exception_data.find_function(0x1400).expect("find function"), |
2210 | | // Some(expected) |
2211 | | // ); |
2212 | | // } |
2213 | | |
2214 | | // #[test] |
2215 | | // fn test_find_function_none() { |
2216 | | // let pe = PE::parse(&PE_DATA).expect("parse PE"); |
2217 | | // let exception_data = pe.exception_data.expect("get exception data"); |
2218 | | |
2219 | | // // 0x1d00 is the end address of the last function. |
2220 | | |
2221 | | // assert_eq!( |
2222 | | // exception_data.find_function(0x1d00).expect("find function"), |
2223 | | // None |
2224 | | // ); |
2225 | | // } |
2226 | | |
2227 | | // #[test] |
2228 | | // fn test_get_unwind_info() { |
2229 | | // let pe = PE::parse(&PE_DATA).expect("parse PE"); |
2230 | | // let exception_data = pe.exception_data.expect("get exception data"); |
2231 | | |
2232 | | // // runtime function #0 directly refers to unwind info |
2233 | | // let rt_function = RuntimeFunction { |
2234 | | // begin_address: 0x1010, |
2235 | | // end_address: 0x1090, |
2236 | | // unwind_info_address: 0x25d8, |
2237 | | // }; |
2238 | | |
2239 | | // let unwind_info = exception_data |
2240 | | // .get_unwind_info(rt_function, &pe.sections) |
2241 | | // .expect("get unwind info"); |
2242 | | |
2243 | | // // Unwind codes just used to assert that the right unwind info was resolved |
2244 | | // let expected = &[4, 98]; |
2245 | | |
2246 | | // assert_eq!(unwind_info.code_bytes, expected); |
2247 | | // } |
2248 | | |
2249 | | // #[test] |
2250 | | // fn test_get_unwind_info_redirect() { |
2251 | | // let pe = PE::parse(&PE_DATA).expect("parse PE"); |
2252 | | // let exception_data = pe.exception_data.expect("get exception data"); |
2253 | | |
2254 | | // // runtime function #4 has a redirect (unwind_info_address & 1). |
2255 | | // let rt_function = RuntimeFunction { |
2256 | | // begin_address: 0x1355, |
2257 | | // end_address: 0x1420, |
2258 | | // unwind_info_address: 0x4019, |
2259 | | // }; |
2260 | | |
2261 | | // let unwind_info = exception_data |
2262 | | // .get_unwind_info(rt_function, &pe.sections) |
2263 | | // .expect("get unwind info"); |
2264 | | |
2265 | | // // Unwind codes just used to assert that the right unwind info was resolved |
2266 | | // let expected = &[ |
2267 | | // 28, 100, 15, 0, 28, 84, 14, 0, 28, 52, 12, 0, 28, 82, 24, 240, 22, 224, 20, 208, 18, |
2268 | | // 192, 16, 112, |
2269 | | // ]; |
2270 | | |
2271 | | // assert_eq!(unwind_info.code_bytes, expected); |
2272 | | // } |
2273 | | |
2274 | | #[test] |
2275 | | fn test_unwind_codes_one_epilog() { |
2276 | | // ; Prologue |
2277 | | // .text:00000001400884E0 000 57 push rdi // Save RDI |
2278 | | // |
2279 | | // .text:00000001400884E1 008 8B C2 mov eax, edx |
2280 | | // .text:00000001400884E3 008 48 8B F9 mov rdi, rcx |
2281 | | // .text:00000001400884E6 008 49 8B C8 mov rcx, r8 |
2282 | | // .text:00000001400884E9 008 F3 AA rep stosb |
2283 | | // .text:00000001400884EB 008 49 8B C1 mov rax, r9 |
2284 | | // |
2285 | | // ; Epilogue |
2286 | | // .text:00000001400884EE 008 5F pop rdi // Restore RDI |
2287 | | // .text:00000001400884EF 000 C3 retn |
2288 | | |
2289 | | const BYTES: &[u8] = &[ |
2290 | | 0x02, 0x01, 0x03, 0x00, // UNWIND_INFO_HDR <2, 0, 1, 3, 0, 0> |
2291 | | 0x02, 0x16, // UNWIND_CODE <<2, 6, 1>> ; UWOP_EPILOG |
2292 | | 0x00, 0x06, // UNWIND_CODE <<0, 6, 0>> ; UWOP_EPILOG |
2293 | | 0x01, 0x70, // UNWIND_CODE <<1, 0, 7>> ; UWOP_PUSH_NONVOL |
2294 | | ]; |
2295 | | |
2296 | | let unwind_info = UnwindInfo::parse(BYTES, 0).unwrap(); |
2297 | | let unwind_codes: Vec<UnwindCode> = unwind_info |
2298 | | .unwind_codes() |
2299 | | .map(|result| result.expect("Unable to parse unwind code")) |
2300 | | .collect(); |
2301 | | assert_eq!(unwind_codes.len(), 3); |
2302 | | assert_eq!( |
2303 | | unwind_codes, |
2304 | | [ |
2305 | | UnwindCode { |
2306 | | code_offset: 2, |
2307 | | operation: UnwindOperation::Epilog { |
2308 | | offset_low_or_size: 2, |
2309 | | offset_high_or_flags: 1, |
2310 | | }, |
2311 | | }, |
2312 | | UnwindCode { |
2313 | | code_offset: 0, |
2314 | | operation: UnwindOperation::Epilog { |
2315 | | offset_low_or_size: 0, |
2316 | | offset_high_or_flags: 0, |
2317 | | }, |
2318 | | }, |
2319 | | UnwindCode { |
2320 | | code_offset: 1, |
2321 | | operation: UnwindOperation::PushNonVolatile(Register(7)), // RDI |
2322 | | }, |
2323 | | ] |
2324 | | ); |
2325 | | } |
2326 | | |
2327 | | #[test] |
2328 | | fn test_unwind_codes_two_epilog() { |
2329 | | // ; Prologue |
2330 | | // .text:00000001400889A0 000 57 push rdi // Save RDI |
2331 | | // .text:00000001400889A1 008 56 push rsi // Save RSI |
2332 | | // |
2333 | | // .text:00000001400889A2 010 48 8B F9 mov rdi, rcx |
2334 | | // .text:00000001400889A5 010 48 8B F2 mov rsi, rdx |
2335 | | // .text:00000001400889A8 010 49 8B C8 mov rcx, r8 |
2336 | | // .text:00000001400889AB 010 F3 A4 rep movsb |
2337 | | // |
2338 | | // ; Epilogue |
2339 | | // .text:00000001400889AD 010 5E pop rsi // Restore RSI |
2340 | | // .text:00000001400889AE 008 5F pop rdi // Restore RDI |
2341 | | // .text:00000001400889AF 000 C3 retn |
2342 | | |
2343 | | const BYTES: &[u8] = &[ |
2344 | | 0x02, 0x02, 0x04, 0x00, // $xdatasym_5 UNWIND_INFO_HDR <2, 0, 2, 4, 0, 0> |
2345 | | 0x03, 0x16, // UNWIND_CODE <<3, 6, 1>> ; UWOP_EPILOG |
2346 | | 0x00, 0x06, // UNWIND_CODE <<0, 6, 0>> ; UWOP_EPILOG |
2347 | | 0x02, 0x60, // UNWIND_CODE <<2, 0, 6>> ; UWOP_PUSH_NONVOL |
2348 | | 0x01, 0x70, // UNWIND_CODE <<1, 0, 7>> ; UWOP_PUSH_NONVOL |
2349 | | ]; |
2350 | | |
2351 | | let unwind_info = UnwindInfo::parse(BYTES, 0).unwrap(); |
2352 | | let unwind_codes: Vec<UnwindCode> = unwind_info |
2353 | | .unwind_codes() |
2354 | | .map(|result| result.expect("Unable to parse unwind code")) |
2355 | | .collect(); |
2356 | | assert_eq!(unwind_codes.len(), 4); |
2357 | | assert_eq!( |
2358 | | unwind_codes, |
2359 | | [ |
2360 | | UnwindCode { |
2361 | | code_offset: 3, |
2362 | | operation: UnwindOperation::Epilog { |
2363 | | offset_low_or_size: 3, |
2364 | | offset_high_or_flags: 1, |
2365 | | }, |
2366 | | }, |
2367 | | UnwindCode { |
2368 | | code_offset: 0, |
2369 | | operation: UnwindOperation::Epilog { |
2370 | | offset_low_or_size: 0, |
2371 | | offset_high_or_flags: 0, |
2372 | | }, |
2373 | | }, |
2374 | | UnwindCode { |
2375 | | code_offset: 2, |
2376 | | operation: UnwindOperation::PushNonVolatile(Register(6)), // RSI |
2377 | | }, |
2378 | | UnwindCode { |
2379 | | code_offset: 1, |
2380 | | operation: UnwindOperation::PushNonVolatile(Register(7)), // RDI |
2381 | | }, |
2382 | | ] |
2383 | | ); |
2384 | | } |
2385 | | |
2386 | | #[test] |
2387 | | fn test_iter_unwind_codes() { |
2388 | | let unwind_info = UnwindInfo { |
2389 | | version: 1, |
2390 | | size_of_prolog: 4, |
2391 | | frame_register: Register(0), |
2392 | | frame_register_offset: 0, |
2393 | | chained_info: None, |
2394 | | handler: None, |
2395 | | code_bytes: &[4, 98], |
2396 | | }; |
2397 | | |
2398 | | let unwind_codes: Vec<UnwindCode> = unwind_info |
2399 | | .unwind_codes() |
2400 | | .map(|result| result.expect("parse unwind code")) |
2401 | | .collect(); |
2402 | | |
2403 | | assert_eq!(unwind_codes.len(), 1); |
2404 | | |
2405 | | let expected = UnwindCode { |
2406 | | code_offset: 4, |
2407 | | operation: UnwindOperation::Alloc(56), |
2408 | | }; |
2409 | | |
2410 | | assert_eq!(unwind_codes[0], expected); |
2411 | | } |
2412 | | |
2413 | | #[rustfmt::skip] |
2414 | | const UNWIND_INFO_C_SCOPE_TABLE: &[u8] = &[ |
2415 | | // UNWIND_INFO_HDR |
2416 | | 0x09, 0x0F, 0x06, 0x00, |
2417 | | |
2418 | | // UNWIND_CODEs |
2419 | | 0x0F, 0x64, // UWOP_SAVE_NONVOL (Offset=6, Reg=0x0F) |
2420 | | 0x09, 0x00, |
2421 | | 0x0F, 0x34, // UWOP_SAVE_NONVOL (Offset=3, Reg=0x0F) |
2422 | | 0x08, 0x00, |
2423 | | 0x0F, 0x52, // UWOP_ALLOC_SMALL (Size = (2 * 8) + 8 = 24 bytes) |
2424 | | 0x0B, 0x70, // UWOP_PUSH_NONVOL (Reg=0x0B) |
2425 | | |
2426 | | // Exception handler RVA |
2427 | | 0xC0, 0x1F, 0x00, 0x00, // __C_specific_handler |
2428 | | |
2429 | | // Scope count |
2430 | | 0x02, 0x00, 0x00, 0x00, // Scope table count = 2 |
2431 | | |
2432 | | // First C_SCOPE_TABLE entry |
2433 | | 0x01, 0x15, 0x00, 0x00, // BeginAddress = 0x00001501 |
2434 | | 0x06, 0x16, 0x00, 0x00, // EndAddress = 0x00001606 |
2435 | | 0x76, 0x1F, 0x00, 0x00, // HandlerAddress = 0x00001F76 |
2436 | | 0x06, 0x16, 0x00, 0x00, // JumpTarget = 0x00001606 |
2437 | | |
2438 | | // Second C_SCOPE_TABLE entry |
2439 | | 0x3A, 0x16, 0x00, 0x00, // BeginAddress = 0x0000163A |
2440 | | 0x4C, 0x16, 0x00, 0x00, // EndAddress = 0x0000164C |
2441 | | 0x76, 0x1F, 0x00, 0x00, // HandlerAddress = 0x00001F76 |
2442 | | 0x06, 0x16, 0x00, 0x00, // JumpTarget = 0x00001606 |
2443 | | ]; |
2444 | | |
2445 | | #[rustfmt::skip] |
2446 | | const UNWIND_INFO_C_SCOPE_TABLE_INVALID: &[u8] = &[ |
2447 | | // UNWIND_INFO_HDR |
2448 | | 0x09, 0x0F, 0x06, 0x00, |
2449 | | |
2450 | | // UNWIND_CODEs |
2451 | | 0x0F, 0x64, // UWOP_SAVE_NONVOL (Offset=6, Reg=0x0F) |
2452 | | 0x09, 0x00, |
2453 | | 0x0F, 0x34, // UWOP_SAVE_NONVOL (Offset=3, Reg=0x0F) |
2454 | | 0x08, 0x00, |
2455 | | 0x0F, 0x52, // UWOP_ALLOC_SMALL (Size = (2 * 8) + 8 = 24 bytes) |
2456 | | 0x0B, 0x70, // UWOP_PUSH_NONVOL (Reg=0x0B) |
2457 | | |
2458 | | // Exception handler RVA |
2459 | | 0xC0, 0x1F, 0x00, 0x00, // __C_specific_handler |
2460 | | |
2461 | | // Scope count |
2462 | | 0x02, 0x00, 0x00, 0x00, // Scope table count = 2 |
2463 | | |
2464 | | // First C_SCOPE_TABLE entry |
2465 | | 0x01, 0x15, 0x00, 0x00, // BeginAddress = 0x00001501 |
2466 | | 0x06, 0x16, 0x00, 0x00, // EndAddress = 0x00001606 |
2467 | | 0x76, 0x1F, 0x00, 0x00, // HandlerAddress = 0x00001F76 |
2468 | | 0x06, 0x16, 0x00, 0x00, // JumpTarget = 0x00001606 |
2469 | | |
2470 | | // Second C_SCOPE_TABLE entry |
2471 | | 0x3A, 0x16, 0x00, 0x00, // BeginAddress = 0x0000163A |
2472 | | 0x4C, 0x16, 0x00, 0x00, // EndAddress = 0x0000164C |
2473 | | 0x76, 0x1F, 0x00, 0x00, // HandlerAddress = 0x00001F76 |
2474 | | 0x06, // JumpTarget = 0x??????06 |
2475 | | ]; |
2476 | | |
2477 | | #[test] |
2478 | | fn parse_c_scope_table() { |
2479 | | let unwind_info = UnwindInfo::parse(UNWIND_INFO_C_SCOPE_TABLE, 0) |
2480 | | .expect("Failed to parse unwind info with C scope table"); |
2481 | | let entries = unwind_info |
2482 | | .c_scope_table_entries() |
2483 | | .expect("C scope table should present"); |
2484 | | let entries = entries.collect::<Vec<_>>(); |
2485 | | assert_eq!(entries.len(), 2); |
2486 | | assert_eq!( |
2487 | | entries[0], |
2488 | | ScopeTableEntry { |
2489 | | begin: 0x00001501, |
2490 | | end: 0x00001606, |
2491 | | handler: 0x00001F76, |
2492 | | target: 0x00001606, |
2493 | | } |
2494 | | ); |
2495 | | assert_eq!( |
2496 | | entries[1], |
2497 | | ScopeTableEntry { |
2498 | | begin: 0x0000163A, |
2499 | | end: 0x0000164C, |
2500 | | handler: 0x00001F76, |
2501 | | target: 0x00001606, |
2502 | | } |
2503 | | ); |
2504 | | } |
2505 | | |
2506 | | #[test] |
2507 | | #[should_panic(expected = "C scope table should present")] |
2508 | | fn malformed_scope_table_is_not_allowed() { |
2509 | | let unwind_info = UnwindInfo::parse(UNWIND_INFO_C_SCOPE_TABLE_INVALID, 0) |
2510 | | .expect("Failed to parse unwind info with C scope table"); |
2511 | | unwind_info |
2512 | | .c_scope_table_entries() |
2513 | | .expect("C scope table should present"); |
2514 | | } |
2515 | | |
2516 | | #[test] |
2517 | | #[should_panic(expected = "Scope table is not aligned")] |
2518 | | fn unaligned_scope_table_is_not_allowed() { |
2519 | | let it = ScopeTableIterator { |
2520 | | data: &[0x00, 0x00, 0x00, 0x00, 0x00], |
2521 | | offset: 0, |
2522 | | }; |
2523 | | let _ = it.collect::<Vec<_>>(); |
2524 | | } |
2525 | | |
2526 | | mod arm64 { |
2527 | | use crate::pe::exception::*; |
2528 | | |
2529 | | #[test] |
2530 | | fn parse_unwind_info_packed() { |
2531 | | // unwind_data = 0x80600021 |
2532 | | // bits [1:0] = 01 -> Flag = 1 (packed function) |
2533 | | // bits [12:2] = 8 -> FunctionLength = 8 * 4 = 32 |
2534 | | // bits [15:13] = 000 -> RegF = 0 |
2535 | | // bits [19:16] = 0000 -> RegI = 0 |
2536 | | // bit [20] = 0 -> H = false |
2537 | | // bits [22:21] = 11 -> CR = 3 (chained) |
2538 | | // bits [31:23] = 0x100 -> FrameSize = 256 * 16 = 0x1000 |
2539 | | const DATA: &[u8] = &[ |
2540 | | 0x78, 0x11, 0x00, 0x00, // begin_address |
2541 | | 0x21, 0x00, 0x60, 0x80, // unwind_data |
2542 | | ]; |
2543 | | |
2544 | | let func = DATA.pread::<Arm64RuntimeFunction>(0).unwrap(); |
2545 | | assert_eq!(func.begin_address, 0x1178); |
2546 | | assert_eq!(func.is_packed(), true); |
2547 | | assert_eq!(func.flag(), ARM64_PDATA_PACKED_UNWIND_FUNCTION); |
2548 | | assert_eq!(func.function_length(), 32); |
2549 | | assert_eq!(func.frame_size(), 0x1000); |
2550 | | assert_eq!(func.cr(), ARM64_PDATA_CR_CHAINED); |
2551 | | assert_eq!(func.h(), false); |
2552 | | assert_eq!(func.reg_f(), 0); |
2553 | | assert_eq!(func.reg_i(), 0); |
2554 | | } |
2555 | | |
2556 | | #[test] |
2557 | | fn parse_packed_all_fields_nonzero() { |
2558 | | // Construct a value where every packed field is non-zero to verify |
2559 | | // that each accessor extracts from the correct bit position. |
2560 | | // |
2561 | | // unwind_data = 0x04556041 |
2562 | | // bits [1:0] = 01 -> Flag = 1 (packed function) |
2563 | | // bits [12:2] = 0x10 -> FunctionLength = 16 * 4 = 64 |
2564 | | // bits [15:13] = 011 -> RegF = 3 |
2565 | | // bits [19:16] = 0101 -> RegI = 5 |
2566 | | // bit [20] = 1 -> H = true |
2567 | | // bits [22:21] = 10 -> CR = 2 (chained with PAC) |
2568 | | // bits [31:23] = 0x08 -> FrameSize = 8 * 16 = 128 |
2569 | | const DATA: &[u8] = &[ |
2570 | | 0x00, 0x10, 0x00, 0x00, // begin_address |
2571 | | 0x41, 0x60, 0x55, 0x04, // unwind_data = 0x04556041 |
2572 | | ]; |
2573 | | |
2574 | | let func = DATA.pread::<Arm64RuntimeFunction>(0).unwrap(); |
2575 | | assert_eq!(func.flag(), ARM64_PDATA_PACKED_UNWIND_FUNCTION); |
2576 | | assert!(func.is_packed()); |
2577 | | assert_eq!(func.function_length(), 64); |
2578 | | assert_eq!(func.frame_size(), 128); |
2579 | | assert_eq!(func.cr(), ARM64_PDATA_CR_CHAINED_WITH_PAC); |
2580 | | assert_eq!(func.h(), true); |
2581 | | assert_eq!(func.reg_i(), 5); |
2582 | | assert_eq!(func.reg_f(), 3); |
2583 | | } |
2584 | | |
2585 | | #[test] |
2586 | | fn parse_packed_bit_positions() { |
2587 | | // unwind_data = 0x02424281 |
2588 | | // bits [1:0] = 01 -> Flag = 1 (packed function) |
2589 | | // bits [12:2] = 0xA0 -> FunctionLength = 160 * 4 = 640 |
2590 | | // bits [15:13] = 010 -> RegF = 2 |
2591 | | // bits [19:16] = 0010 -> RegI = 2 |
2592 | | // bit [20] = 0 -> H = false |
2593 | | // bits [22:21] = 10 -> CR = 2 (chained with PAC) |
2594 | | // bits [31:23] = 0x04 -> FrameSize = 4 * 16 = 64 |
2595 | | let func = Arm64RuntimeFunction { |
2596 | | begin_address: 0, |
2597 | | unwind_data: 0x02424281, |
2598 | | }; |
2599 | | assert_eq!(func.flag(), ARM64_PDATA_PACKED_UNWIND_FUNCTION); |
2600 | | assert_eq!(func.function_length(), 640); |
2601 | | assert_eq!(func.reg_f(), 2); |
2602 | | assert_eq!(func.reg_i(), 2); |
2603 | | assert_eq!(func.h(), false); |
2604 | | assert_eq!(func.cr(), ARM64_PDATA_CR_CHAINED_WITH_PAC); |
2605 | | assert_eq!(func.frame_size(), 64); |
2606 | | } |
2607 | | |
2608 | | #[test] |
2609 | | fn parse_packed_max_field_values() { |
2610 | | // unwind_data = 0xFFFFFFFF |
2611 | | // bits [1:0] = 11 -> Flag = 3 |
2612 | | // bits [12:2] = 0x7FF -> FunctionLength = 0x7FF * 4 |
2613 | | // bits [15:13] = 111 -> RegF = 7 |
2614 | | // bits [19:16] = 1111 -> RegI = 15 |
2615 | | // bit [20] = 1 -> H = true |
2616 | | // bits [22:21] = 11 -> CR = 3 |
2617 | | // bits [31:23] = 0x1FF -> FrameSize = 0x1FF * 16 |
2618 | | let func = Arm64RuntimeFunction { |
2619 | | begin_address: 0, |
2620 | | unwind_data: 0xFFFFFFFF, |
2621 | | }; |
2622 | | assert_eq!(func.flag(), 3); |
2623 | | assert_eq!(func.function_length(), 0x7FF * 4); |
2624 | | assert_eq!(func.reg_f(), 0x7); |
2625 | | assert_eq!(func.reg_i(), 0xF); |
2626 | | assert_eq!(func.h(), true); |
2627 | | assert_eq!(func.cr(), 0x3); |
2628 | | assert_eq!(func.frame_size(), 0x1FF * 16); |
2629 | | } |
2630 | | |
2631 | | #[test] |
2632 | | fn parse_packed_field_isolation() { |
2633 | | // bits [31:23] = 0x001 -> FrameSize = 1 * 16 = 16 |
2634 | | let only_frame = Arm64RuntimeFunction { |
2635 | | begin_address: 0, |
2636 | | unwind_data: 0x1 << 23, |
2637 | | }; |
2638 | | assert_eq!(only_frame.frame_size(), 16); |
2639 | | assert_eq!(only_frame.cr(), 0); |
2640 | | assert_eq!(only_frame.h(), false); |
2641 | | assert_eq!(only_frame.reg_i(), 0); |
2642 | | assert_eq!(only_frame.reg_f(), 0); |
2643 | | |
2644 | | // bits [22:21] = 11 -> CR = 3 |
2645 | | let only_cr = Arm64RuntimeFunction { |
2646 | | begin_address: 0, |
2647 | | unwind_data: 0x3 << 21, |
2648 | | }; |
2649 | | assert_eq!(only_cr.cr(), 3); |
2650 | | assert_eq!(only_cr.frame_size(), 0); |
2651 | | assert_eq!(only_cr.h(), false); |
2652 | | assert_eq!(only_cr.reg_i(), 0); |
2653 | | assert_eq!(only_cr.reg_f(), 0); |
2654 | | |
2655 | | // bit [20] = 1 -> H = true |
2656 | | let only_h = Arm64RuntimeFunction { |
2657 | | begin_address: 0, |
2658 | | unwind_data: 0x1 << 20, |
2659 | | }; |
2660 | | assert_eq!(only_h.h(), true); |
2661 | | assert_eq!(only_h.cr(), 0); |
2662 | | assert_eq!(only_h.frame_size(), 0); |
2663 | | assert_eq!(only_h.reg_i(), 0); |
2664 | | assert_eq!(only_h.reg_f(), 0); |
2665 | | |
2666 | | // bits [19:16] = 1111 -> RegI = 15 |
2667 | | let only_reg_i = Arm64RuntimeFunction { |
2668 | | begin_address: 0, |
2669 | | unwind_data: 0xF << 16, |
2670 | | }; |
2671 | | assert_eq!(only_reg_i.reg_i(), 15); |
2672 | | assert_eq!(only_reg_i.h(), false); |
2673 | | assert_eq!(only_reg_i.cr(), 0); |
2674 | | assert_eq!(only_reg_i.frame_size(), 0); |
2675 | | assert_eq!(only_reg_i.reg_f(), 0); |
2676 | | |
2677 | | // bits [15:13] = 111 -> RegF = 7 |
2678 | | let only_reg_f = Arm64RuntimeFunction { |
2679 | | begin_address: 0, |
2680 | | unwind_data: 0x7 << 13, |
2681 | | }; |
2682 | | assert_eq!(only_reg_f.reg_f(), 7); |
2683 | | assert_eq!(only_reg_f.reg_i(), 0); |
2684 | | assert_eq!(only_reg_f.h(), false); |
2685 | | assert_eq!(only_reg_f.cr(), 0); |
2686 | | assert_eq!(only_reg_f.frame_size(), 0); |
2687 | | } |
2688 | | |
2689 | | #[test] |
2690 | | fn parse_unpacked_xdata_rva() { |
2691 | | // Flag = 0 -> unwind_data is an RVA (low 2 bits cleared) |
2692 | | // unwind_data = 0x00001234, with low 2 bits = 00 |
2693 | | const DATA: &[u8] = &[ |
2694 | | 0x00, 0x10, 0x00, 0x00, // begin_address |
2695 | | 0x34, 0x12, 0x00, 0x00, // unwind_data = 0x00001234 |
2696 | | ]; |
2697 | | |
2698 | | let func = DATA.pread::<Arm64RuntimeFunction>(0).unwrap(); |
2699 | | assert_eq!(func.flag(), ARM64_PDATA_REF_TO_FULL_XDATA); |
2700 | | assert!(!func.is_packed()); |
2701 | | assert_eq!(func.unwind_data_rva(), 0x00001234); |
2702 | | } |
2703 | | |
2704 | | #[test] |
2705 | | fn arm64_runtime_function_iterator() { |
2706 | | // 2 ARM64 entries (8 bytes each = 16 bytes total) |
2707 | | #[rustfmt::skip] |
2708 | | const DATA: &[u8] = &[ |
2709 | | 0x00, 0x10, 0x00, 0x00, 0x34, 0x12, 0x00, 0x00, |
2710 | | 0x00, 0x20, 0x00, 0x00, 0x78, 0x56, 0x00, 0x00, |
2711 | | ]; |
2712 | | |
2713 | | let iter = Arm64RuntimeFunctionIterator { data: DATA }; |
2714 | | let funcs: Vec<_> = iter.map(|r| r.unwrap()).collect(); |
2715 | | assert_eq!(funcs.len(), 2); |
2716 | | assert_eq!(funcs[0].begin_address, 0x1000); |
2717 | | assert_eq!(funcs[0].unwind_data, 0x00001234); |
2718 | | assert_eq!(funcs[1].begin_address, 0x2000); |
2719 | | assert_eq!(funcs[1].unwind_data, 0x00005678); |
2720 | | } |
2721 | | |
2722 | | #[test] |
2723 | | fn xdata_header_with_extension() { |
2724 | | // Header word with epilog_count=0 and code_words=0 -> extension present |
2725 | | #[rustfmt::skip] |
2726 | | const XDATA: &[u8] = &[ |
2727 | | 0x04, 0x00, 0x00, 0x00, // header: func_len=1, ver=0, X=0, E=0, epilog_count=0, code_words=0 |
2728 | | 0x01, 0x00, 0x01, 0x00, // extension: epilog_count=1, code_words=1 |
2729 | | |
2730 | | // Epilog scope |
2731 | | 0x0D, 0x00, 0x00, 0x00, |
2732 | | |
2733 | | // Unwind codes (1 word = 4 bytes) |
2734 | | 0x81, 0xE4, 0xE3, 0xE3, |
2735 | | ]; |
2736 | | |
2737 | | let info = Arm64UnwindInfo::parse(XDATA, 0).unwrap(); |
2738 | | assert!(info.extension.is_some()); |
2739 | | let ext = info.extension.unwrap(); |
2740 | | assert_eq!(ext.epilog_count(), 1); |
2741 | | assert_eq!(ext.code_words(), 1); |
2742 | | assert_eq!(info.epilog_scope_bytes.len(), 4); |
2743 | | assert_eq!(info.unwind_code_bytes.len(), 4); |
2744 | | } |
2745 | | |
2746 | | #[test] |
2747 | | fn epilog_scope_field_extraction() { |
2748 | | // Construct: start_offset=0x3FFFF (18 bits max), condition=0xF (reserved for arm64), start_index=0x3FF (10 bits max) |
2749 | | // But let's use smaller values to be clear: |
2750 | | // start_offset_words = 52 (0x34 / 4 = 0x0D -> bits [17:0] = 0x0D, *4 = 52) |
2751 | | // condition = 0x0E (bits [21:18]) |
2752 | | // start_index = 2 (bits [31:22]) |
2753 | | // |
2754 | | // value = (2 << 22) | (0x0E << 18) | 0x0D = 0x00B8000D |
2755 | | let scope = Arm64EpilogScope(0x00B8000D); |
2756 | | assert_eq!(scope.start_offset_words(), 52); |
2757 | | assert_eq!(scope.condition(), 0x0E); |
2758 | | assert_eq!(scope.start_index(), 2); |
2759 | | } |
2760 | | |
2761 | | #[test] |
2762 | | fn parse_unwind_info_0() { |
2763 | | #[rustfmt::skip] |
2764 | | const XDATA: &[u8] = &[ |
2765 | | 0x96, 0x00, 0x70, 0x10, |
2766 | | |
2767 | | // Unwind codes |
2768 | | 0xE1, // set_fp |
2769 | | 0x87, // save_fplr_x |
2770 | | 0xD1, 0x04, // save_reg |
2771 | | 0xC8, 0x82, // save_regp |
2772 | | 0x26, // save_r19r20_x |
2773 | | 0xE4, // end |
2774 | | |
2775 | | // Exception handler and its exception data |
2776 | | 0x8C, 0x1E, 0x00, 0x00, // imagerel __CxxFrameHandler3_0 |
2777 | | 0xA0, 0x32, 0x00, 0x00, // exception data |
2778 | | ]; |
2779 | | |
2780 | | let info = Arm64UnwindInfo::parse(XDATA, 0).unwrap(); |
2781 | | |
2782 | | // do not count handler-specific dta. |
2783 | | assert_eq!( |
2784 | | Arm64UnwindInfo::size(XDATA).unwrap(), |
2785 | | XDATA.len() - size_of_val(&0u32) |
2786 | | ); |
2787 | | |
2788 | | assert_eq!(info.header.0, 0x10700096); |
2789 | | assert_eq!(info.header.function_length(), 600); |
2790 | | assert_eq!(info.header.version(), 0); |
2791 | | assert_eq!(info.header.exception_data_present(), true); |
2792 | | assert_eq!(info.header.epilog_in_header(), true); |
2793 | | assert_eq!(info.header.epilog_count(), 1); |
2794 | | assert_eq!(info.header.code_words(), 2); |
2795 | | assert_eq!(info.header.has_extension(), false); |
2796 | | |
2797 | | assert_eq!(info.unwind_code_bytes, &XDATA[4..12]); |
2798 | | |
2799 | | let codes = info.unwind_codes(0).unwrap(); |
2800 | | let codes = codes.collect::<Vec<_>>(); |
2801 | | assert_eq!(codes.len(), 6); |
2802 | | assert_eq!( |
2803 | | codes[0].as_ref().unwrap(), |
2804 | | &Arm64UnwindCodeData { |
2805 | | offset: 0, |
2806 | | code: Arm64UnwindCode::SetFp, |
2807 | | len: 1, |
2808 | | } |
2809 | | ); |
2810 | | assert_eq!( |
2811 | | codes[1].as_ref().unwrap(), |
2812 | | &Arm64UnwindCodeData { |
2813 | | offset: 1, |
2814 | | code: Arm64UnwindCode::SaveFpLrPreindexed { offset_bytes: 64 }, |
2815 | | len: 1, |
2816 | | } |
2817 | | ); |
2818 | | assert_eq!( |
2819 | | codes[2].as_ref().unwrap(), |
2820 | | &Arm64UnwindCodeData { |
2821 | | offset: 2, |
2822 | | code: Arm64UnwindCode::SaveReg { |
2823 | | reg: 23, |
2824 | | offset_bytes: 32, |
2825 | | }, |
2826 | | len: 2, |
2827 | | } |
2828 | | ); |
2829 | | assert_eq!( |
2830 | | codes[3].as_ref().unwrap(), |
2831 | | &Arm64UnwindCodeData { |
2832 | | offset: 4, |
2833 | | code: Arm64UnwindCode::SaveRegPair { |
2834 | | first_reg: 21, |
2835 | | offset_bytes: 16, |
2836 | | }, |
2837 | | len: 2, |
2838 | | } |
2839 | | ); |
2840 | | assert_eq!( |
2841 | | codes[4].as_ref().unwrap(), |
2842 | | &Arm64UnwindCodeData { |
2843 | | offset: 6, |
2844 | | code: Arm64UnwindCode::SaveR19R20Preindexed { offset_bytes: 48 }, |
2845 | | len: 1, |
2846 | | } |
2847 | | ); |
2848 | | assert_eq!( |
2849 | | codes[5].as_ref().unwrap(), |
2850 | | &Arm64UnwindCodeData { |
2851 | | offset: 7, |
2852 | | code: Arm64UnwindCode::End, |
2853 | | len: 1, |
2854 | | } |
2855 | | ); |
2856 | | } |
2857 | | |
2858 | | #[test] |
2859 | | fn parse_unwind_info_1() { |
2860 | | #[rustfmt::skip] |
2861 | | const XDATA: &[u8] = &[ |
2862 | | 0x10, 0x00, 0x50, 0x08, |
2863 | | |
2864 | | // Epilog scopes |
2865 | | 0x0D, 0x00, 0x00, 0x00, |
2866 | | |
2867 | | // Unwind codes |
2868 | | 0x81, // save_fplr_x |
2869 | | 0xE4, // end |
2870 | | |
2871 | | 0xE3, 0xE3, // align to 4 |
2872 | | |
2873 | | // Exception handler and its exception data |
2874 | | 0x8C, 0x1E, 0x00, 0x00, // imagerel __CxxFrameHandler3_0 |
2875 | | 0xA0, 0x32, 0x00, 0x00, // exception data |
2876 | | ]; |
2877 | | |
2878 | | let info = Arm64UnwindInfo::parse(XDATA, 0).unwrap(); |
2879 | | |
2880 | | // do not count handler-specific dta. |
2881 | | assert_eq!( |
2882 | | Arm64UnwindInfo::size(XDATA).unwrap(), |
2883 | | XDATA.len() - size_of_val(&0u32) |
2884 | | ); |
2885 | | |
2886 | | assert_eq!(info.header.0, 0x08500010); |
2887 | | assert_eq!(info.header.version(), 0); |
2888 | | assert_eq!(info.header.exception_data_present(), true); |
2889 | | assert_eq!(info.header.epilog_in_header(), false); |
2890 | | |
2891 | | assert_eq!(info.unwind_code_bytes, &XDATA[8..12]); |
2892 | | |
2893 | | let scopes = info.epilog_scopes().unwrap(); |
2894 | | let scopes = scopes.map(Result::unwrap).collect::<Vec<_>>(); |
2895 | | assert_eq!(scopes.len(), 1); |
2896 | | assert_eq!(scopes[0], Arm64EpilogScope(0x0000000D)); |
2897 | | |
2898 | | let codes = info.unwind_codes(0).unwrap(); |
2899 | | let codes = codes.collect::<Vec<_>>(); |
2900 | | assert_eq!(codes.len(), 4); |
2901 | | |
2902 | | assert_eq!( |
2903 | | codes[0].as_ref().unwrap(), |
2904 | | &Arm64UnwindCodeData { |
2905 | | offset: 0, |
2906 | | code: Arm64UnwindCode::SaveFpLrPreindexed { offset_bytes: 16 }, |
2907 | | len: 1, |
2908 | | } |
2909 | | ); |
2910 | | assert_eq!( |
2911 | | codes[1].as_ref().unwrap(), |
2912 | | &Arm64UnwindCodeData { |
2913 | | offset: 1, |
2914 | | code: Arm64UnwindCode::End, |
2915 | | len: 1, |
2916 | | } |
2917 | | ); |
2918 | | assert_eq!( |
2919 | | // padding |
2920 | | codes[2].as_ref().unwrap(), |
2921 | | &Arm64UnwindCodeData { |
2922 | | offset: 2, |
2923 | | code: Arm64UnwindCode::Nop, |
2924 | | len: 1, |
2925 | | } |
2926 | | ); |
2927 | | assert_eq!( |
2928 | | // padding |
2929 | | codes[3].as_ref().unwrap(), |
2930 | | &Arm64UnwindCodeData { |
2931 | | offset: 3, |
2932 | | code: Arm64UnwindCode::Nop, |
2933 | | len: 1, |
2934 | | } |
2935 | | ); |
2936 | | |
2937 | | assert_eq!( |
2938 | | info.exception_handler, |
2939 | | Some(Arm64ExceptionHandler { |
2940 | | rva: 0x00001E8C, |
2941 | | data: &0x000032A0u32.to_le_bytes() |
2942 | | }) |
2943 | | ); |
2944 | | } |
2945 | | |
2946 | | #[test] |
2947 | | fn unwind_code_alloc_large_and_custom_stack() { |
2948 | | // Test alloc_l (0xE0), pac_sign_lr (0xFC), custom stack opcodes, and end |
2949 | | #[rustfmt::skip] |
2950 | | const CODES: &[u8] = &[ |
2951 | | 0xE0, 0x00, 0x01, 0x00, // alloc_l: imm = 0x000100, size = 256 * 16 = 4096 |
2952 | | 0xFC, // pac_sign_lr |
2953 | | 0xE8, // MSFT_OP_TRAP_FRAME |
2954 | | 0xE4, // end |
2955 | | 0xE3, // nop (padding) |
2956 | | ]; |
2957 | | |
2958 | | let iter = Arm64UnwindCodeIterator::new(CODES, 0).unwrap(); |
2959 | | let codes: Vec<_> = iter.map(|r| r.unwrap()).collect(); |
2960 | | assert_eq!(codes.len(), 5); |
2961 | | assert_eq!( |
2962 | | codes[0].code, |
2963 | | Arm64UnwindCode::AllocLarge { size_bytes: 4096 } |
2964 | | ); |
2965 | | assert_eq!(codes[0].len, 4); |
2966 | | assert_eq!(codes[1].code, Arm64UnwindCode::PacSignLr); |
2967 | | assert_eq!( |
2968 | | codes[2].code, |
2969 | | Arm64UnwindCode::CustomStack(Arm64CustomStackCase::TrapFrame) |
2970 | | ); |
2971 | | assert_eq!(codes[3].code, Arm64UnwindCode::End); |
2972 | | assert_eq!(codes[4].code, Arm64UnwindCode::Nop); |
2973 | | } |
2974 | | |
2975 | | #[test] |
2976 | | fn unwind_code_reserved_opcodes() { |
2977 | | // Test reserved multi-byte opcodes |
2978 | | #[rustfmt::skip] |
2979 | | const CODES: &[u8] = &[ |
2980 | | 0xF8, 0xAB, // reserved 2-byte |
2981 | | 0xF9, 0x12, 0x34, // reserved 3-byte |
2982 | | 0xE4, // end |
2983 | | 0xE3, 0xE3, // padding |
2984 | | ]; |
2985 | | |
2986 | | let iter = Arm64UnwindCodeIterator::new(CODES, 0).unwrap(); |
2987 | | let codes: Vec<_> = iter.map(|r| r.unwrap()).collect(); |
2988 | | assert_eq!(codes.len(), 5); |
2989 | | assert_eq!( |
2990 | | codes[0].code, |
2991 | | Arm64UnwindCode::Reserved { |
2992 | | opcode: 0xF8, |
2993 | | data: 0xAB, |
2994 | | data_len: 1 |
2995 | | } |
2996 | | ); |
2997 | | assert_eq!(codes[0].len, 2); |
2998 | | assert_eq!( |
2999 | | codes[1].code, |
3000 | | Arm64UnwindCode::Reserved { |
3001 | | opcode: 0xF9, |
3002 | | data: 0x1234, |
3003 | | data_len: 2 |
3004 | | } |
3005 | | ); |
3006 | | assert_eq!(codes[1].len, 3); |
3007 | | assert_eq!(codes[2].code, Arm64UnwindCode::End); |
3008 | | } |
3009 | | |
3010 | | #[test] |
3011 | | fn unwind_code_freg_operations() { |
3012 | | // save_fregp (0xD8): b0=0xD8, b1=0x42 |
3013 | | // x = ((0xD8 & 0x01) << 2) | ((0x42 >> 6) & 0x03) = (0 << 2) | 1 = 1 |
3014 | | // z = 0x42 & 0x3f = 2 |
3015 | | // first_reg = 8 + 1 = 9, offset = 2 * 8 = 16 |
3016 | | // |
3017 | | // save_freg_x (0xDE): b1=0x65 |
3018 | | // x = (0x65 >> 5) & 0x07 = 3 |
3019 | | // z = 0x65 & 0x1f = 5 |
3020 | | // reg = 8 + 3 = 11, offset = (5+1) * 8 = 48 |
3021 | | #[rustfmt::skip] |
3022 | | const CODES: &[u8] = &[ |
3023 | | 0xD8, 0x42, // save_fregp |
3024 | | 0xDE, 0x65, // save_freg_x |
3025 | | ]; |
3026 | | |
3027 | | let iter = Arm64UnwindCodeIterator::new(CODES, 0).unwrap(); |
3028 | | let codes: Vec<_> = iter.map(|r| r.unwrap()).collect(); |
3029 | | assert_eq!(codes.len(), 2); |
3030 | | assert_eq!( |
3031 | | codes[0].code, |
3032 | | Arm64UnwindCode::SaveFRegPair { |
3033 | | first_reg: 9, |
3034 | | offset_bytes: 16 |
3035 | | } |
3036 | | ); |
3037 | | assert_eq!( |
3038 | | codes[1].code, |
3039 | | Arm64UnwindCode::SaveFRegPreindexed { |
3040 | | reg: 11, |
3041 | | offset_bytes: 48 |
3042 | | } |
3043 | | ); |
3044 | | } |
3045 | | |
3046 | | #[test] |
3047 | | fn unwind_code_iterator_start_index() { |
3048 | | // Verify that start_index correctly skips into the byte stream |
3049 | | #[rustfmt::skip] |
3050 | | const CODES: &[u8] = &[ |
3051 | | 0xE1, // set_fp (offset 0) |
3052 | | 0x87, // save_fplr_x (offset 1) |
3053 | | 0xE4, // end (offset 2) |
3054 | | 0xE3, // nop padding (offset 3) |
3055 | | ]; |
3056 | | |
3057 | | // Start at index 2 -> should see end + nop |
3058 | | let iter = Arm64UnwindCodeIterator::new(CODES, 2).unwrap(); |
3059 | | let codes: Vec<_> = iter.map(|r| r.unwrap()).collect(); |
3060 | | assert_eq!(codes.len(), 2); |
3061 | | assert_eq!(codes[0].code, Arm64UnwindCode::End); |
3062 | | assert_eq!(codes[0].offset, 2); |
3063 | | assert_eq!(codes[1].code, Arm64UnwindCode::Nop); |
3064 | | } |
3065 | | |
3066 | | #[test] |
3067 | | fn unwind_code_iterator_bad_length() { |
3068 | | // Length not a multiple of 4 |
3069 | | let result = Arm64UnwindCodeIterator::new(&[0xE4, 0xE3, 0xE3], 0); |
3070 | | assert!(result.is_err()); |
3071 | | } |
3072 | | |
3073 | | #[test] |
3074 | | fn xdata_no_exception_handler() { |
3075 | | // Header: func_len=4, ver=0, X=0, E=1, epilog_count=0, code_words=1 |
3076 | | // X=0 -> no exception data |
3077 | | // E=1 -> epilog in header |
3078 | | #[rustfmt::skip] |
3079 | | const XDATA: &[u8] = &[ |
3080 | | 0x10, 0x00, 0x20, 0x08, // header |
3081 | | 0x81, 0xE4, 0xE3, 0xE3, // unwind codes |
3082 | | ]; |
3083 | | |
3084 | | let info = Arm64UnwindInfo::parse(XDATA, 0).unwrap(); |
3085 | | assert!(!info.header.exception_data_present()); |
3086 | | assert!(info.header.epilog_in_header()); |
3087 | | assert!(info.exception_handler.is_none()); |
3088 | | assert!(info.epilog_scopes().is_none()); |
3089 | | } |
3090 | | |
3091 | | #[test] |
3092 | | fn xdata_size_without_handler() { |
3093 | | // Same as above — size should be header (4) + code_words (1*4) = 8 |
3094 | | #[rustfmt::skip] |
3095 | | const XDATA: &[u8] = &[ |
3096 | | 0x10, 0x00, 0x20, 0x08, // header |
3097 | | 0x81, 0xE4, 0xE3, 0xE3, // unwind codes |
3098 | | ]; |
3099 | | |
3100 | | assert_eq!(Arm64UnwindInfo::size(XDATA).unwrap(), 8); |
3101 | | } |
3102 | | |
3103 | | #[test] |
3104 | | fn arm64_len_vs_x64_len() { |
3105 | | // Verify len_arm64 gives different (correct) results from len |
3106 | | let ed = ExceptionData { |
3107 | | bytes: &[0u8; 64], |
3108 | | offset: 0, |
3109 | | size: 32, |
3110 | | file_alignment: 4, |
3111 | | }; |
3112 | | // 32 / 12 = 2 (x64) |
3113 | | assert_eq!(ed.len(), 2); |
3114 | | // 32 / 8 = 4 (arm64) |
3115 | | assert_eq!(ed.len_arm64(), 4); |
3116 | | } |
3117 | | } |
3118 | | } |