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