Skip to content

Commit d654d37

Browse files
authoredMar 4, 2025
[lldb-dap] Correctly report breakpoints as resolved on macOS (llvm#129589)
On macOS, breakpoints are briefly unresolved between process launch and when the dynamic loader has informed us about the loaded libraries. This information was being forwarded by lldb-dap, but only partially. In the event handler, we were listening for the `LocationsAdded` and `LocationsRemoved` breakpoint events. For the scenario described above, the latter would trigger and we'd send an event reporting the breakpoint as unresolved. The problem is that when the breakpoint location is resolved again, you receive a `LocationsResolved` event, not a `LocationsAdded` event. As a result, the breakpoint would continue to show up as unresolved in the DAP client. I found a test that tried to test part of this behavior, but the test was broken and disabled. I revived the test and added coverage for the situation described above. Fixes llvm#112629 rdar://137968318
1 parent 6041c74 commit d654d37

File tree

2 files changed

+70
-68
lines changed

2 files changed

+70
-68
lines changed
 

‎lldb/test/API/tools/lldb-dap/breakpoint-events/TestDAP_breakpointEvents.py

+55-55
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
Test lldb-dap setBreakpoints request
33
"""
44

5-
65
import dap_server
76
from lldbsuite.test.decorators import *
87
from lldbsuite.test.lldbtest import *
@@ -12,9 +11,7 @@
1211

1312

1413
class TestDAP_breakpointEvents(lldbdap_testcase.DAPTestCaseBase):
15-
@skipIfWindows
1614
@skipUnlessDarwin
17-
@expectedFailureAll(macos_version=[">=", "13.0"])
1815
def test_breakpoint_events(self):
1916
"""
2017
This test sets a breakpoint in a shared library and runs and stops
@@ -63,70 +60,73 @@ def test_breakpoint_events(self):
6360
response = self.dap_server.request_setBreakpoints(
6461
main_source_path, [main_bp_line]
6562
)
66-
if response:
67-
breakpoints = response["body"]["breakpoints"]
68-
for breakpoint in breakpoints:
69-
main_bp_id = breakpoint["id"]
70-
dap_breakpoint_ids.append("%i" % (main_bp_id))
71-
# line = breakpoint['line']
72-
self.assertTrue(
73-
breakpoint["verified"], "expect main breakpoint to be verified"
74-
)
63+
self.assertTrue(response)
64+
breakpoints = response["body"]["breakpoints"]
65+
for breakpoint in breakpoints:
66+
main_bp_id = breakpoint["id"]
67+
dap_breakpoint_ids.append("%i" % (main_bp_id))
68+
self.assertTrue(
69+
breakpoint["verified"], "expect main breakpoint to be verified"
70+
)
7571

7672
response = self.dap_server.request_setBreakpoints(
7773
foo_source_path, [foo_bp1_line]
7874
)
79-
if response:
80-
breakpoints = response["body"]["breakpoints"]
81-
for breakpoint in breakpoints:
82-
foo_bp_id = breakpoint["id"]
83-
dap_breakpoint_ids.append("%i" % (foo_bp_id))
84-
self.assertFalse(
85-
breakpoint["verified"], "expect foo breakpoint to not be verified"
86-
)
75+
self.assertTrue(response)
76+
breakpoints = response["body"]["breakpoints"]
77+
for breakpoint in breakpoints:
78+
foo_bp_id = breakpoint["id"]
79+
dap_breakpoint_ids.append("%i" % (foo_bp_id))
80+
self.assertFalse(
81+
breakpoint["verified"], "expect foo breakpoint to not be verified"
82+
)
8783

8884
# Get the stop at the entry point
8985
self.continue_to_next_stop()
9086

9187
# We are now stopped at the entry point to the program. Shared
92-
# libraries are not loaded yet (at least on macOS they aren't) and any
93-
# breakpoints set in foo.cpp should not be resolved.
88+
# libraries are not loaded yet (at least on macOS they aren't) and only
89+
# the breakpoint in the main executable should be resolved.
90+
self.assertEqual(len(self.dap_server.breakpoint_events), 1)
91+
event = self.dap_server.breakpoint_events[0]
92+
body = event["body"]
9493
self.assertEqual(
95-
len(self.dap_server.breakpoint_events),
96-
0,
97-
"no breakpoint events when stopped at entry point",
94+
body["reason"], "changed", "breakpoint event should say changed"
9895
)
96+
breakpoint = body["breakpoint"]
97+
self.assertEqual(breakpoint["id"], main_bp_id)
98+
self.assertTrue(breakpoint["verified"], "main breakpoint should be resolved")
99+
100+
# Clear the list of breakpoint events so we don't see this one again.
101+
self.dap_server.breakpoint_events.clear()
99102

100103
# Continue to the breakpoint
101104
self.continue_to_breakpoints(dap_breakpoint_ids)
102105

103-
# Make sure we only get an event for the breakpoint we set via a call
104-
# to self.dap_server.request_setBreakpoints(...), not the breakpoint
105-
# we set with with a LLDB command in preRunCommands.
106-
self.assertEqual(
107-
len(self.dap_server.breakpoint_events),
108-
1,
109-
"make sure we got a breakpoint event",
110-
)
111-
event = self.dap_server.breakpoint_events[0]
112-
# Verify the details of the breakpoint changed notification.
113-
body = event["body"]
114-
self.assertEqual(
115-
body["reason"], "changed", "breakpoint event is says breakpoint is changed"
116-
)
117-
breakpoint = body["breakpoint"]
118-
self.assertTrue(
119-
breakpoint["verified"], "breakpoint event is says it is verified"
120-
)
121-
self.assertEqual(
122-
breakpoint["id"],
123-
foo_bp_id,
124-
"breakpoint event is for breakpoint %i" % (foo_bp_id),
125-
)
126-
self.assertTrue(
127-
"line" in breakpoint and breakpoint["line"] > 0,
128-
"breakpoint event is has a line number",
129-
)
130-
self.assertNotIn(
131-
"source", breakpoint, "breakpoint event should not return a source object"
132-
)
106+
# When the process launches, we first expect to see both the main and
107+
# foo breakpoint as unresolved.
108+
for event in self.dap_server.breakpoint_events[:2]:
109+
body = event["body"]
110+
self.assertEqual(
111+
body["reason"], "changed", "breakpoint event should say changed"
112+
)
113+
breakpoint = body["breakpoint"]
114+
self.assertIn(str(breakpoint["id"]), dap_breakpoint_ids)
115+
self.assertFalse(breakpoint["verified"], "breakpoint should be unresolved")
116+
117+
# Then, once the dynamic loader has given us a load address, they
118+
# should show up as resolved again.
119+
for event in self.dap_server.breakpoint_events[3:]:
120+
body = event["body"]
121+
self.assertEqual(
122+
body["reason"], "changed", "breakpoint event should say changed"
123+
)
124+
breakpoint = body["breakpoint"]
125+
self.assertIn(str(breakpoint["id"]), dap_breakpoint_ids)
126+
self.assertTrue(breakpoint["verified"], "breakpoint should be resolved")
127+
self.assertNotIn(
128+
"source",
129+
breakpoint,
130+
"breakpoint event should not return a source object",
131+
)
132+
self.assertIn("line", breakpoint, "breakpoint event should have line")

‎lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp

+15-13
Original file line numberDiff line numberDiff line change
@@ -137,27 +137,29 @@ static void EventThreadFunction(DAP &dap) {
137137
lldb::SBBreakpoint::GetBreakpointEventTypeFromEvent(event);
138138
auto bp = Breakpoint(
139139
dap, lldb::SBBreakpoint::GetBreakpointFromEvent(event));
140-
// If the breakpoint was originated from the IDE, it will have the
141-
// BreakpointBase::GetBreakpointLabel() label attached. Regardless
142-
// of wether the locations were added or removed, the breakpoint
143-
// ins't going away, so we the reason is always "changed".
140+
// If the breakpoint was set through DAP, it will have the
141+
// BreakpointBase::GetBreakpointLabel() label. Regardless
142+
// of whether locations were added, removed, or resolved, the
143+
// breakpoint isn't going away and the reason is always "changed".
144144
if ((event_type & lldb::eBreakpointEventTypeLocationsAdded ||
145-
event_type & lldb::eBreakpointEventTypeLocationsRemoved) &&
145+
event_type & lldb::eBreakpointEventTypeLocationsRemoved ||
146+
event_type & lldb::eBreakpointEventTypeLocationsResolved) &&
146147
bp.MatchesName(BreakpointBase::GetBreakpointLabel())) {
147-
auto bp_event = CreateEventObject("breakpoint");
148-
llvm::json::Object body;
149-
// As VSCode already knows the path of this breakpoint, we don't
150-
// need to send it back as part of a "changed" event. This
151-
// prevent us from sending to VSCode paths that should be source
152-
// mapped. Note that CreateBreakpoint doesn't apply source mapping.
153-
// Besides, the current implementation of VSCode ignores the
154-
// "source" element of breakpoint events.
148+
// As the DAP client already knows the path of this breakpoint, we
149+
// don't need to send it back as part of the "changed" event. This
150+
// avoids sending paths that should be source mapped. Note that
151+
// CreateBreakpoint doesn't apply source mapping and certain
152+
// implementation ignore the source part of this event anyway.
155153
llvm::json::Value source_bp = CreateBreakpoint(&bp);
156154
source_bp.getAsObject()->erase("source");
157155

156+
llvm::json::Object body;
158157
body.try_emplace("breakpoint", source_bp);
159158
body.try_emplace("reason", "changed");
159+
160+
llvm::json::Object bp_event = CreateEventObject("breakpoint");
160161
bp_event.try_emplace("body", std::move(body));
162+
161163
dap.SendJSON(llvm::json::Value(std::move(bp_event)));
162164
}
163165
}

0 commit comments

Comments
 (0)
Please sign in to comment.