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