summaryrefslogtreecommitdiff
path: root/llvm/lib/Object/WindowsResource.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'llvm/lib/Object/WindowsResource.cpp')
-rw-r--r--llvm/lib/Object/WindowsResource.cpp1013
1 files changed, 1013 insertions, 0 deletions
diff --git a/llvm/lib/Object/WindowsResource.cpp b/llvm/lib/Object/WindowsResource.cpp
new file mode 100644
index 000000000000..10717718b201
--- /dev/null
+++ b/llvm/lib/Object/WindowsResource.cpp
@@ -0,0 +1,1013 @@
+//===-- WindowsResource.cpp -------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements the .res file class.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Object/WindowsResource.h"
+#include "llvm/Object/COFF.h"
+#include "llvm/Support/FileOutputBuffer.h"
+#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/MathExtras.h"
+#include "llvm/Support/ScopedPrinter.h"
+#include <ctime>
+#include <queue>
+#include <system_error>
+
+using namespace llvm;
+using namespace object;
+
+namespace llvm {
+namespace object {
+
+#define RETURN_IF_ERROR(X) \
+ if (auto EC = X) \
+ return EC;
+
+#define UNWRAP_REF_OR_RETURN(Name, Expr) \
+ auto Name##OrErr = Expr; \
+ if (!Name##OrErr) \
+ return Name##OrErr.takeError(); \
+ const auto &Name = *Name##OrErr;
+
+#define UNWRAP_OR_RETURN(Name, Expr) \
+ auto Name##OrErr = Expr; \
+ if (!Name##OrErr) \
+ return Name##OrErr.takeError(); \
+ auto Name = *Name##OrErr;
+
+const uint32_t MIN_HEADER_SIZE = 7 * sizeof(uint32_t) + 2 * sizeof(uint16_t);
+
+// COFF files seem to be inconsistent with alignment between sections, just use
+// 8-byte because it makes everyone happy.
+const uint32_t SECTION_ALIGNMENT = sizeof(uint64_t);
+
+WindowsResource::WindowsResource(MemoryBufferRef Source)
+ : Binary(Binary::ID_WinRes, Source) {
+ size_t LeadingSize = WIN_RES_MAGIC_SIZE + WIN_RES_NULL_ENTRY_SIZE;
+ BBS = BinaryByteStream(Data.getBuffer().drop_front(LeadingSize),
+ support::little);
+}
+
+// static
+Expected<std::unique_ptr<WindowsResource>>
+WindowsResource::createWindowsResource(MemoryBufferRef Source) {
+ if (Source.getBufferSize() < WIN_RES_MAGIC_SIZE + WIN_RES_NULL_ENTRY_SIZE)
+ return make_error<GenericBinaryError>(
+ Source.getBufferIdentifier() + ": too small to be a resource file",
+ object_error::invalid_file_type);
+ std::unique_ptr<WindowsResource> Ret(new WindowsResource(Source));
+ return std::move(Ret);
+}
+
+Expected<ResourceEntryRef> WindowsResource::getHeadEntry() {
+ if (BBS.getLength() < sizeof(WinResHeaderPrefix) + sizeof(WinResHeaderSuffix))
+ return make_error<EmptyResError>(getFileName() + " contains no entries",
+ object_error::unexpected_eof);
+ return ResourceEntryRef::create(BinaryStreamRef(BBS), this);
+}
+
+ResourceEntryRef::ResourceEntryRef(BinaryStreamRef Ref,
+ const WindowsResource *Owner)
+ : Reader(Ref), Owner(Owner) {}
+
+Expected<ResourceEntryRef>
+ResourceEntryRef::create(BinaryStreamRef BSR, const WindowsResource *Owner) {
+ auto Ref = ResourceEntryRef(BSR, Owner);
+ if (auto E = Ref.loadNext())
+ return std::move(E);
+ return Ref;
+}
+
+Error ResourceEntryRef::moveNext(bool &End) {
+ // Reached end of all the entries.
+ if (Reader.bytesRemaining() == 0) {
+ End = true;
+ return Error::success();
+ }
+ RETURN_IF_ERROR(loadNext());
+
+ return Error::success();
+}
+
+static Error readStringOrId(BinaryStreamReader &Reader, uint16_t &ID,
+ ArrayRef<UTF16> &Str, bool &IsString) {
+ uint16_t IDFlag;
+ RETURN_IF_ERROR(Reader.readInteger(IDFlag));
+ IsString = IDFlag != 0xffff;
+
+ if (IsString) {
+ Reader.setOffset(
+ Reader.getOffset() -
+ sizeof(uint16_t)); // Re-read the bytes which we used to check the flag.
+ RETURN_IF_ERROR(Reader.readWideString(Str));
+ } else
+ RETURN_IF_ERROR(Reader.readInteger(ID));
+
+ return Error::success();
+}
+
+Error ResourceEntryRef::loadNext() {
+ const WinResHeaderPrefix *Prefix;
+ RETURN_IF_ERROR(Reader.readObject(Prefix));
+
+ if (Prefix->HeaderSize < MIN_HEADER_SIZE)
+ return make_error<GenericBinaryError>(Owner->getFileName() +
+ ": header size too small",
+ object_error::parse_failed);
+
+ RETURN_IF_ERROR(readStringOrId(Reader, TypeID, Type, IsStringType));
+
+ RETURN_IF_ERROR(readStringOrId(Reader, NameID, Name, IsStringName));
+
+ RETURN_IF_ERROR(Reader.padToAlignment(WIN_RES_HEADER_ALIGNMENT));
+
+ RETURN_IF_ERROR(Reader.readObject(Suffix));
+
+ RETURN_IF_ERROR(Reader.readArray(Data, Prefix->DataSize));
+
+ RETURN_IF_ERROR(Reader.padToAlignment(WIN_RES_DATA_ALIGNMENT));
+
+ return Error::success();
+}
+
+WindowsResourceParser::WindowsResourceParser(bool MinGW)
+ : Root(false), MinGW(MinGW) {}
+
+void printResourceTypeName(uint16_t TypeID, raw_ostream &OS) {
+ switch (TypeID) {
+ case 1: OS << "CURSOR (ID 1)"; break;
+ case 2: OS << "BITMAP (ID 2)"; break;
+ case 3: OS << "ICON (ID 3)"; break;
+ case 4: OS << "MENU (ID 4)"; break;
+ case 5: OS << "DIALOG (ID 5)"; break;
+ case 6: OS << "STRINGTABLE (ID 6)"; break;
+ case 7: OS << "FONTDIR (ID 7)"; break;
+ case 8: OS << "FONT (ID 8)"; break;
+ case 9: OS << "ACCELERATOR (ID 9)"; break;
+ case 10: OS << "RCDATA (ID 10)"; break;
+ case 11: OS << "MESSAGETABLE (ID 11)"; break;
+ case 12: OS << "GROUP_CURSOR (ID 12)"; break;
+ case 14: OS << "GROUP_ICON (ID 14)"; break;
+ case 16: OS << "VERSIONINFO (ID 16)"; break;
+ case 17: OS << "DLGINCLUDE (ID 17)"; break;
+ case 19: OS << "PLUGPLAY (ID 19)"; break;
+ case 20: OS << "VXD (ID 20)"; break;
+ case 21: OS << "ANICURSOR (ID 21)"; break;
+ case 22: OS << "ANIICON (ID 22)"; break;
+ case 23: OS << "HTML (ID 23)"; break;
+ case 24: OS << "MANIFEST (ID 24)"; break;
+ default: OS << "ID " << TypeID; break;
+ }
+}
+
+static bool convertUTF16LEToUTF8String(ArrayRef<UTF16> Src, std::string &Out) {
+ if (!sys::IsBigEndianHost)
+ return convertUTF16ToUTF8String(Src, Out);
+
+ std::vector<UTF16> EndianCorrectedSrc;
+ EndianCorrectedSrc.resize(Src.size() + 1);
+ llvm::copy(Src, EndianCorrectedSrc.begin() + 1);
+ EndianCorrectedSrc[0] = UNI_UTF16_BYTE_ORDER_MARK_SWAPPED;
+ return convertUTF16ToUTF8String(makeArrayRef(EndianCorrectedSrc), Out);
+}
+
+static std::string makeDuplicateResourceError(
+ const ResourceEntryRef &Entry, StringRef File1, StringRef File2) {
+ std::string Ret;
+ raw_string_ostream OS(Ret);
+
+ OS << "duplicate resource:";
+
+ OS << " type ";
+ if (Entry.checkTypeString()) {
+ std::string UTF8;
+ if (!convertUTF16LEToUTF8String(Entry.getTypeString(), UTF8))
+ UTF8 = "(failed conversion from UTF16)";
+ OS << '\"' << UTF8 << '\"';
+ } else
+ printResourceTypeName(Entry.getTypeID(), OS);
+
+ OS << "/name ";
+ if (Entry.checkNameString()) {
+ std::string UTF8;
+ if (!convertUTF16LEToUTF8String(Entry.getNameString(), UTF8))
+ UTF8 = "(failed conversion from UTF16)";
+ OS << '\"' << UTF8 << '\"';
+ } else {
+ OS << "ID " << Entry.getNameID();
+ }
+
+ OS << "/language " << Entry.getLanguage() << ", in " << File1 << " and in "
+ << File2;
+
+ return OS.str();
+}
+
+static void printStringOrID(const WindowsResourceParser::StringOrID &S,
+ raw_string_ostream &OS, bool IsType, bool IsID) {
+ if (S.IsString) {
+ std::string UTF8;
+ if (!convertUTF16LEToUTF8String(S.String, UTF8))
+ UTF8 = "(failed conversion from UTF16)";
+ OS << '\"' << UTF8 << '\"';
+ } else if (IsType)
+ printResourceTypeName(S.ID, OS);
+ else if (IsID)
+ OS << "ID " << S.ID;
+ else
+ OS << S.ID;
+}
+
+static std::string makeDuplicateResourceError(
+ const std::vector<WindowsResourceParser::StringOrID> &Context,
+ StringRef File1, StringRef File2) {
+ std::string Ret;
+ raw_string_ostream OS(Ret);
+
+ OS << "duplicate resource:";
+
+ if (Context.size() >= 1) {
+ OS << " type ";
+ printStringOrID(Context[0], OS, /* IsType */ true, /* IsID */ true);
+ }
+
+ if (Context.size() >= 2) {
+ OS << "/name ";
+ printStringOrID(Context[1], OS, /* IsType */ false, /* IsID */ true);
+ }
+
+ if (Context.size() >= 3) {
+ OS << "/language ";
+ printStringOrID(Context[2], OS, /* IsType */ false, /* IsID */ false);
+ }
+ OS << ", in " << File1 << " and in " << File2;
+
+ return OS.str();
+}
+
+// MinGW specific. Remove default manifests (with language zero) if there are
+// other manifests present, and report an error if there are more than one
+// manifest with a non-zero language code.
+// GCC has the concept of a default manifest resource object, which gets
+// linked in implicitly if present. This default manifest has got language
+// id zero, and should be dropped silently if there's another manifest present.
+// If the user resources surprisignly had a manifest with language id zero,
+// we should also ignore the duplicate default manifest.
+void WindowsResourceParser::cleanUpManifests(
+ std::vector<std::string> &Duplicates) {
+ auto TypeIt = Root.IDChildren.find(/* RT_MANIFEST */ 24);
+ if (TypeIt == Root.IDChildren.end())
+ return;
+
+ TreeNode *TypeNode = TypeIt->second.get();
+ auto NameIt =
+ TypeNode->IDChildren.find(/* CREATEPROCESS_MANIFEST_RESOURCE_ID */ 1);
+ if (NameIt == TypeNode->IDChildren.end())
+ return;
+
+ TreeNode *NameNode = NameIt->second.get();
+ if (NameNode->IDChildren.size() <= 1)
+ return; // None or one manifest present, all good.
+
+ // If we have more than one manifest, drop the language zero one if present,
+ // and check again.
+ auto LangZeroIt = NameNode->IDChildren.find(0);
+ if (LangZeroIt != NameNode->IDChildren.end() &&
+ LangZeroIt->second->IsDataNode) {
+ uint32_t RemovedIndex = LangZeroIt->second->DataIndex;
+ NameNode->IDChildren.erase(LangZeroIt);
+ Data.erase(Data.begin() + RemovedIndex);
+ Root.shiftDataIndexDown(RemovedIndex);
+
+ // If we're now down to one manifest, all is good.
+ if (NameNode->IDChildren.size() <= 1)
+ return;
+ }
+
+ // More than one non-language-zero manifest
+ auto FirstIt = NameNode->IDChildren.begin();
+ uint32_t FirstLang = FirstIt->first;
+ TreeNode *FirstNode = FirstIt->second.get();
+ auto LastIt = NameNode->IDChildren.rbegin();
+ uint32_t LastLang = LastIt->first;
+ TreeNode *LastNode = LastIt->second.get();
+ Duplicates.push_back(
+ ("duplicate non-default manifests with languages " + Twine(FirstLang) +
+ " in " + InputFilenames[FirstNode->Origin] + " and " + Twine(LastLang) +
+ " in " + InputFilenames[LastNode->Origin])
+ .str());
+}
+
+// Ignore duplicates of manifests with language zero (the default manifest),
+// in case the user has provided a manifest with that language id. See
+// the function comment above for context. Only returns true if MinGW is set
+// to true.
+bool WindowsResourceParser::shouldIgnoreDuplicate(
+ const ResourceEntryRef &Entry) const {
+ return MinGW && !Entry.checkTypeString() &&
+ Entry.getTypeID() == /* RT_MANIFEST */ 24 &&
+ !Entry.checkNameString() &&
+ Entry.getNameID() == /* CREATEPROCESS_MANIFEST_RESOURCE_ID */ 1 &&
+ Entry.getLanguage() == 0;
+}
+
+bool WindowsResourceParser::shouldIgnoreDuplicate(
+ const std::vector<StringOrID> &Context) const {
+ return MinGW && Context.size() == 3 && !Context[0].IsString &&
+ Context[0].ID == /* RT_MANIFEST */ 24 && !Context[1].IsString &&
+ Context[1].ID == /* CREATEPROCESS_MANIFEST_RESOURCE_ID */ 1 &&
+ !Context[2].IsString && Context[2].ID == 0;
+}
+
+Error WindowsResourceParser::parse(WindowsResource *WR,
+ std::vector<std::string> &Duplicates) {
+ auto EntryOrErr = WR->getHeadEntry();
+ if (!EntryOrErr) {
+ auto E = EntryOrErr.takeError();
+ if (E.isA<EmptyResError>()) {
+ // Check if the .res file contains no entries. In this case we don't have
+ // to throw an error but can rather just return without parsing anything.
+ // This applies for files which have a valid PE header magic and the
+ // mandatory empty null resource entry. Files which do not fit this
+ // criteria would have already been filtered out by
+ // WindowsResource::createWindowsResource().
+ consumeError(std::move(E));
+ return Error::success();
+ }
+ return E;
+ }
+
+ ResourceEntryRef Entry = EntryOrErr.get();
+ uint32_t Origin = InputFilenames.size();
+ InputFilenames.push_back(WR->getFileName());
+ bool End = false;
+ while (!End) {
+
+ TreeNode *Node;
+ bool IsNewNode = Root.addEntry(Entry, Origin, Data, StringTable, Node);
+ if (!IsNewNode) {
+ if (!shouldIgnoreDuplicate(Entry))
+ Duplicates.push_back(makeDuplicateResourceError(
+ Entry, InputFilenames[Node->Origin], WR->getFileName()));
+ }
+
+ RETURN_IF_ERROR(Entry.moveNext(End));
+ }
+
+ return Error::success();
+}
+
+Error WindowsResourceParser::parse(ResourceSectionRef &RSR, StringRef Filename,
+ std::vector<std::string> &Duplicates) {
+ UNWRAP_REF_OR_RETURN(BaseTable, RSR.getBaseTable());
+ uint32_t Origin = InputFilenames.size();
+ InputFilenames.push_back(Filename);
+ std::vector<StringOrID> Context;
+ return addChildren(Root, RSR, BaseTable, Origin, Context, Duplicates);
+}
+
+void WindowsResourceParser::printTree(raw_ostream &OS) const {
+ ScopedPrinter Writer(OS);
+ Root.print(Writer, "Resource Tree");
+}
+
+bool WindowsResourceParser::TreeNode::addEntry(
+ const ResourceEntryRef &Entry, uint32_t Origin,
+ std::vector<std::vector<uint8_t>> &Data,
+ std::vector<std::vector<UTF16>> &StringTable, TreeNode *&Result) {
+ TreeNode &TypeNode = addTypeNode(Entry, StringTable);
+ TreeNode &NameNode = TypeNode.addNameNode(Entry, StringTable);
+ return NameNode.addLanguageNode(Entry, Origin, Data, Result);
+}
+
+Error WindowsResourceParser::addChildren(TreeNode &Node,
+ ResourceSectionRef &RSR,
+ const coff_resource_dir_table &Table,
+ uint32_t Origin,
+ std::vector<StringOrID> &Context,
+ std::vector<std::string> &Duplicates) {
+
+ for (int i = 0; i < Table.NumberOfNameEntries + Table.NumberOfIDEntries;
+ i++) {
+ UNWRAP_REF_OR_RETURN(Entry, RSR.getTableEntry(Table, i));
+ TreeNode *Child;
+
+ if (Entry.Offset.isSubDir()) {
+
+ // Create a new subdirectory and recurse
+ if (i < Table.NumberOfNameEntries) {
+ UNWRAP_OR_RETURN(NameString, RSR.getEntryNameString(Entry));
+ Child = &Node.addNameChild(NameString, StringTable);
+ Context.push_back(StringOrID(NameString));
+ } else {
+ Child = &Node.addIDChild(Entry.Identifier.ID);
+ Context.push_back(StringOrID(Entry.Identifier.ID));
+ }
+
+ UNWRAP_REF_OR_RETURN(NextTable, RSR.getEntrySubDir(Entry));
+ Error E =
+ addChildren(*Child, RSR, NextTable, Origin, Context, Duplicates);
+ if (E)
+ return E;
+ Context.pop_back();
+
+ } else {
+
+ // Data leaves are supposed to have a numeric ID as identifier (language).
+ if (Table.NumberOfNameEntries > 0)
+ return createStringError(object_error::parse_failed,
+ "unexpected string key for data object");
+
+ // Try adding a data leaf
+ UNWRAP_REF_OR_RETURN(DataEntry, RSR.getEntryData(Entry));
+ TreeNode *Child;
+ Context.push_back(StringOrID(Entry.Identifier.ID));
+ bool Added = Node.addDataChild(Entry.Identifier.ID, Table.MajorVersion,
+ Table.MinorVersion, Table.Characteristics,
+ Origin, Data.size(), Child);
+ if (Added) {
+ UNWRAP_OR_RETURN(Contents, RSR.getContents(DataEntry));
+ Data.push_back(ArrayRef<uint8_t>(
+ reinterpret_cast<const uint8_t *>(Contents.data()),
+ Contents.size()));
+ } else {
+ if (!shouldIgnoreDuplicate(Context))
+ Duplicates.push_back(makeDuplicateResourceError(
+ Context, InputFilenames[Child->Origin], InputFilenames.back()));
+ }
+ Context.pop_back();
+
+ }
+ }
+ return Error::success();
+}
+
+WindowsResourceParser::TreeNode::TreeNode(uint32_t StringIndex)
+ : StringIndex(StringIndex) {}
+
+WindowsResourceParser::TreeNode::TreeNode(uint16_t MajorVersion,
+ uint16_t MinorVersion,
+ uint32_t Characteristics,
+ uint32_t Origin, uint32_t DataIndex)
+ : IsDataNode(true), DataIndex(DataIndex), MajorVersion(MajorVersion),
+ MinorVersion(MinorVersion), Characteristics(Characteristics),
+ Origin(Origin) {}
+
+std::unique_ptr<WindowsResourceParser::TreeNode>
+WindowsResourceParser::TreeNode::createStringNode(uint32_t Index) {
+ return std::unique_ptr<TreeNode>(new TreeNode(Index));
+}
+
+std::unique_ptr<WindowsResourceParser::TreeNode>
+WindowsResourceParser::TreeNode::createIDNode() {
+ return std::unique_ptr<TreeNode>(new TreeNode(0));
+}
+
+std::unique_ptr<WindowsResourceParser::TreeNode>
+WindowsResourceParser::TreeNode::createDataNode(uint16_t MajorVersion,
+ uint16_t MinorVersion,
+ uint32_t Characteristics,
+ uint32_t Origin,
+ uint32_t DataIndex) {
+ return std::unique_ptr<TreeNode>(new TreeNode(
+ MajorVersion, MinorVersion, Characteristics, Origin, DataIndex));
+}
+
+WindowsResourceParser::TreeNode &WindowsResourceParser::TreeNode::addTypeNode(
+ const ResourceEntryRef &Entry,
+ std::vector<std::vector<UTF16>> &StringTable) {
+ if (Entry.checkTypeString())
+ return addNameChild(Entry.getTypeString(), StringTable);
+ else
+ return addIDChild(Entry.getTypeID());
+}
+
+WindowsResourceParser::TreeNode &WindowsResourceParser::TreeNode::addNameNode(
+ const ResourceEntryRef &Entry,
+ std::vector<std::vector<UTF16>> &StringTable) {
+ if (Entry.checkNameString())
+ return addNameChild(Entry.getNameString(), StringTable);
+ else
+ return addIDChild(Entry.getNameID());
+}
+
+bool WindowsResourceParser::TreeNode::addLanguageNode(
+ const ResourceEntryRef &Entry, uint32_t Origin,
+ std::vector<std::vector<uint8_t>> &Data, TreeNode *&Result) {
+ bool Added = addDataChild(Entry.getLanguage(), Entry.getMajorVersion(),
+ Entry.getMinorVersion(), Entry.getCharacteristics(),
+ Origin, Data.size(), Result);
+ if (Added)
+ Data.push_back(Entry.getData());
+ return Added;
+}
+
+bool WindowsResourceParser::TreeNode::addDataChild(
+ uint32_t ID, uint16_t MajorVersion, uint16_t MinorVersion,
+ uint32_t Characteristics, uint32_t Origin, uint32_t DataIndex,
+ TreeNode *&Result) {
+ auto NewChild = createDataNode(MajorVersion, MinorVersion, Characteristics,
+ Origin, DataIndex);
+ auto ElementInserted = IDChildren.emplace(ID, std::move(NewChild));
+ Result = ElementInserted.first->second.get();
+ return ElementInserted.second;
+}
+
+WindowsResourceParser::TreeNode &WindowsResourceParser::TreeNode::addIDChild(
+ uint32_t ID) {
+ auto Child = IDChildren.find(ID);
+ if (Child == IDChildren.end()) {
+ auto NewChild = createIDNode();
+ WindowsResourceParser::TreeNode &Node = *NewChild;
+ IDChildren.emplace(ID, std::move(NewChild));
+ return Node;
+ } else
+ return *(Child->second);
+}
+
+WindowsResourceParser::TreeNode &WindowsResourceParser::TreeNode::addNameChild(
+ ArrayRef<UTF16> NameRef, std::vector<std::vector<UTF16>> &StringTable) {
+ std::string NameString;
+ convertUTF16LEToUTF8String(NameRef, NameString);
+
+ auto Child = StringChildren.find(NameString);
+ if (Child == StringChildren.end()) {
+ auto NewChild = createStringNode(StringTable.size());
+ StringTable.push_back(NameRef);
+ WindowsResourceParser::TreeNode &Node = *NewChild;
+ StringChildren.emplace(NameString, std::move(NewChild));
+ return Node;
+ } else
+ return *(Child->second);
+}
+
+void WindowsResourceParser::TreeNode::print(ScopedPrinter &Writer,
+ StringRef Name) const {
+ ListScope NodeScope(Writer, Name);
+ for (auto const &Child : StringChildren) {
+ Child.second->print(Writer, Child.first);
+ }
+ for (auto const &Child : IDChildren) {
+ Child.second->print(Writer, to_string(Child.first));
+ }
+}
+
+// This function returns the size of the entire resource tree, including
+// directory tables, directory entries, and data entries. It does not include
+// the directory strings or the relocations of the .rsrc section.
+uint32_t WindowsResourceParser::TreeNode::getTreeSize() const {
+ uint32_t Size = (IDChildren.size() + StringChildren.size()) *
+ sizeof(coff_resource_dir_entry);
+
+ // Reached a node pointing to a data entry.
+ if (IsDataNode) {
+ Size += sizeof(coff_resource_data_entry);
+ return Size;
+ }
+
+ // If the node does not point to data, it must have a directory table pointing
+ // to other nodes.
+ Size += sizeof(coff_resource_dir_table);
+
+ for (auto const &Child : StringChildren) {
+ Size += Child.second->getTreeSize();
+ }
+ for (auto const &Child : IDChildren) {
+ Size += Child.second->getTreeSize();
+ }
+ return Size;
+}
+
+// Shift DataIndex of all data children with an Index greater or equal to the
+// given one, to fill a gap from removing an entry from the Data vector.
+void WindowsResourceParser::TreeNode::shiftDataIndexDown(uint32_t Index) {
+ if (IsDataNode && DataIndex >= Index) {
+ DataIndex--;
+ } else {
+ for (auto &Child : IDChildren)
+ Child.second->shiftDataIndexDown(Index);
+ for (auto &Child : StringChildren)
+ Child.second->shiftDataIndexDown(Index);
+ }
+}
+
+class WindowsResourceCOFFWriter {
+public:
+ WindowsResourceCOFFWriter(COFF::MachineTypes MachineType,
+ const WindowsResourceParser &Parser, Error &E);
+ std::unique_ptr<MemoryBuffer> write(uint32_t TimeDateStamp);
+
+private:
+ void performFileLayout();
+ void performSectionOneLayout();
+ void performSectionTwoLayout();
+ void writeCOFFHeader(uint32_t TimeDateStamp);
+ void writeFirstSectionHeader();
+ void writeSecondSectionHeader();
+ void writeFirstSection();
+ void writeSecondSection();
+ void writeSymbolTable();
+ void writeStringTable();
+ void writeDirectoryTree();
+ void writeDirectoryStringTable();
+ void writeFirstSectionRelocations();
+ std::unique_ptr<WritableMemoryBuffer> OutputBuffer;
+ char *BufferStart;
+ uint64_t CurrentOffset = 0;
+ COFF::MachineTypes MachineType;
+ const WindowsResourceParser::TreeNode &Resources;
+ const ArrayRef<std::vector<uint8_t>> Data;
+ uint64_t FileSize;
+ uint32_t SymbolTableOffset;
+ uint32_t SectionOneSize;
+ uint32_t SectionOneOffset;
+ uint32_t SectionOneRelocations;
+ uint32_t SectionTwoSize;
+ uint32_t SectionTwoOffset;
+ const ArrayRef<std::vector<UTF16>> StringTable;
+ std::vector<uint32_t> StringTableOffsets;
+ std::vector<uint32_t> DataOffsets;
+ std::vector<uint32_t> RelocationAddresses;
+};
+
+WindowsResourceCOFFWriter::WindowsResourceCOFFWriter(
+ COFF::MachineTypes MachineType, const WindowsResourceParser &Parser,
+ Error &E)
+ : MachineType(MachineType), Resources(Parser.getTree()),
+ Data(Parser.getData()), StringTable(Parser.getStringTable()) {
+ performFileLayout();
+
+ OutputBuffer = WritableMemoryBuffer::getNewMemBuffer(
+ FileSize, "internal .obj file created from .res files");
+}
+
+void WindowsResourceCOFFWriter::performFileLayout() {
+ // Add size of COFF header.
+ FileSize = COFF::Header16Size;
+
+ // one .rsrc section header for directory tree, another for resource data.
+ FileSize += 2 * COFF::SectionSize;
+
+ performSectionOneLayout();
+ performSectionTwoLayout();
+
+ // We have reached the address of the symbol table.
+ SymbolTableOffset = FileSize;
+
+ FileSize += COFF::Symbol16Size; // size of the @feat.00 symbol.
+ FileSize += 4 * COFF::Symbol16Size; // symbol + aux for each section.
+ FileSize += Data.size() * COFF::Symbol16Size; // 1 symbol per resource.
+ FileSize += 4; // four null bytes for the string table.
+}
+
+void WindowsResourceCOFFWriter::performSectionOneLayout() {
+ SectionOneOffset = FileSize;
+
+ SectionOneSize = Resources.getTreeSize();
+ uint32_t CurrentStringOffset = SectionOneSize;
+ uint32_t TotalStringTableSize = 0;
+ for (auto const &String : StringTable) {
+ StringTableOffsets.push_back(CurrentStringOffset);
+ uint32_t StringSize = String.size() * sizeof(UTF16) + sizeof(uint16_t);
+ CurrentStringOffset += StringSize;
+ TotalStringTableSize += StringSize;
+ }
+ SectionOneSize += alignTo(TotalStringTableSize, sizeof(uint32_t));
+
+ // account for the relocations of section one.
+ SectionOneRelocations = FileSize + SectionOneSize;
+ FileSize += SectionOneSize;
+ FileSize +=
+ Data.size() * COFF::RelocationSize; // one relocation for each resource.
+ FileSize = alignTo(FileSize, SECTION_ALIGNMENT);
+}
+
+void WindowsResourceCOFFWriter::performSectionTwoLayout() {
+ // add size of .rsrc$2 section, which contains all resource data on 8-byte
+ // alignment.
+ SectionTwoOffset = FileSize;
+ SectionTwoSize = 0;
+ for (auto const &Entry : Data) {
+ DataOffsets.push_back(SectionTwoSize);
+ SectionTwoSize += alignTo(Entry.size(), sizeof(uint64_t));
+ }
+ FileSize += SectionTwoSize;
+ FileSize = alignTo(FileSize, SECTION_ALIGNMENT);
+}
+
+std::unique_ptr<MemoryBuffer>
+WindowsResourceCOFFWriter::write(uint32_t TimeDateStamp) {
+ BufferStart = OutputBuffer->getBufferStart();
+
+ writeCOFFHeader(TimeDateStamp);
+ writeFirstSectionHeader();
+ writeSecondSectionHeader();
+ writeFirstSection();
+ writeSecondSection();
+ writeSymbolTable();
+ writeStringTable();
+
+ return std::move(OutputBuffer);
+}
+
+// According to COFF specification, if the Src has a size equal to Dest,
+// it's okay to *not* copy the trailing zero.
+static void coffnamecpy(char (&Dest)[COFF::NameSize], StringRef Src) {
+ assert(Src.size() <= COFF::NameSize &&
+ "Src is not larger than COFF::NameSize");
+ strncpy(Dest, Src.data(), (size_t)COFF::NameSize);
+}
+
+void WindowsResourceCOFFWriter::writeCOFFHeader(uint32_t TimeDateStamp) {
+ // Write the COFF header.
+ auto *Header = reinterpret_cast<coff_file_header *>(BufferStart);
+ Header->Machine = MachineType;
+ Header->NumberOfSections = 2;
+ Header->TimeDateStamp = TimeDateStamp;
+ Header->PointerToSymbolTable = SymbolTableOffset;
+ // One symbol for every resource plus 2 for each section and 1 for @feat.00
+ Header->NumberOfSymbols = Data.size() + 5;
+ Header->SizeOfOptionalHeader = 0;
+ // cvtres.exe sets 32BIT_MACHINE even for 64-bit machine types. Match it.
+ Header->Characteristics = COFF::IMAGE_FILE_32BIT_MACHINE;
+}
+
+void WindowsResourceCOFFWriter::writeFirstSectionHeader() {
+ // Write the first section header.
+ CurrentOffset += sizeof(coff_file_header);
+ auto *SectionOneHeader =
+ reinterpret_cast<coff_section *>(BufferStart + CurrentOffset);
+ coffnamecpy(SectionOneHeader->Name, ".rsrc$01");
+ SectionOneHeader->VirtualSize = 0;
+ SectionOneHeader->VirtualAddress = 0;
+ SectionOneHeader->SizeOfRawData = SectionOneSize;
+ SectionOneHeader->PointerToRawData = SectionOneOffset;
+ SectionOneHeader->PointerToRelocations = SectionOneRelocations;
+ SectionOneHeader->PointerToLinenumbers = 0;
+ SectionOneHeader->NumberOfRelocations = Data.size();
+ SectionOneHeader->NumberOfLinenumbers = 0;
+ SectionOneHeader->Characteristics += COFF::IMAGE_SCN_CNT_INITIALIZED_DATA;
+ SectionOneHeader->Characteristics += COFF::IMAGE_SCN_MEM_READ;
+}
+
+void WindowsResourceCOFFWriter::writeSecondSectionHeader() {
+ // Write the second section header.
+ CurrentOffset += sizeof(coff_section);
+ auto *SectionTwoHeader =
+ reinterpret_cast<coff_section *>(BufferStart + CurrentOffset);
+ coffnamecpy(SectionTwoHeader->Name, ".rsrc$02");
+ SectionTwoHeader->VirtualSize = 0;
+ SectionTwoHeader->VirtualAddress = 0;
+ SectionTwoHeader->SizeOfRawData = SectionTwoSize;
+ SectionTwoHeader->PointerToRawData = SectionTwoOffset;
+ SectionTwoHeader->PointerToRelocations = 0;
+ SectionTwoHeader->PointerToLinenumbers = 0;
+ SectionTwoHeader->NumberOfRelocations = 0;
+ SectionTwoHeader->NumberOfLinenumbers = 0;
+ SectionTwoHeader->Characteristics = COFF::IMAGE_SCN_CNT_INITIALIZED_DATA;
+ SectionTwoHeader->Characteristics += COFF::IMAGE_SCN_MEM_READ;
+}
+
+void WindowsResourceCOFFWriter::writeFirstSection() {
+ // Write section one.
+ CurrentOffset += sizeof(coff_section);
+
+ writeDirectoryTree();
+ writeDirectoryStringTable();
+ writeFirstSectionRelocations();
+
+ CurrentOffset = alignTo(CurrentOffset, SECTION_ALIGNMENT);
+}
+
+void WindowsResourceCOFFWriter::writeSecondSection() {
+ // Now write the .rsrc$02 section.
+ for (auto const &RawDataEntry : Data) {
+ llvm::copy(RawDataEntry, BufferStart + CurrentOffset);
+ CurrentOffset += alignTo(RawDataEntry.size(), sizeof(uint64_t));
+ }
+
+ CurrentOffset = alignTo(CurrentOffset, SECTION_ALIGNMENT);
+}
+
+void WindowsResourceCOFFWriter::writeSymbolTable() {
+ // Now write the symbol table.
+ // First, the feat symbol.
+ auto *Symbol = reinterpret_cast<coff_symbol16 *>(BufferStart + CurrentOffset);
+ coffnamecpy(Symbol->Name.ShortName, "@feat.00");
+ Symbol->Value = 0x11;
+ Symbol->SectionNumber = 0xffff;
+ Symbol->Type = COFF::IMAGE_SYM_DTYPE_NULL;
+ Symbol->StorageClass = COFF::IMAGE_SYM_CLASS_STATIC;
+ Symbol->NumberOfAuxSymbols = 0;
+ CurrentOffset += sizeof(coff_symbol16);
+
+ // Now write the .rsrc1 symbol + aux.
+ Symbol = reinterpret_cast<coff_symbol16 *>(BufferStart + CurrentOffset);
+ coffnamecpy(Symbol->Name.ShortName, ".rsrc$01");
+ Symbol->Value = 0;
+ Symbol->SectionNumber = 1;
+ Symbol->Type = COFF::IMAGE_SYM_DTYPE_NULL;
+ Symbol->StorageClass = COFF::IMAGE_SYM_CLASS_STATIC;
+ Symbol->NumberOfAuxSymbols = 1;
+ CurrentOffset += sizeof(coff_symbol16);
+ auto *Aux = reinterpret_cast<coff_aux_section_definition *>(BufferStart +
+ CurrentOffset);
+ Aux->Length = SectionOneSize;
+ Aux->NumberOfRelocations = Data.size();
+ Aux->NumberOfLinenumbers = 0;
+ Aux->CheckSum = 0;
+ Aux->NumberLowPart = 0;
+ Aux->Selection = 0;
+ CurrentOffset += sizeof(coff_aux_section_definition);
+
+ // Now write the .rsrc2 symbol + aux.
+ Symbol = reinterpret_cast<coff_symbol16 *>(BufferStart + CurrentOffset);
+ coffnamecpy(Symbol->Name.ShortName, ".rsrc$02");
+ Symbol->Value = 0;
+ Symbol->SectionNumber = 2;
+ Symbol->Type = COFF::IMAGE_SYM_DTYPE_NULL;
+ Symbol->StorageClass = COFF::IMAGE_SYM_CLASS_STATIC;
+ Symbol->NumberOfAuxSymbols = 1;
+ CurrentOffset += sizeof(coff_symbol16);
+ Aux = reinterpret_cast<coff_aux_section_definition *>(BufferStart +
+ CurrentOffset);
+ Aux->Length = SectionTwoSize;
+ Aux->NumberOfRelocations = 0;
+ Aux->NumberOfLinenumbers = 0;
+ Aux->CheckSum = 0;
+ Aux->NumberLowPart = 0;
+ Aux->Selection = 0;
+ CurrentOffset += sizeof(coff_aux_section_definition);
+
+ // Now write a symbol for each relocation.
+ for (unsigned i = 0; i < Data.size(); i++) {
+ auto RelocationName = formatv("$R{0:X-6}", i & 0xffffff).sstr<COFF::NameSize>();
+ Symbol = reinterpret_cast<coff_symbol16 *>(BufferStart + CurrentOffset);
+ coffnamecpy(Symbol->Name.ShortName, RelocationName);
+ Symbol->Value = DataOffsets[i];
+ Symbol->SectionNumber = 2;
+ Symbol->Type = COFF::IMAGE_SYM_DTYPE_NULL;
+ Symbol->StorageClass = COFF::IMAGE_SYM_CLASS_STATIC;
+ Symbol->NumberOfAuxSymbols = 0;
+ CurrentOffset += sizeof(coff_symbol16);
+ }
+}
+
+void WindowsResourceCOFFWriter::writeStringTable() {
+ // Just 4 null bytes for the string table.
+ auto COFFStringTable = reinterpret_cast<void *>(BufferStart + CurrentOffset);
+ memset(COFFStringTable, 0, 4);
+}
+
+void WindowsResourceCOFFWriter::writeDirectoryTree() {
+ // Traverse parsed resource tree breadth-first and write the corresponding
+ // COFF objects.
+ std::queue<const WindowsResourceParser::TreeNode *> Queue;
+ Queue.push(&Resources);
+ uint32_t NextLevelOffset =
+ sizeof(coff_resource_dir_table) + (Resources.getStringChildren().size() +
+ Resources.getIDChildren().size()) *
+ sizeof(coff_resource_dir_entry);
+ std::vector<const WindowsResourceParser::TreeNode *> DataEntriesTreeOrder;
+ uint32_t CurrentRelativeOffset = 0;
+
+ while (!Queue.empty()) {
+ auto CurrentNode = Queue.front();
+ Queue.pop();
+ auto *Table = reinterpret_cast<coff_resource_dir_table *>(BufferStart +
+ CurrentOffset);
+ Table->Characteristics = CurrentNode->getCharacteristics();
+ Table->TimeDateStamp = 0;
+ Table->MajorVersion = CurrentNode->getMajorVersion();
+ Table->MinorVersion = CurrentNode->getMinorVersion();
+ auto &IDChildren = CurrentNode->getIDChildren();
+ auto &StringChildren = CurrentNode->getStringChildren();
+ Table->NumberOfNameEntries = StringChildren.size();
+ Table->NumberOfIDEntries = IDChildren.size();
+ CurrentOffset += sizeof(coff_resource_dir_table);
+ CurrentRelativeOffset += sizeof(coff_resource_dir_table);
+
+ // Write the directory entries immediately following each directory table.
+ for (auto const &Child : StringChildren) {
+ auto *Entry = reinterpret_cast<coff_resource_dir_entry *>(BufferStart +
+ CurrentOffset);
+ Entry->Identifier.setNameOffset(
+ StringTableOffsets[Child.second->getStringIndex()]);
+ if (Child.second->checkIsDataNode()) {
+ Entry->Offset.DataEntryOffset = NextLevelOffset;
+ NextLevelOffset += sizeof(coff_resource_data_entry);
+ DataEntriesTreeOrder.push_back(Child.second.get());
+ } else {
+ Entry->Offset.SubdirOffset = NextLevelOffset + (1 << 31);
+ NextLevelOffset += sizeof(coff_resource_dir_table) +
+ (Child.second->getStringChildren().size() +
+ Child.second->getIDChildren().size()) *
+ sizeof(coff_resource_dir_entry);
+ Queue.push(Child.second.get());
+ }
+ CurrentOffset += sizeof(coff_resource_dir_entry);
+ CurrentRelativeOffset += sizeof(coff_resource_dir_entry);
+ }
+ for (auto const &Child : IDChildren) {
+ auto *Entry = reinterpret_cast<coff_resource_dir_entry *>(BufferStart +
+ CurrentOffset);
+ Entry->Identifier.ID = Child.first;
+ if (Child.second->checkIsDataNode()) {
+ Entry->Offset.DataEntryOffset = NextLevelOffset;
+ NextLevelOffset += sizeof(coff_resource_data_entry);
+ DataEntriesTreeOrder.push_back(Child.second.get());
+ } else {
+ Entry->Offset.SubdirOffset = NextLevelOffset + (1 << 31);
+ NextLevelOffset += sizeof(coff_resource_dir_table) +
+ (Child.second->getStringChildren().size() +
+ Child.second->getIDChildren().size()) *
+ sizeof(coff_resource_dir_entry);
+ Queue.push(Child.second.get());
+ }
+ CurrentOffset += sizeof(coff_resource_dir_entry);
+ CurrentRelativeOffset += sizeof(coff_resource_dir_entry);
+ }
+ }
+
+ RelocationAddresses.resize(Data.size());
+ // Now write all the resource data entries.
+ for (auto DataNodes : DataEntriesTreeOrder) {
+ auto *Entry = reinterpret_cast<coff_resource_data_entry *>(BufferStart +
+ CurrentOffset);
+ RelocationAddresses[DataNodes->getDataIndex()] = CurrentRelativeOffset;
+ Entry->DataRVA = 0; // Set to zero because it is a relocation.
+ Entry->DataSize = Data[DataNodes->getDataIndex()].size();
+ Entry->Codepage = 0;
+ Entry->Reserved = 0;
+ CurrentOffset += sizeof(coff_resource_data_entry);
+ CurrentRelativeOffset += sizeof(coff_resource_data_entry);
+ }
+}
+
+void WindowsResourceCOFFWriter::writeDirectoryStringTable() {
+ // Now write the directory string table for .rsrc$01
+ uint32_t TotalStringTableSize = 0;
+ for (auto &String : StringTable) {
+ uint16_t Length = String.size();
+ support::endian::write16le(BufferStart + CurrentOffset, Length);
+ CurrentOffset += sizeof(uint16_t);
+ auto *Start = reinterpret_cast<UTF16 *>(BufferStart + CurrentOffset);
+ llvm::copy(String, Start);
+ CurrentOffset += Length * sizeof(UTF16);
+ TotalStringTableSize += Length * sizeof(UTF16) + sizeof(uint16_t);
+ }
+ CurrentOffset +=
+ alignTo(TotalStringTableSize, sizeof(uint32_t)) - TotalStringTableSize;
+}
+
+void WindowsResourceCOFFWriter::writeFirstSectionRelocations() {
+
+ // Now write the relocations for .rsrc$01
+ // Five symbols already in table before we start, @feat.00 and 2 for each
+ // .rsrc section.
+ uint32_t NextSymbolIndex = 5;
+ for (unsigned i = 0; i < Data.size(); i++) {
+ auto *Reloc =
+ reinterpret_cast<coff_relocation *>(BufferStart + CurrentOffset);
+ Reloc->VirtualAddress = RelocationAddresses[i];
+ Reloc->SymbolTableIndex = NextSymbolIndex++;
+ switch (MachineType) {
+ case COFF::IMAGE_FILE_MACHINE_ARMNT:
+ Reloc->Type = COFF::IMAGE_REL_ARM_ADDR32NB;
+ break;
+ case COFF::IMAGE_FILE_MACHINE_AMD64:
+ Reloc->Type = COFF::IMAGE_REL_AMD64_ADDR32NB;
+ break;
+ case COFF::IMAGE_FILE_MACHINE_I386:
+ Reloc->Type = COFF::IMAGE_REL_I386_DIR32NB;
+ break;
+ case COFF::IMAGE_FILE_MACHINE_ARM64:
+ Reloc->Type = COFF::IMAGE_REL_ARM64_ADDR32NB;
+ break;
+ default:
+ llvm_unreachable("unknown machine type");
+ }
+ CurrentOffset += sizeof(coff_relocation);
+ }
+}
+
+Expected<std::unique_ptr<MemoryBuffer>>
+writeWindowsResourceCOFF(COFF::MachineTypes MachineType,
+ const WindowsResourceParser &Parser,
+ uint32_t TimeDateStamp) {
+ Error E = Error::success();
+ WindowsResourceCOFFWriter Writer(MachineType, Parser, E);
+ if (E)
+ return std::move(E);
+ return Writer.write(TimeDateStamp);
+}
+
+} // namespace object
+} // namespace llvm