[Lldb-commits] [lldb] [lldb] Implement delayed breakpoints (PR #192971)
Felipe de Azevedo Piovezan via lldb-commits
lldb-commits at lists.llvm.org
Thu Apr 30 08:16:37 PDT 2026
https://github.com/felipepiovezan updated https://github.com/llvm/llvm-project/pull/192971
>From 7815c9495b344ed415973e756fa17186d7f1e41e 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 01/13] [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 | 102 ++++++++++++++++--
lldb/source/Target/TargetProperties.td | 5 +
.../Target/ThreadPlanStepOverBreakpoint.cpp | 6 +-
9 files changed, 150 insertions(+), 22 deletions(-)
diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index fb06850d34a19..8bb8929be4a74 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,12 +2292,15 @@ 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);
bool IsBreakpointSiteEnabled(const BreakpointSite &site);
+ bool IsBreakpointSitePhysicallyEnabled(const BreakpointSite &site);
+
// BreakpointLocations use RemoveConstituentFromBreakpointSite to remove
// themselves from the constituent's list of this breakpoint sites.
void RemoveConstituentFromBreakpointSite(lldb::user_id_t site_id,
@@ -3540,6 +3555,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 b4598a422e579..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 deb95d9a03ca3..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 e8f32c564ee03..adf108919b36e 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 ac0f451a3d5fa..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 35774cfa01742..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 58c41c2584676..0c187040f3cf7 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);
@@ -1587,6 +1628,18 @@ Status Process::EnableBreakpointSiteByID(lldb::user_id_t break_id) {
}
bool Process::IsBreakpointSiteEnabled(const BreakpointSite &site) {
+ // `site` won't be mutated, but the cache stores mutable pointers.
+ auto it = m_delayed_breakpoints.m_site_to_action.find(
+ const_cast<BreakpointSite &>(site).shared_from_this());
+
+ // 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::IsBreakpointSitePhysicallyEnabled(const BreakpointSite &site) {
return site.m_enabled;
}
@@ -1648,6 +1701,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) {
@@ -1692,7 +1770,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());
}
}
@@ -3435,6 +3514,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();
@@ -3641,6 +3723,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()) {
@@ -3710,6 +3796,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 572454f9b897a..a08f991ffbfcf 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 654aa2aaba607234067b535ef97a1538fe5a0348 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 02/13] 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 0c187040f3cf7..eedf47a8e5fd7 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -1709,6 +1709,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 5c961814fb23b2311f79d145693bf5366ff23d65 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 03/13] 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 8bb8929be4a74..ed3fcfac2a797 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -3562,7 +3562,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 50356841462aa1466a4cf0b25eef5cd25157ed42 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 04/13] 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 ed3fcfac2a797..e6fc06a1132a5 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,
@@ -3562,16 +3573,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 eedf47a8e5fd7..94ce4e6584314 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -1711,12 +1711,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 facc111acc9abfcbc8d405dbb8599f0f8adb62dd 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 05/13] 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 94ce4e6584314..eece902353a79 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) {
>From 9d92c52b5b5e6421dee5663182385a2c9ce1e94e Mon Sep 17 00:00:00 2001
From: Felipe de Azevedo Piovezan <fpiovezan at apple.com>
Date: Tue, 28 Apr 2026 17:46:30 +0100
Subject: [PATCH 06/13] fixup! Remove delayed breakpoints
---
lldb/include/lldb/Target/Process.h | 6 ++----
lldb/source/Target/Process.cpp | 18 +++++++-----------
2 files changed, 9 insertions(+), 15 deletions(-)
diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index e6fc06a1132a5..af8ba7c396faa 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -2276,8 +2276,7 @@ class Process : public std::enable_shared_from_this<Process>,
public:
Status ExecuteBreakpointSiteAction(BreakpointSite &site,
- Process::BreakpointAction action,
- bool force_now = false);
+ Process::BreakpointAction action);
// This is implemented completely using the lldb::Process API. Subclasses
// don't need to implement this function unless the standard flow of read
@@ -2303,8 +2302,7 @@ 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,
- bool force_now = false);
+ Status DisableBreakpointSiteByID(lldb::user_id_t break_id);
Status EnableBreakpointSiteByID(lldb::user_id_t break_id);
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index eece902353a79..76759472d33cb 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -1568,7 +1568,7 @@ void Process::DisableAllBreakpointSites() {
}
Status Process::ClearBreakpointSiteByID(lldb::user_id_t break_id) {
- Status error(DisableBreakpointSiteByID(break_id, /*force_now=*/true));
+ Status error(DisableBreakpointSiteByID(break_id));
if (error.Success())
m_breakpoint_site_list.Remove(break_id);
@@ -1576,14 +1576,13 @@ Status Process::ClearBreakpointSiteByID(lldb::user_id_t break_id) {
return error;
}
-Status Process::DisableBreakpointSiteByID(lldb::user_id_t break_id,
- bool force_now) {
+Status Process::DisableBreakpointSiteByID(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 = ExecuteBreakpointSiteAction(*bp_site_sp,
- BreakpointAction::Disable, force_now);
+ error =
+ ExecuteBreakpointSiteAction(*bp_site_sp, BreakpointAction::Disable);
} else {
error = Status::FromErrorStringWithFormat(
"invalid breakpoint site ID: %" PRIu64, break_id);
@@ -1593,16 +1592,14 @@ Status Process::DisableBreakpointSiteByID(lldb::user_id_t break_id,
}
Status Process::ExecuteBreakpointSiteAction(BreakpointSite &site,
- BreakpointAction action,
- bool force_now) {
-
+ BreakpointAction action) {
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()) {
+ if (!GetUseDelayedBreakpoints()) {
m_delayed_breakpoints.Enqueue(site.shared_from_this(), action);
return Status();
}
@@ -1776,8 +1773,7 @@ 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())
- ExecuteBreakpointSiteAction(*bp_site_sp, BreakpointAction::Disable,
- /*force_now=*/true);
+ ExecuteBreakpointSiteAction(*bp_site_sp, BreakpointAction::Disable);
m_breakpoint_site_list.RemoveByAddress(bp_site_sp->GetLoadAddress());
}
}
>From 52523cc4b8c7eec7a1815f270fe172f739654373 Mon Sep 17 00:00:00 2001
From: Felipe de Azevedo Piovezan <fpiovezan at apple.com>
Date: Tue, 28 Apr 2026 19:56:11 +0100
Subject: [PATCH 07/13] fixup! fix typos
---
lldb/include/lldb/Target/Process.h | 2 +-
lldb/source/Target/Process.cpp | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index af8ba7c396faa..b5068194d962f 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -2263,7 +2263,7 @@ class Process : public std::enable_shared_from_this<Process>,
/// Compare BreakpointSiteSPs by ID, so that iteration order is independent
/// of pointer addresses.
struct SiteIDCmp {
- bool operator()(const lldb::BreakpointSiteSP lhs,
+ bool operator()(const lldb::BreakpointSiteSP &lhs,
const lldb::BreakpointSiteSP &rhs) const {
return lhs->GetID() < rhs->GetID();
}
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index 76759472d33cb..9f31f6b42873c 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -82,7 +82,7 @@ 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
+ // 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.
>From 4cb7067effe233aa9c0df1b2272a6f1bc95bb883 Mon Sep 17 00:00:00 2001
From: Felipe de Azevedo Piovezan <fpiovezan at apple.com>
Date: Tue, 28 Apr 2026 19:57:31 +0100
Subject: [PATCH 08/13] fixup! use Error instead of Status
---
lldb/include/lldb/Target/Process.h | 4 +--
lldb/source/Target/Process.cpp | 26 ++++++++++---------
.../Target/ThreadPlanStepOverBreakpoint.cpp | 8 +++---
3 files changed, 20 insertions(+), 18 deletions(-)
diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index b5068194d962f..f9887e985a003 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -2275,8 +2275,8 @@ class Process : public std::enable_shared_from_this<Process>,
UpdateBreakpointSites(const BreakpointSiteToActionMap &site_to_action);
public:
- Status ExecuteBreakpointSiteAction(BreakpointSite &site,
- Process::BreakpointAction action);
+ llvm::Error ExecuteBreakpointSiteAction(BreakpointSite &site,
+ Process::BreakpointAction action);
// This is implemented completely using the lldb::Process API. Subclasses
// don't need to implement this function unless the standard flow of read
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index 9f31f6b42873c..b44e1fb08aa9c 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -1563,7 +1563,8 @@ Process::GetBreakpointSiteList() const {
void Process::DisableAllBreakpointSites() {
m_breakpoint_site_list.ForEach([this](BreakpointSite *bp_site) -> void {
- ExecuteBreakpointSiteAction(*bp_site, BreakpointAction::Disable);
+ llvm::consumeError(
+ ExecuteBreakpointSiteAction(*bp_site, BreakpointAction::Disable));
});
}
@@ -1581,8 +1582,8 @@ Status Process::DisableBreakpointSiteByID(lldb::user_id_t break_id) {
BreakpointSiteSP bp_site_sp = m_breakpoint_site_list.FindByID(break_id);
if (bp_site_sp) {
if (IsBreakpointSiteEnabled(*bp_site_sp))
- error =
- ExecuteBreakpointSiteAction(*bp_site_sp, BreakpointAction::Disable);
+ error = Status::FromError(
+ ExecuteBreakpointSiteAction(*bp_site_sp, BreakpointAction::Disable));
} else {
error = Status::FromErrorStringWithFormat(
"invalid breakpoint site ID: %" PRIu64, break_id);
@@ -1591,26 +1592,26 @@ Status Process::DisableBreakpointSiteByID(lldb::user_id_t break_id) {
return error;
}
-Status Process::ExecuteBreakpointSiteAction(BreakpointSite &site,
- BreakpointAction action) {
+llvm::Error Process::ExecuteBreakpointSiteAction(BreakpointSite &site,
+ BreakpointAction action) {
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();
+ return llvm::Error::success();
if (!GetUseDelayedBreakpoints()) {
m_delayed_breakpoints.Enqueue(site.shared_from_this(), action);
- return Status();
+ return llvm::Error::success();
}
m_delayed_breakpoints.RemoveSite(site_sp);
switch (action) {
case BreakpointAction::Enable:
- return EnableBreakpointSite(site_sp.get());
+ return EnableBreakpointSite(site_sp.get()).takeError();
case BreakpointAction::Disable:
- return DisableBreakpointSite(site_sp.get());
+ return DisableBreakpointSite(site_sp.get()).takeError();
}
llvm_unreachable("Unhandled BreakpointAction");
@@ -1621,8 +1622,8 @@ Status Process::EnableBreakpointSiteByID(lldb::user_id_t break_id) {
BreakpointSiteSP bp_site_sp = m_breakpoint_site_list.FindByID(break_id);
if (bp_site_sp) {
if (!IsBreakpointSiteEnabled(*bp_site_sp))
- error =
- ExecuteBreakpointSiteAction(*bp_site_sp, BreakpointAction::Enable);
+ error = Status::FromError(
+ ExecuteBreakpointSiteAction(*bp_site_sp, BreakpointAction::Enable));
} else {
error = Status::FromErrorStringWithFormat(
"invalid breakpoint site ID: %" PRIu64, break_id);
@@ -1773,7 +1774,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())
- ExecuteBreakpointSiteAction(*bp_site_sp, BreakpointAction::Disable);
+ llvm::consumeError(
+ ExecuteBreakpointSiteAction(*bp_site_sp, BreakpointAction::Disable));
m_breakpoint_site_list.RemoveByAddress(bp_site_sp->GetLoadAddress());
}
}
diff --git a/lldb/source/Target/ThreadPlanStepOverBreakpoint.cpp b/lldb/source/Target/ThreadPlanStepOverBreakpoint.cpp
index a08f991ffbfcf..878aba2634f49 100644
--- a/lldb/source/Target/ThreadPlanStepOverBreakpoint.cpp
+++ b/lldb/source/Target/ThreadPlanStepOverBreakpoint.cpp
@@ -121,8 +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.ExecuteBreakpointSiteAction(*bp_site_sp,
- Process::BreakpointAction::Disable);
+ llvm::consumeError(m_process.ExecuteBreakpointSiteAction(
+ *bp_site_sp, Process::BreakpointAction::Disable));
m_reenabled_breakpoint_site = false;
}
}
@@ -168,8 +168,8 @@ void ThreadPlanStepOverBreakpoint::ReenableBreakpointSite() {
if (BreakpointSiteSP bp_site_sp =
m_process.GetBreakpointSiteList().FindByAddress(
m_breakpoint_addr))
- m_process.ExecuteBreakpointSiteAction(
- *bp_site_sp, Process::BreakpointAction::Enable);
+ llvm::consumeError(m_process.ExecuteBreakpointSiteAction(
+ *bp_site_sp, Process::BreakpointAction::Enable));
}
}
}
>From 96027fd2686adbf52f8fade71f16b7ffe502d6e6 Mon Sep 17 00:00:00 2001
From: Felipe de Azevedo Piovezan <fpiovezan at apple.com>
Date: Tue, 28 Apr 2026 20:08:31 +0100
Subject: [PATCH 09/13] fixup! cosmetic changes
---
lldb/source/Target/Process.cpp | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index b44e1fb08aa9c..4d61527a79ffd 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -81,7 +81,8 @@ using namespace std::chrono;
void Process::DelayedBreakpointCache::Enqueue(lldb::BreakpointSiteSP site,
BreakpointAction action) {
- auto [previous, inserted] = m_site_to_action.insert({site, action});
+ auto [previous, inserted] =
+ m_site_to_action.insert({std::move(site), action});
// New site or already enqueued for the same action.
if (inserted || previous->second == action)
return;
@@ -1707,11 +1708,11 @@ static addr_t ComputeConstituentLoadAddress(BreakpointLocation &constituent,
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.
+ // 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);
m_delayed_breakpoints.m_site_to_action.clear();
>From 47a9681ad9cb8e573b682367245ed9110f1a627b Mon Sep 17 00:00:00 2001
From: Felipe de Azevedo Piovezan <fpiovezan at apple.com>
Date: Wed, 29 Apr 2026 13:16:53 +0100
Subject: [PATCH 10/13] fixup! protect bp cache with a lock
---
lldb/include/lldb/Target/Process.h | 1 +
lldb/source/Target/Process.cpp | 7 +++++++
2 files changed, 8 insertions(+)
diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index f9887e985a003..045fa9ee2a05a 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -3575,6 +3575,7 @@ void PruneThreadPlans();
};
DelayedBreakpointCache m_delayed_breakpoints;
+ std::recursive_mutex m_delayed_breakpoints_mutex;
llvm::Error FlushDelayedBreakpoints();
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index 4d61527a79ffd..85f587166ce44 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -1596,6 +1596,7 @@ Status Process::DisableBreakpointSiteByID(lldb::user_id_t break_id) {
llvm::Error Process::ExecuteBreakpointSiteAction(BreakpointSite &site,
BreakpointAction action) {
auto site_sp = site.shared_from_this();
+ std::unique_lock<std::recursive_mutex> guard(m_delayed_breakpoints_mutex);
// Ignore requests that won't change the Site status.
if (IsBreakpointSiteEnabled(*site_sp) == (action == BreakpointAction::Enable))
@@ -1607,6 +1608,7 @@ llvm::Error Process::ExecuteBreakpointSiteAction(BreakpointSite &site,
}
m_delayed_breakpoints.RemoveSite(site_sp);
+ guard.unlock();
switch (action) {
case BreakpointAction::Enable:
@@ -1633,6 +1635,8 @@ Status Process::EnableBreakpointSiteByID(lldb::user_id_t break_id) {
}
bool Process::IsBreakpointSiteEnabled(const BreakpointSite &site) {
+ std::lock_guard<std::recursive_mutex> guard(m_delayed_breakpoints_mutex);
+
// `site` won't be mutated, but the cache stores mutable pointers.
auto it = m_delayed_breakpoints.m_site_to_action.find(
const_cast<BreakpointSite &>(site).shared_from_this());
@@ -1707,6 +1711,8 @@ static addr_t ComputeConstituentLoadAddress(BreakpointLocation &constituent,
}
llvm::Error Process::FlushDelayedBreakpoints() {
+ std::unique_lock<std::recursive_mutex> guard(m_delayed_breakpoints_mutex);
+
// 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
@@ -1716,6 +1722,7 @@ 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();
+ guard.unlock();
return UpdateBreakpointSites(site_to_action);
}
>From 54d505d8c3455f63a93d4916d86393e2e9da2165 Mon Sep 17 00:00:00 2001
From: Felipe de Azevedo Piovezan <fpiovezan at apple.com>
Date: Thu, 30 Apr 2026 08:09:48 +0100
Subject: [PATCH 11/13] fixup! review comments
---
lldb/source/Target/Process.cpp | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index 85f587166ce44..bac48bec510c9 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -81,8 +81,7 @@ using namespace std::chrono;
void Process::DelayedBreakpointCache::Enqueue(lldb::BreakpointSiteSP site,
BreakpointAction action) {
- auto [previous, inserted] =
- m_site_to_action.insert({std::move(site), 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;
@@ -1602,8 +1601,8 @@ llvm::Error Process::ExecuteBreakpointSiteAction(BreakpointSite &site,
if (IsBreakpointSiteEnabled(*site_sp) == (action == BreakpointAction::Enable))
return llvm::Error::success();
- if (!GetUseDelayedBreakpoints()) {
- m_delayed_breakpoints.Enqueue(site.shared_from_this(), action);
+ if (GetUseDelayedBreakpoints()) {
+ m_delayed_breakpoints.Enqueue(site_sp, action);
return llvm::Error::success();
}
@@ -1723,6 +1722,7 @@ llvm::Error Process::FlushDelayedBreakpoints() {
m_delayed_breakpoints.m_site_to_action.clear();
guard.unlock();
+ // Use a copy of the cache so that iteration is safe.
return UpdateBreakpointSites(site_to_action);
}
>From a7916954066f66ba28e767d644c1b54cc71cc1e2 Mon Sep 17 00:00:00 2001
From: Felipe de Azevedo Piovezan <fpiovezan at apple.com>
Date: Thu, 30 Apr 2026 14:19:57 +0100
Subject: [PATCH 12/13] fixup! Also delay initial breakpoint creation
---
lldb/source/Target/Process.cpp | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index bac48bec510c9..e1de4b757ed9c 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -1757,7 +1757,14 @@ Process::CreateBreakpointSite(const BreakpointLocationSP &constituent,
BreakpointSiteSP bp_site_sp(
new BreakpointSite(constituent, load_addr, use_hardware));
- Status error = EnableBreakpointSite(bp_site_sp.get());
+
+ // If a hardware is required, set the Site type here, so that calls to
+ // BreakpointSite::IsHardware won't assert.
+ if (use_hardware)
+ bp_site_sp->SetType(BreakpointSite::Type::eHardware);
+
+ auto error = Status::FromError(
+ ExecuteBreakpointSiteAction(*bp_site_sp, BreakpointAction::Enable));
if (error.Success()) {
constituent->SetBreakpointSite(bp_site_sp);
return m_breakpoint_site_list.Add(bp_site_sp);
>From 97e9716b66567237d5feb3739ceaf14aedcc70d4 Mon Sep 17 00:00:00 2001
From: Felipe de Azevedo Piovezan <fpiovezan at apple.com>
Date: Thu, 30 Apr 2026 15:23:56 +0100
Subject: [PATCH 13/13] fixup! Add test
---
.../breakpoint/delayed_breakpoints/Makefile | 3 ++
.../TestDelayedBreakpoint.py | 41 +++++++++++++++++++
.../breakpoint/delayed_breakpoints/main.c | 7 ++++
3 files changed, 51 insertions(+)
create mode 100644 lldb/test/API/functionalities/breakpoint/delayed_breakpoints/Makefile
create mode 100644 lldb/test/API/functionalities/breakpoint/delayed_breakpoints/TestDelayedBreakpoint.py
create mode 100644 lldb/test/API/functionalities/breakpoint/delayed_breakpoints/main.c
diff --git a/lldb/test/API/functionalities/breakpoint/delayed_breakpoints/Makefile b/lldb/test/API/functionalities/breakpoint/delayed_breakpoints/Makefile
new file mode 100644
index 0000000000000..10495940055b6
--- /dev/null
+++ b/lldb/test/API/functionalities/breakpoint/delayed_breakpoints/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
diff --git a/lldb/test/API/functionalities/breakpoint/delayed_breakpoints/TestDelayedBreakpoint.py b/lldb/test/API/functionalities/breakpoint/delayed_breakpoints/TestDelayedBreakpoint.py
new file mode 100644
index 0000000000000..70a28bb935027
--- /dev/null
+++ b/lldb/test/API/functionalities/breakpoint/delayed_breakpoints/TestDelayedBreakpoint.py
@@ -0,0 +1,41 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+import os
+
+
+ at skipIfWindows
+class TestDelayedBreakpoint(TestBase):
+ def test(self):
+ self.build()
+ logfile = os.path.join(self.getBuildDir(), "log.txt")
+ self.runCmd(f"log enable -f {logfile} gdb-remote packets")
+
+ target, process, _, _ = lldbutil.run_to_source_breakpoint(
+ self, "main", lldb.SBFileSpec("main.c")
+ )
+
+ self.runCmd(f"proc plugin packet send BEFORE_BPS", check=False)
+
+ breakpoint = target.BreakpointCreateByLocation("main.c", 1)
+ self.assertGreater(breakpoint.GetNumResolvedLocations(), 0)
+
+ self.runCmd(f"proc plugin packet send AFTER_BPS", check=False)
+
+ lldbutil.continue_to_breakpoint(process, breakpoint)
+ self.runCmd(f"log disable all")
+
+ self.runCmd(f"proc plugin packet send AFTER_CONTINUE", check=False)
+
+ self.assertTrue(os.path.exists(logfile))
+ log_text = open(logfile).read()
+
+ log_before_continue = log_text.split("BEFORE_BPS", 1)[-1].split("AFTER_BPS", 1)[
+ 0
+ ]
+ self.assertNotIn("send packet: $Z", log_before_continue)
+ self.assertNotIn("send packet: $z", log_before_continue)
+
+ log_after = log_text.split("AFTER_BPS", 1)[-1].split("AFTER_CONTINUE", 1)[0]
+ self.assertIn("send packet: $Z", log_after)
diff --git a/lldb/test/API/functionalities/breakpoint/delayed_breakpoints/main.c b/lldb/test/API/functionalities/breakpoint/delayed_breakpoints/main.c
new file mode 100644
index 0000000000000..a9086f89af86a
--- /dev/null
+++ b/lldb/test/API/functionalities/breakpoint/delayed_breakpoints/main.c
@@ -0,0 +1,7 @@
+int break_on_me() {
+ int i = 10; // breakhere
+ i++;
+ return i; // secondbreak
+}
+
+int main() { return break_on_me(); }
More information about the lldb-commits
mailing list