llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-lldb Author: John Harrison (ashgti) <details> <summary>Changes</summary> This refactors the port listening mode to allocate a new DAP object for each connection, allowing multiple connections to run concurrently. --- Patch is 32.80 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/116392.diff 12 Files Affected: - (modified) lldb/packages/Python/lldbsuite/test/lldbtest.py (+8) - (modified) lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py (+56-19) - (modified) lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py (+27-5) - (added) lldb/test/API/tools/lldb-dap/server/Makefile (+3) - (added) lldb/test/API/tools/lldb-dap/server/TestDAP_server.py (+66) - (added) lldb/test/API/tools/lldb-dap/server/main.c (+6) - (modified) lldb/tools/lldb-dap/DAP.cpp (+5-18) - (modified) lldb/tools/lldb-dap/DAP.h (+27-23) - (modified) lldb/tools/lldb-dap/Options.td (+9) - (modified) lldb/tools/lldb-dap/OutputRedirector.cpp (+4-3) - (modified) lldb/tools/lldb-dap/OutputRedirector.h (+9-3) - (modified) lldb/tools/lldb-dap/lldb-dap.cpp (+286-80) ``````````diff diff --git a/lldb/packages/Python/lldbsuite/test/lldbtest.py b/lldb/packages/Python/lldbsuite/test/lldbtest.py index 8884ef5933ada8..a899854bb5ae14 100644 --- a/lldb/packages/Python/lldbsuite/test/lldbtest.py +++ b/lldb/packages/Python/lldbsuite/test/lldbtest.py @@ -39,6 +39,7 @@ import signal from subprocess import * import sys +import socket import time import traceback @@ -250,6 +251,13 @@ def which(program): return None +def pickrandomport(): + """Returns a random open port.""" + with socket.socket() as sock: + sock.bind(("", 0)) + return sock.getsockname()[1] + + class ValueCheck: def __init__( self, diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py index c29992ce9c7848..e4a53fe0d45907 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py @@ -903,7 +903,7 @@ def request_setBreakpoints(self, file_path, line_array, data=None): "sourceModified": False, } if line_array is not None: - args_dict["lines"] = "%s" % line_array + args_dict["lines"] = line_array breakpoints = [] for i, line in enumerate(line_array): breakpoint_data = None @@ -1154,34 +1154,38 @@ class DebugAdaptorServer(DebugCommunication): def __init__( self, executable=None, + launch=True, port=None, + unix_socket=None, init_commands=[], log_file=None, env=None, ): self.process = None - if executable is not None: - adaptor_env = os.environ.copy() - if env is not None: - adaptor_env.update(env) - - if log_file: - adaptor_env["LLDBDAP_LOG"] = log_file - self.process = subprocess.Popen( - [executable], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - env=adaptor_env, + if launch: + self.process = DebugAdaptorServer.launch( + executable, + port=port, + unix_socket=unix_socket, + log_file=log_file, + env=env, ) - DebugCommunication.__init__( - self, self.process.stdout, self.process.stdin, init_commands, log_file - ) - elif port is not None: + + if port: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("127.0.0.1", port)) DebugCommunication.__init__( - self, s.makefile("r"), s.makefile("w"), init_commands + self, s.makefile("rb"), s.makefile("wb"), init_commands, log_file + ) + elif unix_socket: + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + s.connect(unix_socket) + DebugCommunication.__init__( + self, s.makefile("rb"), s.makefile("wb"), init_commands, log_file + ) + else: + DebugCommunication.__init__( + self, self.process.stdout, self.process.stdin, init_commands, log_file ) def get_pid(self): @@ -1196,6 +1200,39 @@ def terminate(self): self.process.wait() self.process = None + @classmethod + def launch( + cls, executable: str, /, port=None, unix_socket=None, log_file=None, env=None + ) -> subprocess.Popen: + adaptor_env = os.environ.copy() + if env: + adaptor_env.update(env) + + if log_file: + adaptor_env["LLDBDAP_LOG"] = log_file + + args = [executable] + if port: + args.append("--port") + args.append(str(port)) + elif unix_socket: + args.append("--unix-socket") + args.append(unix_socket) + + proc = subprocess.Popen( + args, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=sys.stdout, + env=adaptor_env, + ) + + if port or unix_socket: + # Wait for the server to startup. + time.sleep(0.1) + + return proc + def attach_options_specified(options): if options.pid is not None: diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py index a25466f07fa557..3fcc08e9ff55cb 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py @@ -13,7 +13,7 @@ class DAPTestCaseBase(TestBase): timeoutval = 10 * (10 if ('ASAN_OPTIONS' in os.environ) else 1) NO_DEBUG_INFO_TESTCASE = True - def create_debug_adaptor(self, lldbDAPEnv=None): + def create_debug_adaptor(self, env=None, launch=True, port=None, unix_socket=None): """Create the Visual Studio Code debug adaptor""" self.assertTrue( is_exe(self.lldbDAPExec), "lldb-dap must exist and be executable" @@ -21,14 +21,28 @@ def create_debug_adaptor(self, lldbDAPEnv=None): log_file_path = self.getBuildArtifact("dap.txt") self.dap_server = dap_server.DebugAdaptorServer( executable=self.lldbDAPExec, + launch=launch, + port=port, + unix_socket=unix_socket, init_commands=self.setUpCommands(), log_file=log_file_path, - env=lldbDAPEnv, + env=env, ) - def build_and_create_debug_adaptor(self, lldbDAPEnv=None): + def build_and_create_debug_adaptor( + self, + lldbDAPEnv=None, + lldbDAPLaunch=True, + lldbDAPPort=None, + lldbDAPUnixSocket=None, + ): self.build() - self.create_debug_adaptor(lldbDAPEnv) + self.create_debug_adaptor( + env=lldbDAPEnv, + launch=lldbDAPLaunch, + port=lldbDAPPort, + unix_socket=lldbDAPUnixSocket, + ) def set_source_breakpoints(self, source_path, lines, data=None): """Sets source breakpoints and returns an array of strings containing @@ -475,11 +489,19 @@ def build_and_launch( customThreadFormat=None, launchCommands=None, expectFailure=False, + lldbDAPPort=None, + lldbDAPUnixSocket=None, + lldbDAPLaunch=True, ): """Build the default Makefile target, create the DAP debug adaptor, and launch the process. """ - self.build_and_create_debug_adaptor(lldbDAPEnv) + self.build_and_create_debug_adaptor( + lldbDAPEnv=lldbDAPEnv, + lldbDAPLaunch=lldbDAPLaunch, + lldbDAPPort=lldbDAPPort, + lldbDAPUnixSocket=lldbDAPUnixSocket, + ) self.assertTrue(os.path.exists(program), "executable must exist") return self.launch( diff --git a/lldb/test/API/tools/lldb-dap/server/Makefile b/lldb/test/API/tools/lldb-dap/server/Makefile new file mode 100644 index 00000000000000..10495940055b63 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/server/Makefile @@ -0,0 +1,3 @@ +C_SOURCES := main.c + +include Makefile.rules diff --git a/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py b/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py new file mode 100644 index 00000000000000..46b992a77a4815 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py @@ -0,0 +1,66 @@ +""" +Test lldb-dap server integration. +""" + +import os +import tempfile + +import dap_server +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +import lldbdap_testcase + + +class TestDAP_server(lldbdap_testcase.DAPTestCaseBase): + def do_test_server(self, port=None, unix_socket=None): + log_file_path = self.getBuildArtifact("dap.txt") + server = dap_server.DebugAdaptorServer.launch( + self.lldbDAPExec, port=port, unix_socket=unix_socket, log_file=log_file_path + ) + + def cleanup(): + server.terminate() + server.wait() + + self.addTearDownHook(cleanup) + + self.build() + program = self.getBuildArtifact("a.out") + source = "main.c" + breakpoint_line = line_number(source, "// breakpoint") + + # Initial connection over the port. + self.create_debug_adaptor(launch=False, port=port, unix_socket=unix_socket) + self.launch( + program, + disconnectAutomatically=False, + ) + self.set_source_breakpoints(source, [breakpoint_line]) + self.continue_to_next_stop() + self.continue_to_exit() + output = self.get_stdout() + self.assertEquals(output, "hello world!\r\n") + self.dap_server.request_disconnect() + + # Second connection over the port. + self.create_debug_adaptor(launch=False, port=port, unix_socket=unix_socket) + self.launch(program) + self.set_source_breakpoints(source, [breakpoint_line]) + self.continue_to_next_stop() + self.continue_to_exit() + output = self.get_stdout() + self.assertEquals(output, "hello world!\r\n") + + def test_server_port(self): + """ + Test launching a binary with a lldb-dap in server mode on a specific port. + """ + port = pickrandomport() + self.do_test_server(port=port) + + def test_server_unix_socket(self): + """ + Test launching a binary with a lldb-dap in server mode on a unix socket. + """ + dir = tempfile.gettempdir() + self.do_test_server(unix_socket=dir + "/dap-connection-" + str(os.getpid())) diff --git a/lldb/test/API/tools/lldb-dap/server/main.c b/lldb/test/API/tools/lldb-dap/server/main.c new file mode 100644 index 00000000000000..9a6326f3b57d45 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/server/main.c @@ -0,0 +1,6 @@ +#include <stdio.h> + +int main(int argc, char const *argv[]) { + printf("hello world!\n"); // breakpoint 1 + return 0; +} \ No newline at end of file diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp index 35250d9eef608a..8998036fbedf6b 100644 --- a/lldb/tools/lldb-dap/DAP.cpp +++ b/lldb/tools/lldb-dap/DAP.cpp @@ -32,10 +32,11 @@ using namespace lldb_dap; namespace lldb_dap { -DAP::DAP(llvm::StringRef path, ReplMode repl_mode) +DAP::DAP(llvm::StringRef path, std::shared_ptr<std::ofstream> log, + ReplMode repl_mode, std::vector<std::string> pre_init_commands) : debug_adaptor_path(path), broadcaster("lldb-dap"), - exception_breakpoints(), focus_tid(LLDB_INVALID_THREAD_ID), - stop_at_entry(false), is_attach(false), + log(log), exception_breakpoints(), pre_init_commands(pre_init_commands), + focus_tid(LLDB_INVALID_THREAD_ID), stop_at_entry(false), is_attach(false), enable_auto_variable_summaries(false), enable_synthetic_child_debugging(false), display_extended_backtrace(false), @@ -43,21 +44,7 @@ DAP::DAP(llvm::StringRef path, ReplMode repl_mode) configuration_done_sent(false), waiting_for_run_in_terminal(false), progress_event_reporter( [&](const ProgressEvent &event) { SendJSON(event.ToJSON()); }), - reverse_request_seq(0), repl_mode(repl_mode) { - const char *log_file_path = getenv("LLDBDAP_LOG"); -#if defined(_WIN32) - // Windows opens stdout and stdin in text mode which converts \n to 13,10 - // while the value is just 10 on Darwin/Linux. Setting the file mode to binary - // fixes this. - int result = _setmode(fileno(stdout), _O_BINARY); - assert(result); - result = _setmode(fileno(stdin), _O_BINARY); - UNUSED_IF_ASSERT_DISABLED(result); - assert(result); -#endif - if (log_file_path) - log.reset(new std::ofstream(log_file_path)); -} + reverse_request_seq(0), repl_mode(repl_mode) {} DAP::~DAP() = default; diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h index ae496236f13369..ec70da67edac14 100644 --- a/lldb/tools/lldb-dap/DAP.h +++ b/lldb/tools/lldb-dap/DAP.h @@ -9,36 +9,33 @@ #ifndef LLDB_TOOLS_LLDB_DAP_DAP_H #define LLDB_TOOLS_LLDB_DAP_DAP_H -#include <cstdio> -#include <iosfwd> -#include <map> -#include <optional> -#include <thread> - -#include "llvm/ADT/DenseMap.h" -#include "llvm/ADT/DenseSet.h" -#include "llvm/ADT/StringMap.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/Support/JSON.h" -#include "llvm/Support/Threading.h" -#include "llvm/Support/raw_ostream.h" - +#include "ExceptionBreakpoint.h" +#include "FunctionBreakpoint.h" +#include "IOStream.h" +#include "InstructionBreakpoint.h" +#include "ProgressEvent.h" +#include "SourceBreakpoint.h" #include "lldb/API/SBAttachInfo.h" #include "lldb/API/SBCommandInterpreter.h" #include "lldb/API/SBCommandReturnObject.h" #include "lldb/API/SBDebugger.h" #include "lldb/API/SBEvent.h" +#include "lldb/API/SBFile.h" #include "lldb/API/SBFormat.h" #include "lldb/API/SBLaunchInfo.h" #include "lldb/API/SBTarget.h" #include "lldb/API/SBThread.h" - -#include "ExceptionBreakpoint.h" -#include "FunctionBreakpoint.h" -#include "IOStream.h" -#include "InstructionBreakpoint.h" -#include "ProgressEvent.h" -#include "SourceBreakpoint.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/JSON.h" +#include "llvm/Support/Threading.h" +#include "llvm/Support/raw_ostream.h" +#include <iosfwd> +#include <map> +#include <optional> +#include <thread> #define VARREF_LOCALS (int64_t)1 #define VARREF_GLOBALS (int64_t)2 @@ -140,13 +137,16 @@ struct DAP { llvm::StringRef debug_adaptor_path; InputStream input; OutputStream output; + lldb::SBFile in; + lldb::SBFile out; + lldb::SBFile err; lldb::SBDebugger debugger; lldb::SBTarget target; Variables variables; lldb::SBBroadcaster broadcaster; std::thread event_thread; std::thread progress_event_thread; - std::unique_ptr<std::ofstream> log; + std::shared_ptr<std::ofstream> log; llvm::StringMap<SourceBreakpointMap> source_breakpoints; FunctionBreakpointMap function_breakpoints; InstructionBreakpointMap instruction_breakpoints; @@ -198,10 +198,14 @@ struct DAP { // will contain that expression. std::string last_nonempty_var_expression; - DAP(llvm::StringRef path, ReplMode repl_mode); + DAP(llvm::StringRef path, std::shared_ptr<std::ofstream> log, + ReplMode repl_mode, std::vector<std::string> pre_init_commands); ~DAP(); + + DAP() = delete; DAP(const DAP &rhs) = delete; void operator=(const DAP &rhs) = delete; + ExceptionBreakpoint *GetExceptionBreakpoint(const std::string &filter); ExceptionBreakpoint *GetExceptionBreakpoint(const lldb::break_id_t bp_id); diff --git a/lldb/tools/lldb-dap/Options.td b/lldb/tools/lldb-dap/Options.td index d7b4a065abec01..dfca79b2884ac8 100644 --- a/lldb/tools/lldb-dap/Options.td +++ b/lldb/tools/lldb-dap/Options.td @@ -22,8 +22,17 @@ def port: S<"port">, HelpText<"Communicate with the lldb-dap tool over the defined port.">; def: Separate<["-"], "p">, Alias<port>, + MetaVarName<"<port>">, HelpText<"Alias for --port">; +def unix_socket: S<"unix-socket">, + MetaVarName<"<path>">, + HelpText<"Communicate with the lldb-dap tool over the unix socket or named pipe.">; +def: Separate<["-"], "u">, + Alias<unix_socket>, + MetaVarName<"<path>">, + HelpText<"Alias for --unix_socket">; + def launch_target: S<"launch-target">, MetaVarName<"<target>">, HelpText<"Launch a target for the launchInTerminal request. Any argument " diff --git a/lldb/tools/lldb-dap/OutputRedirector.cpp b/lldb/tools/lldb-dap/OutputRedirector.cpp index 2c2f49569869b4..0b725e1901b9fd 100644 --- a/lldb/tools/lldb-dap/OutputRedirector.cpp +++ b/lldb/tools/lldb-dap/OutputRedirector.cpp @@ -21,7 +21,8 @@ using namespace llvm; namespace lldb_dap { -Error RedirectFd(int fd, std::function<void(llvm::StringRef)> callback) { +Expected<int> RedirectFd(int fd, + std::function<void(llvm::StringRef)> callback) { int new_fd[2]; #if defined(_WIN32) if (_pipe(new_fd, 4096, O_TEXT) == -1) { @@ -34,7 +35,7 @@ Error RedirectFd(int fd, std::function<void(llvm::StringRef)> callback) { strerror(error)); } - if (dup2(new_fd[1], fd) == -1) { + if (fd != -1 && dup2(new_fd[1], fd) == -1) { int error = errno; return createStringError(inconvertibleErrorCode(), "Couldn't override the fd %d. %s", fd, @@ -57,7 +58,7 @@ Error RedirectFd(int fd, std::function<void(llvm::StringRef)> callback) { } }); t.detach(); - return Error::success(); + return new_fd[1]; } } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/OutputRedirector.h b/lldb/tools/lldb-dap/OutputRedirector.h index e26d1648b104f9..418b8bac102c7f 100644 --- a/lldb/tools/lldb-dap/OutputRedirector.h +++ b/lldb/tools/lldb-dap/OutputRedirector.h @@ -16,10 +16,16 @@ namespace lldb_dap { /// Redirects the output of a given file descriptor to a callback. /// +/// \param[in] fd +/// Either -1 or the fd duplicate into the new handle. +/// +/// \param[in] callback +/// A callback invoked each time the file is written. +/// /// \return -/// \a Error::success if the redirection was set up correctly, or an error -/// otherwise. -llvm::Error RedirectFd(int fd, std::function<void(llvm::StringRef)> callback); +/// A new file handle for the output. +llvm::Expected<int> RedirectFd(int fd, + std::function<void(llvm::StringRef)> callback); } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp index 3bfc578806021e..51197587ffccec 100644 --- a/lldb/tools/lldb-dap/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/lldb-dap.cpp @@ -14,6 +14,7 @@ #include "RunInTerminal.h" #include "Watchpoint.h" #include "lldb/API/SBDeclaration.h" +#include "lldb/API/SBFile.h" #include "lldb/API/SBInstruction.h" #include "lldb/API/SBListener.h" #include "lldb/API/SBMemoryRegionInfo.h" @@ -66,6 +67,7 @@ #else #include <netinet/in.h> #include <sys/socket.h> +#include <sys/un.h> #include <unistd.h> #endif @@ -116,6 +118,8 @@ enum LaunchMethod { Launch, Attach, AttachForSuspendedLaunch }; /// Page size used for reporting addtional frames in the 'stackTrace' request. constexpr int StackPageSize = 20; +void RegisterRequestCallbacks(DAP &dap); + /// Prints a welcome message on the editor if the preprocessor variable /// LLDB_DAP_WELCOME_MESSAGE is defined. static void PrintWelcomeMessage(DAP &dap) { @@ -137,42 +141,232 @@ lldb::SBValueList *GetTopLevelScope(DAP &dap, int64_t variablesReference) { } } -SOCKET AcceptConnection(DAP &dap, int portno) { - // Accept a socket connection from any host on "portno". - SOCKET newsockfd = -1; - struct sockaddr_in serv_addr, cli_addr; +/// Redirect stdout and stderr fo the IDE's console output. +/// +/// Errors in this operation will be printed to the log file and the IDE's +/// console output as well. +/// +/// \return +/// A fd pointing to the original stdout. +void SetupRedirection(DAP &dap, int stdoutfd = -1, int stderrfd = -1) { + auto output_callback_stderr = [&dap](llvm::StringRef data) { + dap.SendOutput(OutputType::Stderr, data); + }; + auto output_callback_stdout = [&dap](llvm::StringRef data) { + dap.SendOutput(OutputType::Stdout, data); + }; + + llvm::Expected<int> new_stdout_fd = + RedirectFd(stdoutfd, output_callback_stdout); + if (auto err = new_stdout_fd.takeError()) { + std::string error_message = llvm::toString(std::move(err)); + if (dap.log) + *dap.log << error_message << std::endl; + output_callback_stderr(error_message); + } + dap.out = lldb::SBFile(new_stdout_fd.get(), "w", false); + + llvm::Expected<int> new_stderr_fd = + RedirectFd(stderrfd, output_callback_stderr); + if (auto err = new_stderr_fd.takeError()) { + std::string error_message = llvm::toString(std::move(err)); + if (dap.log) + *dap.log << error_message << std::endl; + output_callback_stderr(error_message); + } + dap.err = lldb::SBFile(new_stderr_fd.get(), "w", false); +} + +void HandleClien... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/116392 _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits