blob: fdd456600da18213dc890e59cf9256fd3680bc8e [file]
//===-- TestAArch64Emulator.cpp ------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "gtest/gtest.h"
#include "lldb/Core/Address.h"
#include "lldb/Core/Disassembler.h"
#include "lldb/Target/ExecutionContext.h"
#include "lldb/Utility/ArchSpec.h"
#include "lldb/Utility/RegisterValue.h"
#include "Plugins/Instruction/ARM64/EmulateInstructionARM64.h"
#include "Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h"
#include "Plugins/Process/Utility/lldb-arm64-register-enums.h"
using namespace lldb;
using namespace lldb_private;
struct Arch64EmulatorTester : public EmulateInstructionARM64 {
RegisterInfoPOSIX_arm64::GPR gpr;
uint8_t memory[64] = {0};
uint64_t memory_offset = 0;
Arch64EmulatorTester()
: EmulateInstructionARM64(ArchSpec("arm64-apple-ios")) {
memset(&gpr, 0, sizeof(gpr));
EmulateInstruction::SetCallbacks(ReadMemoryCallback, WriteMemoryCallback,
ReadRegisterCallback,
WriteRegisterCallback);
}
static bool ReadRegisterCallback(EmulateInstruction *instruction, void *baton,
const RegisterInfo *reg_info,
RegisterValue &reg_value) {
auto *tester = static_cast<Arch64EmulatorTester *>(instruction);
uint32_t reg = reg_info->kinds[eRegisterKindLLDB];
if (reg >= gpr_x0_arm64 && reg <= gpr_x28_arm64) {
reg_value.SetUInt64(tester->gpr.x[reg - gpr_x0_arm64]);
return true;
}
if (reg >= gpr_w0_arm64 && reg <= gpr_w28_arm64) {
reg_value.SetUInt32(tester->gpr.x[reg - gpr_w0_arm64]);
return true;
}
switch (reg) {
case gpr_fp_arm64:
reg_value.SetUInt64(tester->gpr.fp);
return true;
case gpr_lr_arm64:
reg_value.SetUInt64(tester->gpr.lr);
return true;
case gpr_sp_arm64:
reg_value.SetUInt64(tester->gpr.sp);
return true;
case gpr_pc_arm64:
reg_value.SetUInt64(tester->gpr.pc);
return true;
case gpr_cpsr_arm64:
reg_value.SetUInt64(tester->gpr.cpsr);
return true;
default:
return false;
}
}
static bool WriteRegisterCallback(EmulateInstruction *instruction,
void *baton, const Context &context,
const RegisterInfo *reg_info,
const RegisterValue &reg_value) {
auto *tester = static_cast<Arch64EmulatorTester *>(instruction);
uint32_t reg = reg_info->kinds[eRegisterKindLLDB];
if (reg >= gpr_x0_arm64 && reg <= gpr_x28_arm64) {
tester->gpr.x[reg - gpr_x0_arm64] = reg_value.GetAsUInt64();
return true;
}
if (reg >= gpr_w0_arm64 && reg <= gpr_w28_arm64) {
tester->gpr.x[reg - gpr_w0_arm64] = reg_value.GetAsUInt32();
return true;
}
switch (reg) {
case gpr_fp_arm64:
tester->gpr.fp = reg_value.GetAsUInt64();
return true;
case gpr_lr_arm64:
tester->gpr.lr = reg_value.GetAsUInt64();
return true;
case gpr_sp_arm64:
tester->gpr.sp = reg_value.GetAsUInt64();
return true;
case gpr_pc_arm64:
tester->gpr.pc = reg_value.GetAsUInt64();
return true;
case gpr_cpsr_arm64:
tester->gpr.cpsr = reg_value.GetAsUInt64();
return true;
default:
return false;
}
}
static size_t ReadMemoryCallback(EmulateInstruction *instruction, void *baton,
const Context &context, addr_t addr,
void *dst, size_t length) {
auto *tester = static_cast<Arch64EmulatorTester *>(instruction);
assert(addr >= tester->memory_offset);
assert(addr - tester->memory_offset + length <= sizeof(tester->memory));
if (addr >= tester->memory_offset &&
addr - tester->memory_offset + length <= sizeof(tester->memory)) {
memcpy(dst, tester->memory + addr - tester->memory_offset, length);
return length;
}
return 0;
};
static size_t WriteMemoryCallback(EmulateInstruction *instruction,
void *baton, const Context &context,
addr_t addr, const void *dst,
size_t length) {
llvm_unreachable("implement when required");
return 0;
};
static uint64_t AddWithCarry(uint32_t N, uint64_t x, uint64_t y, bool carry_in,
EmulateInstructionARM64::ProcState &proc_state) {
return EmulateInstructionARM64::AddWithCarry(N, x, y, carry_in, proc_state);
}
};
class TestAArch64Emulator : public testing::Test {
public:
static void SetUpTestCase();
static void TearDownTestCase();
protected:
};
void TestAArch64Emulator::SetUpTestCase() {
EmulateInstructionARM64::Initialize();
}
void TestAArch64Emulator::TearDownTestCase() {
EmulateInstructionARM64::Terminate();
}
TEST_F(TestAArch64Emulator, TestOverflow) {
EmulateInstructionARM64::ProcState pstate;
memset(&pstate, 0, sizeof(pstate));
uint64_t ll_max = std::numeric_limits<int64_t>::max();
Arch64EmulatorTester emu;
ASSERT_EQ(emu.AddWithCarry(64, ll_max, 0, 0, pstate), ll_max);
ASSERT_EQ(pstate.V, 0ULL);
ASSERT_EQ(pstate.C, 0ULL);
ASSERT_EQ(emu.AddWithCarry(64, ll_max, 1, 0, pstate), (uint64_t)(ll_max + 1));
ASSERT_EQ(pstate.V, 1ULL);
ASSERT_EQ(pstate.C, 0ULL);
ASSERT_EQ(emu.AddWithCarry(64, ll_max, 0, 1, pstate), (uint64_t)(ll_max + 1));
ASSERT_EQ(pstate.V, 1ULL);
ASSERT_EQ(pstate.C, 0ULL);
}
TEST_F(TestAArch64Emulator, TestAutoAdvancePC) {
Arch64EmulatorTester emu;
emu.memory_offset = 0x123456789abcde00;
emu.gpr.pc = 0x123456789abcde00;
emu.gpr.x[8] = 0x123456789abcde20;
memcpy(emu.memory, "\x08\x01\x40\xb9", 4); // ldr w8, [x8]
memcpy(emu.memory + 0x20, "\x11\x22\x33\x44", 4); // 0x44332211
ASSERT_TRUE(emu.ReadInstruction());
ASSERT_TRUE(
emu.EvaluateInstruction(eEmulateInstructionOptionAutoAdvancePC |
eEmulateInstructionOptionIgnoreConditions));
ASSERT_EQ(emu.gpr.pc, 0x123456789abcde04);
ASSERT_EQ(emu.gpr.x[8], 0x44332211);
}