Skip to content

Commit fc17f98

Browse files
ita-scyuxuanchen1997
authored andcommitted
[lldb][riscv] Fix setting breakpoint for undecoded instruction (#90075)
Summary: This patch adds an interface GetLastInstrSize to get information about the size of last tried to be decoded instruction and uses it to set software breakpoint if the memory can be decoded as instruction. RISC-V architecture instruction format specifies the length of instruction in first bits, so we can set a breakpoint for these cases. This is needed as RISCV have a lot of extensions, that are not supported by `EmulateInstructionRISCV`. Test Plan: Reviewers: Subscribers: Tasks: Tags: Differential Revision: https://phabricator.intern.facebook.com/D60251663
1 parent ff46bd8 commit fc17f98

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)