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