diff options
author | Jason Molenda <jmolenda@apple.com> | 2024-07-19 17:26:13 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-07-19 17:26:13 -0700 |
commit | 05f0e86cc895181b3d2210458c78938f83353002 (patch) | |
tree | b7386dca7ee56a47f0543639829f8c6c4e7ef4ab /lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp | |
parent | be7f1827ff180df7d3f585432048e784eaa932ee (diff) | |
download | llvm-05f0e86cc895181b3d2210458c78938f83353002.zip llvm-05f0e86cc895181b3d2210458c78938f83353002.tar.gz llvm-05f0e86cc895181b3d2210458c78938f83353002.tar.bz2 |
[lldb] Change lldb's breakpoint handling behavior (#96260)
lldb today has two rules: When a thread stops at a BreakpointSite, we
set the thread's StopReason to be "breakpoint hit" (regardless if we've
actually hit the breakpoint, or if we've merely stopped *at* the
breakpoint instruction/point and haven't tripped it yet). And second,
when resuming a process, any thread sitting at a BreakpointSite is
silently stepped over the BreakpointSite -- because we've already
flagged the breakpoint hit when we stopped there originally.
In this patch, I change lldb to only set a thread's stop reason to
breakpoint-hit when we've actually executed the instruction/triggered
the breakpoint. When we resume, we only silently step past a
BreakpointSite that we've registered as hit. We preserve this state
across inferior function calls that the user may do while stopped, etc.
Also, when a user adds a new breakpoint at $pc while stopped, or changes
$pc to be the address of a BreakpointSite, we will silently step past
that breakpoint when the process resumes. This is purely a UX call, I
don't think there's any person who wants to set a breakpoint at $pc and
then hit it immediately on resuming.
One non-intuitive UX from this change, but I'm convinced it is
necessary: If you're stopped at a BreakpointSite that has not yet
executed, you `stepi`, you will hit the breakpoint and the pc will not
yet advance. This thread has not completed its stepi, and the thread
plan is still on the stack. If you then `continue` the thread, lldb will
now stop and say, "instruction step completed", one instruction past the
BreakpointSite. You can continue a second time to resume execution. I
discussed this with Jim, and trying to paper over this behavior will
lead to more complicated scenarios behaving non-intuitively. And mostly
it's the testsuite that was trying to instruction step past a breakpoint
and getting thrown off -- and I changed those tests to expect the new
behavior.
The bugs driving this change are all from lldb dropping the real stop
reason for a thread and setting it to breakpoint-hit when that was not
the case. Jim hit one where we have an aarch64 watchpoint that triggers
one instruction before a BreakpointSite. On this arch we are notified of
the watchpoint hit after the instruction has been unrolled -- we disable
the watchpoint, instruction step, re-enable the watchpoint and collect
the new value. But now we're on a BreakpointSite so the watchpoint-hit
stop reason is lost.
Another was reported by ZequanWu in
https://discourse.llvm.org/t/lldb-unable-to-break-at-start/78282 we
attach to/launch a process with the pc at a BreakpointSite and
misbehave. Caroline Tice mentioned it is also a problem they've had with
putting a breakpoint on _dl_debug_state.
The change to each Process plugin that does execution control is that
1. If we've stopped at a BreakpointSite that has not been executed yet,
we will call Thread::SetThreadStoppedAtUnexecutedBP(pc) to record
that. When the thread resumes, if the pc is still at the same site, we
will continue, hit the breakpoint, and stop again.
2. When we've actually hit a breakpoint (enabled for this thread or not),
the Process plugin should call Thread::SetThreadHitBreakpointSite().
When we go to resume the thread, we will push a step-over-breakpoint
ThreadPlan before resuming.
The biggest set of changes is to StopInfoMachException where we
translate a Mach Exception into a stop reason. The Mach exception codes
differ in a few places depending on the target (unambiguously), and I
didn't want to duplicate the new code for each target so I've tested
what mach exceptions we get for each action on each target, and
reorganized StopInfoMachException::CreateStopReasonWithMachException to
document these possible values, and handle them without specializing
based on the target arch.
rdar://123942164
Diffstat (limited to 'lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp')
-rw-r--r-- | lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp | 93 |
1 files changed, 31 insertions, 62 deletions
diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp index 604c923..9ab03c1 100644 --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp @@ -1598,29 +1598,8 @@ bool ProcessGDBRemote::CalculateThreadStopInfo(ThreadGDBRemote *thread) { // that have stop reasons, and if there is no entry for a thread, then it // has no stop reason. thread->GetRegisterContext()->InvalidateIfNeeded(true); - if (!GetThreadStopInfoFromJSON(thread, m_jstopinfo_sp)) { - // If a thread is stopped at a breakpoint site, set that as the stop - // reason even if it hasn't executed the breakpoint instruction yet. - // We will silently step over the breakpoint when we resume execution - // and miss the fact that this thread hit the breakpoint. - const size_t num_thread_ids = m_thread_ids.size(); - for (size_t i = 0; i < num_thread_ids; i++) { - if (m_thread_ids[i] == thread->GetID() && m_thread_pcs.size() > i) { - addr_t pc = m_thread_pcs[i]; - lldb::BreakpointSiteSP bp_site_sp = - thread->GetProcess()->GetBreakpointSiteList().FindByAddress(pc); - if (bp_site_sp) { - if (bp_site_sp->ValidForThisThread(*thread)) { - thread->SetStopInfo( - StopInfo::CreateStopReasonWithBreakpointSiteID( - *thread, bp_site_sp->GetID())); - return true; - } - } - } - } + if (!GetThreadStopInfoFromJSON(thread, m_jstopinfo_sp)) thread->SetStopInfo(StopInfoSP()); - } return true; } @@ -1729,6 +1708,12 @@ ThreadSP ProcessGDBRemote::SetThreadStopInfo( if (ThreadSP memory_thread_sp = m_thread_list.GetBackingThread(thread_sp)) thread_sp = memory_thread_sp; + addr_t pc = thread_sp->GetRegisterContext()->GetPC(); + BreakpointSiteSP bp_site_sp = + thread_sp->GetProcess()->GetBreakpointSiteList().FindByAddress(pc); + if (bp_site_sp && bp_site_sp->IsEnabled()) + thread_sp->SetThreadStoppedAtUnexecutedBP(pc); + if (exc_type != 0) { const size_t exc_data_size = exc_data.size(); @@ -1745,26 +1730,10 @@ ThreadSP ProcessGDBRemote::SetThreadStopInfo( // to no reason. if (!reason.empty() && reason != "none") { if (reason == "trace") { - addr_t pc = thread_sp->GetRegisterContext()->GetPC(); - lldb::BreakpointSiteSP bp_site_sp = - thread_sp->GetProcess()->GetBreakpointSiteList().FindByAddress( - pc); - - // If the current pc is a breakpoint site then the StopInfo should be - // set to Breakpoint Otherwise, it will be set to Trace. - if (bp_site_sp && bp_site_sp->ValidForThisThread(*thread_sp)) { - thread_sp->SetStopInfo( - StopInfo::CreateStopReasonWithBreakpointSiteID( - *thread_sp, bp_site_sp->GetID())); - } else - thread_sp->SetStopInfo( - StopInfo::CreateStopReasonToTrace(*thread_sp)); + thread_sp->SetStopInfo(StopInfo::CreateStopReasonToTrace(*thread_sp)); handled = true; } else if (reason == "breakpoint") { - addr_t pc = thread_sp->GetRegisterContext()->GetPC(); - lldb::BreakpointSiteSP bp_site_sp = - thread_sp->GetProcess()->GetBreakpointSiteList().FindByAddress( - pc); + thread_sp->SetThreadHitBreakpointSite(); if (bp_site_sp) { // If the breakpoint is for this thread, then we'll report the hit, // but if it is for another thread, we can just report no reason. @@ -1880,39 +1849,41 @@ ThreadSP ProcessGDBRemote::SetThreadStopInfo( StopInfo::CreateStopReasonVForkDone(*thread_sp)); handled = true; } - } else if (!signo) { - addr_t pc = thread_sp->GetRegisterContext()->GetPC(); - lldb::BreakpointSiteSP bp_site_sp = - thread_sp->GetProcess()->GetBreakpointSiteList().FindByAddress(pc); - - // If a thread is stopped at a breakpoint site, set that as the stop - // reason even if it hasn't executed the breakpoint instruction yet. - // We will silently step over the breakpoint when we resume execution - // and miss the fact that this thread hit the breakpoint. - if (bp_site_sp && bp_site_sp->ValidForThisThread(*thread_sp)) { - thread_sp->SetStopInfo(StopInfo::CreateStopReasonWithBreakpointSiteID( - *thread_sp, bp_site_sp->GetID())); - handled = true; - } } if (!handled && signo && !did_exec) { if (signo == SIGTRAP) { // Currently we are going to assume SIGTRAP means we are either // hitting a breakpoint or hardware single stepping. + + // We can't disambiguate between stepping-to-a-breakpointsite and + // hitting-a-breakpointsite. + // + // A user can instruction-step, and be stopped at a BreakpointSite. + // Or a user can be sitting at a BreakpointSite, + // instruction-step which hits the breakpoint and the pc does not + // advance. + // + // In both cases, we're at a BreakpointSite when stopped, and + // the resume state was eStateStepping. + + // Assume if we're at a BreakpointSite, we hit it. handled = true; addr_t pc = thread_sp->GetRegisterContext()->GetPC() + m_breakpoint_pc_offset; - lldb::BreakpointSiteSP bp_site_sp = + BreakpointSiteSP bp_site_sp = thread_sp->GetProcess()->GetBreakpointSiteList().FindByAddress( pc); + // We can't know if we hit it or not. So if we are stopped at + // a BreakpointSite, assume we hit it, and should step past the + // breakpoint when we resume. This is contrary to how we handle + // BreakpointSites in any other location, but we can't know for + // sure what happened so it's a reasonable default. if (bp_site_sp) { - // If the breakpoint is for this thread, then we'll report the hit, - // but if it is for another thread, we can just report no reason. - // We don't need to worry about stepping over the breakpoint here, - // that will be taken care of when the thread resumes and notices - // that there's a breakpoint under the pc. + if (bp_site_sp->IsEnabled()) + thread_sp->SetThreadHitBreakpointSite(); + if (bp_site_sp->ValidForThisThread(*thread_sp)) { if (m_breakpoint_pc_offset != 0) thread_sp->GetRegisterContext()->SetPC(pc); @@ -1926,8 +1897,6 @@ ThreadSP ProcessGDBRemote::SetThreadStopInfo( } else { // If we were stepping then assume the stop was the result of the // trace. If we were not stepping then report the SIGTRAP. - // FIXME: We are still missing the case where we single step over a - // trap instruction. if (thread_sp->GetTemporaryResumeState() == eStateStepping) thread_sp->SetStopInfo( StopInfo::CreateStopReasonToTrace(*thread_sp)); |