Skip to main content

crashrustler/unwind/
frame_pointer.rs

1//! Frame pointer chain walking (fallback unwinder).
2//!
3//! When neither compact unwind nor DWARF CFI data is available,
4//! falls back to walking the frame pointer chain.
5
6use super::registers::RegisterContext;
7use super::{MemoryReader, UnwindError};
8
9/// Attempts to unwind one frame by following the frame pointer chain.
10///
11/// On both ARM64 and x86_64:
12/// - `[FP+0]` → previous frame pointer
13/// - `[FP+8]` → return address
14///
15/// Updates `regs` in place with the new FP, SP, and PC.
16/// Clears volatile registers.
17///
18/// Returns `Ok(true)` if a frame was found, `Ok(false)` if at the bottom.
19pub fn step_frame_pointer(
20    reader: &dyn MemoryReader,
21    regs: &mut RegisterContext,
22    is_64_bit: bool,
23) -> Result<bool, UnwindError> {
24    let fp = regs.fp().ok_or(UnwindError::BrokenFrameChain)?;
25
26    if fp == 0 {
27        return Ok(false);
28    }
29
30    // Validate alignment
31    if is_64_bit && fp % 8 != 0 {
32        return Err(UnwindError::BrokenFrameChain);
33    }
34
35    let ptr_size = if is_64_bit { 8 } else { 4 };
36
37    // Read previous FP and return address
38    let prev_fp = reader
39        .read_pointer(fp, is_64_bit)
40        .ok_or(UnwindError::MemoryReadFailed(fp))?;
41    let return_addr = reader
42        .read_pointer(fp + ptr_size, is_64_bit)
43        .ok_or(UnwindError::MemoryReadFailed(fp + ptr_size))?;
44
45    // Validate: FP should grow (stack grows down, so new FP > old FP for caller)
46    // Exception: FP=0 means bottom of stack
47    if prev_fp != 0 && prev_fp <= fp {
48        return Err(UnwindError::BrokenFrameChain);
49    }
50
51    if return_addr == 0 {
52        return Ok(false);
53    }
54
55    // Clear volatile registers
56    regs.clear_volatile();
57
58    // Update registers
59    regs.set_fp(prev_fp);
60    regs.set_sp(fp + 2 * ptr_size); // SP was FP + 2 pointers (FP + return addr)
61    regs.set_pc(return_addr);
62
63    Ok(true)
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69    use crate::types::CpuType;
70    use crate::unwind::SliceMemoryReader;
71
72    /// Builds a synthetic stack with chained FP/LR frames for ARM64.
73    /// Frame layout: [prev_fp, return_addr] at each frame pointer.
74    fn build_arm64_stack() -> (SliceMemoryReader, RegisterContext) {
75        let base = 0x7000_0000u64;
76        let mut data = vec![0u8; 0x1000];
77
78        // Frame 0 at offset 0x800: FP=base+0x800
79        //   [FP+0] = prev_fp (base+0x900)
80        //   [FP+8] = return_addr (0xDEAD_0001)
81        let frame0_off = 0x800usize;
82        data[frame0_off..frame0_off + 8].copy_from_slice(&(base + 0x900).to_le_bytes());
83        data[frame0_off + 8..frame0_off + 16].copy_from_slice(&0xDEAD_0001u64.to_le_bytes());
84
85        // Frame 1 at offset 0x900: FP=base+0x900
86        //   [FP+0] = prev_fp (base+0xA00)
87        //   [FP+8] = return_addr (0xDEAD_0002)
88        let frame1_off = 0x900usize;
89        data[frame1_off..frame1_off + 8].copy_from_slice(&(base + 0xA00).to_le_bytes());
90        data[frame1_off + 8..frame1_off + 16].copy_from_slice(&0xDEAD_0002u64.to_le_bytes());
91
92        // Frame 2 at offset 0xA00: FP=base+0xA00 (bottom)
93        //   [FP+0] = 0 (no more frames)
94        //   [FP+8] = 0xDEAD_0003
95        let frame2_off = 0xA00usize;
96        data[frame2_off..frame2_off + 8].copy_from_slice(&0u64.to_le_bytes());
97        data[frame2_off + 8..frame2_off + 16].copy_from_slice(&0xDEAD_0003u64.to_le_bytes());
98
99        let reader = SliceMemoryReader {
100            data,
101            base_address: base,
102        };
103
104        let mut regs = RegisterContext::new(CpuType::ARM64);
105        regs.set_fp(base + frame0_off as u64);
106        regs.set_sp(base + frame0_off as u64 - 16);
107        regs.set_pc(0xCAFE_0000);
108
109        (reader, regs)
110    }
111
112    #[test]
113    fn walk_three_frames() {
114        let (reader, mut regs) = build_arm64_stack();
115        let base = 0x7000_0000u64;
116
117        // Step 1: frame 0 → frame 1
118        assert!(step_frame_pointer(&reader, &mut regs, true).unwrap());
119        assert_eq!(regs.fp(), Some(base + 0x900));
120        assert_eq!(regs.pc(), Some(0xDEAD_0001));
121
122        // Step 2: frame 1 → frame 2
123        assert!(step_frame_pointer(&reader, &mut regs, true).unwrap());
124        assert_eq!(regs.fp(), Some(base + 0xA00));
125        assert_eq!(regs.pc(), Some(0xDEAD_0002));
126
127        // Step 3: frame 2 → sets FP=0, PC=0xDEAD_0003 (prev_fp=0 is bottom marker)
128        assert!(step_frame_pointer(&reader, &mut regs, true).unwrap());
129        assert_eq!(regs.pc(), Some(0xDEAD_0003));
130        assert_eq!(regs.fp(), Some(0));
131
132        // Step 4: FP=0, so we're at the bottom
133        assert!(!step_frame_pointer(&reader, &mut regs, true).unwrap());
134    }
135
136    #[test]
137    fn zero_fp_returns_false() {
138        let reader = SliceMemoryReader {
139            data: vec![0u8; 64],
140            base_address: 0x1000,
141        };
142        let mut regs = RegisterContext::new(CpuType::ARM64);
143        regs.set_fp(0);
144        regs.set_pc(0x1234);
145
146        assert!(!step_frame_pointer(&reader, &mut regs, true).unwrap());
147    }
148
149    #[test]
150    fn misaligned_fp_returns_error() {
151        let reader = SliceMemoryReader {
152            data: vec![0u8; 64],
153            base_address: 0x1000,
154        };
155        let mut regs = RegisterContext::new(CpuType::ARM64);
156        regs.set_fp(0x1003); // misaligned
157
158        assert_eq!(
159            step_frame_pointer(&reader, &mut regs, true),
160            Err(UnwindError::BrokenFrameChain)
161        );
162    }
163
164    #[test]
165    fn fp_not_growing_returns_error() {
166        let base = 0x2000u64;
167        let mut data = vec![0u8; 0x100];
168        // FP at 0x2080 points to prev_fp=0x2040 (going backward = error)
169        data[0x80..0x88].copy_from_slice(&(base + 0x40).to_le_bytes());
170        data[0x88..0x90].copy_from_slice(&0xAAAAu64.to_le_bytes());
171
172        let reader = SliceMemoryReader {
173            data,
174            base_address: base,
175        };
176        let mut regs = RegisterContext::new(CpuType::ARM64);
177        regs.set_fp(base + 0x80);
178
179        assert_eq!(
180            step_frame_pointer(&reader, &mut regs, true),
181            Err(UnwindError::BrokenFrameChain)
182        );
183    }
184
185    #[test]
186    fn volatile_regs_cleared_after_step() {
187        let (reader, mut regs) = build_arm64_stack();
188        regs.set(0, 0x1111); // x0 — volatile
189
190        assert!(step_frame_pointer(&reader, &mut regs, true).unwrap());
191        assert!(regs.get(0).is_none()); // volatile cleared
192    }
193}