Windows: fix command line when invoking linker batch file

This command line sent to CreateProcess():

"C:\LDC\bin\amd64.bat" 1 2 "3" 4

is internally transformed to:

C:\Windows\system32\cmd.exe /c "C:\LDC\bin\amd64.bat" 1 2 "3" 4

Now guess what - cmd.exe treats the command string after /c or /k in a
very special way if it begins with double quotes and is followed by other
quoted args. In this example, it tries to invoke:

C:\LDC\bin\amd64.bat" 1 2 "3

Another example:

C:\Windows\system32\cmd.exe /c "C:\L D C\bin\amd64.bat" 1 2 "3" 4
=> C:\L

The fix seems to be enclosing the whole command with additional double
quotes (but no additional escaping of the command!) and using /s for
cmd.exe:

C:\Windows\system32\cmd.exe /s /c ""C:\L D C\bin\amd64.bat" 1 2 "3" 4"

So the command is quoted, but not escaped, i.e., no regular argument to
cmd.exe, so we can't use LLVM's executeAndWait() to launch the cmd.exe
process. :/

Additionally, LLVM's arg-quoting code is not public, so we can't use it
to quote & escape the regular args inside the command.

I therefore implemented a simple quoteArgs() variant which should
suffice for our use cases.
This commit is contained in:
Martin 2015-10-17 20:06:05 +02:00
parent 86f151d0f3
commit 6dfbfe60e9

View file

@ -24,6 +24,7 @@
#include "llvm/Support/Path.h"
#if _WIN32
#include "llvm/Support/SystemUtils.h"
#include <Windows.h>
#endif
//////////////////////////////////////////////////////////////////////////////
@ -278,26 +279,160 @@ static int linkObjToBinaryGcc(bool sharedLib)
//////////////////////////////////////////////////////////////////////////////
static bool setupMSVCEnvironment(std::string& tool, std::vector<std::string>& args)
{
// if the VSINSTALLDIR environment variable is NOT set,
// the environment is most likely not set up properly
bool setup = !getenv("VSINSTALLDIR");
#ifdef _WIN32
if (setup)
namespace windows
{
bool needsQuotes(const std::string& arg)
{
// use a helper batch file to let MSVC set up the environment and
// then invoke the tool
args.push_back(tool); // tool is first arg for batch file
tool = exe_path::prependBinDir(
if ((!arg.empty() && std::find(arg.begin(), arg.end(), ' ') == arg.end()) ||
(arg.size() > 1 && arg[0] == '"' && arg.back() == '"'))
return false;
return true;
}
size_t countPrecedingBackslashes(const std::string& arg, size_t index)
{
size_t count = 0;
for (size_t i = index - 1; i >= 0; --i)
{
if (arg[i] != '\\')
break;
++count;
}
return count;
}
std::string quoteArg(const std::string& arg)
{
if (!needsQuotes(arg))
return arg;
std::string quotedArg;
quotedArg.reserve(3 + 2 * arg.size()); // worst case
quotedArg.push_back('"');
const size_t argLength = arg.length();
for (size_t i = 0; i < argLength; ++i)
{
if (arg[i] == '"')
{
// Escape all preceding backslashes (if any).
// Note that we *don't* need to escape runs of backslashes that don't
// precede a double quote! See MSDN:
// http://msdn.microsoft.com/en-us/library/17w5ykft%28v=vs.85%29.aspx
quotedArg.append(countPrecedingBackslashes(arg, i), '\\');
// Escape the double quote.
quotedArg.push_back('\\');
}
quotedArg.push_back(arg[i]);
}
// Make sure our final double quote doesn't get escaped by a trailing backslash.
quotedArg.append(countPrecedingBackslashes(arg, argLength), '\\');
quotedArg.push_back('"');
return quotedArg;
}
}
int executeMsvcToolAndWait(const std::string& tool, const std::vector<std::string>& args, bool verbose)
{
llvm::SmallString<1024> commandLine; // full command line incl. executable
// if the VSINSTALLDIR environment variable is NOT set,
// the MSVC environment needs to be set up
const bool needMsvcSetup = !getenv("VSINSTALLDIR");
if (needMsvcSetup)
{
/* <command line> => %ComSpec% /s /c "<batch file> <command line>"
*
* cmd.exe /c treats the following string argument (the command)
* in a very peculiar way if it starts with a double-quote.
* By adding /s and enclosing the command in extra double-quotes
* (WITHOUT additionally escaping the command), the command will
* be parsed properly.
*/
std::string cmdPath = getenv("ComSpec");
std::string batchFile = exe_path::prependBinDir(
global.params.targetTriple.isArch64Bit() ? "amd64.bat" : "x86.bat");
commandLine.append(windows::quoteArg(cmdPath));
commandLine.append(" /s /c \"");
commandLine.append(windows::quoteArg(batchFile));
commandLine.push_back(' ');
commandLine.append(windows::quoteArg(tool));
}
else
tool = getProgram(tool.c_str());
{
std::string toolPath = getProgram(tool.c_str());
commandLine.append(windows::quoteArg(toolPath));
}
return setup;
// append (quoted) args
for (size_t i = 0; i < args.size(); ++i)
{
commandLine.push_back(' ');
commandLine.append(windows::quoteArg(args[i]));
}
if (needMsvcSetup)
commandLine.push_back('"');
const char* finalCommandLine = commandLine.c_str();
if (verbose)
{
fprintf(global.stdmsg, finalCommandLine);
fprintf(global.stdmsg, "\n");
fflush(global.stdmsg);
}
STARTUPINFO si;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
PROCESS_INFORMATION pi;
ZeroMemory(&pi, sizeof(pi));
DWORD exitCode;
// according to MSDN, only CreateProcessW (unicode) may modify the passed command line
if (!CreateProcess(NULL, const_cast<char*>(finalCommandLine), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi))
{
exitCode = -1;
}
else
{
if (WaitForSingleObject(pi.hProcess, INFINITE) != 0 ||
!GetExitCodeProcess(pi.hProcess, &exitCode))
exitCode = -2;
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
if (exitCode != 0)
error(Loc(), "%s failed with status: %d", tool.c_str(), exitCode);
return exitCode;
}
#else // !_WIN32
int executeMsvcToolAndWait(const std::string&, const std::vector<std::string>&, bool)
{
assert(0);
}
#endif
//////////////////////////////////////////////////////////////////////////////
static int linkObjToBinaryWin(bool sharedLib)
@ -309,8 +444,6 @@ static int linkObjToBinaryWin(bool sharedLib)
// build arguments
std::vector<std::string> args;
const bool setupMSVC = setupMSVCEnvironment(tool, args);
args.push_back("/NOLOGO");
// specify that the image will contain a table of safe exception handlers (32bit only)
@ -402,7 +535,6 @@ static int linkObjToBinaryWin(bool sharedLib)
Logger::println("Linking with: ");
std::vector<std::string>::const_iterator I = args.begin(), E = args.end();
if (setupMSVC) ++I; // skip link.exe, the first arg for the batch file
Stream logstr = Logger::cout();
for (; I != E; ++I)
if (!(*I).empty())
@ -410,23 +542,23 @@ static int linkObjToBinaryWin(bool sharedLib)
logstr << "\n"; // FIXME where's flush ?
// try to call linker
return executeToolAndWait(tool, args, global.params.verbose);
return executeMsvcToolAndWait(tool, args, global.params.verbose);
}
//////////////////////////////////////////////////////////////////////////////
int linkObjToBinary(bool sharedLib)
{
int status;
int exitCode;
#if LDC_LLVM_VER >= 305
if (global.params.targetTriple.isWindowsMSVCEnvironment())
#else
if (global.params.targetTriple.getOS() == llvm::Triple::Win32)
#endif
status = linkObjToBinaryWin(sharedLib);
exitCode = linkObjToBinaryWin(sharedLib);
else
status = linkObjToBinaryGcc(sharedLib);
return status;
exitCode = linkObjToBinaryGcc(sharedLib);
return exitCode;
}
//////////////////////////////////////////////////////////////////////////////
@ -447,9 +579,6 @@ int createStaticLibrary()
// build arguments
std::vector<std::string> args;
if (isTargetWindows)
setupMSVCEnvironment(tool, args);
// ask ar to create a new library
if (!isTargetWindows)
args.push_back("rcs");
@ -501,8 +630,12 @@ int createStaticLibrary()
CreateDirectoryOnDisk(libName);
// try to call archiver
int rc = executeToolAndWait(tool, args, global.params.verbose);
return rc;
int exitCode;
if (isTargetWindows)
exitCode = executeMsvcToolAndWait(tool, args, global.params.verbose);
else
exitCode = executeToolAndWait(tool, args, global.params.verbose);
return exitCode;
}
//////////////////////////////////////////////////////////////////////////////