#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"
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)) {}
}
}
-// 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 {
return readFile(File2.Path);
}
+static std::unique_ptr<MemoryBuffer>
+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<object::WinResHeaderPrefix *>(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<object::WinResIDs *>(Buf);
+ IDs->setType(RT_MANIFEST);
+ IDs->setName(Config->ManifestID);
+ Buf += sizeof(object::WinResIDs);
+
+ // Write the suffix.
+ auto *Suffix = reinterpret_cast<object::WinResHeaderSuffix *>(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<MemoryBuffer> 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);
-
- // 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.
- 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();
+ std::unique_ptr<MemoryBuffer> Res =
+ createMemoryBufferForManifestRes(Manifest.size());
+
+ char *Buf = const_cast<char *>(Res->getBufferStart());
+ writeResFileHeader(Buf);
+ writeResEntryHeader(Buf, Manifest.size());
+
+ // Copy the manifest data into the .res file.
+ std::copy(Manifest.begin(), Manifest.end(), Buf);
+ return Res;
}
void createSideBySideManifest() {
// using cvtres.exe.
std::unique_ptr<MemoryBuffer>
convertResToCOFF(const std::vector<MemoryBufferRef> &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<TemporaryFile> 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<object::Binary> Bin = check(object::createBinary(MB));
+ object::WindowsResource *RF = dyn_cast<object::WindowsResource>(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<std::unique_ptr<MemoryBuffer>> 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.
// > 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
-# REQUIRES: winres
-
# RUN: rm -rf %t
# RUN: mkdir -p %t
# RUN: cd %t
-# 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
-# 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
-# REQUIRES: winres
+# REQUIRES: win_mt
# RUN: yaml2obj %p/Inputs/ret42.yaml > %t.obj
# RUN: lld-link /out:%t.exe /entry:main \
CHECK: <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
CHECK: <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"><dependency><dependentAssembly><assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"></assemblyIdentity></dependentAssembly></dependency><trustInfo><security><requestedPrivileges><requestedExecutionLevel level="requireAdministrator" uiAccess="false"></requestedExecutionLevel></requestedPrivileges></security></trustInfo></assembly>
+
+# 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
-# 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
-# REQUIRES: winres
# RUN: yaml2obj < %p/Inputs/ret42.yaml > %t.obj
# RUN: mkdir -p %T/out/tmp
-# REQUIRES: winres
-
# RUN: yaml2obj < %p/Inputs/ret42.yaml > %t.obj
# RUN: lld-link /out:%t.exe /entry:main %t.obj %p/Inputs/resource.res
# 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')
'\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,
#include <map>
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:
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<UTF16> Type;
bool IsStringName;
ArrayRef<UTF16> Name;
uint16_t NameID;
- const HeaderSuffix *Suffix = nullptr;
+ const WinResHeaderSuffix *Suffix = nullptr;
ArrayRef<uint8_t> Data;
const WindowsResource *OwningRes = nullptr;
};
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)
// 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<std::unique_ptr<WindowsResource>>
WindowsResource::createWindowsResource(MemoryBufferRef Source) {
- if (Source.getBufferSize() < ResourceMagicSize + NullEntrySize)
+ if (Source.getBufferSize() < WIN_RES_MAGIC_SIZE + WIN_RES_NULL_ENTRY_SIZE)
return make_error<GenericBinaryError>(
"File too small to be a resource file",
object_error::invalid_file_type);
}
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<GenericBinaryError>("Header size is too small.",
object_error::parse_failed);
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();
}
: MachineType(MachineType), Resources(Parser.getTree()),
Data(Parser.getData()), StringTable(Parser.getStringTable()) {
performFileLayout();
+
OutputBuffer = MemoryBuffer::getNewMemBuffer(FileSize);
}
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;
}
std::copy(OutputBuffer->getBufferStart(), OutputBuffer->getBufferEnd(),
FileBuffer->getBufferStart());
error(FileBuffer->commit());
+
if (Verbose) {
Expected<OwningBinary<Binary>> BinaryOrErr = createBinary(OutputFile);
if (!BinaryOrErr)
"\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............";