1 //===- dsymutil.cpp - Debug info dumping utility for llvm -----------------===//
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 program is a utility that aims to be a dropin replacement for Darwin's
10 // dsymutil.
11 //===----------------------------------------------------------------------===//
12 
13 #include "dsymutil.h"
14 #include "BinaryHolder.h"
15 #include "CFBundle.h"
16 #include "DebugMap.h"
17 #include "LinkUtils.h"
18 #include "MachOUtils.h"
19 #include "llvm/ADT/SmallString.h"
20 #include "llvm/ADT/SmallVector.h"
21 #include "llvm/ADT/StringExtras.h"
22 #include "llvm/ADT/StringRef.h"
23 #include "llvm/ADT/Triple.h"
24 #include "llvm/DebugInfo/DIContext.h"
25 #include "llvm/DebugInfo/DWARF/DWARFContext.h"
26 #include "llvm/DebugInfo/DWARF/DWARFVerifier.h"
27 #include "llvm/Object/Binary.h"
28 #include "llvm/Object/MachO.h"
29 #include "llvm/Support/CommandLine.h"
30 #include "llvm/Support/FileSystem.h"
31 #include "llvm/Support/InitLLVM.h"
32 #include "llvm/Support/ManagedStatic.h"
33 #include "llvm/Support/Path.h"
34 #include "llvm/Support/TargetSelect.h"
35 #include "llvm/Support/ThreadPool.h"
36 #include "llvm/Support/WithColor.h"
37 #include "llvm/Support/raw_ostream.h"
38 #include "llvm/Support/thread.h"
39 #include <algorithm>
40 #include <cstdint>
41 #include <cstdlib>
42 #include <string>
43 #include <system_error>
44 
45 using namespace llvm;
46 using namespace llvm::cl;
47 using namespace llvm::dsymutil;
48 using namespace object;
49 
50 static OptionCategory DsymCategory("Specific Options");
51 static opt<bool> Help("h", desc("Alias for -help"), Hidden);
52 static opt<bool> Version("v", desc("Alias for -version"), Hidden);
53 
54 static list<std::string> InputFiles(Positional, OneOrMore,
55                                     desc("<input files>"), cat(DsymCategory));
56 
57 static opt<std::string>
58     OutputFileOpt("o",
59                   desc("Specify the output file. default: <input file>.dwarf"),
60                   value_desc("filename"), cat(DsymCategory));
61 static alias OutputFileOptA("out", desc("Alias for -o"),
62                             aliasopt(OutputFileOpt));
63 
64 static opt<std::string> OsoPrependPath(
65     "oso-prepend-path",
66     desc("Specify a directory to prepend to the paths of object files."),
67     value_desc("path"), cat(DsymCategory));
68 
69 static opt<bool> Assembly(
70     "S",
71     desc("Output textual assembly instead of a binary dSYM companion file."),
72     init(false), cat(DsymCategory), cl::Hidden);
73 
74 static opt<bool> DumpStab(
75     "symtab",
76     desc("Dumps the symbol table found in executable or object file(s) and\n"
77          "exits."),
78     init(false), cat(DsymCategory));
79 static alias DumpStabA("s", desc("Alias for --symtab"), aliasopt(DumpStab));
80 
81 static opt<bool> FlatOut("flat",
82                          desc("Produce a flat dSYM file (not a bundle)."),
83                          init(false), cat(DsymCategory));
84 static alias FlatOutA("f", desc("Alias for --flat"), aliasopt(FlatOut));
85 
86 static opt<bool> Minimize(
87     "minimize",
88     desc("When used when creating a dSYM file with Apple accelerator tables,\n"
89          "this option will suppress the emission of the .debug_inlines, \n"
90          ".debug_pubnames, and .debug_pubtypes sections since dsymutil \n"
91          "has better equivalents: .apple_names and .apple_types. When used in\n"
92          "conjunction with --update option, this option will cause redundant\n"
93          "accelerator tables to be removed."),
94     init(false), cat(DsymCategory));
95 static alias MinimizeA("z", desc("Alias for --minimize"), aliasopt(Minimize));
96 
97 static opt<bool> Update(
98     "update",
99     desc("Updates existing dSYM files to contain the latest accelerator\n"
100          "tables and other DWARF optimizations."),
101     init(false), cat(DsymCategory));
102 static alias UpdateA("u", desc("Alias for --update"), aliasopt(Update));
103 
104 static opt<std::string> SymbolMap(
105     "symbol-map",
106     desc("Updates the existing dSYMs inplace using symbol map specified."),
107     value_desc("bcsymbolmap"), cat(DsymCategory));
108 
109 static cl::opt<AccelTableKind> AcceleratorTable(
110     "accelerator", cl::desc("Output accelerator tables."),
111     cl::values(clEnumValN(AccelTableKind::Default, "Default",
112                           "Default for input."),
113                clEnumValN(AccelTableKind::Apple, "Apple", "Apple"),
114                clEnumValN(AccelTableKind::Dwarf, "Dwarf", "DWARF")),
115     cl::init(AccelTableKind::Default), cat(DsymCategory));
116 
117 static opt<unsigned> NumThreads(
118     "num-threads",
119     desc("Specifies the maximum number (n) of simultaneous threads to use\n"
120          "when linking multiple architectures."),
121     value_desc("n"), init(0), cat(DsymCategory));
122 static alias NumThreadsA("j", desc("Alias for --num-threads"),
123                          aliasopt(NumThreads));
124 
125 static opt<bool> Verbose("verbose", desc("Verbosity level"), init(false),
126                          cat(DsymCategory));
127 
128 static opt<bool>
129     NoOutput("no-output",
130              desc("Do the link in memory, but do not emit the result file."),
131              init(false), cat(DsymCategory));
132 
133 static opt<bool>
134     NoTimestamp("no-swiftmodule-timestamp",
135                 desc("Don't check timestamp for swiftmodule files."),
136                 init(false), cat(DsymCategory));
137 
138 static list<std::string> ArchFlags(
139     "arch",
140     desc("Link DWARF debug information only for specified CPU architecture\n"
141          "types. This option can be specified multiple times, once for each\n"
142          "desired architecture. All CPU architectures will be linked by\n"
143          "default."),
144     value_desc("arch"), ZeroOrMore, cat(DsymCategory));
145 
146 static opt<bool>
147     NoODR("no-odr",
148           desc("Do not use ODR (One Definition Rule) for type uniquing."),
149           init(false), cat(DsymCategory));
150 
151 static opt<bool> DumpDebugMap(
152     "dump-debug-map",
153     desc("Parse and dump the debug map to standard output. Not DWARF link "
154          "will take place."),
155     init(false), cat(DsymCategory));
156 
157 static opt<bool> InputIsYAMLDebugMap(
158     "y", desc("Treat the input file is a YAML debug map rather than a binary."),
159     init(false), cat(DsymCategory));
160 
161 static opt<bool> Verify("verify", desc("Verify the linked DWARF debug info."),
162                         cat(DsymCategory));
163 
164 static opt<std::string>
165     Toolchain("toolchain", desc("Embed toolchain information in dSYM bundle."),
166               cat(DsymCategory));
167 
168 static opt<bool>
169     PaperTrailWarnings("papertrail",
170                        desc("Embed warnings in the linked DWARF debug info."),
171                        cat(DsymCategory));
172 
173 static Error createPlistFile(llvm::StringRef Bin, llvm::StringRef BundleRoot) {
174   if (NoOutput)
175     return Error::success();
176 
177   // Create plist file to write to.
178   llvm::SmallString<128> InfoPlist(BundleRoot);
179   llvm::sys::path::append(InfoPlist, "Contents/Info.plist");
180   std::error_code EC;
181   llvm::raw_fd_ostream PL(InfoPlist, EC, llvm::sys::fs::OF_Text);
182   if (EC)
183     return make_error<StringError>(
184         "cannot create Plist: " + toString(errorCodeToError(EC)), EC);
185 
186   CFBundleInfo BI = getBundleInfo(Bin);
187 
188   if (BI.IDStr.empty()) {
189     llvm::StringRef BundleID = *llvm::sys::path::rbegin(BundleRoot);
190     if (llvm::sys::path::extension(BundleRoot) == ".dSYM")
191       BI.IDStr = llvm::sys::path::stem(BundleID);
192     else
193       BI.IDStr = BundleID;
194   }
195 
196   // Print out information to the plist file.
197   PL << "<?xml version=\"1.0\" encoding=\"UTF-8\"\?>\n"
198      << "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" "
199      << "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
200      << "<plist version=\"1.0\">\n"
201      << "\t<dict>\n"
202      << "\t\t<key>CFBundleDevelopmentRegion</key>\n"
203      << "\t\t<string>English</string>\n"
204      << "\t\t<key>CFBundleIdentifier</key>\n"
205      << "\t\t<string>com.apple.xcode.dsym." << BI.IDStr << "</string>\n"
206      << "\t\t<key>CFBundleInfoDictionaryVersion</key>\n"
207      << "\t\t<string>6.0</string>\n"
208      << "\t\t<key>CFBundlePackageType</key>\n"
209      << "\t\t<string>dSYM</string>\n"
210      << "\t\t<key>CFBundleSignature</key>\n"
211      << "\t\t<string>\?\?\?\?</string>\n";
212 
213   if (!BI.OmitShortVersion()) {
214     PL << "\t\t<key>CFBundleShortVersionString</key>\n";
215     PL << "\t\t<string>";
216     printHTMLEscaped(BI.ShortVersionStr, PL);
217     PL << "</string>\n";
218   }
219 
220   PL << "\t\t<key>CFBundleVersion</key>\n";
221   PL << "\t\t<string>";
222   printHTMLEscaped(BI.VersionStr, PL);
223   PL << "</string>\n";
224 
225   if (!Toolchain.empty()) {
226     PL << "\t\t<key>Toolchain</key>\n";
227     PL << "\t\t<string>";
228     printHTMLEscaped(Toolchain, PL);
229     PL << "</string>\n";
230   }
231 
232   PL << "\t</dict>\n"
233      << "</plist>\n";
234 
235   PL.close();
236   return Error::success();
237 }
238 
239 static Error createBundleDir(llvm::StringRef BundleBase) {
240   if (NoOutput)
241     return Error::success();
242 
243   llvm::SmallString<128> Bundle(BundleBase);
244   llvm::sys::path::append(Bundle, "Contents", "Resources", "DWARF");
245   if (std::error_code EC =
246           create_directories(Bundle.str(), true, llvm::sys::fs::perms::all_all))
247     return make_error<StringError>(
248         "cannot create bundle: " + toString(errorCodeToError(EC)), EC);
249 
250   return Error::success();
251 }
252 
253 static bool verify(llvm::StringRef OutputFile, llvm::StringRef Arch) {
254   if (OutputFile == "-") {
255     WithColor::warning() << "verification skipped for " << Arch
256                          << "because writing to stdout.\n";
257     return true;
258   }
259 
260   Expected<OwningBinary<Binary>> BinOrErr = createBinary(OutputFile);
261   if (!BinOrErr) {
262     WithColor::error() << OutputFile << ": " << toString(BinOrErr.takeError());
263     return false;
264   }
265 
266   Binary &Binary = *BinOrErr.get().getBinary();
267   if (auto *Obj = dyn_cast<MachOObjectFile>(&Binary)) {
268     raw_ostream &os = Verbose ? errs() : nulls();
269     os << "Verifying DWARF for architecture: " << Arch << "\n";
270     std::unique_ptr<DWARFContext> DICtx = DWARFContext::create(*Obj);
271     DIDumpOptions DumpOpts;
272     bool success = DICtx->verify(os, DumpOpts.noImplicitRecursion());
273     if (!success)
274       WithColor::error() << "verification failed for " << Arch << '\n';
275     return success;
276   }
277 
278   return false;
279 }
280 
281 namespace {
282 struct OutputLocation {
283   OutputLocation(std::string DWARFFile,
284                  llvm::Optional<std::string> ResourceDir = {})
285       : DWARFFile(DWARFFile), ResourceDir(ResourceDir) {}
286   /// This method is a workaround for older compilers.
287   llvm::Optional<std::string> getResourceDir() const { return ResourceDir; }
288   std::string DWARFFile;
289   llvm::Optional<std::string> ResourceDir;
290 };
291 }
292 
293 static Expected<OutputLocation> getOutputFileName(llvm::StringRef InputFile) {
294   if (OutputFileOpt == "-")
295     return OutputLocation(OutputFileOpt);
296 
297   // When updating, do in place replacement.
298   if (OutputFileOpt.empty() && (Update || !SymbolMap.empty()))
299     return OutputLocation(InputFile);
300 
301   // If a flat dSYM has been requested, things are pretty simple.
302   if (FlatOut) {
303     if (OutputFileOpt.empty()) {
304       if (InputFile == "-")
305         return OutputLocation{"a.out.dwarf", {}};
306       return OutputLocation((InputFile + ".dwarf").str());
307     }
308 
309     return OutputLocation(OutputFileOpt);
310   }
311 
312   // We need to create/update a dSYM bundle.
313   // A bundle hierarchy looks like this:
314   //   <bundle name>.dSYM/
315   //       Contents/
316   //          Info.plist
317   //          Resources/
318   //             DWARF/
319   //                <DWARF file(s)>
320   std::string DwarfFile =
321       InputFile == "-" ? llvm::StringRef("a.out") : InputFile;
322   llvm::SmallString<128> Path(OutputFileOpt);
323   if (Path.empty())
324     Path = DwarfFile + ".dSYM";
325   if (auto E = createBundleDir(Path))
326     return std::move(E);
327   if (auto E = createPlistFile(DwarfFile, Path))
328     return std::move(E);
329 
330   llvm::sys::path::append(Path, "Contents", "Resources");
331   std::string ResourceDir = Path.str();
332   llvm::sys::path::append(Path, "DWARF", llvm::sys::path::filename(DwarfFile));
333   return OutputLocation(Path.str(), ResourceDir);
334 }
335 
336 /// Parses the command line options into the LinkOptions struct and performs
337 /// some sanity checking. Returns an error in case the latter fails.
338 static Expected<LinkOptions> getOptions() {
339   LinkOptions Options;
340 
341   Options.Verbose = Verbose;
342   Options.NoOutput = NoOutput;
343   Options.NoODR = NoODR;
344   Options.Minimize = Minimize;
345   Options.Update = Update;
346   Options.NoTimestamp = NoTimestamp;
347   Options.PrependPath = OsoPrependPath;
348   Options.TheAccelTableKind = AcceleratorTable;
349 
350   if (!SymbolMap.empty())
351     Options.Update = true;
352 
353   if (Assembly)
354     Options.FileType = OutputFileType::Assembly;
355 
356   if (Options.Update && std::find(InputFiles.begin(), InputFiles.end(), "-") !=
357                             InputFiles.end()) {
358     // FIXME: We cannot use stdin for an update because stdin will be
359     // consumed by the BinaryHolder during the debugmap parsing, and
360     // then we will want to consume it again in DwarfLinker. If we
361     // used a unique BinaryHolder object that could cache multiple
362     // binaries this restriction would go away.
363     return make_error<StringError>(
364         "standard input cannot be used as input for a dSYM update.",
365         inconvertibleErrorCode());
366   }
367 
368   if (NumThreads == 0)
369     Options.Threads = llvm::thread::hardware_concurrency();
370   else
371     Options.Threads = NumThreads;
372   if (DumpDebugMap || Verbose)
373     Options.Threads = 1;
374 
375   return Options;
376 }
377 
378 /// Return a list of input files. This function has logic for dealing with the
379 /// special case where we might have dSYM bundles as input. The function
380 /// returns an error when the directory structure doesn't match that of a dSYM
381 /// bundle.
382 static Expected<std::vector<std::string>> getInputs(bool DsymAsInput) {
383   if (!DsymAsInput)
384     return InputFiles;
385 
386   // If we are updating, we might get dSYM bundles as input.
387   std::vector<std::string> Inputs;
388   for (const auto &Input : InputFiles) {
389     if (!llvm::sys::fs::is_directory(Input)) {
390       Inputs.push_back(Input);
391       continue;
392     }
393 
394     // Make sure that we're dealing with a dSYM bundle.
395     SmallString<256> BundlePath(Input);
396     sys::path::append(BundlePath, "Contents", "Resources", "DWARF");
397     if (!llvm::sys::fs::is_directory(BundlePath))
398       return make_error<StringError>(
399           Input + " is a directory, but doesn't look like a dSYM bundle.",
400           inconvertibleErrorCode());
401 
402     // Create a directory iterator to iterate over all the entries in the
403     // bundle.
404     std::error_code EC;
405     llvm::sys::fs::directory_iterator DirIt(BundlePath, EC);
406     llvm::sys::fs::directory_iterator DirEnd;
407     if (EC)
408       return errorCodeToError(EC);
409 
410     // Add each entry to the list of inputs.
411     while (DirIt != DirEnd) {
412       Inputs.push_back(DirIt->path());
413       DirIt.increment(EC);
414       if (EC)
415         return errorCodeToError(EC);
416     }
417   }
418   return Inputs;
419 }
420 
421 int main(int argc, char **argv) {
422   InitLLVM X(argc, argv);
423 
424   void *P = (void *)(intptr_t)getOutputFileName;
425   std::string SDKPath = llvm::sys::fs::getMainExecutable(argv[0], P);
426   SDKPath = llvm::sys::path::parent_path(SDKPath);
427 
428   HideUnrelatedOptions({&DsymCategory, &ColorCategory});
429   llvm::cl::ParseCommandLineOptions(
430       argc, argv,
431       "manipulate archived DWARF debug symbol files.\n\n"
432       "dsymutil links the DWARF debug information found in the object files\n"
433       "for the executable <input file> by using debug symbols information\n"
434       "contained in its symbol table.\n");
435 
436   if (Help) {
437     PrintHelpMessage();
438     return 0;
439   }
440 
441   if (Version) {
442     llvm::cl::PrintVersionMessage();
443     return 0;
444   }
445 
446   auto OptionsOrErr = getOptions();
447   if (!OptionsOrErr) {
448     WithColor::error() << toString(OptionsOrErr.takeError());
449     return 1;
450   }
451 
452   llvm::InitializeAllTargetInfos();
453   llvm::InitializeAllTargetMCs();
454   llvm::InitializeAllTargets();
455   llvm::InitializeAllAsmPrinters();
456 
457   auto InputsOrErr = getInputs(OptionsOrErr->Update);
458   if (!InputsOrErr) {
459     WithColor::error() << toString(InputsOrErr.takeError()) << '\n';
460     return 1;
461   }
462 
463   if (!FlatOut && OutputFileOpt == "-") {
464     WithColor::error() << "cannot emit to standard output without --flat\n";
465     return 1;
466   }
467 
468   if (InputsOrErr->size() > 1 && FlatOut && !OutputFileOpt.empty()) {
469     WithColor::error() << "cannot use -o with multiple inputs in flat mode\n";
470     return 1;
471   }
472 
473   if (InputFiles.size() > 1 && !SymbolMap.empty() &&
474       !llvm::sys::fs::is_directory(SymbolMap)) {
475     WithColor::error() << "when unobfuscating multiple files, --symbol-map "
476                        << "needs to point to a directory.\n";
477     return 1;
478   }
479 
480   if (getenv("RC_DEBUG_OPTIONS"))
481     PaperTrailWarnings = true;
482 
483   if (PaperTrailWarnings && InputIsYAMLDebugMap)
484     WithColor::warning()
485         << "Paper trail warnings are not supported for YAML input";
486 
487   for (const auto &Arch : ArchFlags)
488     if (Arch != "*" && Arch != "all" &&
489         !llvm::object::MachOObjectFile::isValidArch(Arch)) {
490       WithColor::error() << "unsupported cpu architecture: '" << Arch << "'\n";
491       return 1;
492     }
493 
494   SymbolMapLoader SymMapLoader(SymbolMap);
495 
496   for (auto &InputFile : *InputsOrErr) {
497     // Dump the symbol table for each input file and requested arch
498     if (DumpStab) {
499       if (!dumpStab(InputFile, ArchFlags, OsoPrependPath))
500         return 1;
501       continue;
502     }
503 
504     auto DebugMapPtrsOrErr =
505         parseDebugMap(InputFile, ArchFlags, OsoPrependPath, PaperTrailWarnings,
506                       Verbose, InputIsYAMLDebugMap);
507 
508     if (auto EC = DebugMapPtrsOrErr.getError()) {
509       WithColor::error() << "cannot parse the debug map for '" << InputFile
510                          << "': " << EC.message() << '\n';
511       return 1;
512     }
513 
514     if (OptionsOrErr->Update) {
515       // The debug map should be empty. Add one object file corresponding to
516       // the input file.
517       for (auto &Map : *DebugMapPtrsOrErr)
518         Map->addDebugMapObject(InputFile,
519                                llvm::sys::TimePoint<std::chrono::seconds>());
520     }
521 
522     // Ensure that the debug map is not empty (anymore).
523     if (DebugMapPtrsOrErr->empty()) {
524       WithColor::error() << "no architecture to link\n";
525       return 1;
526     }
527 
528     // Shared a single binary holder for all the link steps.
529     BinaryHolder BinHolder;
530 
531     unsigned ThreadCount =
532         std::min<unsigned>(OptionsOrErr->Threads, DebugMapPtrsOrErr->size());
533     llvm::ThreadPool Threads(ThreadCount);
534 
535     // If there is more than one link to execute, we need to generate
536     // temporary files.
537     bool NeedsTempFiles =
538         !DumpDebugMap && (OutputFileOpt != "-") &&
539         (DebugMapPtrsOrErr->size() != 1 || OptionsOrErr->Update);
540 
541     llvm::SmallVector<MachOUtils::ArchAndFile, 4> TempFiles;
542     std::atomic_char AllOK(1);
543     for (auto &Map : *DebugMapPtrsOrErr) {
544       if (Verbose || DumpDebugMap)
545         Map->print(llvm::outs());
546 
547       if (DumpDebugMap)
548         continue;
549 
550       if (!SymbolMap.empty())
551         OptionsOrErr->Translator = SymMapLoader.Load(InputFile, *Map);
552 
553       if (Map->begin() == Map->end())
554         WithColor::warning()
555             << "no debug symbols in executable (-arch "
556             << MachOUtils::getArchName(Map->getTriple().getArchName()) << ")\n";
557 
558       // Using a std::shared_ptr rather than std::unique_ptr because move-only
559       // types don't work with std::bind in the ThreadPool implementation.
560       std::shared_ptr<raw_fd_ostream> OS;
561 
562       Expected<OutputLocation> OutputLocationOrErr =
563           getOutputFileName(InputFile);
564       if (!OutputLocationOrErr) {
565         WithColor::error() << toString(OutputLocationOrErr.takeError());
566         return 1;
567       }
568       OptionsOrErr->ResourceDir = OutputLocationOrErr->getResourceDir();
569 
570       std::string OutputFile = OutputLocationOrErr->DWARFFile;
571       if (NeedsTempFiles) {
572         TempFiles.emplace_back(Map->getTriple().getArchName().str());
573 
574         auto E = TempFiles.back().createTempFile();
575         if (E) {
576           WithColor::error() << toString(std::move(E));
577           return 1;
578         }
579 
580         auto &TempFile = *(TempFiles.back().File);
581         OS = std::make_shared<raw_fd_ostream>(TempFile.FD,
582                                               /*shouldClose*/ false);
583         OutputFile = TempFile.TmpName;
584       } else {
585         std::error_code EC;
586         OS = std::make_shared<raw_fd_ostream>(NoOutput ? "-" : OutputFile, EC,
587                                               sys::fs::OF_None);
588         if (EC) {
589           WithColor::error() << OutputFile << ": " << EC.message();
590           return 1;
591         }
592       }
593 
594       auto LinkLambda = [&, OutputFile](std::shared_ptr<raw_fd_ostream> Stream,
595                                         LinkOptions Options) {
596         AllOK.fetch_and(
597             linkDwarf(*Stream, BinHolder, *Map, std::move(Options)));
598         Stream->flush();
599         if (Verify && !NoOutput)
600           AllOK.fetch_and(verify(OutputFile, Map->getTriple().getArchName()));
601       };
602 
603       // FIXME: The DwarfLinker can have some very deep recursion that can max
604       // out the (significantly smaller) stack when using threads. We don't
605       // want this limitation when we only have a single thread.
606       if (ThreadCount == 1)
607         LinkLambda(OS, *OptionsOrErr);
608       else
609         Threads.async(LinkLambda, OS, *OptionsOrErr);
610     }
611 
612     Threads.wait();
613 
614     if (!AllOK)
615       return 1;
616 
617     if (NeedsTempFiles) {
618       Expected<OutputLocation> OutputLocationOrErr = getOutputFileName(InputFile);
619       if (!OutputLocationOrErr) {
620         WithColor::error() << toString(OutputLocationOrErr.takeError());
621         return 1;
622       }
623       if (!MachOUtils::generateUniversalBinary(TempFiles,
624                                                OutputLocationOrErr->DWARFFile,
625                                                *OptionsOrErr, SDKPath))
626         return 1;
627     }
628   }
629 
630   return 0;
631 }
632