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 "DebugMap.h"
     16 #include "MachOUtils.h"
     17 #include "dsymutil.h"
     18 #include "llvm/Object/MachO.h"
     19 #include "llvm/Support/FileSystem.h"
     20 #include "llvm/Support/FileUtilities.h"
     21 #include "llvm/Support/ManagedStatic.h"
     22 #include "llvm/Support/Options.h"
     23 #include "llvm/Support/PrettyStackTrace.h"
     24 #include "llvm/Support/Signals.h"
     25 #include "llvm/Support/raw_ostream.h"
     26 #include "llvm/Support/TargetSelect.h"
     27 #include <cstdint>
     28 #include <string>
     29 
     30 using namespace llvm::dsymutil;
     31 
     32 namespace {
     33 using namespace llvm::cl;
     34 
     35 OptionCategory DsymCategory("Specific Options");
     36 static opt<bool> Help("h", desc("Alias for -help"), Hidden);
     37 static opt<bool> Version("v", desc("Alias for -version"), Hidden);
     38 
     39 static list<std::string> InputFiles(Positional, OneOrMore,
     40                                     desc("<input files>"), cat(DsymCategory));
     41 
     42 static opt<std::string>
     43     OutputFileOpt("o",
     44                   desc("Specify the output file. default: <input file>.dwarf"),
     45                   value_desc("filename"), cat(DsymCategory));
     46 
     47 static opt<std::string> OsoPrependPath(
     48     "oso-prepend-path",
     49     desc("Specify a directory to prepend to the paths of object files."),
     50     value_desc("path"), cat(DsymCategory));
     51 
     52 static opt<bool> DumpStab(
     53     "symtab",
     54     desc("Dumps the symbol table found in executable or object file(s) and\n"
     55          "exits."),
     56     init(false), cat(DsymCategory));
     57 static alias DumpStabA("s", desc("Alias for --symtab"), aliasopt(DumpStab));
     58 
     59 static opt<bool> FlatOut("flat",
     60                          desc("Produce a flat dSYM file (not a bundle)."),
     61                          init(false), cat(DsymCategory));
     62 static alias FlatOutA("f", desc("Alias for --flat"), aliasopt(FlatOut));
     63 
     64 static opt<bool> Verbose("verbose", desc("Verbosity level"), init(false),
     65                          cat(DsymCategory));
     66 
     67 static opt<bool>
     68     NoOutput("no-output",
     69              desc("Do the link in memory, but do not emit the result file."),
     70              init(false), cat(DsymCategory));
     71 
     72 static list<std::string> ArchFlags(
     73     "arch",
     74     desc("Link DWARF debug information only for specified CPU architecture\n"
     75          "types. This option can be specified multiple times, once for each\n"
     76          "desired architecture.  All cpu architectures will be linked by\n"
     77          "default."),
     78     ZeroOrMore, cat(DsymCategory));
     79 
     80 static opt<bool>
     81     NoODR("no-odr",
     82           desc("Do not use ODR (One Definition Rule) for type uniquing."),
     83           init(false), cat(DsymCategory));
     84 
     85 static opt<bool> DumpDebugMap(
     86     "dump-debug-map",
     87     desc("Parse and dump the debug map to standard output. Not DWARF link "
     88          "will take place."),
     89     init(false), cat(DsymCategory));
     90 
     91 static opt<bool> InputIsYAMLDebugMap(
     92     "y", desc("Treat the input file is a YAML debug map rather than a binary."),
     93     init(false), cat(DsymCategory));
     94 }
     95 
     96 static bool createPlistFile(llvm::StringRef BundleRoot) {
     97   if (NoOutput)
     98     return true;
     99 
    100   // Create plist file to write to.
    101   llvm::SmallString<128> InfoPlist(BundleRoot);
    102   llvm::sys::path::append(InfoPlist, "Contents/Info.plist");
    103   std::error_code EC;
    104   llvm::raw_fd_ostream PL(InfoPlist, EC, llvm::sys::fs::F_Text);
    105   if (EC) {
    106     llvm::errs() << "error: cannot create plist file " << InfoPlist << ": "
    107                  << EC.message() << '\n';
    108     return false;
    109   }
    110 
    111   // FIXME: Use CoreFoundation to get executable bundle info. Use
    112   // dummy values for now.
    113   std::string bundleVersionStr = "1", bundleShortVersionStr = "1.0",
    114               bundleIDStr;
    115 
    116   llvm::StringRef BundleID = *llvm::sys::path::rbegin(BundleRoot);
    117   if (llvm::sys::path::extension(BundleRoot) == ".dSYM")
    118     bundleIDStr = llvm::sys::path::stem(BundleID);
    119   else
    120     bundleIDStr = BundleID;
    121 
    122   // Print out information to the plist file.
    123   PL << "<?xml version=\"1.0\" encoding=\"UTF-8\"\?>\n"
    124      << "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" "
    125      << "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
    126      << "<plist version=\"1.0\">\n"
    127      << "\t<dict>\n"
    128      << "\t\t<key>CFBundleDevelopmentRegion</key>\n"
    129      << "\t\t<string>English</string>\n"
    130      << "\t\t<key>CFBundleIdentifier</key>\n"
    131      << "\t\t<string>com.apple.xcode.dsym." << bundleIDStr << "</string>\n"
    132      << "\t\t<key>CFBundleInfoDictionaryVersion</key>\n"
    133      << "\t\t<string>6.0</string>\n"
    134      << "\t\t<key>CFBundlePackageType</key>\n"
    135      << "\t\t<string>dSYM</string>\n"
    136      << "\t\t<key>CFBundleSignature</key>\n"
    137      << "\t\t<string>\?\?\?\?</string>\n"
    138      << "\t\t<key>CFBundleShortVersionString</key>\n"
    139      << "\t\t<string>" << bundleShortVersionStr << "</string>\n"
    140      << "\t\t<key>CFBundleVersion</key>\n"
    141      << "\t\t<string>" << bundleVersionStr << "</string>\n"
    142      << "\t</dict>\n"
    143      << "</plist>\n";
    144 
    145   PL.close();
    146   return true;
    147 }
    148 
    149 static bool createBundleDir(llvm::StringRef BundleBase) {
    150   if (NoOutput)
    151     return true;
    152 
    153   llvm::SmallString<128> Bundle(BundleBase);
    154   llvm::sys::path::append(Bundle, "Contents", "Resources", "DWARF");
    155   if (std::error_code EC = create_directories(Bundle.str(), true,
    156                                               llvm::sys::fs::perms::all_all)) {
    157     llvm::errs() << "error: cannot create directory " << Bundle << ": "
    158                  << EC.message() << "\n";
    159     return false;
    160   }
    161   return true;
    162 }
    163 
    164 static std::error_code getUniqueFile(const llvm::Twine &Model, int &ResultFD,
    165                                      llvm::SmallVectorImpl<char> &ResultPath) {
    166   // If in NoOutput mode, use the createUniqueFile variant that
    167   // doesn't open the file but still generates a somewhat unique
    168   // name. In the real usage scenario, we'll want to ensure that the
    169   // file is trully unique, and creating it is the only way to achieve
    170   // that.
    171   if (NoOutput)
    172     return llvm::sys::fs::createUniqueFile(Model, ResultPath);
    173   return llvm::sys::fs::createUniqueFile(Model, ResultFD, ResultPath);
    174 }
    175 
    176 static std::string getOutputFileName(llvm::StringRef InputFile,
    177                                      bool TempFile = false) {
    178   if (TempFile) {
    179     llvm::StringRef Basename =
    180         OutputFileOpt.empty() ? InputFile : llvm::StringRef(OutputFileOpt);
    181     llvm::Twine OutputFile = Basename + ".tmp%%%%%%.dwarf";
    182     int FD;
    183     llvm::SmallString<128> UniqueFile;
    184     if (auto EC = getUniqueFile(OutputFile, FD, UniqueFile)) {
    185       llvm::errs() << "error: failed to create temporary outfile '"
    186                    << OutputFile << "': " << EC.message() << '\n';
    187       return "";
    188     }
    189     llvm::sys::RemoveFileOnSignal(UniqueFile);
    190     if (!NoOutput) {
    191       // Close the file immediately. We know it is unique. It will be
    192       // reopened and written to later.
    193       llvm::raw_fd_ostream CloseImmediately(FD, true /* shouldClose */, true);
    194     }
    195     return UniqueFile.str();
    196   }
    197 
    198   if (FlatOut) {
    199     // If a flat dSYM has been requested, things are pretty simple.
    200     if (OutputFileOpt.empty()) {
    201       if (InputFile == "-")
    202         return "a.out.dwarf";
    203       return (InputFile + ".dwarf").str();
    204     }
    205 
    206     return OutputFileOpt;
    207   }
    208 
    209   // We need to create/update a dSYM bundle.
    210   // A bundle hierarchy looks like this:
    211   //   <bundle name>.dSYM/
    212   //       Contents/
    213   //          Info.plist
    214   //          Resources/
    215   //             DWARF/
    216   //                <DWARF file(s)>
    217   std::string DwarfFile =
    218       InputFile == "-" ? llvm::StringRef("a.out") : InputFile;
    219   llvm::SmallString<128> BundleDir(OutputFileOpt);
    220   if (BundleDir.empty())
    221     BundleDir = DwarfFile + ".dSYM";
    222   if (!createBundleDir(BundleDir) || !createPlistFile(BundleDir))
    223     return "";
    224 
    225   llvm::sys::path::append(BundleDir, "Contents", "Resources", "DWARF",
    226                           llvm::sys::path::filename(DwarfFile));
    227   return BundleDir.str();
    228 }
    229 
    230 void llvm::dsymutil::exitDsymutil(int ExitStatus) {
    231   // Cleanup temporary files.
    232   llvm::sys::RunInterruptHandlers();
    233   exit(ExitStatus);
    234 }
    235 
    236 int main(int argc, char **argv) {
    237   llvm::sys::PrintStackTraceOnErrorSignal();
    238   llvm::PrettyStackTraceProgram StackPrinter(argc, argv);
    239   llvm::llvm_shutdown_obj Shutdown;
    240   LinkOptions Options;
    241   void *MainAddr = (void *)(intptr_t)&exitDsymutil;
    242   std::string SDKPath = llvm::sys::fs::getMainExecutable(argv[0], MainAddr);
    243   SDKPath = llvm::sys::path::parent_path(SDKPath);
    244 
    245   HideUnrelatedOptions(DsymCategory);
    246   llvm::cl::ParseCommandLineOptions(
    247       argc, argv,
    248       "manipulate archived DWARF debug symbol files.\n\n"
    249       "dsymutil links the DWARF debug information found in the object files\n"
    250       "for the executable <input file> by using debug symbols information\n"
    251       "contained in its symbol table.\n");
    252 
    253   if (Help)
    254     PrintHelpMessage();
    255 
    256   if (Version) {
    257     llvm::cl::PrintVersionMessage();
    258     return 0;
    259   }
    260 
    261   Options.Verbose = Verbose;
    262   Options.NoOutput = NoOutput;
    263   Options.NoODR = NoODR;
    264   Options.PrependPath = OsoPrependPath;
    265 
    266   llvm::InitializeAllTargetInfos();
    267   llvm::InitializeAllTargetMCs();
    268   llvm::InitializeAllTargets();
    269   llvm::InitializeAllAsmPrinters();
    270 
    271   if (!FlatOut && OutputFileOpt == "-") {
    272     llvm::errs() << "error: cannot emit to standard output without --flat\n";
    273     return 1;
    274   }
    275 
    276   if (InputFiles.size() > 1 && FlatOut && !OutputFileOpt.empty()) {
    277     llvm::errs() << "error: cannot use -o with multiple inputs in flat mode\n";
    278     return 1;
    279   }
    280 
    281   for (const auto &Arch : ArchFlags)
    282     if (Arch != "*" && Arch != "all" &&
    283         !llvm::object::MachOObjectFile::isValidArch(Arch)) {
    284       llvm::errs() << "error: Unsupported cpu architecture: '" << Arch << "'\n";
    285       exitDsymutil(1);
    286     }
    287 
    288   for (auto &InputFile : InputFiles) {
    289     // Dump the symbol table for each input file and requested arch
    290     if (DumpStab) {
    291       if (!dumpStab(InputFile, ArchFlags, OsoPrependPath))
    292         exitDsymutil(1);
    293       continue;
    294     }
    295 
    296     auto DebugMapPtrsOrErr = parseDebugMap(InputFile, ArchFlags, OsoPrependPath,
    297                                            Verbose, InputIsYAMLDebugMap);
    298 
    299     if (auto EC = DebugMapPtrsOrErr.getError()) {
    300       llvm::errs() << "error: cannot parse the debug map for \"" << InputFile
    301                    << "\": " << EC.message() << '\n';
    302       exitDsymutil(1);
    303     }
    304 
    305     if (DebugMapPtrsOrErr->empty()) {
    306       llvm::errs() << "error: no architecture to link\n";
    307       exitDsymutil(1);
    308     }
    309 
    310     // If there is more than one link to execute, we need to generate
    311     // temporary files.
    312     bool NeedsTempFiles = !DumpDebugMap && (*DebugMapPtrsOrErr).size() != 1;
    313     llvm::SmallVector<MachOUtils::ArchAndFilename, 4> TempFiles;
    314     for (auto &Map : *DebugMapPtrsOrErr) {
    315       if (Verbose || DumpDebugMap)
    316         Map->print(llvm::outs());
    317 
    318       if (DumpDebugMap)
    319         continue;
    320 
    321       if (Map->begin() == Map->end())
    322         llvm::errs() << "warning: no debug symbols in executable (-arch "
    323                      << MachOUtils::getArchName(Map->getTriple().getArchName())
    324                      << ")\n";
    325 
    326       std::string OutputFile = getOutputFileName(InputFile, NeedsTempFiles);
    327       if (OutputFile.empty() || !linkDwarf(OutputFile, *Map, Options))
    328         exitDsymutil(1);
    329 
    330       if (NeedsTempFiles)
    331         TempFiles.emplace_back(Map->getTriple().getArchName().str(),
    332                                OutputFile);
    333     }
    334 
    335     if (NeedsTempFiles &&
    336         !MachOUtils::generateUniversalBinary(
    337             TempFiles, getOutputFileName(InputFile), Options, SDKPath))
    338       exitDsymutil(1);
    339   }
    340 
    341   exitDsymutil(0);
    342 }
    343