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/Format.h"
21#include "llvm/Support/JSON.h"
22#include "llvm/Support/LLVMDriver.h"
23#include "llvm/Support/ManagedStatic.h"
24#include "llvm/Support/MemoryBuffer.h"
25#include "llvm/Support/PrettyStackTrace.h"
26#include "llvm/Support/Regex.h"
27#include "llvm/Support/Signals.h"
28#include "llvm/Support/TargetSelect.h"
29#include "llvm/Support/raw_ostream.h"
30#include "llvm/TargetParser/Triple.h"
31#include <algorithm>
32#include <cstring>
33#include <inttypes.h>
34#include <iostream>
35#include <optional>
36#include <string>
37#include <system_error>
38#include <vector>
39
40#include "llvm/DebugInfo/GSYM/DwarfTransformer.h"
41#include "llvm/DebugInfo/GSYM/FunctionInfo.h"
42#include "llvm/DebugInfo/GSYM/GsymCreator.h"
43#include "llvm/DebugInfo/GSYM/GsymReader.h"
44#include "llvm/DebugInfo/GSYM/InlineInfo.h"
45#include "llvm/DebugInfo/GSYM/LookupResult.h"
46#include "llvm/DebugInfo/GSYM/ObjectFileTransformer.h"
47#include "llvm/DebugInfo/GSYM/OutputAggregator.h"
48#include <optional>
49
50using namespace llvm;
51using namespace gsym;
52using namespace object;
53
54/// @}
55/// Command line options.
56/// @{
57
58using namespace llvm::opt;
59enum ID {
60 OPT_INVALID = 0, // This is not an option ID.
61#define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__),
62#include "Opts.inc"
63#undef OPTION
64};
65
66#define PREFIX(NAME, VALUE) \
67 constexpr llvm::StringLiteral NAME##_init[] = VALUE; \
68 constexpr llvm::ArrayRef<llvm::StringLiteral> NAME( \
69 NAME##_init, std::size(NAME##_init) - 1);
70#include "Opts.inc"
71#undef PREFIX
72
73const opt::OptTable::Info InfoTable[] = {
74#define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__),
75#include "Opts.inc"
76#undef OPTION
77};
78
79class GSYMUtilOptTable : public llvm::opt::GenericOptTable {
80public:
81 GSYMUtilOptTable() : GenericOptTable(InfoTable) {
82 setGroupedShortOptions(true);
83 }
84};
85
86static bool Verbose;
87static std::vector<std::string> InputFilenames;
88static std::string ConvertFilename;
89static std::vector<std::string> ArchFilters;
90static std::string OutputFilename;
91static std::string JsonSummaryFile;
92static bool Verify;
93static unsigned NumThreads;
94static uint64_t SegmentSize;
95static bool Quiet;
96static std::vector<uint64_t> LookupAddresses;
97static bool LookupAddressesFromStdin;
98
99static void parseArgs(int argc, char **argv) {
100 GSYMUtilOptTable Tbl;
101 llvm::StringRef ToolName = argv[0];
102 llvm::BumpPtrAllocator A;
103 llvm::StringSaver Saver{A};
104 llvm::opt::InputArgList Args =
105 Tbl.parseArgs(Argc: argc, Argv: argv, Unknown: OPT_UNKNOWN, Saver, ErrorFn: [&](StringRef Msg) {
106 llvm::errs() << Msg << '\n';
107 std::exit(status: 1);
108 });
109 if (Args.hasArg(OPT_help)) {
110 const char *Overview =
111 "A tool for dumping, searching and creating GSYM files.\n\n"
112 "Specify one or more GSYM paths as arguments to dump all of the "
113 "information in each GSYM file.\n"
114 "Specify a single GSYM file along with one or more --lookup options to "
115 "lookup addresses within that GSYM file.\n"
116 "Use the --convert option to specify a file with option --out-file "
117 "option to convert to GSYM format.\n";
118
119 Tbl.printHelp(OS&: llvm::outs(), Usage: "llvm-gsymutil [options] <input GSYM files>",
120 Title: Overview);
121 std::exit(status: 0);
122 }
123 if (Args.hasArg(OPT_version)) {
124 llvm::outs() << ToolName << '\n';
125 cl::PrintVersionMessage();
126 std::exit(status: 0);
127 }
128
129 Verbose = Args.hasArg(OPT_verbose);
130
131 for (const llvm::opt::Arg *A : Args.filtered(OPT_INPUT))
132 InputFilenames.emplace_back(A->getValue());
133
134 if (const llvm::opt::Arg *A = Args.getLastArg(OPT_convert_EQ))
135 ConvertFilename = A->getValue();
136
137 for (const llvm::opt::Arg *A : Args.filtered(OPT_arch_EQ))
138 ArchFilters.emplace_back(A->getValue());
139
140 if (const llvm::opt::Arg *A = Args.getLastArg(OPT_out_file_EQ))
141 OutputFilename = A->getValue();
142
143 if (const llvm::opt::Arg *A = Args.getLastArg(OPT_json_summary_file_EQ))
144 JsonSummaryFile = A->getValue();
145
146 Verify = Args.hasArg(OPT_verify);
147
148 if (const llvm::opt::Arg *A = Args.getLastArg(OPT_num_threads_EQ)) {
149 StringRef S{A->getValue()};
150 if (!llvm::to_integer(S, Num&: NumThreads, Base: 0)) {
151 llvm::errs() << ToolName << ": for the --num-threads option: '" << S
152 << "' value invalid for uint argument!\n";
153 std::exit(status: 1);
154 }
155 }
156
157 if (const llvm::opt::Arg *A = Args.getLastArg(OPT_segment_size_EQ)) {
158 StringRef S{A->getValue()};
159 if (!llvm::to_integer(S, Num&: SegmentSize, Base: 0)) {
160 llvm::errs() << ToolName << ": for the --segment-size option: '" << S
161 << "' value invalid for uint argument!\n";
162 std::exit(status: 1);
163 }
164 }
165
166 Quiet = Args.hasArg(OPT_quiet);
167
168 for (const llvm::opt::Arg *A : Args.filtered(OPT_address_EQ)) {
169 StringRef S{A->getValue()};
170 if (!llvm::to_integer(S, LookupAddresses.emplace_back(), 0)) {
171 llvm::errs() << ToolName << ": for the --address option: '" << S
172 << "' value invalid for uint argument!\n";
173 std::exit(1);
174 }
175 }
176
177 LookupAddressesFromStdin = Args.hasArg(OPT_addresses_from_stdin);
178}
179
180/// @}
181//===----------------------------------------------------------------------===//
182
183static void error(Error Err) {
184 if (!Err)
185 return;
186 WithColor::error() << toString(E: std::move(Err)) << "\n";
187 exit(status: 1);
188}
189
190static void error(StringRef Prefix, llvm::Error Err) {
191 if (!Err)
192 return;
193 errs() << Prefix << ": " << Err << "\n";
194 consumeError(Err: std::move(Err));
195 exit(status: 1);
196}
197
198static void error(StringRef Prefix, std::error_code EC) {
199 if (!EC)
200 return;
201 errs() << Prefix << ": " << EC.message() << "\n";
202 exit(status: 1);
203}
204
205static uint32_t getCPUType(MachOObjectFile &MachO) {
206 if (MachO.is64Bit())
207 return MachO.getHeader64().cputype;
208 else
209 return MachO.getHeader().cputype;
210}
211
212/// Return true if the object file has not been filtered by an --arch option.
213static bool filterArch(MachOObjectFile &Obj) {
214 if (ArchFilters.empty())
215 return true;
216
217 Triple ObjTriple(Obj.getArchTriple());
218 StringRef ObjArch = ObjTriple.getArchName();
219
220 for (StringRef Arch : ArchFilters) {
221 // Match name.
222 if (Arch == ObjArch)
223 return true;
224
225 // Match architecture number.
226 unsigned Value;
227 if (!Arch.getAsInteger(Radix: 0, Result&: Value))
228 if (Value == getCPUType(MachO&: Obj))
229 return true;
230 }
231 return false;
232}
233
234/// Determine the virtual address that is considered the base address of an ELF
235/// object file.
236///
237/// The base address of an ELF file is the "p_vaddr" of the first program
238/// header whose "p_type" is PT_LOAD.
239///
240/// \param ELFFile An ELF object file we will search.
241///
242/// \returns A valid image base address if we are able to extract one.
243template <class ELFT>
244static std::optional<uint64_t>
245getImageBaseAddress(const object::ELFFile<ELFT> &ELFFile) {
246 auto PhdrRangeOrErr = ELFFile.program_headers();
247 if (!PhdrRangeOrErr) {
248 consumeError(PhdrRangeOrErr.takeError());
249 return std::nullopt;
250 }
251 for (const typename ELFT::Phdr &Phdr : *PhdrRangeOrErr)
252 if (Phdr.p_type == ELF::PT_LOAD)
253 return (uint64_t)Phdr.p_vaddr;
254 return std::nullopt;
255}
256
257/// Determine the virtual address that is considered the base address of mach-o
258/// object file.
259///
260/// The base address of a mach-o file is the vmaddr of the "__TEXT" segment.
261///
262/// \param MachO A mach-o object file we will search.
263///
264/// \returns A valid image base address if we are able to extract one.
265static std::optional<uint64_t>
266getImageBaseAddress(const object::MachOObjectFile *MachO) {
267 for (const auto &Command : MachO->load_commands()) {
268 if (Command.C.cmd == MachO::LC_SEGMENT) {
269 MachO::segment_command SLC = MachO->getSegmentLoadCommand(L: Command);
270 StringRef SegName = SLC.segname;
271 if (SegName == "__TEXT")
272 return SLC.vmaddr;
273 } else if (Command.C.cmd == MachO::LC_SEGMENT_64) {
274 MachO::segment_command_64 SLC = MachO->getSegment64LoadCommand(L: Command);
275 StringRef SegName = SLC.segname;
276 if (SegName == "__TEXT")
277 return SLC.vmaddr;
278 }
279 }
280 return std::nullopt;
281}
282
283/// Determine the virtual address that is considered the base address of an
284/// object file.
285///
286/// Since GSYM files are used for symbolication, many clients will need to
287/// easily adjust addresses they find in stack traces so the lookups happen
288/// on unslid addresses from the original object file. If the base address of
289/// a GSYM file is set to the base address of the image, then this address
290/// adjusting is much easier.
291///
292/// \param Obj An object file we will search.
293///
294/// \returns A valid image base address if we are able to extract one.
295static std::optional<uint64_t> getImageBaseAddress(object::ObjectFile &Obj) {
296 if (const auto *MachO = dyn_cast<object::MachOObjectFile>(Val: &Obj))
297 return getImageBaseAddress(MachO);
298 else if (const auto *ELFObj = dyn_cast<object::ELF32LEObjectFile>(Val: &Obj))
299 return getImageBaseAddress(ELFFile: ELFObj->getELFFile());
300 else if (const auto *ELFObj = dyn_cast<object::ELF32BEObjectFile>(Val: &Obj))
301 return getImageBaseAddress(ELFFile: ELFObj->getELFFile());
302 else if (const auto *ELFObj = dyn_cast<object::ELF64LEObjectFile>(Val: &Obj))
303 return getImageBaseAddress(ELFFile: ELFObj->getELFFile());
304 else if (const auto *ELFObj = dyn_cast<object::ELF64BEObjectFile>(Val: &Obj))
305 return getImageBaseAddress(ELFFile: ELFObj->getELFFile());
306 return std::nullopt;
307}
308
309static llvm::Error handleObjectFile(ObjectFile &Obj, const std::string &OutFile,
310 OutputAggregator &Out) {
311 auto ThreadCount =
312 NumThreads > 0 ? NumThreads : std::thread::hardware_concurrency();
313
314 GsymCreator Gsym(Quiet);
315
316 // See if we can figure out the base address for a given object file, and if
317 // we can, then set the base address to use to this value. This will ease
318 // symbolication since clients can slide the GSYM lookup addresses by using
319 // the load bias of the shared library.
320 if (auto ImageBaseAddr = getImageBaseAddress(Obj))
321 Gsym.setBaseAddress(*ImageBaseAddr);
322
323 // We need to know where the valid sections are that contain instructions.
324 // See header documentation for DWARFTransformer::SetValidTextRanges() for
325 // defails.
326 AddressRanges TextRanges;
327 for (const object::SectionRef &Sect : Obj.sections()) {
328 if (!Sect.isText())
329 continue;
330 const uint64_t Size = Sect.getSize();
331 if (Size == 0)
332 continue;
333 const uint64_t StartAddr = Sect.getAddress();
334 TextRanges.insert(Range: AddressRange(StartAddr, StartAddr + Size));
335 }
336
337 // Make sure there is DWARF to convert first.
338 std::unique_ptr<DWARFContext> DICtx = DWARFContext::create(
339 Obj,
340 /*RelocAction=*/DWARFContext::ProcessDebugRelocations::Process,
341 L: nullptr,
342 /*DWPName=*/"",
343 /*RecoverableErrorHandler=*/WithColor::defaultErrorHandler,
344 /*WarningHandler=*/WithColor::defaultWarningHandler,
345 /*ThreadSafe*/true);
346 if (!DICtx)
347 return createStringError(EC: std::errc::invalid_argument,
348 Fmt: "unable to create DWARF context");
349
350 // Make a DWARF transformer object and populate the ranges of the code
351 // so we don't end up adding invalid functions to GSYM data.
352 DwarfTransformer DT(*DICtx, Gsym);
353 if (!TextRanges.empty())
354 Gsym.SetValidTextRanges(TextRanges);
355
356 // Convert all DWARF to GSYM.
357 if (auto Err = DT.convert(NumThreads: ThreadCount, OS&: Out))
358 return Err;
359
360 // Get the UUID and convert symbol table to GSYM.
361 if (auto Err = ObjectFileTransformer::convert(Obj, Output&: Out, Gsym))
362 return Err;
363
364 // Finalize the GSYM to make it ready to save to disk. This will remove
365 // duplicate FunctionInfo entries where we might have found an entry from
366 // debug info and also a symbol table entry from the object file.
367 if (auto Err = Gsym.finalize(OS&: Out))
368 return Err;
369
370 // Save the GSYM file to disk.
371 llvm::endianness Endian = Obj.makeTriple().isLittleEndian()
372 ? llvm::endianness::little
373 : llvm::endianness::big;
374
375 std::optional<uint64_t> OptSegmentSize;
376 if (SegmentSize > 0)
377 OptSegmentSize = SegmentSize;
378 if (auto Err = Gsym.save(Path: OutFile, ByteOrder: Endian, SegmentSize: OptSegmentSize))
379 return Err;
380
381 // Verify the DWARF if requested. This will ensure all the info in the DWARF
382 // can be looked up in the GSYM and that all lookups get matching data.
383 if (Verify) {
384 if (auto Err = DT.verify(GsymPath: OutFile, OS&: Out))
385 return Err;
386 }
387
388 return Error::success();
389}
390
391static llvm::Error handleBuffer(StringRef Filename, MemoryBufferRef Buffer,
392 const std::string &OutFile,
393 OutputAggregator &Out) {
394 Expected<std::unique_ptr<Binary>> BinOrErr = object::createBinary(Source: Buffer);
395 error(Prefix: Filename, EC: errorToErrorCode(Err: BinOrErr.takeError()));
396
397 if (auto *Obj = dyn_cast<ObjectFile>(Val: BinOrErr->get())) {
398 Triple ObjTriple(Obj->makeTriple());
399 auto ArchName = ObjTriple.getArchName();
400 outs() << "Output file (" << ArchName << "): " << OutFile << "\n";
401 if (auto Err = handleObjectFile(Obj&: *Obj, OutFile, Out))
402 return Err;
403 } else if (auto *Fat = dyn_cast<MachOUniversalBinary>(Val: BinOrErr->get())) {
404 // Iterate over all contained architectures and filter out any that were
405 // not specified with the "--arch <arch>" option. If the --arch option was
406 // not specified on the command line, we will process all architectures.
407 std::vector<std::unique_ptr<MachOObjectFile>> FilterObjs;
408 for (auto &ObjForArch : Fat->objects()) {
409 if (auto MachOOrErr = ObjForArch.getAsObjectFile()) {
410 auto &Obj = **MachOOrErr;
411 if (filterArch(Obj))
412 FilterObjs.emplace_back(args: MachOOrErr->release());
413 } else {
414 error(Prefix: Filename, Err: MachOOrErr.takeError());
415 }
416 }
417 if (FilterObjs.empty())
418 error(Prefix: Filename, Err: createStringError(EC: std::errc::invalid_argument,
419 Fmt: "no matching architectures found"));
420
421 // Now handle each architecture we need to convert.
422 for (auto &Obj : FilterObjs) {
423 Triple ObjTriple(Obj->getArchTriple());
424 auto ArchName = ObjTriple.getArchName();
425 std::string ArchOutFile(OutFile);
426 // If we are only handling a single architecture, then we will use the
427 // normal output file. If we are handling multiple architectures append
428 // the architecture name to the end of the out file path so that we
429 // don't overwrite the previous architecture's gsym file.
430 if (FilterObjs.size() > 1) {
431 ArchOutFile.append(n: 1, c: '.');
432 ArchOutFile.append(str: ArchName.str());
433 }
434 outs() << "Output file (" << ArchName << "): " << ArchOutFile << "\n";
435 if (auto Err = handleObjectFile(Obj&: *Obj, OutFile: ArchOutFile, Out))
436 return Err;
437 }
438 }
439 return Error::success();
440}
441
442static llvm::Error handleFileConversionToGSYM(StringRef Filename,
443 const std::string &OutFile,
444 OutputAggregator &Out) {
445 ErrorOr<std::unique_ptr<MemoryBuffer>> BuffOrErr =
446 MemoryBuffer::getFileOrSTDIN(Filename);
447 error(Prefix: Filename, EC: BuffOrErr.getError());
448 std::unique_ptr<MemoryBuffer> Buffer = std::move(BuffOrErr.get());
449 return handleBuffer(Filename, Buffer: *Buffer, OutFile, Out);
450}
451
452static llvm::Error convertFileToGSYM(OutputAggregator &Out) {
453 // Expand any .dSYM bundles to the individual object files contained therein.
454 std::vector<std::string> Objects;
455 std::string OutFile = OutputFilename;
456 if (OutFile.empty()) {
457 OutFile = ConvertFilename;
458 OutFile += ".gsym";
459 }
460
461 Out << "Input file: " << ConvertFilename << "\n";
462
463 if (auto DsymObjectsOrErr =
464 MachOObjectFile::findDsymObjectMembers(Path: ConvertFilename)) {
465 if (DsymObjectsOrErr->empty())
466 Objects.push_back(x: ConvertFilename);
467 else
468 llvm::append_range(C&: Objects, R&: *DsymObjectsOrErr);
469 } else {
470 error(Err: DsymObjectsOrErr.takeError());
471 }
472
473 for (StringRef Object : Objects)
474 if (Error Err = handleFileConversionToGSYM(Filename: Object, OutFile, Out))
475 return Err;
476 return Error::success();
477}
478
479static void doLookup(GsymReader &Gsym, uint64_t Addr, raw_ostream &OS) {
480 if (auto Result = Gsym.lookup(Addr)) {
481 // If verbose is enabled dump the full function info for the address.
482 if (Verbose) {
483 if (auto FI = Gsym.getFunctionInfo(Addr)) {
484 OS << "FunctionInfo for " << HEX64(Addr) << ":\n";
485 Gsym.dump(OS, FI: *FI);
486 OS << "\nLookupResult for " << HEX64(Addr) << ":\n";
487 }
488 }
489 OS << Result.get();
490 } else {
491 if (Verbose)
492 OS << "\nLookupResult for " << HEX64(Addr) << ":\n";
493 OS << HEX64(Addr) << ": ";
494 logAllUnhandledErrors(E: Result.takeError(), OS, ErrorBanner: "error: ");
495 }
496 if (Verbose)
497 OS << "\n";
498}
499
500int llvm_gsymutil_main(int argc, char **argv, const llvm::ToolContext &) {
501 // Print a stack trace if we signal out.
502 sys::PrintStackTraceOnErrorSignal(Argv0: argv[0]);
503 PrettyStackTraceProgram X(argc, argv);
504 llvm_shutdown_obj Y; // Call llvm_shutdown() on exit.
505
506 llvm::InitializeAllTargets();
507
508 parseArgs(argc, argv);
509
510 raw_ostream &OS = outs();
511
512 OutputAggregator Aggregation(&OS);
513 if (!ConvertFilename.empty()) {
514 // Convert DWARF to GSYM
515 if (!InputFilenames.empty()) {
516 OS << "error: no input files can be specified when using the --convert "
517 "option.\n";
518 return 1;
519 }
520 // Call error() if we have an error and it will exit with a status of 1
521 if (auto Err = convertFileToGSYM(Out&: Aggregation))
522 error(Prefix: "DWARF conversion failed: ", Err: std::move(Err));
523
524 // Report the errors from aggregator:
525 Aggregation.EnumerateResults(handleCounts: [&](StringRef category, unsigned count) {
526 OS << category << " occurred " << count << " time(s)\n";
527 });
528 if (!JsonSummaryFile.empty()) {
529 std::error_code EC;
530 raw_fd_ostream JsonStream(JsonSummaryFile, EC, sys::fs::OF_Text);
531 if (EC) {
532 OS << "error opening aggregate error json file '" << JsonSummaryFile
533 << "' for writing: " << EC.message() << '\n';
534 return 1;
535 }
536
537 llvm::json::Object Categories;
538 uint64_t ErrorCount = 0;
539 Aggregation.EnumerateResults(handleCounts: [&](StringRef Category, unsigned Count) {
540 llvm::json::Object Val;
541 Val.try_emplace(K: "count", Args&: Count);
542 Categories.try_emplace(K: Category, Args: std::move(Val));
543 ErrorCount += Count;
544 });
545 llvm::json::Object RootNode;
546 RootNode.try_emplace(K: "error-categories", Args: std::move(Categories));
547 RootNode.try_emplace(K: "error-count", Args&: ErrorCount);
548
549 JsonStream << llvm::json::Value(std::move(RootNode));
550 }
551 return 0;
552 }
553
554 if (LookupAddressesFromStdin) {
555 if (!LookupAddresses.empty() || !InputFilenames.empty()) {
556 OS << "error: no input files or addresses can be specified when using "
557 "the --addresses-from-stdin "
558 "option.\n";
559 return 1;
560 }
561
562 std::string InputLine;
563 std::string CurrentGSYMPath;
564 std::optional<Expected<GsymReader>> CurrentGsym;
565
566 while (std::getline(is&: std::cin, str&: InputLine)) {
567 // Strip newline characters.
568 std::string StrippedInputLine(InputLine);
569 llvm::erase_if(C&: StrippedInputLine,
570 P: [](char c) { return c == '\r' || c == '\n'; });
571
572 StringRef AddrStr, GSYMPath;
573 std::tie(args&: AddrStr, args&: GSYMPath) =
574 llvm::StringRef{StrippedInputLine}.split(Separator: ' ');
575
576 if (GSYMPath != CurrentGSYMPath) {
577 CurrentGsym = GsymReader::openFile(Path: GSYMPath);
578 if (!*CurrentGsym)
579 error(Prefix: GSYMPath, Err: CurrentGsym->takeError());
580 CurrentGSYMPath = GSYMPath;
581 }
582
583 uint64_t Addr;
584 if (AddrStr.getAsInteger(Radix: 0, Result&: Addr)) {
585 OS << "error: invalid address " << AddrStr
586 << ", expected: Address GsymFile.\n";
587 return 1;
588 }
589
590 doLookup(Gsym&: **CurrentGsym, Addr, OS);
591
592 OS << "\n";
593 OS.flush();
594 }
595
596 return EXIT_SUCCESS;
597 }
598
599 // Dump or access data inside GSYM files
600 for (const auto &GSYMPath : InputFilenames) {
601 auto Gsym = GsymReader::openFile(Path: GSYMPath);
602 if (!Gsym)
603 error(Prefix: GSYMPath, Err: Gsym.takeError());
604
605 if (LookupAddresses.empty()) {
606 Gsym->dump(OS&: outs());
607 continue;
608 }
609
610 // Lookup an address in a GSYM file and print any matches.
611 OS << "Looking up addresses in \"" << GSYMPath << "\":\n";
612 for (auto Addr : LookupAddresses) {
613 doLookup(Gsym&: *Gsym, Addr, OS);
614 }
615 }
616 return EXIT_SUCCESS;
617}
618

source code of llvm/tools/llvm-gsymutil/llvm-gsymutil.cpp