Skip to content

Commit 9edbf83

Browse files
[lldb][windows] fix environment handling in CreateProcessW setup (#168733)
This patch refactors and documents the setup of the `CreateProcessW` invocation in `ProcessLauncherWindows`. It's a dependency of #168729. `CreateEnvironmentBufferW` now sorts the environment variable keys before concatenating them into a string. From [the `CreateProcess` documentation](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw): > An application must manually pass the current directory information to the new process. To do so, the application must explicitly create these environment variable strings, sort them alphabetically (because the system uses a sorted environment), and put them into the environment block. Typically, they will go at the front of the environment block, due to the environment block sort order. `GetFlattenedWindowsCommandStringW` now returns an error which will be surfaced, instead of failing silently. Types were converted to their wide equivalent (i.e appending `W` to them, see `STARTUPINFOEX`) since we are calling the `W` variant of `CreateProcess`.
1 parent b73385d commit 9edbf83

File tree

1 file changed

+66
-42
lines changed

1 file changed

+66
-42
lines changed

lldb/source/Host/windows/ProcessLauncherWindows.cpp

Lines changed: 66 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -21,42 +21,63 @@
2121
using namespace lldb;
2222
using namespace lldb_private;
2323

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'.
24+
/// Create a UTF-16 environment block to use with CreateProcessW.
25+
///
26+
/// The buffer is a sequence 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+
///
30+
/// The keys are sorted to comply with the CreateProcess API calling convention.
31+
///
32+
/// Ensure that the resulting buffer is used in conjunction with
33+
/// CreateProcessW and be sure that dwCreationFlags includes
34+
/// CREATE_UNICODE_ENVIRONMENT.
35+
///
36+
/// \param env The Environment object to convert.
37+
/// \returns The sorted sequence of environment variables and their values,
38+
/// separated by null terminators. The vector is guaranteed to never be empty.
39+
static std::vector<wchar_t> CreateEnvironmentBufferW(const Environment &env) {
40+
std::vector<std::wstring> env_entries;
2941
for (const auto &KV : env) {
30-
std::wstring warg;
31-
if (llvm::ConvertUTF8toWide(Environment::compose(KV), warg)) {
32-
buffer.insert(
33-
buffer.end(), reinterpret_cast<const char *>(warg.c_str()),
34-
reinterpret_cast<const char *>(warg.c_str() + warg.size() + 1));
35-
}
42+
std::wstring wentry;
43+
if (llvm::ConvertUTF8toWide(Environment::compose(KV), wentry))
44+
env_entries.push_back(std::move(wentry));
45+
}
46+
std::sort(env_entries.begin(), env_entries.end(),
47+
[](const std::wstring &a, const std::wstring &b) {
48+
return _wcsicmp(a.c_str(), b.c_str()) < 0;
49+
});
50+
51+
std::vector<wchar_t> buffer;
52+
for (const auto &env_entry : env_entries) {
53+
buffer.insert(buffer.end(), env_entry.begin(), env_entry.end());
54+
buffer.push_back(L'\0');
3655
}
37-
// One null wchar_t (to end the block) is two null bytes
38-
buffer.push_back(0);
39-
buffer.push_back(0);
40-
// Insert extra two bytes, just in case the environment was empty.
41-
buffer.push_back(0);
42-
buffer.push_back(0);
56+
57+
if (buffer.empty())
58+
buffer.push_back(L'\0'); // If there are no environment variables, we have
59+
// to ensure there are 4 zero bytes in the buffer.
60+
buffer.push_back(L'\0');
61+
62+
return buffer;
4363
}
4464

45-
static bool GetFlattenedWindowsCommandString(Args args, std::wstring &command) {
65+
/// Flattens an Args object into a Windows command-line wide string.
66+
///
67+
/// Returns an empty string if args is empty.
68+
///
69+
/// \param args The Args object to flatten.
70+
/// \returns A wide string containing the flattened command line.
71+
static llvm::ErrorOr<std::wstring>
72+
GetFlattenedWindowsCommandStringW(Args args) {
4673
if (args.empty())
47-
return false;
74+
return L"";
4875

4976
std::vector<llvm::StringRef> args_ref;
5077
for (auto &entry : args.entries())
5178
args_ref.push_back(entry.ref());
5279

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;
80+
return llvm::sys::flattenWindowsCommandLine(args_ref);
6081
}
6182

6283
HostProcess
@@ -65,9 +86,8 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info,
6586
error.Clear();
6687

6788
std::string executable;
68-
std::vector<char> environment;
69-
STARTUPINFOEX startupinfoex = {};
70-
STARTUPINFO &startupinfo = startupinfoex.StartupInfo;
89+
STARTUPINFOEXW startupinfoex = {};
90+
STARTUPINFOW &startupinfo = startupinfoex.StartupInfo;
7191
PROCESS_INFORMATION pi = {};
7292

7393
HANDLE stdin_handle = GetStdioHandle(launch_info, STDIN_FILENO);
@@ -149,28 +169,32 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info,
149169
if (launch_info.GetFlags().Test(eLaunchFlagDisableSTDIO))
150170
flags &= ~CREATE_NEW_CONSOLE;
151171

152-
LPVOID env_block = nullptr;
153-
::CreateEnvironmentBuffer(launch_info.GetEnvironment(), environment);
154-
env_block = environment.data();
155-
156-
executable = launch_info.GetExecutableFile().GetPath();
157-
std::wstring wcommandLine;
158-
GetFlattenedWindowsCommandString(launch_info.GetArguments(), wcommandLine);
172+
std::vector<wchar_t> environment =
173+
CreateEnvironmentBufferW(launch_info.GetEnvironment());
159174

160-
std::wstring wexecutable, wworkingDirectory;
161-
llvm::ConvertUTF8toWide(executable, wexecutable);
162-
llvm::ConvertUTF8toWide(launch_info.GetWorkingDirectory().GetPath(),
163-
wworkingDirectory);
175+
auto wcommandLineOrErr =
176+
GetFlattenedWindowsCommandStringW(launch_info.GetArguments());
177+
if (!wcommandLineOrErr) {
178+
error = Status(wcommandLineOrErr.getError());
179+
return HostProcess();
180+
}
181+
std::wstring wcommandLine = *wcommandLineOrErr;
164182
// If the command line is empty, it's best to pass a null pointer to tell
165183
// CreateProcessW to use the executable name as the command line. If the
166184
// command line is not empty, its contents may be modified by CreateProcessW.
167185
WCHAR *pwcommandLine = wcommandLine.empty() ? nullptr : &wcommandLine[0];
168186

187+
std::wstring wexecutable, wworkingDirectory;
188+
llvm::ConvertUTF8toWide(launch_info.GetExecutableFile().GetPath(),
189+
wexecutable);
190+
llvm::ConvertUTF8toWide(launch_info.GetWorkingDirectory().GetPath(),
191+
wworkingDirectory);
192+
169193
BOOL result = ::CreateProcessW(
170194
wexecutable.c_str(), pwcommandLine, NULL, NULL,
171-
/*bInheritHandles=*/!inherited_handles.empty(), flags, env_block,
195+
/*bInheritHandles=*/!inherited_handles.empty(), flags, environment.data(),
172196
wworkingDirectory.size() == 0 ? NULL : wworkingDirectory.c_str(),
173-
reinterpret_cast<STARTUPINFO *>(&startupinfoex), &pi);
197+
reinterpret_cast<STARTUPINFOW *>(&startupinfoex), &pi);
174198

175199
if (!result) {
176200
// Call GetLastError before we make any other system calls.

0 commit comments

Comments
 (0)