Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion lldb/include/lldb/Host/windows/ProcessLauncherWindows.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -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);
Copy link
Member

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?


/// 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>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise

GetFlattenedWindowsCommandStringW(Args args);
};
}

#endif
#endif
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing newline

82 changes: 43 additions & 39 deletions lldb/source/Host/windows/ProcessLauncherWindows.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Copy link
Member

Choose a reason for hiding this comment

The 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 ProcessLauncherWindows. The equivalent signature would be:

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)) {
Copy link
Member

Choose a reason for hiding this comment

The 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;
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the explicit sort?


std::vector<wchar_t> buffer;
buffer.clear();
Copy link
Collaborator

Choose a reason for hiding this comment

The 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');
Copy link
Member

Choose a reason for hiding this comment

The 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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the other function, why is this being moved into ProcessLauncherWindows?

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
Expand All @@ -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);
Expand Down Expand Up @@ -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.
Expand Down
Loading