[llvm-branch-commits] [lldb] [lldb] Implement delayed breakpoints (PR #192971)

Felipe de Azevedo Piovezan via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Tue Apr 28 09:30:24 PDT 2026


https://github.com/felipepiovezan updated https://github.com/llvm/llvm-project/pull/192971

>From 745ea612196a8daf037fa26a137c309fdf19e71f Mon Sep 17 00:00:00 2001
From: Felipe de Azevedo Piovezan <fpiovezan at apple.com>
Date: Thu, 9 Apr 2026 15:07:18 +0100
Subject: [PATCH 1/5] [lldb] Implement delayed breakpoints

This patch changes the Process class so that it delays *physically*
enabling/disabling breakpoints until the process is about to
resume/detach/be destroyed, potentially reducing the packets transmitted
by batching all breakpoints together.

Most classes only need to know whether a breakpoint is "logically"
enabled, as opposed to "physically" enabled (i.e. the remote server has
actually enabled the breakpoint). However, lower level classes like
derived Process classes, or StopInfo may actually need to know whether
the breakpoint was physically enabled. As such, this commit also adds a
"IsPhysicallyEnabled" API.

https://github.com/llvm/llvm-project/pull/192910
---
 lldb/include/lldb/Target/Process.h            |  31 +++++-
 .../Process/MacOSX-Kernel/ProcessKDP.cpp      |   4 +-
 .../Process/Utility/StopInfoMachException.cpp |   8 +-
 .../Process/gdb-remote/ProcessGDBRemote.cpp   |  12 +-
 .../Process/scripted/ScriptedProcess.cpp      |   2 +-
 .../Process/scripted/ScriptedThread.cpp       |   2 +-
 lldb/source/Target/Process.cpp                | 103 ++++++++++++++++--
 lldb/source/Target/TargetProperties.td        |   5 +
 .../Target/ThreadPlanStepOverBreakpoint.cpp   |   6 +-
 9 files changed, 149 insertions(+), 24 deletions(-)

diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index f90c11813ba48..88e9ac4bc802f 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -114,6 +114,7 @@ class ProcessProperties : public Properties {
   Args GetAlwaysRunThreadNames() const;
   FollowForkMode GetFollowForkMode() const;
   bool TrackMemoryCacheChanges() const;
+  bool GetUseDelayedBreakpoints() const;
 
 protected:
   Process *m_process; // Can be nullptr for global ProcessProperties
@@ -2246,6 +2247,9 @@ class Process : public std::enable_shared_from_this<Process>,
   // Process Breakpoints
   size_t GetSoftwareBreakpointTrapOpcode(BreakpointSite *bp_site);
 
+  enum class BreakpointAction { Enable, Disable };
+
+protected:
   virtual Status EnableBreakpointSite(BreakpointSite *bp_site) {
     return Status::FromErrorStringWithFormatv(
         "error: {0} does not support enabling breakpoints", GetPluginName());
@@ -2256,6 +2260,14 @@ class Process : public std::enable_shared_from_this<Process>,
         "error: {0} does not support disabling breakpoints", GetPluginName());
   }
 
+  virtual llvm::Error UpdateBreakpointSites(
+      const std::map<lldb::BreakpointSiteSP, BreakpointAction> &site_to_action);
+
+public:
+  Status ExecuteBreakpointSiteAction(BreakpointSite &site,
+                                     Process::BreakpointAction action,
+                                     bool force_now = false);
+
   // This is implemented completely using the lldb::Process API. Subclasses
   // don't need to implement this function unless the standard flow of read
   // existing opcode, write breakpoint opcode, verify breakpoint opcode doesn't
@@ -2280,7 +2292,8 @@ class Process : public std::enable_shared_from_this<Process>,
   lldb::break_id_t CreateBreakpointSite(const lldb::BreakpointLocationSP &owner,
                                         bool use_hardware);
 
-  Status DisableBreakpointSiteByID(lldb::user_id_t break_id);
+  Status DisableBreakpointSiteByID(lldb::user_id_t break_id,
+                                   bool force_now = false);
 
   Status EnableBreakpointSiteByID(lldb::user_id_t break_id);
 
@@ -2293,6 +2306,8 @@ class Process : public std::enable_shared_from_this<Process>,
                                            lldb::user_id_t constituent_id,
                                            lldb::BreakpointSiteSP &bp_site_sp);
 
+  bool IsBreakpointSitePhysicallyEnabled(BreakpointSite &site);
+
   // Process Watchpoints (optional)
   virtual Status EnableWatchpoint(lldb::WatchpointSP wp_sp, bool notify = true);
 
@@ -3541,6 +3556,20 @@ void PruneThreadPlans();
   /// GetExtendedCrashInformation.
   StructuredData::DictionarySP m_crash_info_dict_sp;
 
+  struct DelayedBreakpointCache {
+    void Enqueue(lldb::BreakpointSiteSP site, BreakpointAction action);
+    void RemoveSite(lldb::BreakpointSiteSP site) {
+      m_site_to_action.erase(site);
+    }
+    void Clear() { m_site_to_action.clear(); }
+
+    std::map<lldb::BreakpointSiteSP, BreakpointAction> m_site_to_action;
+  };
+
+  DelayedBreakpointCache m_delayed_breakpoints;
+
+  llvm::Error FlushDelayedBreakpoints();
+
   size_t RemoveBreakpointOpcodesFromBuffer(lldb::addr_t addr, size_t size,
                                            uint8_t *buf) const;
 
diff --git a/lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.cpp b/lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.cpp
index 6b3354aad09e6..6166096a4e1d3 100644
--- a/lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.cpp
+++ b/lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.cpp
@@ -633,7 +633,7 @@ Status ProcessKDP::EnableBreakpointSite(BreakpointSite *bp_site) {
 
   if (m_comm.LocalBreakpointsAreSupported()) {
     Status error;
-    if (!IsBreakpointSiteEnabled(bp_site)) {
+    if (!IsBreakpointSitePhysicallyEnabled(*bp_site)) {
       if (m_comm.SendRequestBreakpoint(true, bp_site->GetLoadAddress())) {
         SetBreakpointSiteEnabled(*bp_site);
         bp_site->SetType(BreakpointSite::eExternal);
@@ -649,7 +649,7 @@ Status ProcessKDP::EnableBreakpointSite(BreakpointSite *bp_site) {
 Status ProcessKDP::DisableBreakpointSite(BreakpointSite *bp_site) {
   if (m_comm.LocalBreakpointsAreSupported()) {
     Status error;
-    if (IsBreakpointSiteEnabled(bp_site)) {
+    if (IsBreakpointSitePhysicallyEnabled(*bp_site)) {
       BreakpointSite::Type bp_type = bp_site->GetType();
       if (bp_type == BreakpointSite::eExternal) {
         if (m_destroy_in_process && m_comm.IsRunning()) {
diff --git a/lldb/source/Plugins/Process/Utility/StopInfoMachException.cpp b/lldb/source/Plugins/Process/Utility/StopInfoMachException.cpp
index 9951fe1d9e293..5fbc1f62a065f 100644
--- a/lldb/source/Plugins/Process/Utility/StopInfoMachException.cpp
+++ b/lldb/source/Plugins/Process/Utility/StopInfoMachException.cpp
@@ -636,7 +636,7 @@ StopInfoSP StopInfoMachException::CreateStopReasonWithMachException(
   addr_t pc = reg_ctx_sp->GetPC();
   BreakpointSiteSP bp_site_sp =
       process_sp->GetBreakpointSiteList().FindByAddress(pc);
-  if (bp_site_sp && process_sp->IsBreakpointSiteEnabled(bp_site_sp))
+  if (bp_site_sp && process_sp->IsBreakpointSitePhysicallyEnabled(*bp_site_sp))
     thread.SetThreadStoppedAtUnexecutedBP(pc);
 
   switch (exc_type) {
@@ -771,7 +771,8 @@ StopInfoSP StopInfoMachException::CreateStopReasonWithMachException(
       if (!bp_site_sp && reg_ctx_sp) {
         bp_site_sp = process_sp->GetBreakpointSiteList().FindByAddress(pc);
       }
-      if (bp_site_sp && process_sp->IsBreakpointSiteEnabled(bp_site_sp)) {
+      if (bp_site_sp &&
+          process_sp->IsBreakpointSitePhysicallyEnabled(*bp_site_sp)) {
         // We've hit this breakpoint, whether it was intended for this thread
         // or not.  Clear this in the Tread object so we step past it on resume.
         thread.SetThreadHitBreakpointSite();
@@ -865,7 +866,8 @@ bool StopInfoMachException::WasContinueInterrupted(Thread &thread) {
   // We have a hardware breakpoint -- this is the kernel bug.
   auto &bp_site_list = process_sp->GetBreakpointSiteList();
   for (auto &site : bp_site_list.Sites()) {
-    if (site->IsHardware() && process_sp->IsBreakpointSiteEnabled(site)) {
+    if (site->IsHardware() &&
+        process_sp->IsBreakpointSitePhysicallyEnabled(*site)) {
       LLDB_LOGF(log,
                 "Thread stopped with insn-step completed mach exception but "
                 "thread was not stepping; there is a hardware breakpoint set.");
diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
index 145d69d6b6d64..33d3713c948c4 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
@@ -1848,7 +1848,7 @@ ThreadSP ProcessGDBRemote::SetThreadStopInfo(
     addr_t pc = thread_sp->GetRegisterContext()->GetPC();
     BreakpointSiteSP bp_site_sp =
         thread_sp->GetProcess()->GetBreakpointSiteList().FindByAddress(pc);
-    if (bp_site_sp && IsBreakpointSiteEnabled(bp_site_sp))
+    if (bp_site_sp && IsBreakpointSitePhysicallyEnabled(*bp_site_sp))
       thread_sp->SetThreadStoppedAtUnexecutedBP(pc);
 
     if (exc_type != 0) {
@@ -2032,7 +2032,7 @@ ThreadSP ProcessGDBRemote::SetThreadStopInfo(
           // 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 (IsBreakpointSiteEnabled(bp_site_sp))
+            if (IsBreakpointSitePhysicallyEnabled(*bp_site_sp))
               thread_sp->SetThreadHitBreakpointSite();
 
             if (bp_site_sp->ValidForThisThread(*thread_sp)) {
@@ -3429,7 +3429,7 @@ Status ProcessGDBRemote::EnableBreakpointSite(BreakpointSite *bp_site) {
             site_id, (uint64_t)addr);
 
   // Breakpoint already exists and is enabled
-  if (IsBreakpointSiteEnabled(bp_site)) {
+  if (IsBreakpointSitePhysicallyEnabled(*bp_site)) {
     LLDB_LOGF(log,
               "ProcessGDBRemote::EnableBreakpointSite (size_id = %" PRIu64
               ") address = 0x%" PRIx64 " -- SUCCESS (already enabled)",
@@ -3450,7 +3450,7 @@ Status ProcessGDBRemote::DisableBreakpointSite(BreakpointSite *bp_site) {
             ") addr = 0x%8.8" PRIx64,
             site_id, (uint64_t)addr);
 
-  if (!IsBreakpointSiteEnabled(bp_site)) {
+  if (!IsBreakpointSitePhysicallyEnabled(*bp_site)) {
     LLDB_LOGF(log,
               "ProcessGDBRemote::DisableBreakpointSite (site_id = %" PRIu64
               ") addr = 0x%8.8" PRIx64 " -- SUCCESS (already disabled)",
@@ -6112,7 +6112,7 @@ void ProcessGDBRemote::DidForkSwitchSoftwareBreakpoints(
 
   GetBreakpointSiteList().ForEach([this, enable, entry_addr,
                                    log](BreakpointSite *bp_site) {
-    if (IsBreakpointSiteEnabled(bp_site) &&
+    if (IsBreakpointSitePhysicallyEnabled(*bp_site) &&
         (bp_site->GetType() == BreakpointSite::eSoftware ||
          bp_site->GetType() == BreakpointSite::eExternal)) {
       // During expression evaluation, retain the expression-return trap
@@ -6136,7 +6136,7 @@ void ProcessGDBRemote::DidForkSwitchSoftwareBreakpoints(
 void ProcessGDBRemote::DidForkSwitchHardwareTraps(bool enable) {
   if (m_gdb_comm.SupportsGDBStoppointPacket(eBreakpointHardware)) {
     GetBreakpointSiteList().ForEach([this, enable](BreakpointSite *bp_site) {
-      if (IsBreakpointSiteEnabled(bp_site) &&
+      if (IsBreakpointSitePhysicallyEnabled(*bp_site) &&
           bp_site->GetType() == BreakpointSite::eHardware) {
         m_gdb_comm.SendGDBStoppointTypePacket(
             eBreakpointHardware, enable, bp_site->GetLoadAddress(),
diff --git a/lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp b/lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp
index b0718b771295b..c254a6841b707 100644
--- a/lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp
+++ b/lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp
@@ -265,7 +265,7 @@ size_t ScriptedProcess::DoWriteMemory(lldb::addr_t vm_addr, const void *buf,
 Status ScriptedProcess::EnableBreakpointSite(BreakpointSite *bp_site) {
   assert(bp_site != nullptr);
 
-  if (IsBreakpointSiteEnabled(bp_site)) {
+  if (IsBreakpointSitePhysicallyEnabled(*bp_site)) {
     return {};
   }
 
diff --git a/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp b/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp
index aacfcc29e5f97..d11d134295579 100644
--- a/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp
+++ b/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp
@@ -316,7 +316,7 @@ bool ScriptedThread::CalculateStopInfo() {
     ProcessSP proc = GetProcess();
     if (BreakpointSiteSP bp_site_sp =
             proc->GetBreakpointSiteList().FindByAddress(pc))
-      if (proc->IsBreakpointSiteEnabled(bp_site_sp))
+      if (proc->IsBreakpointSitePhysicallyEnabled(*bp_site_sp))
         SetThreadStoppedAtUnexecutedBP(pc);
   }
 
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index f9b6c4a79dd09..1a2323f5de136 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -79,6 +79,17 @@ using namespace lldb;
 using namespace lldb_private;
 using namespace std::chrono;
 
+void Process::DelayedBreakpointCache::Enqueue(lldb::BreakpointSiteSP site,
+                                              BreakpointAction action) {
+  auto [previous, inserted] = m_site_to_action.insert({site, action});
+  // New site or already enqueued for the same action
+  if (inserted || previous->second == action)
+    return;
+  // Previously enqueued for the opposite action, don't update the site.
+  m_site_to_action.erase(previous);
+  assert(site->m_enabled == (action == BreakpointAction::Enable));
+}
+
 class ProcessOptionValueProperties
     : public Cloneable<ProcessOptionValueProperties, OptionValueProperties> {
 public:
@@ -322,6 +333,12 @@ bool ProcessProperties::GetStopOnExec() const {
       idx, g_process_properties[idx].default_uint_value != 0);
 }
 
+bool ProcessProperties::GetUseDelayedBreakpoints() const {
+  const uint32_t idx = ePropertyUseDelayedBreakpoints;
+  return GetPropertyAtIndexAs<bool>(
+      idx, g_process_properties[idx].default_uint_value != 0);
+}
+
 std::chrono::seconds ProcessProperties::GetUtilityExpressionTimeout() const {
   const uint32_t idx = ePropertyUtilityExpressionTimeout;
   uint64_t value = GetPropertyAtIndexAs<uint64_t>(
@@ -1546,12 +1563,12 @@ Process::GetBreakpointSiteList() const {
 
 void Process::DisableAllBreakpointSites() {
   m_breakpoint_site_list.ForEach([this](BreakpointSite *bp_site) -> void {
-    DisableBreakpointSite(bp_site);
+    ExecuteBreakpointSiteAction(*bp_site, BreakpointAction::Disable);
   });
 }
 
 Status Process::ClearBreakpointSiteByID(lldb::user_id_t break_id) {
-  Status error(DisableBreakpointSiteByID(break_id));
+  Status error(DisableBreakpointSiteByID(break_id, /*force_now=*/true));
 
   if (error.Success())
     m_breakpoint_site_list.Remove(break_id);
@@ -1559,12 +1576,14 @@ Status Process::ClearBreakpointSiteByID(lldb::user_id_t break_id) {
   return error;
 }
 
-Status Process::DisableBreakpointSiteByID(lldb::user_id_t break_id) {
+Status Process::DisableBreakpointSiteByID(lldb::user_id_t break_id,
+                                          bool force_now) {
   Status error;
   BreakpointSiteSP bp_site_sp = m_breakpoint_site_list.FindByID(break_id);
   if (bp_site_sp) {
     if (IsBreakpointSiteEnabled(bp_site_sp))
-      error = DisableBreakpointSite(bp_site_sp.get());
+      error = ExecuteBreakpointSiteAction(*bp_site_sp,
+                                          BreakpointAction::Disable, force_now);
   } else {
     error = Status::FromErrorStringWithFormat(
         "invalid breakpoint site ID: %" PRIu64, break_id);
@@ -1573,12 +1592,34 @@ Status Process::DisableBreakpointSiteByID(lldb::user_id_t break_id) {
   return error;
 }
 
+Status Process::ExecuteBreakpointSiteAction(BreakpointSite &site,
+                                            BreakpointAction action,
+                                            bool force_now) {
+  if (!force_now && GetUseDelayedBreakpoints()) {
+    m_delayed_breakpoints.Enqueue(site.shared_from_this(), action);
+    return Status();
+  }
+
+  auto site_sp = site.shared_from_this();
+  m_delayed_breakpoints.RemoveSite(site_sp);
+
+  switch (action) {
+  case BreakpointAction::Enable:
+    return EnableBreakpointSite(site_sp.get());
+  case BreakpointAction::Disable:
+    return DisableBreakpointSite(site_sp.get());
+  }
+
+  llvm_unreachable("Unhandled BreakpointAction");
+}
+
 Status Process::EnableBreakpointSiteByID(lldb::user_id_t break_id) {
   Status error;
   BreakpointSiteSP bp_site_sp = m_breakpoint_site_list.FindByID(break_id);
   if (bp_site_sp) {
     if (!IsBreakpointSiteEnabled(bp_site_sp))
-      error = EnableBreakpointSite(bp_site_sp.get());
+      error =
+          ExecuteBreakpointSiteAction(*bp_site_sp, BreakpointAction::Enable);
   } else {
     error = Status::FromErrorStringWithFormat(
         "invalid breakpoint site ID: %" PRIu64, break_id);
@@ -1588,12 +1629,21 @@ Status Process::EnableBreakpointSiteByID(lldb::user_id_t break_id) {
 
 bool Process::IsBreakpointSiteEnabled(const lldb::BreakpointSiteSP &site) {
   assert(site);
-  return site->m_enabled;
+  auto it = m_delayed_breakpoints.m_site_to_action.find(site);
+  // If no actions are delayed, use the current state of the site.
+  if (it == m_delayed_breakpoints.m_site_to_action.end())
+    return site->m_enabled;
+
+  return it->second == BreakpointAction::Enable;
 }
 
 bool Process::IsBreakpointSiteEnabled(BreakpointSite *site) {
   assert(site);
-  return site->m_enabled;
+  return IsBreakpointSiteEnabled(site->shared_from_this());
+}
+
+bool Process::IsBreakpointSitePhysicallyEnabled(BreakpointSite &site) {
+  return site.m_enabled;
 }
 
 static bool ShouldShowError(Process &process) {
@@ -1654,6 +1704,31 @@ static addr_t ComputeConstituentLoadAddress(BreakpointLocation &constituent,
   return resolved_address.GetOpcodeLoadAddress(&target);
 }
 
+llvm::Error Process::FlushDelayedBreakpoints() {
+  // Clear the cache in m_delayed_breakpoints so it can't affect the actual
+  // enabling of breakpoints. For
+  // example, if `EnableSoftwareBreakpoint` is called outside of
+  // FlushDelayedBreakpoints, it needs to check the delayed breakpoints and
+  // possibly early return. However, when called from FlushDelayedBreakpoints,
+  // the queue better be empty so that no early returns take place.
+  auto site_to_action = std::move(m_delayed_breakpoints.m_site_to_action);
+
+  auto error = UpdateBreakpointSites(site_to_action);
+  return error;
+}
+
+llvm::Error Process::UpdateBreakpointSites(
+    const std::map<lldb::BreakpointSiteSP, BreakpointAction> &site_to_action) {
+  llvm::Error error = llvm::Error::success();
+  for (auto [site, action] : site_to_action) {
+    Status new_error = action == BreakpointAction::Enable
+                           ? EnableBreakpointSite(site.get())
+                           : DisableBreakpointSite(site.get());
+    error = llvm::joinErrors(std::move(error), new_error.takeError());
+  }
+  return error;
+}
+
 lldb::break_id_t
 Process::CreateBreakpointSite(const BreakpointLocationSP &constituent,
                               bool use_hardware) {
@@ -1698,7 +1773,8 @@ void Process::RemoveConstituentFromBreakpointSite(
   if (num_constituents == 0) {
     // Don't try to disable the site if we don't have a live process anymore.
     if (IsAlive())
-      DisableBreakpointSite(bp_site_sp.get());
+      ExecuteBreakpointSiteAction(*bp_site_sp, BreakpointAction::Disable,
+                                  /*force_now=*/true);
     m_breakpoint_site_list.RemoveByAddress(bp_site_sp->GetLoadAddress());
   }
 }
@@ -3441,6 +3517,9 @@ Status Process::PrivateResume() {
             "Process::PrivateResume PreResumeActions failed, not resuming.");
       } else {
         m_mod_id.BumpResumeID();
+        if (auto E = FlushDelayedBreakpoints())
+          LLDB_LOG_ERROR(log, std::move(E),
+                         "Failed to update some delayed breakpoints: {0}");
         error = DoResume(direction);
         if (error.Success()) {
           DidResume();
@@ -3647,6 +3726,10 @@ Status Process::Detach(bool keep_stopped) {
 
     m_thread_list.DiscardThreadPlans();
     DisableAllBreakpointSites();
+    if (auto error = FlushDelayedBreakpoints())
+      LLDB_LOG_ERROR(
+          GetLog(LLDBLog::Process), std::move(error),
+          "Failed to update some delayed breakpoints during detach: {0}");
 
     error = DoDetach(keep_stopped);
     if (error.Success()) {
@@ -3716,6 +3799,10 @@ Status Process::DestroyImpl(bool force_kill) {
       // doing this now.
       m_thread_list.DiscardThreadPlans();
       DisableAllBreakpointSites();
+      if (auto error = FlushDelayedBreakpoints())
+        LLDB_LOG_ERROR(
+            GetLog(LLDBLog::Process), std::move(error),
+            "Failed to update some delayed breakpoints during destroy: {0}");
     }
 
     error = DoDestroy();
diff --git a/lldb/source/Target/TargetProperties.td b/lldb/source/Target/TargetProperties.td
index 223a12e059258..0a46125594ab6 100644
--- a/lldb/source/Target/TargetProperties.td
+++ b/lldb/source/Target/TargetProperties.td
@@ -319,6 +319,11 @@ let Definition = "process", Path = "target.process" in {
         Desc<"A list of thread names. Threads with any of these names will "
              "always be resumed when the process resumes, even when other "
              "threads are suspended during single-stepping operations.">;
+  def UseDelayedBreakpoints
+      : Property<"use-delayed-breakpoints", "Boolean">,
+        DefaultTrue,
+        Desc<"Specify whether to delay setting breakpoints until the process "
+             "is about to resume.">;
 }
 
 let Definition = "platform", Path = "platform" in {
diff --git a/lldb/source/Target/ThreadPlanStepOverBreakpoint.cpp b/lldb/source/Target/ThreadPlanStepOverBreakpoint.cpp
index 597577c208f8b..b215b4be76602 100644
--- a/lldb/source/Target/ThreadPlanStepOverBreakpoint.cpp
+++ b/lldb/source/Target/ThreadPlanStepOverBreakpoint.cpp
@@ -121,7 +121,8 @@ bool ThreadPlanStepOverBreakpoint::DoWillResume(StateType resume_state,
     BreakpointSiteSP bp_site_sp(
         m_process.GetBreakpointSiteList().FindByAddress(m_breakpoint_addr));
     if (bp_site_sp && m_process.IsBreakpointSiteEnabled(bp_site_sp)) {
-      m_process.DisableBreakpointSite(bp_site_sp.get());
+      m_process.ExecuteBreakpointSiteAction(*bp_site_sp,
+                                            Process::BreakpointAction::Disable);
       m_reenabled_breakpoint_site = false;
     }
   }
@@ -167,7 +168,8 @@ void ThreadPlanStepOverBreakpoint::ReenableBreakpointSite() {
       if (BreakpointSiteSP bp_site_sp =
               m_process.GetBreakpointSiteList().FindByAddress(
                   m_breakpoint_addr))
-        m_process.EnableBreakpointSite(bp_site_sp.get());
+        m_process.ExecuteBreakpointSiteAction(
+            *bp_site_sp, Process::BreakpointAction::Enable);
     }
   }
 }

>From f534e4802d7190dcf4cde9e979246a9e19dcf48c Mon Sep 17 00:00:00 2001
From: Felipe de Azevedo Piovezan <fpiovezan at apple.com>
Date: Mon, 27 Apr 2026 18:43:10 +0100
Subject: [PATCH 2/5] fixup! review comments

---
 lldb/source/Target/Process.cpp         | 1 +
 lldb/source/Target/TargetProperties.td | 4 ++--
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index 1a2323f5de136..77ace067021d4 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -1712,6 +1712,7 @@ llvm::Error Process::FlushDelayedBreakpoints() {
   // possibly early return. However, when called from FlushDelayedBreakpoints,
   // the queue better be empty so that no early returns take place.
   auto site_to_action = std::move(m_delayed_breakpoints.m_site_to_action);
+  m_delayed_breakpoints.m_site_to_action.clear();
 
   auto error = UpdateBreakpointSites(site_to_action);
   return error;
diff --git a/lldb/source/Target/TargetProperties.td b/lldb/source/Target/TargetProperties.td
index 0a46125594ab6..23a1bb0b3ce4a 100644
--- a/lldb/source/Target/TargetProperties.td
+++ b/lldb/source/Target/TargetProperties.td
@@ -322,8 +322,8 @@ let Definition = "process", Path = "target.process" in {
   def UseDelayedBreakpoints
       : Property<"use-delayed-breakpoints", "Boolean">,
         DefaultTrue,
-        Desc<"Specify whether to delay setting breakpoints until the process "
-             "is about to resume.">;
+        Desc<"Specify whether updating breakpoints may be delayed until the "
+             "process is about to resume.">;
 }
 
 let Definition = "platform", Path = "platform" in {

>From cbc8a4ab81a5f7c126c281b4d28035ad4d7e99eb Mon Sep 17 00:00:00 2001
From: Felipe de Azevedo Piovezan <fpiovezan at apple.com>
Date: Mon, 27 Apr 2026 18:54:51 +0100
Subject: [PATCH 3/5] fixup! make iteration order independent of pointers

---
 lldb/include/lldb/Target/Process.h | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index 88e9ac4bc802f..c4234cdce6e4e 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -3563,7 +3563,16 @@ void PruneThreadPlans();
     }
     void Clear() { m_site_to_action.clear(); }
 
-    std::map<lldb::BreakpointSiteSP, BreakpointAction> m_site_to_action;
+    /// Compare BreakpointSiteSPs by ID, so that iteration order is independent
+    /// of pointer addresses.
+    struct SiteIDCmp {
+      bool operator()(const lldb::BreakpointSiteSP lhs,
+                      const lldb::BreakpointSiteSP &rhs) const {
+        return lhs->GetID() < rhs->GetID();
+      }
+    };
+    std::map<lldb::BreakpointSiteSP, BreakpointAction, SiteIDCmp>
+        m_site_to_action;
   };
 
   DelayedBreakpointCache m_delayed_breakpoints;

>From 157fcff5e4bcc10ada3aa15a103a905ce49e0d66 Mon Sep 17 00:00:00 2001
From: Felipe de Azevedo Piovezan <fpiovezan at apple.com>
Date: Tue, 28 Apr 2026 08:38:00 +0100
Subject: [PATCH 4/5] fixup! fix order of class declaration

---
 lldb/include/lldb/Target/Process.h | 26 ++++++++++++++------------
 lldb/source/Target/Process.cpp     |  5 ++---
 2 files changed, 16 insertions(+), 15 deletions(-)

diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index c4234cdce6e4e..48c0c2cb9315b 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -2260,8 +2260,19 @@ class Process : public std::enable_shared_from_this<Process>,
         "error: {0} does not support disabling breakpoints", GetPluginName());
   }
 
-  virtual llvm::Error UpdateBreakpointSites(
-      const std::map<lldb::BreakpointSiteSP, BreakpointAction> &site_to_action);
+  /// Compare BreakpointSiteSPs by ID, so that iteration order is independent
+  /// of pointer addresses.
+  struct SiteIDCmp {
+    bool operator()(const lldb::BreakpointSiteSP lhs,
+                    const lldb::BreakpointSiteSP &rhs) const {
+      return lhs->GetID() < rhs->GetID();
+    }
+  };
+  using BreakpointSiteToActionMap =
+      std::map<lldb::BreakpointSiteSP, BreakpointAction, SiteIDCmp>;
+
+  virtual llvm::Error
+  UpdateBreakpointSites(const BreakpointSiteToActionMap &site_to_action);
 
 public:
   Status ExecuteBreakpointSiteAction(BreakpointSite &site,
@@ -3563,16 +3574,7 @@ void PruneThreadPlans();
     }
     void Clear() { m_site_to_action.clear(); }
 
-    /// Compare BreakpointSiteSPs by ID, so that iteration order is independent
-    /// of pointer addresses.
-    struct SiteIDCmp {
-      bool operator()(const lldb::BreakpointSiteSP lhs,
-                      const lldb::BreakpointSiteSP &rhs) const {
-        return lhs->GetID() < rhs->GetID();
-      }
-    };
-    std::map<lldb::BreakpointSiteSP, BreakpointAction, SiteIDCmp>
-        m_site_to_action;
+    BreakpointSiteToActionMap m_site_to_action;
   };
 
   DelayedBreakpointCache m_delayed_breakpoints;
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index 77ace067021d4..030fa73ce38d0 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -1714,12 +1714,11 @@ llvm::Error Process::FlushDelayedBreakpoints() {
   auto site_to_action = std::move(m_delayed_breakpoints.m_site_to_action);
   m_delayed_breakpoints.m_site_to_action.clear();
 
-  auto error = UpdateBreakpointSites(site_to_action);
-  return error;
+  return UpdateBreakpointSites(site_to_action);
 }
 
 llvm::Error Process::UpdateBreakpointSites(
-    const std::map<lldb::BreakpointSiteSP, BreakpointAction> &site_to_action) {
+    const BreakpointSiteToActionMap &site_to_action) {
   llvm::Error error = llvm::Error::success();
   for (auto [site, action] : site_to_action) {
     Status new_error = action == BreakpointAction::Enable

>From 24adcf2393e008e0e4bc8dacaed2222574495b84 Mon Sep 17 00:00:00 2001
From: Felipe de Azevedo Piovezan <fpiovezan at apple.com>
Date: Tue, 28 Apr 2026 17:29:59 +0100
Subject: [PATCH 5/5] fixup! don't enqueue actions that won't change the site
 status

---
 lldb/source/Target/Process.cpp | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index 030fa73ce38d0..fb933b8c1a498 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -1595,12 +1595,18 @@ Status Process::DisableBreakpointSiteByID(lldb::user_id_t break_id,
 Status Process::ExecuteBreakpointSiteAction(BreakpointSite &site,
                                             BreakpointAction action,
                                             bool force_now) {
+
+  auto site_sp = site.shared_from_this();
+
+  // Ignore requests that won't change the Site status.
+  if (IsBreakpointSiteEnabled(site_sp) == (action == BreakpointAction::Enable))
+    return Status();
+
   if (!force_now && GetUseDelayedBreakpoints()) {
     m_delayed_breakpoints.Enqueue(site.shared_from_this(), action);
     return Status();
   }
 
-  auto site_sp = site.shared_from_this();
   m_delayed_breakpoints.RemoveSite(site_sp);
 
   switch (action) {



More information about the llvm-branch-commits mailing list