Skip to content

Commit 0fed265

Browse files
committed
[lldb-dap] Add stdio redirection for integrated and external terminals
1 parent 2284ce0 commit 0fed265

File tree

8 files changed

+120
-7
lines changed

8 files changed

+120
-7
lines changed

lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,27 @@ def test_stdio_redirection(self):
632632
program = self.getBuildArtifact("a.out")
633633

634634
with tempfile.NamedTemporaryFile("rt") as f:
635-
self.launch(program, stdio=[None, f.name, None])
635+
self.launch(program, stdio=[None, f.name])
636+
self.continue_to_exit()
637+
lines = f.readlines()
638+
self.assertIn(
639+
program, lines[0], "make sure program path is in first argument"
640+
)
641+
642+
@skipIfAsan
643+
@skipIfWindows
644+
@skipIf(oslist=["linux"], archs=no_match(["x86_64"]))
645+
def test_stdio_redirection_and_console(self):
646+
"""
647+
Test stdio redirection and console.
648+
"""
649+
self.build_and_create_debug_adapter()
650+
program = self.getBuildArtifact("a.out")
651+
652+
with tempfile.NamedTemporaryFile("rt") as f:
653+
self.launch(
654+
program, console="integratedTerminal", stdio=[None, f.name, None]
655+
)
636656
self.continue_to_exit()
637657
lines = f.readlines()
638658
self.assertIn(

lldb/tools/lldb-dap/Handler/RequestHandler.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ SetupIORedirection(const std::vector<std::optional<std::string>> &stdio,
5757
size_t n = std::max(stdio.size(), static_cast<size_t>(3));
5858
for (size_t i = 0; i < n; i++) {
5959
std::optional<std::string> path;
60-
if (stdio.size() < i)
60+
if (stdio.size() <= i)
6161
path = stdio.back();
6262
else
6363
path = stdio[i];
@@ -107,7 +107,7 @@ RunInTerminal(DAP &dap, const protocol::LaunchRequestArguments &arguments) {
107107

108108
llvm::json::Object reverse_request = CreateRunInTerminalReverseRequest(
109109
arguments.configuration.program, arguments.args, arguments.env,
110-
arguments.cwd, comm_file.m_path, debugger_pid,
110+
arguments.cwd, comm_file.m_path, debugger_pid, arguments.stdio,
111111
arguments.console == protocol::eConsoleExternalTerminal);
112112
dap.SendReverseRequest<LogFailureResponseHandler>("runInTerminal",
113113
std::move(reverse_request));

lldb/tools/lldb-dap/JSONUtils.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -866,7 +866,8 @@ llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit &unit) {
866866
llvm::json::Object CreateRunInTerminalReverseRequest(
867867
llvm::StringRef program, const std::vector<std::string> &args,
868868
const llvm::StringMap<std::string> &env, llvm::StringRef cwd,
869-
llvm::StringRef comm_file, lldb::pid_t debugger_pid, bool external) {
869+
llvm::StringRef comm_file, lldb::pid_t debugger_pid,
870+
const std::vector<std::optional<std::string>> &stdio, bool external) {
870871
llvm::json::Object run_in_terminal_args;
871872
if (external) {
872873
// This indicates the IDE to open an external terminal window.
@@ -885,6 +886,18 @@ llvm::json::Object CreateRunInTerminalReverseRequest(
885886
}
886887
req_args.push_back("--launch-target");
887888
req_args.push_back(program.str());
889+
if (!stdio.empty()) {
890+
req_args.push_back("--stdio");
891+
std::stringstream ss;
892+
for (const auto &file : stdio) {
893+
if (file)
894+
ss << *file;
895+
ss << ":";
896+
}
897+
std::string files = ss.str();
898+
files.pop_back();
899+
req_args.push_back(std::move(files));
900+
}
888901
req_args.insert(req_args.end(), args.begin(), args.end());
889902
run_in_terminal_args.try_emplace("args", req_args);
890903

lldb/tools/lldb-dap/JSONUtils.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,10 @@ llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit &unit);
388388
/// launcher uses it on Linux tell the kernel that it should allow the
389389
/// debugger process to attach.
390390
///
391+
/// \param[in] stdio
392+
/// An array of file paths for redirecting the program's standard IO
393+
/// streams.
394+
///
391395
/// \param[in] external
392396
/// If set to true, the program will run in an external terminal window
393397
/// instead of IDE's integrated terminal.
@@ -398,7 +402,8 @@ llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit &unit);
398402
llvm::json::Object CreateRunInTerminalReverseRequest(
399403
llvm::StringRef program, const std::vector<std::string> &args,
400404
const llvm::StringMap<std::string> &env, llvm::StringRef cwd,
401-
llvm::StringRef comm_file, lldb::pid_t debugger_pid, bool external);
405+
llvm::StringRef comm_file, lldb::pid_t debugger_pid,
406+
const std::vector<std::optional<std::string>> &stdio, bool external);
402407

403408
/// Create a "Terminated" JSON object that contains statistics
404409
///

lldb/tools/lldb-dap/Options.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ def debugger_pid: S<"debugger-pid">,
4747
HelpText<"The PID of the lldb-dap instance that sent the launchInTerminal "
4848
"request when using --launch-target.">;
4949

50+
def stdio: S<"stdio">,
51+
MetaVarName<"<stdin:stdout:stderr:...>">,
52+
HelpText<"An array of file paths for redirecting the program's standard IO "
53+
"streams. A colon-separated list of entries. Empty value means no "
54+
"redirection.">;
55+
5056
def repl_mode
5157
: S<"repl-mode">,
5258
MetaVarName<"<mode>">,

lldb/tools/lldb-dap/Protocol/ProtocolRequests.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ struct LaunchRequestArguments {
300300
/// terminal or external terminal.
301301
Console console = eConsoleInternal;
302302

303+
/// An array of file paths for redirecting the program's standard IO streams.
303304
std::vector<std::optional<std::string>> stdio;
304305

305306
/// @}

lldb/tools/lldb-dap/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -626,7 +626,10 @@
626626
"stdio": {
627627
"type": "array",
628628
"items": {
629-
"type": "string"
629+
"type": [
630+
"string",
631+
"null"
632+
]
630633
},
631634
"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.).",
632635
"default": []

lldb/tools/lldb-dap/tool/lldb-dap.cpp

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "lldb/API/SBStream.h"
1717
#include "lldb/Host/Config.h"
1818
#include "lldb/Host/File.h"
19+
#include "lldb/Host/FileSystem.h"
1920
#include "lldb/Host/MainLoop.h"
2021
#include "lldb/Host/MainLoopBase.h"
2122
#include "lldb/Host/MemoryMonitor.h"
@@ -25,6 +26,7 @@
2526
#include "lldb/lldb-forward.h"
2627
#include "llvm/ADT/ArrayRef.h"
2728
#include "llvm/ADT/ScopeExit.h"
29+
#include "llvm/ADT/SmallVector.h"
2830
#include "llvm/ADT/StringExtras.h"
2931
#include "llvm/ADT/StringRef.h"
3032
#include "llvm/Option/Arg.h"
@@ -143,6 +145,57 @@ static void PrintVersion() {
143145
llvm::outs() << "liblldb: " << lldb::SBDebugger::GetVersionString() << '\n';
144146
}
145147

148+
#if not defined(_WIN32)
149+
static llvm::Error RedirectToFile(int fd, llvm::StringRef file, bool read,
150+
bool write) {
151+
if (!read && !write)
152+
return llvm::Error::success();
153+
int flags = 0;
154+
if (read && write)
155+
flags = O_NOCTTY | O_CREAT | O_RDWR;
156+
else if (read)
157+
flags = O_NOCTTY | O_RDONLY;
158+
else
159+
flags = O_NOCTTY | O_CREAT | O_WRONLY | O_TRUNC;
160+
int target_fd = lldb_private::FileSystem::Instance().Open(file.str().c_str(),
161+
flags, 0666);
162+
if (target_fd == -1)
163+
return llvm::errorCodeToError(
164+
std::error_code(errno, std::generic_category()));
165+
if (target_fd == fd)
166+
return llvm::Error::success();
167+
if (dup2(target_fd, fd) == -1)
168+
return llvm::errorCodeToError(
169+
std::error_code(errno, std::generic_category()));
170+
close(target_fd);
171+
return llvm::Error::success();
172+
}
173+
174+
static llvm::Error
175+
SetupIORedirection(const llvm::SmallVectorImpl<llvm::StringRef> &files) {
176+
for (std::size_t i = 0; i < files.size(); i++) {
177+
if (files[i].empty())
178+
continue;
179+
switch (i) {
180+
case 0:
181+
if (llvm::Error err = RedirectToFile(i, files[i], true, false))
182+
return err;
183+
break;
184+
case 1:
185+
case 2:
186+
if (llvm::Error err = RedirectToFile(i, files[i], false, true))
187+
return err;
188+
break;
189+
default:
190+
if (llvm::Error err = RedirectToFile(i, files[i], true, true))
191+
return err;
192+
break;
193+
}
194+
}
195+
return llvm::Error::success();
196+
}
197+
#endif
198+
146199
// If --launch-target is provided, this instance of lldb-dap becomes a
147200
// runInTerminal launcher. It will ultimately launch the program specified in
148201
// the --launch-target argument, which is the original program the user wanted
@@ -165,6 +218,7 @@ static void PrintVersion() {
165218
static llvm::Error LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg,
166219
llvm::StringRef comm_file,
167220
lldb::pid_t debugger_pid,
221+
llvm::StringRef stdio,
168222
char *argv[]) {
169223
#if defined(_WIN32)
170224
return llvm::createStringError(
@@ -179,6 +233,16 @@ static llvm::Error LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg,
179233
(void)prctl(PR_SET_PTRACER, debugger_pid, 0, 0, 0);
180234
#endif
181235

236+
lldb_private::FileSystem::Initialize();
237+
if (!stdio.empty()) {
238+
llvm::SmallVector<llvm::StringRef, 3> files;
239+
stdio.split(files, ':');
240+
while (files.size() < 3)
241+
files.push_back(files.back());
242+
if (llvm::Error err = SetupIORedirection(files))
243+
return err;
244+
}
245+
182246
RunInTerminalLauncherCommChannel comm_channel(comm_file);
183247
if (llvm::Error err = comm_channel.NotifyPid())
184248
return err;
@@ -484,9 +548,10 @@ int main(int argc, char *argv[]) {
484548
break;
485549
}
486550
}
551+
llvm::StringRef stdio = input_args.getLastArgValue(OPT_stdio);
487552
if (llvm::Error err =
488553
LaunchRunInTerminalTarget(*target_arg, comm_file->getValue(), pid,
489-
argv + target_args_pos)) {
554+
stdio, argv + target_args_pos)) {
490555
llvm::errs() << llvm::toString(std::move(err)) << '\n';
491556
return EXIT_FAILURE;
492557
}

0 commit comments

Comments
 (0)