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 "DebugMap.h"
17 #include "MachOUtils.h"
18 #include "llvm/ADT/SmallString.h"
19 #include "llvm/ADT/SmallVector.h"
20 #include "llvm/ADT/StringRef.h"
21 #include "llvm/ADT/Triple.h"
22 #include "llvm/Object/MachO.h"
23 #include "llvm/Support/CommandLine.h"
24 #include "llvm/Support/FileSystem.h"
25 #include "llvm/Support/ManagedStatic.h"
26 #include "llvm/Support/Path.h"
27 #include "llvm/Support/PrettyStackTrace.h"
28 #include "llvm/Support/Signals.h"
29 #include "llvm/Support/TargetSelect.h"
30 #include "llvm/Support/ThreadPool.h"
31 #include "llvm/Support/raw_ostream.h"
32 #include "llvm/Support/thread.h"
33 #include <algorithm>
34 #include <cstdint>
35 #include <cstdlib>
36 #include <string>
37 #include <system_error>
38 
39 using namespace llvm::cl;
40 using namespace llvm::dsymutil;
41 
42 static OptionCategory DsymCategory("Specific Options");
43 static opt<bool> Help("h", desc("Alias for -help"), Hidden);
44 static opt<bool> Version("v", desc("Alias for -version"), Hidden);
45 
46 static list<std::string> InputFiles(Positional, OneOrMore,
47                                     desc("<input files>"), cat(DsymCategory));
48 
49 static opt<std::string>
50     OutputFileOpt("o",
51                   desc("Specify the output file. default: <input file>.dwarf"),
52                   value_desc("filename"), cat(DsymCategory));
53 
54 static opt<std::string> OsoPrependPath(
55     "oso-prepend-path",
56     desc("Specify a directory to prepend to the paths of object files."),
57     value_desc("path"), cat(DsymCategory));
58 
59 static opt<bool> DumpStab(
60     "symtab",
61     desc("Dumps the symbol table found in executable or object file(s) and\n"
62          "exits."),
63     init(false), cat(DsymCategory));
64 static alias DumpStabA("s", desc("Alias for --symtab"), aliasopt(DumpStab));
65 
66 static opt<bool> FlatOut("flat",
67                          desc("Produce a flat dSYM file (not a bundle)."),
68                          init(false), cat(DsymCategory));
69 static alias FlatOutA("f", desc("Alias for --flat"), aliasopt(FlatOut));
70 
71 static opt<unsigned> NumThreads(
72     "num-threads",
73     desc("Specifies the maximum number (n) of simultaneous threads to use\n"
74          "when linking multiple architectures."),
75     value_desc("n"), init(0), cat(DsymCategory));
76 static alias NumThreadsA("j", desc("Alias for --num-threads"),
77                          aliasopt(NumThreads));
78 
79 static opt<bool> Verbose("verbose", desc("Verbosity level"), init(false),
80                          cat(DsymCategory));
81 
82 static opt<bool>
83     NoOutput("no-output",
84              desc("Do the link in memory, but do not emit the result file."),
85              init(false), cat(DsymCategory));
86 
87 static opt<bool>
88     NoTimestamp("no-swiftmodule-timestamp",
89                 desc("Don't check timestamp for swiftmodule files."),
90                 init(false), cat(DsymCategory));
91 
92 static list<std::string> ArchFlags(
93     "arch",
94     desc("Link DWARF debug information only for specified CPU architecture\n"
95          "types. This option can be specified multiple times, once for each\n"
96          "desired architecture. All CPU architectures will be linked by\n"
97          "default."), value_desc("arch"),
98     ZeroOrMore, cat(DsymCategory));
99 
100 static opt<bool>
101     NoODR("no-odr",
102           desc("Do not use ODR (One Definition Rule) for type uniquing."),
103           init(false), cat(DsymCategory));
104 
105 static opt<bool> DumpDebugMap(
106     "dump-debug-map",
107     desc("Parse and dump the debug map to standard output. Not DWARF link "
108          "will take place."),
109     init(false), cat(DsymCategory));
110 
111 static opt<bool> InputIsYAMLDebugMap(
112     "y", desc("Treat the input file is a YAML debug map rather than a binary."),
113     init(false), cat(DsymCategory));
114 
115 static bool createPlistFile(llvm::StringRef BundleRoot) {
116   if (NoOutput)
117     return true;
118 
119   // Create plist file to write to.
120   llvm::SmallString<128> InfoPlist(BundleRoot);
121   llvm::sys::path::append(InfoPlist, "Contents/Info.plist");
122   std::error_code EC;
123   llvm::raw_fd_ostream PL(InfoPlist, EC, llvm::sys::fs::F_Text);
124   if (EC) {
125     llvm::errs() << "error: cannot create plist file " << InfoPlist << ": "
126                  << EC.message() << '\n';
127     return false;
128   }
129 
130   // FIXME: Use CoreFoundation to get executable bundle info. Use
131   // dummy values for now.
132   std::string bundleVersionStr = "1", bundleShortVersionStr = "1.0",
133               bundleIDStr;
134 
135   llvm::StringRef BundleID = *llvm::sys::path::rbegin(BundleRoot);
136   if (llvm::sys::path::extension(BundleRoot) == ".dSYM")
137     bundleIDStr = llvm::sys::path::stem(BundleID);
138   else
139     bundleIDStr = BundleID;
140 
141   // Print out information to the plist file.
142   PL << "<?xml version=\"1.0\" encoding=\"UTF-8\"\?>\n"
143      << "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" "
144      << "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
145      << "<plist version=\"1.0\">\n"
146      << "\t<dict>\n"
147      << "\t\t<key>CFBundleDevelopmentRegion</key>\n"
148      << "\t\t<string>English</string>\n"
149      << "\t\t<key>CFBundleIdentifier</key>\n"
150      << "\t\t<string>com.apple.xcode.dsym." << bundleIDStr << "</string>\n"
151      << "\t\t<key>CFBundleInfoDictionaryVersion</key>\n"
152      << "\t\t<string>6.0</string>\n"
153      << "\t\t<key>CFBundlePackageType</key>\n"
154      << "\t\t<string>dSYM</string>\n"
155      << "\t\t<key>CFBundleSignature</key>\n"
156      << "\t\t<string>\?\?\?\?</string>\n"
157      << "\t\t<key>CFBundleShortVersionString</key>\n"
158      << "\t\t<string>" << bundleShortVersionStr << "</string>\n"
159      << "\t\t<key>CFBundleVersion</key>\n"
160      << "\t\t<string>" << bundleVersionStr << "</string>\n"
161      << "\t</dict>\n"
162      << "</plist>\n";
163 
164   PL.close();
165   return true;
166 }
167 
168 static bool createBundleDir(llvm::StringRef BundleBase) {
169   if (NoOutput)
170     return true;
171 
172   llvm::SmallString<128> Bundle(BundleBase);
173   llvm::sys::path::append(Bundle, "Contents", "Resources", "DWARF");
174   if (std::error_code EC = create_directories(Bundle.str(), true,
175                                               llvm::sys::fs::perms::all_all)) {
176     llvm::errs() << "error: cannot create directory " << Bundle << ": "
177                  << EC.message() << "\n";
178     return false;
179   }
180   return true;
181 }
182 
183 static std::error_code getUniqueFile(const llvm::Twine &Model, int &ResultFD,
184                                      llvm::SmallVectorImpl<char> &ResultPath) {
185   // If in NoOutput mode, use the createUniqueFile variant that
186   // doesn't open the file but still generates a somewhat unique
187   // name. In the real usage scenario, we'll want to ensure that the
188   // file is trully unique, and creating it is the only way to achieve
189   // that.
190   if (NoOutput)
191     return llvm::sys::fs::createUniqueFile(Model, ResultPath);
192   return llvm::sys::fs::createUniqueFile(Model, ResultFD, ResultPath);
193 }
194 
195 static std::string getOutputFileName(llvm::StringRef InputFile,
196                                      bool TempFile = false) {
197   if (TempFile) {
198     llvm::SmallString<128> TmpFile;
199     llvm::sys::path::system_temp_directory(true, TmpFile);
200     llvm::StringRef Basename =
201         OutputFileOpt.empty() ? InputFile : llvm::StringRef(OutputFileOpt);
202     llvm::sys::path::append(TmpFile, llvm::sys::path::filename(Basename));
203 
204     int FD;
205     llvm::SmallString<128> UniqueFile;
206     if (auto EC = getUniqueFile(TmpFile + ".tmp%%%%%.dwarf", FD, UniqueFile)) {
207       llvm::errs() << "error: failed to create temporary outfile '"
208                    << TmpFile << "': " << EC.message() << '\n';
209       return "";
210     }
211     llvm::sys::RemoveFileOnSignal(UniqueFile);
212     if (!NoOutput) {
213       // Close the file immediately. We know it is unique. It will be
214       // reopened and written to later.
215       llvm::raw_fd_ostream CloseImmediately(FD, true /* shouldClose */, true);
216     }
217     return UniqueFile.str();
218   }
219 
220   if (FlatOut) {
221     // If a flat dSYM has been requested, things are pretty simple.
222     if (OutputFileOpt.empty()) {
223       if (InputFile == "-")
224         return "a.out.dwarf";
225       return (InputFile + ".dwarf").str();
226     }
227 
228     return OutputFileOpt;
229   }
230 
231   // We need to create/update a dSYM bundle.
232   // A bundle hierarchy looks like this:
233   //   <bundle name>.dSYM/
234   //       Contents/
235   //          Info.plist
236   //          Resources/
237   //             DWARF/
238   //                <DWARF file(s)>
239   std::string DwarfFile =
240       InputFile == "-" ? llvm::StringRef("a.out") : InputFile;
241   llvm::SmallString<128> BundleDir(OutputFileOpt);
242   if (BundleDir.empty())
243     BundleDir = DwarfFile + ".dSYM";
244   if (!createBundleDir(BundleDir) || !createPlistFile(BundleDir))
245     return "";
246 
247   llvm::sys::path::append(BundleDir, "Contents", "Resources", "DWARF",
248                           llvm::sys::path::filename(DwarfFile));
249   return BundleDir.str();
250 }
251 
252 void llvm::dsymutil::exitDsymutil(int ExitStatus) {
253   // Cleanup temporary files.
254   llvm::sys::RunInterruptHandlers();
255   exit(ExitStatus);
256 }
257 
258 int main(int argc, char **argv) {
259   llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);
260   llvm::PrettyStackTraceProgram StackPrinter(argc, argv);
261   llvm::llvm_shutdown_obj Shutdown;
262   LinkOptions Options;
263   void *MainAddr = (void *)(intptr_t)&exitDsymutil;
264   std::string SDKPath = llvm::sys::fs::getMainExecutable(argv[0], MainAddr);
265   SDKPath = llvm::sys::path::parent_path(SDKPath);
266 
267   HideUnrelatedOptions(DsymCategory);
268   llvm::cl::ParseCommandLineOptions(
269       argc, argv,
270       "manipulate archived DWARF debug symbol files.\n\n"
271       "dsymutil links the DWARF debug information found in the object files\n"
272       "for the executable <input file> by using debug symbols information\n"
273       "contained in its symbol table.\n");
274 
275   if (Help) {
276     PrintHelpMessage();
277     return 0;
278   }
279 
280   if (Version) {
281     llvm::cl::PrintVersionMessage();
282     return 0;
283   }
284 
285   Options.Verbose = Verbose;
286   Options.NoOutput = NoOutput;
287   Options.NoODR = NoODR;
288   Options.NoTimestamp = NoTimestamp;
289   Options.PrependPath = OsoPrependPath;
290 
291   llvm::InitializeAllTargetInfos();
292   llvm::InitializeAllTargetMCs();
293   llvm::InitializeAllTargets();
294   llvm::InitializeAllAsmPrinters();
295 
296   if (!FlatOut && OutputFileOpt == "-") {
297     llvm::errs() << "error: cannot emit to standard output without --flat\n";
298     return 1;
299   }
300 
301   if (InputFiles.size() > 1 && FlatOut && !OutputFileOpt.empty()) {
302     llvm::errs() << "error: cannot use -o with multiple inputs in flat mode\n";
303     return 1;
304   }
305 
306   for (const auto &Arch : ArchFlags)
307     if (Arch != "*" && Arch != "all" &&
308         !llvm::object::MachOObjectFile::isValidArch(Arch)) {
309       llvm::errs() << "error: Unsupported cpu architecture: '" << Arch << "'\n";
310       exitDsymutil(1);
311     }
312 
313   for (auto &InputFile : InputFiles) {
314     // Dump the symbol table for each input file and requested arch
315     if (DumpStab) {
316       if (!dumpStab(InputFile, ArchFlags, OsoPrependPath))
317         exitDsymutil(1);
318       continue;
319     }
320 
321     auto DebugMapPtrsOrErr = parseDebugMap(InputFile, ArchFlags, OsoPrependPath,
322                                            Verbose, InputIsYAMLDebugMap);
323 
324     if (auto EC = DebugMapPtrsOrErr.getError()) {
325       llvm::errs() << "error: cannot parse the debug map for \"" << InputFile
326                    << "\": " << EC.message() << '\n';
327       exitDsymutil(1);
328     }
329 
330     if (DebugMapPtrsOrErr->empty()) {
331       llvm::errs() << "error: no architecture to link\n";
332       exitDsymutil(1);
333     }
334 
335     if (NumThreads == 0)
336       NumThreads = llvm::thread::hardware_concurrency();
337     if (DumpDebugMap || Verbose)
338       NumThreads = 1;
339     NumThreads = std::min<unsigned>(NumThreads, DebugMapPtrsOrErr->size());
340 
341 
342     // If there is more than one link to execute, we need to generate
343     // temporary files.
344     bool NeedsTempFiles = !DumpDebugMap && (*DebugMapPtrsOrErr).size() != 1;
345     llvm::SmallVector<MachOUtils::ArchAndFilename, 4> TempFiles;
346     for (auto &Map : *DebugMapPtrsOrErr) {
347       if (Verbose || DumpDebugMap)
348         Map->print(llvm::outs());
349 
350       if (DumpDebugMap)
351         continue;
352 
353       if (Map->begin() == Map->end())
354         llvm::errs() << "warning: no debug symbols in executable (-arch "
355                      << MachOUtils::getArchName(Map->getTriple().getArchName())
356                      << ")\n";
357 
358       std::string OutputFile = getOutputFileName(InputFile, NeedsTempFiles);
359 
360       auto LinkLambda = [OutputFile, Options, &Map]() {
361         if (OutputFile.empty() || !linkDwarf(OutputFile, *Map, Options))
362           exitDsymutil(1);
363       };
364 
365       // FIXME: The DwarfLinker can have some very deep recursion that can max
366       // out the (significantly smaller) stack when using threads. We don't
367       // want this limitation when we only have a single thread.
368       if (NumThreads == 1) {
369         LinkLambda();
370       } else {
371         llvm::ThreadPool Threads(NumThreads);
372         Threads.async(LinkLambda);
373         Threads.wait();
374       }
375 
376       if (NeedsTempFiles)
377         TempFiles.emplace_back(Map->getTriple().getArchName().str(),
378                                OutputFile);
379     }
380 
381 
382     if (NeedsTempFiles &&
383         !MachOUtils::generateUniversalBinary(
384             TempFiles, getOutputFileName(InputFile), Options, SDKPath))
385       exitDsymutil(1);
386   }
387 
388   exitDsymutil(0);
389 }
390