diff --git a/lld/COFF/DriverUtils.cpp b/lld/COFF/DriverUtils.cpp index d4c72e7c1ca6..39d582469640 100644 --- a/lld/COFF/DriverUtils.cpp +++ b/lld/COFF/DriverUtils.cpp @@ -20,12 +20,15 @@ #include "Symbols.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/StringSwitch.h" +#include "llvm/BinaryFormat/COFF.h" #include "llvm/Object/COFF.h" +#include "llvm/Object/WindowsResource.h" #include "llvm/Option/Arg.h" #include "llvm/Option/ArgList.h" #include "llvm/Option/Option.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/FileUtilities.h" +#include "llvm/Support/MathExtras.h" #include "llvm/Support/Process.h" #include "llvm/Support/Program.h" #include "llvm/Support/raw_ostream.h" @@ -41,6 +44,9 @@ namespace lld { namespace coff { namespace { +const uint16_t SUBLANG_ENGLISH_US = 0x0409; +const uint16_t RT_MANIFEST = 24; + class Executor { public: explicit Executor(StringRef S) : Prog(Saver.save(S)) {} @@ -260,26 +266,6 @@ void parseManifestUAC(StringRef Arg) { } } -// 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"; - } -} - // An RAII temporary file class that automatically removes a temporary file. namespace { class TemporaryFile { @@ -393,38 +379,62 @@ static std::string createManifestXml() { return readFile(File2.Path); } +static std::unique_ptr +createMemoryBufferForManifestRes(size_t ManifestSize) { + size_t ResSize = alignTo( + object::WIN_RES_MAGIC_SIZE + object::WIN_RES_NULL_ENTRY_SIZE + + sizeof(object::WinResHeaderPrefix) + sizeof(object::WinResIDs) + + sizeof(object::WinResHeaderSuffix) + ManifestSize, + object::WIN_RES_DATA_ALIGNMENT); + return MemoryBuffer::getNewMemBuffer(ResSize); +} + +static void writeResFileHeader(char *&Buf) { + memcpy(Buf, COFF::WinResMagic, sizeof(COFF::WinResMagic)); + Buf += sizeof(COFF::WinResMagic); + memset(Buf, 0, object::WIN_RES_NULL_ENTRY_SIZE); + Buf += object::WIN_RES_NULL_ENTRY_SIZE; +} + +static void writeResEntryHeader(char *&Buf, size_t ManifestSize) { + // Write the prefix. + auto *Prefix = reinterpret_cast(Buf); + Prefix->DataSize = ManifestSize; + Prefix->HeaderSize = sizeof(object::WinResHeaderPrefix) + + sizeof(object::WinResIDs) + + sizeof(object::WinResHeaderSuffix); + Buf += sizeof(object::WinResHeaderPrefix); + + // Write the Type/Name IDs. + auto *IDs = reinterpret_cast(Buf); + IDs->setType(RT_MANIFEST); + IDs->setName(Config->ManifestID); + Buf += sizeof(object::WinResIDs); + + // Write the suffix. + auto *Suffix = reinterpret_cast(Buf); + Suffix->DataVersion = 0; + Suffix->MemoryFlags = object::WIN_RES_PURE_MOVEABLE; + Suffix->Language = SUBLANG_ENGLISH_US; + Suffix->Version = 0; + Suffix->Characteristics = 0; + Buf += sizeof(object::WinResHeaderSuffix); +} + // Create a resource file containing a manifest XML. std::unique_ptr createManifestRes() { - // Create a temporary file for the resource script file. - TemporaryFile RCFile("manifest", "rc"); + std::string Manifest = createManifestXml(); - // Open the temporary file for writing. - std::error_code EC; - raw_fd_ostream Out(RCFile.Path, EC, sys::fs::F_Text); - if (EC) - fatal(EC, "failed to open " + RCFile.Path); + std::unique_ptr Res = + createMemoryBufferForManifestRes(Manifest.size()); - // 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(); + char *Buf = const_cast(Res->getBufferStart()); + writeResFileHeader(Buf); + writeResEntryHeader(Buf, Manifest.size()); - // Create output resource file. - TemporaryFile ResFile("output-resource", "res"); - - Executor E("rc.exe"); - E.add("/fo"); - E.add(ResFile.Path); - E.add("/nologo"); - E.add(RCFile.Path); - E.run(); - return ResFile.getMemoryBuffer(); + // Copy the manifest data into the .res file. + std::copy(Manifest.begin(), Manifest.end(), Buf); + return Res; } void createSideBySideManifest() { @@ -595,40 +605,22 @@ void checkFailIfMismatch(StringRef Arg) { // using cvtres.exe. std::unique_ptr convertResToCOFF(const std::vector &MBs) { - // Create an output file path. - TemporaryFile File("resource-file", "obj"); + object::WindowsResourceParser Parser; - // Execute cvtres.exe. - Executor E("cvtres.exe"); - E.add("/machine:" + machineToStr(Config->Machine)); - E.add("/readonly"); - E.add("/nologo"); - E.add("/out:" + Twine(File.Path)); - - // We must create new files because the memory buffers we have may have no - // underlying file still existing on the disk. - // It happens if it was created from a TemporaryFile, which usually delete - // the file just after creating the MemoryBuffer. - std::vector ResFiles; - ResFiles.reserve(MBs.size()); for (MemoryBufferRef MB : MBs) { - // We store the temporary file in a vector to avoid deletion - // before running cvtres - ResFiles.emplace_back("resource-file", "res"); - TemporaryFile& ResFile = ResFiles.back(); - // Write the content of the resource in a temporary file - std::error_code EC; - raw_fd_ostream OS(ResFile.Path, EC, sys::fs::F_None); - if (EC) - fatal(EC, "failed to open " + ResFile.Path); - OS << MB.getBuffer(); - OS.close(); - - E.add(ResFile.Path); + std::unique_ptr Bin = check(object::createBinary(MB)); + object::WindowsResource *RF = dyn_cast(Bin.get()); + if (!RF) + fatal("cannot compile non-resource file as resource"); + if (auto EC = Parser.parse(RF)) + fatal(EC, "failed to parse .res file"); } - E.run(); - return File.getMemoryBuffer(); + Expected> E = + llvm::object::writeWindowsResourceCOFF(Config->Machine, Parser); + if (!E) + fatal(errorToErrorCode(E.takeError()), "failed to write .res to COFF"); + return std::move(E.get()); } // Run MSVC link.exe for given in-memory object files. diff --git a/lld/test/COFF/combined-resources.test b/lld/test/COFF/combined-resources.test index b7466a397627..e8d5d65008d5 100644 --- a/lld/test/COFF/combined-resources.test +++ b/lld/test/COFF/combined-resources.test @@ -4,8 +4,6 @@ // > rc /fo combined-resources.res /nologo combined-resources.rc // > rc /fo combined-resources-2.res /nologo combined-resources-2.rc -# REQUIRES: winres - # RUN: yaml2obj < %p/Inputs/ret42.yaml > %t.obj # RUN: lld-link /out:%t.exe /entry:main %t.obj %p/Inputs/resource.res \ # RUN: %p/Inputs/combined-resources.res %p/Inputs/combined-resources-2.res diff --git a/lld/test/COFF/def-name.test b/lld/test/COFF/def-name.test index c48424a1bff0..b971007eda37 100644 --- a/lld/test/COFF/def-name.test +++ b/lld/test/COFF/def-name.test @@ -1,5 +1,3 @@ -# REQUIRES: winres - # RUN: rm -rf %t # RUN: mkdir -p %t # RUN: cd %t diff --git a/lld/test/COFF/dll.test b/lld/test/COFF/dll.test index db4b4056552c..abd39f4eeb24 100644 --- a/lld/test/COFF/dll.test +++ b/lld/test/COFF/dll.test @@ -1,5 +1,3 @@ -# REQUIRES: winres - # RUN: yaml2obj < %p/Inputs/export.yaml > %t.obj # RUN: lld-link /out:%t.dll /dll %t.obj /export:exportfn1 /export:exportfn2 \ # RUN: /export:mangled diff --git a/lld/test/COFF/dllimport-gc.test b/lld/test/COFF/dllimport-gc.test index 54ae773e793f..d8523fbc7dae 100644 --- a/lld/test/COFF/dllimport-gc.test +++ b/lld/test/COFF/dllimport-gc.test @@ -1,5 +1,3 @@ -# REQUIRES: winres - # RUN: yaml2obj < %p/Inputs/export.yaml > %t-lib.obj # RUN: lld-link /out:%t.dll /dll %t-lib.obj /implib:%t.lib /export:exportfn1 diff --git a/lld/test/COFF/manifestinput.test b/lld/test/COFF/manifestinput.test index 376d404d604c..4eb1730bb0e4 100644 --- a/lld/test/COFF/manifestinput.test +++ b/lld/test/COFF/manifestinput.test @@ -1,4 +1,4 @@ -# REQUIRES: winres +# REQUIRES: win_mt # RUN: yaml2obj %p/Inputs/ret42.yaml > %t.obj # RUN: lld-link /out:%t.exe /entry:main \ @@ -8,3 +8,28 @@ CHECK: CHECK: + +# RUN: yaml2obj %p/Inputs/ret42.yaml > %t.obj +# RUN: lld-link /out:%t.exe /entry:main \ +# RUN: /manifest:embed \ +# RUN: /manifestuac:"level='requireAdministrator'" \ +# RUN: /manifestinput:%p/Inputs/manifestinput.test %t.obj +# RUN: llvm-readobj -coff-resources -file-headers %t.exe | FileCheck %s \ +# RUN: -check-prefix TEST_EMBED + +TEST_EMBED: ResourceTableRVA: 0x1000 +TEST_EMBED-NEXT: ResourceTableSize: 0x298 +TEST_EMBED-DAG: Resources [ +TEST_EMBED-NEXT: Total Number of Resources: 1 +TEST_EMBED-DAG: Number of String Entries: 0 +TEST_EMBED-NEXT: Number of ID Entries: 1 +TEST_EMBED-NEXT: Type: kRT_MANIFEST (ID 24) [ +TEST_EMBED-NEXT: Table Offset: 0x18 +TEST_EMBED-NEXT: Number of String Entries: 0 +TEST_EMBED-NEXT: Number of ID Entries: 1 +TEST_EMBED-NEXT: Name: (ID 1) [ +TEST_EMBED-NEXT: Table Offset: 0x30 +TEST_EMBED-NEXT: Number of String Entries: 0 +TEST_EMBED-NEXT: Number of ID Entries: 1 +TEST_EMBED-NEXT: Language: (ID 1033) [ +TEST_EMBED-NEXT: Entry Offset: 0x48 diff --git a/lld/test/COFF/noentry.test b/lld/test/COFF/noentry.test index 5e241e46e897..cc02c85e7010 100644 --- a/lld/test/COFF/noentry.test +++ b/lld/test/COFF/noentry.test @@ -1,5 +1,3 @@ -# REQUIRES: winres - # RUN: yaml2obj < %p/Inputs/export.yaml > %t.obj # RUN: lld-link /out:%t.dll /dll %t.obj # RUN: llvm-readobj -file-headers %t.dll | FileCheck -check-prefix=ENTRY %s diff --git a/lld/test/COFF/out.test b/lld/test/COFF/out.test index 67b2ef01b711..a7b56145996f 100644 --- a/lld/test/COFF/out.test +++ b/lld/test/COFF/out.test @@ -1,4 +1,3 @@ -# REQUIRES: winres # RUN: yaml2obj < %p/Inputs/ret42.yaml > %t.obj # RUN: mkdir -p %T/out/tmp diff --git a/lld/test/COFF/resource.test b/lld/test/COFF/resource.test index 1d5219b0d9fe..53242cdcb63a 100644 --- a/lld/test/COFF/resource.test +++ b/lld/test/COFF/resource.test @@ -1,5 +1,3 @@ -# REQUIRES: winres - # RUN: yaml2obj < %p/Inputs/ret42.yaml > %t.obj # RUN: lld-link /out:%t.exe /entry:main %t.obj %p/Inputs/resource.res diff --git a/lld/test/lit.cfg b/lld/test/lit.cfg index d063ffae707b..95bf3c0dc434 100644 --- a/lld/test/lit.cfg +++ b/lld/test/lit.cfg @@ -264,8 +264,7 @@ llvm_config_cmd.wait() # Set a fake constant version so that we get consitent output. config.environment['LLD_VERSION'] = 'LLD 1.0' -# Check if Windows resource file compiler exists. -cvtres = lit.util.which('cvtres', config.environment['PATH']) -rc = lit.util.which('rc', config.environment['PATH']) -if cvtres and rc: - config.available_features.add('winres') +# Indirectly check if the mt.exe Microsoft utility exists by searching for +# cvtres, which always accompanies it. +if lit.util.which('cvtres', config.environment['PATH']): + config.available_features.add('win_mt') diff --git a/llvm/include/llvm/BinaryFormat/COFF.h b/llvm/include/llvm/BinaryFormat/COFF.h index 138e44bfec28..b395db6eaa83 100644 --- a/llvm/include/llvm/BinaryFormat/COFF.h +++ b/llvm/include/llvm/BinaryFormat/COFF.h @@ -46,6 +46,12 @@ static const char ClGlObjMagic[] = { '\xac', '\x9b', '\xd6', '\xb6', '\x22', '\x26', '\x53', '\xc2', }; +// The signature bytes that start a .res file. +static const char WinResMagic[] = { + '\x00', '\x00', '\x00', '\x00', '\x20', '\x00', '\x00', '\x00', + '\xff', '\xff', '\x00', '\x00', '\xff', '\xff', '\x00', '\x00', +}; + // Sizes in bytes of various things in the COFF format. enum { Header16Size = 20, diff --git a/llvm/include/llvm/Object/WindowsResource.h b/llvm/include/llvm/Object/WindowsResource.h index 1ef00e2909f3..3d32409fd4ac 100644 --- a/llvm/include/llvm/Object/WindowsResource.h +++ b/llvm/include/llvm/Object/WindowsResource.h @@ -43,12 +43,47 @@ #include namespace llvm { - namespace object { class WindowsResource; -enum class Machine { UNKNOWN, ARM, X64, X86 }; +const size_t WIN_RES_MAGIC_SIZE = 16; +const size_t WIN_RES_NULL_ENTRY_SIZE = 16; +const uint32_t WIN_RES_HEADER_ALIGNMENT = 4; +const uint32_t WIN_RES_DATA_ALIGNMENT = 4; +const uint16_t WIN_RES_PURE_MOVEABLE = 0x0030; + +struct WinResHeaderPrefix { + support::ulittle32_t DataSize; + support::ulittle32_t HeaderSize; +}; + +// Type and Name may each either be an integer ID or a string. This struct is +// only used in the case where they are both IDs. +struct WinResIDs { + uint16_t TypeFlag; + support::ulittle16_t TypeID; + uint16_t NameFlag; + support::ulittle16_t NameID; + + void setType(uint16_t ID) { + TypeFlag = 0xffff; + TypeID = ID; + } + + void setName(uint16_t ID) { + NameFlag = 0xffff; + NameID = ID; + } +}; + +struct WinResHeaderSuffix { + support::ulittle32_t DataVersion; + support::ulittle16_t MemoryFlags; + support::ulittle16_t Language; + support::ulittle32_t Version; + support::ulittle32_t Characteristics; +}; class ResourceEntryRef { public: @@ -73,14 +108,6 @@ private: Error loadNext(); - struct HeaderSuffix { - support::ulittle32_t DataVersion; - support::ulittle16_t MemoryFlags; - support::ulittle16_t Language; - support::ulittle32_t Version; - support::ulittle32_t Characteristics; - }; - BinaryStreamReader Reader; bool IsStringType; ArrayRef Type; @@ -88,7 +115,7 @@ private: bool IsStringName; ArrayRef Name; uint16_t NameID; - const HeaderSuffix *Suffix = nullptr; + const WinResHeaderSuffix *Suffix = nullptr; ArrayRef Data; const WindowsResource *OwningRes = nullptr; }; diff --git a/llvm/lib/BinaryFormat/Magic.cpp b/llvm/lib/BinaryFormat/Magic.cpp index 5d0a71fc7802..b19a07a9066b 100644 --- a/llvm/lib/BinaryFormat/Magic.cpp +++ b/llvm/lib/BinaryFormat/Magic.cpp @@ -51,7 +51,8 @@ file_magic llvm::identify_magic(StringRef Magic) { return file_magic::coff_import_library; } // Windows resource file - if (startswith(Magic, "\0\0\0\0\x20\0\0\0\xFF")) + if (Magic.size() >= sizeof(COFF::WinResMagic) && + memcmp(Magic.data(), COFF::WinResMagic, sizeof(COFF::WinResMagic)) == 0) return file_magic::windows_resource; // 0x0000 = COFF unknown machine type if (Magic[1] == 0) diff --git a/llvm/lib/Object/WindowsResource.cpp b/llvm/lib/Object/WindowsResource.cpp index be767a154f96..246eee5ddb31 100644 --- a/llvm/lib/Object/WindowsResource.cpp +++ b/llvm/lib/Object/WindowsResource.cpp @@ -36,23 +36,19 @@ const uint32_t MIN_HEADER_SIZE = 7 * sizeof(uint32_t) + 2 * sizeof(uint16_t); // 8-byte because it makes everyone happy. const uint32_t SECTION_ALIGNMENT = sizeof(uint64_t); -static const size_t ResourceMagicSize = 16; - -static const size_t NullEntrySize = 16; - uint32_t WindowsResourceParser::TreeNode::StringCount = 0; uint32_t WindowsResourceParser::TreeNode::DataCount = 0; WindowsResource::WindowsResource(MemoryBufferRef Source) : Binary(Binary::ID_WinRes, Source) { - size_t LeadingSize = ResourceMagicSize + NullEntrySize; + size_t LeadingSize = WIN_RES_MAGIC_SIZE + WIN_RES_NULL_ENTRY_SIZE; BBS = BinaryByteStream(Data.getBuffer().drop_front(LeadingSize), support::little); } Expected> WindowsResource::createWindowsResource(MemoryBufferRef Source) { - if (Source.getBufferSize() < ResourceMagicSize + NullEntrySize) + if (Source.getBufferSize() < WIN_RES_MAGIC_SIZE + WIN_RES_NULL_ENTRY_SIZE) return make_error( "File too small to be a resource file", object_error::invalid_file_type); @@ -105,12 +101,10 @@ static Error readStringOrId(BinaryStreamReader &Reader, uint16_t &ID, } Error ResourceEntryRef::loadNext() { - uint32_t DataSize; - RETURN_IF_ERROR(Reader.readInteger(DataSize)); - uint32_t HeaderSize; - RETURN_IF_ERROR(Reader.readInteger(HeaderSize)); + const WinResHeaderPrefix *Prefix; + RETURN_IF_ERROR(Reader.readObject(Prefix)); - if (HeaderSize < MIN_HEADER_SIZE) + if (Prefix->HeaderSize < MIN_HEADER_SIZE) return make_error("Header size is too small.", object_error::parse_failed); @@ -118,13 +112,13 @@ Error ResourceEntryRef::loadNext() { RETURN_IF_ERROR(readStringOrId(Reader, NameID, Name, IsStringName)); - RETURN_IF_ERROR(Reader.padToAlignment(sizeof(uint32_t))); + RETURN_IF_ERROR(Reader.padToAlignment(WIN_RES_HEADER_ALIGNMENT)); RETURN_IF_ERROR(Reader.readObject(Suffix)); - RETURN_IF_ERROR(Reader.readArray(Data, DataSize)); + RETURN_IF_ERROR(Reader.readArray(Data, Prefix->DataSize)); - RETURN_IF_ERROR(Reader.padToAlignment(sizeof(uint32_t))); + RETURN_IF_ERROR(Reader.padToAlignment(WIN_RES_DATA_ALIGNMENT)); return Error::success(); } @@ -350,6 +344,7 @@ WindowsResourceCOFFWriter::WindowsResourceCOFFWriter( : MachineType(MachineType), Resources(Parser.getTree()), Data(Parser.getData()), StringTable(Parser.getStringTable()) { performFileLayout(); + OutputBuffer = MemoryBuffer::getNewMemBuffer(FileSize); } @@ -467,8 +462,6 @@ void WindowsResourceCOFFWriter::writeFirstSectionHeader() { SectionOneHeader->PointerToLinenumbers = 0; SectionOneHeader->NumberOfRelocations = Data.size(); SectionOneHeader->NumberOfLinenumbers = 0; - SectionOneHeader->Characteristics = COFF::IMAGE_SCN_ALIGN_1BYTES; - SectionOneHeader->Characteristics += COFF::IMAGE_SCN_CNT_INITIALIZED_DATA; SectionOneHeader->Characteristics += COFF::IMAGE_SCN_CNT_INITIALIZED_DATA; SectionOneHeader->Characteristics += COFF::IMAGE_SCN_MEM_READ; } diff --git a/llvm/tools/llvm-cvtres/llvm-cvtres.cpp b/llvm/tools/llvm-cvtres/llvm-cvtres.cpp index 3430b0ab3adb..36c15925e84f 100644 --- a/llvm/tools/llvm-cvtres/llvm-cvtres.cpp +++ b/llvm/tools/llvm-cvtres/llvm-cvtres.cpp @@ -207,6 +207,7 @@ int main(int argc_, const char *argv_[]) { std::copy(OutputBuffer->getBufferStart(), OutputBuffer->getBufferEnd(), FileBuffer->getBufferStart()); error(FileBuffer->commit()); + if (Verbose) { Expected> BinaryOrErr = createBinary(OutputFile); if (!BinaryOrErr) diff --git a/llvm/unittests/BinaryFormat/TestFileMagic.cpp b/llvm/unittests/BinaryFormat/TestFileMagic.cpp index fc2c1eef9fbf..68b3ade00954 100644 --- a/llvm/unittests/BinaryFormat/TestFileMagic.cpp +++ b/llvm/unittests/BinaryFormat/TestFileMagic.cpp @@ -76,7 +76,8 @@ const char macho_dsym_companion[] = "\xfe\xed\xfa\xce........\x00\x00\x00\x0a............"; const char macho_kext_bundle[] = "\xfe\xed\xfa\xce........\x00\x00\x00\x0b............"; -const char windows_resource[] = "\x00\x00\x00\x00\x020\x00\x00\x00\xff"; +const char windows_resource[] = + "\x00\x00\x00\x00\x020\x00\x00\x00\xff\xff\x00\x00\xff\xff\x00\x00"; const char macho_dynamically_linked_shared_lib_stub[] = "\xfe\xed\xfa\xce........\x00\x00\x00\x09............";