WindowsResource.cpp 38 KB

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