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 <harj...@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 <harj...@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); _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits