From 4c3e0090328b35fb8d0d92df3bd5378d9d811047 Mon Sep 17 00:00:00 2001 From: Druzhkov Sergei Date: Mon, 15 Sep 2025 10:49:44 +0300 Subject: [PATCH 1/2] [lldb-dap] Add stdio redirection --- .../test/tools/lldb-dap/dap_server.py | 3 +++ .../tools/lldb-dap/launch/TestDAP_launch.py | 21 ++++++++++++++++ .../tools/lldb-dap/Handler/RequestHandler.cpp | 25 +++++++++++++++++++ .../lldb-dap/Protocol/ProtocolRequests.cpp | 3 ++- .../lldb-dap/Protocol/ProtocolRequests.h | 2 ++ lldb/tools/lldb-dap/README.md | 1 + lldb/tools/lldb-dap/package.json | 8 ++++++ 7 files changed, 62 insertions(+), 1 deletion(-) diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py index 9fe8ca22e820b..daa3e76df6d82 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py @@ -1039,6 +1039,7 @@ def request_launch( disableSTDIO=False, shellExpandArguments=False, console: Optional[str] = None, + stdio: Optional[list[str]] = None, enableAutoVariableSummaries=False, displayExtendedBacktrace=False, enableSyntheticChildDebugging=False, @@ -1090,6 +1091,8 @@ def request_launch( args_dict["sourceMap"] = sourceMap if console: args_dict["console"] = console + if stdio: + args_dict["stdio"] = stdio if postRunCommands: args_dict["postRunCommands"] = postRunCommands if customFrameFormat: 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 22fcd42b3d36a..a301e00f6755f 100644 --- a/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py +++ b/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py @@ -6,6 +6,7 @@ from lldbsuite.test.lldbtest import * import lldbdap_testcase import os +import pathlib import re import tempfile @@ -624,3 +625,23 @@ def test_no_lldbinit_flag(self): # Verify the initCommands were executed self.verify_commands("initCommands", output, initCommands) + + def test_stdio_redirection(self): + """ + Test stdio redirection. + """ + temp_file = tempfile.NamedTemporaryFile().name + self.build_and_create_debug_adapter() + program = self.getBuildArtifact("a.out") + + self.launch(program, stdio=[None, temp_file, None]) + self.continue_to_exit() + + try: + with open(temp_file, "r") as f: + lines = f.readlines() + self.assertIn( + program, lines[0], "make sure program path is in first argument" + ) + finally: + pathlib.Path(temp_file).unlink(missing_ok=True) diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp index 4fadf1c22e0e3..4b727e1e33a64 100644 --- a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp @@ -177,6 +177,31 @@ llvm::Error BaseRequestHandler::LaunchProcess( launch_info.SetEnvironment(env, true); } + if (!arguments.stdio.empty() && !arguments.disableSTDIO) { + size_t n = std::max(arguments.stdio.size(), static_cast(3)); + for (size_t i = 0; i < n; i++) { + std::optional path; + if (arguments.stdio.size() < i) + path = arguments.stdio.back(); + else + path = arguments.stdio[i]; + if (!path) + continue; + switch (i) { + case 0: + launch_info.AddOpenFileAction(i, path->c_str(), true, false); + break; + case 1: + case 2: + launch_info.AddOpenFileAction(i, path->c_str(), false, true); + break; + default: + launch_info.AddOpenFileAction(i, path->c_str(), true, true); + break; + } + } + } + launch_info.SetDetachOnError(arguments.detachOnError); launch_info.SetShellExpandArguments(arguments.shellExpandArguments); diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp index e1806d6230a80..b455112cd37d9 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp +++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp @@ -303,7 +303,8 @@ bool fromJSON(const json::Value &Params, LaunchRequestArguments &LRA, O.mapOptional("disableSTDIO", LRA.disableSTDIO) && O.mapOptional("shellExpandArguments", LRA.shellExpandArguments) && O.mapOptional("runInTerminal", LRA.console) && - O.mapOptional("console", LRA.console) && parseEnv(Params, LRA.env, P); + O.mapOptional("console", LRA.console) && + O.mapOptional("stdio", LRA.stdio) && parseEnv(Params, LRA.env, P); } bool fromJSON(const json::Value &Params, AttachRequestArguments &ARA, diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h index 0848ee53b4410..92dada2295841 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h +++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h @@ -300,6 +300,8 @@ struct LaunchRequestArguments { /// terminal or external terminal. Console console = eConsoleInternal; + std::vector> stdio; + /// @} }; bool fromJSON(const llvm::json::Value &, LaunchRequestArguments &, diff --git a/lldb/tools/lldb-dap/README.md b/lldb/tools/lldb-dap/README.md index 39dabcc1342c8..52bb009b9c796 100644 --- a/lldb/tools/lldb-dap/README.md +++ b/lldb/tools/lldb-dap/README.md @@ -237,6 +237,7 @@ 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] | | Destination for program stdio streams (0 - stdin, 1 - stdout, 2 - stderr, ...). Using `null` value means no redirection. Supported from lldb-dap 22.0 version. | **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/package.json b/lldb/tools/lldb-dap/package.json index 0290a5f18f800..631b768b37c80 100644 --- a/lldb/tools/lldb-dap/package.json +++ b/lldb/tools/lldb-dap/package.json @@ -615,6 +615,14 @@ "description": "Specify where to launch the program: internal console, integrated terminal or external terminal.", "default": "internalConsole" }, + "stdio": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Destination for program stdio streams (0 - stdin, 1 - stdout, 2 - stderr, ...). Using null value means no redirection.", + "default": [] + }, "timeout": { "type": "number", "description": "The time in seconds to wait for a program to stop at entry point when launching with \"launchCommands\". Defaults to 30 seconds." From 7bcc4ba74905aab6eeb65a95520690b0da02ed80 Mon Sep 17 00:00:00 2001 From: Druzhkov Sergei Date: Mon, 15 Sep 2025 21:25:46 +0300 Subject: [PATCH 2/2] Fix review comments --- .../tools/lldb-dap/launch/TestDAP_launch.py | 19 +++---- .../tools/lldb-dap/Handler/RequestHandler.cpp | 53 ++++++++++--------- lldb/tools/lldb-dap/README.md | 30 ++++++++++- lldb/tools/lldb-dap/package.json | 2 +- 4 files changed, 66 insertions(+), 38 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 a301e00f6755f..4d6b4a49a5161 100644 --- a/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py +++ b/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py @@ -630,18 +630,13 @@ def test_stdio_redirection(self): """ Test stdio redirection. """ - temp_file = tempfile.NamedTemporaryFile().name self.build_and_create_debug_adapter() program = self.getBuildArtifact("a.out") - self.launch(program, stdio=[None, temp_file, None]) - self.continue_to_exit() - - try: - with open(temp_file, "r") as f: - lines = f.readlines() - self.assertIn( - program, lines[0], "make sure program path is in first argument" - ) - finally: - pathlib.Path(temp_file).unlink(missing_ok=True) + with tempfile.NamedTemporaryFile("rt") as f: + self.launch(program, stdio=[None, f.name, None]) + self.continue_to_exit() + lines = f.readlines() + self.assertIn( + program, lines[0], "make sure program path is in first argument" + ) diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp index 4b727e1e33a64..773891353db6a 100644 --- a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp @@ -51,6 +51,33 @@ static uint32_t SetLaunchFlag(uint32_t flags, bool flag, return flags; } +static void +SetupIORedirection(const std::vector> &stdio, + lldb::SBLaunchInfo &launch_info) { + size_t n = std::max(stdio.size(), static_cast(3)); + for (size_t i = 0; i < n; i++) { + std::optional path; + if (stdio.size() < i) + path = stdio.back(); + else + path = stdio[i]; + if (!path) + continue; + switch (i) { + case 0: + launch_info.AddOpenFileAction(i, path->c_str(), true, false); + break; + case 1: + case 2: + launch_info.AddOpenFileAction(i, path->c_str(), false, true); + break; + default: + launch_info.AddOpenFileAction(i, path->c_str(), true, true); + break; + } + } +} + static llvm::Error RunInTerminal(DAP &dap, const protocol::LaunchRequestArguments &arguments) { if (!dap.clientFeatures.contains( @@ -177,30 +204,8 @@ llvm::Error BaseRequestHandler::LaunchProcess( launch_info.SetEnvironment(env, true); } - if (!arguments.stdio.empty() && !arguments.disableSTDIO) { - size_t n = std::max(arguments.stdio.size(), static_cast(3)); - for (size_t i = 0; i < n; i++) { - std::optional path; - if (arguments.stdio.size() < i) - path = arguments.stdio.back(); - else - path = arguments.stdio[i]; - if (!path) - continue; - switch (i) { - case 0: - launch_info.AddOpenFileAction(i, path->c_str(), true, false); - break; - case 1: - case 2: - launch_info.AddOpenFileAction(i, path->c_str(), false, true); - break; - default: - launch_info.AddOpenFileAction(i, path->c_str(), true, true); - break; - } - } - } + if (!arguments.stdio.empty() && !arguments.disableSTDIO) + SetupIORedirection(arguments.stdio, launch_info); launch_info.SetDetachOnError(arguments.detachOnError); launch_info.SetShellExpandArguments(arguments.shellExpandArguments); diff --git a/lldb/tools/lldb-dap/README.md b/lldb/tools/lldb-dap/README.md index 52bb009b9c796..e83384f89bc1f 100644 --- a/lldb/tools/lldb-dap/README.md +++ b/lldb/tools/lldb-dap/README.md @@ -44,6 +44,34 @@ adds `FOO=1` and `bar` to the environment: } ``` +#### Launch in integrated terminal + +This will launch process in IDE's integrated terminal. + +```javascript +{ + "type": "lldb-dap", + "request": "launch", + "name": "Debug", + "program": "/tmp/a.out", + "console": "integratedTerminal" +} +``` + +#### Setup IO redirection + +This will launch process and connect `stdin` to `in.txt`, both of `stdout` and `stderr` to `out.txt`. + +```javascript +{ + "type": "lldb-dap", + "request": "launch", + "name": "Debug", + "program": "/tmp/a.out", + "stdio": ["in.txt", "out.txt"] +} +``` + ### Attaching to a process When attaching to a process using LLDB, you can attach in multiple ways: @@ -237,7 +265,7 @@ 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] | | Destination for program stdio streams (0 - stdin, 1 - stdout, 2 - stderr, ...). Using `null` value means no redirection. Supported from lldb-dap 22.0 version. +| **stdio** | [string] | | 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.). Supported from lldb-dap 22.0 version. | **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/package.json b/lldb/tools/lldb-dap/package.json index 631b768b37c80..6566ba3bdee13 100644 --- a/lldb/tools/lldb-dap/package.json +++ b/lldb/tools/lldb-dap/package.json @@ -620,7 +620,7 @@ "items": { "type": "string" }, - "description": "Destination for program stdio streams (0 - stdin, 1 - stdout, 2 - stderr, ...). Using null value means no redirection.", + "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": [] }, "timeout": {