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

John Harrison via lldb-commits lldb-commits at lists.llvm.org
Wed Dec 4 13:54:44 PST 2024


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

>From 547549573aba86c9bf9b9c2c189d49a3d8f62413 Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Mon, 2 Dec 2024 08:36:52 -0800
Subject: [PATCH 1/2] [lldb-dap] Refactoring lldb-dap listening mode to support
 multiple connections.

This adjusts the lldb-dap listening mode to allow for multiple connections. Each client gets a new instanceo of DAP and an associated lldb::SBDebugger instance.
---
 .../test/tools/lldb-dap/dap_server.py         |  91 +++--
 .../test/tools/lldb-dap/lldbdap_testcase.py   |  29 +-
 lldb/test/API/tools/lldb-dap/server/Makefile  |   3 +
 .../tools/lldb-dap/server/TestDAP_server.py   |  72 ++++
 lldb/test/API/tools/lldb-dap/server/main.c    |   6 +
 lldb/tools/lldb-dap/CMakeLists.txt            |   9 +-
 lldb/tools/lldb-dap/DAP.cpp                   | 103 +++---
 lldb/tools/lldb-dap/DAP.h                     |  70 ++--
 lldb/tools/lldb-dap/IOStream.cpp              |  27 +-
 lldb/tools/lldb-dap/IOStream.h                |   8 +-
 lldb/tools/lldb-dap/Options.td                |  13 +-
 lldb/tools/lldb-dap/OutputRedirector.cpp      |  44 ++-
 lldb/tools/lldb-dap/OutputRedirector.h        |  35 +-
 lldb/tools/lldb-dap/lldb-dap.cpp              | 313 +++++++++++-------
 14 files changed, 555 insertions(+), 268 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..37679462118531 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,38 +1150,42 @@ def request_setInstructionBreakpoints(self, memory_reference=[]):
         }
         return self.send_recv(command_dict)
 
+
 class DebugAdaptorServer(DebugCommunication):
     def __init__(
         self,
         executable=None,
-        port=None,
+        launch=True,
+        connection=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, connection = DebugAdaptorServer.launch(
+                executable,
+                connection=connection,
+                log_file=log_file,
+                env=env,
             )
+
+        if connection:
+            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))
+        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):
@@ -1196,6 +1200,53 @@ def terminate(self):
             self.process.wait()
             self.process = None
 
+    @classmethod
+    def launch(
+        cls, executable: str, /, connection=None, log_file=None, env=None
+    ) -> tuple[subprocess.Popen, str]:
+        adaptor_env = os.environ.copy()
+        if env:
+            adaptor_env.update(env)
+
+        if log_file:
+            adaptor_env["LLDBDAP_LOG"] = log_file
+
+        args = [executable]
+        bufsize = -1
+        if connection:
+            bufsize = 0
+            args.append("--connection")
+            args.append(connection)
+
+        proc = subprocess.Popen(
+            args,
+            bufsize=bufsize,
+            stdin=subprocess.PIPE,
+            stdout=subprocess.PIPE,
+            stderr=sys.stdout,
+            env=adaptor_env,
+        )
+
+        if connection:
+            # If a conneciton is specified, lldb-dap will print the listening
+            # address once the listener is made to stdout. The listener is
+            # formatted like `tcp://host:port` or `unix:///path`.
+            expected_prefix = "Listening for: "
+            out = proc.stdout.readline().decode()
+            if not out.startswith(expected_prefix):
+                proc.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")
+            return proc, connection
+
+        return proc, None
+
 
 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..6b1216c8837f7c 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
@@ -10,10 +10,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, env=None, launch=True, connection=None):
         """Create the Visual Studio Code debug adaptor"""
         self.assertTrue(
             is_exe(self.lldbDAPExec), "lldb-dap must exist and be executable"
@@ -21,14 +21,25 @@ 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,
+            connection=connection,
             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,
+        lldbDAPConnection=None,
+    ):
         self.build()
-        self.create_debug_adaptor(lldbDAPEnv)
+        self.create_debug_adaptor(
+            env=lldbDAPEnv,
+            launch=lldbDAPLaunch,
+            connection=lldbDAPConnection,
+        )
 
     def set_source_breakpoints(self, source_path, lines, data=None):
         """Sets source breakpoints and returns an array of strings containing
@@ -475,11 +486,17 @@ def build_and_launch(
         customThreadFormat=None,
         launchCommands=None,
         expectFailure=False,
+        lldbDAPLaunch=True,
+        lldbDAPConnection=None,
     ):
         """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,
+            lldbDAPConnection=lldbDAPConnection,
+        )
         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..c90e6b8da0a303
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
@@ -0,0 +1,72 @@
+"""
+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):
+        log_file_path = self.getBuildArtifact("dap.txt")
+        server, connection = dap_server.DebugAdaptorServer.launch(
+            self.lldbDAPExec, connection, 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, connection=connection)
+        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, connection=connection)
+        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.
+        """
+        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..c3599057621276
--- /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;
+}
diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt
index d68098bf7b3266..43fc18873feb33 100644
--- a/lldb/tools/lldb-dap/CMakeLists.txt
+++ b/lldb/tools/lldb-dap/CMakeLists.txt
@@ -1,7 +1,3 @@
-if ( CMAKE_SYSTEM_NAME MATCHES "Windows" OR CMAKE_SYSTEM_NAME MATCHES "NetBSD" )
-  list(APPEND extra_libs lldbHost)
-endif ()
-
 if (HAVE_LIBPTHREAD)
   list(APPEND extra_libs pthread)
 endif ()
@@ -26,9 +22,11 @@ add_lldb_tool(lldb-dap
   lldb-dap.cpp
   Breakpoint.cpp
   BreakpointBase.cpp
+  DAP.cpp
   ExceptionBreakpoint.cpp
   FifoFiles.cpp
   FunctionBreakpoint.cpp
+  InstructionBreakpoint.cpp
   IOStream.cpp
   JSONUtils.cpp
   LLDBUtils.cpp
@@ -36,12 +34,11 @@ add_lldb_tool(lldb-dap
   ProgressEvent.cpp
   RunInTerminal.cpp
   SourceBreakpoint.cpp
-  DAP.cpp
   Watchpoint.cpp
-  InstructionBreakpoint.cpp
 
   LINK_LIBS
     liblldb
+    lldbHost
     ${extra_libs}
 
   LINK_COMPONENTS
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index 35250d9eef608a..243117782dbe84 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -14,11 +14,14 @@
 #include "DAP.h"
 #include "JSONUtils.h"
 #include "LLDBUtils.h"
+#include "OutputRedirector.h"
 #include "lldb/API/SBCommandInterpreter.h"
 #include "lldb/API/SBLanguageRuntime.h"
 #include "lldb/API/SBListener.h"
 #include "lldb/API/SBStream.h"
 #include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
 #include "llvm/Support/FormatVariadic.h"
 
 #if defined(_WIN32)
@@ -32,10 +35,11 @@ using namespace lldb_dap;
 
 namespace lldb_dap {
 
-DAP::DAP(llvm::StringRef path, ReplMode repl_mode)
-    : debug_adaptor_path(path), broadcaster("lldb-dap"),
-      exception_breakpoints(), focus_tid(LLDB_INVALID_THREAD_ID),
-      stop_at_entry(false), is_attach(false),
+DAP::DAP(llvm::StringRef path, std::ofstream *log, ReplMode repl_mode,
+         std::vector<std::string> pre_init_commands)
+    : debug_adaptor_path(path), broadcaster("lldb-dap"), 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,24 +47,34 @@ 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;
 
+llvm::Error DAP::ConfigureIO(int out_fd, int err_fd) {
+  llvm::Expected<int> new_stdout_fd =
+      redirector.RedirectFd(out_fd, [this](llvm::StringRef data) {
+        SendOutput(OutputType::Stdout, data);
+      });
+  if (auto Err = new_stdout_fd.takeError()) {
+    return Err;
+  }
+  llvm::Expected<int> new_stderr_fd =
+      redirector.RedirectFd(err_fd, [this](llvm::StringRef data) {
+        SendOutput(OutputType::Stderr, data);
+      });
+  if (auto Err = new_stderr_fd.takeError()) {
+    return Err;
+  }
+
+  out = lldb::SBFile(/*fd=*/new_stdout_fd.get(), /*mode=*/"w",
+                     /*transfer_ownership=*/false);
+  err = lldb::SBFile(/*fd=*/new_stderr_fd.get(), /*mode=*/"w",
+                     /*transfer_ownership=*/false);
+
+  return llvm::Error::success();
+}
+
 /// Return string with first character capitalized.
 static std::string capitalize(llvm::StringRef str) {
   if (str.empty())
@@ -208,19 +222,19 @@ std::string DAP::ReadJSON() {
   std::string json_str;
   int length;
 
-  if (!input.read_expected(log.get(), "Content-Length: "))
+  if (!input.read_expected(log, "Content-Length: "))
     return json_str;
 
-  if (!input.read_line(log.get(), length_str))
+  if (!input.read_line(log, length_str))
     return json_str;
 
   if (!llvm::to_integer(length_str, length))
     return json_str;
 
-  if (!input.read_expected(log.get(), "\r\n"))
+  if (!input.read_expected(log, "\r\n"))
     return json_str;
 
-  if (!input.read_full(log.get(), length, json_str))
+  if (!input.read_full(log, length, json_str))
     return json_str;
 
   if (log) {
@@ -526,11 +540,14 @@ ReplMode DAP::DetectReplMode(lldb::SBFrame frame, std::string &expression,
 
     // If we have both a variable and command, warn the user about the conflict.
     if (term_is_command && term_is_variable) {
-      llvm::errs()
-          << "Warning: Expression '" << term
-          << "' is both an LLDB command and variable. It will be evaluated as "
-             "a variable. To evaluate the expression as an LLDB command, use '"
-          << command_escape_prefix << "' as a prefix.\n";
+      SendOutput(
+          OutputType::Stderr,
+          llvm::formatv(
+              "Warning: Expression '{0}' is both an LLDB command and variable. "
+              "It will be evaluated as a variable. To evaluate the expression "
+              "as an LLDB command, use '{1}' as a prefix.",
+              term, command_escape_prefix)
+              .str());
     }
 
     // Variables take preference to commands in auto, since commands can always
@@ -673,9 +690,8 @@ PacketStatus DAP::GetNextObject(llvm::json::Object &object) {
     return PacketStatus::JSONMalformed;
   }
 
-  if (log) {
+  if (log)
     *log << llvm::formatv("{0:2}", *json_value).str() << std::endl;
-  }
 
   llvm::json::Object *object_ptr = json_value->getAsObject();
   if (!object_ptr) {
@@ -705,8 +721,15 @@ bool DAP::HandleObject(const llvm::json::Object &object) {
 
   if (packet_type == "response") {
     auto id = GetSigned(object, "request_seq", 0);
-    ResponseCallback response_handler = [](llvm::Expected<llvm::json::Value>) {
-      llvm::errs() << "Unhandled response\n";
+    ResponseCallback response_handler = [](auto dap, auto value) {
+      if (value)
+        dap.SendOutput(OutputType::Stderr,
+                       llvm::formatv("unexpected response: {0}", *value).str());
+      else
+        dap.SendOutput(OutputType::Stderr,
+                       llvm::formatv("unexpected response: {0}",
+                                     llvm::toString(value.takeError()))
+                           .str());
     };
 
     {
@@ -724,14 +747,15 @@ bool DAP::HandleObject(const llvm::json::Object &object) {
       if (auto *B = object.get("body")) {
         Result = std::move(*B);
       }
-      response_handler(Result);
+      response_handler(*this, Result);
     } else {
       llvm::StringRef message = GetString(object, "message");
       if (message.empty()) {
         message = "Unknown error, response failed";
       }
-      response_handler(llvm::createStringError(
-          std::error_code(-1, std::generic_category()), message));
+      response_handler(
+          *this, llvm::createStringError(
+                     std::error_code(-1, std::generic_category()), message));
     }
 
     return true;
@@ -904,11 +928,14 @@ bool StartDebuggingRequestHandler::DoExecute(
       "startDebugging",
       llvm::json::Object{{"request", request},
                          {"configuration", std::move(*configuration)}},
-      [](llvm::Expected<llvm::json::Value> value) {
+      [](auto dap, auto value) {
         if (!value) {
           llvm::Error err = value.takeError();
-          llvm::errs() << "reverse start debugging request failed: "
-                       << llvm::toString(std::move(err)) << "\n";
+          dap.SendOutput(
+              OutputType::Console,
+              llvm::formatv("reverse start debugging request failed: {0}",
+                            llvm::toString(std::move(err)))
+                  .str());
         }
       });
 
diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h
index ae496236f13369..4b17b0ceb011af 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 "OutputRedirector.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 <map>
+#include <optional>
+#include <thread>
 
 #define VARREF_LOCALS (int64_t)1
 #define VARREF_GLOBALS (int64_t)2
@@ -64,7 +61,7 @@ enum DAPBroadcasterBits {
 };
 
 typedef void (*RequestCallback)(DAP &dap, const llvm::json::Object &command);
-typedef void (*ResponseCallback)(llvm::Expected<llvm::json::Value> value);
+typedef void (*ResponseCallback)(DAP &dap, llvm::Expected<llvm::json::Value> value);
 
 enum class PacketStatus {
   Success = 0,
@@ -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::ofstream *log;
   llvm::StringMap<SourceBreakpointMap> source_breakpoints;
   FunctionBreakpointMap function_breakpoints;
   InstructionBreakpointMap instruction_breakpoints;
@@ -197,11 +197,33 @@ struct DAP {
   // empty; if the previous expression was a variable expression, this string
   // will contain that expression.
   std::string last_nonempty_var_expression;
+  OutputRedirector redirector;
 
-  DAP(llvm::StringRef path, ReplMode repl_mode);
+  DAP(llvm::StringRef path, 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;
+
+  void disconnect();
+
+  /// Configures the DAP session stdout and stderr to redirect to the DAP
+  /// connection.
+  ///
+  /// Errors in this operation will be printed to the log file and the IDE's
+  /// console output as well.
+  ///
+  /// \param[in] out_fd
+  ///    If not -1, uses the given file descriptor as stdout.
+  ///
+  /// \param[in] err_fd
+  ///    If not -1, uses the given file descriptor as stderr.
+  ///
+  /// \return An error indiciating if the configuration was applied.
+  llvm::Error ConfigureIO(int out_fd = -1, int err_fd = -1);
+
   ExceptionBreakpoint *GetExceptionBreakpoint(const std::string &filter);
   ExceptionBreakpoint *GetExceptionBreakpoint(const lldb::break_id_t bp_id);
 
diff --git a/lldb/tools/lldb-dap/IOStream.cpp b/lldb/tools/lldb-dap/IOStream.cpp
index d2e8ec40b0a7b8..d07410af7b2319 100644
--- a/lldb/tools/lldb-dap/IOStream.cpp
+++ b/lldb/tools/lldb-dap/IOStream.cpp
@@ -7,6 +7,8 @@
 //===----------------------------------------------------------------------===//
 
 #include "IOStream.h"
+#include <fstream>
+#include <string>
 
 #if defined(_WIN32)
 #include <io.h>
@@ -16,9 +18,6 @@
 #include <unistd.h>
 #endif
 
-#include <fstream>
-#include <string>
-
 using namespace lldb_dap;
 
 StreamDescriptor::StreamDescriptor() = default;
@@ -27,23 +26,9 @@ 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() = default;
 
 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;
@@ -52,19 +37,17 @@ StreamDescriptor &StreamDescriptor::operator=(StreamDescriptor &&other) {
   return *this;
 }
 
-StreamDescriptor StreamDescriptor::from_socket(SOCKET s, bool close) {
+StreamDescriptor StreamDescriptor::from_socket(SOCKET s) {
   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 StreamDescriptor::from_file(int fd) {
   StreamDescriptor sd;
   sd.m_is_socket = false;
   sd.m_fd = fd;
-  sd.m_close = close;
   return sd;
 }
 
diff --git a/lldb/tools/lldb-dap/IOStream.h b/lldb/tools/lldb-dap/IOStream.h
index 57d5fd458b7165..7296c1dba213e3 100644
--- a/lldb/tools/lldb-dap/IOStream.h
+++ b/lldb/tools/lldb-dap/IOStream.h
@@ -22,7 +22,6 @@ typedef int SOCKET;
 #endif
 
 #include "llvm/ADT/StringRef.h"
-
 #include <fstream>
 #include <string>
 
@@ -32,17 +31,16 @@ typedef int SOCKET;
 // treat them identically.
 namespace lldb_dap {
 struct StreamDescriptor {
-  StreamDescriptor();
+  explicit 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);
+  static StreamDescriptor from_socket(SOCKET s);
+  static StreamDescriptor from_file(int fd);
 
   bool m_is_socket = false;
-  bool m_close = false;
   union {
     int m_fd;
     SOCKET m_socket;
diff --git a/lldb/tools/lldb-dap/Options.td b/lldb/tools/lldb-dap/Options.td
index d7b4a065abec01..41d8912ed6c6bf 100644
--- a/lldb/tools/lldb-dap/Options.td
+++ b/lldb/tools/lldb-dap/Options.td
@@ -17,12 +17,10 @@ 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>">,
@@ -42,7 +40,8 @@ def debugger_pid: S<"debugger-pid">,
 
 def repl_mode: S<"repl-mode">,
   MetaVarName<"<mode>">,
-  HelpText<"The mode for handling repl evaluation requests, supported modes: variable, command, auto.">;
+  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/OutputRedirector.cpp b/lldb/tools/lldb-dap/OutputRedirector.cpp
index 2c2f49569869b4..6a63ed5a95e319 100644
--- a/lldb/tools/lldb-dap/OutputRedirector.cpp
+++ b/lldb/tools/lldb-dap/OutputRedirector.cpp
@@ -16,29 +16,45 @@
 #include "DAP.h"
 #include "OutputRedirector.h"
 #include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+#include <thread>
+#include <utility>
+#include <vector>
 
 using namespace llvm;
 
 namespace lldb_dap {
 
-Error RedirectFd(int fd, std::function<void(llvm::StringRef)> callback) {
+OutputRedirector::~OutputRedirector() {
+  // Make a best effort cleanup of any redirected FDs.
+  for (auto &[from, to] : m_redirects)
+    ::dup2(to, from); // ignoring errors
+
+  for (const auto &fd : m_fds)
+    close(fd);
+}
+
+Expected<int>
+OutputRedirector::RedirectFd(int fd,
+                             std::function<void(llvm::StringRef)> callback) {
   int new_fd[2];
 #if defined(_WIN32)
-  if (_pipe(new_fd, 4096, O_TEXT) == -1) {
+  if (_pipe(new_fd, 4096, O_TEXT) == -1)
 #else
-  if (pipe(new_fd) == -1) {
+  if (pipe(new_fd) == -1)
 #endif
-    int error = errno;
-    return createStringError(inconvertibleErrorCode(),
-                             "Couldn't create new pipe for fd %d. %s", fd,
-                             strerror(error));
-  }
+    return createStringError(llvm::errnoAsErrorCode(),
+                             "Couldn't create new pipe for fd %d.", fd);
+
+  m_fds.push_back(new_fd[0]);
+  m_fds.push_back(new_fd[1]);
+
+  if (fd != -1) {
+    if (dup2(new_fd[1], fd) == -1)
+      return createStringError(llvm::errnoAsErrorCode(),
+                               "Couldn't override the fd %d.", fd);
 
-  if (dup2(new_fd[1], fd) == -1) {
-    int error = errno;
-    return createStringError(inconvertibleErrorCode(),
-                             "Couldn't override the fd %d. %s", fd,
-                             strerror(error));
+    m_redirects.push_back(std::make_pair(new_fd[1], fd));
   }
 
   int read_fd = new_fd[0];
@@ -57,7 +73,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..59de19d5ff1b6b 100644
--- a/lldb/tools/lldb-dap/OutputRedirector.h
+++ b/lldb/tools/lldb-dap/OutputRedirector.h
@@ -11,15 +11,38 @@
 
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/Error.h"
+#include <utility>
+#include <vector>
 
 namespace lldb_dap {
 
-/// Redirects the output of a given file descriptor to a callback.
-///
-/// \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);
+/// Manages the lifetime of file descriptor redirects.
+struct OutputRedirector {
+  OutputRedirector() = default;
+
+  OutputRedirector(const OutputRedirector &) = delete;
+  OutputRedirector &operator=(const OutputRedirector &) = delete;
+
+  ~OutputRedirector();
+
+  /// 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 new file handle for the output.
+  llvm::Expected<int> RedirectFd(int fd,
+                                 std::function<void(llvm::StringRef)> callback);
+
+private:
+  std::vector<int> m_fds; // owned fds, closed on dealloc.
+  std::vector<std::pair<int, int>>
+      m_redirects; // pairs (new, old) of redirected fds.
+};
 
 } // namespace lldb_dap
 
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index 3bfc578806021e..68b482e80e4170 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -10,31 +10,39 @@
 #include "FifoFiles.h"
 #include "JSONUtils.h"
 #include "LLDBUtils.h"
-#include "OutputRedirector.h"
 #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"
 #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>
@@ -44,13 +52,18 @@
 #include <cstdio>
 #include <cstdlib>
 #include <cstring>
+#include <fstream>
+#include <iostream>
 #include <map>
 #include <memory>
 #include <optional>
+#include <ostream>
 #include <set>
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <system_error>
 #include <thread>
+#include <utility>
 #include <vector>
 
 #if defined(_WIN32)
@@ -66,6 +79,7 @@
 #else
 #include <netinet/in.h>
 #include <sys/socket.h>
+#include <sys/un.h>
 #include <unistd.h>
 #endif
 
@@ -81,6 +95,13 @@ typedef int socklen_t;
 #endif
 
 using namespace lldb_dap;
+using lldb_private::DomainSocket;
+using lldb_private::MainLoop;
+using lldb_private::MainLoopBase;
+using lldb_private::NativeSocket;
+using lldb_private::Socket;
+using lldb_private::Status;
+using lldb_private::TCPSocket;
 
 namespace {
 using namespace llvm::opt;
@@ -116,6 +137,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,44 +160,6 @@ 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;
-  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;
-    }
-#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
@@ -306,12 +291,11 @@ void SendThreadStoppedEvent(DAP &dap) {
       if (dap.log)
         *dap.log << "error: SendThreadStoppedEvent() when process"
                     " isn't stopped ("
-                 << lldb::SBDebugger::StateAsCString(state) << ')' << std::endl;
+                 << lldb::SBDebugger::StateAsCString(state) << ")\n";
     }
   } else {
     if (dap.log)
-      *dap.log << "error: SendThreadStoppedEvent() invalid process"
-               << std::endl;
+      *dap.log << "error: SendThreadStoppedEvent() invalid process\n";
   }
   dap.RunStopCommands();
 }
@@ -1868,7 +1852,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)));
@@ -2012,15 +2010,16 @@ llvm::Error request_runInTerminal(DAP &dap,
 #endif
   llvm::json::Object reverse_request = CreateRunInTerminalReverseRequest(
       launch_request, dap.debug_adaptor_path, comm_file.m_path, debugger_pid);
-  dap.SendReverseRequest("runInTerminal", std::move(reverse_request),
-                         [](llvm::Expected<llvm::json::Value> value) {
-                           if (!value) {
-                             llvm::Error err = value.takeError();
-                             llvm::errs()
-                                 << "runInTerminal request failed: "
-                                 << llvm::toString(std::move(err)) << "\n";
-                           }
-                         });
+  dap.SendReverseRequest(
+      "runInTerminal", std::move(reverse_request), [](auto dap, auto value) {
+        if (!value) {
+          dap.SendOutput(
+              OutputType::Stderr,
+              llvm::formatv("runInTerminal request failed: {}",
+                            llvm::toString(std::move(value.takeError())))
+                  .str());
+        }
+      });
 
   if (llvm::Expected<lldb::pid_t> pid = comm_channel.GetLauncherPid())
     attach_info.SetProcessID(*pid);
@@ -4907,36 +4906,27 @@ 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);
+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 new_stdout_fd;
+  return llvm::createStringError(
+      "expected '[unix://]/path' or '[tcp://][host]:port', got '%s'.",
+      conn.str().c_str());
 }
 
 int main(int argc, char *argv[]) {
@@ -5009,17 +4999,20 @@ 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);
   }
 
+  const char *log_file_path = getenv("LLDBDAP_LOG");
+  std::ofstream *log = nullptr;
+  if (log_file_path)
+    log = new 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,48 +5021,128 @@ 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);
+  auto HandleClient = [=](int out_fd, int err_fd, StreamDescriptor input,
+                          StreamDescriptor output) {
+    DAP dap = DAP(program_path, log, default_repl_mode, pre_init_commands);
+    dap.input.descriptor = std::move(input);
+    dap.output.descriptor = std::move(output);
+    RegisterRequestCallbacks(dap);
+
+    if (auto Err = dap.ConfigureIO(out_fd, err_fd)) {
+      if (log)
+        *log << "configureIO failed: " << llvm::toStringWithoutConsuming(Err)
+             << "\n";
+      std::cerr << "failed to configureIO: " << llvm::toString(std::move(Err))
+                << std::endl;
+      return false;
+    }
 
-  RegisterRequestCallbacks(dap);
+    // 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
-  int new_stdout_fd = SetupStdoutStderrRedirection(dap);
-
-  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 {
+    if (auto Err = dap.Loop()) {
+      if (log)
+        *log << "Transport Error: " << llvm::toStringWithoutConsuming(Err)
+             << "\n";
+
+      std::cerr << "Transpot Error: " << llvm::toString(std::move(Err))
+                << std::endl;
+
+      return false;
+    }
+
+    if (log)
+      *log << "connection closed\n";
+
+    return true;
+  };
+
+  if (!connection.empty()) {
+    auto maybeProtoclAndName = parseConnection(connection);
+    if (auto Err = maybeProtoclAndName.takeError()) {
+      std::cerr << "Invalid connection specification "
+                << llvm::toString(std::move(Err)) << std::endl;
       return EXIT_FAILURE;
     }
-  } else {
-    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();
-  }
+    Socket::SocketProtocol protocol;
+    std::string name;
+    std::tie(protocol, name) = *maybeProtoclAndName;
+
+    Status error;
+    std::unique_ptr<Socket> listener = Socket::Create(protocol, false, error);
+    if (error.Fail()) {
+      std::cerr << "Failed to create listener for protocol "
+                << Socket::FindSchemeByProtocol(protocol)
+                << ", error: " << llvm::toString(error.takeError())
+                << std::endl;
+      return EXIT_FAILURE;
+    }
 
-  for (const std::string &arg :
-       input_args.getAllArgValues(OPT_pre_init_command)) {
-    dap.pre_init_commands.push_back(arg);
-  }
+    error = listener->Listen(name, /* backlog */ 5);
+    if (error.Fail()) {
+      std::cerr << "Failed to listen, error: "
+                << llvm::toString(error.takeError()) << std::endl;
+      return EXIT_FAILURE;
+    }
 
-  bool CleanExit = true;
-  if (auto Err = dap.Loop()) {
-    if (dap.log)
-      *dap.log << "Transport Error: " << llvm::toString(std::move(Err)) << "\n";
-    CleanExit = false;
+    std::string address = listener->GetListeningConnectionURI();
+
+    if (log)
+      *log << "started with connection listeners " << address << "\n";
+
+    std::cout << "Listening for: " << address << std::endl;
+    // Ensure listening address are flushed for calles to retrieve the resolve
+    // address.
+    std::flush(std::cout);
+
+    MainLoop mainloop;
+    mainloop.RegisterSignal(
+        SIGHUP, [](auto &RL) { RL.RequestTermination(); }, error);
+
+    auto OnAccept = [=](std::unique_ptr<Socket> client) {
+      // Start a thread for each connection, unblocking the listening thread.
+      std::thread([=, client = std::move(client)]() {
+        HandleClient(
+            /*out_fd=*/-1, /*err_fd=*/-1,
+            StreamDescriptor::from_socket(client->GetNativeSocket()),
+            StreamDescriptor::from_socket(client->GetNativeSocket()));
+      }).detach();
+    };
+
+    auto handles = listener->Accept(mainloop, OnAccept);
+    if (auto Err = handles.takeError()) {
+      std::cerr << "failed to register accept() with the main loop: "
+                << llvm::toString(std::move(Err)) << std::endl;
+      return EXIT_FAILURE;
+    }
+
+    error = mainloop.Run();
+    if (error.Fail()) {
+      std::cerr << "failed to accept()" << llvm::toString(error.takeError())
+                << std::endl;
+      return EXIT_FAILURE;
+    }
+
+    return EXIT_SUCCESS;
   }
 
-  return CleanExit ? EXIT_SUCCESS : EXIT_FAILURE;
+  // stdout/stderr redirection to the IDE's console
+  int new_stdout_fd = dup(fileno(stdout));
+  bool clean_exit = HandleClient(fileno(stdout), fileno(stderr),
+                                 StreamDescriptor::from_file(fileno(stdin)),
+                                 StreamDescriptor::from_file(new_stdout_fd));
+
+  return clean_exit ? EXIT_SUCCESS : EXIT_FAILURE;
 }

>From 31ead1b2c5af659ffa622b77ed2fac06dc65a421 Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Tue, 3 Dec 2024 15:48:21 -0800
Subject: [PATCH 2/2] Rebasing on lldb host changes.

---
 .../test/tools/lldb-dap/dap_server.py         |  2 +-
 lldb/test/Shell/DAP/TestOptions.test          |  4 +--
 lldb/tools/lldb-dap/lldb-dap.cpp              | 36 +++++++++----------
 3 files changed, 19 insertions(+), 23 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 37679462118531..8003062ea8df1f 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
@@ -1242,7 +1242,7 @@ def launch(
                 )
 
             # If the listener expanded into multiple addresses, use the first.
-            connection = out.removeprefix(expected_prefix).rstrip("\r\n")
+            connection = out.removeprefix(expected_prefix).rstrip("\r\n").split(",", 1)[0]
             return proc, connection
 
         return proc, None
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/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index 68b482e80e4170..0bec52c5281a3d 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -5042,8 +5042,7 @@ int main(int argc, char *argv[]) {
       if (log)
         *log << "configureIO failed: " << llvm::toStringWithoutConsuming(Err)
              << "\n";
-      std::cerr << "failed to configureIO: " << llvm::toString(std::move(Err))
-                << std::endl;
+      llvm::errs() << "failed to configureIO: " << Err << "\n";
       return false;
     }
 
@@ -5056,8 +5055,7 @@ int main(int argc, char *argv[]) {
         *log << "Transport Error: " << llvm::toStringWithoutConsuming(Err)
              << "\n";
 
-      std::cerr << "Transpot Error: " << llvm::toString(std::move(Err))
-                << std::endl;
+      llvm::errs() << "Transpot Error: " << Err << "\n";
 
       return false;
     }
@@ -5071,8 +5069,8 @@ int main(int argc, char *argv[]) {
   if (!connection.empty()) {
     auto maybeProtoclAndName = parseConnection(connection);
     if (auto Err = maybeProtoclAndName.takeError()) {
-      std::cerr << "Invalid connection specification "
-                << llvm::toString(std::move(Err)) << std::endl;
+      llvm::errs() << "Invalid connection specification "
+                << Err << "\n";
       return EXIT_FAILURE;
     }
 
@@ -5081,31 +5079,30 @@ int main(int argc, char *argv[]) {
     std::tie(protocol, name) = *maybeProtoclAndName;
 
     Status error;
-    std::unique_ptr<Socket> listener = Socket::Create(protocol, false, error);
+    std::unique_ptr<Socket> listener = Socket::Create(protocol, error);
     if (error.Fail()) {
-      std::cerr << "Failed to create listener for protocol "
-                << Socket::FindSchemeByProtocol(protocol)
-                << ", error: " << llvm::toString(error.takeError())
-                << std::endl;
+      llvm::errs() << "Failed to create listener for protocol "
+                   << Socket::FindSchemeByProtocol(protocol)
+                   << ", error: " << error.takeError() << "\n";
       return EXIT_FAILURE;
     }
 
     error = listener->Listen(name, /* backlog */ 5);
     if (error.Fail()) {
-      std::cerr << "Failed to listen, error: "
-                << llvm::toString(error.takeError()) << std::endl;
+      llvm::errs() << "Failed to listen, error: "
+                << error.takeError() << "\n";
       return EXIT_FAILURE;
     }
 
-    std::string address = listener->GetListeningConnectionURI();
+    std::string address = llvm::join(listener->GetListeningConnectionURI(), ", ");
 
     if (log)
       *log << "started with connection listeners " << address << "\n";
 
-    std::cout << "Listening for: " << address << std::endl;
+    llvm::outs() << "Listening for: " << address << "\n";
     // Ensure listening address are flushed for calles to retrieve the resolve
     // address.
-    std::flush(std::cout);
+    llvm::outs().flush();
 
     MainLoop mainloop;
     mainloop.RegisterSignal(
@@ -5123,15 +5120,14 @@ int main(int argc, char *argv[]) {
 
     auto handles = listener->Accept(mainloop, OnAccept);
     if (auto Err = handles.takeError()) {
-      std::cerr << "failed to register accept() with the main loop: "
-                << llvm::toString(std::move(Err)) << std::endl;
+      llvm::errs() << "failed to register accept() with the main loop: "
+                   << Err << "\n";
       return EXIT_FAILURE;
     }
 
     error = mainloop.Run();
     if (error.Fail()) {
-      std::cerr << "failed to accept()" << llvm::toString(error.takeError())
-                << std::endl;
+      llvm::errs() << "failed to accept()" << error.takeError() << "\n";
       return EXIT_FAILURE;
     }
 



More information about the lldb-commits mailing list