//===-- gsymutil.cpp - GSYM dumping and creation utility for llvm ---------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "llvm/ADT/STLExtras.h" #include "llvm/DebugInfo/DIContext.h" #include "llvm/DebugInfo/DWARF/DWARFContext.h" #include "llvm/Object/Archive.h" #include "llvm/Object/ELFObjectFile.h" #include "llvm/Object/MachOUniversal.h" #include "llvm/Object/ObjectFile.h" #include "llvm/Option/ArgList.h" #include "llvm/Option/Option.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Debug.h" #include "llvm/Support/Format.h" #include "llvm/Support/JSON.h" #include "llvm/Support/LLVMDriver.h" #include "llvm/Support/ManagedStatic.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/PrettyStackTrace.h" #include "llvm/Support/Regex.h" #include "llvm/Support/Signals.h" #include "llvm/Support/TargetSelect.h" #include "llvm/Support/raw_ostream.h" #include "llvm/TargetParser/Triple.h" #include #include #include #include #include #include #include #include #include "llvm/DebugInfo/GSYM/CallSiteInfo.h" #include "llvm/DebugInfo/GSYM/DwarfTransformer.h" #include "llvm/DebugInfo/GSYM/FunctionInfo.h" #include "llvm/DebugInfo/GSYM/GsymCreator.h" #include "llvm/DebugInfo/GSYM/GsymCreatorV1.h" #include "llvm/DebugInfo/GSYM/GsymCreatorV2.h" #include "llvm/DebugInfo/GSYM/GsymReader.h" #include "llvm/DebugInfo/GSYM/Header.h" #include "llvm/DebugInfo/GSYM/HeaderV2.h" #include "llvm/DebugInfo/GSYM/InlineInfo.h" #include "llvm/DebugInfo/GSYM/LookupResult.h" #include "llvm/DebugInfo/GSYM/ObjectFileTransformer.h" #include "llvm/DebugInfo/GSYM/OutputAggregator.h" using namespace llvm; using namespace gsym; using namespace object; /// @} /// Command line options. /// @{ using namespace llvm::opt; enum ID { OPT_INVALID = 0, // This is not an option ID. #define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__), #include "Opts.inc" #undef OPTION }; #define OPTTABLE_STR_TABLE_CODE #include "Opts.inc" #undef OPTTABLE_STR_TABLE_CODE #define OPTTABLE_PREFIXES_TABLE_CODE #include "Opts.inc" #undef OPTTABLE_PREFIXES_TABLE_CODE const opt::OptTable::Info InfoTable[] = { #define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__), #include "Opts.inc" #undef OPTION }; class GSYMUtilOptTable : public llvm::opt::GenericOptTable { public: GSYMUtilOptTable() : GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable) { setGroupedShortOptions(true); } }; static bool Verbose; static std::vector InputFilenames; static std::string ConvertFilename; static std::string SymtabFilename; static std::vector ArchFilters; static std::string OutputFilename; static std::string JsonSummaryFile; static bool Verify; static bool BenchmarkReader; static uint32_t BenchmarkStart; static uint32_t BenchmarkStride; static unsigned NumThreads; static uint64_t SegmentSize; static bool Quiet; static std::vector LookupAddresses; static bool LookupAddressesFromStdin; static bool UseMergedFunctions = false; static bool LoadDwarfCallSites = false; static std::string CallSiteYamlPath; static std::vector MergedFunctionsFilters; // Default output version. Can be overridden by --output-version. static uint32_t OutputVersion = Header::getVersion(); static void parseArgs(int argc, char **argv) { GSYMUtilOptTable Tbl; llvm::StringRef ToolName = argv[0]; llvm::BumpPtrAllocator A; llvm::StringSaver Saver{A}; llvm::opt::InputArgList Args = Tbl.parseArgs(argc, argv, OPT_UNKNOWN, Saver, [&](StringRef Msg) { llvm::errs() << Msg << '\n'; std::exit(1); }); if (Args.hasArg(OPT_help)) { const char *Overview = "A tool for dumping, searching and creating GSYM files.\n\n" "Specify one or more GSYM paths as arguments to dump all of the " "information in each GSYM file.\n" "Specify a single GSYM file along with one or more --lookup options to " "lookup addresses within that GSYM file.\n" "Use the --convert option to specify a file with option --out-file " "option to convert to GSYM format.\n"; Tbl.printHelp(llvm::outs(), "llvm-gsymutil [options] ", Overview); std::exit(0); } if (Args.hasArg(OPT_version)) { llvm::outs() << ToolName << '\n'; cl::PrintVersionMessage(); std::exit(0); } Verbose = Args.hasArg(OPT_verbose); for (const llvm::opt::Arg *A : Args.filtered(OPT_INPUT)) InputFilenames.emplace_back(A->getValue()); if (const llvm::opt::Arg *A = Args.getLastArg(OPT_convert_EQ)) ConvertFilename = A->getValue(); if (const llvm::opt::Arg *A = Args.getLastArg(OPT_symtab_file_EQ)) SymtabFilename = A->getValue(); for (const llvm::opt::Arg *A : Args.filtered(OPT_arch_EQ)) ArchFilters.emplace_back(A->getValue()); if (const llvm::opt::Arg *A = Args.getLastArg(OPT_out_file_EQ)) OutputFilename = A->getValue(); if (const llvm::opt::Arg *A = Args.getLastArg(OPT_json_summary_file_EQ)) JsonSummaryFile = A->getValue(); Verify = Args.hasArg(OPT_verify); BenchmarkStart = 0; BenchmarkStride = 1; if (Args.hasArg(OPT_benchmark_reader_all)) { BenchmarkReader = true; } else if (const llvm::opt::Arg *A = Args.getLastArg(OPT_benchmark_reader)) { BenchmarkReader = true; StringRef S{A->getValue()}; if (!S.empty()) { auto [StartStr, StrideStr] = S.split(','); if (!llvm::to_integer(StartStr, BenchmarkStart, 0)) { llvm::errs() << ToolName << ": for the --benchmark-reader option: invalid start '" << StartStr << "'\n"; std::exit(1); } if (!StrideStr.empty() && !llvm::to_integer(StrideStr, BenchmarkStride, 0)) { llvm::errs() << ToolName << ": for the --benchmark-reader option: invalid stride '" << StrideStr << "'\n"; std::exit(1); } if (BenchmarkStride == 0) { llvm::errs() << ToolName << ": for the --benchmark-reader option: stride must be " "positive\n"; std::exit(1); } } } if (const llvm::opt::Arg *A = Args.getLastArg(OPT_num_threads_EQ)) { StringRef S{A->getValue()}; if (!llvm::to_integer(S, NumThreads, 0)) { llvm::errs() << ToolName << ": for the --num-threads option: '" << S << "' value invalid for uint argument!\n"; std::exit(1); } } if (const llvm::opt::Arg *A = Args.getLastArg(OPT_segment_size_EQ)) { StringRef S{A->getValue()}; if (!llvm::to_integer(S, SegmentSize, 0)) { llvm::errs() << ToolName << ": for the --segment-size option: '" << S << "' value invalid for uint argument!\n"; std::exit(1); } } Quiet = Args.hasArg(OPT_quiet); for (const llvm::opt::Arg *A : Args.filtered(OPT_address_EQ)) { StringRef S{A->getValue()}; if (!llvm::to_integer(S, LookupAddresses.emplace_back(), 0)) { llvm::errs() << ToolName << ": for the --address option: '" << S << "' value invalid for uint argument!\n"; std::exit(1); } } LookupAddressesFromStdin = Args.hasArg(OPT_addresses_from_stdin); UseMergedFunctions = Args.hasArg(OPT_merged_functions); if (Args.hasArg(OPT_callsites_yaml_file_EQ)) { CallSiteYamlPath = Args.getLastArgValue(OPT_callsites_yaml_file_EQ); if (CallSiteYamlPath.empty()) { llvm::errs() << ToolName << ": --callsites-yaml-file option requires a non-empty argument.\n"; std::exit(1); } } LoadDwarfCallSites = Args.hasArg(OPT_dwarf_callsites); for (const llvm::opt::Arg *A : Args.filtered(OPT_merged_functions_filter_EQ)) { MergedFunctionsFilters.push_back(A->getValue()); // Validate the filter is only used with correct flags if (LookupAddresses.empty() && !LookupAddressesFromStdin) { llvm::errs() << ToolName << ": --merged-functions-filter can only be used with " "--address/--addresses-from-stdin\n"; std::exit(1); } if (!UseMergedFunctions) { llvm::errs() << ToolName << ": --merged-functions-filter requires --merged-functions\n"; std::exit(1); } } if (const llvm::opt::Arg *A = Args.getLastArg(OPT_output_version_EQ)) { StringRef Val = A->getValue(); uint32_t Version; if (Val.getAsInteger(10, Version) || (Version != Header::getVersion() && Version != HeaderV2::getVersion())) { llvm::errs() << ToolName << ": for the --output-version option: '" << Val << "' is invalid. Use '1' or '2'.\n"; std::exit(1); } OutputVersion = Version; } } /// @} //===----------------------------------------------------------------------===// static void error(Error Err) { if (!Err) return; WithColor::error() << toString(std::move(Err)) << "\n"; exit(1); } static void error(StringRef Prefix, llvm::Error Err) { if (!Err) return; errs() << Prefix << ": " << Err << "\n"; consumeError(std::move(Err)); exit(1); } static void error(StringRef Prefix, std::error_code EC) { if (!EC) return; errs() << Prefix << ": " << EC.message() << "\n"; exit(1); } static uint32_t getCPUType(MachOObjectFile &MachO) { if (MachO.is64Bit()) return MachO.getHeader64().cputype; else return MachO.getHeader().cputype; } static std::string getArchitectureName(const ObjectFile &Obj) { if (const auto *MachO = dyn_cast(&Obj)) { Triple ObjTriple(MachO->getArchTriple()); return ObjTriple.getArchName().str(); } Triple ObjTriple(Obj.makeTriple()); return ObjTriple.getArchName().str(); } /// Return true if the object file has not been filtered by an --arch option. static bool filterArch(MachOObjectFile &Obj) { if (ArchFilters.empty()) return true; Triple ObjTriple(Obj.getArchTriple()); StringRef ObjArch = ObjTriple.getArchName(); for (StringRef Arch : ArchFilters) { // Match name. if (Arch == ObjArch) return true; // Match architecture number. unsigned Value; if (!Arch.getAsInteger(0, Value)) if (Value == getCPUType(Obj)) return true; } return false; } /// Determine the virtual address that is considered the base address of an ELF /// object file. /// /// The base address of an ELF file is the "p_vaddr" of the first program /// header whose "p_type" is PT_LOAD. /// /// \param ELFFile An ELF object file we will search. /// /// \returns A valid image base address if we are able to extract one. template static std::optional getImageBaseAddress(const object::ELFFile &ELFFile) { auto PhdrRangeOrErr = ELFFile.program_headers(); if (!PhdrRangeOrErr) { consumeError(PhdrRangeOrErr.takeError()); return std::nullopt; } for (const typename ELFT::Phdr &Phdr : *PhdrRangeOrErr) if (Phdr.p_type == ELF::PT_LOAD) return (uint64_t)Phdr.p_vaddr; return std::nullopt; } /// Determine the virtual address that is considered the base address of mach-o /// object file. /// /// The base address of a mach-o file is the vmaddr of the "__TEXT" segment. /// /// \param MachO A mach-o object file we will search. /// /// \returns A valid image base address if we are able to extract one. static std::optional getImageBaseAddress(const object::MachOObjectFile *MachO) { for (const auto &Command : MachO->load_commands()) { if (Command.C.cmd == MachO::LC_SEGMENT) { MachO::segment_command SLC = MachO->getSegmentLoadCommand(Command); StringRef SegName = SLC.segname; if (SegName == "__TEXT") return SLC.vmaddr; } else if (Command.C.cmd == MachO::LC_SEGMENT_64) { MachO::segment_command_64 SLC = MachO->getSegment64LoadCommand(Command); StringRef SegName = SLC.segname; if (SegName == "__TEXT") return SLC.vmaddr; } } return std::nullopt; } /// Determine the virtual address that is considered the base address of an /// object file. /// /// Since GSYM files are used for symbolication, many clients will need to /// easily adjust addresses they find in stack traces so the lookups happen /// on unslid addresses from the original object file. If the base address of /// a GSYM file is set to the base address of the image, then this address /// adjusting is much easier. /// /// \param Obj An object file we will search. /// /// \returns A valid image base address if we are able to extract one. static std::optional getImageBaseAddress(object::ObjectFile &Obj) { if (const auto *MachO = dyn_cast(&Obj)) return getImageBaseAddress(MachO); else if (const auto *ELFObj = dyn_cast(&Obj)) return getImageBaseAddress(ELFObj->getELFFile()); else if (const auto *ELFObj = dyn_cast(&Obj)) return getImageBaseAddress(ELFObj->getELFFile()); else if (const auto *ELFObj = dyn_cast(&Obj)) return getImageBaseAddress(ELFObj->getELFFile()); else if (const auto *ELFObj = dyn_cast(&Obj)) return getImageBaseAddress(ELFObj->getELFFile()); return std::nullopt; } static Expected resolveSymtabObject(StringRef ArchName, Binary *SymtabBinary, StringRef SymtabPath, std::unique_ptr &OwnedSymtabObj) { if (!SymtabBinary) return nullptr; if (auto *SymtabObj = dyn_cast(SymtabBinary)) { std::string SymtabArchName = getArchitectureName(*SymtabObj); if (SymtabArchName != ArchName) return createStringError(std::errc::invalid_argument, "architecture mismatch: input file is %s but " "symbol table file '%s' is %s", ArchName.str().c_str(), SymtabPath.str().c_str(), SymtabArchName.c_str()); return SymtabObj; } if (auto *SymtabFat = dyn_cast(SymtabBinary)) { auto SymtabObjOrErr = SymtabFat->getMachOObjectForArch(ArchName); if (!SymtabObjOrErr) { consumeError(SymtabObjOrErr.takeError()); return createStringError( std::errc::invalid_argument, "symbol table file '%s' does not contain architecture '%s'", SymtabPath.str().c_str(), ArchName.str().c_str()); } OwnedSymtabObj = std::move(*SymtabObjOrErr); return OwnedSymtabObj.get(); } return createStringError(std::errc::invalid_argument, "symbol table file '%s' is not a valid object file", SymtabPath.str().c_str()); } static llvm::Error handleObjectFile(ObjectFile &Obj, ObjectFile *SymtabObj, StringRef SymtabPath, const std::string &OutFile, OutputAggregator &Out) { auto ThreadCount = NumThreads > 0 ? NumThreads : std::thread::hardware_concurrency(); std::unique_ptr GsymPtr; switch (OutputVersion) { case Header::getVersion(): GsymPtr = std::make_unique(Quiet); break; case HeaderV2::getVersion(): GsymPtr = std::make_unique(Quiet); break; default: return createStringError(std::errc::invalid_argument, "invalid --output-version option"); } GsymCreator &Gsym = *GsymPtr; // See if we can figure out the base address for a given object file, and if // we can, then set the base address to use to this value. This will ease // symbolication since clients can slide the GSYM lookup addresses by using // the load bias of the shared library. if (auto ImageBaseAddr = getImageBaseAddress(Obj)) Gsym.setBaseAddress(*ImageBaseAddr); // We need to know where the valid sections are that contain instructions. // See header documentation for DWARFTransformer::SetValidTextRanges() for // defails. AddressRanges TextRanges; for (const object::SectionRef &Sect : Obj.sections()) { if (!Sect.isText()) continue; const uint64_t Size = Sect.getSize(); if (Size == 0) continue; const uint64_t StartAddr = Sect.getAddress(); TextRanges.insert(AddressRange(StartAddr, StartAddr + Size)); } // Make sure there is DWARF to convert first. std::unique_ptr DICtx = DWARFContext::create( Obj, /*RelocAction=*/DWARFContext::ProcessDebugRelocations::Process, nullptr, /*DWPName=*/"", /*RecoverableErrorHandler=*/WithColor::defaultErrorHandler, /*WarningHandler=*/WithColor::defaultWarningHandler, /*ThreadSafe*/true); if (!DICtx) return createStringError(std::errc::invalid_argument, "unable to create DWARF context"); // Make a DWARF transformer object and populate the ranges of the code // so we don't end up adding invalid functions to GSYM data. bool IsMachO = dyn_cast(&Obj) != nullptr; DwarfTransformer DT(*DICtx, Gsym, LoadDwarfCallSites, IsMachO); if (!TextRanges.empty()) Gsym.SetValidTextRanges(TextRanges); // Convert all DWARF to GSYM. if (auto Err = DT.convert(ThreadCount, Out)) return Err; // If enabled, merge functions with identical address ranges as merged // functions in the first FunctionInfo with that address range. Do this right // after loading the DWARF data so we don't have to deal with functions from // the symbol table. if (UseMergedFunctions) Gsym.prepareMergedFunctions(Out); // Get the UUID and convert symbol table to GSYM. if (SymtabObj) { Out << "Using symbol table file: " << SymtabPath << "\n"; if (auto Err = ObjectFileTransformer::convert(*SymtabObj, Out, Gsym)) return Err; } else if (auto Err = ObjectFileTransformer::convert(Obj, Out, Gsym)) { return Err; } // If any call site YAML files were specified, load them now. if (!CallSiteYamlPath.empty()) if (auto Err = Gsym.loadCallSitesFromYAML(CallSiteYamlPath)) return Err; // Finalize the GSYM to make it ready to save to disk. This will remove // duplicate FunctionInfo entries where we might have found an entry from // debug info and also a symbol table entry from the object file. if (auto Err = Gsym.finalize(Out)) return Err; // Save the GSYM file to disk. llvm::endianness Endian = Obj.makeTriple().isLittleEndian() ? llvm::endianness::little : llvm::endianness::big; std::optional OptSegmentSize; if (SegmentSize > 0) OptSegmentSize = SegmentSize; if (auto Err = Gsym.save(OutFile, Endian, OptSegmentSize)) return Err; // Verify the DWARF if requested. This will ensure all the info in the DWARF // can be looked up in the GSYM and that all lookups get matching data. if (Verify) { if (auto Err = DT.verify(OutFile, Out)) return Err; } return Error::success(); } static llvm::Error handleBuffer(StringRef Filename, MemoryBufferRef Buffer, Binary *SymtabBinary, StringRef SymtabPath, const std::string &OutFile, OutputAggregator &Out) { Expected> BinOrErr = object::createBinary(Buffer); error(Filename, errorToErrorCode(BinOrErr.takeError())); if (auto *Obj = dyn_cast(BinOrErr->get())) { std::string ArchName = getArchitectureName(*Obj); std::unique_ptr OwnedSymtabObj; auto SymtabObjOrErr = resolveSymtabObject(ArchName, SymtabBinary, SymtabPath, OwnedSymtabObj); if (!SymtabObjOrErr) return SymtabObjOrErr.takeError(); outs() << "Output file (" << ArchName << "): " << OutFile << "\n"; if (auto Err = handleObjectFile(*Obj, *SymtabObjOrErr, SymtabPath, OutFile, Out)) return Err; } else if (auto *Fat = dyn_cast(BinOrErr->get())) { // Iterate over all contained architectures and filter out any that were // not specified with the "--arch " option. If the --arch option was // not specified on the command line, we will process all architectures. std::vector> FilterObjs; for (auto &ObjForArch : Fat->objects()) { auto MachOOrErr = ObjForArch.getAsObjectFile(); if (!MachOOrErr) { error(Filename, MachOOrErr.takeError()); continue; } std::unique_ptr Obj = std::move(*MachOOrErr); if (filterArch(*Obj)) FilterObjs.emplace_back(std::move(Obj)); } if (FilterObjs.empty()) error(Filename, createStringError(std::errc::invalid_argument, "no matching architectures found")); // Now handle each architecture we need to convert. bool MultipleArchitecturesSelected = FilterObjs.size() > 1; if (MultipleArchitecturesSelected && SymtabBinary && isa(SymtabBinary)) return createStringError( std::errc::invalid_argument, "symbol table file '%s' is not a universal binary, but the input " "contains multiple architectures; use --arch to select a single " "architecture", SymtabPath.str().c_str()); for (auto &Obj : FilterObjs) { std::string ArchName = getArchitectureName(*Obj); std::unique_ptr OwnedSymtabObj; auto SymtabObjOrErr = resolveSymtabObject(ArchName, SymtabBinary, SymtabPath, OwnedSymtabObj); if (!SymtabObjOrErr) return SymtabObjOrErr.takeError(); std::string ArchOutFile(OutFile); // If we are only handling a single architecture, then we will use the // normal output file. If we are handling multiple architectures append // the architecture name to the end of the out file path so that we // don't overwrite the previous architecture's gsym file. if (MultipleArchitecturesSelected) { ArchOutFile.append(1, '.'); ArchOutFile.append(ArchName); } outs() << "Output file (" << ArchName << "): " << ArchOutFile << "\n"; if (auto Err = handleObjectFile(*Obj, *SymtabObjOrErr, SymtabPath, ArchOutFile, Out)) return Err; } } return Error::success(); } static llvm::Error handleFileConversionToGSYM(StringRef Filename, const std::string &OutFile, OutputAggregator &Out) { ErrorOr> BuffOrErr = MemoryBuffer::getFileOrSTDIN(Filename, /*IsText=*/true); error(Filename, BuffOrErr.getError()); std::unique_ptr Buffer = std::move(BuffOrErr.get()); std::unique_ptr SymtabBuffer; std::unique_ptr SymtabBinary; if (!SymtabFilename.empty()) { auto SymtabBufOrErr = MemoryBuffer::getFile(SymtabFilename, /*IsText=*/true); if (!SymtabBufOrErr) return createStringError(SymtabBufOrErr.getError(), "failed to open symbol table file '%s'", SymtabFilename.c_str()); SymtabBuffer = std::move(*SymtabBufOrErr); auto SymtabBinOrErr = object::createBinary(*SymtabBuffer); if (!SymtabBinOrErr) return SymtabBinOrErr.takeError(); SymtabBinary = std::move(*SymtabBinOrErr); } return handleBuffer(Filename, *Buffer, SymtabBinary.get(), SymtabFilename, OutFile, Out); } static llvm::Error convertFileToGSYM(OutputAggregator &Out) { // Expand any .dSYM bundles to the individual object files contained therein. std::vector Objects; std::string OutFile = OutputFilename; if (OutFile.empty()) { OutFile = ConvertFilename; OutFile += ".gsym"; } Out << "Input file: " << ConvertFilename << "\n"; if (auto DsymObjectsOrErr = MachOObjectFile::findDsymObjectMembers(ConvertFilename)) { if (DsymObjectsOrErr->empty()) Objects.push_back(ConvertFilename); else llvm::append_range(Objects, *DsymObjectsOrErr); } else { error(DsymObjectsOrErr.takeError()); } for (StringRef Object : Objects) if (Error Err = handleFileConversionToGSYM(Object, OutFile, Out)) return Err; return Error::success(); } static void doLookup(GsymReader &Gsym, uint64_t Addr, raw_ostream &OS) { if (UseMergedFunctions) { if (auto Results = Gsym.lookupAll(Addr)) { // If we have filters, count matching results first size_t NumMatching = Results->size(); if (!MergedFunctionsFilters.empty()) { NumMatching = 0; for (const auto &Result : *Results) { bool Matches = false; for (const auto &Filter : MergedFunctionsFilters) { Regex Pattern(Filter); if (Pattern.match(Result.FuncName)) { Matches = true; break; } } if (Matches) NumMatching++; } } OS << "Found " << NumMatching << " function" << (NumMatching != 1 ? "s" : "") << " at address " << HEX64(Addr) << ":\n"; for (size_t i = 0; i < Results->size(); ++i) { // Skip if doesn't match any filter if (!MergedFunctionsFilters.empty()) { bool Matches = false; for (const auto &Filter : MergedFunctionsFilters) { Regex Pattern(Filter); if (Pattern.match(Results->at(i).FuncName)) { Matches = true; break; } } if (!Matches) continue; } OS << " " << Results->at(i); if (i != Results->size() - 1) OS << "\n"; } } } else { /* UseMergedFunctions == false */ if (auto Result = Gsym.lookup(Addr)) { // If verbose is enabled dump the full function info for the address. if (Verbose) { if (auto FI = Gsym.getFunctionInfo(Addr)) { OS << "FunctionInfo for " << HEX64(Addr) << ":\n"; Gsym.dump(OS, *FI); OS << "\nLookupResult for " << HEX64(Addr) << ":\n"; } } // Don't print call site info if --merged-functions is not specified. Result->CallSiteFuncRegex.clear(); OS << Result.get(); } else { if (Verbose) OS << "\nLookupResult for " << HEX64(Addr) << ":\n"; OS << HEX64(Addr) << ": "; logAllUnhandledErrors(Result.takeError(), OS, "error: "); } if (Verbose) OS << "\n"; } } static llvm::Error benchmarkReader(StringRef GSYMPath, uint32_t Start, uint32_t Stride) { auto Gsym = GsymReader::openFile(GSYMPath); if (!Gsym) return Gsym.takeError(); uint32_t N = (*Gsym)->getNumAddresses(); uint32_t NumLookups = 0; for (uint32_t I = Start; I < N; I += Stride) { auto Addr = (*Gsym)->getAddress(I); if (!Addr) return createStringError(std::errc::invalid_argument, "failed to extract address[%u]", I); auto LR = (*Gsym)->lookup(*Addr); if (!LR) return LR.takeError(); ++NumLookups; } outs() << "Benchmarked " << NumLookups << " lookups (out of " << N << " addresses) in \"" << GSYMPath << "\"\n"; return Error::success(); } int llvm_gsymutil_main(int argc, char **argv, const llvm::ToolContext &) { // Print a stack trace if we signal out. sys::PrintStackTraceOnErrorSignal(argv[0]); PrettyStackTraceProgram X(argc, argv); llvm_shutdown_obj Y; // Call llvm_shutdown() on exit. llvm::InitializeAllTargets(); parseArgs(argc, argv); raw_ostream &OS = outs(); if (BenchmarkReader) { for (const auto &GSYMPath : InputFilenames) if (auto Err = benchmarkReader(GSYMPath, BenchmarkStart, BenchmarkStride)) error("Benchmark failed: ", std::move(Err)); return EXIT_SUCCESS; } OutputAggregator Aggregation(&OS); if (!ConvertFilename.empty()) { // Convert DWARF to GSYM if (!InputFilenames.empty()) { OS << "error: no input files can be specified when using the --convert " "option.\n"; return 1; } // Call error() if we have an error and it will exit with a status of 1 if (auto Err = convertFileToGSYM(Aggregation)) error("DWARF conversion failed: ", std::move(Err)); // Report the errors from aggregator: Aggregation.EnumerateResults([&](StringRef category, unsigned count) { OS << category << " occurred " << count << " time(s)\n"; }); if (!JsonSummaryFile.empty()) { std::error_code EC; raw_fd_ostream JsonStream(JsonSummaryFile, EC, sys::fs::OF_Text); if (EC) { OS << "error opening aggregate error json file '" << JsonSummaryFile << "' for writing: " << EC.message() << '\n'; return 1; } llvm::json::Object Categories; uint64_t ErrorCount = 0; Aggregation.EnumerateResults([&](StringRef Category, unsigned Count) { llvm::json::Object Val; Val.try_emplace("count", Count); Categories.try_emplace(Category, std::move(Val)); ErrorCount += Count; }); llvm::json::Object RootNode; RootNode.try_emplace("error-categories", std::move(Categories)); RootNode.try_emplace("error-count", ErrorCount); JsonStream << llvm::json::Value(std::move(RootNode)); } return 0; } if (LookupAddressesFromStdin) { if (!LookupAddresses.empty() || !InputFilenames.empty()) { OS << "error: no input files or addresses can be specified when using " "the --addresses-from-stdin " "option.\n"; return 1; } std::string InputLine; std::string CurrentGSYMPath; std::unique_ptr CurrentGsym; while (std::getline(std::cin, InputLine)) { // Strip newline characters. std::string StrippedInputLine(InputLine); llvm::erase_if(StrippedInputLine, [](char c) { return c == '\r' || c == '\n'; }); StringRef AddrStr, GSYMPath; std::tie(AddrStr, GSYMPath) = llvm::StringRef{StrippedInputLine}.split(' '); if (GSYMPath != CurrentGSYMPath) { auto GsymOrErr = GsymReader::openFile(GSYMPath); if (!GsymOrErr) error(GSYMPath, GsymOrErr.takeError()); CurrentGsym = std::move(*GsymOrErr); CurrentGSYMPath = GSYMPath; } uint64_t Addr; if (AddrStr.getAsInteger(0, Addr)) { OS << "error: invalid address " << AddrStr << ", expected: Address GsymFile.\n"; return 1; } doLookup(*CurrentGsym, Addr, OS); OS << "\n"; OS.flush(); } return EXIT_SUCCESS; } // Dump or access data inside GSYM files for (const auto &GSYMPath : InputFilenames) { auto Gsym = GsymReader::openFile(GSYMPath); if (!Gsym) error(GSYMPath, Gsym.takeError()); if (LookupAddresses.empty()) { (*Gsym)->dump(outs()); continue; } // Lookup an address in a GSYM file and print any matches. OS << "Looking up addresses in \"" << GSYMPath << "\":\n"; for (auto Addr : LookupAddresses) { doLookup(**Gsym, Addr, OS); } } return EXIT_SUCCESS; }