[Lldb-commits] [lldb] [lldb-dap] separate the --stdio flag in 3 separate flags (PR #176778)
Charles Zablit via lldb-commits
lldb-commits at lists.llvm.org
Mon Jan 19 09:24:37 PST 2026
https://github.com/charles-zablit updated https://github.com/llvm/llvm-project/pull/176778
>From 0455d9aaaea6c9057d44244f05e1973d8fef8f61 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Mon, 19 Jan 2026 17:20:42 +0000
Subject: [PATCH 1/2] [lldb-dap] separate the --stdio flag in 3 separate flags
---
.../tools/lldb-dap/Handler/RequestHandler.cpp | 44 ++++------
lldb/tools/lldb-dap/JSONUtils.cpp | 26 +++---
lldb/tools/lldb-dap/JSONUtils.h | 15 +++-
.../lldb-dap/Protocol/ProtocolRequests.cpp | 17 ++--
.../lldb-dap/Protocol/ProtocolRequests.h | 10 ++-
lldb/tools/lldb-dap/README.md | 7 +-
lldb/tools/lldb-dap/tool/Options.td | 19 ++--
lldb/tools/lldb-dap/tool/lldb-dap.cpp | 88 ++++++++-----------
8 files changed, 114 insertions(+), 112 deletions(-)
diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp
index 5cb0055f034da..9d85ea765fd80 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp
@@ -60,31 +60,16 @@ static uint32_t SetLaunchFlag(uint32_t flags, bool flag,
return flags;
}
-static void
-SetupIORedirection(const std::vector<std::optional<std::string>> &stdio,
- lldb::SBLaunchInfo &launch_info) {
- for (const auto &[idx, value_opt] : llvm::enumerate(stdio)) {
- if (!value_opt)
- continue;
- const std::string &path = value_opt.value();
- assert(!path.empty() && "paths should not be empty");
-
- const int fd = static_cast<int>(idx);
- switch (fd) {
- case 0:
- launch_info.AddOpenFileAction(STDIN_FILENO, path.c_str(), true, false);
- break;
- case 1:
- launch_info.AddOpenFileAction(STDOUT_FILENO, path.c_str(), false, true);
- break;
- case 2:
- launch_info.AddOpenFileAction(STDERR_FILENO, path.c_str(), false, true);
- break;
- default:
- launch_info.AddOpenFileAction(fd, path.c_str(), true, true);
- break;
- }
- }
+static void SetupIORedirection(const std::optional<std::string> &stdin_path,
+ const std::optional<std::string> &stdout_path,
+ const std::optional<std::string> &stderr_path,
+ lldb::SBLaunchInfo &launch_info) {
+ if (stdin_path)
+ launch_info.AddOpenFileAction(0, stdin_path->c_str(), true, false);
+ if (stdout_path)
+ launch_info.AddOpenFileAction(1, stdout_path->c_str(), false, true);
+ if (stderr_path)
+ launch_info.AddOpenFileAction(2, stderr_path->c_str(), false, true);
}
static llvm::Error
@@ -116,7 +101,8 @@ RunInTerminal(DAP &dap, const protocol::LaunchRequestArguments &arguments) {
llvm::json::Object reverse_request = CreateRunInTerminalReverseRequest(
arguments.configuration.program, arguments.args, arguments.env,
- arguments.cwd, comm_file.m_path, debugger_pid, arguments.stdio,
+ arguments.cwd, comm_file.m_path, debugger_pid, arguments.stdin_path,
+ arguments.stdout_path, arguments.stderr_path,
arguments.console == protocol::eConsoleExternalTerminal);
dap.SendReverseRequest<LogFailureResponseHandler>("runInTerminal",
std::move(reverse_request));
@@ -214,8 +200,10 @@ llvm::Error BaseRequestHandler::LaunchProcess(
launch_info.SetEnvironment(env, true);
}
- if (!arguments.stdio.empty() && !arguments.disableSTDIO)
- SetupIORedirection(arguments.stdio, launch_info);
+ if ((arguments.stdin_path || arguments.stdout_path || arguments.stdin_path) &&
+ !arguments.disableSTDIO)
+ SetupIORedirection(arguments.stdin_path, arguments.stdout_path,
+ arguments.stderr_path, launch_info);
launch_info.SetDetachOnError(arguments.detachOnError);
launch_info.SetShellExpandArguments(arguments.shellExpandArguments);
diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp
index 5c33c6aa591a6..382d4dbcaf7c2 100644
--- a/lldb/tools/lldb-dap/JSONUtils.cpp
+++ b/lldb/tools/lldb-dap/JSONUtils.cpp
@@ -707,7 +707,9 @@ llvm::json::Object CreateRunInTerminalReverseRequest(
llvm::StringRef program, const std::vector<std::string> &args,
const llvm::StringMap<std::string> &env, llvm::StringRef cwd,
llvm::StringRef comm_file, lldb::pid_t debugger_pid,
- const std::vector<std::optional<std::string>> &stdio, bool external) {
+ const std::optional<std::string> &stdin_path,
+ const std::optional<std::string> &stdout_path,
+ const std::optional<std::string> &stderr_path, bool external) {
llvm::json::Object run_in_terminal_args;
if (external) {
// This indicates the IDE to open an external terminal window.
@@ -725,17 +727,17 @@ llvm::json::Object CreateRunInTerminalReverseRequest(
req_args.push_back(std::to_string(debugger_pid));
}
- if (!stdio.empty()) {
- req_args.emplace_back("--stdio");
-
- std::stringstream ss;
- std::string_view delimiter;
- for (const std::optional<std::string> &file : stdio) {
- ss << std::exchange(delimiter, ":");
- if (file)
- ss << *file;
- }
- req_args.push_back(ss.str());
+ if (stdin_path) {
+ req_args.push_back("--stdin-path");
+ req_args.push_back(*stdin_path);
+ }
+ if (stdout_path) {
+ req_args.push_back("--stdout-path");
+ req_args.push_back(*stdout_path);
+ }
+ if (stderr_path) {
+ req_args.push_back("--stderr-path");
+ req_args.push_back(*stderr_path);
}
// WARNING: Any argument added after `launch-target` is passed to to the
diff --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h
index 15449d6ece62a..0c2f51b776691 100644
--- a/lldb/tools/lldb-dap/JSONUtils.h
+++ b/lldb/tools/lldb-dap/JSONUtils.h
@@ -340,9 +340,14 @@ std::pair<int64_t, bool> UnpackLocation(int64_t location_id);
/// launcher uses it on Linux tell the kernel that it should allow the
/// debugger process to attach.
///
-/// \param[in] stdio
-/// An array of file paths for redirecting the program's standard IO
-/// streams.
+/// \param[in] stdin_path
+/// A file path for redirecting the program's stdout stream.
+///
+/// \param[in] stdout_path
+/// A file path for redirecting the program's stdin stream.
+///
+/// \param[in] stderr_path
+/// A file path for redirecting the program's stderr stream.
///
/// \param[in] external
/// If set to true, the program will run in an external terminal window
@@ -355,7 +360,9 @@ llvm::json::Object CreateRunInTerminalReverseRequest(
llvm::StringRef program, const std::vector<std::string> &args,
const llvm::StringMap<std::string> &env, llvm::StringRef cwd,
llvm::StringRef comm_file, lldb::pid_t debugger_pid,
- const std::vector<std::optional<std::string>> &stdio, bool external);
+ const std::optional<std::string> &stdin_path,
+ const std::optional<std::string> &stdout_path,
+ const std::optional<std::string> &stderr_path, bool external);
/// Create a "Terminated" JSON object that contains statistics
///
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
index a72802a33fc9c..227d628aead56 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
@@ -307,15 +307,20 @@ bool fromJSON(const json::Value &Params, LaunchRequestArguments &LRA,
O.mapOptional("shellExpandArguments", LRA.shellExpandArguments) &&
O.mapOptional("runInTerminal", LRA.console) &&
O.mapOptional("console", LRA.console) &&
- O.mapOptional("stdio", LRA.stdio) && parseEnv(Params, LRA.env, P);
+ O.mapOptional("stdin_path", LRA.stdin_path) &&
+ O.mapOptional("stdout_path", LRA.stdout_path) &&
+ O.mapOptional("stderr_path", LRA.stderr_path) &&
+ parseEnv(Params, LRA.env, P);
if (!success)
return false;
- for (std::optional<std::string> &io_path : LRA.stdio) {
- // set empty paths to null.
- if (io_path && llvm::StringRef(*io_path).trim().empty())
- io_path.reset();
- }
+ // set empty paths to null.
+ if (LRA.stdin_path && llvm::StringRef(*LRA.stdin_path).trim().empty())
+ LRA.stdin_path.reset();
+ if (LRA.stdout_path && llvm::StringRef(*LRA.stdout_path).trim().empty())
+ LRA.stdout_path.reset();
+ if (LRA.stderr_path && llvm::StringRef(*LRA.stderr_path).trim().empty())
+ LRA.stderr_path.reset();
// Validate that we have a well formed launch request.
if (!LRA.launchCommands.empty() &&
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
index 28c9f48200e0c..a0e3d43144e1d 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
@@ -301,8 +301,14 @@ struct LaunchRequestArguments {
/// terminal or external terminal.
Console console = eConsoleInternal;
- /// An array of file paths for redirecting the program's standard IO streams.
- std::vector<std::optional<std::string>> stdio;
+ /// A file path for redirecting the program's stdin stream.
+ std::optional<std::string> stdin_path;
+
+ /// A file path for redirecting the program's stdout stream.
+ std::optional<std::string> stdout_path;
+
+ /// A file path for redirecting the program's stderr stream.
+ std::optional<std::string> stderr_path;
/// @}
};
diff --git a/lldb/tools/lldb-dap/README.md b/lldb/tools/lldb-dap/README.md
index 8c7066a3b551b..002e367a8bd7c 100644
--- a/lldb/tools/lldb-dap/README.md
+++ b/lldb/tools/lldb-dap/README.md
@@ -68,7 +68,8 @@ This will launch process and connect `stdin` to `in.txt`, both of `stdout` and `
"request": "launch",
"name": "Debug",
"program": "/tmp/a.out",
- "stdio": ["in.txt", "out.txt"]
+ "stdin-path": "in.txt",
+ "stdout-path": "out.txt"
}
```
@@ -265,7 +266,9 @@ contain the following key/value pairs:
| **stopOnEntry** | boolean | | Whether to stop program immediately after launching.
| **runInTerminal** (deprecated) | boolean | | Launch the program inside an integrated terminal in the IDE. Useful for debugging interactive command line programs.
| **console** | string | | Specify where to launch the program: internal console (`internalConsole`), integrated terminal (`integratedTerminal`) or external terminal (`externalTerminal`). Supported from lldb-dap 21.0 version.
-| **stdio** | [string] | | Redirects the debuggee's standard I/O (stdin, stdout, stderr) to a file path, named pipe, or TTY device.<br/>Use null to redirect a stream to the default debug terminal.<br/>The first three values map to stdin, stdout, and stderr respectively, Additional values create extra file descriptors (4, 5, etc.).<br/><br/>Example: `stdio: [null, "./output_file"]`, the debuggee uses the default terminal for `stdin` and `stderr`, but writes `stdout` to `./output_file`.<br/>Added in version 22.0.
+| **stdin-path** | [string] | | Redirects the debuggee's standard input (stdin) to a file path, named pipe, or TTY device.<br/>Use null to redirect the stream to the default debug terminal.
+| **stdout-path** | [string] | | Redirects the debuggee's standard output (stdout) to a file path, named pipe, or TTY device.<br/>Use null to redirect the stream to the default debug terminal.
+| **stderr-path** | [string] | | Redirects the debuggee's standard error (stderr) to a file path, named pipe, or TTY device.<br/>Use null to redirect the stream to the default debug terminal.
| **launchCommands** | [string] | | LLDB commands executed to launch the program.
For JSON configurations of `"type": "attach"`, the JSON configuration can contain
diff --git a/lldb/tools/lldb-dap/tool/Options.td b/lldb/tools/lldb-dap/tool/Options.td
index 339a64fed6c32..2dbcab8732d83 100644
--- a/lldb/tools/lldb-dap/tool/Options.td
+++ b/lldb/tools/lldb-dap/tool/Options.td
@@ -47,11 +47,20 @@ def debugger_pid: S<"debugger-pid">,
HelpText<"The PID of the lldb-dap instance that sent the launchInTerminal "
"request when using --launch-target.">;
-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 "
- "redirection.">;
+def stdin_path: S<"stdin-path">,
+ MetaVarName<"<stdin>">,
+ HelpText<"A file path for redirecting the program's stdin stream. Empty "
+ "value means no redirection.">;
+
+def stdout_path: S<"stdout-path">,
+ MetaVarName<"<stdout>">,
+ HelpText<"A file path for redirecting the program's stdout stream. Empty "
+ "value means no redirection.">;
+
+def stderr_path: S<"stderr-path">,
+ MetaVarName<"<stderr>">,
+ HelpText<"A file path for redirecting the program's stderr stream. Empty "
+ "value means no redirection.">;
def repl_mode
: S<"repl-mode">,
diff --git a/lldb/tools/lldb-dap/tool/lldb-dap.cpp b/lldb/tools/lldb-dap/tool/lldb-dap.cpp
index 15c63543e86f5..3e9a1a69eeaba 100644
--- a/lldb/tools/lldb-dap/tool/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/tool/lldb-dap.cpp
@@ -194,54 +194,34 @@ struct FDGroup {
bool write = false;
};
-static llvm::Error RedirectToFile(const FDGroup &fdg, llvm::StringRef file) {
- if (!fdg.read && !fdg.write)
- return llvm::Error::success();
- int target_fd = lldb_private::FileSystem::Instance().Open(
- file.str().c_str(), fdg.GetFlags(), 0666);
- if (target_fd == -1)
- return llvm::errorCodeToError(
- std::error_code(errno, std::generic_category()));
- for (int fd : fdg.fds) {
- if (target_fd == fd)
- continue;
- if (::dup2(target_fd, fd) == -1)
+static llvm::Error SetupIORedirection(llvm::StringRef stdin_path,
+ llvm::StringRef stdout_path,
+ llvm::StringRef stderr_path) {
+ if (!stdin_path.empty()) {
+ int target_fd = lldb_private::FileSystem::Instance().Open(
+ stdin_path.str().c_str(), O_NOCTTY | O_RDONLY, 0666);
+ if (target_fd == -1)
return llvm::errorCodeToError(
std::error_code(errno, std::generic_category()));
+ ::close(target_fd);
}
- ::close(target_fd);
- return llvm::Error::success();
-}
-
-static llvm::Error
-SetupIORedirection(const llvm::SmallVectorImpl<llvm::StringRef> &files) {
- llvm::SmallDenseMap<llvm::StringRef, FDGroup> groups;
- for (size_t i = 0; i < files.size(); i++) {
- if (files[i].empty())
- continue;
- auto group = groups.find(files[i]);
- if (group == groups.end())
- group = groups.insert({files[i], {{static_cast<int>(i)}}}).first;
- else
- group->second.fds.push_back(i);
- switch (i) {
- case 0:
- group->second.read = true;
- break;
- case 1:
- case 2:
- group->second.write = true;
- break;
- default:
- group->second.read = true;
- group->second.write = true;
- break;
- }
+ if (!stdout_path.empty()) {
+ int target_fd = lldb_private::FileSystem::Instance().Open(
+ stdout_path.str().c_str(), O_NOCTTY | O_CREAT | O_WRONLY | O_TRUNC,
+ 0666);
+ if (target_fd == -1)
+ return llvm::errorCodeToError(
+ std::error_code(errno, std::generic_category()));
+ ::close(target_fd);
}
- for (const auto &[file, group] : groups) {
- if (llvm::Error err = RedirectToFile(group, file))
- return llvm::createStringError(
- llvm::formatv("{0}: {1}", file, llvm::toString(std::move(err))));
+ if (!stderr_path.empty()) {
+ int target_fd = lldb_private::FileSystem::Instance().Open(
+ stderr_path.str().c_str(), O_NOCTTY | O_CREAT | O_WRONLY | O_TRUNC,
+ 0666);
+ if (target_fd == -1)
+ return llvm::errorCodeToError(
+ std::error_code(errno, std::generic_category()));
+ ::close(target_fd);
}
return llvm::Error::success();
}
@@ -266,11 +246,11 @@ 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[]) {
+static llvm::Error
+LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg, llvm::StringRef comm_file,
+ lldb::pid_t debugger_pid, llvm::StringRef stdin_path,
+ llvm::StringRef stdout_path,
+ llvm::StringRef stderr_path, char *argv[]) {
#if defined(_WIN32)
return llvm::createStringError(
"runInTerminal is only supported on POSIX systems");
@@ -588,10 +568,12 @@ int main(int argc, char *argv[]) {
break;
}
}
- 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::StringRef stdin_path = input_args.getLastArgValue(OPT_stdin_path);
+ llvm::StringRef stdout_path = input_args.getLastArgValue(OPT_stdout_path);
+ llvm::StringRef stderr_path = input_args.getLastArgValue(OPT_stderr_path);
+ if (llvm::Error err = LaunchRunInTerminalTarget(
+ *target_arg, comm_file->getValue(), pid, stdin_path, stdout_path,
+ stderr_path, argv + target_args_pos)) {
llvm::errs() << llvm::toString(std::move(err)) << '\n';
return EXIT_FAILURE;
}
>From 062ad0a22a5c32389509ac6be9a90ca502f672c9 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Mon, 19 Jan 2026 17:24:26 +0000
Subject: [PATCH 2/2] remove leftover --stdio reference
---
lldb/tools/lldb-dap/tool/lldb-dap.cpp | 9 +++------
1 file changed, 3 insertions(+), 6 deletions(-)
diff --git a/lldb/tools/lldb-dap/tool/lldb-dap.cpp b/lldb/tools/lldb-dap/tool/lldb-dap.cpp
index 3e9a1a69eeaba..8feaa0f187fe4 100644
--- a/lldb/tools/lldb-dap/tool/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/tool/lldb-dap.cpp
@@ -265,12 +265,9 @@ LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg, llvm::StringRef comm_file,
#endif
lldb_private::FileSystem::Initialize();
- if (!stdio.empty()) {
- constexpr size_t num_of_stdio = 3;
- llvm::SmallVector<llvm::StringRef, num_of_stdio> stdio_files;
- stdio.split(stdio_files, ':');
- stdio_files.resize(std::max(num_of_stdio, stdio_files.size()));
- if (llvm::Error err = SetupIORedirection(stdio_files))
+ if (!stdin_path.empty() || !stdout_path.empty() || !stderr_path.empty()) {
+ if (llvm::Error err =
+ SetupIORedirection(stdin_path, stdout_path, stderr_path))
return err;
} else if ((isatty(STDIN_FILENO) != 0) &&
llvm::StringRef(getenv("TERM")).starts_with_insensitive("xterm")) {
More information about the lldb-commits
mailing list