Author: satyanarayana reddy janga Date: 2026-02-25T14:46:13-06:00 New Revision: 7be9d66c29a7af78c36a6807d0d71efd6420099c
URL: https://github.com/llvm/llvm-project/commit/7be9d66c29a7af78c36a6807d0d71efd6420099c DIFF: https://github.com/llvm/llvm-project/commit/7be9d66c29a7af78c36a6807d0d71efd6420099c.diff LOG: Revert "[lldb] Batch breakpoint step-over for threads stopped at the … (#183378) …same site (re-land) (#182944)" This reverts commit 94d9f1b3cbb02700d9cd3339c1dbf44c0d13b550. Added: Modified: lldb/include/lldb/Target/ThreadList.h lldb/include/lldb/Target/ThreadPlanStepOverBreakpoint.h lldb/source/Target/ThreadList.cpp lldb/source/Target/ThreadPlanStepOverBreakpoint.cpp Removed: lldb/test/API/functionalities/gdb_remote_client/TestBatchedBreakpointStepOver.py 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 - - -@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), - ) _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
