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::SmallString<128> TmpFile;
180 llvm::sys::path::system_temp_directory(true, TmpFile);
181 llvm::StringRef Basename =
182 OutputFileOpt.empty() ? InputFile : llvm::StringRef(OutputFileOpt);
183 llvm::sys::path::append(TmpFile, llvm::sys::path::filename(Basename));
184
185 int FD;
186 llvm::SmallString<128> UniqueFile;
187 if (auto EC = getUniqueFile(TmpFile + ".tmp%%%%%.dwarf", FD, UniqueFile)) {
188 llvm::errs() << "error: failed to create temporary outfile '"
189 << TmpFile << "': " << EC.message() << '\n';
190 return "";
191 }
192 llvm::sys::RemoveFileOnSignal(UniqueFile);
193 if (!NoOutput) {
194 // Close the file immediately. We know it is unique. It will be
195 // reopened and written to later.
196 llvm::raw_fd_ostream CloseImmediately(FD, true /* shouldClose */, true);
197 }
198 return UniqueFile.str();
199 }
200
201 if (FlatOut) {
202 // If a flat dSYM has been requested, things are pretty simple.
203 if (OutputFileOpt.empty()) {
204 if (InputFile == "-")
205 return "a.out.dwarf";
206 return (InputFile + ".dwarf").str();
207 }
208
209 return OutputFileOpt;
210 }
211
212 // We need to create/update a dSYM bundle.
213 // A bundle hierarchy looks like this:
214 // <bundle name>.dSYM/
215 // Contents/
216 // Info.plist
217 // Resources/
218 // DWARF/
219 // <DWARF file(s)>
220 std::string DwarfFile =
221 InputFile == "-" ? llvm::StringRef("a.out") : InputFile;
222 llvm::SmallString<128> BundleDir(OutputFileOpt);
223 if (BundleDir.empty())
224 BundleDir = DwarfFile + ".dSYM";
225 if (!createBundleDir(BundleDir) || !createPlistFile(BundleDir))
226 return "";
227
228 llvm::sys::path::append(BundleDir, "Contents", "Resources", "DWARF",
229 llvm::sys::path::filename(DwarfFile));
230 return BundleDir.str();
231 }
232
exitDsymutil(int ExitStatus)233 void llvm::dsymutil::exitDsymutil(int ExitStatus) {
234 // Cleanup temporary files.
235 llvm::sys::RunInterruptHandlers();
236 exit(ExitStatus);
237 }
238
main(int argc,char ** argv)239 int main(int argc, char **argv) {
240 llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);
241 llvm::PrettyStackTraceProgram StackPrinter(argc, argv);
242 llvm::llvm_shutdown_obj Shutdown;
243 LinkOptions Options;
244 void *MainAddr = (void *)(intptr_t)&exitDsymutil;
245 std::string SDKPath = llvm::sys::fs::getMainExecutable(argv[0], MainAddr);
246 SDKPath = llvm::sys::path::parent_path(SDKPath);
247
248 HideUnrelatedOptions(DsymCategory);
249 llvm::cl::ParseCommandLineOptions(
250 argc, argv,
251 "manipulate archived DWARF debug symbol files.\n\n"
252 "dsymutil links the DWARF debug information found in the object files\n"
253 "for the executable <input file> by using debug symbols information\n"
254 "contained in its symbol table.\n");
255
256 if (Help)
257 PrintHelpMessage();
258
259 if (Version) {
260 llvm::cl::PrintVersionMessage();
261 return 0;
262 }
263
264 Options.Verbose = Verbose;
265 Options.NoOutput = NoOutput;
266 Options.NoODR = NoODR;
267 Options.PrependPath = OsoPrependPath;
268
269 llvm::InitializeAllTargetInfos();
270 llvm::InitializeAllTargetMCs();
271 llvm::InitializeAllTargets();
272 llvm::InitializeAllAsmPrinters();
273
274 if (!FlatOut && OutputFileOpt == "-") {
275 llvm::errs() << "error: cannot emit to standard output without --flat\n";
276 return 1;
277 }
278
279 if (InputFiles.size() > 1 && FlatOut && !OutputFileOpt.empty()) {
280 llvm::errs() << "error: cannot use -o with multiple inputs in flat mode\n";
281 return 1;
282 }
283
284 for (const auto &Arch : ArchFlags)
285 if (Arch != "*" && Arch != "all" &&
286 !llvm::object::MachOObjectFile::isValidArch(Arch)) {
287 llvm::errs() << "error: Unsupported cpu architecture: '" << Arch << "'\n";
288 exitDsymutil(1);
289 }
290
291 for (auto &InputFile : InputFiles) {
292 // Dump the symbol table for each input file and requested arch
293 if (DumpStab) {
294 if (!dumpStab(InputFile, ArchFlags, OsoPrependPath))
295 exitDsymutil(1);
296 continue;
297 }
298
299 auto DebugMapPtrsOrErr = parseDebugMap(InputFile, ArchFlags, OsoPrependPath,
300 Verbose, InputIsYAMLDebugMap);
301
302 if (auto EC = DebugMapPtrsOrErr.getError()) {
303 llvm::errs() << "error: cannot parse the debug map for \"" << InputFile
304 << "\": " << EC.message() << '\n';
305 exitDsymutil(1);
306 }
307
308 if (DebugMapPtrsOrErr->empty()) {
309 llvm::errs() << "error: no architecture to link\n";
310 exitDsymutil(1);
311 }
312
313 // If there is more than one link to execute, we need to generate
314 // temporary files.
315 bool NeedsTempFiles = !DumpDebugMap && (*DebugMapPtrsOrErr).size() != 1;
316 llvm::SmallVector<MachOUtils::ArchAndFilename, 4> TempFiles;
317 for (auto &Map : *DebugMapPtrsOrErr) {
318 if (Verbose || DumpDebugMap)
319 Map->print(llvm::outs());
320
321 if (DumpDebugMap)
322 continue;
323
324 if (Map->begin() == Map->end())
325 llvm::errs() << "warning: no debug symbols in executable (-arch "
326 << MachOUtils::getArchName(Map->getTriple().getArchName())
327 << ")\n";
328
329 std::string OutputFile = getOutputFileName(InputFile, NeedsTempFiles);
330 if (OutputFile.empty() || !linkDwarf(OutputFile, *Map, Options))
331 exitDsymutil(1);
332
333 if (NeedsTempFiles)
334 TempFiles.emplace_back(Map->getTriple().getArchName().str(),
335 OutputFile);
336 }
337
338 if (NeedsTempFiles &&
339 !MachOUtils::generateUniversalBinary(
340 TempFiles, getOutputFileName(InputFile), Options, SDKPath))
341 exitDsymutil(1);
342 }
343
344 exitDsymutil(0);
345 }
346