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