-
Notifications
You must be signed in to change notification settings - Fork 13.5k
[lldb][riscv] Fix setting breakpoint for undecoded instruction #90075
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -94,6 +94,38 @@ static lldb::addr_t ReadFlags(NativeRegisterContext ®siter_context) { | |
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); | ||
Status error; | ||
error = process.SetBreakpoint(pc, size_hint, /*hardware=*/false); | ||
|
||
// If setting the breakpoint fails because pc is out of the address | ||
// space, ignore it and let the debugee segfault. | ||
if (error.GetError() == EIO || error.GetError() == EFAULT) | ||
return Status(); | ||
if (error.Fail()) | ||
return error; | ||
|
||
return Status(); | ||
} | ||
|
||
Status NativeProcessSoftwareSingleStep::SetupSoftwareSingleStepping( | ||
NativeThreadProtocol &thread) { | ||
Status error; | ||
|
@@ -115,8 +147,23 @@ Status NativeProcessSoftwareSingleStep::SetupSoftwareSingleStepping( | |
emulator_up->SetWriteMemCallback(&WriteMemoryCallback); | ||
emulator_up->SetWriteRegCallback(&WriteRegisterCallback); | ||
|
||
if (!emulator_up->ReadInstruction()) | ||
return Status("Read instruction failed!"); | ||
if (!emulator_up->ReadInstruction()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this block now be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah wait, I see. This method is trying to decode where the next instruction will go, with branches and jumps decoded, so we can put a breakpoint there. And you're handling the case where we can't decode the current instruction (I now understand why you used that in your test case). It seems harmless to call GetLastInstrSize() if the instruction that couldn't be decoded, and add the length of the instruction to pc. We can assume the emulation engine will emulate all branching instructions. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, you are right: I'm adding an interface to get instruction size, as if it is not a jump or branch, actually, we do not need to emulate it on engine. As RISCV have a lot of extensions, even vendor-specific, we can not simply emulate all of them. I've added a common interface, as it may be helpful for other architectures, as for new implementations later user will need to implement only branches and jumps. |
||
// try to get at least the size of next instruction to set breakpoint. | ||
auto instr_size = emulator_up->GetLastInstrSize(); | ||
if (!instr_size) | ||
return Status("Read instruction failed!"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We've defined the new GetLastInstrSize() method for the RISCV EmulateInstruction plugin, but others like AArch64 won't have that, so this will error out on them, won't it? What we really want to express is "if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, in this MR this is a common code, and every other architecture now not implemented |
||
bool success = false; | ||
auto pc = emulator_up->ReadRegisterUnsigned(eRegisterKindGeneric, | ||
LLDB_REGNUM_GENERIC_PC, | ||
LLDB_INVALID_ADDRESS, &success); | ||
if (!success) | ||
return Status("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; | ||
} | ||
|
||
bool emulation_result = | ||
emulator_up->EvaluateInstruction(eEmulateInstructionOptionAutoAdvancePC); | ||
|
@@ -157,29 +204,7 @@ Status NativeProcessSoftwareSingleStep::SetupSoftwareSingleStepping( | |
// modifying the PC but we don't know how. | ||
return Status("Instruction emulation failed unexpectedly."); | ||
} | ||
|
||
int size_hint = 0; | ||
if (arch.GetMachine() == llvm::Triple::arm) { | ||
if (next_flags & 0x20) { | ||
// Thumb mode | ||
size_hint = 2; | ||
} else { | ||
// Arm mode | ||
size_hint = 4; | ||
} | ||
} else if (arch.IsMIPS() || arch.GetTriple().isPPC64() || | ||
arch.GetTriple().isRISCV() || arch.GetTriple().isLoongArch()) | ||
size_hint = 4; | ||
error = process.SetBreakpoint(next_pc, size_hint, /*hardware=*/false); | ||
|
||
// If setting the breakpoint fails because next_pc is out of the address | ||
// space, ignore it and let the debugee segfault. | ||
if (error.GetError() == EIO || error.GetError() == EFAULT) { | ||
return Status(); | ||
} else if (error.Fail()) | ||
return error; | ||
|
||
auto result = SetSoftwareBreakpointOnPC(arch, next_pc, next_flags, process); | ||
m_threads_stepping_with_breakpoint.insert({thread.GetID(), next_pc}); | ||
|
||
return Status(); | ||
return result; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
C_SOURCES := main.c | ||
|
||
include Makefile.rules |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
""" | ||
Test that we can set up software breakpoint even if we failed to decode and execute instruction | ||
""" | ||
|
||
import lldb | ||
from lldbsuite.test.decorators import * | ||
from lldbsuite.test.lldbtest import * | ||
from lldbsuite.test import lldbutil | ||
|
||
|
||
class TestBreakpointIllegal(TestBase): | ||
@skipIf(archs=no_match(["rv64gc"])) | ||
def test_4(self): | ||
self.build() | ||
(target, process, cur_thread, bkpt) = lldbutil.run_to_source_breakpoint( | ||
self, "main", lldb.SBFileSpec("main.c") | ||
) | ||
self.runCmd("thread step-inst") | ||
# we need to step more, as some compilers do not set appropriate debug info. | ||
while cur_thread.GetStopDescription(256) == "instruction step into": | ||
self.runCmd("thread step-inst") | ||
# The stop reason of the thread should be illegal opcode. | ||
self.expect( | ||
"thread list", | ||
STOPPED_DUE_TO_SIGNAL, | ||
substrs=["stopped", "stop reason = signal SIGILL: illegal opcode"], | ||
) | ||
|
||
@skipIf(archs=no_match(["rv64gc"])) | ||
def test_2(self): | ||
self.build(dictionary={"C_SOURCES": "compressed.c", "EXE": "compressed.x"}) | ||
(target, process, cur_thread, bkpt) = lldbutil.run_to_source_breakpoint( | ||
self, "main", lldb.SBFileSpec("compressed.c"), exe_name="compressed.x" | ||
) | ||
self.runCmd("thread step-inst") | ||
# we need to step more, as some compilers do not set appropriate debug info. | ||
while cur_thread.GetStopDescription(256) == "instruction step into": | ||
self.runCmd("thread step-inst") | ||
# The stop reason of the thread should be illegal opcode. | ||
self.expect( | ||
"thread list", | ||
STOPPED_DUE_TO_SIGNAL, | ||
substrs=["stopped", "stop reason = signal SIGILL: illegal opcode"], | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
int main() { | ||
// This instruction is not valid, but we have an ability to set | ||
// software breakpoint. | ||
// This results in illegal instruction during execution, not fail to set | ||
// breakpoint | ||
asm volatile(".2byte 0xaf"); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
int main() { | ||
// This instruction is not valid, but we have an ability to set | ||
// software breakpoint. | ||
// This results in illegal instruction during execution, not fail to set | ||
// breakpoint | ||
asm volatile(".4byte 0xc58573" : :); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small nit, but in recent years we've been preferring using
llvm::Error
overlldb::Status
where possible. The benefit ofError
is that it must be checked and it's trivial to convert between the two. It would be nice if this function would return anllvm::Error
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like to keep
Status
in this MR, as from my point of view, this MR will do more that it should -- it will add a new functionality and change the way we work with errors here.Moreover, changing this will result in unnecessary casts from
Status
toError
(process.SetBreakpoint
) and fromError
toStatus
(SetupSoftwareSingleStepping
).Note: I've checked that we have
Status::ToError
andStatus(llvm::Error error)
ctor, but I'm not sure these functions will suffice (AFAIUm_code
will be saved only ifm_type == ErrorType::eErrorTypePOSIX
).