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 <harj...@google.com> Date: Tue, 28 Jan 2025 12:39:38 -0800 Subject: [PATCH 1/3] [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 <harj...@google.com> Date: Thu, 30 Jan 2025 10:38:05 -0800 Subject: [PATCH 2/3] 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; } >From 876ddbe894af7ab41b6542a3d26939c6633985b1 Mon Sep 17 00:00:00 2001 From: John Harrison <harj...@google.com> Date: Thu, 30 Jan 2025 10:49:56 -0800 Subject: [PATCH 3/3] Applying formatting to python files. --- lldb/test/API/tools/lldb-dap/server/TestDAP_server.py | 1 + 1 file changed, 1 insertion(+) 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 59daa6117b44e4..9597f73b0659be 100644 --- a/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py +++ b/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py @@ -68,6 +68,7 @@ def test_server_unix_socket(self): def cleanup(): os.unlink(name) + self.addTearDownHook(cleanup) self.build() _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits