framehop/x86_64/unwind_rule.rs
1use super::register_ordering;
2use super::unwindregs::{Reg, UnwindRegsX86_64};
3use crate::add_signed::checked_add_signed;
4use crate::error::Error;
5use crate::unwind_rule::UnwindRule;
6use arrayvec::ArrayVec;
7
8/// For all of these: return address is *(new_sp - 8)
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub enum UnwindRuleX86_64 {
11 EndOfStack,
12 /// (sp, bp) = (sp + 8, bp)
13 JustReturn,
14 /// (sp, bp) = if is_first_frame (sp + 8, bp) else (bp + 16, *bp)
15 JustReturnIfFirstFrameOtherwiseFp,
16 /// (sp, bp) = (sp + 8x, bp)
17 OffsetSp {
18 sp_offset_by_8: u16,
19 },
20 /// (sp, bp) = (sp + 8x, *(sp + 8y))
21 OffsetSpAndRestoreBp {
22 sp_offset_by_8: u16,
23 bp_storage_offset_from_sp_by_8: i16,
24 },
25 /// (sp, bp) = (bp + 16, *bp)
26 UseFramePointer,
27 /// (sp, ...) = (sp + 8 * (offset + register count), ... popped according to encoded ordering)
28 /// This supports the common case of pushed callee-saved registers followed by a stack
29 /// allocation. Up to 8 registers can be stored, which covers all callee-saved registers (aside
30 /// from RSP which is implicit).
31 ///
32 /// The registers are stored in a separate compressed ordering to facilitate restoring register
33 /// values if desired. If not for this we could simply store the total offset.
34 OffsetSpAndPopRegisters {
35 /// The additional stack pointer offset to undo before popping the registers, divided by 8 bytes.
36 sp_offset_by_8: u16,
37 /// The number of registers to pop from the stack.
38 register_count: u8,
39 /// An encoded ordering of the callee-save registers to pop from the stack, see register_ordering.
40 encoded_registers_to_pop: u16,
41 },
42}
43
44pub enum OffsetOrPop {
45 None,
46 OffsetBy8(u16),
47 Pop(Reg),
48}
49
50impl UnwindRuleX86_64 {
51 /// Get the rule which represents the given operations, if possible.
52 pub fn for_sequence_of_offset_or_pop<I, T>(iter: I) -> Option<Self>
53 where
54 I: Iterator<Item = T>,
55 T: Into<OffsetOrPop>,
56 {
57 let mut iter = iter.map(Into::into).peekable();
58 let sp_offset_by_8 = if let Some(&OffsetOrPop::OffsetBy8(offset)) = iter.peek() {
59 iter.next();
60 offset
61 } else {
62 0
63 };
64
65 let mut regs = ArrayVec::<Reg, 8>::new();
66 for i in iter {
67 if let OffsetOrPop::Pop(reg) = i {
68 // If try_push errors we've exceeded the number of supported registers: there's no
69 // way to encode these operations as an unwind rule.
70 regs.try_push(reg).ok()?;
71 } else {
72 return None;
73 }
74 }
75
76 if regs.is_empty() && sp_offset_by_8 == 0 {
77 Some(Self::JustReturn)
78 } else {
79 let (register_count, encoded_registers_to_pop) = register_ordering::encode(®s)?;
80 Some(Self::OffsetSpAndPopRegisters {
81 sp_offset_by_8,
82 register_count,
83 encoded_registers_to_pop,
84 })
85 }
86 }
87}
88
89impl UnwindRule for UnwindRuleX86_64 {
90 type UnwindRegs = UnwindRegsX86_64;
91
92 fn rule_for_stub_functions() -> Self {
93 UnwindRuleX86_64::JustReturn
94 }
95 fn rule_for_function_start() -> Self {
96 UnwindRuleX86_64::JustReturn
97 }
98 fn fallback_rule() -> Self {
99 UnwindRuleX86_64::UseFramePointer
100 }
101
102 fn exec<F>(
103 self,
104 is_first_frame: bool,
105 regs: &mut UnwindRegsX86_64,
106 read_stack: &mut F,
107 ) -> Result<Option<u64>, Error>
108 where
109 F: FnMut(u64) -> Result<u64, ()>,
110 {
111 let sp = regs.sp();
112 let (new_sp, new_bp) = match self {
113 UnwindRuleX86_64::EndOfStack => return Ok(None),
114 UnwindRuleX86_64::JustReturn => {
115 let new_sp = sp.checked_add(8).ok_or(Error::IntegerOverflow)?;
116 (new_sp, regs.bp())
117 }
118 UnwindRuleX86_64::JustReturnIfFirstFrameOtherwiseFp => {
119 if is_first_frame {
120 let new_sp = sp.checked_add(8).ok_or(Error::IntegerOverflow)?;
121 (new_sp, regs.bp())
122 } else {
123 let sp = regs.sp();
124 let bp = regs.bp();
125 let new_sp = bp.checked_add(16).ok_or(Error::IntegerOverflow)?;
126 if new_sp <= sp {
127 return Err(Error::FramepointerUnwindingMovedBackwards);
128 }
129 let new_bp = read_stack(bp).map_err(|_| Error::CouldNotReadStack(bp))?;
130 (new_sp, new_bp)
131 }
132 }
133 UnwindRuleX86_64::OffsetSp { sp_offset_by_8 } => {
134 let sp_offset = u64::from(sp_offset_by_8) * 8;
135 let new_sp = sp.checked_add(sp_offset).ok_or(Error::IntegerOverflow)?;
136 (new_sp, regs.bp())
137 }
138 UnwindRuleX86_64::OffsetSpAndRestoreBp {
139 sp_offset_by_8,
140 bp_storage_offset_from_sp_by_8,
141 } => {
142 let sp_offset = u64::from(sp_offset_by_8) * 8;
143 let new_sp = sp.checked_add(sp_offset).ok_or(Error::IntegerOverflow)?;
144 let bp_storage_offset_from_sp = i64::from(bp_storage_offset_from_sp_by_8) * 8;
145 let bp_location = checked_add_signed(sp, bp_storage_offset_from_sp)
146 .ok_or(Error::IntegerOverflow)?;
147 let new_bp = match read_stack(bp_location) {
148 Ok(new_bp) => new_bp,
149 Err(()) if is_first_frame && bp_location < sp => {
150 // Ignore errors when reading beyond the stack pointer in the first frame.
151 // These negative offsets are sometimes seen in x86_64 epilogues, where
152 // a bunch of registers are popped one after the other, and the compiler
153 // doesn't always set the already-popped register to "unchanged" (because
154 // doing so would take up extra space in the dwarf information).
155 // read_stack may legitimately refuse to read beyond the stack pointer,
156 // for example when the stack bytes are coming from a linux perf event
157 // sample record, where the ustack bytes are copied starting from sp.
158 regs.bp()
159 }
160 Err(()) => return Err(Error::CouldNotReadStack(bp_location)),
161 };
162 (new_sp, new_bp)
163 }
164 UnwindRuleX86_64::UseFramePointer => {
165 // Do a frame pointer stack walk. Code that is compiled with frame pointers
166 // has the following function prologues and epilogues:
167 //
168 // Function prologue:
169 // pushq %rbp
170 // movq %rsp, %rbp
171 //
172 // Function epilogue:
173 // popq %rbp
174 // ret
175 //
176 // Functions are called with callq; callq pushes the return address onto the stack.
177 // When a function reaches its end, ret pops the return address from the stack and jumps to it.
178 // So when a function is called, we have the following stack layout:
179 //
180 // [... rest of the stack]
181 // ^ rsp ^ rbp
182 // callq some_function
183 // [return address] [... rest of the stack]
184 // ^ rsp ^ rbp
185 // pushq %rbp
186 // [caller's frame pointer] [return address] [... rest of the stack]
187 // ^ rsp ^ rbp
188 // movq %rsp, %rbp
189 // [caller's frame pointer] [return address] [... rest of the stack]
190 // ^ rsp, rbp
191 // <other instructions>
192 // [... more stack] [caller's frame pointer] [return address] [... rest of the stack]
193 // ^ rsp ^ rbp
194 //
195 // So: *rbp is the caller's frame pointer, and *(rbp + 8) is the return address.
196 //
197 // Or, in other words, the following linked list is built up on the stack:
198 // #[repr(C)]
199 // struct CallFrameInfo {
200 // previous: *const CallFrameInfo,
201 // return_address: *const c_void,
202 // }
203 // and rbp is a *const CallFrameInfo.
204 let sp = regs.sp();
205 let bp = regs.bp();
206 if bp == 0 {
207 return Ok(None);
208 }
209 let new_sp = bp.checked_add(16).ok_or(Error::IntegerOverflow)?;
210 if new_sp <= sp {
211 return Err(Error::FramepointerUnwindingMovedBackwards);
212 }
213 let new_bp = read_stack(bp).map_err(|_| Error::CouldNotReadStack(bp))?;
214 // new_bp is the caller's bp. If the caller uses frame pointers, then bp should be
215 // a valid frame pointer and we could do a coherency check on new_bp to make sure
216 // it's moving in the right direction. But if the caller is using bp as a general
217 // purpose register, then any value (including zero) would be a valid value.
218 // At this point we don't know how the caller uses bp, so we leave new_bp unchecked.
219
220 (new_sp, new_bp)
221 }
222 UnwindRuleX86_64::OffsetSpAndPopRegisters {
223 sp_offset_by_8,
224 register_count,
225 encoded_registers_to_pop,
226 } => {
227 let sp = regs.sp();
228 let mut sp = sp
229 .checked_add(sp_offset_by_8 as u64 * 8)
230 .ok_or(Error::IntegerOverflow)?;
231 for reg in register_ordering::decode(register_count, encoded_registers_to_pop) {
232 let value = read_stack(sp).map_err(|_| Error::CouldNotReadStack(sp))?;
233 sp = sp.checked_add(8).ok_or(Error::IntegerOverflow)?;
234 regs.set(reg, value);
235 }
236 (sp.checked_add(8).ok_or(Error::IntegerOverflow)?, regs.bp())
237 }
238 };
239 let return_address =
240 read_stack(new_sp - 8).map_err(|_| Error::CouldNotReadStack(new_sp - 8))?;
241 if return_address == 0 {
242 return Ok(None);
243 }
244 if new_sp == sp && return_address == regs.ip() {
245 return Err(Error::DidNotAdvance);
246 }
247 regs.set_ip(return_address);
248 regs.set_sp(new_sp);
249 regs.set_bp(new_bp);
250 Ok(Some(return_address))
251 }
252}
253
254#[cfg(test)]
255mod test {
256 use super::*;
257
258 #[test]
259 fn test_basic() {
260 let stack = [
261 1, 2, 0x100300, 4, 0x40, 0x100200, 5, 6, 0x70, 0x100100, 7, 8, 9, 10, 0x0, 0x0,
262 ];
263 let mut read_stack = |addr| Ok(stack[(addr / 8) as usize]);
264 let mut regs = UnwindRegsX86_64::new(0x100400, 0x10, 0x20);
265 let res =
266 UnwindRuleX86_64::OffsetSp { sp_offset_by_8: 1 }.exec(true, &mut regs, &mut read_stack);
267 assert_eq!(res, Ok(Some(0x100300)));
268 assert_eq!(regs.ip(), 0x100300);
269 assert_eq!(regs.sp(), 0x18);
270 assert_eq!(regs.bp(), 0x20);
271 let res = UnwindRuleX86_64::UseFramePointer.exec(true, &mut regs, &mut read_stack);
272 assert_eq!(res, Ok(Some(0x100200)));
273 assert_eq!(regs.ip(), 0x100200);
274 assert_eq!(regs.sp(), 0x30);
275 assert_eq!(regs.bp(), 0x40);
276 let res = UnwindRuleX86_64::UseFramePointer.exec(false, &mut regs, &mut read_stack);
277 assert_eq!(res, Ok(Some(0x100100)));
278 assert_eq!(regs.ip(), 0x100100);
279 assert_eq!(regs.sp(), 0x50);
280 assert_eq!(regs.bp(), 0x70);
281 let res = UnwindRuleX86_64::UseFramePointer.exec(false, &mut regs, &mut read_stack);
282 assert_eq!(res, Ok(None));
283 }
284
285 #[test]
286 fn test_overflow() {
287 // This test makes sure that debug builds don't panic when trying to use frame pointer
288 // unwinding on code that was using the bp register as a general-purpose register and
289 // storing -1 in it. -1 is u64::MAX, so an unchecked add panics in debug builds.
290 let stack = [
291 1, 2, 0x100300, 4, 0x40, 0x100200, 5, 6, 0x70, 0x100100, 7, 8, 9, 10, 0x0, 0x0,
292 ];
293 let mut read_stack = |addr| Ok(stack[(addr / 8) as usize]);
294 let mut regs = UnwindRegsX86_64::new(0x100400, u64::MAX / 8 * 8, u64::MAX);
295 let res = UnwindRuleX86_64::JustReturn.exec(true, &mut regs, &mut read_stack);
296 assert_eq!(res, Err(Error::IntegerOverflow));
297 let res =
298 UnwindRuleX86_64::OffsetSp { sp_offset_by_8: 1 }.exec(true, &mut regs, &mut read_stack);
299 assert_eq!(res, Err(Error::IntegerOverflow));
300 let res = UnwindRuleX86_64::OffsetSpAndRestoreBp {
301 sp_offset_by_8: 1,
302 bp_storage_offset_from_sp_by_8: 2,
303 }
304 .exec(true, &mut regs, &mut read_stack);
305 assert_eq!(res, Err(Error::IntegerOverflow));
306 let res = UnwindRuleX86_64::UseFramePointer.exec(true, &mut regs, &mut read_stack);
307 assert_eq!(res, Err(Error::IntegerOverflow));
308 }
309}