[Lldb-commits] [lldb] 1f0638c - [lldb-dap][windows] add integratedTerminal and externalTerminal support (#174635)

via lldb-commits lldb-commits at lists.llvm.org
Mon Mar 30 09:01:45 PDT 2026


Author: Charles Zablit
Date: 2026-03-30T17:01:40+01:00
New Revision: 1f0638cd3e53b394e31adb214a6ab12348cb722e

URL: https://github.com/llvm/llvm-project/commit/1f0638cd3e53b394e31adb214a6ab12348cb722e
DIFF: https://github.com/llvm/llvm-project/commit/1f0638cd3e53b394e31adb214a6ab12348cb722e.diff

LOG: [lldb-dap][windows] add integratedTerminal and externalTerminal support (#174635)

Added: 
    

Modified: 
    lldb/include/lldb/Host/windows/ProcessLauncherWindows.h
    lldb/source/Host/windows/ProcessLauncherWindows.cpp
    lldb/test/API/tools/lldb-dap/launch/TestDAP_launch_stdio_redirection_and_console.py
    lldb/test/API/tools/lldb-dap/restart/TestDAP_restart_console.py
    lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py
    lldb/tools/lldb-dap/Handler/RequestHandler.cpp
    lldb/tools/lldb-dap/JSONUtils.cpp
    lldb/tools/lldb-dap/RunInTerminal.cpp
    lldb/tools/lldb-dap/RunInTerminal.h
    lldb/tools/lldb-dap/tool/Options.td
    lldb/tools/lldb-dap/tool/lldb-dap.cpp

Removed: 
    


################################################################################
diff  --git a/lldb/include/lldb/Host/windows/ProcessLauncherWindows.h b/lldb/include/lldb/Host/windows/ProcessLauncherWindows.h
index 7e1402f9a7d93..876cff12468ef 100644
--- a/lldb/include/lldb/Host/windows/ProcessLauncherWindows.h
+++ b/lldb/include/lldb/Host/windows/ProcessLauncherWindows.h
@@ -81,7 +81,6 @@ class ProcessLauncherWindows : public ProcessLauncher {
   HostProcess LaunchProcess(const ProcessLaunchInfo &launch_info,
                             Status &error) override;
 
-protected:
   /// Get the list of Windows handles that should be inherited by the child
   /// process and update `STARTUPINFOEXW` with the handle list.
   ///

diff  --git a/lldb/source/Host/windows/ProcessLauncherWindows.cpp b/lldb/source/Host/windows/ProcessLauncherWindows.cpp
index fb091eb75d9b8..86acc5e2d3d5e 100644
--- a/lldb/source/Host/windows/ProcessLauncherWindows.cpp
+++ b/lldb/source/Host/windows/ProcessLauncherWindows.cpp
@@ -81,9 +81,7 @@ GetFlattenedWindowsCommandStringW(llvm::ArrayRef<const char *> args) {
   if (args.empty())
     return L"";
 
-  std::vector<llvm::StringRef> args_ref;
-  for (int i = 0; args[i] != nullptr; ++i)
-    args_ref.push_back(args[i]);
+  std::vector<llvm::StringRef> args_ref(args.begin(), args.end());
 
   return llvm::sys::flattenWindowsCommandLine(args_ref);
 }

diff  --git a/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch_stdio_redirection_and_console.py b/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch_stdio_redirection_and_console.py
index bec76fb4ef5e1..5b836e99a48fb 100644
--- a/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch_stdio_redirection_and_console.py
+++ b/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch_stdio_redirection_and_console.py
@@ -19,7 +19,6 @@ class TestDAP_launch_stdio_redirection_and_console(lldbdap_testcase.DAPTestCaseB
     """
 
     @skipIfAsan
-    @skipIfWindows  # https://github.com/llvm/llvm-project/issues/62336
     @skipIf(oslist=["linux"], archs=no_match(["x86_64"]))
     @skipIfBuildType(["debug"])
     def test(self):

diff  --git a/lldb/test/API/tools/lldb-dap/restart/TestDAP_restart_console.py b/lldb/test/API/tools/lldb-dap/restart/TestDAP_restart_console.py
index e98216b2144f2..c80a3cdc10d95 100644
--- a/lldb/test/API/tools/lldb-dap/restart/TestDAP_restart_console.py
+++ b/lldb/test/API/tools/lldb-dap/restart/TestDAP_restart_console.py
@@ -12,7 +12,7 @@
 @skipIfBuildType(["debug"])
 class TestDAP_restart_console(lldbdap_testcase.DAPTestCaseBase):
     @skipIfAsan
-    @skipIfWindows
+    @expectedFailureWindows
     @skipIf(oslist=["linux"], archs=["arm$"])  # Always times out on buildbot
     def test_basic_functionality(self):
         """
@@ -61,7 +61,7 @@ def test_basic_functionality(self):
         self.continue_to_exit()
 
     @skipIfAsan
-    @skipIfWindows
+    @expectedFailureWindows
     @skipIf(oslist=["linux"], archs=["arm$"])  # Always times out on buildbot
     def test_stopOnEntry(self):
         """

diff  --git a/lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py b/lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py
index 0fdc719b6cb76..4e09efadfdcdc 100644
--- a/lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py
+++ b/lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py
@@ -2,6 +2,7 @@
 Test lldb-dap runInTerminal reverse request
 """
 
+from contextlib import contextmanager
 from lldbsuite.test.decorators import *
 from lldbsuite.test.lldbtest import line_number
 import lldbdap_testcase
@@ -10,26 +11,102 @@
 import json
 
 
+ at contextmanager
+def fifo(*args, **kwargs):
+    if sys.platform == "win32":
+        import ctypes
+
+        comm_file = r"\\.\pipe\lldb-dap-run-in-terminal-comm"
+        PIPE_ACCESS_DUPLEX = 0x00000003
+        PIPE_TYPE_MESSAGE = 0x00000004
+        PIPE_READMODE_MESSAGE = 0x00000002
+        PIPE_WAIT = 0x00000000
+        PIPE_UNLIMITED_INSTANCES = 255
+        kernel32 = ctypes.windll.kernel32
+
+        pipe = kernel32.CreateNamedPipeW(
+            comm_file,
+            PIPE_ACCESS_DUPLEX,
+            PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
+            PIPE_UNLIMITED_INSTANCES,
+            4096,
+            4096,
+            0,
+            None,
+        )
+    else:
+        comm_file = os.path.join(kwargs["directory"], "comm-file")
+        pipe = None
+        os.mkfifo(comm_file)
+
+    try:
+        yield comm_file, pipe
+    finally:
+        if pipe is not None:
+            kernel32.DisconnectNamedPipe(pipe)
+            kernel32.CloseHandle(pipe)
+
+
+def read_pipe_message(pipe):
+    import ctypes
+
+    ERROR_MORE_DATA = 234
+    kernel32 = ctypes.windll.kernel32
+    buffer = b""
+    while True:
+        chunk = ctypes.create_string_buffer(4096)
+        bytes_read = ctypes.wintypes.DWORD()
+        success = kernel32.ReadFile(pipe, chunk, 4096, ctypes.byref(bytes_read), None)
+        buffer += chunk.raw[: bytes_read.value]
+        if success:
+            break
+        if ctypes.GetLastError() != ERROR_MORE_DATA:
+            break
+    return buffer.decode()
+
+
 @skipIfBuildType(["debug"])
 class TestDAP_runInTerminal(lldbdap_testcase.DAPTestCaseBase):
     SHARED_BUILD_TESTCASE = False
 
-    def read_pid_message(self, fifo_file):
-        with open(fifo_file, "r") as file:
-            self.assertIn("pid", file.readline())
+    def read_pid_message(self, fifo_file, pipe):
+        if sys.platform == "win32":
+            import ctypes
+
+            ctypes.windll.kernel32.ConnectNamedPipe(pipe, None)
+            self.assertIn("pid", read_pipe_message(pipe))
+        else:
+            with open(fifo_file, "r") as file:
+                self.assertIn("pid", file.readline())
 
     @staticmethod
-    def send_did_attach_message(fifo_file):
-        with open(fifo_file, "w") as file:
-            file.write(json.dumps({"kind": "didAttach"}) + "\n")
+    def send_did_attach_message(fifo_file, pipe=None):
+        message = json.dumps({"kind": "didAttach"}) + "\n"
+        if sys.platform == "win32":
+            import ctypes
+
+            kernel32 = ctypes.windll.kernel32
+            bytes_written = ctypes.wintypes.DWORD()
+            kernel32.ConnectNamedPipe(pipe, None)
+            kernel32.WriteFile(
+                pipe, message.encode(), len(message), ctypes.byref(bytes_written), None
+            )
+        else:
+            with open(fifo_file, "w") as file:
+                file.write(message)
 
     @staticmethod
-    def read_error_message(fifo_file):
-        with open(fifo_file, "r") as file:
-            return file.readline()
+    def read_error_message(fifo_file, pipe=None):
+        if sys.platform == "win32":
+            import ctypes
+
+            ctypes.windll.kernel32.ConnectNamedPipe(pipe, None)
+            return read_pipe_message(pipe)
+        else:
+            with open(fifo_file, "r") as file:
+                return file.readline()
 
     @skipIfAsan
-    @skipIfWindows
     def test_runInTerminal(self):
         """
         Tests the "runInTerminal" reverse request. It makes sure that the IDE can
@@ -77,7 +154,6 @@ def test_runInTerminal(self):
         self.continue_to_exit()
 
     @skipIfAsan
-    @skipIfWindows
     def test_runInTerminalWithObjectEnv(self):
         """
         Tests the "runInTerminal" reverse request. It makes sure that the IDE can
@@ -101,7 +177,6 @@ def test_runInTerminalWithObjectEnv(self):
 
         self.continue_to_exit()
 
-    @skipIfWindows
     def test_runInTerminalInvalidTarget(self):
         self.build_and_create_debug_adapter()
         response = self.launch_and_configurationDone(
@@ -116,7 +191,6 @@ def test_runInTerminalInvalidTarget(self):
             response["body"]["error"]["format"],
         )
 
-    @skipIfWindows
     def test_missingArgInRunInTerminalLauncher(self):
         proc = subprocess.run(
             [self.lldbDAPExec, "--launch-target", "INVALIDPROGRAM"],
@@ -128,94 +202,91 @@ def test_missingArgInRunInTerminalLauncher(self):
             '"--launch-target" requires "--comm-file" to be specified', proc.stderr
         )
 
-    @skipIfWindows
     def test_FakeAttachedRunInTerminalLauncherWithInvalidProgram(self):
-        comm_file = os.path.join(self.getBuildDir(), "comm-file")
-        os.mkfifo(comm_file)
-
-        proc = subprocess.Popen(
-            [
-                self.lldbDAPExec,
-                "--comm-file",
-                comm_file,
-                "--launch-target",
-                "INVALIDPROGRAM",
-            ],
-            universal_newlines=True,
-            stderr=subprocess.PIPE,
-        )
-
-        self.read_pid_message(comm_file)
-        self.send_did_attach_message(comm_file)
-        self.assertIn("No such file or directory", self.read_error_message(comm_file))
+        with fifo(directory=self.getBuildDir()) as (comm_file, pipe):
+            proc = subprocess.Popen(
+                [
+                    self.lldbDAPExec,
+                    "--comm-file",
+                    comm_file,
+                    "--launch-target",
+                    "INVALIDPROGRAM",
+                ],
+                universal_newlines=True,
+                stderr=subprocess.PIPE,
+            )
+            if sys.platform == "win32":
+                _, stderr = proc.communicate()
+                self.assertIn("Failed to launch target process", stderr)
+            else:
+                self.read_pid_message(comm_file, pipe)
+                self.send_did_attach_message(comm_file, pipe)
+                self.assertIn(
+                    "No such file or directory",
+                    self.read_error_message(comm_file, pipe),
+                )
+
+                _, stderr = proc.communicate()
+                self.assertIn("No such file or directory", stderr)
 
-        _, stderr = proc.communicate()
-        self.assertIn("No such file or directory", stderr)
-
-    @skipIfWindows
     def test_FakeAttachedRunInTerminalLauncherWithValidProgram(self):
-        comm_file = os.path.join(self.getBuildDir(), "comm-file")
-        os.mkfifo(comm_file)
+        with fifo(directory=self.getBuildDir()) as (comm_file, pipe):
+            proc = subprocess.Popen(
+                [
+                    self.lldbDAPExec,
+                    "--comm-file",
+                    comm_file,
+                    "--launch-target",
+                    "echo",
+                    "foo",
+                ],
+                universal_newlines=True,
+                stdout=subprocess.PIPE,
+            )
+
+            self.read_pid_message(comm_file, pipe)
+            self.send_did_attach_message(comm_file, pipe)
+
+            stdout, _ = proc.communicate()
 
-        proc = subprocess.Popen(
-            [
-                self.lldbDAPExec,
-                "--comm-file",
-                comm_file,
-                "--launch-target",
-                "echo",
-                "foo",
-            ],
-            universal_newlines=True,
-            stdout=subprocess.PIPE,
-        )
-
-        self.read_pid_message(comm_file)
-        self.send_did_attach_message(comm_file)
-
-        stdout, _ = proc.communicate()
         self.assertIn("foo", stdout)
 
-    @skipIfWindows
     def test_FakeAttachedRunInTerminalLauncherAndCheckEnvironment(self):
-        comm_file = os.path.join(self.getBuildDir(), "comm-file")
-        os.mkfifo(comm_file)
+        with fifo(directory=self.getBuildDir()) as (comm_file, pipe):
+            proc = subprocess.Popen(
+                [self.lldbDAPExec, "--comm-file", comm_file, "--launch-target", "env"],
+                universal_newlines=True,
+                stdout=subprocess.PIPE,
+                env={**os.environ, "FOO": "BAR"},
+            )
 
-        proc = subprocess.Popen(
-            [self.lldbDAPExec, "--comm-file", comm_file, "--launch-target", "env"],
-            universal_newlines=True,
-            stdout=subprocess.PIPE,
-            env={**os.environ, "FOO": "BAR"},
-        )
+            self.read_pid_message(comm_file, pipe)
+            self.send_did_attach_message(comm_file, pipe)
 
-        self.read_pid_message(comm_file)
-        self.send_did_attach_message(comm_file)
+            stdout, _ = proc.communicate()
 
-        stdout, _ = proc.communicate()
         self.assertIn("FOO=BAR", stdout)
 
-    @skipIfWindows
     def test_NonAttachedRunInTerminalLauncher(self):
-        comm_file = os.path.join(self.getBuildDir(), "comm-file")
-        os.mkfifo(comm_file)
-
-        proc = subprocess.Popen(
-            [
-                self.lldbDAPExec,
-                "--comm-file",
-                comm_file,
-                "--launch-target",
-                "echo",
-                "foo",
-            ],
-            universal_newlines=True,
-            stderr=subprocess.PIPE,
-            env={**os.environ, "LLDB_DAP_RIT_TIMEOUT_IN_MS": "1000"},
-        )
-
-        self.read_pid_message(comm_file)
+        with fifo(directory=self.getBuildDir()) as (comm_file, pipe):
+            proc = subprocess.Popen(
+                [
+                    self.lldbDAPExec,
+                    "--comm-file",
+                    comm_file,
+                    "--launch-target",
+                    "echo",
+                    "foo",
+                ],
+                universal_newlines=True,
+                stderr=subprocess.PIPE,
+                env={**os.environ, "LLDB_DAP_RIT_TIMEOUT_IN_MS": "1000"},
+            )
+
+            self.read_pid_message(comm_file, pipe)
+
+            _, stderr = proc.communicate()
 
-        _, stderr = proc.communicate()
         self.assertIn("Timed out trying to get messages from the debug adapter", stderr)
 
     def test_client_missing_runInTerminal_feature(self):

diff  --git a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp
index 103d1f7beb873..8979d9e3a5993 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp
@@ -118,7 +118,8 @@ RunInTerminal(DAP &dap, const protocol::LaunchRequestArguments &arguments) {
       arguments.console == protocol::eConsoleExternalTerminal);
   dap.SendReverseRequest<LogFailureResponseHandler>("runInTerminal",
                                                     std::move(reverse_request));
-
+  // We need to wait for the client to connect to the pipe.
+  comm_file->Connect();
   if (llvm::Expected<lldb::pid_t> pid = comm_channel.GetLauncherPid())
     attach_info.SetProcessID(*pid);
   else

diff  --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp
index 5cc37917f4b2e..21647b314e3cd 100644
--- a/lldb/tools/lldb-dap/JSONUtils.cpp
+++ b/lldb/tools/lldb-dap/JSONUtils.cpp
@@ -393,7 +393,11 @@ llvm::json::Object CreateRunInTerminalReverseRequest(
     std::stringstream ss;
     std::string_view delimiter;
     for (const std::optional<protocol::String> &file : stdio) {
+#ifdef _WIN32
+      ss << std::exchange(delimiter, ";");
+#else
       ss << std::exchange(delimiter, ":");
+#endif
       if (file)
         ss << file->str();
     }

diff  --git a/lldb/tools/lldb-dap/RunInTerminal.cpp b/lldb/tools/lldb-dap/RunInTerminal.cpp
index ccdad350cdef7..28be4a9904942 100644
--- a/lldb/tools/lldb-dap/RunInTerminal.cpp
+++ b/lldb/tools/lldb-dap/RunInTerminal.cpp
@@ -9,7 +9,9 @@
 #include "RunInTerminal.h"
 #include "JSONUtils.h"
 
-#if !defined(_WIN32)
+#ifdef _WIN32
+#include "lldb/Host/windows/windows.h"
+#else
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
@@ -112,7 +114,11 @@ Error RunInTerminalLauncherCommChannel::WaitUntilDebugAdapterAttaches(
 }
 
 Error RunInTerminalLauncherCommChannel::NotifyPid() {
-  return m_io.SendJSON(RunInTerminalMessagePid(getpid()).ToJSON());
+  return NotifyPid(getpid());
+}
+
+Error RunInTerminalLauncherCommChannel::NotifyPid(lldb::pid_t pid) {
+  return m_io.SendJSON(RunInTerminalMessagePid(pid).ToJSON());
 }
 
 void RunInTerminalLauncherCommChannel::NotifyError(StringRef error) {
@@ -163,12 +169,19 @@ std::string RunInTerminalDebugAdapterCommChannel::GetLauncherError() {
 
 Expected<std::shared_ptr<FifoFile>> CreateRunInTerminalCommFile() {
   SmallString<256> comm_file;
+#if _WIN32
+  char pipe_name[MAX_PATH];
+  sprintf(pipe_name, "\\\\.\\pipe\\lldb-dap-run-in-terminal-comm-%d",
+          GetCurrentProcessId());
+  return CreateFifoFile(pipe_name);
+#else
   if (std::error_code EC = sys::fs::getPotentiallyUniqueTempFileName(
           "lldb-dap-run-in-terminal-comm", "", comm_file))
     return createStringError(EC, "Error making unique file name for "
                                  "runInTerminal communication files");
 
   return CreateFifoFile(comm_file.str());
+#endif
 }
 
 } // namespace lldb_dap

diff  --git a/lldb/tools/lldb-dap/RunInTerminal.h b/lldb/tools/lldb-dap/RunInTerminal.h
index f6e922e59a095..0bed2cf2138be 100644
--- a/lldb/tools/lldb-dap/RunInTerminal.h
+++ b/lldb/tools/lldb-dap/RunInTerminal.h
@@ -89,6 +89,8 @@ class RunInTerminalLauncherCommChannel {
   ///     out.
   llvm::Error NotifyPid();
 
+  llvm::Error NotifyPid(lldb::pid_t pid);
+
   /// Notify the debug adapter that there's been an error.
   void NotifyError(llvm::StringRef error);
 

diff  --git a/lldb/tools/lldb-dap/tool/Options.td b/lldb/tools/lldb-dap/tool/Options.td
index f8adef7a19672..048021cd4457f 100644
--- a/lldb/tools/lldb-dap/tool/Options.td
+++ b/lldb/tools/lldb-dap/tool/Options.td
@@ -54,7 +54,8 @@ def debugger_pid: S<"debugger-pid">,
 def stdio: S<"stdio">,
   MetaVarName<"<stdin:stdout:stderr:...>">,
   HelpText<"An array of file paths for redirecting the program's standard IO "
-    "streams. A colon-separated list of entries. Empty value means no "
+    "streams. On Windows, a semicolon-separated list of entries, a "
+    "colon-separated list on other platforms. Empty value means no "
     "redirection.">;
 
 def repl_mode

diff  --git a/lldb/tools/lldb-dap/tool/lldb-dap.cpp b/lldb/tools/lldb-dap/tool/lldb-dap.cpp
index afbc2c7f4c28c..61d42ed49ffa1 100644
--- a/lldb/tools/lldb-dap/tool/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/tool/lldb-dap.cpp
@@ -70,7 +70,10 @@
 #undef GetObject
 #include <io.h>
 typedef int socklen_t;
+#include "lldb/Host/windows/ProcessLauncherWindows.h"
 #include "lldb/Host/windows/PythonPathSetup/PythonPathSetup.h"
+#include "llvm/Support/ConvertUTF.h"
+#include "llvm/Support/Program.h"
 #else
 #include <netinet/in.h>
 #include <sys/socket.h>
@@ -179,6 +182,22 @@ static llvm::Error LaunchClient(const llvm::opt::InputArgList &args) {
   return ClientLauncher::GetLauncher(*client)->Launch(launch_args);
 }
 
+llvm::Error
+notifyError(RunInTerminalLauncherCommChannel &comm_channel, std::string message,
+            std::optional<std::error_code> error_code = std::nullopt) {
+  comm_channel.NotifyError(message);
+
+  std::error_code ec = error_code.value_or(
+#ifdef _WIN32
+      std::error_code(GetLastError(), std::system_category())
+#else
+      llvm::inconvertibleErrorCode()
+#endif
+  );
+
+  return llvm::createStringError(ec, std::move(message));
+}
+
 #if not defined(_WIN32)
 struct FDGroup {
   int GetFlags() const {
@@ -266,15 +285,15 @@ SetupIORedirection(const llvm::SmallVectorImpl<llvm::StringRef> &files) {
 //
 // In case of errors launching the target, a suitable error message will be
 // emitted to the debug adapter.
-static llvm::Error LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg,
-                                             llvm::StringRef comm_file,
-                                             lldb::pid_t debugger_pid,
-                                             llvm::StringRef stdio,
-                                             char *argv[]) {
-#if defined(_WIN32)
-  return llvm::createStringError(
-      "runInTerminal is only supported on POSIX systems");
-#else
+static llvm::Expected<int> LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg,
+                                                     llvm::StringRef comm_file,
+                                                     lldb::pid_t debugger_pid,
+                                                     llvm::StringRef stdio,
+                                                     char *argv[], int argc) {
+  // This env var should be used only for tests.
+  const char *timeout_env_var = getenv("LLDB_DAP_RIT_TIMEOUT_IN_MS");
+  int timeout_in_ms =
+      timeout_env_var != nullptr ? atoi(timeout_env_var) : 20000;
 
   // On Linux with the Yama security module enabled, a process can only attach
   // to its descendants by default. In the runInTerminal case the target
@@ -285,6 +304,118 @@ static llvm::Error LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg,
 #endif
 
   lldb_private::FileSystem::Initialize();
+
+#ifdef _WIN32
+  RunInTerminalLauncherCommChannel comm_channel(comm_file);
+
+  llvm::ArrayRef<const char *> args_arr = llvm::ArrayRef(argv, argc);
+  auto wcommand_line_or_err =
+      lldb_private::GetFlattenedWindowsCommandStringW(args_arr);
+  if (!wcommand_line_or_err)
+    return notifyError(comm_channel, "Failed to process arguments");
+
+  STARTUPINFOEXW startupinfoex = {};
+  startupinfoex.StartupInfo.cb = sizeof(STARTUPINFOEXW);
+  startupinfoex.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
+
+  HANDLE stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
+  HANDLE stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
+  HANDLE stderr_handle = GetStdHandle(STD_ERROR_HANDLE);
+
+  auto attributelist_or_err =
+      lldb_private::ProcThreadAttributeList::Create(startupinfoex);
+  if (!attributelist_or_err) {
+    return notifyError(comm_channel, "Could not open inherited handles",
+                       attributelist_or_err.getError());
+  }
+
+  if (!stdio.empty()) {
+    llvm::SmallVector<llvm::StringRef, 3> files;
+    stdio.split(files, ';');
+    while (files.size() < 3)
+      files.push_back(files.back());
+
+    stdin_handle = lldb_private::ProcessLauncherWindows::GetStdioHandle(
+        files[0], STDIN_FILENO);
+    stdout_handle = lldb_private::ProcessLauncherWindows::GetStdioHandle(
+        files[1], STDOUT_FILENO);
+    stderr_handle = lldb_private::ProcessLauncherWindows::GetStdioHandle(
+        files[2], STDERR_FILENO);
+  }
+
+  llvm::scope_exit close_handles([&] {
+    // Only close the handles we created
+    if (stdio.empty())
+      return;
+    if (stdin_handle)
+      CloseHandle(stdin_handle);
+    if (stdout_handle)
+      CloseHandle(stdout_handle);
+    if (stderr_handle)
+      CloseHandle(stderr_handle);
+  });
+
+  auto inherited_handles_or_err =
+      lldb_private::ProcessLauncherWindows::GetInheritedHandles(
+          startupinfoex, /*launch_info*=*/nullptr, stdout_handle, stderr_handle,
+          stdin_handle);
+
+  if (!inherited_handles_or_err)
+    return notifyError(comm_channel, "Failed to get inherited handles",
+                       inherited_handles_or_err.getError());
+  std::vector<HANDLE> inherited_handles = std::move(*inherited_handles_or_err);
+
+  PROCESS_INFORMATION pi = {};
+
+  // Start the process in a suspended state, while we attach the debugger.
+  BOOL result = CreateProcessW(
+      /*lpApplicationName=*/NULL,
+      /*lpCommandLine=*/wcommand_line_or_err->data(),
+      /*lpProcessAttributes=*/NULL, /*lpThreadAttributes=*/NULL,
+      /*bInheritHandles=*/!inherited_handles.empty(),
+      /*dwCreationFlags=*/CREATE_SUSPENDED, /*lpEnvironment=*/NULL,
+      /*lpCurrentDirectory=*/NULL,
+      /*lpStartupInfo=*/reinterpret_cast<STARTUPINFOW *>(&startupinfoex),
+      /*lpProcessInformation=*/&pi);
+
+  if (!result)
+    return notifyError(comm_channel, "Failed to launch target process");
+
+  auto cleanup_and_return = [&](llvm::Error err) -> llvm::Expected<int> {
+    if (pi.hProcess)
+      TerminateProcess(pi.hProcess, 1);
+    if (pi.hThread)
+      CloseHandle(pi.hThread);
+    if (pi.hProcess)
+      CloseHandle(pi.hProcess);
+    return err;
+  };
+
+  // Notify the pid of the process to debug to the debugger. It will attach to
+  // the newly created process.
+  if (llvm::Error err = comm_channel.NotifyPid(pi.dwProcessId))
+    return cleanup_and_return(std::move(err));
+
+  if (llvm::Error err = comm_channel.WaitUntilDebugAdapterAttaches(
+          std::chrono::milliseconds(timeout_in_ms)))
+    return cleanup_and_return(std::move(err));
+
+  // The debugger attached to the process. We can resume it.
+  if (ResumeThread(pi.hThread) == (DWORD)-1)
+    return cleanup_and_return(
+        notifyError(comm_channel, "Failed to resume the target process"));
+
+  // Wait for child to complete to match POSIX behavior.
+  WaitForSingleObject(pi.hProcess, INFINITE);
+  DWORD code = 0;
+  if (!::GetExitCodeProcess(pi.hProcess, &code))
+    return cleanup_and_return(notifyError(
+        comm_channel, "Failed to get the target's process return code"));
+
+  CloseHandle(pi.hThread);
+  CloseHandle(pi.hProcess);
+  return code;
+#else
   if (!stdio.empty()) {
     constexpr size_t num_of_stdio = 3;
     llvm::SmallVector<llvm::StringRef, num_of_stdio> stdio_files;
@@ -310,10 +441,6 @@ static llvm::Error LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg,
   // We will wait to be attached with a timeout. We don't wait indefinitely
   // using a signal to prevent being paused forever.
 
-  // This env var should be used only for tests.
-  const char *timeout_env_var = getenv("LLDB_DAP_RIT_TIMEOUT_IN_MS");
-  int timeout_in_ms =
-      timeout_env_var != nullptr ? atoi(timeout_env_var) : 20000;
   if (llvm::Error err = comm_channel.WaitUntilDebugAdapterAttaches(
           std::chrono::milliseconds(timeout_in_ms))) {
     return err;
@@ -322,10 +449,7 @@ static llvm::Error LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg,
   const char *target = target_arg.getValue();
   execvp(target, argv);
 
-  std::string error = std::strerror(errno);
-  comm_channel.NotifyError(error);
-  return llvm::createStringError(llvm::inconvertibleErrorCode(),
-                                 std::move(error));
+  return notifyError(comm_channel, std::strerror(errno));
 #endif
 }
 
@@ -613,12 +737,14 @@ int main(int argc, char *argv[]) {
         }
       }
       llvm::StringRef stdio = input_args.getLastArgValue(OPT_stdio);
-      if (llvm::Error err =
-              LaunchRunInTerminalTarget(*target_arg, comm_file->getValue(), pid,
-                                        stdio, argv + target_args_pos)) {
-        llvm::errs() << llvm::toString(std::move(err)) << '\n';
+      auto return_code_or_err = LaunchRunInTerminalTarget(
+          *target_arg, comm_file->getValue(), pid, stdio,
+          argv + target_args_pos, argc - target_args_pos);
+      if (!return_code_or_err) {
+        llvm::errs() << llvm::toString(return_code_or_err.takeError()) << '\n';
         return EXIT_FAILURE;
       }
+      return *return_code_or_err;
     } else {
       llvm::errs() << "\"--launch-target\" requires \"--comm-file\" to be "
                       "specified\n";


        


More information about the lldb-commits mailing list