framehop/aarch64/instruction_analysis/
epilogue.rs

1use super::super::unwind_rule::UnwindRuleAarch64;
2
3struct EpilogueDetectorAarch64 {
4    sp_offset: i32,
5    fp_offset_from_initial_sp: Option<i32>,
6    lr_offset_from_initial_sp: Option<i32>,
7}
8
9enum EpilogueStepResult {
10    NeedMore,
11    FoundBodyInstruction(UnexpectedInstructionType),
12    FoundReturn,
13    FoundTailCall,
14    CouldBeAuthTailCall,
15}
16
17#[derive(Clone, Debug, PartialEq, Eq)]
18enum EpilogueResult {
19    ProbablyStillInBody(UnexpectedInstructionType),
20    ReachedFunctionEndWithoutReturn,
21    FoundReturnOrTailCall {
22        sp_offset: i32,
23        fp_offset_from_initial_sp: Option<i32>,
24        lr_offset_from_initial_sp: Option<i32>,
25    },
26}
27
28#[derive(Clone, Debug, PartialEq, Eq)]
29enum UnexpectedInstructionType {
30    LoadOfWrongSize,
31    LoadReferenceRegisterNotSp,
32    AddSubNotOperatingOnSp,
33    AutibspNotFollowedByExpectedTailCall,
34    BranchWithUnadjustedStackPointer,
35    Unknown,
36}
37
38#[derive(Clone, Debug, PartialEq, Eq)]
39enum EpilogueInstructionType {
40    NotExpectedInEpilogue,
41    CouldBeTailCall {
42        /// If auth tail call, the offset in bytes where the autibsp would be.
43        /// If regular tail call, we just check if the previous instruction
44        /// adjusts the stack pointer.
45        offset_of_expected_autibsp: u8,
46    },
47    CouldBePartOfAuthTailCall {
48        /// In bytes
49        offset_of_expected_autibsp: u8,
50    },
51    VeryLikelyPartOfEpilogue,
52}
53
54impl EpilogueDetectorAarch64 {
55    pub fn new() -> Self {
56        Self {
57            sp_offset: 0,
58            fp_offset_from_initial_sp: None,
59            lr_offset_from_initial_sp: None,
60        }
61    }
62
63    pub fn analyze_slice(&mut self, function_bytes: &[u8], pc_offset: usize) -> EpilogueResult {
64        let mut bytes = &function_bytes[pc_offset..];
65        if bytes.len() < 4 {
66            return EpilogueResult::ReachedFunctionEndWithoutReturn;
67        }
68        let mut word = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
69        bytes = &bytes[4..];
70        match Self::analyze_instruction(word) {
71            EpilogueInstructionType::NotExpectedInEpilogue => {
72                return EpilogueResult::ProbablyStillInBody(UnexpectedInstructionType::Unknown)
73            }
74            EpilogueInstructionType::CouldBeTailCall {
75                offset_of_expected_autibsp,
76            } => {
77                if pc_offset >= offset_of_expected_autibsp as usize {
78                    let auth_tail_call_bytes =
79                        &function_bytes[pc_offset - offset_of_expected_autibsp as usize..];
80                    if auth_tail_call_bytes[0..4] == [0xff, 0x23, 0x03, 0xd5]
81                        && Self::is_auth_tail_call(&auth_tail_call_bytes[4..])
82                    {
83                        return EpilogueResult::FoundReturnOrTailCall {
84                            sp_offset: 0,
85                            fp_offset_from_initial_sp: None,
86                            lr_offset_from_initial_sp: None,
87                        };
88                    }
89                }
90                if pc_offset >= 4 {
91                    let prev_b = &function_bytes[pc_offset - 4..pc_offset];
92                    let prev_word =
93                        u32::from_le_bytes([prev_b[0], prev_b[1], prev_b[2], prev_b[3]]);
94                    if Self::instruction_adjusts_stack_pointer(prev_word) {
95                        return EpilogueResult::FoundReturnOrTailCall {
96                            sp_offset: 0,
97                            fp_offset_from_initial_sp: None,
98                            lr_offset_from_initial_sp: None,
99                        };
100                    }
101                }
102                return EpilogueResult::ProbablyStillInBody(UnexpectedInstructionType::Unknown);
103            }
104            EpilogueInstructionType::CouldBePartOfAuthTailCall {
105                offset_of_expected_autibsp,
106            } => {
107                if pc_offset >= offset_of_expected_autibsp as usize {
108                    let auth_tail_call_bytes =
109                        &function_bytes[pc_offset - offset_of_expected_autibsp as usize..];
110                    if auth_tail_call_bytes[0..4] == [0xff, 0x23, 0x03, 0xd5]
111                        && Self::is_auth_tail_call(&auth_tail_call_bytes[4..])
112                    {
113                        return EpilogueResult::FoundReturnOrTailCall {
114                            sp_offset: 0,
115                            fp_offset_from_initial_sp: None,
116                            lr_offset_from_initial_sp: None,
117                        };
118                    }
119                }
120                return EpilogueResult::ProbablyStillInBody(UnexpectedInstructionType::Unknown);
121            }
122            EpilogueInstructionType::VeryLikelyPartOfEpilogue => {}
123        }
124
125        loop {
126            match self.step_instruction(word) {
127                EpilogueStepResult::NeedMore => {
128                    if bytes.len() < 4 {
129                        return EpilogueResult::ReachedFunctionEndWithoutReturn;
130                    }
131                    word = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
132                    bytes = &bytes[4..];
133                    continue;
134                }
135                EpilogueStepResult::FoundBodyInstruction(uit) => {
136                    return EpilogueResult::ProbablyStillInBody(uit);
137                }
138                EpilogueStepResult::FoundReturn | EpilogueStepResult::FoundTailCall => {}
139                EpilogueStepResult::CouldBeAuthTailCall => {
140                    if !Self::is_auth_tail_call(bytes) {
141                        return EpilogueResult::ProbablyStillInBody(
142                            UnexpectedInstructionType::AutibspNotFollowedByExpectedTailCall,
143                        );
144                    }
145                }
146            }
147            return EpilogueResult::FoundReturnOrTailCall {
148                sp_offset: self.sp_offset,
149                fp_offset_from_initial_sp: self.fp_offset_from_initial_sp,
150                lr_offset_from_initial_sp: self.lr_offset_from_initial_sp,
151            };
152        }
153    }
154
155    fn instruction_adjusts_stack_pointer(word: u32) -> bool {
156        // Detect load from sp-relative offset with writeback.
157        if (word >> 22) & 0b1011111011 == 0b1010100011 && (word >> 5) & 0b11111 == 31 {
158            return true;
159        }
160        // Detect sub sp, sp, 0xXXXX
161        if (word >> 23) & 0b111111111 == 0b100100010
162            && word & 0b11111 == 31
163            && (word >> 5) & 0b11111 == 31
164        {
165            return true;
166        }
167        false
168    }
169
170    fn is_auth_tail_call(bytes_after_autibsp: &[u8]) -> bool {
171        // libsystem_malloc.dylib contains over a hundred of these.
172        // At the end of the function, after restoring the registers from the stack,
173        // there's an autibsp instruction, followed by some check (not sure what it
174        // does), and then a tail call. These instructions should all be counted as
175        // part of the epilogue; returning at this point is just "follow lr" instead
176        // of "use the frame pointer".
177        //
178        // 180139058 ff 23 03 d5      autibsp
179        //
180        // 18013905c d0 07 1e ca      eor        x16, lr, lr, lsl #1
181        // 180139060 50 00 f0 b6      tbz        x16, 0x3e, $+0x8
182        // 180139064 20 8e 38 d4      brk        #0xc471              ; "breakpoint trap"
183        //
184        // and then a tail call, of one of these forms:
185        //
186        // 180139068 13 00 00 14      b          some_outside_function
187        //
188        // 18013a364 f0 36 88 d2      mov        x16, #0xXXXX
189        // 18013a368 70 08 1f d7      braa       xX, x16
190        //
191
192        if bytes_after_autibsp.len() < 16 {
193            return false;
194        }
195        let eor_tbz_brk = &bytes_after_autibsp[..12];
196        if eor_tbz_brk
197            != [
198                0xd0, 0x07, 0x1e, 0xca, 0x50, 0x00, 0xf0, 0xb6, 0x20, 0x8e, 0x38, 0xd4,
199            ]
200        {
201            return false;
202        }
203
204        let first_tail_call_instruction_opcode = u32::from_le_bytes([
205            bytes_after_autibsp[12],
206            bytes_after_autibsp[13],
207            bytes_after_autibsp[14],
208            bytes_after_autibsp[15],
209        ]);
210        let bits_26_to_32 = first_tail_call_instruction_opcode >> 26;
211        if bits_26_to_32 == 0b000101 {
212            // This is a `b` instruction. We've found the tail call.
213            return true;
214        }
215
216        // If we get here, it's either not a recognized instruction sequence,
217        // or the tail call is of the form `mov x16, #0xXXXX`, `braa xX, x16`.
218        if bytes_after_autibsp.len() < 20 {
219            return false;
220        }
221
222        let bits_23_to_32 = first_tail_call_instruction_opcode >> 23;
223        let is_64_mov = (bits_23_to_32 & 0b111000111) == 0b110000101;
224        let result_reg = first_tail_call_instruction_opcode & 0b11111;
225        if !is_64_mov || result_reg != 16 {
226            return false;
227        }
228
229        let braa_opcode = u32::from_le_bytes([
230            bytes_after_autibsp[16],
231            bytes_after_autibsp[17],
232            bytes_after_autibsp[18],
233            bytes_after_autibsp[19],
234        ]);
235        (braa_opcode & 0xff_ff_fc_00) == 0xd7_1f_08_00 && (braa_opcode & 0b11111) == 16
236    }
237
238    pub fn analyze_instruction(word: u32) -> EpilogueInstructionType {
239        // Detect ret and retab
240        if word == 0xd65f03c0 || word == 0xd65f0fff {
241            return EpilogueInstructionType::VeryLikelyPartOfEpilogue;
242        }
243        // Detect autibsp
244        if word == 0xd50323ff {
245            return EpilogueInstructionType::CouldBePartOfAuthTailCall {
246                offset_of_expected_autibsp: 0,
247            };
248        }
249        // Detect `eor x16, lr, lr, lsl #1`
250        if word == 0xca1e07d0 {
251            return EpilogueInstructionType::CouldBePartOfAuthTailCall {
252                offset_of_expected_autibsp: 4,
253            };
254        }
255        // Detect `tbz x16, 0x3e, $+0x8`
256        if word == 0xb6f00050 {
257            return EpilogueInstructionType::CouldBePartOfAuthTailCall {
258                offset_of_expected_autibsp: 8,
259            };
260        }
261        // Detect `brk #0xc471`
262        if word == 0xd4388e20 {
263            return EpilogueInstructionType::CouldBePartOfAuthTailCall {
264                offset_of_expected_autibsp: 12,
265            };
266        }
267        // Detect `b` and `br xX`
268        if (word >> 26) == 0b000101 || word & 0xff_ff_fc_1f == 0xd6_1f_00_00 {
269            // This could be a branch with a target inside this function, or
270            // a tail call outside of this function.
271            return EpilogueInstructionType::CouldBeTailCall {
272                offset_of_expected_autibsp: 16,
273            };
274        }
275        // Detect `mov x16, #0xXXXX`
276        if (word >> 23) & 0b111000111 == 0b110000101 && word & 0b11111 == 16 {
277            return EpilogueInstructionType::CouldBePartOfAuthTailCall {
278                offset_of_expected_autibsp: 16,
279            };
280        }
281        // Detect `braa xX, x16`
282        if word & 0xff_ff_fc_00 == 0xd7_1f_08_00 && word & 0b11111 == 16 {
283            return EpilogueInstructionType::CouldBePartOfAuthTailCall {
284                offset_of_expected_autibsp: 20,
285            };
286        }
287        if (word >> 22) & 0b1011111001 == 0b1010100001 {
288            // Section C3.3, Loads and stores.
289            // but only loads that are commonly seen in prologues / epilogues (bits 29 and 31 are set)
290            let writeback_bits = (word >> 23) & 0b11;
291            if writeback_bits == 0b00 {
292                // Not 64-bit load.
293                return EpilogueInstructionType::NotExpectedInEpilogue;
294            }
295            let reference_reg = ((word >> 5) & 0b11111) as u16;
296            if reference_reg != 31 {
297                return EpilogueInstructionType::NotExpectedInEpilogue;
298            }
299            return EpilogueInstructionType::VeryLikelyPartOfEpilogue;
300        }
301        if (word >> 23) & 0b111111111 == 0b100100010 {
302            // Section C3.4, Data processing - immediate
303            // unsigned add imm, size class X (8 bytes)
304            let result_reg = (word & 0b11111) as u16;
305            let input_reg = ((word >> 5) & 0b11111) as u16;
306            if result_reg != 31 || input_reg != 31 {
307                return EpilogueInstructionType::NotExpectedInEpilogue;
308            }
309            return EpilogueInstructionType::VeryLikelyPartOfEpilogue;
310        }
311        EpilogueInstructionType::NotExpectedInEpilogue
312    }
313
314    pub fn step_instruction(&mut self, word: u32) -> EpilogueStepResult {
315        // Detect ret and retab
316        if word == 0xd65f03c0 || word == 0xd65f0fff {
317            return EpilogueStepResult::FoundReturn;
318        }
319        // Detect autibsp
320        if word == 0xd50323ff {
321            return EpilogueStepResult::CouldBeAuthTailCall;
322        }
323        // Detect b
324        if (word >> 26) == 0b000101 {
325            // This could be a branch with a target inside this function, or
326            // a tail call outside of this function.
327            // Let's use the following heuristic: If this instruction is followed
328            // by valid epilogue instructions which adjusted the stack pointer, then
329            // we treat it as a tail call.
330            if self.sp_offset != 0 {
331                return EpilogueStepResult::FoundTailCall;
332            }
333            return EpilogueStepResult::FoundBodyInstruction(
334                UnexpectedInstructionType::BranchWithUnadjustedStackPointer,
335            );
336        }
337        if (word >> 22) & 0b1011111001 == 0b1010100001 {
338            // Section C3.3, Loads and stores.
339            // but only those that are commonly seen in prologues / epilogues (bits 29 and 31 are set)
340            let writeback_bits = (word >> 23) & 0b11;
341            if writeback_bits == 0b00 {
342                // Not 64-bit load/store.
343                return EpilogueStepResult::FoundBodyInstruction(
344                    UnexpectedInstructionType::LoadOfWrongSize,
345                );
346            }
347            let reference_reg = ((word >> 5) & 0b11111) as u16;
348            if reference_reg != 31 {
349                return EpilogueStepResult::FoundBodyInstruction(
350                    UnexpectedInstructionType::LoadReferenceRegisterNotSp,
351                );
352            }
353            let is_preindexed_writeback = writeback_bits == 0b11; // TODO: are there preindexed loads? What do they mean?
354            let is_postindexed_writeback = writeback_bits == 0b01;
355            let imm7 = (((((word >> 15) & 0b1111111) as i16) << 9) >> 6) as i32;
356            let reg_loc = if is_postindexed_writeback {
357                self.sp_offset
358            } else {
359                self.sp_offset + imm7
360            };
361            let pair_reg_1 = (word & 0b11111) as u16;
362            if pair_reg_1 == 29 {
363                self.fp_offset_from_initial_sp = Some(reg_loc);
364            } else if pair_reg_1 == 30 {
365                self.lr_offset_from_initial_sp = Some(reg_loc);
366            }
367            let pair_reg_2 = ((word >> 10) & 0b11111) as u16;
368            if pair_reg_2 == 29 {
369                self.fp_offset_from_initial_sp = Some(reg_loc + 8);
370            } else if pair_reg_2 == 30 {
371                self.lr_offset_from_initial_sp = Some(reg_loc + 8);
372            }
373            if is_preindexed_writeback || is_postindexed_writeback {
374                self.sp_offset += imm7;
375            }
376            return EpilogueStepResult::NeedMore;
377        }
378        if (word >> 23) & 0b111111111 == 0b100100010 {
379            // Section C3.4, Data processing - immediate
380            // unsigned add imm, size class X (8 bytes)
381            let result_reg = (word & 0b11111) as u16;
382            let input_reg = ((word >> 5) & 0b11111) as u16;
383            if result_reg != 31 || input_reg != 31 {
384                return EpilogueStepResult::FoundBodyInstruction(
385                    UnexpectedInstructionType::AddSubNotOperatingOnSp,
386                );
387            }
388            let mut imm12 = ((word >> 10) & 0b111111111111) as i32;
389            let shift_immediate_by_12 = ((word >> 22) & 0b1) == 0b1;
390            if shift_immediate_by_12 {
391                imm12 <<= 12
392            }
393            self.sp_offset += imm12;
394            return EpilogueStepResult::NeedMore;
395        }
396        EpilogueStepResult::FoundBodyInstruction(UnexpectedInstructionType::Unknown)
397    }
398}
399
400pub fn unwind_rule_from_detected_epilogue(
401    bytes: &[u8],
402    pc_offset: usize,
403) -> Option<UnwindRuleAarch64> {
404    let mut detector = EpilogueDetectorAarch64::new();
405    match detector.analyze_slice(bytes, pc_offset) {
406        EpilogueResult::ProbablyStillInBody(_)
407        | EpilogueResult::ReachedFunctionEndWithoutReturn => None,
408        EpilogueResult::FoundReturnOrTailCall {
409            sp_offset,
410            fp_offset_from_initial_sp,
411            lr_offset_from_initial_sp,
412        } => {
413            let sp_offset_by_16 = u16::try_from(sp_offset / 16).ok()?;
414            let rule = match (fp_offset_from_initial_sp, lr_offset_from_initial_sp) {
415                (None, None) if sp_offset_by_16 == 0 => UnwindRuleAarch64::NoOp,
416                (None, None) => UnwindRuleAarch64::OffsetSp { sp_offset_by_16 },
417                (None, Some(lr_offset)) => UnwindRuleAarch64::OffsetSpAndRestoreLr {
418                    sp_offset_by_16,
419                    lr_storage_offset_from_sp_by_8: i16::try_from(lr_offset / 8).ok()?,
420                },
421                (Some(_), None) => return None,
422                (Some(fp_offset), Some(lr_offset)) => {
423                    UnwindRuleAarch64::OffsetSpAndRestoreFpAndLr {
424                        sp_offset_by_16,
425                        fp_storage_offset_from_sp_by_8: i16::try_from(fp_offset / 8).ok()?,
426                        lr_storage_offset_from_sp_by_8: i16::try_from(lr_offset / 8).ok()?,
427                    }
428                }
429            };
430            Some(rule)
431        }
432    }
433}
434
435#[cfg(test)]
436mod test {
437    use super::*;
438
439    #[test]
440    fn test_epilogue_1() {
441        // 1000e0d18 fd 7b 44 a9     ldp        fp, lr, [sp, #0x40]
442        // 1000e0d1c f4 4f 43 a9     ldp        x20, x19, [sp, #0x30]
443        // 1000e0d20 f6 57 42 a9     ldp        x22, x21, [sp, #0x20]
444        // 1000e0d24 ff 43 01 91     add        sp, sp, #0x50
445        // 1000e0d28 c0 03 5f d6     ret
446
447        let bytes = &[
448            0xfd, 0x7b, 0x44, 0xa9, 0xf4, 0x4f, 0x43, 0xa9, 0xf6, 0x57, 0x42, 0xa9, 0xff, 0x43,
449            0x01, 0x91, 0xc0, 0x03, 0x5f, 0xd6,
450        ];
451        assert_eq!(
452            unwind_rule_from_detected_epilogue(bytes, 0),
453            Some(UnwindRuleAarch64::OffsetSpAndRestoreFpAndLr {
454                sp_offset_by_16: 5,
455                fp_storage_offset_from_sp_by_8: 8,
456                lr_storage_offset_from_sp_by_8: 9,
457            })
458        );
459        assert_eq!(
460            unwind_rule_from_detected_epilogue(bytes, 4),
461            Some(UnwindRuleAarch64::OffsetSp { sp_offset_by_16: 5 })
462        );
463        assert_eq!(
464            unwind_rule_from_detected_epilogue(bytes, 8),
465            Some(UnwindRuleAarch64::OffsetSp { sp_offset_by_16: 5 })
466        );
467        assert_eq!(
468            unwind_rule_from_detected_epilogue(bytes, 12),
469            Some(UnwindRuleAarch64::OffsetSp { sp_offset_by_16: 5 })
470        );
471        assert_eq!(
472            unwind_rule_from_detected_epilogue(bytes, 16),
473            Some(UnwindRuleAarch64::NoOp)
474        );
475        assert_eq!(unwind_rule_from_detected_epilogue(bytes, 20), None);
476    }
477
478    #[test]
479    fn test_epilogue_with_retab() {
480        //         _malloc_zone_realloc epilogue
481        // 18012466c e0 03 16 aa     mov        x0,x22
482        // 180124670 fd 7b 43 a9     ldp        x29=>local_10,x30,[sp, #0x30]
483        // 180124674 f4 4f 42 a9     ldp        x20,x19,[sp, #local_20]
484        // 180124678 f6 57 41 a9     ldp        x22,x21,[sp, #local_30]
485        // 18012467c f8 5f c4 a8     ldp        x24,x23,[sp], #0x40
486        // 180124680 ff 0f 5f d6     retab
487        // 180124684 a0 01 80 52     mov        w0,#0xd
488        // 180124688 20 60 a6 72     movk       w0,#0x3301, LSL #16
489
490        let bytes = &[
491            0xe0, 0x03, 0x16, 0xaa, 0xfd, 0x7b, 0x43, 0xa9, 0xf4, 0x4f, 0x42, 0xa9, 0xf6, 0x57,
492            0x41, 0xa9, 0xf8, 0x5f, 0xc4, 0xa8, 0xff, 0x0f, 0x5f, 0xd6, 0xa0, 0x01, 0x80, 0x52,
493            0x20, 0x60, 0xa6, 0x72,
494        ];
495        assert_eq!(unwind_rule_from_detected_epilogue(bytes, 0), None);
496        assert_eq!(
497            unwind_rule_from_detected_epilogue(bytes, 4),
498            Some(UnwindRuleAarch64::OffsetSpAndRestoreFpAndLr {
499                sp_offset_by_16: 4,
500                fp_storage_offset_from_sp_by_8: 6,
501                lr_storage_offset_from_sp_by_8: 7
502            })
503        );
504        assert_eq!(
505            unwind_rule_from_detected_epilogue(bytes, 8),
506            Some(UnwindRuleAarch64::OffsetSp { sp_offset_by_16: 4 })
507        );
508        assert_eq!(
509            unwind_rule_from_detected_epilogue(bytes, 12),
510            Some(UnwindRuleAarch64::OffsetSp { sp_offset_by_16: 4 })
511        );
512        assert_eq!(
513            unwind_rule_from_detected_epilogue(bytes, 16),
514            Some(UnwindRuleAarch64::OffsetSp { sp_offset_by_16: 4 })
515        );
516        assert_eq!(
517            unwind_rule_from_detected_epilogue(bytes, 20),
518            Some(UnwindRuleAarch64::NoOp)
519        );
520        assert_eq!(unwind_rule_from_detected_epilogue(bytes, 24), None);
521    }
522
523    #[test]
524    fn test_epilogue_with_retab_2() {
525        // _tiny_free_list_add_ptr:
526        // ...
527        // 18013e114 28 01 00 79     strh       w8, [x9]
528        // 18013e118 fd 7b c1 a8     ldp        fp, lr, [sp], #0x10
529        // 18013e11c ff 0f 5f d6     retab
530        // 18013e120 e2 03 08 aa     mov        x2, x8
531        // 18013e124 38 76 00 94     bl         _free_list_checksum_botch
532        // ...
533
534        let bytes = &[
535            0x28, 0x01, 0x00, 0x79, 0xfd, 0x7b, 0xc1, 0xa8, 0xff, 0x0f, 0x5f, 0xd6, 0xe2, 0x03,
536            0x08, 0xaa, 0x38, 0x76, 0x00, 0x94,
537        ];
538        assert_eq!(unwind_rule_from_detected_epilogue(bytes, 0), None);
539        assert_eq!(
540            unwind_rule_from_detected_epilogue(bytes, 4),
541            Some(UnwindRuleAarch64::OffsetSpAndRestoreFpAndLr {
542                sp_offset_by_16: 1,
543                fp_storage_offset_from_sp_by_8: 0,
544                lr_storage_offset_from_sp_by_8: 1
545            })
546        );
547        assert_eq!(
548            unwind_rule_from_detected_epilogue(bytes, 8),
549            Some(UnwindRuleAarch64::NoOp)
550        );
551        assert_eq!(unwind_rule_from_detected_epilogue(bytes, 12), None);
552        assert_eq!(unwind_rule_from_detected_epilogue(bytes, 16), None);
553    }
554
555    #[test]
556    fn test_epilogue_with_regular_tail_call() {
557        // (in rustup) __ZN126_$LT$$LT$toml..value..Value$u20$as$u20$serde..de..Deserialize$GT$..deserialize..ValueVisitor$u20$as$u20$serde..de..Visitor$GT$9visit_map17h0afd4b269ef00eebE
558        // ...
559        // 1002566b4 fc 6f c6 a8     ldp        x28, x27, [sp], #0x60
560        // 1002566b8 bc ba ff 17     b          __ZN4core3ptr41drop_in_place$LT$toml..de..MapVisitor$GT$17hd4556de1a4edab42E
561        // ...
562        let bytes = &[0xfc, 0x6f, 0xc6, 0xa8, 0xbc, 0xba, 0xff, 0x17];
563        assert_eq!(
564            unwind_rule_from_detected_epilogue(bytes, 0),
565            Some(UnwindRuleAarch64::OffsetSp { sp_offset_by_16: 6 })
566        );
567    }
568
569    // This test fails at the moment.
570    #[test]
571    fn test_epilogue_with_register_tail_call() {
572        // This test requires lookbehind in the epilogue detection.
573        // We want to detect the `br` as a tail call. We should do this
574        // based on the fact that the previous instruction adjusted the
575        // stack pointer.
576        //
577        // (in rustup) __ZN4core3fmt9Formatter3pad17h3f40041e7f99f180E
578        // ...
579        // 1000500bc fa 67 c5 a8     ldp        x26, x25, [sp], #0x50
580        // 1000500c0 60 00 1f d6     br         x3
581        // ...
582        let bytes = &[0xfa, 0x67, 0xc5, 0xa8, 0x60, 0x00, 0x1f, 0xd6];
583        assert_eq!(
584            unwind_rule_from_detected_epilogue(bytes, 4),
585            Some(UnwindRuleAarch64::NoOp)
586        );
587    }
588
589    #[test]
590    fn test_epilogue_with_auth_tail_call() {
591        // _nanov2_free_definite_size
592        // ...
593        // 180139048 e1 03 13 aa      mov        x1, x19
594        // 18013904c fd 7b 42 a9      ldp        fp, lr, [sp, #0x20]
595        // 180139050 f4 4f 41 a9      ldp        x20, x19, [sp, #0x10]
596        // 180139054 f6 57 c3 a8      ldp        x22, x21, [sp], #0x30
597        // 180139058 ff 23 03 d5      autibsp
598        // 18013905c d0 07 1e ca      eor        x16, lr, lr, lsl #1
599        // 180139060 50 00 f0 b6      tbz        x16, 0x3e, loc_180139068
600        // 180139064 20 8e 38 d4      brk        #0xc471
601        //                       loc_180139068:
602        // 180139068 13 00 00 14      b          _nanov2_free_to_block
603        //                       loc_18013906c:
604        // 18013906c a0 16 78 f9      ldr        x0, [x21, #0x7028]
605        // 180139070 03 3c 40 f9      ldr        x3, [x0, #0x78]
606        // ...
607        let bytes = &[
608            0xe1, 0x03, 0x13, 0xaa, 0xfd, 0x7b, 0x42, 0xa9, 0xf4, 0x4f, 0x41, 0xa9, 0xf6, 0x57,
609            0xc3, 0xa8, 0xff, 0x23, 0x03, 0xd5, 0xd0, 0x07, 0x1e, 0xca, 0x50, 0x00, 0xf0, 0xb6,
610            0x20, 0x8e, 0x38, 0xd4, 0x13, 0x00, 0x00, 0x14, 0xa0, 0x16, 0x78, 0xf9, 0x03, 0x3c,
611            0x40, 0xf9,
612        ];
613        assert_eq!(unwind_rule_from_detected_epilogue(bytes, 0), None);
614        assert_eq!(
615            unwind_rule_from_detected_epilogue(bytes, 4),
616            Some(UnwindRuleAarch64::OffsetSpAndRestoreFpAndLr {
617                sp_offset_by_16: 3,
618                fp_storage_offset_from_sp_by_8: 4,
619                lr_storage_offset_from_sp_by_8: 5
620            })
621        );
622        assert_eq!(
623            unwind_rule_from_detected_epilogue(bytes, 8),
624            Some(UnwindRuleAarch64::OffsetSp { sp_offset_by_16: 3 })
625        );
626        assert_eq!(
627            unwind_rule_from_detected_epilogue(bytes, 12),
628            Some(UnwindRuleAarch64::OffsetSp { sp_offset_by_16: 3 })
629        );
630        assert_eq!(
631            unwind_rule_from_detected_epilogue(bytes, 16),
632            Some(UnwindRuleAarch64::NoOp)
633        );
634        assert_eq!(
635            unwind_rule_from_detected_epilogue(bytes, 20),
636            Some(UnwindRuleAarch64::NoOp)
637        );
638        assert_eq!(
639            unwind_rule_from_detected_epilogue(bytes, 24),
640            Some(UnwindRuleAarch64::NoOp)
641        );
642        assert_eq!(
643            unwind_rule_from_detected_epilogue(bytes, 28),
644            Some(UnwindRuleAarch64::NoOp)
645        );
646    }
647
648    #[test]
649    fn test_epilogue_with_auth_tail_call_2() {
650        // _malloc_zone_claimed_addres
651        // ...
652        // 1801457ac e1 03 13 aa     mov        x1, x19
653        // 1801457b0 fd 7b 41 a9     ldp        fp, lr, [sp, #0x10]
654        // 1801457b4 f4 4f c2 a8     ldp        x20, x19, [sp], #0x20
655        // 1801457b8 ff 23 03 d5     autibsp
656        // 1801457bc d0 07 1e ca     eor        x16, lr, lr, lsl #1
657        // 1801457c0 50 00 f0 b6     tbz        x16, 0x3e, loc_1801457c8
658        // 1801457c4 20 8e 38 d4     brk        #0xc471
659        //                       loc_1801457c8:
660        // 1801457c8 f0 77 9c d2     mov        x16, #0xe3bf
661        // 1801457cc 50 08 1f d7     braa       x2, x16
662        // ...
663        let bytes = &[
664            0xe1, 0x03, 0x13, 0xaa, 0xfd, 0x7b, 0x41, 0xa9, 0xf4, 0x4f, 0xc2, 0xa8, 0xff, 0x23,
665            0x03, 0xd5, 0xd0, 0x07, 0x1e, 0xca, 0x50, 0x00, 0xf0, 0xb6, 0x20, 0x8e, 0x38, 0xd4,
666            0xf0, 0x77, 0x9c, 0xd2, 0x50, 0x08, 0x1f, 0xd7,
667        ];
668        assert_eq!(unwind_rule_from_detected_epilogue(bytes, 0), None);
669        assert_eq!(
670            unwind_rule_from_detected_epilogue(bytes, 4),
671            Some(UnwindRuleAarch64::OffsetSpAndRestoreFpAndLr {
672                sp_offset_by_16: 2,
673                fp_storage_offset_from_sp_by_8: 2,
674                lr_storage_offset_from_sp_by_8: 3
675            })
676        );
677        assert_eq!(
678            unwind_rule_from_detected_epilogue(bytes, 8),
679            Some(UnwindRuleAarch64::OffsetSp { sp_offset_by_16: 2 })
680        );
681        assert_eq!(
682            unwind_rule_from_detected_epilogue(bytes, 12),
683            Some(UnwindRuleAarch64::NoOp)
684        );
685        assert_eq!(
686            unwind_rule_from_detected_epilogue(bytes, 16),
687            Some(UnwindRuleAarch64::NoOp)
688        );
689        assert_eq!(
690            unwind_rule_from_detected_epilogue(bytes, 20),
691            Some(UnwindRuleAarch64::NoOp)
692        );
693        assert_eq!(
694            unwind_rule_from_detected_epilogue(bytes, 24),
695            Some(UnwindRuleAarch64::NoOp)
696        );
697        assert_eq!(
698            unwind_rule_from_detected_epilogue(bytes, 28),
699            Some(UnwindRuleAarch64::NoOp)
700        );
701    }
702}