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