| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/profiler/chrome_unwinder_android_v2.h" |
| |
| #include "base/profiler/chrome_unwind_info_android.h" |
| #include "base/test/gtest_util.h" |
| #include "build/build_config.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace base { |
| |
| TEST(ChromeAndroidUnwindInstructionTest, |
| TestSmallStackPointerIncrementMinValue) { |
| RegisterContext thread_context = {}; |
| const uint8_t instruction = 0b00000000; |
| const uint8_t* current_instruction = &instruction; |
| thread_context.arm_sp = 0x10000000; |
| bool pc_was_updated = false; |
| ASSERT_EQ(ExecuteUnwindInstruction(current_instruction, pc_was_updated, |
| &thread_context), |
| UnwindInstructionResult::kInstructionPending); |
| EXPECT_FALSE(pc_was_updated); |
| ASSERT_EQ(current_instruction, &instruction + 1); |
| EXPECT_EQ(0x10000004ul, thread_context.arm_sp); |
| } |
| |
| TEST(ChromeAndroidUnwindInstructionTest, |
| TestSmallStackPointerIncrementMidValue) { |
| // xxxxxx = 4; vsp = vsp + (4 << 2) + 4 = vsp + 16 + 4 = vsp + 0x14. |
| RegisterContext thread_context = {}; |
| const uint8_t instruction = 0b00000100; |
| const uint8_t* current_instruction = &instruction; |
| thread_context.arm_sp = 0x10000000; |
| bool pc_was_updated = false; |
| ASSERT_EQ(ExecuteUnwindInstruction(current_instruction, pc_was_updated, |
| &thread_context), |
| UnwindInstructionResult::kInstructionPending); |
| EXPECT_FALSE(pc_was_updated); |
| ASSERT_EQ(current_instruction, &instruction + 1); |
| EXPECT_EQ(0x10000014ul, thread_context.arm_sp); |
| } |
| |
| TEST(ChromeAndroidUnwindInstructionTest, |
| TestSmallStackPointerIncrementMaxValue) { |
| RegisterContext thread_context = {}; |
| const uint8_t instruction = 0b00111111; |
| const uint8_t* current_instruction = &instruction; |
| thread_context.arm_sp = 0x10000000; |
| bool pc_was_updated = false; |
| ASSERT_EQ(ExecuteUnwindInstruction(current_instruction, pc_was_updated, |
| &thread_context), |
| UnwindInstructionResult::kInstructionPending); |
| EXPECT_FALSE(pc_was_updated); |
| ASSERT_EQ(current_instruction, &instruction + 1); |
| EXPECT_EQ(0x10000100ul, thread_context.arm_sp); |
| } |
| |
| TEST(ChromeAndroidUnwindInstructionTest, |
| TestSmallStackPointerIncrementOverflow) { |
| RegisterContext thread_context = {}; |
| const uint8_t instruction = 0b00111111; |
| const uint8_t* current_instruction = &instruction; |
| thread_context.arm_sp = 0xffffffff; |
| bool pc_was_updated = false; |
| ASSERT_EQ(ExecuteUnwindInstruction(current_instruction, pc_was_updated, |
| &thread_context), |
| UnwindInstructionResult::kAborted); |
| ASSERT_EQ(current_instruction, &instruction + 1); |
| EXPECT_EQ(0xffffffff, thread_context.arm_sp); |
| } |
| |
| TEST(ChromeAndroidUnwindInstructionTest, |
| TestSmallStackPointerDecrementMinValue) { |
| RegisterContext thread_context = {}; |
| const uint8_t instruction = 0b01000000; |
| const uint8_t* current_instruction = &instruction; |
| thread_context.arm_sp = 0x10000000; |
| bool pc_was_updated = false; |
| ASSERT_EQ(ExecuteUnwindInstruction(current_instruction, pc_was_updated, |
| &thread_context), |
| UnwindInstructionResult::kInstructionPending); |
| EXPECT_FALSE(pc_was_updated); |
| ASSERT_EQ(current_instruction, &instruction + 1); |
| EXPECT_EQ(0x0ffffffcul, thread_context.arm_sp); |
| } |
| |
| TEST(ChromeAndroidUnwindInstructionTest, |
| TestSmallStackPointerDecrementMidValue) { |
| // xxxxxx = 4; vsp = vsp - (4 << 2) - 4 = vsp - 16 - 4 = vsp - 0x14. |
| RegisterContext thread_context = {}; |
| const uint8_t instruction = 0b01000100; |
| const uint8_t* current_instruction = &instruction; |
| thread_context.arm_sp = 0x10000000; |
| bool pc_was_updated = false; |
| ASSERT_EQ(ExecuteUnwindInstruction(current_instruction, pc_was_updated, |
| &thread_context), |
| UnwindInstructionResult::kInstructionPending); |
| EXPECT_FALSE(pc_was_updated); |
| ASSERT_EQ(current_instruction, &instruction + 1); |
| EXPECT_EQ(0x0fffffecul, thread_context.arm_sp); |
| } |
| |
| TEST(ChromeAndroidUnwindInstructionTest, |
| TestSmallStackPointerDecrementMaxValue) { |
| RegisterContext thread_context = {}; |
| const uint8_t instruction = 0b01111111; |
| const uint8_t* current_instruction = &instruction; |
| thread_context.arm_sp = 0x10000000; |
| bool pc_was_updated = false; |
| ASSERT_EQ(ExecuteUnwindInstruction(current_instruction, pc_was_updated, |
| &thread_context), |
| UnwindInstructionResult::kInstructionPending); |
| EXPECT_FALSE(pc_was_updated); |
| ASSERT_EQ(current_instruction, &instruction + 1); |
| EXPECT_EQ(0x0fffff00ul, thread_context.arm_sp); |
| } |
| |
| TEST(ChromeAndroidUnwindInstructionTest, |
| TestSmallStackPointerDecrementUnderflow) { |
| RegisterContext thread_context = {}; |
| const uint8_t instruction = 0b01111111; |
| const uint8_t* current_instruction = &instruction; |
| thread_context.arm_sp = 0x00000000; |
| bool pc_was_updated = false; |
| ASSERT_EQ(ExecuteUnwindInstruction(current_instruction, pc_was_updated, |
| &thread_context), |
| UnwindInstructionResult::kAborted); |
| EXPECT_FALSE(pc_was_updated); |
| ASSERT_EQ(current_instruction, &instruction + 1); |
| EXPECT_EQ(0x0ul, thread_context.arm_sp); |
| } |
| |
| using ChromeAndroidUnwindSetStackPointerFromRegisterValueTest = |
| ::testing::TestWithParam<uint8_t>; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| ChromeAndroidUnwindSetStackPointerFromRegisterValueTest, |
| // The function should set all registers except |
| // - callee saved registers (r0, r1, r2, r3) |
| // - sp (r13) |
| // - pc (r15) |
| ::testing::Values(4, 5, 6, 7, 8, 9, 10, 11, 12, 14)); |
| |
| TEST_P(ChromeAndroidUnwindSetStackPointerFromRegisterValueTest, |
| TestSetStackPointerFromRegisterValue) { |
| const uint8_t register_index = GetParam(); |
| |
| RegisterContext thread_context = {}; |
| thread_context.arm_r0 = 100; |
| thread_context.arm_r1 = 101; |
| thread_context.arm_r2 = 102; |
| thread_context.arm_r3 = 103; |
| thread_context.arm_r4 = 104; |
| thread_context.arm_r5 = 105; |
| thread_context.arm_r6 = 106; |
| thread_context.arm_r7 = 107; |
| thread_context.arm_r8 = 108; |
| thread_context.arm_r9 = 109; |
| thread_context.arm_r10 = 110; |
| thread_context.arm_fp = 111; // r11 |
| thread_context.arm_ip = 112; // r12 |
| thread_context.arm_lr = 114; // r14 |
| |
| const uint8_t instruction = 0b10010000 + register_index; |
| const uint8_t* current_instruction = &instruction; |
| bool pc_was_updated = false; |
| ASSERT_EQ(ExecuteUnwindInstruction(current_instruction, pc_was_updated, |
| &thread_context), |
| UnwindInstructionResult::kInstructionPending); |
| EXPECT_FALSE(pc_was_updated); |
| ASSERT_EQ(current_instruction, &instruction + 1); |
| EXPECT_EQ(100ul + register_index, thread_context.arm_sp); |
| } |
| |
| TEST(ChromeAndroidUnwindInstructionTest, TestCompleteWithNoPriorPCUpdate) { |
| RegisterContext thread_context = {}; |
| thread_context.arm_lr = 114; // r14 |
| thread_context.arm_pc = 115; // r15 |
| const uint8_t instruction = 0b10110000; |
| const uint8_t* current_instruction = &instruction; |
| bool pc_was_updated = false; |
| ASSERT_EQ(ExecuteUnwindInstruction(current_instruction, pc_was_updated, |
| &thread_context), |
| UnwindInstructionResult::kCompleted); |
| ASSERT_EQ(current_instruction, &instruction + 1); |
| EXPECT_EQ(114ul, thread_context.arm_pc); |
| } |
| |
| TEST(ChromeAndroidUnwindInstructionTest, TestCompleteWithPriorPCUpdate) { |
| RegisterContext thread_context = {}; |
| thread_context.arm_lr = 114; // r14 |
| thread_context.arm_pc = 115; // r15 |
| const uint8_t instruction = 0b10110000; |
| const uint8_t* current_instruction = &instruction; |
| bool pc_was_updated = true; |
| ASSERT_EQ(ExecuteUnwindInstruction(current_instruction, pc_was_updated, |
| &thread_context), |
| UnwindInstructionResult::kCompleted); |
| ASSERT_EQ(current_instruction, &instruction + 1); |
| EXPECT_EQ(115ul, thread_context.arm_pc); |
| } |
| |
| TEST(ChromeAndroidUnwindInstructionTest, |
| TestPopDiscontinuousRegistersIncludingPC) { |
| RegisterContext thread_context = {}; |
| |
| thread_context.arm_r0 = 100; |
| thread_context.arm_r1 = 101; |
| thread_context.arm_r2 = 102; |
| thread_context.arm_r3 = 103; |
| thread_context.arm_r4 = 104; |
| thread_context.arm_r5 = 105; |
| thread_context.arm_r6 = 106; |
| thread_context.arm_r7 = 107; |
| thread_context.arm_r8 = 108; |
| thread_context.arm_r9 = 109; |
| thread_context.arm_r10 = 110; |
| thread_context.arm_fp = 111; |
| thread_context.arm_ip = 112; |
| thread_context.arm_lr = 113; |
| thread_context.arm_pc = 114; |
| |
| // Pop up to 12 integer registers under masks {r15-r12}, {r11-r4}. |
| const uintptr_t stack[9] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; |
| |
| thread_context.arm_sp = reinterpret_cast<uintptr_t>(&stack[0]); |
| // Pop r15, r12, r8, r4. |
| const uint8_t instruction[] = {0b10001001, 0b00010001}; |
| const uint8_t* current_instruction = instruction; |
| |
| bool pc_was_updated = false; |
| ASSERT_EQ(ExecuteUnwindInstruction(current_instruction, pc_was_updated, |
| &thread_context), |
| UnwindInstructionResult::kInstructionPending); |
| EXPECT_TRUE(pc_was_updated); |
| ASSERT_EQ(current_instruction, instruction + 2); |
| EXPECT_EQ(reinterpret_cast<uintptr_t>(&stack[0] + 4), thread_context.arm_sp); |
| |
| EXPECT_EQ(100ul, thread_context.arm_r0); |
| EXPECT_EQ(101ul, thread_context.arm_r1); |
| EXPECT_EQ(102ul, thread_context.arm_r2); |
| EXPECT_EQ(103ul, thread_context.arm_r3); |
| EXPECT_EQ(1ul, thread_context.arm_r4); |
| EXPECT_EQ(105ul, thread_context.arm_r5); |
| EXPECT_EQ(106ul, thread_context.arm_r6); |
| EXPECT_EQ(107ul, thread_context.arm_r7); |
| EXPECT_EQ(2ul, thread_context.arm_r8); |
| EXPECT_EQ(109ul, thread_context.arm_r9); |
| EXPECT_EQ(110ul, thread_context.arm_r10); |
| EXPECT_EQ(111ul, thread_context.arm_fp); |
| EXPECT_EQ(3ul, thread_context.arm_ip); |
| EXPECT_EQ(113ul, thread_context.arm_lr); |
| EXPECT_EQ(4ul, thread_context.arm_pc); |
| } |
| |
| TEST(ChromeAndroidUnwindInstructionTest, TestPopDiscontinuousRegisters) { |
| RegisterContext thread_context = {}; |
| |
| thread_context.arm_r0 = 100; |
| thread_context.arm_r1 = 101; |
| thread_context.arm_r2 = 102; |
| thread_context.arm_r3 = 103; |
| thread_context.arm_r4 = 104; |
| thread_context.arm_r5 = 105; |
| thread_context.arm_r6 = 106; |
| thread_context.arm_r7 = 107; |
| thread_context.arm_r8 = 108; |
| thread_context.arm_r9 = 109; |
| thread_context.arm_r10 = 110; |
| thread_context.arm_fp = 111; |
| thread_context.arm_ip = 112; |
| thread_context.arm_lr = 113; |
| thread_context.arm_pc = 114; |
| |
| // Pop up to 12 integer registers under masks {r15-r12}, {r11-r4}. |
| const uintptr_t stack[9] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; |
| |
| thread_context.arm_sp = reinterpret_cast<uintptr_t>(&stack[0]); |
| // Pop r12, r8, r4. |
| const uint8_t instruction[] = {0b10000001, 0b00010001}; |
| const uint8_t* current_instruction = instruction; |
| |
| bool pc_was_updated = false; |
| ASSERT_EQ(ExecuteUnwindInstruction(current_instruction, pc_was_updated, |
| &thread_context), |
| UnwindInstructionResult::kInstructionPending); |
| EXPECT_FALSE(pc_was_updated); |
| ASSERT_EQ(current_instruction, instruction + 2); |
| EXPECT_EQ(reinterpret_cast<uintptr_t>(&stack[0] + 3), thread_context.arm_sp); |
| |
| EXPECT_EQ(100ul, thread_context.arm_r0); |
| EXPECT_EQ(101ul, thread_context.arm_r1); |
| EXPECT_EQ(102ul, thread_context.arm_r2); |
| EXPECT_EQ(103ul, thread_context.arm_r3); |
| EXPECT_EQ(1ul, thread_context.arm_r4); |
| EXPECT_EQ(105ul, thread_context.arm_r5); |
| EXPECT_EQ(106ul, thread_context.arm_r6); |
| EXPECT_EQ(107ul, thread_context.arm_r7); |
| EXPECT_EQ(2ul, thread_context.arm_r8); |
| EXPECT_EQ(109ul, thread_context.arm_r9); |
| EXPECT_EQ(110ul, thread_context.arm_r10); |
| EXPECT_EQ(111ul, thread_context.arm_fp); |
| EXPECT_EQ(3ul, thread_context.arm_ip); |
| EXPECT_EQ(113ul, thread_context.arm_lr); |
| EXPECT_EQ(114ul, thread_context.arm_pc); |
| } |
| |
| TEST(ChromeAndroidUnwindInstructionTest, |
| TestPopDiscontinuousRegistersOverflow) { |
| RegisterContext thread_context = {}; |
| |
| thread_context.arm_r0 = 100; |
| thread_context.arm_r1 = 101; |
| thread_context.arm_r2 = 102; |
| thread_context.arm_r3 = 103; |
| thread_context.arm_r4 = 104; |
| thread_context.arm_r5 = 105; |
| thread_context.arm_r6 = 106; |
| thread_context.arm_r7 = 107; |
| thread_context.arm_r8 = 108; |
| thread_context.arm_r9 = 109; |
| thread_context.arm_r10 = 110; |
| thread_context.arm_fp = 111; |
| thread_context.arm_ip = 112; |
| thread_context.arm_lr = 113; |
| thread_context.arm_pc = 114; |
| |
| // Pop up to 12 integer registers under masks {r15-r12}, {r11-r4}. |
| thread_context.arm_sp = 0xffffffff; |
| // Pop r15, r12, r8, r4. |
| const uint8_t instruction[] = {0b10001001, 0b00010001}; |
| const uint8_t* current_instruction = instruction; |
| |
| bool pc_was_updated = false; |
| ASSERT_EQ(ExecuteUnwindInstruction(current_instruction, pc_was_updated, |
| &thread_context), |
| UnwindInstructionResult::kAborted); |
| EXPECT_FALSE(pc_was_updated); |
| ASSERT_EQ(current_instruction, instruction + 2); |
| EXPECT_EQ(0xffffffff, thread_context.arm_sp); |
| |
| EXPECT_EQ(100ul, thread_context.arm_r0); |
| EXPECT_EQ(101ul, thread_context.arm_r1); |
| EXPECT_EQ(102ul, thread_context.arm_r2); |
| EXPECT_EQ(103ul, thread_context.arm_r3); |
| EXPECT_EQ(104ul, thread_context.arm_r4); |
| EXPECT_EQ(105ul, thread_context.arm_r5); |
| EXPECT_EQ(106ul, thread_context.arm_r6); |
| EXPECT_EQ(107ul, thread_context.arm_r7); |
| EXPECT_EQ(108ul, thread_context.arm_r8); |
| EXPECT_EQ(109ul, thread_context.arm_r9); |
| EXPECT_EQ(110ul, thread_context.arm_r10); |
| EXPECT_EQ(111ul, thread_context.arm_fp); |
| EXPECT_EQ(112ul, thread_context.arm_ip); |
| EXPECT_EQ(113ul, thread_context.arm_lr); |
| EXPECT_EQ(114ul, thread_context.arm_pc); |
| } |
| |
| TEST(ChromeAndroidUnwindInstructionTest, TestRefuseToUnwind) { |
| RegisterContext thread_context = {}; |
| |
| const uint8_t instruction[] = {0b10000000, 0b0}; |
| const uint8_t* current_instruction = instruction; |
| |
| bool pc_was_updated = false; |
| ASSERT_EQ(ExecuteUnwindInstruction(current_instruction, pc_was_updated, |
| &thread_context), |
| UnwindInstructionResult::kAborted); |
| EXPECT_FALSE(pc_was_updated); |
| ASSERT_EQ(current_instruction, instruction + 2); |
| } |
| |
| TEST(ChromeAndroidUnwindInstructionTest, |
| TestPopRegistersIncludingR14MinRegisters) { |
| RegisterContext thread_context = {}; |
| |
| thread_context.arm_r0 = 100; |
| thread_context.arm_r1 = 101; |
| thread_context.arm_r2 = 102; |
| thread_context.arm_r3 = 103; |
| thread_context.arm_r4 = 104; |
| thread_context.arm_r5 = 105; |
| thread_context.arm_r6 = 106; |
| thread_context.arm_r7 = 107; |
| thread_context.arm_r8 = 108; |
| thread_context.arm_r9 = 109; |
| thread_context.arm_r10 = 110; |
| thread_context.arm_fp = 111; |
| thread_context.arm_ip = 112; |
| thread_context.arm_lr = 113; |
| |
| // Popping r4 - r[4 + nnn], r14, at most 9 registers. |
| // r14 = lr |
| const uintptr_t stack[9] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; |
| |
| thread_context.arm_sp = reinterpret_cast<uintptr_t>(&stack[0]); |
| const uint8_t instruction = 0b10101000; |
| const uint8_t* current_instruction = &instruction; |
| bool pc_was_updated = false; |
| ASSERT_EQ(ExecuteUnwindInstruction(current_instruction, pc_was_updated, |
| &thread_context), |
| UnwindInstructionResult::kInstructionPending); |
| EXPECT_FALSE(pc_was_updated); |
| ASSERT_EQ(current_instruction, &instruction + 1); |
| EXPECT_EQ(reinterpret_cast<uintptr_t>(&stack[0] + 2), thread_context.arm_sp); |
| |
| EXPECT_EQ(100ul, thread_context.arm_r0); |
| EXPECT_EQ(101ul, thread_context.arm_r1); |
| EXPECT_EQ(102ul, thread_context.arm_r2); |
| EXPECT_EQ(103ul, thread_context.arm_r3); |
| EXPECT_EQ(1ul, thread_context.arm_r4); |
| EXPECT_EQ(105ul, thread_context.arm_r5); |
| EXPECT_EQ(106ul, thread_context.arm_r6); |
| EXPECT_EQ(107ul, thread_context.arm_r7); |
| EXPECT_EQ(108ul, thread_context.arm_r8); |
| EXPECT_EQ(109ul, thread_context.arm_r9); |
| EXPECT_EQ(110ul, thread_context.arm_r10); |
| EXPECT_EQ(111ul, thread_context.arm_fp); |
| EXPECT_EQ(112ul, thread_context.arm_ip); |
| EXPECT_EQ(2ul, thread_context.arm_lr); |
| } |
| |
| TEST(ChromeAndroidUnwindInstructionTest, |
| TestPopRegistersIncludingR14MidRegisters) { |
| RegisterContext thread_context = {}; |
| |
| thread_context.arm_r0 = 100; |
| thread_context.arm_r1 = 101; |
| thread_context.arm_r2 = 102; |
| thread_context.arm_r3 = 103; |
| thread_context.arm_r4 = 104; |
| thread_context.arm_r5 = 105; |
| thread_context.arm_r6 = 106; |
| thread_context.arm_r7 = 107; |
| thread_context.arm_r8 = 108; |
| thread_context.arm_r9 = 109; |
| thread_context.arm_r10 = 110; |
| thread_context.arm_fp = 111; |
| thread_context.arm_ip = 112; |
| thread_context.arm_lr = 113; |
| |
| // Popping r4 - r[4 + nnn], r14, at most 9 registers. |
| // r14 = lr |
| const uintptr_t stack[9] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; |
| |
| thread_context.arm_sp = reinterpret_cast<uintptr_t>(&stack[0]); |
| const uint8_t instruction = 0b10101100; // Pop r4-r8, r14. |
| const uint8_t* current_instruction = &instruction; |
| bool pc_was_updated = false; |
| ASSERT_EQ(ExecuteUnwindInstruction(current_instruction, pc_was_updated, |
| &thread_context), |
| UnwindInstructionResult::kInstructionPending); |
| EXPECT_FALSE(pc_was_updated); |
| ASSERT_EQ(current_instruction, &instruction + 1); |
| EXPECT_EQ(reinterpret_cast<uintptr_t>(&stack[0] + 6), thread_context.arm_sp); |
| |
| EXPECT_EQ(100ul, thread_context.arm_r0); |
| EXPECT_EQ(101ul, thread_context.arm_r1); |
| EXPECT_EQ(102ul, thread_context.arm_r2); |
| EXPECT_EQ(103ul, thread_context.arm_r3); |
| EXPECT_EQ(1ul, thread_context.arm_r4); |
| EXPECT_EQ(2ul, thread_context.arm_r5); |
| EXPECT_EQ(3ul, thread_context.arm_r6); |
| EXPECT_EQ(4ul, thread_context.arm_r7); |
| EXPECT_EQ(5ul, thread_context.arm_r8); |
| EXPECT_EQ(109ul, thread_context.arm_r9); |
| EXPECT_EQ(110ul, thread_context.arm_r10); |
| EXPECT_EQ(111ul, thread_context.arm_fp); |
| EXPECT_EQ(112ul, thread_context.arm_ip); |
| EXPECT_EQ(6ul, thread_context.arm_lr); |
| } |
| |
| TEST(ChromeAndroidUnwindInstructionTest, |
| TestPopRegistersIncludingR14MaxRegisters) { |
| RegisterContext thread_context = {}; |
| |
| thread_context.arm_r0 = 100; |
| thread_context.arm_r1 = 101; |
| thread_context.arm_r2 = 102; |
| thread_context.arm_r3 = 103; |
| thread_context.arm_r4 = 104; |
| thread_context.arm_r5 = 105; |
| thread_context.arm_r6 = 106; |
| thread_context.arm_r7 = 107; |
| thread_context.arm_r8 = 108; |
| thread_context.arm_r9 = 109; |
| thread_context.arm_r10 = 110; |
| thread_context.arm_fp = 111; |
| thread_context.arm_ip = 112; |
| thread_context.arm_lr = 113; |
| |
| // Popping r4 - r[4 + nnn], r14, at most 9 registers. |
| // r14 = lr |
| const uintptr_t stack[9] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; |
| |
| thread_context.arm_sp = reinterpret_cast<uintptr_t>(&stack[0]); |
| const uint8_t instruction = 0b10101111; // Pop r4 - r11, r14. |
| const uint8_t* current_instruction = &instruction; |
| bool pc_was_updated = false; |
| ASSERT_EQ(ExecuteUnwindInstruction(current_instruction, pc_was_updated, |
| &thread_context), |
| UnwindInstructionResult::kInstructionPending); |
| EXPECT_FALSE(pc_was_updated); |
| ASSERT_EQ(current_instruction, &instruction + 1); |
| EXPECT_EQ(reinterpret_cast<uintptr_t>(&stack[0] + 9), thread_context.arm_sp); |
| |
| EXPECT_EQ(100ul, thread_context.arm_r0); |
| EXPECT_EQ(101ul, thread_context.arm_r1); |
| EXPECT_EQ(102ul, thread_context.arm_r2); |
| EXPECT_EQ(103ul, thread_context.arm_r3); |
| EXPECT_EQ(1ul, thread_context.arm_r4); |
| EXPECT_EQ(2ul, thread_context.arm_r5); |
| EXPECT_EQ(3ul, thread_context.arm_r6); |
| EXPECT_EQ(4ul, thread_context.arm_r7); |
| EXPECT_EQ(5ul, thread_context.arm_r8); |
| EXPECT_EQ(6ul, thread_context.arm_r9); |
| EXPECT_EQ(7ul, thread_context.arm_r10); |
| EXPECT_EQ(8ul, thread_context.arm_fp); |
| EXPECT_EQ(112ul, thread_context.arm_ip); |
| EXPECT_EQ(9ul, thread_context.arm_lr); |
| } |
| |
| TEST(ChromeAndroidUnwindInstructionTest, TestPopRegistersIncludingR14Overflow) { |
| RegisterContext thread_context = {}; |
| |
| thread_context.arm_r0 = 100; |
| thread_context.arm_r1 = 101; |
| thread_context.arm_r2 = 102; |
| thread_context.arm_r3 = 103; |
| thread_context.arm_r4 = 104; |
| thread_context.arm_r5 = 105; |
| thread_context.arm_r6 = 106; |
| thread_context.arm_r7 = 107; |
| thread_context.arm_r8 = 108; |
| thread_context.arm_r9 = 109; |
| thread_context.arm_r10 = 110; |
| thread_context.arm_fp = 111; |
| thread_context.arm_ip = 112; |
| thread_context.arm_lr = 113; |
| |
| // Popping r4 - r[4 + nnn], r14, at most 9 registers. |
| // r14 = lr |
| thread_context.arm_sp = 0xffffffff; |
| const uint8_t instruction = 0b10101111; // Pop r4 - r11, r14. |
| const uint8_t* current_instruction = &instruction; |
| bool pc_was_updated = false; |
| ASSERT_EQ(ExecuteUnwindInstruction(current_instruction, pc_was_updated, |
| &thread_context), |
| UnwindInstructionResult::kAborted); |
| EXPECT_FALSE(pc_was_updated); |
| ASSERT_EQ(current_instruction, &instruction + 1); |
| EXPECT_EQ(0xffffffff, thread_context.arm_sp); |
| |
| EXPECT_EQ(100ul, thread_context.arm_r0); |
| EXPECT_EQ(101ul, thread_context.arm_r1); |
| EXPECT_EQ(102ul, thread_context.arm_r2); |
| EXPECT_EQ(103ul, thread_context.arm_r3); |
| EXPECT_EQ(104ul, thread_context.arm_r4); |
| EXPECT_EQ(105ul, thread_context.arm_r5); |
| EXPECT_EQ(106ul, thread_context.arm_r6); |
| EXPECT_EQ(107ul, thread_context.arm_r7); |
| EXPECT_EQ(108ul, thread_context.arm_r8); |
| EXPECT_EQ(109ul, thread_context.arm_r9); |
| EXPECT_EQ(110ul, thread_context.arm_r10); |
| EXPECT_EQ(111ul, thread_context.arm_fp); |
| EXPECT_EQ(112ul, thread_context.arm_ip); |
| EXPECT_EQ(113ul, thread_context.arm_lr); |
| } |
| |
| TEST(ChromeAndroidUnwindInstructionTest, TestBigStackPointerIncrementMinValue) { |
| RegisterContext thread_context = {}; |
| thread_context.arm_sp = 0x10000000; |
| |
| const uint8_t increment_0[] = { |
| 0b10110010, |
| 0b00000000, |
| }; |
| const uint8_t* current_instruction = &increment_0[0]; |
| bool pc_was_updated = false; |
| ASSERT_EQ(ExecuteUnwindInstruction(current_instruction, pc_was_updated, |
| &thread_context), |
| UnwindInstructionResult::kInstructionPending); |
| EXPECT_FALSE(pc_was_updated); |
| ASSERT_EQ(current_instruction, increment_0 + sizeof(increment_0)); |
| // vsp + 0x204 + (0 << 2) |
| // = vsp + 0x204 |
| EXPECT_EQ(0x10000204ul, thread_context.arm_sp); |
| } |
| |
| TEST(ChromeAndroidUnwindInstructionTest, TestBigStackPointerIncrementMidValue) { |
| RegisterContext thread_context = {}; |
| thread_context.arm_sp = 0x10000000; |
| |
| const uint8_t increment_4[] = { |
| 0b10110010, |
| 0b00000100, |
| }; |
| const uint8_t* current_instruction = &increment_4[0]; |
| |
| // vsp + 0x204 + (4 << 2) |
| // = vsp + 0x204 + 0x10 |
| // = vsp + 0x214 |
| bool pc_was_updated = false; |
| ASSERT_EQ(ExecuteUnwindInstruction(current_instruction, pc_was_updated, |
| &thread_context), |
| UnwindInstructionResult::kInstructionPending); |
| EXPECT_FALSE(pc_was_updated); |
| ASSERT_EQ(current_instruction, increment_4 + sizeof(increment_4)); |
| EXPECT_EQ(0x10000214ul, thread_context.arm_sp); |
| } |
| |
| TEST(ChromeAndroidUnwindInstructionTest, |
| TestBigStackPointerIncrementLargeValue) { |
| RegisterContext thread_context = {}; |
| thread_context.arm_sp = 0x10000000; |
| |
| const uint8_t increment_128[] = { |
| 0b10110010, |
| 0b10000000, |
| 0b00000001, |
| }; |
| const uint8_t* current_instruction = &increment_128[0]; |
| // vsp + 0x204 + (128 << 2) |
| // = vsp + 0x204 + 512 |
| // = vsp + 0x204 + 0x200 |
| // = vsp + 0x404 |
| bool pc_was_updated = false; |
| ASSERT_EQ(ExecuteUnwindInstruction(current_instruction, pc_was_updated, |
| &thread_context), |
| UnwindInstructionResult::kInstructionPending); |
| EXPECT_FALSE(pc_was_updated); |
| ASSERT_EQ(current_instruction, increment_128 + sizeof(increment_128)); |
| EXPECT_EQ(0x10000404ul, thread_context.arm_sp); |
| } |
| |
| TEST(ChromeAndroidUnwindInstructionTest, TestBigStackPointerIncrementOverflow) { |
| RegisterContext thread_context = {}; |
| thread_context.arm_sp = 0xffffffff; |
| |
| const uint8_t increment_overflow[] = { |
| 0b10110010, |
| 0b10000000, |
| 0b00000001, |
| }; // ULEB128 = 128 |
| const uint8_t* current_instruction = &increment_overflow[0]; |
| bool pc_was_updated = false; |
| ASSERT_EQ(ExecuteUnwindInstruction(current_instruction, pc_was_updated, |
| &thread_context), |
| UnwindInstructionResult::kAborted); |
| EXPECT_FALSE(pc_was_updated); |
| ASSERT_EQ(current_instruction, |
| increment_overflow + sizeof(increment_overflow)); |
| EXPECT_EQ(0xfffffffful, thread_context.arm_sp); |
| } |
| |
| TEST(ChromeUnwinderAndroidV2Test, |
| TestFunctionOffsetTableLookupExactMatchingOffset) { |
| const uint8_t function_offset_table[] = { |
| // Function 1: [(130, 2), (128, 3), (0, 4)] |
| // offset = 130 |
| 0b10000010, |
| 0b00000001, |
| // unwind index = 2 |
| 0b00000010, |
| // offset = 128 |
| 0b10000000, |
| 0b00000001, |
| // unwind index = 3 |
| 0b00000011, |
| // offset = 0 |
| 0b00000000, |
| // unwind index = 4 |
| 0b00000100, |
| }; |
| |
| EXPECT_EQ(3ul, GetFirstUnwindInstructionIndexFromFunctionOffsetTableEntry( |
| &function_offset_table[0], |
| /* instruction_offset_from_function_start */ 128)); |
| } |
| |
| TEST(ChromeUnwinderAndroidV2Test, |
| TestFunctionOffsetTableLookupNonExactMatchingOffset) { |
| const uint8_t function_offset_table[] = { |
| // Function 1: [(130, 2), (128, 3), (0, 4)] |
| // offset = 130 |
| 0b10000010, |
| 0b00000001, |
| // unwind index = 2 |
| 0b00000010, |
| // offset = 128 |
| 0b10000000, |
| 0b00000001, |
| // unwind index = 3 |
| 0b00000011, |
| // offset = 0 |
| 0b00000000, |
| // unwind index = 4 |
| 0b00000100, |
| }; |
| |
| EXPECT_EQ(3ul, GetFirstUnwindInstructionIndexFromFunctionOffsetTableEntry( |
| &function_offset_table[0], |
| /* instruction_offset_from_function_start */ 129)); |
| } |
| |
| TEST(ChromeUnwinderAndroidV2Test, TestFunctionOffsetTableLookupZeroOffset) { |
| const uint8_t function_offset_table[] = { |
| // Function 1: [(130, 2), (128, 3), (0, 4)] |
| // offset = 130 |
| 0b10000010, |
| 0b00000001, |
| // unwind index = 2 |
| 0b00000010, |
| // offset = 128 |
| 0b10000000, |
| 0b00000001, |
| // unwind index = 3 |
| 0b00000011, |
| // offset = 0 |
| 0b00000000, |
| // unwind index = 4 |
| 0b00000100, |
| }; |
| |
| EXPECT_EQ(4ul, GetFirstUnwindInstructionIndexFromFunctionOffsetTableEntry( |
| &function_offset_table[0], |
| /* instruction_offset_from_function_start */ 0)); |
| } |
| |
| TEST(ChromeUnwinderAndroidV2Test, TestAddressTableLookupEntryInPage) { |
| const uint32_t page_start_instructions[] = {0, 2}; |
| const FunctionTableEntry function_offset_table_indices[] = { |
| // Page 0 |
| { |
| /* function_start_address_page_instruction_offset */ 0, |
| /* function_offset_table_byte_index */ 20, |
| }, |
| { |
| /* function_start_address_page_instruction_offset */ 4, |
| /* function_offset_table_byte_index */ 40, |
| }, |
| // Page 1 |
| { |
| /* function_start_address_page_instruction_offset */ 6, |
| /* function_offset_table_byte_index */ 70, |
| }, |
| }; |
| |
| { |
| const uint32_t page_number = 0; |
| const uint32_t page_instruction_offset = 4; |
| const auto entry_found = GetFunctionTableIndexFromInstructionOffset( |
| page_start_instructions, function_offset_table_indices, |
| /* instruction_offset */ (page_instruction_offset << 1) + |
| (page_number << 17)); |
| ASSERT_NE(absl::nullopt, entry_found); |
| EXPECT_EQ(0, entry_found->instruction_offset_from_function_start); |
| EXPECT_EQ(40ul, entry_found->function_offset_table_byte_index); |
| } |
| |
| { |
| const uint32_t page_number = 0; |
| const uint32_t page_instruction_offset = 50; |
| const auto entry_found = GetFunctionTableIndexFromInstructionOffset( |
| page_start_instructions, function_offset_table_indices, |
| /* instruction_offset */ (page_instruction_offset << 1) + |
| (page_number << 17)); |
| ASSERT_NE(absl::nullopt, entry_found); |
| EXPECT_EQ(46, entry_found->instruction_offset_from_function_start); |
| EXPECT_EQ(40ul, entry_found->function_offset_table_byte_index); |
| } |
| |
| // Lookup last instruction in last function. |
| { |
| const uint32_t page_number = 1; |
| const uint32_t page_instruction_offset = 0xffff; |
| const auto entry_found = GetFunctionTableIndexFromInstructionOffset( |
| page_start_instructions, function_offset_table_indices, |
| /* instruction_offset */ (page_instruction_offset << 1) + |
| (page_number << 17)); |
| ASSERT_NE(absl::nullopt, entry_found); |
| // 0xffff - 6 = 0xfff9. |
| EXPECT_EQ(0xfff9, entry_found->instruction_offset_from_function_start); |
| EXPECT_EQ(70ul, entry_found->function_offset_table_byte_index); |
| } |
| } |
| |
| TEST(ChromeUnwinderAndroidV2Test, TestAddressTableLookupEmptyPage) { |
| const uint32_t page_start_instructions[] = {0, 1, 1}; |
| const FunctionTableEntry function_offset_table_indices[] = { |
| // Page 0 |
| { |
| /* function_start_address_page_instruction_offset */ 0, |
| /* function_offset_table_byte_index */ 20, |
| }, |
| // Page 1 is empty |
| // Page 2 |
| { |
| /* function_start_address_page_instruction_offset */ 6, |
| /* function_offset_table_byte_index */ 70, |
| }, |
| }; |
| |
| const uint32_t page_number = 1; |
| const uint32_t page_instruction_offset = 4; |
| const auto entry_found = GetFunctionTableIndexFromInstructionOffset( |
| page_start_instructions, function_offset_table_indices, |
| /* instruction_offset */ (page_instruction_offset << 1) + |
| (page_number << 17)); |
| ASSERT_NE(absl::nullopt, entry_found); |
| EXPECT_EQ(0x10004, entry_found->instruction_offset_from_function_start); |
| EXPECT_EQ(20ul, entry_found->function_offset_table_byte_index); |
| } |
| |
| TEST(ChromeUnwinderAndroidV2Test, |
| TestAddressTableLookupInvalidIntructionOffset) { |
| const uint32_t page_start_instructions[] = {0, 1}; |
| const FunctionTableEntry function_offset_table_indices[] = { |
| // Page 0 |
| // This function spans from page 0 offset 0 to page 1 offset 5. |
| { |
| /* function_start_address_page_instruction_offset */ 0, |
| /* function_offset_table_byte_index */ 20, |
| }, |
| // Page 1 |
| { |
| /* function_start_address_page_instruction_offset */ 6, |
| /* function_offset_table_byte_index */ 70, |
| }, |
| }; |
| |
| // Instruction offset lies after last page on page table. |
| { |
| const uint32_t page_number = 50; |
| const uint32_t page_instruction_offset = 6; |
| const auto entry_found = GetFunctionTableIndexFromInstructionOffset( |
| page_start_instructions, function_offset_table_indices, |
| /* instruction_offset */ (page_instruction_offset << 1) + |
| (page_number << 17)); |
| ASSERT_EQ(absl::nullopt, entry_found); |
| } |
| { |
| const uint32_t page_number = 2; |
| const uint32_t page_instruction_offset = 0; |
| const auto entry_found = GetFunctionTableIndexFromInstructionOffset( |
| page_start_instructions, function_offset_table_indices, |
| /* instruction_offset */ (page_instruction_offset << 1) + |
| (page_number << 17)); |
| ASSERT_EQ(absl::nullopt, entry_found); |
| } |
| } |
| |
| TEST(ChromeUnwinderAndroidV2Test, |
| TestAddressTableLookupOnSecondPageOfFunctionSpanningPageBoundary) { |
| const uint32_t page_start_instructions[] = {0, 1, 2}; |
| const FunctionTableEntry function_offset_table_indices[] = { |
| // Page 0 |
| { |
| /* function_start_address_page_instruction_offset */ 0, |
| /* function_offset_table_byte_index */ 20, |
| }, |
| // Page 1 |
| { |
| /* function_start_address_page_instruction_offset */ 6, |
| /* function_offset_table_byte_index */ 70, |
| }, |
| // Page 2 |
| { |
| /* function_start_address_page_instruction_offset */ 10, |
| /* function_offset_table_byte_index */ 80, |
| }}; |
| |
| const uint32_t page_number = 1; |
| const uint32_t page_instruction_offset = 4; |
| const auto entry_found = GetFunctionTableIndexFromInstructionOffset( |
| page_start_instructions, function_offset_table_indices, |
| /* instruction_offset */ (page_instruction_offset << 1) + |
| (page_number << 17)); |
| ASSERT_NE(absl::nullopt, entry_found); |
| EXPECT_EQ(0x10004, entry_found->instruction_offset_from_function_start); |
| EXPECT_EQ(20ul, entry_found->function_offset_table_byte_index); |
| } |
| |
| TEST(ChromeUnwinderAndroidV2Test, |
| TestAddressTableLookupWithinFunctionSpanningMultiplePages) { |
| const uint32_t page_start_instructions[] = {0, 1, 1, 1}; |
| const FunctionTableEntry function_offset_table_indices[] = { |
| // Page 0 |
| // This function spans from page 0 offset 0 to page 3 offset 5. |
| { |
| /* function_start_address_page_instruction_offset */ 0, |
| /* function_offset_table_byte_index */ 20, |
| }, |
| // Page 1 is empty |
| // Page 2 is empty |
| // Page 3 |
| { |
| /* function_start_address_page_instruction_offset */ 6, |
| /* function_offset_table_byte_index */ 70, |
| }, |
| }; |
| |
| { |
| const uint32_t page_number = 0; |
| const uint32_t page_instruction_offset = 4; |
| const auto entry_found = GetFunctionTableIndexFromInstructionOffset( |
| page_start_instructions, function_offset_table_indices, |
| /* instruction_offset */ (page_instruction_offset << 1) + |
| (page_number << 17)); |
| ASSERT_NE(absl::nullopt, entry_found); |
| EXPECT_EQ(0x4, entry_found->instruction_offset_from_function_start); |
| EXPECT_EQ(20ul, entry_found->function_offset_table_byte_index); |
| } |
| { |
| const uint32_t page_number = 1; |
| const uint32_t page_instruction_offset = 4; |
| const auto entry_found = GetFunctionTableIndexFromInstructionOffset( |
| page_start_instructions, function_offset_table_indices, |
| /* instruction_offset */ (page_instruction_offset << 1) + |
| (page_number << 17)); |
| ASSERT_NE(absl::nullopt, entry_found); |
| EXPECT_EQ(0x10004, entry_found->instruction_offset_from_function_start); |
| EXPECT_EQ(20ul, entry_found->function_offset_table_byte_index); |
| } |
| { |
| const uint32_t page_number = 2; |
| const uint32_t page_instruction_offset = 4; |
| const auto entry_found = GetFunctionTableIndexFromInstructionOffset( |
| page_start_instructions, function_offset_table_indices, |
| /* instruction_offset */ (page_instruction_offset << 1) + |
| (page_number << 17)); |
| ASSERT_NE(absl::nullopt, entry_found); |
| EXPECT_EQ(0x20004, entry_found->instruction_offset_from_function_start); |
| EXPECT_EQ(20ul, entry_found->function_offset_table_byte_index); |
| } |
| { |
| const uint32_t page_number = 3; |
| const uint32_t page_instruction_offset = 4; |
| const auto entry_found = GetFunctionTableIndexFromInstructionOffset( |
| page_start_instructions, function_offset_table_indices, |
| /* instruction_offset */ (page_instruction_offset << 1) + |
| (page_number << 17)); |
| ASSERT_NE(absl::nullopt, entry_found); |
| EXPECT_EQ(0x30004, entry_found->instruction_offset_from_function_start); |
| EXPECT_EQ(20ul, entry_found->function_offset_table_byte_index); |
| } |
| } |
| |
| class TestModule : public ModuleCache::Module { |
| public: |
| TestModule(uintptr_t base_address, |
| size_t size, |
| const std::string& build_id = "TestModule") |
| : base_address_(base_address), size_(size), build_id_(build_id) {} |
| |
| uintptr_t GetBaseAddress() const override { return base_address_; } |
| std::string GetId() const override { return build_id_; } |
| FilePath GetDebugBasename() const override { return FilePath(); } |
| size_t GetSize() const override { return size_; } |
| bool IsNative() const override { return true; } |
| |
| private: |
| const uintptr_t base_address_; |
| const size_t size_; |
| const std::string build_id_; |
| }; |
| |
| // Utility function to add a single native module during test setup. Returns |
| // a pointer to the provided module. |
| const ModuleCache::Module* AddNativeModule( |
| ModuleCache* cache, |
| std::unique_ptr<const ModuleCache::Module> module) { |
| const ModuleCache::Module* module_ptr = module.get(); |
| cache->AddCustomNativeModule(std::move(module)); |
| return module_ptr; |
| } |
| |
| TEST(ChromeUnwinderAndroidV2Test, CanUnwindFrom) { |
| const uint32_t page_table[] = {0}; |
| const FunctionTableEntry function_table[] = {{0, 0}}; |
| const uint8_t function_offset_table[] = {0}; |
| const uint8_t unwind_instruction_table[] = {0}; |
| auto dummy_unwind_info = ChromeUnwindInfoAndroid{ |
| make_span(unwind_instruction_table, 1ul), |
| make_span(function_offset_table, 1ul), |
| make_span(function_table, 1ul), |
| make_span(page_table, 1ul), |
| }; |
| |
| auto chrome_module = |
| std::make_unique<TestModule>(0x1000, 0x500, "ChromeModule"); |
| auto non_chrome_module = |
| std::make_unique<TestModule>(0x2000, 0x500, "OtherModule"); |
| |
| ModuleCache module_cache; |
| ChromeUnwinderAndroidV2 unwinder(dummy_unwind_info, |
| chrome_module->GetBaseAddress(), |
| /* text_section_start_address */ |
| chrome_module->GetBaseAddress() + 4); |
| unwinder.Initialize(&module_cache); |
| |
| EXPECT_TRUE(unwinder.CanUnwindFrom({0x1100, chrome_module.get()})); |
| EXPECT_TRUE(unwinder.CanUnwindFrom({0x1000, chrome_module.get()})); |
| EXPECT_FALSE(unwinder.CanUnwindFrom({0x2100, non_chrome_module.get()})); |
| EXPECT_FALSE(unwinder.CanUnwindFrom({0x400, nullptr})); |
| } |
| |
| void ExpectFramesEq(const std::vector<Frame>& actual, |
| const std::vector<Frame>& expected) { |
| EXPECT_EQ(actual.size(), expected.size()); |
| if (actual.size() != expected.size()) |
| return; |
| |
| for (size_t i = 0; i < actual.size(); i++) { |
| EXPECT_EQ(actual[i].module, expected[i].module); |
| EXPECT_EQ(actual[i].instruction_pointer, expected[i].instruction_pointer); |
| } |
| } |
| |
| TEST(ChromeUnwinderAndroidV2Test, TryUnwind) { |
| const uint32_t page_table[] = {0, 2}; |
| const size_t number_of_pages = base::size(page_table); |
| const size_t page_size = 1 << 17; |
| |
| const FunctionTableEntry function_table[] = { |
| // Page 0. |
| {0, 0}, // Function 0. |
| {0x10, 4}, // Function 1. The function to unwind 2 times. |
| // Page 1. |
| {0x5, 8}, // Function 2. |
| {0x20, 12}, // Function 3. |
| }; |
| const uint8_t function_offset_table[] = { |
| // Function 0. |
| 0x2, |
| 0, |
| 0x0, |
| 2, |
| // Function 1. |
| 0x7f, |
| 0, |
| 0x0, |
| 2, |
| // Function 2. |
| 0x78, |
| 0, |
| 0x0, |
| 2, |
| // Function 3. |
| 0x2, |
| 0, |
| 0x0, |
| 2, |
| }; |
| const uint8_t unwind_instruction_table[] = { |
| // Offset 0: Pop r14 from stack top. |
| 0b10000100, |
| 0b00000000, |
| // Offset 2: COMPLETE. |
| 0b10110000, |
| }; |
| |
| auto unwind_info = ChromeUnwindInfoAndroid{ |
| make_span(unwind_instruction_table, base::size(unwind_instruction_table)), |
| make_span(function_offset_table, base::size(function_offset_table)), |
| make_span(function_table, base::size(function_table)), |
| make_span(page_table, base::size(page_table)), |
| }; |
| |
| ModuleCache module_cache; |
| const ModuleCache::Module* chrome_module = AddNativeModule( |
| &module_cache, std::make_unique<TestModule>( |
| 0x1000, number_of_pages * page_size, "ChromeModule")); |
| |
| uintptr_t text_section_start_address = 0x1100; |
| ChromeUnwinderAndroidV2 unwinder(unwind_info, chrome_module->GetBaseAddress(), |
| text_section_start_address); |
| |
| unwinder.Initialize(&module_cache); |
| |
| // Both first_pc and second_pc lie in Function 1's address range. |
| uintptr_t first_pc = text_section_start_address + 0x20; |
| uintptr_t second_pc = text_section_start_address + page_size + 0x4; |
| // third_pc lies outside chrome_module's address range. |
| uintptr_t third_pc = text_section_start_address + 3 * page_size; |
| |
| const std::vector<uintptr_t> stack_memory = { |
| third_pc, |
| 0xFFFF, |
| }; |
| uintptr_t stack_top = |
| reinterpret_cast<uintptr_t>(stack_memory.data() + stack_memory.size()); |
| |
| std::vector<Frame> unwound_frames = {{first_pc, chrome_module}}; |
| RegisterContext context; |
| RegisterContextInstructionPointer(&context) = first_pc; |
| RegisterContextStackPointer(&context) = |
| reinterpret_cast<uintptr_t>(stack_memory.data()); |
| context.arm_lr = second_pc; |
| |
| EXPECT_EQ(UnwindResult::kUnrecognizedFrame, |
| unwinder.TryUnwind(&context, stack_top, &unwound_frames)); |
| ExpectFramesEq(std::vector<Frame>({{first_pc, chrome_module}, |
| {second_pc, chrome_module}, |
| {third_pc, nullptr}}), |
| unwound_frames); |
| } |
| |
| } // namespace base |