vmi_os_windows/unwind/amd64.rs
1//! x64 (AMD64) stack unwinding using .pdata / UNWIND_INFO.
2//!
3//! Implements the Windows x64 stack unwinding algorithm by reading
4//! RUNTIME_FUNCTION entries from the PE exception directory and
5//! processing UNWIND_INFO structures to recover caller register state.
6
7use vmi_arch_amd64::Registers;
8use vmi_core::{Va, VmiError, VmiState, driver::VmiRead};
9use zerocopy::{FromBytes, IntoBytes};
10
11use super::{Frame, Unwinder, Unwound};
12use crate::{
13 ArchAdapter, WindowsOs,
14 pe::{ImageRuntimeFunctionEntry, PeImage},
15};
16
17// Reference:
18// https://github.com/dotnet/coreclr/blob/a9f3fc16483eecfc47fb79c362811d870be02249/src/unwinder/amd64/unwinder_amd64.cpp
19// TODO: epilog unwind
20
21/// Process UNWIND_INFO entries, following chains.
22/// Limit chain depth to guard against corrupted/circular data.
23pub const UNWIND_CHAIN_LIMIT: u32 = 32;
24
25// Unwind operation codes.
26
27/// Push a nonvolatile integer register, decrementing RSP by 8.
28///
29/// The operation info is the number of the register. Because of the constraints
30/// on epilogs, `UWOP_PUSH_NONVOL` unwind codes must appear first in the prolog
31/// and correspondingly, last in the unwind code array. This relative ordering
32/// applies to all other unwind codes except `UWOP_PUSH_MACHFRAME`.
33pub const UWOP_PUSH_NONVOL: u8 = 0;
34
35/// Allocate a large-sized area on the stack.
36///
37/// There are two forms. If the operation info equals 0, then the size of the
38/// allocation divided by 8 is recorded in the next slot, allowing an allocation
39/// up to 512K - 8. If the operation info equals 1, then the unscaled size of
40/// the allocation is recorded in the next two slots in little-endian format,
41/// allowing allocations up to 4GB - 8.
42pub const UWOP_ALLOC_LARGE: u8 = 1;
43
44/// Allocate a small-sized area on the stack.
45///
46/// The size of the allocation is the operation info field * 8 + 8, allowing
47/// allocations from 8 to 128 bytes.
48///
49/// The unwind code for a stack allocation should always use the shortest
50/// possible encoding:
51///
52/// | Allocation Size | Unwind Code |
53/// | ------------------- | -------------------------------------- |
54/// | 8 to 128 bytes | `UWOP_ALLOC_SMALL` |
55/// | 136 to 512K-8 bytes | `UWOP_ALLOC_LARGE`, operation info = 0 |
56/// | 512K to 4G-8 bytes | `UWOP_ALLOC_LARGE`, operation info = 1 |
57pub const UWOP_ALLOC_SMALL: u8 = 2;
58
59/// Establish the frame pointer register by setting the register to some offset of the current RSP.
60///
61/// The offset is equal to the Frame Register offset (scaled) field in the
62/// `UNWIND_INFO * 16`, allowing offsets from 0 to 240. The use of an offset
63/// permits establishing a frame pointer that points to the middle of the fixed
64/// stack allocation, helping code density by allowing more accesses to use
65/// short instruction forms. The operation info field is reserved and shouldn't
66/// be used.
67pub const UWOP_SET_FPREG: u8 = 3;
68
69/// Save a nonvolatile integer register on the stack using a MOV instead of a PUSH.
70///
71/// This code is primarily used for *shrink-wrapping*, where a nonvolatile
72/// register is saved to the stack in a position that was previously allocated.
73/// The operation info is the number of the register. The scaled-by-8 stack
74/// offset is recorded in the next unwind operation code slot.
75pub const UWOP_SAVE_NONVOL: u8 = 4;
76
77/// Save a nonvolatile integer register on the stack with a long offset, using a MOV instead of a PUSH.
78///
79/// This code is primarily used for *shrink-wrapping*, where a nonvolatile
80/// register is saved to the stack in a position that was previously allocated.
81/// The operation info is the number of the register. The unscaled stack offset
82/// is recorded in the next two unwind operation code slots.
83pub const UWOP_SAVE_NONVOL_FAR: u8 = 5;
84
85/// Undocumented.
86pub const UWOP_EPILOG: u8 = 6;
87
88/// Undocumented.
89///
90/// Previously 64-bit `UWOP_SAVE_XMM_FAR`.
91pub const UWOP_SPARE_CODE: u8 = 7;
92
93/// Save all 128 bits of a nonvolatile XMM register on the stack.
94///
95/// The operation info is the number of the register. The scaled-by-16 stack
96/// offset is recorded in the next slot.
97pub const UWOP_SAVE_XMM128: u8 = 8;
98
99/// Save all 128 bits of a nonvolatile XMM register on the stack with a long offset.
100///
101/// The operation info is the number of the register. The unscaled stack offset
102/// is recorded in the next two slots.
103pub const UWOP_SAVE_XMM128_FAR: u8 = 9;
104
105/// Push a machine frame.
106///
107/// This unwind code is used to record the effect of a hardware interrupt or
108/// exception. There are two forms. If the operation info equals 0, one of these
109/// frames has been pushed on the stack:
110///
111/// | Location | Value |
112/// | -------- | ------- |
113/// | RSP+32 | SS |
114/// | RSP+24 | Old RSP |
115/// | RSP+16 | EFLAGS |
116/// | RSP+8 | CS |
117/// | RSP | RIP |
118///
119/// If the operation info equals 1, then one of these frames has been pushed:
120///
121/// | Location | Value |
122/// | -------- | ---------- |
123/// | RSP+40 | SS |
124/// | RSP+32 | Old RSP |
125/// | RSP+24 | EFLAGS |
126/// | RSP+16 | CS |
127/// | RSP+8 | RIP |
128/// | RSP | Error code |
129///
130/// This unwind code always appears in a dummy prolog, which is never actually
131/// executed, but instead appears before the real entry point of an interrupt
132/// routine, and exists only to provide a place to simulate the push of a
133/// machine frame. `UWOP_PUSH_MACHFRAME` records that simulation, which
134/// indicates the machine has conceptually done this operation:
135///
136/// 1. Pop RIP return address from top of stack into *Temp*
137/// 2. Push SS
138/// 3. Push old RSP
139/// 4. Push EFLAGS
140/// 5. Push CS
141/// 6. Push *Temp*
142/// 7. Push Error Code (if op info equals 1)
143///
144/// The simulated `UWOP_PUSH_MACHFRAME` operation decrements RSP by 40 (op info
145/// equals 0) or 48 (op info equals 1).
146pub const UWOP_PUSH_MACHFRAME: u8 = 10;
147
148// UNW_FLAG values.
149
150/// The function has no handler.
151pub const UNW_FLAG_NHANDLER: u8 = 0x0;
152
153/// The function has an exception handler that should be called.
154pub const UNW_FLAG_EHANDLER: u8 = 0x1;
155
156/// The function has a termination handler that should be called when unwinding an exception.
157pub const UNW_FLAG_UHANDLER: u8 = 0x2;
158
159/// The FunctionEntry member is the contents of a previous function table entry.
160pub const UNW_FLAG_CHAININFO: u8 = 0x4;
161
162/// Corresponds to the first 4 bytes of `UNWIND_INFO`.
163#[allow(non_camel_case_types, non_snake_case)]
164#[repr(C)]
165#[derive(Debug, Clone, Copy, FromBytes, IntoBytes)]
166pub struct UNWIND_INFO {
167 /// UCHAR Version : 3;
168 /// UCHAR Flags : 5;
169 pub VersionAndFlags: u8,
170
171 /// UCHAR SizeOfProlog;
172 pub SizeOfProlog: u8,
173
174 /// UCHAR CountOfCodes;
175 pub CountOfCodes: u8,
176
177 /// UCHAR FrameRegister : 4;
178 /// UCHAR FrameOffset : 4;
179 pub FrameRegisterAndOffset: u8,
180}
181
182impl UNWIND_INFO {
183 /// Returns the version field (bits 0-2).
184 pub fn version(&self) -> u8 {
185 self.VersionAndFlags & 0x07
186 }
187
188 /// Returns the flags field (bits 3-7).
189 pub fn flags(&self) -> u8 {
190 (self.VersionAndFlags >> 3) & 0x1f
191 }
192
193 /// Returns the size of the prolog in bytes.
194 pub fn size_of_prolog(&self) -> u8 {
195 self.SizeOfProlog
196 }
197
198 /// Returns the number of unwind codes.
199 pub fn count_of_codes(&self) -> u8 {
200 self.CountOfCodes
201 }
202
203 /// Returns the frame register index (bits 0-3), or 0 if none.
204 pub fn frame_register(&self) -> u8 {
205 self.FrameRegisterAndOffset & 0x0f
206 }
207
208 /// Returns the scaled frame offset (bits 4-7).
209 pub fn frame_offset(&self) -> u8 {
210 (self.FrameRegisterAndOffset >> 4) & 0x0f
211 }
212
213 /// Returns the byte offset past the unwind codes where a chained
214 /// `RUNTIME_FUNCTION` or handler data starts. The codes are
215 /// 2 bytes each, aligned to a 4-byte boundary.
216 pub fn codes_end_offset(&self) -> u32 {
217 let aligned = self.CountOfCodes as u32 + (self.CountOfCodes as u32 & 1);
218 4 + aligned * 2
219 }
220}
221
222// x64 register encoding indices, matching the CPU instruction encoding
223// (REX.B + reg field) and the UNWIND_CODE.OpInfo numbering. These are
224// used to index into the unwind context when processing unwind codes.
225
226/// Index of `AMD64_CONTEXT::Rax`.
227pub const REG_RAX: u8 = 0;
228
229/// Index of `AMD64_CONTEXT::Rcx`.
230pub const REG_RCX: u8 = 1;
231
232/// Index of `AMD64_CONTEXT::Rdx`.
233pub const REG_RDX: u8 = 2;
234
235/// Index of `AMD64_CONTEXT::Rbx`.
236pub const REG_RBX: u8 = 3;
237
238/// Index of `AMD64_CONTEXT::Rsp`.
239pub const REG_RSP: u8 = 4;
240
241/// Index of `AMD64_CONTEXT::Rbp`.
242pub const REG_RBP: u8 = 5;
243
244/// Index of `AMD64_CONTEXT::Rsi`.
245pub const REG_RSI: u8 = 6;
246
247/// Index of `AMD64_CONTEXT::Rdi`.
248pub const REG_RDI: u8 = 7;
249
250/// Index of `AMD64_CONTEXT::R8`.
251pub const REG_R8: u8 = 8;
252
253/// Index of `AMD64_CONTEXT::R9`.
254pub const REG_R9: u8 = 9;
255
256/// Index of `AMD64_CONTEXT::R10`.
257pub const REG_R10: u8 = 10;
258
259/// Index of `AMD64_CONTEXT::R11`.
260pub const REG_R11: u8 = 11;
261
262/// Index of `AMD64_CONTEXT::R12`.
263pub const REG_R12: u8 = 12;
264
265/// Index of `AMD64_CONTEXT::R13`.
266pub const REG_R13: u8 = 13;
267
268/// Index of `AMD64_CONTEXT::R14`.
269pub const REG_R14: u8 = 14;
270
271/// Index of `AMD64_CONTEXT::R15`.
272pub const REG_R15: u8 = 15;
273
274/// Unwind context for x64 (AMD64).
275///
276/// Holds the register state needed for stack unwinding, including
277/// the instruction pointer, stack pointer, and all callee-saved
278/// (non-volatile) general-purpose registers.
279#[derive(Debug, Clone)]
280pub struct UnwindContextAmd64 {
281 /// Instruction pointer (RIP).
282 pub rip: u64,
283 /// Stack pointer (RSP).
284 pub rsp: u64,
285 /// Base pointer (RBP) - callee-saved.
286 pub rbp: u64,
287 /// RBX - callee-saved.
288 pub rbx: u64,
289 /// RSI - callee-saved.
290 pub rsi: u64,
291 /// RDI - callee-saved.
292 pub rdi: u64,
293 /// R12 - callee-saved.
294 pub r12: u64,
295 /// R13 - callee-saved.
296 pub r13: u64,
297 /// R14 - callee-saved.
298 pub r14: u64,
299 /// R15 - callee-saved.
300 pub r15: u64,
301}
302
303impl From<&Registers> for UnwindContextAmd64 {
304 fn from(value: &Registers) -> Self {
305 Self {
306 rip: value.rip,
307 rsp: value.rsp,
308 rbp: value.rbp,
309 rbx: value.rbx,
310 rsi: value.rsi,
311 rdi: value.rdi,
312 r12: value.r12,
313 r13: value.r13,
314 r14: value.r14,
315 r15: value.r15,
316 }
317 }
318}
319
320impl UnwindContextAmd64 {
321 /// Returns the value of a register by its x64 encoding index.
322 pub fn get_register(&self, reg: u8) -> u64 {
323 match reg {
324 REG_RBX => self.rbx,
325 REG_RBP => self.rbp,
326 REG_RSI => self.rsi,
327 REG_RDI => self.rdi,
328 REG_R12 => self.r12,
329 REG_R13 => self.r13,
330 REG_R14 => self.r14,
331 REG_R15 => self.r15,
332 _ => 0,
333 }
334 }
335
336 /// Sets the value of a register by its x64 encoding index.
337 ///
338 /// Only updates non-volatile registers; volatile registers are ignored.
339 pub fn set_register(&mut self, reg: u8, value: u64) {
340 match reg {
341 REG_RBX => self.rbx = value,
342 REG_RBP => self.rbp = value,
343 REG_RSI => self.rsi = value,
344 REG_RDI => self.rdi = value,
345 REG_R12 => self.r12 = value,
346 REG_R13 => self.r13 = value,
347 REG_R14 => self.r14 = value,
348 REG_R15 => self.r15 = value,
349 _ => {} // volatile registers - ignore
350 }
351 }
352}
353
354/// x64 stack unwinder.
355///
356/// Implements stack unwinding for the Windows x64 ABI by reading
357/// .pdata RUNTIME_FUNCTION entries and processing UNWIND_INFO
358/// structures to recover the caller's register state.
359pub struct UnwinderAmd64;
360
361impl<Driver> Unwinder<Driver> for UnwinderAmd64
362where
363 Driver: VmiRead,
364 Driver::Architecture: ArchAdapter<Driver>,
365{
366 type Context = UnwindContextAmd64;
367
368 fn unwind(
369 &self,
370 vmi: &VmiState<WindowsOs<Driver>>,
371 image_base: Va,
372 image: &impl PeImage,
373 context: &mut UnwindContextAmd64,
374 ) -> Result<Unwound, VmiError> {
375 // Compute RVA of the current instruction pointer.
376 let rva = context.rip.saturating_sub(image_base.0) as u32;
377
378 // Look up RUNTIME_FUNCTION for this RVA.
379 let exception_dir = image.exception_directory()?;
380 let runtime_function = exception_dir.as_ref().and_then(|dir| dir.find(rva));
381
382 let entry = match runtime_function {
383 Some(entry) => entry,
384 None => {
385 // Leaf function: return address is at [RSP].
386 return unwind_leaf(vmi, context);
387 }
388 };
389
390 let begin_address = entry.begin_address;
391 let unwind_data_rva = entry.unwind_info_address_or_data;
392 let mut unwind_rva = unwind_data_rva;
393
394 // RIP offset within the function, for prolog detection.
395 // Only meaningful for the first (non-chained) UNWIND_INFO.
396 let rip_offset = rva.saturating_sub(begin_address);
397 let mut is_first = true;
398 let mut machine_frame = false;
399 let mut chain_count = 0u32;
400
401 loop {
402 let header = image.read_struct_at_rva::<UNWIND_INFO>(unwind_rva)?;
403
404 let flags = header.flags();
405 let size_of_prolog = header.size_of_prolog();
406 let count_of_codes = header.count_of_codes() as usize;
407 let frame_register = header.frame_register();
408 let frame_offset = header.frame_offset();
409
410 // Read unwind codes.
411 let codes_size = count_of_codes * 2;
412 let mut codes_data = vec![0u8; codes_size];
413 if codes_size > 0 {
414 image.read_at_rva(unwind_rva + 4, &mut codes_data)?;
415 }
416
417 // Compute FrameBase (per Microsoft spec, computed ONCE before
418 // processing codes). SAVE_NONVOL offsets are relative to this.
419 //
420 // `wrapping_sub`: when the prolog has not yet executed the LEA
421 // that establishes the frame register, the register holds an
422 // unrelated value that may be smaller than `frame_offset * 16`.
423 // Frame base is unread in that case (the gate at line below
424 // and the `code_offset > rip_offset` skip in the loop both
425 // exclude it), so an underflowing computation is harmless.
426 let frame_base = if frame_register != 0 {
427 context
428 .get_register(frame_register)
429 .wrapping_sub((frame_offset as u64) * 16)
430 }
431 else {
432 context.rsp
433 };
434
435 // If frame_register is set and we are past the prolog (or in
436 // a chained entry), restore RSP from the frame register.
437 if frame_register != 0 && (!is_first || rip_offset >= size_of_prolog as u32) {
438 context.rsp = frame_base;
439 }
440
441 // Process unwind codes.
442 let mut slot = 0;
443 while slot < count_of_codes {
444 let code_offset = codes_data[slot * 2];
445 let op_info = codes_data[slot * 2 + 1];
446 let op = op_info & 0x0f;
447 let info = (op_info >> 4) & 0x0f;
448
449 // In the first (non-chained) entry, if we are in the prolog,
450 // skip codes whose instructions have not yet executed.
451 if is_first
452 && (rip_offset < size_of_prolog as u32)
453 && (code_offset as u32 > rip_offset)
454 {
455 slot += slots_for_op(op, info);
456 continue;
457 }
458
459 match op {
460 UWOP_PUSH_NONVOL => {
461 let value = vmi.read_u64(Va(context.rsp))?;
462 context.set_register(info, value);
463 context.rsp += 8;
464 slot += 1;
465 }
466 UWOP_ALLOC_LARGE => {
467 if info == 0 {
468 let alloc = read_u16_from_codes(&codes_data, slot + 1) as u64 * 8;
469 context.rsp += alloc;
470 slot += 2;
471 }
472 else {
473 let alloc = read_u32_from_codes(&codes_data, slot + 1) as u64;
474 context.rsp += alloc;
475 slot += 3;
476 }
477 }
478 UWOP_ALLOC_SMALL => {
479 context.rsp += info as u64 * 8 + 8;
480 slot += 1;
481 }
482 UWOP_SET_FPREG => {
483 // Restore RSP from the frame register.
484 // frame_base was already computed with the same formula.
485 context.rsp = frame_base;
486 slot += 1;
487 }
488 UWOP_SAVE_NONVOL => {
489 // Offset is relative to FrameBase, NOT current RSP.
490 // `wrapping_add`: if frame_base underflowed above,
491 // the resulting address is bogus and the read will
492 // fail with VmiError instead of panicking.
493 let offset = read_u16_from_codes(&codes_data, slot + 1) as u64 * 8;
494 let value = vmi.read_u64(Va(frame_base.wrapping_add(offset)))?;
495 context.set_register(info, value);
496 slot += 2;
497 }
498 UWOP_SAVE_NONVOL_FAR => {
499 // Offset is relative to FrameBase, NOT current RSP.
500 // `wrapping_add`: see UWOP_SAVE_NONVOL above.
501 let offset = read_u32_from_codes(&codes_data, slot + 1) as u64;
502 let value = vmi.read_u64(Va(frame_base.wrapping_add(offset)))?;
503 context.set_register(info, value);
504 slot += 3;
505 }
506 UWOP_EPILOG => {
507 // v2 epilog descriptor - skip.
508 slot += 2;
509 }
510 UWOP_SPARE_CODE => {
511 // Reserved/undocumented opcode, consumes 3 slots.
512 slot += 3;
513 }
514 UWOP_SAVE_XMM128 => {
515 slot += 2;
516 }
517 UWOP_SAVE_XMM128_FAR => {
518 slot += 3;
519 }
520 UWOP_PUSH_MACHFRAME => {
521 if info == 1 {
522 context.rsp += 8; // skip error code
523 }
524 // Machine frame: RIP at [RSP], RSP at [RSP+24].
525 let new_rip = vmi.read_u64(Va(context.rsp))?;
526 let new_rsp = vmi.read_u64(Va(context.rsp + 24))?;
527 context.rip = new_rip;
528 context.rsp = new_rsp;
529 machine_frame = true;
530 slot += 1;
531 }
532 _ => {
533 tracing::warn!(op, info, "unknown unwind opcode");
534 slot += 1;
535 }
536 }
537 }
538
539 // If no chained info, we are done processing codes.
540 if flags & UNW_FLAG_CHAININFO == 0 {
541 break;
542 }
543
544 // Follow the chain: RUNTIME_FUNCTION is after the unwind codes.
545 let chain_rva = unwind_rva + header.codes_end_offset();
546
547 let mut chain_buf = [0u8; 12];
548 image.read_at_rva(chain_rva, &mut chain_buf)?;
549
550 unwind_rva = u32::from_le_bytes(chain_buf[8..12].try_into().unwrap());
551 is_first = false;
552 chain_count += 1;
553
554 if chain_count > UNWIND_CHAIN_LIMIT {
555 tracing::warn!("unwind chain limit exceeded");
556 break;
557 }
558 }
559
560 // If a machine frame was encountered, RIP/RSP are already set.
561 if machine_frame {
562 if context.rip == 0 {
563 return Ok(Unwound::MachineEnd);
564 }
565
566 // Read home space from the caller's RSP.
567 let params = read_params(vmi, Va(context.rsp));
568
569 return Ok(Unwound::Frame(Frame {
570 instruction_pointer: Va(context.rip),
571 stack_pointer: Va(context.rsp),
572 params,
573 machine_frame: true,
574 }));
575 }
576
577 // Pop return address from [RSP].
578 let return_addr = vmi.read_u64(Va(context.rsp))?;
579 context.rsp += 8;
580
581 if return_addr == 0 {
582 return Ok(Unwound::End);
583 }
584
585 context.rip = return_addr;
586
587 // Read home space from the caller's RSP (after popping the
588 // return address, RSP points to the start of the home space).
589 let params = read_params(vmi, Va(context.rsp));
590
591 Ok(Unwound::Frame(Frame {
592 instruction_pointer: Va(return_addr),
593 stack_pointer: Va(context.rsp),
594 params,
595 machine_frame: false,
596 }))
597 }
598}
599
600/// Reads the four parameter home-space values from the caller's stack.
601///
602/// Called after unwinding and popping the return address, so RSP
603/// points to the start of the home space (P1Home at RSP+0).
604/// Returns zeros for any unreadable slots.
605pub fn read_params<Driver>(vmi: &VmiState<WindowsOs<Driver>>, rsp: Va) -> [u64; 4]
606where
607 Driver: VmiRead,
608 Driver::Architecture: ArchAdapter<Driver>,
609{
610 // Params are informational only - don't abort the unwind on failure.
611 [
612 vmi.read_u64(rsp).unwrap_or(0),
613 vmi.read_u64(rsp + 8).unwrap_or(0),
614 vmi.read_u64(rsp + 16).unwrap_or(0),
615 vmi.read_u64(rsp + 24).unwrap_or(0),
616 ]
617}
618
619/// Unwinds a leaf function (no RUNTIME_FUNCTION entry).
620///
621/// For leaf functions, the return address is at `[RSP]` and the
622/// caller's RSP is `RSP + 8`. This is also useful as a fallback
623/// when .pdata is unavailable (e.g., file-backed pages missing
624/// from a crash dump).
625pub fn unwind_leaf<Driver>(
626 vmi: &VmiState<WindowsOs<Driver>>,
627 context: &mut UnwindContextAmd64,
628) -> Result<Unwound, VmiError>
629where
630 Driver: VmiRead,
631 Driver::Architecture: ArchAdapter<Driver>,
632{
633 let return_addr = vmi.read_u64(Va(context.rsp))?;
634 context.rsp += 8;
635
636 if return_addr == 0 {
637 return Ok(Unwound::End);
638 }
639
640 context.rip = return_addr;
641
642 // Read home space from the caller's RSP.
643 let params = read_params(vmi, Va(context.rsp));
644
645 Ok(Unwound::Frame(Frame {
646 instruction_pointer: Va(return_addr),
647 stack_pointer: Va(context.rsp),
648 params,
649 machine_frame: false,
650 }))
651}
652
653/// Resolves an RVA to the primary function's BeginAddress by following
654/// `UNW_FLAG_CHAININFO` chains in the exception directory.
655///
656/// MSVC can split a single function into non-contiguous chunks scattered
657/// across the binary (common with SEH and large functions). Each chunk
658/// has its own `RUNTIME_FUNCTION` entry in `.pdata`, with the chunk's
659/// `UNWIND_INFO` carrying the `UNW_FLAG_CHAININFO` flag and a pointer
660/// to the parent entry. This function follows that chain back to the
661/// primary (non-chained) entry whose `BeginAddress` is the real
662/// function start - the address that maps to the symbol in the PDB.
663///
664/// Returns `None` if the RVA is not covered by any `RUNTIME_FUNCTION`.
665pub fn resolve_primary_function(image: &impl PeImage, rva: u32) -> Result<Option<u32>, VmiError> {
666 let exception_dir = match image.exception_directory()? {
667 Some(exception_dir) => exception_dir,
668 None => return Ok(None),
669 };
670
671 let entry = match exception_dir.find(rva) {
672 Some(entry) => entry,
673 None => return Ok(None),
674 };
675
676 let mut begin_address = entry.begin_address;
677 let mut unwind_rva = entry.unwind_info_address_or_data;
678
679 for _ in 0..UNWIND_CHAIN_LIMIT {
680 let header = image.read_struct_at_rva::<UNWIND_INFO>(unwind_rva)?;
681
682 if header.flags() & UNW_FLAG_CHAININFO == 0 {
683 break;
684 }
685
686 let entry = image.read_struct_at_rva::<ImageRuntimeFunctionEntry>(
687 unwind_rva + header.codes_end_offset(),
688 )?;
689
690 begin_address = entry.begin_address;
691 unwind_rva = entry.unwind_info_address_or_data;
692 }
693
694 Ok(Some(begin_address))
695}
696
697/// Returns the number of code slots consumed by an unwind operation.
698fn slots_for_op(op: u8, info: u8) -> usize {
699 match op {
700 UWOP_PUSH_NONVOL => 1,
701 UWOP_ALLOC_LARGE => {
702 if info == 0 {
703 2
704 }
705 else {
706 3
707 }
708 }
709 UWOP_ALLOC_SMALL => 1,
710 UWOP_SET_FPREG => 1,
711 UWOP_SAVE_NONVOL => 2,
712 UWOP_SAVE_NONVOL_FAR => 3,
713 UWOP_SPARE_CODE => 3,
714 UWOP_SAVE_XMM128 => 2,
715 UWOP_SAVE_XMM128_FAR => 3,
716 UWOP_PUSH_MACHFRAME => 1,
717 _ => 1,
718 }
719}
720
721/// Reads a u16 from the unwind code data at the given slot index.
722///
723/// Each slot is 2 bytes. The u16 is stored in little-endian format.
724fn read_u16_from_codes(data: &[u8], slot: usize) -> u16 {
725 let offset = slot * 2;
726 if offset + 2 <= data.len() {
727 u16::from_le_bytes([data[offset], data[offset + 1]])
728 }
729 else {
730 0
731 }
732}
733
734/// Reads a u32 from the unwind code data at the given slot index.
735///
736/// The u32 spans two consecutive slots (4 bytes) in little-endian format.
737fn read_u32_from_codes(data: &[u8], slot: usize) -> u32 {
738 let offset = slot * 2;
739 if offset + 4 <= data.len() {
740 u32::from_le_bytes([
741 data[offset],
742 data[offset + 1],
743 data[offset + 2],
744 data[offset + 3],
745 ])
746 }
747 else {
748 0
749 }
750}