-
Notifications
You must be signed in to change notification settings - Fork 15.2k
[lldb][windows] refactor CreateProcessW setup #168733
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,9 @@ | |
|
|
||
| #include "lldb/Host/ProcessLauncher.h" | ||
| #include "lldb/Host/windows/windows.h" | ||
| #include "lldb/Utility/Args.h" | ||
| #include "lldb/Utility/Environment.h" | ||
| #include "llvm/Support/ErrorOr.h" | ||
|
|
||
| namespace lldb_private { | ||
|
|
||
|
|
@@ -23,7 +26,33 @@ class ProcessLauncherWindows : public ProcessLauncher { | |
|
|
||
| protected: | ||
| HANDLE GetStdioHandle(const ProcessLaunchInfo &launch_info, int fd); | ||
|
|
||
| /// Create a UTF-16 environment block to use with CreateProcessW. | ||
| /// | ||
| /// The buffer is a sequence of null-terminated UTF-16 strings, followed by an | ||
| /// extra L'\0' (two bytes of 0). An empty environment must have one | ||
| /// empty string, followed by an extra L'\0'. | ||
| /// | ||
| /// The keys are sorted to comply with the CreateProcess' calling convention. | ||
| /// | ||
| /// Ensure that the resulting buffer is used in conjunction with | ||
| /// CreateProcessW and be sure that dwCreationFlags includes | ||
| /// CREATE_UNICODE_ENVIRONMENT. | ||
| /// | ||
| /// \param env The Environment object to convert. | ||
| /// \returns The sorted sequence of environment variables and their values, | ||
| /// separated by null terminators. | ||
| static std::vector<wchar_t> CreateEnvironmentBufferW(const Environment &env); | ||
|
|
||
| /// Flattens an Args object into a Windows command-line wide string. | ||
| /// | ||
| /// Returns an empty string if args is empty. | ||
| /// | ||
| /// \param args The Args object to flatten. | ||
| /// \returns A wide string containing the flattened command line. | ||
| static llvm::ErrorOr<std::wstring> | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Likewise |
||
| GetFlattenedWindowsCommandStringW(Args args); | ||
| }; | ||
| } | ||
|
|
||
| #endif | ||
| #endif | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. missing newline |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,42 +21,41 @@ | |
| using namespace lldb; | ||
| using namespace lldb_private; | ||
|
|
||
| static void CreateEnvironmentBuffer(const Environment &env, | ||
| std::vector<char> &buffer) { | ||
| // The buffer is a list of null-terminated UTF-16 strings, followed by an | ||
| // extra L'\0' (two bytes of 0). An empty environment must have one | ||
| // empty string, followed by an extra L'\0'. | ||
| std::vector<wchar_t> | ||
| ProcessLauncherWindows::CreateEnvironmentBufferW(const Environment &env) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand why this cannot remain an internal detail of the implementation and needs to be moved into std::vector<wchar_t> CreateEnvironmentBufferW(const Environment &); |
||
| std::vector<std::wstring> env_entries; | ||
| for (const auto &KV : env) { | ||
| std::wstring warg; | ||
| if (llvm::ConvertUTF8toWide(Environment::compose(KV), warg)) { | ||
| buffer.insert( | ||
| buffer.end(), reinterpret_cast<const char *>(warg.c_str()), | ||
| reinterpret_cast<const char *>(warg.c_str() + warg.size() + 1)); | ||
| std::wstring wentry; | ||
| if (llvm::ConvertUTF8toWide(Environment::compose(KV), wentry)) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd drop the unnecessary braces |
||
| env_entries.push_back(std::move(wentry)); | ||
| } | ||
| } | ||
| // One null wchar_t (to end the block) is two null bytes | ||
| buffer.push_back(0); | ||
| buffer.push_back(0); | ||
| // Insert extra two bytes, just in case the environment was empty. | ||
| buffer.push_back(0); | ||
| buffer.push_back(0); | ||
| std::sort(env_entries.begin(), env_entries.end(), | ||
| [](const std::wstring &a, const std::wstring &b) { | ||
| return _wcsicmp(a.c_str(), b.c_str()) < 0; | ||
| }); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why the explicit sort? |
||
|
|
||
| std::vector<wchar_t> buffer; | ||
| buffer.clear(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the purpose of of this? |
||
| for (const auto &env_entry : env_entries) { | ||
| buffer.insert(buffer.end(), env_entry.begin(), env_entry.end()); | ||
| buffer.push_back(L'\0'); | ||
| } | ||
| buffer.push_back(L'\0'); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This really makes this far more expensive than it was previously - we now end up double allocating and copying. |
||
|
|
||
| return buffer; | ||
| } | ||
|
|
||
| static bool GetFlattenedWindowsCommandString(Args args, std::wstring &command) { | ||
| llvm::ErrorOr<std::wstring> | ||
| ProcessLauncherWindows::GetFlattenedWindowsCommandStringW(Args args) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar to the other function, why is this being moved into |
||
| if (args.empty()) | ||
| return false; | ||
| return L""; | ||
|
|
||
| std::vector<llvm::StringRef> args_ref; | ||
| for (auto &entry : args.entries()) | ||
| args_ref.push_back(entry.ref()); | ||
|
|
||
| llvm::ErrorOr<std::wstring> result = | ||
| llvm::sys::flattenWindowsCommandLine(args_ref); | ||
| if (result.getError()) | ||
| return false; | ||
|
|
||
| command = *result; | ||
| return true; | ||
| return llvm::sys::flattenWindowsCommandLine(args_ref); | ||
| } | ||
|
|
||
| HostProcess | ||
|
|
@@ -66,8 +65,8 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info, | |
|
|
||
| std::string executable; | ||
| std::vector<char> environment; | ||
| STARTUPINFOEX startupinfoex = {}; | ||
| STARTUPINFO &startupinfo = startupinfoex.StartupInfo; | ||
| STARTUPINFOEXW startupinfoex = {}; | ||
| STARTUPINFOW &startupinfo = startupinfoex.StartupInfo; | ||
| PROCESS_INFORMATION pi = {}; | ||
|
|
||
| HANDLE stdin_handle = GetStdioHandle(launch_info, STDIN_FILENO); | ||
|
|
@@ -149,28 +148,33 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info, | |
| if (launch_info.GetFlags().Test(eLaunchFlagDisableSTDIO)) | ||
| flags &= ~CREATE_NEW_CONSOLE; | ||
|
|
||
| LPVOID env_block = nullptr; | ||
| ::CreateEnvironmentBuffer(launch_info.GetEnvironment(), environment); | ||
| env_block = environment.data(); | ||
|
|
||
| executable = launch_info.GetExecutableFile().GetPath(); | ||
| std::wstring wcommandLine; | ||
| GetFlattenedWindowsCommandString(launch_info.GetArguments(), wcommandLine); | ||
| std::vector<wchar_t> environment = | ||
| CreateEnvironmentBufferW(launch_info.GetEnvironment()); | ||
| LPVOID env_block = environment.empty() ? nullptr : environment.data(); | ||
|
|
||
| std::wstring wexecutable, wworkingDirectory; | ||
| llvm::ConvertUTF8toWide(executable, wexecutable); | ||
| llvm::ConvertUTF8toWide(launch_info.GetWorkingDirectory().GetPath(), | ||
| wworkingDirectory); | ||
| auto wcommandLineOrErr = | ||
| GetFlattenedWindowsCommandStringW(launch_info.GetArguments()); | ||
| if (!wcommandLineOrErr) { | ||
| error = Status(wcommandLineOrErr.getError()); | ||
| return HostProcess(); | ||
| } | ||
| std::wstring wcommandLine = *wcommandLineOrErr; | ||
| // If the command line is empty, it's best to pass a null pointer to tell | ||
| // CreateProcessW to use the executable name as the command line. If the | ||
| // command line is not empty, its contents may be modified by CreateProcessW. | ||
| WCHAR *pwcommandLine = wcommandLine.empty() ? nullptr : &wcommandLine[0]; | ||
|
|
||
| std::wstring wexecutable, wworkingDirectory; | ||
| llvm::ConvertUTF8toWide(launch_info.GetExecutableFile().GetPath(), | ||
| wexecutable); | ||
| llvm::ConvertUTF8toWide(launch_info.GetWorkingDirectory().GetPath(), | ||
| wworkingDirectory); | ||
|
|
||
| BOOL result = ::CreateProcessW( | ||
| wexecutable.c_str(), pwcommandLine, NULL, NULL, | ||
| /*bInheritHandles=*/!inherited_handles.empty(), flags, env_block, | ||
| wworkingDirectory.size() == 0 ? NULL : wworkingDirectory.c_str(), | ||
| reinterpret_cast<STARTUPINFO *>(&startupinfoex), &pi); | ||
| reinterpret_cast<STARTUPINFOW *>(&startupinfoex), &pi); | ||
|
|
||
| if (!result) { | ||
| // Call GetLastError before we make any other system calls. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this being moved into the type rather than keeping it standalone?