forked from OSchip/llvm-project
[LLDB][RISCV] Make software single stepping work
Add: - `EmulateInstructionRISCV`, which can be used for riscv32 and riscv64. - Add unittests for EmulateInstructionRISCV. Note: Compressed instructions set (RVC) was still not supported in this patch. Reviewed By: DavidSpickett Differential Revision: https://reviews.llvm.org/D131759
This commit is contained in:
parent
8ed3e75c96
commit
4fc7e9cba2
|
@ -3,3 +3,4 @@ add_subdirectory(ARM64)
|
|||
add_subdirectory(MIPS)
|
||||
add_subdirectory(MIPS64)
|
||||
add_subdirectory(PPC64)
|
||||
add_subdirectory(RISCV)
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
add_lldb_library(lldbPluginInstructionRISCV PLUGIN
|
||||
EmulateInstructionRISCV.cpp
|
||||
|
||||
LINK_LIBS
|
||||
lldbCore
|
||||
lldbInterpreter
|
||||
lldbSymbol
|
||||
lldbPluginProcessUtility
|
||||
LINK_COMPONENTS
|
||||
Support
|
||||
)
|
|
@ -0,0 +1,356 @@
|
|||
//===-- EmulateInstructionRISCV.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 <cstdlib>
|
||||
|
||||
#include "EmulateInstructionRISCV.h"
|
||||
#include "Plugins/Process/Utility/RegisterInfoPOSIX_riscv64.h"
|
||||
#include "Plugins/Process/Utility/lldb-riscv-register-enums.h"
|
||||
|
||||
#include "lldb/Core/Address.h"
|
||||
#include "lldb/Core/PluginManager.h"
|
||||
#include "lldb/Interpreter/OptionValueArray.h"
|
||||
#include "lldb/Interpreter/OptionValueDictionary.h"
|
||||
#include "lldb/Symbol/UnwindPlan.h"
|
||||
#include "lldb/Utility/ArchSpec.h"
|
||||
#include "lldb/Utility/LLDBLog.h"
|
||||
#include "lldb/Utility/RegisterValue.h"
|
||||
#include "lldb/Utility/Stream.h"
|
||||
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "llvm/Support/MathExtras.h"
|
||||
|
||||
using namespace lldb;
|
||||
using namespace lldb_private;
|
||||
|
||||
LLDB_PLUGIN_DEFINE_ADV(EmulateInstructionRISCV, InstructionRISCV)
|
||||
|
||||
namespace lldb_private {
|
||||
|
||||
// Masks for detecting instructions types. According to riscv-spec Chap 26.
|
||||
constexpr uint32_t I_MASK = 0b111000001111111;
|
||||
constexpr uint32_t J_MASK = 0b000000001111111;
|
||||
// no funct3 in the b-mask because the logic executing B<CMP> is quite similar.
|
||||
constexpr uint32_t B_MASK = 0b000000001111111;
|
||||
|
||||
// The funct3 is the type of compare in B<CMP> instructions.
|
||||
// funct3 means "3-bits function selector", which RISC-V ISA uses as minor
|
||||
// opcode. It reuses the major opcode encoding space.
|
||||
constexpr uint32_t BEQ = 0b000;
|
||||
constexpr uint32_t BNE = 0b001;
|
||||
constexpr uint32_t BLT = 0b100;
|
||||
constexpr uint32_t BGE = 0b101;
|
||||
constexpr uint32_t BLTU = 0b110;
|
||||
constexpr uint32_t BGEU = 0b111;
|
||||
|
||||
constexpr uint32_t DecodeRD(uint32_t inst) { return (inst & 0xF80) >> 7; }
|
||||
constexpr uint32_t DecodeRS1(uint32_t inst) { return (inst & 0xF8000) >> 15; }
|
||||
constexpr uint32_t DecodeRS2(uint32_t inst) { return (inst & 0x1F00000) >> 20; }
|
||||
constexpr uint32_t DecodeFunct3(uint32_t inst) { return (inst & 0x7000) >> 12; }
|
||||
|
||||
constexpr int32_t SignExt(uint32_t imm) { return int32_t(imm); }
|
||||
|
||||
constexpr uint32_t DecodeJImm(uint32_t inst) {
|
||||
return (uint64_t(int64_t(int32_t(inst & 0x80000000)) >> 11)) // imm[20]
|
||||
| (inst & 0xff000) // imm[19:12]
|
||||
| ((inst >> 9) & 0x800) // imm[11]
|
||||
| ((inst >> 20) & 0x7fe); // imm[10:1]
|
||||
}
|
||||
|
||||
constexpr uint32_t DecodeIImm(uint32_t inst) {
|
||||
return int64_t(int32_t(inst)) >> 20; // imm[11:0]
|
||||
}
|
||||
|
||||
constexpr uint32_t DecodeBImm(uint32_t inst) {
|
||||
return (uint64_t(int64_t(int32_t(inst & 0x80000000)) >> 19)) // imm[12]
|
||||
| ((inst & 0x80) << 4) // imm[11]
|
||||
| ((inst >> 20) & 0x7e0) // imm[10:5]
|
||||
| ((inst >> 7) & 0x1e); // imm[4:1]
|
||||
}
|
||||
|
||||
static uint32_t GPREncodingToLLDB(uint32_t reg_encode) {
|
||||
if (reg_encode == 0)
|
||||
return gpr_x0_riscv;
|
||||
if (reg_encode >= 1 && reg_encode <= 31)
|
||||
return gpr_x1_riscv + reg_encode - 1;
|
||||
return LLDB_INVALID_REGNUM;
|
||||
}
|
||||
|
||||
static bool ReadRegister(EmulateInstructionRISCV *emulator, uint32_t reg_encode,
|
||||
RegisterValue &value) {
|
||||
uint32_t lldb_reg = GPREncodingToLLDB(reg_encode);
|
||||
return emulator->ReadRegister(eRegisterKindLLDB, lldb_reg, value);
|
||||
}
|
||||
|
||||
static bool WriteRegister(EmulateInstructionRISCV *emulator,
|
||||
uint32_t reg_encode, const RegisterValue &value) {
|
||||
uint32_t lldb_reg = GPREncodingToLLDB(reg_encode);
|
||||
EmulateInstruction::Context ctx;
|
||||
ctx.type = EmulateInstruction::eContextRegisterStore;
|
||||
ctx.SetNoArgs();
|
||||
return emulator->WriteRegister(ctx, eRegisterKindLLDB, lldb_reg, value);
|
||||
}
|
||||
|
||||
static bool ExecJAL(EmulateInstructionRISCV *emulator, uint32_t inst, bool) {
|
||||
bool success = false;
|
||||
int64_t offset = SignExt(DecodeJImm(inst));
|
||||
int64_t pc = emulator->ReadPC(&success);
|
||||
return success && emulator->WritePC(pc + offset) &&
|
||||
WriteRegister(emulator, DecodeRD(inst),
|
||||
RegisterValue(uint64_t(pc + 4)));
|
||||
}
|
||||
|
||||
static bool ExecJALR(EmulateInstructionRISCV *emulator, uint32_t inst, bool) {
|
||||
int64_t offset = SignExt(DecodeIImm(inst));
|
||||
RegisterValue value;
|
||||
if (!ReadRegister(emulator, DecodeRS1(inst), value))
|
||||
return false;
|
||||
bool success = false;
|
||||
int64_t pc = emulator->ReadPC(&success);
|
||||
int64_t rs1 = int64_t(value.GetAsUInt64());
|
||||
// JALR clears the bottom bit. According to riscv-spec:
|
||||
// "The JALR instruction now clears the lowest bit of the calculated target
|
||||
// address, to simplify hardware and to allow auxiliary information to be
|
||||
// stored in function pointers."
|
||||
return emulator->WritePC((rs1 + offset) & ~1) &&
|
||||
WriteRegister(emulator, DecodeRD(inst),
|
||||
RegisterValue(uint64_t(pc + 4)));
|
||||
}
|
||||
|
||||
static bool CompareB(uint64_t rs1, uint64_t rs2, uint32_t funct3) {
|
||||
switch (funct3) {
|
||||
case BEQ:
|
||||
return rs1 == rs2;
|
||||
case BNE:
|
||||
return rs1 != rs2;
|
||||
case BLT:
|
||||
return int64_t(rs1) < int64_t(rs2);
|
||||
case BGE:
|
||||
return int64_t(rs1) >= int64_t(rs2);
|
||||
case BLTU:
|
||||
return rs1 < rs2;
|
||||
case BGEU:
|
||||
return rs1 >= rs2;
|
||||
default:
|
||||
llvm_unreachable("unexpected funct3");
|
||||
}
|
||||
}
|
||||
|
||||
static bool ExecB(EmulateInstructionRISCV *emulator, uint32_t inst,
|
||||
bool ignore_cond) {
|
||||
bool success = false;
|
||||
uint64_t pc = emulator->ReadPC(&success);
|
||||
if (!success)
|
||||
return false;
|
||||
|
||||
uint64_t offset = SignExt(DecodeBImm(inst));
|
||||
uint64_t target = pc + offset;
|
||||
if (ignore_cond)
|
||||
return emulator->WritePC(target);
|
||||
|
||||
RegisterValue value1;
|
||||
RegisterValue value2;
|
||||
if (!ReadRegister(emulator, DecodeRS1(inst), value1) ||
|
||||
!ReadRegister(emulator, DecodeRS2(inst), value2))
|
||||
return false;
|
||||
|
||||
uint32_t funct3 = DecodeFunct3(inst);
|
||||
if (CompareB(value1.GetAsUInt64(), value2.GetAsUInt64(), funct3))
|
||||
return emulator->WritePC(target);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
struct InstrPattern {
|
||||
const char *name;
|
||||
/// Bit mask to check the type of a instruction (B-Type, I-Type, J-Type, etc.)
|
||||
uint32_t type_mask;
|
||||
/// Characteristic value after bitwise-and with type_mask.
|
||||
uint32_t eigen;
|
||||
bool (*exec)(EmulateInstructionRISCV *emulator, uint32_t inst,
|
||||
bool ignore_cond);
|
||||
};
|
||||
|
||||
static InstrPattern PATTERNS[] = {
|
||||
{"JAL", J_MASK, 0b1101111, ExecJAL},
|
||||
{"JALR", I_MASK, 0b000000001100111, ExecJALR},
|
||||
{"B<CMP>", B_MASK, 0b1100011, ExecB},
|
||||
// TODO: {LR/SC}.{W/D} and ECALL
|
||||
};
|
||||
|
||||
/// This function only determines the next instruction address for software
|
||||
/// sigle stepping by emulating branching instructions including:
|
||||
/// - from Base Instruction Set : JAL, JALR, B<CMP>, ECALL
|
||||
/// - from Atomic Instruction Set: LR -> BNE -> SC -> BNE
|
||||
/// We will get rid of this tedious code when the riscv debug spec is ratified.
|
||||
bool EmulateInstructionRISCV::DecodeAndExecute(uint32_t inst,
|
||||
bool ignore_cond) {
|
||||
Log *log = GetLog(LLDBLog::Process | LLDBLog::Breakpoints);
|
||||
for (int i = 0; i < llvm::array_lengthof(PATTERNS); ++i) {
|
||||
const InstrPattern &pat = PATTERNS[i];
|
||||
if ((inst & pat.type_mask) == pat.eigen) {
|
||||
LLDB_LOGF(log, "EmulateInstructionRISCV::%s: inst(%x) was decoded to %s",
|
||||
__FUNCTION__, inst, pat.name);
|
||||
return pat.exec(this, inst, ignore_cond);
|
||||
}
|
||||
}
|
||||
|
||||
LLDB_LOGF(log,
|
||||
"EmulateInstructionRISCV::%s: inst(0x%x) does not branch: "
|
||||
"no need to calculate the next pc address which is trivial.",
|
||||
__FUNCTION__, inst);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EmulateInstructionRISCV::EvaluateInstruction(uint32_t options) {
|
||||
uint32_t inst_size = m_opcode.GetByteSize();
|
||||
uint32_t inst = m_opcode.GetOpcode32();
|
||||
bool increase_pc = options & eEmulateInstructionOptionAutoAdvancePC;
|
||||
bool ignore_cond = options & eEmulateInstructionOptionIgnoreConditions;
|
||||
bool success = false;
|
||||
|
||||
lldb::addr_t old_pc = 0;
|
||||
if (increase_pc) {
|
||||
old_pc = ReadPC(&success);
|
||||
if (!success)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (inst_size == 2) {
|
||||
// TODO: execute RVC
|
||||
return false;
|
||||
}
|
||||
|
||||
success = DecodeAndExecute(inst, ignore_cond);
|
||||
if (!success)
|
||||
return false;
|
||||
|
||||
if (increase_pc) {
|
||||
lldb::addr_t new_pc = ReadPC(&success);
|
||||
if (!success)
|
||||
return false;
|
||||
|
||||
if (new_pc == old_pc) {
|
||||
if (!WritePC(old_pc + inst_size))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EmulateInstructionRISCV::ReadInstruction() {
|
||||
bool success = false;
|
||||
m_addr = ReadPC(&success);
|
||||
if (!success) {
|
||||
m_addr = LLDB_INVALID_ADDRESS;
|
||||
return false;
|
||||
}
|
||||
|
||||
Context ctx;
|
||||
ctx.type = eContextReadOpcode;
|
||||
ctx.SetNoArgs();
|
||||
uint32_t inst = (uint32_t)ReadMemoryUnsigned(ctx, m_addr, 4, 0, &success);
|
||||
uint16_t try_rvc = (uint16_t)(inst & 0x0000ffff);
|
||||
// check whether the compressed encode could be valid
|
||||
uint16_t mask = try_rvc & 0b11;
|
||||
if (try_rvc != 0 && mask != 3) {
|
||||
m_opcode.SetOpcode16(try_rvc, GetByteOrder());
|
||||
} else {
|
||||
m_opcode.SetOpcode32(inst, GetByteOrder());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
lldb::addr_t EmulateInstructionRISCV::ReadPC(bool *success) {
|
||||
return ReadRegisterUnsigned(eRegisterKindGeneric, LLDB_REGNUM_GENERIC_PC,
|
||||
LLDB_INVALID_ADDRESS, success);
|
||||
}
|
||||
|
||||
bool EmulateInstructionRISCV::WritePC(lldb::addr_t pc) {
|
||||
EmulateInstruction::Context ctx;
|
||||
ctx.type = eContextAdvancePC;
|
||||
ctx.SetNoArgs();
|
||||
return WriteRegisterUnsigned(ctx, eRegisterKindGeneric,
|
||||
LLDB_REGNUM_GENERIC_PC, pc);
|
||||
}
|
||||
|
||||
bool EmulateInstructionRISCV::GetRegisterInfo(lldb::RegisterKind reg_kind,
|
||||
uint32_t reg_index,
|
||||
RegisterInfo ®_info) {
|
||||
if (reg_kind == eRegisterKindGeneric) {
|
||||
switch (reg_index) {
|
||||
case LLDB_REGNUM_GENERIC_PC:
|
||||
reg_kind = eRegisterKindLLDB;
|
||||
reg_index = gpr_pc_riscv;
|
||||
break;
|
||||
case LLDB_REGNUM_GENERIC_SP:
|
||||
reg_kind = eRegisterKindLLDB;
|
||||
reg_index = gpr_sp_riscv;
|
||||
break;
|
||||
case LLDB_REGNUM_GENERIC_FP:
|
||||
reg_kind = eRegisterKindLLDB;
|
||||
reg_index = gpr_fp_riscv;
|
||||
break;
|
||||
case LLDB_REGNUM_GENERIC_RA:
|
||||
reg_kind = eRegisterKindLLDB;
|
||||
reg_index = gpr_ra_riscv;
|
||||
break;
|
||||
// We may handle LLDB_REGNUM_GENERIC_ARGx when more instructions are
|
||||
// supported.
|
||||
default:
|
||||
llvm_unreachable("unsupported register");
|
||||
}
|
||||
}
|
||||
|
||||
const RegisterInfo *array =
|
||||
RegisterInfoPOSIX_riscv64::GetRegisterInfoPtr(m_arch);
|
||||
const uint32_t length =
|
||||
RegisterInfoPOSIX_riscv64::GetRegisterInfoCount(m_arch);
|
||||
|
||||
if (reg_index >= length || reg_kind != eRegisterKindLLDB)
|
||||
return false;
|
||||
|
||||
reg_info = array[reg_index];
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EmulateInstructionRISCV::SetTargetTriple(const ArchSpec &arch) {
|
||||
return SupportsThisArch(arch);
|
||||
}
|
||||
|
||||
bool EmulateInstructionRISCV::TestEmulation(Stream *out_stream, ArchSpec &arch,
|
||||
OptionValueDictionary *test_data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void EmulateInstructionRISCV::Initialize() {
|
||||
PluginManager::RegisterPlugin(GetPluginNameStatic(),
|
||||
GetPluginDescriptionStatic(), CreateInstance);
|
||||
}
|
||||
|
||||
void EmulateInstructionRISCV::Terminate() {
|
||||
PluginManager::UnregisterPlugin(CreateInstance);
|
||||
}
|
||||
|
||||
lldb_private::EmulateInstruction *
|
||||
EmulateInstructionRISCV::CreateInstance(const ArchSpec &arch,
|
||||
InstructionType inst_type) {
|
||||
if (EmulateInstructionRISCV::SupportsThisInstructionType(inst_type) &&
|
||||
SupportsThisArch(arch)) {
|
||||
return new EmulateInstructionRISCV(arch);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool EmulateInstructionRISCV::SupportsThisArch(const ArchSpec &arch) {
|
||||
return arch.GetTriple().isRISCV();
|
||||
}
|
||||
|
||||
} // namespace lldb_private
|
|
@ -0,0 +1,72 @@
|
|||
//===-- EmulateInstructionRISCV.h -----------------------------------------===//
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLDB_SOURCE_PLUGINS_INSTRUCTION_RISCV_EMULATEINSTRUCTIONRISCV_H
|
||||
#define LLDB_SOURCE_PLUGINS_INSTRUCTION_RISCV_EMULATEINSTRUCTIONRISCV_H
|
||||
|
||||
#include "lldb/Core/EmulateInstruction.h"
|
||||
#include "lldb/Interpreter/OptionValue.h"
|
||||
#include "lldb/Utility/Log.h"
|
||||
#include "lldb/Utility/Status.h"
|
||||
|
||||
namespace lldb_private {
|
||||
|
||||
class EmulateInstructionRISCV : public EmulateInstruction {
|
||||
public:
|
||||
static llvm::StringRef GetPluginNameStatic() { return "riscv"; }
|
||||
|
||||
static llvm::StringRef GetPluginDescriptionStatic() {
|
||||
return "Emulate instructions for the RISC-V architecture.";
|
||||
}
|
||||
|
||||
static bool SupportsThisInstructionType(InstructionType inst_type) {
|
||||
switch (inst_type) {
|
||||
case eInstructionTypeAny:
|
||||
case eInstructionTypePCModifying:
|
||||
return true;
|
||||
case eInstructionTypePrologueEpilogue:
|
||||
case eInstructionTypeAll:
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool SupportsThisArch(const ArchSpec &arch);
|
||||
|
||||
static lldb_private::EmulateInstruction *
|
||||
CreateInstance(const lldb_private::ArchSpec &arch, InstructionType inst_type);
|
||||
|
||||
static void Initialize();
|
||||
|
||||
static void Terminate();
|
||||
|
||||
public:
|
||||
EmulateInstructionRISCV(const ArchSpec &arch) : EmulateInstruction(arch) {}
|
||||
|
||||
llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); }
|
||||
|
||||
bool SupportsEmulatingInstructionsOfType(InstructionType inst_type) override {
|
||||
return SupportsThisInstructionType(inst_type);
|
||||
}
|
||||
|
||||
bool SetTargetTriple(const ArchSpec &arch) override;
|
||||
bool ReadInstruction() override;
|
||||
bool EvaluateInstruction(uint32_t options) override;
|
||||
bool TestEmulation(Stream *out_stream, ArchSpec &arch,
|
||||
OptionValueDictionary *test_data) override;
|
||||
bool GetRegisterInfo(lldb::RegisterKind reg_kind, uint32_t reg_num,
|
||||
RegisterInfo ®_info) override;
|
||||
|
||||
lldb::addr_t ReadPC(bool *success);
|
||||
bool WritePC(lldb::addr_t pc);
|
||||
bool DecodeAndExecute(uint32_t inst, bool ignore_cond);
|
||||
};
|
||||
|
||||
} // namespace lldb_private
|
||||
|
||||
#endif // LLDB_SOURCE_PLUGINS_INSTRUCTION_RISCV_EMULATEINSTRUCTIONRISCV_H
|
|
@ -882,7 +882,8 @@ bool NativeProcessLinux::MonitorClone(NativeThreadLinux &parent,
|
|||
}
|
||||
|
||||
bool NativeProcessLinux::SupportHardwareSingleStepping() const {
|
||||
if (m_arch.GetMachine() == llvm::Triple::arm || m_arch.IsMIPS())
|
||||
if (m_arch.IsMIPS() || m_arch.GetMachine() == llvm::Triple::arm ||
|
||||
m_arch.GetTriple().isRISCV())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -167,7 +167,8 @@ Status NativeProcessSoftwareSingleStep::SetupSoftwareSingleStepping(
|
|||
// Arm mode
|
||||
size_hint = 4;
|
||||
}
|
||||
} else if (arch.IsMIPS() || arch.GetTriple().isPPC64())
|
||||
} else if (arch.IsMIPS() || arch.GetTriple().isPPC64() ||
|
||||
arch.GetTriple().isRISCV())
|
||||
size_hint = 4;
|
||||
error = process.SetBreakpoint(next_pc, size_hint, /*hardware=*/false);
|
||||
|
||||
|
|
|
@ -53,6 +53,7 @@ add_lldb_tool(lldb-server
|
|||
lldbPluginInstructionARM
|
||||
lldbPluginInstructionMIPS
|
||||
lldbPluginInstructionMIPS64
|
||||
lldbPluginInstructionRISCV
|
||||
${LLDB_SYSTEM_LIBS}
|
||||
|
||||
LINK_COMPONENTS
|
||||
|
|
|
@ -41,6 +41,11 @@ using HostObjectFile = ObjectFileELF;
|
|||
#include "Plugins/Instruction/MIPS/EmulateInstructionMIPS.h"
|
||||
#endif
|
||||
|
||||
#if defined(__riscv)
|
||||
#define LLDB_TARGET_RISCV
|
||||
#include "Plugins/Instruction/RISCV/EmulateInstructionRISCV.h"
|
||||
#endif
|
||||
|
||||
using namespace lldb_private;
|
||||
|
||||
llvm::Error SystemInitializerLLGS::Initialize() {
|
||||
|
@ -58,6 +63,9 @@ llvm::Error SystemInitializerLLGS::Initialize() {
|
|||
#if defined(LLDB_TARGET_MIPS64)
|
||||
EmulateInstructionMIPS64::Initialize();
|
||||
#endif
|
||||
#if defined(LLDB_TARGET_RISCV)
|
||||
EmulateInstructionRISCV::Initialize();
|
||||
#endif
|
||||
|
||||
return llvm::Error::success();
|
||||
}
|
||||
|
@ -74,6 +82,9 @@ void SystemInitializerLLGS::Terminate() {
|
|||
#if defined(LLDB_TARGET_MIPS64)
|
||||
EmulateInstructionMIPS64::Terminate();
|
||||
#endif
|
||||
#if defined(LLDB_TARGET_RISCV)
|
||||
EmulateInstructionRISCV::Terminate();
|
||||
#endif
|
||||
|
||||
SystemInitializerCommon::Terminate();
|
||||
}
|
||||
|
|
|
@ -1,12 +1,29 @@
|
|||
if("ARM" IN_LIST LLVM_TARGETS_TO_BUILD)
|
||||
set(FILES "")
|
||||
set(DEPS "")
|
||||
|
||||
if ("ARM" IN_LIST LLVM_TARGETS_TO_BUILD)
|
||||
list(APPEND FILES ARM64/TestAArch64Emulator.cpp)
|
||||
list(APPEND DEPS lldbPluginInstructionARM64)
|
||||
endif ()
|
||||
|
||||
if ("RISCV" IN_LIST LLVM_TARGETS_TO_BUILD)
|
||||
list(APPEND FILES RISCV/TestRISCVEmulator.cpp)
|
||||
list(APPEND DEPS lldbPluginInstructionRISCV)
|
||||
endif ()
|
||||
|
||||
list(LENGTH FILES LISTLEN)
|
||||
|
||||
if (LISTLEN GREATER 0)
|
||||
add_lldb_unittest(EmulatorTests
|
||||
TestAArch64Emulator.cpp
|
||||
${FILES}
|
||||
|
||||
LINK_LIBS
|
||||
lldbCore
|
||||
lldbSymbol
|
||||
lldbTarget
|
||||
lldbPluginInstructionARM64
|
||||
${DEPS}
|
||||
LINK_COMPONENTS
|
||||
Support
|
||||
${LLVM_TARGETS_TO_BUILD})
|
||||
endif()
|
||||
${LLVM_TARGETS_TO_BUILD}
|
||||
)
|
||||
endif ()
|
||||
|
|
|
@ -0,0 +1,196 @@
|
|||
//===-- TestRISCVEmulator.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/Core/PluginManager.h"
|
||||
#include "lldb/Target/ExecutionContext.h"
|
||||
#include "lldb/Utility/ArchSpec.h"
|
||||
#include "lldb/Utility/RegisterValue.h"
|
||||
|
||||
#include "Plugins/Instruction/RISCV/EmulateInstructionRISCV.h"
|
||||
#include "Plugins/Process/Utility/RegisterInfoPOSIX_riscv64.h"
|
||||
#include "Plugins/Process/Utility/lldb-riscv-register-enums.h"
|
||||
|
||||
using namespace lldb;
|
||||
using namespace lldb_private;
|
||||
|
||||
struct RISCVEmulatorTester : public EmulateInstructionRISCV, testing::Test {
|
||||
RegisterInfoPOSIX_riscv64::GPR gpr;
|
||||
|
||||
RISCVEmulatorTester()
|
||||
: EmulateInstructionRISCV(ArchSpec("riscv64-unknown-linux-gnu")) {
|
||||
EmulateInstruction::SetReadRegCallback(ReadRegisterCallback);
|
||||
EmulateInstruction::SetWriteRegCallback(WriteRegisterCallback);
|
||||
}
|
||||
|
||||
static bool ReadRegisterCallback(EmulateInstruction *instruction, void *baton,
|
||||
const RegisterInfo *reg_info,
|
||||
RegisterValue ®_value) {
|
||||
RISCVEmulatorTester *tester = (RISCVEmulatorTester *)instruction;
|
||||
uint32_t reg = reg_info->kinds[eRegisterKindLLDB];
|
||||
if (reg == gpr_x0_riscv)
|
||||
reg_value.SetUInt(0, reg_info->byte_size);
|
||||
else
|
||||
reg_value.SetUInt(tester->gpr.gpr[reg], reg_info->byte_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool WriteRegisterCallback(EmulateInstruction *instruction,
|
||||
void *baton, const Context &context,
|
||||
const RegisterInfo *reg_info,
|
||||
const RegisterValue ®_value) {
|
||||
RISCVEmulatorTester *tester = (RISCVEmulatorTester *)instruction;
|
||||
uint32_t reg = reg_info->kinds[eRegisterKindLLDB];
|
||||
if (reg != gpr_x0_riscv)
|
||||
tester->gpr.gpr[reg] = reg_value.GetAsUInt64();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(RISCVEmulatorTester, testJAL) {
|
||||
lldb::addr_t old_pc = 0x114514;
|
||||
WritePC(old_pc);
|
||||
// jal x1, -6*4
|
||||
uint32_t inst = 0b11111110100111111111000011101111;
|
||||
ASSERT_TRUE(DecodeAndExecute(inst, false));
|
||||
auto x1 = gpr.gpr[1];
|
||||
|
||||
bool success = false;
|
||||
auto pc = ReadPC(&success);
|
||||
|
||||
ASSERT_TRUE(success);
|
||||
ASSERT_EQ(x1, old_pc + 4);
|
||||
ASSERT_EQ(pc, old_pc + (-6 * 4));
|
||||
}
|
||||
|
||||
constexpr uint32_t EncodeIType(uint32_t opcode, uint32_t funct3, uint32_t rd,
|
||||
uint32_t rs1, uint32_t imm) {
|
||||
return imm << 20 | rs1 << 15 | funct3 << 12 | rd << 7 | opcode;
|
||||
}
|
||||
|
||||
constexpr uint32_t JALR(uint32_t rd, uint32_t rs1, int32_t offset) {
|
||||
return EncodeIType(0b1100111, 0, rd, rs1, uint32_t(offset));
|
||||
}
|
||||
|
||||
TEST_F(RISCVEmulatorTester, testJALR) {
|
||||
lldb::addr_t old_pc = 0x114514;
|
||||
lldb::addr_t old_x2 = 0x1024;
|
||||
WritePC(old_pc);
|
||||
gpr.gpr[2] = old_x2;
|
||||
// jalr x1, x2(-255)
|
||||
uint32_t inst = JALR(1, 2, -255);
|
||||
ASSERT_TRUE(DecodeAndExecute(inst, false));
|
||||
auto x1 = gpr.gpr[1];
|
||||
|
||||
bool success = false;
|
||||
auto pc = ReadPC(&success);
|
||||
|
||||
ASSERT_TRUE(success);
|
||||
ASSERT_EQ(x1, old_pc + 4);
|
||||
// JALR always zeros the bottom bit of the target address.
|
||||
ASSERT_EQ(pc, (old_x2 + (-255)) & (~1));
|
||||
}
|
||||
|
||||
constexpr uint32_t EncodeBType(uint32_t opcode, uint32_t funct3, uint32_t rs1,
|
||||
uint32_t rs2, uint32_t imm) {
|
||||
uint32_t bimm = (imm & (0b1 << 11)) >> 4 | (imm & (0b11110)) << 7 |
|
||||
(imm & (0b111111 << 5)) << 20 | (imm & (0b1 << 12)) << 19;
|
||||
|
||||
return rs2 << 20 | rs1 << 15 | funct3 << 12 | opcode | bimm;
|
||||
}
|
||||
|
||||
constexpr uint32_t BEQ(uint32_t rs1, uint32_t rs2, int32_t offset) {
|
||||
return EncodeBType(0b1100011, 0b000, rs1, rs2, uint32_t(offset));
|
||||
}
|
||||
|
||||
constexpr uint32_t BNE(uint32_t rs1, uint32_t rs2, int32_t offset) {
|
||||
return EncodeBType(0b1100011, 0b001, rs1, rs2, uint32_t(offset));
|
||||
}
|
||||
|
||||
constexpr uint32_t BLT(uint32_t rs1, uint32_t rs2, int32_t offset) {
|
||||
return EncodeBType(0b1100011, 0b100, rs1, rs2, uint32_t(offset));
|
||||
}
|
||||
|
||||
constexpr uint32_t BGE(uint32_t rs1, uint32_t rs2, int32_t offset) {
|
||||
return EncodeBType(0b1100011, 0b101, rs1, rs2, uint32_t(offset));
|
||||
}
|
||||
|
||||
constexpr uint32_t BLTU(uint32_t rs1, uint32_t rs2, int32_t offset) {
|
||||
return EncodeBType(0b1100011, 0b110, rs1, rs2, uint32_t(offset));
|
||||
}
|
||||
|
||||
constexpr uint32_t BGEU(uint32_t rs1, uint32_t rs2, int32_t offset) {
|
||||
return EncodeBType(0b1100011, 0b111, rs1, rs2, uint32_t(offset));
|
||||
}
|
||||
|
||||
using EncoderB = uint32_t (*)(uint32_t rs1, uint32_t rs2, int32_t offset);
|
||||
|
||||
void testBranch(RISCVEmulatorTester *tester, EncoderB encoder, bool branched,
|
||||
uint64_t rs1, uint64_t rs2) {
|
||||
// prepare test registers
|
||||
lldb::addr_t old_pc = 0x114514;
|
||||
tester->WritePC(old_pc);
|
||||
tester->gpr.gpr[1] = rs1;
|
||||
tester->gpr.gpr[2] = rs2;
|
||||
// b<cmp> x1, x2, (-256)
|
||||
uint32_t inst = encoder(1, 2, -256);
|
||||
ASSERT_TRUE(tester->DecodeAndExecute(inst, false));
|
||||
bool success = false;
|
||||
auto pc = tester->ReadPC(&success);
|
||||
ASSERT_TRUE(success);
|
||||
ASSERT_EQ(pc, old_pc + (branched ? (-256) : 0));
|
||||
}
|
||||
|
||||
#define GEN_BRANCH_TEST(name, rs1, rs2_branched, rs2_continued) \
|
||||
TEST_F(RISCVEmulatorTester, test##name##Branched) { \
|
||||
testBranch(this, name, true, rs1, rs2_branched); \
|
||||
} \
|
||||
TEST_F(RISCVEmulatorTester, test##name##Continued) { \
|
||||
testBranch(this, name, false, rs1, rs2_continued); \
|
||||
}
|
||||
|
||||
// GEN_BRANCH_TEST(opcode, imm1, imm2, imm3):
|
||||
// It should branch for instruction `opcode imm1, imm2`
|
||||
// It should do nothing for instruction `opcode imm1, imm3`
|
||||
GEN_BRANCH_TEST(BEQ, 1, 1, 0)
|
||||
GEN_BRANCH_TEST(BNE, 1, 0, 1)
|
||||
GEN_BRANCH_TEST(BLT, -2, 1, -3)
|
||||
GEN_BRANCH_TEST(BGE, -2, -3, 1)
|
||||
GEN_BRANCH_TEST(BLTU, -2, -1, 1)
|
||||
GEN_BRANCH_TEST(BGEU, -2, 1, -1)
|
||||
|
||||
void testNothing(RISCVEmulatorTester *tester, uint32_t inst) {
|
||||
lldb::addr_t old_pc = 0x114514;
|
||||
tester->WritePC(old_pc);
|
||||
tester->SetInstruction(Opcode(inst, tester->GetByteOrder()),
|
||||
LLDB_INVALID_ADDRESS, nullptr);
|
||||
ASSERT_TRUE(tester->EvaluateInstruction(0));
|
||||
bool success = false;
|
||||
auto pc = tester->ReadPC(&success);
|
||||
ASSERT_TRUE(success);
|
||||
ASSERT_EQ(pc, old_pc);
|
||||
ASSERT_TRUE(
|
||||
tester->EvaluateInstruction(eEmulateInstructionOptionAutoAdvancePC));
|
||||
pc = tester->ReadPC(&success);
|
||||
ASSERT_TRUE(success);
|
||||
ASSERT_EQ(pc, old_pc + 4);
|
||||
}
|
||||
|
||||
#define GEN_NOTHING_TEST(name, inst) \
|
||||
TEST_F(RISCVEmulatorTester, testDoNothing_##name) { testNothing(this, inst); }
|
||||
|
||||
// GEN_NOTHING_TEST(name, inst):
|
||||
// It should do nothing (except increasing pc) for instruction `inst`
|
||||
GEN_NOTHING_TEST(mv, 0x01813083) // mv a0, a5
|
||||
GEN_NOTHING_TEST(li, 0x00078513) // li a5, 0
|
||||
GEN_NOTHING_TEST(sd, 0x02010413) // sd s0, sp(16)
|
||||
GEN_NOTHING_TEST(lw, 0x0007879b) // lw a5, s0(-20)
|
||||
GEN_NOTHING_TEST(addi, 0x00113423) // addi sp, sp, -16
|
Loading…
Reference in New Issue