| 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 | |
| 9 | #include "lldb/Host/windows/ProcessLauncherWindows.h" |
| 10 | #include "lldb/Host/HostProcess.h" |
| 11 | #include "lldb/Host/ProcessLaunchInfo.h" |
| 12 | |
| 13 | #include "llvm/ADT/ScopeExit.h" |
| 14 | #include "llvm/ADT/SmallVector.h" |
| 15 | #include "llvm/Support/ConvertUTF.h" |
| 16 | #include "llvm/Support/Program.h" |
| 17 | |
| 18 | #include <string> |
| 19 | #include <vector> |
| 20 | |
| 21 | using namespace lldb; |
| 22 | using namespace lldb_private; |
| 23 | |
| 24 | static void CreateEnvironmentBuffer(const Environment &env, |
| 25 | std::vector<char> &buffer) { |
| 26 | // The buffer is a list of null-terminated UTF-16 strings, followed by an |
| 27 | // extra L'\0' (two bytes of 0). An empty environment must have one |
| 28 | // empty string, followed by an extra L'\0'. |
| 29 | for (const auto &KV : env) { |
| 30 | std::wstring warg; |
| 31 | if (llvm::ConvertUTF8toWide(Source: Environment::compose(KeyValue: KV), Result&: warg)) { |
| 32 | buffer.insert( |
| 33 | position: buffer.end(), first: reinterpret_cast<const char *>(warg.c_str()), |
| 34 | last: reinterpret_cast<const char *>(warg.c_str() + warg.size() + 1)); |
| 35 | } |
| 36 | } |
| 37 | // One null wchar_t (to end the block) is two null bytes |
| 38 | buffer.push_back(x: 0); |
| 39 | buffer.push_back(x: 0); |
| 40 | // Insert extra two bytes, just in case the environment was empty. |
| 41 | buffer.push_back(x: 0); |
| 42 | buffer.push_back(x: 0); |
| 43 | } |
| 44 | |
| 45 | static bool GetFlattenedWindowsCommandString(Args args, std::wstring &command) { |
| 46 | if (args.empty()) |
| 47 | return false; |
| 48 | |
| 49 | std::vector<llvm::StringRef> args_ref; |
| 50 | for (auto &entry : args.entries()) |
| 51 | args_ref.push_back(x: entry.ref()); |
| 52 | |
| 53 | llvm::ErrorOr<std::wstring> result = |
| 54 | llvm::sys::flattenWindowsCommandLine(args_ref); |
| 55 | if (result.getError()) |
| 56 | return false; |
| 57 | |
| 58 | command = *result; |
| 59 | return true; |
| 60 | } |
| 61 | |
| 62 | HostProcess |
| 63 | ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info, |
| 64 | Status &error) { |
| 65 | error.Clear(); |
| 66 | |
| 67 | std::string executable; |
| 68 | std::vector<char> environment; |
| 69 | STARTUPINFOEX startupinfoex = {}; |
| 70 | STARTUPINFO &startupinfo = startupinfoex.StartupInfo; |
| 71 | PROCESS_INFORMATION pi = {}; |
| 72 | |
| 73 | HANDLE stdin_handle = GetStdioHandle(launch_info, STDIN_FILENO); |
| 74 | HANDLE stdout_handle = GetStdioHandle(launch_info, STDOUT_FILENO); |
| 75 | HANDLE stderr_handle = GetStdioHandle(launch_info, STDERR_FILENO); |
| 76 | auto close_handles = llvm::make_scope_exit(F: [&] { |
| 77 | if (stdin_handle) |
| 78 | ::CloseHandle(stdin_handle); |
| 79 | if (stdout_handle) |
| 80 | ::CloseHandle(stdout_handle); |
| 81 | if (stderr_handle) |
| 82 | ::CloseHandle(stderr_handle); |
| 83 | }); |
| 84 | |
| 85 | startupinfo.cb = sizeof(startupinfoex); |
| 86 | startupinfo.dwFlags |= STARTF_USESTDHANDLES; |
| 87 | startupinfo.hStdError = |
| 88 | stderr_handle ? stderr_handle : ::GetStdHandle(STD_ERROR_HANDLE); |
| 89 | startupinfo.hStdInput = |
| 90 | stdin_handle ? stdin_handle : ::GetStdHandle(STD_INPUT_HANDLE); |
| 91 | startupinfo.hStdOutput = |
| 92 | stdout_handle ? stdout_handle : ::GetStdHandle(STD_OUTPUT_HANDLE); |
| 93 | |
| 94 | std::vector<HANDLE> inherited_handles; |
| 95 | if (startupinfo.hStdError) |
| 96 | inherited_handles.push_back(startupinfo.hStdError); |
| 97 | if (startupinfo.hStdInput) |
| 98 | inherited_handles.push_back(startupinfo.hStdInput); |
| 99 | if (startupinfo.hStdOutput) |
| 100 | inherited_handles.push_back(startupinfo.hStdOutput); |
| 101 | |
| 102 | SIZE_T attributelist_size = 0; |
| 103 | InitializeProcThreadAttributeList(/*lpAttributeList=*/nullptr, |
| 104 | /*dwAttributeCount=*/1, /*dwFlags=*/0, |
| 105 | &attributelist_size); |
| 106 | |
| 107 | startupinfoex.lpAttributeList = |
| 108 | static_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(malloc(attributelist_size)); |
| 109 | auto free_attributelist = |
| 110 | llvm::make_scope_exit(F: [&] { free(startupinfoex.lpAttributeList); }); |
| 111 | if (!InitializeProcThreadAttributeList(startupinfoex.lpAttributeList, |
| 112 | /*dwAttributeCount=*/1, /*dwFlags=*/0, |
| 113 | &attributelist_size)) { |
| 114 | error = Status(::GetLastError(), eErrorTypeWin32); |
| 115 | return HostProcess(); |
| 116 | } |
| 117 | auto delete_attributelist = llvm::make_scope_exit( |
| 118 | F: [&] { DeleteProcThreadAttributeList(startupinfoex.lpAttributeList); }); |
| 119 | for (size_t i = 0; i < launch_info.GetNumFileActions(); ++i) { |
| 120 | const FileAction *act = launch_info.GetFileActionAtIndex(idx: i); |
| 121 | if (act->GetAction() == FileAction::eFileActionDuplicate && |
| 122 | act->GetFD() == act->GetActionArgument()) |
| 123 | inherited_handles.push_back(reinterpret_cast<HANDLE>(act->GetFD())); |
| 124 | } |
| 125 | if (!inherited_handles.empty()) { |
| 126 | if (!UpdateProcThreadAttribute( |
| 127 | startupinfoex.lpAttributeList, /*dwFlags=*/0, |
| 128 | PROC_THREAD_ATTRIBUTE_HANDLE_LIST, inherited_handles.data(), |
| 129 | inherited_handles.size() * sizeof(HANDLE), |
| 130 | /*lpPreviousValue=*/nullptr, /*lpReturnSize=*/nullptr)) { |
| 131 | error = Status(::GetLastError(), eErrorTypeWin32); |
| 132 | return HostProcess(); |
| 133 | } |
| 134 | } |
| 135 | |
| 136 | const char *hide_console_var = |
| 137 | getenv(name: "LLDB_LAUNCH_INFERIORS_WITHOUT_CONSOLE" ); |
| 138 | if (hide_console_var && |
| 139 | llvm::StringRef(hide_console_var).equals_insensitive(RHS: "true" )) { |
| 140 | startupinfo.dwFlags |= STARTF_USESHOWWINDOW; |
| 141 | startupinfo.wShowWindow = SW_HIDE; |
| 142 | } |
| 143 | |
| 144 | DWORD flags = CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT | |
| 145 | EXTENDED_STARTUPINFO_PRESENT; |
| 146 | if (launch_info.GetFlags().Test(bit: eLaunchFlagDebug)) |
| 147 | flags |= DEBUG_ONLY_THIS_PROCESS; |
| 148 | |
| 149 | if (launch_info.GetFlags().Test(bit: eLaunchFlagDisableSTDIO)) |
| 150 | flags &= ~CREATE_NEW_CONSOLE; |
| 151 | |
| 152 | LPVOID env_block = nullptr; |
| 153 | ::CreateEnvironmentBuffer(env: launch_info.GetEnvironment(), buffer&: environment); |
| 154 | env_block = environment.data(); |
| 155 | |
| 156 | executable = launch_info.GetExecutableFile().GetPath(); |
| 157 | std::wstring wcommandLine; |
| 158 | GetFlattenedWindowsCommandString(args: launch_info.GetArguments(), command&: wcommandLine); |
| 159 | |
| 160 | std::wstring wexecutable, wworkingDirectory; |
| 161 | llvm::ConvertUTF8toWide(Source: executable, Result&: wexecutable); |
| 162 | llvm::ConvertUTF8toWide(Source: launch_info.GetWorkingDirectory().GetPath(), |
| 163 | Result&: wworkingDirectory); |
| 164 | // If the command line is empty, it's best to pass a null pointer to tell |
| 165 | // CreateProcessW to use the executable name as the command line. If the |
| 166 | // command line is not empty, its contents may be modified by CreateProcessW. |
| 167 | WCHAR *pwcommandLine = wcommandLine.empty() ? nullptr : &wcommandLine[0]; |
| 168 | |
| 169 | BOOL result = ::CreateProcessW( |
| 170 | wexecutable.c_str(), pwcommandLine, NULL, NULL, |
| 171 | /*bInheritHandles=*/!inherited_handles.empty(), flags, env_block, |
| 172 | wworkingDirectory.size() == 0 ? NULL : wworkingDirectory.c_str(), |
| 173 | reinterpret_cast<STARTUPINFO *>(&startupinfoex), &pi); |
| 174 | |
| 175 | if (!result) { |
| 176 | // Call GetLastError before we make any other system calls. |
| 177 | error = Status(::GetLastError(), eErrorTypeWin32); |
| 178 | // Note that error 50 ("The request is not supported") will occur if you |
| 179 | // try debug a 64-bit inferior from a 32-bit LLDB. |
| 180 | } |
| 181 | |
| 182 | if (result) { |
| 183 | // Do not call CloseHandle on pi.hProcess, since we want to pass that back |
| 184 | // through the HostProcess. |
| 185 | ::CloseHandle(pi.hThread); |
| 186 | } |
| 187 | |
| 188 | if (!result) |
| 189 | return HostProcess(); |
| 190 | |
| 191 | return HostProcess(pi.hProcess); |
| 192 | } |
| 193 | |
| 194 | HANDLE |
| 195 | ProcessLauncherWindows::GetStdioHandle(const ProcessLaunchInfo &launch_info, |
| 196 | int fd) { |
| 197 | const FileAction *action = launch_info.GetFileActionForFD(fd); |
| 198 | if (action == nullptr) |
| 199 | return NULL; |
| 200 | SECURITY_ATTRIBUTES secattr = {}; |
| 201 | secattr.nLength = sizeof(SECURITY_ATTRIBUTES); |
| 202 | secattr.bInheritHandle = TRUE; |
| 203 | |
| 204 | llvm::StringRef path = action->GetPath(); |
| 205 | DWORD access = 0; |
| 206 | DWORD share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; |
| 207 | DWORD create = 0; |
| 208 | DWORD flags = 0; |
| 209 | if (fd == STDIN_FILENO) { |
| 210 | access = GENERIC_READ; |
| 211 | create = OPEN_EXISTING; |
| 212 | flags = FILE_ATTRIBUTE_READONLY; |
| 213 | } |
| 214 | if (fd == STDOUT_FILENO || fd == STDERR_FILENO) { |
| 215 | access = GENERIC_WRITE; |
| 216 | create = CREATE_ALWAYS; |
| 217 | if (fd == STDERR_FILENO) |
| 218 | flags = FILE_FLAG_WRITE_THROUGH; |
| 219 | } |
| 220 | |
| 221 | std::wstring wpath; |
| 222 | llvm::ConvertUTF8toWide(Source: path, Result&: wpath); |
| 223 | HANDLE result = ::CreateFileW(wpath.c_str(), access, share, &secattr, create, |
| 224 | flags, NULL); |
| 225 | return (result == INVALID_HANDLE_VALUE) ? NULL : result; |
| 226 | } |
| 227 | |