1 //===- dsymutil.cpp - Debug info dumping utility for llvm -----------------===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 // This program is a utility that aims to be a dropin replacement for Darwin's
11 //===----------------------------------------------------------------------===//
14 #include "BinaryHolder.h"
17 #include "LinkUtils.h"
18 #include "MachOUtils.h"
19 #include "llvm/ADT/SmallString.h"
20 #include "llvm/ADT/SmallVector.h"
21 #include "llvm/ADT/StringExtras.h"
22 #include "llvm/ADT/StringRef.h"
23 #include "llvm/ADT/Triple.h"
24 #include "llvm/DebugInfo/DIContext.h"
25 #include "llvm/DebugInfo/DWARF/DWARFContext.h"
26 #include "llvm/DebugInfo/DWARF/DWARFVerifier.h"
27 #include "llvm/Object/Binary.h"
28 #include "llvm/Object/MachO.h"
29 #include "llvm/Support/CommandLine.h"
30 #include "llvm/Support/FileSystem.h"
31 #include "llvm/Support/InitLLVM.h"
32 #include "llvm/Support/ManagedStatic.h"
33 #include "llvm/Support/Path.h"
34 #include "llvm/Support/TargetSelect.h"
35 #include "llvm/Support/ThreadPool.h"
36 #include "llvm/Support/WithColor.h"
37 #include "llvm/Support/raw_ostream.h"
38 #include "llvm/Support/thread.h"
43 #include <system_error>
46 using namespace llvm::cl
;
47 using namespace llvm::dsymutil
;
48 using namespace object
;
50 static OptionCategory
DsymCategory("Specific Options");
51 static opt
<bool> Help("h", desc("Alias for -help"), Hidden
);
52 static opt
<bool> Version("v", desc("Alias for -version"), Hidden
);
54 static list
<std::string
> InputFiles(Positional
, OneOrMore
,
55 desc("<input files>"), cat(DsymCategory
));
57 static opt
<std::string
>
59 desc("Specify the output file. default: <input file>.dwarf"),
60 value_desc("filename"), cat(DsymCategory
));
61 static alias
OutputFileOptA("out", desc("Alias for -o"),
62 aliasopt(OutputFileOpt
));
64 static opt
<std::string
> OsoPrependPath(
66 desc("Specify a directory to prepend to the paths of object files."),
67 value_desc("path"), cat(DsymCategory
));
69 static opt
<bool> Assembly(
71 desc("Output textual assembly instead of a binary dSYM companion file."),
72 init(false), cat(DsymCategory
), cl::Hidden
);
74 static opt
<bool> DumpStab(
76 desc("Dumps the symbol table found in executable or object file(s) and\n"
78 init(false), cat(DsymCategory
));
79 static alias
DumpStabA("s", desc("Alias for --symtab"), aliasopt(DumpStab
));
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
));
86 static opt
<bool> 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
));
97 static opt
<bool> 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
));
104 static opt
<std::string
> SymbolMap(
106 desc("Updates the existing dSYMs inplace using symbol map specified."),
107 value_desc("bcsymbolmap"), cat(DsymCategory
));
109 static cl::opt
<AccelTableKind
> AcceleratorTable(
110 "accelerator", cl::desc("Output accelerator tables."),
111 cl::values(clEnumValN(AccelTableKind::Default
, "Default",
112 "Default for input."),
113 clEnumValN(AccelTableKind::Apple
, "Apple", "Apple"),
114 clEnumValN(AccelTableKind::Dwarf
, "Dwarf", "DWARF")),
115 cl::init(AccelTableKind::Default
), cat(DsymCategory
));
117 static opt
<unsigned> NumThreads(
119 desc("Specifies the maximum number (n) of simultaneous threads to use\n"
120 "when linking multiple architectures."),
121 value_desc("n"), init(0), cat(DsymCategory
));
122 static alias
NumThreadsA("j", desc("Alias for --num-threads"),
123 aliasopt(NumThreads
));
125 static opt
<bool> Verbose("verbose", desc("Verbosity level"), init(false),
129 NoOutput("no-output",
130 desc("Do the link in memory, but do not emit the result file."),
131 init(false), cat(DsymCategory
));
134 NoTimestamp("no-swiftmodule-timestamp",
135 desc("Don't check timestamp for swiftmodule files."),
136 init(false), cat(DsymCategory
));
138 static list
<std::string
> ArchFlags(
140 desc("Link DWARF debug information only for specified CPU architecture\n"
141 "types. This option can be specified multiple times, once for each\n"
142 "desired architecture. All CPU architectures will be linked by\n"
144 value_desc("arch"), ZeroOrMore
, cat(DsymCategory
));
148 desc("Do not use ODR (One Definition Rule) for type uniquing."),
149 init(false), cat(DsymCategory
));
151 static opt
<bool> DumpDebugMap(
153 desc("Parse and dump the debug map to standard output. Not DWARF link "
155 init(false), cat(DsymCategory
));
157 static opt
<bool> InputIsYAMLDebugMap(
158 "y", desc("Treat the input file is a YAML debug map rather than a binary."),
159 init(false), cat(DsymCategory
));
161 static opt
<bool> Verify("verify", desc("Verify the linked DWARF debug info."),
164 static opt
<std::string
>
165 Toolchain("toolchain", desc("Embed toolchain information in dSYM bundle."),
169 PaperTrailWarnings("papertrail",
170 desc("Embed warnings in the linked DWARF debug info."),
173 static Error
createPlistFile(llvm::StringRef Bin
, llvm::StringRef BundleRoot
) {
175 return Error::success();
177 // Create plist file to write to.
178 llvm::SmallString
<128> InfoPlist(BundleRoot
);
179 llvm::sys::path::append(InfoPlist
, "Contents/Info.plist");
181 llvm::raw_fd_ostream
PL(InfoPlist
, EC
, llvm::sys::fs::OF_Text
);
183 return make_error
<StringError
>(
184 "cannot create Plist: " + toString(errorCodeToError(EC
)), EC
);
186 CFBundleInfo BI
= getBundleInfo(Bin
);
188 if (BI
.IDStr
.empty()) {
189 llvm::StringRef BundleID
= *llvm::sys::path::rbegin(BundleRoot
);
190 if (llvm::sys::path::extension(BundleRoot
) == ".dSYM")
191 BI
.IDStr
= llvm::sys::path::stem(BundleID
);
196 // Print out information to the plist file.
197 PL
<< "<?xml version=\"1.0\" encoding=\"UTF-8\"\?>\n"
198 << "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" "
199 << "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
200 << "<plist version=\"1.0\">\n"
202 << "\t\t<key>CFBundleDevelopmentRegion</key>\n"
203 << "\t\t<string>English</string>\n"
204 << "\t\t<key>CFBundleIdentifier</key>\n"
205 << "\t\t<string>com.apple.xcode.dsym." << BI
.IDStr
<< "</string>\n"
206 << "\t\t<key>CFBundleInfoDictionaryVersion</key>\n"
207 << "\t\t<string>6.0</string>\n"
208 << "\t\t<key>CFBundlePackageType</key>\n"
209 << "\t\t<string>dSYM</string>\n"
210 << "\t\t<key>CFBundleSignature</key>\n"
211 << "\t\t<string>\?\?\?\?</string>\n";
213 if (!BI
.OmitShortVersion()) {
214 PL
<< "\t\t<key>CFBundleShortVersionString</key>\n";
215 PL
<< "\t\t<string>";
216 printHTMLEscaped(BI
.ShortVersionStr
, PL
);
220 PL
<< "\t\t<key>CFBundleVersion</key>\n";
221 PL
<< "\t\t<string>";
222 printHTMLEscaped(BI
.VersionStr
, PL
);
225 if (!Toolchain
.empty()) {
226 PL
<< "\t\t<key>Toolchain</key>\n";
227 PL
<< "\t\t<string>";
228 printHTMLEscaped(Toolchain
, PL
);
236 return Error::success();
239 static Error
createBundleDir(llvm::StringRef BundleBase
) {
241 return Error::success();
243 llvm::SmallString
<128> Bundle(BundleBase
);
244 llvm::sys::path::append(Bundle
, "Contents", "Resources", "DWARF");
245 if (std::error_code EC
=
246 create_directories(Bundle
.str(), true, llvm::sys::fs::perms::all_all
))
247 return make_error
<StringError
>(
248 "cannot create bundle: " + toString(errorCodeToError(EC
)), EC
);
250 return Error::success();
253 static bool verify(llvm::StringRef OutputFile
, llvm::StringRef Arch
) {
254 if (OutputFile
== "-") {
255 WithColor::warning() << "verification skipped for " << Arch
256 << "because writing to stdout.\n";
260 Expected
<OwningBinary
<Binary
>> BinOrErr
= createBinary(OutputFile
);
262 WithColor::error() << OutputFile
<< ": " << toString(BinOrErr
.takeError());
266 Binary
&Binary
= *BinOrErr
.get().getBinary();
267 if (auto *Obj
= dyn_cast
<MachOObjectFile
>(&Binary
)) {
268 raw_ostream
&os
= Verbose
? errs() : nulls();
269 os
<< "Verifying DWARF for architecture: " << Arch
<< "\n";
270 std::unique_ptr
<DWARFContext
> DICtx
= DWARFContext::create(*Obj
);
271 DIDumpOptions DumpOpts
;
272 bool success
= DICtx
->verify(os
, DumpOpts
.noImplicitRecursion());
274 WithColor::error() << "verification failed for " << Arch
<< '\n';
282 struct OutputLocation
{
283 OutputLocation(std::string DWARFFile
,
284 llvm::Optional
<std::string
> ResourceDir
= {})
285 : DWARFFile(DWARFFile
), ResourceDir(ResourceDir
) {}
286 /// This method is a workaround for older compilers.
287 llvm::Optional
<std::string
> getResourceDir() const { return ResourceDir
; }
288 std::string DWARFFile
;
289 llvm::Optional
<std::string
> ResourceDir
;
293 static Expected
<OutputLocation
> getOutputFileName(llvm::StringRef InputFile
) {
294 if (OutputFileOpt
== "-")
295 return OutputLocation(OutputFileOpt
);
297 // When updating, do in place replacement.
298 if (OutputFileOpt
.empty() && (Update
|| !SymbolMap
.empty()))
299 return OutputLocation(InputFile
);
301 // If a flat dSYM has been requested, things are pretty simple.
303 if (OutputFileOpt
.empty()) {
304 if (InputFile
== "-")
305 return OutputLocation
{"a.out.dwarf", {}};
306 return OutputLocation((InputFile
+ ".dwarf").str());
309 return OutputLocation(OutputFileOpt
);
312 // We need to create/update a dSYM bundle.
313 // A bundle hierarchy looks like this:
314 // <bundle name>.dSYM/
320 std::string DwarfFile
=
321 InputFile
== "-" ? llvm::StringRef("a.out") : InputFile
;
322 llvm::SmallString
<128> Path(OutputFileOpt
);
324 Path
= DwarfFile
+ ".dSYM";
325 if (auto E
= createBundleDir(Path
))
327 if (auto E
= createPlistFile(DwarfFile
, Path
))
330 llvm::sys::path::append(Path
, "Contents", "Resources");
331 std::string ResourceDir
= Path
.str();
332 llvm::sys::path::append(Path
, "DWARF", llvm::sys::path::filename(DwarfFile
));
333 return OutputLocation(Path
.str(), ResourceDir
);
336 /// Parses the command line options into the LinkOptions struct and performs
337 /// some sanity checking. Returns an error in case the latter fails.
338 static Expected
<LinkOptions
> getOptions() {
341 Options
.Verbose
= Verbose
;
342 Options
.NoOutput
= NoOutput
;
343 Options
.NoODR
= NoODR
;
344 Options
.Minimize
= Minimize
;
345 Options
.Update
= Update
;
346 Options
.NoTimestamp
= NoTimestamp
;
347 Options
.PrependPath
= OsoPrependPath
;
348 Options
.TheAccelTableKind
= AcceleratorTable
;
350 if (!SymbolMap
.empty())
351 Options
.Update
= true;
354 Options
.FileType
= OutputFileType::Assembly
;
356 if (Options
.Update
&& std::find(InputFiles
.begin(), InputFiles
.end(), "-") !=
358 // FIXME: We cannot use stdin for an update because stdin will be
359 // consumed by the BinaryHolder during the debugmap parsing, and
360 // then we will want to consume it again in DwarfLinker. If we
361 // used a unique BinaryHolder object that could cache multiple
362 // binaries this restriction would go away.
363 return make_error
<StringError
>(
364 "standard input cannot be used as input for a dSYM update.",
365 inconvertibleErrorCode());
369 Options
.Threads
= llvm::thread::hardware_concurrency();
371 Options
.Threads
= NumThreads
;
372 if (DumpDebugMap
|| Verbose
)
378 /// Return a list of input files. This function has logic for dealing with the
379 /// special case where we might have dSYM bundles as input. The function
380 /// returns an error when the directory structure doesn't match that of a dSYM
382 static Expected
<std::vector
<std::string
>> getInputs(bool DsymAsInput
) {
386 // If we are updating, we might get dSYM bundles as input.
387 std::vector
<std::string
> Inputs
;
388 for (const auto &Input
: InputFiles
) {
389 if (!llvm::sys::fs::is_directory(Input
)) {
390 Inputs
.push_back(Input
);
394 // Make sure that we're dealing with a dSYM bundle.
395 SmallString
<256> BundlePath(Input
);
396 sys::path::append(BundlePath
, "Contents", "Resources", "DWARF");
397 if (!llvm::sys::fs::is_directory(BundlePath
))
398 return make_error
<StringError
>(
399 Input
+ " is a directory, but doesn't look like a dSYM bundle.",
400 inconvertibleErrorCode());
402 // Create a directory iterator to iterate over all the entries in the
405 llvm::sys::fs::directory_iterator
DirIt(BundlePath
, EC
);
406 llvm::sys::fs::directory_iterator DirEnd
;
408 return errorCodeToError(EC
);
410 // Add each entry to the list of inputs.
411 while (DirIt
!= DirEnd
) {
412 Inputs
.push_back(DirIt
->path());
415 return errorCodeToError(EC
);
421 int main(int argc
, char **argv
) {
422 InitLLVM
X(argc
, argv
);
424 void *P
= (void *)(intptr_t)getOutputFileName
;
425 std::string SDKPath
= llvm::sys::fs::getMainExecutable(argv
[0], P
);
426 SDKPath
= llvm::sys::path::parent_path(SDKPath
);
428 HideUnrelatedOptions({&DsymCategory
, &ColorCategory
});
429 llvm::cl::ParseCommandLineOptions(
431 "manipulate archived DWARF debug symbol files.\n\n"
432 "dsymutil links the DWARF debug information found in the object files\n"
433 "for the executable <input file> by using debug symbols information\n"
434 "contained in its symbol table.\n");
442 llvm::cl::PrintVersionMessage();
446 auto OptionsOrErr
= getOptions();
448 WithColor::error() << toString(OptionsOrErr
.takeError());
452 llvm::InitializeAllTargetInfos();
453 llvm::InitializeAllTargetMCs();
454 llvm::InitializeAllTargets();
455 llvm::InitializeAllAsmPrinters();
457 auto InputsOrErr
= getInputs(OptionsOrErr
->Update
);
459 WithColor::error() << toString(InputsOrErr
.takeError()) << '\n';
463 if (!FlatOut
&& OutputFileOpt
== "-") {
464 WithColor::error() << "cannot emit to standard output without --flat\n";
468 if (InputsOrErr
->size() > 1 && FlatOut
&& !OutputFileOpt
.empty()) {
469 WithColor::error() << "cannot use -o with multiple inputs in flat mode\n";
473 if (InputFiles
.size() > 1 && !SymbolMap
.empty() &&
474 !llvm::sys::fs::is_directory(SymbolMap
)) {
475 WithColor::error() << "when unobfuscating multiple files, --symbol-map "
476 << "needs to point to a directory.\n";
480 if (getenv("RC_DEBUG_OPTIONS"))
481 PaperTrailWarnings
= true;
483 if (PaperTrailWarnings
&& InputIsYAMLDebugMap
)
485 << "Paper trail warnings are not supported for YAML input";
487 for (const auto &Arch
: ArchFlags
)
488 if (Arch
!= "*" && Arch
!= "all" &&
489 !llvm::object::MachOObjectFile::isValidArch(Arch
)) {
490 WithColor::error() << "unsupported cpu architecture: '" << Arch
<< "'\n";
494 SymbolMapLoader
SymMapLoader(SymbolMap
);
496 for (auto &InputFile
: *InputsOrErr
) {
497 // Dump the symbol table for each input file and requested arch
499 if (!dumpStab(InputFile
, ArchFlags
, OsoPrependPath
))
504 auto DebugMapPtrsOrErr
=
505 parseDebugMap(InputFile
, ArchFlags
, OsoPrependPath
, PaperTrailWarnings
,
506 Verbose
, InputIsYAMLDebugMap
);
508 if (auto EC
= DebugMapPtrsOrErr
.getError()) {
509 WithColor::error() << "cannot parse the debug map for '" << InputFile
510 << "': " << EC
.message() << '\n';
514 if (OptionsOrErr
->Update
) {
515 // The debug map should be empty. Add one object file corresponding to
517 for (auto &Map
: *DebugMapPtrsOrErr
)
518 Map
->addDebugMapObject(InputFile
,
519 llvm::sys::TimePoint
<std::chrono::seconds
>());
522 // Ensure that the debug map is not empty (anymore).
523 if (DebugMapPtrsOrErr
->empty()) {
524 WithColor::error() << "no architecture to link\n";
528 // Shared a single binary holder for all the link steps.
529 BinaryHolder BinHolder
;
531 unsigned ThreadCount
=
532 std::min
<unsigned>(OptionsOrErr
->Threads
, DebugMapPtrsOrErr
->size());
533 llvm::ThreadPool
Threads(ThreadCount
);
535 // If there is more than one link to execute, we need to generate
537 bool NeedsTempFiles
=
538 !DumpDebugMap
&& (OutputFileOpt
!= "-") &&
539 (DebugMapPtrsOrErr
->size() != 1 || OptionsOrErr
->Update
);
541 llvm::SmallVector
<MachOUtils::ArchAndFile
, 4> TempFiles
;
542 std::atomic_char
AllOK(1);
543 for (auto &Map
: *DebugMapPtrsOrErr
) {
544 if (Verbose
|| DumpDebugMap
)
545 Map
->print(llvm::outs());
550 if (!SymbolMap
.empty())
551 OptionsOrErr
->Translator
= SymMapLoader
.Load(InputFile
, *Map
);
553 if (Map
->begin() == Map
->end())
555 << "no debug symbols in executable (-arch "
556 << MachOUtils::getArchName(Map
->getTriple().getArchName()) << ")\n";
558 // Using a std::shared_ptr rather than std::unique_ptr because move-only
559 // types don't work with std::bind in the ThreadPool implementation.
560 std::shared_ptr
<raw_fd_ostream
> OS
;
562 Expected
<OutputLocation
> OutputLocationOrErr
=
563 getOutputFileName(InputFile
);
564 if (!OutputLocationOrErr
) {
565 WithColor::error() << toString(OutputLocationOrErr
.takeError());
568 OptionsOrErr
->ResourceDir
= OutputLocationOrErr
->getResourceDir();
570 std::string OutputFile
= OutputLocationOrErr
->DWARFFile
;
571 if (NeedsTempFiles
) {
572 TempFiles
.emplace_back(Map
->getTriple().getArchName().str());
574 auto E
= TempFiles
.back().createTempFile();
576 WithColor::error() << toString(std::move(E
));
580 auto &TempFile
= *(TempFiles
.back().File
);
581 OS
= std::make_shared
<raw_fd_ostream
>(TempFile
.FD
,
582 /*shouldClose*/ false);
583 OutputFile
= TempFile
.TmpName
;
586 OS
= std::make_shared
<raw_fd_ostream
>(NoOutput
? "-" : OutputFile
, EC
,
589 WithColor::error() << OutputFile
<< ": " << EC
.message();
594 auto LinkLambda
= [&, OutputFile
](std::shared_ptr
<raw_fd_ostream
> Stream
,
595 LinkOptions Options
) {
597 linkDwarf(*Stream
, BinHolder
, *Map
, std::move(Options
)));
599 if (Verify
&& !NoOutput
)
600 AllOK
.fetch_and(verify(OutputFile
, Map
->getTriple().getArchName()));
603 // FIXME: The DwarfLinker can have some very deep recursion that can max
604 // out the (significantly smaller) stack when using threads. We don't
605 // want this limitation when we only have a single thread.
606 if (ThreadCount
== 1)
607 LinkLambda(OS
, *OptionsOrErr
);
609 Threads
.async(LinkLambda
, OS
, *OptionsOrErr
);
617 if (NeedsTempFiles
) {
618 Expected
<OutputLocation
> OutputLocationOrErr
= getOutputFileName(InputFile
);
619 if (!OutputLocationOrErr
) {
620 WithColor::error() << toString(OutputLocationOrErr
.takeError());
623 if (!MachOUtils::generateUniversalBinary(TempFiles
,
624 OutputLocationOrErr
->DWARFFile
,
625 *OptionsOrErr
, SDKPath
))