[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
Tue Nov 26 14:33:46 PST 2024
https://github.com/ashgti updated https://github.com/llvm/llvm-project/pull/116392
>From 8f69fb64cde9582bbeb64774ff506ace6877fddd 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 1/4] [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)) {
>From 8591c84c0dd3fed765766e4a2e33862af60a1df4 Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Fri, 22 Nov 2024 16:02:43 -0800
Subject: [PATCH 2/4] Working to address some of the basic architectural issues
with the socket listeners.
* Moved the socket handling logic into lldb/tools/lldb-dap/Socket.{h,cpp}
* Reworked the unit tests to read the socket information from the DAP process, this allows for lldb-dap to start with a connection like 'tcp://localhost:0' and pick a random port for the test. The output will be printed to stdout, listing all the addresses that were resolved from the connection parameter.
---
.../test/tools/lldb-dap/dap_server.py | 79 ++-
.../test/tools/lldb-dap/lldbdap_testcase.py | 19 +-
.../tools/lldb-dap/server/TestDAP_server.py | 17 +-
lldb/tools/lldb-dap/CMakeLists.txt | 5 +-
lldb/tools/lldb-dap/DAP.cpp | 50 +-
lldb/tools/lldb-dap/DAP.h | 21 +-
lldb/tools/lldb-dap/IOStream.cpp | 10 +-
lldb/tools/lldb-dap/IOStream.h | 8 +-
lldb/tools/lldb-dap/Options.td | 22 +-
lldb/tools/lldb-dap/Socket.cpp | 500 ++++++++++++++++++
lldb/tools/lldb-dap/Socket.h | 182 +++++++
lldb/tools/lldb-dap/lldb-dap.cpp | 370 ++++---------
12 files changed, 923 insertions(+), 360 deletions(-)
create mode 100644 lldb/tools/lldb-dap/Socket.cpp
create mode 100644 lldb/tools/lldb-dap/Socket.h
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 e4a53fe0d45907..8a71b9e709f138 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
@@ -9,6 +9,7 @@
import string
import subprocess
import sys
+import selectors
import threading
import time
@@ -1150,36 +1151,37 @@ def request_setInstructionBreakpoints(self, memory_reference=[]):
}
return self.send_recv(command_dict)
+
class DebugAdaptorServer(DebugCommunication):
def __init__(
self,
executable=None,
launch=True,
- port=None,
- unix_socket=None,
+ connection=None,
init_commands=[],
log_file=None,
env=None,
):
self.process = None
if launch:
- self.process = DebugAdaptorServer.launch(
+ self.process, connection = DebugAdaptorServer.launch(
executable,
- port=port,
- unix_socket=unix_socket,
+ connection=connection,
log_file=log_file,
env=env,
)
- if port:
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- s.connect(("127.0.0.1", port))
- DebugCommunication.__init__(
- 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)
+ if connection:
+ print("attempting connection", connection)
+ if connection.startswith("unix://"): # unix:///path
+ s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ s.connect(connection.removeprefix("unix://"))
+ elif connection.startswith("tcp://"): # tcp://host:port
+ host, port = connection.removeprefix("tcp://").split(":", 1)
+ # create_connection with try both ipv4 and ipv6.
+ s = socket.create_connection((host, int(port)))
+ else:
+ raise ValueError("invalid connection: {}".format(connection))
DebugCommunication.__init__(
self, s.makefile("rb"), s.makefile("wb"), init_commands, log_file
)
@@ -1202,8 +1204,8 @@ def terminate(self):
@classmethod
def launch(
- cls, executable: str, /, port=None, unix_socket=None, log_file=None, env=None
- ) -> subprocess.Popen:
+ 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)
@@ -1211,13 +1213,13 @@ def launch(
if log_file:
adaptor_env["LLDBDAP_LOG"] = log_file
+ if os.uname().sysname == "Darwin":
+ adaptor_env["NSUnbufferedIO"] = "YES"
+
args = [executable]
- if port:
- args.append("--port")
- args.append(str(port))
- elif unix_socket:
- args.append("--unix-socket")
- args.append(unix_socket)
+ if connection:
+ args.append("--connection")
+ args.append(connection)
proc = subprocess.Popen(
args,
@@ -1227,11 +1229,34 @@ def launch(
env=adaptor_env,
)
- if port or unix_socket:
- # Wait for the server to startup.
- time.sleep(0.1)
-
- return proc
+ 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`.
+ with selectors.DefaultSelector() as sel:
+ print("Reading stdout for the listening connection")
+ os.set_blocking(proc.stdout.fileno(), False)
+ stdout_key = sel.register(proc.stdout, selectors.EVENT_READ)
+ rdy_fds = sel.select(timeout=10.0)
+ for key, _ in rdy_fds:
+ if key != stdout_key:
+ continue
+
+ outs = proc.stdout.read(1024).decode()
+ os.set_blocking(proc.stdout.fileno(), True)
+ for line in outs.split("\n"):
+ if not line.startswith("Listening for: "):
+ continue
+ # If the listener expanded into multiple addresses, use the first.
+ connection = line.removeprefix("Listening for: ").split(",")[0]
+ print("")
+ return proc, connection
+ proc.kill()
+ raise ValueError(
+ "lldb-dap started with a connection but failed to write the listening address to stdout."
+ )
+
+ return proc, None
def attach_options_specified(options):
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 3fcc08e9ff55cb..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, env=None, launch=True, port=None, unix_socket=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"
@@ -22,8 +22,7 @@ def create_debug_adaptor(self, env=None, launch=True, port=None, unix_socket=Non
self.dap_server = dap_server.DebugAdaptorServer(
executable=self.lldbDAPExec,
launch=launch,
- port=port,
- unix_socket=unix_socket,
+ connection=connection,
init_commands=self.setUpCommands(),
log_file=log_file_path,
env=env,
@@ -33,15 +32,13 @@ def build_and_create_debug_adaptor(
self,
lldbDAPEnv=None,
lldbDAPLaunch=True,
- lldbDAPPort=None,
- lldbDAPUnixSocket=None,
+ lldbDAPConnection=None,
):
self.build()
self.create_debug_adaptor(
env=lldbDAPEnv,
launch=lldbDAPLaunch,
- port=lldbDAPPort,
- unix_socket=lldbDAPUnixSocket,
+ connection=lldbDAPConnection,
)
def set_source_breakpoints(self, source_path, lines, data=None):
@@ -489,9 +486,8 @@ def build_and_launch(
customThreadFormat=None,
launchCommands=None,
expectFailure=False,
- lldbDAPPort=None,
- lldbDAPUnixSocket=None,
lldbDAPLaunch=True,
+ lldbDAPConnection=None,
):
"""Build the default Makefile target, create the DAP debug adaptor,
and launch the process.
@@ -499,8 +495,7 @@ def build_and_launch(
self.build_and_create_debug_adaptor(
lldbDAPEnv=lldbDAPEnv,
lldbDAPLaunch=lldbDAPLaunch,
- lldbDAPPort=lldbDAPPort,
- lldbDAPUnixSocket=lldbDAPUnixSocket,
+ lldbDAPConnection=lldbDAPConnection,
)
self.assertTrue(os.path.exists(program), "executable must exist")
diff --git a/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py b/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
index 46b992a77a4815..27bd583d4f6290 100644
--- a/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
+++ b/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
@@ -12,10 +12,10 @@
class TestDAP_server(lldbdap_testcase.DAPTestCaseBase):
- def do_test_server(self, port=None, unix_socket=None):
+ def do_test_server(self, connection):
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
+ server, connection = dap_server.DebugAdaptorServer.launch(
+ self.lldbDAPExec, connection, log_file=log_file_path
)
def cleanup():
@@ -30,7 +30,7 @@ def cleanup():
breakpoint_line = line_number(source, "// breakpoint")
# Initial connection over the port.
- self.create_debug_adaptor(launch=False, port=port, unix_socket=unix_socket)
+ self.create_debug_adaptor(launch=False, connection=connection)
self.launch(
program,
disconnectAutomatically=False,
@@ -43,7 +43,7 @@ def cleanup():
self.dap_server.request_disconnect()
# Second connection over the port.
- self.create_debug_adaptor(launch=False, port=port, unix_socket=unix_socket)
+ self.create_debug_adaptor(launch=False, connection=connection)
self.launch(program)
self.set_source_breakpoints(source, [breakpoint_line])
self.continue_to_next_stop()
@@ -55,12 +55,13 @@ 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)
+ self.do_test_server(connection="tcp://localhost:0")
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()))
+ self.do_test_server(
+ connection="unix://" + dir + "/dap-connection-" + str(os.getpid())
+ )
diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt
index d68098bf7b3266..f81be385c62aab 100644
--- a/lldb/tools/lldb-dap/CMakeLists.txt
+++ b/lldb/tools/lldb-dap/CMakeLists.txt
@@ -26,19 +26,20 @@ 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
OutputRedirector.cpp
ProgressEvent.cpp
RunInTerminal.cpp
+ Socket.cpp
SourceBreakpoint.cpp
- DAP.cpp
Watchpoint.cpp
- InstructionBreakpoint.cpp
LINK_LIBS
liblldb
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index 8998036fbedf6b..29e200f2475eb8 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,10 @@ using namespace lldb_dap;
namespace lldb_dap {
-DAP::DAP(llvm::StringRef path, std::shared_ptr<std::ofstream> log,
+DAP::DAP(llvm::StringRef path, std::shared_ptr<llvm::raw_ostream> 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),
+ : 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),
@@ -48,6 +51,28 @@ DAP::DAP(llvm::StringRef path, std::shared_ptr<std::ofstream> log,
DAP::~DAP() = default;
+llvm::Error DAP::ConfigureIO(int out_fd, int err_fd) {
+ llvm::Expected<int> new_stdout_fd =
+ 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 =
+ RedirectFd(err_fd, [this](llvm::StringRef data) {
+ SendOutput(OutputType::Stderr, data);
+ });
+ if (auto Err = new_stderr_fd.takeError()) {
+ return Err;
+ }
+
+ out = lldb::SBFile(new_stdout_fd.get(), "w", false);
+ err = lldb::SBFile(new_stderr_fd.get(), "w", false);
+
+ return llvm::Error::success();
+}
+
/// Return string with first character capitalized.
static std::string capitalize(llvm::StringRef str) {
if (str.empty())
@@ -183,9 +208,9 @@ void DAP::SendJSON(const llvm::json::Value &json) {
if (log) {
auto now = std::chrono::duration<double>(
std::chrono::system_clock::now().time_since_epoch());
- *log << llvm::formatv("{0:f9} <-- ", now.count()).str() << std::endl
+ *log << llvm::formatv("{0:f9} <-- ", now.count()).str() << "\n"
<< "Content-Length: " << json_str.size() << "\r\n\r\n"
- << llvm::formatv("{0:2}", json).str() << std::endl;
+ << llvm::formatv("{0:2}", json).str() << "\n";
}
}
@@ -213,8 +238,8 @@ std::string DAP::ReadJSON() {
if (log) {
auto now = std::chrono::duration<double>(
std::chrono::system_clock::now().time_since_epoch());
- *log << llvm::formatv("{0:f9} --> ", now.count()).str() << std::endl
- << "Content-Length: " << length << "\r\n\r\n";
+ *log << llvm::formatv("{0:f9} --> ", now.count()).str()
+ << "\nContent-Length: " << length << "\r\n\r\n";
}
return json_str;
}
@@ -654,20 +679,20 @@ PacketStatus DAP::GetNextObject(llvm::json::Object &object) {
std::string error_str;
llvm::raw_string_ostream strm(error_str);
strm << error;
- *log << "error: failed to parse JSON: " << error_str << std::endl
- << json << std::endl;
+ *log << "error: failed to parse JSON: " << error_str << "\n"
+ << json << "\n";
}
return PacketStatus::JSONMalformed;
}
if (log) {
- *log << llvm::formatv("{0:2}", *json_value).str() << std::endl;
+ *log << llvm::formatv("{0:2}", *json_value).str() << "\n";
}
llvm::json::Object *object_ptr = json_value->getAsObject();
if (!object_ptr) {
if (log)
- *log << "error: json packet isn't a object" << std::endl;
+ *log << "error: json packet isn't a object\n";
return PacketStatus::JSONNotObject;
}
object = *object_ptr;
@@ -681,8 +706,7 @@ bool DAP::HandleObject(const llvm::json::Object &object) {
auto handler_pos = request_handlers.find(command);
if (handler_pos == request_handlers.end()) {
if (log)
- *log << "error: unhandled command \"" << command.data() << "\""
- << std::endl;
+ *log << "error: unhandled command \"" << command.data() << "\"\n";
return false; // Fail
}
diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h
index ec70da67edac14..8f847c1fdffbe4 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -146,7 +146,7 @@ struct DAP {
lldb::SBBroadcaster broadcaster;
std::thread event_thread;
std::thread progress_event_thread;
- std::shared_ptr<std::ofstream> log;
+ std::shared_ptr<llvm::raw_ostream> log;
llvm::StringMap<SourceBreakpointMap> source_breakpoints;
FunctionBreakpointMap function_breakpoints;
InstructionBreakpointMap instruction_breakpoints;
@@ -198,7 +198,7 @@ struct DAP {
// will contain that expression.
std::string last_nonempty_var_expression;
- DAP(llvm::StringRef path, std::shared_ptr<std::ofstream> log,
+ DAP(llvm::StringRef path, std::shared_ptr<llvm::raw_ostream> log,
ReplMode repl_mode, std::vector<std::string> pre_init_commands);
~DAP();
@@ -206,6 +206,23 @@ struct DAP {
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..188f6ec1fae9d9 100644
--- a/lldb/tools/lldb-dap/IOStream.cpp
+++ b/lldb/tools/lldb-dap/IOStream.cpp
@@ -7,6 +7,8 @@
//===----------------------------------------------------------------------===//
#include "IOStream.h"
+#include <string>
+#include "llvm/Support/raw_ostream.h"
#if defined(_WIN32)
#include <io.h>
@@ -16,8 +18,6 @@
#include <unistd.h>
#endif
-#include <fstream>
-#include <string>
using namespace lldb_dap;
@@ -87,7 +87,7 @@ bool OutputStream::write_full(llvm::StringRef str) {
return true;
}
-bool InputStream::read_full(std::ofstream *log, size_t length,
+bool InputStream::read_full(llvm::raw_ostream *log, size_t length,
std::string &text) {
std::string data;
data.resize(length);
@@ -131,7 +131,7 @@ bool InputStream::read_full(std::ofstream *log, size_t length,
return true;
}
-bool InputStream::read_line(std::ofstream *log, std::string &line) {
+bool InputStream::read_line(llvm::raw_ostream *log, std::string &line) {
line.clear();
while (true) {
if (!read_full(log, 1, line))
@@ -144,7 +144,7 @@ bool InputStream::read_line(std::ofstream *log, std::string &line) {
return true;
}
-bool InputStream::read_expected(std::ofstream *log, llvm::StringRef expected) {
+bool InputStream::read_expected(llvm::raw_ostream *log, llvm::StringRef expected) {
std::string result;
if (!read_full(log, expected.size(), result))
return false;
diff --git a/lldb/tools/lldb-dap/IOStream.h b/lldb/tools/lldb-dap/IOStream.h
index 57d5fd458b7165..1933399e075363 100644
--- a/lldb/tools/lldb-dap/IOStream.h
+++ b/lldb/tools/lldb-dap/IOStream.h
@@ -22,7 +22,7 @@ typedef int SOCKET;
#endif
#include "llvm/ADT/StringRef.h"
-
+#include "llvm/Support/raw_ostream.h"
#include <fstream>
#include <string>
@@ -52,11 +52,11 @@ struct StreamDescriptor {
struct InputStream {
StreamDescriptor descriptor;
- bool read_full(std::ofstream *log, size_t length, std::string &text);
+ bool read_full(llvm::raw_ostream *log, size_t length, std::string &text);
- bool read_line(std::ofstream *log, std::string &line);
+ bool read_line(llvm::raw_ostream *log, std::string &line);
- bool read_expected(std::ofstream *log, llvm::StringRef expected);
+ bool read_expected(llvm::raw_ostream *log, llvm::StringRef expected);
};
struct OutputStream {
diff --git a/lldb/tools/lldb-dap/Options.td b/lldb/tools/lldb-dap/Options.td
index dfca79b2884ac8..41d8912ed6c6bf 100644
--- a/lldb/tools/lldb-dap/Options.td
+++ b/lldb/tools/lldb-dap/Options.td
@@ -17,21 +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>,
- 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 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>">,
@@ -51,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/Socket.cpp b/lldb/tools/lldb-dap/Socket.cpp
new file mode 100644
index 00000000000000..c5c90a81547878
--- /dev/null
+++ b/lldb/tools/lldb-dap/Socket.cpp
@@ -0,0 +1,500 @@
+//===-- Socket.cpp ----------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "Socket.h"
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Config/config.h"
+#include "llvm/Support/Errno.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/Signals.h"
+#include "llvm/Support/Threading.h"
+#include <cstdint>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <sys/_types/_socklen_t.h>
+#include <sys/poll.h>
+#include <system_error>
+
+#ifndef _WIN32
+#include <poll.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#else
+#include "llvm/Support/Windows/WindowsSupport.h"
+// winsock2.h must be included before afunix.h. Briefly turn off clang-format to
+// avoid error.
+// clang-format off
+#include <winsock2.h>
+#include <afunix.h>
+// clang-format on
+#include <io.h>
+#endif // _WIN32
+
+#if defined(HAVE_UNISTD_H)
+#include <unistd.h>
+#endif
+
+namespace {
+
+enum SocketProtocol { ProtocolTcp, ProtocolUnixDomain };
+
+std::error_code getLastSocketErrorCode() {
+#ifdef _WIN32
+ return std::error_code(::WSAGetLastError(), std::system_category());
+#else
+ return llvm::errnoAsErrorCode();
+#endif
+}
+
+llvm::Expected<std::vector<lldb_dap::SocketAddress>>
+getAddressInfo(llvm::StringRef host) {
+ std::vector<lldb_dap::SocketAddress> add_list;
+
+ struct addrinfo hints;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+ hints.ai_flags = 0;
+
+ struct addrinfo *service_info_list = nullptr;
+
+ int err =
+ getaddrinfo(host.str().c_str(), nullptr, &hints, &service_info_list);
+ if (err != 0)
+ return llvm::createStringError("getaddrinfo failed: %s", gai_strerror(err));
+
+ for (struct addrinfo *service_ptr = service_info_list; service_ptr != nullptr;
+ service_ptr = service_ptr->ai_next) {
+ add_list.emplace_back(lldb_dap::SocketAddress(service_ptr));
+ }
+
+ if (service_info_list)
+ ::freeaddrinfo(service_info_list);
+
+ return add_list;
+}
+
+int CloseSocket(NativeSocket sockfd) {
+#ifdef _WIN32
+ return ::closesocket(sockfd);
+#else
+ return ::close(sockfd);
+#endif
+}
+
+llvm::Expected<std::pair<llvm::StringRef, int>>
+detectHostAndPort(llvm::StringRef name) {
+ llvm::StringRef host;
+ llvm::StringRef port;
+ std::tie(host, port) = name.split(":");
+
+ if (host == "" || host == "*") {
+ host = "0.0.0.0";
+ }
+
+ if (port == "") {
+ port = "0";
+ }
+
+ int portnu = 0;
+ if (!llvm::to_integer(port, portnu, 10)) {
+ return llvm::createStringError(llvm::inconvertibleErrorCode(),
+ "invalid host:port specification: '%s'",
+ name.str().c_str());
+ }
+
+ return std::make_pair(host, portnu);
+}
+
+} // namespace
+
+namespace lldb_dap {
+
+#ifdef _WIN32
+WSABalancer::WSABalancer() {
+ WSADATA WsaData;
+ ::memset(&WsaData, 0, sizeof(WsaData));
+ if (WSAStartup(MAKEWORD(2, 2), &WsaData) != 0) {
+ llvm::report_fatal_error("WSAStartup failed");
+ }
+}
+
+WSABalancer::~WSABalancer() { WSACleanup(); }
+#endif // _WIN32
+
+SocketAddress::SocketAddress() { clear(); }
+
+SocketAddress::SocketAddress(const struct addrinfo *info) {
+ clear();
+ if (info && info->ai_addrlen > 0 &&
+ size_t(info->ai_addrlen) <= sizeof m_address) {
+ memcpy(&m_address.sa, info->ai_addr, static_cast<size_t>(info->ai_addrlen));
+ m_address.sa.sa_len = info->ai_addrlen;
+ }
+}
+
+SocketAddress::SocketAddress(const struct sockaddr &sa) { m_address.sa = sa; }
+
+SocketAddress::SocketAddress(const struct sockaddr_in &sa) {
+ m_address.sa_ipv4 = sa;
+}
+
+SocketAddress::SocketAddress(const struct sockaddr_in6 &sa) {
+ m_address.sa_ipv6 = sa;
+}
+
+SocketAddress::SocketAddress(const struct sockaddr_un &su) {
+ m_address.su = su;
+}
+
+std::string SocketAddress::getPath() const {
+ if (getFamily() == AF_UNIX)
+ return std::string(m_address.su.sun_path);
+ return "";
+}
+
+std::string SocketAddress::getName() const {
+ if (getFamily() == AF_UNIX)
+ return "unix://" + getPath();
+
+ uint64_t port = getPort();
+ std::string hostname;
+ if (isLocalhost())
+ hostname = "localhost";
+ else if (isAnyAddr())
+ hostname = getFamily() == AF_INET ? "0.0.0.0" : "[::]";
+ else {
+ char hbuf[NI_MAXHOST];
+ if (getnameinfo(&m_address.sa, getLength(), hbuf, sizeof(hbuf), nullptr, 0,
+ NI_NUMERICHOST) == 0) {
+ hostname = std::string(hbuf);
+ }
+ }
+
+ return "tcp://" + hostname + ":" + std::to_string(port);
+}
+
+bool SocketAddress::isLocalhost() const {
+ switch (getFamily()) {
+ case AF_INET:
+ return m_address.sa_ipv4.sin_addr.s_addr == htonl(INADDR_LOOPBACK);
+ case AF_INET6:
+ return 0 == memcmp(&m_address.sa_ipv6.sin6_addr, &in6addr_loopback, 16);
+ }
+ return false;
+}
+
+bool SocketAddress::isAnyAddr() const {
+ switch (getFamily()) {
+ case AF_INET:
+ return m_address.sa_ipv4.sin_addr.s_addr == htonl(INADDR_ANY);
+ case AF_INET6:
+ return 0 == memcmp(&m_address.sa_ipv6.sin6_addr, &in6addr_any, 16);
+ }
+ return false;
+}
+
+void SocketAddress::setFamily(sa_family_t family) {
+ m_address.sa.sa_family = family;
+#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || \
+ defined(__OpenBSD__)
+ switch (family) {
+ case AF_INET:
+ m_address.sa.sa_len = sizeof(struct sockaddr_in);
+ break;
+
+ case AF_INET6:
+ m_address.sa.sa_len = sizeof(struct sockaddr_in6);
+ break;
+
+ case AF_UNIX:
+ m_address.sa.sa_len = SUN_LEN(&m_address.su);
+ break;
+
+ default:
+ assert(0 && "unsupported socket family");
+ }
+#endif
+}
+
+SocketAddress SocketAddress::addressOf(const Socket &s, std::error_code &EC) {
+ SocketAddress sa;
+ socklen_t len = sizeof(sockaddr_t);
+
+ if (::getsockname(s.fd, &sa.sockaddr(), &len) == -1) {
+ EC = getLastSocketErrorCode();
+ return SocketAddress{};
+ }
+
+ // If the socket is a struct sockaddr_un then the length is returned by the
+ // len parameter not the sa_len field. Update the field to match the
+ // returned address.
+ sa.m_address.sa.sa_len = len;
+ return sa;
+}
+
+bool SocketAddress::setToAnyAddress(sa_family_t family, uint16_t port) {
+ switch (family) {
+ case AF_INET:
+ setFamily(family);
+ if (setPort(port)) {
+ m_address.sa_ipv4.sin_addr.s_addr = htonl(INADDR_ANY);
+ return true;
+ }
+ break;
+
+ case AF_INET6:
+ setFamily(family);
+ if (setPort(port)) {
+ m_address.sa_ipv6.sin6_addr = in6addr_any;
+ return true;
+ }
+ break;
+ }
+
+ clear();
+ return false;
+}
+
+Socket::Socket(NativeSocket fd) : fd(fd) {}
+Socket::Socket(Socket &&S) : fd(S.fd) { S.fd = -1; }
+
+Socket::~Socket() {
+ close();
+}
+
+void Socket::close() {
+ if (fd == -1) {
+ return;
+ }
+
+ CloseSocket(fd);
+ fd = -1;
+}
+
+SocketListener::SocketListener(std::vector<Socket> sockets, int fds[2])
+ : m_listening(true), m_sockets(std::move(sockets)), m_pipe{fds[0], fds[1]} {
+}
+
+SocketListener::SocketListener(SocketListener &&SL)
+ : m_listening(SL.m_listening.load()), m_sockets(std::move(SL.m_sockets)),
+ m_pipe{SL.m_pipe[0], SL.m_pipe[1]} {
+ SL.m_listening = false;
+ SL.m_sockets.clear();
+ SL.m_pipe[0] = -1;
+ SL.m_pipe[1] = -1;
+}
+
+llvm::Expected<SocketListener>
+SocketListener::createListener(llvm::StringRef name) {
+ SocketProtocol protocol;
+
+ if (name.consume_front("tcp://") || name.consume_front("tcp:") ||
+ name.starts_with(":")) {
+ protocol = ProtocolTcp;
+ } else if (name.consume_front("unix://") || name.consume_front("unix:") ||
+ name.starts_with("/")) {
+ protocol = ProtocolUnixDomain;
+ } else if (name.contains(":")) {
+ protocol = ProtocolTcp;
+ } else {
+ return llvm::createStringError(
+ "invalid address, expected '[tcp://][host]:port' or "
+ "'[unix://]/path' but got %s.",
+ name.str().c_str());
+ }
+
+ std::vector<SocketAddress> addresses;
+ if (protocol == ProtocolTcp) {
+ auto maybeHostAndPort = detectHostAndPort(name);
+ if (auto Err = maybeHostAndPort.takeError())
+ return Err;
+
+ llvm::StringRef host;
+ int port;
+ std::tie(host, port) = *maybeHostAndPort;
+
+ llvm::Expected<std::vector<SocketAddress>> maybeAddresses =
+ getAddressInfo(host);
+ if (auto Err = maybeAddresses.takeError()) {
+ return Err;
+ }
+
+ for (auto &address : *maybeAddresses) {
+ SocketAddress listen_address = address;
+ if (!listen_address.isLocalhost())
+ listen_address.setToAnyAddress(address.getFamily(), port);
+ else
+ listen_address.setPort(port);
+
+ addresses.push_back(std::move(listen_address));
+ }
+ } else {
+ llvm::StringRef path = name;
+ if (llvm::sys::fs::exists(path)) {
+ return llvm::make_error<llvm::StringError>(
+ std::make_error_code(std::errc::file_exists), "file exists at path");
+ }
+
+ struct sockaddr_un addr;
+ bzero(&addr, sizeof(struct sockaddr_un));
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, path.str().c_str(), sizeof(addr.sun_path) - 1);
+ addr.sun_len = SUN_LEN(&addr);
+
+ addresses.push_back(SocketAddress(addr));
+ }
+
+ std::vector<Socket> sockets;
+ for (auto &address : addresses) {
+ bool isTCP =
+ address.getFamily() == AF_INET || address.getFamily() == AF_INET6;
+
+ NativeSocket fd =
+ ::socket(address.getFamily(), SOCK_STREAM, isTCP ? IPPROTO_TCP : 0);
+ if (fd == -1) {
+ return llvm::createStringError(getLastSocketErrorCode(),
+ "socket() failed");
+ }
+
+ int val = 1;
+ if (::setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(int)) == -1) {
+ return llvm::make_error<llvm::StringError>(getLastSocketErrorCode(),
+ "setsockopt() failed");
+ }
+
+ if (::bind(fd, &address.sockaddr(), address.getLength()) == -1) {
+ return llvm::make_error<llvm::StringError>(getLastSocketErrorCode(),
+ "bind() failed");
+ }
+
+ if (::listen(fd, llvm::hardware_concurrency().compute_thread_count()) ==
+ -1) {
+ return llvm::make_error<llvm::StringError>(getLastSocketErrorCode(),
+ "listen() failed");
+ }
+
+ sockets.emplace_back(fd);
+ }
+
+ int pipeFD[2];
+#ifdef _WIN32
+ // Reserve 1 byte for the pipe and use default textmode
+ if (::_pipe(pipeFD, 1, 0) == -1)
+#else
+ if (::pipe(pipeFD) == -1)
+#endif // _WIN32
+ return llvm::make_error<llvm::StringError>(getLastSocketErrorCode(),
+ "pipe failed");
+
+ return SocketListener{std::move(sockets), pipeFD};
+}
+
+SocketListener::~SocketListener() {
+ shutdown();
+ if (m_pipe[0] != -1)
+ close(m_pipe[0]);
+ if (m_pipe[1] != -1)
+ close(m_pipe[1]);
+}
+
+llvm::Expected<std::vector<std::string>> SocketListener::addresses() const {
+ std::vector<std::string> addrs;
+ std::error_code EC;
+ for (auto &s : m_sockets) {
+ auto addr = SocketAddress::addressOf(s, EC);
+ if (EC)
+ return llvm::make_error<llvm::StringError>(EC);
+
+ addrs.push_back(addr.getName());
+ }
+ return addrs;
+}
+
+void SocketListener::shutdown() {
+ bool listening = m_listening;
+
+ if (!listening)
+ return;
+
+ if (!m_listening.compare_exchange_strong(listening, false))
+ return;
+
+ for (auto &s : m_sockets) {
+ // If the socket has a path (e.g. a unix:// socket), remove it after
+ // closing.
+ std::error_code _; // ignoring failures during shutdown.
+ std::string path = SocketAddress::addressOf(s, _).getPath();
+ s.close();
+ if (!path.empty())
+ unlink(path.c_str());
+ }
+
+ // Write to the pipe to indiciate that accept() should exit immediately.
+ char byte = 'A';
+ ssize_t written = ::write(m_pipe[1], &byte, 1);
+
+ // Ignore any write() error
+ (void)written;
+}
+
+llvm::Expected<Socket> SocketListener::accept() {
+ if (m_sockets.empty()) {
+ return llvm::createStringError(
+ std::make_error_code(std::errc::bad_file_descriptor), "no open socket");
+ }
+
+ std::vector<struct pollfd> pollfds;
+
+ for (auto &s : m_sockets)
+ pollfds.emplace_back(pollfd{s.fd, POLLIN, 0});
+
+ pollfds.emplace_back(pollfd{m_pipe[0], POLLIN, 0});
+
+ while (m_listening.load()) {
+ int status;
+#ifdef _WIN32
+ status = WSAPoll(pollfds.data(), pollfds.size(), -1);
+#else
+ status = ::poll(pollfds.data(), pollfds.size(), -1);
+#endif
+
+ if (status == -1) {
+ return llvm::make_error<llvm::StringError>(getLastSocketErrorCode(),
+ "poll() failed");
+ }
+
+ for (auto &pfd : pollfds) {
+ if (pfd.revents & POLLIN) {
+ // Check if shutdown was requested.
+ if (pfd.fd == m_pipe[0]) {
+ break;
+ }
+
+ struct sockaddr client;
+ socklen_t len;
+ int fd =
+ llvm::sys::RetryAfterSignal(-1, ::accept, pfd.fd, &client, &len);
+ if (fd == -1) {
+ return llvm::make_error<llvm::StringError>(getLastSocketErrorCode(),
+ "accept() failed");
+ }
+
+ return Socket{fd};
+ }
+ }
+ }
+
+ return llvm::make_error<llvm::StringError>(
+ std::make_error_code(std::errc::connection_aborted), "socket shutdown");
+}
+
+} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Socket.h b/lldb/tools/lldb-dap/Socket.h
new file mode 100644
index 00000000000000..98923032f91417
--- /dev/null
+++ b/lldb/tools/lldb-dap/Socket.h
@@ -0,0 +1,182 @@
+//===-- Socket.h ------------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_TOOLS_LLDB_DAP_SOCKET_H
+#define LLDB_TOOLS_LLDB_DAP_SOCKET_H
+
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+#include <cstdint>
+#include <string>
+#include <vector>
+
+#ifdef _WIN32
+#include "lldb/Host/windows/windows.h"
+#include <winsock2.h>
+#include <ws2tcpip.h>
+typedef ADDRESS_FAMILY sa_family_t;
+typedef SOCKET NativeSocket;
+#else
+#include <netdb.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+typedef int NativeSocket;
+#endif
+
+namespace lldb_dap {
+
+struct Socket;
+
+/// Manages socket addresses for socket based communication.
+struct SocketAddress {
+ SocketAddress();
+ explicit SocketAddress(const struct addrinfo *);
+ explicit SocketAddress(const struct sockaddr &);
+ explicit SocketAddress(const struct sockaddr_in &);
+ explicit SocketAddress(const struct sockaddr_in6 &);
+ explicit SocketAddress(const struct sockaddr_un &);
+
+ static SocketAddress addressOf(const Socket &s, std::error_code &EC);
+
+ sa_family_t getFamily() const { return m_address.sa.sa_family; }
+ uint16_t getPort() const {
+ switch (getFamily()) {
+ case AF_INET:
+ return ntohs(m_address.sa_ipv4.sin_port);
+ case AF_INET6:
+ return ntohs(m_address.sa_ipv6.sin6_port);
+ }
+ return 0;
+ }
+ std::string getName() const;
+ /// Returns the path for AF_UNIX sockets, otherwise "".
+ std::string getPath() const;
+ /// Get the length for the current socket address family
+ socklen_t getLength() const { return m_address.sa.sa_len; };
+
+ bool isLocalhost() const;
+ bool isAnyAddr() const;
+
+ void setFamily(sa_family_t family);
+ bool setToAnyAddress(sa_family_t family, uint16_t port);
+ bool setPort(uint16_t port) {
+ switch (getFamily()) {
+ case AF_INET:
+ m_address.sa_ipv4.sin_port = htons(port);
+ return true;
+ case AF_INET6:
+ m_address.sa_ipv6.sin6_port = htons(port);
+ return true;
+ }
+ return false;
+ }
+
+ void clear() { bzero(&m_address, sizeof(sockaddr_t)); }
+
+ struct sockaddr &sockaddr() { return m_address.sa; }
+ const struct sockaddr &sockaddr() const { return m_address.sa; }
+
+private:
+ typedef union sockaddr_tag {
+ struct sockaddr sa;
+ struct sockaddr_in sa_ipv4;
+ struct sockaddr_in6 sa_ipv6;
+ struct sockaddr_un su;
+ struct sockaddr_storage ss;
+ } sockaddr_t;
+ sockaddr_t m_address;
+};
+
+/// Manages the lifetime of a socket.
+struct Socket {
+ NativeSocket fd;
+
+ /// Close the socket.
+ void close();
+
+ explicit Socket(NativeSocket fd);
+ ~Socket();
+ Socket(Socket &&S);
+ Socket(const Socket &S) = delete;
+ Socket &operator=(const Socket &S) = delete;
+};
+
+#ifdef _WIN32
+/// Ensures proper initialization and cleanup of winsock resources
+///
+/// Make sure that calls to WSAStartup and WSACleanup are balanced.
+class WSABalancer {
+public:
+ WSABalancer();
+ ~WSABalancer();
+};
+#endif // _WIN32
+
+/// Manages listening for socket connections.
+///
+/// SocketListener handles listening for both unix and tcp based connections.
+struct SocketListener {
+ SocketListener(const SocketListener &S) = delete;
+ SocketListener &operator=(const SocketListener &S) = delete;
+ SocketListener(SocketListener &&SL);
+ ~SocketListener();
+
+ /// Creates a listening socket bound to the specified name.
+ ///
+ /// Handles the socket creation, binding, and immediately starts listening for
+ /// incoming connections.
+ ///
+ /// \param[in] name
+ /// Socket names formatted like `protocol:name`.
+ ///
+ /// Supported protocols include tcp and unix sockets.
+ ///
+ /// Names must follow the following formats:
+ ///
+ /// * tcp://host:port
+ /// * tcp:host:port
+ /// * tcp:port (host will be assumed 0.0.0.0)
+ /// * :port (implicit tcp)
+ /// * unix:///path
+ /// * /path (implicit unix)
+ static llvm::Expected<SocketListener> createListener(llvm::StringRef name);
+
+ /// Returns an array of active listening sockets.
+ llvm::Expected<std::vector<std::string>> addresses() const;
+
+ /// Shutdown the socket listener.
+ void shutdown();
+
+ /// Returns true if listening, otherwise false.
+ bool isListening() const { return m_listening; }
+
+ /// Accept returns the next client connection, blocking until a connection is
+ /// made or Shutdown is called.
+ llvm::Expected<Socket> accept();
+
+private:
+ SocketListener(std::vector<Socket> sockets, int fds[2]);
+
+ std::atomic<bool> m_listening;
+ std::vector<Socket> m_sockets;
+
+ /// If a separate thread calls shutdown, the listening file descriptor
+ /// could be closed while ::poll is waiting for it to be ready to perform a
+ /// I/O operations. ::poll will continue to block even after FD is closed so
+ /// use a self-pipe mechanism to get ::poll to return
+ int m_pipe[2] = {};
+
+#ifdef _WIN32
+ WSABalancer _;
+#endif // _WIN32
+};
+
+} // namespace lldb_dap
+
+#endif
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index 51197587ffccec..0c602ecbbbe836 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -10,8 +10,8 @@
#include "FifoFiles.h"
#include "JSONUtils.h"
#include "LLDBUtils.h"
-#include "OutputRedirector.h"
#include "RunInTerminal.h"
+#include "Socket.h"
#include "Watchpoint.h"
#include "lldb/API/SBDeclaration.h"
#include "lldb/API/SBFile.h"
@@ -31,11 +31,12 @@
#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>
@@ -45,6 +46,7 @@
#include <cstdio>
#include <cstdlib>
#include <cstring>
+#include <iostream>
#include <map>
#include <memory>
#include <optional>
@@ -52,6 +54,7 @@
#include <sys/stat.h>
#include <sys/types.h>
#include <thread>
+#include <utility>
#include <vector>
#if defined(_WIN32)
@@ -141,234 +144,6 @@ lldb::SBValueList *GetTopLevelScope(DAP &dap, int64_t variablesReference) {
}
}
-/// 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) {
- 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");
- }
-
- 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) {
// 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
@@ -500,12 +275,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();
}
@@ -5115,6 +4889,8 @@ static void redirection_test() {
fflush(stderr);
}
+static SocketListener *g_listener = nullptr;
+
int main(int argc, char *argv[]) {
llvm::InitLLVM IL(argc, argv, /*InstallPipeSignalExitHandler=*/false);
#if !defined(__APPLE__)
@@ -5185,27 +4961,23 @@ 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 unix_socket_path;
- if (auto *arg = input_args.getLastArg(OPT_unix_socket)) {
+ std::string connection;
+ if (auto *arg = input_args.getLastArg(OPT_connection)) {
const auto *path = arg->getValue();
- unix_socket_path.assign(path);
+ connection.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);
+ std::shared_ptr<llvm::raw_ostream> log;
+ if (log_file_path) {
+ std::error_code EC;
+ log.reset(new llvm::raw_fd_ostream(log_file_path, EC));
+ if (EC) {
+ llvm::errs() << "Could not open log file: " << EC.message() << ", "
+ << log_file_path << '\n';
+ return EXIT_FAILURE;
+ }
+ }
const auto pre_init_commands =
input_args.getAllArgValues(OPT_pre_init_command);
@@ -5228,25 +5000,90 @@ int main(int argc, char *argv[]) {
auto terminate_debugger =
llvm::make_scope_exit([] { lldb::SBDebugger::Terminate(); });
- 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 (!connection.empty()) {
+ auto maybeListener = SocketListener::createListener(connection);
+ if (auto Err = maybeListener.takeError()) {
+ llvm::errs() << "Failed to listen for connections: "
+ << llvm::toString(std::move(Err)) << "\n";
+ return EXIT_FAILURE;
+ }
+
+ g_listener = &*maybeListener;
+
+ llvm::sys::SetInterruptFunction([]() {
+ if (g_listener)
+ g_listener->shutdown();
+ });
+
+ auto maybeListenerAddresses = g_listener->addresses();
+ if (auto Err = maybeListenerAddresses.takeError()) {
+ llvm::errs() << "listener failed to retrieve listening socket address: "
+ << Err << "\n";
+ return EXIT_FAILURE;
+ }
+
+ if (log)
+ *log << "started with connection listeners "
+ << llvm::join(*maybeListenerAddresses, ", ") << "\n";
+
+ llvm::outs() << "Listening for: "
+ << llvm::join(*maybeListenerAddresses, ", ") << "\n";
+ // Ensure listening address are flushed for calles to retrieve the resolve address.
+ llvm::outs().flush();
+
+ while (g_listener && g_listener->isListening()) {
+ llvm::Expected<Socket> maybeClient = g_listener->accept();
+ if (auto Err = maybeClient.takeError()) {
+ if (log)
+ *log << "client accept failed: " << Err << "\n";
+ llvm::errs() << "client connection failed: " << Err << "\n";
+ continue;
+ }
+
+ Socket client = std::move(*maybeClient);
+ std::thread t([=, client = std::move(client)]() mutable {
+ DAP dap = DAP(program_path, log, default_repl_mode, pre_init_commands);
+
+ if (auto Err = dap.ConfigureIO()) {
+ llvm::errs() << "failed to configure client connect: "
+ << llvm::toString(std::move(Err)) << "\n";
+ return;
+ }
+
+ RegisterRequestCallbacks(dap);
+ dap.input.descriptor = StreamDescriptor::from_socket(client.fd, false);
+ dap.output.descriptor = StreamDescriptor::from_socket(client.fd, false);
+ if (auto Err = dap.Loop()) {
+ if (log)
+ *log << "client[" << client.fd
+ << "] Transport Error: " << llvm::toString(std::move(Err))
+ << "\n";
+ }
+
+ if (log)
+ *log << "client[" << client.fd << "] connection closed\n";
+
+ client.close();
+ });
+ t.detach();
+ }
- if (!unix_socket_path.empty()) {
- return AcceptConnection(program_path.str(), pre_init_commands, log,
- default_repl_mode, unix_socket_path);
+ return EXIT_SUCCESS;
}
+ // stdout/stderr redirection to the IDE's console
+ int new_stdout_fd = dup(fileno(stdout));
DAP dap = DAP(program_path.str(), log, default_repl_mode, pre_init_commands);
-
+ if (auto Err = dap.ConfigureIO(fileno(stdout), fileno(stderr))) {
+ llvm::errs() << "Failed to create lldb-dap instance: " << Err << "\n";
+ return EXIT_FAILURE;
+ }
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.
+ // 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);
@@ -5254,10 +5091,6 @@ int main(int argc, char *argv[]) {
assert(result);
#endif
- // stdout/stderr redirection to the IDE's console
- int new_stdout_fd = dup(fileno(stdout));
- SetupRedirection(dap, fileno(stdout), fileno(stderr));
-
dap.input.descriptor = StreamDescriptor::from_file(fileno(stdin), false);
dap.output.descriptor = StreamDescriptor::from_file(new_stdout_fd, false);
@@ -5265,15 +5098,10 @@ int main(int argc, char *argv[]) {
if (getenv("LLDB_DAP_TEST_STDOUT_STDERR_REDIRECTION") != nullptr)
redirection_test();
- for (const std::string &arg :
- input_args.getAllArgValues(OPT_pre_init_command)) {
- dap.pre_init_commands.push_back(arg);
- }
-
bool CleanExit = true;
if (auto Err = dap.Loop()) {
if (dap.log)
- *dap.log << "Transport Error: " << llvm::toString(std::move(Err)) << "\n";
+ *dap.log << "Transport Error: " << Err << "\n";
CleanExit = false;
}
>From b9713bbe60a4b8778cd517bc92d8ae0bf46dd1fe Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Fri, 22 Nov 2024 17:12:47 -0800
Subject: [PATCH 3/4] Addressing review feedback.
---
lldb/test/API/tools/lldb-dap/server/main.c | 2 +-
lldb/tools/lldb-dap/DAP.cpp | 16 +++++++---------
lldb/tools/lldb-dap/DAP.h | 7 +++----
lldb/tools/lldb-dap/IOStream.cpp | 6 +++---
lldb/tools/lldb-dap/IOStream.h | 1 -
lldb/tools/lldb-dap/lldb-dap.cpp | 11 ++++++-----
6 files changed, 20 insertions(+), 23 deletions(-)
diff --git a/lldb/test/API/tools/lldb-dap/server/main.c b/lldb/test/API/tools/lldb-dap/server/main.c
index 9a6326f3b57d45..c3599057621276 100644
--- a/lldb/test/API/tools/lldb-dap/server/main.c
+++ b/lldb/test/API/tools/lldb-dap/server/main.c
@@ -3,4 +3,4 @@
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 29e200f2475eb8..46d7d15df4e324 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -8,7 +8,6 @@
#include <chrono>
#include <cstdarg>
-#include <fstream>
#include <mutex>
#include "DAP.h"
@@ -35,8 +34,8 @@ using namespace lldb_dap;
namespace lldb_dap {
-DAP::DAP(llvm::StringRef path, std::shared_ptr<llvm::raw_ostream> log,
- ReplMode repl_mode, std::vector<std::string> pre_init_commands)
+DAP::DAP(llvm::StringRef path, llvm::raw_ostream *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),
@@ -220,19 +219,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) {
@@ -685,9 +684,8 @@ PacketStatus DAP::GetNextObject(llvm::json::Object &object) {
return PacketStatus::JSONMalformed;
}
- if (log) {
+ if (log)
*log << llvm::formatv("{0:2}", *json_value).str() << "\n";
- }
llvm::json::Object *object_ptr = json_value->getAsObject();
if (!object_ptr) {
diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h
index 8f847c1fdffbe4..5570320b7fc46e 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -32,7 +32,6 @@
#include "llvm/Support/JSON.h"
#include "llvm/Support/Threading.h"
#include "llvm/Support/raw_ostream.h"
-#include <iosfwd>
#include <map>
#include <optional>
#include <thread>
@@ -146,7 +145,7 @@ struct DAP {
lldb::SBBroadcaster broadcaster;
std::thread event_thread;
std::thread progress_event_thread;
- std::shared_ptr<llvm::raw_ostream> log;
+ llvm::raw_ostream *log;
llvm::StringMap<SourceBreakpointMap> source_breakpoints;
FunctionBreakpointMap function_breakpoints;
InstructionBreakpointMap instruction_breakpoints;
@@ -198,8 +197,8 @@ struct DAP {
// will contain that expression.
std::string last_nonempty_var_expression;
- DAP(llvm::StringRef path, std::shared_ptr<llvm::raw_ostream> log,
- ReplMode repl_mode, std::vector<std::string> pre_init_commands);
+ DAP(llvm::StringRef path, llvm::raw_ostream *log, ReplMode repl_mode,
+ std::vector<std::string> pre_init_commands);
~DAP();
DAP() = delete;
diff --git a/lldb/tools/lldb-dap/IOStream.cpp b/lldb/tools/lldb-dap/IOStream.cpp
index 188f6ec1fae9d9..82e4477943656f 100644
--- a/lldb/tools/lldb-dap/IOStream.cpp
+++ b/lldb/tools/lldb-dap/IOStream.cpp
@@ -7,8 +7,8 @@
//===----------------------------------------------------------------------===//
#include "IOStream.h"
-#include <string>
#include "llvm/Support/raw_ostream.h"
+#include <string>
#if defined(_WIN32)
#include <io.h>
@@ -18,7 +18,6 @@
#include <unistd.h>
#endif
-
using namespace lldb_dap;
StreamDescriptor::StreamDescriptor() = default;
@@ -144,7 +143,8 @@ bool InputStream::read_line(llvm::raw_ostream *log, std::string &line) {
return true;
}
-bool InputStream::read_expected(llvm::raw_ostream *log, llvm::StringRef expected) {
+bool InputStream::read_expected(llvm::raw_ostream *log,
+ llvm::StringRef expected) {
std::string result;
if (!read_full(log, expected.size(), result))
return false;
diff --git a/lldb/tools/lldb-dap/IOStream.h b/lldb/tools/lldb-dap/IOStream.h
index 1933399e075363..d7af0dbe94ce17 100644
--- a/lldb/tools/lldb-dap/IOStream.h
+++ b/lldb/tools/lldb-dap/IOStream.h
@@ -23,7 +23,6 @@ typedef int SOCKET;
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/raw_ostream.h"
-#include <fstream>
#include <string>
// Windows requires different system calls for dealing with sockets and other
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index 0c602ecbbbe836..3821704236919e 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -4968,10 +4968,10 @@ int main(int argc, char *argv[]) {
}
const char *log_file_path = getenv("LLDBDAP_LOG");
- std::shared_ptr<llvm::raw_ostream> log;
+ llvm::raw_ostream *log;
if (log_file_path) {
std::error_code EC;
- log.reset(new llvm::raw_fd_ostream(log_file_path, EC));
+ log = new llvm::raw_fd_ostream(log_file_path, EC);
if (EC) {
llvm::errs() << "Could not open log file: " << EC.message() << ", "
<< log_file_path << '\n';
@@ -5028,7 +5028,8 @@ int main(int argc, char *argv[]) {
llvm::outs() << "Listening for: "
<< llvm::join(*maybeListenerAddresses, ", ") << "\n";
- // Ensure listening address are flushed for calles to retrieve the resolve address.
+ // Ensure listening address are flushed for calles to retrieve the resolve
+ // address.
llvm::outs().flush();
while (g_listener && g_listener->isListening()) {
@@ -5100,8 +5101,8 @@ int main(int argc, char *argv[]) {
bool CleanExit = true;
if (auto Err = dap.Loop()) {
- if (dap.log)
- *dap.log << "Transport Error: " << Err << "\n";
+ if (log)
+ *log << "Transport Error: " << Err << "\n";
CleanExit = false;
}
>From 0ff097a7ab38db979f7062595a1ed2aa7e0023de Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Tue, 26 Nov 2024 12:14:19 -0800
Subject: [PATCH 4/4] Refactoring the socket management for lldb-dap server
mode to use lldb/Host/Socket.h'.
---
lldb/include/lldb/Host/Socket.h | 3 +
lldb/include/lldb/Host/common/TCPSocket.h | 2 +
lldb/include/lldb/Host/posix/DomainSocket.h | 2 +
.../Python/lldbsuite/test/lldbtest.py | 8 -
.../test/tools/lldb-dap/dap_server.py | 47 +-
lldb/source/Host/common/TCPSocket.cpp | 22 +-
lldb/source/Host/posix/DomainSocket.cpp | 17 +-
.../tools/lldb-dap/server/TestDAP_server.py | 9 +-
lldb/tools/lldb-dap/CMakeLists.txt | 6 +-
lldb/tools/lldb-dap/DAP.cpp | 72 ++-
lldb/tools/lldb-dap/DAP.h | 8 +-
lldb/tools/lldb-dap/IOStream.cpp | 9 +-
lldb/tools/lldb-dap/IOStream.h | 8 +-
lldb/tools/lldb-dap/OutputRedirector.cpp | 43 +-
lldb/tools/lldb-dap/OutputRedirector.h | 41 +-
lldb/tools/lldb-dap/Socket.cpp | 500 ------------------
lldb/tools/lldb-dap/Socket.h | 182 -------
lldb/tools/lldb-dap/lldb-dap.cpp | 198 ++++---
18 files changed, 311 insertions(+), 866 deletions(-)
delete mode 100644 lldb/tools/lldb-dap/Socket.cpp
delete mode 100644 lldb/tools/lldb-dap/Socket.h
diff --git a/lldb/include/lldb/Host/Socket.h b/lldb/include/lldb/Host/Socket.h
index 14468c98ac5a3a..0bde0e14c9c155 100644
--- a/lldb/include/lldb/Host/Socket.h
+++ b/lldb/include/lldb/Host/Socket.h
@@ -152,6 +152,9 @@ class Socket : public IOObject {
// If this Socket is connected then return the URI used to connect.
virtual std::string GetRemoteConnectionURI() const { return ""; };
+ // If the Socket is listening then return the URI for clients to connect.
+ virtual std::string GetListeningConnectionURI() const { return ""; }
+
protected:
Socket(SocketProtocol protocol, bool should_close,
bool m_child_process_inherit);
diff --git a/lldb/include/lldb/Host/common/TCPSocket.h b/lldb/include/lldb/Host/common/TCPSocket.h
index eefe0240fe4a95..09408d6431c16d 100644
--- a/lldb/include/lldb/Host/common/TCPSocket.h
+++ b/lldb/include/lldb/Host/common/TCPSocket.h
@@ -53,6 +53,8 @@ class TCPSocket : public Socket {
std::string GetRemoteConnectionURI() const override;
+ std::string GetListeningConnectionURI() const override;
+
private:
TCPSocket(NativeSocket socket, const TCPSocket &listen_socket);
diff --git a/lldb/include/lldb/Host/posix/DomainSocket.h b/lldb/include/lldb/Host/posix/DomainSocket.h
index 983f43bd633719..26f3c42d39e5d0 100644
--- a/lldb/include/lldb/Host/posix/DomainSocket.h
+++ b/lldb/include/lldb/Host/posix/DomainSocket.h
@@ -28,6 +28,8 @@ class DomainSocket : public Socket {
std::string GetRemoteConnectionURI() const override;
+ std::string GetListeningConnectionURI() const override;
+
protected:
DomainSocket(SocketProtocol protocol, bool child_processes_inherit);
diff --git a/lldb/packages/Python/lldbsuite/test/lldbtest.py b/lldb/packages/Python/lldbsuite/test/lldbtest.py
index a899854bb5ae14..8884ef5933ada8 100644
--- a/lldb/packages/Python/lldbsuite/test/lldbtest.py
+++ b/lldb/packages/Python/lldbsuite/test/lldbtest.py
@@ -39,7 +39,6 @@
import signal
from subprocess import *
import sys
-import socket
import time
import traceback
@@ -251,13 +250,6 @@ 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 8a71b9e709f138..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
@@ -9,7 +9,6 @@
import string
import subprocess
import sys
-import selectors
import threading
import time
@@ -1172,14 +1171,13 @@ def __init__(
)
if connection:
- print("attempting connection", connection)
- if connection.startswith("unix://"): # unix:///path
+ if connection.startswith("unix-connect://"): # unix-connect:///path
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
- s.connect(connection.removeprefix("unix://"))
- elif connection.startswith("tcp://"): # tcp://host:port
- host, port = connection.removeprefix("tcp://").split(":", 1)
+ 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, int(port)))
+ s = socket.create_connection((host.strip("[]"), int(port)))
else:
raise ValueError("invalid connection: {}".format(connection))
DebugCommunication.__init__(
@@ -1213,16 +1211,16 @@ def launch(
if log_file:
adaptor_env["LLDBDAP_LOG"] = log_file
- if os.uname().sysname == "Darwin":
- adaptor_env["NSUnbufferedIO"] = "YES"
-
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,
@@ -1233,29 +1231,20 @@ def launch(
# 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`.
- with selectors.DefaultSelector() as sel:
- print("Reading stdout for the listening connection")
- os.set_blocking(proc.stdout.fileno(), False)
- stdout_key = sel.register(proc.stdout, selectors.EVENT_READ)
- rdy_fds = sel.select(timeout=10.0)
- for key, _ in rdy_fds:
- if key != stdout_key:
- continue
-
- outs = proc.stdout.read(1024).decode()
- os.set_blocking(proc.stdout.fileno(), True)
- for line in outs.split("\n"):
- if not line.startswith("Listening for: "):
- continue
- # If the listener expanded into multiple addresses, use the first.
- connection = line.removeprefix("Listening for: ").split(",")[0]
- print("")
- return proc, connection
+ expected_prefix = "Listening for: "
+ out = proc.stdout.readline().decode()
+ if not out.startswith(expected_prefix):
proc.kill()
raise ValueError(
- "lldb-dap started with a connection but failed to write the listening address to stdout."
+ "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
diff --git a/lldb/source/Host/common/TCPSocket.cpp b/lldb/source/Host/common/TCPSocket.cpp
index 2d16b605af9497..54a86e54674055 100644
--- a/lldb/source/Host/common/TCPSocket.cpp
+++ b/lldb/source/Host/common/TCPSocket.cpp
@@ -84,6 +84,12 @@ std::string TCPSocket::GetLocalIPAddress() const {
socklen_t sock_addr_len = sock_addr.GetMaxLength();
if (::getsockname(m_socket, sock_addr, &sock_addr_len) == 0)
return sock_addr.GetIPAddress();
+ } else if (!m_listen_sockets.empty()) {
+ SocketAddress sock_addr;
+ socklen_t sock_addr_len = sock_addr.GetMaxLength();
+ if (::getsockname(m_listen_sockets.begin()->first, sock_addr,
+ &sock_addr_len) == 0)
+ return sock_addr.GetIPAddress();
}
return "";
}
@@ -118,6 +124,15 @@ std::string TCPSocket::GetRemoteConnectionURI() const {
return "";
}
+std::string TCPSocket::GetListeningConnectionURI() const {
+ if (!m_listen_sockets.empty()) {
+ return std::string(llvm::formatv(
+ "connection://[{0}]:{1}", GetLocalIPAddress(), GetLocalPortNumber()));
+ }
+
+ return "";
+}
+
Status TCPSocket::CreateSocket(int domain) {
Status error;
if (IsValid())
@@ -180,8 +195,9 @@ Status TCPSocket::Listen(llvm::StringRef name, int backlog) {
if (host_port->hostname == "*")
host_port->hostname = "0.0.0.0";
- std::vector<SocketAddress> addresses = SocketAddress::GetAddressInfo(
- host_port->hostname.c_str(), nullptr, AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP);
+ std::vector<SocketAddress> addresses =
+ SocketAddress::GetAddressInfo(host_port->hostname.c_str(), nullptr,
+ AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP);
for (SocketAddress &address : addresses) {
int fd = Socket::CreateSocket(address.GetFamily(), kType, IPPROTO_TCP,
m_child_processes_inherit, error);
@@ -195,7 +211,7 @@ Status TCPSocket::Listen(llvm::StringRef name, int backlog) {
}
SocketAddress listen_address = address;
- if(!listen_address.IsLocalhost())
+ if (!listen_address.IsLocalhost())
listen_address.SetToAnyAddress(address.GetFamily(), host_port->port);
else
listen_address.SetPort(host_port->port);
diff --git a/lldb/source/Host/posix/DomainSocket.cpp b/lldb/source/Host/posix/DomainSocket.cpp
index 369123f2239302..f957f885102fb9 100644
--- a/lldb/source/Host/posix/DomainSocket.cpp
+++ b/lldb/source/Host/posix/DomainSocket.cpp
@@ -90,7 +90,8 @@ Status DomainSocket::Connect(llvm::StringRef name) {
if (error.Fail())
return error;
if (llvm::sys::RetryAfterSignal(-1, ::connect, GetNativeSocket(),
- (struct sockaddr *)&saddr_un, saddr_un_len) < 0)
+ (struct sockaddr *)&saddr_un,
+ saddr_un_len) < 0)
SetLastError(error);
return error;
@@ -181,3 +182,17 @@ std::string DomainSocket::GetRemoteConnectionURI() const {
"{0}://{1}",
GetNameOffset() == 0 ? "unix-connect" : "unix-abstract-connect", name);
}
+
+std::string DomainSocket::GetListeningConnectionURI() const {
+ if (m_socket == kInvalidSocketValue)
+ return "";
+
+ struct sockaddr_un addr;
+ bzero(&addr, sizeof(struct sockaddr_un));
+ addr.sun_family = AF_UNIX;
+ socklen_t addr_len = sizeof(struct sockaddr_un);
+ if (::getsockname(m_socket, (struct sockaddr *)&addr, &addr_len) != 0)
+ return "";
+
+ return llvm::formatv("unix-connect://{0}", addr.sun_path);
+}
diff --git a/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py b/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
index 27bd583d4f6290..d6b2bb24ca707a 100644
--- a/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
+++ b/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
@@ -57,11 +57,14 @@ def test_server_port(self):
"""
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()
- self.do_test_server(
- connection="unix://" + dir + "/dap-connection-" + str(os.getpid())
- )
+ 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/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt
index f81be385c62aab..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 ()
@@ -37,12 +33,12 @@ add_lldb_tool(lldb-dap
OutputRedirector.cpp
ProgressEvent.cpp
RunInTerminal.cpp
- Socket.cpp
SourceBreakpoint.cpp
Watchpoint.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 46d7d15df4e324..243117782dbe84 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -8,6 +8,7 @@
#include <chrono>
#include <cstdarg>
+#include <fstream>
#include <mutex>
#include "DAP.h"
@@ -34,7 +35,7 @@ using namespace lldb_dap;
namespace lldb_dap {
-DAP::DAP(llvm::StringRef path, llvm::raw_ostream *log, ReplMode repl_mode,
+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),
@@ -52,22 +53,24 @@ DAP::~DAP() = default;
llvm::Error DAP::ConfigureIO(int out_fd, int err_fd) {
llvm::Expected<int> new_stdout_fd =
- RedirectFd(out_fd, [this](llvm::StringRef data) {
+ 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 =
- RedirectFd(err_fd, [this](llvm::StringRef data) {
+ redirector.RedirectFd(err_fd, [this](llvm::StringRef data) {
SendOutput(OutputType::Stderr, data);
});
if (auto Err = new_stderr_fd.takeError()) {
return Err;
}
- out = lldb::SBFile(new_stdout_fd.get(), "w", false);
- err = lldb::SBFile(new_stderr_fd.get(), "w", false);
+ 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();
}
@@ -207,9 +210,9 @@ void DAP::SendJSON(const llvm::json::Value &json) {
if (log) {
auto now = std::chrono::duration<double>(
std::chrono::system_clock::now().time_since_epoch());
- *log << llvm::formatv("{0:f9} <-- ", now.count()).str() << "\n"
+ *log << llvm::formatv("{0:f9} <-- ", now.count()).str() << std::endl
<< "Content-Length: " << json_str.size() << "\r\n\r\n"
- << llvm::formatv("{0:2}", json).str() << "\n";
+ << llvm::formatv("{0:2}", json).str() << std::endl;
}
}
@@ -237,8 +240,8 @@ std::string DAP::ReadJSON() {
if (log) {
auto now = std::chrono::duration<double>(
std::chrono::system_clock::now().time_since_epoch());
- *log << llvm::formatv("{0:f9} --> ", now.count()).str()
- << "\nContent-Length: " << length << "\r\n\r\n";
+ *log << llvm::formatv("{0:f9} --> ", now.count()).str() << std::endl
+ << "Content-Length: " << length << "\r\n\r\n";
}
return json_str;
}
@@ -537,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
@@ -678,19 +684,19 @@ PacketStatus DAP::GetNextObject(llvm::json::Object &object) {
std::string error_str;
llvm::raw_string_ostream strm(error_str);
strm << error;
- *log << "error: failed to parse JSON: " << error_str << "\n"
- << json << "\n";
+ *log << "error: failed to parse JSON: " << error_str << std::endl
+ << json << std::endl;
}
return PacketStatus::JSONMalformed;
}
if (log)
- *log << llvm::formatv("{0:2}", *json_value).str() << "\n";
+ *log << llvm::formatv("{0:2}", *json_value).str() << std::endl;
llvm::json::Object *object_ptr = json_value->getAsObject();
if (!object_ptr) {
if (log)
- *log << "error: json packet isn't a object\n";
+ *log << "error: json packet isn't a object" << std::endl;
return PacketStatus::JSONNotObject;
}
object = *object_ptr;
@@ -704,7 +710,8 @@ bool DAP::HandleObject(const llvm::json::Object &object) {
auto handler_pos = request_handlers.find(command);
if (handler_pos == request_handlers.end()) {
if (log)
- *log << "error: unhandled command \"" << command.data() << "\"\n";
+ *log << "error: unhandled command \"" << command.data() << "\""
+ << std::endl;
return false; // Fail
}
@@ -714,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());
};
{
@@ -733,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;
@@ -913,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 5570320b7fc46e..4b17b0ceb011af 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -13,6 +13,7 @@
#include "FunctionBreakpoint.h"
#include "IOStream.h"
#include "InstructionBreakpoint.h"
+#include "OutputRedirector.h"
#include "ProgressEvent.h"
#include "SourceBreakpoint.h"
#include "lldb/API/SBAttachInfo.h"
@@ -60,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,
@@ -145,7 +146,7 @@ struct DAP {
lldb::SBBroadcaster broadcaster;
std::thread event_thread;
std::thread progress_event_thread;
- llvm::raw_ostream *log;
+ std::ofstream *log;
llvm::StringMap<SourceBreakpointMap> source_breakpoints;
FunctionBreakpointMap function_breakpoints;
InstructionBreakpointMap instruction_breakpoints;
@@ -196,8 +197,9 @@ 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, llvm::raw_ostream *log, ReplMode repl_mode,
+ DAP(llvm::StringRef path, std::ofstream *log, ReplMode repl_mode,
std::vector<std::string> pre_init_commands);
~DAP();
diff --git a/lldb/tools/lldb-dap/IOStream.cpp b/lldb/tools/lldb-dap/IOStream.cpp
index 82e4477943656f..7d0f363440f53d 100644
--- a/lldb/tools/lldb-dap/IOStream.cpp
+++ b/lldb/tools/lldb-dap/IOStream.cpp
@@ -7,7 +7,7 @@
//===----------------------------------------------------------------------===//
#include "IOStream.h"
-#include "llvm/Support/raw_ostream.h"
+#include <fstream>
#include <string>
#if defined(_WIN32)
@@ -86,7 +86,7 @@ bool OutputStream::write_full(llvm::StringRef str) {
return true;
}
-bool InputStream::read_full(llvm::raw_ostream *log, size_t length,
+bool InputStream::read_full(std::ofstream *log, size_t length,
std::string &text) {
std::string data;
data.resize(length);
@@ -130,7 +130,7 @@ bool InputStream::read_full(llvm::raw_ostream *log, size_t length,
return true;
}
-bool InputStream::read_line(llvm::raw_ostream *log, std::string &line) {
+bool InputStream::read_line(std::ofstream *log, std::string &line) {
line.clear();
while (true) {
if (!read_full(log, 1, line))
@@ -143,8 +143,7 @@ bool InputStream::read_line(llvm::raw_ostream *log, std::string &line) {
return true;
}
-bool InputStream::read_expected(llvm::raw_ostream *log,
- llvm::StringRef expected) {
+bool InputStream::read_expected(std::ofstream *log, llvm::StringRef expected) {
std::string result;
if (!read_full(log, expected.size(), result))
return false;
diff --git a/lldb/tools/lldb-dap/IOStream.h b/lldb/tools/lldb-dap/IOStream.h
index d7af0dbe94ce17..3284b0899397ce 100644
--- a/lldb/tools/lldb-dap/IOStream.h
+++ b/lldb/tools/lldb-dap/IOStream.h
@@ -22,7 +22,7 @@ typedef int SOCKET;
#endif
#include "llvm/ADT/StringRef.h"
-#include "llvm/Support/raw_ostream.h"
+#include <fstream>
#include <string>
// Windows requires different system calls for dealing with sockets and other
@@ -51,11 +51,11 @@ struct StreamDescriptor {
struct InputStream {
StreamDescriptor descriptor;
- bool read_full(llvm::raw_ostream *log, size_t length, std::string &text);
+ bool read_full(std::ofstream *log, size_t length, std::string &text);
- bool read_line(llvm::raw_ostream *log, std::string &line);
+ bool read_line(std::ofstream *log, std::string &line);
- bool read_expected(llvm::raw_ostream *log, llvm::StringRef expected);
+ bool read_expected(std::ofstream *log, llvm::StringRef expected);
};
struct OutputStream {
diff --git a/lldb/tools/lldb-dap/OutputRedirector.cpp b/lldb/tools/lldb-dap/OutputRedirector.cpp
index 0b725e1901b9fd..6a63ed5a95e319 100644
--- a/lldb/tools/lldb-dap/OutputRedirector.cpp
+++ b/lldb/tools/lldb-dap/OutputRedirector.cpp
@@ -16,30 +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 {
-Expected<int> 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 (fd != -1 && 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];
diff --git a/lldb/tools/lldb-dap/OutputRedirector.h b/lldb/tools/lldb-dap/OutputRedirector.h
index 418b8bac102c7f..59de19d5ff1b6b 100644
--- a/lldb/tools/lldb-dap/OutputRedirector.h
+++ b/lldb/tools/lldb-dap/OutputRedirector.h
@@ -11,21 +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.
-///
-/// \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);
+/// 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/Socket.cpp b/lldb/tools/lldb-dap/Socket.cpp
deleted file mode 100644
index c5c90a81547878..00000000000000
--- a/lldb/tools/lldb-dap/Socket.cpp
+++ /dev/null
@@ -1,500 +0,0 @@
-//===-- Socket.cpp ----------------------------------------------*- C++ -*-===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-
-#include "Socket.h"
-#include "llvm/ADT/StringExtras.h"
-#include "llvm/ADT/StringRef.h"
-#include "llvm/Config/config.h"
-#include "llvm/Support/Errno.h"
-#include "llvm/Support/Error.h"
-#include "llvm/Support/FileSystem.h"
-#include "llvm/Support/Signals.h"
-#include "llvm/Support/Threading.h"
-#include <cstdint>
-#include <netdb.h>
-#include <netinet/in.h>
-#include <sys/_types/_socklen_t.h>
-#include <sys/poll.h>
-#include <system_error>
-
-#ifndef _WIN32
-#include <poll.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-#else
-#include "llvm/Support/Windows/WindowsSupport.h"
-// winsock2.h must be included before afunix.h. Briefly turn off clang-format to
-// avoid error.
-// clang-format off
-#include <winsock2.h>
-#include <afunix.h>
-// clang-format on
-#include <io.h>
-#endif // _WIN32
-
-#if defined(HAVE_UNISTD_H)
-#include <unistd.h>
-#endif
-
-namespace {
-
-enum SocketProtocol { ProtocolTcp, ProtocolUnixDomain };
-
-std::error_code getLastSocketErrorCode() {
-#ifdef _WIN32
- return std::error_code(::WSAGetLastError(), std::system_category());
-#else
- return llvm::errnoAsErrorCode();
-#endif
-}
-
-llvm::Expected<std::vector<lldb_dap::SocketAddress>>
-getAddressInfo(llvm::StringRef host) {
- std::vector<lldb_dap::SocketAddress> add_list;
-
- struct addrinfo hints;
- memset(&hints, 0, sizeof(hints));
- hints.ai_family = AF_UNSPEC;
- hints.ai_socktype = SOCK_STREAM;
- hints.ai_protocol = IPPROTO_TCP;
- hints.ai_flags = 0;
-
- struct addrinfo *service_info_list = nullptr;
-
- int err =
- getaddrinfo(host.str().c_str(), nullptr, &hints, &service_info_list);
- if (err != 0)
- return llvm::createStringError("getaddrinfo failed: %s", gai_strerror(err));
-
- for (struct addrinfo *service_ptr = service_info_list; service_ptr != nullptr;
- service_ptr = service_ptr->ai_next) {
- add_list.emplace_back(lldb_dap::SocketAddress(service_ptr));
- }
-
- if (service_info_list)
- ::freeaddrinfo(service_info_list);
-
- return add_list;
-}
-
-int CloseSocket(NativeSocket sockfd) {
-#ifdef _WIN32
- return ::closesocket(sockfd);
-#else
- return ::close(sockfd);
-#endif
-}
-
-llvm::Expected<std::pair<llvm::StringRef, int>>
-detectHostAndPort(llvm::StringRef name) {
- llvm::StringRef host;
- llvm::StringRef port;
- std::tie(host, port) = name.split(":");
-
- if (host == "" || host == "*") {
- host = "0.0.0.0";
- }
-
- if (port == "") {
- port = "0";
- }
-
- int portnu = 0;
- if (!llvm::to_integer(port, portnu, 10)) {
- return llvm::createStringError(llvm::inconvertibleErrorCode(),
- "invalid host:port specification: '%s'",
- name.str().c_str());
- }
-
- return std::make_pair(host, portnu);
-}
-
-} // namespace
-
-namespace lldb_dap {
-
-#ifdef _WIN32
-WSABalancer::WSABalancer() {
- WSADATA WsaData;
- ::memset(&WsaData, 0, sizeof(WsaData));
- if (WSAStartup(MAKEWORD(2, 2), &WsaData) != 0) {
- llvm::report_fatal_error("WSAStartup failed");
- }
-}
-
-WSABalancer::~WSABalancer() { WSACleanup(); }
-#endif // _WIN32
-
-SocketAddress::SocketAddress() { clear(); }
-
-SocketAddress::SocketAddress(const struct addrinfo *info) {
- clear();
- if (info && info->ai_addrlen > 0 &&
- size_t(info->ai_addrlen) <= sizeof m_address) {
- memcpy(&m_address.sa, info->ai_addr, static_cast<size_t>(info->ai_addrlen));
- m_address.sa.sa_len = info->ai_addrlen;
- }
-}
-
-SocketAddress::SocketAddress(const struct sockaddr &sa) { m_address.sa = sa; }
-
-SocketAddress::SocketAddress(const struct sockaddr_in &sa) {
- m_address.sa_ipv4 = sa;
-}
-
-SocketAddress::SocketAddress(const struct sockaddr_in6 &sa) {
- m_address.sa_ipv6 = sa;
-}
-
-SocketAddress::SocketAddress(const struct sockaddr_un &su) {
- m_address.su = su;
-}
-
-std::string SocketAddress::getPath() const {
- if (getFamily() == AF_UNIX)
- return std::string(m_address.su.sun_path);
- return "";
-}
-
-std::string SocketAddress::getName() const {
- if (getFamily() == AF_UNIX)
- return "unix://" + getPath();
-
- uint64_t port = getPort();
- std::string hostname;
- if (isLocalhost())
- hostname = "localhost";
- else if (isAnyAddr())
- hostname = getFamily() == AF_INET ? "0.0.0.0" : "[::]";
- else {
- char hbuf[NI_MAXHOST];
- if (getnameinfo(&m_address.sa, getLength(), hbuf, sizeof(hbuf), nullptr, 0,
- NI_NUMERICHOST) == 0) {
- hostname = std::string(hbuf);
- }
- }
-
- return "tcp://" + hostname + ":" + std::to_string(port);
-}
-
-bool SocketAddress::isLocalhost() const {
- switch (getFamily()) {
- case AF_INET:
- return m_address.sa_ipv4.sin_addr.s_addr == htonl(INADDR_LOOPBACK);
- case AF_INET6:
- return 0 == memcmp(&m_address.sa_ipv6.sin6_addr, &in6addr_loopback, 16);
- }
- return false;
-}
-
-bool SocketAddress::isAnyAddr() const {
- switch (getFamily()) {
- case AF_INET:
- return m_address.sa_ipv4.sin_addr.s_addr == htonl(INADDR_ANY);
- case AF_INET6:
- return 0 == memcmp(&m_address.sa_ipv6.sin6_addr, &in6addr_any, 16);
- }
- return false;
-}
-
-void SocketAddress::setFamily(sa_family_t family) {
- m_address.sa.sa_family = family;
-#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || \
- defined(__OpenBSD__)
- switch (family) {
- case AF_INET:
- m_address.sa.sa_len = sizeof(struct sockaddr_in);
- break;
-
- case AF_INET6:
- m_address.sa.sa_len = sizeof(struct sockaddr_in6);
- break;
-
- case AF_UNIX:
- m_address.sa.sa_len = SUN_LEN(&m_address.su);
- break;
-
- default:
- assert(0 && "unsupported socket family");
- }
-#endif
-}
-
-SocketAddress SocketAddress::addressOf(const Socket &s, std::error_code &EC) {
- SocketAddress sa;
- socklen_t len = sizeof(sockaddr_t);
-
- if (::getsockname(s.fd, &sa.sockaddr(), &len) == -1) {
- EC = getLastSocketErrorCode();
- return SocketAddress{};
- }
-
- // If the socket is a struct sockaddr_un then the length is returned by the
- // len parameter not the sa_len field. Update the field to match the
- // returned address.
- sa.m_address.sa.sa_len = len;
- return sa;
-}
-
-bool SocketAddress::setToAnyAddress(sa_family_t family, uint16_t port) {
- switch (family) {
- case AF_INET:
- setFamily(family);
- if (setPort(port)) {
- m_address.sa_ipv4.sin_addr.s_addr = htonl(INADDR_ANY);
- return true;
- }
- break;
-
- case AF_INET6:
- setFamily(family);
- if (setPort(port)) {
- m_address.sa_ipv6.sin6_addr = in6addr_any;
- return true;
- }
- break;
- }
-
- clear();
- return false;
-}
-
-Socket::Socket(NativeSocket fd) : fd(fd) {}
-Socket::Socket(Socket &&S) : fd(S.fd) { S.fd = -1; }
-
-Socket::~Socket() {
- close();
-}
-
-void Socket::close() {
- if (fd == -1) {
- return;
- }
-
- CloseSocket(fd);
- fd = -1;
-}
-
-SocketListener::SocketListener(std::vector<Socket> sockets, int fds[2])
- : m_listening(true), m_sockets(std::move(sockets)), m_pipe{fds[0], fds[1]} {
-}
-
-SocketListener::SocketListener(SocketListener &&SL)
- : m_listening(SL.m_listening.load()), m_sockets(std::move(SL.m_sockets)),
- m_pipe{SL.m_pipe[0], SL.m_pipe[1]} {
- SL.m_listening = false;
- SL.m_sockets.clear();
- SL.m_pipe[0] = -1;
- SL.m_pipe[1] = -1;
-}
-
-llvm::Expected<SocketListener>
-SocketListener::createListener(llvm::StringRef name) {
- SocketProtocol protocol;
-
- if (name.consume_front("tcp://") || name.consume_front("tcp:") ||
- name.starts_with(":")) {
- protocol = ProtocolTcp;
- } else if (name.consume_front("unix://") || name.consume_front("unix:") ||
- name.starts_with("/")) {
- protocol = ProtocolUnixDomain;
- } else if (name.contains(":")) {
- protocol = ProtocolTcp;
- } else {
- return llvm::createStringError(
- "invalid address, expected '[tcp://][host]:port' or "
- "'[unix://]/path' but got %s.",
- name.str().c_str());
- }
-
- std::vector<SocketAddress> addresses;
- if (protocol == ProtocolTcp) {
- auto maybeHostAndPort = detectHostAndPort(name);
- if (auto Err = maybeHostAndPort.takeError())
- return Err;
-
- llvm::StringRef host;
- int port;
- std::tie(host, port) = *maybeHostAndPort;
-
- llvm::Expected<std::vector<SocketAddress>> maybeAddresses =
- getAddressInfo(host);
- if (auto Err = maybeAddresses.takeError()) {
- return Err;
- }
-
- for (auto &address : *maybeAddresses) {
- SocketAddress listen_address = address;
- if (!listen_address.isLocalhost())
- listen_address.setToAnyAddress(address.getFamily(), port);
- else
- listen_address.setPort(port);
-
- addresses.push_back(std::move(listen_address));
- }
- } else {
- llvm::StringRef path = name;
- if (llvm::sys::fs::exists(path)) {
- return llvm::make_error<llvm::StringError>(
- std::make_error_code(std::errc::file_exists), "file exists at path");
- }
-
- struct sockaddr_un addr;
- bzero(&addr, sizeof(struct sockaddr_un));
- addr.sun_family = AF_UNIX;
- strncpy(addr.sun_path, path.str().c_str(), sizeof(addr.sun_path) - 1);
- addr.sun_len = SUN_LEN(&addr);
-
- addresses.push_back(SocketAddress(addr));
- }
-
- std::vector<Socket> sockets;
- for (auto &address : addresses) {
- bool isTCP =
- address.getFamily() == AF_INET || address.getFamily() == AF_INET6;
-
- NativeSocket fd =
- ::socket(address.getFamily(), SOCK_STREAM, isTCP ? IPPROTO_TCP : 0);
- if (fd == -1) {
- return llvm::createStringError(getLastSocketErrorCode(),
- "socket() failed");
- }
-
- int val = 1;
- if (::setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(int)) == -1) {
- return llvm::make_error<llvm::StringError>(getLastSocketErrorCode(),
- "setsockopt() failed");
- }
-
- if (::bind(fd, &address.sockaddr(), address.getLength()) == -1) {
- return llvm::make_error<llvm::StringError>(getLastSocketErrorCode(),
- "bind() failed");
- }
-
- if (::listen(fd, llvm::hardware_concurrency().compute_thread_count()) ==
- -1) {
- return llvm::make_error<llvm::StringError>(getLastSocketErrorCode(),
- "listen() failed");
- }
-
- sockets.emplace_back(fd);
- }
-
- int pipeFD[2];
-#ifdef _WIN32
- // Reserve 1 byte for the pipe and use default textmode
- if (::_pipe(pipeFD, 1, 0) == -1)
-#else
- if (::pipe(pipeFD) == -1)
-#endif // _WIN32
- return llvm::make_error<llvm::StringError>(getLastSocketErrorCode(),
- "pipe failed");
-
- return SocketListener{std::move(sockets), pipeFD};
-}
-
-SocketListener::~SocketListener() {
- shutdown();
- if (m_pipe[0] != -1)
- close(m_pipe[0]);
- if (m_pipe[1] != -1)
- close(m_pipe[1]);
-}
-
-llvm::Expected<std::vector<std::string>> SocketListener::addresses() const {
- std::vector<std::string> addrs;
- std::error_code EC;
- for (auto &s : m_sockets) {
- auto addr = SocketAddress::addressOf(s, EC);
- if (EC)
- return llvm::make_error<llvm::StringError>(EC);
-
- addrs.push_back(addr.getName());
- }
- return addrs;
-}
-
-void SocketListener::shutdown() {
- bool listening = m_listening;
-
- if (!listening)
- return;
-
- if (!m_listening.compare_exchange_strong(listening, false))
- return;
-
- for (auto &s : m_sockets) {
- // If the socket has a path (e.g. a unix:// socket), remove it after
- // closing.
- std::error_code _; // ignoring failures during shutdown.
- std::string path = SocketAddress::addressOf(s, _).getPath();
- s.close();
- if (!path.empty())
- unlink(path.c_str());
- }
-
- // Write to the pipe to indiciate that accept() should exit immediately.
- char byte = 'A';
- ssize_t written = ::write(m_pipe[1], &byte, 1);
-
- // Ignore any write() error
- (void)written;
-}
-
-llvm::Expected<Socket> SocketListener::accept() {
- if (m_sockets.empty()) {
- return llvm::createStringError(
- std::make_error_code(std::errc::bad_file_descriptor), "no open socket");
- }
-
- std::vector<struct pollfd> pollfds;
-
- for (auto &s : m_sockets)
- pollfds.emplace_back(pollfd{s.fd, POLLIN, 0});
-
- pollfds.emplace_back(pollfd{m_pipe[0], POLLIN, 0});
-
- while (m_listening.load()) {
- int status;
-#ifdef _WIN32
- status = WSAPoll(pollfds.data(), pollfds.size(), -1);
-#else
- status = ::poll(pollfds.data(), pollfds.size(), -1);
-#endif
-
- if (status == -1) {
- return llvm::make_error<llvm::StringError>(getLastSocketErrorCode(),
- "poll() failed");
- }
-
- for (auto &pfd : pollfds) {
- if (pfd.revents & POLLIN) {
- // Check if shutdown was requested.
- if (pfd.fd == m_pipe[0]) {
- break;
- }
-
- struct sockaddr client;
- socklen_t len;
- int fd =
- llvm::sys::RetryAfterSignal(-1, ::accept, pfd.fd, &client, &len);
- if (fd == -1) {
- return llvm::make_error<llvm::StringError>(getLastSocketErrorCode(),
- "accept() failed");
- }
-
- return Socket{fd};
- }
- }
- }
-
- return llvm::make_error<llvm::StringError>(
- std::make_error_code(std::errc::connection_aborted), "socket shutdown");
-}
-
-} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/Socket.h b/lldb/tools/lldb-dap/Socket.h
deleted file mode 100644
index 98923032f91417..00000000000000
--- a/lldb/tools/lldb-dap/Socket.h
+++ /dev/null
@@ -1,182 +0,0 @@
-//===-- Socket.h ------------------------------------------------*- C++ -*-===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-
-#ifndef LLDB_TOOLS_LLDB_DAP_SOCKET_H
-#define LLDB_TOOLS_LLDB_DAP_SOCKET_H
-
-#include "llvm/ADT/StringRef.h"
-#include "llvm/Support/Error.h"
-#include <cstdint>
-#include <string>
-#include <vector>
-
-#ifdef _WIN32
-#include "lldb/Host/windows/windows.h"
-#include <winsock2.h>
-#include <ws2tcpip.h>
-typedef ADDRESS_FAMILY sa_family_t;
-typedef SOCKET NativeSocket;
-#else
-#include <netdb.h>
-#include <netinet/in.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-typedef int NativeSocket;
-#endif
-
-namespace lldb_dap {
-
-struct Socket;
-
-/// Manages socket addresses for socket based communication.
-struct SocketAddress {
- SocketAddress();
- explicit SocketAddress(const struct addrinfo *);
- explicit SocketAddress(const struct sockaddr &);
- explicit SocketAddress(const struct sockaddr_in &);
- explicit SocketAddress(const struct sockaddr_in6 &);
- explicit SocketAddress(const struct sockaddr_un &);
-
- static SocketAddress addressOf(const Socket &s, std::error_code &EC);
-
- sa_family_t getFamily() const { return m_address.sa.sa_family; }
- uint16_t getPort() const {
- switch (getFamily()) {
- case AF_INET:
- return ntohs(m_address.sa_ipv4.sin_port);
- case AF_INET6:
- return ntohs(m_address.sa_ipv6.sin6_port);
- }
- return 0;
- }
- std::string getName() const;
- /// Returns the path for AF_UNIX sockets, otherwise "".
- std::string getPath() const;
- /// Get the length for the current socket address family
- socklen_t getLength() const { return m_address.sa.sa_len; };
-
- bool isLocalhost() const;
- bool isAnyAddr() const;
-
- void setFamily(sa_family_t family);
- bool setToAnyAddress(sa_family_t family, uint16_t port);
- bool setPort(uint16_t port) {
- switch (getFamily()) {
- case AF_INET:
- m_address.sa_ipv4.sin_port = htons(port);
- return true;
- case AF_INET6:
- m_address.sa_ipv6.sin6_port = htons(port);
- return true;
- }
- return false;
- }
-
- void clear() { bzero(&m_address, sizeof(sockaddr_t)); }
-
- struct sockaddr &sockaddr() { return m_address.sa; }
- const struct sockaddr &sockaddr() const { return m_address.sa; }
-
-private:
- typedef union sockaddr_tag {
- struct sockaddr sa;
- struct sockaddr_in sa_ipv4;
- struct sockaddr_in6 sa_ipv6;
- struct sockaddr_un su;
- struct sockaddr_storage ss;
- } sockaddr_t;
- sockaddr_t m_address;
-};
-
-/// Manages the lifetime of a socket.
-struct Socket {
- NativeSocket fd;
-
- /// Close the socket.
- void close();
-
- explicit Socket(NativeSocket fd);
- ~Socket();
- Socket(Socket &&S);
- Socket(const Socket &S) = delete;
- Socket &operator=(const Socket &S) = delete;
-};
-
-#ifdef _WIN32
-/// Ensures proper initialization and cleanup of winsock resources
-///
-/// Make sure that calls to WSAStartup and WSACleanup are balanced.
-class WSABalancer {
-public:
- WSABalancer();
- ~WSABalancer();
-};
-#endif // _WIN32
-
-/// Manages listening for socket connections.
-///
-/// SocketListener handles listening for both unix and tcp based connections.
-struct SocketListener {
- SocketListener(const SocketListener &S) = delete;
- SocketListener &operator=(const SocketListener &S) = delete;
- SocketListener(SocketListener &&SL);
- ~SocketListener();
-
- /// Creates a listening socket bound to the specified name.
- ///
- /// Handles the socket creation, binding, and immediately starts listening for
- /// incoming connections.
- ///
- /// \param[in] name
- /// Socket names formatted like `protocol:name`.
- ///
- /// Supported protocols include tcp and unix sockets.
- ///
- /// Names must follow the following formats:
- ///
- /// * tcp://host:port
- /// * tcp:host:port
- /// * tcp:port (host will be assumed 0.0.0.0)
- /// * :port (implicit tcp)
- /// * unix:///path
- /// * /path (implicit unix)
- static llvm::Expected<SocketListener> createListener(llvm::StringRef name);
-
- /// Returns an array of active listening sockets.
- llvm::Expected<std::vector<std::string>> addresses() const;
-
- /// Shutdown the socket listener.
- void shutdown();
-
- /// Returns true if listening, otherwise false.
- bool isListening() const { return m_listening; }
-
- /// Accept returns the next client connection, blocking until a connection is
- /// made or Shutdown is called.
- llvm::Expected<Socket> accept();
-
-private:
- SocketListener(std::vector<Socket> sockets, int fds[2]);
-
- std::atomic<bool> m_listening;
- std::vector<Socket> m_sockets;
-
- /// If a separate thread calls shutdown, the listening file descriptor
- /// could be closed while ::poll is waiting for it to be ready to perform a
- /// I/O operations. ::poll will continue to block even after FD is closed so
- /// use a self-pipe mechanism to get ::poll to return
- int m_pipe[2] = {};
-
-#ifdef _WIN32
- WSABalancer _;
-#endif // _WIN32
-};
-
-} // namespace lldb_dap
-
-#endif
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index 3821704236919e..230cf69752c877 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -11,7 +11,6 @@
#include "JSONUtils.h"
#include "LLDBUtils.h"
#include "RunInTerminal.h"
-#include "Socket.h"
#include "Watchpoint.h"
#include "lldb/API/SBDeclaration.h"
#include "lldb/API/SBFile.h"
@@ -21,11 +20,18 @@
#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"
@@ -46,13 +52,16 @@
#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>
@@ -86,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;
@@ -1994,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);
@@ -4889,7 +4906,28 @@ static void redirection_test() {
fflush(stderr);
}
-static SocketListener *g_listener = nullptr;
+static llvm::Expected<std::pair<Socket::SocketProtocol, std::string>>
+parseConnection(llvm::StringRef conn) {
+ if (conn.contains("://")) {
+ llvm::StringRef scheme, rest;
+ std::tie(scheme, rest) = conn.split("://");
+
+ if (scheme == "unix" || scheme == "unix-connect") {
+ return std::make_pair(Socket::ProtocolUnixDomain, rest.str());
+ }
+ if (scheme == "tcp" || scheme == "connect") {
+ return std::make_pair(Socket::ProtocolTcp, rest.str());
+ }
+ } else if (conn.starts_with("/")) {
+ return std::make_pair(Socket::ProtocolUnixDomain, conn.str());
+ } else if (conn.contains(":")) {
+ return std::make_pair(Socket::ProtocolTcp, conn.str());
+ }
+
+ return llvm::createStringError(
+ "expected '[unix://]/path' or '[tcp://][host]:port', got '%s'.",
+ conn.str().c_str());
+}
int main(int argc, char *argv[]) {
llvm::InitLLVM IL(argc, argv, /*InstallPipeSignalExitHandler=*/false);
@@ -4968,16 +5006,9 @@ int main(int argc, char *argv[]) {
}
const char *log_file_path = getenv("LLDBDAP_LOG");
- llvm::raw_ostream *log;
- if (log_file_path) {
- std::error_code EC;
- log = new llvm::raw_fd_ostream(log_file_path, EC);
- if (EC) {
- llvm::errs() << "Could not open log file: " << EC.message() << ", "
- << log_file_path << '\n';
- return EXIT_FAILURE;
- }
- }
+ 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);
@@ -5001,72 +5032,95 @@ int main(int argc, char *argv[]) {
llvm::make_scope_exit([] { lldb::SBDebugger::Terminate(); });
if (!connection.empty()) {
- auto maybeListener = SocketListener::createListener(connection);
- if (auto Err = maybeListener.takeError()) {
- llvm::errs() << "Failed to listen for connections: "
- << llvm::toString(std::move(Err)) << "\n";
+ auto maybeProtoclAndName = parseConnection(connection);
+ if (auto Err = maybeProtoclAndName.takeError()) {
+ std::cerr << "Invalid connection specification "
+ << llvm::toString(std::move(Err)) << std::endl;
return EXIT_FAILURE;
}
- g_listener = &*maybeListener;
-
- llvm::sys::SetInterruptFunction([]() {
- if (g_listener)
- g_listener->shutdown();
- });
+ 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;
+ }
- auto maybeListenerAddresses = g_listener->addresses();
- if (auto Err = maybeListenerAddresses.takeError()) {
- llvm::errs() << "listener failed to retrieve listening socket address: "
- << Err << "\n";
+ error = listener->Listen(name, /* backlog */ 5);
+ if (error.Fail()) {
+ std::cerr << "Failed to listen, error: "
+ << llvm::toString(error.takeError()) << std::endl;
return EXIT_FAILURE;
}
+ std::string address = listener->GetListeningConnectionURI();
+
if (log)
- *log << "started with connection listeners "
- << llvm::join(*maybeListenerAddresses, ", ") << "\n";
+ *log << "started with connection listeners " << address << "\n";
- llvm::outs() << "Listening for: "
- << llvm::join(*maybeListenerAddresses, ", ") << "\n";
+ std::cout << "Listening for: " << address << std::endl;
// Ensure listening address are flushed for calles to retrieve the resolve
// address.
- llvm::outs().flush();
+ std::flush(std::cout);
+
+ MainLoop mainloop;
+ mainloop.RegisterSignal(
+ SIGHUP, [](auto &RL) { RL.RequestTermination(); }, error);
- while (g_listener && g_listener->isListening()) {
- llvm::Expected<Socket> maybeClient = g_listener->accept();
- if (auto Err = maybeClient.takeError()) {
+ auto start_dap = [=](std::unique_ptr<Socket> client) {
+ DAP dap = DAP(program_path, log, default_repl_mode, pre_init_commands);
+ NativeSocket sock = client->GetNativeSocket();
+
+ if (auto Err = dap.ConfigureIO()) {
if (log)
- *log << "client accept failed: " << Err << "\n";
- llvm::errs() << "client connection failed: " << Err << "\n";
- continue;
+ *log << "client[" << sock << "] configureIO failed: "
+ << llvm::toStringWithoutConsuming(Err) << "\n";
+ std::cerr << "failed to configure client connect: "
+ << llvm::toString(std::move(Err)) << std::endl;
+ return;
}
- Socket client = std::move(*maybeClient);
- std::thread t([=, client = std::move(client)]() mutable {
- DAP dap = DAP(program_path, log, default_repl_mode, pre_init_commands);
+ RegisterRequestCallbacks(dap);
+ dap.input.descriptor = StreamDescriptor::from_socket(sock, false);
+ dap.output.descriptor = StreamDescriptor::from_socket(sock, false);
+ if (auto Err = dap.Loop()) {
+ if (log)
+ *log << "client[" << sock
+ << "] Transport Error: " << llvm::toStringWithoutConsuming(Err)
+ << "\n";
+ std::cerr << "client transport error: "
+ << llvm::toString(std::move(Err)) << std::endl;
+ }
- if (auto Err = dap.ConfigureIO()) {
- llvm::errs() << "failed to configure client connect: "
- << llvm::toString(std::move(Err)) << "\n";
- return;
- }
+ if (log)
+ *log << "client[" << sock << "] connection closed\n";
+ };
- RegisterRequestCallbacks(dap);
- dap.input.descriptor = StreamDescriptor::from_socket(client.fd, false);
- dap.output.descriptor = StreamDescriptor::from_socket(client.fd, false);
- if (auto Err = dap.Loop()) {
- if (log)
- *log << "client[" << client.fd
- << "] Transport Error: " << llvm::toString(std::move(Err))
- << "\n";
- }
+ auto on_accept = [=](std::unique_ptr<Socket> client) {
+ llvm::errs() << "Client connected...\n";
+ // Start a thread for each connection, unblocking the listening thread.
+ std::thread(start_dap, std::move(client)).detach();
+ };
- if (log)
- *log << "client[" << client.fd << "] connection closed\n";
+ auto handles = listener->Accept(mainloop, on_accept);
+ 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;
+ }
- client.close();
- });
- t.detach();
+ error = mainloop.Run();
+ if (error.Fail()) {
+ std::cerr << "failed to accept()" << llvm::toString(error.takeError())
+ << std::endl;
+ return EXIT_FAILURE;
}
return EXIT_SUCCESS;
@@ -5076,7 +5130,8 @@ int main(int argc, char *argv[]) {
int new_stdout_fd = dup(fileno(stdout));
DAP dap = DAP(program_path.str(), log, default_repl_mode, pre_init_commands);
if (auto Err = dap.ConfigureIO(fileno(stdout), fileno(stderr))) {
- llvm::errs() << "Failed to create lldb-dap instance: " << Err << "\n";
+ std::cout << "Failed to create lldb-dap instance: "
+ << llvm::toString(std::move(Err)) << "\n";
return EXIT_FAILURE;
}
RegisterRequestCallbacks(dap);
@@ -5102,7 +5157,10 @@ int main(int argc, char *argv[]) {
bool CleanExit = true;
if (auto Err = dap.Loop()) {
if (log)
- *log << "Transport Error: " << Err << "\n";
+ *log << "Transport Error: " << llvm::toStringWithoutConsuming(Err)
+ << "\n";
+ std::cerr << "Transport Error: " << llvm::toString(std::move(Err))
+ << std::endl;
CleanExit = false;
}
More information about the lldb-commits
mailing list