COFF: Support Windows resource files.
authorRui Ueyama <ruiu@google.com>
Sun, 14 Jun 2015 21:50:50 +0000 (21:50 +0000)
committerRui Ueyama <ruiu@google.com>
Sun, 14 Jun 2015 21:50:50 +0000 (21:50 +0000)
Resource files are data files containing i18n messages, icon images, etc.
MSVC has a tool to convert a resource file to a regular COFF file so that
you can just link that file to embed resources to an executable.

However, you can directly pass resource files to the linker. If you do that,
the linker invokes the tool automatically. This patch implements that feature.

llvm-svn: 239704

lld/COFF/Chunks.cpp
lld/COFF/Driver.cpp
lld/COFF/Driver.h
lld/COFF/DriverUtils.cpp
lld/COFF/Writer.cpp
lld/test/COFF/Inputs/resource.res [new file with mode: 0644]
lld/test/COFF/resource.test [new file with mode: 0644]

index c8b2262..cc7e724 100644 (file)
@@ -31,9 +31,10 @@ SectionChunk::SectionChunk(ObjectFile *F, const coff_section *H, uint32_t SI)
   // Initialize SectionName.
   File->getCOFFObj()->getSectionName(Header, SectionName);
 
-  // Bit [20:24] contains section alignment.
-  unsigned Shift = ((Header->Characteristics & 0xF00000) >> 20) - 1;
-  Align = uint32_t(1) << Shift;
+  // Bit [20:24] contains section alignment. Both 0 and 1 mean alignment 1.
+  unsigned Shift = (Header->Characteristics >> 20) & 0xF;
+  if (Shift > 0)
+    Align = uint32_t(1) << (Shift - 1);
 
   // When a new chunk is created, we don't if if it's going to make it
   // to the final output. Initially all sections are unmarked in terms
index 257c9b4..a9ef480 100644 (file)
@@ -25,6 +25,7 @@
 #include "llvm/Support/Process.h"
 #include "llvm/Support/TargetSelect.h"
 #include "llvm/Support/raw_ostream.h"
+#include <algorithm>
 #include <memory>
 
 using namespace llvm;
@@ -58,23 +59,26 @@ static std::string getOutputPath(StringRef Path) {
 
 // Opens a file. Path has to be resolved already.
 // Newly created memory buffers are owned by this driver.
-ErrorOr<std::unique_ptr<InputFile>> LinkerDriver::openFile(StringRef Path) {
+ErrorOr<MemoryBufferRef> LinkerDriver::openFile(StringRef Path) {
   auto MBOrErr = MemoryBuffer::getFile(Path);
   if (auto EC = MBOrErr.getError())
     return EC;
   std::unique_ptr<MemoryBuffer> MB = std::move(MBOrErr.get());
   MemoryBufferRef MBRef = MB->getMemBufferRef();
   OwningMBs.push_back(std::move(MB)); // take ownership
+  return MBRef;
+}
 
+static std::unique_ptr<InputFile> createFile(MemoryBufferRef MB) {
   // File type is detected by contents, not by file extension.
-  file_magic Magic = identify_magic(MBRef.getBuffer());
+  file_magic Magic = identify_magic(MB.getBuffer());
   if (Magic == file_magic::archive)
-    return std::unique_ptr<InputFile>(new ArchiveFile(MBRef));
+    return std::unique_ptr<InputFile>(new ArchiveFile(MB));
   if (Magic == file_magic::bitcode)
-    return std::unique_ptr<InputFile>(new BitcodeFile(MBRef));
+    return std::unique_ptr<InputFile>(new BitcodeFile(MB));
   if (Config->OutputFile == "")
-    Config->OutputFile = getOutputPath(Path);
-  return std::unique_ptr<InputFile>(new ObjectFile(MBRef));
+    Config->OutputFile = getOutputPath(MB.getBufferIdentifier());
+  return std::unique_ptr<InputFile>(new ObjectFile(MB));
 }
 
 // Parses .drectve section contents and returns a list of files
@@ -94,10 +98,10 @@ LinkerDriver::parseDirectives(StringRef S,
   // Handle /defaultlib
   for (auto *Arg : Args->filtered(OPT_defaultlib)) {
     if (Optional<StringRef> Path = findLib(Arg->getValue())) {
-      auto FileOrErr = openFile(*Path);
-      if (auto EC = FileOrErr.getError())
+      ErrorOr<MemoryBufferRef> MBOrErr = openFile(*Path);
+      if (auto EC = MBOrErr.getError())
         return EC;
-      std::unique_ptr<InputFile> File = std::move(FileOrErr.get());
+      std::unique_ptr<InputFile> File = createFile(MBOrErr.get());
       Res->push_back(std::move(File));
     }
   }
@@ -312,13 +316,22 @@ bool LinkerDriver::link(int Argc, const char *Argv[]) {
 
   // Create a list of input files. Files can be given as arguments
   // for /defaultlib option.
-  std::vector<StringRef> Inputs;
+  std::vector<StringRef> InputPaths;
+  std::vector<MemoryBufferRef> Inputs;
   for (auto *Arg : Args->filtered(OPT_INPUT))
     if (Optional<StringRef> Path = findFile(Arg->getValue()))
-      Inputs.push_back(*Path);
+      InputPaths.push_back(*Path);
   for (auto *Arg : Args->filtered(OPT_defaultlib))
     if (Optional<StringRef> Path = findLib(Arg->getValue()))
-      Inputs.push_back(*Path);
+      InputPaths.push_back(*Path);
+  for (StringRef Path : InputPaths) {
+    ErrorOr<MemoryBufferRef> MBOrErr = openFile(Path);
+    if (auto EC = MBOrErr.getError()) {
+      llvm::errs() << "cannot open " << Path << ": " << EC.message() << "\n";
+      return false;
+    }
+    Inputs.push_back(MBOrErr.get());
+  }
 
   // Create a symbol table.
   SymbolTable Symtab;
@@ -331,19 +344,32 @@ bool LinkerDriver::link(int Argc, const char *Argv[]) {
     Config->GCRoots.insert(Sym);
   }
 
+  // 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.
+  auto IsResource = [](MemoryBufferRef MB) {
+    return identify_magic(MB.getBuffer()) == file_magic::windows_resource;
+  };
+  auto It = std::stable_partition(Inputs.begin(), Inputs.end(), IsResource);
+  if (It != Inputs.begin()) {
+    std::vector<MemoryBufferRef> Files(Inputs.begin(), It);
+    auto MBOrErr = convertResToCOFF(Files);
+    if (MBOrErr.getError())
+      return false;
+    std::unique_ptr<MemoryBuffer> MB = std::move(MBOrErr.get());
+    Inputs = std::vector<MemoryBufferRef>(It, Inputs.end());
+    Inputs.push_back(MB->getMemBufferRef());
+    OwningMBs.push_back(std::move(MB)); // take ownership
+  }
+
   // Parse all input files and put all symbols to the symbol table.
   // The symbol table will take care of name resolution.
-  for (StringRef Path : Inputs) {
-    auto FileOrErr = openFile(Path);
-    if (auto EC = FileOrErr.getError()) {
-      llvm::errs() << Path << ": " << EC.message() << "\n";
-      return false;
-    }
-    std::unique_ptr<InputFile> File = std::move(FileOrErr.get());
+  for (MemoryBufferRef MB : Inputs) {
+    std::unique_ptr<InputFile> File = createFile(MB);
     if (Config->Verbose)
       llvm::outs() << "Reading " << File->getName() << "\n";
     if (auto EC = Symtab.addFile(std::move(File))) {
-      llvm::errs() << Path << ": " << EC.message() << "\n";
+      llvm::errs() << File->getName() << ": " << EC.message() << "\n";
       return false;
     }
   }
index 45bcfbf..136ecd2 100644 (file)
@@ -76,7 +76,7 @@ private:
   ArgParser Parser;
 
   // Opens a file. Path has to be resolved already.
-  ErrorOr<std::unique_ptr<InputFile>> openFile(StringRef Path);
+  ErrorOr<MemoryBufferRef> openFile(StringRef Path);
 
   // Searches a file from search paths.
   Optional<StringRef> findFile(StringRef Filename);
@@ -121,6 +121,11 @@ std::error_code parseSubsystem(StringRef Arg, WindowsSubsystem *Sys,
 // incompatible objects.
 std::error_code checkFailIfMismatch(llvm::opt::InputArgList *Args);
 
+// Convert Windows resource files (.res files) to a .obj file
+// using cvtres.exe.
+ErrorOr<std::unique_ptr<MemoryBuffer>>
+convertResToCOFF(const std::vector<MemoryBufferRef> &MBs);
+
 // Create enum with OPT_xxx values for each option in Options.td
 enum {
   OPT_INVALID = 0,
index 22fdb8b..9f65b45 100644 (file)
@@ -24,8 +24,8 @@
 #include "llvm/Option/ArgList.h"
 #include "llvm/Option/Option.h"
 #include "llvm/Support/CommandLine.h"
-#include "llvm/Support/Path.h"
 #include "llvm/Support/Process.h"
+#include "llvm/Support/Program.h"
 #include "llvm/Support/raw_ostream.h"
 #include <memory>
 
@@ -137,6 +137,45 @@ std::error_code checkFailIfMismatch(llvm::opt::InputArgList *Args) {
   return std::error_code();
 }
 
+// Convert Windows resource files (.res files) to a .obj file
+// using cvtres.exe.
+ErrorOr<std::unique_ptr<MemoryBuffer>>
+convertResToCOFF(const std::vector<MemoryBufferRef> &MBs) {
+  // Find cvtres.exe.
+  std::string Prog = "cvtres.exe";
+  ErrorOr<std::string> ExeOrErr = llvm::sys::findProgramByName(Prog);
+  if (auto EC = ExeOrErr.getError()) {
+    llvm::errs() << "unable to find " << Prog << " in PATH: "
+                 << EC.message() << "\n";
+    return make_error_code(LLDError::InvalidOption);
+  }
+  llvm::BumpPtrAllocator Alloc;
+  llvm::BumpPtrStringSaver S(Alloc);
+  const char *Exe = S.save(ExeOrErr.get());
+
+  // Create an output file path.
+  SmallString<128> Path;
+  if (llvm::sys::fs::createTemporaryFile("resource", "obj", Path))
+    return make_error_code(LLDError::InvalidOption);
+
+  // Execute cvtres.exe.
+  std::vector<const char *> Args;
+  Args.push_back(Exe);
+  Args.push_back("/machine:x64");
+  Args.push_back("/readonly");
+  Args.push_back("/nologo");
+  Args.push_back(S.save("/out:" + Path));
+  for (MemoryBufferRef MB : MBs)
+    Args.push_back(S.save(MB.getBufferIdentifier()));
+  Args.push_back(nullptr);
+  llvm::errs() << "\n";
+  if (llvm::sys::ExecuteAndWait(Args[0], Args.data()) != 0) {
+    llvm::errs() << Exe << " failed\n";
+    return make_error_code(LLDError::InvalidOption);
+  }
+  return MemoryBuffer::getFile(Path);
+}
+
 // Create OptTable
 
 // Create prefix string literals used in Options.td
index d4ccc58..f85651a 100644 (file)
@@ -281,6 +281,10 @@ void Writer::writeHeader() {
     DataDirectory[IAT].RelativeVirtualAddress = Idata->getIATRVA();
     DataDirectory[IAT].Size = Idata->getIATSize();
   }
+  if (OutputSection *Sec = findSection(".rsrc")) {
+    DataDirectory[RESOURCE_TABLE].RelativeVirtualAddress = Sec->getRVA();
+    DataDirectory[RESOURCE_TABLE].Size = Sec->getRawSize();
+  }
 
   // Section table
   // Name field in the section table is 8 byte long. Longer names need
diff --git a/lld/test/COFF/Inputs/resource.res b/lld/test/COFF/Inputs/resource.res
new file mode 100644 (file)
index 0000000..f1c799f
Binary files /dev/null and b/lld/test/COFF/Inputs/resource.res differ
diff --git a/lld/test/COFF/resource.test b/lld/test/COFF/resource.test
new file mode 100644 (file)
index 0000000..025a52e
--- /dev/null
@@ -0,0 +1,14 @@
+# REQUIRES: winres
+
+# RUN: yaml2obj < %p/Inputs/ret42.yaml > %t.obj
+# RUN: lld -flavor link2 /out:%t.exe %t.obj %p/Inputs/resource.res
+
+# Check if the binary contains UTF-16LE string "Hello" copied from resource.res.
+# RUN: FileCheck --check-prefix=EXE %s < %t.exe
+
+EXE: {{H.e.l.l.o}}
+
+# RUN: llvm-readobj -file-headers %t.exe | FileCheck  --check-prefix=HEADER %s
+
+HEADER: ResourceTableRVA: 0x1000
+HEADER: ResourceTableSize: 0x200