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 = hPC != INVALID_HANDLE_VALUE &&
97 launch_info.GetNumFileActions() == 0 &&
98 launch_info.GetFlags().Test(lldb::eLaunchFlagLaunchInTTY);
99
100 HANDLE stdin_handle = GetStdioHandle(launch_info, STDIN_FILENO);
101 HANDLE stdout_handle = GetStdioHandle(launch_info, STDOUT_FILENO);
102 HANDLE stderr_handle = GetStdioHandle(launch_info, STDERR_FILENO);
103 auto close_handles = llvm::make_scope_exit([&] {
104 if (stdin_handle)
105 ::CloseHandle(stdin_handle);
106 if (stdout_handle)
107 ::CloseHandle(stdout_handle);
108 if (stderr_handle)
109 ::CloseHandle(stderr_handle);
110 });
111
112 SIZE_T attributelist_size = 0;
113 InitializeProcThreadAttributeList(/*lpAttributeList=*/nullptr,
114 /*dwAttributeCount=*/1, /*dwFlags=*/0,
115 &attributelist_size);
116
117 startupinfoex.lpAttributeList =
118 static_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(malloc(attributelist_size));
119 auto free_attributelist =
120 llvm::make_scope_exit([&] { free(startupinfoex.lpAttributeList); });
121 if (!InitializeProcThreadAttributeList(startupinfoex.lpAttributeList,
122 /*dwAttributeCount=*/1, /*dwFlags=*/0,
123 &attributelist_size)) {
124 error = Status(::GetLastError(), eErrorTypeWin32);
125 return HostProcess();
126 }
127 auto delete_attributelist = llvm::make_scope_exit(
128 [&] { DeleteProcThreadAttributeList(startupinfoex.lpAttributeList); });
129
130 std::vector<HANDLE> inherited_handles;
131 if (use_pty) {
132 if (!UpdateProcThreadAttribute(startupinfoex.lpAttributeList, 0,
134 sizeof(hPC), NULL, NULL)) {
135 error = Status(::GetLastError(), eErrorTypeWin32);
136 return HostProcess();
137 }
138 } else {
139 auto inherited_handles_or_err = GetInheritedHandles(
140 launch_info, startupinfoex, stdout_handle, stderr_handle, stdin_handle);
141 if (!inherited_handles_or_err) {
142 error = Status(inherited_handles_or_err.getError());
143 return HostProcess();
144 }
145 inherited_handles = std::move(*inherited_handles_or_err);
146 }
147
148 const char *hide_console_var =
149 getenv("LLDB_LAUNCH_INFERIORS_WITHOUT_CONSOLE");
150 if (hide_console_var &&
151 llvm::StringRef(hide_console_var).equals_insensitive("true")) {
152 startupinfoex.StartupInfo.dwFlags |= STARTF_USESHOWWINDOW;
153 startupinfoex.StartupInfo.wShowWindow = SW_HIDE;
154 }
155
156 DWORD flags = CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT |
157 EXTENDED_STARTUPINFO_PRESENT;
158 if (launch_info.GetFlags().Test(eLaunchFlagDebug))
159 flags |= DEBUG_ONLY_THIS_PROCESS;
160
161 if (launch_info.GetFlags().Test(eLaunchFlagDisableSTDIO) || use_pty)
162 flags &= ~CREATE_NEW_CONSOLE;
163
164 std::vector<wchar_t> environment =
166
167 auto wcommandLineOrErr =
169 if (!wcommandLineOrErr) {
170 error = Status(wcommandLineOrErr.getError());
171 return HostProcess();
172 }
173 std::wstring wcommandLine = *wcommandLineOrErr;
174 // If the command line is empty, it's best to pass a null pointer to tell
175 // CreateProcessW to use the executable name as the command line. If the
176 // command line is not empty, its contents may be modified by CreateProcessW.
177 WCHAR *pwcommandLine = wcommandLine.empty() ? nullptr : &wcommandLine[0];
178
179 std::wstring wexecutable, wworkingDirectory;
180 llvm::ConvertUTF8toWide(launch_info.GetExecutableFile().GetPath(),
181 wexecutable);
182 llvm::ConvertUTF8toWide(launch_info.GetWorkingDirectory().GetPath(),
183 wworkingDirectory);
184
185 PROCESS_INFORMATION pi = {};
186
187 BOOL result = ::CreateProcessW(
188 wexecutable.c_str(), pwcommandLine, NULL, NULL,
189 /*bInheritHandles=*/!inherited_handles.empty(), flags, environment.data(),
190 wworkingDirectory.size() == 0 ? NULL : wworkingDirectory.c_str(),
191 reinterpret_cast<STARTUPINFOW *>(&startupinfoex), &pi);
192
193 if (!result) {
194 // Call GetLastError before we make any other system calls.
195 error = Status(::GetLastError(), eErrorTypeWin32);
196 // Note that error 50 ("The request is not supported") will occur if you
197 // try debug a 64-bit inferior from a 32-bit LLDB.
198 }
199
200 if (result) {
201 // Do not call CloseHandle on pi.hProcess, since we want to pass that back
202 // through the HostProcess.
203 ::CloseHandle(pi.hThread);
204 }
205
206 if (!result)
207 return HostProcess();
208
209 return HostProcess(pi.hProcess);
210}
211
212llvm::ErrorOr<std::vector<HANDLE>> ProcessLauncherWindows::GetInheritedHandles(
213 const ProcessLaunchInfo &launch_info, STARTUPINFOEXW &startupinfoex,
214 HANDLE stdout_handle, HANDLE stderr_handle, HANDLE stdin_handle) {
215 std::vector<HANDLE> inherited_handles;
216
217 startupinfoex.StartupInfo.hStdError =
218 stderr_handle ? stderr_handle : GetStdHandle(STD_ERROR_HANDLE);
219 startupinfoex.StartupInfo.hStdInput =
220 stdin_handle ? stdin_handle : GetStdHandle(STD_INPUT_HANDLE);
221 startupinfoex.StartupInfo.hStdOutput =
222 stdout_handle ? stdout_handle : GetStdHandle(STD_OUTPUT_HANDLE);
223
224 if (startupinfoex.StartupInfo.hStdError)
225 inherited_handles.push_back(startupinfoex.StartupInfo.hStdError);
226 if (startupinfoex.StartupInfo.hStdInput)
227 inherited_handles.push_back(startupinfoex.StartupInfo.hStdInput);
228 if (startupinfoex.StartupInfo.hStdOutput)
229 inherited_handles.push_back(startupinfoex.StartupInfo.hStdOutput);
230
231 for (size_t i = 0; i < launch_info.GetNumFileActions(); ++i) {
232 const FileAction *act = launch_info.GetFileActionAtIndex(i);
234 act->GetFD() == act->GetActionArgument())
235 inherited_handles.push_back(reinterpret_cast<HANDLE>(act->GetFD()));
236 }
237
238 if (inherited_handles.empty())
239 return inherited_handles;
240
241 if (!UpdateProcThreadAttribute(
242 startupinfoex.lpAttributeList, /*dwFlags=*/0,
243 PROC_THREAD_ATTRIBUTE_HANDLE_LIST, inherited_handles.data(),
244 inherited_handles.size() * sizeof(HANDLE),
245 /*lpPreviousValue=*/nullptr, /*lpReturnSize=*/nullptr))
246 return llvm::mapWindowsError(::GetLastError());
247
248 return inherited_handles;
249}
250
251HANDLE
253 int fd) {
254 const FileAction *action = launch_info.GetFileActionForFD(fd);
255 if (action == nullptr)
256 return NULL;
257 SECURITY_ATTRIBUTES secattr = {};
258 secattr.nLength = sizeof(SECURITY_ATTRIBUTES);
259 secattr.bInheritHandle = TRUE;
260
261 DWORD access = 0;
262 DWORD share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
263 DWORD create = 0;
264 DWORD flags = 0;
265 if (fd == STDIN_FILENO) {
266 access = GENERIC_READ;
267 create = OPEN_EXISTING;
268 flags = FILE_ATTRIBUTE_READONLY;
269 }
270 if (fd == STDOUT_FILENO || fd == STDERR_FILENO) {
271 access = GENERIC_WRITE;
272 create = CREATE_ALWAYS;
273 if (fd == STDERR_FILENO)
274 flags = FILE_FLAG_WRITE_THROUGH;
275 }
276
277 const std::string path = action->GetFileSpec().GetPath();
278 std::wstring wpath;
279 llvm::ConvertUTF8toWide(path, wpath);
280 HANDLE result = ::CreateFileW(wpath.c_str(), access, share, &secattr, create,
281 flags, NULL);
282 return (result == INVALID_HANDLE_VALUE) ? NULL : result;
283}
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.