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::SmallString<128> TmpFile;
    180     llvm::sys::path::system_temp_directory(true, TmpFile);
    181     llvm::StringRef Basename =
    182         OutputFileOpt.empty() ? InputFile : llvm::StringRef(OutputFileOpt);
    183     llvm::sys::path::append(TmpFile, llvm::sys::path::filename(Basename));
    184 
    185     int FD;
    186     llvm::SmallString<128> UniqueFile;
    187     if (auto EC = getUniqueFile(TmpFile + ".tmp%%%%%.dwarf", FD, UniqueFile)) {
    188       llvm::errs() << "error: failed to create temporary outfile '"
    189                    << TmpFile << "': " << EC.message() << '\n';
    190       return "";
    191     }
    192     llvm::sys::RemoveFileOnSignal(UniqueFile);
    193     if (!NoOutput) {
    194       // Close the file immediately. We know it is unique. It will be
    195       // reopened and written to later.
    196       llvm::raw_fd_ostream CloseImmediately(FD, true /* shouldClose */, true);
    197     }
    198     return UniqueFile.str();
    199   }
    200 
    201   if (FlatOut) {
    202     // If a flat dSYM has been requested, things are pretty simple.
    203     if (OutputFileOpt.empty()) {
    204       if (InputFile == "-")
    205         return "a.out.dwarf";
    206       return (InputFile + ".dwarf").str();
    207     }
    208 
    209     return OutputFileOpt;
    210   }
    211 
    212   // We need to create/update a dSYM bundle.
    213   // A bundle hierarchy looks like this:
    214   //   <bundle name>.dSYM/
    215   //       Contents/
    216   //          Info.plist
    217   //          Resources/
    218   //             DWARF/
    219   //                <DWARF file(s)>
    220   std::string DwarfFile =
    221       InputFile == "-" ? llvm::StringRef("a.out") : InputFile;
    222   llvm::SmallString<128> BundleDir(OutputFileOpt);
    223   if (BundleDir.empty())
    224     BundleDir = DwarfFile + ".dSYM";
    225   if (!createBundleDir(BundleDir) || !createPlistFile(BundleDir))
    226     return "";
    227 
    228   llvm::sys::path::append(BundleDir, "Contents", "Resources", "DWARF",
    229                           llvm::sys::path::filename(DwarfFile));
    230   return BundleDir.str();
    231 }
    232 
    233 void llvm::dsymutil::exitDsymutil(int ExitStatus) {
    234   // Cleanup temporary files.
    235   llvm::sys::RunInterruptHandlers();
    236   exit(ExitStatus);
    237 }
    238 
    239 int main(int argc, char **argv) {
    240   llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);
    241   llvm::PrettyStackTraceProgram StackPrinter(argc, argv);
    242   llvm::llvm_shutdown_obj Shutdown;
    243   LinkOptions Options;
    244   void *MainAddr = (void *)(intptr_t)&exitDsymutil;
    245   std::string SDKPath = llvm::sys::fs::getMainExecutable(argv[0], MainAddr);
    246   SDKPath = llvm::sys::path::parent_path(SDKPath);
    247 
    248   HideUnrelatedOptions(DsymCategory);
    249   llvm::cl::ParseCommandLineOptions(
    250       argc, argv,
    251       "manipulate archived DWARF debug symbol files.\n\n"
    252       "dsymutil links the DWARF debug information found in the object files\n"
    253       "for the executable <input file> by using debug symbols information\n"
    254       "contained in its symbol table.\n");
    255 
    256   if (Help)
    257     PrintHelpMessage();
    258 
    259   if (Version) {
    260     llvm::cl::PrintVersionMessage();
    261     return 0;
    262   }
    263 
    264   Options.Verbose = Verbose;
    265   Options.NoOutput = NoOutput;
    266   Options.NoODR = NoODR;
    267   Options.PrependPath = OsoPrependPath;
    268 
    269   llvm::InitializeAllTargetInfos();
    270   llvm::InitializeAllTargetMCs();
    271   llvm::InitializeAllTargets();
    272   llvm::InitializeAllAsmPrinters();
    273 
    274   if (!FlatOut && OutputFileOpt == "-") {
    275     llvm::errs() << "error: cannot emit to standard output without --flat\n";
    276     return 1;
    277   }
    278 
    279   if (InputFiles.size() > 1 && FlatOut && !OutputFileOpt.empty()) {
    280     llvm::errs() << "error: cannot use -o with multiple inputs in flat mode\n";
    281     return 1;
    282   }
    283 
    284   for (const auto &Arch : ArchFlags)
    285     if (Arch != "*" && Arch != "all" &&
    286         !llvm::object::MachOObjectFile::isValidArch(Arch)) {
    287       llvm::errs() << "error: Unsupported cpu architecture: '" << Arch << "'\n";
    288       exitDsymutil(1);
    289     }
    290 
    291   for (auto &InputFile : InputFiles) {
    292     // Dump the symbol table for each input file and requested arch
    293     if (DumpStab) {
    294       if (!dumpStab(InputFile, ArchFlags, OsoPrependPath))
    295         exitDsymutil(1);
    296       continue;
    297     }
    298 
    299     auto DebugMapPtrsOrErr = parseDebugMap(InputFile, ArchFlags, OsoPrependPath,
    300                                            Verbose, InputIsYAMLDebugMap);
    301 
    302     if (auto EC = DebugMapPtrsOrErr.getError()) {
    303       llvm::errs() << "error: cannot parse the debug map for \"" << InputFile
    304                    << "\": " << EC.message() << '\n';
    305       exitDsymutil(1);
    306     }
    307 
    308     if (DebugMapPtrsOrErr->empty()) {
    309       llvm::errs() << "error: no architecture to link\n";
    310       exitDsymutil(1);
    311     }
    312 
    313     // If there is more than one link to execute, we need to generate
    314     // temporary files.
    315     bool NeedsTempFiles = !DumpDebugMap && (*DebugMapPtrsOrErr).size() != 1;
    316     llvm::SmallVector<MachOUtils::ArchAndFilename, 4> TempFiles;
    317     for (auto &Map : *DebugMapPtrsOrErr) {
    318       if (Verbose || DumpDebugMap)
    319         Map->print(llvm::outs());
    320 
    321       if (DumpDebugMap)
    322         continue;
    323 
    324       if (Map->begin() == Map->end())
    325         llvm::errs() << "warning: no debug symbols in executable (-arch "
    326                      << MachOUtils::getArchName(Map->getTriple().getArchName())
    327                      << ")\n";
    328 
    329       std::string OutputFile = getOutputFileName(InputFile, NeedsTempFiles);
    330       if (OutputFile.empty() || !linkDwarf(OutputFile, *Map, Options))
    331         exitDsymutil(1);
    332 
    333       if (NeedsTempFiles)
    334         TempFiles.emplace_back(Map->getTriple().getArchName().str(),
    335                                OutputFile);
    336     }
    337 
    338     if (NeedsTempFiles &&
    339         !MachOUtils::generateUniversalBinary(
    340             TempFiles, getOutputFileName(InputFile), Options, SDKPath))
    341       exitDsymutil(1);
    342   }
    343 
    344   exitDsymutil(0);
    345 }
    346