[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