[Lldb-commits] [lldb] [lldb-dap] Add stdio redirection for integrated and external terminals (PR #161089)
Sergei Druzhkov via lldb-commits
lldb-commits at lists.llvm.org
Wed Oct 1 14:01:30 PDT 2025
https://github.com/DrSergei updated https://github.com/llvm/llvm-project/pull/161089
>From 0fed265f9bb48efbf07d2d11b32737ca0efbc2af Mon Sep 17 00:00:00 2001
From: Druzhkov Sergei <serzhdruzhok at gmail.com>
Date: Sun, 28 Sep 2025 12:45:01 +0300
Subject: [PATCH 1/2] [lldb-dap] Add stdio redirection for integrated and
external terminals
---
.../tools/lldb-dap/launch/TestDAP_launch.py | 22 +++++-
.../tools/lldb-dap/Handler/RequestHandler.cpp | 4 +-
lldb/tools/lldb-dap/JSONUtils.cpp | 15 ++++-
lldb/tools/lldb-dap/JSONUtils.h | 7 +-
lldb/tools/lldb-dap/Options.td | 6 ++
.../lldb-dap/Protocol/ProtocolRequests.h | 1 +
lldb/tools/lldb-dap/package.json | 5 +-
lldb/tools/lldb-dap/tool/lldb-dap.cpp | 67 ++++++++++++++++++-
8 files changed, 120 insertions(+), 7 deletions(-)
diff --git a/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py b/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py
index ceef95dfcd0d5..8db2316e73fc8 100644
--- a/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py
+++ b/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py
@@ -632,7 +632,27 @@ def test_stdio_redirection(self):
program = self.getBuildArtifact("a.out")
with tempfile.NamedTemporaryFile("rt") as f:
- self.launch(program, stdio=[None, f.name, None])
+ self.launch(program, stdio=[None, f.name])
+ self.continue_to_exit()
+ lines = f.readlines()
+ self.assertIn(
+ program, lines[0], "make sure program path is in first argument"
+ )
+
+ @skipIfAsan
+ @skipIfWindows
+ @skipIf(oslist=["linux"], archs=no_match(["x86_64"]))
+ def test_stdio_redirection_and_console(self):
+ """
+ Test stdio redirection and console.
+ """
+ self.build_and_create_debug_adapter()
+ program = self.getBuildArtifact("a.out")
+
+ with tempfile.NamedTemporaryFile("rt") as f:
+ self.launch(
+ program, console="integratedTerminal", stdio=[None, f.name, None]
+ )
self.continue_to_exit()
lines = f.readlines()
self.assertIn(
diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp
index 773891353db6a..e7d9b89653f1c 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp
@@ -57,7 +57,7 @@ SetupIORedirection(const std::vector<std::optional<std::string>> &stdio,
size_t n = std::max(stdio.size(), static_cast<size_t>(3));
for (size_t i = 0; i < n; i++) {
std::optional<std::string> path;
- if (stdio.size() < i)
+ if (stdio.size() <= i)
path = stdio.back();
else
path = stdio[i];
@@ -107,7 +107,7 @@ 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.cwd, comm_file.m_path, debugger_pid, arguments.stdio,
arguments.console == protocol::eConsoleExternalTerminal);
dap.SendReverseRequest<LogFailureResponseHandler>("runInTerminal",
std::move(reverse_request));
diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp
index 4f26599a49bac..9ecb3a36543ce 100644
--- a/lldb/tools/lldb-dap/JSONUtils.cpp
+++ b/lldb/tools/lldb-dap/JSONUtils.cpp
@@ -866,7 +866,8 @@ llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit &unit) {
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, bool external) {
+ llvm::StringRef comm_file, lldb::pid_t debugger_pid,
+ const std::vector<std::optional<std::string>> &stdio, bool external) {
llvm::json::Object run_in_terminal_args;
if (external) {
// This indicates the IDE to open an external terminal window.
@@ -885,6 +886,18 @@ llvm::json::Object CreateRunInTerminalReverseRequest(
}
req_args.push_back("--launch-target");
req_args.push_back(program.str());
+ if (!stdio.empty()) {
+ req_args.push_back("--stdio");
+ std::stringstream ss;
+ for (const auto &file : stdio) {
+ if (file)
+ ss << *file;
+ ss << ":";
+ }
+ std::string files = ss.str();
+ files.pop_back();
+ req_args.push_back(std::move(files));
+ }
req_args.insert(req_args.end(), args.begin(), args.end());
run_in_terminal_args.try_emplace("args", req_args);
diff --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h
index e9094f67b94ec..0c865a33a6ce4 100644
--- a/lldb/tools/lldb-dap/JSONUtils.h
+++ b/lldb/tools/lldb-dap/JSONUtils.h
@@ -388,6 +388,10 @@ llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit &unit);
/// 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] external
/// If set to true, the program will run in an external terminal window
/// instead of IDE's integrated terminal.
@@ -398,7 +402,8 @@ llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit &unit);
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, bool external);
+ llvm::StringRef comm_file, lldb::pid_t debugger_pid,
+ const std::vector<std::optional<std::string>> &stdio, bool external);
/// Create a "Terminated" JSON object that contains statistics
///
diff --git a/lldb/tools/lldb-dap/Options.td b/lldb/tools/lldb-dap/Options.td
index c8492c6a62b25..5e9dd7a1d6419 100644
--- a/lldb/tools/lldb-dap/Options.td
+++ b/lldb/tools/lldb-dap/Options.td
@@ -47,6 +47,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 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 repl_mode
: S<"repl-mode">,
MetaVarName<"<mode>">,
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
index 92dada2295841..a85a68b87014c 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
@@ -300,6 +300,7 @@ 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;
/// @}
diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json
index e961c2e48b258..3f0f150c0d98e 100644
--- a/lldb/tools/lldb-dap/package.json
+++ b/lldb/tools/lldb-dap/package.json
@@ -626,7 +626,10 @@
"stdio": {
"type": "array",
"items": {
- "type": "string"
+ "type": [
+ "string",
+ "null"
+ ]
},
"description": "The stdio property specifies the redirection targets for the debuggee's stdio streams. A null value redirects a stream to the default debug terminal. String can be a path to file, named pipe or TTY device. If less than three values are provided, the list will be padded with the last value. Specifying more than three values will create additional file descriptors (4, 5, etc.).",
"default": []
diff --git a/lldb/tools/lldb-dap/tool/lldb-dap.cpp b/lldb/tools/lldb-dap/tool/lldb-dap.cpp
index 93446c051eb54..e1e444e7738b6 100644
--- a/lldb/tools/lldb-dap/tool/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/tool/lldb-dap.cpp
@@ -16,6 +16,7 @@
#include "lldb/API/SBStream.h"
#include "lldb/Host/Config.h"
#include "lldb/Host/File.h"
+#include "lldb/Host/FileSystem.h"
#include "lldb/Host/MainLoop.h"
#include "lldb/Host/MainLoopBase.h"
#include "lldb/Host/MemoryMonitor.h"
@@ -25,6 +26,7 @@
#include "lldb/lldb-forward.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/ScopeExit.h"
+#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Option/Arg.h"
@@ -143,6 +145,57 @@ static void PrintVersion() {
llvm::outs() << "liblldb: " << lldb::SBDebugger::GetVersionString() << '\n';
}
+#if not defined(_WIN32)
+static llvm::Error RedirectToFile(int fd, llvm::StringRef file, bool read,
+ bool write) {
+ if (!read && !write)
+ return llvm::Error::success();
+ int flags = 0;
+ if (read && write)
+ flags = O_NOCTTY | O_CREAT | O_RDWR;
+ else if (read)
+ flags = O_NOCTTY | O_RDONLY;
+ else
+ flags = O_NOCTTY | O_CREAT | O_WRONLY | O_TRUNC;
+ int target_fd = lldb_private::FileSystem::Instance().Open(file.str().c_str(),
+ flags, 0666);
+ if (target_fd == -1)
+ return llvm::errorCodeToError(
+ std::error_code(errno, std::generic_category()));
+ if (target_fd == fd)
+ return llvm::Error::success();
+ if (dup2(target_fd, fd) == -1)
+ return llvm::errorCodeToError(
+ std::error_code(errno, std::generic_category()));
+ close(target_fd);
+ return llvm::Error::success();
+}
+
+static llvm::Error
+SetupIORedirection(const llvm::SmallVectorImpl<llvm::StringRef> &files) {
+ for (std::size_t i = 0; i < files.size(); i++) {
+ if (files[i].empty())
+ continue;
+ switch (i) {
+ case 0:
+ if (llvm::Error err = RedirectToFile(i, files[i], true, false))
+ return err;
+ break;
+ case 1:
+ case 2:
+ if (llvm::Error err = RedirectToFile(i, files[i], false, true))
+ return err;
+ break;
+ default:
+ if (llvm::Error err = RedirectToFile(i, files[i], true, true))
+ return err;
+ break;
+ }
+ }
+ return llvm::Error::success();
+}
+#endif
+
// If --launch-target is provided, this instance of lldb-dap becomes a
// runInTerminal launcher. It will ultimately launch the program specified in
// the --launch-target argument, which is the original program the user wanted
@@ -165,6 +218,7 @@ static void PrintVersion() {
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(
@@ -179,6 +233,16 @@ static llvm::Error LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg,
(void)prctl(PR_SET_PTRACER, debugger_pid, 0, 0, 0);
#endif
+ lldb_private::FileSystem::Initialize();
+ if (!stdio.empty()) {
+ llvm::SmallVector<llvm::StringRef, 3> files;
+ stdio.split(files, ':');
+ while (files.size() < 3)
+ files.push_back(files.back());
+ if (llvm::Error err = SetupIORedirection(files))
+ return err;
+ }
+
RunInTerminalLauncherCommChannel comm_channel(comm_file);
if (llvm::Error err = comm_channel.NotifyPid())
return err;
@@ -484,9 +548,10 @@ 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,
- argv + target_args_pos)) {
+ stdio, argv + target_args_pos)) {
llvm::errs() << llvm::toString(std::move(err)) << '\n';
return EXIT_FAILURE;
}
>From 725ed726dd0de7838ed17b4324f6cb34c14fe284 Mon Sep 17 00:00:00 2001
From: Druzhkov Sergei <serzhdruzhok at gmail.com>
Date: Wed, 1 Oct 2025 23:18:43 +0300
Subject: [PATCH 2/2] Fix review comments
---
lldb/tools/lldb-dap/JSONUtils.cpp | 2 +-
lldb/tools/lldb-dap/tool/lldb-dap.cpp | 55 ++++++++++++++++++---------
2 files changed, 38 insertions(+), 19 deletions(-)
diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp
index 9ecb3a36543ce..71e91f8f41a68 100644
--- a/lldb/tools/lldb-dap/JSONUtils.cpp
+++ b/lldb/tools/lldb-dap/JSONUtils.cpp
@@ -889,7 +889,7 @@ llvm::json::Object CreateRunInTerminalReverseRequest(
if (!stdio.empty()) {
req_args.push_back("--stdio");
std::stringstream ss;
- for (const auto &file : stdio) {
+ for (const std::optional<std::string> &file : stdio) {
if (file)
ss << *file;
ss << ":";
diff --git a/lldb/tools/lldb-dap/tool/lldb-dap.cpp b/lldb/tools/lldb-dap/tool/lldb-dap.cpp
index e1e444e7738b6..544c7db655bfe 100644
--- a/lldb/tools/lldb-dap/tool/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/tool/lldb-dap.cpp
@@ -25,6 +25,7 @@
#include "lldb/Utility/UriParser.h"
#include "lldb/lldb-forward.h"
#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringExtras.h"
@@ -44,8 +45,10 @@
#include "llvm/Support/WithColor.h"
#include "llvm/Support/raw_ostream.h"
#include <condition_variable>
+#include <cstddef>
#include <cstdio>
#include <cstdlib>
+#include <exception>
#include <fcntl.h>
#include <map>
#include <memory>
@@ -146,14 +149,19 @@ static void PrintVersion() {
}
#if not defined(_WIN32)
-static llvm::Error RedirectToFile(int fd, llvm::StringRef file, bool read,
- bool write) {
- if (!read && !write)
+struct FDGroup {
+ std::vector<int> fds;
+ bool read = false;
+ bool write = false;
+};
+
+static llvm::Error RedirectToFile(const FDGroup &fdg, llvm::StringRef file) {
+ if (!fdg.read && !fdg.write)
return llvm::Error::success();
int flags = 0;
- if (read && write)
+ if (fdg.read && fdg.write)
flags = O_NOCTTY | O_CREAT | O_RDWR;
- else if (read)
+ else if (fdg.read)
flags = O_NOCTTY | O_RDONLY;
else
flags = O_NOCTTY | O_CREAT | O_WRONLY | O_TRUNC;
@@ -162,36 +170,47 @@ static llvm::Error RedirectToFile(int fd, llvm::StringRef file, bool read,
if (target_fd == -1)
return llvm::errorCodeToError(
std::error_code(errno, std::generic_category()));
- if (target_fd == fd)
- return llvm::Error::success();
- if (dup2(target_fd, fd) == -1)
- return llvm::errorCodeToError(
- std::error_code(errno, std::generic_category()));
- close(target_fd);
+ for (int fd : fdg.fds) {
+ if (target_fd == fd)
+ continue;
+ if (::dup2(target_fd, fd) == -1)
+ return llvm::errorCodeToError(
+ std::error_code(errno, std::generic_category()));
+ }
+ ::close(target_fd);
return llvm::Error::success();
}
static llvm::Error
SetupIORedirection(const llvm::SmallVectorImpl<llvm::StringRef> &files) {
- for (std::size_t i = 0; i < files.size(); i++) {
+ 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:
- if (llvm::Error err = RedirectToFile(i, files[i], true, false))
- return err;
+ group->second.read = true;
break;
case 1:
case 2:
- if (llvm::Error err = RedirectToFile(i, files[i], false, true))
- return err;
+ group->second.write = true;
break;
default:
- if (llvm::Error err = RedirectToFile(i, files[i], true, true))
- return err;
+ group->second.read = true;
+ group->second.write = true;
break;
}
}
+ 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))));
+ }
return llvm::Error::success();
}
#endif
More information about the lldb-commits
mailing list