Skip to content

Commit 1f98bdc

Browse files
JDevliegherepetrhosek
authored andcommitted
[lldb-dap] Change the launch sequence (llvm#138219) (reland)
This PR changes how we treat the launch sequence in lldb-dap. - Send the initialized event after we finish handling the initialize request, rather than after we finish attaching or launching. - Delay handling the launch and attach request until we have handled the configurationDone request. The latter is now largely a NO-OP and only exists to signal lldb-dap that it can handle the launch and attach requests. - Delay handling the initial threads requests until we have handled the launch or attach request. - Make all attaching and launching synchronous, including when we have attach or launch commands. This removes the need to synchronize between the request and event thread. Background: https://discourse.llvm.org/t/reliability-of-the-lldb-dap-tests/86125
1 parent e6152fe commit 1f98bdc

31 files changed

+332
-244
lines changed

lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py

Lines changed: 34 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,6 @@ def __init__(self, recv, send, init_commands, log_file=None):
132132
self.exit_status = None
133133
self.initialize_body = None
134134
self.thread_stop_reasons = {}
135-
self.breakpoint_events = []
136135
self.progress_events = []
137136
self.reverse_requests = []
138137
self.module_events = []
@@ -244,13 +243,6 @@ def handle_recv_packet(self, packet):
244243
self._process_stopped()
245244
tid = body["threadId"]
246245
self.thread_stop_reasons[tid] = body
247-
elif event == "breakpoint":
248-
# Breakpoint events come in when a breakpoint has locations
249-
# added or removed. Keep track of them so we can look for them
250-
# in tests.
251-
self.breakpoint_events.append(packet)
252-
# no need to add 'breakpoint' event packets to our packets list
253-
return keepGoing
254246
elif event.startswith("progress"):
255247
# Progress events come in as 'progressStart', 'progressUpdate',
256248
# and 'progressEnd' events. Keep these around in case test
@@ -412,6 +404,15 @@ def wait_for_stopped(self, timeout=None):
412404
self.threads = []
413405
return stopped_events
414406

407+
def wait_for_breakpoint_events(self, timeout=None):
408+
breakpoint_events = []
409+
while True:
410+
event = self.wait_for_event("breakpoint", timeout=timeout)
411+
if not event:
412+
break
413+
breakpoint_events.append(event)
414+
return breakpoint_events
415+
415416
def wait_for_exited(self):
416417
event_dict = self.wait_for_event("exited")
417418
if event_dict is None:
@@ -591,6 +592,7 @@ def request_attach(
591592
attachCommands=None,
592593
terminateCommands=None,
593594
coreFile=None,
595+
stopOnAttach=True,
594596
postRunCommands=None,
595597
sourceMap=None,
596598
gdbRemotePort=None,
@@ -620,6 +622,8 @@ def request_attach(
620622
args_dict["attachCommands"] = attachCommands
621623
if coreFile:
622624
args_dict["coreFile"] = coreFile
625+
if stopOnAttach:
626+
args_dict["stopOnEntry"] = stopOnAttach
623627
if postRunCommands:
624628
args_dict["postRunCommands"] = postRunCommands
625629
if sourceMap:
@@ -632,7 +636,7 @@ def request_attach(
632636
response = self.send_recv(command_dict)
633637

634638
if response["success"]:
635-
self.wait_for_events(["process", "initialized"])
639+
self.wait_for_event("process")
636640
return response
637641

638642
def request_breakpointLocations(
@@ -666,10 +670,6 @@ def request_configurationDone(self):
666670
response = self.send_recv(command_dict)
667671
if response:
668672
self.configuration_done_sent = True
669-
# Client requests the baseline of currently existing threads after
670-
# a successful launch or attach.
671-
# Kick off the threads request that follows
672-
self.request_threads()
673673
return response
674674

675675
def _process_stopped(self):
@@ -887,7 +887,7 @@ def request_launch(
887887
response = self.send_recv(command_dict)
888888

889889
if response["success"]:
890-
self.wait_for_events(["process", "initialized"])
890+
self.wait_for_event("process")
891891
return response
892892

893893
def request_next(self, threadId, granularity="statement"):
@@ -1325,6 +1325,26 @@ def attach_options_specified(options):
13251325

13261326
def run_vscode(dbg, args, options):
13271327
dbg.request_initialize(options.sourceInitFile)
1328+
1329+
if options.sourceBreakpoints:
1330+
source_to_lines = {}
1331+
for file_line in options.sourceBreakpoints:
1332+
(path, line) = file_line.split(":")
1333+
if len(path) == 0 or len(line) == 0:
1334+
print('error: invalid source with line "%s"' % (file_line))
1335+
1336+
else:
1337+
if path in source_to_lines:
1338+
source_to_lines[path].append(int(line))
1339+
else:
1340+
source_to_lines[path] = [int(line)]
1341+
for source in source_to_lines:
1342+
dbg.request_setBreakpoints(source, source_to_lines[source])
1343+
if options.funcBreakpoints:
1344+
dbg.request_setFunctionBreakpoints(options.funcBreakpoints)
1345+
1346+
dbg.request_configurationDone()
1347+
13281348
if attach_options_specified(options):
13291349
response = dbg.request_attach(
13301350
program=options.program,
@@ -1353,23 +1373,6 @@ def run_vscode(dbg, args, options):
13531373
)
13541374

13551375
if response["success"]:
1356-
if options.sourceBreakpoints:
1357-
source_to_lines = {}
1358-
for file_line in options.sourceBreakpoints:
1359-
(path, line) = file_line.split(":")
1360-
if len(path) == 0 or len(line) == 0:
1361-
print('error: invalid source with line "%s"' % (file_line))
1362-
1363-
else:
1364-
if path in source_to_lines:
1365-
source_to_lines[path].append(int(line))
1366-
else:
1367-
source_to_lines[path] = [int(line)]
1368-
for source in source_to_lines:
1369-
dbg.request_setBreakpoints(source, source_to_lines[source])
1370-
if options.funcBreakpoints:
1371-
dbg.request_setFunctionBreakpoints(options.funcBreakpoints)
1372-
dbg.request_configurationDone()
13731376
dbg.wait_for_stopped()
13741377
else:
13751378
if "message" in response:

lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,7 @@ def attach(
340340
exitCommands=None,
341341
attachCommands=None,
342342
coreFile=None,
343+
stopOnAttach=True,
343344
disconnectAutomatically=True,
344345
terminateCommands=None,
345346
postRunCommands=None,
@@ -348,6 +349,8 @@ def attach(
348349
expectFailure=False,
349350
gdbRemotePort=None,
350351
gdbRemoteHostname=None,
352+
sourceBreakpoints=None,
353+
functionBreakpoints=None,
351354
):
352355
"""Build the default Makefile target, create the DAP debug adapter,
353356
and attach to the process.
@@ -364,6 +367,28 @@ def cleanup():
364367
self.addTearDownHook(cleanup)
365368
# Initialize and launch the program
366369
self.dap_server.request_initialize(sourceInitFile)
370+
self.dap_server.wait_for_event("initialized")
371+
372+
# Set source breakpoints as part of the launch sequence.
373+
if sourceBreakpoints:
374+
for source_path, lines in sourceBreakpoints:
375+
response = self.dap_server.request_setBreakpoints(source_path, lines)
376+
self.assertTrue(
377+
response["success"],
378+
"setBreakpoints failed (%s)" % (response),
379+
)
380+
381+
# Set function breakpoints as part of the launch sequence.
382+
if functionBreakpoints:
383+
response = self.dap_server.request_setFunctionBreakpoints(
384+
functionBreakpoints
385+
)
386+
self.assertTrue(
387+
response["success"],
388+
"setFunctionBreakpoint failed (%s)" % (response),
389+
)
390+
391+
self.dap_server.request_configurationDone()
367392
response = self.dap_server.request_attach(
368393
program=program,
369394
pid=pid,
@@ -376,6 +401,7 @@ def cleanup():
376401
attachCommands=attachCommands,
377402
terminateCommands=terminateCommands,
378403
coreFile=coreFile,
404+
stopOnAttach=stopOnAttach,
379405
postRunCommands=postRunCommands,
380406
sourceMap=sourceMap,
381407
gdbRemotePort=gdbRemotePort,
@@ -419,6 +445,8 @@ def launch(
419445
commandEscapePrefix=None,
420446
customFrameFormat=None,
421447
customThreadFormat=None,
448+
sourceBreakpoints=None,
449+
functionBreakpoints=None,
422450
):
423451
"""Sending launch request to dap"""
424452

@@ -434,6 +462,29 @@ def cleanup():
434462

435463
# Initialize and launch the program
436464
self.dap_server.request_initialize(sourceInitFile)
465+
self.dap_server.wait_for_event("initialized")
466+
467+
# Set source breakpoints as part of the launch sequence.
468+
if sourceBreakpoints:
469+
for source_path, lines in sourceBreakpoints:
470+
response = self.dap_server.request_setBreakpoints(source_path, lines)
471+
self.assertTrue(
472+
response["success"],
473+
"setBreakpoints failed (%s)" % (response),
474+
)
475+
476+
# Set function breakpoints as part of the launch sequence.
477+
if functionBreakpoints:
478+
response = self.dap_server.request_setFunctionBreakpoints(
479+
functionBreakpoints
480+
)
481+
self.assertTrue(
482+
response["success"],
483+
"setFunctionBreakpoint failed (%s)" % (response),
484+
)
485+
486+
self.dap_server.request_configurationDone()
487+
437488
response = self.dap_server.request_launch(
438489
program,
439490
args=args,
@@ -504,6 +555,8 @@ def build_and_launch(
504555
customThreadFormat=None,
505556
launchCommands=None,
506557
expectFailure=False,
558+
sourceBreakpoints=None,
559+
functionBreakpoints=None,
507560
):
508561
"""Build the default Makefile target, create the DAP debug adapter,
509562
and launch the process.
@@ -540,6 +593,8 @@ def build_and_launch(
540593
customThreadFormat=customThreadFormat,
541594
launchCommands=launchCommands,
542595
expectFailure=expectFailure,
596+
sourceBreakpoints=sourceBreakpoints,
597+
functionBreakpoints=functionBreakpoints,
543598
)
544599

545600
def getBuiltinDebugServerTool(self):

lldb/test/API/tools/lldb-dap/attach/TestDAP_attach.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ def spawn_and_wait(program, delay):
2727
@skip
2828
class TestDAP_attach(lldbdap_testcase.DAPTestCaseBase):
2929
def set_and_hit_breakpoint(self, continueToExit=True):
30+
self.dap_server.wait_for_stopped()
31+
3032
source = "main.c"
3133
breakpoint1_line = line_number(source, "// breakpoint 1")
3234
lines = [breakpoint1_line]

lldb/test/API/tools/lldb-dap/attach/TestDAP_attachByPortNum.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,17 @@
1818
import socket
1919

2020

21-
@skip
2221
class TestDAP_attachByPortNum(lldbdap_testcase.DAPTestCaseBase):
2322
default_timeout = 20
2423

2524
def set_and_hit_breakpoint(self, continueToExit=True):
25+
self.dap_server.wait_for_stopped()
26+
2627
source = "main.c"
27-
main_source_path = os.path.join(os.getcwd(), source)
28-
breakpoint1_line = line_number(main_source_path, "// breakpoint 1")
28+
breakpoint1_line = line_number(source, "// breakpoint 1")
2929
lines = [breakpoint1_line]
3030
# Set breakpoint in the thread function so we can step the threads
31-
breakpoint_ids = self.set_source_breakpoints(main_source_path, lines)
31+
breakpoint_ids = self.set_source_breakpoints(source, lines)
3232
self.assertEqual(
3333
len(breakpoint_ids), len(lines), "expect correct number of breakpoints"
3434
)

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

Lines changed: 18 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -81,52 +81,27 @@ def test_breakpoint_events(self):
8181
breakpoint["verified"], "expect foo breakpoint to not be verified"
8282
)
8383

84-
# Get the stop at the entry point
85-
self.continue_to_next_stop()
84+
# Make sure we're stopped.
85+
self.dap_server.wait_for_stopped()
8686

87-
# We are now stopped at the entry point to the program. Shared
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"]
93-
self.assertEqual(
94-
body["reason"], "changed", "breakpoint event should say changed"
95-
)
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()
87+
# Flush the breakpoint events.
88+
self.dap_server.wait_for_breakpoint_events(timeout=5)
10289

10390
# Continue to the breakpoint
10491
self.continue_to_breakpoints(dap_breakpoint_ids)
10592

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")
93+
verified_breakpoint_ids = []
94+
unverified_breakpoint_ids = []
95+
for breakpoint_event in self.dap_server.wait_for_breakpoint_events(timeout=5):
96+
breakpoint = breakpoint_event["body"]["breakpoint"]
97+
id = breakpoint["id"]
98+
if breakpoint["verified"]:
99+
verified_breakpoint_ids.append(id)
100+
else:
101+
unverified_breakpoint_ids.append(id)
116102

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")
103+
self.assertIn(main_bp_id, unverified_breakpoint_ids)
104+
self.assertIn(foo_bp_id, unverified_breakpoint_ids)
105+
106+
self.assertIn(main_bp_id, verified_breakpoint_ids)
107+
self.assertIn(foo_bp_id, verified_breakpoint_ids)

0 commit comments

Comments
 (0)