COFF: Support /manifest{,uac,dependency,file} options.

The linker has to create an XML file for each executable.
This patch supports that feature.

You can optionally embed an XML file to an executable as .rsrc
section. If you choose to do that (by passing /manifest:embed
option), the linker has to create a textual resource file
containing an XML file, compile that using rc.exe to a binary
resource file, conver that resource file to a COFF file using
cvtres.exe, and then link that COFF file. This patch implements
that feature too.

llvm-svn: 239978
This commit is contained in:
Rui Ueyama 2015-06-18 00:12:42 +00:00
parent c6e8bfc41d
commit 24c5fd0419
5 changed files with 283 additions and 2 deletions

View File

@ -37,6 +37,8 @@ struct Export {
// Global configuration.
struct Configuration {
enum ManifestKind { SideBySide, Embed, No };
llvm::COFF::MachineTypes MachineType = llvm::COFF::IMAGE_FILE_MACHINE_AMD64;
bool Verbose = false;
WindowsSubsystem Subsystem = llvm::COFF::IMAGE_SUBSYSTEM_UNKNOWN;
@ -55,6 +57,15 @@ struct Configuration {
bool DLL = false;
std::vector<Export> Exports;
// Options for manifest files.
ManifestKind Manifest = SideBySide;
int ManifestID = 1;
StringRef ManifestDependency;
bool ManifestUAC = true;
StringRef ManifestLevel = "'asInvoker'";
StringRef ManifestUIAccess = "'false'";
StringRef ManifestFile;
// Used by /failifmismatch option.
std::map<StringRef, StringRef> MustMatch;

View File

@ -241,8 +241,10 @@ bool LinkerDriver::link(int Argc, const char *Argv[]) {
Config->Verbose = true;
// Handle /dll
if (Args->hasArg(OPT_dll))
if (Args->hasArg(OPT_dll)) {
Config->DLL = true;
Config->ManifestID = 2;
}
// Handle /entry
if (auto *Arg = Args->getLastArg(OPT_entry))
@ -367,6 +369,30 @@ bool LinkerDriver::link(int Argc, const char *Argv[]) {
return false;
}
// Handle /manifest
if (auto *Arg = Args->getLastArg(OPT_manifest_colon)) {
if (auto EC = parseManifest(Arg->getValue())) {
llvm::errs() << "/manifest: " << EC.message() << "\n";
return false;
}
}
// Handle /manifestuac
if (auto *Arg = Args->getLastArg(OPT_manifestuac)) {
if (auto EC = parseManifestUAC(Arg->getValue())) {
llvm::errs() << "/manifestuac: " << EC.message() << "\n";
return false;
}
}
// Handle /manifestdependency
if (auto *Arg = Args->getLastArg(OPT_manifestdependency))
Config->ManifestDependency = Arg->getValue();
// Handle /manifestfile
if (auto *Arg = Args->getLastArg(OPT_manifestfile))
Config->ManifestFile = Arg->getValue();
// Handle miscellaneous boolean flags.
if (Args->hasArg(OPT_allowbind_no)) Config->AllowBind = false;
if (Args->hasArg(OPT_allowisolation_no)) Config->AllowIsolation = false;
@ -405,6 +431,16 @@ bool LinkerDriver::link(int Argc, const char *Argv[]) {
Config->GCRoots.insert(Sym);
}
// Windows specific -- Create a resource file containing a manifest file.
if (Config->Manifest == Configuration::Embed) {
auto MBOrErr = createManifestRes();
if (MBOrErr.getError())
return false;
std::unique_ptr<MemoryBuffer> MB = std::move(MBOrErr.get());
Inputs.push_back(MB->getMemBufferRef());
OwningMBs.push_back(std::move(MB)); // take ownership
}
// Windows specific -- Input files can be Windows resource files (.res files).
// We invoke cvtres.exe to convert resource files to a regular COFF file
// then link the result file normally.
@ -513,6 +549,11 @@ bool LinkerDriver::link(int Argc, const char *Argv[]) {
return false;
}
// Windows specific -- Create a side-by-side manifest file.
if (Config->Manifest == Configuration::SideBySide)
if (createSideBySideManifest())
return false;
// Write the result.
Writer Out(&Symtab);
if (auto EC = Out.write(Config->OutputFile)) {

View File

@ -119,6 +119,16 @@ std::error_code parseVersion(StringRef Arg, uint32_t *Major, uint32_t *Minor);
std::error_code parseSubsystem(StringRef Arg, WindowsSubsystem *Sys,
uint32_t *Major, uint32_t *Minor);
// Parses a string in the form of "EMBED[,=<integer>]|NO".
std::error_code parseManifest(StringRef Arg);
// Parses a string in the form of "level=<string>|uiAccess=<string>"
std::error_code parseManifestUAC(StringRef Arg);
// Create a resource file containing a manifest XML.
ErrorOr<std::unique_ptr<MemoryBuffer>> createManifestRes();
std::error_code createSideBySideManifest();
// Used for dllexported symbols.
ErrorOr<Export> parseExport(StringRef Arg);
std::error_code fixupExports();

View File

@ -60,7 +60,10 @@ public:
Args.insert(Args.begin(), Exe);
Args.push_back(nullptr);
if (llvm::sys::ExecuteAndWait(Args[0], Args.data()) != 0) {
llvm::errs() << Exe << " failed\n";
for (const char *S : Args)
if (S)
llvm::errs() << S << " ";
llvm::errs() << "failed\n";
return make_error_code(LLDError::InvalidOption);
}
return std::error_code();
@ -151,6 +154,163 @@ std::error_code parseSubsystem(StringRef Arg, WindowsSubsystem *Sys,
return std::error_code();
}
// Parses a string in the form of "EMBED[,=<integer>]|NO".
// Results are directly written to Config.
std::error_code parseManifest(StringRef Arg) {
if (Arg.equals_lower("no")) {
Config->Manifest = Configuration::No;
return std::error_code();
}
if (!Arg.startswith_lower("embed"))
return make_error_code(LLDError::InvalidOption);
Config->Manifest = Configuration::Embed;
Arg = Arg.substr(strlen("embed"));
if (Arg.empty())
return std::error_code();
if (!Arg.startswith_lower(",id="))
return make_error_code(LLDError::InvalidOption);
Arg = Arg.substr(strlen(",id="));
if (Arg.getAsInteger(0, Config->ManifestID))
return make_error_code(LLDError::InvalidOption);
return std::error_code();
}
// Parses a string in the form of "level=<string>|uiAccess=<string>|NO".
// Results are directly written to Config.
std::error_code parseManifestUAC(StringRef Arg) {
if (Arg.equals_lower("no")) {
Config->ManifestUAC = false;
return std::error_code();
}
for (;;) {
Arg = Arg.ltrim();
if (Arg.empty())
return std::error_code();
if (Arg.startswith_lower("level=")) {
Arg = Arg.substr(strlen("level="));
std::tie(Config->ManifestLevel, Arg) = Arg.split(" ");
continue;
}
if (Arg.startswith_lower("uiaccess=")) {
Arg = Arg.substr(strlen("uiaccess="));
std::tie(Config->ManifestUIAccess, Arg) = Arg.split(" ");
continue;
}
return make_error_code(LLDError::InvalidOption);
}
}
// Quote each line with "". Existing double-quote is converted
// to two double-quotes.
static void quoteAndPrint(raw_ostream &Out, StringRef S) {
while (!S.empty()) {
StringRef Line;
std::tie(Line, S) = S.split("\n");
if (Line.empty())
continue;
Out << '\"';
for (int I = 0, E = Line.size(); I != E; ++I) {
if (Line[I] == '\"') {
Out << "\"\"";
} else {
Out << Line[I];
}
}
Out << "\"\n";
}
}
// Create a manifest file contents.
static std::string createManifestXml() {
std::string S;
llvm::raw_string_ostream OS(S);
// Emit the XML. Note that we do *not* verify that the XML attributes are
// syntactically correct. This is intentional for link.exe compatibility.
OS << "<?xml version=\"1.0\" standalone=\"yes\"?>\n"
<< "<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\"\n"
<< " manifestVersion=\"1.0\">\n";
if (Config->ManifestUAC) {
OS << " <trustInfo>\n"
<< " <security>\n"
<< " <requestedPrivileges>\n"
<< " <requestedExecutionLevel level=" << Config->ManifestLevel
<< " uiAccess=" << Config->ManifestUIAccess << "/>\n"
<< " </requestedPrivileges>\n"
<< " </security>\n"
<< " </trustInfo>\n";
if (!Config->ManifestDependency.empty()) {
OS << " <dependency>\n"
<< " <dependentAssembly>\n"
<< " <assemblyIdentity " << Config->ManifestDependency << " />\n"
<< " </dependentAssembly>\n"
<< " </dependency>\n";
}
}
OS << "</assembly>\n";
OS.flush();
return S;
}
// Create a resource file containing a manifest XML.
ErrorOr<std::unique_ptr<MemoryBuffer>> createManifestRes() {
// Create a temporary file for the resource script file.
SmallString<128> RCPath;
if (sys::fs::createTemporaryFile("tmp", "rc", RCPath)) {
llvm::errs() << "cannot create a temporary file\n";
return make_error_code(LLDError::InvalidOption);
}
FileRemover RCRemover(RCPath);
// Open the temporary file for writing.
std::error_code EC;
llvm::raw_fd_ostream Out(RCPath, EC, sys::fs::F_Text);
if (EC) {
llvm::errs() << "failed to open " << RCPath << ": " << EC.message() << "\n";
return make_error_code(LLDError::InvalidOption);
}
// Write resource script to the RC file.
Out << "#define LANG_ENGLISH 9\n"
<< "#define SUBLANG_DEFAULT 1\n"
<< "#define APP_MANIFEST " << Config->ManifestID << "\n"
<< "#define RT_MANIFEST 24\n"
<< "LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT\n"
<< "APP_MANIFEST RT_MANIFEST {\n";
quoteAndPrint(Out, createManifestXml());
Out << "}\n";
Out.close();
// Create output resource file.
SmallString<128> ResPath;
if (sys::fs::createTemporaryFile("tmp", "res", ResPath)) {
llvm::errs() << "cannot create a temporary file\n";
return make_error_code(LLDError::InvalidOption);
}
Executor E("rc.exe");
E.add("/fo");
E.add(ResPath.str());
E.add("/nologo");
E.add(RCPath.str());
if (auto EC = E.run())
return EC;
return MemoryBuffer::getFile(ResPath);
}
std::error_code createSideBySideManifest() {
std::string Path = Config->ManifestFile;
if (Path == "")
Path = (Twine(Config->OutputFile) + ".manifest").str();
std::error_code EC;
llvm::raw_fd_ostream Out(Path, EC, llvm::sys::fs::F_Text);
if (EC) {
llvm::errs() << EC.message() << "\n";
return EC;
}
Out << createManifestXml();
return std::error_code();
}
// Parse a string in the form of
// "<name>[=<internalname>][,@ordinal[,NONAME]][,DATA][,PRIVATE]".
// Used for parsing /export arguments.

View File

@ -0,0 +1,59 @@
# RUN: yaml2obj %p/Inputs/ret42.yaml > %t.obj
# RUN: lld -flavor link2 /out:%t.exe %t.obj
# RUN: FileCheck -check-prefix=MANIFEST %s < %t.exe.manifest
MANIFEST: <?xml version="1.0" standalone="yes"?>
MANIFEST: <assembly xmlns="urn:schemas-microsoft-com:asm.v1"
MANIFEST: manifestVersion="1.0">
MANIFEST: <trustInfo>
MANIFEST: <security>
MANIFEST: <requestedPrivileges>
MANIFEST: <requestedExecutionLevel level='asInvoker' uiAccess='false'/>
MANIFEST: </requestedPrivileges>
MANIFEST: </security>
MANIFEST: </trustInfo>
MANIFEST: </assembly>
# RUN: lld -flavor link2 /out:%t.exe /manifestuac:"level='requireAdministrator' uiAccess='true'" %t.obj
# RUN: FileCheck -check-prefix=UAC %s < %t.exe.manifest
UAC: <?xml version="1.0" standalone="yes"?>
UAC: <assembly xmlns="urn:schemas-microsoft-com:asm.v1"
UAC: manifestVersion="1.0">
UAC: <trustInfo>
UAC: <security>
UAC: <requestedPrivileges>
UAC: <requestedExecutionLevel level='requireAdministrator' uiAccess='true'/>
UAC: </requestedPrivileges>
UAC: </security>
UAC: </trustInfo>
UAC: </assembly>
# RUN: lld -flavor link2 /out:%t.exe /manifestdependency:"foo='bar'" %t.obj
# RUN: FileCheck -check-prefix=DEPENDENCY %s < %t.exe.manifest
DEPENDENCY: <?xml version="1.0" standalone="yes"?>
DEPENDENCY: <assembly xmlns="urn:schemas-microsoft-com:asm.v1"
DEPENDENCY: manifestVersion="1.0">
DEPENDENCY: <trustInfo>
DEPENDENCY: <security>
DEPENDENCY: <requestedPrivileges>
DEPENDENCY: <requestedExecutionLevel level='asInvoker' uiAccess='false'/>
DEPENDENCY: </requestedPrivileges>
DEPENDENCY: </security>
DEPENDENCY: </trustInfo>
DEPENDENCY: <dependency>
DEPENDENCY: <dependentAssembly>
DEPENDENCY: <assemblyIdentity foo='bar' />
DEPENDENCY: </dependentAssembly>
DEPENDENCY: </dependency>
DEPENDENCY: </assembly>
# RUN: lld -flavor link2 /out:%t.exe /manifestuac:no %t.obj
# RUN: FileCheck -check-prefix=NOUAC %s < %t.exe.manifest
NOUAC: <?xml version="1.0" standalone="yes"?>
NOUAC: <assembly xmlns="urn:schemas-microsoft-com:asm.v1"
NOUAC: manifestVersion="1.0">
NOUAC: </assembly>