Skip to content

Commit 8591c84

Browse files
committed
Working to address some of the basic architectural issues with the socket listeners.
* Moved the socket handling logic into lldb/tools/lldb-dap/Socket.{h,cpp} * Reworked the unit tests to read the socket information from the DAP process, this allows for lldb-dap to start with a connection like 'tcp://localhost:0' and pick a random port for the test. The output will be printed to stdout, listing all the addresses that were resolved from the connection parameter.
1 parent 8f69fb6 commit 8591c84

File tree

12 files changed

+923
-360
lines changed

12 files changed

+923
-360
lines changed

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

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import string
1010
import subprocess
1111
import sys
12+
import selectors
1213
import threading
1314
import time
1415

@@ -1150,36 +1151,37 @@ def request_setInstructionBreakpoints(self, memory_reference=[]):
11501151
}
11511152
return self.send_recv(command_dict)
11521153

1154+
11531155
class DebugAdaptorServer(DebugCommunication):
11541156
def __init__(
11551157
self,
11561158
executable=None,
11571159
launch=True,
1158-
port=None,
1159-
unix_socket=None,
1160+
connection=None,
11601161
init_commands=[],
11611162
log_file=None,
11621163
env=None,
11631164
):
11641165
self.process = None
11651166
if launch:
1166-
self.process = DebugAdaptorServer.launch(
1167+
self.process, connection = DebugAdaptorServer.launch(
11671168
executable,
1168-
port=port,
1169-
unix_socket=unix_socket,
1169+
connection=connection,
11701170
log_file=log_file,
11711171
env=env,
11721172
)
11731173

1174-
if port:
1175-
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1176-
s.connect(("127.0.0.1", port))
1177-
DebugCommunication.__init__(
1178-
self, s.makefile("rb"), s.makefile("wb"), init_commands, log_file
1179-
)
1180-
elif unix_socket:
1181-
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
1182-
s.connect(unix_socket)
1174+
if connection:
1175+
print("attempting connection", connection)
1176+
if connection.startswith("unix://"): # unix:///path
1177+
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
1178+
s.connect(connection.removeprefix("unix://"))
1179+
elif connection.startswith("tcp://"): # tcp://host:port
1180+
host, port = connection.removeprefix("tcp://").split(":", 1)
1181+
# create_connection with try both ipv4 and ipv6.
1182+
s = socket.create_connection((host, int(port)))
1183+
else:
1184+
raise ValueError("invalid connection: {}".format(connection))
11831185
DebugCommunication.__init__(
11841186
self, s.makefile("rb"), s.makefile("wb"), init_commands, log_file
11851187
)
@@ -1202,22 +1204,22 @@ def terminate(self):
12021204

12031205
@classmethod
12041206
def launch(
1205-
cls, executable: str, /, port=None, unix_socket=None, log_file=None, env=None
1206-
) -> subprocess.Popen:
1207+
cls, executable: str, /, connection=None, log_file=None, env=None
1208+
) -> tuple[subprocess.Popen, str]:
12071209
adaptor_env = os.environ.copy()
12081210
if env:
12091211
adaptor_env.update(env)
12101212

12111213
if log_file:
12121214
adaptor_env["LLDBDAP_LOG"] = log_file
12131215

1216+
if os.uname().sysname == "Darwin":
1217+
adaptor_env["NSUnbufferedIO"] = "YES"
1218+
12141219
args = [executable]
1215-
if port:
1216-
args.append("--port")
1217-
args.append(str(port))
1218-
elif unix_socket:
1219-
args.append("--unix-socket")
1220-
args.append(unix_socket)
1220+
if connection:
1221+
args.append("--connection")
1222+
args.append(connection)
12211223

12221224
proc = subprocess.Popen(
12231225
args,
@@ -1227,11 +1229,34 @@ def launch(
12271229
env=adaptor_env,
12281230
)
12291231

1230-
if port or unix_socket:
1231-
# Wait for the server to startup.
1232-
time.sleep(0.1)
1233-
1234-
return proc
1232+
if connection:
1233+
# If a conneciton is specified, lldb-dap will print the listening
1234+
# address once the listener is made to stdout. The listener is
1235+
# formatted like `tcp://host:port` or `unix:///path`.
1236+
with selectors.DefaultSelector() as sel:
1237+
print("Reading stdout for the listening connection")
1238+
os.set_blocking(proc.stdout.fileno(), False)
1239+
stdout_key = sel.register(proc.stdout, selectors.EVENT_READ)
1240+
rdy_fds = sel.select(timeout=10.0)
1241+
for key, _ in rdy_fds:
1242+
if key != stdout_key:
1243+
continue
1244+
1245+
outs = proc.stdout.read(1024).decode()
1246+
os.set_blocking(proc.stdout.fileno(), True)
1247+
for line in outs.split("\n"):
1248+
if not line.startswith("Listening for: "):
1249+
continue
1250+
# If the listener expanded into multiple addresses, use the first.
1251+
connection = line.removeprefix("Listening for: ").split(",")[0]
1252+
print("")
1253+
return proc, connection
1254+
proc.kill()
1255+
raise ValueError(
1256+
"lldb-dap started with a connection but failed to write the listening address to stdout."
1257+
)
1258+
1259+
return proc, None
12351260

12361261

12371262
def attach_options_specified(options):

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

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@
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, env=None, launch=True, port=None, unix_socket=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"
@@ -22,8 +22,7 @@ def create_debug_adaptor(self, env=None, launch=True, port=None, unix_socket=Non
2222
self.dap_server = dap_server.DebugAdaptorServer(
2323
executable=self.lldbDAPExec,
2424
launch=launch,
25-
port=port,
26-
unix_socket=unix_socket,
25+
connection=connection,
2726
init_commands=self.setUpCommands(),
2827
log_file=log_file_path,
2928
env=env,
@@ -33,15 +32,13 @@ def build_and_create_debug_adaptor(
3332
self,
3433
lldbDAPEnv=None,
3534
lldbDAPLaunch=True,
36-
lldbDAPPort=None,
37-
lldbDAPUnixSocket=None,
35+
lldbDAPConnection=None,
3836
):
3937
self.build()
4038
self.create_debug_adaptor(
4139
env=lldbDAPEnv,
4240
launch=lldbDAPLaunch,
43-
port=lldbDAPPort,
44-
unix_socket=lldbDAPUnixSocket,
41+
connection=lldbDAPConnection,
4542
)
4643

4744
def set_source_breakpoints(self, source_path, lines, data=None):
@@ -489,18 +486,16 @@ def build_and_launch(
489486
customThreadFormat=None,
490487
launchCommands=None,
491488
expectFailure=False,
492-
lldbDAPPort=None,
493-
lldbDAPUnixSocket=None,
494489
lldbDAPLaunch=True,
490+
lldbDAPConnection=None,
495491
):
496492
"""Build the default Makefile target, create the DAP debug adaptor,
497493
and launch the process.
498494
"""
499495
self.build_and_create_debug_adaptor(
500496
lldbDAPEnv=lldbDAPEnv,
501497
lldbDAPLaunch=lldbDAPLaunch,
502-
lldbDAPPort=lldbDAPPort,
503-
lldbDAPUnixSocket=lldbDAPUnixSocket,
498+
lldbDAPConnection=lldbDAPConnection,
504499
)
505500
self.assertTrue(os.path.exists(program), "executable must exist")
506501

lldb/test/API/tools/lldb-dap/server/TestDAP_server.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212

1313

1414
class TestDAP_server(lldbdap_testcase.DAPTestCaseBase):
15-
def do_test_server(self, port=None, unix_socket=None):
15+
def do_test_server(self, connection):
1616
log_file_path = self.getBuildArtifact("dap.txt")
17-
server = dap_server.DebugAdaptorServer.launch(
18-
self.lldbDAPExec, port=port, unix_socket=unix_socket, log_file=log_file_path
17+
server, connection = dap_server.DebugAdaptorServer.launch(
18+
self.lldbDAPExec, connection, log_file=log_file_path
1919
)
2020

2121
def cleanup():
@@ -30,7 +30,7 @@ def cleanup():
3030
breakpoint_line = line_number(source, "// breakpoint")
3131

3232
# Initial connection over the port.
33-
self.create_debug_adaptor(launch=False, port=port, unix_socket=unix_socket)
33+
self.create_debug_adaptor(launch=False, connection=connection)
3434
self.launch(
3535
program,
3636
disconnectAutomatically=False,
@@ -43,7 +43,7 @@ def cleanup():
4343
self.dap_server.request_disconnect()
4444

4545
# Second connection over the port.
46-
self.create_debug_adaptor(launch=False, port=port, unix_socket=unix_socket)
46+
self.create_debug_adaptor(launch=False, connection=connection)
4747
self.launch(program)
4848
self.set_source_breakpoints(source, [breakpoint_line])
4949
self.continue_to_next_stop()
@@ -55,12 +55,13 @@ def test_server_port(self):
5555
"""
5656
Test launching a binary with a lldb-dap in server mode on a specific port.
5757
"""
58-
port = pickrandomport()
59-
self.do_test_server(port=port)
58+
self.do_test_server(connection="tcp://localhost:0")
6059

6160
def test_server_unix_socket(self):
6261
"""
6362
Test launching a binary with a lldb-dap in server mode on a unix socket.
6463
"""
6564
dir = tempfile.gettempdir()
66-
self.do_test_server(unix_socket=dir + "/dap-connection-" + str(os.getpid()))
65+
self.do_test_server(
66+
connection="unix://" + dir + "/dap-connection-" + str(os.getpid())
67+
)

lldb/tools/lldb-dap/CMakeLists.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,20 @@ add_lldb_tool(lldb-dap
2626
lldb-dap.cpp
2727
Breakpoint.cpp
2828
BreakpointBase.cpp
29+
DAP.cpp
2930
ExceptionBreakpoint.cpp
3031
FifoFiles.cpp
3132
FunctionBreakpoint.cpp
33+
InstructionBreakpoint.cpp
3234
IOStream.cpp
3335
JSONUtils.cpp
3436
LLDBUtils.cpp
3537
OutputRedirector.cpp
3638
ProgressEvent.cpp
3739
RunInTerminal.cpp
40+
Socket.cpp
3841
SourceBreakpoint.cpp
39-
DAP.cpp
4042
Watchpoint.cpp
41-
InstructionBreakpoint.cpp
4243

4344
LINK_LIBS
4445
liblldb

lldb/tools/lldb-dap/DAP.cpp

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,14 @@
1414
#include "DAP.h"
1515
#include "JSONUtils.h"
1616
#include "LLDBUtils.h"
17+
#include "OutputRedirector.h"
1718
#include "lldb/API/SBCommandInterpreter.h"
1819
#include "lldb/API/SBLanguageRuntime.h"
1920
#include "lldb/API/SBListener.h"
2021
#include "lldb/API/SBStream.h"
2122
#include "llvm/ADT/StringExtras.h"
23+
#include "llvm/ADT/StringRef.h"
24+
#include "llvm/Support/Error.h"
2225
#include "llvm/Support/FormatVariadic.h"
2326

2427
#if defined(_WIN32)
@@ -32,10 +35,10 @@ using namespace lldb_dap;
3235

3336
namespace lldb_dap {
3437

35-
DAP::DAP(llvm::StringRef path, std::shared_ptr<std::ofstream> log,
38+
DAP::DAP(llvm::StringRef path, std::shared_ptr<llvm::raw_ostream> log,
3639
ReplMode repl_mode, std::vector<std::string> pre_init_commands)
37-
: debug_adaptor_path(path), broadcaster("lldb-dap"),
38-
log(log), exception_breakpoints(), pre_init_commands(pre_init_commands),
40+
: debug_adaptor_path(path), broadcaster("lldb-dap"), log(log),
41+
exception_breakpoints(), pre_init_commands(pre_init_commands),
3942
focus_tid(LLDB_INVALID_THREAD_ID), stop_at_entry(false), is_attach(false),
4043
enable_auto_variable_summaries(false),
4144
enable_synthetic_child_debugging(false),
@@ -48,6 +51,28 @@ DAP::DAP(llvm::StringRef path, std::shared_ptr<std::ofstream> log,
4851

4952
DAP::~DAP() = default;
5053

54+
llvm::Error DAP::ConfigureIO(int out_fd, int err_fd) {
55+
llvm::Expected<int> new_stdout_fd =
56+
RedirectFd(out_fd, [this](llvm::StringRef data) {
57+
SendOutput(OutputType::Stdout, data);
58+
});
59+
if (auto Err = new_stdout_fd.takeError()) {
60+
return Err;
61+
}
62+
llvm::Expected<int> new_stderr_fd =
63+
RedirectFd(err_fd, [this](llvm::StringRef data) {
64+
SendOutput(OutputType::Stderr, data);
65+
});
66+
if (auto Err = new_stderr_fd.takeError()) {
67+
return Err;
68+
}
69+
70+
out = lldb::SBFile(new_stdout_fd.get(), "w", false);
71+
err = lldb::SBFile(new_stderr_fd.get(), "w", false);
72+
73+
return llvm::Error::success();
74+
}
75+
5176
/// Return string with first character capitalized.
5277
static std::string capitalize(llvm::StringRef str) {
5378
if (str.empty())
@@ -183,9 +208,9 @@ void DAP::SendJSON(const llvm::json::Value &json) {
183208
if (log) {
184209
auto now = std::chrono::duration<double>(
185210
std::chrono::system_clock::now().time_since_epoch());
186-
*log << llvm::formatv("{0:f9} <-- ", now.count()).str() << std::endl
211+
*log << llvm::formatv("{0:f9} <-- ", now.count()).str() << "\n"
187212
<< "Content-Length: " << json_str.size() << "\r\n\r\n"
188-
<< llvm::formatv("{0:2}", json).str() << std::endl;
213+
<< llvm::formatv("{0:2}", json).str() << "\n";
189214
}
190215
}
191216

@@ -213,8 +238,8 @@ std::string DAP::ReadJSON() {
213238
if (log) {
214239
auto now = std::chrono::duration<double>(
215240
std::chrono::system_clock::now().time_since_epoch());
216-
*log << llvm::formatv("{0:f9} --> ", now.count()).str() << std::endl
217-
<< "Content-Length: " << length << "\r\n\r\n";
241+
*log << llvm::formatv("{0:f9} --> ", now.count()).str()
242+
<< "\nContent-Length: " << length << "\r\n\r\n";
218243
}
219244
return json_str;
220245
}
@@ -654,20 +679,20 @@ PacketStatus DAP::GetNextObject(llvm::json::Object &object) {
654679
std::string error_str;
655680
llvm::raw_string_ostream strm(error_str);
656681
strm << error;
657-
*log << "error: failed to parse JSON: " << error_str << std::endl
658-
<< json << std::endl;
682+
*log << "error: failed to parse JSON: " << error_str << "\n"
683+
<< json << "\n";
659684
}
660685
return PacketStatus::JSONMalformed;
661686
}
662687

663688
if (log) {
664-
*log << llvm::formatv("{0:2}", *json_value).str() << std::endl;
689+
*log << llvm::formatv("{0:2}", *json_value).str() << "\n";
665690
}
666691

667692
llvm::json::Object *object_ptr = json_value->getAsObject();
668693
if (!object_ptr) {
669694
if (log)
670-
*log << "error: json packet isn't a object" << std::endl;
695+
*log << "error: json packet isn't a object\n";
671696
return PacketStatus::JSONNotObject;
672697
}
673698
object = *object_ptr;
@@ -681,8 +706,7 @@ bool DAP::HandleObject(const llvm::json::Object &object) {
681706
auto handler_pos = request_handlers.find(command);
682707
if (handler_pos == request_handlers.end()) {
683708
if (log)
684-
*log << "error: unhandled command \"" << command.data() << "\""
685-
<< std::endl;
709+
*log << "error: unhandled command \"" << command.data() << "\"\n";
686710
return false; // Fail
687711
}
688712

0 commit comments

Comments
 (0)