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/StringSwitch.h"
24 #include "llvm/ADT/Triple.h"
25 #include "llvm/DebugInfo/DIContext.h"
26 #include "llvm/DebugInfo/DWARF/DWARFContext.h"
27 #include "llvm/DebugInfo/DWARF/DWARFVerifier.h"
28 #include "llvm/Object/Binary.h"
29 #include "llvm/Object/MachO.h"
30 #include "llvm/Option/Arg.h"
31 #include "llvm/Option/ArgList.h"
32 #include "llvm/Option/Option.h"
33 #include "llvm/Support/CommandLine.h"
34 #include "llvm/Support/FileSystem.h"
35 #include "llvm/Support/InitLLVM.h"
36 #include "llvm/Support/ManagedStatic.h"
37 #include "llvm/Support/Path.h"
38 #include "llvm/Support/TargetSelect.h"
39 #include "llvm/Support/ThreadPool.h"
40 #include "llvm/Support/WithColor.h"
41 #include "llvm/Support/raw_ostream.h"
42 #include "llvm/Support/thread.h"
47 #include <system_error>
50 using namespace llvm::dsymutil
;
51 using namespace object
;
55 OPT_INVALID
= 0, // This is not an option ID.
56 #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \
57 HELPTEXT, METAVAR, VALUES) \
59 #include "Options.inc"
63 #define PREFIX(NAME, VALUE) const char *const NAME[] = VALUE;
64 #include "Options.inc"
67 const opt::OptTable::Info InfoTable
[] = {
68 #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \
69 HELPTEXT, METAVAR, VALUES) \
71 PREFIX, NAME, HELPTEXT, \
72 METAVAR, OPT_##ID, opt::Option::KIND##Class, \
73 PARAM, FLAGS, OPT_##GROUP, \
74 OPT_##ALIAS, ALIASARGS, VALUES},
75 #include "Options.inc"
79 class DsymutilOptTable
: public opt::OptTable
{
81 DsymutilOptTable() : OptTable(InfoTable
) {}
85 struct DsymutilOptions
{
86 bool DumpDebugMap
= false;
87 bool DumpStab
= false;
89 bool InputIsYAMLDebugMap
= false;
90 bool PaperTrailWarnings
= false;
92 std::string SymbolMap
;
93 std::string OutputFile
;
94 std::string Toolchain
;
95 std::vector
<std::string
> Archs
;
96 std::vector
<std::string
> InputFiles
;
98 dsymutil::LinkOptions LinkOpts
;
101 /// Return a list of input files. This function has logic for dealing with the
102 /// special case where we might have dSYM bundles as input. The function
103 /// returns an error when the directory structure doesn't match that of a dSYM
105 static Expected
<std::vector
<std::string
>> getInputs(opt::InputArgList
&Args
,
107 std::vector
<std::string
> InputFiles
;
108 for (auto *File
: Args
.filtered(OPT_INPUT
))
109 InputFiles
.push_back(File
->getValue());
114 // If we are updating, we might get dSYM bundles as input.
115 std::vector
<std::string
> Inputs
;
116 for (const auto &Input
: InputFiles
) {
117 if (!sys::fs::is_directory(Input
)) {
118 Inputs
.push_back(Input
);
122 // Make sure that we're dealing with a dSYM bundle.
123 SmallString
<256> BundlePath(Input
);
124 sys::path::append(BundlePath
, "Contents", "Resources", "DWARF");
125 if (!sys::fs::is_directory(BundlePath
))
126 return make_error
<StringError
>(
127 Input
+ " is a directory, but doesn't look like a dSYM bundle.",
128 inconvertibleErrorCode());
130 // Create a directory iterator to iterate over all the entries in the
133 sys::fs::directory_iterator
DirIt(BundlePath
, EC
);
134 sys::fs::directory_iterator DirEnd
;
136 return errorCodeToError(EC
);
138 // Add each entry to the list of inputs.
139 while (DirIt
!= DirEnd
) {
140 Inputs
.push_back(DirIt
->path());
143 return errorCodeToError(EC
);
149 // Verify that the given combination of options makes sense.
150 static Error
verifyOptions(const DsymutilOptions
&Options
) {
151 if (Options
.LinkOpts
.Update
&&
152 std::find(Options
.InputFiles
.begin(), Options
.InputFiles
.end(), "-") !=
153 Options
.InputFiles
.end()) {
154 // FIXME: We cannot use stdin for an update because stdin will be
155 // consumed by the BinaryHolder during the debugmap parsing, and
156 // then we will want to consume it again in DwarfLinker. If we
157 // used a unique BinaryHolder object that could cache multiple
158 // binaries this restriction would go away.
159 return make_error
<StringError
>(
160 "standard input cannot be used as input for a dSYM update.",
161 errc::invalid_argument
);
164 if (!Options
.Flat
&& Options
.OutputFile
== "-")
165 return make_error
<StringError
>(
166 "cannot emit to standard output without --flat.",
167 errc::invalid_argument
);
169 if (Options
.InputFiles
.size() > 1 && Options
.Flat
&&
170 !Options
.OutputFile
.empty())
171 return make_error
<StringError
>(
172 "cannot use -o with multiple inputs in flat mode.",
173 errc::invalid_argument
);
175 if (Options
.PaperTrailWarnings
&& Options
.InputIsYAMLDebugMap
)
176 return make_error
<StringError
>(
177 "paper trail warnings are not supported for YAML input.",
178 errc::invalid_argument
);
180 return Error::success();
183 static Expected
<AccelTableKind
> getAccelTableKind(opt::InputArgList
&Args
) {
184 if (opt::Arg
*Accelerator
= Args
.getLastArg(OPT_accelerator
)) {
185 StringRef S
= Accelerator
->getValue();
187 return AccelTableKind::Apple
;
189 return AccelTableKind::Dwarf
;
191 return AccelTableKind::Default
;
192 return make_error
<StringError
>(
193 "invalid accelerator type specified: '" + S
+
194 "'. Support values are 'Apple', 'Dwarf' and 'Default'.",
195 inconvertibleErrorCode());
197 return AccelTableKind::Default
;
200 /// Parses the command line options into the LinkOptions struct and performs
201 /// some sanity checking. Returns an error in case the latter fails.
202 static Expected
<DsymutilOptions
> getOptions(opt::InputArgList
&Args
) {
203 DsymutilOptions Options
;
205 Options
.DumpDebugMap
= Args
.hasArg(OPT_dump_debug_map
);
206 Options
.DumpStab
= Args
.hasArg(OPT_symtab
);
207 Options
.Flat
= Args
.hasArg(OPT_flat
);
208 Options
.InputIsYAMLDebugMap
= Args
.hasArg(OPT_yaml_input
);
209 Options
.PaperTrailWarnings
= Args
.hasArg(OPT_papertrail
);
210 Options
.Verify
= Args
.hasArg(OPT_verify
);
212 Options
.LinkOpts
.Minimize
= Args
.hasArg(OPT_minimize
);
213 Options
.LinkOpts
.NoODR
= Args
.hasArg(OPT_no_odr
);
214 Options
.LinkOpts
.NoOutput
= Args
.hasArg(OPT_no_output
);
215 Options
.LinkOpts
.NoTimestamp
= Args
.hasArg(OPT_no_swiftmodule_timestamp
);
216 Options
.LinkOpts
.Update
= Args
.hasArg(OPT_update
);
217 Options
.LinkOpts
.Verbose
= Args
.hasArg(OPT_verbose
);
219 if (Expected
<AccelTableKind
> AccelKind
= getAccelTableKind(Args
)) {
220 Options
.LinkOpts
.TheAccelTableKind
= *AccelKind
;
222 return AccelKind
.takeError();
225 if (opt::Arg
*SymbolMap
= Args
.getLastArg(OPT_symbolmap
))
226 Options
.SymbolMap
= SymbolMap
->getValue();
228 if (Args
.hasArg(OPT_symbolmap
))
229 Options
.LinkOpts
.Update
= true;
231 if (Expected
<std::vector
<std::string
>> InputFiles
=
232 getInputs(Args
, Options
.LinkOpts
.Update
)) {
233 Options
.InputFiles
= std::move(*InputFiles
);
235 return InputFiles
.takeError();
238 for (auto *Arch
: Args
.filtered(OPT_arch
))
239 Options
.Archs
.push_back(Arch
->getValue());
241 if (opt::Arg
*OsoPrependPath
= Args
.getLastArg(OPT_oso_prepend_path
))
242 Options
.LinkOpts
.PrependPath
= OsoPrependPath
->getValue();
244 if (opt::Arg
*OutputFile
= Args
.getLastArg(OPT_output
))
245 Options
.OutputFile
= OutputFile
->getValue();
247 if (opt::Arg
*Toolchain
= Args
.getLastArg(OPT_toolchain
))
248 Options
.Toolchain
= Toolchain
->getValue();
250 if (Args
.hasArg(OPT_assembly
))
251 Options
.LinkOpts
.FileType
= OutputFileType::Assembly
;
253 if (opt::Arg
*NumThreads
= Args
.getLastArg(OPT_threads
))
254 Options
.LinkOpts
.Threads
= atoi(NumThreads
->getValue());
256 Options
.LinkOpts
.Threads
= thread::hardware_concurrency();
258 if (Options
.DumpDebugMap
|| Options
.LinkOpts
.Verbose
)
259 Options
.LinkOpts
.Threads
= 1;
261 if (getenv("RC_DEBUG_OPTIONS"))
262 Options
.PaperTrailWarnings
= true;
264 if (Error E
= verifyOptions(Options
))
269 static Error
createPlistFile(StringRef Bin
, StringRef BundleRoot
,
270 StringRef Toolchain
) {
271 // Create plist file to write to.
272 SmallString
<128> InfoPlist(BundleRoot
);
273 sys::path::append(InfoPlist
, "Contents/Info.plist");
275 raw_fd_ostream
PL(InfoPlist
, EC
, sys::fs::OF_Text
);
277 return make_error
<StringError
>(
278 "cannot create Plist: " + toString(errorCodeToError(EC
)), EC
);
280 CFBundleInfo BI
= getBundleInfo(Bin
);
282 if (BI
.IDStr
.empty()) {
283 StringRef BundleID
= *sys::path::rbegin(BundleRoot
);
284 if (sys::path::extension(BundleRoot
) == ".dSYM")
285 BI
.IDStr
= sys::path::stem(BundleID
);
290 // Print out information to the plist file.
291 PL
<< "<?xml version=\"1.0\" encoding=\"UTF-8\"\?>\n"
292 << "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" "
293 << "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
294 << "<plist version=\"1.0\">\n"
296 << "\t\t<key>CFBundleDevelopmentRegion</key>\n"
297 << "\t\t<string>English</string>\n"
298 << "\t\t<key>CFBundleIdentifier</key>\n"
299 << "\t\t<string>com.apple.xcode.dsym." << BI
.IDStr
<< "</string>\n"
300 << "\t\t<key>CFBundleInfoDictionaryVersion</key>\n"
301 << "\t\t<string>6.0</string>\n"
302 << "\t\t<key>CFBundlePackageType</key>\n"
303 << "\t\t<string>dSYM</string>\n"
304 << "\t\t<key>CFBundleSignature</key>\n"
305 << "\t\t<string>\?\?\?\?</string>\n";
307 if (!BI
.OmitShortVersion()) {
308 PL
<< "\t\t<key>CFBundleShortVersionString</key>\n";
309 PL
<< "\t\t<string>";
310 printHTMLEscaped(BI
.ShortVersionStr
, PL
);
314 PL
<< "\t\t<key>CFBundleVersion</key>\n";
315 PL
<< "\t\t<string>";
316 printHTMLEscaped(BI
.VersionStr
, PL
);
319 if (!Toolchain
.empty()) {
320 PL
<< "\t\t<key>Toolchain</key>\n";
321 PL
<< "\t\t<string>";
322 printHTMLEscaped(Toolchain
, PL
);
330 return Error::success();
333 static Error
createBundleDir(StringRef BundleBase
) {
334 SmallString
<128> Bundle(BundleBase
);
335 sys::path::append(Bundle
, "Contents", "Resources", "DWARF");
336 if (std::error_code EC
=
337 create_directories(Bundle
.str(), true, sys::fs::perms::all_all
))
338 return make_error
<StringError
>(
339 "cannot create bundle: " + toString(errorCodeToError(EC
)), EC
);
341 return Error::success();
344 static bool verify(StringRef OutputFile
, StringRef Arch
, bool Verbose
) {
345 if (OutputFile
== "-") {
346 WithColor::warning() << "verification skipped for " << Arch
347 << "because writing to stdout.\n";
351 Expected
<OwningBinary
<Binary
>> BinOrErr
= createBinary(OutputFile
);
353 WithColor::error() << OutputFile
<< ": " << toString(BinOrErr
.takeError());
357 Binary
&Binary
= *BinOrErr
.get().getBinary();
358 if (auto *Obj
= dyn_cast
<MachOObjectFile
>(&Binary
)) {
359 raw_ostream
&os
= Verbose
? errs() : nulls();
360 os
<< "Verifying DWARF for architecture: " << Arch
<< "\n";
361 std::unique_ptr
<DWARFContext
> DICtx
= DWARFContext::create(*Obj
);
362 DIDumpOptions DumpOpts
;
363 bool success
= DICtx
->verify(os
, DumpOpts
.noImplicitRecursion());
365 WithColor::error() << "verification failed for " << Arch
<< '\n';
373 struct OutputLocation
{
374 OutputLocation(std::string DWARFFile
, Optional
<std::string
> ResourceDir
= {})
375 : DWARFFile(DWARFFile
), ResourceDir(ResourceDir
) {}
376 /// This method is a workaround for older compilers.
377 Optional
<std::string
> getResourceDir() const { return ResourceDir
; }
378 std::string DWARFFile
;
379 Optional
<std::string
> ResourceDir
;
383 static Expected
<OutputLocation
>
384 getOutputFileName(StringRef InputFile
, const DsymutilOptions
&Options
) {
385 if (Options
.OutputFile
== "-")
386 return OutputLocation(Options
.OutputFile
);
388 // When updating, do in place replacement.
389 if (Options
.OutputFile
.empty() &&
390 (Options
.LinkOpts
.Update
|| !Options
.SymbolMap
.empty()))
391 return OutputLocation(InputFile
);
393 // If a flat dSYM has been requested, things are pretty simple.
395 if (Options
.OutputFile
.empty()) {
396 if (InputFile
== "-")
397 return OutputLocation
{"a.out.dwarf", {}};
398 return OutputLocation((InputFile
+ ".dwarf").str());
401 return OutputLocation(Options
.OutputFile
);
404 // We need to create/update a dSYM bundle.
405 // A bundle hierarchy looks like this:
406 // <bundle name>.dSYM/
412 std::string DwarfFile
= InputFile
== "-" ? StringRef("a.out") : InputFile
;
413 SmallString
<128> Path(Options
.OutputFile
);
415 Path
= DwarfFile
+ ".dSYM";
416 if (!Options
.LinkOpts
.NoOutput
) {
417 if (auto E
= createBundleDir(Path
))
419 if (auto E
= createPlistFile(DwarfFile
, Path
, Options
.Toolchain
))
423 sys::path::append(Path
, "Contents", "Resources");
424 std::string ResourceDir
= Path
.str();
425 sys::path::append(Path
, "DWARF", sys::path::filename(DwarfFile
));
426 return OutputLocation(Path
.str(), ResourceDir
);
429 int main(int argc
, char **argv
) {
430 InitLLVM
X(argc
, argv
);
436 ArrayRef
<const char *> ArgsArr
= makeArrayRef(argv
+ 1, argc
- 1);
437 opt::InputArgList Args
= T
.ParseArgs(ArgsArr
, MAI
, MAC
);
439 void *P
= (void *)(intptr_t)getOutputFileName
;
440 std::string SDKPath
= sys::fs::getMainExecutable(argv
[0], P
);
441 SDKPath
= sys::path::parent_path(SDKPath
);
443 if (Args
.hasArg(OPT_help
)) {
445 outs(), (std::string(argv
[0]) + " [options] <input files>").c_str(),
446 "manipulate archived DWARF debug symbol files.\n\n"
447 "dsymutil links the DWARF debug information found in the object files\n"
448 "for the executable <input file> by using debug symbols information\n"
449 "contained in its symbol table.\n",
454 if (Args
.hasArg(OPT_version
)) {
455 cl::PrintVersionMessage();
459 auto OptionsOrErr
= getOptions(Args
);
461 WithColor::error() << toString(OptionsOrErr
.takeError());
465 auto &Options
= *OptionsOrErr
;
467 InitializeAllTargetInfos();
468 InitializeAllTargetMCs();
469 InitializeAllTargets();
470 InitializeAllAsmPrinters();
472 for (const auto &Arch
: Options
.Archs
)
473 if (Arch
!= "*" && Arch
!= "all" &&
474 !object::MachOObjectFile::isValidArch(Arch
)) {
475 WithColor::error() << "unsupported cpu architecture: '" << Arch
<< "'\n";
479 SymbolMapLoader
SymMapLoader(Options
.SymbolMap
);
481 for (auto &InputFile
: Options
.InputFiles
) {
482 // Dump the symbol table for each input file and requested arch
483 if (Options
.DumpStab
) {
484 if (!dumpStab(InputFile
, Options
.Archs
, Options
.LinkOpts
.PrependPath
))
489 auto DebugMapPtrsOrErr
=
490 parseDebugMap(InputFile
, Options
.Archs
, Options
.LinkOpts
.PrependPath
,
491 Options
.PaperTrailWarnings
, Options
.LinkOpts
.Verbose
,
492 Options
.InputIsYAMLDebugMap
);
494 if (auto EC
= DebugMapPtrsOrErr
.getError()) {
495 WithColor::error() << "cannot parse the debug map for '" << InputFile
496 << "': " << EC
.message() << '\n';
500 if (Options
.LinkOpts
.Update
) {
501 // The debug map should be empty. Add one object file corresponding to
503 for (auto &Map
: *DebugMapPtrsOrErr
)
504 Map
->addDebugMapObject(InputFile
,
505 sys::TimePoint
<std::chrono::seconds
>());
508 // Ensure that the debug map is not empty (anymore).
509 if (DebugMapPtrsOrErr
->empty()) {
510 WithColor::error() << "no architecture to link\n";
514 // Shared a single binary holder for all the link steps.
515 BinaryHolder BinHolder
;
517 unsigned ThreadCount
=
518 std::min
<unsigned>(Options
.LinkOpts
.Threads
, DebugMapPtrsOrErr
->size());
519 ThreadPool
Threads(ThreadCount
);
521 // If there is more than one link to execute, we need to generate
523 const bool NeedsTempFiles
=
524 !Options
.DumpDebugMap
&& (Options
.OutputFile
!= "-") &&
525 (DebugMapPtrsOrErr
->size() != 1 || Options
.LinkOpts
.Update
);
526 const bool Verify
= Options
.Verify
&& !Options
.LinkOpts
.NoOutput
;
528 SmallVector
<MachOUtils::ArchAndFile
, 4> TempFiles
;
529 std::atomic_char
AllOK(1);
530 for (auto &Map
: *DebugMapPtrsOrErr
) {
531 if (Options
.LinkOpts
.Verbose
|| Options
.DumpDebugMap
)
534 if (Options
.DumpDebugMap
)
537 if (!Options
.SymbolMap
.empty())
538 Options
.LinkOpts
.Translator
= SymMapLoader
.Load(InputFile
, *Map
);
540 if (Map
->begin() == Map
->end())
542 << "no debug symbols in executable (-arch "
543 << MachOUtils::getArchName(Map
->getTriple().getArchName()) << ")\n";
545 // Using a std::shared_ptr rather than std::unique_ptr because move-only
546 // types don't work with std::bind in the ThreadPool implementation.
547 std::shared_ptr
<raw_fd_ostream
> OS
;
549 Expected
<OutputLocation
> OutputLocationOrErr
=
550 getOutputFileName(InputFile
, Options
);
551 if (!OutputLocationOrErr
) {
552 WithColor::error() << toString(OutputLocationOrErr
.takeError());
555 Options
.LinkOpts
.ResourceDir
= OutputLocationOrErr
->getResourceDir();
557 std::string OutputFile
= OutputLocationOrErr
->DWARFFile
;
558 if (NeedsTempFiles
) {
559 TempFiles
.emplace_back(Map
->getTriple().getArchName().str());
561 auto E
= TempFiles
.back().createTempFile();
563 WithColor::error() << toString(std::move(E
));
567 auto &TempFile
= *(TempFiles
.back().File
);
568 OS
= std::make_shared
<raw_fd_ostream
>(TempFile
.FD
,
569 /*shouldClose*/ false);
570 OutputFile
= TempFile
.TmpName
;
573 OS
= std::make_shared
<raw_fd_ostream
>(
574 Options
.LinkOpts
.NoOutput
? "-" : OutputFile
, EC
, sys::fs::OF_None
);
576 WithColor::error() << OutputFile
<< ": " << EC
.message();
581 auto LinkLambda
= [&, OutputFile
](std::shared_ptr
<raw_fd_ostream
> Stream
,
582 LinkOptions Options
) {
584 linkDwarf(*Stream
, BinHolder
, *Map
, std::move(Options
)));
587 AllOK
.fetch_and(verify(OutputFile
, Map
->getTriple().getArchName(),
591 // FIXME: The DwarfLinker can have some very deep recursion that can max
592 // out the (significantly smaller) stack when using threads. We don't
593 // want this limitation when we only have a single thread.
594 if (ThreadCount
== 1)
595 LinkLambda(OS
, Options
.LinkOpts
);
597 Threads
.async(LinkLambda
, OS
, Options
.LinkOpts
);
605 if (NeedsTempFiles
) {
606 Expected
<OutputLocation
> OutputLocationOrErr
=
607 getOutputFileName(InputFile
, Options
);
608 if (!OutputLocationOrErr
) {
609 WithColor::error() << toString(OutputLocationOrErr
.takeError());
612 if (!MachOUtils::generateUniversalBinary(TempFiles
,
613 OutputLocationOrErr
->DWARFFile
,
614 Options
.LinkOpts
, SDKPath
))