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