| // 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 unwind_instruction_table[] = {0, 1, 2, 3, 4, 5, 6}; |
| 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(unwind_instruction_table + 3, |
| GetFirstUnwindInstructionFromFunctionOffsetTableIndex( |
| unwind_instruction_table, function_offset_table, |
| {/* instruction_offset_from_function_start */ 128, |
| /* function_offset_table_byte_index */ 0x0})); |
| } |
| |
| TEST(ChromeUnwinderAndroidV2Test, |
| TestFunctionOffsetTableLookupNonExactMatchingOffset) { |
| const uint8_t unwind_instruction_table[] = {0, 1, 2, 3, 4, 5, 6}; |
| 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(unwind_instruction_table + 3, |
| GetFirstUnwindInstructionFromFunctionOffsetTableIndex( |
| unwind_instruction_table, function_offset_table, |
| {/* instruction_offset_from_function_start */ 129, |
| /* function_offset_table_byte_index */ 0x0})); |
| } |
| |
| TEST(ChromeUnwinderAndroidV2Test, TestFunctionOffsetTableLookupZeroOffset) { |
| const uint8_t unwind_instruction_table[] = {0, 1, 2, 3, 4, 5, 6}; |
| 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(unwind_instruction_table + 4, |
| GetFirstUnwindInstructionFromFunctionOffsetTableIndex( |
| unwind_instruction_table, function_offset_table, |
| {/* instruction_offset_from_function_start */ 0, |
| /* function_offset_table_byte_index */ 0x0})); |
| } |
| |
| 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); |
| } |
| } |
| } // namespace base |