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. Pass along
549 // the object file that the symbols came from so the size of a trailing
550 // symbol that has no size doesn't extend past the end of its section.
551 if (auto Err = Gsym.finalize(OS&: Out, Obj: SymtabObj ? SymtabObj : &Obj))
552 return Err;
553
554 // Save the GSYM file to disk.
555 llvm::endianness Endian = Obj.makeTriple().isLittleEndian()
556 ? llvm::endianness::little
557 : llvm::endianness::big;
558
559 std::optional<uint64_t> OptSegmentSize;
560 if (SegmentSize > 0)
561 OptSegmentSize = SegmentSize;
562 if (auto Err = Gsym.save(Path: OutFile, ByteOrder: Endian, SegmentSize: OptSegmentSize))
563 return Err;
564
565 // Verify the DWARF if requested. This will ensure all the info in the DWARF
566 // can be looked up in the GSYM and that all lookups get matching data.
567 if (Verify) {
568 if (auto Err = DT.verify(GsymPath: OutFile, OS&: Out))
569 return Err;
570 }
571
572 return Error::success();
573}
574
575static llvm::Error handleBuffer(StringRef Filename, MemoryBufferRef Buffer,
576 Binary *SymtabBinary, StringRef SymtabPath,
577 const std::string &OutFile,
578 OutputAggregator &Out) {
579 Expected<std::unique_ptr<Binary>> BinOrErr = object::createBinary(Source: Buffer);
580 error(Prefix: Filename, EC: errorToErrorCode(Err: BinOrErr.takeError()));
581
582 if (auto *Obj = dyn_cast<ObjectFile>(Val: BinOrErr->get())) {
583 std::string ArchName = getArchitectureName(Obj: *Obj);
584 std::unique_ptr<ObjectFile> OwnedSymtabObj;
585 auto SymtabObjOrErr =
586 resolveSymtabObject(ArchName, SymtabBinary, SymtabPath, OwnedSymtabObj);
587 if (!SymtabObjOrErr)
588 return SymtabObjOrErr.takeError();
589
590 outs() << "Output file (" << ArchName << "): " << OutFile << "\n";
591 if (auto Err =
592 handleObjectFile(Obj&: *Obj, SymtabObj: *SymtabObjOrErr, SymtabPath, OutFile, Out))
593 return Err;
594 } else if (auto *Fat = dyn_cast<MachOUniversalBinary>(Val: BinOrErr->get())) {
595 // Iterate over all contained architectures and filter out any that were
596 // not specified with the "--arch <arch>" option. If the --arch option was
597 // not specified on the command line, we will process all architectures.
598 std::vector<std::unique_ptr<MachOObjectFile>> FilterObjs;
599 for (auto &ObjForArch : Fat->objects()) {
600 auto MachOOrErr = ObjForArch.getAsObjectFile();
601 if (!MachOOrErr) {
602 error(Prefix: Filename, Err: MachOOrErr.takeError());
603 continue;
604 }
605
606 std::unique_ptr<MachOObjectFile> Obj = std::move(*MachOOrErr);
607 if (filterArch(Obj&: *Obj))
608 FilterObjs.emplace_back(args: std::move(Obj));
609 }
610 if (FilterObjs.empty())
611 error(Prefix: Filename, Err: createStringError(EC: std::errc::invalid_argument,
612 Fmt: "no matching architectures found"));
613
614 // Now handle each architecture we need to convert.
615 bool MultipleArchitecturesSelected = FilterObjs.size() > 1;
616 if (MultipleArchitecturesSelected && SymtabBinary &&
617 isa<ObjectFile>(Val: SymtabBinary))
618 return createStringError(
619 EC: std::errc::invalid_argument,
620 Fmt: "symbol table file '%s' is not a universal binary, but the input "
621 "contains multiple architectures; use --arch to select a single "
622 "architecture",
623 Vals: SymtabPath.str().c_str());
624
625 for (auto &Obj : FilterObjs) {
626 std::string ArchName = getArchitectureName(Obj: *Obj);
627 std::unique_ptr<ObjectFile> OwnedSymtabObj;
628 auto SymtabObjOrErr = resolveSymtabObject(ArchName, SymtabBinary,
629 SymtabPath, OwnedSymtabObj);
630 if (!SymtabObjOrErr)
631 return SymtabObjOrErr.takeError();
632
633 std::string ArchOutFile(OutFile);
634 // If we are only handling a single architecture, then we will use the
635 // normal output file. If we are handling multiple architectures append
636 // the architecture name to the end of the out file path so that we
637 // don't overwrite the previous architecture's gsym file.
638 if (MultipleArchitecturesSelected) {
639 ArchOutFile.append(n: 1, c: '.');
640 ArchOutFile.append(str: ArchName);
641 }
642 outs() << "Output file (" << ArchName << "): " << ArchOutFile << "\n";
643 if (auto Err = handleObjectFile(Obj&: *Obj, SymtabObj: *SymtabObjOrErr, SymtabPath,
644 OutFile: ArchOutFile, Out))
645 return Err;
646 }
647 }
648 return Error::success();
649}
650
651static llvm::Error handleFileConversionToGSYM(StringRef Filename,
652 const std::string &OutFile,
653 OutputAggregator &Out) {
654 ErrorOr<std::unique_ptr<MemoryBuffer>> BuffOrErr =
655 MemoryBuffer::getFileOrSTDIN(Filename, /*IsText=*/true);
656 error(Prefix: Filename, EC: BuffOrErr.getError());
657 std::unique_ptr<MemoryBuffer> Buffer = std::move(BuffOrErr.get());
658
659 std::unique_ptr<MemoryBuffer> SymtabBuffer;
660 std::unique_ptr<Binary> SymtabBinary;
661 if (!SymtabFilename.empty()) {
662 auto SymtabBufOrErr =
663 MemoryBuffer::getFile(Filename: SymtabFilename, /*IsText=*/true);
664 if (!SymtabBufOrErr)
665 return createStringError(EC: SymtabBufOrErr.getError(),
666 Fmt: "failed to open symbol table file '%s'",
667 Vals: SymtabFilename.c_str());
668
669 SymtabBuffer = std::move(*SymtabBufOrErr);
670 auto SymtabBinOrErr = object::createBinary(Source: *SymtabBuffer);
671 if (!SymtabBinOrErr)
672 return SymtabBinOrErr.takeError();
673 SymtabBinary = std::move(*SymtabBinOrErr);
674 }
675
676 return handleBuffer(Filename, Buffer: *Buffer, SymtabBinary: SymtabBinary.get(), SymtabPath: SymtabFilename,
677 OutFile, Out);
678}
679
680static llvm::Error convertFileToGSYM(OutputAggregator &Out) {
681 // Expand any .dSYM bundles to the individual object files contained therein.
682 std::vector<std::string> Objects;
683 std::string OutFile = OutputFilename;
684 if (OutFile.empty()) {
685 OutFile = ConvertFilename;
686 OutFile += ".gsym";
687 }
688
689 Out << "Input file: " << ConvertFilename << "\n";
690
691 if (auto DsymObjectsOrErr =
692 MachOObjectFile::findDsymObjectMembers(Path: ConvertFilename)) {
693 if (DsymObjectsOrErr->empty())
694 Objects.push_back(x: ConvertFilename);
695 else
696 llvm::append_range(C&: Objects, R&: *DsymObjectsOrErr);
697 } else {
698 error(Err: DsymObjectsOrErr.takeError());
699 }
700
701 for (StringRef Object : Objects)
702 if (Error Err = handleFileConversionToGSYM(Filename: Object, OutFile, Out))
703 return Err;
704 return Error::success();
705}
706
707static void doLookup(GsymReader &Gsym, uint64_t Addr, raw_ostream &OS) {
708 if (UseMergedFunctions) {
709 if (auto Results = Gsym.lookupAll(Addr)) {
710 // If we have filters, count matching results first
711 size_t NumMatching = Results->size();
712 if (!MergedFunctionsFilters.empty()) {
713 NumMatching = 0;
714 for (const auto &Result : *Results) {
715 bool Matches = false;
716 for (const auto &Filter : MergedFunctionsFilters) {
717 Regex Pattern(Filter);
718 if (Pattern.match(String: Result.FuncName)) {
719 Matches = true;
720 break;
721 }
722 }
723 if (Matches)
724 NumMatching++;
725 }
726 }
727
728 OS << "Found " << NumMatching << " function"
729 << (NumMatching != 1 ? "s" : "") << " at address " << HEX64(Addr)
730 << ":\n";
731
732 for (size_t i = 0; i < Results->size(); ++i) {
733 // Skip if doesn't match any filter
734 if (!MergedFunctionsFilters.empty()) {
735 bool Matches = false;
736 for (const auto &Filter : MergedFunctionsFilters) {
737 Regex Pattern(Filter);
738 if (Pattern.match(String: Results->at(n: i).FuncName)) {
739 Matches = true;
740 break;
741 }
742 }
743 if (!Matches)
744 continue;
745 }
746
747 OS << " " << Results->at(n: i);
748
749 if (i != Results->size() - 1)
750 OS << "\n";
751 }
752 } else {
753 if (Verbose)
754 OS << "\nLookupResult for " << HEX64(Addr) << ":\n";
755 OS << HEX64(Addr) << ": ";
756 logAllUnhandledErrors(E: Results.takeError(), OS, ErrorBanner: "error: ");
757 }
758 } else { /* UseMergedFunctions == false */
759 if (auto Result = Gsym.lookup(Addr)) {
760 // If verbose is enabled dump the full function info for the address.
761 if (Verbose) {
762 if (auto FI = Gsym.getFunctionInfo(Addr)) {
763 OS << "FunctionInfo for " << HEX64(Addr) << ":\n";
764 Gsym.dump(OS, FI: *FI);
765 OS << "\nLookupResult for " << HEX64(Addr) << ":\n";
766 }
767 }
768 // Don't print call site info if --merged-functions is not specified.
769 Result->CallSiteFuncRegex.clear();
770 OS << Result.get();
771 } else {
772 if (Verbose)
773 OS << "\nLookupResult for " << HEX64(Addr) << ":\n";
774 OS << HEX64(Addr) << ": ";
775 logAllUnhandledErrors(E: Result.takeError(), OS, ErrorBanner: "error: ");
776 }
777 if (Verbose)
778 OS << "\n";
779 }
780}
781
782static llvm::Error benchmarkReader(StringRef GSYMPath, uint32_t Start,
783 uint32_t Stride) {
784 auto Gsym = GsymReader::openFile(Path: GSYMPath);
785 if (!Gsym)
786 return Gsym.takeError();
787 uint32_t N = (*Gsym)->getNumAddresses();
788 uint32_t NumLookups = 0;
789 for (uint32_t I = Start; I < N; I += Stride) {
790 auto Addr = (*Gsym)->getAddress(Index: I);
791 if (!Addr)
792 return createStringError(EC: std::errc::invalid_argument,
793 Fmt: "failed to extract address[%u]", Vals: I);
794 auto LR = (*Gsym)->lookup(Addr: *Addr);
795 if (!LR)
796 return LR.takeError();
797 ++NumLookups;
798 }
799 outs() << "Benchmarked " << NumLookups << " lookups (out of " << N
800 << " addresses) in \"" << GSYMPath << "\"\n";
801 return Error::success();
802}
803
804int llvm_gsymutil_main(int argc, char **argv, const llvm::ToolContext &) {
805 // Print a stack trace if we signal out.
806 sys::PrintStackTraceOnErrorSignal(Argv0: argv[0]);
807 PrettyStackTraceProgram X(argc, argv);
808 llvm_shutdown_obj Y; // Call llvm_shutdown() on exit.
809
810 llvm::InitializeAllTargets();
811
812 parseArgs(argc, argv);
813
814 raw_ostream &OS = outs();
815
816 if (BenchmarkReader) {
817 for (const auto &GSYMPath : InputFilenames)
818 if (auto Err = benchmarkReader(GSYMPath, Start: BenchmarkStart, Stride: BenchmarkStride))
819 error(Prefix: "Benchmark failed: ", Err: std::move(Err));
820 return EXIT_SUCCESS;
821 }
822
823 OutputAggregator Aggregation(&OS, Quiet);
824 if (!ConvertFilename.empty()) {
825 // Convert DWARF to GSYM
826 if (!InputFilenames.empty()) {
827 OS << "error: no input files can be specified when using the --convert "
828 "option.\n";
829 return 1;
830 }
831 // Call error() if we have an error and it will exit with a status of 1
832 if (auto Err = convertFileToGSYM(Out&: Aggregation))
833 error(Prefix: "DWARF conversion failed: ", Err: std::move(Err));
834
835 // Report the errors from aggregator:
836 Aggregation.EnumerateResults(handleCounts: [&](StringRef category, unsigned count) {
837 OS << category << " occurred " << count << " time(s)\n";
838 });
839 if (!JsonSummaryFile.empty()) {
840 std::error_code EC;
841 raw_fd_ostream JsonStream(JsonSummaryFile, EC, sys::fs::OF_Text);
842 if (EC) {
843 OS << "error opening aggregate error json file '" << JsonSummaryFile
844 << "' for writing: " << EC.message() << '\n';
845 return 1;
846 }
847
848 llvm::json::Object Categories;
849 uint64_t ErrorCount = 0;
850 Aggregation.EnumerateResults(handleCounts: [&](StringRef Category, unsigned Count) {
851 llvm::json::Object Val;
852 Val.try_emplace(K: "count", Args&: Count);
853 Categories.try_emplace(K: Category, Args: std::move(Val));
854 ErrorCount += Count;
855 });
856 llvm::json::Object RootNode;
857 RootNode.try_emplace(K: "error-categories", Args: std::move(Categories));
858 RootNode.try_emplace(K: "error-count", Args&: ErrorCount);
859
860 JsonStream << llvm::json::Value(std::move(RootNode));
861 }
862 return 0;
863 }
864
865 if (LookupAddressesFromStdin) {
866 if (!LookupAddresses.empty() || !InputFilenames.empty()) {
867 OS << "error: no input files or addresses can be specified when using "
868 "the --addresses-from-stdin "
869 "option.\n";
870 return 1;
871 }
872
873 std::string InputLine;
874 std::string CurrentGSYMPath;
875 std::unique_ptr<GsymReader> CurrentGsym;
876
877 while (std::getline(is&: std::cin, str&: InputLine)) {
878 // Strip newline characters.
879 std::string StrippedInputLine(InputLine);
880 llvm::erase_if(C&: StrippedInputLine,
881 P: [](char c) { return c == '\r' || c == '\n'; });
882
883 StringRef AddrStr, GSYMPath;
884 std::tie(args&: AddrStr, args&: GSYMPath) =
885 llvm::StringRef{StrippedInputLine}.split(Separator: ' ');
886
887 if (GSYMPath != CurrentGSYMPath) {
888 auto GsymOrErr = GsymReader::openFile(Path: GSYMPath);
889 if (!GsymOrErr)
890 error(Prefix: GSYMPath, Err: GsymOrErr.takeError());
891 CurrentGsym = std::move(*GsymOrErr);
892 CurrentGSYMPath = GSYMPath;
893 }
894
895 uint64_t Addr;
896 if (AddrStr.getAsInteger(Radix: 0, Result&: Addr)) {
897 OS << "error: invalid address " << AddrStr
898 << ", expected: Address GsymFile.\n";
899 return 1;
900 }
901
902 doLookup(Gsym&: *CurrentGsym, Addr, OS);
903
904 OS << "\n";
905 OS.flush();
906 }
907
908 return EXIT_SUCCESS;
909 }
910
911 // Dump or access data inside GSYM files
912 for (const auto &GSYMPath : InputFilenames) {
913 auto Gsym = GsymReader::openFile(Path: GSYMPath);
914 if (!Gsym)
915 error(Prefix: GSYMPath, Err: Gsym.takeError());
916
917 if (ShowStatistics) {
918 (*Gsym)->dumpStatistics(OS, Format: StatisticsFormat, GSYMPath);
919 continue;
920 }
921
922 if (LookupAddresses.empty()) {
923 (*Gsym)->dump(OS&: outs());
924 continue;
925 }
926
927 // Lookup an address in a GSYM file and print any matches.
928 OS << "Looking up addresses in \"" << GSYMPath << "\":\n";
929 for (auto Addr : LookupAddresses) {
930 doLookup(Gsym&: **Gsym, Addr, OS);
931 }
932 }
933 return EXIT_SUCCESS;
934}
935