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