Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion lldb/include/lldb/Core/Opcode.h
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,9 @@ class Opcode {
int Dump(Stream *s, uint32_t min_byte_width) const;

const void *GetOpcodeBytes() const {
return ((m_type == Opcode::eTypeBytes) ? m_data.inst.bytes : nullptr);
return ((m_type == Opcode::eTypeBytes || m_type == Opcode::eType16_32Tuples)
? m_data.inst.bytes
: nullptr);
}

uint32_t GetByteSize() const {
Expand Down
176 changes: 164 additions & 12 deletions lldb/source/Plugins/Instruction/RISCV/EmulateInstructionRISCV.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ LLDB_PLUGIN_DEFINE_ADV(EmulateInstructionRISCV, InstructionRISCV)

namespace lldb_private {

// RISC-V General Purpose Register numbers
static constexpr uint32_t RISCV_GPR_SP = 2; // x2 is the stack pointer
static constexpr uint32_t RISCV_GPR_FP = 8; // x8 is the frame pointer

/// Returns all values wrapped in Optional, or std::nullopt if any of the values
/// is std::nullopt.
template <typename... Ts>
Expand Down Expand Up @@ -108,6 +112,16 @@ static uint32_t FPREncodingToLLDB(uint32_t reg_encode) {
return LLDB_INVALID_REGNUM;
}

// Helper function to get register info from GPR encoding
static std::optional<RegisterInfo>
GPREncodingToRegisterInfo(EmulateInstructionRISCV &emulator,
uint32_t reg_encode) {
uint32_t lldb_reg = GPREncodingToLLDB(reg_encode);
if (lldb_reg == LLDB_INVALID_REGNUM)
return std::nullopt;
return emulator.GetRegisterInfo(eRegisterKindLLDB, lldb_reg);
}

bool Rd::Write(EmulateInstructionRISCV &emulator, uint64_t value) {
uint32_t lldb_reg = GPREncodingToLLDB(rd);
EmulateInstruction::Context ctx;
Expand Down Expand Up @@ -230,10 +244,34 @@ Load(EmulateInstructionRISCV &emulator, I inst, uint64_t (*extend)(E)) {
auto addr = LoadStoreAddr(emulator, inst);
if (!addr)
return false;
return transformOptional(
emulator.ReadMem<T>(*addr),
[&](T t) { return inst.rd.Write(emulator, extend(E(t))); })
.value_or(false);

// Set up context for the load operation, similar to ARM64.
EmulateInstructionRISCV::Context context;

// Get register info for base register
std::optional<RegisterInfo> reg_info_rs1 =
GPREncodingToRegisterInfo(emulator, inst.rs1.rs);

if (!reg_info_rs1)
return false;

// Set context type based on whether this is a stack-based load.
if (inst.rs1.rs == RISCV_GPR_SP)
context.type = EmulateInstruction::eContextPopRegisterOffStack;
else
context.type = EmulateInstruction::eContextRegisterLoad;

// Set the context address information
context.SetAddress(*addr);

// Read from memory with context and write to register.
bool success = false;
uint64_t value =
emulator.ReadMemoryUnsigned(context, *addr, sizeof(T), 0, &success);
if (!success)
return false;

return inst.rd.Write(emulator, extend(E(T(value))));
}

template <typename I, typename T>
Expand All @@ -242,9 +280,35 @@ Store(EmulateInstructionRISCV &emulator, I inst) {
auto addr = LoadStoreAddr(emulator, inst);
if (!addr)
return false;
return transformOptional(
inst.rs2.Read(emulator),
[&](uint64_t rs2) { return emulator.WriteMem<T>(*addr, rs2); })

// Set up context for the store operation, similar to ARM64.
EmulateInstructionRISCV::Context context;

// Get register info for source and base registers.
std::optional<RegisterInfo> reg_info_rs1 =
GPREncodingToRegisterInfo(emulator, inst.rs1.rs);
std::optional<RegisterInfo> reg_info_rs2 =
GPREncodingToRegisterInfo(emulator, inst.rs2.rs);

if (!reg_info_rs1 || !reg_info_rs2)
return false;

// Set context type based on whether this is a stack-based store.
if (inst.rs1.rs == RISCV_GPR_SP)
context.type = EmulateInstruction::eContextPushRegisterOnStack;
else
context.type = EmulateInstruction::eContextRegisterStore;

// Set the context to show which register is being stored to which base
// register + offset.
context.SetRegisterToRegisterPlusOffset(*reg_info_rs2, *reg_info_rs1,
SignExt(inst.imm));

return transformOptional(inst.rs2.Read(emulator),
[&](uint64_t rs2) {
return emulator.WriteMemoryUnsigned(
context, *addr, rs2, sizeof(T));
})
.value_or(false);
}

Expand Down Expand Up @@ -737,11 +801,44 @@ class Executor {
bool operator()(SH inst) { return Store<SH, uint16_t>(m_emu, inst); }
bool operator()(SW inst) { return Store<SW, uint32_t>(m_emu, inst); }
bool operator()(ADDI inst) {
return transformOptional(inst.rs1.ReadI64(m_emu),
[&](int64_t rs1) {
return inst.rd.Write(
m_emu, rs1 + int64_t(SignExt(inst.imm)));
})
return transformOptional(
inst.rs1.ReadI64(m_emu),
[&](int64_t rs1) {
int64_t result = rs1 + int64_t(SignExt(inst.imm));
// Check if this is a stack pointer adjustment.
if (inst.rd.rd == RISCV_GPR_SP &&
inst.rs1.rs == RISCV_GPR_SP) {
EmulateInstruction::Context context;
context.type =
EmulateInstruction::eContextAdjustStackPointer;
context.SetImmediateSigned(SignExt(inst.imm));
uint32_t sp_lldb_reg = GPREncodingToLLDB(RISCV_GPR_SP);
RegisterValue registerValue;
registerValue.SetUInt64(result);
return m_emu.WriteRegister(context, eRegisterKindLLDB,
sp_lldb_reg, registerValue);
}
// Check if this is setting up the frame pointer.
// addi fp, sp, imm -> fp = sp + imm (frame pointer setup).
if (inst.rd.rd == RISCV_GPR_FP &&
inst.rs1.rs == RISCV_GPR_SP) {
EmulateInstruction::Context context;
context.type = EmulateInstruction::eContextSetFramePointer;
auto sp_reg_info = m_emu.GetRegisterInfo(
eRegisterKindLLDB, GPREncodingToLLDB(RISCV_GPR_SP));
if (sp_reg_info) {
context.SetRegisterPlusOffset(*sp_reg_info,
SignExt(inst.imm));
}
uint32_t fp_lldb_reg = GPREncodingToLLDB(RISCV_GPR_FP);
RegisterValue registerValue;
registerValue.SetUInt64(result);
return m_emu.WriteRegister(context, eRegisterKindLLDB,
fp_lldb_reg, registerValue);
}
// Regular ADDI instruction.
return inst.rd.Write(m_emu, result);
})
.value_or(false);
}
bool operator()(SLTI inst) {
Expand Down Expand Up @@ -1745,6 +1842,61 @@ EmulateInstructionRISCV::GetRegisterInfo(RegisterKind reg_kind,
return array[reg_index];
}

bool EmulateInstructionRISCV::SetInstruction(const Opcode &opcode,
const Address &inst_addr,
Target *target) {
// Call the base class implementation.
if (!EmulateInstruction::SetInstruction(opcode, inst_addr, target))
return false;

// Extract instruction data from the opcode.
uint32_t inst_data = 0;
const void *opcode_data = m_opcode.GetOpcodeBytes();
if (!opcode_data)
return false;

if (m_opcode.GetByteSize() == 2) {
// 16-bit compressed instruction.
const uint16_t *data = static_cast<const uint16_t *>(opcode_data);
inst_data = *data;
} else if (m_opcode.GetByteSize() == 4) {
// 32-bit instruction.
const uint32_t *data = static_cast<const uint32_t *>(opcode_data);
inst_data = *data;
} else {
return false;
}

// Decode the instruction.
auto decoded_inst = Decode(inst_data);
if (!decoded_inst)
return false;

// Store the decoded result.
m_decoded = *decoded_inst;
return true;
}

bool EmulateInstructionRISCV::CreateFunctionEntryUnwind(
UnwindPlan &unwind_plan) {
unwind_plan.Clear();
unwind_plan.SetRegisterKind(eRegisterKindLLDB);

UnwindPlan::Row row;

row.GetCFAValue().SetIsRegisterPlusOffset(gpr_sp_riscv, 0);
row.SetRegisterLocationToSame(gpr_ra_riscv, /*must_replace=*/false);
row.SetRegisterLocationToSame(gpr_fp_riscv, /*must_replace=*/false);

unwind_plan.AppendRow(std::move(row));
unwind_plan.SetSourceName("EmulateInstructionRISCV");
unwind_plan.SetSourcedFromCompiler(eLazyBoolNo);
unwind_plan.SetUnwindPlanValidAtAllInstructions(eLazyBoolYes);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this unwind plan truly valid at all instructions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from my understanding, No, this unwind plan is NOT valid at all instructions because it assumes SP+0 and unchanged registers which becomes incorrect after prologue instructions execute. eLazyBoolYes means valid at every instruction while eLazyBoolNo means only valid at call sites/exception points, so this basic function entry plan should use eLazyBoolNo. It seems like other emulators (ARM, MIPS for example) are using the same things.

unwind_plan.SetUnwindPlanForSignalTrap(eLazyBoolNo);
unwind_plan.SetReturnAddressRegister(gpr_ra_riscv);
return true;
}

bool EmulateInstructionRISCV::SetTargetTriple(const ArchSpec &arch) {
return SupportsThisArch(arch);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class EmulateInstructionRISCV : public EmulateInstruction {
case eInstructionTypePCModifying:
return true;
case eInstructionTypePrologueEpilogue:
return true;
case eInstructionTypeAll:
return false;
}
Expand All @@ -85,6 +86,7 @@ class EmulateInstructionRISCV : public EmulateInstruction {
return SupportsThisInstructionType(inst_type);
}

bool CreateFunctionEntryUnwind(UnwindPlan &unwind_plan) override;
bool SetTargetTriple(const ArchSpec &arch) override;
bool ReadInstruction() override;
std::optional<uint32_t> GetLastInstrSize() override { return m_last_size; }
Expand All @@ -94,6 +96,8 @@ class EmulateInstructionRISCV : public EmulateInstruction {
std::optional<RegisterInfo> GetRegisterInfo(lldb::RegisterKind reg_kind,
uint32_t reg_num) override;

bool SetInstruction(const Opcode &opcode, const Address &inst_addr,
Target *target) override;
std::optional<DecodeResult> ReadInstructionAt(lldb::addr_t addr);
std::optional<DecodeResult> Decode(uint32_t inst);
bool Execute(DecodeResult inst, bool ignore_cond);
Expand Down
5 changes: 5 additions & 0 deletions lldb/unittests/Instruction/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@ add_lldb_unittest(EmulatorTests
ARM64/TestAArch64Emulator.cpp
LoongArch/TestLoongArchEmulator.cpp
RISCV/TestRISCVEmulator.cpp
RISCV/TestRiscvInstEmulation.cpp

LINK_COMPONENTS
Support
${LLVM_TARGETS_TO_BUILD}
LINK_LIBS
lldbCore
lldbSymbol
lldbTarget
lldbPluginInstructionARM64
lldbPluginInstructionLoongArch
lldbPluginInstructionRISCV
lldbPluginDisassemblerLLVMC
lldbPluginUnwindAssemblyInstEmulation
lldbPluginProcessUtility
)
Loading
Loading