LLDB mainline
ProcessLauncherWindows.cpp
Go to the documentation of this file.
1//===-- ProcessLauncherWindows.cpp ----------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
14
15#include "llvm/ADT/ScopeExit.h"
16#include "llvm/ADT/SmallVector.h"
17#include "llvm/Support/ConvertUTF.h"
18#include "llvm/Support/Program.h"
19#include "llvm/Support/WindowsError.h"
20
21#include <string>
22#include <vector>
23
24using namespace lldb;
25using namespace lldb_private;
26
27/// Create a UTF-16 environment block to use with CreateProcessW.
28///
29/// The buffer is a sequence of null-terminated UTF-16 strings, followed by an
30/// extra L'\0' (two bytes of 0). An empty environment must have one
31/// empty string, followed by an extra L'\0'.
32///
33/// The keys are sorted to comply with the CreateProcess API calling convention.
34///
35/// Ensure that the resulting buffer is used in conjunction with
36/// CreateProcessW and be sure that dwCreationFlags includes
37/// CREATE_UNICODE_ENVIRONMENT.
38///
39/// \param env The Environment object to convert.
40/// \returns The sorted sequence of environment variables and their values,
41/// separated by null terminators. The vector is guaranteed to never be empty.
42static std::vector<wchar_t> CreateEnvironmentBufferW(const Environment &env) {
43 std::vector<std::wstring> env_entries;
44 for (const auto &KV : env) {
45 std::wstring wentry;
46 if (llvm::ConvertUTF8toWide(Environment::compose(KV), wentry))
47 env_entries.push_back(std::move(wentry));
48 }
49 std::sort(env_entries.begin(), env_entries.end(),
50 [](const std::wstring &a, const std::wstring &b) {
51 return _wcsicmp(a.c_str(), b.c_str()) < 0;
52 });
53
54 std::vector<wchar_t> buffer;
55 for (const auto &env_entry : env_entries) {
56 buffer.insert(buffer.end(), env_entry.begin(), env_entry.end());
57 buffer.push_back(L'\0');
58 }
59
60 if (buffer.empty())
61 buffer.push_back(L'\0'); // If there are no environment variables, we have
62 // to ensure there are 4 zero bytes in the buffer.
63 buffer.push_back(L'\0');
64
65 return buffer;
66}
67
68/// Flattens an Args object into a Windows command-line wide string.
69///
70/// Returns an empty string if args is empty.
71///
72/// \param args The Args object to flatten.
73/// \returns A wide string containing the flattened command line.
74static llvm::ErrorOr<std::wstring>
76 if (args.empty())
77 return L"";
78
79 std::vector<llvm::StringRef> args_ref;
80 for (auto &entry : args.entries())
81 args_ref.push_back(entry.ref());
82
83 return llvm::sys::flattenWindowsCommandLine(args_ref);
84}
85
88 Status &error) {
89 error.Clear();
90
91 STARTUPINFOEXW startupinfoex = {};
92 startupinfoex.StartupInfo.cb = sizeof(STARTUPINFOEXW);
93 startupinfoex.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
94
95 HPCON hPC = launch_info.GetPTY().GetPseudoTerminalHandle();
96 bool use_pty =
97 hPC != INVALID_HANDLE_VALUE && launch_info.GetNumFileActions() == 0;
98
99 HANDLE stdin_handle = GetStdioHandle(launch_info, STDIN_FILENO);
100 HANDLE stdout_handle = GetStdioHandle(launch_info, STDOUT_FILENO);
101 HANDLE stderr_handle = GetStdioHandle(launch_info, STDERR_FILENO);
102 auto close_handles = llvm::make_scope_exit([&] {
103 if (stdin_handle)
104 ::CloseHandle(stdin_handle);
105 if (stdout_handle)
106 ::CloseHandle(stdout_handle);
107 if (stderr_handle)
108 ::CloseHandle(stderr_handle);
109 });
110
111 SIZE_T attributelist_size = 0;
112 InitializeProcThreadAttributeList(/*lpAttributeList=*/nullptr,
113 /*dwAttributeCount=*/1, /*dwFlags=*/0,
114 &attributelist_size);
115
116 startupinfoex.lpAttributeList =
117 static_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(malloc(attributelist_size));
118 auto free_attributelist =
119 llvm::make_scope_exit([&] { free(startupinfoex.lpAttributeList); });
120 if (!InitializeProcThreadAttributeList(startupinfoex.lpAttributeList,
121 /*dwAttributeCount=*/1, /*dwFlags=*/0,
122 &attributelist_size)) {
123 error = Status(::GetLastError(), eErrorTypeWin32);
124 return HostProcess();
125 }
126 auto delete_attributelist = llvm::make_scope_exit(
127 [&] { DeleteProcThreadAttributeList(startupinfoex.lpAttributeList); });
128
129 std::vector<HANDLE> inherited_handles;
130 if (use_pty) {
131 if (!UpdateProcThreadAttribute(startupinfoex.lpAttributeList, 0,
133 sizeof(hPC), NULL, NULL)) {
134 error = Status(::GetLastError(), eErrorTypeWin32);
135 return HostProcess();
136 }
137 } else {
138 auto inherited_handles_or_err = GetInheritedHandles(
139 launch_info, startupinfoex, stdout_handle, stderr_handle, stdin_handle);
140 if (!inherited_handles_or_err) {
141 error = Status(inherited_handles_or_err.getError());
142 return HostProcess();
143 }
144 inherited_handles = std::move(*inherited_handles_or_err);
145 }
146
147 const char *hide_console_var =
148 getenv("LLDB_LAUNCH_INFERIORS_WITHOUT_CONSOLE");
149 if (hide_console_var &&
150 llvm::StringRef(hide_console_var).equals_insensitive("true")) {
151 startupinfoex.StartupInfo.dwFlags |= STARTF_USESHOWWINDOW;
152 startupinfoex.StartupInfo.wShowWindow = SW_HIDE;
153 }
154
155 DWORD flags = CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT |
156 EXTENDED_STARTUPINFO_PRESENT;
157 if (launch_info.GetFlags().Test(eLaunchFlagDebug))
158 flags |= DEBUG_ONLY_THIS_PROCESS;
159
160 if (launch_info.GetFlags().Test(eLaunchFlagDisableSTDIO) || use_pty)
161 flags &= ~CREATE_NEW_CONSOLE;
162
163 std::vector<wchar_t> environment =
165
166 auto wcommandLineOrErr =
168 if (!wcommandLineOrErr) {
169 error = Status(wcommandLineOrErr.getError());
170 return HostProcess();
171 }
172 std::wstring wcommandLine = *wcommandLineOrErr;
173 // If the command line is empty, it's best to pass a null pointer to tell
174 // CreateProcessW to use the executable name as the command line. If the
175 // command line is not empty, its contents may be modified by CreateProcessW.
176 WCHAR *pwcommandLine = wcommandLine.empty() ? nullptr : &wcommandLine[0];
177
178 std::wstring wexecutable, wworkingDirectory;
179 llvm::ConvertUTF8toWide(launch_info.GetExecutableFile().GetPath(),
180 wexecutable);
181 llvm::ConvertUTF8toWide(launch_info.GetWorkingDirectory().GetPath(),
182 wworkingDirectory);
183
184 PROCESS_INFORMATION pi = {};
185
186 BOOL result = ::CreateProcessW(
187 wexecutable.c_str(), pwcommandLine, NULL, NULL,
188 /*bInheritHandles=*/!inherited_handles.empty(), flags, environment.data(),
189 wworkingDirectory.size() == 0 ? NULL : wworkingDirectory.c_str(),
190 reinterpret_cast<STARTUPINFOW *>(&startupinfoex), &pi);
191
192 if (!result) {
193 // Call GetLastError before we make any other system calls.
194 error = Status(::GetLastError(), eErrorTypeWin32);
195 // Note that error 50 ("The request is not supported") will occur if you
196 // try debug a 64-bit inferior from a 32-bit LLDB.
197 }
198
199 if (result) {
200 // Do not call CloseHandle on pi.hProcess, since we want to pass that back
201 // through the HostProcess.
202 ::CloseHandle(pi.hThread);
203 }
204
205 if (!result)
206 return HostProcess();
207
208 return HostProcess(pi.hProcess);
209}
210
211llvm::ErrorOr<std::vector<HANDLE>> ProcessLauncherWindows::GetInheritedHandles(
212 const ProcessLaunchInfo &launch_info, STARTUPINFOEXW &startupinfoex,
213 HANDLE stdout_handle, HANDLE stderr_handle, HANDLE stdin_handle) {
214 std::vector<HANDLE> inherited_handles;
215
216 startupinfoex.StartupInfo.hStdError =
217 stderr_handle ? stderr_handle : GetStdHandle(STD_ERROR_HANDLE);
218 startupinfoex.StartupInfo.hStdInput =
219 stdin_handle ? stdin_handle : GetStdHandle(STD_INPUT_HANDLE);
220 startupinfoex.StartupInfo.hStdOutput =
221 stdout_handle ? stdout_handle : GetStdHandle(STD_OUTPUT_HANDLE);
222
223 if (startupinfoex.StartupInfo.hStdError)
224 inherited_handles.push_back(startupinfoex.StartupInfo.hStdError);
225 if (startupinfoex.StartupInfo.hStdInput)
226 inherited_handles.push_back(startupinfoex.StartupInfo.hStdInput);
227 if (startupinfoex.StartupInfo.hStdOutput)
228 inherited_handles.push_back(startupinfoex.StartupInfo.hStdOutput);
229
230 for (size_t i = 0; i < launch_info.GetNumFileActions(); ++i) {
231 const FileAction *act = launch_info.GetFileActionAtIndex(i);
233 act->GetFD() == act->GetActionArgument())
234 inherited_handles.push_back(reinterpret_cast<HANDLE>(act->GetFD()));
235 }
236
237 if (inherited_handles.empty())
238 return inherited_handles;
239
240 if (!UpdateProcThreadAttribute(
241 startupinfoex.lpAttributeList, /*dwFlags=*/0,
242 PROC_THREAD_ATTRIBUTE_HANDLE_LIST, inherited_handles.data(),
243 inherited_handles.size() * sizeof(HANDLE),
244 /*lpPreviousValue=*/nullptr, /*lpReturnSize=*/nullptr))
245 return llvm::mapWindowsError(::GetLastError());
246
247 return inherited_handles;
248}
249
250HANDLE
252 int fd) {
253 const FileAction *action = launch_info.GetFileActionForFD(fd);
254 if (action == nullptr)
255 return NULL;
256 SECURITY_ATTRIBUTES secattr = {};
257 secattr.nLength = sizeof(SECURITY_ATTRIBUTES);
258 secattr.bInheritHandle = TRUE;
259
260 DWORD access = 0;
261 DWORD share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
262 DWORD create = 0;
263 DWORD flags = 0;
264 if (fd == STDIN_FILENO) {
265 access = GENERIC_READ;
266 create = OPEN_EXISTING;
267 flags = FILE_ATTRIBUTE_READONLY;
268 }
269 if (fd == STDOUT_FILENO || fd == STDERR_FILENO) {
270 access = GENERIC_WRITE;
271 create = CREATE_ALWAYS;
272 if (fd == STDERR_FILENO)
273 flags = FILE_FLAG_WRITE_THROUGH;
274 }
275
276 const std::string path = action->GetFileSpec().GetPath();
277 std::wstring wpath;
278 llvm::ConvertUTF8toWide(path, wpath);
279 HANDLE result = ::CreateFileW(wpath.c_str(), access, share, &secattr, create,
280 flags, NULL);
281 return (result == INVALID_HANDLE_VALUE) ? NULL : result;
282}
static llvm::raw_ostream & error(Stream &strm)
static llvm::ErrorOr< std::wstring > GetFlattenedWindowsCommandStringW(Args args)
Flattens an Args object into a Windows command-line wide string.
static std::vector< wchar_t > CreateEnvironmentBufferW(const Environment &env)
Create a UTF-16 environment block to use with CreateProcessW.
#define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
void * HPCON
void * HANDLE
A command line argument class.
Definition Args.h:33
llvm::ArrayRef< ArgEntry > entries() const
Definition Args.h:132
bool empty() const
Definition Args.h:122
static std::string compose(const value_type &KeyValue)
Definition Environment.h:80
Action GetAction() const
Definition FileAction.h:38
int GetActionArgument() const
Definition FileAction.h:40
const FileSpec & GetFileSpec() const
size_t GetPath(char *path, size_t max_path_length, bool denormalize=true) const
Extract the full path to the file.
Definition FileSpec.cpp:374
bool Test(ValueType bit) const
Test a single flag bit.
Definition Flags.h:96
FileSpec & GetExecutableFile()
Definition ProcessInfo.h:43
Environment & GetEnvironment()
Definition ProcessInfo.h:88
const FileAction * GetFileActionAtIndex(size_t idx) const
const FileAction * GetFileActionForFD(int fd) const
const FileSpec & GetWorkingDirectory() const
HostProcess LaunchProcess(const ProcessLaunchInfo &launch_info, Status &error) override
llvm::ErrorOr< std::vector< HANDLE > > GetInheritedHandles(const ProcessLaunchInfo &launch_info, STARTUPINFOEXW &startupinfoex, HANDLE stdout_handle=NULL, HANDLE stderr_handle=NULL, HANDLE stdin_handle=NULL)
Get the list of Windows handles that should be inherited by the child process and update STARTUPINFOE...
HANDLE GetStdioHandle(const ProcessLaunchInfo &launch_info, int fd)
An error handling class.
Definition Status.h:118
A class that represents a running process on the host machine.
@ eErrorTypeWin32
Standard Win32 error codes.