[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
Tue Feb 4 10:57:51 PST 2025
https://github.com/ashgti updated https://github.com/llvm/llvm-project/pull/116392
>From 88a8522f1b29b2ff392974322acdb722b7e00b70 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 | 74 ++++-
.../test/tools/lldb-dap/lldbdap_testcase.py | 6 +-
lldb/test/API/tools/lldb-dap/server/Makefile | 3 +
.../tools/lldb-dap/server/TestDAP_server.py | 77 +++++
lldb/test/API/tools/lldb-dap/server/main.c | 10 +
lldb/test/Shell/DAP/TestOptions.test | 4 +-
lldb/tools/lldb-dap/DAP.cpp | 18 +-
lldb/tools/lldb-dap/DAP.h | 6 +-
lldb/tools/lldb-dap/DAPForward.h | 2 +
lldb/tools/lldb-dap/Options.td | 22 +-
lldb/tools/lldb-dap/lldb-dap.cpp | 283 +++++++++++-------
11 files changed, 358 insertions(+), 147 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..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
@@ -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,62 @@ 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]
+ )
+
+ scheme, address = connection.split("://")
+ if scheme == "unix-connect": # unix-connect:///path
+ s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ 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:
+ 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):
@@ -1349,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,
)
@@ -1498,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/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..9597f73b0659be
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
@@ -0,0 +1,77 @@
+"""
+Test lldb-dap server integration.
+"""
+
+import os
+import signal
+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 start_server(self, connection):
+ 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)
+
+ return server
+
+ def run_debug_session(self, connection, name):
+ self.dap_server = dap_server.DebugAdaptorServer(
+ connection=connection,
+ )
+ program = self.getBuildArtifact("a.out")
+ source = "main.c"
+ breakpoint_line = line_number(source, "// breakpoint")
+
+ self.launch(
+ program,
+ 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, f"Hello {name}!\r\n")
+ self.dap_server.request_disconnect()
+
+ def test_server_port(self):
+ """
+ Test launching a binary with a lldb-dap in server mode on a specific port.
+ """
+ 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):
+ """
+ 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.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/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..11a4db5c7f0f48 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"
@@ -58,12 +57,13 @@ 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,
+ StreamDescriptor input, StreamDescriptor 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),
@@ -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..7c28dbf801179f 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,9 @@ 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,
+ StreamDescriptor input, StreamDescriptor 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/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..2c7fdb0f0d3cfb 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/Socket.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"
#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/ThreadPool.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 <string>
#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,9 @@ typedef int socklen_t;
#endif
using namespace lldb_dap;
+using lldb_private::NativeSocket;
+using lldb_private::Socket;
+using lldb_private::Status;
namespace {
using namespace llvm::opt;
@@ -142,43 +156,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
@@ -4857,10 +4834,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.
@@ -4952,6 +4929,29 @@ static int DuplicateFileDescriptor(int fd) {
#endif
}
+static llvm::Expected<std::pair<Socket::SocketProtocol, std::string>>
+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 (uri && (uri->scheme == "unix" || uri->scheme == "unix-connect" ||
+ uri->path != "/")) {
+ return std::make_pair(Socket::ProtocolUnixDomain, uri->path.str());
+ }
+
+ return llvm::createStringError(
+ "Unsupported connection specifier, expected 'unix-connect:///path' or "
+ "'connect://[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,144 @@ 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 RunDAP = [](llvm::StringRef program_path, ReplMode repl_mode,
+ std::vector<std::string> pre_init_commands,
+ std::ofstream *log, std::string name, StreamDescriptor input,
+ StreamDescriptor output, std::FILE *redirectOut = nullptr,
+ std::FILE *redirectErr = nullptr) -> bool {
+ DAP dap = DAP(name, program_path, log, std::move(input), std::move(output),
+ repl_mode, pre_init_commands);
- 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 false;
+ }
+
+ RegisterRequestCallbacks(dap);
+
+ // 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: " << errorMessage << "\n";
+ return false;
+ }
+ return true;
+ };
+
+ if (!connection.empty()) {
+ auto maybeProtoclAndName = validateConnection(connection);
+ if (auto Err = maybeProtoclAndName.takeError()) {
+ llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
+ "Invalid connection: ");
return EXIT_FAILURE;
}
- redirectOut = stdout;
- redirectErr = stderr;
+ Socket::SocketProtocol protocol;
+ std::string name;
+ std::tie(protocol, name) = *maybeProtoclAndName;
- input = StreamDescriptor::from_file(fileno(stdin), false);
- output = StreamDescriptor::from_file(stdout_fd, false);
- }
+ Status error;
+ std::unique_ptr<Socket> listener = Socket::Create(protocol, error);
+ if (error.Fail()) {
+ llvm::logAllUnhandledErrors(error.takeError(), llvm::errs(),
+ "Failed to create socket listener: ");
+ return EXIT_FAILURE;
+ }
- DAP dap = DAP(program_path.str(), log.get(), default_repl_mode,
- std::move(input), std::move(output));
+ error = listener->Listen(name, /* backlog */ 5);
+ if (error.Fail()) {
+ llvm::logAllUnhandledErrors(error.takeError(), llvm::errs(),
+ "Failed to listen for connections: ");
+ return EXIT_FAILURE;
+ }
- // 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;
- }
+ std::string address =
+ llvm::join(listener->GetListeningConnectionURI(), ", ");
+ if (log)
+ *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();
+
+ llvm::DefaultThreadPool pool(llvm::optimal_concurrency());
+ unsigned int clientCount = 0;
+
+ while (true) {
+ Socket *accepted_socket;
+ error = listener->Accept(/*timeout=*/std::nullopt, accepted_socket);
+ if (error.Fail()) {
+ llvm::logAllUnhandledErrors(error.takeError(), llvm::errs(),
+ "accept failed: ");
+ return EXIT_FAILURE;
+ }
- RegisterRequestCallbacks(dap);
+ 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}", now.count()).str()
+ << " client connected: " << name << "\n";
+ }
- for (const std::string &arg :
- input_args.getAllArgValues(OPT_pre_init_command)) {
- dap.pre_init_commands.push_back(arg);
+ // Move the client into the connection pool to unblock accepting the next
+ // client.
+ pool.async(
+ [=](Socket *accepted_socket, std::ofstream *log) {
+ StreamDescriptor input = StreamDescriptor::from_socket(
+ accepted_socket->GetNativeSocket(), false);
+ // Close the output last for the best chance at error reporting.
+ StreamDescriptor output = StreamDescriptor::from_socket(
+ accepted_socket->GetNativeSocket(), true);
+ RunDAP(program_path, default_repl_mode, pre_init_commands, log,
+ name, std::move(input), std::move(output));
+ },
+ accepted_socket, log.get());
+ }
+
+ pool.wait();
+
+ return EXIT_SUCCESS;
}
- // used only by TestVSCode_redirection_to_console.py
- if (getenv("LLDB_DAP_TEST_STDOUT_STDERR_REDIRECTION") != nullptr)
- redirection_test();
+#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
- bool CleanExit = true;
- if (auto Err = dap.Loop()) {
- if (log)
- *log << "Transport Error: " << llvm::toString(std::move(Err)) << "\n";
- CleanExit = false;
+ 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;
}
- return CleanExit ? EXIT_SUCCESS : EXIT_FAILURE;
+ StreamDescriptor input = StreamDescriptor::from_file(fileno(stdin), false);
+ StreamDescriptor output = StreamDescriptor::from_file(stdout_fd, false);
+
+ bool status = RunDAP(program_path, default_repl_mode, pre_init_commands,
+ log.get(), "stdin/stdout", std::move(input),
+ std::move(output), stdout, stderr);
+ return status ? EXIT_SUCCESS : EXIT_FAILURE;
}
>From 2fe4f85f7620be29c28452783a3d2cffafb91c31 Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Tue, 4 Feb 2025 10:57:32 -0800
Subject: [PATCH 2/2] Applying review comments.
---
lldb/test/API/tools/lldb-dap/server/Makefile | 2 +-
lldb/tools/lldb-dap/DAP.cpp | 2 +-
lldb/tools/lldb-dap/DAPForward.h | 2 --
lldb/tools/lldb-dap/lldb-dap.cpp | 5 ++---
4 files changed, 4 insertions(+), 7 deletions(-)
diff --git a/lldb/test/API/tools/lldb-dap/server/Makefile b/lldb/test/API/tools/lldb-dap/server/Makefile
index 451278a0946ef2..10495940055b63 100644
--- a/lldb/test/API/tools/lldb-dap/server/Makefile
+++ b/lldb/test/API/tools/lldb-dap/server/Makefile
@@ -1,3 +1,3 @@
C_SOURCES := main.c
-include Makefile.rules
\ No newline at end of file
+include Makefile.rules
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index 11a4db5c7f0f48..cfaa8ad53c6340 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -62,7 +62,7 @@ DAP::DAP(std::string name, llvm::StringRef path, std::ofstream *log,
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(), pre_init_commands(pre_init_commands),
+ exception_breakpoints(), pre_init_commands(std::move(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),
diff --git a/lldb/tools/lldb-dap/DAPForward.h b/lldb/tools/lldb-dap/DAPForward.h
index 15e286f3d08dc1..0196d83dcd6a91 100644
--- a/lldb/tools/lldb-dap/DAPForward.h
+++ b/lldb/tools/lldb-dap/DAPForward.h
@@ -11,8 +11,6 @@
// IWYU pragma: begin_exports
-#include "lldb/lldb-forward.h"
-
namespace lldb_dap {
struct BreakpointBase;
struct ExceptionBreakpoint;
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index 2c7fdb0f0d3cfb..fd86c8d8b3a9b5 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -5110,7 +5110,7 @@ int main(int argc, char *argv[]) {
return EXIT_FAILURE;
}
- error = listener->Listen(name, /* backlog */ 5);
+ error = listener->Listen(name, /*backlog=*/5);
if (error.Fail()) {
llvm::logAllUnhandledErrors(error.takeError(), llvm::errs(),
"Failed to listen for connections: ");
@@ -5127,9 +5127,8 @@ int main(int argc, char *argv[]) {
// address.
llvm::outs().flush();
- llvm::DefaultThreadPool pool(llvm::optimal_concurrency());
+ llvm::DefaultThreadPool pool;
unsigned int clientCount = 0;
-
while (true) {
Socket *accepted_socket;
error = listener->Accept(/*timeout=*/std::nullopt, accepted_socket);
More information about the lldb-commits
mailing list