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

source code of llvm/tools/dsymutil/dsymutil.cpp