[Lldb-commits] [lldb] 4fc7e9c - [LLDB][RISCV] Make software single stepping work

via lldb-commits lldb-commits at lists.llvm.org
Tue Aug 16 08:44:59 PDT 2022


Author: Emmmer
Date: 2022-08-16T23:44:50+08:00
New Revision: 4fc7e9cba24b3f96f274126218a7985f31aa5861

URL: https://github.com/llvm/llvm-project/commit/4fc7e9cba24b3f96f274126218a7985f31aa5861
DIFF: https://github.com/llvm/llvm-project/commit/4fc7e9cba24b3f96f274126218a7985f31aa5861.diff

LOG: [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

Added: 
    lldb/source/Plugins/Instruction/RISCV/CMakeLists.txt
    lldb/source/Plugins/Instruction/RISCV/EmulateInstructionRISCV.cpp
    lldb/source/Plugins/Instruction/RISCV/EmulateInstructionRISCV.h
    lldb/unittests/Instruction/ARM64/TestAArch64Emulator.cpp
    lldb/unittests/Instruction/RISCV/TestRISCVEmulator.cpp

Modified: 
    lldb/source/Plugins/Instruction/CMakeLists.txt
    lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp
    lldb/source/Plugins/Process/Utility/NativeProcessSoftwareSingleStep.cpp
    lldb/tools/lldb-server/CMakeLists.txt
    lldb/tools/lldb-server/SystemInitializerLLGS.cpp
    lldb/unittests/Instruction/CMakeLists.txt

Removed: 
    lldb/unittests/Instruction/TestAArch64Emulator.cpp


################################################################################
diff  --git a/lldb/source/Plugins/Instruction/CMakeLists.txt b/lldb/source/Plugins/Instruction/CMakeLists.txt
index 89771e8f46d14..631c0b307cac4 100644
--- a/lldb/source/Plugins/Instruction/CMakeLists.txt
+++ b/lldb/source/Plugins/Instruction/CMakeLists.txt
@@ -3,3 +3,4 @@ add_subdirectory(ARM64)
 add_subdirectory(MIPS)
 add_subdirectory(MIPS64)
 add_subdirectory(PPC64)
+add_subdirectory(RISCV)

diff  --git a/lldb/source/Plugins/Instruction/RISCV/CMakeLists.txt b/lldb/source/Plugins/Instruction/RISCV/CMakeLists.txt
new file mode 100644
index 0000000000000..ed05d4a3132a1
--- /dev/null
+++ b/lldb/source/Plugins/Instruction/RISCV/CMakeLists.txt
@@ -0,0 +1,11 @@
+add_lldb_library(lldbPluginInstructionRISCV PLUGIN
+  EmulateInstructionRISCV.cpp
+
+  LINK_LIBS
+    lldbCore
+    lldbInterpreter
+    lldbSymbol
+    lldbPluginProcessUtility
+  LINK_COMPONENTS
+    Support
+  )

diff  --git a/lldb/source/Plugins/Instruction/RISCV/EmulateInstructionRISCV.cpp b/lldb/source/Plugins/Instruction/RISCV/EmulateInstructionRISCV.cpp
new file mode 100644
index 0000000000000..458b996d6ff62
--- /dev/null
+++ b/lldb/source/Plugins/Instruction/RISCV/EmulateInstructionRISCV.cpp
@@ -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 &reg_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

diff  --git a/lldb/source/Plugins/Instruction/RISCV/EmulateInstructionRISCV.h b/lldb/source/Plugins/Instruction/RISCV/EmulateInstructionRISCV.h
new file mode 100644
index 0000000000000..6df9a7d409a75
--- /dev/null
+++ b/lldb/source/Plugins/Instruction/RISCV/EmulateInstructionRISCV.h
@@ -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 &reg_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

diff  --git a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp
index a0f9f476a3c6f..a76cc6a8f0acf 100644
--- a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp
+++ b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp
@@ -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;
 }

diff  --git a/lldb/source/Plugins/Process/Utility/NativeProcessSoftwareSingleStep.cpp b/lldb/source/Plugins/Process/Utility/NativeProcessSoftwareSingleStep.cpp
index f9520cf4cfb75..b8bf30f3b6de4 100644
--- a/lldb/source/Plugins/Process/Utility/NativeProcessSoftwareSingleStep.cpp
+++ b/lldb/source/Plugins/Process/Utility/NativeProcessSoftwareSingleStep.cpp
@@ -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);
 

diff  --git a/lldb/tools/lldb-server/CMakeLists.txt b/lldb/tools/lldb-server/CMakeLists.txt
index 3fb22b0a61098..833563b5a0fe5 100644
--- a/lldb/tools/lldb-server/CMakeLists.txt
+++ b/lldb/tools/lldb-server/CMakeLists.txt
@@ -53,6 +53,7 @@ add_lldb_tool(lldb-server
       lldbPluginInstructionARM
       lldbPluginInstructionMIPS
       lldbPluginInstructionMIPS64
+      lldbPluginInstructionRISCV
       ${LLDB_SYSTEM_LIBS}
 
     LINK_COMPONENTS

diff  --git a/lldb/tools/lldb-server/SystemInitializerLLGS.cpp b/lldb/tools/lldb-server/SystemInitializerLLGS.cpp
index b93e6b40dcd94..a8bdcdf0e77c9 100644
--- a/lldb/tools/lldb-server/SystemInitializerLLGS.cpp
+++ b/lldb/tools/lldb-server/SystemInitializerLLGS.cpp
@@ -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();
 }

diff  --git a/lldb/unittests/Instruction/TestAArch64Emulator.cpp b/lldb/unittests/Instruction/ARM64/TestAArch64Emulator.cpp
similarity index 100%
rename from lldb/unittests/Instruction/TestAArch64Emulator.cpp
rename to lldb/unittests/Instruction/ARM64/TestAArch64Emulator.cpp

diff  --git a/lldb/unittests/Instruction/CMakeLists.txt b/lldb/unittests/Instruction/CMakeLists.txt
index 63d8298310239..c02afe95617eb 100644
--- a/lldb/unittests/Instruction/CMakeLists.txt
+++ b/lldb/unittests/Instruction/CMakeLists.txt
@@ -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 ()

diff  --git a/lldb/unittests/Instruction/RISCV/TestRISCVEmulator.cpp b/lldb/unittests/Instruction/RISCV/TestRISCVEmulator.cpp
new file mode 100644
index 0000000000000..c49f33ddd0d7e
--- /dev/null
+++ b/lldb/unittests/Instruction/RISCV/TestRISCVEmulator.cpp
@@ -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 &reg_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 &reg_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


        


More information about the lldb-commits mailing list