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
22 using namespace llvm;
23 using namespace object;
24
25 namespace llvm {
26 namespace 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
44 const 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.
48 const uint32_t SECTION_ALIGNMENT = sizeof(uint64_t);
49
WindowsResource(MemoryBufferRef Source)50 WindowsResource::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(LeadingSize),
54 llvm::endianness::little);
55 }
56
57 // static
58 Expected<std::unique_ptr<WindowsResource>>
createWindowsResource(MemoryBufferRef Source)59 WindowsResource::createWindowsResource(MemoryBufferRef Source) {
60 if (Source.getBufferSize() < WIN_RES_MAGIC_SIZE + WIN_RES_NULL_ENTRY_SIZE)
61 return make_error<GenericBinaryError>(
62 Source.getBufferIdentifier() + ": too small to be a resource file",
63 object_error::invalid_file_type);
64 std::unique_ptr<WindowsResource> Ret(new WindowsResource(Source));
65 return std::move(Ret);
66 }
67
getHeadEntry()68 Expected<ResourceEntryRef> WindowsResource::getHeadEntry() {
69 if (BBS.getLength() < sizeof(WinResHeaderPrefix) + sizeof(WinResHeaderSuffix))
70 return make_error<EmptyResError>(getFileName() + " contains no entries",
71 object_error::unexpected_eof);
72 return ResourceEntryRef::create(BinaryStreamRef(BBS), this);
73 }
74
ResourceEntryRef(BinaryStreamRef Ref,const WindowsResource * Owner)75 ResourceEntryRef::ResourceEntryRef(BinaryStreamRef Ref,
76 const WindowsResource *Owner)
77 : Reader(Ref), Owner(Owner) {}
78
79 Expected<ResourceEntryRef>
create(BinaryStreamRef BSR,const WindowsResource * Owner)80 ResourceEntryRef::create(BinaryStreamRef BSR, const WindowsResource *Owner) {
81 auto Ref = ResourceEntryRef(BSR, Owner);
82 if (auto E = Ref.loadNext())
83 return E;
84 return Ref;
85 }
86
moveNext(bool & End)87 Error 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
readStringOrId(BinaryStreamReader & Reader,uint16_t & ID,ArrayRef<UTF16> & Str,bool & IsString)98 static 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
loadNext()115 Error 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>(Owner->getFileName() +
121 ": header size too small",
122 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
WindowsResourceParser(bool MinGW)139 WindowsResourceParser::WindowsResourceParser(bool MinGW)
140 : Root(false), MinGW(MinGW) {}
141
printResourceTypeName(uint16_t TypeID,raw_ostream & OS)142 void 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
convertUTF16LEToUTF8String(ArrayRef<UTF16> Src,std::string & Out)169 static 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(Src.size() + 1);
175 llvm::copy(Src, EndianCorrectedSrc.begin() + 1);
176 EndianCorrectedSrc[0] = UNI_UTF16_BYTE_ORDER_MARK_SWAPPED;
177 return convertUTF16ToUTF8String(ArrayRef(EndianCorrectedSrc), Out);
178 }
179
makeDuplicateResourceError(const ResourceEntryRef & Entry,StringRef File1,StringRef File2)180 static 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(Entry.getTypeString(), UTF8))
191 UTF8 = "(failed conversion from UTF16)";
192 OS << '\"' << UTF8 << '\"';
193 } else
194 printResourceTypeName(Entry.getTypeID(), OS);
195
196 OS << "/name ";
197 if (Entry.checkNameString()) {
198 std::string UTF8;
199 if (!convertUTF16LEToUTF8String(Entry.getNameString(), 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
printStringOrID(const WindowsResourceParser::StringOrID & S,raw_string_ostream & OS,bool IsType,bool IsID)212 static 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(S.String, UTF8))
217 UTF8 = "(failed conversion from UTF16)";
218 OS << '\"' << UTF8 << '\"';
219 } else if (IsType)
220 printResourceTypeName(S.ID, OS);
221 else if (IsID)
222 OS << "ID " << S.ID;
223 else
224 OS << S.ID;
225 }
226
makeDuplicateResourceError(const std::vector<WindowsResourceParser::StringOrID> & Context,StringRef File1,StringRef File2)227 static 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(Context[0], OS, /* IsType */ true, /* IsID */ true);
238 }
239
240 if (Context.size() >= 2) {
241 OS << "/name ";
242 printStringOrID(Context[1], OS, /* IsType */ false, /* IsID */ true);
243 }
244
245 if (Context.size() >= 3) {
246 OS << "/language ";
247 printStringOrID(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.
cleanUpManifests(std::vector<std::string> & Duplicates)262 void WindowsResourceParser::cleanUpManifests(
263 std::vector<std::string> &Duplicates) {
264 auto TypeIt = Root.IDChildren.find(/* RT_MANIFEST */ 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 */ 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(0);
281 if (LangZeroIt != NameNode->IDChildren.end() &&
282 LangZeroIt->second->IsDataNode) {
283 uint32_t RemovedIndex = LangZeroIt->second->DataIndex;
284 NameNode->IDChildren.erase(LangZeroIt);
285 Data.erase(Data.begin() + RemovedIndex);
286 Root.shiftDataIndexDown(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 ("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.
shouldIgnoreDuplicate(const ResourceEntryRef & Entry) const311 bool 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
shouldIgnoreDuplicate(const std::vector<StringOrID> & Context) const320 bool 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
parse(WindowsResource * WR,std::vector<std::string> & Duplicates)328 Error 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(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(std::string(WR->getFileName()));
349 bool End = false;
350 while (!End) {
351
352 TreeNode *Node;
353 bool IsNewNode = Root.addEntry(Entry, Origin, Data, StringTable, Node);
354 if (!IsNewNode) {
355 if (!shouldIgnoreDuplicate(Entry))
356 Duplicates.push_back(makeDuplicateResourceError(
357 Entry, InputFilenames[Node->Origin], WR->getFileName()));
358 }
359
360 RETURN_IF_ERROR(Entry.moveNext(End));
361 }
362
363 return Error::success();
364 }
365
parse(ResourceSectionRef & RSR,StringRef Filename,std::vector<std::string> & Duplicates)366 Error 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(std::string(Filename));
371 std::vector<StringOrID> Context;
372 return addChildren(Root, RSR, BaseTable, Origin, Context, Duplicates);
373 }
374
printTree(raw_ostream & OS) const375 void WindowsResourceParser::printTree(raw_ostream &OS) const {
376 ScopedPrinter Writer(OS);
377 Root.print(Writer, "Resource Tree");
378 }
379
addEntry(const ResourceEntryRef & Entry,uint32_t Origin,std::vector<std::vector<uint8_t>> & Data,std::vector<std::vector<UTF16>> & StringTable,TreeNode * & Result)380 bool 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
addChildren(TreeNode & Node,ResourceSectionRef & RSR,const coff_resource_dir_table & Table,uint32_t Origin,std::vector<StringOrID> & Context,std::vector<std::string> & Duplicates)389 Error 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(NameString, StringTable);
407 Context.push_back(StringOrID(NameString));
408 } else {
409 Child = &Node.addIDChild(Entry.Identifier.ID);
410 Context.push_back(StringOrID(Entry.Identifier.ID));
411 }
412
413 UNWRAP_REF_OR_RETURN(NextTable, RSR.getEntrySubDir(Entry));
414 Error E =
415 addChildren(*Child, RSR, 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(object_error::parse_failed,
425 "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(StringOrID(Entry.Identifier.ID));
431 bool Added = Node.addDataChild(Entry.Identifier.ID, Table.MajorVersion,
432 Table.MinorVersion, Table.Characteristics,
433 Origin, Data.size(), Child);
434 if (Added) {
435 UNWRAP_OR_RETURN(Contents, RSR.getContents(DataEntry));
436 Data.push_back(ArrayRef<uint8_t>(
437 reinterpret_cast<const uint8_t *>(Contents.data()),
438 Contents.size()));
439 } else {
440 if (!shouldIgnoreDuplicate(Context))
441 Duplicates.push_back(makeDuplicateResourceError(
442 Context, InputFilenames[Child->Origin], InputFilenames.back()));
443 }
444 Context.pop_back();
445
446 }
447 }
448 return Error::success();
449 }
450
TreeNode(uint32_t StringIndex)451 WindowsResourceParser::TreeNode::TreeNode(uint32_t StringIndex)
452 : StringIndex(StringIndex) {}
453
TreeNode(uint16_t MajorVersion,uint16_t MinorVersion,uint32_t Characteristics,uint32_t Origin,uint32_t DataIndex)454 WindowsResourceParser::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
462 std::unique_ptr<WindowsResourceParser::TreeNode>
createStringNode(uint32_t Index)463 WindowsResourceParser::TreeNode::createStringNode(uint32_t Index) {
464 return std::unique_ptr<TreeNode>(new TreeNode(Index));
465 }
466
467 std::unique_ptr<WindowsResourceParser::TreeNode>
createIDNode()468 WindowsResourceParser::TreeNode::createIDNode() {
469 return std::unique_ptr<TreeNode>(new TreeNode(0));
470 }
471
472 std::unique_ptr<WindowsResourceParser::TreeNode>
createDataNode(uint16_t MajorVersion,uint16_t MinorVersion,uint32_t Characteristics,uint32_t Origin,uint32_t DataIndex)473 WindowsResourceParser::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
addTypeNode(const ResourceEntryRef & Entry,std::vector<std::vector<UTF16>> & StringTable)482 WindowsResourceParser::TreeNode &WindowsResourceParser::TreeNode::addTypeNode(
483 const ResourceEntryRef &Entry,
484 std::vector<std::vector<UTF16>> &StringTable) {
485 if (Entry.checkTypeString())
486 return addNameChild(Entry.getTypeString(), StringTable);
487 else
488 return addIDChild(Entry.getTypeID());
489 }
490
addNameNode(const ResourceEntryRef & Entry,std::vector<std::vector<UTF16>> & StringTable)491 WindowsResourceParser::TreeNode &WindowsResourceParser::TreeNode::addNameNode(
492 const ResourceEntryRef &Entry,
493 std::vector<std::vector<UTF16>> &StringTable) {
494 if (Entry.checkNameString())
495 return addNameChild(Entry.getNameString(), StringTable);
496 else
497 return addIDChild(Entry.getNameID());
498 }
499
addLanguageNode(const ResourceEntryRef & Entry,uint32_t Origin,std::vector<std::vector<uint8_t>> & Data,TreeNode * & Result)500 bool WindowsResourceParser::TreeNode::addLanguageNode(
501 const ResourceEntryRef &Entry, uint32_t Origin,
502 std::vector<std::vector<uint8_t>> &Data, TreeNode *&Result) {
503 bool Added = addDataChild(Entry.getLanguage(), Entry.getMajorVersion(),
504 Entry.getMinorVersion(), Entry.getCharacteristics(),
505 Origin, Data.size(), Result);
506 if (Added)
507 Data.push_back(Entry.getData());
508 return Added;
509 }
510
addDataChild(uint32_t ID,uint16_t MajorVersion,uint16_t MinorVersion,uint32_t Characteristics,uint32_t Origin,uint32_t DataIndex,TreeNode * & Result)511 bool 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(ID, std::move(NewChild));
518 Result = ElementInserted.first->second.get();
519 return ElementInserted.second;
520 }
521
addIDChild(uint32_t ID)522 WindowsResourceParser::TreeNode &WindowsResourceParser::TreeNode::addIDChild(
523 uint32_t ID) {
524 auto Child = IDChildren.find(ID);
525 if (Child == IDChildren.end()) {
526 auto NewChild = createIDNode();
527 WindowsResourceParser::TreeNode &Node = *NewChild;
528 IDChildren.emplace(ID, std::move(NewChild));
529 return Node;
530 } else
531 return *(Child->second);
532 }
533
addNameChild(ArrayRef<UTF16> NameRef,std::vector<std::vector<UTF16>> & StringTable)534 WindowsResourceParser::TreeNode &WindowsResourceParser::TreeNode::addNameChild(
535 ArrayRef<UTF16> NameRef, std::vector<std::vector<UTF16>> &StringTable) {
536 std::string NameString;
537 convertUTF16LEToUTF8String(NameRef, NameString);
538
539 auto Child = StringChildren.find(NameString);
540 if (Child == StringChildren.end()) {
541 auto NewChild = createStringNode(StringTable.size());
542 StringTable.push_back(NameRef);
543 WindowsResourceParser::TreeNode &Node = *NewChild;
544 StringChildren.emplace(NameString, std::move(NewChild));
545 return Node;
546 } else
547 return *(Child->second);
548 }
549
print(ScopedPrinter & Writer,StringRef Name) const550 void 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, Child.first);
555 }
556 for (auto const &Child : IDChildren) {
557 Child.second->print(Writer, to_string(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.
getTreeSize() const564 uint32_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.
shiftDataIndexDown(uint32_t Index)589 void 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
600 class WindowsResourceCOFFWriter {
601 public:
602 WindowsResourceCOFFWriter(COFF::MachineTypes MachineType,
603 const WindowsResourceParser &Parser, Error &E);
604 std::unique_ptr<MemoryBuffer> write(uint32_t TimeDateStamp);
605
606 private:
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
WindowsResourceCOFFWriter(COFF::MachineTypes MachineType,const WindowsResourceParser & Parser,Error & E)639 WindowsResourceCOFFWriter::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 FileSize, "internal .obj file created from .res files");
648 }
649
performFileLayout()650 void 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
performSectionOneLayout()669 void 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(CurrentStringOffset);
677 uint32_t StringSize = String.size() * sizeof(UTF16) + sizeof(uint16_t);
678 CurrentStringOffset += StringSize;
679 TotalStringTableSize += StringSize;
680 }
681 SectionOneSize += alignTo(TotalStringTableSize, 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(FileSize, SECTION_ALIGNMENT);
689 }
690
performSectionTwoLayout()691 void 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(SectionTwoSize);
698 SectionTwoSize += alignTo(Entry.size(), sizeof(uint64_t));
699 }
700 FileSize += SectionTwoSize;
701 FileSize = alignTo(FileSize, SECTION_ALIGNMENT);
702 }
703
704 std::unique_ptr<MemoryBuffer>
write(uint32_t TimeDateStamp)705 WindowsResourceCOFFWriter::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.
coffnamecpy(char (& Dest)[COFF::NameSize],StringRef Src)721 static 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, Src.data(), Src.size());
727 }
728
writeCOFFHeader(uint32_t TimeDateStamp)729 void 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
writeFirstSectionHeader()743 void 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(SectionOneHeader->Name, ".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
writeSecondSectionHeader()761 void 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(SectionTwoHeader->Name, ".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
writeFirstSection()779 void WindowsResourceCOFFWriter::writeFirstSection() {
780 // Write section one.
781 CurrentOffset += sizeof(coff_section);
782
783 writeDirectoryTree();
784 writeDirectoryStringTable();
785 writeFirstSectionRelocations();
786
787 CurrentOffset = alignTo(CurrentOffset, SECTION_ALIGNMENT);
788 }
789
writeSecondSection()790 void WindowsResourceCOFFWriter::writeSecondSection() {
791 // Now write the .rsrc$02 section.
792 for (auto const &RawDataEntry : Data) {
793 llvm::copy(RawDataEntry, BufferStart + CurrentOffset);
794 CurrentOffset += alignTo(RawDataEntry.size(), sizeof(uint64_t));
795 }
796
797 CurrentOffset = alignTo(CurrentOffset, SECTION_ALIGNMENT);
798 }
799
writeSymbolTable()800 void WindowsResourceCOFFWriter::writeSymbolTable() {
801 // Now write the symbol table.
802 // First, the feat symbol.
803 auto *Symbol = reinterpret_cast<coff_symbol16 *>(BufferStart + CurrentOffset);
804 coffnamecpy(Symbol->Name.ShortName, "@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(Symbol->Name.ShortName, ".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(Symbol->Name.ShortName, ".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("$R{0:X-6}", i & 0xffffff).sstr<COFF::NameSize>();
853 Symbol = reinterpret_cast<coff_symbol16 *>(BufferStart + CurrentOffset);
854 coffnamecpy(Symbol->Name.ShortName, 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
writeStringTable()864 void WindowsResourceCOFFWriter::writeStringTable() {
865 // Just 4 null bytes for the string table.
866 auto COFFStringTable = reinterpret_cast<void *>(BufferStart + CurrentOffset);
867 memset(COFFStringTable, 0, 4);
868 }
869
writeDirectoryTree()870 void 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(&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(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(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(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(Child.second.get());
934 }
935 CurrentOffset += sizeof(coff_resource_dir_entry);
936 CurrentRelativeOffset += sizeof(coff_resource_dir_entry);
937 }
938 }
939
940 RelocationAddresses.resize(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
writeDirectoryStringTable()955 void 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(BufferStart + CurrentOffset, Length);
961 CurrentOffset += sizeof(uint16_t);
962 auto *Start = reinterpret_cast<UTF16 *>(BufferStart + CurrentOffset);
963 llvm::copy(String, Start);
964 CurrentOffset += Length * sizeof(UTF16);
965 TotalStringTableSize += Length * sizeof(UTF16) + sizeof(uint16_t);
966 }
967 CurrentOffset +=
968 alignTo(TotalStringTableSize, sizeof(uint32_t)) - TotalStringTableSize;
969 }
970
writeFirstSectionRelocations()971 void 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(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
1002 Expected<std::unique_ptr<MemoryBuffer>>
writeWindowsResourceCOFF(COFF::MachineTypes MachineType,const WindowsResourceParser & Parser,uint32_t TimeDateStamp)1003 writeWindowsResourceCOFF(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 E;
1010 return Writer.write(TimeDateStamp);
1011 }
1012
1013 } // namespace object
1014 } // namespace llvm
1015