1//===-- WindowsResource.cpp -------------------------------------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file implements the .res file class.
10//
11//===----------------------------------------------------------------------===//
12
13#include "llvm/Object/WindowsResource.h"
14#include "llvm/Object/COFF.h"
15#include "llvm/Object/WindowsMachineFlag.h"
16#include "llvm/Support/FormatVariadic.h"
17#include "llvm/Support/MathExtras.h"
18#include "llvm/Support/ScopedPrinter.h"
19#include <ctime>
20#include <queue>
21
22using namespace llvm;
23using namespace object;
24
25namespace llvm {
26namespace object {
27
28#define RETURN_IF_ERROR(X) \
29 if (auto EC = X) \
30 return EC;
31
32#define UNWRAP_REF_OR_RETURN(Name, Expr) \
33 auto Name##OrErr = Expr; \
34 if (!Name##OrErr) \
35 return Name##OrErr.takeError(); \
36 const auto &Name = *Name##OrErr;
37
38#define UNWRAP_OR_RETURN(Name, Expr) \
39 auto Name##OrErr = Expr; \
40 if (!Name##OrErr) \
41 return Name##OrErr.takeError(); \
42 auto Name = *Name##OrErr;
43
44const uint32_t MIN_HEADER_SIZE = 7 * sizeof(uint32_t) + 2 * sizeof(uint16_t);
45
46// COFF files seem to be inconsistent with alignment between sections, just use
47// 8-byte because it makes everyone happy.
48const uint32_t SECTION_ALIGNMENT = sizeof(uint64_t);
49
50WindowsResource::WindowsResource(MemoryBufferRef Source)
51 : Binary(Binary::ID_WinRes, Source) {
52 size_t LeadingSize = WIN_RES_MAGIC_SIZE + WIN_RES_NULL_ENTRY_SIZE;
53 BBS = BinaryByteStream(Data.getBuffer().drop_front(N: LeadingSize),
54 llvm::endianness::little);
55}
56
57// static
58Expected<std::unique_ptr<WindowsResource>>
59WindowsResource::createWindowsResource(MemoryBufferRef Source) {
60 if (Source.getBufferSize() < WIN_RES_MAGIC_SIZE + WIN_RES_NULL_ENTRY_SIZE)
61 return make_error<GenericBinaryError>(
62 Args: Source.getBufferIdentifier() + ": too small to be a resource file",
63 Args: object_error::invalid_file_type);
64 std::unique_ptr<WindowsResource> Ret(new WindowsResource(Source));
65 return std::move(Ret);
66}
67
68Expected<ResourceEntryRef> WindowsResource::getHeadEntry() {
69 if (BBS.getLength() < sizeof(WinResHeaderPrefix) + sizeof(WinResHeaderSuffix))
70 return make_error<EmptyResError>(Args: getFileName() + " contains no entries",
71 Args: object_error::unexpected_eof);
72 return ResourceEntryRef::create(Ref: BinaryStreamRef(BBS), Owner: this);
73}
74
75ResourceEntryRef::ResourceEntryRef(BinaryStreamRef Ref,
76 const WindowsResource *Owner)
77 : Reader(Ref), Owner(Owner) {}
78
79Expected<ResourceEntryRef>
80ResourceEntryRef::create(BinaryStreamRef BSR, const WindowsResource *Owner) {
81 auto Ref = ResourceEntryRef(BSR, Owner);
82 if (auto E = Ref.loadNext())
83 return std::move(E);
84 return Ref;
85}
86
87Error ResourceEntryRef::moveNext(bool &End) {
88 // Reached end of all the entries.
89 if (Reader.bytesRemaining() == 0) {
90 End = true;
91 return Error::success();
92 }
93 RETURN_IF_ERROR(loadNext());
94
95 return Error::success();
96}
97
98static Error readStringOrId(BinaryStreamReader &Reader, uint16_t &ID,
99 ArrayRef<UTF16> &Str, bool &IsString) {
100 uint16_t IDFlag;
101 RETURN_IF_ERROR(Reader.readInteger(IDFlag));
102 IsString = IDFlag != 0xffff;
103
104 if (IsString) {
105 Reader.setOffset(
106 Reader.getOffset() -
107 sizeof(uint16_t)); // Re-read the bytes which we used to check the flag.
108 RETURN_IF_ERROR(Reader.readWideString(Str));
109 } else
110 RETURN_IF_ERROR(Reader.readInteger(ID));
111
112 return Error::success();
113}
114
115Error ResourceEntryRef::loadNext() {
116 const WinResHeaderPrefix *Prefix;
117 RETURN_IF_ERROR(Reader.readObject(Prefix));
118
119 if (Prefix->HeaderSize < MIN_HEADER_SIZE)
120 return make_error<GenericBinaryError>(Args: Owner->getFileName() +
121 ": header size too small",
122 Args: object_error::parse_failed);
123
124 RETURN_IF_ERROR(readStringOrId(Reader, TypeID, Type, IsStringType));
125
126 RETURN_IF_ERROR(readStringOrId(Reader, NameID, Name, IsStringName));
127
128 RETURN_IF_ERROR(Reader.padToAlignment(WIN_RES_HEADER_ALIGNMENT));
129
130 RETURN_IF_ERROR(Reader.readObject(Suffix));
131
132 RETURN_IF_ERROR(Reader.readArray(Data, Prefix->DataSize));
133
134 RETURN_IF_ERROR(Reader.padToAlignment(WIN_RES_DATA_ALIGNMENT));
135
136 return Error::success();
137}
138
139WindowsResourceParser::WindowsResourceParser(bool MinGW)
140 : Root(false), MinGW(MinGW) {}
141
142void printResourceTypeName(uint16_t TypeID, raw_ostream &OS) {
143 switch (TypeID) {
144 case 1: OS << "CURSOR (ID 1)"; break;
145 case 2: OS << "BITMAP (ID 2)"; break;
146 case 3: OS << "ICON (ID 3)"; break;
147 case 4: OS << "MENU (ID 4)"; break;
148 case 5: OS << "DIALOG (ID 5)"; break;
149 case 6: OS << "STRINGTABLE (ID 6)"; break;
150 case 7: OS << "FONTDIR (ID 7)"; break;
151 case 8: OS << "FONT (ID 8)"; break;
152 case 9: OS << "ACCELERATOR (ID 9)"; break;
153 case 10: OS << "RCDATA (ID 10)"; break;
154 case 11: OS << "MESSAGETABLE (ID 11)"; break;
155 case 12: OS << "GROUP_CURSOR (ID 12)"; break;
156 case 14: OS << "GROUP_ICON (ID 14)"; break;
157 case 16: OS << "VERSIONINFO (ID 16)"; break;
158 case 17: OS << "DLGINCLUDE (ID 17)"; break;
159 case 19: OS << "PLUGPLAY (ID 19)"; break;
160 case 20: OS << "VXD (ID 20)"; break;
161 case 21: OS << "ANICURSOR (ID 21)"; break;
162 case 22: OS << "ANIICON (ID 22)"; break;
163 case 23: OS << "HTML (ID 23)"; break;
164 case 24: OS << "MANIFEST (ID 24)"; break;
165 default: OS << "ID " << TypeID; break;
166 }
167}
168
169static bool convertUTF16LEToUTF8String(ArrayRef<UTF16> Src, std::string &Out) {
170 if (!sys::IsBigEndianHost)
171 return convertUTF16ToUTF8String(Src, Out);
172
173 std::vector<UTF16> EndianCorrectedSrc;
174 EndianCorrectedSrc.resize(new_size: Src.size() + 1);
175 llvm::copy(Range&: Src, Out: EndianCorrectedSrc.begin() + 1);
176 EndianCorrectedSrc[0] = UNI_UTF16_BYTE_ORDER_MARK_SWAPPED;
177 return convertUTF16ToUTF8String(Src: ArrayRef(EndianCorrectedSrc), Out);
178}
179
180static std::string makeDuplicateResourceError(
181 const ResourceEntryRef &Entry, StringRef File1, StringRef File2) {
182 std::string Ret;
183 raw_string_ostream OS(Ret);
184
185 OS << "duplicate resource:";
186
187 OS << " type ";
188 if (Entry.checkTypeString()) {
189 std::string UTF8;
190 if (!convertUTF16LEToUTF8String(Src: Entry.getTypeString(), Out&: UTF8))
191 UTF8 = "(failed conversion from UTF16)";
192 OS << '\"' << UTF8 << '\"';
193 } else
194 printResourceTypeName(TypeID: Entry.getTypeID(), OS);
195
196 OS << "/name ";
197 if (Entry.checkNameString()) {
198 std::string UTF8;
199 if (!convertUTF16LEToUTF8String(Src: Entry.getNameString(), Out&: UTF8))
200 UTF8 = "(failed conversion from UTF16)";
201 OS << '\"' << UTF8 << '\"';
202 } else {
203 OS << "ID " << Entry.getNameID();
204 }
205
206 OS << "/language " << Entry.getLanguage() << ", in " << File1 << " and in "
207 << File2;
208
209 return OS.str();
210}
211
212static void printStringOrID(const WindowsResourceParser::StringOrID &S,
213 raw_string_ostream &OS, bool IsType, bool IsID) {
214 if (S.IsString) {
215 std::string UTF8;
216 if (!convertUTF16LEToUTF8String(Src: S.String, Out&: UTF8))
217 UTF8 = "(failed conversion from UTF16)";
218 OS << '\"' << UTF8 << '\"';
219 } else if (IsType)
220 printResourceTypeName(TypeID: S.ID, OS);
221 else if (IsID)
222 OS << "ID " << S.ID;
223 else
224 OS << S.ID;
225}
226
227static std::string makeDuplicateResourceError(
228 const std::vector<WindowsResourceParser::StringOrID> &Context,
229 StringRef File1, StringRef File2) {
230 std::string Ret;
231 raw_string_ostream OS(Ret);
232
233 OS << "duplicate resource:";
234
235 if (Context.size() >= 1) {
236 OS << " type ";
237 printStringOrID(S: Context[0], OS, /* IsType */ true, /* IsID */ true);
238 }
239
240 if (Context.size() >= 2) {
241 OS << "/name ";
242 printStringOrID(S: Context[1], OS, /* IsType */ false, /* IsID */ true);
243 }
244
245 if (Context.size() >= 3) {
246 OS << "/language ";
247 printStringOrID(S: Context[2], OS, /* IsType */ false, /* IsID */ false);
248 }
249 OS << ", in " << File1 << " and in " << File2;
250
251 return OS.str();
252}
253
254// MinGW specific. Remove default manifests (with language zero) if there are
255// other manifests present, and report an error if there are more than one
256// manifest with a non-zero language code.
257// GCC has the concept of a default manifest resource object, which gets
258// linked in implicitly if present. This default manifest has got language
259// id zero, and should be dropped silently if there's another manifest present.
260// If the user resources surprisignly had a manifest with language id zero,
261// we should also ignore the duplicate default manifest.
262void WindowsResourceParser::cleanUpManifests(
263 std::vector<std::string> &Duplicates) {
264 auto TypeIt = Root.IDChildren.find(/* RT_MANIFEST */ x: 24);
265 if (TypeIt == Root.IDChildren.end())
266 return;
267
268 TreeNode *TypeNode = TypeIt->second.get();
269 auto NameIt =
270 TypeNode->IDChildren.find(/* CREATEPROCESS_MANIFEST_RESOURCE_ID */ x: 1);
271 if (NameIt == TypeNode->IDChildren.end())
272 return;
273
274 TreeNode *NameNode = NameIt->second.get();
275 if (NameNode->IDChildren.size() <= 1)
276 return; // None or one manifest present, all good.
277
278 // If we have more than one manifest, drop the language zero one if present,
279 // and check again.
280 auto LangZeroIt = NameNode->IDChildren.find(x: 0);
281 if (LangZeroIt != NameNode->IDChildren.end() &&
282 LangZeroIt->second->IsDataNode) {
283 uint32_t RemovedIndex = LangZeroIt->second->DataIndex;
284 NameNode->IDChildren.erase(position: LangZeroIt);
285 Data.erase(position: Data.begin() + RemovedIndex);
286 Root.shiftDataIndexDown(Index: RemovedIndex);
287
288 // If we're now down to one manifest, all is good.
289 if (NameNode->IDChildren.size() <= 1)
290 return;
291 }
292
293 // More than one non-language-zero manifest
294 auto FirstIt = NameNode->IDChildren.begin();
295 uint32_t FirstLang = FirstIt->first;
296 TreeNode *FirstNode = FirstIt->second.get();
297 auto LastIt = NameNode->IDChildren.rbegin();
298 uint32_t LastLang = LastIt->first;
299 TreeNode *LastNode = LastIt->second.get();
300 Duplicates.push_back(
301 x: ("duplicate non-default manifests with languages " + Twine(FirstLang) +
302 " in " + InputFilenames[FirstNode->Origin] + " and " + Twine(LastLang) +
303 " in " + InputFilenames[LastNode->Origin])
304 .str());
305}
306
307// Ignore duplicates of manifests with language zero (the default manifest),
308// in case the user has provided a manifest with that language id. See
309// the function comment above for context. Only returns true if MinGW is set
310// to true.
311bool WindowsResourceParser::shouldIgnoreDuplicate(
312 const ResourceEntryRef &Entry) const {
313 return MinGW && !Entry.checkTypeString() &&
314 Entry.getTypeID() == /* RT_MANIFEST */ 24 &&
315 !Entry.checkNameString() &&
316 Entry.getNameID() == /* CREATEPROCESS_MANIFEST_RESOURCE_ID */ 1 &&
317 Entry.getLanguage() == 0;
318}
319
320bool WindowsResourceParser::shouldIgnoreDuplicate(
321 const std::vector<StringOrID> &Context) const {
322 return MinGW && Context.size() == 3 && !Context[0].IsString &&
323 Context[0].ID == /* RT_MANIFEST */ 24 && !Context[1].IsString &&
324 Context[1].ID == /* CREATEPROCESS_MANIFEST_RESOURCE_ID */ 1 &&
325 !Context[2].IsString && Context[2].ID == 0;
326}
327
328Error WindowsResourceParser::parse(WindowsResource *WR,
329 std::vector<std::string> &Duplicates) {
330 auto EntryOrErr = WR->getHeadEntry();
331 if (!EntryOrErr) {
332 auto E = EntryOrErr.takeError();
333 if (E.isA<EmptyResError>()) {
334 // Check if the .res file contains no entries. In this case we don't have
335 // to throw an error but can rather just return without parsing anything.
336 // This applies for files which have a valid PE header magic and the
337 // mandatory empty null resource entry. Files which do not fit this
338 // criteria would have already been filtered out by
339 // WindowsResource::createWindowsResource().
340 consumeError(Err: std::move(E));
341 return Error::success();
342 }
343 return E;
344 }
345
346 ResourceEntryRef Entry = EntryOrErr.get();
347 uint32_t Origin = InputFilenames.size();
348 InputFilenames.push_back(x: std::string(WR->getFileName()));
349 bool End = false;
350 while (!End) {
351
352 TreeNode *Node;
353 bool IsNewNode = Root.addEntry(Entry, Origin, Data, StringTable, Result&: Node);
354 if (!IsNewNode) {
355 if (!shouldIgnoreDuplicate(Entry))
356 Duplicates.push_back(x: makeDuplicateResourceError(
357 Entry, File1: InputFilenames[Node->Origin], File2: WR->getFileName()));
358 }
359
360 RETURN_IF_ERROR(Entry.moveNext(End));
361 }
362
363 return Error::success();
364}
365
366Error WindowsResourceParser::parse(ResourceSectionRef &RSR, StringRef Filename,
367 std::vector<std::string> &Duplicates) {
368 UNWRAP_REF_OR_RETURN(BaseTable, RSR.getBaseTable());
369 uint32_t Origin = InputFilenames.size();
370 InputFilenames.push_back(x: std::string(Filename));
371 std::vector<StringOrID> Context;
372 return addChildren(Node&: Root, RSR, Table: BaseTable, Origin, Context, Duplicates);
373}
374
375void WindowsResourceParser::printTree(raw_ostream &OS) const {
376 ScopedPrinter Writer(OS);
377 Root.print(Writer, Name: "Resource Tree");
378}
379
380bool WindowsResourceParser::TreeNode::addEntry(
381 const ResourceEntryRef &Entry, uint32_t Origin,
382 std::vector<std::vector<uint8_t>> &Data,
383 std::vector<std::vector<UTF16>> &StringTable, TreeNode *&Result) {
384 TreeNode &TypeNode = addTypeNode(Entry, StringTable);
385 TreeNode &NameNode = TypeNode.addNameNode(Entry, StringTable);
386 return NameNode.addLanguageNode(Entry, Origin, Data, Result);
387}
388
389Error WindowsResourceParser::addChildren(TreeNode &Node,
390 ResourceSectionRef &RSR,
391 const coff_resource_dir_table &Table,
392 uint32_t Origin,
393 std::vector<StringOrID> &Context,
394 std::vector<std::string> &Duplicates) {
395
396 for (int i = 0; i < Table.NumberOfNameEntries + Table.NumberOfIDEntries;
397 i++) {
398 UNWRAP_REF_OR_RETURN(Entry, RSR.getTableEntry(Table, i));
399 TreeNode *Child;
400
401 if (Entry.Offset.isSubDir()) {
402
403 // Create a new subdirectory and recurse
404 if (i < Table.NumberOfNameEntries) {
405 UNWRAP_OR_RETURN(NameString, RSR.getEntryNameString(Entry));
406 Child = &Node.addNameChild(NameRef: NameString, StringTable);
407 Context.push_back(x: StringOrID(NameString));
408 } else {
409 Child = &Node.addIDChild(ID: Entry.Identifier.ID);
410 Context.push_back(x: StringOrID(Entry.Identifier.ID));
411 }
412
413 UNWRAP_REF_OR_RETURN(NextTable, RSR.getEntrySubDir(Entry));
414 Error E =
415 addChildren(Node&: *Child, RSR, Table: NextTable, Origin, Context, Duplicates);
416 if (E)
417 return E;
418 Context.pop_back();
419
420 } else {
421
422 // Data leaves are supposed to have a numeric ID as identifier (language).
423 if (Table.NumberOfNameEntries > 0)
424 return createStringError(EC: object_error::parse_failed,
425 Msg: "unexpected string key for data object");
426
427 // Try adding a data leaf
428 UNWRAP_REF_OR_RETURN(DataEntry, RSR.getEntryData(Entry));
429 TreeNode *Child;
430 Context.push_back(x: StringOrID(Entry.Identifier.ID));
431 bool Added = Node.addDataChild(ID: Entry.Identifier.ID, MajorVersion: Table.MajorVersion,
432 MinorVersion: Table.MinorVersion, Characteristics: Table.Characteristics,
433 Origin, DataIndex: Data.size(), Result&: Child);
434 if (Added) {
435 UNWRAP_OR_RETURN(Contents, RSR.getContents(DataEntry));
436 Data.push_back(x: ArrayRef<uint8_t>(
437 reinterpret_cast<const uint8_t *>(Contents.data()),
438 Contents.size()));
439 } else {
440 if (!shouldIgnoreDuplicate(Context))
441 Duplicates.push_back(x: makeDuplicateResourceError(
442 Context, File1: InputFilenames[Child->Origin], File2: InputFilenames.back()));
443 }
444 Context.pop_back();
445
446 }
447 }
448 return Error::success();
449}
450
451WindowsResourceParser::TreeNode::TreeNode(uint32_t StringIndex)
452 : StringIndex(StringIndex) {}
453
454WindowsResourceParser::TreeNode::TreeNode(uint16_t MajorVersion,
455 uint16_t MinorVersion,
456 uint32_t Characteristics,
457 uint32_t Origin, uint32_t DataIndex)
458 : IsDataNode(true), DataIndex(DataIndex), MajorVersion(MajorVersion),
459 MinorVersion(MinorVersion), Characteristics(Characteristics),
460 Origin(Origin) {}
461
462std::unique_ptr<WindowsResourceParser::TreeNode>
463WindowsResourceParser::TreeNode::createStringNode(uint32_t Index) {
464 return std::unique_ptr<TreeNode>(new TreeNode(Index));
465}
466
467std::unique_ptr<WindowsResourceParser::TreeNode>
468WindowsResourceParser::TreeNode::createIDNode() {
469 return std::unique_ptr<TreeNode>(new TreeNode(0));
470}
471
472std::unique_ptr<WindowsResourceParser::TreeNode>
473WindowsResourceParser::TreeNode::createDataNode(uint16_t MajorVersion,
474 uint16_t MinorVersion,
475 uint32_t Characteristics,
476 uint32_t Origin,
477 uint32_t DataIndex) {
478 return std::unique_ptr<TreeNode>(new TreeNode(
479 MajorVersion, MinorVersion, Characteristics, Origin, DataIndex));
480}
481
482WindowsResourceParser::TreeNode &WindowsResourceParser::TreeNode::addTypeNode(
483 const ResourceEntryRef &Entry,
484 std::vector<std::vector<UTF16>> &StringTable) {
485 if (Entry.checkTypeString())
486 return addNameChild(NameRef: Entry.getTypeString(), StringTable);
487 else
488 return addIDChild(ID: Entry.getTypeID());
489}
490
491WindowsResourceParser::TreeNode &WindowsResourceParser::TreeNode::addNameNode(
492 const ResourceEntryRef &Entry,
493 std::vector<std::vector<UTF16>> &StringTable) {
494 if (Entry.checkNameString())
495 return addNameChild(NameRef: Entry.getNameString(), StringTable);
496 else
497 return addIDChild(ID: Entry.getNameID());
498}
499
500bool WindowsResourceParser::TreeNode::addLanguageNode(
501 const ResourceEntryRef &Entry, uint32_t Origin,
502 std::vector<std::vector<uint8_t>> &Data, TreeNode *&Result) {
503 bool Added = addDataChild(ID: Entry.getLanguage(), MajorVersion: Entry.getMajorVersion(),
504 MinorVersion: Entry.getMinorVersion(), Characteristics: Entry.getCharacteristics(),
505 Origin, DataIndex: Data.size(), Result);
506 if (Added)
507 Data.push_back(x: Entry.getData());
508 return Added;
509}
510
511bool WindowsResourceParser::TreeNode::addDataChild(
512 uint32_t ID, uint16_t MajorVersion, uint16_t MinorVersion,
513 uint32_t Characteristics, uint32_t Origin, uint32_t DataIndex,
514 TreeNode *&Result) {
515 auto NewChild = createDataNode(MajorVersion, MinorVersion, Characteristics,
516 Origin, DataIndex);
517 auto ElementInserted = IDChildren.emplace(args&: ID, args: std::move(NewChild));
518 Result = ElementInserted.first->second.get();
519 return ElementInserted.second;
520}
521
522WindowsResourceParser::TreeNode &WindowsResourceParser::TreeNode::addIDChild(
523 uint32_t ID) {
524 auto Child = IDChildren.find(x: ID);
525 if (Child == IDChildren.end()) {
526 auto NewChild = createIDNode();
527 WindowsResourceParser::TreeNode &Node = *NewChild;
528 IDChildren.emplace(args&: ID, args: std::move(NewChild));
529 return Node;
530 } else
531 return *(Child->second);
532}
533
534WindowsResourceParser::TreeNode &WindowsResourceParser::TreeNode::addNameChild(
535 ArrayRef<UTF16> NameRef, std::vector<std::vector<UTF16>> &StringTable) {
536 std::string NameString;
537 convertUTF16LEToUTF8String(Src: NameRef, Out&: NameString);
538
539 auto Child = StringChildren.find(x: NameString);
540 if (Child == StringChildren.end()) {
541 auto NewChild = createStringNode(Index: StringTable.size());
542 StringTable.push_back(x: NameRef);
543 WindowsResourceParser::TreeNode &Node = *NewChild;
544 StringChildren.emplace(args&: NameString, args: std::move(NewChild));
545 return Node;
546 } else
547 return *(Child->second);
548}
549
550void WindowsResourceParser::TreeNode::print(ScopedPrinter &Writer,
551 StringRef Name) const {
552 ListScope NodeScope(Writer, Name);
553 for (auto const &Child : StringChildren) {
554 Child.second->print(Writer, Name: Child.first);
555 }
556 for (auto const &Child : IDChildren) {
557 Child.second->print(Writer, Name: to_string(Value: Child.first));
558 }
559}
560
561// This function returns the size of the entire resource tree, including
562// directory tables, directory entries, and data entries. It does not include
563// the directory strings or the relocations of the .rsrc section.
564uint32_t WindowsResourceParser::TreeNode::getTreeSize() const {
565 uint32_t Size = (IDChildren.size() + StringChildren.size()) *
566 sizeof(coff_resource_dir_entry);
567
568 // Reached a node pointing to a data entry.
569 if (IsDataNode) {
570 Size += sizeof(coff_resource_data_entry);
571 return Size;
572 }
573
574 // If the node does not point to data, it must have a directory table pointing
575 // to other nodes.
576 Size += sizeof(coff_resource_dir_table);
577
578 for (auto const &Child : StringChildren) {
579 Size += Child.second->getTreeSize();
580 }
581 for (auto const &Child : IDChildren) {
582 Size += Child.second->getTreeSize();
583 }
584 return Size;
585}
586
587// Shift DataIndex of all data children with an Index greater or equal to the
588// given one, to fill a gap from removing an entry from the Data vector.
589void WindowsResourceParser::TreeNode::shiftDataIndexDown(uint32_t Index) {
590 if (IsDataNode && DataIndex >= Index) {
591 DataIndex--;
592 } else {
593 for (auto &Child : IDChildren)
594 Child.second->shiftDataIndexDown(Index);
595 for (auto &Child : StringChildren)
596 Child.second->shiftDataIndexDown(Index);
597 }
598}
599
600class WindowsResourceCOFFWriter {
601public:
602 WindowsResourceCOFFWriter(COFF::MachineTypes MachineType,
603 const WindowsResourceParser &Parser, Error &E);
604 std::unique_ptr<MemoryBuffer> write(uint32_t TimeDateStamp);
605
606private:
607 void performFileLayout();
608 void performSectionOneLayout();
609 void performSectionTwoLayout();
610 void writeCOFFHeader(uint32_t TimeDateStamp);
611 void writeFirstSectionHeader();
612 void writeSecondSectionHeader();
613 void writeFirstSection();
614 void writeSecondSection();
615 void writeSymbolTable();
616 void writeStringTable();
617 void writeDirectoryTree();
618 void writeDirectoryStringTable();
619 void writeFirstSectionRelocations();
620 std::unique_ptr<WritableMemoryBuffer> OutputBuffer;
621 char *BufferStart;
622 uint64_t CurrentOffset = 0;
623 COFF::MachineTypes MachineType;
624 const WindowsResourceParser::TreeNode &Resources;
625 const ArrayRef<std::vector<uint8_t>> Data;
626 uint64_t FileSize;
627 uint32_t SymbolTableOffset;
628 uint32_t SectionOneSize;
629 uint32_t SectionOneOffset;
630 uint32_t SectionOneRelocations;
631 uint32_t SectionTwoSize;
632 uint32_t SectionTwoOffset;
633 const ArrayRef<std::vector<UTF16>> StringTable;
634 std::vector<uint32_t> StringTableOffsets;
635 std::vector<uint32_t> DataOffsets;
636 std::vector<uint32_t> RelocationAddresses;
637};
638
639WindowsResourceCOFFWriter::WindowsResourceCOFFWriter(
640 COFF::MachineTypes MachineType, const WindowsResourceParser &Parser,
641 Error &E)
642 : MachineType(MachineType), Resources(Parser.getTree()),
643 Data(Parser.getData()), StringTable(Parser.getStringTable()) {
644 performFileLayout();
645
646 OutputBuffer = WritableMemoryBuffer::getNewMemBuffer(
647 Size: FileSize, BufferName: "internal .obj file created from .res files");
648}
649
650void WindowsResourceCOFFWriter::performFileLayout() {
651 // Add size of COFF header.
652 FileSize = COFF::Header16Size;
653
654 // one .rsrc section header for directory tree, another for resource data.
655 FileSize += 2 * COFF::SectionSize;
656
657 performSectionOneLayout();
658 performSectionTwoLayout();
659
660 // We have reached the address of the symbol table.
661 SymbolTableOffset = FileSize;
662
663 FileSize += COFF::Symbol16Size; // size of the @feat.00 symbol.
664 FileSize += 4 * COFF::Symbol16Size; // symbol + aux for each section.
665 FileSize += Data.size() * COFF::Symbol16Size; // 1 symbol per resource.
666 FileSize += 4; // four null bytes for the string table.
667}
668
669void WindowsResourceCOFFWriter::performSectionOneLayout() {
670 SectionOneOffset = FileSize;
671
672 SectionOneSize = Resources.getTreeSize();
673 uint32_t CurrentStringOffset = SectionOneSize;
674 uint32_t TotalStringTableSize = 0;
675 for (auto const &String : StringTable) {
676 StringTableOffsets.push_back(x: CurrentStringOffset);
677 uint32_t StringSize = String.size() * sizeof(UTF16) + sizeof(uint16_t);
678 CurrentStringOffset += StringSize;
679 TotalStringTableSize += StringSize;
680 }
681 SectionOneSize += alignTo(Value: TotalStringTableSize, Align: sizeof(uint32_t));
682
683 // account for the relocations of section one.
684 SectionOneRelocations = FileSize + SectionOneSize;
685 FileSize += SectionOneSize;
686 FileSize +=
687 Data.size() * COFF::RelocationSize; // one relocation for each resource.
688 FileSize = alignTo(Value: FileSize, Align: SECTION_ALIGNMENT);
689}
690
691void WindowsResourceCOFFWriter::performSectionTwoLayout() {
692 // add size of .rsrc$2 section, which contains all resource data on 8-byte
693 // alignment.
694 SectionTwoOffset = FileSize;
695 SectionTwoSize = 0;
696 for (auto const &Entry : Data) {
697 DataOffsets.push_back(x: SectionTwoSize);
698 SectionTwoSize += alignTo(Value: Entry.size(), Align: sizeof(uint64_t));
699 }
700 FileSize += SectionTwoSize;
701 FileSize = alignTo(Value: FileSize, Align: SECTION_ALIGNMENT);
702}
703
704std::unique_ptr<MemoryBuffer>
705WindowsResourceCOFFWriter::write(uint32_t TimeDateStamp) {
706 BufferStart = OutputBuffer->getBufferStart();
707
708 writeCOFFHeader(TimeDateStamp);
709 writeFirstSectionHeader();
710 writeSecondSectionHeader();
711 writeFirstSection();
712 writeSecondSection();
713 writeSymbolTable();
714 writeStringTable();
715
716 return std::move(OutputBuffer);
717}
718
719// According to COFF specification, if the Src has a size equal to Dest,
720// it's okay to *not* copy the trailing zero.
721static void coffnamecpy(char (&Dest)[COFF::NameSize], StringRef Src) {
722 assert(Src.size() <= COFF::NameSize &&
723 "Src is larger than COFF::NameSize");
724 assert((Src.size() == COFF::NameSize || Dest[Src.size()] == '\0') &&
725 "Dest not zeroed upon initialization");
726 memcpy(dest: Dest, src: Src.data(), n: Src.size());
727}
728
729void WindowsResourceCOFFWriter::writeCOFFHeader(uint32_t TimeDateStamp) {
730 // Write the COFF header.
731 auto *Header = reinterpret_cast<coff_file_header *>(BufferStart);
732 Header->Machine = MachineType;
733 Header->NumberOfSections = 2;
734 Header->TimeDateStamp = TimeDateStamp;
735 Header->PointerToSymbolTable = SymbolTableOffset;
736 // One symbol for every resource plus 2 for each section and 1 for @feat.00
737 Header->NumberOfSymbols = Data.size() + 5;
738 Header->SizeOfOptionalHeader = 0;
739 // cvtres.exe sets 32BIT_MACHINE even for 64-bit machine types. Match it.
740 Header->Characteristics = COFF::IMAGE_FILE_32BIT_MACHINE;
741}
742
743void WindowsResourceCOFFWriter::writeFirstSectionHeader() {
744 // Write the first section header.
745 CurrentOffset += sizeof(coff_file_header);
746 auto *SectionOneHeader =
747 reinterpret_cast<coff_section *>(BufferStart + CurrentOffset);
748 coffnamecpy(Dest&: SectionOneHeader->Name, Src: ".rsrc$01");
749 SectionOneHeader->VirtualSize = 0;
750 SectionOneHeader->VirtualAddress = 0;
751 SectionOneHeader->SizeOfRawData = SectionOneSize;
752 SectionOneHeader->PointerToRawData = SectionOneOffset;
753 SectionOneHeader->PointerToRelocations = SectionOneRelocations;
754 SectionOneHeader->PointerToLinenumbers = 0;
755 SectionOneHeader->NumberOfRelocations = Data.size();
756 SectionOneHeader->NumberOfLinenumbers = 0;
757 SectionOneHeader->Characteristics += COFF::IMAGE_SCN_CNT_INITIALIZED_DATA;
758 SectionOneHeader->Characteristics += COFF::IMAGE_SCN_MEM_READ;
759}
760
761void WindowsResourceCOFFWriter::writeSecondSectionHeader() {
762 // Write the second section header.
763 CurrentOffset += sizeof(coff_section);
764 auto *SectionTwoHeader =
765 reinterpret_cast<coff_section *>(BufferStart + CurrentOffset);
766 coffnamecpy(Dest&: SectionTwoHeader->Name, Src: ".rsrc$02");
767 SectionTwoHeader->VirtualSize = 0;
768 SectionTwoHeader->VirtualAddress = 0;
769 SectionTwoHeader->SizeOfRawData = SectionTwoSize;
770 SectionTwoHeader->PointerToRawData = SectionTwoOffset;
771 SectionTwoHeader->PointerToRelocations = 0;
772 SectionTwoHeader->PointerToLinenumbers = 0;
773 SectionTwoHeader->NumberOfRelocations = 0;
774 SectionTwoHeader->NumberOfLinenumbers = 0;
775 SectionTwoHeader->Characteristics = COFF::IMAGE_SCN_CNT_INITIALIZED_DATA;
776 SectionTwoHeader->Characteristics += COFF::IMAGE_SCN_MEM_READ;
777}
778
779void WindowsResourceCOFFWriter::writeFirstSection() {
780 // Write section one.
781 CurrentOffset += sizeof(coff_section);
782
783 writeDirectoryTree();
784 writeDirectoryStringTable();
785 writeFirstSectionRelocations();
786
787 CurrentOffset = alignTo(Value: CurrentOffset, Align: SECTION_ALIGNMENT);
788}
789
790void WindowsResourceCOFFWriter::writeSecondSection() {
791 // Now write the .rsrc$02 section.
792 for (auto const &RawDataEntry : Data) {
793 llvm::copy(Range: RawDataEntry, Out: BufferStart + CurrentOffset);
794 CurrentOffset += alignTo(Value: RawDataEntry.size(), Align: sizeof(uint64_t));
795 }
796
797 CurrentOffset = alignTo(Value: CurrentOffset, Align: SECTION_ALIGNMENT);
798}
799
800void WindowsResourceCOFFWriter::writeSymbolTable() {
801 // Now write the symbol table.
802 // First, the feat symbol.
803 auto *Symbol = reinterpret_cast<coff_symbol16 *>(BufferStart + CurrentOffset);
804 coffnamecpy(Dest&: Symbol->Name.ShortName, Src: "@feat.00");
805 Symbol->Value = 0x11;
806 Symbol->SectionNumber = 0xffff;
807 Symbol->Type = COFF::IMAGE_SYM_DTYPE_NULL;
808 Symbol->StorageClass = COFF::IMAGE_SYM_CLASS_STATIC;
809 Symbol->NumberOfAuxSymbols = 0;
810 CurrentOffset += sizeof(coff_symbol16);
811
812 // Now write the .rsrc1 symbol + aux.
813 Symbol = reinterpret_cast<coff_symbol16 *>(BufferStart + CurrentOffset);
814 coffnamecpy(Dest&: Symbol->Name.ShortName, Src: ".rsrc$01");
815 Symbol->Value = 0;
816 Symbol->SectionNumber = 1;
817 Symbol->Type = COFF::IMAGE_SYM_DTYPE_NULL;
818 Symbol->StorageClass = COFF::IMAGE_SYM_CLASS_STATIC;
819 Symbol->NumberOfAuxSymbols = 1;
820 CurrentOffset += sizeof(coff_symbol16);
821 auto *Aux = reinterpret_cast<coff_aux_section_definition *>(BufferStart +
822 CurrentOffset);
823 Aux->Length = SectionOneSize;
824 Aux->NumberOfRelocations = Data.size();
825 Aux->NumberOfLinenumbers = 0;
826 Aux->CheckSum = 0;
827 Aux->NumberLowPart = 0;
828 Aux->Selection = 0;
829 CurrentOffset += sizeof(coff_aux_section_definition);
830
831 // Now write the .rsrc2 symbol + aux.
832 Symbol = reinterpret_cast<coff_symbol16 *>(BufferStart + CurrentOffset);
833 coffnamecpy(Dest&: Symbol->Name.ShortName, Src: ".rsrc$02");
834 Symbol->Value = 0;
835 Symbol->SectionNumber = 2;
836 Symbol->Type = COFF::IMAGE_SYM_DTYPE_NULL;
837 Symbol->StorageClass = COFF::IMAGE_SYM_CLASS_STATIC;
838 Symbol->NumberOfAuxSymbols = 1;
839 CurrentOffset += sizeof(coff_symbol16);
840 Aux = reinterpret_cast<coff_aux_section_definition *>(BufferStart +
841 CurrentOffset);
842 Aux->Length = SectionTwoSize;
843 Aux->NumberOfRelocations = 0;
844 Aux->NumberOfLinenumbers = 0;
845 Aux->CheckSum = 0;
846 Aux->NumberLowPart = 0;
847 Aux->Selection = 0;
848 CurrentOffset += sizeof(coff_aux_section_definition);
849
850 // Now write a symbol for each relocation.
851 for (unsigned i = 0; i < Data.size(); i++) {
852 auto RelocationName = formatv(Fmt: "$R{0:X-6}", Vals: i & 0xffffff).sstr<COFF::NameSize>();
853 Symbol = reinterpret_cast<coff_symbol16 *>(BufferStart + CurrentOffset);
854 coffnamecpy(Dest&: Symbol->Name.ShortName, Src: RelocationName);
855 Symbol->Value = DataOffsets[i];
856 Symbol->SectionNumber = 2;
857 Symbol->Type = COFF::IMAGE_SYM_DTYPE_NULL;
858 Symbol->StorageClass = COFF::IMAGE_SYM_CLASS_STATIC;
859 Symbol->NumberOfAuxSymbols = 0;
860 CurrentOffset += sizeof(coff_symbol16);
861 }
862}
863
864void WindowsResourceCOFFWriter::writeStringTable() {
865 // Just 4 null bytes for the string table.
866 auto COFFStringTable = reinterpret_cast<void *>(BufferStart + CurrentOffset);
867 memset(s: COFFStringTable, c: 0, n: 4);
868}
869
870void WindowsResourceCOFFWriter::writeDirectoryTree() {
871 // Traverse parsed resource tree breadth-first and write the corresponding
872 // COFF objects.
873 std::queue<const WindowsResourceParser::TreeNode *> Queue;
874 Queue.push(x: &Resources);
875 uint32_t NextLevelOffset =
876 sizeof(coff_resource_dir_table) + (Resources.getStringChildren().size() +
877 Resources.getIDChildren().size()) *
878 sizeof(coff_resource_dir_entry);
879 std::vector<const WindowsResourceParser::TreeNode *> DataEntriesTreeOrder;
880 uint32_t CurrentRelativeOffset = 0;
881
882 while (!Queue.empty()) {
883 auto CurrentNode = Queue.front();
884 Queue.pop();
885 auto *Table = reinterpret_cast<coff_resource_dir_table *>(BufferStart +
886 CurrentOffset);
887 Table->Characteristics = CurrentNode->getCharacteristics();
888 Table->TimeDateStamp = 0;
889 Table->MajorVersion = CurrentNode->getMajorVersion();
890 Table->MinorVersion = CurrentNode->getMinorVersion();
891 auto &IDChildren = CurrentNode->getIDChildren();
892 auto &StringChildren = CurrentNode->getStringChildren();
893 Table->NumberOfNameEntries = StringChildren.size();
894 Table->NumberOfIDEntries = IDChildren.size();
895 CurrentOffset += sizeof(coff_resource_dir_table);
896 CurrentRelativeOffset += sizeof(coff_resource_dir_table);
897
898 // Write the directory entries immediately following each directory table.
899 for (auto const &Child : StringChildren) {
900 auto *Entry = reinterpret_cast<coff_resource_dir_entry *>(BufferStart +
901 CurrentOffset);
902 Entry->Identifier.setNameOffset(
903 StringTableOffsets[Child.second->getStringIndex()]);
904 if (Child.second->checkIsDataNode()) {
905 Entry->Offset.DataEntryOffset = NextLevelOffset;
906 NextLevelOffset += sizeof(coff_resource_data_entry);
907 DataEntriesTreeOrder.push_back(x: Child.second.get());
908 } else {
909 Entry->Offset.SubdirOffset = NextLevelOffset + (1 << 31);
910 NextLevelOffset += sizeof(coff_resource_dir_table) +
911 (Child.second->getStringChildren().size() +
912 Child.second->getIDChildren().size()) *
913 sizeof(coff_resource_dir_entry);
914 Queue.push(x: Child.second.get());
915 }
916 CurrentOffset += sizeof(coff_resource_dir_entry);
917 CurrentRelativeOffset += sizeof(coff_resource_dir_entry);
918 }
919 for (auto const &Child : IDChildren) {
920 auto *Entry = reinterpret_cast<coff_resource_dir_entry *>(BufferStart +
921 CurrentOffset);
922 Entry->Identifier.ID = Child.first;
923 if (Child.second->checkIsDataNode()) {
924 Entry->Offset.DataEntryOffset = NextLevelOffset;
925 NextLevelOffset += sizeof(coff_resource_data_entry);
926 DataEntriesTreeOrder.push_back(x: Child.second.get());
927 } else {
928 Entry->Offset.SubdirOffset = NextLevelOffset + (1 << 31);
929 NextLevelOffset += sizeof(coff_resource_dir_table) +
930 (Child.second->getStringChildren().size() +
931 Child.second->getIDChildren().size()) *
932 sizeof(coff_resource_dir_entry);
933 Queue.push(x: Child.second.get());
934 }
935 CurrentOffset += sizeof(coff_resource_dir_entry);
936 CurrentRelativeOffset += sizeof(coff_resource_dir_entry);
937 }
938 }
939
940 RelocationAddresses.resize(new_size: Data.size());
941 // Now write all the resource data entries.
942 for (const auto *DataNodes : DataEntriesTreeOrder) {
943 auto *Entry = reinterpret_cast<coff_resource_data_entry *>(BufferStart +
944 CurrentOffset);
945 RelocationAddresses[DataNodes->getDataIndex()] = CurrentRelativeOffset;
946 Entry->DataRVA = 0; // Set to zero because it is a relocation.
947 Entry->DataSize = Data[DataNodes->getDataIndex()].size();
948 Entry->Codepage = 0;
949 Entry->Reserved = 0;
950 CurrentOffset += sizeof(coff_resource_data_entry);
951 CurrentRelativeOffset += sizeof(coff_resource_data_entry);
952 }
953}
954
955void WindowsResourceCOFFWriter::writeDirectoryStringTable() {
956 // Now write the directory string table for .rsrc$01
957 uint32_t TotalStringTableSize = 0;
958 for (auto &String : StringTable) {
959 uint16_t Length = String.size();
960 support::endian::write16le(P: BufferStart + CurrentOffset, V: Length);
961 CurrentOffset += sizeof(uint16_t);
962 auto *Start = reinterpret_cast<UTF16 *>(BufferStart + CurrentOffset);
963 llvm::copy(Range: String, Out: Start);
964 CurrentOffset += Length * sizeof(UTF16);
965 TotalStringTableSize += Length * sizeof(UTF16) + sizeof(uint16_t);
966 }
967 CurrentOffset +=
968 alignTo(Value: TotalStringTableSize, Align: sizeof(uint32_t)) - TotalStringTableSize;
969}
970
971void WindowsResourceCOFFWriter::writeFirstSectionRelocations() {
972
973 // Now write the relocations for .rsrc$01
974 // Five symbols already in table before we start, @feat.00 and 2 for each
975 // .rsrc section.
976 uint32_t NextSymbolIndex = 5;
977 for (unsigned i = 0; i < Data.size(); i++) {
978 auto *Reloc =
979 reinterpret_cast<coff_relocation *>(BufferStart + CurrentOffset);
980 Reloc->VirtualAddress = RelocationAddresses[i];
981 Reloc->SymbolTableIndex = NextSymbolIndex++;
982 switch (getMachineArchType(machine: MachineType)) {
983 case Triple::thumb:
984 Reloc->Type = COFF::IMAGE_REL_ARM_ADDR32NB;
985 break;
986 case Triple::x86_64:
987 Reloc->Type = COFF::IMAGE_REL_AMD64_ADDR32NB;
988 break;
989 case Triple::x86:
990 Reloc->Type = COFF::IMAGE_REL_I386_DIR32NB;
991 break;
992 case Triple::aarch64:
993 Reloc->Type = COFF::IMAGE_REL_ARM64_ADDR32NB;
994 break;
995 default:
996 llvm_unreachable("unknown machine type");
997 }
998 CurrentOffset += sizeof(coff_relocation);
999 }
1000}
1001
1002Expected<std::unique_ptr<MemoryBuffer>>
1003writeWindowsResourceCOFF(COFF::MachineTypes MachineType,
1004 const WindowsResourceParser &Parser,
1005 uint32_t TimeDateStamp) {
1006 Error E = Error::success();
1007 WindowsResourceCOFFWriter Writer(MachineType, Parser, E);
1008 if (E)
1009 return std::move(E);
1010 return Writer.write(TimeDateStamp);
1011}
1012
1013} // namespace object
1014} // namespace llvm
1015

source code of llvm/lib/Object/WindowsResource.cpp