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 offset_of_expected_autibsp: u8,
46 },
47 CouldBePartOfAuthTailCall {
48 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 if (word >> 22) & 0b1011111011 == 0b1010100011 && (word >> 5) & 0b11111 == 31 {
158 return true;
159 }
160 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 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 return true;
214 }
215
216 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 if word == 0xd65f03c0 || word == 0xd65f0fff {
241 return EpilogueInstructionType::VeryLikelyPartOfEpilogue;
242 }
243 if word == 0xd50323ff {
245 return EpilogueInstructionType::CouldBePartOfAuthTailCall {
246 offset_of_expected_autibsp: 0,
247 };
248 }
249 if word == 0xca1e07d0 {
251 return EpilogueInstructionType::CouldBePartOfAuthTailCall {
252 offset_of_expected_autibsp: 4,
253 };
254 }
255 if word == 0xb6f00050 {
257 return EpilogueInstructionType::CouldBePartOfAuthTailCall {
258 offset_of_expected_autibsp: 8,
259 };
260 }
261 if word == 0xd4388e20 {
263 return EpilogueInstructionType::CouldBePartOfAuthTailCall {
264 offset_of_expected_autibsp: 12,
265 };
266 }
267 if (word >> 26) == 0b000101 || word & 0xff_ff_fc_1f == 0xd6_1f_00_00 {
269 return EpilogueInstructionType::CouldBeTailCall {
272 offset_of_expected_autibsp: 16,
273 };
274 }
275 if (word >> 23) & 0b111000111 == 0b110000101 && word & 0b11111 == 16 {
277 return EpilogueInstructionType::CouldBePartOfAuthTailCall {
278 offset_of_expected_autibsp: 16,
279 };
280 }
281 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 let writeback_bits = (word >> 23) & 0b11;
291 if writeback_bits == 0b00 {
292 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 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 if word == 0xd65f03c0 || word == 0xd65f0fff {
317 return EpilogueStepResult::FoundReturn;
318 }
319 if word == 0xd50323ff {
321 return EpilogueStepResult::CouldBeAuthTailCall;
322 }
323 if (word >> 26) == 0b000101 {
325 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 let writeback_bits = (word >> 23) & 0b11;
341 if writeback_bits == 0b00 {
342 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; 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 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 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 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 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 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 #[test]
571 fn test_epilogue_with_register_tail_call() {
572 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 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 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}