Home | History | Annotate | Download | only in dsymutil
      1 //===- dsymutil.cpp - Debug info dumping utility for llvm -----------------===//
      2 //
      3 //                     The LLVM Compiler Infrastructure
      4 //
      5 // This file is distributed under the University of Illinois Open Source
      6 // License. See LICENSE.TXT for details.
      7 //
      8 //===----------------------------------------------------------------------===//
      9 //
     10 // This program is a utility that aims to be a dropin replacement for
     11 // Darwin's dsymutil.
     12 //
     13 //===----------------------------------------------------------------------===//
     14 
     15 #include "dsymutil.h"
     16 #include "BinaryHolder.h"
     17 #include "CFBundle.h"
     18 #include "DebugMap.h"
     19 #include "LinkUtils.h"
     20 #include "MachOUtils.h"
     21 #include "llvm/ADT/SmallString.h"
     22 #include "llvm/ADT/SmallVector.h"
     23 #include "llvm/ADT/StringExtras.h"
     24 #include "llvm/ADT/StringRef.h"
     25 #include "llvm/ADT/Triple.h"
     26 #include "llvm/DebugInfo/DIContext.h"
     27 #include "llvm/DebugInfo/DWARF/DWARFContext.h"
     28 #include "llvm/DebugInfo/DWARF/DWARFVerifier.h"
     29 #include "llvm/Object/Binary.h"
     30 #include "llvm/Object/MachO.h"
     31 #include "llvm/Support/CommandLine.h"
     32 #include "llvm/Support/FileSystem.h"
     33 #include "llvm/Support/InitLLVM.h"
     34 #include "llvm/Support/ManagedStatic.h"
     35 #include "llvm/Support/Path.h"
     36 #include "llvm/Support/TargetSelect.h"
     37 #include "llvm/Support/ThreadPool.h"
     38 #include "llvm/Support/WithColor.h"
     39 #include "llvm/Support/raw_ostream.h"
     40 #include "llvm/Support/thread.h"
     41 #include <algorithm>
     42 #include <cstdint>
     43 #include <cstdlib>
     44 #include <string>
     45 #include <system_error>
     46 
     47 using namespace llvm;
     48 using namespace llvm::cl;
     49 using namespace llvm::dsymutil;
     50 using namespace object;
     51 
     52 static OptionCategory DsymCategory("Specific Options");
     53 static opt<bool> Help("h", desc("Alias for -help"), Hidden);
     54 static opt<bool> Version("v", desc("Alias for -version"), Hidden);
     55 
     56 static list<std::string> InputFiles(Positional, OneOrMore,
     57                                     desc("<input files>"), cat(DsymCategory));
     58 
     59 static opt<std::string>
     60     OutputFileOpt("o",
     61                   desc("Specify the output file. default: <input file>.dwarf"),
     62                   value_desc("filename"), cat(DsymCategory));
     63 
     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));
     68 
     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);
     73 
     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));
     80 
     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));
     85 
     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));
     96 
     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));
    103 
    104 static cl::opt<AccelTableKind> AcceleratorTable(
    105     "accelerator", cl::desc("Output accelerator tables."),
    106     cl::values(clEnumValN(AccelTableKind::Default, "Default",
    107                           "Default for input."),
    108                clEnumValN(AccelTableKind::Apple, "Apple", "Apple"),
    109                clEnumValN(AccelTableKind::Dwarf, "Dwarf", "DWARF")),
    110     cl::init(AccelTableKind::Default), cat(DsymCategory));
    111 
    112 static opt<unsigned> NumThreads(
    113     "num-threads",
    114     desc("Specifies the maximum number (n) of simultaneous threads to use\n"
    115          "when linking multiple architectures."),
    116     value_desc("n"), init(0), cat(DsymCategory));
    117 static alias NumThreadsA("j", desc("Alias for --num-threads"),
    118                          aliasopt(NumThreads));
    119 
    120 static opt<bool> Verbose("verbose", desc("Verbosity level"), init(false),
    121                          cat(DsymCategory));
    122 
    123 static opt<bool>
    124     NoOutput("no-output",
    125              desc("Do the link in memory, but do not emit the result file."),
    126              init(false), cat(DsymCategory));
    127 
    128 static opt<bool>
    129     NoTimestamp("no-swiftmodule-timestamp",
    130                 desc("Don't check timestamp for swiftmodule files."),
    131                 init(false), cat(DsymCategory));
    132 
    133 static list<std::string> ArchFlags(
    134     "arch",
    135     desc("Link DWARF debug information only for specified CPU architecture\n"
    136          "types. This option can be specified multiple times, once for each\n"
    137          "desired architecture. All CPU architectures will be linked by\n"
    138          "default."),
    139     value_desc("arch"), ZeroOrMore, cat(DsymCategory));
    140 
    141 static opt<bool>
    142     NoODR("no-odr",
    143           desc("Do not use ODR (One Definition Rule) for type uniquing."),
    144           init(false), cat(DsymCategory));
    145 
    146 static opt<bool> DumpDebugMap(
    147     "dump-debug-map",
    148     desc("Parse and dump the debug map to standard output. Not DWARF link "
    149          "will take place."),
    150     init(false), cat(DsymCategory));
    151 
    152 static opt<bool> InputIsYAMLDebugMap(
    153     "y", desc("Treat the input file is a YAML debug map rather than a binary."),
    154     init(false), cat(DsymCategory));
    155 
    156 static opt<bool> Verify("verify", desc("Verify the linked DWARF debug info."),
    157                         cat(DsymCategory));
    158 
    159 static opt<std::string>
    160     Toolchain("toolchain", desc("Embed toolchain information in dSYM bundle."),
    161               cat(DsymCategory));
    162 
    163 static opt<bool>
    164     PaperTrailWarnings("papertrail",
    165                        desc("Embed warnings in the linked DWARF debug info."),
    166                        cat(DsymCategory));
    167 
    168 static bool createPlistFile(llvm::StringRef Bin, llvm::StringRef BundleRoot) {
    169   if (NoOutput)
    170     return true;
    171 
    172   // Create plist file to write to.
    173   llvm::SmallString<128> InfoPlist(BundleRoot);
    174   llvm::sys::path::append(InfoPlist, "Contents/Info.plist");
    175   std::error_code EC;
    176   llvm::raw_fd_ostream PL(InfoPlist, EC, llvm::sys::fs::F_Text);
    177   if (EC) {
    178     WithColor::error() << "cannot create plist file " << InfoPlist << ": "
    179                        << EC.message() << '\n';
    180     return false;
    181   }
    182 
    183   CFBundleInfo BI = getBundleInfo(Bin);
    184 
    185   if (BI.IDStr.empty()) {
    186     llvm::StringRef BundleID = *llvm::sys::path::rbegin(BundleRoot);
    187     if (llvm::sys::path::extension(BundleRoot) == ".dSYM")
    188       BI.IDStr = llvm::sys::path::stem(BundleID);
    189     else
    190       BI.IDStr = BundleID;
    191   }
    192 
    193   // Print out information to the plist file.
    194   PL << "<?xml version=\"1.0\" encoding=\"UTF-8\"\?>\n"
    195      << "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" "
    196      << "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
    197      << "<plist version=\"1.0\">\n"
    198      << "\t<dict>\n"
    199      << "\t\t<key>CFBundleDevelopmentRegion</key>\n"
    200      << "\t\t<string>English</string>\n"
    201      << "\t\t<key>CFBundleIdentifier</key>\n"
    202      << "\t\t<string>com.apple.xcode.dsym." << BI.IDStr << "</string>\n"
    203      << "\t\t<key>CFBundleInfoDictionaryVersion</key>\n"
    204      << "\t\t<string>6.0</string>\n"
    205      << "\t\t<key>CFBundlePackageType</key>\n"
    206      << "\t\t<string>dSYM</string>\n"
    207      << "\t\t<key>CFBundleSignature</key>\n"
    208      << "\t\t<string>\?\?\?\?</string>\n";
    209 
    210   if (!BI.OmitShortVersion()) {
    211     PL << "\t\t<key>CFBundleShortVersionString</key>\n";
    212     PL << "\t\t<string>";
    213     printHTMLEscaped(BI.ShortVersionStr, PL);
    214     PL << "</string>\n";
    215   }
    216 
    217   PL << "\t\t<key>CFBundleVersion</key>\n";
    218   PL << "\t\t<string>";
    219   printHTMLEscaped(BI.VersionStr, PL);
    220   PL << "</string>\n";
    221 
    222   if (!Toolchain.empty()) {
    223     PL << "\t\t<key>Toolchain</key>\n";
    224     PL << "\t\t<string>";
    225     printHTMLEscaped(Toolchain, PL);
    226     PL << "</string>\n";
    227   }
    228 
    229   PL << "\t</dict>\n"
    230      << "</plist>\n";
    231 
    232   PL.close();
    233   return true;
    234 }
    235 
    236 static bool createBundleDir(llvm::StringRef BundleBase) {
    237   if (NoOutput)
    238     return true;
    239 
    240   llvm::SmallString<128> Bundle(BundleBase);
    241   llvm::sys::path::append(Bundle, "Contents", "Resources", "DWARF");
    242   if (std::error_code EC = create_directories(Bundle.str(), true,
    243                                               llvm::sys::fs::perms::all_all)) {
    244     WithColor::error() << "cannot create directory " << Bundle << ": "
    245                        << EC.message() << "\n";
    246     return false;
    247   }
    248   return true;
    249 }
    250 
    251 static bool verify(llvm::StringRef OutputFile, llvm::StringRef Arch) {
    252   if (OutputFile == "-") {
    253     WithColor::warning() << "verification skipped for " << Arch
    254                          << "because writing to stdout.\n";
    255     return true;
    256   }
    257 
    258   Expected<OwningBinary<Binary>> BinOrErr = createBinary(OutputFile);
    259   if (!BinOrErr) {
    260     errs() << OutputFile << ": " << toString(BinOrErr.takeError());
    261     return false;
    262   }
    263 
    264   Binary &Binary = *BinOrErr.get().getBinary();
    265   if (auto *Obj = dyn_cast<MachOObjectFile>(&Binary)) {
    266     raw_ostream &os = Verbose ? errs() : nulls();
    267     os << "Verifying DWARF for architecture: " << Arch << "\n";
    268     std::unique_ptr<DWARFContext> DICtx = DWARFContext::create(*Obj);
    269     DIDumpOptions DumpOpts;
    270     bool success = DICtx->verify(os, DumpOpts.noImplicitRecursion());
    271     if (!success)
    272       WithColor::error() << "verification failed for " << Arch << '\n';
    273     return success;
    274   }
    275 
    276   return false;
    277 }
    278 
    279 static std::string getOutputFileName(llvm::StringRef InputFile) {
    280   // When updating, do in place replacement.
    281   if (OutputFileOpt.empty() && Update)
    282     return InputFile;
    283 
    284   // If a flat dSYM has been requested, things are pretty simple.
    285   if (FlatOut) {
    286     if (OutputFileOpt.empty()) {
    287       if (InputFile == "-")
    288         return "a.out.dwarf";
    289       return (InputFile + ".dwarf").str();
    290     }
    291 
    292     return OutputFileOpt;
    293   }
    294 
    295   // We need to create/update a dSYM bundle.
    296   // A bundle hierarchy looks like this:
    297   //   <bundle name>.dSYM/
    298   //       Contents/
    299   //          Info.plist
    300   //          Resources/
    301   //             DWARF/
    302   //                <DWARF file(s)>
    303   std::string DwarfFile =
    304       InputFile == "-" ? llvm::StringRef("a.out") : InputFile;
    305   llvm::SmallString<128> BundleDir(OutputFileOpt);
    306   if (BundleDir.empty())
    307     BundleDir = DwarfFile + ".dSYM";
    308   if (!createBundleDir(BundleDir) || !createPlistFile(DwarfFile, BundleDir))
    309     return "";
    310 
    311   llvm::sys::path::append(BundleDir, "Contents", "Resources", "DWARF",
    312                           llvm::sys::path::filename(DwarfFile));
    313   return BundleDir.str();
    314 }
    315 
    316 /// Parses the command line options into the LinkOptions struct and performs
    317 /// some sanity checking. Returns an error in case the latter fails.
    318 static Expected<LinkOptions> getOptions() {
    319   LinkOptions Options;
    320 
    321   Options.Verbose = Verbose;
    322   Options.NoOutput = NoOutput;
    323   Options.NoODR = NoODR;
    324   Options.Minimize = Minimize;
    325   Options.Update = Update;
    326   Options.NoTimestamp = NoTimestamp;
    327   Options.PrependPath = OsoPrependPath;
    328   Options.TheAccelTableKind = AcceleratorTable;
    329 
    330   if (Assembly)
    331     Options.FileType = OutputFileType::Assembly;
    332 
    333   if (Options.Update && std::find(InputFiles.begin(), InputFiles.end(), "-") !=
    334                             InputFiles.end()) {
    335     // FIXME: We cannot use stdin for an update because stdin will be
    336     // consumed by the BinaryHolder during the debugmap parsing, and
    337     // then we will want to consume it again in DwarfLinker. If we
    338     // used a unique BinaryHolder object that could cache multiple
    339     // binaries this restriction would go away.
    340     return make_error<StringError>(
    341         "standard input cannot be used as input for a dSYM update.",
    342         inconvertibleErrorCode());
    343   }
    344 
    345   if (NumThreads == 0)
    346     Options.Threads = llvm::thread::hardware_concurrency();
    347   if (DumpDebugMap || Verbose)
    348     Options.Threads = 1;
    349 
    350   return Options;
    351 }
    352 
    353 /// Return a list of input files. This function has logic for dealing with the
    354 /// special case where we might have dSYM bundles as input. The function
    355 /// returns an error when the directory structure doesn't match that of a dSYM
    356 /// bundle.
    357 static Expected<std::vector<std::string>> getInputs(bool DsymAsInput) {
    358   if (!DsymAsInput)
    359     return InputFiles;
    360 
    361   // If we are updating, we might get dSYM bundles as input.
    362   std::vector<std::string> Inputs;
    363   for (const auto &Input : InputFiles) {
    364     if (!llvm::sys::fs::is_directory(Input)) {
    365       Inputs.push_back(Input);
    366       continue;
    367     }
    368 
    369     // Make sure that we're dealing with a dSYM bundle.
    370     SmallString<256> BundlePath(Input);
    371     sys::path::append(BundlePath, "Contents", "Resources", "DWARF");
    372     if (!llvm::sys::fs::is_directory(BundlePath))
    373       return make_error<StringError>(
    374           Input + " is a directory, but doesn't look like a dSYM bundle.",
    375           inconvertibleErrorCode());
    376 
    377     // Create a directory iterator to iterate over all the entries in the
    378     // bundle.
    379     std::error_code EC;
    380     llvm::sys::fs::directory_iterator DirIt(BundlePath, EC);
    381     llvm::sys::fs::directory_iterator DirEnd;
    382     if (EC)
    383       return errorCodeToError(EC);
    384 
    385     // Add each entry to the list of inputs.
    386     while (DirIt != DirEnd) {
    387       Inputs.push_back(DirIt->path());
    388       DirIt.increment(EC);
    389       if (EC)
    390         return errorCodeToError(EC);
    391     }
    392   }
    393   return Inputs;
    394 }
    395 
    396 int main(int argc, char **argv) {
    397   InitLLVM X(argc, argv);
    398 
    399   void *P = (void *)(intptr_t)getOutputFileName;
    400   std::string SDKPath = llvm::sys::fs::getMainExecutable(argv[0], P);
    401   SDKPath = llvm::sys::path::parent_path(SDKPath);
    402 
    403   HideUnrelatedOptions({&DsymCategory, &ColorCategory});
    404   llvm::cl::ParseCommandLineOptions(
    405       argc, argv,
    406       "manipulate archived DWARF debug symbol files.\n\n"
    407       "dsymutil links the DWARF debug information found in the object files\n"
    408       "for the executable <input file> by using debug symbols information\n"
    409       "contained in its symbol table.\n");
    410 
    411   if (Help) {
    412     PrintHelpMessage();
    413     return 0;
    414   }
    415 
    416   if (Version) {
    417     llvm::cl::PrintVersionMessage();
    418     return 0;
    419   }
    420 
    421   auto OptionsOrErr = getOptions();
    422   if (!OptionsOrErr) {
    423     WithColor::error() << toString(OptionsOrErr.takeError());
    424     return 1;
    425   }
    426 
    427   llvm::InitializeAllTargetInfos();
    428   llvm::InitializeAllTargetMCs();
    429   llvm::InitializeAllTargets();
    430   llvm::InitializeAllAsmPrinters();
    431 
    432   auto InputsOrErr = getInputs(OptionsOrErr->Update);
    433   if (!InputsOrErr) {
    434     WithColor::error() << toString(InputsOrErr.takeError()) << '\n';
    435     return 1;
    436   }
    437 
    438   if (!FlatOut && OutputFileOpt == "-") {
    439     WithColor::error() << "cannot emit to standard output without --flat\n";
    440     return 1;
    441   }
    442 
    443   if (InputsOrErr->size() > 1 && FlatOut && !OutputFileOpt.empty()) {
    444     WithColor::error() << "cannot use -o with multiple inputs in flat mode\n";
    445     return 1;
    446   }
    447 
    448   if (getenv("RC_DEBUG_OPTIONS"))
    449     PaperTrailWarnings = true;
    450 
    451   if (PaperTrailWarnings && InputIsYAMLDebugMap)
    452     WithColor::warning()
    453         << "Paper trail warnings are not supported for YAML input";
    454 
    455   for (const auto &Arch : ArchFlags)
    456     if (Arch != "*" && Arch != "all" &&
    457         !llvm::object::MachOObjectFile::isValidArch(Arch)) {
    458       WithColor::error() << "unsupported cpu architecture: '" << Arch << "'\n";
    459       return 1;
    460     }
    461 
    462   for (auto &InputFile : *InputsOrErr) {
    463     // Dump the symbol table for each input file and requested arch
    464     if (DumpStab) {
    465       if (!dumpStab(InputFile, ArchFlags, OsoPrependPath))
    466         return 1;
    467       continue;
    468     }
    469 
    470     auto DebugMapPtrsOrErr =
    471         parseDebugMap(InputFile, ArchFlags, OsoPrependPath, PaperTrailWarnings,
    472                       Verbose, InputIsYAMLDebugMap);
    473 
    474     if (auto EC = DebugMapPtrsOrErr.getError()) {
    475       WithColor::error() << "cannot parse the debug map for '" << InputFile
    476                          << "': " << EC.message() << '\n';
    477       return 1;
    478     }
    479 
    480     if (OptionsOrErr->Update) {
    481       // The debug map should be empty. Add one object file corresponding to
    482       // the input file.
    483       for (auto &Map : *DebugMapPtrsOrErr)
    484         Map->addDebugMapObject(InputFile,
    485                                llvm::sys::TimePoint<std::chrono::seconds>());
    486     }
    487 
    488     // Ensure that the debug map is not empty (anymore).
    489     if (DebugMapPtrsOrErr->empty()) {
    490       WithColor::error() << "no architecture to link\n";
    491       return 1;
    492     }
    493 
    494     // Shared a single binary holder for all the link steps.
    495     BinaryHolder BinHolder;
    496 
    497     NumThreads =
    498         std::min<unsigned>(OptionsOrErr->Threads, DebugMapPtrsOrErr->size());
    499     llvm::ThreadPool Threads(NumThreads);
    500 
    501     // If there is more than one link to execute, we need to generate
    502     // temporary files.
    503     bool NeedsTempFiles =
    504         !DumpDebugMap && (OutputFileOpt != "-") &&
    505         (DebugMapPtrsOrErr->size() != 1 || OptionsOrErr->Update);
    506 
    507     llvm::SmallVector<MachOUtils::ArchAndFile, 4> TempFiles;
    508     std::atomic_char AllOK(1);
    509     for (auto &Map : *DebugMapPtrsOrErr) {
    510       if (Verbose || DumpDebugMap)
    511         Map->print(llvm::outs());
    512 
    513       if (DumpDebugMap)
    514         continue;
    515 
    516       if (Map->begin() == Map->end())
    517         WithColor::warning()
    518             << "no debug symbols in executable (-arch "
    519             << MachOUtils::getArchName(Map->getTriple().getArchName()) << ")\n";
    520 
    521       // Using a std::shared_ptr rather than std::unique_ptr because move-only
    522       // types don't work with std::bind in the ThreadPool implementation.
    523       std::shared_ptr<raw_fd_ostream> OS;
    524       std::string OutputFile = getOutputFileName(InputFile);
    525       if (NeedsTempFiles) {
    526         TempFiles.emplace_back(Map->getTriple().getArchName().str());
    527 
    528         auto E = TempFiles.back().createTempFile();
    529         if (E) {
    530           errs() << toString(std::move(E));
    531           return 1;
    532         }
    533 
    534         auto &TempFile = *(TempFiles.back().File);
    535         OS = std::make_shared<raw_fd_ostream>(TempFile.FD,
    536                                               /*shouldClose*/ false);
    537         OutputFile = TempFile.TmpName;
    538       } else {
    539         std::error_code EC;
    540         OS = std::make_shared<raw_fd_ostream>(NoOutput ? "-" : OutputFile, EC,
    541                                               sys::fs::F_None);
    542         if (EC) {
    543           errs() << OutputFile << ": " << EC.message();
    544           return 1;
    545         }
    546       }
    547 
    548       auto LinkLambda = [&,
    549                          OutputFile](std::shared_ptr<raw_fd_ostream> Stream) {
    550         AllOK.fetch_and(linkDwarf(*Stream, BinHolder, *Map, *OptionsOrErr));
    551         Stream->flush();
    552         if (Verify && !NoOutput)
    553           AllOK.fetch_and(verify(OutputFile, Map->getTriple().getArchName()));
    554       };
    555 
    556       // FIXME: The DwarfLinker can have some very deep recursion that can max
    557       // out the (significantly smaller) stack when using threads. We don't
    558       // want this limitation when we only have a single thread.
    559       if (NumThreads == 1)
    560         LinkLambda(OS);
    561       else
    562         Threads.async(LinkLambda, OS);
    563     }
    564 
    565     Threads.wait();
    566 
    567     if (!AllOK)
    568       return 1;
    569 
    570     if (NeedsTempFiles &&
    571         !MachOUtils::generateUniversalBinary(
    572             TempFiles, getOutputFileName(InputFile), *OptionsOrErr, SDKPath))
    573       return 1;
    574   }
    575 
    576   return 0;
    577 }
    578