Skip to content

Commit 5475495

Browse files
committed
[lldb-dap] Refactoring lldb-dap listening mode to support multiple connections.
This adjusts the lldb-dap listening mode to allow for multiple connections. Each client gets a new instanceo of DAP and an associated lldb::SBDebugger instance.
1 parent a6e7749 commit 5475495

File tree

14 files changed

+555
-268
lines changed

14 files changed

+555
-268
lines changed

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

Lines changed: 71 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -903,7 +903,7 @@ def request_setBreakpoints(self, file_path, line_array, data=None):
903903
"sourceModified": False,
904904
}
905905
if line_array is not None:
906-
args_dict["lines"] = "%s" % line_array
906+
args_dict["lines"] = line_array
907907
breakpoints = []
908908
for i, line in enumerate(line_array):
909909
breakpoint_data = None
@@ -1150,38 +1150,42 @@ def request_setInstructionBreakpoints(self, memory_reference=[]):
11501150
}
11511151
return self.send_recv(command_dict)
11521152

1153+
11531154
class DebugAdaptorServer(DebugCommunication):
11541155
def __init__(
11551156
self,
11561157
executable=None,
1157-
port=None,
1158+
launch=True,
1159+
connection=None,
11581160
init_commands=[],
11591161
log_file=None,
11601162
env=None,
11611163
):
11621164
self.process = None
1163-
if executable is not None:
1164-
adaptor_env = os.environ.copy()
1165-
if env is not None:
1166-
adaptor_env.update(env)
1167-
1168-
if log_file:
1169-
adaptor_env["LLDBDAP_LOG"] = log_file
1170-
self.process = subprocess.Popen(
1171-
[executable],
1172-
stdin=subprocess.PIPE,
1173-
stdout=subprocess.PIPE,
1174-
stderr=subprocess.PIPE,
1175-
env=adaptor_env,
1165+
if launch:
1166+
self.process, connection = DebugAdaptorServer.launch(
1167+
executable,
1168+
connection=connection,
1169+
log_file=log_file,
1170+
env=env,
11761171
)
1172+
1173+
if connection:
1174+
if connection.startswith("unix-connect://"): # unix-connect:///path
1175+
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
1176+
s.connect(connection.removeprefix("unix-connect://"))
1177+
elif connection.startswith("connection://"): # connection://[host]:port
1178+
host, port = connection.removeprefix("connection://").rsplit(":", 1)
1179+
# create_connection with try both ipv4 and ipv6.
1180+
s = socket.create_connection((host.strip("[]"), int(port)))
1181+
else:
1182+
raise ValueError("invalid connection: {}".format(connection))
11771183
DebugCommunication.__init__(
1178-
self, self.process.stdout, self.process.stdin, init_commands, log_file
1184+
self, s.makefile("rb"), s.makefile("wb"), init_commands, log_file
11791185
)
1180-
elif port is not None:
1181-
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1182-
s.connect(("127.0.0.1", port))
1186+
else:
11831187
DebugCommunication.__init__(
1184-
self, s.makefile("r"), s.makefile("w"), init_commands
1188+
self, self.process.stdout, self.process.stdin, init_commands, log_file
11851189
)
11861190

11871191
def get_pid(self):
@@ -1196,6 +1200,53 @@ def terminate(self):
11961200
self.process.wait()
11971201
self.process = None
11981202

1203+
@classmethod
1204+
def launch(
1205+
cls, executable: str, /, connection=None, log_file=None, env=None
1206+
) -> tuple[subprocess.Popen, str]:
1207+
adaptor_env = os.environ.copy()
1208+
if env:
1209+
adaptor_env.update(env)
1210+
1211+
if log_file:
1212+
adaptor_env["LLDBDAP_LOG"] = log_file
1213+
1214+
args = [executable]
1215+
bufsize = -1
1216+
if connection:
1217+
bufsize = 0
1218+
args.append("--connection")
1219+
args.append(connection)
1220+
1221+
proc = subprocess.Popen(
1222+
args,
1223+
bufsize=bufsize,
1224+
stdin=subprocess.PIPE,
1225+
stdout=subprocess.PIPE,
1226+
stderr=sys.stdout,
1227+
env=adaptor_env,
1228+
)
1229+
1230+
if connection:
1231+
# If a conneciton is specified, lldb-dap will print the listening
1232+
# address once the listener is made to stdout. The listener is
1233+
# formatted like `tcp://host:port` or `unix:///path`.
1234+
expected_prefix = "Listening for: "
1235+
out = proc.stdout.readline().decode()
1236+
if not out.startswith(expected_prefix):
1237+
proc.kill()
1238+
raise ValueError(
1239+
"lldb-dap failed to print listening address, expected '{}', got '{}'".format(
1240+
expected_prefix, out
1241+
)
1242+
)
1243+
1244+
# If the listener expanded into multiple addresses, use the first.
1245+
connection = out.removeprefix(expected_prefix).rstrip("\r\n")
1246+
return proc, connection
1247+
1248+
return proc, None
1249+
11991250

12001251
def attach_options_specified(options):
12011252
if options.pid is not None:

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

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,36 @@
1010
class DAPTestCaseBase(TestBase):
1111
# set timeout based on whether ASAN was enabled or not. Increase
1212
# timeout by a factor of 10 if ASAN is enabled.
13-
timeoutval = 10 * (10 if ('ASAN_OPTIONS' in os.environ) else 1)
13+
timeoutval = 10 * (10 if ("ASAN_OPTIONS" in os.environ) else 1)
1414
NO_DEBUG_INFO_TESTCASE = True
1515

16-
def create_debug_adaptor(self, lldbDAPEnv=None):
16+
def create_debug_adaptor(self, env=None, launch=True, connection=None):
1717
"""Create the Visual Studio Code debug adaptor"""
1818
self.assertTrue(
1919
is_exe(self.lldbDAPExec), "lldb-dap must exist and be executable"
2020
)
2121
log_file_path = self.getBuildArtifact("dap.txt")
2222
self.dap_server = dap_server.DebugAdaptorServer(
2323
executable=self.lldbDAPExec,
24+
launch=launch,
25+
connection=connection,
2426
init_commands=self.setUpCommands(),
2527
log_file=log_file_path,
26-
env=lldbDAPEnv,
28+
env=env,
2729
)
2830

29-
def build_and_create_debug_adaptor(self, lldbDAPEnv=None):
31+
def build_and_create_debug_adaptor(
32+
self,
33+
lldbDAPEnv=None,
34+
lldbDAPLaunch=True,
35+
lldbDAPConnection=None,
36+
):
3037
self.build()
31-
self.create_debug_adaptor(lldbDAPEnv)
38+
self.create_debug_adaptor(
39+
env=lldbDAPEnv,
40+
launch=lldbDAPLaunch,
41+
connection=lldbDAPConnection,
42+
)
3243

3344
def set_source_breakpoints(self, source_path, lines, data=None):
3445
"""Sets source breakpoints and returns an array of strings containing
@@ -475,11 +486,17 @@ def build_and_launch(
475486
customThreadFormat=None,
476487
launchCommands=None,
477488
expectFailure=False,
489+
lldbDAPLaunch=True,
490+
lldbDAPConnection=None,
478491
):
479492
"""Build the default Makefile target, create the DAP debug adaptor,
480493
and launch the process.
481494
"""
482-
self.build_and_create_debug_adaptor(lldbDAPEnv)
495+
self.build_and_create_debug_adaptor(
496+
lldbDAPEnv=lldbDAPEnv,
497+
lldbDAPLaunch=lldbDAPLaunch,
498+
lldbDAPConnection=lldbDAPConnection,
499+
)
483500
self.assertTrue(os.path.exists(program), "executable must exist")
484501

485502
return self.launch(
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
C_SOURCES := main.c
2+
3+
include Makefile.rules
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"""
2+
Test lldb-dap server integration.
3+
"""
4+
5+
import os
6+
import tempfile
7+
8+
import dap_server
9+
from lldbsuite.test.decorators import *
10+
from lldbsuite.test.lldbtest import *
11+
import lldbdap_testcase
12+
13+
14+
class TestDAP_server(lldbdap_testcase.DAPTestCaseBase):
15+
def do_test_server(self, connection):
16+
log_file_path = self.getBuildArtifact("dap.txt")
17+
server, connection = dap_server.DebugAdaptorServer.launch(
18+
self.lldbDAPExec, connection, log_file=log_file_path
19+
)
20+
21+
def cleanup():
22+
server.terminate()
23+
server.wait()
24+
25+
self.addTearDownHook(cleanup)
26+
27+
self.build()
28+
program = self.getBuildArtifact("a.out")
29+
source = "main.c"
30+
breakpoint_line = line_number(source, "// breakpoint")
31+
32+
# Initial connection over the port.
33+
self.create_debug_adaptor(launch=False, connection=connection)
34+
self.launch(
35+
program,
36+
disconnectAutomatically=False,
37+
)
38+
self.set_source_breakpoints(source, [breakpoint_line])
39+
self.continue_to_next_stop()
40+
self.continue_to_exit()
41+
output = self.get_stdout()
42+
self.assertEquals(output, "hello world!\r\n")
43+
self.dap_server.request_disconnect()
44+
45+
# Second connection over the port.
46+
self.create_debug_adaptor(launch=False, connection=connection)
47+
self.launch(program)
48+
self.set_source_breakpoints(source, [breakpoint_line])
49+
self.continue_to_next_stop()
50+
self.continue_to_exit()
51+
output = self.get_stdout()
52+
self.assertEquals(output, "hello world!\r\n")
53+
54+
def test_server_port(self):
55+
"""
56+
Test launching a binary with a lldb-dap in server mode on a specific port.
57+
"""
58+
self.do_test_server(connection="tcp://localhost:0")
59+
60+
@skipIfWindows
61+
def test_server_unix_socket(self):
62+
"""
63+
Test launching a binary with a lldb-dap in server mode on a unix socket.
64+
"""
65+
dir = tempfile.gettempdir()
66+
name = dir + "/dap-connection-" + str(os.getpid())
67+
68+
def cleanup():
69+
os.unlink(name)
70+
71+
self.addTearDownHook(cleanup)
72+
self.do_test_server(connection="unix://" + name)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#include <stdio.h>
2+
3+
int main(int argc, char const *argv[]) {
4+
printf("hello world!\n"); // breakpoint 1
5+
return 0;
6+
}

lldb/tools/lldb-dap/CMakeLists.txt

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
if ( CMAKE_SYSTEM_NAME MATCHES "Windows" OR CMAKE_SYSTEM_NAME MATCHES "NetBSD" )
2-
list(APPEND extra_libs lldbHost)
3-
endif ()
4-
51
if (HAVE_LIBPTHREAD)
62
list(APPEND extra_libs pthread)
73
endif ()
@@ -26,22 +22,23 @@ add_lldb_tool(lldb-dap
2622
lldb-dap.cpp
2723
Breakpoint.cpp
2824
BreakpointBase.cpp
25+
DAP.cpp
2926
ExceptionBreakpoint.cpp
3027
FifoFiles.cpp
3128
FunctionBreakpoint.cpp
29+
InstructionBreakpoint.cpp
3230
IOStream.cpp
3331
JSONUtils.cpp
3432
LLDBUtils.cpp
3533
OutputRedirector.cpp
3634
ProgressEvent.cpp
3735
RunInTerminal.cpp
3836
SourceBreakpoint.cpp
39-
DAP.cpp
4037
Watchpoint.cpp
41-
InstructionBreakpoint.cpp
4238

4339
LINK_LIBS
4440
liblldb
41+
lldbHost
4542
${extra_libs}
4643

4744
LINK_COMPONENTS

0 commit comments

Comments
 (0)