[lld] r193298 - [PECOFF] Support embedding resource file into executable.

Rui Ueyama ruiu at google.com
Wed Oct 23 18:39:43 PDT 2013


Author: ruiu
Date: Wed Oct 23 20:39:43 2013
New Revision: 193298

URL: http://llvm.org/viewvc/llvm-project?rev=193298&view=rev
Log:
[PECOFF] Support embedding resource file into executable.

Instead of making the linker to create a manifest XML file in the same
directory as the resulting binary, you can embed the XML as a part of
resource into the executable.

In order to do that, the linker first creates a resource script file containing
the XML file, compile it into a binary resource file with RC.EXE, and then
convert it to a COFF file with CVTRES.EXE.

Modified:
    lld/trunk/include/lld/ReaderWriter/PECOFFLinkingContext.h
    lld/trunk/lib/Driver/WinLinkDriver.cpp

Modified: lld/trunk/include/lld/ReaderWriter/PECOFFLinkingContext.h
URL: http://llvm.org/viewvc/llvm-project/lld/trunk/include/lld/ReaderWriter/PECOFFLinkingContext.h?rev=193298&r1=193297&r2=193298&view=diff
==============================================================================
--- lld/trunk/include/lld/ReaderWriter/PECOFFLinkingContext.h (original)
+++ lld/trunk/include/lld/ReaderWriter/PECOFFLinkingContext.h Wed Oct 23 20:39:43 2013
@@ -20,6 +20,7 @@
 #include "llvm/Support/Allocator.h"
 #include "llvm/Support/COFF.h"
 #include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/FileUtilities.h"
 
 using llvm::COFF::MachineTypes;
 using llvm::COFF::WindowsSubsystem;
@@ -76,6 +77,12 @@ public:
     return _inputSearchPaths;
   }
 
+  void registerTemporaryFile(StringRef path) {
+    std::unique_ptr<llvm::FileRemover> fileRemover(
+        new llvm::FileRemover(Twine(allocateString(path))));
+    _tempFiles.push_back(std::move(fileRemover));
+  }
+
   StringRef searchLibraryFile(StringRef path) const;
 
   /// Returns the decorated name of the given symbol name. On 32-bit x86, it
@@ -247,6 +254,9 @@ private:
   std::vector<StringRef> _inputSearchPaths;
   std::unique_ptr<Reader> _reader;
   std::unique_ptr<Writer> _writer;
+
+  // List of files that will be removed on destruction.
+  std::vector<std::unique_ptr<llvm::FileRemover> > _tempFiles;
 };
 
 } // end namespace lld

Modified: lld/trunk/lib/Driver/WinLinkDriver.cpp
URL: http://llvm.org/viewvc/llvm-project/lld/trunk/lib/Driver/WinLinkDriver.cpp?rev=193298&r1=193297&r2=193298&view=diff
==============================================================================
--- lld/trunk/lib/Driver/WinLinkDriver.cpp (original)
+++ lld/trunk/lib/Driver/WinLinkDriver.cpp Wed Oct 23 20:39:43 2013
@@ -27,12 +27,17 @@
 #include "llvm/Option/Option.h"
 #include "llvm/Support/Path.h"
 #include "llvm/Support/Process.h"
+#include "llvm/Support/Program.h"
 #include "llvm/Support/raw_ostream.h"
 
 namespace lld {
 
 namespace {
 
+//
+// Option definitions
+//
+
 // Create enum with OPT_xxx values for each option in WinLinkOptions.td
 enum {
   OPT_INVALID = 0,
@@ -68,6 +73,10 @@ public:
                  /* ignoreCase */ true) {}
 };
 
+//
+// Functions to parse each command line option
+//
+
 // Split the given string with spaces.
 std::vector<std::string> splitArgList(const std::string &str) {
   std::stringstream stream(str);
@@ -205,23 +214,12 @@ StringRef replaceExtension(PECOFFLinking
   return ctx.allocateString(val.str());
 }
 
-// Create a side-by-side manifest file. The manifest file will convey some
-// information to the linker, such as whether the binary needs to run as
-// Administrator or not. Instead of being placed in the PE/COFF header, it's in
-// XML format for some reason -- I guess it's probably because it's invented in
-// the early dot-com era.
-//
-// Instead of having the linker emit a manifest file as a separate file, you
-// could have the linker embed the contents of XML into the resource section of
-// the executable. The feature is not implemented in LLD yet, though.
-bool createManifestFile(PECOFFLinkingContext &ctx, raw_ostream &diagnostics) {
-  std::string errorInfo;
-  llvm::raw_fd_ostream out(ctx.getManifestOutputPath().data(), errorInfo);
-  if (!errorInfo.empty()) {
-    diagnostics << "Failed to open " << ctx.getManifestOutputPath() << ": "
-                << errorInfo << "\n";
-    return false;
-  }
+// Create a manifest file contents.
+std::string createManifestXml(PECOFFLinkingContext &ctx) {
+  std::string ret;
+  llvm::raw_string_ostream out(ret);
+  // Emit the XML. Note that we do *not* verify that the XML attributes are
+  // syntactically correct. This is intentional for link.exe compatibility.
   out << "<?xml version=\"1.0\" standalone=\"yes\"?>\n"
       << "<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\"\n"
       << "          manifestVersion=\"1.0\">\n"
@@ -242,9 +240,148 @@ bool createManifestFile(PECOFFLinkingCon
         << "  </dependency>\n";
   }
   out << "</assembly>\n";
+  out.flush();
+  return std::move(ret);
+}
+
+// Quote double quotes and backslashes in the given string, so that we can embed
+// the string into a resource script file.
+std::string quoteXml(StringRef str) {
+  std::string ret;
+  ret.reserve(str.size() * 2);
+  StringRef line;
+  for (;;) {
+    if (str.empty())
+      return std::move(ret);
+    llvm::tie(line, str) = str.split("\n");
+    if (!line.empty())
+      continue;
+    ret.append("\"");
+    const char *p = line.data();
+    for (int i = 0, size = line.size(); i < size; ++i) {
+      switch (p[i]) {
+      case '\"':
+      case '\\':
+        ret.append("\\");
+        // fallthrough
+      default:
+        ret.append(1, p[i]);
+      }
+    }
+    ret.append("\"\n");
+  }
+}
+
+// Create a resource file (.res file) containing the manifest XML. This is done
+// in two steps:
+//
+//  1. Create a resource script file containing the XML as a literal string.
+//  2. Run RC.EXE command to compile the script file to a resource file.
+//
+// The temporary file created in step 1 will be deleted on exit from this
+// function. The file created in step 2 will have the same lifetime as the
+// PECOFFLinkingContext.
+bool createManifestResourceFile(PECOFFLinkingContext &ctx,
+                                raw_ostream &diagnostics,
+                                std::string &resFile) {
+  // Create a temporary file for the resource script file.
+  SmallString<128> rcFileSmallString;
+  if (llvm::sys::fs::createTemporaryFile("tmp", "rc", rcFileSmallString)) {
+    diagnostics << "Cannot create a temporary file\n";
+    return false;
+  }
+  StringRef rcFile(rcFileSmallString.str());
+  llvm::FileRemover rcFileRemover((Twine(rcFile)));
+
+  // Open the temporary file for writing.
+  std::string errorInfo;
+  llvm::raw_fd_ostream out(rcFile.data(), errorInfo);
+  if (!errorInfo.empty()) {
+    diagnostics << "Failed to open " << ctx.getManifestOutputPath() << ": "
+                << errorInfo << "\n";
+    return false;
+  }
+
+  // Write resource script to the RC file.
+  out << "#define LANG_ENGLISH 9\n"
+      << "#define SUBLANG_DEFAULT 1\n"
+      << "#define APP_MANIFEST " << ctx.getManifestId() << "\n"
+      << "#define RT_MANIFEST 24\n"
+      << "LANGUAGE LANG_ENGLISH, SUBLANG_DEFAULT\n"
+      << "APP_MANIFEST RT_MANIFEST {\n"
+      << quoteXml(createManifestXml(ctx))
+      << "}\n";
+  out.close();
+
+  // Create output resource file.
+  SmallString<128> resFileSmallString;
+  if (llvm::sys::fs::createTemporaryFile("tmp", "res", resFileSmallString)) {
+    diagnostics << "Cannot create a temporary file";
+    return false;
+  }
+  resFile = resFileSmallString.str();
+
+  // Register the resource file path so that the file will be deleted when the
+  // context's destructor is called.
+  ctx.registerTemporaryFile(resFile);
+
+  // Run RC.EXE /fo tmp.res tmp.rc
+  std::string program = "rc.exe";
+  std::string programPath = llvm::sys::FindProgramByName(program);
+  if (programPath.empty()) {
+    diagnostics << "Unable to find " << program << " in PATH\n";
+    return false;
+  }
+  std::vector<const char *> args;
+  args.push_back(programPath.c_str());
+  args.push_back("/fo");
+  args.push_back(resFile.c_str());
+  args.push_back(rcFile.data());
+  args.push_back(nullptr);
+
+  if (llvm::sys::ExecuteAndWait(programPath.c_str(), &args[0]) != 0) {
+    llvm::errs() << program << " failed\n";
+    return false;
+  }
+  return true;
+}
+
+// Create a side-by-side manifest file. The side-by-side manifest file is a
+// separate XML file having ".manifest" extension. It will be created in the
+// same directory as the resulting executable.
+bool createSideBySideManifestFile(PECOFFLinkingContext &ctx,
+                                  raw_ostream &diagnostics) {
+  std::string errorInfo;
+  llvm::raw_fd_ostream out(ctx.getManifestOutputPath().data(), errorInfo);
+  if (!errorInfo.empty()) {
+    diagnostics << "Failed to open " << ctx.getManifestOutputPath() << ": "
+                << errorInfo << "\n";
+    return false;
+  }
+  out << createManifestXml(ctx);
   return true;
 }
 
+// Create the a side-by-side manifest file, or create a resource file for the
+// manifest file and add it to the input graph.
+//
+// The manifest file will convey some information to the linker, such as whether
+// the binary needs to run as Administrator or not. Instead of being placed in
+// the PE/COFF header, it's in XML format for some reason -- I guess it's
+// probably because it's invented in the early dot-com era.
+bool createManifest(PECOFFLinkingContext &ctx, raw_ostream &diagnostics) {
+  if (ctx.getEmbedManifest()) {
+    std::string resourceFilePath;
+    if (!createManifestResourceFile(ctx, diagnostics, resourceFilePath))
+      return false;
+    std::unique_ptr<InputElement> inputElement(
+        new PECOFFFileNode(ctx, resourceFilePath));
+    ctx.inputGraph().addInputElement(std::move(inputElement));
+    return true;
+  }
+  return createSideBySideManifestFile(ctx, diagnostics);
+}
+
 // Handle /failifmismatch option.
 bool handleFailIfMismatchOption(StringRef option,
                                 std::map<StringRef, StringRef> &mustMatch,
@@ -265,6 +402,10 @@ bool handleFailIfMismatchOption(StringRe
   return false;
 }
 
+//
+// Environment variable
+//
+
 // Process "LINK" environment variable. If defined, the value of the variable
 // should be processed as command line arguments.
 std::vector<const char *> processLinkEnv(PECOFFLinkingContext &context,
@@ -344,6 +485,10 @@ parseArgs(int argc, const char *argv[],
 
 } // namespace
 
+//
+// Main driver
+//
+
 ErrorOr<StringRef> PECOFFFileNode::getPath(const LinkingContext &) const {
   if (_path.endswith(".lib"))
     return _ctx.searchLibraryFile(_path);
@@ -365,6 +510,12 @@ bool WinLinkDriver::linkPECOFF(int argc,
   processLibEnv(context);
   if (!parse(newargv.size() - 1, &newargv[0], context, diagnostics))
     return false;
+
+  // Create the file if needed.
+  if (context.getCreateManifest())
+    if (!createManifest(context, diagnostics))
+      return false;
+
   return link(context, diagnostics);
 }
 
@@ -714,11 +865,6 @@ WinLinkDriver::parse(int argc, const cha
   for (auto &e : inputElements)
     ctx.inputGraph().addInputElement(std::move(e));
 
-  // Create the side-by-side manifest file if needed.
-  if (!isReadingDirectiveSection && ctx.getCreateManifest())
-    if (!createManifestFile(ctx, diagnostics))
-      return false;
-
   // Validate the combination of options used.
   return ctx.validate(diagnostics);
 }





More information about the llvm-commits mailing list