[Lldb-commits] [lldb] Revert "[lldb] Batch breakpoint step-over for threads stopped at the … (PR #182431)
Aiden Grossman via lldb-commits
lldb-commits at lists.llvm.org
Thu Feb 19 20:01:05 PST 2026
https://github.com/boomanaiden154 created https://github.com/llvm/llvm-project/pull/182431
…same site (#180101)"
This reverts commit f45754b65e32789bbb0c0fae50a529fed244546d. This reverts commit 80c0e50fd939388b41ac43deef3170f7b75ba41b.
This was causing very high test flakiness that was not fixed by the follow-up patch that also gets reverted.
https://lab.llvm.org/staging/#/builders/192/builds/20709/steps/4/logs/stdio
>From 2cdcf4df7026339c38a5f2bac7e02a4764c08146 Mon Sep 17 00:00:00 2001
From: Aiden Grossman <aidengrossman at google.com>
Date: Fri, 20 Feb 2026 03:59:26 +0000
Subject: [PATCH] Revert "[lldb] Batch breakpoint step-over for threads stopped
at the same site (#180101)"
This reverts commit f45754b65e32789bbb0c0fae50a529fed244546d.
This reverts commit 80c0e50fd939388b41ac43deef3170f7b75ba41b.
This was causing very high test flakiness that was not fixed by the
follow-up patch that also gets reverted.
https://lab.llvm.org/staging/#/builders/192/builds/20709/steps/4/logs/stdio
---
lldb/include/lldb/Target/ThreadList.h | 23 --
.../Target/ThreadPlanStepOverBreakpoint.h | 19 --
lldb/source/Target/ThreadList.cpp | 165 +------------
.../Target/ThreadPlanStepOverBreakpoint.cpp | 33 +--
.../TestBatchedBreakpointStepOver.py | 216 ------------------
...TestConcurrentBatchedBreakpointStepOver.py | 127 ----------
6 files changed, 13 insertions(+), 570 deletions(-)
delete mode 100644 lldb/test/API/functionalities/gdb_remote_client/TestBatchedBreakpointStepOver.py
delete mode 100644 lldb/test/API/functionalities/thread/concurrent_events/TestConcurrentBatchedBreakpointStepOver.py
diff --git a/lldb/include/lldb/Target/ThreadList.h b/lldb/include/lldb/Target/ThreadList.h
index 6920cefc20fd9..c108962003598 100644
--- a/lldb/include/lldb/Target/ThreadList.h
+++ b/lldb/include/lldb/Target/ThreadList.h
@@ -18,9 +18,6 @@
#include "lldb/Utility/UserID.h"
#include "lldb/lldb-private.h"
-#include "llvm/ADT/DenseMap.h"
-#include "llvm/ADT/DenseSet.h"
-
namespace lldb_private {
// This is a thread list with lots of functionality for use only by the process
@@ -144,19 +141,6 @@ class ThreadList : public ThreadCollection {
/// Precondition: both thread lists must be belong to the same process.
void Update(ThreadList &rhs);
- /// Called by ThreadPlanStepOverBreakpoint when a thread finishes stepping
- /// over a breakpoint. This tracks which threads are still stepping over
- /// each breakpoint address, and only re-enables the breakpoint when ALL
- /// threads have finished stepping over it.
- void ThreadFinishedSteppingOverBreakpoint(lldb::addr_t breakpoint_addr,
- lldb::tid_t tid);
-
- /// Register a thread that is about to step over a breakpoint.
- /// The breakpoint will be re-enabled only after all registered threads
- /// have called ThreadFinishedSteppingOverBreakpoint.
- void RegisterThreadSteppingOverBreakpoint(lldb::addr_t breakpoint_addr,
- lldb::tid_t tid);
-
protected:
void SetShouldReportStop(Vote vote);
@@ -170,13 +154,6 @@ class ThreadList : public ThreadCollection {
m_selected_tid; ///< For targets that need the notion of a current thread.
std::vector<lldb::tid_t> m_expression_tid_stack;
- /// Tracks which threads are currently stepping over each breakpoint address.
- /// Key: breakpoint address, Value: set of thread IDs stepping over it.
- /// When a thread finishes stepping, it's removed from the set. When the set
- /// becomes empty, the breakpoint is re-enabled.
- llvm::DenseMap<lldb::addr_t, llvm::DenseSet<lldb::tid_t>>
- m_threads_stepping_over_bp;
-
private:
ThreadList() = delete;
};
diff --git a/lldb/include/lldb/Target/ThreadPlanStepOverBreakpoint.h b/lldb/include/lldb/Target/ThreadPlanStepOverBreakpoint.h
index 6537ac91af449..0da8dbf44ffd8 100644
--- a/lldb/include/lldb/Target/ThreadPlanStepOverBreakpoint.h
+++ b/lldb/include/lldb/Target/ThreadPlanStepOverBreakpoint.h
@@ -36,24 +36,6 @@ class ThreadPlanStepOverBreakpoint : public ThreadPlan {
lldb::addr_t GetBreakpointLoadAddress() const { return m_breakpoint_addr; }
- /// When set to true, the breakpoint site will NOT be re-enabled directly
- /// by this plan. Instead, the plan will call
- /// ThreadList::ThreadFinishedSteppingOverBreakpoint() when it completes,
- /// allowing ThreadList to track all threads stepping over the same
- /// breakpoint and only re-enable it when ALL threads have finished.
- void SetDeferReenableBreakpointSite(bool defer) {
- m_defer_reenable_breakpoint_site = defer;
- }
-
- bool GetDeferReenableBreakpointSite() const {
- return m_defer_reenable_breakpoint_site;
- }
-
- /// Mark the breakpoint site as already re-enabled, suppressing any
- /// re-enable in DidPop()/ThreadDestroyed(). Used when discarding plans
- /// during WillResume cleanup to avoid spurious breakpoint toggles.
- void SetReenabledBreakpointSite() { m_reenabled_breakpoint_site = true; }
-
protected:
bool DoPlanExplainsStop(Event *event_ptr) override;
bool DoWillResume(lldb::StateType resume_state, bool current_plan) override;
@@ -65,7 +47,6 @@ class ThreadPlanStepOverBreakpoint : public ThreadPlan {
lldb::user_id_t m_breakpoint_site_id;
bool m_auto_continue;
bool m_reenabled_breakpoint_site;
- bool m_defer_reenable_breakpoint_site;
ThreadPlanStepOverBreakpoint(const ThreadPlanStepOverBreakpoint &) = delete;
const ThreadPlanStepOverBreakpoint &
diff --git a/lldb/source/Target/ThreadList.cpp b/lldb/source/Target/ThreadList.cpp
index eb69bd038cc42..77a1c40b95f70 100644
--- a/lldb/source/Target/ThreadList.cpp
+++ b/lldb/source/Target/ThreadList.cpp
@@ -15,13 +15,10 @@
#include "lldb/Target/Thread.h"
#include "lldb/Target/ThreadList.h"
#include "lldb/Target/ThreadPlan.h"
-#include "lldb/Target/ThreadPlanStepOverBreakpoint.h"
#include "lldb/Utility/LLDBAssert.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/State.h"
-#include "llvm/ADT/DenseSet.h"
-#include "llvm/ADT/SmallVector.h"
using namespace lldb;
using namespace lldb_private;
@@ -507,30 +504,9 @@ bool ThreadList::WillResume(RunDirection &direction) {
collection::iterator pos, end = m_threads.end();
- // Clear tracking state from the previous stop and pop any leftover
- // StepOverBreakpoint plans. This gives us a clean slate: plans will be
- // recreated fresh by SetupToStepOverBreakpointIfNeeded below, and the
- // batching logic will recompute deferred state from scratch.
- m_threads_stepping_over_bp.clear();
- for (const auto &thread_sp : m_threads) {
- ThreadPlan *plan = thread_sp->GetCurrentPlan();
- if (plan && plan->GetKind() == ThreadPlan::eKindStepOverBreakpoint) {
- // Suppress the re-enable side effect in DidPop() — the breakpoint
- // may still be disabled from the previous batch, and we don't want
- // to toggle it. The new plans will handle disable/re-enable correctly.
- static_cast<ThreadPlanStepOverBreakpoint *>(plan)
- ->SetReenabledBreakpointSite();
- thread_sp->DiscardPlan();
- }
- }
-
// Go through the threads and see if any thread wants to run just itself.
// if so then pick one and run it.
- // Collect threads for batched vCont for multiple threads at the same
- // breakpoint.
- llvm::SmallVector<ThreadSP> batched_step_threads;
-
ThreadList run_me_only_list(m_process);
run_me_only_list.SetStopID(m_process.GetStopID());
@@ -600,13 +576,6 @@ bool ThreadList::WillResume(RunDirection &direction) {
assert(thread_to_run->GetCurrentPlan()->GetDirection() == direction);
}
} else {
- // Pre-scan to find all threads that need to step over a breakpoint,
- // and group them by breakpoint address. This optimization allows us to
- // step multiple threads over the same breakpoint with minimal breakpoint
- // swaps, only the last thread in each group will re-enable the breakpoint.
- llvm::DenseMap<lldb::addr_t, llvm::SmallVector<ThreadSP>> breakpoint_groups;
- bool found_run_before_public_stop = false;
-
for (pos = m_threads.begin(); pos != end; ++pos) {
ThreadSP thread_sp(*pos);
if (thread_sp->GetResumeState() != eStateSuspended) {
@@ -620,69 +589,14 @@ bool ThreadList::WillResume(RunDirection &direction) {
assert(thread_sp->GetCurrentPlan()->GetDirection() == direction);
// You can't say "stop others" and also want yourself to be suspended.
assert(thread_sp->GetCurrentPlan()->RunState() != eStateSuspended);
-
- // Get the breakpoint address from the step-over-breakpoint plan.
- ThreadPlan *current_plan = thread_sp->GetCurrentPlan();
- if (current_plan &&
- current_plan->GetKind() == ThreadPlan::eKindStepOverBreakpoint) {
- ThreadPlanStepOverBreakpoint *bp_plan =
- static_cast<ThreadPlanStepOverBreakpoint *>(current_plan);
- lldb::addr_t bp_addr = bp_plan->GetBreakpointLoadAddress();
- breakpoint_groups[bp_addr].push_back(thread_sp);
- }
-
thread_to_run = thread_sp;
if (thread_sp->ShouldRunBeforePublicStop()) {
// This takes precedence, so if we find one of these, service it:
- found_run_before_public_stop = true;
break;
}
}
}
}
-
- // Only apply batching optimization if we have a complete picture of
- // breakpoint groups. If a ShouldRunBeforePublicStop thread caused the
- // scan to exit early, the groups are incomplete and the priority thread
- // must run solo. Deferred state will be cleaned up on next WillResume().
- if (!found_run_before_public_stop) {
- // For each group of threads at the same breakpoint, register them with
- // ThreadList and set them to use deferred re-enable. The breakpoint will
- // only be re-enabled when ALL threads have finished stepping over it.
- // Also collect threads for batched vCont if multiple threads at same BP.
- for (auto &group : breakpoint_groups) {
- lldb::addr_t bp_addr = group.first;
- llvm::SmallVector<ThreadSP> &threads = group.second;
-
- if (threads.size() > 1) {
- // Use tracking since multiple threads are stepping over the same
- // breakpoint.
- for (ThreadSP &thread_sp : threads) {
- // Register this thread as stepping over the breakpoint.
- RegisterThreadSteppingOverBreakpoint(bp_addr, thread_sp->GetID());
-
- // Set the plan to defer re-enabling (use callback instead).
- ThreadPlan *plan = thread_sp->GetCurrentPlan();
- // Verify the plan is actually a StepOverBreakpoint plan.
- if (plan &&
- plan->GetKind() == ThreadPlan::eKindStepOverBreakpoint) {
- ThreadPlanStepOverBreakpoint *bp_plan =
- static_cast<ThreadPlanStepOverBreakpoint *>(plan);
- bp_plan->SetDeferReenableBreakpointSite(true);
- }
- }
-
- // Pick the largest group for batched vCont.
- if (threads.size() > batched_step_threads.size())
- batched_step_threads = threads;
- }
- // Keeps default behavior for a single thread at breakpoint.
- }
-
- // If we found a batch, use the first thread as thread_to_run.
- if (!batched_step_threads.empty())
- thread_to_run = batched_step_threads[0];
- }
}
if (thread_to_run != nullptr) {
@@ -701,24 +615,7 @@ bool ThreadList::WillResume(RunDirection &direction) {
bool need_to_resume = true;
- if (!batched_step_threads.empty()) {
- // Batched stepping: all threads in the batch step together,
- // all other threads stay suspended.
- llvm::DenseSet<lldb::tid_t> batch_tids;
- for (ThreadSP &thread_sp : batched_step_threads)
- batch_tids.insert(thread_sp->GetID());
-
- for (const auto &thread_sp : m_threads) {
- if (batch_tids.count(thread_sp->GetID()) > 0) {
- // This thread is in the batch, let it step.
- if (!thread_sp->ShouldResume(thread_sp->GetCurrentPlan()->RunState()))
- need_to_resume = false;
- } else {
- // Suspend it since it's not in the batch.
- thread_sp->ShouldResume(eStateSuspended);
- }
- }
- } else if (thread_to_run == nullptr) {
+ if (thread_to_run == nullptr) {
// Everybody runs as they wish:
for (pos = m_threads.begin(); pos != end; ++pos) {
ThreadSP thread_sp(*pos);
@@ -904,63 +801,3 @@ ThreadList::ExpressionExecutionThreadPusher::ExpressionExecutionThreadPusher(
m_thread_list->PushExpressionExecutionThread(m_tid);
}
}
-
-void ThreadList::RegisterThreadSteppingOverBreakpoint(addr_t breakpoint_addr,
- tid_t tid) {
- std::lock_guard<std::recursive_mutex> guard(GetMutex());
- m_threads_stepping_over_bp[breakpoint_addr].insert(tid);
-
- Log *log = GetLog(LLDBLog::Step);
- LLDB_LOGF(
- log,
- "ThreadList::%s: Registered thread 0x%" PRIx64
- " stepping over breakpoint at 0x%" PRIx64 " (now %zu threads)",
- __FUNCTION__, tid, breakpoint_addr,
- static_cast<size_t>(m_threads_stepping_over_bp[breakpoint_addr].size()));
-}
-
-void ThreadList::ThreadFinishedSteppingOverBreakpoint(addr_t breakpoint_addr,
- tid_t tid) {
- std::lock_guard<std::recursive_mutex> guard(GetMutex());
-
- Log *log = GetLog(LLDBLog::Step);
-
- auto it = m_threads_stepping_over_bp.find(breakpoint_addr);
- if (it == m_threads_stepping_over_bp.end()) {
- // No threads registered for this breakpoint, re-enable directly.
- LLDB_LOGF(log,
- "ThreadList::%s: Thread 0x%" PRIx64
- " finished stepping over breakpoint at 0x%" PRIx64
- " but no threads were registered, re-enabling directly",
- __FUNCTION__, tid, breakpoint_addr);
- if (BreakpointSiteSP bp_site_sp =
- m_process.GetBreakpointSiteList().FindByAddress(breakpoint_addr))
- m_process.EnableBreakpointSite(bp_site_sp.get());
- return;
- }
-
- // Remove this thread from the set.
- it->second.erase(tid);
-
- LLDB_LOGF(log,
- "ThreadList::%s: Thread 0x%" PRIx64
- " finished stepping over breakpoint at 0x%" PRIx64
- " (%zu threads remaining)",
- __FUNCTION__, tid, breakpoint_addr,
- static_cast<size_t>(it->second.size()));
-
- // If no more threads are stepping over this breakpoint, re-enable it.
- if (it->second.empty()) {
- LLDB_LOGF(log,
- "ThreadList::%s: All threads finished stepping over breakpoint "
- "at 0x%" PRIx64 ", re-enabling breakpoint",
- __FUNCTION__, breakpoint_addr);
-
- if (BreakpointSiteSP bp_site_sp =
- m_process.GetBreakpointSiteList().FindByAddress(breakpoint_addr))
- m_process.EnableBreakpointSite(bp_site_sp.get());
-
- // Clean up the entry.
- m_threads_stepping_over_bp.erase(it);
- }
-}
diff --git a/lldb/source/Target/ThreadPlanStepOverBreakpoint.cpp b/lldb/source/Target/ThreadPlanStepOverBreakpoint.cpp
index 8b58ae541f368..3602527a9231b 100644
--- a/lldb/source/Target/ThreadPlanStepOverBreakpoint.cpp
+++ b/lldb/source/Target/ThreadPlanStepOverBreakpoint.cpp
@@ -10,7 +10,6 @@
#include "lldb/Target/Process.h"
#include "lldb/Target/RegisterContext.h"
-#include "lldb/Target/ThreadList.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/Stream.h"
@@ -22,14 +21,14 @@ using namespace lldb_private;
// the pc.
ThreadPlanStepOverBreakpoint::ThreadPlanStepOverBreakpoint(Thread &thread)
- : ThreadPlan(ThreadPlan::eKindStepOverBreakpoint,
- "Step over breakpoint trap", thread, eVoteNo,
- eVoteNoOpinion), // We need to report the run since this
- // happens first in the thread plan stack when
- // stepping over a breakpoint
- m_breakpoint_addr(LLDB_INVALID_ADDRESS), m_auto_continue(false),
- m_reenabled_breakpoint_site(false),
- m_defer_reenable_breakpoint_site(false)
+ : ThreadPlan(
+ ThreadPlan::eKindStepOverBreakpoint, "Step over breakpoint trap",
+ thread, eVoteNo,
+ eVoteNoOpinion), // We need to report the run since this happens
+ // first in the thread plan stack when stepping over
+ // a breakpoint
+ m_breakpoint_addr(LLDB_INVALID_ADDRESS),
+ m_auto_continue(false), m_reenabled_breakpoint_site(false)
{
m_breakpoint_addr = thread.GetRegisterContext()->GetPC();
@@ -156,18 +155,10 @@ bool ThreadPlanStepOverBreakpoint::MischiefManaged() {
void ThreadPlanStepOverBreakpoint::ReenableBreakpointSite() {
if (!m_reenabled_breakpoint_site) {
m_reenabled_breakpoint_site = true;
-
- if (m_defer_reenable_breakpoint_site) {
- // Let ThreadList track all threads stepping over this breakpoint.
- // It will re-enable the breakpoint only when ALL threads have finished.
- m_process.GetThreadList().ThreadFinishedSteppingOverBreakpoint(
- m_breakpoint_addr, GetThread().GetID());
- } else {
- // Default behavior: re-enable the breakpoint directly.
- if (BreakpointSiteSP bp_site_sp =
- m_process.GetBreakpointSiteList().FindByAddress(
- m_breakpoint_addr))
- m_process.EnableBreakpointSite(bp_site_sp.get());
+ BreakpointSiteSP bp_site_sp(
+ m_process.GetBreakpointSiteList().FindByAddress(m_breakpoint_addr));
+ if (bp_site_sp) {
+ m_process.EnableBreakpointSite(bp_site_sp.get());
}
}
}
diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestBatchedBreakpointStepOver.py b/lldb/test/API/functionalities/gdb_remote_client/TestBatchedBreakpointStepOver.py
deleted file mode 100644
index 953294a77f658..0000000000000
--- a/lldb/test/API/functionalities/gdb_remote_client/TestBatchedBreakpointStepOver.py
+++ /dev/null
@@ -1,216 +0,0 @@
-"""
-Test that when multiple threads are stopped at the same breakpoint, LLDB sends
-a batched vCont with multiple step actions and only one breakpoint disable/
-re-enable pair, rather than stepping each thread individually with repeated
-breakpoint toggles.
-
-Uses a mock GDB server to directly verify the packets LLDB sends.
-"""
-
-import re
-
-import lldb
-from lldbsuite.test.lldbtest import *
-from lldbsuite.test.decorators import *
-from lldbsuite.test.gdbclientutils import *
-from lldbsuite.test.lldbgdbclient import GDBRemoteTestBase
-
-
-class TestBatchedBreakpointStepOver(GDBRemoteTestBase):
- @skipIfXmlSupportMissing
- def test(self):
- BP_ADDR = 0x0000000000401020
- # PC after stepping past the breakpoint instruction.
- STEPPED_PC = BP_ADDR + 1
- NUM_THREADS = 10
- TIDS = [0x101 + i for i in range(NUM_THREADS)]
-
- class MyResponder(MockGDBServerResponder):
- def __init__(self):
- MockGDBServerResponder.__init__(self)
- self.resume_count = 0
- # Track which threads have completed their step.
- self.stepped_threads = set()
-
- def qSupported(self, client_supported):
- return (
- "PacketSize=3fff;QStartNoAckMode+;"
- "qXfer:features:read+;swbreak+;hwbreak+"
- )
-
- def qfThreadInfo(self):
- return "m" + ",".join("{:x}".format(t) for t in TIDS)
-
- def qsThreadInfo(self):
- return "l"
-
- def haltReason(self):
- # All threads stopped at the breakpoint address.
- threads_str = ",".join("{:x}".format(t) for t in TIDS)
- pcs_str = ",".join("{:x}".format(BP_ADDR) for _ in TIDS)
- return "T05thread:{:x};threads:{};thread-pcs:{};" "swbreak:;".format(
- TIDS[0], threads_str, pcs_str
- )
-
- def threadStopInfo(self, threadnum):
- threads_str = ",".join("{:x}".format(t) for t in TIDS)
- pcs_str = ",".join("{:x}".format(BP_ADDR) for _ in TIDS)
- return "T05thread:{:x};threads:{};thread-pcs:{};" "swbreak:;".format(
- threadnum, threads_str, pcs_str
- )
-
- def setBreakpoint(self, packet):
- return "OK"
-
- def readRegisters(self):
- return "00" * 160
-
- def readRegister(self, regno):
- return "00" * 8
-
- def qXferRead(self, obj, annex, offset, length):
- if annex == "target.xml":
- return (
- """<?xml version="1.0"?>
- <target version="1.0">
- <architecture>i386:x86-64</architecture>
- <feature name="org.gnu.gdb.i386.core">
- <reg name="rax" bitsize="64" regnum="0" type="int" group="general"/>
- <reg name="rbx" bitsize="64" regnum="1" type="int" group="general"/>
- <reg name="rcx" bitsize="64" regnum="2" type="int" group="general"/>
- <reg name="rdx" bitsize="64" regnum="3" type="int" group="general"/>
- <reg name="rsi" bitsize="64" regnum="4" type="int" group="general"/>
- <reg name="rdi" bitsize="64" regnum="5" type="int" group="general"/>
- <reg name="rbp" bitsize="64" regnum="6" type="data_ptr" group="general"/>
- <reg name="rsp" bitsize="64" regnum="7" type="data_ptr" group="general"/>
- <reg name="r8" bitsize="64" regnum="8" type="int" group="general"/>
- <reg name="r9" bitsize="64" regnum="9" type="int" group="general"/>
- <reg name="r10" bitsize="64" regnum="10" type="int" group="general"/>
- <reg name="r11" bitsize="64" regnum="11" type="int" group="general"/>
- <reg name="r12" bitsize="64" regnum="12" type="int" group="general"/>
- <reg name="r13" bitsize="64" regnum="13" type="int" group="general"/>
- <reg name="r14" bitsize="64" regnum="14" type="int" group="general"/>
- <reg name="r15" bitsize="64" regnum="15" type="int" group="general"/>
- <reg name="rip" bitsize="64" regnum="16" type="code_ptr" group="general"/>
- <reg name="eflags" bitsize="32" regnum="17" type="int" group="general"/>
- <reg name="cs" bitsize="32" regnum="18" type="int" group="general"/>
- <reg name="ss" bitsize="32" regnum="19" type="int" group="general"/>
- </feature>
- </target>""",
- False,
- )
- return None, False
-
- def other(self, packet):
- if packet == "vCont?":
- return "vCont;c;C;s;S"
- if packet.startswith("vCont;"):
- return self._handle_vCont(packet)
- if packet.startswith("z"):
- return "OK"
- return ""
-
- def _handle_vCont(self, packet):
- self.resume_count += 1
- # Parse step actions from vCont.
- stepping_tids = []
- for action in packet[6:].split(";"):
- if not action:
- continue
- if action.startswith("s:"):
- tid_str = action[2:]
- if "." in tid_str:
- tid_str = tid_str.split(".")[1]
- stepping_tids.append(int(tid_str, 16))
-
- # All stepping threads complete their step.
- for tid in stepping_tids:
- self.stepped_threads.add(tid)
-
- all_done = self.stepped_threads >= set(TIDS)
-
- # Report stop, use the first stepping thread as the reporter.
- report_tid = stepping_tids[0] if stepping_tids else TIDS[0]
- threads_str = ",".join("{:x}".format(t) for t in TIDS)
- if all_done:
- # All threads moved past breakpoint.
- pcs_str = ",".join("{:x}".format(STEPPED_PC) for _ in TIDS)
- else:
- # Stepped threads moved, others still at breakpoint.
- pcs_str = ",".join(
- "{:x}".format(
- STEPPED_PC if t in self.stepped_threads else BP_ADDR
- )
- for t in TIDS
- )
- return "T05thread:{:x};threads:{};thread-pcs:{};".format(
- report_tid, threads_str, pcs_str
- )
-
- self.server.responder = MyResponder()
- self.runCmd("platform select remote-linux")
- target = self.createTarget("a.yaml")
- process = self.connect(target)
-
- self.assertEqual(process.GetNumThreads(), NUM_THREADS)
-
- # Set a breakpoint at BP_ADDR, all threads are already stopped there.
- bkpt = target.BreakpointCreateByAddress(BP_ADDR)
- self.assertTrue(bkpt.IsValid())
-
- # Continue, LLDB should step all threads over the breakpoint.
- process.Continue()
-
- # Collect packets from the log.
- received = self.server.responder.packetLog.get_received()
-
- bp_addr_hex = "{:x}".format(BP_ADDR)
-
- # Count z0 (disable) and Z0 (enable) packets for our breakpoint.
- z0_packets = []
- Z0_packets = []
- vcont_step_packets = []
-
- for pkt in received:
- if pkt.startswith("z0,{},".format(bp_addr_hex)):
- z0_packets.append(pkt)
- elif pkt.startswith("Z0,{},".format(bp_addr_hex)):
- Z0_packets.append(pkt)
- elif pkt.startswith("vCont;"):
- step_count = len(re.findall(r";s:", pkt))
- if step_count > 0:
- vcont_step_packets.append((step_count, pkt))
-
- # Verify: exactly 1 breakpoint disable (z0)
- self.assertEqual(
- len(z0_packets),
- 1,
- "Expected 1 z0 (disable) packet, got {}: {}".format(
- len(z0_packets), z0_packets
- ),
- )
-
- # The initial Z0 is the breakpoint set. After stepping, there should
- # be exactly 1 re-enable Z0 (total Z0 count = 2: set + re-enable).
- # But we set the breakpoint via SB API, so count Z0 packets with
- # our address, initial set + 1 re-enable = 2.
- self.assertEqual(
- len(Z0_packets),
- 2,
- "Expected 2 Z0 packets (1 set + 1 re-enable), got {}: {}".format(
- len(Z0_packets), Z0_packets
- ),
- )
-
- # At least one batched vCont with multiple step actions.
- max_batch = max((count for count, _ in vcont_step_packets), default=0)
- self.assertGreaterEqual(
- max_batch,
- NUM_THREADS,
- "Expected a vCont with {} step actions (batched), "
- "but max was {}. Packets: {}".format(
- NUM_THREADS,
- max_batch,
- [(c, p) for c, p in vcont_step_packets],
- ),
- )
diff --git a/lldb/test/API/functionalities/thread/concurrent_events/TestConcurrentBatchedBreakpointStepOver.py b/lldb/test/API/functionalities/thread/concurrent_events/TestConcurrentBatchedBreakpointStepOver.py
deleted file mode 100644
index 4f480e64a9dbd..0000000000000
--- a/lldb/test/API/functionalities/thread/concurrent_events/TestConcurrentBatchedBreakpointStepOver.py
+++ /dev/null
@@ -1,127 +0,0 @@
-"""
-Test that the batched breakpoint step-over optimization activates when
-multiple threads hit the same breakpoint. Verifies that the optimization
-reduces breakpoint toggle operations compared to stepping one at a time.
-"""
-
-import os
-import re
-
-from lldbsuite.test.decorators import *
-from lldbsuite.test.concurrent_base import ConcurrentEventsBase
-from lldbsuite.test.lldbtest import TestBase
-
-
- at skipIfWindows
-class ConcurrentBatchedBreakpointStepOver(ConcurrentEventsBase):
- @skipIf(triple="^mips")
- @skipIf(archs=["aarch64"])
- def test(self):
- """Test that batched breakpoint step-over reduces breakpoint
- toggle operations when multiple threads hit the same breakpoint."""
- self.build()
-
- num_threads = 10
-
- # Enable logging to capture optimization messages and GDB packets.
- lldb_logfile = self.getBuildArtifact("lldb-log.txt")
- self.runCmd("log enable lldb step break -f {}".format(lldb_logfile))
-
- gdb_logfile = self.getBuildArtifact("gdb-remote-log.txt")
- self.runCmd("log enable gdb-remote packets -f {}".format(gdb_logfile))
-
- # Run with breakpoint threads.
- self.do_thread_actions(num_breakpoint_threads=num_threads)
-
- self.assertTrue(os.path.isfile(lldb_logfile), "lldb log file not found")
- with open(lldb_logfile, "r") as f:
- lldb_log = f.read()
-
- # Verify the optimization activated by looking for "Registered thread"
- # messages, which indicate threads were grouped for batching.
- registered_matches = re.findall(
- r"Registered thread 0x[0-9a-fA-F]+ stepping over "
- r"breakpoint at (0x[0-9a-fA-F]+)",
- lldb_log,
- )
- self.assertGreater(
- len(registered_matches),
- 0,
- "Expected batched breakpoint step-over optimization to be "
- "used (no 'Registered thread' messages found in log).",
- )
- thread_bp_addr = registered_matches[0]
-
- # Verify all threads completed their step-over.
- completed_count = lldb_log.count("Completed step over breakpoint plan.")
- self.assertGreaterEqual(
- completed_count,
- num_threads,
- "Expected at least {} 'Completed step over breakpoint plan.' "
- "messages (one per thread), but got {}.".format(
- num_threads, completed_count
- ),
- )
-
- # Count z0/Z0 packets for the thread breakpoint address.
- # z0 = remove (disable) software breakpoint.
- # Z0 = set (enable) software breakpoint.
- # Strip the "0x" prefix and leading zeros to match the GDB packet
- # format (which uses lowercase hex without "0x" prefix).
- bp_addr_hex = thread_bp_addr[2:].lstrip("0") if thread_bp_addr else ""
-
- z0_count = 0 # disable packets
- Z0_count = 0 # enable packets
- initial_Z0_seen = False
- max_vcont_step_threads = 0 # largest number of s: actions in one vCont
-
- self.assertTrue(os.path.isfile(gdb_logfile), "gdb-remote log file not found")
- with open(gdb_logfile, "r") as f:
- for line in f:
- if "send packet: $" not in line:
- continue
-
- # Match z0,<addr> (disable) or Z0,<addr> (enable).
- m = re.search(r"send packet: \$([Zz])0,([0-9a-fA-F]+),", line)
- if m and m.group(2) == bp_addr_hex:
- if m.group(1) == "Z":
- if not initial_Z0_seen:
- initial_Z0_seen = True
- else:
- Z0_count += 1
- else:
- z0_count += 1
-
- # Count step actions in vCont packets to detect batching.
- # A batched vCont looks like: vCont;s:tid1;s:tid2;...
- vcont_m = re.search(r"send packet: \$vCont((?:;[^#]+)*)", line)
- if vcont_m:
- actions = vcont_m.group(1)
- step_count = len(re.findall(r";s:", actions))
- if step_count > max_vcont_step_threads:
- max_vcont_step_threads = step_count
-
- # With the optimization, fewer breakpoint toggles should occur.
- # Without optimization we'd see num_threads z0 and num_threads Z0.
- # With batching, even partial, we expect fewer toggles.
- self.assertLess(
- z0_count,
- num_threads,
- "Expected fewer than {} breakpoint disables (z0) due to "
- "batching, but got {}.".format(num_threads, z0_count),
- )
- self.assertLess(
- Z0_count,
- num_threads,
- "Expected fewer than {} breakpoint re-enables (Z0) due to "
- "batching, but got {}.".format(num_threads, Z0_count),
- )
-
- # Verify at least one batched vCont packet contained multiple
- # step actions, proving threads were stepped together.
- self.assertGreater(
- max_vcont_step_threads,
- 1,
- "Expected at least one batched vCont packet with multiple "
- "step actions (s:), but the maximum was {}.".format(max_vcont_step_threads),
- )
More information about the lldb-commits
mailing list