WindowsResource.cpp 38 KB

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