From 24c5fd04196e8008e9cf47799e012f5856d4dc4b Mon Sep 17 00:00:00 2001 From: Rui Ueyama Date: Thu, 18 Jun 2015 00:12:42 +0000 Subject: [PATCH] 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 --- lld/COFF/Config.h | 11 +++ lld/COFF/Driver.cpp | 43 +++++++++++- lld/COFF/Driver.h | 10 +++ lld/COFF/DriverUtils.cpp | 162 +++++++++++++++++++++++++++++++++++++++++++- lld/test/COFF/manifest.test | 59 ++++++++++++++++ 5 files changed, 283 insertions(+), 2 deletions(-) create mode 100644 lld/test/COFF/manifest.test diff --git a/lld/COFF/Config.h b/lld/COFF/Config.h index 28aba45..1e6195c 100644 --- a/lld/COFF/Config.h +++ b/lld/COFF/Config.h @@ -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 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 MustMatch; diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp index af31667..e724a8f 100644 --- a/lld/COFF/Driver.cpp +++ b/lld/COFF/Driver.cpp @@ -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 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)) { diff --git a/lld/COFF/Driver.h b/lld/COFF/Driver.h index 8b43e36..1d588d8 100644 --- a/lld/COFF/Driver.h +++ b/lld/COFF/Driver.h @@ -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[,=]|NO". +std::error_code parseManifest(StringRef Arg); + +// Parses a string in the form of "level=|uiAccess=" +std::error_code parseManifestUAC(StringRef Arg); + +// Create a resource file containing a manifest XML. +ErrorOr> createManifestRes(); +std::error_code createSideBySideManifest(); + // Used for dllexported symbols. ErrorOr parseExport(StringRef Arg); std::error_code fixupExports(); diff --git a/lld/COFF/DriverUtils.cpp b/lld/COFF/DriverUtils.cpp index e5c436f..4cb10a7 100644 --- a/lld/COFF/DriverUtils.cpp +++ b/lld/COFF/DriverUtils.cpp @@ -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[,=]|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=|uiAccess=|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 << "\n" + << "\n"; + if (Config->ManifestUAC) { + OS << " \n" + << " \n" + << " \n" + << " \n" + << " \n" + << " \n" + << " \n"; + if (!Config->ManifestDependency.empty()) { + OS << " \n" + << " \n" + << " ManifestDependency << " />\n" + << " \n" + << " \n"; + } + } + OS << "\n"; + OS.flush(); + return S; +} + +// Create a resource file containing a manifest XML. +ErrorOr> 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 // "[=][,@ordinal[,NONAME]][,DATA][,PRIVATE]". // Used for parsing /export arguments. diff --git a/lld/test/COFF/manifest.test b/lld/test/COFF/manifest.test new file mode 100644 index 0000000..6b3806e --- /dev/null +++ b/lld/test/COFF/manifest.test @@ -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: +MANIFEST: +MANIFEST: +MANIFEST: +MANIFEST: +MANIFEST: +MANIFEST: +MANIFEST: +MANIFEST: +MANIFEST: + +# RUN: lld -flavor link2 /out:%t.exe /manifestuac:"level='requireAdministrator' uiAccess='true'" %t.obj +# RUN: FileCheck -check-prefix=UAC %s < %t.exe.manifest + +UAC: +UAC: +UAC: +UAC: +UAC: +UAC: +UAC: +UAC: +UAC: +UAC: + +# RUN: lld -flavor link2 /out:%t.exe /manifestdependency:"foo='bar'" %t.obj +# RUN: FileCheck -check-prefix=DEPENDENCY %s < %t.exe.manifest + +DEPENDENCY: +DEPENDENCY: +DEPENDENCY: +DEPENDENCY: +DEPENDENCY: +DEPENDENCY: +DEPENDENCY: +DEPENDENCY: +DEPENDENCY: +DEPENDENCY: +DEPENDENCY: +DEPENDENCY: +DEPENDENCY: +DEPENDENCY: +DEPENDENCY: + +# RUN: lld -flavor link2 /out:%t.exe /manifestuac:no %t.obj +# RUN: FileCheck -check-prefix=NOUAC %s < %t.exe.manifest + +NOUAC: +NOUAC: +NOUAC: -- 2.7.4