Skip to content

Commit a6e9602

Browse files
committed
[lldb][riscv] Fix setting breakpoint for undecoded instruction
Copy gdb behaviour: For RISCV we can set breakpoint even for unknown instruction, as RISCV encoding have information about size of instruction.
1 parent 8ef5c98 commit a6e9602

File tree

8 files changed

+137
-29
lines changed

8 files changed

+137
-29
lines changed

lldb/include/lldb/Core/EmulateInstruction.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,8 @@ class EmulateInstruction : public PluginInterface {
369369

370370
virtual bool ReadInstruction() = 0;
371371

372+
virtual std::optional<uint32_t> GetLastInstrSize() { return std::nullopt; }
373+
372374
virtual bool EvaluateInstruction(uint32_t evaluate_options) = 0;
373375

374376
virtual InstructionCondition GetInstructionCondition() {

lldb/source/Plugins/Instruction/RISCV/EmulateInstructionRISCV.cpp

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -624,9 +624,26 @@ std::optional<DecodeResult> EmulateInstructionRISCV::Decode(uint32_t inst) {
624624
uint16_t try_rvc = uint16_t(inst & 0x0000ffff);
625625
// check whether the compressed encode could be valid
626626
uint16_t mask = try_rvc & 0b11;
627-
bool is_rvc = try_rvc != 0 && mask != 3;
628627
uint8_t inst_type = RV64;
629628

629+
// Try to get size of RISCV instruction.
630+
// 1.2 Instruction Length Encoding
631+
bool is_16b = (inst & 0b11) != 0b11;
632+
bool is_32b = (inst & 0x1f) != 0x1f;
633+
bool is_48b = (inst & 0x3f) != 0x1f;
634+
bool is_64b = (inst & 0x7f) != 0x3f;
635+
if (is_16b)
636+
m_last_size = 2;
637+
else if (is_32b)
638+
m_last_size = 4;
639+
else if (is_48b)
640+
m_last_size = 6;
641+
else if (is_64b)
642+
m_last_size = 8;
643+
else
644+
// Not Valid
645+
m_last_size = std::nullopt;
646+
630647
// if we have ArchSpec::eCore_riscv128 in the future,
631648
// we also need to check it here
632649
if (m_arch.GetCore() == ArchSpec::eCore_riscv32)
@@ -638,8 +655,8 @@ std::optional<DecodeResult> EmulateInstructionRISCV::Decode(uint32_t inst) {
638655
LLDB_LOGF(
639656
log, "EmulateInstructionRISCV::%s: inst(%x at %" PRIx64 ") was decoded to %s",
640657
__FUNCTION__, inst, m_addr, pat.name);
641-
auto decoded = is_rvc ? pat.decode(try_rvc) : pat.decode(inst);
642-
return DecodeResult{decoded, inst, is_rvc, pat};
658+
auto decoded = is_16b ? pat.decode(try_rvc) : pat.decode(inst);
659+
return DecodeResult{decoded, inst, is_16b, pat};
643660
}
644661
}
645662
LLDB_LOGF(log, "EmulateInstructionRISCV::%s: inst(0x%x) was unsupported",

lldb/source/Plugins/Instruction/RISCV/EmulateInstructionRISCV.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ class EmulateInstructionRISCV : public EmulateInstruction {
6060

6161
bool SetTargetTriple(const ArchSpec &arch) override;
6262
bool ReadInstruction() override;
63+
std::optional<uint32_t> GetLastInstrSize() override { return m_last_size; }
6364
bool EvaluateInstruction(uint32_t options) override;
6465
bool TestEmulation(Stream &out_stream, ArchSpec &arch,
6566
OptionValueDictionary *test_data) override;
@@ -99,6 +100,8 @@ class EmulateInstructionRISCV : public EmulateInstruction {
99100
private:
100101
/// Last decoded instruction from m_opcode
101102
DecodeResult m_decoded;
103+
/// Last decoded instruction size estimate.
104+
std::optional<uint32_t> m_last_size;
102105
};
103106

104107
} // namespace lldb_private

lldb/source/Plugins/Process/Utility/NativeProcessSoftwareSingleStep.cpp

Lines changed: 51 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,38 @@ static lldb::addr_t ReadFlags(NativeRegisterContext &regsiter_context) {
9494
LLDB_INVALID_ADDRESS);
9595
}
9696

97+
static int GetSoftwareBreakpointSize(const ArchSpec &arch,
98+
lldb::addr_t next_flags) {
99+
if (arch.GetMachine() == llvm::Triple::arm) {
100+
if (next_flags & 0x20)
101+
// Thumb mode
102+
return 2;
103+
// Arm mode
104+
return 4;
105+
}
106+
if (arch.IsMIPS() || arch.GetTriple().isPPC64() ||
107+
arch.GetTriple().isRISCV() || arch.GetTriple().isLoongArch())
108+
return 4;
109+
return 0;
110+
}
111+
112+
static Status SetSoftwareBreakpointOnPC(const ArchSpec &arch, lldb::addr_t pc,
113+
lldb::addr_t next_flags,
114+
NativeProcessProtocol &process) {
115+
int size_hint = GetSoftwareBreakpointSize(arch, next_flags);
116+
Status error;
117+
error = process.SetBreakpoint(pc, size_hint, /*hardware=*/false);
118+
119+
// If setting the breakpoint fails because pc is out of the address
120+
// space, ignore it and let the debugee segfault.
121+
if (error.GetError() == EIO || error.GetError() == EFAULT)
122+
return Status();
123+
if (error.Fail())
124+
return error;
125+
126+
return Status();
127+
}
128+
97129
Status NativeProcessSoftwareSingleStep::SetupSoftwareSingleStepping(
98130
NativeThreadProtocol &thread) {
99131
Status error;
@@ -115,8 +147,23 @@ Status NativeProcessSoftwareSingleStep::SetupSoftwareSingleStepping(
115147
emulator_up->SetWriteMemCallback(&WriteMemoryCallback);
116148
emulator_up->SetWriteRegCallback(&WriteRegisterCallback);
117149

118-
if (!emulator_up->ReadInstruction())
119-
return Status("Read instruction failed!");
150+
if (!emulator_up->ReadInstruction()) {
151+
// try to get at least the size of next instruction to set breakpoint.
152+
auto instr_size = emulator_up->GetLastInstrSize();
153+
if (!instr_size)
154+
return Status("Read instruction failed!");
155+
bool success = false;
156+
auto pc = emulator_up->ReadRegisterUnsigned(eRegisterKindGeneric,
157+
LLDB_REGNUM_GENERIC_PC,
158+
LLDB_INVALID_ADDRESS, &success);
159+
if (!success)
160+
return Status("Reading pc failed!");
161+
lldb::addr_t next_pc = pc + *instr_size;
162+
auto result =
163+
SetSoftwareBreakpointOnPC(arch, next_pc, /* next_flags */ 0x0, process);
164+
m_threads_stepping_with_breakpoint.insert({thread.GetID(), next_pc});
165+
return result;
166+
}
120167

121168
bool emulation_result =
122169
emulator_up->EvaluateInstruction(eEmulateInstructionOptionAutoAdvancePC);
@@ -157,29 +204,7 @@ Status NativeProcessSoftwareSingleStep::SetupSoftwareSingleStepping(
157204
// modifying the PC but we don't know how.
158205
return Status("Instruction emulation failed unexpectedly.");
159206
}
160-
161-
int size_hint = 0;
162-
if (arch.GetMachine() == llvm::Triple::arm) {
163-
if (next_flags & 0x20) {
164-
// Thumb mode
165-
size_hint = 2;
166-
} else {
167-
// Arm mode
168-
size_hint = 4;
169-
}
170-
} else if (arch.IsMIPS() || arch.GetTriple().isPPC64() ||
171-
arch.GetTriple().isRISCV() || arch.GetTriple().isLoongArch())
172-
size_hint = 4;
173-
error = process.SetBreakpoint(next_pc, size_hint, /*hardware=*/false);
174-
175-
// If setting the breakpoint fails because next_pc is out of the address
176-
// space, ignore it and let the debugee segfault.
177-
if (error.GetError() == EIO || error.GetError() == EFAULT) {
178-
return Status();
179-
} else if (error.Fail())
180-
return error;
181-
207+
auto result = SetSoftwareBreakpointOnPC(arch, next_pc, next_flags, process);
182208
m_threads_stepping_with_breakpoint.insert({thread.GetID(), next_pc});
183-
184-
return Status();
209+
return result;
185210
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
C_SOURCES := main.c
2+
3+
include Makefile.rules
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""
2+
Test that we can set up software breakpoint even if we failed to decode and execute instruction
3+
"""
4+
5+
import lldb
6+
from lldbsuite.test.decorators import *
7+
from lldbsuite.test.lldbtest import *
8+
from lldbsuite.test import lldbutil
9+
10+
11+
class TestBreakpointIllegal(TestBase):
12+
@skipIf(archs=no_match(["rv64gc"]))
13+
def test_4(self):
14+
self.build()
15+
(target, process, cur_thread, bkpt) = lldbutil.run_to_source_breakpoint(
16+
self, "main", lldb.SBFileSpec("main.c")
17+
)
18+
self.runCmd("thread step-inst")
19+
# we need to step more, as some compilers do not set appropriate debug info.
20+
while cur_thread.GetStopDescription(256) == "instruction step into":
21+
self.runCmd("thread step-inst")
22+
# The stop reason of the thread should be illegal opcode.
23+
self.expect(
24+
"thread list",
25+
STOPPED_DUE_TO_SIGNAL,
26+
substrs=["stopped", "stop reason = signal SIGILL: illegal opcode"],
27+
)
28+
29+
@skipIf(archs=no_match(["rv64gc"]))
30+
def test_2(self):
31+
self.build(dictionary={"C_SOURCES": "compressed.c", "EXE": "compressed.x"})
32+
(target, process, cur_thread, bkpt) = lldbutil.run_to_source_breakpoint(
33+
self, "main", lldb.SBFileSpec("compressed.c"), exe_name="compressed.x"
34+
)
35+
self.runCmd("thread step-inst")
36+
# we need to step more, as some compilers do not set appropriate debug info.
37+
while cur_thread.GetStopDescription(256) == "instruction step into":
38+
self.runCmd("thread step-inst")
39+
# The stop reason of the thread should be illegal opcode.
40+
self.expect(
41+
"thread list",
42+
STOPPED_DUE_TO_SIGNAL,
43+
substrs=["stopped", "stop reason = signal SIGILL: illegal opcode"],
44+
)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
int main() {
2+
// This instruction is not valid, but we have an ability to set
3+
// software breakpoint.
4+
// This results in illegal instruction during execution, not fail to set
5+
// breakpoint
6+
asm volatile(".2byte 0xaf");
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
int main() {
2+
// This instruction is not valid, but we have an ability to set
3+
// software breakpoint.
4+
// This results in illegal instruction during execution, not fail to set
5+
// breakpoint
6+
asm volatile(".4byte 0xc58573" : :);
7+
}

0 commit comments

Comments
 (0)