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