[Lldb-commits] [lldb] [lldb-da] Refactoring lldb-dap port listening mode to allow multiple connections. (PR #116392)

John Harrison via lldb-commits lldb-commits at lists.llvm.org
Fri Nov 15 07:05:17 PST 2024


https://github.com/ashgti updated https://github.com/llvm/llvm-project/pull/116392

>From acfdb2da30b7a49711c3d1ec3be3c9282d6c51b4 Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at 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)) {



More information about the lldb-commits mailing list