https://github.com/ashgti created https://github.com/llvm/llvm-project/pull/116392
This refactors the port listening mode to allocate a new DAP object for each connection, allowing multiple connections to run concurrently. >From 43b8fc2a222e5460dd56782e31bd1b1ac399e03e Mon Sep 17 00:00:00 2001 From: John Harrison <harj...@google.com> Date: Fri, 15 Nov 2024 09:56:43 -0500 Subject: [PATCH] [lldb-da] Refactoring lldb-dap port listening mode to allow multiple connections. This refactors the port listening mode to allocate a new DAP object for each connection, allowing multiple connections to run concurrently. --- .../Python/lldbsuite/test/lldbtest.py | 8 + .../test/tools/lldb-dap/dap_server.py | 75 +++- .../test/tools/lldb-dap/lldbdap_testcase.py | 32 +- lldb/test/API/tools/lldb-dap/server/Makefile | 3 + .../tools/lldb-dap/server/TestDAP_server.py | 66 ++++ lldb/test/API/tools/lldb-dap/server/main.c | 6 + lldb/tools/lldb-dap/DAP.cpp | 23 +- lldb/tools/lldb-dap/DAP.h | 50 +-- lldb/tools/lldb-dap/Options.td | 9 + lldb/tools/lldb-dap/OutputRedirector.cpp | 7 +- lldb/tools/lldb-dap/OutputRedirector.h | 12 +- lldb/tools/lldb-dap/lldb-dap.cpp | 366 ++++++++++++++---- 12 files changed, 506 insertions(+), 151 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/lldbtest.py b/lldb/packages/Python/lldbsuite/test/lldbtest.py index 8884ef5933ada8..a899854bb5ae14 100644 --- a/lldb/packages/Python/lldbsuite/test/lldbtest.py +++ b/lldb/packages/Python/lldbsuite/test/lldbtest.py @@ -39,6 +39,7 @@ import signal from subprocess import * import sys +import socket import time import traceback @@ -250,6 +251,13 @@ def which(program): return None +def pickrandomport(): + """Returns a random open port.""" + with socket.socket() as sock: + sock.bind(("", 0)) + return sock.getsockname()[1] + + class ValueCheck: def __init__( self, 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..e4a53fe0d45907 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 @@ -1154,34 +1154,38 @@ class DebugAdaptorServer(DebugCommunication): def __init__( self, executable=None, + launch=True, port=None, + unix_socket=None, init_commands=[], log_file=None, env=None, ): self.process = None - if executable is not None: - adaptor_env = os.environ.copy() - if env is not None: - adaptor_env.update(env) - - if log_file: - adaptor_env["LLDBDAP_LOG"] = log_file - self.process = subprocess.Popen( - [executable], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - env=adaptor_env, + if launch: + self.process = DebugAdaptorServer.launch( + executable, + port=port, + unix_socket=unix_socket, + log_file=log_file, + env=env, ) - DebugCommunication.__init__( - self, self.process.stdout, self.process.stdin, init_commands, log_file - ) - elif port is not None: + + if port: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("127.0.0.1", port)) DebugCommunication.__init__( - self, s.makefile("r"), s.makefile("w"), init_commands + self, s.makefile("rb"), s.makefile("wb"), init_commands, log_file + ) + elif unix_socket: + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + s.connect(unix_socket) + DebugCommunication.__init__( + self, s.makefile("rb"), s.makefile("wb"), init_commands, log_file + ) + else: + DebugCommunication.__init__( + self, self.process.stdout, self.process.stdin, init_commands, log_file ) def get_pid(self): @@ -1196,6 +1200,39 @@ def terminate(self): self.process.wait() self.process = None + @classmethod + def launch( + cls, executable: str, /, port=None, unix_socket=None, log_file=None, env=None + ) -> subprocess.Popen: + adaptor_env = os.environ.copy() + if env: + adaptor_env.update(env) + + if log_file: + adaptor_env["LLDBDAP_LOG"] = log_file + + args = [executable] + if port: + args.append("--port") + args.append(str(port)) + elif unix_socket: + args.append("--unix-socket") + args.append(unix_socket) + + proc = subprocess.Popen( + args, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=sys.stdout, + env=adaptor_env, + ) + + if port or unix_socket: + # Wait for the server to startup. + time.sleep(0.1) + + return proc + def attach_options_specified(options): if options.pid is not None: 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..3fcc08e9ff55cb 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 @@ -13,7 +13,7 @@ class DAPTestCaseBase(TestBase): 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, env=None, launch=True, port=None, unix_socket=None): """Create the Visual Studio Code debug adaptor""" self.assertTrue( is_exe(self.lldbDAPExec), "lldb-dap must exist and be executable" @@ -21,14 +21,28 @@ def create_debug_adaptor(self, lldbDAPEnv=None): log_file_path = self.getBuildArtifact("dap.txt") self.dap_server = dap_server.DebugAdaptorServer( executable=self.lldbDAPExec, + launch=launch, + port=port, + unix_socket=unix_socket, init_commands=self.setUpCommands(), log_file=log_file_path, - env=lldbDAPEnv, + env=env, ) - def build_and_create_debug_adaptor(self, lldbDAPEnv=None): + def build_and_create_debug_adaptor( + self, + lldbDAPEnv=None, + lldbDAPLaunch=True, + lldbDAPPort=None, + lldbDAPUnixSocket=None, + ): self.build() - self.create_debug_adaptor(lldbDAPEnv) + self.create_debug_adaptor( + env=lldbDAPEnv, + launch=lldbDAPLaunch, + port=lldbDAPPort, + unix_socket=lldbDAPUnixSocket, + ) def set_source_breakpoints(self, source_path, lines, data=None): """Sets source breakpoints and returns an array of strings containing @@ -475,11 +489,19 @@ def build_and_launch( customThreadFormat=None, launchCommands=None, expectFailure=False, + lldbDAPPort=None, + lldbDAPUnixSocket=None, + lldbDAPLaunch=True, ): """Build the default Makefile target, create the DAP debug adaptor, and launch the process. """ - self.build_and_create_debug_adaptor(lldbDAPEnv) + self.build_and_create_debug_adaptor( + lldbDAPEnv=lldbDAPEnv, + lldbDAPLaunch=lldbDAPLaunch, + lldbDAPPort=lldbDAPPort, + lldbDAPUnixSocket=lldbDAPUnixSocket, + ) self.assertTrue(os.path.exists(program), "executable must exist") return self.launch( 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..10495940055b63 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/server/Makefile @@ -0,0 +1,3 @@ +C_SOURCES := main.c + +include Makefile.rules 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..46b992a77a4815 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py @@ -0,0 +1,66 @@ +""" +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, port=None, unix_socket=None): + log_file_path = self.getBuildArtifact("dap.txt") + server = dap_server.DebugAdaptorServer.launch( + self.lldbDAPExec, port=port, unix_socket=unix_socket, log_file=log_file_path + ) + + def cleanup(): + server.terminate() + server.wait() + + self.addTearDownHook(cleanup) + + self.build() + program = self.getBuildArtifact("a.out") + source = "main.c" + breakpoint_line = line_number(source, "// breakpoint") + + # Initial connection over the port. + self.create_debug_adaptor(launch=False, port=port, unix_socket=unix_socket) + self.launch( + program, + disconnectAutomatically=False, + ) + self.set_source_breakpoints(source, [breakpoint_line]) + self.continue_to_next_stop() + self.continue_to_exit() + output = self.get_stdout() + self.assertEquals(output, "hello world!\r\n") + self.dap_server.request_disconnect() + + # Second connection over the port. + self.create_debug_adaptor(launch=False, port=port, unix_socket=unix_socket) + self.launch(program) + self.set_source_breakpoints(source, [breakpoint_line]) + self.continue_to_next_stop() + self.continue_to_exit() + output = self.get_stdout() + self.assertEquals(output, "hello world!\r\n") + + def test_server_port(self): + """ + Test launching a binary with a lldb-dap in server mode on a specific port. + """ + port = pickrandomport() + self.do_test_server(port=port) + + def test_server_unix_socket(self): + """ + Test launching a binary with a lldb-dap in server mode on a unix socket. + """ + dir = tempfile.gettempdir() + self.do_test_server(unix_socket=dir + "/dap-connection-" + str(os.getpid())) 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..9a6326f3b57d45 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/server/main.c @@ -0,0 +1,6 @@ +#include <stdio.h> + +int main(int argc, char const *argv[]) { + printf("hello world!\n"); // breakpoint 1 + return 0; +} \ No newline at end of file diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp index 35250d9eef608a..8998036fbedf6b 100644 --- a/lldb/tools/lldb-dap/DAP.cpp +++ b/lldb/tools/lldb-dap/DAP.cpp @@ -32,10 +32,11 @@ using namespace lldb_dap; namespace lldb_dap { -DAP::DAP(llvm::StringRef path, ReplMode repl_mode) +DAP::DAP(llvm::StringRef path, std::shared_ptr<std::ofstream> log, + ReplMode repl_mode, std::vector<std::string> pre_init_commands) : debug_adaptor_path(path), broadcaster("lldb-dap"), - exception_breakpoints(), focus_tid(LLDB_INVALID_THREAD_ID), - stop_at_entry(false), is_attach(false), + log(log), 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), @@ -43,21 +44,7 @@ DAP::DAP(llvm::StringRef path, ReplMode repl_mode) configuration_done_sent(false), waiting_for_run_in_terminal(false), progress_event_reporter( [&](const ProgressEvent &event) { SendJSON(event.ToJSON()); }), - reverse_request_seq(0), repl_mode(repl_mode) { - const char *log_file_path = getenv("LLDBDAP_LOG"); -#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 - if (log_file_path) - log.reset(new std::ofstream(log_file_path)); -} + reverse_request_seq(0), repl_mode(repl_mode) {} DAP::~DAP() = default; diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h index ae496236f13369..ec70da67edac14 100644 --- a/lldb/tools/lldb-dap/DAP.h +++ b/lldb/tools/lldb-dap/DAP.h @@ -9,36 +9,33 @@ #ifndef LLDB_TOOLS_LLDB_DAP_DAP_H #define LLDB_TOOLS_LLDB_DAP_DAP_H -#include <cstdio> -#include <iosfwd> -#include <map> -#include <optional> -#include <thread> - -#include "llvm/ADT/DenseMap.h" -#include "llvm/ADT/DenseSet.h" -#include "llvm/ADT/StringMap.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/Support/JSON.h" -#include "llvm/Support/Threading.h" -#include "llvm/Support/raw_ostream.h" - +#include "ExceptionBreakpoint.h" +#include "FunctionBreakpoint.h" +#include "IOStream.h" +#include "InstructionBreakpoint.h" +#include "ProgressEvent.h" +#include "SourceBreakpoint.h" #include "lldb/API/SBAttachInfo.h" #include "lldb/API/SBCommandInterpreter.h" #include "lldb/API/SBCommandReturnObject.h" #include "lldb/API/SBDebugger.h" #include "lldb/API/SBEvent.h" +#include "lldb/API/SBFile.h" #include "lldb/API/SBFormat.h" #include "lldb/API/SBLaunchInfo.h" #include "lldb/API/SBTarget.h" #include "lldb/API/SBThread.h" - -#include "ExceptionBreakpoint.h" -#include "FunctionBreakpoint.h" -#include "IOStream.h" -#include "InstructionBreakpoint.h" -#include "ProgressEvent.h" -#include "SourceBreakpoint.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/JSON.h" +#include "llvm/Support/Threading.h" +#include "llvm/Support/raw_ostream.h" +#include <iosfwd> +#include <map> +#include <optional> +#include <thread> #define VARREF_LOCALS (int64_t)1 #define VARREF_GLOBALS (int64_t)2 @@ -140,13 +137,16 @@ struct DAP { llvm::StringRef debug_adaptor_path; InputStream input; OutputStream output; + lldb::SBFile in; + lldb::SBFile out; + lldb::SBFile err; lldb::SBDebugger debugger; lldb::SBTarget target; Variables variables; lldb::SBBroadcaster broadcaster; std::thread event_thread; std::thread progress_event_thread; - std::unique_ptr<std::ofstream> log; + std::shared_ptr<std::ofstream> log; llvm::StringMap<SourceBreakpointMap> source_breakpoints; FunctionBreakpointMap function_breakpoints; InstructionBreakpointMap instruction_breakpoints; @@ -198,10 +198,14 @@ struct DAP { // will contain that expression. std::string last_nonempty_var_expression; - DAP(llvm::StringRef path, ReplMode repl_mode); + DAP(llvm::StringRef path, std::shared_ptr<std::ofstream> log, + ReplMode repl_mode, std::vector<std::string> pre_init_commands); ~DAP(); + + DAP() = delete; DAP(const DAP &rhs) = delete; void operator=(const DAP &rhs) = delete; + ExceptionBreakpoint *GetExceptionBreakpoint(const std::string &filter); ExceptionBreakpoint *GetExceptionBreakpoint(const lldb::break_id_t bp_id); diff --git a/lldb/tools/lldb-dap/Options.td b/lldb/tools/lldb-dap/Options.td index d7b4a065abec01..dfca79b2884ac8 100644 --- a/lldb/tools/lldb-dap/Options.td +++ b/lldb/tools/lldb-dap/Options.td @@ -22,8 +22,17 @@ def port: S<"port">, HelpText<"Communicate with the lldb-dap tool over the defined port.">; def: Separate<["-"], "p">, Alias<port>, + MetaVarName<"<port>">, HelpText<"Alias for --port">; +def unix_socket: S<"unix-socket">, + MetaVarName<"<path>">, + HelpText<"Communicate with the lldb-dap tool over the unix socket or named pipe.">; +def: Separate<["-"], "u">, + Alias<unix_socket>, + MetaVarName<"<path>">, + HelpText<"Alias for --unix_socket">; + def launch_target: S<"launch-target">, MetaVarName<"<target>">, HelpText<"Launch a target for the launchInTerminal request. Any argument " diff --git a/lldb/tools/lldb-dap/OutputRedirector.cpp b/lldb/tools/lldb-dap/OutputRedirector.cpp index 2c2f49569869b4..0b725e1901b9fd 100644 --- a/lldb/tools/lldb-dap/OutputRedirector.cpp +++ b/lldb/tools/lldb-dap/OutputRedirector.cpp @@ -21,7 +21,8 @@ using namespace llvm; namespace lldb_dap { -Error RedirectFd(int fd, std::function<void(llvm::StringRef)> callback) { +Expected<int> RedirectFd(int fd, + std::function<void(llvm::StringRef)> callback) { int new_fd[2]; #if defined(_WIN32) if (_pipe(new_fd, 4096, O_TEXT) == -1) { @@ -34,7 +35,7 @@ Error RedirectFd(int fd, std::function<void(llvm::StringRef)> callback) { strerror(error)); } - if (dup2(new_fd[1], fd) == -1) { + if (fd != -1 && dup2(new_fd[1], fd) == -1) { int error = errno; return createStringError(inconvertibleErrorCode(), "Couldn't override the fd %d. %s", fd, @@ -57,7 +58,7 @@ Error RedirectFd(int fd, std::function<void(llvm::StringRef)> callback) { } }); t.detach(); - return Error::success(); + return new_fd[1]; } } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/OutputRedirector.h b/lldb/tools/lldb-dap/OutputRedirector.h index e26d1648b104f9..418b8bac102c7f 100644 --- a/lldb/tools/lldb-dap/OutputRedirector.h +++ b/lldb/tools/lldb-dap/OutputRedirector.h @@ -16,10 +16,16 @@ namespace lldb_dap { /// Redirects the output of a given file descriptor to a callback. /// +/// \param[in] fd +/// Either -1 or the fd duplicate into the new handle. +/// +/// \param[in] callback +/// A callback invoked each time the file is written. +/// /// \return -/// \a Error::success if the redirection was set up correctly, or an error -/// otherwise. -llvm::Error RedirectFd(int fd, std::function<void(llvm::StringRef)> callback); +/// A new file handle for the output. +llvm::Expected<int> RedirectFd(int fd, + std::function<void(llvm::StringRef)> callback); } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp index 3bfc578806021e..51197587ffccec 100644 --- a/lldb/tools/lldb-dap/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/lldb-dap.cpp @@ -14,6 +14,7 @@ #include "RunInTerminal.h" #include "Watchpoint.h" #include "lldb/API/SBDeclaration.h" +#include "lldb/API/SBFile.h" #include "lldb/API/SBInstruction.h" #include "lldb/API/SBListener.h" #include "lldb/API/SBMemoryRegionInfo.h" @@ -66,6 +67,7 @@ #else #include <netinet/in.h> #include <sys/socket.h> +#include <sys/un.h> #include <unistd.h> #endif @@ -116,6 +118,8 @@ enum LaunchMethod { Launch, Attach, AttachForSuspendedLaunch }; /// Page size used for reporting addtional frames in the 'stackTrace' request. constexpr int StackPageSize = 20; +void RegisterRequestCallbacks(DAP &dap); + /// Prints a welcome message on the editor if the preprocessor variable /// LLDB_DAP_WELCOME_MESSAGE is defined. static void PrintWelcomeMessage(DAP &dap) { @@ -137,42 +141,232 @@ lldb::SBValueList *GetTopLevelScope(DAP &dap, int64_t variablesReference) { } } -SOCKET AcceptConnection(DAP &dap, int portno) { - // Accept a socket connection from any host on "portno". - SOCKET newsockfd = -1; - struct sockaddr_in serv_addr, cli_addr; +/// Redirect stdout and stderr fo the IDE's console output. +/// +/// Errors in this operation will be printed to the log file and the IDE's +/// console output as well. +/// +/// \return +/// A fd pointing to the original stdout. +void SetupRedirection(DAP &dap, int stdoutfd = -1, int stderrfd = -1) { + auto output_callback_stderr = [&dap](llvm::StringRef data) { + dap.SendOutput(OutputType::Stderr, data); + }; + auto output_callback_stdout = [&dap](llvm::StringRef data) { + dap.SendOutput(OutputType::Stdout, data); + }; + + llvm::Expected<int> new_stdout_fd = + RedirectFd(stdoutfd, output_callback_stdout); + if (auto err = new_stdout_fd.takeError()) { + std::string error_message = llvm::toString(std::move(err)); + if (dap.log) + *dap.log << error_message << std::endl; + output_callback_stderr(error_message); + } + dap.out = lldb::SBFile(new_stdout_fd.get(), "w", false); + + llvm::Expected<int> new_stderr_fd = + RedirectFd(stderrfd, output_callback_stderr); + if (auto err = new_stderr_fd.takeError()) { + std::string error_message = llvm::toString(std::move(err)); + if (dap.log) + *dap.log << error_message << std::endl; + output_callback_stderr(error_message); + } + dap.err = lldb::SBFile(new_stderr_fd.get(), "w", false); +} + +void HandleClient(int clientfd, llvm::StringRef program_path, + const std::vector<std::string> &pre_init_commands, + std::shared_ptr<std::ofstream> log, + ReplMode default_repl_mode) { + if (log) + *log << "client[" << clientfd << "] connected\n"; + DAP dap = DAP(program_path, log, default_repl_mode, pre_init_commands); + dap.debug_adaptor_path = program_path; + + SetupRedirection(dap); + RegisterRequestCallbacks(dap); + + dap.input.descriptor = StreamDescriptor::from_socket(clientfd, false); + dap.output.descriptor = StreamDescriptor::from_socket(clientfd, false); + + for (const std::string &arg : pre_init_commands) { + dap.pre_init_commands.push_back(arg); + } + + if (auto Err = dap.Loop()) { + if (log) + *log << "Transport Error: " << llvm::toString(std::move(Err)) << "\n"; + } + + if (log) + *log << "client[" << clientfd << "] connection closed\n"; +#if defined(_WIN32) + closesocket(clientfd); +#else + close(clientfd); +#endif +} + +std::error_code getLastSocketErrorCode() { +#ifdef _WIN32 + return std::error_code(::WSAGetLastError(), std::system_category()); +#else + return llvm::errnoAsErrorCode(); +#endif +} + +llvm::Expected<int> getSocketFD(llvm::StringRef path) { + if (llvm::sys::fs::exists(path) && (::remove(path.str().c_str()) == -1)) { + return llvm::make_error<llvm::StringError>(getLastSocketErrorCode(), + "Remove existing socket failed"); + } + + SOCKET sockfd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sockfd == -1) { + return llvm::make_error<llvm::StringError>(getLastSocketErrorCode(), + "Create socket failed"); + } + + struct sockaddr_un addr; + bzero(&addr, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, path.str().c_str(), sizeof(addr.sun_path) - 1); + + if (::bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) { +#if defined(_WIN32) + closesocket(sockfd); +#else + close(sockfd); +#endif + return llvm::make_error<llvm::StringError>(getLastSocketErrorCode(), + "Socket bind() failed"); + } + + if (listen(sockfd, llvm::hardware_concurrency().compute_thread_count()) < 0) { +#if defined(_WIN32) + closesocket(sockfd); +#else + close(sockfd); +#endif + return llvm::make_error<llvm::StringError>(getLastSocketErrorCode(), + "Socket listen() failed"); + } + + return sockfd; +} + +llvm::Expected<int> getSocketFD(int portno) { SOCKET sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { - if (dap.log) - *dap.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 (dap.log) - *dap.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 (dap.log) - *dap.log << "error: accept (" << strerror(errno) << ")" << std::endl; - } + return llvm::make_error<llvm::StringError>(getLastSocketErrorCode(), + "Create socket failed"); + } + + struct sockaddr_in serv_addr; + bzero(&serv_addr, 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 defined(_WIN32) closesocket(sockfd); #else close(sockfd); #endif + return llvm::make_error<llvm::StringError>(getLastSocketErrorCode(), + "Socket bind() failed"); } - return newsockfd; + + if (listen(sockfd, llvm::hardware_concurrency().compute_thread_count()) < 0) { +#if defined(_WIN32) + closesocket(sockfd); +#else + close(sockfd); +#endif + return llvm::make_error<llvm::StringError>(getLastSocketErrorCode(), + "Socket listen() failed"); + } + + return sockfd; +} + +int AcceptConnection(llvm::StringRef program_path, + const std::vector<std::string> &pre_init_commands, + std::shared_ptr<std::ofstream> log, + ReplMode default_repl_mode, + llvm::StringRef unix_socket_path) { + auto listening = getSocketFD(unix_socket_path); + if (auto E = listening.takeError()) { + llvm::errs() << "Listening on " << unix_socket_path + << " failed: " << llvm::toString(std::move(E)) << "\n"; + return EXIT_FAILURE; + } + + while (true) { + struct sockaddr_un cli_addr; + bzero(&cli_addr, sizeof(struct sockaddr_un)); + socklen_t clilen = sizeof(cli_addr); + SOCKET clientfd = + llvm::sys::RetryAfterSignal(static_cast<SOCKET>(-1), accept, *listening, + (struct sockaddr *)&cli_addr, &clilen); + if (clientfd < 0) { + llvm::errs() << "Client accept failed: " + << getLastSocketErrorCode().message() << "\n"; + return EXIT_FAILURE; + } + + std::thread t(HandleClient, clientfd, program_path, pre_init_commands, log, + default_repl_mode); + t.detach(); + } + +#if defined(_WIN32) + closesocket(*listening); +#else + close(*listening); +#endif + return 0; +} + +int AcceptConnection(llvm::StringRef program_path, + const std::vector<std::string> &pre_init_commands, + std::shared_ptr<std::ofstream> log, + ReplMode default_repl_mode, int portno) { + auto listening = getSocketFD(portno); + if (auto E = listening.takeError()) { + llvm::errs() << "Listening on " << portno + << " failed: " << llvm::toString(std::move(E)) << "\n"; + return EXIT_FAILURE; + } + + while (true) { + struct sockaddr_in cli_addr; + bzero(&cli_addr, sizeof(struct sockaddr_in)); + socklen_t clilen = sizeof(cli_addr); + SOCKET clientfd = + llvm::sys::RetryAfterSignal(static_cast<SOCKET>(-1), accept, *listening, + (struct sockaddr *)&cli_addr, &clilen); + if (clientfd < 0) { + llvm::errs() << "Client accept failed: " + << getLastSocketErrorCode().message() << "\n"; + return EXIT_FAILURE; + } + + std::thread t(HandleClient, clientfd, program_path, pre_init_commands, log, + default_repl_mode); + t.detach(); + } + +#if defined(_WIN32) + closesocket(*listening); +#else + close(*listening); +#endif + return 0; } std::vector<const char *> MakeArgv(const llvm::ArrayRef<std::string> &strs) { @@ -1868,7 +2062,21 @@ void request_initialize(DAP &dap, const llvm::json::Object &request) { // which may affect the outcome of tests. bool source_init_file = GetBoolean(arguments, "sourceInitFile", true); - dap.debugger = lldb::SBDebugger::Create(source_init_file); + lldb::SBDebugger debugger = lldb::SBDebugger::Create(false); + debugger.SetInputFile(dap.in); + debugger.SetOutputFile(dap.out); + debugger.SetErrorFile(dap.err); + + lldb::SBCommandInterpreter interp = debugger.GetCommandInterpreter(); + + if (source_init_file) { + lldb::SBCommandReturnObject result; + interp.SourceInitFileInCurrentWorkingDirectory(result); + interp.SourceInitFileInHomeDirectory(result, false); + } + + dap.debugger = debugger; + if (llvm::Error err = dap.RunPreInitCommands()) { response["success"] = false; EmplaceSafeString(response, "message", llvm::toString(std::move(err))); @@ -4907,38 +5115,6 @@ static void redirection_test() { fflush(stderr); } -/// Redirect stdout and stderr fo the IDE's console output. -/// -/// Errors in this operation will be printed to the log file and the IDE's -/// console output as well. -/// -/// \return -/// A fd pointing to the original stdout. -static int SetupStdoutStderrRedirection(DAP &dap) { - int stdoutfd = fileno(stdout); - int new_stdout_fd = dup(stdoutfd); - auto output_callback_stderr = [&dap](llvm::StringRef data) { - dap.SendOutput(OutputType::Stderr, data); - }; - auto output_callback_stdout = [&dap](llvm::StringRef data) { - dap.SendOutput(OutputType::Stdout, data); - }; - if (llvm::Error err = RedirectFd(stdoutfd, output_callback_stdout)) { - std::string error_message = llvm::toString(std::move(err)); - if (dap.log) - *dap.log << error_message << std::endl; - output_callback_stderr(error_message); - } - if (llvm::Error err = RedirectFd(fileno(stderr), output_callback_stderr)) { - std::string error_message = llvm::toString(std::move(err)); - if (dap.log) - *dap.log << error_message << std::endl; - output_callback_stderr(error_message); - } - - return new_stdout_fd; -} - int main(int argc, char *argv[]) { llvm::InitLLVM IL(argc, argv, /*InstallPipeSignalExitHandler=*/false); #if !defined(__APPLE__) @@ -5020,6 +5196,20 @@ int main(int argc, char *argv[]) { } } + std::string unix_socket_path; + if (auto *arg = input_args.getLastArg(OPT_unix_socket)) { + const auto *path = arg->getValue(); + unix_socket_path.assign(path); + } + + const char *log_file_path = getenv("LLDBDAP_LOG"); + std::shared_ptr<std::ofstream> log; + if (log_file_path) + log = std::make_shared<std::ofstream>(log_file_path); + + const auto pre_init_commands = + input_args.getAllArgValues(OPT_pre_init_command); + #if !defined(_WIN32) if (input_args.hasArg(OPT_wait_for_debugger)) { printf("Paused waiting for debugger to attach (pid = %i)...\n", getpid()); @@ -5028,36 +5218,52 @@ int main(int argc, char *argv[]) { #endif // Initialize LLDB first before we do anything. - lldb::SBDebugger::Initialize(); + lldb::SBError error = lldb::SBDebugger::InitializeWithErrorHandling(); + if (error.Fail()) { + llvm::errs() << "Failed to initialize LLDB: " << error.GetCString() << "\n"; + return EXIT_FAILURE; + } // Terminate the debugger before the C++ destructor chain kicks in. auto terminate_debugger = llvm::make_scope_exit([] { lldb::SBDebugger::Terminate(); }); - DAP dap = DAP(program_path.str(), default_repl_mode); + if (portno != -1) { + llvm::errs() << llvm::format("Listening on port %i...\n", portno); + return AcceptConnection(program_path.str(), pre_init_commands, log, + default_repl_mode, portno); + } + + if (!unix_socket_path.empty()) { + return AcceptConnection(program_path.str(), pre_init_commands, log, + default_repl_mode, unix_socket_path); + } + + DAP dap = DAP(program_path.str(), log, default_repl_mode, pre_init_commands); RegisterRequestCallbacks(dap); +#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 + // stdout/stderr redirection to the IDE's console - int new_stdout_fd = SetupStdoutStderrRedirection(dap); + int new_stdout_fd = dup(fileno(stdout)); + SetupRedirection(dap, fileno(stdout), fileno(stderr)); - if (portno != -1) { - printf("Listening on port %i...\n", portno); - SOCKET socket_fd = AcceptConnection(dap, portno); - if (socket_fd >= 0) { - dap.input.descriptor = StreamDescriptor::from_socket(socket_fd, true); - dap.output.descriptor = StreamDescriptor::from_socket(socket_fd, false); - } else { - return EXIT_FAILURE; - } - } else { - dap.input.descriptor = StreamDescriptor::from_file(fileno(stdin), false); - dap.output.descriptor = StreamDescriptor::from_file(new_stdout_fd, false); + dap.input.descriptor = StreamDescriptor::from_file(fileno(stdin), false); + dap.output.descriptor = StreamDescriptor::from_file(new_stdout_fd, false); - /// used only by TestVSCode_redirection_to_console.py - if (getenv("LLDB_DAP_TEST_STDOUT_STDERR_REDIRECTION") != nullptr) - redirection_test(); - } + /// used only by TestVSCode_redirection_to_console.py + if (getenv("LLDB_DAP_TEST_STDOUT_STDERR_REDIRECTION") != nullptr) + redirection_test(); for (const std::string &arg : input_args.getAllArgValues(OPT_pre_init_command)) { _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits