From 2bf6a122387d57e1f5e9bf52bfd11e6a8849ab6b Mon Sep 17 00:00:00 2001 From: Rui Ueyama Date: Sun, 14 Jun 2015 21:50:50 +0000 Subject: [PATCH] COFF: Support Windows resource files. 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 | 7 ++-- lld/COFF/Driver.cpp | 66 ++++++++++++++++++++++++++------------ lld/COFF/Driver.h | 7 +++- lld/COFF/DriverUtils.cpp | 41 ++++++++++++++++++++++- lld/COFF/Writer.cpp | 4 +++ lld/test/COFF/Inputs/resource.res | Bin 0 -> 108 bytes lld/test/COFF/resource.test | 14 ++++++++ 7 files changed, 114 insertions(+), 25 deletions(-) create mode 100644 lld/test/COFF/Inputs/resource.res create mode 100644 lld/test/COFF/resource.test diff --git a/lld/COFF/Chunks.cpp b/lld/COFF/Chunks.cpp index c8b2262..cc7e724 100644 --- a/lld/COFF/Chunks.cpp +++ b/lld/COFF/Chunks.cpp @@ -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 diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp index 257c9b4..a9ef480 100644 --- a/lld/COFF/Driver.cpp +++ b/lld/COFF/Driver.cpp @@ -25,6 +25,7 @@ #include "llvm/Support/Process.h" #include "llvm/Support/TargetSelect.h" #include "llvm/Support/raw_ostream.h" +#include #include 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> LinkerDriver::openFile(StringRef Path) { +ErrorOr LinkerDriver::openFile(StringRef Path) { auto MBOrErr = MemoryBuffer::getFile(Path); if (auto EC = MBOrErr.getError()) return EC; std::unique_ptr MB = std::move(MBOrErr.get()); MemoryBufferRef MBRef = MB->getMemBufferRef(); OwningMBs.push_back(std::move(MB)); // take ownership + return MBRef; +} +static std::unique_ptr 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(new ArchiveFile(MBRef)); + return std::unique_ptr(new ArchiveFile(MB)); if (Magic == file_magic::bitcode) - return std::unique_ptr(new BitcodeFile(MBRef)); + return std::unique_ptr(new BitcodeFile(MB)); if (Config->OutputFile == "") - Config->OutputFile = getOutputPath(Path); - return std::unique_ptr(new ObjectFile(MBRef)); + Config->OutputFile = getOutputPath(MB.getBufferIdentifier()); + return std::unique_ptr(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 Path = findLib(Arg->getValue())) { - auto FileOrErr = openFile(*Path); - if (auto EC = FileOrErr.getError()) + ErrorOr MBOrErr = openFile(*Path); + if (auto EC = MBOrErr.getError()) return EC; - std::unique_ptr File = std::move(FileOrErr.get()); + std::unique_ptr 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 Inputs; + std::vector InputPaths; + std::vector Inputs; for (auto *Arg : Args->filtered(OPT_INPUT)) if (Optional Path = findFile(Arg->getValue())) - Inputs.push_back(*Path); + InputPaths.push_back(*Path); for (auto *Arg : Args->filtered(OPT_defaultlib)) if (Optional Path = findLib(Arg->getValue())) - Inputs.push_back(*Path); + InputPaths.push_back(*Path); + for (StringRef Path : InputPaths) { + ErrorOr 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 Files(Inputs.begin(), It); + auto MBOrErr = convertResToCOFF(Files); + if (MBOrErr.getError()) + return false; + std::unique_ptr MB = std::move(MBOrErr.get()); + Inputs = std::vector(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 File = std::move(FileOrErr.get()); + for (MemoryBufferRef MB : Inputs) { + std::unique_ptr 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; } } diff --git a/lld/COFF/Driver.h b/lld/COFF/Driver.h index 45bcfbf..136ecd2 100644 --- a/lld/COFF/Driver.h +++ b/lld/COFF/Driver.h @@ -76,7 +76,7 @@ private: ArgParser Parser; // Opens a file. Path has to be resolved already. - ErrorOr> openFile(StringRef Path); + ErrorOr openFile(StringRef Path); // Searches a file from search paths. Optional 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> +convertResToCOFF(const std::vector &MBs); + // Create enum with OPT_xxx values for each option in Options.td enum { OPT_INVALID = 0, diff --git a/lld/COFF/DriverUtils.cpp b/lld/COFF/DriverUtils.cpp index 22fdb8ba..9f65b45 100644 --- a/lld/COFF/DriverUtils.cpp +++ b/lld/COFF/DriverUtils.cpp @@ -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 @@ -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> +convertResToCOFF(const std::vector &MBs) { + // Find cvtres.exe. + std::string Prog = "cvtres.exe"; + ErrorOr 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 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 diff --git a/lld/COFF/Writer.cpp b/lld/COFF/Writer.cpp index d4ccc58..f85651a 100644 --- a/lld/COFF/Writer.cpp +++ b/lld/COFF/Writer.cpp @@ -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 index 0000000000000000000000000000000000000000..f1c799fbbb08f4fe197f029008a1c54fd911fbfc GIT binary patch literal 108 zcmZQzU|>)H;{X347|28cT0oux5dZ(r2E>eDIRgPs7BB-$urhcsq%!0HVLn0-D+>Sx CaSE^i literal 0 HcmV?d00001 diff --git a/lld/test/COFF/resource.test b/lld/test/COFF/resource.test new file mode 100644 index 0000000..025a52e --- /dev/null +++ b/lld/test/COFF/resource.test @@ -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 -- 2.7.4