[llvm-readobj] - Remove unwrapOrError(ErrorOr<T> EO) helper.
[llvm-complete.git] / tools / dsymutil / dsymutil.cpp
blobbf42ec73269c7c454cb812abff77fa84571f05ce
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 //===----------------------------------------------------------------------===//
13 #include "dsymutil.h"
14 #include "BinaryHolder.h"
15 #include "CFBundle.h"
16 #include "DebugMap.h"
17 #include "LinkUtils.h"
18 #include "MachOUtils.h"
19 #include "llvm/ADT/SmallString.h"
20 #include "llvm/ADT/SmallVector.h"
21 #include "llvm/ADT/StringExtras.h"
22 #include "llvm/ADT/StringRef.h"
23 #include "llvm/ADT/Triple.h"
24 #include "llvm/DebugInfo/DIContext.h"
25 #include "llvm/DebugInfo/DWARF/DWARFContext.h"
26 #include "llvm/DebugInfo/DWARF/DWARFVerifier.h"
27 #include "llvm/Object/Binary.h"
28 #include "llvm/Object/MachO.h"
29 #include "llvm/Support/CommandLine.h"
30 #include "llvm/Support/FileSystem.h"
31 #include "llvm/Support/InitLLVM.h"
32 #include "llvm/Support/ManagedStatic.h"
33 #include "llvm/Support/Path.h"
34 #include "llvm/Support/TargetSelect.h"
35 #include "llvm/Support/ThreadPool.h"
36 #include "llvm/Support/WithColor.h"
37 #include "llvm/Support/raw_ostream.h"
38 #include "llvm/Support/thread.h"
39 #include <algorithm>
40 #include <cstdint>
41 #include <cstdlib>
42 #include <string>
43 #include <system_error>
45 using namespace llvm;
46 using namespace llvm::cl;
47 using namespace llvm::dsymutil;
48 using namespace object;
50 static OptionCategory DsymCategory("Specific Options");
51 static opt<bool> Help("h", desc("Alias for -help"), Hidden);
52 static opt<bool> Version("v", desc("Alias for -version"), Hidden);
54 static list<std::string> InputFiles(Positional, OneOrMore,
55 desc("<input files>"), cat(DsymCategory));
57 static opt<std::string>
58 OutputFileOpt("o",
59 desc("Specify the output file. default: <input file>.dwarf"),
60 value_desc("filename"), cat(DsymCategory));
61 static alias OutputFileOptA("out", desc("Alias for -o"),
62 aliasopt(OutputFileOpt));
64 static opt<std::string> OsoPrependPath(
65 "oso-prepend-path",
66 desc("Specify a directory to prepend to the paths of object files."),
67 value_desc("path"), cat(DsymCategory));
69 static opt<bool> Assembly(
70 "S",
71 desc("Output textual assembly instead of a binary dSYM companion file."),
72 init(false), cat(DsymCategory), cl::Hidden);
74 static opt<bool> DumpStab(
75 "symtab",
76 desc("Dumps the symbol table found in executable or object file(s) and\n"
77 "exits."),
78 init(false), cat(DsymCategory));
79 static alias DumpStabA("s", desc("Alias for --symtab"), aliasopt(DumpStab));
81 static opt<bool> FlatOut("flat",
82 desc("Produce a flat dSYM file (not a bundle)."),
83 init(false), cat(DsymCategory));
84 static alias FlatOutA("f", desc("Alias for --flat"), aliasopt(FlatOut));
86 static opt<bool> Minimize(
87 "minimize",
88 desc("When used when creating a dSYM file with Apple accelerator tables,\n"
89 "this option will suppress the emission of the .debug_inlines, \n"
90 ".debug_pubnames, and .debug_pubtypes sections since dsymutil \n"
91 "has better equivalents: .apple_names and .apple_types. When used in\n"
92 "conjunction with --update option, this option will cause redundant\n"
93 "accelerator tables to be removed."),
94 init(false), cat(DsymCategory));
95 static alias MinimizeA("z", desc("Alias for --minimize"), aliasopt(Minimize));
97 static opt<bool> Update(
98 "update",
99 desc("Updates existing dSYM files to contain the latest accelerator\n"
100 "tables and other DWARF optimizations."),
101 init(false), cat(DsymCategory));
102 static alias UpdateA("u", desc("Alias for --update"), aliasopt(Update));
104 static opt<std::string> SymbolMap(
105 "symbol-map",
106 desc("Updates the existing dSYMs inplace using symbol map specified."),
107 value_desc("bcsymbolmap"), cat(DsymCategory));
109 static cl::opt<AccelTableKind> AcceleratorTable(
110 "accelerator", cl::desc("Output accelerator tables."),
111 cl::values(clEnumValN(AccelTableKind::Default, "Default",
112 "Default for input."),
113 clEnumValN(AccelTableKind::Apple, "Apple", "Apple"),
114 clEnumValN(AccelTableKind::Dwarf, "Dwarf", "DWARF")),
115 cl::init(AccelTableKind::Default), cat(DsymCategory));
117 static opt<unsigned> NumThreads(
118 "num-threads",
119 desc("Specifies the maximum number (n) of simultaneous threads to use\n"
120 "when linking multiple architectures."),
121 value_desc("n"), init(0), cat(DsymCategory));
122 static alias NumThreadsA("j", desc("Alias for --num-threads"),
123 aliasopt(NumThreads));
125 static opt<bool> Verbose("verbose", desc("Verbosity level"), init(false),
126 cat(DsymCategory));
128 static opt<bool>
129 NoOutput("no-output",
130 desc("Do the link in memory, but do not emit the result file."),
131 init(false), cat(DsymCategory));
133 static opt<bool>
134 NoTimestamp("no-swiftmodule-timestamp",
135 desc("Don't check timestamp for swiftmodule files."),
136 init(false), cat(DsymCategory));
138 static list<std::string> ArchFlags(
139 "arch",
140 desc("Link DWARF debug information only for specified CPU architecture\n"
141 "types. This option can be specified multiple times, once for each\n"
142 "desired architecture. All CPU architectures will be linked by\n"
143 "default."),
144 value_desc("arch"), ZeroOrMore, cat(DsymCategory));
146 static opt<bool>
147 NoODR("no-odr",
148 desc("Do not use ODR (One Definition Rule) for type uniquing."),
149 init(false), cat(DsymCategory));
151 static opt<bool> DumpDebugMap(
152 "dump-debug-map",
153 desc("Parse and dump the debug map to standard output. Not DWARF link "
154 "will take place."),
155 init(false), cat(DsymCategory));
157 static opt<bool> InputIsYAMLDebugMap(
158 "y", desc("Treat the input file is a YAML debug map rather than a binary."),
159 init(false), cat(DsymCategory));
161 static opt<bool> Verify("verify", desc("Verify the linked DWARF debug info."),
162 cat(DsymCategory));
164 static opt<std::string>
165 Toolchain("toolchain", desc("Embed toolchain information in dSYM bundle."),
166 cat(DsymCategory));
168 static opt<bool>
169 PaperTrailWarnings("papertrail",
170 desc("Embed warnings in the linked DWARF debug info."),
171 cat(DsymCategory));
173 static Error createPlistFile(llvm::StringRef Bin, llvm::StringRef BundleRoot) {
174 if (NoOutput)
175 return Error::success();
177 // Create plist file to write to.
178 llvm::SmallString<128> InfoPlist(BundleRoot);
179 llvm::sys::path::append(InfoPlist, "Contents/Info.plist");
180 std::error_code EC;
181 llvm::raw_fd_ostream PL(InfoPlist, EC, llvm::sys::fs::OF_Text);
182 if (EC)
183 return make_error<StringError>(
184 "cannot create Plist: " + toString(errorCodeToError(EC)), EC);
186 CFBundleInfo BI = getBundleInfo(Bin);
188 if (BI.IDStr.empty()) {
189 llvm::StringRef BundleID = *llvm::sys::path::rbegin(BundleRoot);
190 if (llvm::sys::path::extension(BundleRoot) == ".dSYM")
191 BI.IDStr = llvm::sys::path::stem(BundleID);
192 else
193 BI.IDStr = BundleID;
196 // Print out information to the plist file.
197 PL << "<?xml version=\"1.0\" encoding=\"UTF-8\"\?>\n"
198 << "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" "
199 << "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
200 << "<plist version=\"1.0\">\n"
201 << "\t<dict>\n"
202 << "\t\t<key>CFBundleDevelopmentRegion</key>\n"
203 << "\t\t<string>English</string>\n"
204 << "\t\t<key>CFBundleIdentifier</key>\n"
205 << "\t\t<string>com.apple.xcode.dsym." << BI.IDStr << "</string>\n"
206 << "\t\t<key>CFBundleInfoDictionaryVersion</key>\n"
207 << "\t\t<string>6.0</string>\n"
208 << "\t\t<key>CFBundlePackageType</key>\n"
209 << "\t\t<string>dSYM</string>\n"
210 << "\t\t<key>CFBundleSignature</key>\n"
211 << "\t\t<string>\?\?\?\?</string>\n";
213 if (!BI.OmitShortVersion()) {
214 PL << "\t\t<key>CFBundleShortVersionString</key>\n";
215 PL << "\t\t<string>";
216 printHTMLEscaped(BI.ShortVersionStr, PL);
217 PL << "</string>\n";
220 PL << "\t\t<key>CFBundleVersion</key>\n";
221 PL << "\t\t<string>";
222 printHTMLEscaped(BI.VersionStr, PL);
223 PL << "</string>\n";
225 if (!Toolchain.empty()) {
226 PL << "\t\t<key>Toolchain</key>\n";
227 PL << "\t\t<string>";
228 printHTMLEscaped(Toolchain, PL);
229 PL << "</string>\n";
232 PL << "\t</dict>\n"
233 << "</plist>\n";
235 PL.close();
236 return Error::success();
239 static Error createBundleDir(llvm::StringRef BundleBase) {
240 if (NoOutput)
241 return Error::success();
243 llvm::SmallString<128> Bundle(BundleBase);
244 llvm::sys::path::append(Bundle, "Contents", "Resources", "DWARF");
245 if (std::error_code EC =
246 create_directories(Bundle.str(), true, llvm::sys::fs::perms::all_all))
247 return make_error<StringError>(
248 "cannot create bundle: " + toString(errorCodeToError(EC)), EC);
250 return Error::success();
253 static bool verify(llvm::StringRef OutputFile, llvm::StringRef Arch) {
254 if (OutputFile == "-") {
255 WithColor::warning() << "verification skipped for " << Arch
256 << "because writing to stdout.\n";
257 return true;
260 Expected<OwningBinary<Binary>> BinOrErr = createBinary(OutputFile);
261 if (!BinOrErr) {
262 WithColor::error() << OutputFile << ": " << toString(BinOrErr.takeError());
263 return false;
266 Binary &Binary = *BinOrErr.get().getBinary();
267 if (auto *Obj = dyn_cast<MachOObjectFile>(&Binary)) {
268 raw_ostream &os = Verbose ? errs() : nulls();
269 os << "Verifying DWARF for architecture: " << Arch << "\n";
270 std::unique_ptr<DWARFContext> DICtx = DWARFContext::create(*Obj);
271 DIDumpOptions DumpOpts;
272 bool success = DICtx->verify(os, DumpOpts.noImplicitRecursion());
273 if (!success)
274 WithColor::error() << "verification failed for " << Arch << '\n';
275 return success;
278 return false;
281 namespace {
282 struct OutputLocation {
283 OutputLocation(std::string DWARFFile,
284 llvm::Optional<std::string> ResourceDir = {})
285 : DWARFFile(DWARFFile), ResourceDir(ResourceDir) {}
286 /// This method is a workaround for older compilers.
287 llvm::Optional<std::string> getResourceDir() const { return ResourceDir; }
288 std::string DWARFFile;
289 llvm::Optional<std::string> ResourceDir;
293 static Expected<OutputLocation> getOutputFileName(llvm::StringRef InputFile) {
294 if (OutputFileOpt == "-")
295 return OutputLocation(OutputFileOpt);
297 // When updating, do in place replacement.
298 if (OutputFileOpt.empty() && (Update || !SymbolMap.empty()))
299 return OutputLocation(InputFile);
301 // If a flat dSYM has been requested, things are pretty simple.
302 if (FlatOut) {
303 if (OutputFileOpt.empty()) {
304 if (InputFile == "-")
305 return OutputLocation{"a.out.dwarf", {}};
306 return OutputLocation((InputFile + ".dwarf").str());
309 return OutputLocation(OutputFileOpt);
312 // We need to create/update a dSYM bundle.
313 // A bundle hierarchy looks like this:
314 // <bundle name>.dSYM/
315 // Contents/
316 // Info.plist
317 // Resources/
318 // DWARF/
319 // <DWARF file(s)>
320 std::string DwarfFile =
321 InputFile == "-" ? llvm::StringRef("a.out") : InputFile;
322 llvm::SmallString<128> Path(OutputFileOpt);
323 if (Path.empty())
324 Path = DwarfFile + ".dSYM";
325 if (auto E = createBundleDir(Path))
326 return std::move(E);
327 if (auto E = createPlistFile(DwarfFile, Path))
328 return std::move(E);
330 llvm::sys::path::append(Path, "Contents", "Resources");
331 std::string ResourceDir = Path.str();
332 llvm::sys::path::append(Path, "DWARF", llvm::sys::path::filename(DwarfFile));
333 return OutputLocation(Path.str(), ResourceDir);
336 /// Parses the command line options into the LinkOptions struct and performs
337 /// some sanity checking. Returns an error in case the latter fails.
338 static Expected<LinkOptions> getOptions() {
339 LinkOptions Options;
341 Options.Verbose = Verbose;
342 Options.NoOutput = NoOutput;
343 Options.NoODR = NoODR;
344 Options.Minimize = Minimize;
345 Options.Update = Update;
346 Options.NoTimestamp = NoTimestamp;
347 Options.PrependPath = OsoPrependPath;
348 Options.TheAccelTableKind = AcceleratorTable;
350 if (!SymbolMap.empty())
351 Options.Update = true;
353 if (Assembly)
354 Options.FileType = OutputFileType::Assembly;
356 if (Options.Update && std::find(InputFiles.begin(), InputFiles.end(), "-") !=
357 InputFiles.end()) {
358 // FIXME: We cannot use stdin for an update because stdin will be
359 // consumed by the BinaryHolder during the debugmap parsing, and
360 // then we will want to consume it again in DwarfLinker. If we
361 // used a unique BinaryHolder object that could cache multiple
362 // binaries this restriction would go away.
363 return make_error<StringError>(
364 "standard input cannot be used as input for a dSYM update.",
365 inconvertibleErrorCode());
368 if (NumThreads == 0)
369 Options.Threads = llvm::thread::hardware_concurrency();
370 else
371 Options.Threads = NumThreads;
372 if (DumpDebugMap || Verbose)
373 Options.Threads = 1;
375 return Options;
378 /// Return a list of input files. This function has logic for dealing with the
379 /// special case where we might have dSYM bundles as input. The function
380 /// returns an error when the directory structure doesn't match that of a dSYM
381 /// bundle.
382 static Expected<std::vector<std::string>> getInputs(bool DsymAsInput) {
383 if (!DsymAsInput)
384 return InputFiles;
386 // If we are updating, we might get dSYM bundles as input.
387 std::vector<std::string> Inputs;
388 for (const auto &Input : InputFiles) {
389 if (!llvm::sys::fs::is_directory(Input)) {
390 Inputs.push_back(Input);
391 continue;
394 // Make sure that we're dealing with a dSYM bundle.
395 SmallString<256> BundlePath(Input);
396 sys::path::append(BundlePath, "Contents", "Resources", "DWARF");
397 if (!llvm::sys::fs::is_directory(BundlePath))
398 return make_error<StringError>(
399 Input + " is a directory, but doesn't look like a dSYM bundle.",
400 inconvertibleErrorCode());
402 // Create a directory iterator to iterate over all the entries in the
403 // bundle.
404 std::error_code EC;
405 llvm::sys::fs::directory_iterator DirIt(BundlePath, EC);
406 llvm::sys::fs::directory_iterator DirEnd;
407 if (EC)
408 return errorCodeToError(EC);
410 // Add each entry to the list of inputs.
411 while (DirIt != DirEnd) {
412 Inputs.push_back(DirIt->path());
413 DirIt.increment(EC);
414 if (EC)
415 return errorCodeToError(EC);
418 return Inputs;
421 int main(int argc, char **argv) {
422 InitLLVM X(argc, argv);
424 void *P = (void *)(intptr_t)getOutputFileName;
425 std::string SDKPath = llvm::sys::fs::getMainExecutable(argv[0], P);
426 SDKPath = llvm::sys::path::parent_path(SDKPath);
428 HideUnrelatedOptions({&DsymCategory, &ColorCategory});
429 llvm::cl::ParseCommandLineOptions(
430 argc, argv,
431 "manipulate archived DWARF debug symbol files.\n\n"
432 "dsymutil links the DWARF debug information found in the object files\n"
433 "for the executable <input file> by using debug symbols information\n"
434 "contained in its symbol table.\n");
436 if (Help) {
437 PrintHelpMessage();
438 return 0;
441 if (Version) {
442 llvm::cl::PrintVersionMessage();
443 return 0;
446 auto OptionsOrErr = getOptions();
447 if (!OptionsOrErr) {
448 WithColor::error() << toString(OptionsOrErr.takeError());
449 return 1;
452 llvm::InitializeAllTargetInfos();
453 llvm::InitializeAllTargetMCs();
454 llvm::InitializeAllTargets();
455 llvm::InitializeAllAsmPrinters();
457 auto InputsOrErr = getInputs(OptionsOrErr->Update);
458 if (!InputsOrErr) {
459 WithColor::error() << toString(InputsOrErr.takeError()) << '\n';
460 return 1;
463 if (!FlatOut && OutputFileOpt == "-") {
464 WithColor::error() << "cannot emit to standard output without --flat\n";
465 return 1;
468 if (InputsOrErr->size() > 1 && FlatOut && !OutputFileOpt.empty()) {
469 WithColor::error() << "cannot use -o with multiple inputs in flat mode\n";
470 return 1;
473 if (InputFiles.size() > 1 && !SymbolMap.empty() &&
474 !llvm::sys::fs::is_directory(SymbolMap)) {
475 WithColor::error() << "when unobfuscating multiple files, --symbol-map "
476 << "needs to point to a directory.\n";
477 return 1;
480 if (getenv("RC_DEBUG_OPTIONS"))
481 PaperTrailWarnings = true;
483 if (PaperTrailWarnings && InputIsYAMLDebugMap)
484 WithColor::warning()
485 << "Paper trail warnings are not supported for YAML input";
487 for (const auto &Arch : ArchFlags)
488 if (Arch != "*" && Arch != "all" &&
489 !llvm::object::MachOObjectFile::isValidArch(Arch)) {
490 WithColor::error() << "unsupported cpu architecture: '" << Arch << "'\n";
491 return 1;
494 SymbolMapLoader SymMapLoader(SymbolMap);
496 for (auto &InputFile : *InputsOrErr) {
497 // Dump the symbol table for each input file and requested arch
498 if (DumpStab) {
499 if (!dumpStab(InputFile, ArchFlags, OsoPrependPath))
500 return 1;
501 continue;
504 auto DebugMapPtrsOrErr =
505 parseDebugMap(InputFile, ArchFlags, OsoPrependPath, PaperTrailWarnings,
506 Verbose, InputIsYAMLDebugMap);
508 if (auto EC = DebugMapPtrsOrErr.getError()) {
509 WithColor::error() << "cannot parse the debug map for '" << InputFile
510 << "': " << EC.message() << '\n';
511 return 1;
514 if (OptionsOrErr->Update) {
515 // The debug map should be empty. Add one object file corresponding to
516 // the input file.
517 for (auto &Map : *DebugMapPtrsOrErr)
518 Map->addDebugMapObject(InputFile,
519 llvm::sys::TimePoint<std::chrono::seconds>());
522 // Ensure that the debug map is not empty (anymore).
523 if (DebugMapPtrsOrErr->empty()) {
524 WithColor::error() << "no architecture to link\n";
525 return 1;
528 // Shared a single binary holder for all the link steps.
529 BinaryHolder BinHolder;
531 unsigned ThreadCount =
532 std::min<unsigned>(OptionsOrErr->Threads, DebugMapPtrsOrErr->size());
533 llvm::ThreadPool Threads(ThreadCount);
535 // If there is more than one link to execute, we need to generate
536 // temporary files.
537 bool NeedsTempFiles =
538 !DumpDebugMap && (OutputFileOpt != "-") &&
539 (DebugMapPtrsOrErr->size() != 1 || OptionsOrErr->Update);
541 llvm::SmallVector<MachOUtils::ArchAndFile, 4> TempFiles;
542 std::atomic_char AllOK(1);
543 for (auto &Map : *DebugMapPtrsOrErr) {
544 if (Verbose || DumpDebugMap)
545 Map->print(llvm::outs());
547 if (DumpDebugMap)
548 continue;
550 if (!SymbolMap.empty())
551 OptionsOrErr->Translator = SymMapLoader.Load(InputFile, *Map);
553 if (Map->begin() == Map->end())
554 WithColor::warning()
555 << "no debug symbols in executable (-arch "
556 << MachOUtils::getArchName(Map->getTriple().getArchName()) << ")\n";
558 // Using a std::shared_ptr rather than std::unique_ptr because move-only
559 // types don't work with std::bind in the ThreadPool implementation.
560 std::shared_ptr<raw_fd_ostream> OS;
562 Expected<OutputLocation> OutputLocationOrErr =
563 getOutputFileName(InputFile);
564 if (!OutputLocationOrErr) {
565 WithColor::error() << toString(OutputLocationOrErr.takeError());
566 return 1;
568 OptionsOrErr->ResourceDir = OutputLocationOrErr->getResourceDir();
570 std::string OutputFile = OutputLocationOrErr->DWARFFile;
571 if (NeedsTempFiles) {
572 TempFiles.emplace_back(Map->getTriple().getArchName().str());
574 auto E = TempFiles.back().createTempFile();
575 if (E) {
576 WithColor::error() << toString(std::move(E));
577 return 1;
580 auto &TempFile = *(TempFiles.back().File);
581 OS = std::make_shared<raw_fd_ostream>(TempFile.FD,
582 /*shouldClose*/ false);
583 OutputFile = TempFile.TmpName;
584 } else {
585 std::error_code EC;
586 OS = std::make_shared<raw_fd_ostream>(NoOutput ? "-" : OutputFile, EC,
587 sys::fs::OF_None);
588 if (EC) {
589 WithColor::error() << OutputFile << ": " << EC.message();
590 return 1;
594 auto LinkLambda = [&, OutputFile](std::shared_ptr<raw_fd_ostream> Stream,
595 LinkOptions Options) {
596 AllOK.fetch_and(
597 linkDwarf(*Stream, BinHolder, *Map, std::move(Options)));
598 Stream->flush();
599 if (Verify && !NoOutput)
600 AllOK.fetch_and(verify(OutputFile, Map->getTriple().getArchName()));
603 // FIXME: The DwarfLinker can have some very deep recursion that can max
604 // out the (significantly smaller) stack when using threads. We don't
605 // want this limitation when we only have a single thread.
606 if (ThreadCount == 1)
607 LinkLambda(OS, *OptionsOrErr);
608 else
609 Threads.async(LinkLambda, OS, *OptionsOrErr);
612 Threads.wait();
614 if (!AllOK)
615 return 1;
617 if (NeedsTempFiles) {
618 Expected<OutputLocation> OutputLocationOrErr = getOutputFileName(InputFile);
619 if (!OutputLocationOrErr) {
620 WithColor::error() << toString(OutputLocationOrErr.takeError());
621 return 1;
623 if (!MachOUtils::generateUniversalBinary(TempFiles,
624 OutputLocationOrErr->DWARFFile,
625 *OptionsOrErr, SDKPath))
626 return 1;
630 return 0;