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