[Lldb-commits] [lldb] [lldb-dap] Refactoring lldb-dap port listening mode to allow multiple connections. (PR #116392)
John Harrison via lldb-commits
lldb-commits at lists.llvm.org
Thu Jan 30 10:41:48 PST 2025
https://github.com/ashgti updated https://github.com/llvm/llvm-project/pull/116392
>From f946fd70bb550320bb153a59b8acd702dc97e75d Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Tue, 28 Jan 2025 12:39:38 -0800
Subject: [PATCH 1/2] [lldb-dap] Refactoring lldb-dap port listening mode to
allow multiple connections.
This adjusts the lldb-dap listening mode to accept multiple clients.
Each client initializes a new instance of DAP and an associated `lldb::SBDebugger` instance.
The listening mode is configured with the `--connection` option and supports listening on a port or a unix socket on supported platforms.
---
.../test/tools/lldb-dap/dap_server.py | 57 +++-
.../test/tools/lldb-dap/lldbdap_testcase.py | 6 +-
lldb/test/API/tools/lldb-dap/server/Makefile | 3 +
.../tools/lldb-dap/server/TestDAP_server.py | 84 ++++++
lldb/test/API/tools/lldb-dap/server/main.c | 10 +
lldb/test/Shell/DAP/TestOptions.test | 4 +-
lldb/tools/lldb-dap/DAP.cpp | 12 +-
lldb/tools/lldb-dap/DAP.h | 5 +-
lldb/tools/lldb-dap/Options.td | 22 +-
lldb/tools/lldb-dap/lldb-dap.cpp | 281 +++++++++++-------
10 files changed, 351 insertions(+), 133 deletions(-)
create mode 100644 lldb/test/API/tools/lldb-dap/server/Makefile
create mode 100644 lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
create mode 100644 lldb/test/API/tools/lldb-dap/server/main.c
diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
index c29992ce9c7848..6a71579030afcf 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
@@ -903,7 +903,7 @@ def request_setBreakpoints(self, file_path, line_array, data=None):
"sourceModified": False,
}
if line_array is not None:
- args_dict["lines"] = "%s" % line_array
+ args_dict["lines"] = line_array
breakpoints = []
for i, line in enumerate(line_array):
breakpoint_data = None
@@ -1150,11 +1150,12 @@ def request_setInstructionBreakpoints(self, memory_reference=[]):
}
return self.send_recv(command_dict)
+
class DebugAdaptorServer(DebugCommunication):
def __init__(
self,
executable=None,
- port=None,
+ connection=None,
init_commands=[],
log_file=None,
env=None,
@@ -1167,21 +1168,61 @@ def __init__(
if log_file:
adaptor_env["LLDBDAP_LOG"] = log_file
+ args = [executable]
+
+ if connection is not None:
+ args.append("--connection")
+ args.append(connection)
+
self.process = subprocess.Popen(
- [executable],
+ args,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=adaptor_env,
)
+
+ if connection is not None:
+ # If the process was also launched, parse the connection from the
+ # resolved connection. For example, if the connection
+ # `connection://localhost:0` was specified then the OS would pick a
+ # random port for listening and lldb-dap would print the listening
+ # port to stdout.
+ if self.process is not None:
+ # lldb-dap will print the listening address once the listener is
+ # made to stdout. The listener is formatted like
+ # `connection://host:port` or `unix-connection:///path`.
+ expected_prefix = "Listening for: "
+ out = self.process.stdout.readline().decode()
+ if not out.startswith(expected_prefix):
+ self.process.kill()
+ raise ValueError(
+ "lldb-dap failed to print listening address, expected '{}', got '{}'".format(
+ expected_prefix, out
+ )
+ )
+
+ # If the listener expanded into multiple addresses, use the first.
+ connection = (
+ out.removeprefix(expected_prefix).rstrip("\r\n").split(",", 1)[0]
+ )
+
+ if connection.startswith("unix-connect://"): # unix-connect:///path
+ s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ s.connect(connection.removeprefix("unix-connect://"))
+ elif connection.startswith("connection://"): # connection://[host]:port
+ host, port = connection.removeprefix("connection://").rsplit(":", 1)
+ # create_connection with try both ipv4 and ipv6.
+ s = socket.create_connection((host.strip("[]"), int(port)))
+ else:
+ raise ValueError("invalid connection: {}".format(connection))
DebugCommunication.__init__(
- self, self.process.stdout, self.process.stdin, init_commands, log_file
+ self, s.makefile("rb"), s.makefile("wb"), init_commands, log_file
)
- elif port is not None:
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- s.connect(("127.0.0.1", port))
+ self.connection = connection
+ else:
DebugCommunication.__init__(
- self, s.makefile("r"), s.makefile("w"), init_commands
+ self, self.process.stdout, self.process.stdin, init_commands, log_file
)
def get_pid(self):
diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
index a25466f07fa557..015c613956d86b 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
@@ -1,5 +1,6 @@
import os
import time
+import subprocess
import dap_server
from lldbsuite.test.lldbtest import *
@@ -10,10 +11,10 @@
class DAPTestCaseBase(TestBase):
# set timeout based on whether ASAN was enabled or not. Increase
# timeout by a factor of 10 if ASAN is enabled.
- timeoutval = 10 * (10 if ('ASAN_OPTIONS' in os.environ) else 1)
+ timeoutval = 10 * (10 if ("ASAN_OPTIONS" in os.environ) else 1)
NO_DEBUG_INFO_TESTCASE = True
- def create_debug_adaptor(self, lldbDAPEnv=None):
+ def create_debug_adaptor(self, lldbDAPEnv=None, connection=None):
"""Create the Visual Studio Code debug adaptor"""
self.assertTrue(
is_exe(self.lldbDAPExec), "lldb-dap must exist and be executable"
@@ -21,6 +22,7 @@ def create_debug_adaptor(self, lldbDAPEnv=None):
log_file_path = self.getBuildArtifact("dap.txt")
self.dap_server = dap_server.DebugAdaptorServer(
executable=self.lldbDAPExec,
+ connection=connection,
init_commands=self.setUpCommands(),
log_file=log_file_path,
env=lldbDAPEnv,
diff --git a/lldb/test/API/tools/lldb-dap/server/Makefile b/lldb/test/API/tools/lldb-dap/server/Makefile
new file mode 100644
index 00000000000000..451278a0946ef2
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/server/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
\ No newline at end of file
diff --git a/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py b/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
new file mode 100644
index 00000000000000..b37f6976a7591b
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
@@ -0,0 +1,84 @@
+"""
+Test lldb-dap server integration.
+"""
+
+import os
+import tempfile
+
+import dap_server
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+import lldbdap_testcase
+
+
+class TestDAP_server(lldbdap_testcase.DAPTestCaseBase):
+ def do_test_server(self, connection):
+ self.build()
+
+ log_file_path = self.getBuildArtifact("dap.txt")
+ server = dap_server.DebugAdaptorServer(
+ executable=self.lldbDAPExec,
+ connection=connection,
+ init_commands=self.setUpCommands(),
+ log_file=log_file_path,
+ )
+
+ def cleanup():
+ server.terminate()
+
+ self.addTearDownHook(cleanup)
+
+ self.dap_server = dap_server.DebugAdaptorServer(
+ connection=server.connection,
+ )
+ program = self.getBuildArtifact("a.out")
+ source = "main.c"
+ breakpoint_line = line_number(source, "// breakpoint")
+
+ # Initial connection over the connection.
+ self.launch(
+ program,
+ args=["Alice"],
+ disconnectAutomatically=False,
+ )
+ self.set_source_breakpoints(source, [breakpoint_line])
+ self.continue_to_next_stop()
+ self.continue_to_exit()
+ output = self.get_stdout()
+ self.assertEqual(output, "Hello Alice!\r\n")
+ self.dap_server.request_disconnect()
+
+ # Second connection over the connection.
+ self.dap_server = dap_server.DebugAdaptorServer(
+ connection=server.connection,
+ )
+ self.launch(
+ program,
+ args=["Bob"],
+ disconnectAutomatically=False,
+ )
+ self.set_source_breakpoints(source, [breakpoint_line])
+ self.continue_to_next_stop()
+ self.continue_to_exit()
+ output = self.get_stdout()
+ self.assertEqual(output, "Hello Bob!\r\n")
+
+ def test_server_port(self):
+ """
+ Test launching a binary with a lldb-dap in server mode on a specific port.
+ """
+ self.do_test_server(connection="tcp://localhost:0")
+
+ @skipIfWindows
+ def test_server_unix_socket(self):
+ """
+ Test launching a binary with a lldb-dap in server mode on a unix socket.
+ """
+ dir = tempfile.gettempdir()
+ name = dir + "/dap-connection-" + str(os.getpid())
+
+ def cleanup():
+ os.unlink(name)
+
+ self.addTearDownHook(cleanup)
+ self.do_test_server(connection="unix://" + name)
diff --git a/lldb/test/API/tools/lldb-dap/server/main.c b/lldb/test/API/tools/lldb-dap/server/main.c
new file mode 100644
index 00000000000000..446ae82532af57
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/server/main.c
@@ -0,0 +1,10 @@
+#include <stdio.h>
+
+int main(int argc, char const *argv[]) {
+ if (argc == 2) { // breakpoint 1
+ printf("Hello %s!\n", argv[1]);
+ } else {
+ printf("Hello World!\n");
+ }
+ return 0;
+}
diff --git a/lldb/test/Shell/DAP/TestOptions.test b/lldb/test/Shell/DAP/TestOptions.test
index e37e9116e3cddb..d290cdae590fd6 100644
--- a/lldb/test/Shell/DAP/TestOptions.test
+++ b/lldb/test/Shell/DAP/TestOptions.test
@@ -1,8 +1,8 @@
# RUN: lldb-dap --help | FileCheck %s
+# CHECK: --connection
# CHECK: -g
# CHECK: --help
# CHECK: -h
-# CHECK: --port
-# CHECK: -p
+# CHECK: --repl-mode
# CHECK: --wait-for-debugger
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index a67abe582abd40..ac20445e3a1cf0 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -58,9 +58,9 @@ const char DEV_NULL[] = "/dev/null";
namespace lldb_dap {
-DAP::DAP(llvm::StringRef path, std::ofstream *log, ReplMode repl_mode,
- StreamDescriptor input, StreamDescriptor output)
- : debug_adaptor_path(path), log(log), input(std::move(input)),
+DAP::DAP(std::string name, llvm::StringRef path, std::ofstream *log,
+ ReplMode repl_mode, StreamDescriptor input, StreamDescriptor output)
+ : name(name), debug_adaptor_path(path), log(log), input(std::move(input)),
output(std::move(output)), broadcaster("lldb-dap"),
exception_breakpoints(), focus_tid(LLDB_INVALID_THREAD_ID),
stop_at_entry(false), is_attach(false),
@@ -249,7 +249,8 @@ void DAP::SendJSON(const llvm::json::Value &json) {
if (log) {
auto now = std::chrono::duration<double>(
std::chrono::system_clock::now().time_since_epoch());
- *log << llvm::formatv("{0:f9} <-- ", now.count()).str() << std::endl
+ *log << llvm::formatv("{0:f9} {1} <-- ", now.count(), name).str()
+ << std::endl
<< "Content-Length: " << json_str.size() << "\r\n\r\n"
<< llvm::formatv("{0:2}", json).str() << std::endl;
}
@@ -279,7 +280,8 @@ std::string DAP::ReadJSON() {
if (log) {
auto now = std::chrono::duration<double>(
std::chrono::system_clock::now().time_since_epoch());
- *log << llvm::formatv("{0:f9} --> ", now.count()).str() << std::endl
+ *log << llvm::formatv("{0:f9} {1} --> ", now.count(), name).str()
+ << std::endl
<< "Content-Length: " << length << "\r\n\r\n";
}
return json_str;
diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h
index 846300cb945b0d..2190d941ea68ea 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -139,6 +139,7 @@ struct SendEventRequestHandler : public lldb::SBCommandPluginInterface {
};
struct DAP {
+ std::string name;
llvm::StringRef debug_adaptor_path;
std::ofstream *log;
InputStream input;
@@ -203,8 +204,8 @@ struct DAP {
// will contain that expression.
std::string last_nonempty_var_expression;
- DAP(llvm::StringRef path, std::ofstream *log, ReplMode repl_mode,
- StreamDescriptor input, StreamDescriptor output);
+ DAP(std::string name, llvm::StringRef path, std::ofstream *log,
+ ReplMode repl_mode, StreamDescriptor input, StreamDescriptor output);
~DAP();
DAP(const DAP &rhs) = delete;
void operator=(const DAP &rhs) = delete;
diff --git a/lldb/tools/lldb-dap/Options.td b/lldb/tools/lldb-dap/Options.td
index d7b4a065abec01..97a6ec118c47b5 100644
--- a/lldb/tools/lldb-dap/Options.td
+++ b/lldb/tools/lldb-dap/Options.td
@@ -17,12 +17,13 @@ def: Flag<["-"], "g">,
Alias<wait_for_debugger>,
HelpText<"Alias for --wait-for-debugger">;
-def port: S<"port">,
- MetaVarName<"<port>">,
- HelpText<"Communicate with the lldb-dap tool over the defined port.">;
-def: Separate<["-"], "p">,
- Alias<port>,
- HelpText<"Alias for --port">;
+def connection
+ : S<"connection">,
+ MetaVarName<"<connection>">,
+ HelpText<
+ "Communicate with the lldb-dap tool over the specified connection. "
+ "Connections are specified like 'tcp://[host]:port' or "
+ "'unix:///path'.">;
def launch_target: S<"launch-target">,
MetaVarName<"<target>">,
@@ -40,9 +41,12 @@ def debugger_pid: S<"debugger-pid">,
HelpText<"The PID of the lldb-dap instance that sent the launchInTerminal "
"request when using --launch-target.">;
-def repl_mode: S<"repl-mode">,
- MetaVarName<"<mode>">,
- HelpText<"The mode for handling repl evaluation requests, supported modes: variable, command, auto.">;
+def repl_mode
+ : S<"repl-mode">,
+ MetaVarName<"<mode>">,
+ HelpText<
+ "The mode for handling repl evaluation requests, supported modes: "
+ "variable, command, auto.">;
def pre_init_command: S<"pre-init-command">,
MetaVarName<"<command>">,
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index 9e0e7f21ce4fc7..1a5b024dcf3f36 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -14,45 +14,55 @@
#include "Watchpoint.h"
#include "lldb/API/SBDeclaration.h"
#include "lldb/API/SBEvent.h"
+#include "lldb/API/SBFile.h"
#include "lldb/API/SBInstruction.h"
#include "lldb/API/SBListener.h"
#include "lldb/API/SBMemoryRegionInfo.h"
#include "lldb/API/SBStream.h"
#include "lldb/API/SBStringList.h"
#include "lldb/Host/Config.h"
+#include "lldb/Host/MainLoop.h"
+#include "lldb/Host/MainLoopBase.h"
+#include "lldb/Host/Socket.h"
+#include "lldb/Host/common/TCPSocket.h"
+#include "lldb/Host/posix/DomainSocket.h"
+#include "lldb/Utility/Status.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/StringRef.h"
#include "llvm/Option/Arg.h"
#include "llvm/Option/ArgList.h"
#include "llvm/Option/OptTable.h"
#include "llvm/Option/Option.h"
#include "llvm/Support/Base64.h"
-#include "llvm/Support/Errno.h"
+#include "llvm/Support/Error.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/InitLLVM.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/PrettyStackTrace.h"
+#include "llvm/Support/Signals.h"
#include "llvm/Support/raw_ostream.h"
#include <algorithm>
#include <array>
-#include <cassert>
-#include <climits>
-#include <cstdarg>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fcntl.h>
+#include <fstream>
+#include <iostream>
#include <map>
#include <memory>
#include <optional>
+#include <ostream>
#include <set>
#include <sys/stat.h>
#include <sys/types.h>
#include <thread>
+#include <utility>
#include <vector>
#if defined(_WIN32)
@@ -68,6 +78,7 @@
#else
#include <netinet/in.h>
#include <sys/socket.h>
+#include <sys/un.h>
#include <unistd.h>
#endif
@@ -83,6 +94,10 @@ typedef int socklen_t;
#endif
using namespace lldb_dap;
+using lldb_private::MainLoop;
+using lldb_private::NativeSocket;
+using lldb_private::Socket;
+using lldb_private::Status;
namespace {
using namespace llvm::opt;
@@ -142,43 +157,6 @@ lldb::SBValueList *GetTopLevelScope(DAP &dap, int64_t variablesReference) {
}
}
-SOCKET AcceptConnection(std::ofstream *log, int portno) {
- // Accept a socket connection from any host on "portno".
- SOCKET newsockfd = -1;
- struct sockaddr_in serv_addr, cli_addr;
- SOCKET sockfd = socket(AF_INET, SOCK_STREAM, 0);
- if (sockfd < 0) {
- if (log)
- *log << "error: opening socket (" << strerror(errno) << ")" << std::endl;
- } else {
- memset((char *)&serv_addr, 0, sizeof(serv_addr));
- serv_addr.sin_family = AF_INET;
- // serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
- serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
- serv_addr.sin_port = htons(portno);
- if (bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
- if (log)
- *log << "error: binding socket (" << strerror(errno) << ")"
- << std::endl;
- } else {
- listen(sockfd, 5);
- socklen_t clilen = sizeof(cli_addr);
- newsockfd =
- llvm::sys::RetryAfterSignal(static_cast<SOCKET>(-1), accept, sockfd,
- (struct sockaddr *)&cli_addr, &clilen);
- if (newsockfd < 0)
- if (log)
- *log << "error: accept (" << strerror(errno) << ")" << std::endl;
- }
-#if defined(_WIN32)
- closesocket(sockfd);
-#else
- close(sockfd);
-#endif
- }
- return newsockfd;
-}
-
std::vector<const char *> MakeArgv(const llvm::ArrayRef<std::string> &strs) {
// Create and return an array of "const char *", one for each C string in
// "strs" and terminate the list with a NULL. This can be used for argument
@@ -4952,6 +4930,28 @@ static int DuplicateFileDescriptor(int fd) {
#endif
}
+static llvm::Expected<std::pair<Socket::SocketProtocol, std::string>>
+parseConnection(llvm::StringRef conn) {
+ if (conn.contains("://")) {
+ llvm::StringRef scheme, rest;
+ std::tie(scheme, rest) = conn.split("://");
+
+ if (scheme == "unix" || scheme == "unix-connect") {
+ return std::make_pair(Socket::ProtocolUnixDomain, rest.str());
+ }
+ if (scheme == "tcp" || scheme == "connect") {
+ return std::make_pair(Socket::ProtocolTcp, rest.str());
+ }
+ } else if (conn.starts_with("/")) {
+ return std::make_pair(Socket::ProtocolUnixDomain, conn.str());
+ } else if (conn.contains(":")) {
+ return std::make_pair(Socket::ProtocolTcp, conn.str());
+ }
+ return llvm::createStringError(
+ "expected '[unix://]/path' or '[tcp://][host]:port', got '%s'.",
+ conn.str().c_str());
+}
+
int main(int argc, char *argv[]) {
llvm::InitLLVM IL(argc, argv, /*InstallPipeSignalExitHandler=*/false);
#if !defined(__APPLE__)
@@ -4987,9 +4987,9 @@ int main(int argc, char *argv[]) {
} else if (repl_mode_value == "command") {
default_repl_mode = ReplMode::Command;
} else {
- llvm::errs()
- << "'" << repl_mode_value
- << "' is not a valid option, use 'variable', 'command' or 'auto'.\n";
+ llvm::errs() << "'" << repl_mode_value
+ << "' is not a valid option, use 'variable', 'command' or "
+ "'auto'.\n";
return EXIT_FAILURE;
}
}
@@ -5022,15 +5022,10 @@ int main(int argc, char *argv[]) {
}
}
- int portno = -1;
- if (auto *arg = input_args.getLastArg(OPT_port)) {
- const auto *optarg = arg->getValue();
- char *remainder;
- portno = strtol(optarg, &remainder, 0);
- if (remainder == optarg || *remainder != '\0') {
- fprintf(stderr, "'%s' is not a valid port number.\n", optarg);
- return EXIT_FAILURE;
- }
+ std::string connection;
+ if (auto *arg = input_args.getLastArg(OPT_connection)) {
+ const auto *path = arg->getValue();
+ connection.assign(path);
}
#if !defined(_WIN32)
@@ -5058,72 +5053,148 @@ int main(int argc, char *argv[]) {
auto terminate_debugger =
llvm::make_scope_exit([] { lldb::SBDebugger::Terminate(); });
- StreamDescriptor input;
- StreamDescriptor output;
- std::FILE *redirectOut = nullptr;
- std::FILE *redirectErr = nullptr;
- if (portno != -1) {
- printf("Listening on port %i...\n", portno);
- SOCKET socket_fd = AcceptConnection(log.get(), portno);
- if (socket_fd < 0)
- return EXIT_FAILURE;
+ std::vector<std::string> pre_init_commands;
+ for (const std::string &arg :
+ input_args.getAllArgValues(OPT_pre_init_command)) {
+ pre_init_commands.push_back(arg);
+ }
- input = StreamDescriptor::from_socket(socket_fd, true);
- output = StreamDescriptor::from_socket(socket_fd, false);
- } else {
-#if defined(_WIN32)
- // Windows opens stdout and stdin in text mode which converts \n to 13,10
- // while the value is just 10 on Darwin/Linux. Setting the file mode to
- // binary fixes this.
- int result = _setmode(fileno(stdout), _O_BINARY);
- assert(result);
- result = _setmode(fileno(stdin), _O_BINARY);
- UNUSED_IF_ASSERT_DISABLED(result);
- assert(result);
-#endif
+ auto HandleClient =
+ [=, log = log.get()](std::string name, StreamDescriptor input,
+ StreamDescriptor output, std::FILE *redirectOut,
+ std::FILE *redirectErr) -> bool {
+ DAP dap = DAP(name, program_path.str(), log, default_repl_mode,
+ std::move(input), std::move(output));
- int stdout_fd = DuplicateFileDescriptor(fileno(stdout));
- if (stdout_fd == -1) {
+ // stdout/stderr redirection to the IDE's console
+ if (auto Err = dap.ConfigureIO(redirectOut, redirectErr)) {
llvm::logAllUnhandledErrors(
- llvm::errorCodeToError(llvm::errnoAsErrorCode()), llvm::errs(),
- "Failed to configure stdout redirect: ");
+ std::move(Err), llvm::errs(),
+ "Failed to configure lldb-dap IO operations: ");
return EXIT_FAILURE;
}
- redirectOut = stdout;
- redirectErr = stderr;
+ RegisterRequestCallbacks(dap);
- input = StreamDescriptor::from_file(fileno(stdin), false);
- output = StreamDescriptor::from_file(stdout_fd, false);
- }
+ dap.pre_init_commands = pre_init_commands;
- DAP dap = DAP(program_path.str(), log.get(), default_repl_mode,
- std::move(input), std::move(output));
+ // used only by TestVSCode_redirection_to_console.py
+ if (getenv("LLDB_DAP_TEST_STDOUT_STDERR_REDIRECTION") != nullptr)
+ redirection_test();
- // stdout/stderr redirection to the IDE's console
- if (auto Err = dap.ConfigureIO(redirectOut, redirectErr)) {
- llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
- "Failed to configure lldb-dap IO operations: ");
- return EXIT_FAILURE;
- }
+ if (auto Err = dap.Loop()) {
+ if (log)
+ *log << "Transport Error: " << llvm::toString(std::move(Err)) << "\n";
+ return false;
+ }
+ return true;
+ };
- RegisterRequestCallbacks(dap);
+ if (!connection.empty()) {
+ auto maybeProtoclAndName = parseConnection(connection);
+ if (auto Err = maybeProtoclAndName.takeError()) {
+ llvm::errs() << "Invalid connection specification " << Err << "\n";
+ return EXIT_FAILURE;
+ }
- for (const std::string &arg :
- input_args.getAllArgValues(OPT_pre_init_command)) {
- dap.pre_init_commands.push_back(arg);
- }
+ Socket::SocketProtocol protocol;
+ std::string name;
+ std::tie(protocol, name) = *maybeProtoclAndName;
- // used only by TestVSCode_redirection_to_console.py
- if (getenv("LLDB_DAP_TEST_STDOUT_STDERR_REDIRECTION") != nullptr)
- redirection_test();
+ Status error;
+ std::unique_ptr<Socket> listener = Socket::Create(protocol, error);
+ if (error.Fail()) {
+ llvm::errs() << "Failed to create listener for protocol "
+ << Socket::FindSchemeByProtocol(protocol)
+ << ", error: " << error.takeError() << "\n";
+ return EXIT_FAILURE;
+ }
- bool CleanExit = true;
- if (auto Err = dap.Loop()) {
+ error = listener->Listen(name, /* backlog */ 5);
+ if (error.Fail()) {
+ llvm::errs() << "Failed to listen, error: " << error.takeError() << "\n";
+ return EXIT_FAILURE;
+ }
+
+ std::string address =
+ llvm::join(listener->GetListeningConnectionURI(), ", ");
if (log)
- *log << "Transport Error: " << llvm::toString(std::move(Err)) << "\n";
- CleanExit = false;
+ *log << "started with connection listeners " << address << "\n";
+
+ llvm::outs() << "Listening for: " << address << "\n";
+ // Ensure listening address are flushed for calles to retrieve the resolve
+ // address.
+ llvm::outs().flush();
+
+ MainLoop mainloop;
+ mainloop.RegisterSignal(
+ SIGHUP, [](auto &RL) { RL.RequestTermination(); }, error);
+
+ unsigned int clientCount = 0;
+ auto OnAccept = [=, log = log.get(),
+ &clientCount](std::unique_ptr<Socket> client) {
+ std::string name = llvm::formatv("client_{0}", clientCount++).str();
+
+ if (log) {
+ auto now = std::chrono::duration<double>(
+ std::chrono::system_clock::now().time_since_epoch());
+ *log << llvm::formatv("{0:f9} client connected: {1}", now.count(), name)
+ .str()
+ << std::endl;
+ }
+ // Start a thread for each connection, unblocking the listening thread.
+ std::thread([=, client = std::move(client)]() {
+ HandleClient(
+ name,
+ StreamDescriptor::from_socket(client->GetNativeSocket(), false),
+ StreamDescriptor::from_socket(client->GetNativeSocket(), false),
+ /*=redirectOut*/ nullptr, /*=redirectErr*/ nullptr);
+ }).detach();
+ };
+
+ auto handles = listener->Accept(mainloop, OnAccept);
+ if (auto Err = handles.takeError()) {
+ llvm::errs() << "failed to register accept() with the main loop: " << Err
+ << "\n";
+ return EXIT_FAILURE;
+ }
+
+ error = mainloop.Run();
+ if (error.Fail()) {
+ llvm::errs() << "failed to accept()" << error.takeError() << "\n";
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
}
- return CleanExit ? EXIT_SUCCESS : EXIT_FAILURE;
+#if defined(_WIN32)
+ // Windows opens stdout and stdin in text mode which converts \n to 13,10
+ // while the value is just 10 on Darwin/Linux. Setting the file mode to
+ // binary fixes this.
+ int result = _setmode(fileno(stdout), _O_BINARY);
+ assert(result);
+ result = _setmode(fileno(stdin), _O_BINARY);
+ UNUSED_IF_ASSERT_DISABLED(result);
+ assert(result);
+#endif
+
+ int stdout_fd = DuplicateFileDescriptor(fileno(stdout));
+ if (stdout_fd == -1) {
+ llvm::logAllUnhandledErrors(
+ llvm::errorCodeToError(llvm::errnoAsErrorCode()), llvm::errs(),
+ "Failed to configure stdout redirect: ");
+ return EXIT_FAILURE;
+ }
+
+ std::FILE *redirectOut = stdout;
+ std::FILE *redirectErr = stderr;
+
+ StreamDescriptor input = StreamDescriptor::from_file(fileno(stdin), false);
+ StreamDescriptor output = StreamDescriptor::from_file(stdout_fd, false);
+
+ return HandleClient("stdin/stdout", std::move(input), std::move(output),
+ redirectOut, redirectErr)
+ ? EXIT_SUCCESS
+ : EXIT_FAILURE;
}
>From 74b5c2e33e3855ce63a608a8f12247526e015416 Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Thu, 30 Jan 2025 10:38:05 -0800
Subject: [PATCH 2/2] Addressing feedback:
* Trying to ensure we more uniformly handle errors. In some cases they were not properly consumed.
* Adjusted IOStream to use the existing `lldb::IOObjectSP` which greatly simplifies the implementation.
---
.../test/tools/lldb-dap/dap_server.py | 25 ++--
.../tools/lldb-dap/server/TestDAP_server.py | 42 +++---
lldb/tools/lldb-dap/DAP.cpp | 16 +--
lldb/tools/lldb-dap/DAP.h | 3 +-
lldb/tools/lldb-dap/DAPForward.h | 2 +
lldb/tools/lldb-dap/IOStream.cpp | 125 +++---------------
lldb/tools/lldb-dap/IOStream.h | 48 ++-----
lldb/tools/lldb-dap/lldb-dap.cpp | 125 +++++++++---------
8 files changed, 132 insertions(+), 254 deletions(-)
diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
index 6a71579030afcf..6d765e10236733 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
@@ -1207,11 +1207,12 @@ def __init__(
out.removeprefix(expected_prefix).rstrip("\r\n").split(",", 1)[0]
)
- if connection.startswith("unix-connect://"): # unix-connect:///path
+ scheme, address = connection.split("://")
+ if scheme == "unix-connect": # unix-connect:///path
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
- s.connect(connection.removeprefix("unix-connect://"))
- elif connection.startswith("connection://"): # connection://[host]:port
- host, port = connection.removeprefix("connection://").rsplit(":", 1)
+ s.connect(address)
+ elif scheme == "connection": # connection://[host]:port
+ host, port = address.rsplit(":", 1)
# create_connection with try both ipv4 and ipv6.
s = socket.create_connection((host.strip("[]"), int(port)))
else:
@@ -1390,10 +1391,9 @@ def main():
)
parser.add_option(
- "--port",
- type="int",
- dest="port",
- help="Attach a socket to a port instead of using STDIN for VSCode",
+ "--connection",
+ dest="connection",
+ help="Attach a socket connection of using STDIN for VSCode",
default=None,
)
@@ -1539,15 +1539,16 @@ def main():
(options, args) = parser.parse_args(sys.argv[1:])
- if options.vscode_path is None and options.port is None:
+ if options.vscode_path is None and options.connection is None:
print(
"error: must either specify a path to a Visual Studio Code "
"Debug Adaptor vscode executable path using the --vscode "
- "option, or a port to attach to for an existing lldb-dap "
- "using the --port option"
+ "option, or using the --connection option"
)
return
- dbg = DebugAdaptorServer(executable=options.vscode_path, port=options.port)
+ dbg = DebugAdaptorServer(
+ executable=options.vscode_path, connection=options.connection
+ )
if options.debug:
raw_input('Waiting for debugger to attach pid "%i"' % (dbg.get_pid()))
if options.replay:
diff --git a/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py b/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
index b37f6976a7591b..59daa6117b44e4 100644
--- a/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
+++ b/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
@@ -3,6 +3,7 @@
"""
import os
+import signal
import tempfile
import dap_server
@@ -12,9 +13,7 @@
class TestDAP_server(lldbdap_testcase.DAPTestCaseBase):
- def do_test_server(self, connection):
- self.build()
-
+ def start_server(self, connection):
log_file_path = self.getBuildArtifact("dap.txt")
server = dap_server.DebugAdaptorServer(
executable=self.lldbDAPExec,
@@ -28,46 +27,36 @@ def cleanup():
self.addTearDownHook(cleanup)
+ return server
+
+ def run_debug_session(self, connection, name):
self.dap_server = dap_server.DebugAdaptorServer(
- connection=server.connection,
+ connection=connection,
)
program = self.getBuildArtifact("a.out")
source = "main.c"
breakpoint_line = line_number(source, "// breakpoint")
- # Initial connection over the connection.
self.launch(
program,
- args=["Alice"],
+ args=[name],
disconnectAutomatically=False,
)
self.set_source_breakpoints(source, [breakpoint_line])
self.continue_to_next_stop()
self.continue_to_exit()
output = self.get_stdout()
- self.assertEqual(output, "Hello Alice!\r\n")
+ self.assertEqual(output, f"Hello {name}!\r\n")
self.dap_server.request_disconnect()
- # Second connection over the connection.
- self.dap_server = dap_server.DebugAdaptorServer(
- connection=server.connection,
- )
- self.launch(
- program,
- args=["Bob"],
- disconnectAutomatically=False,
- )
- self.set_source_breakpoints(source, [breakpoint_line])
- self.continue_to_next_stop()
- self.continue_to_exit()
- output = self.get_stdout()
- self.assertEqual(output, "Hello Bob!\r\n")
-
def test_server_port(self):
"""
Test launching a binary with a lldb-dap in server mode on a specific port.
"""
- self.do_test_server(connection="tcp://localhost:0")
+ self.build()
+ server = self.start_server(connection="tcp://localhost:0")
+ self.run_debug_session(server.connection, "Alice")
+ self.run_debug_session(server.connection, "Bob")
@skipIfWindows
def test_server_unix_socket(self):
@@ -79,6 +68,9 @@ def test_server_unix_socket(self):
def cleanup():
os.unlink(name)
-
self.addTearDownHook(cleanup)
- self.do_test_server(connection="unix://" + name)
+
+ self.build()
+ server = self.start_server(connection="unix://" + name)
+ self.run_debug_session(server.connection, "Alice")
+ self.run_debug_session(server.connection, "Bob")
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index ac20445e3a1cf0..be79fdb7f5a0b4 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -17,7 +17,6 @@
#include "lldb/API/SBListener.h"
#include "lldb/API/SBProcess.h"
#include "lldb/API/SBStream.h"
-#include "lldb/Host/FileSystem.h"
#include "lldb/Utility/Status.h"
#include "lldb/lldb-defines.h"
#include "lldb/lldb-enumerations.h"
@@ -59,11 +58,12 @@ const char DEV_NULL[] = "/dev/null";
namespace lldb_dap {
DAP::DAP(std::string name, llvm::StringRef path, std::ofstream *log,
- ReplMode repl_mode, StreamDescriptor input, StreamDescriptor output)
+ lldb::IOObjectSP input, lldb::IOObjectSP output, ReplMode repl_mode,
+ std::vector<std::string> pre_init_commands)
: name(name), debug_adaptor_path(path), log(log), input(std::move(input)),
output(std::move(output)), broadcaster("lldb-dap"),
- exception_breakpoints(), focus_tid(LLDB_INVALID_THREAD_ID),
- stop_at_entry(false), is_attach(false),
+ exception_breakpoints(), pre_init_commands(pre_init_commands),
+ focus_tid(LLDB_INVALID_THREAD_ID), stop_at_entry(false), is_attach(false),
enable_auto_variable_summaries(false),
enable_synthetic_child_debugging(false),
display_extended_backtrace(false),
@@ -230,10 +230,10 @@ void DAP::StopIO() {
// "Content-Length:" field followed by the length, followed by the raw
// JSON bytes.
void DAP::SendJSON(const std::string &json_str) {
- output.write_full("Content-Length: ");
- output.write_full(llvm::utostr(json_str.size()));
- output.write_full("\r\n\r\n");
- output.write_full(json_str);
+ output.write_full(log, "Content-Length: ");
+ output.write_full(log, llvm::utostr(json_str.size()));
+ output.write_full(log, "\r\n\r\n");
+ output.write_full(log, json_str);
}
// Serialize the JSON value into a string and send the JSON packet to
diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h
index 2190d941ea68ea..263efb344f94e0 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -205,7 +205,8 @@ struct DAP {
std::string last_nonempty_var_expression;
DAP(std::string name, llvm::StringRef path, std::ofstream *log,
- ReplMode repl_mode, StreamDescriptor input, StreamDescriptor output);
+ lldb::IOObjectSP input, lldb::IOObjectSP output, ReplMode repl_mode,
+ std::vector<std::string> pre_init_commands);
~DAP();
DAP(const DAP &rhs) = delete;
void operator=(const DAP &rhs) = delete;
diff --git a/lldb/tools/lldb-dap/DAPForward.h b/lldb/tools/lldb-dap/DAPForward.h
index 0196d83dcd6a91..15e286f3d08dc1 100644
--- a/lldb/tools/lldb-dap/DAPForward.h
+++ b/lldb/tools/lldb-dap/DAPForward.h
@@ -11,6 +11,8 @@
// IWYU pragma: begin_exports
+#include "lldb/lldb-forward.h"
+
namespace lldb_dap {
struct BreakpointBase;
struct ExceptionBreakpoint;
diff --git a/lldb/tools/lldb-dap/IOStream.cpp b/lldb/tools/lldb-dap/IOStream.cpp
index d2e8ec40b0a7b8..01214daae4aa60 100644
--- a/lldb/tools/lldb-dap/IOStream.cpp
+++ b/lldb/tools/lldb-dap/IOStream.cpp
@@ -7,83 +7,21 @@
//===----------------------------------------------------------------------===//
#include "IOStream.h"
-
-#if defined(_WIN32)
-#include <io.h>
-#else
-#include <netinet/in.h>
-#include <sys/socket.h>
-#include <unistd.h>
-#endif
-
+#include "lldb/Utility/IOObject.h" // IWYU pragma: keep
+#include "lldb/Utility/Status.h"
#include <fstream>
-#include <string>
using namespace lldb_dap;
-StreamDescriptor::StreamDescriptor() = default;
-
-StreamDescriptor::StreamDescriptor(StreamDescriptor &&other) {
- *this = std::move(other);
-}
-
-StreamDescriptor::~StreamDescriptor() {
- if (!m_close)
- return;
-
- if (m_is_socket)
-#if defined(_WIN32)
- ::closesocket(m_socket);
-#else
- ::close(m_socket);
-#endif
- else
- ::close(m_fd);
-}
-
-StreamDescriptor &StreamDescriptor::operator=(StreamDescriptor &&other) {
- m_close = other.m_close;
- other.m_close = false;
- m_is_socket = other.m_is_socket;
- if (m_is_socket)
- m_socket = other.m_socket;
- else
- m_fd = other.m_fd;
- return *this;
-}
-
-StreamDescriptor StreamDescriptor::from_socket(SOCKET s, bool close) {
- StreamDescriptor sd;
- sd.m_is_socket = true;
- sd.m_socket = s;
- sd.m_close = close;
- return sd;
-}
-
-StreamDescriptor StreamDescriptor::from_file(int fd, bool close) {
- StreamDescriptor sd;
- sd.m_is_socket = false;
- sd.m_fd = fd;
- sd.m_close = close;
- return sd;
-}
-
-bool OutputStream::write_full(llvm::StringRef str) {
- while (!str.empty()) {
- int bytes_written = 0;
- if (descriptor.m_is_socket)
- bytes_written = ::send(descriptor.m_socket, str.data(), str.size(), 0);
- else
- bytes_written = ::write(descriptor.m_fd, str.data(), str.size());
-
- if (bytes_written < 0) {
- if (errno == EINTR || errno == EAGAIN)
- continue;
- return false;
- }
- str = str.drop_front(bytes_written);
+bool OutputStream::write_full(std::ofstream *log, llvm::StringRef str) {
+ size_t num_bytes = str.size();
+ auto status = descriptor->Write(str.data(), num_bytes);
+ if (status.Fail()) {
+ std::string error = llvm::toString(status.takeError());
+ if (log)
+ *log << error << "\n";
+ return false;
}
-
return true;
}
@@ -91,42 +29,17 @@ bool InputStream::read_full(std::ofstream *log, size_t length,
std::string &text) {
std::string data;
data.resize(length);
-
char *ptr = &data[0];
- while (length != 0) {
- int bytes_read = 0;
- if (descriptor.m_is_socket)
- bytes_read = ::recv(descriptor.m_socket, ptr, length, 0);
- else
- bytes_read = ::read(descriptor.m_fd, ptr, length);
+ size_t num_bytes = length;
+ auto status = descriptor->Read(ptr, num_bytes);
- if (bytes_read == 0) {
- if (log)
- *log << "End of file (EOF) reading from input file.\n";
- return false;
- }
- if (bytes_read < 0) {
- int reason = 0;
-#if defined(_WIN32)
- if (descriptor.m_is_socket)
- reason = WSAGetLastError();
- else
- reason = errno;
-#else
- reason = errno;
- if (reason == EINTR || reason == EAGAIN)
- continue;
-#endif
-
- if (log)
- *log << "Error " << reason << " reading from input file.\n";
- return false;
- }
-
- assert(bytes_read >= 0 && (size_t)bytes_read <= length);
- ptr += bytes_read;
- length -= bytes_read;
+ if (status.Fail() || length != num_bytes) {
+ std::string error = llvm::toString(status.takeError());
+ if (log)
+ *log << error << "\n";
+ return false;
}
+
text += data;
return true;
}
@@ -151,7 +64,7 @@ bool InputStream::read_expected(std::ofstream *log, llvm::StringRef expected) {
if (expected != result) {
if (log)
*log << "Warning: Expected '" << expected.str() << "', got '" << result
- << "\n";
+ << "'\n";
}
return true;
}
diff --git a/lldb/tools/lldb-dap/IOStream.h b/lldb/tools/lldb-dap/IOStream.h
index 74889eb2e5a866..7d56e08afdfca6 100644
--- a/lldb/tools/lldb-dap/IOStream.h
+++ b/lldb/tools/lldb-dap/IOStream.h
@@ -9,50 +9,17 @@
#ifndef LLDB_TOOLS_LLDB_DAP_IOSTREAM_H
#define LLDB_TOOLS_LLDB_DAP_IOSTREAM_H
-#if defined(_WIN32)
-// We need to #define NOMINMAX in order to skip `min()` and `max()` macro
-// definitions that conflict with other system headers.
-// We also need to #undef GetObject (which is defined to GetObjectW) because
-// the JSON code we use also has methods named `GetObject()` and we conflict
-// against these.
-#define NOMINMAX
-#include <windows.h>
-#else
-typedef int SOCKET;
-#endif
-
+#include "lldb/lldb-forward.h"
#include "llvm/ADT/StringRef.h"
-
#include <fstream>
-#include <string>
-// Windows requires different system calls for dealing with sockets and other
-// types of files, so we can't simply have one code path that just uses read
-// and write everywhere. So we need an abstraction in order to allow us to
-// treat them identically.
namespace lldb_dap {
-struct StreamDescriptor {
- StreamDescriptor();
- ~StreamDescriptor();
- StreamDescriptor(StreamDescriptor &&other);
-
- StreamDescriptor &operator=(StreamDescriptor &&other);
-
- static StreamDescriptor from_socket(SOCKET s, bool close);
- static StreamDescriptor from_file(int fd, bool close);
-
- bool m_is_socket = false;
- bool m_close = false;
- union {
- int m_fd;
- SOCKET m_socket;
- };
-};
struct InputStream {
- StreamDescriptor descriptor;
+ // IOObject represent either a FD or socket.
+ lldb::IOObjectSP descriptor;
- explicit InputStream(StreamDescriptor descriptor)
+ explicit InputStream(lldb::IOObjectSP descriptor)
: descriptor(std::move(descriptor)) {}
bool read_full(std::ofstream *log, size_t length, std::string &text);
@@ -63,12 +30,13 @@ struct InputStream {
};
struct OutputStream {
- StreamDescriptor descriptor;
+ // IOObject represent either a FD or socket.
+ lldb::IOObjectSP descriptor;
- explicit OutputStream(StreamDescriptor descriptor)
+ explicit OutputStream(lldb::IOObjectSP descriptor)
: descriptor(std::move(descriptor)) {}
- bool write_full(llvm::StringRef str);
+ bool write_full(std::ofstream *log, llvm::StringRef str);
};
} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index 1a5b024dcf3f36..803b28b3328eec 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -21,12 +21,13 @@
#include "lldb/API/SBStream.h"
#include "lldb/API/SBStringList.h"
#include "lldb/Host/Config.h"
+#include "lldb/Host/File.h"
#include "lldb/Host/MainLoop.h"
#include "lldb/Host/MainLoopBase.h"
#include "lldb/Host/Socket.h"
-#include "lldb/Host/common/TCPSocket.h"
-#include "lldb/Host/posix/DomainSocket.h"
#include "lldb/Utility/Status.h"
+#include "lldb/Utility/UriParser.h"
+#include "lldb/lldb-forward.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/DenseSet.h"
@@ -44,6 +45,7 @@
#include "llvm/Support/Path.h"
#include "llvm/Support/PrettyStackTrace.h"
#include "llvm/Support/Signals.h"
+#include "llvm/Support/ThreadPool.h"
#include "llvm/Support/raw_ostream.h"
#include <algorithm>
#include <array>
@@ -59,6 +61,7 @@
#include <optional>
#include <ostream>
#include <set>
+#include <string>
#include <sys/stat.h>
#include <sys/types.h>
#include <thread>
@@ -95,6 +98,8 @@ typedef int socklen_t;
using namespace lldb_dap;
using lldb_private::MainLoop;
+using lldb_private::MainLoopBase;
+using lldb_private::NativeFile;
using lldb_private::NativeSocket;
using lldb_private::Socket;
using lldb_private::Status;
@@ -4835,10 +4840,10 @@ static void printHelp(LLDBDAPOptTable &table, llvm::StringRef tool_name) {
The debug adapter can be started in two modes.
Running lldb-dap without any arguments will start communicating with the
- parent over stdio. Passing a port number causes lldb-dap to start listening
- for connections on that port.
+ parent over stdio. Passing a --connection URI will cause lldb-dap to listen
+ for a connection in the specified mode.
- lldb-dap -p <port>
+ lldb-dap --connection connection://localhost:<port>
Passing --wait-for-debugger will pause the process at startup and wait for a
debugger to attach to the process.
@@ -4931,24 +4936,25 @@ static int DuplicateFileDescriptor(int fd) {
}
static llvm::Expected<std::pair<Socket::SocketProtocol, std::string>>
-parseConnection(llvm::StringRef conn) {
- if (conn.contains("://")) {
- llvm::StringRef scheme, rest;
- std::tie(scheme, rest) = conn.split("://");
+validateConnection(llvm::StringRef conn) {
+ auto uri = lldb_private::URI::Parse(conn);
+
+ if (uri && (uri->scheme == "tcp" || uri->scheme == "connect" ||
+ !uri->hostname.empty() || uri->port)) {
+ return std::make_pair(
+ Socket::ProtocolTcp,
+ formatv("[{0}]:{1}", uri->hostname.empty() ? "0.0.0.0" : uri->hostname,
+ uri->port.value_or(0)));
+ }
- if (scheme == "unix" || scheme == "unix-connect") {
- return std::make_pair(Socket::ProtocolUnixDomain, rest.str());
- }
- if (scheme == "tcp" || scheme == "connect") {
- return std::make_pair(Socket::ProtocolTcp, rest.str());
- }
- } else if (conn.starts_with("/")) {
- return std::make_pair(Socket::ProtocolUnixDomain, conn.str());
- } else if (conn.contains(":")) {
- return std::make_pair(Socket::ProtocolTcp, conn.str());
+ if (uri && (uri->scheme == "unix" || uri->scheme == "unix-connect" ||
+ uri->path != "/")) {
+ return std::make_pair(Socket::ProtocolUnixDomain, uri->path.str());
}
+
return llvm::createStringError(
- "expected '[unix://]/path' or '[tcp://][host]:port', got '%s'.",
+ "Unsupported connection specifier, expected 'unix-connect:///path' or "
+ "'connect://[host]:port', got '%s'.",
conn.str().c_str());
}
@@ -5060,40 +5066,40 @@ int main(int argc, char *argv[]) {
}
auto HandleClient =
- [=, log = log.get()](std::string name, StreamDescriptor input,
- StreamDescriptor output, std::FILE *redirectOut,
+ [=, log = log.get()](std::string name, lldb::IOObjectSP input,
+ lldb::IOObjectSP output, std::FILE *redirectOut,
std::FILE *redirectErr) -> bool {
- DAP dap = DAP(name, program_path.str(), log, default_repl_mode,
- std::move(input), std::move(output));
+ DAP dap = DAP(name, program_path.str(), log, std::move(input),
+ std::move(output), default_repl_mode, pre_init_commands);
// stdout/stderr redirection to the IDE's console
if (auto Err = dap.ConfigureIO(redirectOut, redirectErr)) {
llvm::logAllUnhandledErrors(
std::move(Err), llvm::errs(),
"Failed to configure lldb-dap IO operations: ");
- return EXIT_FAILURE;
+ return false;
}
RegisterRequestCallbacks(dap);
- dap.pre_init_commands = pre_init_commands;
-
// used only by TestVSCode_redirection_to_console.py
if (getenv("LLDB_DAP_TEST_STDOUT_STDERR_REDIRECTION") != nullptr)
redirection_test();
if (auto Err = dap.Loop()) {
+ std::string errorMessage = llvm::toString(std::move(Err));
if (log)
- *log << "Transport Error: " << llvm::toString(std::move(Err)) << "\n";
+ *log << "Transport Error: " << errorMessage << "\n";
return false;
}
return true;
};
if (!connection.empty()) {
- auto maybeProtoclAndName = parseConnection(connection);
+ auto maybeProtoclAndName = validateConnection(connection);
if (auto Err = maybeProtoclAndName.takeError()) {
- llvm::errs() << "Invalid connection specification " << Err << "\n";
+ llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
+ "Invalid connection: ");
return EXIT_FAILURE;
}
@@ -5104,15 +5110,15 @@ int main(int argc, char *argv[]) {
Status error;
std::unique_ptr<Socket> listener = Socket::Create(protocol, error);
if (error.Fail()) {
- llvm::errs() << "Failed to create listener for protocol "
- << Socket::FindSchemeByProtocol(protocol)
- << ", error: " << error.takeError() << "\n";
+ llvm::logAllUnhandledErrors(error.takeError(), llvm::errs(),
+ "Failed to create socket listener: ");
return EXIT_FAILURE;
}
error = listener->Listen(name, /* backlog */ 5);
if (error.Fail()) {
- llvm::errs() << "Failed to listen, error: " << error.takeError() << "\n";
+ llvm::logAllUnhandledErrors(error.takeError(), llvm::errs(),
+ "Failed to listen for connections: ");
return EXIT_FAILURE;
}
@@ -5127,44 +5133,42 @@ int main(int argc, char *argv[]) {
llvm::outs().flush();
MainLoop mainloop;
- mainloop.RegisterSignal(
- SIGHUP, [](auto &RL) { RL.RequestTermination(); }, error);
-
unsigned int clientCount = 0;
- auto OnAccept = [=, log = log.get(),
- &clientCount](std::unique_ptr<Socket> client) {
+ llvm::DefaultThreadPool pool(llvm::optimal_concurrency());
+ auto OnAccept = [=, &pool, &clientCount,
+ log = log.get()](std::unique_ptr<Socket> client) {
std::string name = llvm::formatv("client_{0}", clientCount++).str();
if (log) {
auto now = std::chrono::duration<double>(
std::chrono::system_clock::now().time_since_epoch());
- *log << llvm::formatv("{0:f9} client connected: {1}", now.count(), name)
- .str()
- << std::endl;
+ *log << llvm::formatv("{0:f9}", now.count()).str()
+ << " client connected: " << name << "\n";
}
- // Start a thread for each connection, unblocking the listening thread.
- std::thread([=, client = std::move(client)]() {
- HandleClient(
- name,
- StreamDescriptor::from_socket(client->GetNativeSocket(), false),
- StreamDescriptor::from_socket(client->GetNativeSocket(), false),
- /*=redirectOut*/ nullptr, /*=redirectErr*/ nullptr);
- }).detach();
+
+ // Move the client into the connection pool to unblock accepting the next
+ // client.
+ lldb::IOObjectSP IO(std::move(client));
+ pool.async(HandleClient, name, IO, IO,
+ /*redirectOut=*/nullptr, /*redirectErr=*/nullptr);
};
auto handles = listener->Accept(mainloop, OnAccept);
if (auto Err = handles.takeError()) {
- llvm::errs() << "failed to register accept() with the main loop: " << Err
- << "\n";
+ llvm::logAllUnhandledErrors(error.takeError(), llvm::errs(),
+ "Failed to register accept handler: ");
return EXIT_FAILURE;
}
error = mainloop.Run();
if (error.Fail()) {
- llvm::errs() << "failed to accept()" << error.takeError() << "\n";
+ llvm::logAllUnhandledErrors(error.takeError(), llvm::errs(),
+ "Main run loop failed: ");
return EXIT_FAILURE;
}
+ pool.wait();
+
return EXIT_SUCCESS;
}
@@ -5187,14 +5191,11 @@ int main(int argc, char *argv[]) {
return EXIT_FAILURE;
}
- std::FILE *redirectOut = stdout;
- std::FILE *redirectErr = stderr;
-
- StreamDescriptor input = StreamDescriptor::from_file(fileno(stdin), false);
- StreamDescriptor output = StreamDescriptor::from_file(stdout_fd, false);
+ lldb::IOObjectSP input = std::make_shared<NativeFile>(stdin, false);
+ lldb::IOObjectSP output = std::make_shared<NativeFile>(
+ stdout_fd, lldb_private::NativeFile::eOpenOptionWriteOnly, false);
- return HandleClient("stdin/stdout", std::move(input), std::move(output),
- redirectOut, redirectErr)
- ? EXIT_SUCCESS
- : EXIT_FAILURE;
+ bool status = HandleClient("stdin/stdout", std::move(input),
+ std::move(output), stdout, stderr);
+ return status ? EXIT_SUCCESS : EXIT_FAILURE;
}
More information about the lldb-commits
mailing list