Skip to content

Commit 676be9d

Browse files
Merge pull request #12015 from charles-zablit/charles-zablit/lldb/windows/refactor-processlauncher-to-6.3
🍒 [lldb][windows] fix environment handling in CreateProcessW setup
2 parents 791b434 + 83cb9f7 commit 676be9d

File tree

5 files changed

+178
-102
lines changed

5 files changed

+178
-102
lines changed

lldb/include/lldb/Host/windows/ProcessLauncherWindows.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
#include "lldb/Host/ProcessLauncher.h"
1313
#include "lldb/Host/windows/windows.h"
14+
#include "llvm/Support/ErrorOr.h"
1415

1516
namespace lldb_private {
1617

@@ -23,6 +24,36 @@ class ProcessLauncherWindows : public ProcessLauncher {
2324

2425
protected:
2526
HANDLE GetStdioHandle(const ProcessLaunchInfo &launch_info, int fd);
27+
28+
/// Get the list of Windows handles that should be inherited by the child
29+
/// process and update `STARTUPINFOEXW` with the handle list.
30+
///
31+
/// If no handles need to be inherited, an empty vector is returned.
32+
///
33+
/// Otherwise, the function populates the
34+
/// `PROC_THREAD_ATTRIBUTE_HANDLE_LIST` attribute in `startupinfoex` with the
35+
/// collected handles using `UpdateProcThreadAttribute`. On success, the
36+
/// vector of inherited handles is returned.
37+
///
38+
/// \param launch_info
39+
/// The process launch configuration.
40+
///
41+
/// \param startupinfoex
42+
/// The extended STARTUPINFO structure for the process being created.
43+
///
44+
/// \param stdout_handle
45+
/// \param stderr_handle
46+
/// \param stdin_handle
47+
/// Optional explicit standard stream handles to use for the child process.
48+
///
49+
/// \returns
50+
/// `std::vector<HANDLE>` containing all handles that the child must
51+
/// inherit.
52+
llvm::ErrorOr<std::vector<HANDLE>>
53+
GetInheritedHandles(const ProcessLaunchInfo &launch_info,
54+
STARTUPINFOEXW &startupinfoex,
55+
HANDLE stdout_handle = NULL, HANDLE stderr_handle = NULL,
56+
HANDLE stdin_handle = NULL);
2657
};
2758
}
2859

lldb/source/Host/windows/ProcessLauncherWindows.cpp

Lines changed: 118 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -14,61 +14,81 @@
1414
#include "llvm/ADT/SmallVector.h"
1515
#include "llvm/Support/ConvertUTF.h"
1616
#include "llvm/Support/Program.h"
17+
#include "llvm/Support/WindowsError.h"
1718

1819
#include <string>
1920
#include <vector>
2021

2122
using namespace lldb;
2223
using namespace lldb_private;
2324

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

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

4977
std::vector<llvm::StringRef> args_ref;
5078
for (auto &entry : args.entries())
5179
args_ref.push_back(entry.ref());
5280

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;
81+
return llvm::sys::flattenWindowsCommandLine(args_ref);
6082
}
6183

6284
HostProcess
6385
ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info,
6486
Status &error) {
6587
error.Clear();
6688

67-
std::string executable;
68-
std::vector<char> environment;
69-
STARTUPINFOEX startupinfoex = {};
70-
STARTUPINFO &startupinfo = startupinfoex.StartupInfo;
71-
PROCESS_INFORMATION pi = {};
89+
STARTUPINFOEXW startupinfoex = {};
90+
startupinfoex.StartupInfo.cb = sizeof(startupinfoex);
91+
startupinfoex.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
7292

7393
HANDLE stdin_handle = GetStdioHandle(launch_info, STDIN_FILENO);
7494
HANDLE stdout_handle = GetStdioHandle(launch_info, STDOUT_FILENO);
@@ -82,23 +102,6 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info,
82102
::CloseHandle(stderr_handle);
83103
});
84104

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-
102105
SIZE_T attributelist_size = 0;
103106
InitializeProcThreadAttributeList(/*lpAttributeList=*/nullptr,
104107
/*dwAttributeCount=*/1, /*dwFlags=*/0,
@@ -116,29 +119,21 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info,
116119
}
117120
auto delete_attributelist = llvm::make_scope_exit(
118121
[&] { DeleteProcThreadAttributeList(startupinfoex.lpAttributeList); });
119-
for (size_t i = 0; i < launch_info.GetNumFileActions(); ++i) {
120-
const FileAction *act = launch_info.GetFileActionAtIndex(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-
}
122+
123+
auto inherited_handles_or_err = GetInheritedHandles(
124+
launch_info, startupinfoex, stdout_handle, stderr_handle, stdin_handle);
125+
if (!inherited_handles_or_err) {
126+
error = Status(inherited_handles_or_err.getError());
127+
return HostProcess();
134128
}
129+
std::vector<HANDLE> inherited_handles = *inherited_handles_or_err;
135130

136131
const char *hide_console_var =
137132
getenv("LLDB_LAUNCH_INFERIORS_WITHOUT_CONSOLE");
138133
if (hide_console_var &&
139134
llvm::StringRef(hide_console_var).equals_insensitive("true")) {
140-
startupinfo.dwFlags |= STARTF_USESHOWWINDOW;
141-
startupinfo.wShowWindow = SW_HIDE;
135+
startupinfoex.StartupInfo.dwFlags |= STARTF_USESHOWWINDOW;
136+
startupinfoex.StartupInfo.wShowWindow = SW_HIDE;
142137
}
143138

144139
DWORD flags = CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT |
@@ -149,28 +144,34 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info,
149144
if (launch_info.GetFlags().Test(eLaunchFlagDisableSTDIO))
150145
flags &= ~CREATE_NEW_CONSOLE;
151146

152-
LPVOID env_block = nullptr;
153-
::CreateEnvironmentBuffer(launch_info.GetEnvironment(), environment);
154-
env_block = environment.data();
147+
std::vector<wchar_t> environment =
148+
CreateEnvironmentBufferW(launch_info.GetEnvironment());
155149

156-
executable = launch_info.GetExecutableFile().GetPath();
157-
std::wstring wcommandLine;
158-
GetFlattenedWindowsCommandString(launch_info.GetArguments(), wcommandLine);
159-
160-
std::wstring wexecutable, wworkingDirectory;
161-
llvm::ConvertUTF8toWide(executable, wexecutable);
162-
llvm::ConvertUTF8toWide(launch_info.GetWorkingDirectory().GetPath(),
163-
wworkingDirectory);
150+
auto wcommandLineOrErr =
151+
GetFlattenedWindowsCommandStringW(launch_info.GetArguments());
152+
if (!wcommandLineOrErr) {
153+
error = Status(wcommandLineOrErr.getError());
154+
return HostProcess();
155+
}
156+
std::wstring wcommandLine = *wcommandLineOrErr;
164157
// If the command line is empty, it's best to pass a null pointer to tell
165158
// CreateProcessW to use the executable name as the command line. If the
166159
// command line is not empty, its contents may be modified by CreateProcessW.
167160
WCHAR *pwcommandLine = wcommandLine.empty() ? nullptr : &wcommandLine[0];
168161

162+
std::wstring wexecutable, wworkingDirectory;
163+
llvm::ConvertUTF8toWide(launch_info.GetExecutableFile().GetPath(),
164+
wexecutable);
165+
llvm::ConvertUTF8toWide(launch_info.GetWorkingDirectory().GetPath(),
166+
wworkingDirectory);
167+
168+
PROCESS_INFORMATION pi = {};
169+
169170
BOOL result = ::CreateProcessW(
170171
wexecutable.c_str(), pwcommandLine, NULL, NULL,
171-
/*bInheritHandles=*/!inherited_handles.empty(), flags, env_block,
172+
/*bInheritHandles=*/!inherited_handles.empty(), flags, environment.data(),
172173
wworkingDirectory.size() == 0 ? NULL : wworkingDirectory.c_str(),
173-
reinterpret_cast<STARTUPINFO *>(&startupinfoex), &pi);
174+
reinterpret_cast<STARTUPINFOW *>(&startupinfoex), &pi);
174175

175176
if (!result) {
176177
// Call GetLastError before we make any other system calls.
@@ -191,6 +192,45 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info,
191192
return HostProcess(pi.hProcess);
192193
}
193194

195+
llvm::ErrorOr<std::vector<HANDLE>> ProcessLauncherWindows::GetInheritedHandles(
196+
const ProcessLaunchInfo &launch_info, STARTUPINFOEXW &startupinfoex,
197+
HANDLE stdout_handle, HANDLE stderr_handle, HANDLE stdin_handle) {
198+
std::vector<HANDLE> inherited_handles;
199+
200+
startupinfoex.StartupInfo.hStdError =
201+
stderr_handle ? stderr_handle : GetStdHandle(STD_ERROR_HANDLE);
202+
startupinfoex.StartupInfo.hStdInput =
203+
stdin_handle ? stdin_handle : GetStdHandle(STD_INPUT_HANDLE);
204+
startupinfoex.StartupInfo.hStdOutput =
205+
stdout_handle ? stdout_handle : GetStdHandle(STD_OUTPUT_HANDLE);
206+
207+
if (startupinfoex.StartupInfo.hStdError)
208+
inherited_handles.push_back(startupinfoex.StartupInfo.hStdError);
209+
if (startupinfoex.StartupInfo.hStdInput)
210+
inherited_handles.push_back(startupinfoex.StartupInfo.hStdInput);
211+
if (startupinfoex.StartupInfo.hStdOutput)
212+
inherited_handles.push_back(startupinfoex.StartupInfo.hStdOutput);
213+
214+
for (size_t i = 0; i < launch_info.GetNumFileActions(); ++i) {
215+
const FileAction *act = launch_info.GetFileActionAtIndex(i);
216+
if (act->GetAction() == FileAction::eFileActionDuplicate &&
217+
act->GetFD() == act->GetActionArgument())
218+
inherited_handles.push_back(reinterpret_cast<HANDLE>(act->GetFD()));
219+
}
220+
221+
if (inherited_handles.empty())
222+
return inherited_handles;
223+
224+
if (!UpdateProcThreadAttribute(
225+
startupinfoex.lpAttributeList, /*dwFlags=*/0,
226+
PROC_THREAD_ATTRIBUTE_HANDLE_LIST, inherited_handles.data(),
227+
inherited_handles.size() * sizeof(HANDLE),
228+
/*lpPreviousValue=*/nullptr, /*lpReturnSize=*/nullptr))
229+
return llvm::mapWindowsError(::GetLastError());
230+
231+
return inherited_handles;
232+
}
233+
194234
HANDLE
195235
ProcessLauncherWindows::GetStdioHandle(const ProcessLaunchInfo &launch_info,
196236
int fd) {

lldb/source/Interpreter/ScriptInterpreter.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ SymbolContext ScriptInterpreter::GetOpaqueTypeFromSBSymbolContext(
143143
return {};
144144
}
145145

146-
std::optional<MemoryRegionInfo>
146+
std::optional<lldb_private::MemoryRegionInfo>
147147
ScriptInterpreter::GetOpaqueTypeFromSBMemoryRegionInfo(
148148
const lldb::SBMemoryRegionInfo &mem_region) const {
149149
if (!mem_region.m_opaque_up)

lldb/source/Target/Process.cpp

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6779,7 +6779,7 @@ Status Process::WriteMemoryTags(lldb::addr_t addr, size_t len,
67796779

67806780
// Create a CoreFileMemoryRange from a MemoryRegionInfo
67816781
static CoreFileMemoryRange
6782-
CreateCoreFileMemoryRange(const MemoryRegionInfo &region) {
6782+
CreateCoreFileMemoryRange(const lldb_private::MemoryRegionInfo &region) {
67836783
const addr_t addr = region.GetRange().GetRangeBase();
67846784
llvm::AddressRange range(addr, addr + region.GetRange().GetByteSize());
67856785
return {range, region.GetLLDBPermissions()};
@@ -6788,7 +6788,7 @@ CreateCoreFileMemoryRange(const MemoryRegionInfo &region) {
67886788
// Add dirty pages to the core file ranges and return true if dirty pages
67896789
// were added. Return false if the dirty page information is not valid or in
67906790
// the region.
6791-
static bool AddDirtyPages(const MemoryRegionInfo &region,
6791+
static bool AddDirtyPages(const lldb_private::MemoryRegionInfo &region,
67926792
CoreFileMemoryRanges &ranges) {
67936793
const auto &dirty_page_list = region.GetDirtyPageList();
67946794
if (!dirty_page_list)
@@ -6827,8 +6827,8 @@ static bool AddDirtyPages(const MemoryRegionInfo &region,
68276827
// given region. If the region has dirty page information, only dirty pages
68286828
// will be added to \a ranges, else the entire range will be added to \a
68296829
// ranges.
6830-
static void AddRegion(const MemoryRegionInfo &region, bool try_dirty_pages,
6831-
CoreFileMemoryRanges &ranges) {
6830+
static void AddRegion(const lldb_private::MemoryRegionInfo &region,
6831+
bool try_dirty_pages, CoreFileMemoryRanges &ranges) {
68326832
// Don't add empty ranges.
68336833
if (region.GetRange().GetByteSize() == 0)
68346834
return;
@@ -6851,7 +6851,7 @@ static void SaveDynamicLoaderSections(Process &process,
68516851
if (!dyld)
68526852
return;
68536853

6854-
std::vector<MemoryRegionInfo> dynamic_loader_mem_regions;
6854+
std::vector<lldb_private::MemoryRegionInfo> dynamic_loader_mem_regions;
68556855
std::function<bool(const lldb_private::Thread &)> save_thread_predicate =
68566856
[&](const lldb_private::Thread &t) -> bool {
68576857
return options.ShouldThreadBeSaved(t.GetID());
@@ -6976,10 +6976,11 @@ static void GetCoreFileSaveRangesStackOnly(Process &process,
69766976

69776977
// TODO: We should refactor CoreFileMemoryRanges to use the lldb range type, and
69786978
// then add an intersect method on it, or MemoryRegionInfo.
6979-
static MemoryRegionInfo Intersect(const MemoryRegionInfo &lhs,
6980-
const MemoryRegionInfo::RangeType &rhs) {
6979+
static lldb_private::MemoryRegionInfo
6980+
Intersect(const lldb_private::MemoryRegionInfo &lhs,
6981+
const lldb_private::MemoryRegionInfo::RangeType &rhs) {
69816982

6982-
MemoryRegionInfo region_info;
6983+
lldb_private::MemoryRegionInfo region_info;
69836984
region_info.SetLLDBPermissions(lhs.GetLLDBPermissions());
69846985
region_info.GetRange() = lhs.GetRange().Intersect(rhs);
69856986

0 commit comments

Comments
 (0)