[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
Thu Feb 6 13:21:15 PST 2025
https://github.com/ashgti updated https://github.com/llvm/llvm-project/pull/116392
>From 88a8522f1b29b2ff392974322acdb722b7e00b70 Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Tue, 28 Jan 2025 12:39:38 -0800
Subject: [PATCH 1/4] [lldb-dap] Refactoring lldb-dap port listening mode to
allow multiple connections.
This adjusts the lldb-dap listening mode to accept multiple clients.
Each client initializes a new instance of DAP and an associated `lldb::SBDebugger` instance.
The listening mode is configured with the `--connection` option and supports listening on a port or a unix socket on supported platforms.
---
.../test/tools/lldb-dap/dap_server.py | 74 ++++-
.../test/tools/lldb-dap/lldbdap_testcase.py | 6 +-
lldb/test/API/tools/lldb-dap/server/Makefile | 3 +
.../tools/lldb-dap/server/TestDAP_server.py | 77 +++++
lldb/test/API/tools/lldb-dap/server/main.c | 10 +
lldb/test/Shell/DAP/TestOptions.test | 4 +-
lldb/tools/lldb-dap/DAP.cpp | 18 +-
lldb/tools/lldb-dap/DAP.h | 6 +-
lldb/tools/lldb-dap/DAPForward.h | 2 +
lldb/tools/lldb-dap/Options.td | 22 +-
lldb/tools/lldb-dap/lldb-dap.cpp | 283 +++++++++++-------
11 files changed, 358 insertions(+), 147 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 c29992ce9c7848e..6d765e10236733a 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,11 +1150,12 @@ def request_setInstructionBreakpoints(self, memory_reference=[]):
}
return self.send_recv(command_dict)
+
class DebugAdaptorServer(DebugCommunication):
def __init__(
self,
executable=None,
- port=None,
+ connection=None,
init_commands=[],
log_file=None,
env=None,
@@ -1167,21 +1168,62 @@ def __init__(
if log_file:
adaptor_env["LLDBDAP_LOG"] = log_file
+ args = [executable]
+
+ if connection is not None:
+ args.append("--connection")
+ args.append(connection)
+
self.process = subprocess.Popen(
- [executable],
+ args,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=adaptor_env,
)
+
+ if connection is not None:
+ # If the process was also launched, parse the connection from the
+ # resolved connection. For example, if the connection
+ # `connection://localhost:0` was specified then the OS would pick a
+ # random port for listening and lldb-dap would print the listening
+ # port to stdout.
+ if self.process is not None:
+ # lldb-dap will print the listening address once the listener is
+ # made to stdout. The listener is formatted like
+ # `connection://host:port` or `unix-connection:///path`.
+ expected_prefix = "Listening for: "
+ out = self.process.stdout.readline().decode()
+ if not out.startswith(expected_prefix):
+ self.process.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").split(",", 1)[0]
+ )
+
+ scheme, address = connection.split("://")
+ if scheme == "unix-connect": # unix-connect:///path
+ s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ s.connect(address)
+ elif scheme == "connection": # connection://[host]:port
+ host, port = address.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))
+ self.connection = connection
+ 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):
@@ -1349,10 +1391,9 @@ def main():
)
parser.add_option(
- "--port",
- type="int",
- dest="port",
- help="Attach a socket to a port instead of using STDIN for VSCode",
+ "--connection",
+ dest="connection",
+ help="Attach a socket connection of using STDIN for VSCode",
default=None,
)
@@ -1498,15 +1539,16 @@ def main():
(options, args) = parser.parse_args(sys.argv[1:])
- if options.vscode_path is None and options.port is None:
+ if options.vscode_path is None and options.connection is None:
print(
"error: must either specify a path to a Visual Studio Code "
"Debug Adaptor vscode executable path using the --vscode "
- "option, or a port to attach to for an existing lldb-dap "
- "using the --port option"
+ "option, or using the --connection option"
)
return
- dbg = DebugAdaptorServer(executable=options.vscode_path, port=options.port)
+ dbg = DebugAdaptorServer(
+ executable=options.vscode_path, connection=options.connection
+ )
if options.debug:
raw_input('Waiting for debugger to attach pid "%i"' % (dbg.get_pid()))
if options.replay:
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 a25466f07fa557f..015c613956d86b1 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
@@ -1,5 +1,6 @@
import os
import time
+import subprocess
import dap_server
from lldbsuite.test.lldbtest import *
@@ -10,10 +11,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, lldbDAPEnv=None, connection=None):
"""Create the Visual Studio Code debug adaptor"""
self.assertTrue(
is_exe(self.lldbDAPExec), "lldb-dap must exist and be executable"
@@ -21,6 +22,7 @@ def create_debug_adaptor(self, lldbDAPEnv=None):
log_file_path = self.getBuildArtifact("dap.txt")
self.dap_server = dap_server.DebugAdaptorServer(
executable=self.lldbDAPExec,
+ connection=connection,
init_commands=self.setUpCommands(),
log_file=log_file_path,
env=lldbDAPEnv,
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 000000000000000..451278a0946ef2b
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/server/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
\ No newline at end of file
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 000000000000000..9597f73b0659bee
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
@@ -0,0 +1,77 @@
+"""
+Test lldb-dap server integration.
+"""
+
+import os
+import signal
+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 start_server(self, connection):
+ log_file_path = self.getBuildArtifact("dap.txt")
+ server = dap_server.DebugAdaptorServer(
+ executable=self.lldbDAPExec,
+ connection=connection,
+ init_commands=self.setUpCommands(),
+ log_file=log_file_path,
+ )
+
+ def cleanup():
+ server.terminate()
+
+ self.addTearDownHook(cleanup)
+
+ return server
+
+ def run_debug_session(self, connection, name):
+ self.dap_server = dap_server.DebugAdaptorServer(
+ connection=connection,
+ )
+ program = self.getBuildArtifact("a.out")
+ source = "main.c"
+ breakpoint_line = line_number(source, "// breakpoint")
+
+ self.launch(
+ program,
+ args=[name],
+ disconnectAutomatically=False,
+ )
+ self.set_source_breakpoints(source, [breakpoint_line])
+ self.continue_to_next_stop()
+ self.continue_to_exit()
+ output = self.get_stdout()
+ self.assertEqual(output, f"Hello {name}!\r\n")
+ self.dap_server.request_disconnect()
+
+ def test_server_port(self):
+ """
+ Test launching a binary with a lldb-dap in server mode on a specific port.
+ """
+ self.build()
+ server = self.start_server(connection="tcp://localhost:0")
+ self.run_debug_session(server.connection, "Alice")
+ self.run_debug_session(server.connection, "Bob")
+
+ @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.build()
+ server = self.start_server(connection="unix://" + name)
+ self.run_debug_session(server.connection, "Alice")
+ self.run_debug_session(server.connection, "Bob")
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 000000000000000..446ae82532af57e
--- /dev/null
+++ b/lldb/test/API/tools/lldb-dap/server/main.c
@@ -0,0 +1,10 @@
+#include <stdio.h>
+
+int main(int argc, char const *argv[]) {
+ if (argc == 2) { // breakpoint 1
+ printf("Hello %s!\n", argv[1]);
+ } else {
+ printf("Hello World!\n");
+ }
+ return 0;
+}
diff --git a/lldb/test/Shell/DAP/TestOptions.test b/lldb/test/Shell/DAP/TestOptions.test
index e37e9116e3cddb5..d290cdae590fd67 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/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index a67abe582abd40c..11a4db5c7f0f48a 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -17,7 +17,6 @@
#include "lldb/API/SBListener.h"
#include "lldb/API/SBProcess.h"
#include "lldb/API/SBStream.h"
-#include "lldb/Host/FileSystem.h"
#include "lldb/Utility/Status.h"
#include "lldb/lldb-defines.h"
#include "lldb/lldb-enumerations.h"
@@ -58,12 +57,13 @@ const char DEV_NULL[] = "/dev/null";
namespace lldb_dap {
-DAP::DAP(llvm::StringRef path, std::ofstream *log, ReplMode repl_mode,
- StreamDescriptor input, StreamDescriptor output)
- : debug_adaptor_path(path), log(log), input(std::move(input)),
+DAP::DAP(std::string name, llvm::StringRef path, std::ofstream *log,
+ StreamDescriptor input, StreamDescriptor output, ReplMode repl_mode,
+ std::vector<std::string> pre_init_commands)
+ : name(name), debug_adaptor_path(path), log(log), input(std::move(input)),
output(std::move(output)), broadcaster("lldb-dap"),
- exception_breakpoints(), focus_tid(LLDB_INVALID_THREAD_ID),
- stop_at_entry(false), is_attach(false),
+ 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),
@@ -249,7 +249,8 @@ 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} {1} <-- ", now.count(), name).str()
+ << std::endl
<< "Content-Length: " << json_str.size() << "\r\n\r\n"
<< llvm::formatv("{0:2}", json).str() << std::endl;
}
@@ -279,7 +280,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
+ *log << llvm::formatv("{0:f9} {1} --> ", now.count(), name).str()
+ << std::endl
<< "Content-Length: " << length << "\r\n\r\n";
}
return json_str;
diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h
index 846300cb945b0d1..7c28dbf801179fa 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -139,6 +139,7 @@ struct SendEventRequestHandler : public lldb::SBCommandPluginInterface {
};
struct DAP {
+ std::string name;
llvm::StringRef debug_adaptor_path;
std::ofstream *log;
InputStream input;
@@ -203,8 +204,9 @@ struct DAP {
// will contain that expression.
std::string last_nonempty_var_expression;
- DAP(llvm::StringRef path, std::ofstream *log, ReplMode repl_mode,
- StreamDescriptor input, StreamDescriptor output);
+ DAP(std::string name, llvm::StringRef path, std::ofstream *log,
+ StreamDescriptor input, StreamDescriptor output, ReplMode repl_mode,
+ std::vector<std::string> pre_init_commands);
~DAP();
DAP(const DAP &rhs) = delete;
void operator=(const DAP &rhs) = delete;
diff --git a/lldb/tools/lldb-dap/DAPForward.h b/lldb/tools/lldb-dap/DAPForward.h
index 0196d83dcd6a91b..15e286f3d08dc1e 100644
--- a/lldb/tools/lldb-dap/DAPForward.h
+++ b/lldb/tools/lldb-dap/DAPForward.h
@@ -11,6 +11,8 @@
// IWYU pragma: begin_exports
+#include "lldb/lldb-forward.h"
+
namespace lldb_dap {
struct BreakpointBase;
struct ExceptionBreakpoint;
diff --git a/lldb/tools/lldb-dap/Options.td b/lldb/tools/lldb-dap/Options.td
index d7b4a065abec010..97a6ec118c47b58 100644
--- a/lldb/tools/lldb-dap/Options.td
+++ b/lldb/tools/lldb-dap/Options.td
@@ -17,12 +17,13 @@ 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>">,
@@ -40,9 +41,12 @@ def debugger_pid: S<"debugger-pid">,
HelpText<"The PID of the lldb-dap instance that sent the launchInTerminal "
"request when using --launch-target.">;
-def repl_mode: S<"repl-mode">,
- MetaVarName<"<mode>">,
- HelpText<"The mode for handling repl evaluation requests, supported modes: variable, command, auto.">;
+def repl_mode
+ : S<"repl-mode">,
+ MetaVarName<"<mode>">,
+ 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/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index 9e0e7f21ce4fc7a..2c7fdb0f0d3cfb2 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -14,45 +14,55 @@
#include "Watchpoint.h"
#include "lldb/API/SBDeclaration.h"
#include "lldb/API/SBEvent.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/Socket.h"
+#include "lldb/Utility/Status.h"
+#include "lldb/Utility/UriParser.h"
+#include "lldb/lldb-forward.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/ThreadPool.h"
#include "llvm/Support/raw_ostream.h"
#include <algorithm>
#include <array>
-#include <cassert>
-#include <climits>
-#include <cstdarg>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fcntl.h>
+#include <fstream>
+#include <iostream>
#include <map>
#include <memory>
#include <optional>
+#include <ostream>
#include <set>
+#include <string>
#include <sys/stat.h>
#include <sys/types.h>
#include <thread>
+#include <utility>
#include <vector>
#if defined(_WIN32)
@@ -68,6 +78,7 @@
#else
#include <netinet/in.h>
#include <sys/socket.h>
+#include <sys/un.h>
#include <unistd.h>
#endif
@@ -83,6 +94,9 @@ typedef int socklen_t;
#endif
using namespace lldb_dap;
+using lldb_private::NativeSocket;
+using lldb_private::Socket;
+using lldb_private::Status;
namespace {
using namespace llvm::opt;
@@ -142,43 +156,6 @@ lldb::SBValueList *GetTopLevelScope(DAP &dap, int64_t variablesReference) {
}
}
-SOCKET AcceptConnection(std::ofstream *log, 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 (log)
- *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 (log)
- *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 (log)
- *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
@@ -4857,10 +4834,10 @@ static void printHelp(LLDBDAPOptTable &table, llvm::StringRef tool_name) {
The debug adapter can be started in two modes.
Running lldb-dap without any arguments will start communicating with the
- parent over stdio. Passing a port number causes lldb-dap to start listening
- for connections on that port.
+ parent over stdio. Passing a --connection URI will cause lldb-dap to listen
+ for a connection in the specified mode.
- lldb-dap -p <port>
+ lldb-dap --connection connection://localhost:<port>
Passing --wait-for-debugger will pause the process at startup and wait for a
debugger to attach to the process.
@@ -4952,6 +4929,29 @@ static int DuplicateFileDescriptor(int fd) {
#endif
}
+static llvm::Expected<std::pair<Socket::SocketProtocol, std::string>>
+validateConnection(llvm::StringRef conn) {
+ auto uri = lldb_private::URI::Parse(conn);
+
+ if (uri && (uri->scheme == "tcp" || uri->scheme == "connect" ||
+ !uri->hostname.empty() || uri->port)) {
+ return std::make_pair(
+ Socket::ProtocolTcp,
+ formatv("[{0}]:{1}", uri->hostname.empty() ? "0.0.0.0" : uri->hostname,
+ uri->port.value_or(0)));
+ }
+
+ if (uri && (uri->scheme == "unix" || uri->scheme == "unix-connect" ||
+ uri->path != "/")) {
+ return std::make_pair(Socket::ProtocolUnixDomain, uri->path.str());
+ }
+
+ return llvm::createStringError(
+ "Unsupported connection specifier, expected 'unix-connect:///path' or "
+ "'connect://[host]:port', got '%s'.",
+ conn.str().c_str());
+}
+
int main(int argc, char *argv[]) {
llvm::InitLLVM IL(argc, argv, /*InstallPipeSignalExitHandler=*/false);
#if !defined(__APPLE__)
@@ -4987,9 +4987,9 @@ int main(int argc, char *argv[]) {
} else if (repl_mode_value == "command") {
default_repl_mode = ReplMode::Command;
} else {
- llvm::errs()
- << "'" << repl_mode_value
- << "' is not a valid option, use 'variable', 'command' or 'auto'.\n";
+ llvm::errs() << "'" << repl_mode_value
+ << "' is not a valid option, use 'variable', 'command' or "
+ "'auto'.\n";
return EXIT_FAILURE;
}
}
@@ -5022,15 +5022,10 @@ 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);
}
#if !defined(_WIN32)
@@ -5058,72 +5053,144 @@ int main(int argc, char *argv[]) {
auto terminate_debugger =
llvm::make_scope_exit([] { lldb::SBDebugger::Terminate(); });
- StreamDescriptor input;
- StreamDescriptor output;
- std::FILE *redirectOut = nullptr;
- std::FILE *redirectErr = nullptr;
- if (portno != -1) {
- printf("Listening on port %i...\n", portno);
- SOCKET socket_fd = AcceptConnection(log.get(), portno);
- if (socket_fd < 0)
- return EXIT_FAILURE;
+ std::vector<std::string> pre_init_commands;
+ for (const std::string &arg :
+ input_args.getAllArgValues(OPT_pre_init_command)) {
+ pre_init_commands.push_back(arg);
+ }
- input = StreamDescriptor::from_socket(socket_fd, true);
- output = StreamDescriptor::from_socket(socket_fd, false);
- } else {
-#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
+ auto RunDAP = [](llvm::StringRef program_path, ReplMode repl_mode,
+ std::vector<std::string> pre_init_commands,
+ std::ofstream *log, std::string name, StreamDescriptor input,
+ StreamDescriptor output, std::FILE *redirectOut = nullptr,
+ std::FILE *redirectErr = nullptr) -> bool {
+ DAP dap = DAP(name, program_path, log, std::move(input), std::move(output),
+ repl_mode, pre_init_commands);
- int stdout_fd = DuplicateFileDescriptor(fileno(stdout));
- if (stdout_fd == -1) {
+ // stdout/stderr redirection to the IDE's console
+ if (auto Err = dap.ConfigureIO(redirectOut, redirectErr)) {
llvm::logAllUnhandledErrors(
- llvm::errorCodeToError(llvm::errnoAsErrorCode()), llvm::errs(),
- "Failed to configure stdout redirect: ");
+ std::move(Err), llvm::errs(),
+ "Failed to configure lldb-dap IO operations: ");
+ return false;
+ }
+
+ RegisterRequestCallbacks(dap);
+
+ // used only by TestVSCode_redirection_to_console.py
+ if (getenv("LLDB_DAP_TEST_STDOUT_STDERR_REDIRECTION") != nullptr)
+ redirection_test();
+
+ if (auto Err = dap.Loop()) {
+ std::string errorMessage = llvm::toString(std::move(Err));
+ if (log)
+ *log << "Transport Error: " << errorMessage << "\n";
+ return false;
+ }
+ return true;
+ };
+
+ if (!connection.empty()) {
+ auto maybeProtoclAndName = validateConnection(connection);
+ if (auto Err = maybeProtoclAndName.takeError()) {
+ llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
+ "Invalid connection: ");
return EXIT_FAILURE;
}
- redirectOut = stdout;
- redirectErr = stderr;
+ Socket::SocketProtocol protocol;
+ std::string name;
+ std::tie(protocol, name) = *maybeProtoclAndName;
- input = StreamDescriptor::from_file(fileno(stdin), false);
- output = StreamDescriptor::from_file(stdout_fd, false);
- }
+ Status error;
+ std::unique_ptr<Socket> listener = Socket::Create(protocol, error);
+ if (error.Fail()) {
+ llvm::logAllUnhandledErrors(error.takeError(), llvm::errs(),
+ "Failed to create socket listener: ");
+ return EXIT_FAILURE;
+ }
- DAP dap = DAP(program_path.str(), log.get(), default_repl_mode,
- std::move(input), std::move(output));
+ error = listener->Listen(name, /* backlog */ 5);
+ if (error.Fail()) {
+ llvm::logAllUnhandledErrors(error.takeError(), llvm::errs(),
+ "Failed to listen for connections: ");
+ return EXIT_FAILURE;
+ }
- // stdout/stderr redirection to the IDE's console
- if (auto Err = dap.ConfigureIO(redirectOut, redirectErr)) {
- llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
- "Failed to configure lldb-dap IO operations: ");
- return EXIT_FAILURE;
- }
+ std::string address =
+ llvm::join(listener->GetListeningConnectionURI(), ", ");
+ if (log)
+ *log << "started with connection listeners " << address << "\n";
+
+ llvm::outs() << "Listening for: " << address << "\n";
+ // Ensure listening address are flushed for calles to retrieve the resolve
+ // address.
+ llvm::outs().flush();
+
+ llvm::DefaultThreadPool pool(llvm::optimal_concurrency());
+ unsigned int clientCount = 0;
+
+ while (true) {
+ Socket *accepted_socket;
+ error = listener->Accept(/*timeout=*/std::nullopt, accepted_socket);
+ if (error.Fail()) {
+ llvm::logAllUnhandledErrors(error.takeError(), llvm::errs(),
+ "accept failed: ");
+ return EXIT_FAILURE;
+ }
- RegisterRequestCallbacks(dap);
+ std::string name = llvm::formatv("client_{0}", clientCount++).str();
+ if (log) {
+ auto now = std::chrono::duration<double>(
+ std::chrono::system_clock::now().time_since_epoch());
+ *log << llvm::formatv("{0:f9}", now.count()).str()
+ << " client connected: " << name << "\n";
+ }
- for (const std::string &arg :
- input_args.getAllArgValues(OPT_pre_init_command)) {
- dap.pre_init_commands.push_back(arg);
+ // Move the client into the connection pool to unblock accepting the next
+ // client.
+ pool.async(
+ [=](Socket *accepted_socket, std::ofstream *log) {
+ StreamDescriptor input = StreamDescriptor::from_socket(
+ accepted_socket->GetNativeSocket(), false);
+ // Close the output last for the best chance at error reporting.
+ StreamDescriptor output = StreamDescriptor::from_socket(
+ accepted_socket->GetNativeSocket(), true);
+ RunDAP(program_path, default_repl_mode, pre_init_commands, log,
+ name, std::move(input), std::move(output));
+ },
+ accepted_socket, log.get());
+ }
+
+ pool.wait();
+
+ return EXIT_SUCCESS;
}
- // used only by TestVSCode_redirection_to_console.py
- if (getenv("LLDB_DAP_TEST_STDOUT_STDERR_REDIRECTION") != nullptr)
- redirection_test();
+#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
- bool CleanExit = true;
- if (auto Err = dap.Loop()) {
- if (log)
- *log << "Transport Error: " << llvm::toString(std::move(Err)) << "\n";
- CleanExit = false;
+ int stdout_fd = DuplicateFileDescriptor(fileno(stdout));
+ if (stdout_fd == -1) {
+ llvm::logAllUnhandledErrors(
+ llvm::errorCodeToError(llvm::errnoAsErrorCode()), llvm::errs(),
+ "Failed to configure stdout redirect: ");
+ return EXIT_FAILURE;
}
- return CleanExit ? EXIT_SUCCESS : EXIT_FAILURE;
+ StreamDescriptor input = StreamDescriptor::from_file(fileno(stdin), false);
+ StreamDescriptor output = StreamDescriptor::from_file(stdout_fd, false);
+
+ bool status = RunDAP(program_path, default_repl_mode, pre_init_commands,
+ log.get(), "stdin/stdout", std::move(input),
+ std::move(output), stdout, stderr);
+ return status ? EXIT_SUCCESS : EXIT_FAILURE;
}
>From 2fe4f85f7620be29c28452783a3d2cffafb91c31 Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Tue, 4 Feb 2025 10:57:32 -0800
Subject: [PATCH 2/4] Applying review comments.
---
lldb/test/API/tools/lldb-dap/server/Makefile | 2 +-
lldb/tools/lldb-dap/DAP.cpp | 2 +-
lldb/tools/lldb-dap/DAPForward.h | 2 --
lldb/tools/lldb-dap/lldb-dap.cpp | 5 ++---
4 files changed, 4 insertions(+), 7 deletions(-)
diff --git a/lldb/test/API/tools/lldb-dap/server/Makefile b/lldb/test/API/tools/lldb-dap/server/Makefile
index 451278a0946ef2b..10495940055b63d 100644
--- a/lldb/test/API/tools/lldb-dap/server/Makefile
+++ b/lldb/test/API/tools/lldb-dap/server/Makefile
@@ -1,3 +1,3 @@
C_SOURCES := main.c
-include Makefile.rules
\ No newline at end of file
+include Makefile.rules
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index 11a4db5c7f0f48a..cfaa8ad53c6340a 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -62,7 +62,7 @@ DAP::DAP(std::string name, llvm::StringRef path, std::ofstream *log,
std::vector<std::string> pre_init_commands)
: name(name), debug_adaptor_path(path), log(log), input(std::move(input)),
output(std::move(output)), broadcaster("lldb-dap"),
- exception_breakpoints(), pre_init_commands(pre_init_commands),
+ exception_breakpoints(), pre_init_commands(std::move(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),
diff --git a/lldb/tools/lldb-dap/DAPForward.h b/lldb/tools/lldb-dap/DAPForward.h
index 15e286f3d08dc1e..0196d83dcd6a91b 100644
--- a/lldb/tools/lldb-dap/DAPForward.h
+++ b/lldb/tools/lldb-dap/DAPForward.h
@@ -11,8 +11,6 @@
// IWYU pragma: begin_exports
-#include "lldb/lldb-forward.h"
-
namespace lldb_dap {
struct BreakpointBase;
struct ExceptionBreakpoint;
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index 2c7fdb0f0d3cfb2..fd86c8d8b3a9b56 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -5110,7 +5110,7 @@ int main(int argc, char *argv[]) {
return EXIT_FAILURE;
}
- error = listener->Listen(name, /* backlog */ 5);
+ error = listener->Listen(name, /*backlog=*/5);
if (error.Fail()) {
llvm::logAllUnhandledErrors(error.takeError(), llvm::errs(),
"Failed to listen for connections: ");
@@ -5127,9 +5127,8 @@ int main(int argc, char *argv[]) {
// address.
llvm::outs().flush();
- llvm::DefaultThreadPool pool(llvm::optimal_concurrency());
+ llvm::DefaultThreadPool pool;
unsigned int clientCount = 0;
-
while (true) {
Socket *accepted_socket;
error = listener->Accept(/*timeout=*/std::nullopt, accepted_socket);
>From 606decbf8ea180880e899625c73de11f0fecafa1 Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Wed, 5 Feb 2025 10:06:05 -0800
Subject: [PATCH 3/4] Ensuring the server mode tracks active DAP sessions and
adding an interrupt handler to disconnect active sessions.
---
.../tools/lldb-dap/server/TestDAP_server.py | 31 +++
lldb/tools/lldb-dap/DAP.cpp | 51 ++++
lldb/tools/lldb-dap/DAP.h | 12 +-
lldb/tools/lldb-dap/lldb-dap.cpp | 225 +++++++++---------
4 files changed, 210 insertions(+), 109 deletions(-)
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 9597f73b0659bee..595bb7a0d139212 100644
--- a/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
+++ b/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py
@@ -75,3 +75,34 @@ def cleanup():
server = self.start_server(connection="unix://" + name)
self.run_debug_session(server.connection, "Alice")
self.run_debug_session(server.connection, "Bob")
+
+ @skipIfWindows
+ def test_server_interrupt(self):
+ """
+ Test launching a binary with lldb-dap in server mode and shutting down the server while the debug session is still active.
+ """
+ self.build()
+ server = self.start_server(connection="tcp://localhost:0")
+ self.dap_server = dap_server.DebugAdaptorServer(
+ connection=server.connection,
+ )
+ program = self.getBuildArtifact("a.out")
+ source = "main.c"
+ breakpoint_line = line_number(source, "// breakpoint")
+
+ self.launch(
+ program,
+ args=["Alice"],
+ disconnectAutomatically=False,
+ )
+ self.set_source_breakpoints(source, [breakpoint_line])
+ self.continue_to_next_stop()
+
+ # Interrupt the server which should disconnect all clients.
+ server.process.send_signal(signal.SIGINT)
+
+ self.dap_server.wait_for_terminated()
+ self.assertIsNone(
+ self.dap_server.exit_status,
+ "Process exited before interrupting lldb-dap server",
+ )
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index cfaa8ad53c6340a..4da077e857a4a2f 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -795,6 +795,57 @@ bool DAP::HandleObject(const llvm::json::Object &object) {
return false;
}
+void DAP::SendTerminatedEvent() {
+ // Prevent races if the process exits while we're being asked to disconnect.
+ llvm::call_once(terminated_event_flag, [&] {
+ RunTerminateCommands();
+ // Send a "terminated" event
+ llvm::json::Object event(CreateTerminatedEventObject(target));
+ SendJSON(llvm::json::Value(std::move(event)));
+ });
+}
+
+lldb::SBError DAP::Disconnect() { return Disconnect(is_attach); }
+
+lldb::SBError DAP::Disconnect(bool terminateDebuggee) {
+ lldb::SBError error;
+ lldb::SBProcess process = target.GetProcess();
+ auto state = process.GetState();
+ switch (state) {
+ case lldb::eStateInvalid:
+ case lldb::eStateUnloaded:
+ case lldb::eStateDetached:
+ case lldb::eStateExited:
+ break;
+ case lldb::eStateConnected:
+ case lldb::eStateAttaching:
+ case lldb::eStateLaunching:
+ case lldb::eStateStepping:
+ case lldb::eStateCrashed:
+ case lldb::eStateSuspended:
+ case lldb::eStateStopped:
+ case lldb::eStateRunning:
+ debugger.SetAsync(false);
+ error = terminateDebuggee ? process.Kill() : process.Detach();
+ debugger.SetAsync(true);
+ break;
+ }
+ SendTerminatedEvent();
+
+ if (event_thread.joinable()) {
+ broadcaster.BroadcastEventByType(eBroadcastBitStopEventThread);
+ event_thread.join();
+ }
+ if (progress_event_thread.joinable()) {
+ broadcaster.BroadcastEventByType(eBroadcastBitStopProgressThread);
+ progress_event_thread.join();
+ }
+ StopIO();
+ disconnecting = true;
+
+ return error;
+}
+
llvm::Error DAP::Loop() {
while (!disconnecting) {
llvm::json::Object object;
diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h
index 7c28dbf801179fa..6347dfb57890eb8 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -217,7 +217,8 @@ struct DAP {
///
/// Errors in this operation will be printed to the log file and the IDE's
/// console output as well.
- llvm::Error ConfigureIO(std::FILE *overrideOut, std::FILE *overrideErr);
+ llvm::Error ConfigureIO(std::FILE *overrideOut = nullptr,
+ std::FILE *overrideErr = nullptr);
/// Stop the redirected IO threads and associated pipes.
void StopIO();
@@ -305,6 +306,15 @@ struct DAP {
PacketStatus GetNextObject(llvm::json::Object &object);
bool HandleObject(const llvm::json::Object &object);
+ /// Disconnect the DAP session.
+ lldb::SBError Disconnect();
+
+ /// Disconnect the DAP session and optionally terminate the debuggee.
+ lldb::SBError Disconnect(bool terminateDebuggee);
+
+ /// Send a "terminated" event to indicate the process is done being debugged.
+ void SendTerminatedEvent();
+
llvm::Error Loop();
/// Send a Debug Adapter Protocol reverse request to the IDE.
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index fd86c8d8b3a9b56..4b9b5fb4310ac58 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -21,6 +21,8 @@
#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/Utility/Status.h"
#include "lldb/Utility/UriParser.h"
@@ -42,7 +44,7 @@
#include "llvm/Support/Path.h"
#include "llvm/Support/PrettyStackTrace.h"
#include "llvm/Support/Signals.h"
-#include "llvm/Support/ThreadPool.h"
+#include "llvm/Support/Threading.h"
#include "llvm/Support/raw_ostream.h"
#include <algorithm>
#include <array>
@@ -55,6 +57,7 @@
#include <iostream>
#include <map>
#include <memory>
+#include <mutex>
#include <optional>
#include <ostream>
#include <set>
@@ -207,18 +210,6 @@ void SendContinuedEvent(DAP &dap) {
dap.SendJSON(llvm::json::Value(std::move(event)));
}
-// Send a "terminated" event to indicate the process is done being
-// debugged.
-void SendTerminatedEvent(DAP &dap) {
- // Prevent races if the process exits while we're being asked to disconnect.
- llvm::call_once(dap.terminated_event_flag, [&] {
- dap.RunTerminateCommands();
- // Send a "terminated" event
- llvm::json::Object event(CreateTerminatedEventObject(dap.target));
- dap.SendJSON(llvm::json::Value(std::move(event)));
- });
-}
-
// Send a thread stopped event for all threads as long as the process
// is stopped.
void SendThreadStoppedEvent(DAP &dap) {
@@ -390,6 +381,7 @@ void SendStdOutStdErr(DAP &dap, lldb::SBProcess &process) {
}
void ProgressEventThreadFunction(DAP &dap) {
+ llvm::set_thread_name(dap.name + ".progress_handler");
lldb::SBListener listener("lldb-dap.progress.listener");
dap.debugger.GetBroadcaster().AddListener(
listener, lldb::SBDebugger::eBroadcastBitProgress |
@@ -424,8 +416,10 @@ void ProgressEventThreadFunction(DAP &dap) {
// them prevent multiple threads from writing simultaneously so no locking
// is required.
void EventThreadFunction(DAP &dap) {
+ llvm::set_thread_name(dap.name + ".event_handler");
lldb::SBEvent event;
lldb::SBListener listener = dap.debugger.GetListener();
+ dap.broadcaster.AddListener(listener, eBroadcastBitStopEventThread);
bool done = false;
while (!done) {
if (listener.WaitForEvent(1, event)) {
@@ -490,7 +484,7 @@ void EventThreadFunction(DAP &dap) {
// launch.json
dap.RunExitCommands();
SendProcessExitedEvent(dap, process);
- SendTerminatedEvent(dap);
+ dap.SendTerminatedEvent();
done = true;
}
break;
@@ -1048,41 +1042,12 @@ void request_disconnect(DAP &dap, const llvm::json::Object &request) {
bool defaultTerminateDebuggee = dap.is_attach ? false : true;
bool terminateDebuggee =
GetBoolean(arguments, "terminateDebuggee", defaultTerminateDebuggee);
- lldb::SBProcess process = dap.target.GetProcess();
- auto state = process.GetState();
- switch (state) {
- case lldb::eStateInvalid:
- case lldb::eStateUnloaded:
- case lldb::eStateDetached:
- case lldb::eStateExited:
- break;
- case lldb::eStateConnected:
- case lldb::eStateAttaching:
- case lldb::eStateLaunching:
- case lldb::eStateStepping:
- case lldb::eStateCrashed:
- case lldb::eStateSuspended:
- case lldb::eStateStopped:
- case lldb::eStateRunning:
- dap.debugger.SetAsync(false);
- lldb::SBError error = terminateDebuggee ? process.Kill() : process.Detach();
- if (!error.Success())
- EmplaceSafeString(response, "error", error.GetCString());
- dap.debugger.SetAsync(true);
- break;
- }
- SendTerminatedEvent(dap);
+
+ lldb::SBError error = dap.Disconnect(terminateDebuggee);
+ if (error.Fail())
+ EmplaceSafeString(response, "error", error.GetCString());
+
dap.SendJSON(llvm::json::Value(std::move(response)));
- if (dap.event_thread.joinable()) {
- dap.broadcaster.BroadcastEventByType(eBroadcastBitStopEventThread);
- dap.event_thread.join();
- }
- if (dap.progress_event_thread.joinable()) {
- dap.broadcaster.BroadcastEventByType(eBroadcastBitStopProgressThread);
- dap.progress_event_thread.join();
- }
- dap.StopIO();
- dap.disconnecting = true;
}
// "ExceptionInfoRequest": {
@@ -5059,37 +5024,6 @@ int main(int argc, char *argv[]) {
pre_init_commands.push_back(arg);
}
- auto RunDAP = [](llvm::StringRef program_path, ReplMode repl_mode,
- std::vector<std::string> pre_init_commands,
- std::ofstream *log, std::string name, StreamDescriptor input,
- StreamDescriptor output, std::FILE *redirectOut = nullptr,
- std::FILE *redirectErr = nullptr) -> bool {
- DAP dap = DAP(name, program_path, log, std::move(input), std::move(output),
- repl_mode, pre_init_commands);
-
- // stdout/stderr redirection to the IDE's console
- if (auto Err = dap.ConfigureIO(redirectOut, redirectErr)) {
- llvm::logAllUnhandledErrors(
- std::move(Err), llvm::errs(),
- "Failed to configure lldb-dap IO operations: ");
- return false;
- }
-
- RegisterRequestCallbacks(dap);
-
- // used only by TestVSCode_redirection_to_console.py
- if (getenv("LLDB_DAP_TEST_STDOUT_STDERR_REDIRECTION") != nullptr)
- redirection_test();
-
- if (auto Err = dap.Loop()) {
- std::string errorMessage = llvm::toString(std::move(Err));
- if (log)
- *log << "Transport Error: " << errorMessage << "\n";
- return false;
- }
- return true;
- };
-
if (!connection.empty()) {
auto maybeProtoclAndName = validateConnection(connection);
if (auto Err = maybeProtoclAndName.takeError()) {
@@ -5103,7 +5037,7 @@ int main(int argc, char *argv[]) {
std::tie(protocol, name) = *maybeProtoclAndName;
Status error;
- std::unique_ptr<Socket> listener = Socket::Create(protocol, error);
+ static std::unique_ptr<Socket> listener = Socket::Create(protocol, error);
if (error.Fail()) {
llvm::logAllUnhandledErrors(error.takeError(), llvm::errs(),
"Failed to create socket listener: ");
@@ -5127,17 +5061,18 @@ int main(int argc, char *argv[]) {
// address.
llvm::outs().flush();
- llvm::DefaultThreadPool pool;
+ static lldb_private::MainLoop g_loop;
+ llvm::sys::SetInterruptFunction([]() {
+ g_loop.AddPendingCallback(
+ [](lldb_private::MainLoopBase &loop) { loop.RequestTermination(); });
+ });
+ std::mutex active_dap_sessions_mutext;
+ std::set<DAP *> active_dap_sessions;
unsigned int clientCount = 0;
- while (true) {
- Socket *accepted_socket;
- error = listener->Accept(/*timeout=*/std::nullopt, accepted_socket);
- if (error.Fail()) {
- llvm::logAllUnhandledErrors(error.takeError(), llvm::errs(),
- "accept failed: ");
- return EXIT_FAILURE;
- }
-
+ auto handle = listener->Accept(g_loop, [=, &active_dap_sessions_mutext,
+ &active_dap_sessions, &clientCount,
+ log = log.get()](
+ std::unique_ptr<Socket> sock) {
std::string name = llvm::formatv("client_{0}", clientCount++).str();
if (log) {
auto now = std::chrono::duration<double>(
@@ -5146,24 +5081,80 @@ int main(int argc, char *argv[]) {
<< " client connected: " << name << "\n";
}
- // Move the client into the connection pool to unblock accepting the next
+ // Move the client into a background thread to unblock accepting the next
// client.
- pool.async(
- [=](Socket *accepted_socket, std::ofstream *log) {
- StreamDescriptor input = StreamDescriptor::from_socket(
- accepted_socket->GetNativeSocket(), false);
- // Close the output last for the best chance at error reporting.
- StreamDescriptor output = StreamDescriptor::from_socket(
- accepted_socket->GetNativeSocket(), true);
- RunDAP(program_path, default_repl_mode, pre_init_commands, log,
- name, std::move(input), std::move(output));
- },
- accepted_socket, log.get());
+ std::thread client([=, &active_dap_sessions_mutext, &active_dap_sessions,
+ sock = std::move(sock)]() {
+ llvm::set_thread_name(name + ".runloop");
+ StreamDescriptor input =
+ StreamDescriptor::from_socket(sock->GetNativeSocket(), false);
+ // Close the output last for the best chance at error reporting.
+ StreamDescriptor output =
+ StreamDescriptor::from_socket(sock->GetNativeSocket(), false);
+ DAP dap = DAP(name, program_path, log, std::move(input),
+ std::move(output), default_repl_mode, pre_init_commands);
+
+ if (auto Err = dap.ConfigureIO()) {
+ llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
+ "Failed to configure stdout redirect: ");
+ return;
+ }
+
+ RegisterRequestCallbacks(dap);
+
+ {
+ std::scoped_lock lock(active_dap_sessions_mutext);
+ active_dap_sessions.insert(&dap);
+ }
+
+ if (auto Err = dap.Loop()) {
+ llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
+ "DAP session error: ");
+ }
+
+ {
+ std::scoped_lock lock(active_dap_sessions_mutext);
+ active_dap_sessions.erase(&dap);
+ }
+
+ if (log) {
+ auto now = std::chrono::duration<double>(
+ std::chrono::system_clock::now().time_since_epoch());
+ *log << llvm::formatv("{0:f9}", now.count()).str()
+ << " client closed: " << name << "\n";
+ }
+ });
+ client.detach();
+ });
+ if (auto Err = handle.takeError()) {
+ llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
+ "Registering accept handler failed: ");
+ return EXIT_FAILURE;
}
- pool.wait();
+ error = g_loop.Run();
+ if (error.Fail()) {
+ llvm::logAllUnhandledErrors(error.takeError(), llvm::errs(),
+ "MainLoop failed: ");
+ return EXIT_FAILURE;
+ }
- return EXIT_SUCCESS;
+ if (log)
+ *log << "lldb-dap server shutdown requested, disconnecting remaining "
+ "clients...\n";
+
+ bool client_failed = false;
+ std::scoped_lock lock(active_dap_sessions_mutext);
+ for (auto *dap : active_dap_sessions) {
+ auto error = dap->Disconnect();
+ if (error.Fail()) {
+ client_failed = true;
+ llvm::errs() << "DAP client " << dap->name
+ << " disconnected failed: " << error.GetCString() << "\n";
+ }
+ }
+
+ return client_failed ? EXIT_FAILURE : EXIT_SUCCESS;
}
#if defined(_WIN32)
@@ -5188,8 +5179,26 @@ int main(int argc, char *argv[]) {
StreamDescriptor input = StreamDescriptor::from_file(fileno(stdin), false);
StreamDescriptor output = StreamDescriptor::from_file(stdout_fd, false);
- bool status = RunDAP(program_path, default_repl_mode, pre_init_commands,
- log.get(), "stdin/stdout", std::move(input),
- std::move(output), stdout, stderr);
- return status ? EXIT_SUCCESS : EXIT_FAILURE;
+ DAP dap = DAP("stdin/stdout", program_path, log.get(), std::move(input),
+ std::move(output), default_repl_mode, pre_init_commands);
+
+ // stdout/stderr redirection to the IDE's console
+ if (auto Err = dap.ConfigureIO(stdout, stderr)) {
+ llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
+ "Failed to configure stdout redirect: ");
+ return EXIT_FAILURE;
+ }
+
+ RegisterRequestCallbacks(dap);
+
+ // used only by TestVSCode_redirection_to_console.py
+ if (getenv("LLDB_DAP_TEST_STDOUT_STDERR_REDIRECTION") != nullptr)
+ redirection_test();
+
+ if (auto Err = dap.Loop()) {
+ llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(),
+ "DAP session error: ");
+ return EXIT_FAILURE;
+ }
+ return EXIT_SUCCESS;
}
>From 784cb25449a9f7dc25be70fe14289a13dd6f0e1d Mon Sep 17 00:00:00 2001
From: John Harrison <harjohn at google.com>
Date: Thu, 6 Feb 2025 13:16:19 -0800
Subject: [PATCH 4/4] Using a std::condition_variable to notify the main thread
after all DAP sessions have disconnected to coordinate shutdowns.
---
lldb/tools/lldb-dap/DAP.cpp | 21 ++++++----
lldb/tools/lldb-dap/IOStream.cpp | 5 +--
lldb/tools/lldb-dap/OutputRedirector.cpp | 3 ++
lldb/tools/lldb-dap/lldb-dap.cpp | 52 ++++++++++++++----------
4 files changed, 48 insertions(+), 33 deletions(-)
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index 4da077e857a4a2f..e7904d9a9c4ac79 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -21,6 +21,7 @@
#include "lldb/lldb-defines.h"
#include "lldb/lldb-enumerations.h"
#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/Error.h"
@@ -224,6 +225,15 @@ llvm::Error DAP::ConfigureIO(std::FILE *overrideOut, std::FILE *overrideErr) {
void DAP::StopIO() {
out.Stop();
err.Stop();
+
+ if (event_thread.joinable()) {
+ broadcaster.BroadcastEventByType(eBroadcastBitStopEventThread);
+ event_thread.join();
+ }
+ if (progress_event_thread.joinable()) {
+ broadcaster.BroadcastEventByType(eBroadcastBitStopProgressThread);
+ progress_event_thread.join();
+ }
}
// Send the JSON in "json_str" to the "out" stream. Correctly send the
@@ -830,23 +840,16 @@ lldb::SBError DAP::Disconnect(bool terminateDebuggee) {
debugger.SetAsync(true);
break;
}
+
SendTerminatedEvent();
- if (event_thread.joinable()) {
- broadcaster.BroadcastEventByType(eBroadcastBitStopEventThread);
- event_thread.join();
- }
- if (progress_event_thread.joinable()) {
- broadcaster.BroadcastEventByType(eBroadcastBitStopProgressThread);
- progress_event_thread.join();
- }
- StopIO();
disconnecting = true;
return error;
}
llvm::Error DAP::Loop() {
+ auto stop_io = llvm::make_scope_exit([this]() { StopIO(); });
while (!disconnecting) {
llvm::json::Object object;
lldb_dap::PacketStatus status = GetNextObject(object);
diff --git a/lldb/tools/lldb-dap/IOStream.cpp b/lldb/tools/lldb-dap/IOStream.cpp
index d2e8ec40b0a7b85..7d0f363440f53d6 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;
diff --git a/lldb/tools/lldb-dap/OutputRedirector.cpp b/lldb/tools/lldb-dap/OutputRedirector.cpp
index 7935e17a653bec3..84ee810f1bfa4cc 100644
--- a/lldb/tools/lldb-dap/OutputRedirector.cpp
+++ b/lldb/tools/lldb-dap/OutputRedirector.cpp
@@ -67,6 +67,9 @@ Error OutputRedirector::RedirectTo(std::function<void(StringRef)> callback) {
continue;
break;
}
+ // Skip the null byte used to trigger a Stop.
+ if (bytes_count == 1 && buffer[0] == '\0')
+ continue;
callback(StringRef(buffer, bytes_count));
}
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index 4b9b5fb4310ac58..8fa0bf007794118 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -48,6 +48,7 @@
#include "llvm/Support/raw_ostream.h"
#include <algorithm>
#include <array>
+#include <condition_variable>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
@@ -5066,12 +5067,13 @@ int main(int argc, char *argv[]) {
g_loop.AddPendingCallback(
[](lldb_private::MainLoopBase &loop) { loop.RequestTermination(); });
});
- std::mutex active_dap_sessions_mutext;
- std::set<DAP *> active_dap_sessions;
+ std::condition_variable dap_sessions_condition;
+ std::mutex dap_sessions_mutex;
+ std::map<Socket *, DAP *> dap_sessions;
unsigned int clientCount = 0;
- auto handle = listener->Accept(g_loop, [=, &active_dap_sessions_mutext,
- &active_dap_sessions, &clientCount,
- log = log.get()](
+ auto handle = listener->Accept(g_loop, [=, &dap_sessions_condition,
+ &dap_sessions_mutex, &dap_sessions,
+ &clientCount, log = log.get()](
std::unique_ptr<Socket> sock) {
std::string name = llvm::formatv("client_{0}", clientCount++).str();
if (log) {
@@ -5083,8 +5085,8 @@ int main(int argc, char *argv[]) {
// Move the client into a background thread to unblock accepting the next
// client.
- std::thread client([=, &active_dap_sessions_mutext, &active_dap_sessions,
- sock = std::move(sock)]() {
+ std::thread client([=, &dap_sessions_condition, &dap_sessions_mutex,
+ &dap_sessions, sock = std::move(sock)]() {
llvm::set_thread_name(name + ".runloop");
StreamDescriptor input =
StreamDescriptor::from_socket(sock->GetNativeSocket(), false);
@@ -5103,8 +5105,8 @@ int main(int argc, char *argv[]) {
RegisterRequestCallbacks(dap);
{
- std::scoped_lock lock(active_dap_sessions_mutext);
- active_dap_sessions.insert(&dap);
+ std::scoped_lock lock(dap_sessions_mutex);
+ dap_sessions[sock.get()] = &dap;
}
if (auto Err = dap.Loop()) {
@@ -5112,17 +5114,16 @@ int main(int argc, char *argv[]) {
"DAP session error: ");
}
- {
- std::scoped_lock lock(active_dap_sessions_mutext);
- active_dap_sessions.erase(&dap);
- }
-
if (log) {
auto now = std::chrono::duration<double>(
std::chrono::system_clock::now().time_since_epoch());
*log << llvm::formatv("{0:f9}", now.count()).str()
<< " client closed: " << name << "\n";
}
+
+ std::unique_lock lock(dap_sessions_mutex);
+ dap_sessions.erase(sock.get());
+ std::notify_all_at_thread_exit(dap_sessions_condition, std::move(lock));
});
client.detach();
});
@@ -5144,16 +5145,25 @@ int main(int argc, char *argv[]) {
"clients...\n";
bool client_failed = false;
- std::scoped_lock lock(active_dap_sessions_mutext);
- for (auto *dap : active_dap_sessions) {
- auto error = dap->Disconnect();
- if (error.Fail()) {
- client_failed = true;
- llvm::errs() << "DAP client " << dap->name
- << " disconnected failed: " << error.GetCString() << "\n";
+ {
+ std::scoped_lock lock(dap_sessions_mutex);
+ for (auto [sock, dap] : dap_sessions) {
+ auto error = dap->Disconnect();
+ if (error.Fail()) {
+ client_failed = true;
+ llvm::errs() << "DAP client " << dap->name
+ << " disconnected failed: " << error.GetCString()
+ << "\n";
+ }
+ // Close the socket to ensure the DAP::Loop read finishes.
+ sock->Close();
}
}
+ // Wait for all clients to finish disconnecting.
+ std::unique_lock lock(dap_sessions_mutex);
+ dap_sessions_condition.wait(lock, [&] { return dap_sessions.empty(); });
+
return client_failed ? EXIT_FAILURE : EXIT_SUCCESS;
}
More information about the lldb-commits
mailing list