| //===-- dsymutil.cpp - Debug info dumping utility for llvm ----------------===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This program is a utility that aims to be a dropin replacement for |
| // Darwin's dsymutil. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "DebugMap.h" |
| #include "MachOUtils.h" |
| #include "dsymutil.h" |
| #include "llvm/Object/MachO.h" |
| #include "llvm/Support/FileSystem.h" |
| #include "llvm/Support/FileUtilities.h" |
| #include "llvm/Support/ManagedStatic.h" |
| #include "llvm/Support/Options.h" |
| #include "llvm/Support/PrettyStackTrace.h" |
| #include "llvm/Support/Signals.h" |
| #include "llvm/Support/raw_ostream.h" |
| #include "llvm/Support/TargetSelect.h" |
| #include <cstdint> |
| #include <string> |
| |
| using namespace llvm::dsymutil; |
| |
| namespace { |
| using namespace llvm::cl; |
| |
| OptionCategory DsymCategory("Specific Options"); |
| static opt<bool> Help("h", desc("Alias for -help"), Hidden); |
| static opt<bool> Version("v", desc("Alias for -version"), Hidden); |
| |
| static list<std::string> InputFiles(Positional, OneOrMore, |
| desc("<input files>"), cat(DsymCategory)); |
| |
| static opt<std::string> |
| OutputFileOpt("o", |
| desc("Specify the output file. default: <input file>.dwarf"), |
| value_desc("filename"), cat(DsymCategory)); |
| |
| static opt<std::string> OsoPrependPath( |
| "oso-prepend-path", |
| desc("Specify a directory to prepend to the paths of object files."), |
| value_desc("path"), cat(DsymCategory)); |
| |
| static opt<bool> DumpStab( |
| "symtab", |
| desc("Dumps the symbol table found in executable or object file(s) and\n" |
| "exits."), |
| init(false), cat(DsymCategory)); |
| static alias DumpStabA("s", desc("Alias for --symtab"), aliasopt(DumpStab)); |
| |
| static opt<bool> FlatOut("flat", |
| desc("Produce a flat dSYM file (not a bundle)."), |
| init(false), cat(DsymCategory)); |
| static alias FlatOutA("f", desc("Alias for --flat"), aliasopt(FlatOut)); |
| |
| static opt<bool> Verbose("verbose", desc("Verbosity level"), init(false), |
| cat(DsymCategory)); |
| |
| static opt<bool> |
| NoOutput("no-output", |
| desc("Do the link in memory, but do not emit the result file."), |
| init(false), cat(DsymCategory)); |
| |
| static list<std::string> ArchFlags( |
| "arch", |
| desc("Link DWARF debug information only for specified CPU architecture\n" |
| "types. This option can be specified multiple times, once for each\n" |
| "desired architecture. All cpu architectures will be linked by\n" |
| "default."), |
| ZeroOrMore, cat(DsymCategory)); |
| |
| static opt<bool> |
| NoODR("no-odr", |
| desc("Do not use ODR (One Definition Rule) for type uniquing."), |
| init(false), cat(DsymCategory)); |
| |
| static opt<bool> DumpDebugMap( |
| "dump-debug-map", |
| desc("Parse and dump the debug map to standard output. Not DWARF link " |
| "will take place."), |
| init(false), cat(DsymCategory)); |
| |
| static opt<bool> InputIsYAMLDebugMap( |
| "y", desc("Treat the input file is a YAML debug map rather than a binary."), |
| init(false), cat(DsymCategory)); |
| } |
| |
| static bool createPlistFile(llvm::StringRef BundleRoot) { |
| if (NoOutput) |
| return true; |
| |
| // Create plist file to write to. |
| llvm::SmallString<128> InfoPlist(BundleRoot); |
| llvm::sys::path::append(InfoPlist, "Contents/Info.plist"); |
| std::error_code EC; |
| llvm::raw_fd_ostream PL(InfoPlist, EC, llvm::sys::fs::F_Text); |
| if (EC) { |
| llvm::errs() << "error: cannot create plist file " << InfoPlist << ": " |
| << EC.message() << '\n'; |
| return false; |
| } |
| |
| // FIXME: Use CoreFoundation to get executable bundle info. Use |
| // dummy values for now. |
| std::string bundleVersionStr = "1", bundleShortVersionStr = "1.0", |
| bundleIDStr; |
| |
| llvm::StringRef BundleID = *llvm::sys::path::rbegin(BundleRoot); |
| if (llvm::sys::path::extension(BundleRoot) == ".dSYM") |
| bundleIDStr = llvm::sys::path::stem(BundleID); |
| else |
| bundleIDStr = BundleID; |
| |
| // Print out information to the plist file. |
| PL << "<?xml version=\"1.0\" encoding=\"UTF-8\"\?>\n" |
| << "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" " |
| << "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" |
| << "<plist version=\"1.0\">\n" |
| << "\t<dict>\n" |
| << "\t\t<key>CFBundleDevelopmentRegion</key>\n" |
| << "\t\t<string>English</string>\n" |
| << "\t\t<key>CFBundleIdentifier</key>\n" |
| << "\t\t<string>com.apple.xcode.dsym." << bundleIDStr << "</string>\n" |
| << "\t\t<key>CFBundleInfoDictionaryVersion</key>\n" |
| << "\t\t<string>6.0</string>\n" |
| << "\t\t<key>CFBundlePackageType</key>\n" |
| << "\t\t<string>dSYM</string>\n" |
| << "\t\t<key>CFBundleSignature</key>\n" |
| << "\t\t<string>\?\?\?\?</string>\n" |
| << "\t\t<key>CFBundleShortVersionString</key>\n" |
| << "\t\t<string>" << bundleShortVersionStr << "</string>\n" |
| << "\t\t<key>CFBundleVersion</key>\n" |
| << "\t\t<string>" << bundleVersionStr << "</string>\n" |
| << "\t</dict>\n" |
| << "</plist>\n"; |
| |
| PL.close(); |
| return true; |
| } |
| |
| static bool createBundleDir(llvm::StringRef BundleBase) { |
| if (NoOutput) |
| return true; |
| |
| llvm::SmallString<128> Bundle(BundleBase); |
| llvm::sys::path::append(Bundle, "Contents", "Resources", "DWARF"); |
| if (std::error_code EC = create_directories(Bundle.str(), true, |
| llvm::sys::fs::perms::all_all)) { |
| llvm::errs() << "error: cannot create directory " << Bundle << ": " |
| << EC.message() << "\n"; |
| return false; |
| } |
| return true; |
| } |
| |
| static std::error_code getUniqueFile(const llvm::Twine &Model, int &ResultFD, |
| llvm::SmallVectorImpl<char> &ResultPath) { |
| // If in NoOutput mode, use the createUniqueFile variant that |
| // doesn't open the file but still generates a somewhat unique |
| // name. In the real usage scenario, we'll want to ensure that the |
| // file is trully unique, and creating it is the only way to achieve |
| // that. |
| if (NoOutput) |
| return llvm::sys::fs::createUniqueFile(Model, ResultPath); |
| return llvm::sys::fs::createUniqueFile(Model, ResultFD, ResultPath); |
| } |
| |
| static std::string getOutputFileName(llvm::StringRef InputFile, |
| bool TempFile = false) { |
| if (TempFile) { |
| llvm::SmallString<128> TmpFile; |
| llvm::sys::path::system_temp_directory(true, TmpFile); |
| llvm::StringRef Basename = |
| OutputFileOpt.empty() ? InputFile : llvm::StringRef(OutputFileOpt); |
| llvm::sys::path::append(TmpFile, llvm::sys::path::filename(Basename)); |
| |
| int FD; |
| llvm::SmallString<128> UniqueFile; |
| if (auto EC = getUniqueFile(TmpFile + ".tmp%%%%%.dwarf", FD, UniqueFile)) { |
| llvm::errs() << "error: failed to create temporary outfile '" |
| << TmpFile << "': " << EC.message() << '\n'; |
| return ""; |
| } |
| llvm::sys::RemoveFileOnSignal(UniqueFile); |
| if (!NoOutput) { |
| // Close the file immediately. We know it is unique. It will be |
| // reopened and written to later. |
| llvm::raw_fd_ostream CloseImmediately(FD, true /* shouldClose */, true); |
| } |
| return UniqueFile.str(); |
| } |
| |
| if (FlatOut) { |
| // If a flat dSYM has been requested, things are pretty simple. |
| if (OutputFileOpt.empty()) { |
| if (InputFile == "-") |
| return "a.out.dwarf"; |
| return (InputFile + ".dwarf").str(); |
| } |
| |
| return OutputFileOpt; |
| } |
| |
| // We need to create/update a dSYM bundle. |
| // A bundle hierarchy looks like this: |
| // <bundle name>.dSYM/ |
| // Contents/ |
| // Info.plist |
| // Resources/ |
| // DWARF/ |
| // <DWARF file(s)> |
| std::string DwarfFile = |
| InputFile == "-" ? llvm::StringRef("a.out") : InputFile; |
| llvm::SmallString<128> BundleDir(OutputFileOpt); |
| if (BundleDir.empty()) |
| BundleDir = DwarfFile + ".dSYM"; |
| if (!createBundleDir(BundleDir) || !createPlistFile(BundleDir)) |
| return ""; |
| |
| llvm::sys::path::append(BundleDir, "Contents", "Resources", "DWARF", |
| llvm::sys::path::filename(DwarfFile)); |
| return BundleDir.str(); |
| } |
| |
| void llvm::dsymutil::exitDsymutil(int ExitStatus) { |
| // Cleanup temporary files. |
| llvm::sys::RunInterruptHandlers(); |
| exit(ExitStatus); |
| } |
| |
| int main(int argc, char **argv) { |
| llvm::sys::PrintStackTraceOnErrorSignal(argv[0]); |
| llvm::PrettyStackTraceProgram StackPrinter(argc, argv); |
| llvm::llvm_shutdown_obj Shutdown; |
| LinkOptions Options; |
| void *MainAddr = (void *)(intptr_t)&exitDsymutil; |
| std::string SDKPath = llvm::sys::fs::getMainExecutable(argv[0], MainAddr); |
| SDKPath = llvm::sys::path::parent_path(SDKPath); |
| |
| HideUnrelatedOptions(DsymCategory); |
| llvm::cl::ParseCommandLineOptions( |
| argc, argv, |
| "manipulate archived DWARF debug symbol files.\n\n" |
| "dsymutil links the DWARF debug information found in the object files\n" |
| "for the executable <input file> by using debug symbols information\n" |
| "contained in its symbol table.\n"); |
| |
| if (Help) |
| PrintHelpMessage(); |
| |
| if (Version) { |
| llvm::cl::PrintVersionMessage(); |
| return 0; |
| } |
| |
| Options.Verbose = Verbose; |
| Options.NoOutput = NoOutput; |
| Options.NoODR = NoODR; |
| Options.PrependPath = OsoPrependPath; |
| |
| llvm::InitializeAllTargetInfos(); |
| llvm::InitializeAllTargetMCs(); |
| llvm::InitializeAllTargets(); |
| llvm::InitializeAllAsmPrinters(); |
| |
| if (!FlatOut && OutputFileOpt == "-") { |
| llvm::errs() << "error: cannot emit to standard output without --flat\n"; |
| return 1; |
| } |
| |
| if (InputFiles.size() > 1 && FlatOut && !OutputFileOpt.empty()) { |
| llvm::errs() << "error: cannot use -o with multiple inputs in flat mode\n"; |
| return 1; |
| } |
| |
| for (const auto &Arch : ArchFlags) |
| if (Arch != "*" && Arch != "all" && |
| !llvm::object::MachOObjectFile::isValidArch(Arch)) { |
| llvm::errs() << "error: Unsupported cpu architecture: '" << Arch << "'\n"; |
| exitDsymutil(1); |
| } |
| |
| for (auto &InputFile : InputFiles) { |
| // Dump the symbol table for each input file and requested arch |
| if (DumpStab) { |
| if (!dumpStab(InputFile, ArchFlags, OsoPrependPath)) |
| exitDsymutil(1); |
| continue; |
| } |
| |
| auto DebugMapPtrsOrErr = parseDebugMap(InputFile, ArchFlags, OsoPrependPath, |
| Verbose, InputIsYAMLDebugMap); |
| |
| if (auto EC = DebugMapPtrsOrErr.getError()) { |
| llvm::errs() << "error: cannot parse the debug map for \"" << InputFile |
| << "\": " << EC.message() << '\n'; |
| exitDsymutil(1); |
| } |
| |
| if (DebugMapPtrsOrErr->empty()) { |
| llvm::errs() << "error: no architecture to link\n"; |
| exitDsymutil(1); |
| } |
| |
| // If there is more than one link to execute, we need to generate |
| // temporary files. |
| bool NeedsTempFiles = !DumpDebugMap && (*DebugMapPtrsOrErr).size() != 1; |
| llvm::SmallVector<MachOUtils::ArchAndFilename, 4> TempFiles; |
| for (auto &Map : *DebugMapPtrsOrErr) { |
| if (Verbose || DumpDebugMap) |
| Map->print(llvm::outs()); |
| |
| if (DumpDebugMap) |
| continue; |
| |
| if (Map->begin() == Map->end()) |
| llvm::errs() << "warning: no debug symbols in executable (-arch " |
| << MachOUtils::getArchName(Map->getTriple().getArchName()) |
| << ")\n"; |
| |
| std::string OutputFile = getOutputFileName(InputFile, NeedsTempFiles); |
| if (OutputFile.empty() || !linkDwarf(OutputFile, *Map, Options)) |
| exitDsymutil(1); |
| |
| if (NeedsTempFiles) |
| TempFiles.emplace_back(Map->getTriple().getArchName().str(), |
| OutputFile); |
| } |
| |
| if (NeedsTempFiles && |
| !MachOUtils::generateUniversalBinary( |
| TempFiles, getOutputFileName(InputFile), Options, SDKPath)) |
| exitDsymutil(1); |
| } |
| |
| exitDsymutil(0); |
| } |