https://github.com/ashgti updated https://github.com/llvm/llvm-project/pull/116392
>From 61fefbe13eaf98600d308e0873e98134fbadc452 Mon Sep 17 00:00:00 2001 From: John Harrison <harj...@google.com> Date: Tue, 28 Jan 2025 12:39:38 -0800 Subject: [PATCH] [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 instanceo 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; } _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits