[Lldb-commits] [lldb] [lldb-dap] Refactoring lldb-dap port listening mode to allow multiple connections. (PR #116392)
John Harrison via lldb-commits
lldb-commits at lists.llvm.org
Wed Dec 4 13:54:44 PST 2024
https://github.com/ashgti updated https://github.com/llvm/llvm-project/pull/116392
>From 547549573aba86c9bf9b9c2c189d49a3d8f62413 Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Mon, 2 Dec 2024 08:36:52 -0800
Subject: [PATCH 1/2] [lldb-dap] Refactoring lldb-dap listening mode to support
multiple connections.
This adjusts the lldb-dap listening mode to allow for multiple connections. Each client gets a new instanceo of DAP and an associated lldb::SBDebugger instance.
---
.../test/tools/lldb-dap/dap_server.py | 91 +++--
.../test/tools/lldb-dap/lldbdap_testcase.py | 29 +-
lldb/test/API/tools/lldb-dap/server/Makefile | 3 +
.../tools/lldb-dap/server/TestDAP_server.py | 72 ++++
lldb/test/API/tools/lldb-dap/server/main.c | 6 +
lldb/tools/lldb-dap/CMakeLists.txt | 9 +-
lldb/tools/lldb-dap/DAP.cpp | 103 +++---
lldb/tools/lldb-dap/DAP.h | 70 ++--
lldb/tools/lldb-dap/IOStream.cpp | 27 +-
lldb/tools/lldb-dap/IOStream.h | 8 +-
lldb/tools/lldb-dap/Options.td | 13 +-
lldb/tools/lldb-dap/OutputRedirector.cpp | 44 ++-
lldb/tools/lldb-dap/OutputRedirector.h | 35 +-
lldb/tools/lldb-dap/lldb-dap.cpp | 313 +++++++++++-------
14 files changed, 555 insertions(+), 268 deletions(-)
create mode 100644 lldb/test/API/tools/lldb-dap/server/Makefile
create mode 100644 lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
create mode 100644 lldb/test/API/tools/lldb-dap/server/main.c
diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
index c29992ce9c7848..37679462118531 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
@@ -903,7 +903,7 @@ def request_setBreakpoints(self, file_path, line_array, data=None):
"sourceModified": False,
}
if line_array is not None:
- args_dict["lines"] = "%s" % line_array
+ args_dict["lines"] = line_array
breakpoints = []
for i, line in enumerate(line_array):
breakpoint_data = None
@@ -1150,38 +1150,42 @@ def request_setInstructionBreakpoints(self, memory_reference=[]):
}
return self.send_recv(command_dict)
+
class DebugAdaptorServer(DebugCommunication):
def __init__(
self,
executable=None,
- port=None,
+ launch=True,
+ connection=None,
init_commands=[],
log_file=None,
env=None,
):
self.process = None
- if executable is not None:
- adaptor_env = os.environ.copy()
- if env is not None:
- adaptor_env.update(env)
-
- if log_file:
- adaptor_env["LLDBDAP_LOG"] = log_file
- self.process = subprocess.Popen(
- [executable],
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- env=adaptor_env,
+ if launch:
+ self.process, connection = DebugAdaptorServer.launch(
+ executable,
+ connection=connection,
+ log_file=log_file,
+ env=env,
)
+
+ if connection:
+ if connection.startswith("unix-connect://"): # unix-connect:///path
+ s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ s.connect(connection.removeprefix("unix-connect://"))
+ elif connection.startswith("connection://"): # connection://[host]:port
+ host, port = connection.removeprefix("connection://").rsplit(":", 1)
+ # create_connection with try both ipv4 and ipv6.
+ s = socket.create_connection((host.strip("[]"), int(port)))
+ else:
+ raise ValueError("invalid connection: {}".format(connection))
DebugCommunication.__init__(
- self, self.process.stdout, self.process.stdin, init_commands, log_file
+ self, s.makefile("rb"), s.makefile("wb"), init_commands, log_file
)
- elif port is not None:
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- s.connect(("127.0.0.1", port))
+ else:
DebugCommunication.__init__(
- self, s.makefile("r"), s.makefile("w"), init_commands
+ self, self.process.stdout, self.process.stdin, init_commands, log_file
)
def get_pid(self):
@@ -1196,6 +1200,53 @@ def terminate(self):
self.process.wait()
self.process = None
+ @classmethod
+ def launch(
+ cls, executable: str, /, connection=None, log_file=None, env=None
+ ) -> tuple[subprocess.Popen, str]:
+ adaptor_env = os.environ.copy()
+ if env:
+ adaptor_env.update(env)
+
+ if log_file:
+ adaptor_env["LLDBDAP_LOG"] = log_file
+
+ args = [executable]
+ bufsize = -1
+ if connection:
+ bufsize = 0
+ args.append("--connection")
+ args.append(connection)
+
+ proc = subprocess.Popen(
+ args,
+ bufsize=bufsize,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=sys.stdout,
+ env=adaptor_env,
+ )
+
+ if connection:
+ # If a conneciton is specified, lldb-dap will print the listening
+ # address once the listener is made to stdout. The listener is
+ # formatted like `tcp://host:port` or `unix:///path`.
+ expected_prefix = "Listening for: "
+ out = proc.stdout.readline().decode()
+ if not out.startswith(expected_prefix):
+ proc.kill()
+ raise ValueError(
+ "lldb-dap failed to print listening address, expected '{}', got '{}'".format(
+ expected_prefix, out
+ )
+ )
+
+ # If the listener expanded into multiple addresses, use the first.
+ connection = out.removeprefix(expected_prefix).rstrip("\r\n")
+ return proc, connection
+
+ return proc, None
+
def attach_options_specified(options):
if options.pid is not None:
diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
index a25466f07fa557..6b1216c8837f7c 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py
@@ -10,10 +10,10 @@
class DAPTestCaseBase(TestBase):
# set timeout based on whether ASAN was enabled or not. Increase
# timeout by a factor of 10 if ASAN is enabled.
- timeoutval = 10 * (10 if ('ASAN_OPTIONS' in os.environ) else 1)
+ timeoutval = 10 * (10 if ("ASAN_OPTIONS" in os.environ) else 1)
NO_DEBUG_INFO_TESTCASE = True
- def create_debug_adaptor(self, lldbDAPEnv=None):
+ def create_debug_adaptor(self, env=None, launch=True, connection=None):
"""Create the Visual Studio Code debug adaptor"""
self.assertTrue(
is_exe(self.lldbDAPExec), "lldb-dap must exist and be executable"
@@ -21,14 +21,25 @@ def create_debug_adaptor(self, lldbDAPEnv=None):
log_file_path = self.getBuildArtifact("dap.txt")
self.dap_server = dap_server.DebugAdaptorServer(
executable=self.lldbDAPExec,
+ launch=launch,
+ connection=connection,
init_commands=self.setUpCommands(),
log_file=log_file_path,
- env=lldbDAPEnv,
+ env=env,
)
- def build_and_create_debug_adaptor(self, lldbDAPEnv=None):
+ def build_and_create_debug_adaptor(
+ self,
+ lldbDAPEnv=None,
+ lldbDAPLaunch=True,
+ lldbDAPConnection=None,
+ ):
self.build()
- self.create_debug_adaptor(lldbDAPEnv)
+ self.create_debug_adaptor(
+ env=lldbDAPEnv,
+ launch=lldbDAPLaunch,
+ connection=lldbDAPConnection,
+ )
def set_source_breakpoints(self, source_path, lines, data=None):
"""Sets source breakpoints and returns an array of strings containing
@@ -475,11 +486,17 @@ def build_and_launch(
customThreadFormat=None,
launchCommands=None,
expectFailure=False,
+ lldbDAPLaunch=True,
+ lldbDAPConnection=None,
):
"""Build the default Makefile target, create the DAP debug adaptor,
and launch the process.
"""
- self.build_and_create_debug_adaptor(lldbDAPEnv)
+ self.build_and_create_debug_adaptor(
+ lldbDAPEnv=lldbDAPEnv,
+ lldbDAPLaunch=lldbDAPLaunch,
+ lldbDAPConnection=lldbDAPConnection,
+ )
self.assertTrue(os.path.exists(program), "executable must exist")
return self.launch(
diff --git a/lldb/test/API/tools/lldb-dap/server/Makefile b/lldb/test/API/tools/lldb-dap/server/Makefile
new file mode 100644
index 00000000000000..10495940055b63
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/server/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
diff --git a/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py b/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
new file mode 100644
index 00000000000000..c90e6b8da0a303
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
@@ -0,0 +1,72 @@
+"""
+Test lldb-dap server integration.
+"""
+
+import os
+import tempfile
+
+import dap_server
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+import lldbdap_testcase
+
+
+class TestDAP_server(lldbdap_testcase.DAPTestCaseBase):
+ def do_test_server(self, connection):
+ log_file_path = self.getBuildArtifact("dap.txt")
+ server, connection = dap_server.DebugAdaptorServer.launch(
+ self.lldbDAPExec, connection, log_file=log_file_path
+ )
+
+ def cleanup():
+ server.terminate()
+ server.wait()
+
+ self.addTearDownHook(cleanup)
+
+ self.build()
+ program = self.getBuildArtifact("a.out")
+ source = "main.c"
+ breakpoint_line = line_number(source, "// breakpoint")
+
+ # Initial connection over the port.
+ self.create_debug_adaptor(launch=False, connection=connection)
+ self.launch(
+ program,
+ disconnectAutomatically=False,
+ )
+ self.set_source_breakpoints(source, [breakpoint_line])
+ self.continue_to_next_stop()
+ self.continue_to_exit()
+ output = self.get_stdout()
+ self.assertEquals(output, "hello world!\r\n")
+ self.dap_server.request_disconnect()
+
+ # Second connection over the port.
+ self.create_debug_adaptor(launch=False, connection=connection)
+ self.launch(program)
+ self.set_source_breakpoints(source, [breakpoint_line])
+ self.continue_to_next_stop()
+ self.continue_to_exit()
+ output = self.get_stdout()
+ self.assertEquals(output, "hello world!\r\n")
+
+ def test_server_port(self):
+ """
+ Test launching a binary with a lldb-dap in server mode on a specific port.
+ """
+ self.do_test_server(connection="tcp://localhost:0")
+
+ @skipIfWindows
+ def test_server_unix_socket(self):
+ """
+ Test launching a binary with a lldb-dap in server mode on a unix socket.
+ """
+ dir = tempfile.gettempdir()
+ name = dir + "/dap-connection-" + str(os.getpid())
+
+ def cleanup():
+ os.unlink(name)
+
+ self.addTearDownHook(cleanup)
+ self.do_test_server(connection="unix://" + name)
diff --git a/lldb/test/API/tools/lldb-dap/server/main.c b/lldb/test/API/tools/lldb-dap/server/main.c
new file mode 100644
index 00000000000000..c3599057621276
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/server/main.c
@@ -0,0 +1,6 @@
+#include <stdio.h>
+
+int main(int argc, char const *argv[]) {
+ printf("hello world!\n"); // breakpoint 1
+ return 0;
+}
diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt
index d68098bf7b3266..43fc18873feb33 100644
--- a/lldb/tools/lldb-dap/CMakeLists.txt
+++ b/lldb/tools/lldb-dap/CMakeLists.txt
@@ -1,7 +1,3 @@
-if ( CMAKE_SYSTEM_NAME MATCHES "Windows" OR CMAKE_SYSTEM_NAME MATCHES "NetBSD" )
- list(APPEND extra_libs lldbHost)
-endif ()
-
if (HAVE_LIBPTHREAD)
list(APPEND extra_libs pthread)
endif ()
@@ -26,9 +22,11 @@ add_lldb_tool(lldb-dap
lldb-dap.cpp
Breakpoint.cpp
BreakpointBase.cpp
+ DAP.cpp
ExceptionBreakpoint.cpp
FifoFiles.cpp
FunctionBreakpoint.cpp
+ InstructionBreakpoint.cpp
IOStream.cpp
JSONUtils.cpp
LLDBUtils.cpp
@@ -36,12 +34,11 @@ add_lldb_tool(lldb-dap
ProgressEvent.cpp
RunInTerminal.cpp
SourceBreakpoint.cpp
- DAP.cpp
Watchpoint.cpp
- InstructionBreakpoint.cpp
LINK_LIBS
liblldb
+ lldbHost
${extra_libs}
LINK_COMPONENTS
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index 35250d9eef608a..243117782dbe84 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -14,11 +14,14 @@
#include "DAP.h"
#include "JSONUtils.h"
#include "LLDBUtils.h"
+#include "OutputRedirector.h"
#include "lldb/API/SBCommandInterpreter.h"
#include "lldb/API/SBLanguageRuntime.h"
#include "lldb/API/SBListener.h"
#include "lldb/API/SBStream.h"
#include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
#include "llvm/Support/FormatVariadic.h"
#if defined(_WIN32)
@@ -32,10 +35,11 @@ using namespace lldb_dap;
namespace lldb_dap {
-DAP::DAP(llvm::StringRef path, ReplMode repl_mode)
- : debug_adaptor_path(path), broadcaster("lldb-dap"),
- exception_breakpoints(), focus_tid(LLDB_INVALID_THREAD_ID),
- stop_at_entry(false), is_attach(false),
+DAP::DAP(llvm::StringRef path, std::ofstream *log, ReplMode repl_mode,
+ std::vector<std::string> pre_init_commands)
+ : debug_adaptor_path(path), broadcaster("lldb-dap"), log(log),
+ exception_breakpoints(), pre_init_commands(pre_init_commands),
+ focus_tid(LLDB_INVALID_THREAD_ID), stop_at_entry(false), is_attach(false),
enable_auto_variable_summaries(false),
enable_synthetic_child_debugging(false),
display_extended_backtrace(false),
@@ -43,24 +47,34 @@ DAP::DAP(llvm::StringRef path, ReplMode repl_mode)
configuration_done_sent(false), waiting_for_run_in_terminal(false),
progress_event_reporter(
[&](const ProgressEvent &event) { SendJSON(event.ToJSON()); }),
- reverse_request_seq(0), repl_mode(repl_mode) {
- const char *log_file_path = getenv("LLDBDAP_LOG");
-#if defined(_WIN32)
- // Windows opens stdout and stdin in text mode which converts \n to 13,10
- // while the value is just 10 on Darwin/Linux. Setting the file mode to binary
- // fixes this.
- int result = _setmode(fileno(stdout), _O_BINARY);
- assert(result);
- result = _setmode(fileno(stdin), _O_BINARY);
- UNUSED_IF_ASSERT_DISABLED(result);
- assert(result);
-#endif
- if (log_file_path)
- log.reset(new std::ofstream(log_file_path));
-}
+ reverse_request_seq(0), repl_mode(repl_mode) {}
DAP::~DAP() = default;
+llvm::Error DAP::ConfigureIO(int out_fd, int err_fd) {
+ llvm::Expected<int> new_stdout_fd =
+ redirector.RedirectFd(out_fd, [this](llvm::StringRef data) {
+ SendOutput(OutputType::Stdout, data);
+ });
+ if (auto Err = new_stdout_fd.takeError()) {
+ return Err;
+ }
+ llvm::Expected<int> new_stderr_fd =
+ redirector.RedirectFd(err_fd, [this](llvm::StringRef data) {
+ SendOutput(OutputType::Stderr, data);
+ });
+ if (auto Err = new_stderr_fd.takeError()) {
+ return Err;
+ }
+
+ out = lldb::SBFile(/*fd=*/new_stdout_fd.get(), /*mode=*/"w",
+ /*transfer_ownership=*/false);
+ err = lldb::SBFile(/*fd=*/new_stderr_fd.get(), /*mode=*/"w",
+ /*transfer_ownership=*/false);
+
+ return llvm::Error::success();
+}
+
/// Return string with first character capitalized.
static std::string capitalize(llvm::StringRef str) {
if (str.empty())
@@ -208,19 +222,19 @@ std::string DAP::ReadJSON() {
std::string json_str;
int length;
- if (!input.read_expected(log.get(), "Content-Length: "))
+ if (!input.read_expected(log, "Content-Length: "))
return json_str;
- if (!input.read_line(log.get(), length_str))
+ if (!input.read_line(log, length_str))
return json_str;
if (!llvm::to_integer(length_str, length))
return json_str;
- if (!input.read_expected(log.get(), "\r\n"))
+ if (!input.read_expected(log, "\r\n"))
return json_str;
- if (!input.read_full(log.get(), length, json_str))
+ if (!input.read_full(log, length, json_str))
return json_str;
if (log) {
@@ -526,11 +540,14 @@ ReplMode DAP::DetectReplMode(lldb::SBFrame frame, std::string &expression,
// If we have both a variable and command, warn the user about the conflict.
if (term_is_command && term_is_variable) {
- llvm::errs()
- << "Warning: Expression '" << term
- << "' is both an LLDB command and variable. It will be evaluated as "
- "a variable. To evaluate the expression as an LLDB command, use '"
- << command_escape_prefix << "' as a prefix.\n";
+ SendOutput(
+ OutputType::Stderr,
+ llvm::formatv(
+ "Warning: Expression '{0}' is both an LLDB command and variable. "
+ "It will be evaluated as a variable. To evaluate the expression "
+ "as an LLDB command, use '{1}' as a prefix.",
+ term, command_escape_prefix)
+ .str());
}
// Variables take preference to commands in auto, since commands can always
@@ -673,9 +690,8 @@ PacketStatus DAP::GetNextObject(llvm::json::Object &object) {
return PacketStatus::JSONMalformed;
}
- if (log) {
+ if (log)
*log << llvm::formatv("{0:2}", *json_value).str() << std::endl;
- }
llvm::json::Object *object_ptr = json_value->getAsObject();
if (!object_ptr) {
@@ -705,8 +721,15 @@ bool DAP::HandleObject(const llvm::json::Object &object) {
if (packet_type == "response") {
auto id = GetSigned(object, "request_seq", 0);
- ResponseCallback response_handler = [](llvm::Expected<llvm::json::Value>) {
- llvm::errs() << "Unhandled response\n";
+ ResponseCallback response_handler = [](auto dap, auto value) {
+ if (value)
+ dap.SendOutput(OutputType::Stderr,
+ llvm::formatv("unexpected response: {0}", *value).str());
+ else
+ dap.SendOutput(OutputType::Stderr,
+ llvm::formatv("unexpected response: {0}",
+ llvm::toString(value.takeError()))
+ .str());
};
{
@@ -724,14 +747,15 @@ bool DAP::HandleObject(const llvm::json::Object &object) {
if (auto *B = object.get("body")) {
Result = std::move(*B);
}
- response_handler(Result);
+ response_handler(*this, Result);
} else {
llvm::StringRef message = GetString(object, "message");
if (message.empty()) {
message = "Unknown error, response failed";
}
- response_handler(llvm::createStringError(
- std::error_code(-1, std::generic_category()), message));
+ response_handler(
+ *this, llvm::createStringError(
+ std::error_code(-1, std::generic_category()), message));
}
return true;
@@ -904,11 +928,14 @@ bool StartDebuggingRequestHandler::DoExecute(
"startDebugging",
llvm::json::Object{{"request", request},
{"configuration", std::move(*configuration)}},
- [](llvm::Expected<llvm::json::Value> value) {
+ [](auto dap, auto value) {
if (!value) {
llvm::Error err = value.takeError();
- llvm::errs() << "reverse start debugging request failed: "
- << llvm::toString(std::move(err)) << "\n";
+ dap.SendOutput(
+ OutputType::Console,
+ llvm::formatv("reverse start debugging request failed: {0}",
+ llvm::toString(std::move(err)))
+ .str());
}
});
diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h
index ae496236f13369..4b17b0ceb011af 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -9,36 +9,33 @@
#ifndef LLDB_TOOLS_LLDB_DAP_DAP_H
#define LLDB_TOOLS_LLDB_DAP_DAP_H
-#include <cstdio>
-#include <iosfwd>
-#include <map>
-#include <optional>
-#include <thread>
-
-#include "llvm/ADT/DenseMap.h"
-#include "llvm/ADT/DenseSet.h"
-#include "llvm/ADT/StringMap.h"
-#include "llvm/ADT/StringRef.h"
-#include "llvm/Support/JSON.h"
-#include "llvm/Support/Threading.h"
-#include "llvm/Support/raw_ostream.h"
-
+#include "ExceptionBreakpoint.h"
+#include "FunctionBreakpoint.h"
+#include "IOStream.h"
+#include "InstructionBreakpoint.h"
+#include "OutputRedirector.h"
+#include "ProgressEvent.h"
+#include "SourceBreakpoint.h"
#include "lldb/API/SBAttachInfo.h"
#include "lldb/API/SBCommandInterpreter.h"
#include "lldb/API/SBCommandReturnObject.h"
#include "lldb/API/SBDebugger.h"
#include "lldb/API/SBEvent.h"
+#include "lldb/API/SBFile.h"
#include "lldb/API/SBFormat.h"
#include "lldb/API/SBLaunchInfo.h"
#include "lldb/API/SBTarget.h"
#include "lldb/API/SBThread.h"
-
-#include "ExceptionBreakpoint.h"
-#include "FunctionBreakpoint.h"
-#include "IOStream.h"
-#include "InstructionBreakpoint.h"
-#include "ProgressEvent.h"
-#include "SourceBreakpoint.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/StringMap.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/JSON.h"
+#include "llvm/Support/Threading.h"
+#include "llvm/Support/raw_ostream.h"
+#include <map>
+#include <optional>
+#include <thread>
#define VARREF_LOCALS (int64_t)1
#define VARREF_GLOBALS (int64_t)2
@@ -64,7 +61,7 @@ enum DAPBroadcasterBits {
};
typedef void (*RequestCallback)(DAP &dap, const llvm::json::Object &command);
-typedef void (*ResponseCallback)(llvm::Expected<llvm::json::Value> value);
+typedef void (*ResponseCallback)(DAP &dap, llvm::Expected<llvm::json::Value> value);
enum class PacketStatus {
Success = 0,
@@ -140,13 +137,16 @@ struct DAP {
llvm::StringRef debug_adaptor_path;
InputStream input;
OutputStream output;
+ lldb::SBFile in;
+ lldb::SBFile out;
+ lldb::SBFile err;
lldb::SBDebugger debugger;
lldb::SBTarget target;
Variables variables;
lldb::SBBroadcaster broadcaster;
std::thread event_thread;
std::thread progress_event_thread;
- std::unique_ptr<std::ofstream> log;
+ std::ofstream *log;
llvm::StringMap<SourceBreakpointMap> source_breakpoints;
FunctionBreakpointMap function_breakpoints;
InstructionBreakpointMap instruction_breakpoints;
@@ -197,11 +197,33 @@ struct DAP {
// empty; if the previous expression was a variable expression, this string
// will contain that expression.
std::string last_nonempty_var_expression;
+ OutputRedirector redirector;
- DAP(llvm::StringRef path, ReplMode repl_mode);
+ DAP(llvm::StringRef path, std::ofstream *log, ReplMode repl_mode,
+ std::vector<std::string> pre_init_commands);
~DAP();
+
+ DAP() = delete;
DAP(const DAP &rhs) = delete;
void operator=(const DAP &rhs) = delete;
+
+ void disconnect();
+
+ /// Configures the DAP session stdout and stderr to redirect to the DAP
+ /// connection.
+ ///
+ /// Errors in this operation will be printed to the log file and the IDE's
+ /// console output as well.
+ ///
+ /// \param[in] out_fd
+ /// If not -1, uses the given file descriptor as stdout.
+ ///
+ /// \param[in] err_fd
+ /// If not -1, uses the given file descriptor as stderr.
+ ///
+ /// \return An error indiciating if the configuration was applied.
+ llvm::Error ConfigureIO(int out_fd = -1, int err_fd = -1);
+
ExceptionBreakpoint *GetExceptionBreakpoint(const std::string &filter);
ExceptionBreakpoint *GetExceptionBreakpoint(const lldb::break_id_t bp_id);
diff --git a/lldb/tools/lldb-dap/IOStream.cpp b/lldb/tools/lldb-dap/IOStream.cpp
index d2e8ec40b0a7b8..d07410af7b2319 100644
--- a/lldb/tools/lldb-dap/IOStream.cpp
+++ b/lldb/tools/lldb-dap/IOStream.cpp
@@ -7,6 +7,8 @@
//===----------------------------------------------------------------------===//
#include "IOStream.h"
+#include <fstream>
+#include <string>
#if defined(_WIN32)
#include <io.h>
@@ -16,9 +18,6 @@
#include <unistd.h>
#endif
-#include <fstream>
-#include <string>
-
using namespace lldb_dap;
StreamDescriptor::StreamDescriptor() = default;
@@ -27,23 +26,9 @@ StreamDescriptor::StreamDescriptor(StreamDescriptor &&other) {
*this = std::move(other);
}
-StreamDescriptor::~StreamDescriptor() {
- if (!m_close)
- return;
-
- if (m_is_socket)
-#if defined(_WIN32)
- ::closesocket(m_socket);
-#else
- ::close(m_socket);
-#endif
- else
- ::close(m_fd);
-}
+StreamDescriptor::~StreamDescriptor() = default;
StreamDescriptor &StreamDescriptor::operator=(StreamDescriptor &&other) {
- m_close = other.m_close;
- other.m_close = false;
m_is_socket = other.m_is_socket;
if (m_is_socket)
m_socket = other.m_socket;
@@ -52,19 +37,17 @@ StreamDescriptor &StreamDescriptor::operator=(StreamDescriptor &&other) {
return *this;
}
-StreamDescriptor StreamDescriptor::from_socket(SOCKET s, bool close) {
+StreamDescriptor StreamDescriptor::from_socket(SOCKET s) {
StreamDescriptor sd;
sd.m_is_socket = true;
sd.m_socket = s;
- sd.m_close = close;
return sd;
}
-StreamDescriptor StreamDescriptor::from_file(int fd, bool close) {
+StreamDescriptor StreamDescriptor::from_file(int fd) {
StreamDescriptor sd;
sd.m_is_socket = false;
sd.m_fd = fd;
- sd.m_close = close;
return sd;
}
diff --git a/lldb/tools/lldb-dap/IOStream.h b/lldb/tools/lldb-dap/IOStream.h
index 57d5fd458b7165..7296c1dba213e3 100644
--- a/lldb/tools/lldb-dap/IOStream.h
+++ b/lldb/tools/lldb-dap/IOStream.h
@@ -22,7 +22,6 @@ typedef int SOCKET;
#endif
#include "llvm/ADT/StringRef.h"
-
#include <fstream>
#include <string>
@@ -32,17 +31,16 @@ typedef int SOCKET;
// treat them identically.
namespace lldb_dap {
struct StreamDescriptor {
- StreamDescriptor();
+ explicit StreamDescriptor();
~StreamDescriptor();
StreamDescriptor(StreamDescriptor &&other);
StreamDescriptor &operator=(StreamDescriptor &&other);
- static StreamDescriptor from_socket(SOCKET s, bool close);
- static StreamDescriptor from_file(int fd, bool close);
+ static StreamDescriptor from_socket(SOCKET s);
+ static StreamDescriptor from_file(int fd);
bool m_is_socket = false;
- bool m_close = false;
union {
int m_fd;
SOCKET m_socket;
diff --git a/lldb/tools/lldb-dap/Options.td b/lldb/tools/lldb-dap/Options.td
index d7b4a065abec01..41d8912ed6c6bf 100644
--- a/lldb/tools/lldb-dap/Options.td
+++ b/lldb/tools/lldb-dap/Options.td
@@ -17,12 +17,10 @@ def: Flag<["-"], "g">,
Alias<wait_for_debugger>,
HelpText<"Alias for --wait-for-debugger">;
-def port: S<"port">,
- MetaVarName<"<port>">,
- HelpText<"Communicate with the lldb-dap tool over the defined port.">;
-def: Separate<["-"], "p">,
- Alias<port>,
- HelpText<"Alias for --port">;
+def connection: S<"connection">,
+ MetaVarName<"<connection>">,
+ HelpText<"Communicate with the lldb-dap tool over the specified connection. "
+ "Connections are specified like 'tcp://[host]:port' or 'unix:///path'.">;
def launch_target: S<"launch-target">,
MetaVarName<"<target>">,
@@ -42,7 +40,8 @@ def debugger_pid: S<"debugger-pid">,
def repl_mode: S<"repl-mode">,
MetaVarName<"<mode>">,
- HelpText<"The mode for handling repl evaluation requests, supported modes: variable, command, auto.">;
+ HelpText<"The mode for handling repl evaluation requests, supported modes: "
+ "variable, command, auto.">;
def pre_init_command: S<"pre-init-command">,
MetaVarName<"<command>">,
diff --git a/lldb/tools/lldb-dap/OutputRedirector.cpp b/lldb/tools/lldb-dap/OutputRedirector.cpp
index 2c2f49569869b4..6a63ed5a95e319 100644
--- a/lldb/tools/lldb-dap/OutputRedirector.cpp
+++ b/lldb/tools/lldb-dap/OutputRedirector.cpp
@@ -16,29 +16,45 @@
#include "DAP.h"
#include "OutputRedirector.h"
#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+#include <thread>
+#include <utility>
+#include <vector>
using namespace llvm;
namespace lldb_dap {
-Error RedirectFd(int fd, std::function<void(llvm::StringRef)> callback) {
+OutputRedirector::~OutputRedirector() {
+ // Make a best effort cleanup of any redirected FDs.
+ for (auto &[from, to] : m_redirects)
+ ::dup2(to, from); // ignoring errors
+
+ for (const auto &fd : m_fds)
+ close(fd);
+}
+
+Expected<int>
+OutputRedirector::RedirectFd(int fd,
+ std::function<void(llvm::StringRef)> callback) {
int new_fd[2];
#if defined(_WIN32)
- if (_pipe(new_fd, 4096, O_TEXT) == -1) {
+ if (_pipe(new_fd, 4096, O_TEXT) == -1)
#else
- if (pipe(new_fd) == -1) {
+ if (pipe(new_fd) == -1)
#endif
- int error = errno;
- return createStringError(inconvertibleErrorCode(),
- "Couldn't create new pipe for fd %d. %s", fd,
- strerror(error));
- }
+ return createStringError(llvm::errnoAsErrorCode(),
+ "Couldn't create new pipe for fd %d.", fd);
+
+ m_fds.push_back(new_fd[0]);
+ m_fds.push_back(new_fd[1]);
+
+ if (fd != -1) {
+ if (dup2(new_fd[1], fd) == -1)
+ return createStringError(llvm::errnoAsErrorCode(),
+ "Couldn't override the fd %d.", fd);
- if (dup2(new_fd[1], fd) == -1) {
- int error = errno;
- return createStringError(inconvertibleErrorCode(),
- "Couldn't override the fd %d. %s", fd,
- strerror(error));
+ m_redirects.push_back(std::make_pair(new_fd[1], fd));
}
int read_fd = new_fd[0];
@@ -57,7 +73,7 @@ Error RedirectFd(int fd, std::function<void(llvm::StringRef)> callback) {
}
});
t.detach();
- return Error::success();
+ return new_fd[1];
}
} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/OutputRedirector.h b/lldb/tools/lldb-dap/OutputRedirector.h
index e26d1648b104f9..59de19d5ff1b6b 100644
--- a/lldb/tools/lldb-dap/OutputRedirector.h
+++ b/lldb/tools/lldb-dap/OutputRedirector.h
@@ -11,15 +11,38 @@
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"
+#include <utility>
+#include <vector>
namespace lldb_dap {
-/// Redirects the output of a given file descriptor to a callback.
-///
-/// \return
-/// \a Error::success if the redirection was set up correctly, or an error
-/// otherwise.
-llvm::Error RedirectFd(int fd, std::function<void(llvm::StringRef)> callback);
+/// Manages the lifetime of file descriptor redirects.
+struct OutputRedirector {
+ OutputRedirector() = default;
+
+ OutputRedirector(const OutputRedirector &) = delete;
+ OutputRedirector &operator=(const OutputRedirector &) = delete;
+
+ ~OutputRedirector();
+
+ /// Redirects the output of a given file descriptor to a callback.
+ ///
+ /// \param[in] fd
+ /// Either -1 or the fd duplicate into the new handle.
+ ///
+ /// \param[in] callback
+ /// A callback invoked each time the file is written.
+ ///
+ /// \return
+ /// A new file handle for the output.
+ llvm::Expected<int> RedirectFd(int fd,
+ std::function<void(llvm::StringRef)> callback);
+
+private:
+ std::vector<int> m_fds; // owned fds, closed on dealloc.
+ std::vector<std::pair<int, int>>
+ m_redirects; // pairs (new, old) of redirected fds.
+};
} // namespace lldb_dap
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index 3bfc578806021e..68b482e80e4170 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -10,31 +10,39 @@
#include "FifoFiles.h"
#include "JSONUtils.h"
#include "LLDBUtils.h"
-#include "OutputRedirector.h"
#include "RunInTerminal.h"
#include "Watchpoint.h"
#include "lldb/API/SBDeclaration.h"
+#include "lldb/API/SBFile.h"
#include "lldb/API/SBInstruction.h"
#include "lldb/API/SBListener.h"
#include "lldb/API/SBMemoryRegionInfo.h"
#include "lldb/API/SBStream.h"
#include "lldb/API/SBStringList.h"
#include "lldb/Host/Config.h"
+#include "lldb/Host/MainLoop.h"
+#include "lldb/Host/MainLoopBase.h"
+#include "lldb/Host/Socket.h"
+#include "lldb/Host/common/TCPSocket.h"
+#include "lldb/Host/posix/DomainSocket.h"
+#include "lldb/Utility/Status.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/StringRef.h"
#include "llvm/Option/Arg.h"
#include "llvm/Option/ArgList.h"
#include "llvm/Option/OptTable.h"
#include "llvm/Option/Option.h"
#include "llvm/Support/Base64.h"
-#include "llvm/Support/Errno.h"
+#include "llvm/Support/Error.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/InitLLVM.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/PrettyStackTrace.h"
+#include "llvm/Support/Signals.h"
#include "llvm/Support/raw_ostream.h"
#include <algorithm>
#include <array>
@@ -44,13 +52,18 @@
#include <cstdio>
#include <cstdlib>
#include <cstring>
+#include <fstream>
+#include <iostream>
#include <map>
#include <memory>
#include <optional>
+#include <ostream>
#include <set>
#include <sys/stat.h>
#include <sys/types.h>
+#include <system_error>
#include <thread>
+#include <utility>
#include <vector>
#if defined(_WIN32)
@@ -66,6 +79,7 @@
#else
#include <netinet/in.h>
#include <sys/socket.h>
+#include <sys/un.h>
#include <unistd.h>
#endif
@@ -81,6 +95,13 @@ typedef int socklen_t;
#endif
using namespace lldb_dap;
+using lldb_private::DomainSocket;
+using lldb_private::MainLoop;
+using lldb_private::MainLoopBase;
+using lldb_private::NativeSocket;
+using lldb_private::Socket;
+using lldb_private::Status;
+using lldb_private::TCPSocket;
namespace {
using namespace llvm::opt;
@@ -116,6 +137,8 @@ enum LaunchMethod { Launch, Attach, AttachForSuspendedLaunch };
/// Page size used for reporting addtional frames in the 'stackTrace' request.
constexpr int StackPageSize = 20;
+void RegisterRequestCallbacks(DAP &dap);
+
/// Prints a welcome message on the editor if the preprocessor variable
/// LLDB_DAP_WELCOME_MESSAGE is defined.
static void PrintWelcomeMessage(DAP &dap) {
@@ -137,44 +160,6 @@ lldb::SBValueList *GetTopLevelScope(DAP &dap, int64_t variablesReference) {
}
}
-SOCKET AcceptConnection(DAP &dap, int portno) {
- // Accept a socket connection from any host on "portno".
- SOCKET newsockfd = -1;
- struct sockaddr_in serv_addr, cli_addr;
- SOCKET sockfd = socket(AF_INET, SOCK_STREAM, 0);
- if (sockfd < 0) {
- if (dap.log)
- *dap.log << "error: opening socket (" << strerror(errno) << ")"
- << std::endl;
- } else {
- memset((char *)&serv_addr, 0, sizeof(serv_addr));
- serv_addr.sin_family = AF_INET;
- // serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
- serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
- serv_addr.sin_port = htons(portno);
- if (bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
- if (dap.log)
- *dap.log << "error: binding socket (" << strerror(errno) << ")"
- << std::endl;
- } else {
- listen(sockfd, 5);
- socklen_t clilen = sizeof(cli_addr);
- newsockfd =
- llvm::sys::RetryAfterSignal(static_cast<SOCKET>(-1), accept, sockfd,
- (struct sockaddr *)&cli_addr, &clilen);
- if (newsockfd < 0)
- if (dap.log)
- *dap.log << "error: accept (" << strerror(errno) << ")" << std::endl;
- }
-#if defined(_WIN32)
- closesocket(sockfd);
-#else
- close(sockfd);
-#endif
- }
- return newsockfd;
-}
-
std::vector<const char *> MakeArgv(const llvm::ArrayRef<std::string> &strs) {
// Create and return an array of "const char *", one for each C string in
// "strs" and terminate the list with a NULL. This can be used for argument
@@ -306,12 +291,11 @@ void SendThreadStoppedEvent(DAP &dap) {
if (dap.log)
*dap.log << "error: SendThreadStoppedEvent() when process"
" isn't stopped ("
- << lldb::SBDebugger::StateAsCString(state) << ')' << std::endl;
+ << lldb::SBDebugger::StateAsCString(state) << ")\n";
}
} else {
if (dap.log)
- *dap.log << "error: SendThreadStoppedEvent() invalid process"
- << std::endl;
+ *dap.log << "error: SendThreadStoppedEvent() invalid process\n";
}
dap.RunStopCommands();
}
@@ -1868,7 +1852,21 @@ void request_initialize(DAP &dap, const llvm::json::Object &request) {
// which may affect the outcome of tests.
bool source_init_file = GetBoolean(arguments, "sourceInitFile", true);
- dap.debugger = lldb::SBDebugger::Create(source_init_file);
+ lldb::SBDebugger debugger = lldb::SBDebugger::Create(false);
+ debugger.SetInputFile(dap.in);
+ debugger.SetOutputFile(dap.out);
+ debugger.SetErrorFile(dap.err);
+
+ lldb::SBCommandInterpreter interp = debugger.GetCommandInterpreter();
+
+ if (source_init_file) {
+ lldb::SBCommandReturnObject result;
+ interp.SourceInitFileInCurrentWorkingDirectory(result);
+ interp.SourceInitFileInHomeDirectory(result, false);
+ }
+
+ dap.debugger = debugger;
+
if (llvm::Error err = dap.RunPreInitCommands()) {
response["success"] = false;
EmplaceSafeString(response, "message", llvm::toString(std::move(err)));
@@ -2012,15 +2010,16 @@ llvm::Error request_runInTerminal(DAP &dap,
#endif
llvm::json::Object reverse_request = CreateRunInTerminalReverseRequest(
launch_request, dap.debug_adaptor_path, comm_file.m_path, debugger_pid);
- dap.SendReverseRequest("runInTerminal", std::move(reverse_request),
- [](llvm::Expected<llvm::json::Value> value) {
- if (!value) {
- llvm::Error err = value.takeError();
- llvm::errs()
- << "runInTerminal request failed: "
- << llvm::toString(std::move(err)) << "\n";
- }
- });
+ dap.SendReverseRequest(
+ "runInTerminal", std::move(reverse_request), [](auto dap, auto value) {
+ if (!value) {
+ dap.SendOutput(
+ OutputType::Stderr,
+ llvm::formatv("runInTerminal request failed: {}",
+ llvm::toString(std::move(value.takeError())))
+ .str());
+ }
+ });
if (llvm::Expected<lldb::pid_t> pid = comm_channel.GetLauncherPid())
attach_info.SetProcessID(*pid);
@@ -4907,36 +4906,27 @@ static void redirection_test() {
fflush(stderr);
}
-/// Redirect stdout and stderr fo the IDE's console output.
-///
-/// Errors in this operation will be printed to the log file and the IDE's
-/// console output as well.
-///
-/// \return
-/// A fd pointing to the original stdout.
-static int SetupStdoutStderrRedirection(DAP &dap) {
- int stdoutfd = fileno(stdout);
- int new_stdout_fd = dup(stdoutfd);
- auto output_callback_stderr = [&dap](llvm::StringRef data) {
- dap.SendOutput(OutputType::Stderr, data);
- };
- auto output_callback_stdout = [&dap](llvm::StringRef data) {
- dap.SendOutput(OutputType::Stdout, data);
- };
- if (llvm::Error err = RedirectFd(stdoutfd, output_callback_stdout)) {
- std::string error_message = llvm::toString(std::move(err));
- if (dap.log)
- *dap.log << error_message << std::endl;
- output_callback_stderr(error_message);
- }
- if (llvm::Error err = RedirectFd(fileno(stderr), output_callback_stderr)) {
- std::string error_message = llvm::toString(std::move(err));
- if (dap.log)
- *dap.log << error_message << std::endl;
- output_callback_stderr(error_message);
+static llvm::Expected<std::pair<Socket::SocketProtocol, std::string>>
+parseConnection(llvm::StringRef conn) {
+ if (conn.contains("://")) {
+ llvm::StringRef scheme, rest;
+ std::tie(scheme, rest) = conn.split("://");
+
+ if (scheme == "unix" || scheme == "unix-connect") {
+ return std::make_pair(Socket::ProtocolUnixDomain, rest.str());
+ }
+ if (scheme == "tcp" || scheme == "connect") {
+ return std::make_pair(Socket::ProtocolTcp, rest.str());
+ }
+ } else if (conn.starts_with("/")) {
+ return std::make_pair(Socket::ProtocolUnixDomain, conn.str());
+ } else if (conn.contains(":")) {
+ return std::make_pair(Socket::ProtocolTcp, conn.str());
}
- return new_stdout_fd;
+ return llvm::createStringError(
+ "expected '[unix://]/path' or '[tcp://][host]:port', got '%s'.",
+ conn.str().c_str());
}
int main(int argc, char *argv[]) {
@@ -5009,17 +4999,20 @@ int main(int argc, char *argv[]) {
}
}
- int portno = -1;
- if (auto *arg = input_args.getLastArg(OPT_port)) {
- const auto *optarg = arg->getValue();
- char *remainder;
- portno = strtol(optarg, &remainder, 0);
- if (remainder == optarg || *remainder != '\0') {
- fprintf(stderr, "'%s' is not a valid port number.\n", optarg);
- return EXIT_FAILURE;
- }
+ std::string connection;
+ if (auto *arg = input_args.getLastArg(OPT_connection)) {
+ const auto *path = arg->getValue();
+ connection.assign(path);
}
+ const char *log_file_path = getenv("LLDBDAP_LOG");
+ std::ofstream *log = nullptr;
+ if (log_file_path)
+ log = new std::ofstream(log_file_path);
+
+ const auto pre_init_commands =
+ input_args.getAllArgValues(OPT_pre_init_command);
+
#if !defined(_WIN32)
if (input_args.hasArg(OPT_wait_for_debugger)) {
printf("Paused waiting for debugger to attach (pid = %i)...\n", getpid());
@@ -5028,48 +5021,128 @@ int main(int argc, char *argv[]) {
#endif
// Initialize LLDB first before we do anything.
- lldb::SBDebugger::Initialize();
+ lldb::SBError error = lldb::SBDebugger::InitializeWithErrorHandling();
+ if (error.Fail()) {
+ llvm::errs() << "Failed to initialize LLDB: " << error.GetCString() << "\n";
+ return EXIT_FAILURE;
+ }
// Terminate the debugger before the C++ destructor chain kicks in.
auto terminate_debugger =
llvm::make_scope_exit([] { lldb::SBDebugger::Terminate(); });
- DAP dap = DAP(program_path.str(), default_repl_mode);
+ auto HandleClient = [=](int out_fd, int err_fd, StreamDescriptor input,
+ StreamDescriptor output) {
+ DAP dap = DAP(program_path, log, default_repl_mode, pre_init_commands);
+ dap.input.descriptor = std::move(input);
+ dap.output.descriptor = std::move(output);
+ RegisterRequestCallbacks(dap);
+
+ if (auto Err = dap.ConfigureIO(out_fd, err_fd)) {
+ if (log)
+ *log << "configureIO failed: " << llvm::toStringWithoutConsuming(Err)
+ << "\n";
+ std::cerr << "failed to configureIO: " << llvm::toString(std::move(Err))
+ << std::endl;
+ return false;
+ }
- RegisterRequestCallbacks(dap);
+ // used only by TestVSCode_redirection_to_console.py
+ if (getenv("LLDB_DAP_TEST_STDOUT_STDERR_REDIRECTION") != nullptr)
+ redirection_test();
- // stdout/stderr redirection to the IDE's console
- int new_stdout_fd = SetupStdoutStderrRedirection(dap);
-
- if (portno != -1) {
- printf("Listening on port %i...\n", portno);
- SOCKET socket_fd = AcceptConnection(dap, portno);
- if (socket_fd >= 0) {
- dap.input.descriptor = StreamDescriptor::from_socket(socket_fd, true);
- dap.output.descriptor = StreamDescriptor::from_socket(socket_fd, false);
- } else {
+ if (auto Err = dap.Loop()) {
+ if (log)
+ *log << "Transport Error: " << llvm::toStringWithoutConsuming(Err)
+ << "\n";
+
+ std::cerr << "Transpot Error: " << llvm::toString(std::move(Err))
+ << std::endl;
+
+ return false;
+ }
+
+ if (log)
+ *log << "connection closed\n";
+
+ return true;
+ };
+
+ if (!connection.empty()) {
+ auto maybeProtoclAndName = parseConnection(connection);
+ if (auto Err = maybeProtoclAndName.takeError()) {
+ std::cerr << "Invalid connection specification "
+ << llvm::toString(std::move(Err)) << std::endl;
return EXIT_FAILURE;
}
- } else {
- dap.input.descriptor = StreamDescriptor::from_file(fileno(stdin), false);
- dap.output.descriptor = StreamDescriptor::from_file(new_stdout_fd, false);
- /// used only by TestVSCode_redirection_to_console.py
- if (getenv("LLDB_DAP_TEST_STDOUT_STDERR_REDIRECTION") != nullptr)
- redirection_test();
- }
+ Socket::SocketProtocol protocol;
+ std::string name;
+ std::tie(protocol, name) = *maybeProtoclAndName;
+
+ Status error;
+ std::unique_ptr<Socket> listener = Socket::Create(protocol, false, error);
+ if (error.Fail()) {
+ std::cerr << "Failed to create listener for protocol "
+ << Socket::FindSchemeByProtocol(protocol)
+ << ", error: " << llvm::toString(error.takeError())
+ << std::endl;
+ return EXIT_FAILURE;
+ }
- for (const std::string &arg :
- input_args.getAllArgValues(OPT_pre_init_command)) {
- dap.pre_init_commands.push_back(arg);
- }
+ error = listener->Listen(name, /* backlog */ 5);
+ if (error.Fail()) {
+ std::cerr << "Failed to listen, error: "
+ << llvm::toString(error.takeError()) << std::endl;
+ return EXIT_FAILURE;
+ }
- bool CleanExit = true;
- if (auto Err = dap.Loop()) {
- if (dap.log)
- *dap.log << "Transport Error: " << llvm::toString(std::move(Err)) << "\n";
- CleanExit = false;
+ std::string address = listener->GetListeningConnectionURI();
+
+ if (log)
+ *log << "started with connection listeners " << address << "\n";
+
+ std::cout << "Listening for: " << address << std::endl;
+ // Ensure listening address are flushed for calles to retrieve the resolve
+ // address.
+ std::flush(std::cout);
+
+ MainLoop mainloop;
+ mainloop.RegisterSignal(
+ SIGHUP, [](auto &RL) { RL.RequestTermination(); }, error);
+
+ auto OnAccept = [=](std::unique_ptr<Socket> client) {
+ // Start a thread for each connection, unblocking the listening thread.
+ std::thread([=, client = std::move(client)]() {
+ HandleClient(
+ /*out_fd=*/-1, /*err_fd=*/-1,
+ StreamDescriptor::from_socket(client->GetNativeSocket()),
+ StreamDescriptor::from_socket(client->GetNativeSocket()));
+ }).detach();
+ };
+
+ auto handles = listener->Accept(mainloop, OnAccept);
+ if (auto Err = handles.takeError()) {
+ std::cerr << "failed to register accept() with the main loop: "
+ << llvm::toString(std::move(Err)) << std::endl;
+ return EXIT_FAILURE;
+ }
+
+ error = mainloop.Run();
+ if (error.Fail()) {
+ std::cerr << "failed to accept()" << llvm::toString(error.takeError())
+ << std::endl;
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
}
- return CleanExit ? EXIT_SUCCESS : EXIT_FAILURE;
+ // stdout/stderr redirection to the IDE's console
+ int new_stdout_fd = dup(fileno(stdout));
+ bool clean_exit = HandleClient(fileno(stdout), fileno(stderr),
+ StreamDescriptor::from_file(fileno(stdin)),
+ StreamDescriptor::from_file(new_stdout_fd));
+
+ return clean_exit ? EXIT_SUCCESS : EXIT_FAILURE;
}
>From 31ead1b2c5af659ffa622b77ed2fac06dc65a421 Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Tue, 3 Dec 2024 15:48:21 -0800
Subject: [PATCH 2/2] Rebasing on lldb host changes.
---
.../test/tools/lldb-dap/dap_server.py | 2 +-
lldb/test/Shell/DAP/TestOptions.test | 4 +--
lldb/tools/lldb-dap/lldb-dap.cpp | 36 +++++++++----------
3 files changed, 19 insertions(+), 23 deletions(-)
diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
index 37679462118531..8003062ea8df1f 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
@@ -1242,7 +1242,7 @@ def launch(
)
# If the listener expanded into multiple addresses, use the first.
- connection = out.removeprefix(expected_prefix).rstrip("\r\n")
+ connection = out.removeprefix(expected_prefix).rstrip("\r\n").split(",", 1)[0]
return proc, connection
return proc, None
diff --git a/lldb/test/Shell/DAP/TestOptions.test b/lldb/test/Shell/DAP/TestOptions.test
index e37e9116e3cddb..d290cdae590fd6 100644
--- a/lldb/test/Shell/DAP/TestOptions.test
+++ b/lldb/test/Shell/DAP/TestOptions.test
@@ -1,8 +1,8 @@
# RUN: lldb-dap --help | FileCheck %s
+# CHECK: --connection
# CHECK: -g
# CHECK: --help
# CHECK: -h
-# CHECK: --port
-# CHECK: -p
+# CHECK: --repl-mode
# CHECK: --wait-for-debugger
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index 68b482e80e4170..0bec52c5281a3d 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -5042,8 +5042,7 @@ int main(int argc, char *argv[]) {
if (log)
*log << "configureIO failed: " << llvm::toStringWithoutConsuming(Err)
<< "\n";
- std::cerr << "failed to configureIO: " << llvm::toString(std::move(Err))
- << std::endl;
+ llvm::errs() << "failed to configureIO: " << Err << "\n";
return false;
}
@@ -5056,8 +5055,7 @@ int main(int argc, char *argv[]) {
*log << "Transport Error: " << llvm::toStringWithoutConsuming(Err)
<< "\n";
- std::cerr << "Transpot Error: " << llvm::toString(std::move(Err))
- << std::endl;
+ llvm::errs() << "Transpot Error: " << Err << "\n";
return false;
}
@@ -5071,8 +5069,8 @@ int main(int argc, char *argv[]) {
if (!connection.empty()) {
auto maybeProtoclAndName = parseConnection(connection);
if (auto Err = maybeProtoclAndName.takeError()) {
- std::cerr << "Invalid connection specification "
- << llvm::toString(std::move(Err)) << std::endl;
+ llvm::errs() << "Invalid connection specification "
+ << Err << "\n";
return EXIT_FAILURE;
}
@@ -5081,31 +5079,30 @@ int main(int argc, char *argv[]) {
std::tie(protocol, name) = *maybeProtoclAndName;
Status error;
- std::unique_ptr<Socket> listener = Socket::Create(protocol, false, error);
+ std::unique_ptr<Socket> listener = Socket::Create(protocol, error);
if (error.Fail()) {
- std::cerr << "Failed to create listener for protocol "
- << Socket::FindSchemeByProtocol(protocol)
- << ", error: " << llvm::toString(error.takeError())
- << std::endl;
+ llvm::errs() << "Failed to create listener for protocol "
+ << Socket::FindSchemeByProtocol(protocol)
+ << ", error: " << error.takeError() << "\n";
return EXIT_FAILURE;
}
error = listener->Listen(name, /* backlog */ 5);
if (error.Fail()) {
- std::cerr << "Failed to listen, error: "
- << llvm::toString(error.takeError()) << std::endl;
+ llvm::errs() << "Failed to listen, error: "
+ << error.takeError() << "\n";
return EXIT_FAILURE;
}
- std::string address = listener->GetListeningConnectionURI();
+ std::string address = llvm::join(listener->GetListeningConnectionURI(), ", ");
if (log)
*log << "started with connection listeners " << address << "\n";
- std::cout << "Listening for: " << address << std::endl;
+ llvm::outs() << "Listening for: " << address << "\n";
// Ensure listening address are flushed for calles to retrieve the resolve
// address.
- std::flush(std::cout);
+ llvm::outs().flush();
MainLoop mainloop;
mainloop.RegisterSignal(
@@ -5123,15 +5120,14 @@ int main(int argc, char *argv[]) {
auto handles = listener->Accept(mainloop, OnAccept);
if (auto Err = handles.takeError()) {
- std::cerr << "failed to register accept() with the main loop: "
- << llvm::toString(std::move(Err)) << std::endl;
+ llvm::errs() << "failed to register accept() with the main loop: "
+ << Err << "\n";
return EXIT_FAILURE;
}
error = mainloop.Run();
if (error.Fail()) {
- std::cerr << "failed to accept()" << llvm::toString(error.takeError())
- << std::endl;
+ llvm::errs() << "failed to accept()" << error.takeError() << "\n";
return EXIT_FAILURE;
}
More information about the lldb-commits
mailing list