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
.InputFiles
.empty()) {
152 return make_error
<StringError
>("no input files specified",
153 errc::invalid_argument
);
156 if (Options
.LinkOpts
.Update
&&
157 std::find(Options
.InputFiles
.begin(), Options
.InputFiles
.end(), "-") !=
158 Options
.InputFiles
.end()) {
159 // FIXME: We cannot use stdin for an update because stdin will be
160 // consumed by the BinaryHolder during the debugmap parsing, and
161 // then we will want to consume it again in DwarfLinker. If we
162 // used a unique BinaryHolder object that could cache multiple
163 // binaries this restriction would go away.
164 return make_error
<StringError
>(
165 "standard input cannot be used as input for a dSYM update.",
166 errc::invalid_argument
);
169 if (!Options
.Flat
&& Options
.OutputFile
== "-")
170 return make_error
<StringError
>(
171 "cannot emit to standard output without --flat.",
172 errc::invalid_argument
);
174 if (Options
.InputFiles
.size() > 1 && Options
.Flat
&&
175 !Options
.OutputFile
.empty())
176 return make_error
<StringError
>(
177 "cannot use -o with multiple inputs in flat mode.",
178 errc::invalid_argument
);
180 if (Options
.PaperTrailWarnings
&& Options
.InputIsYAMLDebugMap
)
181 return make_error
<StringError
>(
182 "paper trail warnings are not supported for YAML input.",
183 errc::invalid_argument
);
185 return Error::success();
188 static Expected
<AccelTableKind
> getAccelTableKind(opt::InputArgList
&Args
) {
189 if (opt::Arg
*Accelerator
= Args
.getLastArg(OPT_accelerator
)) {
190 StringRef S
= Accelerator
->getValue();
192 return AccelTableKind::Apple
;
194 return AccelTableKind::Dwarf
;
196 return AccelTableKind::Default
;
197 return make_error
<StringError
>(
198 "invalid accelerator type specified: '" + S
+
199 "'. Support values are 'Apple', 'Dwarf' and 'Default'.",
200 inconvertibleErrorCode());
202 return AccelTableKind::Default
;
205 /// Parses the command line options into the LinkOptions struct and performs
206 /// some sanity checking. Returns an error in case the latter fails.
207 static Expected
<DsymutilOptions
> getOptions(opt::InputArgList
&Args
) {
208 DsymutilOptions Options
;
210 Options
.DumpDebugMap
= Args
.hasArg(OPT_dump_debug_map
);
211 Options
.DumpStab
= Args
.hasArg(OPT_symtab
);
212 Options
.Flat
= Args
.hasArg(OPT_flat
);
213 Options
.InputIsYAMLDebugMap
= Args
.hasArg(OPT_yaml_input
);
214 Options
.PaperTrailWarnings
= Args
.hasArg(OPT_papertrail
);
215 Options
.Verify
= Args
.hasArg(OPT_verify
);
217 Options
.LinkOpts
.Minimize
= Args
.hasArg(OPT_minimize
);
218 Options
.LinkOpts
.NoODR
= Args
.hasArg(OPT_no_odr
);
219 Options
.LinkOpts
.NoOutput
= Args
.hasArg(OPT_no_output
);
220 Options
.LinkOpts
.NoTimestamp
= Args
.hasArg(OPT_no_swiftmodule_timestamp
);
221 Options
.LinkOpts
.Update
= Args
.hasArg(OPT_update
);
222 Options
.LinkOpts
.Verbose
= Args
.hasArg(OPT_verbose
);
224 if (Expected
<AccelTableKind
> AccelKind
= getAccelTableKind(Args
)) {
225 Options
.LinkOpts
.TheAccelTableKind
= *AccelKind
;
227 return AccelKind
.takeError();
230 if (opt::Arg
*SymbolMap
= Args
.getLastArg(OPT_symbolmap
))
231 Options
.SymbolMap
= SymbolMap
->getValue();
233 if (Args
.hasArg(OPT_symbolmap
))
234 Options
.LinkOpts
.Update
= true;
236 if (Expected
<std::vector
<std::string
>> InputFiles
=
237 getInputs(Args
, Options
.LinkOpts
.Update
)) {
238 Options
.InputFiles
= std::move(*InputFiles
);
240 return InputFiles
.takeError();
243 for (auto *Arch
: Args
.filtered(OPT_arch
))
244 Options
.Archs
.push_back(Arch
->getValue());
246 if (opt::Arg
*OsoPrependPath
= Args
.getLastArg(OPT_oso_prepend_path
))
247 Options
.LinkOpts
.PrependPath
= OsoPrependPath
->getValue();
249 if (opt::Arg
*OutputFile
= Args
.getLastArg(OPT_output
))
250 Options
.OutputFile
= OutputFile
->getValue();
252 if (opt::Arg
*Toolchain
= Args
.getLastArg(OPT_toolchain
))
253 Options
.Toolchain
= Toolchain
->getValue();
255 if (Args
.hasArg(OPT_assembly
))
256 Options
.LinkOpts
.FileType
= OutputFileType::Assembly
;
258 if (opt::Arg
*NumThreads
= Args
.getLastArg(OPT_threads
))
259 Options
.LinkOpts
.Threads
= atoi(NumThreads
->getValue());
261 Options
.LinkOpts
.Threads
= thread::hardware_concurrency();
263 if (Options
.DumpDebugMap
|| Options
.LinkOpts
.Verbose
)
264 Options
.LinkOpts
.Threads
= 1;
266 if (getenv("RC_DEBUG_OPTIONS"))
267 Options
.PaperTrailWarnings
= true;
269 if (Error E
= verifyOptions(Options
))
274 static Error
createPlistFile(StringRef Bin
, StringRef BundleRoot
,
275 StringRef Toolchain
) {
276 // Create plist file to write to.
277 SmallString
<128> InfoPlist(BundleRoot
);
278 sys::path::append(InfoPlist
, "Contents/Info.plist");
280 raw_fd_ostream
PL(InfoPlist
, EC
, sys::fs::OF_Text
);
282 return make_error
<StringError
>(
283 "cannot create Plist: " + toString(errorCodeToError(EC
)), EC
);
285 CFBundleInfo BI
= getBundleInfo(Bin
);
287 if (BI
.IDStr
.empty()) {
288 StringRef BundleID
= *sys::path::rbegin(BundleRoot
);
289 if (sys::path::extension(BundleRoot
) == ".dSYM")
290 BI
.IDStr
= sys::path::stem(BundleID
);
295 // Print out information to the plist file.
296 PL
<< "<?xml version=\"1.0\" encoding=\"UTF-8\"\?>\n"
297 << "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" "
298 << "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
299 << "<plist version=\"1.0\">\n"
301 << "\t\t<key>CFBundleDevelopmentRegion</key>\n"
302 << "\t\t<string>English</string>\n"
303 << "\t\t<key>CFBundleIdentifier</key>\n"
304 << "\t\t<string>com.apple.xcode.dsym." << BI
.IDStr
<< "</string>\n"
305 << "\t\t<key>CFBundleInfoDictionaryVersion</key>\n"
306 << "\t\t<string>6.0</string>\n"
307 << "\t\t<key>CFBundlePackageType</key>\n"
308 << "\t\t<string>dSYM</string>\n"
309 << "\t\t<key>CFBundleSignature</key>\n"
310 << "\t\t<string>\?\?\?\?</string>\n";
312 if (!BI
.OmitShortVersion()) {
313 PL
<< "\t\t<key>CFBundleShortVersionString</key>\n";
314 PL
<< "\t\t<string>";
315 printHTMLEscaped(BI
.ShortVersionStr
, PL
);
319 PL
<< "\t\t<key>CFBundleVersion</key>\n";
320 PL
<< "\t\t<string>";
321 printHTMLEscaped(BI
.VersionStr
, PL
);
324 if (!Toolchain
.empty()) {
325 PL
<< "\t\t<key>Toolchain</key>\n";
326 PL
<< "\t\t<string>";
327 printHTMLEscaped(Toolchain
, PL
);
335 return Error::success();
338 static Error
createBundleDir(StringRef BundleBase
) {
339 SmallString
<128> Bundle(BundleBase
);
340 sys::path::append(Bundle
, "Contents", "Resources", "DWARF");
341 if (std::error_code EC
=
342 create_directories(Bundle
.str(), true, sys::fs::perms::all_all
))
343 return make_error
<StringError
>(
344 "cannot create bundle: " + toString(errorCodeToError(EC
)), EC
);
346 return Error::success();
349 static bool verify(StringRef OutputFile
, StringRef Arch
, bool Verbose
) {
350 if (OutputFile
== "-") {
351 WithColor::warning() << "verification skipped for " << Arch
352 << "because writing to stdout.\n";
356 Expected
<OwningBinary
<Binary
>> BinOrErr
= createBinary(OutputFile
);
358 WithColor::error() << OutputFile
<< ": " << toString(BinOrErr
.takeError());
362 Binary
&Binary
= *BinOrErr
.get().getBinary();
363 if (auto *Obj
= dyn_cast
<MachOObjectFile
>(&Binary
)) {
364 raw_ostream
&os
= Verbose
? errs() : nulls();
365 os
<< "Verifying DWARF for architecture: " << Arch
<< "\n";
366 std::unique_ptr
<DWARFContext
> DICtx
= DWARFContext::create(*Obj
);
367 DIDumpOptions DumpOpts
;
368 bool success
= DICtx
->verify(os
, DumpOpts
.noImplicitRecursion());
370 WithColor::error() << "verification failed for " << Arch
<< '\n';
378 struct OutputLocation
{
379 OutputLocation(std::string DWARFFile
, Optional
<std::string
> ResourceDir
= {})
380 : DWARFFile(DWARFFile
), ResourceDir(ResourceDir
) {}
381 /// This method is a workaround for older compilers.
382 Optional
<std::string
> getResourceDir() const { return ResourceDir
; }
383 std::string DWARFFile
;
384 Optional
<std::string
> ResourceDir
;
388 static Expected
<OutputLocation
>
389 getOutputFileName(StringRef InputFile
, const DsymutilOptions
&Options
) {
390 if (Options
.OutputFile
== "-")
391 return OutputLocation(Options
.OutputFile
);
393 // When updating, do in place replacement.
394 if (Options
.OutputFile
.empty() &&
395 (Options
.LinkOpts
.Update
|| !Options
.SymbolMap
.empty()))
396 return OutputLocation(InputFile
);
398 // If a flat dSYM has been requested, things are pretty simple.
400 if (Options
.OutputFile
.empty()) {
401 if (InputFile
== "-")
402 return OutputLocation
{"a.out.dwarf", {}};
403 return OutputLocation((InputFile
+ ".dwarf").str());
406 return OutputLocation(Options
.OutputFile
);
409 // We need to create/update a dSYM bundle.
410 // A bundle hierarchy looks like this:
411 // <bundle name>.dSYM/
417 std::string DwarfFile
= InputFile
== "-" ? StringRef("a.out") : InputFile
;
418 SmallString
<128> Path(Options
.OutputFile
);
420 Path
= DwarfFile
+ ".dSYM";
421 if (!Options
.LinkOpts
.NoOutput
) {
422 if (auto E
= createBundleDir(Path
))
424 if (auto E
= createPlistFile(DwarfFile
, Path
, Options
.Toolchain
))
428 sys::path::append(Path
, "Contents", "Resources");
429 std::string ResourceDir
= Path
.str();
430 sys::path::append(Path
, "DWARF", sys::path::filename(DwarfFile
));
431 return OutputLocation(Path
.str(), ResourceDir
);
434 int main(int argc
, char **argv
) {
435 InitLLVM
X(argc
, argv
);
441 ArrayRef
<const char *> ArgsArr
= makeArrayRef(argv
+ 1, argc
- 1);
442 opt::InputArgList Args
= T
.ParseArgs(ArgsArr
, MAI
, MAC
);
444 void *P
= (void *)(intptr_t)getOutputFileName
;
445 std::string SDKPath
= sys::fs::getMainExecutable(argv
[0], P
);
446 SDKPath
= sys::path::parent_path(SDKPath
);
448 for (auto *Arg
: Args
.filtered(OPT_UNKNOWN
)) {
449 WithColor::warning() << "ignoring unknown option: " << Arg
->getSpelling()
453 if (Args
.hasArg(OPT_help
)) {
455 outs(), (std::string(argv
[0]) + " [options] <input files>").c_str(),
456 "manipulate archived DWARF debug symbol files.\n\n"
457 "dsymutil links the DWARF debug information found in the object files\n"
458 "for the executable <input file> by using debug symbols information\n"
459 "contained in its symbol table.\n",
464 if (Args
.hasArg(OPT_version
)) {
465 cl::PrintVersionMessage();
469 auto OptionsOrErr
= getOptions(Args
);
471 WithColor::error() << toString(OptionsOrErr
.takeError());
475 auto &Options
= *OptionsOrErr
;
477 InitializeAllTargetInfos();
478 InitializeAllTargetMCs();
479 InitializeAllTargets();
480 InitializeAllAsmPrinters();
482 for (const auto &Arch
: Options
.Archs
)
483 if (Arch
!= "*" && Arch
!= "all" &&
484 !object::MachOObjectFile::isValidArch(Arch
)) {
485 WithColor::error() << "unsupported cpu architecture: '" << Arch
<< "'\n";
489 SymbolMapLoader
SymMapLoader(Options
.SymbolMap
);
491 for (auto &InputFile
: Options
.InputFiles
) {
492 // Dump the symbol table for each input file and requested arch
493 if (Options
.DumpStab
) {
494 if (!dumpStab(InputFile
, Options
.Archs
, Options
.LinkOpts
.PrependPath
))
499 auto DebugMapPtrsOrErr
=
500 parseDebugMap(InputFile
, Options
.Archs
, Options
.LinkOpts
.PrependPath
,
501 Options
.PaperTrailWarnings
, Options
.LinkOpts
.Verbose
,
502 Options
.InputIsYAMLDebugMap
);
504 if (auto EC
= DebugMapPtrsOrErr
.getError()) {
505 WithColor::error() << "cannot parse the debug map for '" << InputFile
506 << "': " << EC
.message() << '\n';
510 if (Options
.LinkOpts
.Update
) {
511 // The debug map should be empty. Add one object file corresponding to
513 for (auto &Map
: *DebugMapPtrsOrErr
)
514 Map
->addDebugMapObject(InputFile
,
515 sys::TimePoint
<std::chrono::seconds
>());
518 // Ensure that the debug map is not empty (anymore).
519 if (DebugMapPtrsOrErr
->empty()) {
520 WithColor::error() << "no architecture to link\n";
524 // Shared a single binary holder for all the link steps.
525 BinaryHolder BinHolder
;
527 unsigned ThreadCount
=
528 std::min
<unsigned>(Options
.LinkOpts
.Threads
, DebugMapPtrsOrErr
->size());
529 ThreadPool
Threads(ThreadCount
);
531 // If there is more than one link to execute, we need to generate
533 const bool NeedsTempFiles
=
534 !Options
.DumpDebugMap
&& (Options
.OutputFile
!= "-") &&
535 (DebugMapPtrsOrErr
->size() != 1 || Options
.LinkOpts
.Update
);
536 const bool Verify
= Options
.Verify
&& !Options
.LinkOpts
.NoOutput
;
538 SmallVector
<MachOUtils::ArchAndFile
, 4> TempFiles
;
539 std::atomic_char
AllOK(1);
540 for (auto &Map
: *DebugMapPtrsOrErr
) {
541 if (Options
.LinkOpts
.Verbose
|| Options
.DumpDebugMap
)
544 if (Options
.DumpDebugMap
)
547 if (!Options
.SymbolMap
.empty())
548 Options
.LinkOpts
.Translator
= SymMapLoader
.Load(InputFile
, *Map
);
550 if (Map
->begin() == Map
->end())
552 << "no debug symbols in executable (-arch "
553 << MachOUtils::getArchName(Map
->getTriple().getArchName()) << ")\n";
555 // Using a std::shared_ptr rather than std::unique_ptr because move-only
556 // types don't work with std::bind in the ThreadPool implementation.
557 std::shared_ptr
<raw_fd_ostream
> OS
;
559 Expected
<OutputLocation
> OutputLocationOrErr
=
560 getOutputFileName(InputFile
, Options
);
561 if (!OutputLocationOrErr
) {
562 WithColor::error() << toString(OutputLocationOrErr
.takeError());
565 Options
.LinkOpts
.ResourceDir
= OutputLocationOrErr
->getResourceDir();
567 std::string OutputFile
= OutputLocationOrErr
->DWARFFile
;
568 if (NeedsTempFiles
) {
569 TempFiles
.emplace_back(Map
->getTriple().getArchName().str());
571 auto E
= TempFiles
.back().createTempFile();
573 WithColor::error() << toString(std::move(E
));
577 auto &TempFile
= *(TempFiles
.back().File
);
578 OS
= std::make_shared
<raw_fd_ostream
>(TempFile
.FD
,
579 /*shouldClose*/ false);
580 OutputFile
= TempFile
.TmpName
;
583 OS
= std::make_shared
<raw_fd_ostream
>(
584 Options
.LinkOpts
.NoOutput
? "-" : OutputFile
, EC
, sys::fs::OF_None
);
586 WithColor::error() << OutputFile
<< ": " << EC
.message();
591 auto LinkLambda
= [&, OutputFile
](std::shared_ptr
<raw_fd_ostream
> Stream
,
592 LinkOptions Options
) {
594 linkDwarf(*Stream
, BinHolder
, *Map
, std::move(Options
)));
597 AllOK
.fetch_and(verify(OutputFile
, Map
->getTriple().getArchName(),
601 // FIXME: The DwarfLinker can have some very deep recursion that can max
602 // out the (significantly smaller) stack when using threads. We don't
603 // want this limitation when we only have a single thread.
604 if (ThreadCount
== 1)
605 LinkLambda(OS
, Options
.LinkOpts
);
607 Threads
.async(LinkLambda
, OS
, Options
.LinkOpts
);
615 if (NeedsTempFiles
) {
616 Expected
<OutputLocation
> OutputLocationOrErr
=
617 getOutputFileName(InputFile
, Options
);
618 if (!OutputLocationOrErr
) {
619 WithColor::error() << toString(OutputLocationOrErr
.takeError());
622 if (!MachOUtils::generateUniversalBinary(TempFiles
,
623 OutputLocationOrErr
->DWARFFile
,
624 Options
.LinkOpts
, SDKPath
))