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