1//===-- gsymutil.cpp - GSYM dumping and creation utility for llvm ---------===//
2//
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
6//
7//===----------------------------------------------------------------------===//
8
9#include "llvm/ADT/STLExtras.h"
10#include "llvm/DebugInfo/DIContext.h"
11#include "llvm/DebugInfo/DWARF/DWARFContext.h"
12#include "llvm/Object/Archive.h"
13#include "llvm/Object/ELFObjectFile.h"
14#include "llvm/Object/MachOUniversal.h"
15#include "llvm/Object/ObjectFile.h"
16#include "llvm/Option/ArgList.h"
17#include "llvm/Option/Option.h"
18#include "llvm/Support/CommandLine.h"
19#include "llvm/Support/Debug.h"
20#include "llvm/Support/Driver.h"
21#include "llvm/Support/Error.h"
22#include "llvm/Support/FileSystem.h"
23#include "llvm/Support/Format.h"
24#include "llvm/Support/JSON.h"
25#include "llvm/Support/ManagedStatic.h"
26#include "llvm/Support/MathExtras.h"
27#include "llvm/Support/MemoryBuffer.h"
28#include "llvm/Support/PrettyStackTrace.h"
29#include "llvm/Support/Regex.h"
30#include "llvm/Support/Signals.h"
31#include "llvm/Support/TargetSelect.h"
32#include "llvm/Support/raw_ostream.h"
33#include "llvm/TargetParser/Triple.h"
34#include <algorithm>
35#include <cstring>
36#include <inttypes.h>
37#include <iostream>
38#include <optional>
39#include <string>
40#include <system_error>
41#include <vector>
42
43#include "llvm/DebugInfo/GSYM/CallSiteInfo.h"
44#include "llvm/DebugInfo/GSYM/DwarfTransformer.h"
45#include "llvm/DebugInfo/GSYM/FunctionInfo.h"
46#include "llvm/DebugInfo/GSYM/GsymCreator.h"
47#include "llvm/DebugInfo/GSYM/GsymCreatorV1.h"
48#include "llvm/DebugInfo/GSYM/GsymCreatorV2.h"
49#include "llvm/DebugInfo/GSYM/GsymReader.h"
50#include "llvm/DebugInfo/GSYM/Header.h"
51#include "llvm/DebugInfo/GSYM/HeaderV2.h"
52#include "llvm/DebugInfo/GSYM/InlineInfo.h"
53#include "llvm/DebugInfo/GSYM/LookupResult.h"
54#include "llvm/DebugInfo/GSYM/ObjectFileTransformer.h"
55#include "llvm/DebugInfo/GSYM/OutputAggregator.h"
56
57using namespace llvm;
58using namespace gsym;
59using namespace object;
60
61/// @}
62/// Command line options.
63/// @{
64
65using namespace llvm::opt;
66enum ID {
67 OPT_INVALID = 0, // This is not an option ID.
68#define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__),
69#include "Opts.inc"
70#undef OPTION
71};
72
73#define OPTTABLE_CODE
74#include "Opts.inc"
75
76class GSYMUtilOptTable : public llvm::opt::OptTable {
77public:
78 GSYMUtilOptTable() : OptTable(optionTables()) {
79 setGroupedShortOptions(true);
80 }
81};
82
83static bool Verbose;
84static std::vector<std::string> InputFilenames;
85static std::string ConvertFilename;
86static std::string SymtabFilename;
87static std::vector<std::string> ArchFilters;
88static std::string OutputFilename;
89static std::string JsonSummaryFile;
90static bool Verify;
91static bool BenchmarkReader;
92static uint32_t BenchmarkStart;
93static uint32_t BenchmarkStride;
94static unsigned NumThreads;
95static uint64_t SegmentSize;
96static bool Quiet;
97static std::vector<uint64_t> LookupAddresses;
98static bool LookupAddressesFromStdin;
99static bool UseMergedFunctions = false;
100static bool LoadDwarfCallSites = false;
101static std::string CallSiteYamlPath;
102static std::vector<std::string> MergedFunctionsFilters;
103// Default output version. Can be overridden by --output-version.
104static uint32_t OutputVersion = Header::getVersion();
105static bool ShowStatistics;
106static GsymReader::StatisticsFormat StatisticsFormat;
107
108static void parseArgs(int argc, char **argv) {
109 GSYMUtilOptTable Tbl;
110 llvm::StringRef ToolName = argv[0];
111 llvm::BumpPtrAllocator A;
112 llvm::StringSaver Saver{A};
113 llvm::opt::InputArgList Args =
114 Tbl.parseArgs(Argc: argc, Argv: argv, Unknown: OPT_UNKNOWN, Saver, ErrorFn: [&](StringRef Msg) {
115 llvm::errs() << Msg << '\n';
116 std::exit(status: 1);
117 });
118 if (Args.hasArg(Ids: OPT_help)) {
119 const char *Overview =
120 "A tool for dumping, searching and creating GSYM files.\n\n"
121 "Specify one or more GSYM paths as arguments to dump all of the "
122 "information in each GSYM file.\n"
123 "Specify a single GSYM file along with one or more --lookup options to "
124 "lookup addresses within that GSYM file.\n"
125 "Use the --convert option to specify a file with option --out-file "
126 "option to convert to GSYM format.\n";
127
128 Tbl.printHelp(OS&: llvm::outs(), Usage: "llvm-gsymutil [options] <input GSYM files>",
129 Title: Overview);
130 std::exit(status: 0);
131 }
132 if (Args.hasArg(Ids: OPT_version)) {
133 llvm::outs() << ToolName << '\n';
134 cl::PrintVersionMessage();
135 std::exit(status: 0);
136 }
137
138 Verbose = Args.hasArg(Ids: OPT_verbose);
139
140 for (const llvm::opt::Arg *A : Args.filtered(Ids: OPT_INPUT))
141 InputFilenames.emplace_back(args: A->getValue());
142
143 if (const llvm::opt::Arg *A = Args.getLastArg(Ids: OPT_convert_EQ))
144 ConvertFilename = A->getValue();
145
146 if (const llvm::opt::Arg *A = Args.getLastArg(Ids: OPT_symtab_file_EQ))
147 SymtabFilename = A->getValue();
148
149 for (const llvm::opt::Arg *A : Args.filtered(Ids: OPT_arch_EQ))
150 ArchFilters.emplace_back(args: A->getValue());
151
152 if (const llvm::opt::Arg *A = Args.getLastArg(Ids: OPT_out_file_EQ))
153 OutputFilename = A->getValue();
154
155 if (const llvm::opt::Arg *A = Args.getLastArg(Ids: OPT_json_summary_file_EQ))
156 JsonSummaryFile = A->getValue();
157
158 Verify = Args.hasArg(Ids: OPT_verify);
159 BenchmarkStart = 0;
160 BenchmarkStride = 1;
161 if (Args.hasArg(Ids: OPT_benchmark_reader_all)) {
162 BenchmarkReader = true;
163 } else if (const llvm::opt::Arg *A = Args.getLastArg(Ids: OPT_benchmark_reader)) {
164 BenchmarkReader = true;
165 StringRef S{A->getValue()};
166 if (!S.empty()) {
167 auto [StartStr, StrideStr] = S.split(Separator: ',');
168 if (!llvm::to_integer(S: StartStr, Num&: BenchmarkStart, Base: 0)) {
169 llvm::errs() << ToolName
170 << ": for the --benchmark-reader option: invalid start '"
171 << StartStr << "'\n";
172 std::exit(status: 1);
173 }
174 if (!StrideStr.empty() &&
175 !llvm::to_integer(S: StrideStr, Num&: BenchmarkStride, Base: 0)) {
176 llvm::errs() << ToolName
177 << ": for the --benchmark-reader option: invalid stride '"
178 << StrideStr << "'\n";
179 std::exit(status: 1);
180 }
181 if (BenchmarkStride == 0) {
182 llvm::errs() << ToolName
183 << ": for the --benchmark-reader option: stride must be "
184 "positive\n";
185 std::exit(status: 1);
186 }
187 }
188 }
189
190 if (const llvm::opt::Arg *A = Args.getLastArg(Ids: OPT_num_threads_EQ)) {
191 StringRef S{A->getValue()};
192 if (!llvm::to_integer(S, Num&: NumThreads, Base: 0)) {
193 llvm::errs() << ToolName << ": for the --num-threads option: '" << S
194 << "' value invalid for uint argument!\n";
195 std::exit(status: 1);
196 }
197 }
198
199 if (const llvm::opt::Arg *A = Args.getLastArg(Ids: OPT_segment_size_EQ)) {
200 StringRef S{A->getValue()};
201 if (!llvm::to_integer(S, Num&: SegmentSize, Base: 0)) {
202 llvm::errs() << ToolName << ": for the --segment-size option: '" << S
203 << "' value invalid for uint argument!\n";
204 std::exit(status: 1);
205 }
206 }
207
208 Quiet = Args.hasArg(Ids: OPT_quiet);
209
210 for (const llvm::opt::Arg *A : Args.filtered(Ids: OPT_address_EQ)) {
211 StringRef S{A->getValue()};
212 if (!llvm::to_integer(S, Num&: LookupAddresses.emplace_back(), Base: 0)) {
213 llvm::errs() << ToolName << ": for the --address option: '" << S
214 << "' value invalid for uint argument!\n";
215 std::exit(status: 1);
216 }
217 }
218
219 LookupAddressesFromStdin = Args.hasArg(Ids: OPT_addresses_from_stdin);
220 UseMergedFunctions = Args.hasArg(Ids: OPT_merged_functions);
221
222 if (Args.hasArg(Ids: OPT_callsites_yaml_file_EQ)) {
223 CallSiteYamlPath = Args.getLastArgValue(Id: OPT_callsites_yaml_file_EQ);
224 if (CallSiteYamlPath.empty()) {
225 llvm::errs()
226 << ToolName
227 << ": --callsites-yaml-file option requires a non-empty argument.\n";
228 std::exit(status: 1);
229 }
230 }
231
232 LoadDwarfCallSites = Args.hasArg(Ids: OPT_dwarf_callsites);
233
234 ShowStatistics = Args.hasArg(Ids: OPT_statistics_EQ);
235 if (const llvm::opt::Arg *A = Args.getLastArg(Ids: OPT_statistics_EQ)) {
236 StringRef Val = A->getValue();
237 if (Val == "" || Val == "text")
238 StatisticsFormat = GsymReader::StatisticsFormat::Text;
239 else if (Val == "json")
240 StatisticsFormat = GsymReader::StatisticsFormat::JSON;
241 else if (Val == "pretty-json")
242 StatisticsFormat = GsymReader::StatisticsFormat::PrettyJSON;
243 else {
244 errs() << "error: unknown statistics format '" << Val
245 << "'. Supported formats: text, json, pretty-json\n";
246 std::exit(status: 1);
247 }
248 }
249
250 for (const llvm::opt::Arg *A :
251 Args.filtered(Ids: OPT_merged_functions_filter_EQ)) {
252 MergedFunctionsFilters.push_back(x: A->getValue());
253 // Validate the filter is only used with correct flags
254 if (LookupAddresses.empty() && !LookupAddressesFromStdin) {
255 llvm::errs() << ToolName
256 << ": --merged-functions-filter can only be used with "
257 "--address/--addresses-from-stdin\n";
258 std::exit(status: 1);
259 }
260 if (!UseMergedFunctions) {
261 llvm::errs()
262 << ToolName
263 << ": --merged-functions-filter requires --merged-functions\n";
264 std::exit(status: 1);
265 }
266 }
267
268 if (const llvm::opt::Arg *A = Args.getLastArg(Ids: OPT_output_version_EQ)) {
269 StringRef Val = A->getValue();
270 uint32_t Version;
271 if (Val.getAsInteger(Radix: 10, Result&: Version) || (Version != Header::getVersion() &&
272 Version != HeaderV2::getVersion())) {
273 llvm::errs() << ToolName << ": for the --output-version option: '" << Val
274 << "' is invalid. Use '1' or '2'.\n";
275 std::exit(status: 1);
276 }
277 OutputVersion = Version;
278 }
279}
280
281/// @}
282//===----------------------------------------------------------------------===//
283
284static void error(Error Err) {
285 if (!Err)
286 return;
287 WithColor::error() << toString(E: std::move(Err)) << "\n";
288 exit(status: 1);
289}
290
291static void error(StringRef Prefix, llvm::Error Err) {
292 if (!Err)
293 return;
294 errs() << Prefix << ": " << Err << "\n";
295 consumeError(Err: std::move(Err));
296 exit(status: 1);
297}
298
299static void error(StringRef Prefix, std::error_code EC) {
300 if (!EC)
301 return;
302 errs() << Prefix << ": " << EC.message() << "\n";
303 exit(status: 1);
304}
305
306static uint32_t getCPUType(MachOObjectFile &MachO) {
307 if (MachO.is64Bit())
308 return MachO.getHeader64().cputype;
309 else
310 return MachO.getHeader().cputype;
311}
312
313static std::string getArchitectureName(const ObjectFile &Obj) {
314 if (const auto *MachO = dyn_cast<object::MachOObjectFile>(Val: &Obj)) {
315 Triple ObjTriple(MachO->getArchTriple());
316 return ObjTriple.getArchName().str();
317 }
318
319 Triple ObjTriple(Obj.makeTriple());
320 return ObjTriple.getArchName().str();
321}
322
323/// Return true if the object file has not been filtered by an --arch option.
324static bool filterArch(MachOObjectFile &Obj) {
325 if (ArchFilters.empty())
326 return true;
327
328 Triple ObjTriple(Obj.getArchTriple());
329 StringRef ObjArch = ObjTriple.getArchName();
330
331 for (StringRef Arch : ArchFilters) {
332 // Match name.
333 if (Arch == ObjArch)
334 return true;
335
336 // Match architecture number.
337 unsigned Value;
338 if (!Arch.getAsInteger(Radix: 0, Result&: Value))
339 if (Value == getCPUType(MachO&: Obj))
340 return true;
341 }
342 return false;
343}
344
345/// Determine the virtual address that is considered the base address of an ELF
346/// object file.
347///
348/// The base address of an ELF file is the "p_vaddr" of the first program
349/// header whose "p_type" is PT_LOAD.
350///
351/// \param ELFFile An ELF object file we will search.
352///
353/// \returns A valid image base address if we are able to extract one.
354template <class ELFT>
355static std::optional<uint64_t>
356getImageBaseAddress(const object::ELFFile<ELFT> &ELFFile) {
357 auto PhdrRangeOrErr = ELFFile.program_headers();
358 if (!PhdrRangeOrErr) {
359 consumeError(PhdrRangeOrErr.takeError());
360 return std::nullopt;
361 }
362 for (const typename ELFT::Phdr &Phdr : *PhdrRangeOrErr)
363 if (Phdr.p_type == ELF::PT_LOAD)
364 return (uint64_t)Phdr.p_vaddr;
365 return std::nullopt;
366}
367
368/// Determine the virtual address that is considered the base address of mach-o
369/// object file.
370///
371/// The base address of a mach-o file is the vmaddr of the "__TEXT" segment.
372///
373/// \param MachO A mach-o object file we will search.
374///
375/// \returns A valid image base address if we are able to extract one.
376static std::optional<uint64_t>
377getImageBaseAddress(const object::MachOObjectFile *MachO) {
378 for (const auto &Command : MachO->load_commands()) {
379 if (Command.C.cmd == MachO::LC_SEGMENT) {
380 MachO::segment_command SLC = MachO->getSegmentLoadCommand(L: Command);
381 StringRef SegName = SLC.segname;
382 if (SegName == "__TEXT")
383 return SLC.vmaddr;
384 } else if (Command.C.cmd == MachO::LC_SEGMENT_64) {
385 MachO::segment_command_64 SLC = MachO->getSegment64LoadCommand(L: Command);
386 StringRef SegName = SLC.segname;
387 if (SegName == "__TEXT")
388 return SLC.vmaddr;
389 }
390 }
391 return std::nullopt;
392}
393
394/// Determine the virtual address that is considered the base address of an
395/// object file.
396///
397/// Since GSYM files are used for symbolication, many clients will need to
398/// easily adjust addresses they find in stack traces so the lookups happen
399/// on unslid addresses from the original object file. If the base address of
400/// a GSYM file is set to the base address of the image, then this address
401/// adjusting is much easier.
402///
403/// \param Obj An object file we will search.
404///
405/// \returns A valid image base address if we are able to extract one.
406static std::optional<uint64_t> getImageBaseAddress(object::ObjectFile &Obj) {
407 if (const auto *MachO = dyn_cast<object::MachOObjectFile>(Val: &Obj))
408 return getImageBaseAddress(MachO);
409 else if (const auto *ELFObj = dyn_cast<object::ELF32LEObjectFile>(Val: &Obj))
410 return getImageBaseAddress(ELFFile: ELFObj->getELFFile());
411 else if (const auto *ELFObj = dyn_cast<object::ELF32BEObjectFile>(Val: &Obj))
412 return getImageBaseAddress(ELFFile: ELFObj->getELFFile());
413 else if (const auto *ELFObj = dyn_cast<object::ELF64LEObjectFile>(Val: &Obj))
414 return getImageBaseAddress(ELFFile: ELFObj->getELFFile());
415 else if (const auto *ELFObj = dyn_cast<object::ELF64BEObjectFile>(Val: &Obj))
416 return getImageBaseAddress(ELFFile: ELFObj->getELFFile());
417 return std::nullopt;
418}
419
420static Expected<ObjectFile *>
421resolveSymtabObject(StringRef ArchName, Binary *SymtabBinary,
422 StringRef SymtabPath,
423 std::unique_ptr<ObjectFile> &OwnedSymtabObj) {
424 if (!SymtabBinary)
425 return nullptr;
426
427 if (auto *SymtabObj = dyn_cast<ObjectFile>(Val: SymtabBinary)) {
428 std::string SymtabArchName = getArchitectureName(Obj: *SymtabObj);
429 if (SymtabArchName != ArchName)
430 return createStringError(EC: std::errc::invalid_argument,
431 Fmt: "architecture mismatch: input file is %s but "
432 "symbol table file '%s' is %s",
433 Vals: ArchName.str().c_str(), Vals: SymtabPath.str().c_str(),
434 Vals: SymtabArchName.c_str());
435
436 return SymtabObj;
437 }
438
439 if (auto *SymtabFat = dyn_cast<MachOUniversalBinary>(Val: SymtabBinary)) {
440 auto SymtabObjOrErr = SymtabFat->getMachOObjectForArch(ArchName);
441 if (!SymtabObjOrErr) {
442 consumeError(Err: SymtabObjOrErr.takeError());
443 return createStringError(
444 EC: std::errc::invalid_argument,
445 Fmt: "symbol table file '%s' does not contain architecture '%s'",
446 Vals: SymtabPath.str().c_str(), Vals: ArchName.str().c_str());
447 }
448
449 OwnedSymtabObj = std::move(*SymtabObjOrErr);
450 return OwnedSymtabObj.get();
451 }
452
453 return createStringError(EC: std::errc::invalid_argument,
454 Fmt: "symbol table file '%s' is not a valid object file",
455 Vals: SymtabPath.str().c_str());
456}
457
458static llvm::Error handleObjectFile(ObjectFile &Obj, ObjectFile *SymtabObj,
459 StringRef SymtabPath,
460 const std::string &OutFile,
461 OutputAggregator &Out) {
462 auto ThreadCount =
463 NumThreads > 0 ? NumThreads : std::thread::hardware_concurrency();
464
465 std::unique_ptr<GsymCreator> GsymPtr;
466 switch (OutputVersion) {
467 case Header::getVersion():
468 GsymPtr = std::make_unique<GsymCreatorV1>();
469 break;
470 case HeaderV2::getVersion():
471 GsymPtr = std::make_unique<GsymCreatorV2>();
472 break;
473 default:
474 return createStringError(EC: std::errc::invalid_argument,
475 Fmt: "invalid --output-version option");
476 }
477 GsymCreator &Gsym = *GsymPtr;
478
479 // See if we can figure out the base address for a given object file, and if
480 // we can, then set the base address to use to this value. This will ease
481 // symbolication since clients can slide the GSYM lookup addresses by using
482 // the load bias of the shared library.
483 if (auto ImageBaseAddr = getImageBaseAddress(Obj))
484 Gsym.setBaseAddress(*ImageBaseAddr);
485
486 // We need to know where the valid sections are that contain instructions.
487 // See header documentation for DWARFTransformer::SetValidTextRanges() for
488 // defails.
489 AddressRanges TextRanges;
490 for (const object::SectionRef &Sect : Obj.sections()) {
491 if (!Sect.isText())
492 continue;
493 const uint64_t Size = Sect.getSize();
494 if (Size == 0)
495 continue;
496 const uint64_t StartAddr = Sect.getAddress();
497 TextRanges.insert(Range: AddressRange(StartAddr, StartAddr + Size));
498 }
499
500 // Make sure there is DWARF to convert first.
501 std::unique_ptr<DWARFContext> DICtx = DWARFContext::create(
502 Obj,
503 /*RelocAction=*/DWARFContext::ProcessDebugRelocations::Process,
504 L: nullptr,
505 /*DWPName=*/"",
506 /*RecoverableErrorHandler=*/WithColor::defaultErrorHandler,
507 /*WarningHandler=*/WithColor::defaultWarningHandler,
508 /*ThreadSafe*/true);
509 if (!DICtx)
510 return createStringError(EC: std::errc::invalid_argument,
511 Fmt: "unable to create DWARF context");
512
513 // Make a DWARF transformer object and populate the ranges of the code
514 // so we don't end up adding invalid functions to GSYM data.
515 bool IsMachO = dyn_cast<object::MachOObjectFile>(Val: &Obj) != nullptr;
516
517 DwarfTransformer DT(*DICtx, Gsym, LoadDwarfCallSites, IsMachO);
518 if (!TextRanges.empty())
519 Gsym.SetValidTextRanges(TextRanges);
520
521 // Convert all DWARF to GSYM.
522 if (auto Err = DT.convert(NumThreads: ThreadCount, OS&: Out))
523 return Err;
524
525 // If enabled, merge functions with identical address ranges as merged
526 // functions in the first FunctionInfo with that address range. Do this right
527 // after loading the DWARF data so we don't have to deal with functions from
528 // the symbol table.
529 if (UseMergedFunctions)
530 Gsym.prepareMergedFunctions(Out);
531
532 // Get the UUID and convert symbol table to GSYM.
533 if (SymtabObj) {
534 Out << "Using symbol table file: " << SymtabPath << "\n";
535 if (auto Err = ObjectFileTransformer::convert(Obj: *SymtabObj, Output&: Out, Gsym))
536 return Err;
537 } else if (auto Err = ObjectFileTransformer::convert(Obj, Output&: Out, Gsym)) {
538 return Err;
539 }
540
541 // If any call site YAML files were specified, load them now.
542 if (!CallSiteYamlPath.empty())
543 if (auto Err = Gsym.loadCallSitesFromYAML(YAMLFile: CallSiteYamlPath))
544 return Err;
545
546 // Finalize the GSYM to make it ready to save to disk. This will remove
547 // duplicate FunctionInfo entries where we might have found an entry from
548 // debug info and also a symbol table entry from the object file.
549 if (auto Err = Gsym.finalize(OS&: Out))
550 return Err;
551
552 // Save the GSYM file to disk.
553 llvm::endianness Endian = Obj.makeTriple().isLittleEndian()
554 ? llvm::endianness::little
555 : llvm::endianness::big;
556
557 std::optional<uint64_t> OptSegmentSize;
558 if (SegmentSize > 0)
559 OptSegmentSize = SegmentSize;
560 if (auto Err = Gsym.save(Path: OutFile, ByteOrder: Endian, SegmentSize: OptSegmentSize))
561 return Err;
562
563 // Verify the DWARF if requested. This will ensure all the info in the DWARF
564 // can be looked up in the GSYM and that all lookups get matching data.
565 if (Verify) {
566 if (auto Err = DT.verify(GsymPath: OutFile, OS&: Out))
567 return Err;
568 }
569
570 return Error::success();
571}
572
573static llvm::Error handleBuffer(StringRef Filename, MemoryBufferRef Buffer,
574 Binary *SymtabBinary, StringRef SymtabPath,
575 const std::string &OutFile,
576 OutputAggregator &Out) {
577 Expected<std::unique_ptr<Binary>> BinOrErr = object::createBinary(Source: Buffer);
578 error(Prefix: Filename, EC: errorToErrorCode(Err: BinOrErr.takeError()));
579
580 if (auto *Obj = dyn_cast<ObjectFile>(Val: BinOrErr->get())) {
581 std::string ArchName = getArchitectureName(Obj: *Obj);
582 std::unique_ptr<ObjectFile> OwnedSymtabObj;
583 auto SymtabObjOrErr =
584 resolveSymtabObject(ArchName, SymtabBinary, SymtabPath, OwnedSymtabObj);
585 if (!SymtabObjOrErr)
586 return SymtabObjOrErr.takeError();
587
588 outs() << "Output file (" << ArchName << "): " << OutFile << "\n";
589 if (auto Err =
590 handleObjectFile(Obj&: *Obj, SymtabObj: *SymtabObjOrErr, SymtabPath, OutFile, Out))
591 return Err;
592 } else if (auto *Fat = dyn_cast<MachOUniversalBinary>(Val: BinOrErr->get())) {
593 // Iterate over all contained architectures and filter out any that were
594 // not specified with the "--arch <arch>" option. If the --arch option was
595 // not specified on the command line, we will process all architectures.
596 std::vector<std::unique_ptr<MachOObjectFile>> FilterObjs;
597 for (auto &ObjForArch : Fat->objects()) {
598 auto MachOOrErr = ObjForArch.getAsObjectFile();
599 if (!MachOOrErr) {
600 error(Prefix: Filename, Err: MachOOrErr.takeError());
601 continue;
602 }
603
604 std::unique_ptr<MachOObjectFile> Obj = std::move(*MachOOrErr);
605 if (filterArch(Obj&: *Obj))
606 FilterObjs.emplace_back(args: std::move(Obj));
607 }
608 if (FilterObjs.empty())
609 error(Prefix: Filename, Err: createStringError(EC: std::errc::invalid_argument,
610 Fmt: "no matching architectures found"));
611
612 // Now handle each architecture we need to convert.
613 bool MultipleArchitecturesSelected = FilterObjs.size() > 1;
614 if (MultipleArchitecturesSelected && SymtabBinary &&
615 isa<ObjectFile>(Val: SymtabBinary))
616 return createStringError(
617 EC: std::errc::invalid_argument,
618 Fmt: "symbol table file '%s' is not a universal binary, but the input "
619 "contains multiple architectures; use --arch to select a single "
620 "architecture",
621 Vals: SymtabPath.str().c_str());
622
623 for (auto &Obj : FilterObjs) {
624 std::string ArchName = getArchitectureName(Obj: *Obj);
625 std::unique_ptr<ObjectFile> OwnedSymtabObj;
626 auto SymtabObjOrErr = resolveSymtabObject(ArchName, SymtabBinary,
627 SymtabPath, OwnedSymtabObj);
628 if (!SymtabObjOrErr)
629 return SymtabObjOrErr.takeError();
630
631 std::string ArchOutFile(OutFile);
632 // If we are only handling a single architecture, then we will use the
633 // normal output file. If we are handling multiple architectures append
634 // the architecture name to the end of the out file path so that we
635 // don't overwrite the previous architecture's gsym file.
636 if (MultipleArchitecturesSelected) {
637 ArchOutFile.append(n: 1, c: '.');
638 ArchOutFile.append(str: ArchName);
639 }
640 outs() << "Output file (" << ArchName << "): " << ArchOutFile << "\n";
641 if (auto Err = handleObjectFile(Obj&: *Obj, SymtabObj: *SymtabObjOrErr, SymtabPath,
642 OutFile: ArchOutFile, Out))
643 return Err;
644 }
645 }
646 return Error::success();
647}
648
649static llvm::Error handleFileConversionToGSYM(StringRef Filename,
650 const std::string &OutFile,
651 OutputAggregator &Out) {
652 ErrorOr<std::unique_ptr<MemoryBuffer>> BuffOrErr =
653 MemoryBuffer::getFileOrSTDIN(Filename, /*IsText=*/true);
654 error(Prefix: Filename, EC: BuffOrErr.getError());
655 std::unique_ptr<MemoryBuffer> Buffer = std::move(BuffOrErr.get());
656
657 std::unique_ptr<MemoryBuffer> SymtabBuffer;
658 std::unique_ptr<Binary> SymtabBinary;
659 if (!SymtabFilename.empty()) {
660 auto SymtabBufOrErr =
661 MemoryBuffer::getFile(Filename: SymtabFilename, /*IsText=*/true);
662 if (!SymtabBufOrErr)
663 return createStringError(EC: SymtabBufOrErr.getError(),
664 Fmt: "failed to open symbol table file '%s'",
665 Vals: SymtabFilename.c_str());
666
667 SymtabBuffer = std::move(*SymtabBufOrErr);
668 auto SymtabBinOrErr = object::createBinary(Source: *SymtabBuffer);
669 if (!SymtabBinOrErr)
670 return SymtabBinOrErr.takeError();
671 SymtabBinary = std::move(*SymtabBinOrErr);
672 }
673
674 return handleBuffer(Filename, Buffer: *Buffer, SymtabBinary: SymtabBinary.get(), SymtabPath: SymtabFilename,
675 OutFile, Out);
676}
677
678static llvm::Error convertFileToGSYM(OutputAggregator &Out) {
679 // Expand any .dSYM bundles to the individual object files contained therein.
680 std::vector<std::string> Objects;
681 std::string OutFile = OutputFilename;
682 if (OutFile.empty()) {
683 OutFile = ConvertFilename;
684 OutFile += ".gsym";
685 }
686
687 Out << "Input file: " << ConvertFilename << "\n";
688
689 if (auto DsymObjectsOrErr =
690 MachOObjectFile::findDsymObjectMembers(Path: ConvertFilename)) {
691 if (DsymObjectsOrErr->empty())
692 Objects.push_back(x: ConvertFilename);
693 else
694 llvm::append_range(C&: Objects, R&: *DsymObjectsOrErr);
695 } else {
696 error(Err: DsymObjectsOrErr.takeError());
697 }
698
699 for (StringRef Object : Objects)
700 if (Error Err = handleFileConversionToGSYM(Filename: Object, OutFile, Out))
701 return Err;
702 return Error::success();
703}
704
705static void doLookup(GsymReader &Gsym, uint64_t Addr, raw_ostream &OS) {
706 if (UseMergedFunctions) {
707 if (auto Results = Gsym.lookupAll(Addr)) {
708 // If we have filters, count matching results first
709 size_t NumMatching = Results->size();
710 if (!MergedFunctionsFilters.empty()) {
711 NumMatching = 0;
712 for (const auto &Result : *Results) {
713 bool Matches = false;
714 for (const auto &Filter : MergedFunctionsFilters) {
715 Regex Pattern(Filter);
716 if (Pattern.match(String: Result.FuncName)) {
717 Matches = true;
718 break;
719 }
720 }
721 if (Matches)
722 NumMatching++;
723 }
724 }
725
726 OS << "Found " << NumMatching << " function"
727 << (NumMatching != 1 ? "s" : "") << " at address " << HEX64(Addr)
728 << ":\n";
729
730 for (size_t i = 0; i < Results->size(); ++i) {
731 // Skip if doesn't match any filter
732 if (!MergedFunctionsFilters.empty()) {
733 bool Matches = false;
734 for (const auto &Filter : MergedFunctionsFilters) {
735 Regex Pattern(Filter);
736 if (Pattern.match(String: Results->at(n: i).FuncName)) {
737 Matches = true;
738 break;
739 }
740 }
741 if (!Matches)
742 continue;
743 }
744
745 OS << " " << Results->at(n: i);
746
747 if (i != Results->size() - 1)
748 OS << "\n";
749 }
750 } else {
751 if (Verbose)
752 OS << "\nLookupResult for " << HEX64(Addr) << ":\n";
753 OS << HEX64(Addr) << ": ";
754 logAllUnhandledErrors(E: Results.takeError(), OS, ErrorBanner: "error: ");
755 }
756 } else { /* UseMergedFunctions == false */
757 if (auto Result = Gsym.lookup(Addr)) {
758 // If verbose is enabled dump the full function info for the address.
759 if (Verbose) {
760 if (auto FI = Gsym.getFunctionInfo(Addr)) {
761 OS << "FunctionInfo for " << HEX64(Addr) << ":\n";
762 Gsym.dump(OS, FI: *FI);
763 OS << "\nLookupResult for " << HEX64(Addr) << ":\n";
764 }
765 }
766 // Don't print call site info if --merged-functions is not specified.
767 Result->CallSiteFuncRegex.clear();
768 OS << Result.get();
769 } else {
770 if (Verbose)
771 OS << "\nLookupResult for " << HEX64(Addr) << ":\n";
772 OS << HEX64(Addr) << ": ";
773 logAllUnhandledErrors(E: Result.takeError(), OS, ErrorBanner: "error: ");
774 }
775 if (Verbose)
776 OS << "\n";
777 }
778}
779
780static llvm::Error benchmarkReader(StringRef GSYMPath, uint32_t Start,
781 uint32_t Stride) {
782 auto Gsym = GsymReader::openFile(Path: GSYMPath);
783 if (!Gsym)
784 return Gsym.takeError();
785 uint32_t N = (*Gsym)->getNumAddresses();
786 uint32_t NumLookups = 0;
787 for (uint32_t I = Start; I < N; I += Stride) {
788 auto Addr = (*Gsym)->getAddress(Index: I);
789 if (!Addr)
790 return createStringError(EC: std::errc::invalid_argument,
791 Fmt: "failed to extract address[%u]", Vals: I);
792 auto LR = (*Gsym)->lookup(Addr: *Addr);
793 if (!LR)
794 return LR.takeError();
795 ++NumLookups;
796 }
797 outs() << "Benchmarked " << NumLookups << " lookups (out of " << N
798 << " addresses) in \"" << GSYMPath << "\"\n";
799 return Error::success();
800}
801
802int llvm_gsymutil_main(int argc, char **argv, const llvm::ToolContext &) {
803 // Print a stack trace if we signal out.
804 sys::PrintStackTraceOnErrorSignal(Argv0: argv[0]);
805 PrettyStackTraceProgram X(argc, argv);
806 llvm_shutdown_obj Y; // Call llvm_shutdown() on exit.
807
808 llvm::InitializeAllTargets();
809
810 parseArgs(argc, argv);
811
812 raw_ostream &OS = outs();
813
814 if (BenchmarkReader) {
815 for (const auto &GSYMPath : InputFilenames)
816 if (auto Err = benchmarkReader(GSYMPath, Start: BenchmarkStart, Stride: BenchmarkStride))
817 error(Prefix: "Benchmark failed: ", Err: std::move(Err));
818 return EXIT_SUCCESS;
819 }
820
821 OutputAggregator Aggregation(&OS, Quiet);
822 if (!ConvertFilename.empty()) {
823 // Convert DWARF to GSYM
824 if (!InputFilenames.empty()) {
825 OS << "error: no input files can be specified when using the --convert "
826 "option.\n";
827 return 1;
828 }
829 // Call error() if we have an error and it will exit with a status of 1
830 if (auto Err = convertFileToGSYM(Out&: Aggregation))
831 error(Prefix: "DWARF conversion failed: ", Err: std::move(Err));
832
833 // Report the errors from aggregator:
834 Aggregation.EnumerateResults(handleCounts: [&](StringRef category, unsigned count) {
835 OS << category << " occurred " << count << " time(s)\n";
836 });
837 if (!JsonSummaryFile.empty()) {
838 std::error_code EC;
839 raw_fd_ostream JsonStream(JsonSummaryFile, EC, sys::fs::OF_Text);
840 if (EC) {
841 OS << "error opening aggregate error json file '" << JsonSummaryFile
842 << "' for writing: " << EC.message() << '\n';
843 return 1;
844 }
845
846 llvm::json::Object Categories;
847 uint64_t ErrorCount = 0;
848 Aggregation.EnumerateResults(handleCounts: [&](StringRef Category, unsigned Count) {
849 llvm::json::Object Val;
850 Val.try_emplace(K: "count", Args&: Count);
851 Categories.try_emplace(K: Category, Args: std::move(Val));
852 ErrorCount += Count;
853 });
854 llvm::json::Object RootNode;
855 RootNode.try_emplace(K: "error-categories", Args: std::move(Categories));
856 RootNode.try_emplace(K: "error-count", Args&: ErrorCount);
857
858 JsonStream << llvm::json::Value(std::move(RootNode));
859 }
860 return 0;
861 }
862
863 if (LookupAddressesFromStdin) {
864 if (!LookupAddresses.empty() || !InputFilenames.empty()) {
865 OS << "error: no input files or addresses can be specified when using "
866 "the --addresses-from-stdin "
867 "option.\n";
868 return 1;
869 }
870
871 std::string InputLine;
872 std::string CurrentGSYMPath;
873 std::unique_ptr<GsymReader> CurrentGsym;
874
875 while (std::getline(is&: std::cin, str&: InputLine)) {
876 // Strip newline characters.
877 std::string StrippedInputLine(InputLine);
878 llvm::erase_if(C&: StrippedInputLine,
879 P: [](char c) { return c == '\r' || c == '\n'; });
880
881 StringRef AddrStr, GSYMPath;
882 std::tie(args&: AddrStr, args&: GSYMPath) =
883 llvm::StringRef{StrippedInputLine}.split(Separator: ' ');
884
885 if (GSYMPath != CurrentGSYMPath) {
886 auto GsymOrErr = GsymReader::openFile(Path: GSYMPath);
887 if (!GsymOrErr)
888 error(Prefix: GSYMPath, Err: GsymOrErr.takeError());
889 CurrentGsym = std::move(*GsymOrErr);
890 CurrentGSYMPath = GSYMPath;
891 }
892
893 uint64_t Addr;
894 if (AddrStr.getAsInteger(Radix: 0, Result&: Addr)) {
895 OS << "error: invalid address " << AddrStr
896 << ", expected: Address GsymFile.\n";
897 return 1;
898 }
899
900 doLookup(Gsym&: *CurrentGsym, Addr, OS);
901
902 OS << "\n";
903 OS.flush();
904 }
905
906 return EXIT_SUCCESS;
907 }
908
909 // Dump or access data inside GSYM files
910 for (const auto &GSYMPath : InputFilenames) {
911 auto Gsym = GsymReader::openFile(Path: GSYMPath);
912 if (!Gsym)
913 error(Prefix: GSYMPath, Err: Gsym.takeError());
914
915 if (ShowStatistics) {
916 (*Gsym)->dumpStatistics(OS, Format: StatisticsFormat, GSYMPath);
917 continue;
918 }
919
920 if (LookupAddresses.empty()) {
921 (*Gsym)->dump(OS&: outs());
922 continue;
923 }
924
925 // Lookup an address in a GSYM file and print any matches.
926 OS << "Looking up addresses in \"" << GSYMPath << "\":\n";
927 for (auto Addr : LookupAddresses) {
928 doLookup(Gsym&: **Gsym, Addr, OS);
929 }
930 }
931 return EXIT_SUCCESS;
932}
933