Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions lldb/include/lldb/Target/Process.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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());
Expand All @@ -2256,6 +2260,24 @@ class Process : public std::enable_shared_from_this<Process>,
"error: {0} does not support disabling breakpoints", GetPluginName());
}

/// 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();
}
};
Comment on lines +2263 to +2270

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this competing with another comparison operator? If not, why not make this default?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I follow, these are shared pointers, so the default comparison is the pointer comparison. I don't believe we can change that, unless I'm misinterpreting the question

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't you do something like this? https://godbolt.org/z/Pb4P3njGa

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see. Well, I guess we could do that, but this would need to go in the header, as this is where the type is defined. Placing this in the header would then change the BreakpointSiteSP comparison everywhere where this header is used... which sounds a bit invasive? I doubt we sort these anywhere else, but still... not sure how I feel about it, what do you think?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't feel strongly about it but I'm just worried that at some point we'll need an ordering elsewhere and I'm not confident that person will find SiteIDCmp. Maybe a potential compromise (that minimizes risk) is to do this in a follow-up PR (which we don't cherrypick to the stable/21.x branch on the Swift fork).

using BreakpointSiteToActionMap =
std::map<lldb::BreakpointSiteSP, BreakpointAction, SiteIDCmp>;
Comment thread
JDevlieghere marked this conversation as resolved.

virtual llvm::Error
UpdateBreakpointSites(const BreakpointSiteToActionMap &site_to_action);

public:
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
// existing opcode, write breakpoint opcode, verify breakpoint opcode doesn't
Expand Down Expand Up @@ -2286,6 +2308,8 @@ class Process : public std::enable_shared_from_this<Process>,

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,
Expand Down Expand Up @@ -3540,6 +3564,21 @@ 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(); }

BreakpointSiteToActionMap m_site_to_action;
};

DelayedBreakpointCache m_delayed_breakpoints;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to protect this from concurrent access, or is the private state the only one messing with this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good question. It is private state, but can breakpoints be set simultaneously from different threads? Maybe @jimingham has some intuition here.

@jimingham jimingham Apr 28, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Breakpoints get added to the Target's BreakpointList with BreakpointList::AddBreakpoint, which is guarded by a BreakpointList mutex. Ditto for BreakpointList::RemoveBreakpoint. The BreakpointSiteList additions, removals and ForEach's are protected by a BreakpointSiteList mutex. So the main parts of adding breakpoints are serialized in lldb.

If access to m_delayed_breakpoints always happens as part of adding a breakpoint, or adding a site, or iterating over sites, then you should not need another access protection for the DelayedBreakpointCache.

However, in places like your addition to: Process::DisableBreakpointSiteByID the ExecuteBreakpointSiteAction is accessing the Cache outside the BreakpointSiteList lock, so you certainly could have more than one thread get to that code simultaneously. You probably do need some kind of locking in those places.

Given that a good portion of the DelayedBreakpointCache operations do happen protected by the BreakpointSiteList mutex, you could protect the other in Process by locking the BreakpointList mutex rather than inventing a new one.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I've given this a try. There are three methods that need to be protected: FlushDelayedBreakpoints, ExecuteBreakpointSiteAction, and IsBreakpointSiteEnabled.

I looked into re-using the BreakpointSiteList mutex, but it felt too implicit to re-use the lock inside a different data structure, so I opted for a new one.

std::recursive_mutex m_delayed_breakpoints_mutex;

llvm::Error FlushDelayedBreakpoints();

size_t RemoveBreakpointOpcodesFromBuffer(lldb::addr_t addr, size_t size,
uint8_t *buf) const;

Expand Down
4 changes: 2 additions & 2 deletions lldb/source/Plugins/Process/MacOSX-Kernel/ProcessKDP.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ void ProcessWindows::RefreshStateAfterStop() {
// If we're at a BreakpointSite, mark this as an Unexecuted Breakpoint.
// We'll clear that state if we've actually executed the breakpoint.
BreakpointSiteSP site(GetBreakpointSiteList().FindByAddress(pc));
if (site && IsBreakpointSiteEnabled(*site))
if (site && IsBreakpointSitePhysicallyEnabled(*site))
stop_thread->SetThreadStoppedAtUnexecutedBP(pc);

switch (active_exception->GetExceptionCode()) {
Expand Down
12 changes: 6 additions & 6 deletions lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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)",
Expand All @@ -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)",
Expand Down Expand Up @@ -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
Expand All @@ -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(),
Expand Down
2 changes: 1 addition & 1 deletion lldb/source/Plugins/Process/scripted/ScriptedProcess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {};
}

Expand Down
2 changes: 1 addition & 1 deletion lldb/source/Plugins/Process/scripted/ScriptedThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
Loading