Skip to content

Commit 471ed2b

Browse files
committed
[lldb] Correctly annotate threads at a bp site as hitting it (llvm#82709)
This is next in my series of "fix the racey tests that fail on greendragon" addressing the failure of TestConcurrentManyBreakpoints.py where we set a breakpoint in a function that 100 threads execute, and we check that we hit the breakpoint 100 times. But sometimes it is only hit 99 times, and the test fails. When we hit a software breakpoint, the pc value for the thread is the address of the breakpoint instruction - as if it had not been hit yet. And because a user might ADD a breakpoint for the current pc from the commandline, when we go to resume execution, any thread that is sitting at a breakpoint site will be silently advanced past the breakpoint instruction (disable bp, instruction step that thread, re-enable bp) before resuming -- whether that thread has hit its breakpoint or not. What this test is exposing is that there is another corner case, a thread that is sitting at a breakpoint site but has not yet executed the breakpoint instruction. The thread will have no stop reason, no mach exception, so it will not be recorded as having hit the breakpoint (because it hasn't yet). But when we resume execution, because it is sitting at a breakpoint site, we advance past it and miss the breakpoint hit. In 2016 Abhishek Aggarwal handled a similar issue with a patch in `ProcessGDBRemote::SetThreadStopInfo()`, adding a breakpoint StopInfo for a thread sitting at a breakpoint site that has no stop reason. debugserver's `jThreadsInfo` would not correctly execute Abhishek's code though because it would respond with `"reason":"none"` for a thread with no stop reason, and `SetThreadStopInfo()` expected an empty reason here. The first part of my patch is to clear the `reason` if it is `"none"` so we flow through the code correctly. On Darwin, though, our stop reply packet (Txx...) includes the `threads`, `thread-pcs`, and `jstopinfo` keys, which give us the tids for all current threads, the pc values for those threads, and `jstopinfo` has a JSON dictionary with the mach exceptions for all threads that have a mach exception. In `ProcessGDBRemote::CalculateThreadStopInfo()` we set the StopInfo for each thread for a private stop and if we have `jstopinfo` it is the source of all the StopInfos. I have to add the same logic here, to give the thread a breakpoint StopInfo even though it hasn't executed the breakpoint yet. In this case we are very early in thread construction and I only have the information in the Txx stop reply packet -- tids, pcs, and jstopinfo, so I can't use the normal general mechanisms of going through the RegisterContext to get the pc, it's a bit different. If I hack debugserver to not issue `jstopinfo`, `CalculateThreadStopInfo` will fall back to sending `qThreadStopInfo` for each thread and going through `ProcessGDBRemote::SetThreadStopInfo()` to set the stop infos (and with the `reason:none` fix, use Abhishek's code). rdar://110549165 (cherry picked from commit 87fadb3)
1 parent 1b02227 commit 471ed2b

File tree

2 files changed

+41
-13
lines changed

2 files changed

+41
-13
lines changed

lldb/include/lldb/Target/Thread.h

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1188,13 +1188,20 @@ class Thread : public std::enable_shared_from_this<Thread>,
11881188

11891189
void CalculatePublicStopInfo();
11901190

1191-
// Ask the thread subclass to set its stop info.
1192-
//
1193-
// Thread subclasses should call Thread::SetStopInfo(...) with the reason the
1194-
// thread stopped.
1195-
//
1196-
// \return
1197-
// True if Thread::SetStopInfo(...) was called, false otherwise.
1191+
/// Ask the thread subclass to set its stop info.
1192+
///
1193+
/// Thread subclasses should call Thread::SetStopInfo(...) with the reason the
1194+
/// thread stopped.
1195+
///
1196+
/// A thread that is sitting at a breakpoint site, but has not yet executed
1197+
/// the breakpoint instruction, should have a breakpoint-hit StopInfo set.
1198+
/// When execution is resumed, any thread sitting at a breakpoint site will
1199+
/// instruction-step over the breakpoint instruction silently, and we will
1200+
/// never record this breakpoint as being hit, updating the hit count,
1201+
/// possibly executing breakpoint commands or conditions.
1202+
///
1203+
/// \return
1204+
/// True if Thread::SetStopInfo(...) was called, false otherwise.
11981205
virtual bool CalculateStopInfo() = 0;
11991206

12001207
// Gets the temporary resume state for a thread.

lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1601,6 +1601,26 @@ bool ProcessGDBRemote::CalculateThreadStopInfo(ThreadGDBRemote *thread) {
16011601
// has no stop reason.
16021602
thread->GetRegisterContext()->InvalidateIfNeeded(true);
16031603
if (!GetThreadStopInfoFromJSON(thread, m_jstopinfo_sp)) {
1604+
// If a thread is stopped at a breakpoint site, set that as the stop
1605+
// reason even if it hasn't executed the breakpoint instruction yet.
1606+
// We will silently step over the breakpoint when we resume execution
1607+
// and miss the fact that this thread hit the breakpoint.
1608+
const size_t num_thread_ids = m_thread_ids.size();
1609+
for (size_t i = 0; i < num_thread_ids; i++) {
1610+
if (m_thread_ids[i] == thread->GetID() && m_thread_pcs.size() > i) {
1611+
addr_t pc = m_thread_pcs[i];
1612+
lldb::BreakpointSiteSP bp_site_sp =
1613+
thread->GetProcess()->GetBreakpointSiteList().FindByAddress(pc);
1614+
if (bp_site_sp) {
1615+
if (bp_site_sp->ValidForThisThread(*thread)) {
1616+
thread->SetStopInfo(
1617+
StopInfo::CreateStopReasonWithBreakpointSiteID(
1618+
*thread, bp_site_sp->GetID()));
1619+
return true;
1620+
}
1621+
}
1622+
}
1623+
}
16041624
thread->SetStopInfo(StopInfoSP());
16051625
}
16061626
return true;
@@ -1715,7 +1735,9 @@ ThreadSP ProcessGDBRemote::SetThreadStopInfo(
17151735
} else {
17161736
bool handled = false;
17171737
bool did_exec = false;
1718-
if (!reason.empty()) {
1738+
// debugserver can send reason = "none" which is equivalent
1739+
// to no reason.
1740+
if (!reason.empty() && reason != "none") {
17191741
if (reason == "trace") {
17201742
addr_t pc = thread_sp->GetRegisterContext()->GetPC();
17211743
lldb::BreakpointSiteSP bp_site_sp =
@@ -1852,11 +1874,10 @@ ThreadSP ProcessGDBRemote::SetThreadStopInfo(
18521874
lldb::BreakpointSiteSP bp_site_sp =
18531875
thread_sp->GetProcess()->GetBreakpointSiteList().FindByAddress(pc);
18541876

1855-
// If the current pc is a breakpoint site then the StopInfo should be
1856-
// set to Breakpoint even though the remote stub did not set it as such.
1857-
// This can happen when the thread is involuntarily interrupted (e.g.
1858-
// due to stops on other threads) just as it is about to execute the
1859-
// breakpoint instruction.
1877+
// If a thread is stopped at a breakpoint site, set that as the stop
1878+
// reason even if it hasn't executed the breakpoint instruction yet.
1879+
// We will silently step over the breakpoint when we resume execution
1880+
// and miss the fact that this thread hit the breakpoint.
18601881
if (bp_site_sp && bp_site_sp->ValidForThisThread(*thread_sp)) {
18611882
thread_sp->SetStopInfo(StopInfo::CreateStopReasonWithBreakpointSiteID(
18621883
*thread_sp, bp_site_sp->GetID()));

0 commit comments

Comments
 (0)