[lldb][RISCV] fix LR/SC atomic sequence handling in lldb-server (#127505)

lldb-server had limited support for single-stepping through the lr/sc
atomic sequence. This patch enhances that support for all possible
atomic sequences.
This commit is contained in:
dlav-sc
2025-06-24 19:52:38 +03:00
committed by GitHub
parent 7d2293d1d9
commit 3bc1fc6493
19 changed files with 643 additions and 188 deletions

View File

@@ -22,6 +22,8 @@
#include "lldb/lldb-private-types.h"
#include "lldb/lldb-types.h"
#include "llvm/Support/Error.h"
#include <cstddef>
#include <cstdint>
@@ -32,6 +34,38 @@ class RegisterValue;
class Stream;
class Target;
class UnwindPlan;
class EmulateInstruction;
using BreakpointLocations = std::vector<lldb::addr_t>;
class SingleStepBreakpointLocationsPredictor {
public:
SingleStepBreakpointLocationsPredictor(
std::unique_ptr<EmulateInstruction> emulator_up)
: m_emulator_up{std::move(emulator_up)} {}
virtual BreakpointLocations GetBreakpointLocations(Status &status);
virtual llvm::Expected<unsigned>
GetBreakpointSize([[maybe_unused]] lldb::addr_t bp_addr) {
return 4;
}
virtual ~SingleStepBreakpointLocationsPredictor() = default;
protected:
// This function retrieves the address of the next instruction as it appears
// in the binary file. Essentially, it reads the value of the PC register,
// determines the size of the current instruction (where the PC is pointing),
// and returns the sum of these two values.
lldb::addr_t GetNextInstructionAddress(Status &error);
lldb::addr_t GetBreakpointLocationAddress(lldb::addr_t entry_pc,
Status &error);
std::unique_ptr<EmulateInstruction> m_emulator_up;
bool m_emulation_result = false;
};
/// \class EmulateInstruction EmulateInstruction.h
/// "lldb/Core/EmulateInstruction.h"
@@ -497,7 +531,19 @@ public:
static uint32_t GetInternalRegisterNumber(RegisterContext *reg_ctx,
const RegisterInfo &reg_info);
static std::unique_ptr<SingleStepBreakpointLocationsPredictor>
CreateBreakpointLocationPredictor(
std::unique_ptr<EmulateInstruction> emulator_up);
// Helper functions
std::optional<lldb::addr_t> ReadPC();
bool WritePC(lldb::addr_t addr);
protected:
using BreakpointLocationsPredictorCreator =
std::function<std::unique_ptr<SingleStepBreakpointLocationsPredictor>(
std::unique_ptr<EmulateInstruction>)>;
ArchSpec m_arch;
void *m_baton = nullptr;
ReadMemoryCallback m_read_mem_callback = &ReadMemoryDefault;
@@ -508,6 +554,21 @@ protected:
Opcode m_opcode;
private:
virtual BreakpointLocationsPredictorCreator
GetSingleStepBreakpointLocationsPredictorCreator() {
if (!m_arch.IsMIPS() && !m_arch.GetTriple().isPPC64() &&
!m_arch.GetTriple().isLoongArch()) {
// Unsupported architecture
return [](std::unique_ptr<EmulateInstruction> emulator_up) {
return nullptr;
};
}
return [](std::unique_ptr<EmulateInstruction> emulator_up) {
return std::make_unique<SingleStepBreakpointLocationsPredictor>(
std::move(emulator_up));
};
}
// For EmulateInstruction only
EmulateInstruction(const EmulateInstruction &) = delete;
const EmulateInstruction &operator=(const EmulateInstruction &) = delete;

View File

@@ -588,7 +588,99 @@ EmulateInstruction::GetInternalRegisterNumber(RegisterContext *reg_ctx,
return LLDB_INVALID_REGNUM;
}
std::unique_ptr<SingleStepBreakpointLocationsPredictor>
EmulateInstruction::CreateBreakpointLocationPredictor(
std::unique_ptr<EmulateInstruction> emulator_up) {
auto creator =
emulator_up->GetSingleStepBreakpointLocationsPredictorCreator();
return creator(std::move(emulator_up));
}
std::optional<lldb::addr_t> EmulateInstruction::ReadPC() {
bool success = false;
auto addr = ReadRegisterUnsigned(eRegisterKindGeneric, LLDB_REGNUM_GENERIC_PC,
LLDB_INVALID_ADDRESS, &success);
return success ? std::optional<addr_t>(addr) : std::nullopt;
}
bool EmulateInstruction::WritePC(lldb::addr_t addr) {
EmulateInstruction::Context ctx;
ctx.type = eContextAdvancePC;
ctx.SetNoArgs();
return WriteRegisterUnsigned(ctx, eRegisterKindGeneric,
LLDB_REGNUM_GENERIC_PC, addr);
}
bool EmulateInstruction::CreateFunctionEntryUnwind(UnwindPlan &unwind_plan) {
unwind_plan.Clear();
return false;
}
BreakpointLocations
SingleStepBreakpointLocationsPredictor::GetBreakpointLocations(Status &status) {
if (!m_emulator_up->ReadInstruction()) {
// try to get at least the size of next instruction to set breakpoint.
lldb::addr_t next_pc = GetNextInstructionAddress(status);
return BreakpointLocations{next_pc};
}
auto entry_pc = m_emulator_up->ReadPC();
if (!entry_pc) {
status = Status("Can't read PC");
return {};
}
m_emulation_result = m_emulator_up->EvaluateInstruction(
eEmulateInstructionOptionAutoAdvancePC);
lldb::addr_t next_pc = GetBreakpointLocationAddress(*entry_pc, status);
return BreakpointLocations{next_pc};
}
lldb::addr_t SingleStepBreakpointLocationsPredictor::GetNextInstructionAddress(
Status &error) {
auto instr_size = m_emulator_up->GetLastInstrSize();
if (!instr_size) {
error = Status("Read instruction failed!");
return LLDB_INVALID_ADDRESS;
}
auto pc = m_emulator_up->ReadPC();
if (!pc) {
error = Status("Can't read PC");
return LLDB_INVALID_ADDRESS;
}
lldb::addr_t next_pc = *pc + *instr_size;
return next_pc;
}
lldb::addr_t
SingleStepBreakpointLocationsPredictor::GetBreakpointLocationAddress(
lldb::addr_t entry_pc, Status &error) {
auto addr = m_emulator_up->ReadPC();
if (!addr) {
error = Status("Can't read PC");
return LLDB_INVALID_ADDRESS;
}
lldb::addr_t pc = *addr;
if (m_emulation_result) {
assert(entry_pc != pc && "Emulation was successfull but PC wasn't updated");
return pc;
}
if (entry_pc == pc) {
// Emulate instruction failed and it hasn't changed PC. Advance PC with
// the size of the current opcode because the emulation of all
// PC modifying instruction should be successful. The failure most
// likely caused by an unsupported instruction which does not modify PC.
return pc + m_emulator_up->GetOpcode().GetByteSize();
}
// The instruction emulation failed after it modified the PC. It is an
// unknown error where we can't continue because the next instruction is
// modifying the PC but we don't know how.
error = Status("Instruction emulation failed unexpectedly.");
return LLDB_INVALID_ADDRESS;
}

View File

@@ -14471,3 +14471,16 @@ bool EmulateInstructionARM::CreateFunctionEntryUnwind(UnwindPlan &unwind_plan) {
unwind_plan.SetReturnAddressRegister(dwarf_lr);
return true;
}
llvm::Expected<unsigned>
ARMSingleStepBreakpointLocationsPredictor::GetBreakpointSize(
lldb::addr_t bp_addr) {
auto flags = m_emulator_up->ReadRegisterUnsigned(
eRegisterKindGeneric, LLDB_REGNUM_GENERIC_FLAGS, LLDB_INVALID_ADDRESS,
nullptr);
if (flags == LLDB_INVALID_ADDRESS)
return llvm::createStringError(llvm::inconvertibleErrorCode(),
"Reading flags failed!");
return (flags & 0x20) ? /* Thumb mode */ 2 : /* Arm mode */ 4;
}

View File

@@ -16,6 +16,16 @@
namespace lldb_private {
class ARMSingleStepBreakpointLocationsPredictor
: public SingleStepBreakpointLocationsPredictor {
public:
ARMSingleStepBreakpointLocationsPredictor(
std::unique_ptr<EmulateInstruction> emulator_up)
: SingleStepBreakpointLocationsPredictor{std::move(emulator_up)} {}
llvm::Expected<unsigned> GetBreakpointSize(lldb::addr_t bp_addr) override;
};
// ITSession - Keep track of the IT Block progression.
class ITSession {
public:
@@ -770,6 +780,14 @@ protected:
// B6.2.13 SUBS PC, LR and related instructions
bool EmulateSUBSPcLrEtc(const uint32_t opcode, const ARMEncoding encoding);
BreakpointLocationsPredictorCreator
GetSingleStepBreakpointLocationsPredictorCreator() override {
return [](std::unique_ptr<EmulateInstruction> emulator_up) {
return std::make_unique<ARMSingleStepBreakpointLocationsPredictor>(
std::move(emulator_up));
};
}
uint32_t m_arm_isa;
Mode m_opcode_mode;
uint32_t m_opcode_cpsr;

View File

@@ -86,7 +86,6 @@ bool EmulateInstructionLoongArch::EvaluateInstruction(uint32_t options) {
uint32_t inst_size = m_opcode.GetByteSize();
uint32_t inst = m_opcode.GetOpcode32();
bool increase_pc = options & eEmulateInstructionOptionAutoAdvancePC;
bool success = false;
Opcode *opcode_data = GetOpcodeForInstruction(inst);
if (!opcode_data)
@@ -94,9 +93,10 @@ bool EmulateInstructionLoongArch::EvaluateInstruction(uint32_t options) {
lldb::addr_t old_pc = 0;
if (increase_pc) {
old_pc = ReadPC(&success);
if (!success)
auto addr = ReadPC();
if (!addr)
return false;
old_pc = *addr;
}
// Call the Emulate... function.
@@ -104,9 +104,10 @@ bool EmulateInstructionLoongArch::EvaluateInstruction(uint32_t options) {
return false;
if (increase_pc) {
lldb::addr_t new_pc = ReadPC(&success);
if (!success)
auto addr = ReadPC();
if (!addr)
return false;
lldb::addr_t new_pc = *addr;
if (new_pc == old_pc && !WritePC(old_pc + inst_size))
return false;
@@ -115,13 +116,14 @@ bool EmulateInstructionLoongArch::EvaluateInstruction(uint32_t options) {
}
bool EmulateInstructionLoongArch::ReadInstruction() {
bool success = false;
m_addr = ReadPC(&success);
if (!success) {
auto addr = ReadPC();
if (!addr) {
m_addr = LLDB_INVALID_ADDRESS;
return false;
}
m_addr = *addr;
bool success = false;
Context ctx;
ctx.type = eContextReadOpcode;
ctx.SetNoArgs();
@@ -131,19 +133,6 @@ bool EmulateInstructionLoongArch::ReadInstruction() {
return true;
}
lldb::addr_t EmulateInstructionLoongArch::ReadPC(bool *success) {
return ReadRegisterUnsigned(eRegisterKindGeneric, LLDB_REGNUM_GENERIC_PC,
LLDB_INVALID_ADDRESS, success);
}
bool EmulateInstructionLoongArch::WritePC(lldb::addr_t pc) {
EmulateInstruction::Context ctx;
ctx.type = eContextAdvancePC;
ctx.SetNoArgs();
return WriteRegisterUnsigned(ctx, eRegisterKindGeneric,
LLDB_REGNUM_GENERIC_PC, pc);
}
std::optional<RegisterInfo>
EmulateInstructionLoongArch::GetRegisterInfo(lldb::RegisterKind reg_kind,
uint32_t reg_index) {
@@ -273,9 +262,12 @@ bool EmulateInstructionLoongArch::EmulateNonJMP(uint32_t inst) { return false; }
bool EmulateInstructionLoongArch::EmulateBEQZ64(uint32_t inst) {
bool success = false;
uint32_t rj = Bits32(inst, 9, 5);
uint64_t pc = ReadPC(&success);
if (!success)
auto addr = ReadPC();
if (!addr)
return false;
uint64_t pc = *addr;
uint32_t offs21 = Bits32(inst, 25, 10) + (Bits32(inst, 4, 0) << 16);
uint64_t rj_val = ReadRegisterUnsigned(eRegisterKindLLDB, rj, 0, &success);
if (!success)
@@ -293,9 +285,12 @@ bool EmulateInstructionLoongArch::EmulateBEQZ64(uint32_t inst) {
bool EmulateInstructionLoongArch::EmulateBNEZ64(uint32_t inst) {
bool success = false;
uint32_t rj = Bits32(inst, 9, 5);
uint64_t pc = ReadPC(&success);
if (!success)
auto addr = ReadPC();
if (!addr)
return false;
uint64_t pc = *addr;
uint32_t offs21 = Bits32(inst, 25, 10) + (Bits32(inst, 4, 0) << 16);
uint64_t rj_val = ReadRegisterUnsigned(eRegisterKindLLDB, rj, 0, &success);
if (!success)
@@ -313,9 +308,12 @@ bool EmulateInstructionLoongArch::EmulateBNEZ64(uint32_t inst) {
bool EmulateInstructionLoongArch::EmulateBCEQZ64(uint32_t inst) {
bool success = false;
uint32_t cj = Bits32(inst, 7, 5) + fpr_fcc0_loongarch;
uint64_t pc = ReadPC(&success);
if (!success)
auto addr = ReadPC();
if (!addr)
return false;
uint64_t pc = *addr;
uint32_t offs21 = Bits32(inst, 25, 10) + (Bits32(inst, 4, 0) << 16);
uint8_t cj_val =
(uint8_t)ReadRegisterUnsigned(eRegisterKindLLDB, cj, 0, &success);
@@ -335,9 +333,12 @@ bool EmulateInstructionLoongArch::EmulateBCEQZ64(uint32_t inst) {
bool EmulateInstructionLoongArch::EmulateBCNEZ64(uint32_t inst) {
bool success = false;
uint32_t cj = Bits32(inst, 7, 5) + fpr_fcc0_loongarch;
uint64_t pc = ReadPC(&success);
if (!success)
auto addr = ReadPC();
if (!addr)
return false;
uint64_t pc = *addr;
uint32_t offs21 = Bits32(inst, 25, 10) + (Bits32(inst, 4, 0) << 16);
uint8_t cj_val =
(uint8_t)ReadRegisterUnsigned(eRegisterKindLLDB, cj, 0, &success);
@@ -358,9 +359,12 @@ bool EmulateInstructionLoongArch::EmulateJIRL64(uint32_t inst) {
uint32_t rj = Bits32(inst, 9, 5);
uint32_t rd = Bits32(inst, 4, 0);
bool success = false;
uint64_t pc = ReadPC(&success);
if (!success)
auto addr = ReadPC();
if (!addr)
return false;
uint64_t pc = *addr;
EmulateInstruction::Context ctx;
if (!WriteRegisterUnsigned(ctx, eRegisterKindLLDB, rd, pc + 4))
return false;
@@ -374,10 +378,11 @@ bool EmulateInstructionLoongArch::EmulateJIRL64(uint32_t inst) {
// b offs26
// PC = PC + SignExtend({offs26, 2' b0}, GRLEN)
bool EmulateInstructionLoongArch::EmulateB64(uint32_t inst) {
bool success = false;
uint64_t pc = ReadPC(&success);
if (!success)
auto addr = ReadPC();
if (!addr)
return false;
uint64_t pc = *addr;
uint32_t offs26 = Bits32(inst, 25, 10) + (Bits32(inst, 9, 0) << 16);
uint64_t next_pc = pc + llvm::SignExtend64<28>(offs26 << 2);
return WritePC(next_pc);
@@ -387,10 +392,11 @@ bool EmulateInstructionLoongArch::EmulateB64(uint32_t inst) {
// GR[1] = PC + 4
// PC = PC + SignExtend({offs26, 2'b0}, GRLEN)
bool EmulateInstructionLoongArch::EmulateBL64(uint32_t inst) {
bool success = false;
uint64_t pc = ReadPC(&success);
if (!success)
auto addr = ReadPC();
if (!addr)
return false;
uint64_t pc = *addr;
EmulateInstruction::Context ctx;
if (!WriteRegisterUnsigned(ctx, eRegisterKindLLDB, gpr_r1_loongarch, pc + 4))
return false;
@@ -406,9 +412,12 @@ bool EmulateInstructionLoongArch::EmulateBEQ64(uint32_t inst) {
bool success = false;
uint32_t rj = Bits32(inst, 9, 5);
uint32_t rd = Bits32(inst, 4, 0);
uint64_t pc = ReadPC(&success);
if (!success)
auto addr = ReadPC();
if (!addr)
return false;
uint64_t pc = *addr;
uint64_t rj_val = ReadRegisterUnsigned(eRegisterKindLLDB, rj, 0, &success);
if (!success)
return false;
@@ -429,9 +438,12 @@ bool EmulateInstructionLoongArch::EmulateBNE64(uint32_t inst) {
bool success = false;
uint32_t rj = Bits32(inst, 9, 5);
uint32_t rd = Bits32(inst, 4, 0);
uint64_t pc = ReadPC(&success);
if (!success)
auto addr = ReadPC();
if (!addr)
return false;
uint64_t pc = *addr;
uint64_t rj_val = ReadRegisterUnsigned(eRegisterKindLLDB, rj, 0, &success);
if (!success)
return false;
@@ -452,9 +464,12 @@ bool EmulateInstructionLoongArch::EmulateBLT64(uint32_t inst) {
bool success = false;
uint32_t rj = Bits32(inst, 9, 5);
uint32_t rd = Bits32(inst, 4, 0);
uint64_t pc = ReadPC(&success);
if (!success)
auto addr = ReadPC();
if (!addr)
return false;
uint64_t pc = *addr;
int64_t rj_val =
(int64_t)ReadRegisterUnsigned(eRegisterKindLLDB, rj, 0, &success);
if (!success)
@@ -477,9 +492,12 @@ bool EmulateInstructionLoongArch::EmulateBGE64(uint32_t inst) {
bool success = false;
uint32_t rj = Bits32(inst, 9, 5);
uint32_t rd = Bits32(inst, 4, 0);
uint64_t pc = ReadPC(&success);
if (!success)
auto addr = ReadPC();
if (!addr)
return false;
uint64_t pc = *addr;
int64_t rj_val =
(int64_t)ReadRegisterUnsigned(eRegisterKindLLDB, rj, 0, &success);
if (!success)
@@ -502,9 +520,12 @@ bool EmulateInstructionLoongArch::EmulateBLTU64(uint32_t inst) {
bool success = false;
uint32_t rj = Bits32(inst, 9, 5);
uint32_t rd = Bits32(inst, 4, 0);
uint64_t pc = ReadPC(&success);
if (!success)
auto addr = ReadPC();
if (!addr)
return false;
uint64_t pc = *addr;
uint64_t rj_val = ReadRegisterUnsigned(eRegisterKindLLDB, rj, 0, &success);
if (!success)
return false;
@@ -525,9 +546,12 @@ bool EmulateInstructionLoongArch::EmulateBGEU64(uint32_t inst) {
bool success = false;
uint32_t rj = Bits32(inst, 9, 5);
uint32_t rd = Bits32(inst, 4, 0);
uint64_t pc = ReadPC(&success);
if (!success)
auto addr = ReadPC();
if (!addr)
return false;
uint64_t pc = *addr;
uint64_t rj_val = ReadRegisterUnsigned(eRegisterKindLLDB, rj, 0, &success);
if (!success)
return false;

View File

@@ -57,8 +57,6 @@ public:
std::optional<RegisterInfo> GetRegisterInfo(lldb::RegisterKind reg_kind,
uint32_t reg_num) override;
lldb::addr_t ReadPC(bool *success);
bool WritePC(lldb::addr_t pc);
bool IsLoongArch64() { return m_arch_subtype == llvm::Triple::loongarch64; }
bool TestExecute(uint32_t inst);

View File

@@ -650,9 +650,10 @@ std::optional<DecodeResult> EmulateInstructionRISCV::Decode(uint32_t inst) {
for (const InstrPattern &pat : PATTERNS) {
if ((inst & pat.type_mask) == pat.eigen &&
(inst_type & pat.inst_type) != 0) {
LLDB_LOGF(
log, "EmulateInstructionRISCV::%s: inst(%x at %" PRIx64 ") was decoded to %s",
__FUNCTION__, inst, m_addr, pat.name);
LLDB_LOGF(log,
"EmulateInstructionRISCV::%s: inst(%x at %" PRIx64
") was decoded to %s",
__FUNCTION__, inst, m_addr, pat.name);
auto decoded = is_16b ? pat.decode(try_rvc) : pat.decode(inst);
return DecodeResult{decoded, inst, is_16b, pat};
}
@@ -1649,21 +1650,6 @@ bool EmulateInstructionRISCV::ReadInstruction() {
return true;
}
std::optional<addr_t> EmulateInstructionRISCV::ReadPC() {
bool success = false;
auto addr = ReadRegisterUnsigned(eRegisterKindGeneric, LLDB_REGNUM_GENERIC_PC,
LLDB_INVALID_ADDRESS, &success);
return success ? std::optional<addr_t>(addr) : std::nullopt;
}
bool EmulateInstructionRISCV::WritePC(addr_t pc) {
EmulateInstruction::Context ctx;
ctx.type = eContextAdvancePC;
ctx.SetNoArgs();
return WriteRegisterUnsigned(ctx, eRegisterKindGeneric,
LLDB_REGNUM_GENERIC_PC, pc);
}
RoundingMode EmulateInstructionRISCV::GetRoundingMode() {
bool success = false;
auto fcsr = ReadRegisterUnsigned(eRegisterKindLLDB, fpr_fcsr_riscv,
@@ -1792,4 +1778,128 @@ bool EmulateInstructionRISCV::SupportsThisArch(const ArchSpec &arch) {
return arch.GetTriple().isRISCV();
}
BreakpointLocations
RISCVSingleStepBreakpointLocationsPredictor::GetBreakpointLocations(
Status &status) {
EmulateInstructionRISCV *riscv_emulator =
static_cast<EmulateInstructionRISCV *>(m_emulator_up.get());
auto pc = riscv_emulator->ReadPC();
if (!pc) {
status = Status("Can't read PC");
return {};
}
auto inst = riscv_emulator->ReadInstructionAt(*pc);
if (!inst) {
// Can't read instruction. Try default handler.
return SingleStepBreakpointLocationsPredictor::GetBreakpointLocations(
status);
}
if (FoundLoadReserve(inst->decoded))
return HandleAtomicSequence(*pc, status);
if (FoundStoreConditional(inst->decoded)) {
// Ill-formed atomic sequence (SC doesn't have corresponding LR
// instruction). Consider SC instruction like a non-atomic store and set a
// breakpoint at the next instruction.
Log *log = GetLog(LLDBLog::Unwind);
LLDB_LOGF(log,
"RISCVSingleStepBreakpointLocationsPredictor::%s: can't find "
"corresponding load reserve insturuction",
__FUNCTION__);
return {*pc + (inst->is_rvc ? 2u : 4u)};
}
return SingleStepBreakpointLocationsPredictor::GetBreakpointLocations(status);
}
llvm::Expected<unsigned>
RISCVSingleStepBreakpointLocationsPredictor::GetBreakpointSize(
lldb::addr_t bp_addr) {
EmulateInstructionRISCV *riscv_emulator =
static_cast<EmulateInstructionRISCV *>(m_emulator_up.get());
if (auto inst = riscv_emulator->ReadInstructionAt(bp_addr); inst)
return inst->is_rvc ? 2 : 4;
// Try last instruction size.
if (auto last_instr_size = riscv_emulator->GetLastInstrSize();
last_instr_size)
return *last_instr_size;
// Just place non-compressed software trap.
return 4;
}
BreakpointLocations
RISCVSingleStepBreakpointLocationsPredictor::HandleAtomicSequence(
lldb::addr_t pc, Status &error) {
EmulateInstructionRISCV *riscv_emulator =
static_cast<EmulateInstructionRISCV *>(m_emulator_up.get());
// Handle instructions between LR and SC. According to unprivilleged
// RISC-V ISA there can be at most 16 instructions in the sequence.
lldb::addr_t entry_pc = pc; // LR instruction address
auto lr_inst = riscv_emulator->ReadInstructionAt(entry_pc);
pc += lr_inst->is_rvc ? 2 : 4;
size_t atomic_length = 0;
std::optional<DecodeResult> inst;
std::vector<lldb::addr_t> bp_addrs;
do {
inst = riscv_emulator->ReadInstructionAt(pc);
if (!inst) {
error = Status("Can't read instruction");
return {};
}
if (B *branch = std::get_if<B>(&inst->decoded))
bp_addrs.push_back(pc + SignExt(branch->imm));
unsigned addent = inst->is_rvc ? 2 : 4;
pc += addent;
atomic_length += addent;
} while ((atomic_length < s_max_atomic_sequence_length) &&
!FoundStoreConditional(inst->decoded));
if (atomic_length >= s_max_atomic_sequence_length) {
// Ill-formed atomic sequence (LR doesn't have corresponding SC
// instruction). In this case consider LR like a non-atomic load instruction
// and set a breakpoint at the next after LR instruction.
Log *log = GetLog(LLDBLog::Unwind);
LLDB_LOGF(log,
"RISCVSingleStepBreakpointLocationsPredictor::%s: can't find "
"corresponding store conditional insturuction",
__FUNCTION__);
return {entry_pc + (lr_inst->is_rvc ? 2u : 4u)};
}
lldb::addr_t exit_pc = pc;
// Check if we have a branch to the start of the atomic sequence after SC
// instruction. If we have such branch, consider it as a part of the atomic
// sequence.
inst = riscv_emulator->ReadInstructionAt(exit_pc);
if (inst) {
B *branch = std::get_if<B>(&inst->decoded);
if (branch && (exit_pc + SignExt(branch->imm)) == entry_pc)
exit_pc += inst->is_rvc ? 2 : 4;
}
// Set breakpoints at the jump addresses of the forward branches that points
// after the end of the atomic sequence.
bp_addrs.erase(llvm::remove_if(bp_addrs,
[exit_pc](lldb::addr_t bp_addr) {
return exit_pc >= bp_addr;
}),
bp_addrs.end());
// Set breakpoint at the end of atomic sequence.
bp_addrs.push_back(exit_pc);
return bp_addrs;
}
} // namespace lldb_private

View File

@@ -20,6 +20,33 @@
namespace lldb_private {
class RISCVSingleStepBreakpointLocationsPredictor
: public SingleStepBreakpointLocationsPredictor {
public:
RISCVSingleStepBreakpointLocationsPredictor(
std::unique_ptr<EmulateInstruction> emulator)
: SingleStepBreakpointLocationsPredictor{std::move(emulator)} {}
BreakpointLocations GetBreakpointLocations(Status &status) override;
llvm::Expected<unsigned> GetBreakpointSize(lldb::addr_t bp_addr) override;
private:
static bool FoundLoadReserve(const RISCVInst &inst) {
return std::holds_alternative<LR_W>(inst) ||
std::holds_alternative<LR_D>(inst);
}
static bool FoundStoreConditional(const RISCVInst &inst) {
return std::holds_alternative<SC_W>(inst) ||
std::holds_alternative<SC_D>(inst);
}
BreakpointLocations HandleAtomicSequence(lldb::addr_t pc, Status &error);
static constexpr size_t s_max_atomic_sequence_length = 64;
};
class EmulateInstructionRISCV : public EmulateInstruction {
public:
static llvm::StringRef GetPluginNameStatic() { return "riscv"; }
@@ -67,9 +94,6 @@ public:
std::optional<RegisterInfo> GetRegisterInfo(lldb::RegisterKind reg_kind,
uint32_t reg_num) override;
std::optional<lldb::addr_t> ReadPC();
bool WritePC(lldb::addr_t pc);
std::optional<DecodeResult> ReadInstructionAt(lldb::addr_t addr);
std::optional<DecodeResult> Decode(uint32_t inst);
bool Execute(DecodeResult inst, bool ignore_cond);
@@ -98,6 +122,13 @@ public:
bool SetAccruedExceptions(llvm::APFloatBase::opStatus);
private:
BreakpointLocationsPredictorCreator
GetSingleStepBreakpointLocationsPredictorCreator() override {
return [](std::unique_ptr<EmulateInstruction> emulator_up) {
return std::make_unique<RISCVSingleStepBreakpointLocationsPredictor>(
std::move(emulator_up));
};
}
/// Last decoded instruction from m_opcode
DecodeResult m_decoded;
/// Last decoded instruction size estimate.

View File

@@ -324,12 +324,14 @@ void NativeProcessFreeBSD::MonitorSIGTRAP(lldb::pid_t pid) {
auto thread_info =
m_threads_stepping_with_breakpoint.find(thread->GetID());
if (thread_info != m_threads_stepping_with_breakpoint.end() &&
thread_info->second == regctx.GetPC()) {
llvm::is_contained(thread_info->second, regctx.GetPC())) {
thread->SetStoppedByTrace();
Status brkpt_error = RemoveBreakpoint(thread_info->second);
if (brkpt_error.Fail())
LLDB_LOG(log, "pid = {0} remove stepping breakpoint: {1}",
thread_info->first, brkpt_error);
for (auto &&bp_addr : thread_info->second) {
Status brkpt_error = RemoveBreakpoint(bp_addr);
if (brkpt_error.Fail())
LLDB_LOG(log, "pid = {0} remove stepping breakpoint: {1}",
thread_info->first, brkpt_error);
}
m_threads_stepping_with_breakpoint.erase(thread_info);
} else
thread->SetStoppedByBreakpoint();

View File

@@ -833,7 +833,7 @@ void NativeProcessLinux::MonitorBreakpoint(NativeThreadLinux &thread) {
auto stepping_with_bp_it =
m_threads_stepping_with_breakpoint.find(thread.GetID());
if (stepping_with_bp_it != m_threads_stepping_with_breakpoint.end() &&
stepping_with_bp_it->second == reg_ctx.GetPC())
llvm::is_contained(stepping_with_bp_it->second, reg_ctx.GetPC()))
thread.SetStoppedByTrace();
StopRunningThreads(thread.GetID());
@@ -1960,10 +1960,12 @@ void NativeProcessLinux::SignalIfAllThreadsStopped() {
// Clear any temporary breakpoints we used to implement software single
// stepping.
for (const auto &thread_info : m_threads_stepping_with_breakpoint) {
Status error = RemoveBreakpoint(thread_info.second);
if (error.Fail())
LLDB_LOG(log, "pid = {0} remove stepping breakpoint: {1}",
thread_info.first, error);
for (auto &&bp_addr : thread_info.second) {
Status error = RemoveBreakpoint(bp_addr);
if (error.Fail())
LLDB_LOG(log, "pid = {0} remove stepping breakpoint: {1}",
thread_info.first, error);
}
}
m_threads_stepping_with_breakpoint.clear();

View File

@@ -87,34 +87,10 @@ static size_t WriteMemoryCallback(EmulateInstruction *instruction, void *baton,
return length;
}
static lldb::addr_t ReadFlags(NativeRegisterContext &regsiter_context) {
const RegisterInfo *flags_info = regsiter_context.GetRegisterInfo(
eRegisterKindGeneric, LLDB_REGNUM_GENERIC_FLAGS);
return regsiter_context.ReadRegisterAsUnsigned(flags_info,
LLDB_INVALID_ADDRESS);
}
static int GetSoftwareBreakpointSize(const ArchSpec &arch,
lldb::addr_t next_flags) {
if (arch.GetMachine() == llvm::Triple::arm) {
if (next_flags & 0x20)
// Thumb mode
return 2;
// Arm mode
return 4;
}
if (arch.IsMIPS() || arch.GetTriple().isPPC64() ||
arch.GetTriple().isRISCV() || arch.GetTriple().isLoongArch())
return 4;
return 0;
}
static Status SetSoftwareBreakpointOnPC(const ArchSpec &arch, lldb::addr_t pc,
lldb::addr_t next_flags,
NativeProcessProtocol &process) {
int size_hint = GetSoftwareBreakpointSize(arch, next_flags);
static Status SetSoftwareBreakpoint(lldb::addr_t bp_addr, unsigned bp_size,
NativeProcessProtocol &process) {
Status error;
error = process.SetBreakpoint(pc, size_hint, /*hardware=*/false);
error = process.SetBreakpoint(bp_addr, bp_size, /*hardware=*/false);
// If setting the breakpoint fails because pc is out of the address
// space, ignore it and let the debugee segfault.
@@ -136,7 +112,6 @@ Status NativeProcessSoftwareSingleStep::SetupSoftwareSingleStepping(
std::unique_ptr<EmulateInstruction> emulator_up(
EmulateInstruction::FindPlugin(arch, eInstructionTypePCModifying,
nullptr));
if (emulator_up == nullptr)
return Status::FromErrorString("Instruction emulator not found!");
@@ -147,65 +122,24 @@ Status NativeProcessSoftwareSingleStep::SetupSoftwareSingleStepping(
emulator_up->SetWriteMemCallback(&WriteMemoryCallback);
emulator_up->SetWriteRegCallback(&WriteRegisterCallback);
if (!emulator_up->ReadInstruction()) {
// try to get at least the size of next instruction to set breakpoint.
auto instr_size = emulator_up->GetLastInstrSize();
if (!instr_size)
return Status::FromErrorString("Read instruction failed!");
bool success = false;
auto pc = emulator_up->ReadRegisterUnsigned(eRegisterKindGeneric,
LLDB_REGNUM_GENERIC_PC,
LLDB_INVALID_ADDRESS, &success);
if (!success)
return Status::FromErrorString("Reading pc failed!");
lldb::addr_t next_pc = pc + *instr_size;
auto result =
SetSoftwareBreakpointOnPC(arch, next_pc, /* next_flags */ 0x0, process);
m_threads_stepping_with_breakpoint.insert({thread.GetID(), next_pc});
return result;
auto bp_locaions_predictor =
EmulateInstruction::CreateBreakpointLocationPredictor(
std::move(emulator_up));
auto bp_locations = bp_locaions_predictor->GetBreakpointLocations(error);
if (error.Fail())
return error;
for (auto &&bp_addr : bp_locations) {
auto bp_size = bp_locaions_predictor->GetBreakpointSize(bp_addr);
if (auto err = bp_size.takeError())
return Status(toString(std::move(err)));
error = SetSoftwareBreakpoint(bp_addr, *bp_size, process);
if (error.Fail())
return error;
}
bool emulation_result =
emulator_up->EvaluateInstruction(eEmulateInstructionOptionAutoAdvancePC);
const RegisterInfo *reg_info_pc = register_context.GetRegisterInfo(
eRegisterKindGeneric, LLDB_REGNUM_GENERIC_PC);
const RegisterInfo *reg_info_flags = register_context.GetRegisterInfo(
eRegisterKindGeneric, LLDB_REGNUM_GENERIC_FLAGS);
auto pc_it =
baton.m_register_values.find(reg_info_pc->kinds[eRegisterKindDWARF]);
auto flags_it = reg_info_flags == nullptr
? baton.m_register_values.end()
: baton.m_register_values.find(
reg_info_flags->kinds[eRegisterKindDWARF]);
lldb::addr_t next_pc;
lldb::addr_t next_flags;
if (emulation_result) {
assert(pc_it != baton.m_register_values.end() &&
"Emulation was successfull but PC wasn't updated");
next_pc = pc_it->second.GetAsUInt64();
if (flags_it != baton.m_register_values.end())
next_flags = flags_it->second.GetAsUInt64();
else
next_flags = ReadFlags(register_context);
} else if (pc_it == baton.m_register_values.end()) {
// Emulate instruction failed and it haven't changed PC. Advance PC with
// the size of the current opcode because the emulation of all
// PC modifying instruction should be successful. The failure most
// likely caused by a not supported instruction which don't modify PC.
next_pc = register_context.GetPC() + emulator_up->GetOpcode().GetByteSize();
next_flags = ReadFlags(register_context);
} else {
// The instruction emulation failed after it modified the PC. It is an
// unknown error where we can't continue because the next instruction is
// modifying the PC but we don't know how.
return Status::FromErrorString(
"Instruction emulation failed unexpectedly.");
}
auto result = SetSoftwareBreakpointOnPC(arch, next_pc, next_flags, process);
m_threads_stepping_with_breakpoint.insert({thread.GetID(), next_pc});
return result;
m_threads_stepping_with_breakpoint.insert({thread.GetID(), bp_locations});
return error;
}

View File

@@ -23,7 +23,8 @@ public:
protected:
// List of thread ids stepping with a breakpoint with the address of
// the relevan breakpoint
std::map<lldb::tid_t, lldb::addr_t> m_threads_stepping_with_breakpoint;
std::map<lldb::tid_t, std::vector<lldb::addr_t>>
m_threads_stepping_with_breakpoint;
};
} // namespace lldb_private

View File

@@ -0,0 +1,3 @@
C_SOURCES := main.c
include Makefile.rules

View File

@@ -0,0 +1,79 @@
"""
Test software step-inst, also known as instruction level single step, in risc-v atomic sequence.
For more information about atomic sequences, see the RISC-V Unprivileged ISA specification.
"""
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
class TestSoftwareStep(TestBase):
def do_sequence_test(self, filename, bkpt_name):
source_file = filename + ".c"
exe_file = filename + ".x"
self.build(dictionary={"C_SOURCES": source_file, "EXE": exe_file})
(target, process, cur_thread, bkpt) = lldbutil.run_to_name_breakpoint(
self, bkpt_name, exe_name=exe_file
)
entry_pc = cur_thread.GetFrameAtIndex(0).GetPC()
self.runCmd("thread step-inst")
self.expect(
"thread list",
substrs=["stopped", "stop reason = instruction step into"],
)
pc = cur_thread.GetFrameAtIndex(0).GetPC()
return pc - entry_pc
@skipIf(archs=no_match("rv*"))
def test_cas(self):
"""
This test verifies LLDB instruction step handling of a proper lr/sc pair.
"""
difference = self.do_sequence_test("main", "cas")
self.assertEqual(difference, 0x1A)
@skipIf(archs=no_match("rv*"))
def test_branch_cas(self):
"""
LLDB cannot predict the actual state of registers within a critical section (i.e., inside an atomic
sequence). Therefore, it should identify all forward branches inside the atomic sequence and set
breakpoints at every jump address that lies beyond the end of the sequence (after the sc instruction).
This ensures that if any such branch is taken, execution will pause at its target address.
This test includes an lr/sc sequence containing an active forward branch with a jump address located
after the end of the atomic section. LLDB should correctly stop at this branch's target address. The
test is nearly identical to the previous one, except for the branch condition, which is inverted and
will result in a taken jump.
"""
difference = self.do_sequence_test("branch", "branch_cas")
self.assertEqual(difference, 0x1A)
@skipIf(archs=no_match("rv*"))
def test_incomplete_sequence_without_lr(self):
"""
This test verifies the behavior of a standalone sc instruction without a preceding lr. Since the sc
lacks the required lr pairing, LLDB should treat it as a non-atomic store rather than part of an
atomic sequence.
"""
difference = self.do_sequence_test(
"incomplete_sequence_without_lr", "incomplete_cas"
)
self.assertEqual(difference, 0x4)
@skipIf(archs=no_match("rv*"))
def test_incomplete_sequence_without_sc(self):
"""
This test checks the behavior of a standalone lr instruction without a subsequent sc. Since the lr
lacks its required sc counterpart, LLDB should treat it as a non-atomic load rather than part of an
atomic sequence.
"""
difference = self.do_sequence_test(
"incomplete_sequence_without_sc", "incomplete_cas"
)
self.assertEqual(difference, 0x4)

View File

@@ -0,0 +1,22 @@
void __attribute__((naked)) branch_cas(int *a, int *b) {
// Stop at the first instruction. The atomic sequence contains active forward
// branch (bne a5, a1, 2f). After step instruction lldb should stop at the
// branch's target address (ret instruction).
asm volatile("1:\n\t"
"lr.w a2, (a0)\n\t"
"and a5, a2, a4\n\t"
"bne a5, a1, 2f\n\t"
"xor a5, a2, a0\n\t"
"and a5, a5, a4\n\t"
"xor a5, a2, a5\n\t"
"sc.w a5, a1, (a3)\n\t"
"beqz a5, 1b\n\t"
"2:\n\t"
"ret\n\t");
}
int main() {
int a = 4;
int b = 2;
branch_cas(&a, &b);
}

View File

@@ -0,0 +1,22 @@
void __attribute__((naked)) incomplete_cas(int *a, int *b) {
// Stop at the first instruction (an sc without a corresponding lr), then make
// a step instruction and ensure that execution stops at the next instruction
// (and).
asm volatile("1:\n\t"
"sc.w a5, a1, (a3)\n\t"
"and a5, a2, a4\n\t"
"beq a5, a1, 2f\n\t"
"xor a5, a2, a0\n\t"
"and a5, a5, a4\n\t"
"xor a5, a2, a5\n\t"
"sc.w a5, a1, (a3)\n\t"
"bnez a5, 1b\n\t"
"2:\n\t"
"ret\n\t");
}
int main() {
int a = 4;
int b = 2;
incomplete_cas(&a, &b);
}

View File

@@ -0,0 +1,21 @@
void __attribute__((naked)) incomplete_cas(int *a, int *b) {
// Stop at the first instruction (an lr without a corresponding sc), then make
// a step instruction and ensure that execution stops at the next instruction
// (and).
asm volatile("1:\n\t"
"lr.w a2, (a0)\n\t"
"and a5, a2, a4\n\t"
"beq a5, a1, 2f\n\t"
"xor a5, a2, a0\n\t"
"and a5, a5, a4\n\t"
"xor a5, a2, a5\n\t"
"bnez a5, 1b\n\t"
"2:\n\t"
"ret\n\t");
}
int main() {
int a = 4;
int b = 2;
incomplete_cas(&a, &b);
}

View File

@@ -0,0 +1,22 @@
void __attribute__((naked)) cas(int *a, int *b) {
// This atomic sequence implements a copy-and-swap function. This test should
// at the first instruction, and after step instruction, we should stop at the
// end of the sequence (on the ret instruction).
asm volatile("1:\n\t"
"lr.w a2, (a0)\n\t"
"and a5, a2, a4\n\t"
"beq a5, a1, 2f\n\t"
"xor a5, a2, a0\n\t"
"and a5, a5, a4\n\t"
"xor a5, a2, a5\n\t"
"sc.w a5, a1, (a3)\n\t"
"beqz a5, 1b\n\t"
"2:\n\t"
"ret\n\t");
}
int main() {
int a = 4;
int b = 2;
cas(&a, &b);
}

View File

@@ -177,10 +177,10 @@ TEST_F(LoongArch64EmulatorTester, testJIRL) {
gpr.gpr[12] = 0x12000400;
ASSERT_TRUE(TestExecute(inst));
auto r1 = gpr.gpr[1];
auto pc = ReadPC(&success);
ASSERT_TRUE(success);
auto pc = ReadPC();
ASSERT_TRUE(pc);
ASSERT_EQ(r1, old_pc + 4);
ASSERT_EQ(pc, gpr.gpr[12] + (offs16 * 4));
ASSERT_EQ(*pc, gpr.gpr[12] + (offs16 * 4));
}
TEST_F(LoongArch64EmulatorTester, testB) {
@@ -193,9 +193,9 @@ TEST_F(LoongArch64EmulatorTester, testB) {
uint32_t inst = 0b01010000000000000100000000000001;
uint32_t offs26 = 0x10010;
ASSERT_TRUE(TestExecute(inst));
auto pc = ReadPC(&success);
ASSERT_TRUE(success);
ASSERT_EQ(pc, old_pc + (offs26 * 4));
auto pc = ReadPC();
ASSERT_TRUE(pc);
ASSERT_EQ(*pc, old_pc + (offs26 * 4));
}
TEST_F(LoongArch64EmulatorTester, testBL) {
@@ -209,10 +209,10 @@ TEST_F(LoongArch64EmulatorTester, testBL) {
uint32_t offs26 = 0x10010;
ASSERT_TRUE(TestExecute(inst));
auto r1 = gpr.gpr[1];
auto pc = ReadPC(&success);
ASSERT_TRUE(success);
auto pc = ReadPC();
ASSERT_TRUE(pc);
ASSERT_EQ(r1, old_pc + 4);
ASSERT_EQ(pc, old_pc + (offs26 * 4));
ASSERT_EQ(*pc, old_pc + (offs26 * 4));
}
static void testBcondBranch(LoongArch64EmulatorTester *tester,
@@ -226,9 +226,9 @@ static void testBcondBranch(LoongArch64EmulatorTester *tester,
// b<cmp> r12, r13, (-256)
uint32_t inst = encoder(12, 13, -256);
ASSERT_TRUE(tester->TestExecute(inst));
auto pc = tester->ReadPC(&success);
ASSERT_TRUE(success);
ASSERT_EQ(pc, old_pc + (branched ? (-256 * 4) : 4));
auto pc = tester->ReadPC();
ASSERT_TRUE(pc);
ASSERT_EQ(*pc, old_pc + (branched ? (-256 * 4) : 4));
}
static void testBZcondBranch(LoongArch64EmulatorTester *tester,
@@ -241,9 +241,9 @@ static void testBZcondBranch(LoongArch64EmulatorTester *tester,
// b<cmp>z r4, (-256)
uint32_t inst = encoder(4, -256);
ASSERT_TRUE(tester->TestExecute(inst));
auto pc = tester->ReadPC(&success);
ASSERT_TRUE(success);
ASSERT_EQ(pc, old_pc + (branched ? (-256 * 4) : 4));
auto pc = tester->ReadPC();
ASSERT_TRUE(pc);
ASSERT_EQ(*pc, old_pc + (branched ? (-256 * 4) : 4));
}
static void testBCZcondBranch(LoongArch64EmulatorTester *tester,
@@ -256,9 +256,9 @@ static void testBCZcondBranch(LoongArch64EmulatorTester *tester,
// bc<cmp>z fcc0, 256
uint32_t inst = encoder(0, 256);
ASSERT_TRUE(tester->TestExecute(inst));
auto pc = tester->ReadPC(&success);
ASSERT_TRUE(success);
ASSERT_EQ(pc, old_pc + (branched ? (256 * 4) : 4));
auto pc = tester->ReadPC();
ASSERT_TRUE(pc);
ASSERT_EQ(*pc, old_pc + (branched ? (256 * 4) : 4));
}
GEN_BCOND_TEST(64, BEQ, 1, 1, 0)