Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
4 changes: 2 additions & 2 deletions lldb/tools/lldb-dap/Handler/RequestHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -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));
Expand Down
15 changes: 14 additions & 1 deletion lldb/tools/lldb-dap/JSONUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 std::optional<std::string> &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);

Expand Down
7 changes: 6 additions & 1 deletion lldb/tools/lldb-dap/JSONUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
///
Expand Down
6 changes: 6 additions & 0 deletions lldb/tools/lldb-dap/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -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">,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it be possible to redirect 2 to 1, for example, instead of providing full paths? If that works, state a couple of examples of different kinds of inputs here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expect only paths here because we have full control over this option when starting the runInTerminal launcher. Therefore, we can resolve everything on the callee side

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>">,
Expand Down
1 change: 1 addition & 0 deletions lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/// @}
Expand Down
5 changes: 4 additions & 1 deletion lldb/tools/lldb-dap/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": []
Expand Down
87 changes: 86 additions & 1 deletion lldb/tools/lldb-dap/tool/lldb-dap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -24,7 +25,9 @@
#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"
#include "llvm/ADT/StringRef.h"
#include "llvm/Option/Arg.h"
Expand All @@ -42,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>
Expand Down Expand Up @@ -143,6 +148,74 @@ static void PrintVersion() {
llvm::outs() << "liblldb: " << lldb::SBDebugger::GetVersionString() << '\n';
}

#if not defined(_WIN32)
struct FDGroup {
int GetFlags() const {
if (read && write)
return O_NOCTTY | O_CREAT | O_RDWR;
if (read)
return O_NOCTTY | O_RDONLY;
return O_NOCTTY | O_CREAT | O_WRONLY | O_TRUNC;
}

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 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)
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) {
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;
}
}
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

// 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
Expand All @@ -165,6 +238,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(
Expand All @@ -179,6 +253,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;
Expand Down Expand Up @@ -484,9 +568,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;
}
Expand Down
Loading