blob: ba39d7e7be50c7461054250f2664a4fd326a4ad5 [file] [log] [blame]
// 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