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 "DwarfLinkerForBinary.h"
18#include "LinkUtils.h"
19#include "MachOUtils.h"
20#include "Reproducer.h"
21#include "llvm/ADT/STLExtras.h"
22#include "llvm/ADT/SmallString.h"
23#include "llvm/ADT/SmallVector.h"
24#include "llvm/ADT/StringExtras.h"
25#include "llvm/ADT/StringRef.h"
26#include "llvm/DebugInfo/DIContext.h"
27#include "llvm/DebugInfo/DWARF/DWARFContext.h"
28#include "llvm/DebugInfo/DWARF/DWARFVerifier.h"
29#include "llvm/MC/MCSubtargetInfo.h"
30#include "llvm/Object/Binary.h"
31#include "llvm/Object/MachO.h"
32#include "llvm/Option/Arg.h"
33#include "llvm/Option/ArgList.h"
34#include "llvm/Option/Option.h"
35#include "llvm/Support/CommandLine.h"
36#include "llvm/Support/CrashRecoveryContext.h"
37#include "llvm/Support/FileCollector.h"
38#include "llvm/Support/FileSystem.h"
39#include "llvm/Support/FormatVariadic.h"
40#include "llvm/Support/LLVMDriver.h"
41#include "llvm/Support/Path.h"
42#include "llvm/Support/TargetSelect.h"
43#include "llvm/Support/ThreadPool.h"
44#include "llvm/Support/WithColor.h"
45#include "llvm/Support/raw_ostream.h"
46#include "llvm/Support/thread.h"
47#include "llvm/TargetParser/Triple.h"
48#include <algorithm>
49#include <cstdint>
50#include <cstdlib>
51#include <string>
52#include <system_error>
53
54using namespace llvm;
55using namespace llvm::dsymutil;
56using namespace object;
57using namespace llvm::dwarf_linker;
58
59namespace {
60enum ID {
61 OPT_INVALID = 0, // This is not an option ID.
62#define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__),
63#include "Options.inc"
64#undef OPTION
65};
66
67#define OPTTABLE_STR_TABLE_CODE
68#include "Options.inc"
69#undef OPTTABLE_STR_TABLE_CODE
70
71#define OPTTABLE_PREFIXES_TABLE_CODE
72#include "Options.inc"
73#undef OPTTABLE_PREFIXES_TABLE_CODE
74
75using namespace llvm::opt;
76static constexpr opt::OptTable::Info InfoTable[] = {
77#define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__),
78#include "Options.inc"
79#undef OPTION
80};
81
82class DsymutilOptTable : public opt::GenericOptTable {
83public:
84 DsymutilOptTable()
85 : opt::GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable) {}
86};
87} // namespace
88
89enum class DWARFVerify : uint8_t {
90 None = 0,
91 Input = 1 << 0,
92 Output = 1 << 1,
93 OutputOnValidInput = 1 << 2,
94 All = Input | Output,
95 Auto = Input | OutputOnValidInput,
96#if !defined(NDEBUG) || defined(EXPENSIVE_CHECKS)
97 Default = Auto
98#else
99 Default = None
100#endif
101};
102
103inline bool flagIsSet(DWARFVerify Flags, DWARFVerify SingleFlag) {
104 return static_cast<uint8_t>(Flags) & static_cast<uint8_t>(SingleFlag);
105}
106
107struct DsymutilOptions {
108 bool DumpDebugMap = false;
109 bool DumpStab = false;
110 bool Flat = false;
111 bool InputIsYAMLDebugMap = false;
112 bool ForceKeepFunctionForStatic = false;
113 bool NoObjectTimestamp = false;
114 std::string OutputFile;
115 std::string Toolchain;
116 std::string ReproducerPath;
117 std::vector<std::string> Archs;
118 std::vector<std::string> InputFiles;
119 unsigned NumThreads;
120 DWARFVerify Verify = DWARFVerify::Default;
121 ReproducerMode ReproMode = ReproducerMode::GenerateOnCrash;
122 dsymutil::LinkOptions LinkOpts;
123};
124
125/// Return a list of input files. This function has logic for dealing with the
126/// special case where we might have dSYM bundles as input. The function
127/// returns an error when the directory structure doesn't match that of a dSYM
128/// bundle.
129static Expected<std::vector<std::string>> getInputs(opt::InputArgList &Args,
130 bool DsymAsInput) {
131 std::vector<std::string> InputFiles;
132 for (auto *File : Args.filtered(Ids: OPT_INPUT))
133 InputFiles.push_back(x: File->getValue());
134
135 if (!DsymAsInput)
136 return InputFiles;
137
138 // If we are updating, we might get dSYM bundles as input.
139 std::vector<std::string> Inputs;
140 for (const auto &Input : InputFiles) {
141 if (!sys::fs::is_directory(Path: Input)) {
142 Inputs.push_back(x: Input);
143 continue;
144 }
145
146 // Make sure that we're dealing with a dSYM bundle.
147 SmallString<256> BundlePath(Input);
148 sys::path::append(path&: BundlePath, a: "Contents", b: "Resources", c: "DWARF");
149 if (!sys::fs::is_directory(Path: BundlePath))
150 return make_error<StringError>(
151 Args: Input + " is a directory, but doesn't look like a dSYM bundle.",
152 Args: inconvertibleErrorCode());
153
154 // Create a directory iterator to iterate over all the entries in the
155 // bundle.
156 std::error_code EC;
157 sys::fs::directory_iterator DirIt(BundlePath, EC);
158 sys::fs::directory_iterator DirEnd;
159 if (EC)
160 return errorCodeToError(EC);
161
162 // Add each entry to the list of inputs.
163 while (DirIt != DirEnd) {
164 Inputs.push_back(x: DirIt->path());
165 DirIt.increment(ec&: EC);
166 if (EC)
167 return errorCodeToError(EC);
168 }
169 }
170 return Inputs;
171}
172
173// Verify that the given combination of options makes sense.
174static Error verifyOptions(const DsymutilOptions &Options) {
175 if (Options.LinkOpts.Verbose && Options.LinkOpts.Quiet) {
176 return make_error<StringError>(
177 Args: "--quiet and --verbose cannot be specified together",
178 Args: errc::invalid_argument);
179 }
180
181 if (Options.InputFiles.empty()) {
182 return make_error<StringError>(Args: "no input files specified",
183 Args: errc::invalid_argument);
184 }
185
186 if (!Options.Flat && Options.OutputFile == "-")
187 return make_error<StringError>(
188 Args: "cannot emit to standard output without --flat.",
189 Args: errc::invalid_argument);
190
191 if (Options.InputFiles.size() > 1 && Options.Flat &&
192 !Options.OutputFile.empty())
193 return make_error<StringError>(
194 Args: "cannot use -o with multiple inputs in flat mode.",
195 Args: errc::invalid_argument);
196
197 if (!Options.ReproducerPath.empty() &&
198 Options.ReproMode != ReproducerMode::Use)
199 return make_error<StringError>(
200 Args: "cannot combine --gen-reproducer and --use-reproducer.",
201 Args: errc::invalid_argument);
202
203 return Error::success();
204}
205
206static Expected<DsymutilAccelTableKind>
207getAccelTableKind(opt::InputArgList &Args) {
208 if (opt::Arg *Accelerator = Args.getLastArg(Ids: OPT_accelerator)) {
209 StringRef S = Accelerator->getValue();
210 if (S == "Apple")
211 return DsymutilAccelTableKind::Apple;
212 if (S == "Dwarf")
213 return DsymutilAccelTableKind::Dwarf;
214 if (S == "Pub")
215 return DsymutilAccelTableKind::Pub;
216 if (S == "Default")
217 return DsymutilAccelTableKind::Default;
218 if (S == "None")
219 return DsymutilAccelTableKind::None;
220 return make_error<StringError>(Args: "invalid accelerator type specified: '" + S +
221 "'. Supported values are 'Apple', "
222 "'Dwarf', 'Pub', 'Default' and 'None'.",
223 Args: inconvertibleErrorCode());
224 }
225 return DsymutilAccelTableKind::Default;
226}
227
228static Expected<DsymutilDWARFLinkerType>
229getDWARFLinkerType(opt::InputArgList &Args) {
230 if (opt::Arg *LinkerType = Args.getLastArg(Ids: OPT_linker)) {
231 StringRef S = LinkerType->getValue();
232 if (S == "classic")
233 return DsymutilDWARFLinkerType::Classic;
234 if (S == "parallel")
235 return DsymutilDWARFLinkerType::Parallel;
236 return make_error<StringError>(Args: "invalid DWARF linker type specified: '" +
237 S +
238 "'. Supported values are 'classic', "
239 "'parallel'.",
240 Args: inconvertibleErrorCode());
241 }
242
243 return DsymutilDWARFLinkerType::Classic;
244}
245
246static Expected<ReproducerMode> getReproducerMode(opt::InputArgList &Args) {
247 if (Args.hasArg(Ids: OPT_gen_reproducer))
248 return ReproducerMode::GenerateOnExit;
249 if (opt::Arg *Reproducer = Args.getLastArg(Ids: OPT_reproducer)) {
250 StringRef S = Reproducer->getValue();
251 if (S == "GenerateOnExit")
252 return ReproducerMode::GenerateOnExit;
253 if (S == "GenerateOnCrash")
254 return ReproducerMode::GenerateOnCrash;
255 if (S == "Off")
256 return ReproducerMode::Off;
257 return make_error<StringError>(
258 Args: "invalid reproducer mode: '" + S +
259 "'. Supported values are 'GenerateOnExit', 'GenerateOnCrash', "
260 "'Off'.",
261 Args: inconvertibleErrorCode());
262 }
263 return ReproducerMode::GenerateOnCrash;
264}
265
266static Expected<DWARFVerify> getVerifyKind(opt::InputArgList &Args) {
267 if (Args.hasArg(Ids: OPT_verify))
268 return DWARFVerify::Output;
269 if (opt::Arg *Verify = Args.getLastArg(Ids: OPT_verify_dwarf)) {
270 StringRef S = Verify->getValue();
271 if (S == "input")
272 return DWARFVerify::Input;
273 if (S == "output")
274 return DWARFVerify::Output;
275 if (S == "all")
276 return DWARFVerify::All;
277 if (S == "auto")
278 return DWARFVerify::Auto;
279 if (S == "none")
280 return DWARFVerify::None;
281 return make_error<StringError>(Args: "invalid verify type specified: '" + S +
282 "'. Supported values are 'none', "
283 "'input', 'output', 'all' and 'auto'.",
284 Args: inconvertibleErrorCode());
285 }
286 return DWARFVerify::Default;
287}
288
289/// Parses the command line options into the LinkOptions struct and performs
290/// some sanity checking. Returns an error in case the latter fails.
291static Expected<DsymutilOptions> getOptions(opt::InputArgList &Args) {
292 DsymutilOptions Options;
293
294 Options.DumpDebugMap = Args.hasArg(Ids: OPT_dump_debug_map);
295 Options.DumpStab = Args.hasArg(Ids: OPT_symtab);
296 Options.Flat = Args.hasArg(Ids: OPT_flat);
297 Options.InputIsYAMLDebugMap = Args.hasArg(Ids: OPT_yaml_input);
298 Options.NoObjectTimestamp = Args.hasArg(Ids: OPT_no_object_timestamp);
299
300 if (Expected<DWARFVerify> Verify = getVerifyKind(Args)) {
301 Options.Verify = *Verify;
302 } else {
303 return Verify.takeError();
304 }
305
306 Options.LinkOpts.NoODR = Args.hasArg(Ids: OPT_no_odr);
307 Options.LinkOpts.VerifyInputDWARF =
308 flagIsSet(Flags: Options.Verify, SingleFlag: DWARFVerify::Input);
309 Options.LinkOpts.NoOutput = Args.hasArg(Ids: OPT_no_output);
310 Options.LinkOpts.NoTimestamp = Args.hasArg(Ids: OPT_no_swiftmodule_timestamp);
311 Options.LinkOpts.Update = Args.hasArg(Ids: OPT_update);
312 Options.LinkOpts.Verbose = Args.hasArg(Ids: OPT_verbose);
313 Options.LinkOpts.Quiet = Args.hasArg(Ids: OPT_quiet);
314 Options.LinkOpts.Statistics = Args.hasArg(Ids: OPT_statistics);
315 Options.LinkOpts.Fat64 = Args.hasArg(Ids: OPT_fat64);
316 Options.LinkOpts.KeepFunctionForStatic =
317 Args.hasArg(Ids: OPT_keep_func_for_static);
318 Options.LinkOpts.AllowSectionHeaderOffsetOverflow =
319 Args.hasArg(Ids: OPT_allow_section_header_offset_overflow);
320
321 if (opt::Arg *ReproducerPath = Args.getLastArg(Ids: OPT_use_reproducer)) {
322 Options.ReproMode = ReproducerMode::Use;
323 Options.ReproducerPath = ReproducerPath->getValue();
324 } else {
325 if (Expected<ReproducerMode> ReproMode = getReproducerMode(Args)) {
326 Options.ReproMode = *ReproMode;
327 } else {
328 return ReproMode.takeError();
329 }
330 }
331
332 if (Expected<DsymutilAccelTableKind> AccelKind = getAccelTableKind(Args)) {
333 Options.LinkOpts.TheAccelTableKind = *AccelKind;
334 } else {
335 return AccelKind.takeError();
336 }
337
338 if (Expected<DsymutilDWARFLinkerType> DWARFLinkerType =
339 getDWARFLinkerType(Args)) {
340 Options.LinkOpts.DWARFLinkerType = *DWARFLinkerType;
341 } else {
342 return DWARFLinkerType.takeError();
343 }
344
345 if (Expected<std::vector<std::string>> InputFiles =
346 getInputs(Args, DsymAsInput: Options.LinkOpts.Update)) {
347 Options.InputFiles = std::move(*InputFiles);
348 } else {
349 return InputFiles.takeError();
350 }
351
352 for (auto *Arch : Args.filtered(Ids: OPT_arch))
353 Options.Archs.push_back(x: Arch->getValue());
354
355 if (opt::Arg *OsoPrependPath = Args.getLastArg(Ids: OPT_oso_prepend_path))
356 Options.LinkOpts.PrependPath = OsoPrependPath->getValue();
357
358 for (const auto &Arg : Args.getAllArgValues(Id: OPT_object_prefix_map)) {
359 auto Split = StringRef(Arg).split(Separator: '=');
360 Options.LinkOpts.ObjectPrefixMap.insert(
361 x: {std::string(Split.first), std::string(Split.second)});
362 }
363
364 if (opt::Arg *OutputFile = Args.getLastArg(Ids: OPT_output))
365 Options.OutputFile = OutputFile->getValue();
366
367 if (opt::Arg *Toolchain = Args.getLastArg(Ids: OPT_toolchain))
368 Options.Toolchain = Toolchain->getValue();
369
370 if (Args.hasArg(Ids: OPT_assembly))
371 Options.LinkOpts.FileType = DWARFLinkerBase::OutputFileType::Assembly;
372
373 if (opt::Arg *NumThreads = Args.getLastArg(Ids: OPT_threads))
374 Options.LinkOpts.Threads = atoi(nptr: NumThreads->getValue());
375 else
376 Options.LinkOpts.Threads = 0; // Use all available hardware threads
377
378 if (Options.DumpDebugMap || Options.LinkOpts.Verbose)
379 Options.LinkOpts.Threads = 1;
380
381 if (opt::Arg *RemarksPrependPath = Args.getLastArg(Ids: OPT_remarks_prepend_path))
382 Options.LinkOpts.RemarksPrependPath = RemarksPrependPath->getValue();
383
384 if (opt::Arg *RemarksOutputFormat =
385 Args.getLastArg(Ids: OPT_remarks_output_format)) {
386 if (Expected<remarks::Format> FormatOrErr =
387 remarks::parseFormat(FormatStr: RemarksOutputFormat->getValue()))
388 Options.LinkOpts.RemarksFormat = *FormatOrErr;
389 else
390 return FormatOrErr.takeError();
391 }
392
393 Options.LinkOpts.RemarksKeepAll =
394 !Args.hasArg(Ids: OPT_remarks_drop_without_debug);
395
396 Options.LinkOpts.IncludeSwiftModulesFromInterface =
397 Args.hasArg(Ids: OPT_include_swiftmodules_from_interface);
398
399 if (opt::Arg *BuildVariantSuffix = Args.getLastArg(Ids: OPT_build_variant_suffix))
400 Options.LinkOpts.BuildVariantSuffix = BuildVariantSuffix->getValue();
401
402 for (auto *SearchPath : Args.filtered(Ids: OPT_dsym_search_path))
403 Options.LinkOpts.DSYMSearchPaths.push_back(x: SearchPath->getValue());
404
405 if (Error E = verifyOptions(Options))
406 return std::move(E);
407 return Options;
408}
409
410static Error createPlistFile(StringRef Bin, StringRef BundleRoot,
411 StringRef Toolchain) {
412 // Create plist file to write to.
413 SmallString<128> InfoPlist(BundleRoot);
414 sys::path::append(path&: InfoPlist, a: "Contents/Info.plist");
415 std::error_code EC;
416 raw_fd_ostream PL(InfoPlist, EC, sys::fs::OF_TextWithCRLF);
417 if (EC)
418 return make_error<StringError>(
419 Args: "cannot create Plist: " + toString(E: errorCodeToError(EC)), Args&: EC);
420
421 CFBundleInfo BI = getBundleInfo(ExePath: Bin);
422
423 if (BI.IDStr.empty()) {
424 StringRef BundleID = *sys::path::rbegin(path: BundleRoot);
425 if (sys::path::extension(path: BundleRoot) == ".dSYM")
426 BI.IDStr = std::string(sys::path::stem(path: BundleID));
427 else
428 BI.IDStr = std::string(BundleID);
429 }
430
431 // Print out information to the plist file.
432 PL << "<?xml version=\"1.0\" encoding=\"UTF-8\"\?>\n"
433 << "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" "
434 << "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
435 << "<plist version=\"1.0\">\n"
436 << "\t<dict>\n"
437 << "\t\t<key>CFBundleDevelopmentRegion</key>\n"
438 << "\t\t<string>English</string>\n"
439 << "\t\t<key>CFBundleIdentifier</key>\n"
440 << "\t\t<string>com.apple.xcode.dsym.";
441 printHTMLEscaped(String: BI.IDStr, Out&: PL);
442 PL << "</string>\n"
443 << "\t\t<key>CFBundleInfoDictionaryVersion</key>\n"
444 << "\t\t<string>6.0</string>\n"
445 << "\t\t<key>CFBundlePackageType</key>\n"
446 << "\t\t<string>dSYM</string>\n"
447 << "\t\t<key>CFBundleSignature</key>\n"
448 << "\t\t<string>\?\?\?\?</string>\n";
449
450 if (!BI.OmitShortVersion()) {
451 PL << "\t\t<key>CFBundleShortVersionString</key>\n";
452 PL << "\t\t<string>";
453 printHTMLEscaped(String: BI.ShortVersionStr, Out&: PL);
454 PL << "</string>\n";
455 }
456
457 PL << "\t\t<key>CFBundleVersion</key>\n";
458 PL << "\t\t<string>";
459 printHTMLEscaped(String: BI.VersionStr, Out&: PL);
460 PL << "</string>\n";
461
462 if (!Toolchain.empty()) {
463 PL << "\t\t<key>Toolchain</key>\n";
464 PL << "\t\t<string>";
465 printHTMLEscaped(String: Toolchain, Out&: PL);
466 PL << "</string>\n";
467 }
468
469 PL << "\t</dict>\n"
470 << "</plist>\n";
471
472 PL.close();
473 return Error::success();
474}
475
476static Error createBundleDir(StringRef BundleBase) {
477 SmallString<128> Bundle(BundleBase);
478 sys::path::append(path&: Bundle, a: "Contents", b: "Resources", c: "DWARF");
479 if (std::error_code EC =
480 create_directories(path: Bundle.str(), IgnoreExisting: true, Perms: sys::fs::perms::all_all))
481 return make_error<StringError>(
482 Args: "cannot create bundle: " + toString(E: errorCodeToError(EC)), Args&: EC);
483
484 return Error::success();
485}
486
487static bool verifyOutput(StringRef OutputFile, StringRef Arch,
488 DsymutilOptions Options, std::mutex &Mutex) {
489
490 if (OutputFile == "-") {
491 if (!Options.LinkOpts.Quiet) {
492 std::lock_guard<std::mutex> Guard(Mutex);
493 WithColor::warning() << "verification skipped for " << Arch
494 << " because writing to stdout.\n";
495 }
496 return true;
497 }
498
499 if (Options.LinkOpts.NoOutput) {
500 if (!Options.LinkOpts.Quiet) {
501 std::lock_guard<std::mutex> Guard(Mutex);
502 WithColor::warning() << "verification skipped for " << Arch
503 << " because --no-output was passed.\n";
504 }
505 return true;
506 }
507
508 Expected<OwningBinary<Binary>> BinOrErr = createBinary(Path: OutputFile);
509 if (!BinOrErr) {
510 std::lock_guard<std::mutex> Guard(Mutex);
511 WithColor::error() << OutputFile << ": " << toString(E: BinOrErr.takeError());
512 return false;
513 }
514
515 Binary &Binary = *BinOrErr.get().getBinary();
516 if (auto *Obj = dyn_cast<MachOObjectFile>(Val: &Binary)) {
517 std::unique_ptr<DWARFContext> DICtx = DWARFContext::create(Obj: *Obj);
518 if (DICtx->getMaxVersion() > 5) {
519 if (!Options.LinkOpts.Quiet) {
520 std::lock_guard<std::mutex> Guard(Mutex);
521 WithColor::warning() << "verification skipped for " << Arch
522 << " because DWARF standard greater than v5 is "
523 "not supported yet.\n";
524 }
525 return true;
526 }
527
528 if (Options.LinkOpts.Verbose) {
529 std::lock_guard<std::mutex> Guard(Mutex);
530 errs() << "Verifying DWARF for architecture: " << Arch << "\n";
531 }
532
533 std::string Buffer;
534 raw_string_ostream OS(Buffer);
535
536 DIDumpOptions DumpOpts;
537 bool success = DICtx->verify(OS, DumpOpts: DumpOpts.noImplicitRecursion());
538 if (!success) {
539 std::lock_guard<std::mutex> Guard(Mutex);
540 errs() << OS.str();
541 WithColor::error() << "output verification failed for " << Arch << '\n';
542 }
543 return success;
544 }
545
546 return false;
547}
548
549namespace {
550struct OutputLocation {
551 OutputLocation(std::string DWARFFile,
552 std::optional<std::string> ResourceDir = {})
553 : DWARFFile(DWARFFile), ResourceDir(ResourceDir) {}
554 /// This method is a workaround for older compilers.
555 std::optional<std::string> getResourceDir() const { return ResourceDir; }
556 std::string DWARFFile;
557 std::optional<std::string> ResourceDir;
558};
559} // namespace
560
561static Expected<OutputLocation>
562getOutputFileName(StringRef InputFile, const DsymutilOptions &Options) {
563 if (Options.OutputFile == "-")
564 return OutputLocation(Options.OutputFile);
565
566 // When updating, do in place replacement.
567 if (Options.OutputFile.empty() && Options.LinkOpts.Update)
568 return OutputLocation(std::string(InputFile));
569
570 // When dumping the debug map, just return an empty output location. This
571 // allows us to compute the output location once.
572 if (Options.DumpDebugMap)
573 return OutputLocation("");
574
575 // If a flat dSYM has been requested, things are pretty simple.
576 if (Options.Flat) {
577 if (Options.OutputFile.empty()) {
578 if (InputFile == "-")
579 return OutputLocation{"a.out.dwarf", {}};
580 return OutputLocation((InputFile + ".dwarf").str());
581 }
582
583 return OutputLocation(Options.OutputFile);
584 }
585
586 // We need to create/update a dSYM bundle.
587 // A bundle hierarchy looks like this:
588 // <bundle name>.dSYM/
589 // Contents/
590 // Info.plist
591 // Resources/
592 // DWARF/
593 // <DWARF file(s)>
594 std::string DwarfFile =
595 std::string(InputFile == "-" ? StringRef("a.out") : InputFile);
596 SmallString<128> Path(Options.OutputFile);
597 if (Path.empty())
598 Path = DwarfFile + ".dSYM";
599 if (!Options.LinkOpts.NoOutput) {
600 if (auto E = createBundleDir(BundleBase: Path))
601 return std::move(E);
602 if (auto E = createPlistFile(Bin: DwarfFile, BundleRoot: Path, Toolchain: Options.Toolchain))
603 return std::move(E);
604 }
605
606 sys::path::append(path&: Path, a: "Contents", b: "Resources");
607 std::string ResourceDir = std::string(Path);
608 sys::path::append(path&: Path, a: "DWARF", b: sys::path::filename(path: DwarfFile));
609 return OutputLocation(std::string(Path), ResourceDir);
610}
611
612int dsymutil_main(int argc, char **argv, const llvm::ToolContext &) {
613 // Parse arguments.
614 DsymutilOptTable T;
615 unsigned MAI;
616 unsigned MAC;
617 ArrayRef<const char *> ArgsArr = ArrayRef(argv + 1, argc - 1);
618 opt::InputArgList Args = T.ParseArgs(Args: ArgsArr, MissingArgIndex&: MAI, MissingArgCount&: MAC);
619
620 void *P = (void *)(intptr_t)getOutputFileName;
621 std::string SDKPath = sys::fs::getMainExecutable(argv0: argv[0], MainExecAddr: P);
622 SDKPath = std::string(sys::path::parent_path(path: SDKPath));
623
624 for (auto *Arg : Args.filtered(Ids: OPT_UNKNOWN)) {
625 WithColor::warning() << "ignoring unknown option: " << Arg->getSpelling()
626 << '\n';
627 }
628
629 if (Args.hasArg(Ids: OPT_help)) {
630 T.printHelp(
631 OS&: outs(), Usage: (std::string(argv[0]) + " [options] <input files>").c_str(),
632 Title: "manipulate archived DWARF debug symbol files.\n\n"
633 "dsymutil links the DWARF debug information found in the object files\n"
634 "for the executable <input file> by using debug symbols information\n"
635 "contained in its symbol table.\n",
636 ShowHidden: false);
637 return EXIT_SUCCESS;
638 }
639
640 if (Args.hasArg(Ids: OPT_version)) {
641 cl::PrintVersionMessage();
642 return EXIT_SUCCESS;
643 }
644
645 auto OptionsOrErr = getOptions(Args);
646 if (!OptionsOrErr) {
647 WithColor::error() << toString(E: OptionsOrErr.takeError()) << '\n';
648 return EXIT_FAILURE;
649 }
650
651 auto &Options = *OptionsOrErr;
652
653 InitializeAllTargetInfos();
654 InitializeAllTargetMCs();
655 InitializeAllTargets();
656 InitializeAllAsmPrinters();
657
658 auto Repro = Reproducer::createReproducer(Mode: Options.ReproMode,
659 Root: Options.ReproducerPath, Argc: argc, Argv: argv);
660 if (!Repro) {
661 WithColor::error() << toString(E: Repro.takeError()) << '\n';
662 return EXIT_FAILURE;
663 }
664
665 Options.LinkOpts.VFS = (*Repro)->getVFS();
666
667 for (const auto &Arch : Options.Archs)
668 if (Arch != "*" && Arch != "all" &&
669 !object::MachOObjectFile::isValidArch(ArchFlag: Arch)) {
670 WithColor::error() << "unsupported cpu architecture: '" << Arch << "'\n";
671 return EXIT_FAILURE;
672 }
673
674 for (auto &InputFile : Options.InputFiles) {
675 // Shared a single binary holder for all the link steps.
676 BinaryHolder::Options BinOpts;
677 BinOpts.Verbose = Options.LinkOpts.Verbose;
678 BinOpts.Warn = !Options.NoObjectTimestamp;
679 BinaryHolder BinHolder(Options.LinkOpts.VFS, BinOpts);
680
681 // Dump the symbol table for each input file and requested arch
682 if (Options.DumpStab) {
683 if (!dumpStab(BinHolder, InputFile, Archs: Options.Archs,
684 DSYMSearchPaths: Options.LinkOpts.DSYMSearchPaths,
685 PrependPath: Options.LinkOpts.PrependPath,
686 VariantSuffix: Options.LinkOpts.BuildVariantSuffix))
687 return EXIT_FAILURE;
688 continue;
689 }
690
691 auto DebugMapPtrsOrErr = parseDebugMap(
692 BinHolder, InputFile, Archs: Options.Archs, DSYMSearchPaths: Options.LinkOpts.DSYMSearchPaths,
693 PrependPath: Options.LinkOpts.PrependPath, VariantSuffix: Options.LinkOpts.BuildVariantSuffix,
694 Verbose: Options.LinkOpts.Verbose, InputIsYAML: Options.InputIsYAMLDebugMap);
695
696 if (auto EC = DebugMapPtrsOrErr.getError()) {
697 WithColor::error() << "cannot parse the debug map for '" << InputFile
698 << "': " << EC.message() << '\n';
699 return EXIT_FAILURE;
700 }
701
702 // Remember the number of debug maps that are being processed to decide how
703 // to name the remark files.
704 Options.LinkOpts.NumDebugMaps = DebugMapPtrsOrErr->size();
705
706 if (Options.LinkOpts.Update) {
707 // The debug map should be empty. Add one object file corresponding to
708 // the input file.
709 for (auto &Map : *DebugMapPtrsOrErr)
710 Map->addDebugMapObject(ObjectFilePath: InputFile,
711 Timestamp: sys::TimePoint<std::chrono::seconds>());
712 }
713
714 // Ensure that the debug map is not empty (anymore).
715 if (DebugMapPtrsOrErr->empty()) {
716 WithColor::error() << "no architecture to link\n";
717 return EXIT_FAILURE;
718 }
719
720 // Compute the output location and update the resource directory.
721 Expected<OutputLocation> OutputLocationOrErr =
722 getOutputFileName(InputFile, Options);
723 if (!OutputLocationOrErr) {
724 WithColor::error() << toString(E: OutputLocationOrErr.takeError()) << "\n";
725 return EXIT_FAILURE;
726 }
727 Options.LinkOpts.ResourceDir = OutputLocationOrErr->getResourceDir();
728
729 // Statistics only require different architectures to be processed
730 // sequentially, the link itself can still happen in parallel. Change the
731 // thread pool strategy here instead of modifying LinkOpts.Threads.
732 ThreadPoolStrategy S = hardware_concurrency(
733 ThreadCount: Options.LinkOpts.Statistics ? 1 : Options.LinkOpts.Threads);
734 if (Options.LinkOpts.Threads == 0) {
735 // If NumThreads is not specified, create one thread for each input, up to
736 // the number of hardware threads.
737 S.ThreadsRequested = DebugMapPtrsOrErr->size();
738 S.Limit = true;
739 }
740 DefaultThreadPool Threads(S);
741
742 // If there is more than one link to execute, we need to generate
743 // temporary files.
744 const bool NeedsTempFiles =
745 !Options.DumpDebugMap && (Options.OutputFile != "-") &&
746 (DebugMapPtrsOrErr->size() != 1 || Options.LinkOpts.Update);
747
748 std::atomic_char AllOK(1);
749 SmallVector<MachOUtils::ArchAndFile, 4> TempFiles;
750
751 std::mutex ErrorHandlerMutex;
752
753 // Set up a crash recovery context.
754 CrashRecoveryContext::Enable();
755 CrashRecoveryContext CRC;
756 CRC.DumpStackAndCleanupOnFailure = true;
757
758 const bool Crashed = !CRC.RunSafely(Fn: [&]() {
759 for (auto &Map : *DebugMapPtrsOrErr) {
760 if (Options.LinkOpts.Verbose || Options.DumpDebugMap)
761 Map->print(OS&: outs());
762
763 if (Options.DumpDebugMap)
764 continue;
765
766 if (Map->begin() == Map->end()) {
767 if (!Options.LinkOpts.Quiet) {
768 std::lock_guard<std::mutex> Guard(ErrorHandlerMutex);
769 WithColor::warning()
770 << "no debug symbols in executable (-arch "
771 << MachOUtils::getArchName(Arch: Map->getTriple().getArchName())
772 << ")\n";
773 }
774 }
775
776 // Using a std::shared_ptr rather than std::unique_ptr because move-only
777 // types don't work with std::bind in the ThreadPool implementation.
778 std::shared_ptr<raw_fd_ostream> OS;
779
780 std::string OutputFile = OutputLocationOrErr->DWARFFile;
781 if (NeedsTempFiles) {
782 TempFiles.emplace_back(Args: Map->getTriple().getArchName().str());
783
784 auto E = TempFiles.back().createTempFile();
785 if (E) {
786 std::lock_guard<std::mutex> Guard(ErrorHandlerMutex);
787 WithColor::error() << toString(E: std::move(E));
788 AllOK.fetch_and(i: false);
789 return;
790 }
791
792 MachOUtils::ArchAndFile &AF = TempFiles.back();
793 OS = std::make_shared<raw_fd_ostream>(args: AF.getFD(),
794 /*shouldClose*/ args: false);
795 OutputFile = AF.getPath();
796 } else {
797 std::error_code EC;
798 OS = std::make_shared<raw_fd_ostream>(
799 args: Options.LinkOpts.NoOutput ? "-" : OutputFile, args&: EC,
800 args: sys::fs::OF_None);
801 if (EC) {
802 WithColor::error() << OutputFile << ": " << EC.message() << "\n";
803 AllOK.fetch_and(i: false);
804 return;
805 }
806 }
807
808 auto LinkLambda = [&,
809 OutputFile](std::shared_ptr<raw_fd_ostream> Stream) {
810 DwarfLinkerForBinary Linker(*Stream, BinHolder, Options.LinkOpts,
811 ErrorHandlerMutex);
812 AllOK.fetch_and(i: Linker.link(*Map));
813 Stream->flush();
814 if (flagIsSet(Flags: Options.Verify, SingleFlag: DWARFVerify::Output) ||
815 (flagIsSet(Flags: Options.Verify, SingleFlag: DWARFVerify::OutputOnValidInput) &&
816 !Linker.InputVerificationFailed())) {
817 AllOK.fetch_and(i: verifyOutput(OutputFile,
818 Arch: Map->getTriple().getArchName(),
819 Options, Mutex&: ErrorHandlerMutex));
820 }
821 };
822
823 // FIXME: The DwarfLinker can have some very deep recursion that can max
824 // out the (significantly smaller) stack when using threads. We don't
825 // want this limitation when we only have a single thread.
826 if (S.ThreadsRequested == 1)
827 LinkLambda(OS);
828 else
829 Threads.async(F&: LinkLambda, ArgList&: OS);
830 }
831
832 Threads.wait();
833 });
834
835 if (Crashed)
836 (*Repro)->generate();
837
838 if (!AllOK || Crashed)
839 return EXIT_FAILURE;
840
841 if (NeedsTempFiles) {
842 bool Fat64 = Options.LinkOpts.Fat64;
843 if (!Fat64) {
844 // Universal Mach-O files can't have an archicture slice that starts
845 // beyond the 4GB boundary. "lipo" can create a 64 bit universal
846 // header, but older tools may not support these files so we want to
847 // emit a warning if the file can't be encoded as a file with a 32 bit
848 // universal header. To detect this, we check the size of each
849 // architecture's skinny Mach-O file and add up the offsets. If they
850 // exceed 4GB, we emit a warning.
851
852 // First we compute the right offset where the first architecture will
853 // fit followin the 32 bit universal header. The 32 bit universal header
854 // starts with a uint32_t magic and a uint32_t number of architecture
855 // infos. Then it is followed by 5 uint32_t values for each
856 // architecture. So we set the start offset to the right value so we can
857 // calculate the exact offset that the first architecture slice can
858 // start at.
859 constexpr uint64_t MagicAndCountSize = 2 * 4;
860 constexpr uint64_t UniversalArchInfoSize = 5 * 4;
861 uint64_t FileOffset =
862 MagicAndCountSize + UniversalArchInfoSize * TempFiles.size();
863 for (const auto &File : TempFiles) {
864 ErrorOr<vfs::Status> stat =
865 Options.LinkOpts.VFS->status(Path: File.getPath());
866 if (!stat)
867 break;
868 if (FileOffset > UINT32_MAX) {
869 Fat64 = true;
870 WithColor::warning() << formatv(
871 Fmt: "the universal binary has a slice with a starting offset "
872 "({0:x}) that exceeds 4GB. To avoid producing an invalid "
873 "Mach-O file, a universal binary with a 64-bit header will be "
874 "generated, which may not be supported by older tools. Use the "
875 "-fat64 flag to force a 64-bit header and silence this "
876 "warning.",
877 Vals&: FileOffset);
878 }
879 FileOffset += stat->getSize();
880 }
881 }
882 if (!MachOUtils::generateUniversalBinary(
883 ArchFiles&: TempFiles, OutputFileName: OutputLocationOrErr->DWARFFile, Options.LinkOpts,
884 SDKPath, Fat64))
885 return EXIT_FAILURE;
886 }
887 }
888
889 return EXIT_SUCCESS;
890}
891