[llvm-branch-commits] [lldb] [lldb] Override UpdateBreakpointSites in ProcessGDBRemote to use MultiBreakpoint (PR #192988)
Felipe de Azevedo Piovezan via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Thu Apr 23 07:53:48 PDT 2026
https://github.com/felipepiovezan updated https://github.com/llvm/llvm-project/pull/192988
>From ff8d8acaa9393ed0fdc610f82a07ea51805b1e1b Mon Sep 17 00:00:00 2001
From: Felipe de Azevedo Piovezan <fpiovezan at apple.com>
Date: Tue, 24 Mar 2026 08:39:54 +0000
Subject: [PATCH 1/4] [lldb][GDBRemote] Parse MultiBreakpoint+ capability
---
.../GDBRemoteCommunicationClient.cpp | 10 +++++++++
.../gdb-remote/GDBRemoteCommunicationClient.h | 3 +++
.../GDBRemoteCommunicationClientTest.cpp | 22 +++++++++++++++++++
3 files changed, 35 insertions(+)
diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
index 9ec0b07b592f7..6f0e43f765b9a 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp
@@ -217,6 +217,12 @@ bool GDBRemoteCommunicationClient::GetMultiMemReadSupported() {
return m_supports_multi_mem_read == eLazyBoolYes;
}
+bool GDBRemoteCommunicationClient::GetMultiBreakpointSupported() {
+ if (m_supports_multi_breakpoint == eLazyBoolCalculate)
+ GetRemoteQSupported();
+ return m_supports_multi_breakpoint == eLazyBoolYes;
+}
+
bool GDBRemoteCommunicationClient::QueryNoAckModeSupported() {
if (m_supports_not_sending_acks == eLazyBoolCalculate) {
m_send_acks = true;
@@ -346,6 +352,7 @@ void GDBRemoteCommunicationClient::ResetDiscoverableSettings(bool did_exec) {
m_supported_async_json_packets_sp.reset();
m_supports_jModulesInfo = true;
m_supports_multi_mem_read = eLazyBoolCalculate;
+ m_supports_multi_breakpoint = eLazyBoolCalculate;
}
// These flags should be reset when we first connect to a GDB server and when
@@ -373,6 +380,7 @@ void GDBRemoteCommunicationClient::GetRemoteQSupported() {
m_supports_reverse_continue = eLazyBoolNo;
m_supports_reverse_step = eLazyBoolNo;
m_supports_multi_mem_read = eLazyBoolNo;
+ m_supports_multi_breakpoint = eLazyBoolNo;
m_max_packet_size = UINT64_MAX; // It's supposed to always be there, but if
// not, we assume no limit
@@ -434,6 +442,8 @@ void GDBRemoteCommunicationClient::GetRemoteQSupported() {
m_supports_reverse_step = eLazyBoolYes;
else if (x == "MultiMemRead+")
m_supports_multi_mem_read = eLazyBoolYes;
+ else if (x == "MultiBreakpoint+")
+ m_supports_multi_breakpoint = eLazyBoolYes;
// Look for a list of compressions in the features list e.g.
// qXfer:features:read+;PacketSize=20000;qEcho+;SupportedCompressions=zlib-
// deflate,lzma
diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
index 6b5ed99583439..79ca0bcd3ed22 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h
@@ -344,6 +344,8 @@ class GDBRemoteCommunicationClient : public GDBRemoteClientBase {
bool GetMultiMemReadSupported();
+ bool GetMultiBreakpointSupported();
+
LazyBool SupportsAllocDeallocMemory() // const
{
// Uncomment this to have lldb pretend the debug server doesn't respond to
@@ -579,6 +581,7 @@ class GDBRemoteCommunicationClient : public GDBRemoteClientBase {
LazyBool m_supports_reverse_continue = eLazyBoolCalculate;
LazyBool m_supports_reverse_step = eLazyBoolCalculate;
LazyBool m_supports_multi_mem_read = eLazyBoolCalculate;
+ LazyBool m_supports_multi_breakpoint = eLazyBoolCalculate;
bool m_supports_qProcessInfoPID : 1, m_supports_qfProcessInfo : 1,
m_supports_qUserName : 1, m_supports_qGroupName : 1,
diff --git a/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp b/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp
index b6082b6acbd7c..0d41bdefd7507 100644
--- a/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp
+++ b/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp
@@ -709,3 +709,25 @@ TEST_F(GDBRemoteCommunicationClientTest, MultiMemReadNotSupported) {
ASSERT_FALSE(client.GetMultiMemReadSupported());
async_result.wait();
}
+
+TEST_F(GDBRemoteCommunicationClientTest, MultiBreakpointdSupported) {
+ std::future<bool> async_result = std::async(std::launch::async, [&] {
+ StringExtractorGDBRemote qSupported_packet_request;
+ server.GetPacket(qSupported_packet_request);
+ server.SendPacket("MultiBreakpoint+;");
+ return true;
+ });
+ ASSERT_TRUE(client.GetMultiBreakpointSupported());
+ async_result.wait();
+}
+
+TEST_F(GDBRemoteCommunicationClientTest, MultiBreakpointdNotSupported) {
+ std::future<bool> async_result = std::async(std::launch::async, [&] {
+ StringExtractorGDBRemote qSupported_packet_request;
+ server.GetPacket(qSupported_packet_request);
+ server.SendPacket(";");
+ return true;
+ });
+ ASSERT_FALSE(client.GetMultiBreakpointSupported());
+ async_result.wait();
+}
>From 2070181e3096f9ce2b55c1f34b3464dbfbc68b0a Mon Sep 17 00:00:00 2001
From: Felipe de Azevedo Piovezan <fpiovezan at apple.com>
Date: Fri, 10 Apr 2026 14:25:32 +0100
Subject: [PATCH 2/4] [lldb][NFC] Move BreakpointSite::IsEnabled/SetEnabled
into Process
The Process class is the one responsible for managing the state of a
BreakpointSite inside the process. As such, it should be the one
answering questions about the state of the site.
https://github.com/llvm/llvm-project/pull/192910
---
lldb/include/lldb/Breakpoint/BreakpointSite.h | 20 +++----
lldb/include/lldb/Target/Process.h | 10 ++++
lldb/source/Breakpoint/BreakpointSite.cpp | 2 -
.../Process/MacOSX-Kernel/ProcessKDP.cpp | 10 ++--
.../Process/Utility/StopInfoMachException.cpp | 6 +--
.../Process/gdb-remote/ProcessGDBRemote.cpp | 52 ++++++++-----------
.../Process/gdb-remote/ProcessGDBRemote.h | 8 +++
.../Process/scripted/ScriptedProcess.cpp | 2 +-
.../Process/scripted/ScriptedThread.cpp | 5 +-
lldb/source/Target/Process.cpp | 23 +++++---
.../Target/ThreadPlanStepOverBreakpoint.cpp | 2 +-
11 files changed, 76 insertions(+), 64 deletions(-)
diff --git a/lldb/include/lldb/Breakpoint/BreakpointSite.h b/lldb/include/lldb/Breakpoint/BreakpointSite.h
index e189ed77e261b..e40eb8e3545c1 100644
--- a/lldb/include/lldb/Breakpoint/BreakpointSite.h
+++ b/lldb/include/lldb/Breakpoint/BreakpointSite.h
@@ -77,20 +77,6 @@ class BreakpointSite : public std::enable_shared_from_this<BreakpointSite>,
lldb::addr_t *intersect_addr, size_t *intersect_size,
size_t *opcode_offset) const;
- /// Tells whether the current breakpoint site is enabled or not
- ///
- /// This is a low-level enable bit for the breakpoint sites. If a
- /// breakpoint site has no enabled constituents, it should just get removed.
- /// This enable/disable is for the low-level target code to enable and disable
- /// breakpoint sites when single stepping, etc.
- bool IsEnabled() const;
-
- /// Sets whether the current breakpoint site is enabled or not
- ///
- /// \param[in] enabled
- /// \b true if the breakpoint is enabled, \b false otherwise.
- void SetEnabled(bool enabled);
-
/// Enquires of the breakpoint locations that produced this breakpoint site
/// whether we should stop at this location.
///
@@ -223,6 +209,12 @@ class BreakpointSite : public std::enable_shared_from_this<BreakpointSite>,
size_t RemoveConstituent(lldb::break_id_t break_id,
lldb::break_id_t break_loc_id);
+ /// Sets whether the current breakpoint site is enabled or not.
+ ///
+ /// \param[in] enabled
+ /// \b true if the breakpoint is enabled, \b false otherwise.
+ void SetEnabled(bool enabled);
+
BreakpointSite::Type m_type; ///< The type of this breakpoint site.
uint8_t m_saved_opcode[8]; ///< The saved opcode bytes if this breakpoint site
///uses trap opcodes.
diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h
index f12d873133f9c..cf329f39713a4 100644
--- a/lldb/include/lldb/Target/Process.h
+++ b/lldb/include/lldb/Target/Process.h
@@ -2282,6 +2282,9 @@ class Process : public std::enable_shared_from_this<Process>,
Status EnableBreakpointSiteByID(lldb::user_id_t break_id);
+ bool IsBreakpointSiteEnabled(const lldb::BreakpointSiteSP &site);
+ bool IsBreakpointSiteEnabled(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,
@@ -3617,6 +3620,13 @@ void PruneThreadPlans();
void SetAddressableBitMasks(AddressableBits bit_masks);
+ // Updates the state of site.
+ // This should be used by derived Process classes after they have changed the
+ // state of a site.
+ void SetBreakpointSiteEnabled(BreakpointSite &site, bool is_enabled = true) {
+ site.SetEnabled(is_enabled);
+ }
+
private:
Status DestroyImpl(bool force_kill);
diff --git a/lldb/source/Breakpoint/BreakpointSite.cpp b/lldb/source/Breakpoint/BreakpointSite.cpp
index 8639379afe1df..584ff1ff52c07 100644
--- a/lldb/source/Breakpoint/BreakpointSite.cpp
+++ b/lldb/source/Breakpoint/BreakpointSite.cpp
@@ -135,8 +135,6 @@ const uint8_t *BreakpointSite::GetSavedOpcodeBytes() const {
return &m_saved_opcode[0];
}
-bool BreakpointSite::IsEnabled() const { return m_enabled; }
-
void BreakpointSite::SetEnabled(bool enabled) { m_enabled = enabled; }
void BreakpointSite::AddConstituent(const BreakpointLocationSP &constituent) {
diff --git a/lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.cpp b/lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.cpp
index e81a5dea193a2..6b3354aad09e6 100644
--- a/lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.cpp
+++ b/lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.cpp
@@ -633,9 +633,9 @@ Status ProcessKDP::EnableBreakpointSite(BreakpointSite *bp_site) {
if (m_comm.LocalBreakpointsAreSupported()) {
Status error;
- if (!bp_site->IsEnabled()) {
+ if (!IsBreakpointSiteEnabled(bp_site)) {
if (m_comm.SendRequestBreakpoint(true, bp_site->GetLoadAddress())) {
- bp_site->SetEnabled(true);
+ SetBreakpointSiteEnabled(*bp_site);
bp_site->SetType(BreakpointSite::eExternal);
} else {
return Status::FromErrorString("KDP set breakpoint failed");
@@ -649,15 +649,15 @@ Status ProcessKDP::EnableBreakpointSite(BreakpointSite *bp_site) {
Status ProcessKDP::DisableBreakpointSite(BreakpointSite *bp_site) {
if (m_comm.LocalBreakpointsAreSupported()) {
Status error;
- if (bp_site->IsEnabled()) {
+ if (IsBreakpointSiteEnabled(bp_site)) {
BreakpointSite::Type bp_type = bp_site->GetType();
if (bp_type == BreakpointSite::eExternal) {
if (m_destroy_in_process && m_comm.IsRunning()) {
// We are trying to destroy our connection and we are running
- bp_site->SetEnabled(false);
+ SetBreakpointSiteEnabled(*bp_site, false);
} else {
if (m_comm.SendRequestBreakpoint(false, bp_site->GetLoadAddress()))
- bp_site->SetEnabled(false);
+ SetBreakpointSiteEnabled(*bp_site, false);
else
return Status::FromErrorString("KDP remove breakpoint failed");
}
diff --git a/lldb/source/Plugins/Process/Utility/StopInfoMachException.cpp b/lldb/source/Plugins/Process/Utility/StopInfoMachException.cpp
index d374742b5722c..9951fe1d9e293 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 && bp_site_sp->IsEnabled())
+ if (bp_site_sp && process_sp->IsBreakpointSiteEnabled(bp_site_sp))
thread.SetThreadStoppedAtUnexecutedBP(pc);
switch (exc_type) {
@@ -771,7 +771,7 @@ StopInfoSP StopInfoMachException::CreateStopReasonWithMachException(
if (!bp_site_sp && reg_ctx_sp) {
bp_site_sp = process_sp->GetBreakpointSiteList().FindByAddress(pc);
}
- if (bp_site_sp && bp_site_sp->IsEnabled()) {
+ if (bp_site_sp && process_sp->IsBreakpointSiteEnabled(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 +865,7 @@ 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() && site->IsEnabled()) {
+ if (site->IsHardware() && process_sp->IsBreakpointSiteEnabled(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 07ef71917f771..c3abb1a2e50cc 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
@@ -1843,7 +1843,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 && bp_site_sp->IsEnabled())
+ if (bp_site_sp && IsBreakpointSiteEnabled(bp_site_sp))
thread_sp->SetThreadStoppedAtUnexecutedBP(pc);
if (exc_type != 0) {
@@ -2027,7 +2027,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 (bp_site_sp->IsEnabled())
+ if (IsBreakpointSiteEnabled(bp_site_sp))
thread_sp->SetThreadHitBreakpointSite();
if (bp_site_sp->ValidForThisThread(*thread_sp)) {
@@ -3269,12 +3269,11 @@ size_t ProcessGDBRemote::PutSTDIN(const char *src, size_t src_len,
/// Enable a single breakpoint site by trying Z0 (software), then Z1
/// (hardware), then manual memory write as a last resort.
-static llvm::Error DoEnableBreakpointSite(ProcessGDBRemote &proc,
- BreakpointSite &bp_site) {
+llvm::Error ProcessGDBRemote::DoEnableBreakpointSite(BreakpointSite &bp_site) {
Log *log = GetLog(GDBRLog::Breakpoints);
const addr_t addr = bp_site.GetLoadAddress();
- const size_t bp_op_size = proc.GetSoftwareBreakpointTrapOpcode(&bp_site);
- auto &gdb_comm = proc.GetGDBRemote();
+ const size_t bp_op_size = GetSoftwareBreakpointTrapOpcode(&bp_site);
+ auto &gdb_comm = GetGDBRemote();
// SupportsGDBStoppointPacket always returns true unless a previously sent
// packet failed. As such, query the function before AND after sending the
@@ -3282,10 +3281,9 @@ static llvm::Error DoEnableBreakpointSite(ProcessGDBRemote &proc,
if (gdb_comm.SupportsGDBStoppointPacket(eBreakpointSoftware) &&
!bp_site.HardwareRequired()) {
uint8_t error_no = gdb_comm.SendGDBStoppointTypePacket(
- eBreakpointSoftware, true, addr, bp_op_size,
- proc.GetInterruptTimeout());
+ eBreakpointSoftware, true, addr, bp_op_size, GetInterruptTimeout());
if (error_no == 0) {
- bp_site.SetEnabled(true);
+ SetBreakpointSiteEnabled(bp_site);
bp_site.SetType(BreakpointSite::eExternal);
return llvm::Error::success();
}
@@ -3301,10 +3299,9 @@ static llvm::Error DoEnableBreakpointSite(ProcessGDBRemote &proc,
// Like above, this is also queried twice.
if (gdb_comm.SupportsGDBStoppointPacket(eBreakpointHardware)) {
uint8_t error_no = gdb_comm.SendGDBStoppointTypePacket(
- eBreakpointHardware, true, addr, bp_op_size,
- proc.GetInterruptTimeout());
+ eBreakpointHardware, true, addr, bp_op_size, GetInterruptTimeout());
if (error_no == 0) {
- bp_site.SetEnabled(true);
+ SetBreakpointSiteEnabled(bp_site);
bp_site.SetType(BreakpointSite::eHardware);
return llvm::Error::success();
}
@@ -3324,38 +3321,35 @@ static llvm::Error DoEnableBreakpointSite(ProcessGDBRemote &proc,
if (bp_site.HardwareRequired())
return llvm::createStringError("hardware breakpoints are not supported");
- return proc.EnableSoftwareBreakpoint(&bp_site).takeError();
+ return EnableSoftwareBreakpoint(&bp_site).takeError();
}
/// Disable a single breakpoint site directly by sending the appropriate
/// z packet or restoring the original instruction.
-static llvm::Error DoDisableBreakpointSite(ProcessGDBRemote &proc,
- BreakpointSite &bp_site) {
+llvm::Error ProcessGDBRemote::DoDisableBreakpointSite(BreakpointSite &bp_site) {
const addr_t addr = bp_site.GetLoadAddress();
- const size_t bp_op_size = proc.GetSoftwareBreakpointTrapOpcode(&bp_site);
- auto &gdb_comm = proc.GetGDBRemote();
+ const size_t bp_op_size = GetSoftwareBreakpointTrapOpcode(&bp_site);
+ auto &gdb_comm = GetGDBRemote();
switch (bp_site.GetType()) {
case BreakpointSite::eSoftware: {
- Status error = proc.DisableSoftwareBreakpoint(&bp_site);
+ Status error = DisableSoftwareBreakpoint(&bp_site);
if (error.Fail())
return error.takeError();
break;
}
case BreakpointSite::eHardware:
if (gdb_comm.SendGDBStoppointTypePacket(eBreakpointHardware, false, addr,
- bp_op_size,
- proc.GetInterruptTimeout()))
+ bp_op_size, GetInterruptTimeout()))
return llvm::createStringError("unknown error");
break;
case BreakpointSite::eExternal:
if (gdb_comm.SendGDBStoppointTypePacket(eBreakpointSoftware, false, addr,
- bp_op_size,
- proc.GetInterruptTimeout()))
+ bp_op_size, GetInterruptTimeout()))
return llvm::createStringError("unknown error");
break;
}
- bp_site.SetEnabled(false);
+ SetBreakpointSiteEnabled(bp_site, false);
return llvm::Error::success();
}
@@ -3376,7 +3370,7 @@ Status ProcessGDBRemote::EnableBreakpointSite(BreakpointSite *bp_site) {
site_id, (uint64_t)addr);
// Breakpoint already exists and is enabled
- if (bp_site->IsEnabled()) {
+ if (IsBreakpointSiteEnabled(bp_site)) {
LLDB_LOGF(log,
"ProcessGDBRemote::EnableBreakpointSite (size_id = %" PRIu64
") address = 0x%" PRIx64 " -- SUCCESS (already enabled)",
@@ -3384,7 +3378,7 @@ Status ProcessGDBRemote::EnableBreakpointSite(BreakpointSite *bp_site) {
return Status();
}
- return Status::FromError(DoEnableBreakpointSite(*this, *bp_site));
+ return Status::FromError(DoEnableBreakpointSite(*bp_site));
}
Status ProcessGDBRemote::DisableBreakpointSite(BreakpointSite *bp_site) {
@@ -3397,7 +3391,7 @@ Status ProcessGDBRemote::DisableBreakpointSite(BreakpointSite *bp_site) {
") addr = 0x%8.8" PRIx64,
site_id, (uint64_t)addr);
- if (!bp_site->IsEnabled()) {
+ if (!IsBreakpointSiteEnabled(bp_site)) {
LLDB_LOGF(log,
"ProcessGDBRemote::DisableBreakpointSite (site_id = %" PRIu64
") addr = 0x%8.8" PRIx64 " -- SUCCESS (already disabled)",
@@ -3405,7 +3399,7 @@ Status ProcessGDBRemote::DisableBreakpointSite(BreakpointSite *bp_site) {
return Status();
}
- return Status::FromError(DoDisableBreakpointSite(*this, *bp_site));
+ return Status::FromError(DoDisableBreakpointSite(*bp_site));
}
// Pre-requisite: wp != NULL.
@@ -6047,7 +6041,7 @@ CommandObject *ProcessGDBRemote::GetPluginCommandObject() {
void ProcessGDBRemote::DidForkSwitchSoftwareBreakpoints(bool enable) {
GetBreakpointSiteList().ForEach([this, enable](BreakpointSite *bp_site) {
- if (bp_site->IsEnabled() &&
+ if (IsBreakpointSiteEnabled(bp_site) &&
(bp_site->GetType() == BreakpointSite::eSoftware ||
bp_site->GetType() == BreakpointSite::eExternal)) {
m_gdb_comm.SendGDBStoppointTypePacket(
@@ -6060,7 +6054,7 @@ void ProcessGDBRemote::DidForkSwitchSoftwareBreakpoints(bool enable) {
void ProcessGDBRemote::DidForkSwitchHardwareTraps(bool enable) {
if (m_gdb_comm.SupportsGDBStoppointPacket(eBreakpointHardware)) {
GetBreakpointSiteList().ForEach([this, enable](BreakpointSite *bp_site) {
- if (bp_site->IsEnabled() &&
+ if (IsBreakpointSiteEnabled(bp_site) &&
bp_site->GetType() == BreakpointSite::eHardware) {
m_gdb_comm.SendGDBStoppointTypePacket(
eBreakpointHardware, enable, bp_site->GetLoadAddress(),
diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
index fc3b0c0f513d9..15d9506479231 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
@@ -450,6 +450,14 @@ class ProcessGDBRemote : public Process,
std::map<uint64_t, uint32_t> m_thread_id_to_used_usec_map;
uint64_t m_last_signals_version = 0;
+ /// Enable a single breakpoint site by trying Z0 (software), then Z1
+ /// (hardware), then manual memory write as a last resort.
+ llvm::Error DoEnableBreakpointSite(BreakpointSite &bp_site);
+
+ /// Disable a single breakpoint site directly by sending the appropriate
+ /// z packet or restoring the original instruction.
+ llvm::Error DoDisableBreakpointSite(BreakpointSite &bp_site);
+
static bool NewThreadNotifyBreakpointHit(void *baton,
StoppointCallbackContext *context,
lldb::user_id_t break_id,
diff --git a/lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp b/lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp
index b8e2e4933bcc7..b0718b771295b 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 (bp_site->IsEnabled()) {
+ if (IsBreakpointSiteEnabled(bp_site)) {
return {};
}
diff --git a/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp b/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp
index eafd4b90ce296..d20396d73f776 100644
--- a/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp
+++ b/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp
@@ -308,9 +308,10 @@ bool ScriptedThread::CalculateStopInfo() {
// if we CreateStopReasonWithBreakpointSiteID.
if (RegisterContextSP reg_ctx_sp = GetRegisterContext()) {
addr_t pc = reg_ctx_sp->GetPC();
+ ProcessSP proc = GetProcess();
if (BreakpointSiteSP bp_site_sp =
- GetProcess()->GetBreakpointSiteList().FindByAddress(pc))
- if (bp_site_sp->IsEnabled())
+ proc->GetBreakpointSiteList().FindByAddress(pc))
+ if (proc->IsBreakpointSiteEnabled(bp_site_sp))
SetThreadStoppedAtUnexecutedBP(pc);
}
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index 34e15e19b82bd..c18484f349c15 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -1546,7 +1546,6 @@ Process::GetBreakpointSiteList() const {
void Process::DisableAllBreakpointSites() {
m_breakpoint_site_list.ForEach([this](BreakpointSite *bp_site) -> void {
- // bp_site->SetEnabled(true);
DisableBreakpointSite(bp_site);
});
}
@@ -1564,7 +1563,7 @@ 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 (bp_site_sp->IsEnabled())
+ if (IsBreakpointSiteEnabled(bp_site_sp))
error = DisableBreakpointSite(bp_site_sp.get());
} else {
error = Status::FromErrorStringWithFormat(
@@ -1578,7 +1577,7 @@ 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 (!bp_site_sp->IsEnabled())
+ if (!IsBreakpointSiteEnabled(bp_site_sp))
error = EnableBreakpointSite(bp_site_sp.get());
} else {
error = Status::FromErrorStringWithFormat(
@@ -1587,6 +1586,16 @@ Status Process::EnableBreakpointSiteByID(lldb::user_id_t break_id) {
return error;
}
+bool Process::IsBreakpointSiteEnabled(const lldb::BreakpointSiteSP &site) {
+ assert(site);
+ return site->m_enabled;
+}
+
+bool Process::IsBreakpointSiteEnabled(BreakpointSite *site) {
+ assert(site);
+ return site->m_enabled;
+}
+
static bool ShouldShowError(Process &process) {
switch (process.GetState()) {
case eStateInvalid:
@@ -1739,7 +1748,7 @@ Status Process::EnableSoftwareBreakpoint(BreakpointSite *bp_site) {
LLDB_LOGF(
log, "Process::EnableSoftwareBreakpoint (site_id = %d) addr = 0x%" PRIx64,
bp_site->GetID(), (uint64_t)bp_addr);
- if (bp_site->IsEnabled()) {
+ if (IsBreakpointSiteEnabled(bp_site)) {
LLDB_LOGF(
log,
"Process::EnableSoftwareBreakpoint (site_id = %d) addr = 0x%" PRIx64
@@ -1783,7 +1792,7 @@ Status Process::EnableSoftwareBreakpoint(BreakpointSite *bp_site) {
error) == bp_opcode_size) {
if (::memcmp(bp_opcode_bytes, verify_bp_opcode_bytes,
bp_opcode_size) == 0) {
- bp_site->SetEnabled(true);
+ SetBreakpointSiteEnabled(*bp_site);
bp_site->SetType(BreakpointSite::eSoftware);
LLDB_LOGF(log,
"Process::EnableSoftwareBreakpoint (site_id = %d) "
@@ -1825,7 +1834,7 @@ Status Process::DisableSoftwareBreakpoint(BreakpointSite *bp_site) {
if (bp_site->IsHardware()) {
error =
Status::FromErrorString("Breakpoint site is a hardware breakpoint.");
- } else if (bp_site->IsEnabled()) {
+ } else if (IsBreakpointSiteEnabled(bp_site)) {
const size_t break_op_size = bp_site->GetByteSize();
const uint8_t *const break_op = bp_site->GetTrapOpcodeBytes();
if (break_op_size > 0) {
@@ -1867,7 +1876,7 @@ Status Process::DisableSoftwareBreakpoint(BreakpointSite *bp_site) {
if (::memcmp(bp_site->GetSavedOpcodeBytes(), verify_opcode,
break_op_size) == 0) {
// SUCCESS
- bp_site->SetEnabled(false);
+ SetBreakpointSiteEnabled(*bp_site, false);
LLDB_LOGF(log,
"Process::DisableSoftwareBreakpoint (site_id = %d) "
"addr = 0x%" PRIx64 " -- SUCCESS",
diff --git a/lldb/source/Target/ThreadPlanStepOverBreakpoint.cpp b/lldb/source/Target/ThreadPlanStepOverBreakpoint.cpp
index 8b58ae541f368..597577c208f8b 100644
--- a/lldb/source/Target/ThreadPlanStepOverBreakpoint.cpp
+++ b/lldb/source/Target/ThreadPlanStepOverBreakpoint.cpp
@@ -120,7 +120,7 @@ bool ThreadPlanStepOverBreakpoint::DoWillResume(StateType resume_state,
if (current_plan) {
BreakpointSiteSP bp_site_sp(
m_process.GetBreakpointSiteList().FindByAddress(m_breakpoint_addr));
- if (bp_site_sp && bp_site_sp->IsEnabled()) {
+ if (bp_site_sp && m_process.IsBreakpointSiteEnabled(bp_site_sp)) {
m_process.DisableBreakpointSite(bp_site_sp.get());
m_reenabled_breakpoint_site = false;
}
>From 9a7fcf500837af61f0000c97773d08a58e72994b 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 3/4] [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 cf329f39713a4..3e592fcf6067b 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
@@ -2244,6 +2245,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());
@@ -2254,6 +2258,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
@@ -2278,7 +2290,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);
@@ -2291,6 +2304,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);
@@ -3539,6 +3554,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 c3abb1a2e50cc..e4e0c00a8f648 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
@@ -1843,7 +1843,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) {
@@ -2027,7 +2027,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)) {
@@ -3370,7 +3370,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)",
@@ -3391,7 +3391,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)",
@@ -6041,7 +6041,7 @@ CommandObject *ProcessGDBRemote::GetPluginCommandObject() {
void ProcessGDBRemote::DidForkSwitchSoftwareBreakpoints(bool enable) {
GetBreakpointSiteList().ForEach([this, enable](BreakpointSite *bp_site) {
- if (IsBreakpointSiteEnabled(bp_site) &&
+ if (IsBreakpointSitePhysicallyEnabled(*bp_site) &&
(bp_site->GetType() == BreakpointSite::eSoftware ||
bp_site->GetType() == BreakpointSite::eExternal)) {
m_gdb_comm.SendGDBStoppointTypePacket(
@@ -6054,7 +6054,7 @@ void ProcessGDBRemote::DidForkSwitchSoftwareBreakpoints(bool enable) {
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 d20396d73f776..1526f3a85ece4 100644
--- a/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp
+++ b/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp
@@ -311,7 +311,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 c18484f349c15..7c378bbbac778 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 937ff349c9d5c..f15bf63b13cf4 100644
--- a/lldb/source/Target/TargetProperties.td
+++ b/lldb/source/Target/TargetProperties.td
@@ -316,6 +316,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 14daadcbfa09e1e539766f360ccdf1a6bda43f7a Mon Sep 17 00:00:00 2001
From: Felipe de Azevedo Piovezan <fpiovezan at apple.com>
Date: Thu, 9 Apr 2026 15:07:40 +0100
Subject: [PATCH 4/4] [lldb] Override UpdateBreakpointSites in ProcessGDBRemote
to use MultiBreakpoint
This concludes the implementation of MultiBreakpoint by actually using
the new packet to batch breakpoint requests.
https://github.com/llvm/llvm-project/pull/192910
---
.../Process/gdb-remote/ProcessGDBRemote.cpp | 190 ++++++++++++++++++
.../Process/gdb-remote/ProcessGDBRemote.h | 8 +
2 files changed, 198 insertions(+)
diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
index e4e0c00a8f648..b267c27a7e279 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
@@ -6215,3 +6215,193 @@ void ProcessGDBRemote::DidExec() {
}
Process::DidExec();
}
+
+llvm::Error ProcessGDBRemote::UpdateBreakpointSitesNotBatched(
+ const std::map<lldb::BreakpointSiteSP, Process::BreakpointAction>
+ &site_to_action) {
+ llvm::Error joined = llvm::Error::success();
+ for (auto &[site, action] : site_to_action) {
+ llvm::Error error = action == Process::BreakpointAction::Enable
+ ? DoEnableBreakpointSite(*site)
+ : DoDisableBreakpointSite(*site);
+ joined = llvm::joinErrors(std::move(joined), std::move(error));
+ }
+ return joined;
+}
+
+static llvm::Expected<StringExtractorGDBRemote>
+SendMultiBreakpointPacket(GDBRemoteCommunicationClient &gdb_comm,
+ llvm::StringRef packet_str,
+ std::chrono::seconds interrupt_timeout) {
+ StringExtractorGDBRemote response;
+ GDBRemoteCommunication::PacketResult packet_result =
+ gdb_comm.SendPacketAndWaitForResponse(packet_str, response,
+ interrupt_timeout);
+ if (packet_result != GDBRemoteCommunication::PacketResult::Success)
+ return llvm::createStringErrorV(
+ "MultiBreakpoint failed to send packet: '{0}'", packet_str);
+
+ if (response.IsUnsupportedResponse())
+ return llvm::createStringErrorV(
+ "MultiBreakpoint unsupported response: '{0}'", response.GetStringRef());
+
+ return response;
+}
+
+/// Parse a MultiBreakpoint response into per-request results.
+/// Returns a vector of results: std::nullopt means OK, a uint8_t value is the
+/// error code from an Exx response.
+static llvm::SmallVector<std::optional<uint8_t>>
+ParseMultiBreakpointResponse(llvm::StringRef response_str) {
+ llvm::SmallVector<std::optional<uint8_t>> results;
+
+ StructuredData::ObjectSP parsed = StructuredData::ParseJSON(response_str);
+ StructuredData::Dictionary *dict =
+ parsed ? parsed->GetAsDictionary() : nullptr;
+ StructuredData::Array *array = nullptr;
+ if (dict)
+ dict->GetValueForKeyAsArray("results", array);
+ if (!array)
+ return results;
+
+ array->ForEach([&results](StructuredData::Object *object) -> bool {
+ llvm::StringRef token;
+ if (auto *string = object->GetAsString())
+ token = string->GetValue();
+ if (token == "OK") {
+ results.push_back(std::nullopt);
+ return true;
+ }
+ if (token.size() != 3 || !token.starts_with("E")) {
+ results.push_back(uint8_t(0xff));
+ return true;
+ }
+ uint8_t error_code = 0;
+ if (token.drop_front(1).getAsInteger(16, error_code))
+ results.push_back(0xff);
+ else
+ results.push_back(error_code);
+ return true;
+ });
+ return results;
+}
+
+/// Determine the GDB stoppoint type for a breakpoint site by checking which
+/// packet types the remote supports (for insertions), or by checking the site
+/// type (for deletions).
+static std::optional<GDBStoppointType>
+GetStoppointType(BreakpointSite &site, bool insert,
+ GDBRemoteCommunicationClient &gdb_comm) {
+ if (insert) {
+ if (!site.HardwareRequired() &&
+ gdb_comm.SupportsGDBStoppointPacket(eBreakpointSoftware))
+ return eBreakpointSoftware;
+ if (gdb_comm.SupportsGDBStoppointPacket(eBreakpointHardware))
+ return eBreakpointHardware;
+ return std::nullopt;
+ }
+
+ switch (site.GetType()) {
+ case BreakpointSite::eExternal:
+ return eBreakpointSoftware;
+ case BreakpointSite::eHardware:
+ return eBreakpointHardware;
+ case BreakpointSite::eSoftware:
+ return std::nullopt;
+ }
+ llvm_unreachable("unhandled BreakpointSite type");
+}
+
+namespace {
+struct BreakpointPacketInfo {
+ BreakpointSite &site;
+ size_t trap_opcode_size;
+ GDBStoppointType type;
+ bool is_enable;
+};
+
+std::string to_string(const BreakpointPacketInfo &info) {
+ char packet = info.is_enable ? 'Z' : 'z';
+ return llvm::formatv("{0}{1},{2:x-},{3:x-}", packet,
+ static_cast<int>(info.type), info.site.GetLoadAddress(),
+ info.trap_opcode_size)
+ .str();
+}
+} // namespace
+
+llvm::Error ProcessGDBRemote::UpdateBreakpointSites(
+ const std::map<lldb::BreakpointSiteSP, BreakpointAction> &site_to_action) {
+ if (site_to_action.empty())
+ return llvm::Error::success();
+ if (!m_gdb_comm.GetMultiBreakpointSupported())
+ return UpdateBreakpointSitesNotBatched(site_to_action);
+
+ Log *log = GetLog(GDBRLog::Breakpoints);
+
+ std::vector<BreakpointPacketInfo> breakpoint_infos;
+
+ for (auto [site, action] : site_to_action) {
+ addr_t addr = site->GetLoadAddress();
+ size_t trap_opcode_size = GetSoftwareBreakpointTrapOpcode(site.get());
+ std::optional<GDBStoppointType> type =
+ GetStoppointType(*site, action == BreakpointAction::Enable, m_gdb_comm);
+ if (!type) {
+ LLDB_LOG(log, "MultiBreakpoint: site {0} at {1:x} can't be batched",
+ site->GetID(), addr);
+ return UpdateBreakpointSitesNotBatched(site_to_action);
+ }
+ breakpoint_infos.push_back(
+ {*site, trap_opcode_size, *type, action == BreakpointAction::Enable});
+ }
+
+ StreamString stream;
+ stream << "jMultiBreakpoint:";
+
+ auto args_array = std::make_shared<StructuredData::Array>();
+ for (auto &bp_info : breakpoint_infos)
+ args_array->AddStringItem(to_string(bp_info));
+
+ StructuredData::Dictionary packet_dict;
+ packet_dict.AddItem("breakpoint_requests", args_array);
+ packet_dict.Dump(stream, false);
+
+ llvm::Expected<StringExtractorGDBRemote> response = SendMultiBreakpointPacket(
+ m_gdb_comm, stream.GetString(), GetInterruptTimeout());
+
+ if (!response) {
+ LLDB_LOG_ERROR(log, response.takeError(), "jMultiBreakpoint failed: {0}");
+ return UpdateBreakpointSitesNotBatched(site_to_action);
+ }
+
+ llvm::SmallVector<std::optional<uint8_t>> results =
+ ParseMultiBreakpointResponse(response->GetStringRef());
+
+ // This is a protocol violation, do nothing.
+ if (results.size() != site_to_action.size())
+ return llvm::createStringErrorV(
+ "MultiBreakpoint response count mismatch (expected {0}, got {1})",
+ site_to_action.size(), results.size());
+
+ // Process results: mark successful sites as enabled/disabled, retry failed
+ // sites individually.
+ llvm::Error joined = llvm::Error::success();
+ for (auto [error_code, bp_info] : llvm::zip(results, breakpoint_infos)) {
+ BreakpointSite &site = bp_info.site;
+ if (error_code == std::nullopt) {
+ SetBreakpointSiteEnabled(site, bp_info.is_enable);
+ if (bp_info.is_enable)
+ site.SetType(bp_info.type == eBreakpointHardware
+ ? BreakpointSite::eHardware
+ : BreakpointSite::eExternal);
+ continue;
+ }
+ LLDB_LOG(log,
+ "MultiBreakpoint: site {0} at {1:x} failed (E{2:X-2}), retrying",
+ site.GetID(), site.GetLoadAddress(), *error_code);
+ llvm::Error error = bp_info.is_enable ? DoEnableBreakpointSite(site)
+ : DoDisableBreakpointSite(site);
+ joined = llvm::joinErrors(std::move(joined), std::move(error));
+ }
+
+ return joined;
+}
diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
index 15d9506479231..a4e92fd425998 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
@@ -170,6 +170,10 @@ class ProcessGDBRemote : public Process,
// Process Breakpoints
Status EnableBreakpointSite(BreakpointSite *bp_site) override;
+ llvm::Error UpdateBreakpointSites(
+ const std::map<lldb::BreakpointSiteSP, BreakpointAction> &site_to_action)
+ override;
+
Status DisableBreakpointSite(BreakpointSite *bp_site) override;
// Process Watchpoints
@@ -458,6 +462,10 @@ class ProcessGDBRemote : public Process,
/// z packet or restoring the original instruction.
llvm::Error DoDisableBreakpointSite(BreakpointSite &bp_site);
+ llvm::Error UpdateBreakpointSitesNotBatched(
+ const std::map<lldb::BreakpointSiteSP, Process::BreakpointAction>
+ &site_to_action);
+
static bool NewThreadNotifyBreakpointHit(void *baton,
StoppointCallbackContext *context,
lldb::user_id_t break_id,
More information about the llvm-branch-commits
mailing list