//===- GsymReader.h ---------------------------------------------*- C++ -*-===// // // 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 // //===----------------------------------------------------------------------===// #ifndef LLVM_DEBUGINFO_GSYM_GSYMREADER_H #define LLVM_DEBUGINFO_GSYM_GSYMREADER_H #include "llvm/ADT/ArrayRef.h" #include "llvm/DebugInfo/GSYM/FileEntry.h" #include "llvm/DebugInfo/GSYM/FunctionInfo.h" #include "llvm/DebugInfo/GSYM/GlobalData.h" #include "llvm/DebugInfo/GSYM/GsymDataExtractor.h" #include "llvm/DebugInfo/GSYM/Header.h" #include "llvm/DebugInfo/GSYM/LineEntry.h" #include "llvm/DebugInfo/GSYM/StringTable.h" #include "llvm/Support/Compiler.h" #include "llvm/Support/Endian.h" #include "llvm/Support/ErrorOr.h" #include "llvm/Support/MemoryBuffer.h" #include #include #include #include #include namespace llvm { class MemoryBuffer; class raw_ostream; namespace gsym { /// GsymReader is used to read GSYM data from a file or buffer. /// /// This class is optimized for very quick lookups when the endianness matches /// the host system. The header and the address table are designed to be mmap'ed /// as read only into memory and used without any parsing needed. If the /// endianness doesn't match, we swap the byte order of the address table into a /// separate buffer for efficient binary search. All the other data are parsed /// on demand with the correct endianness. /// /// GsymReader objects must use one of the static functions to create an /// instance: GsymReader::openFile(...) and GsymReader::copyBuffer(...). class GsymReader { protected: std::unique_ptr MemBuffer; llvm::endianness Endian; /// Parsed GlobalData entries, keyed by type. Populated by /// parseHeaderAndGlobalDataEntries(). std::map GlobalDataSections; ArrayRef AddrOffsets; std::vector SwappedAddrOffsets; GsymDataExtractor AddrInfoOffsetsData; GsymDataExtractor FileEntryData; StringTable StrTab; LLVM_ABI GsymReader(std::unique_ptr Buffer, llvm::endianness Endian); public: GsymReader(GsymReader &&RHS) = default; virtual ~GsymReader() = default; bool isLittleEndian() const { return Endian == llvm::endianness::little; } /// Get the GSYM version for this reader. virtual uint16_t getVersion() const = 0; /// Get the base address of this GSYM file. virtual uint64_t getBaseAddress() const = 0; /// Get the number of addresses in this GSYM file. virtual uint64_t getNumAddresses() const = 0; /// Get the address offset byte size for this GSYM file. virtual uint8_t getAddressOffsetSize() const = 0; /// Get the address info offset byte size for this GSYM file. virtual uint8_t getAddressInfoOffsetSize() const = 0; /// Get the string offset byte size for this GSYM file. virtual uint8_t getStringOffsetSize() const = 0; /// Construct a GsymReader from a file on disk. /// /// \param Path The file path the GSYM file to read. /// \returns An expected GsymReader that contains the object or an error /// object that indicates reason for failing to read the GSYM. LLVM_ABI static llvm::Expected> openFile(StringRef Path); /// Construct a GsymReader from a buffer. /// /// \param Bytes A set of bytes that will be copied and owned by the /// returned object on success. /// \returns An expected GsymReader that contains the object or an error /// object that indicates reason for failing to read the GSYM. LLVM_ABI static llvm::Expected> copyBuffer(StringRef Bytes); /// Get the full function info for an address. /// /// This should be called when a client will store a copy of the complete /// FunctionInfo for a given address. For one off lookups, use the lookup() /// function below. /// /// Symbolication server processes might want to parse the entire function /// info for a given address and cache it if the process stays around to /// service many symbolication addresses, like for parsing profiling /// information. /// /// \param Addr A virtual address from the orignal object file to lookup. /// /// \returns An expected FunctionInfo that contains the function info object /// or an error object that indicates reason for failing to lookup the /// address. LLVM_ABI llvm::Expected getFunctionInfo(uint64_t Addr) const; /// Get the full function info given an address index. /// /// \param AddrIdx A address index for an address in the address table. /// /// \returns An expected FunctionInfo that contains the function info object /// or an error object that indicates reason for failing get the function /// info object. LLVM_ABI llvm::Expected getFunctionInfoAtIndex(uint64_t AddrIdx) const; /// Lookup an address in the a GSYM. /// /// Lookup just the information needed for a specific address \a Addr. This /// function is faster that calling getFunctionInfo() as it will only return /// information that pertains to \a Addr and allows the parsing to skip any /// extra information encoded for other addresses. For example the line table /// parsing can stop when a matching LineEntry has been fouhnd, and the /// InlineInfo can stop parsing early once a match has been found and also /// skip information that doesn't match. This avoids memory allocations and /// is much faster for lookups. /// /// \param Addr A virtual address from the orignal object file to lookup. /// /// \param MergedFuncsData A pointer to an optional GsymDataExtractor that, if /// non-null, will be set to the raw data of the MergedFunctionInfo, if /// present. /// /// \returns An expected LookupResult that contains only the information /// needed for the current address, or an error object that indicates reason /// for failing to lookup the address. LLVM_ABI llvm::Expected lookup(uint64_t Addr, std::optional *MergedFuncsData = nullptr) const; /// Lookup all merged functions for a given address. /// /// This function performs a lookup for the specified address and then /// retrieves additional LookupResults from any merged functions associated /// with the primary LookupResult. /// /// \param Addr The address to lookup. /// /// \returns A vector of LookupResult objects, where the first element is the /// primary result, followed by results for any merged functions LLVM_ABI llvm::Expected> lookupAll(uint64_t Addr) const; /// Get a string from the string table. /// /// \param Offset The string table offset for the string to retrieve. /// \returns The string from the strin table. StringRef getString(gsym_strp_t Offset) const { return StrTab[Offset]; } /// Get the a file entry for the suppplied file index. /// /// Used to convert any file indexes in the FunctionInfo data back into /// files. This function can be used for iteration, but is more commonly used /// for random access when doing lookups. /// /// \param Index An index into the file table. /// \returns An optional FileInfo that will be valid if the file index is /// valid, or std::nullopt if the file index is out of bounds, std::optional getFile(uint32_t Index) const { uint64_t EntrySize = FileEntry::getEncodedSize(FileEntryData.getStringOffsetSize()); uint64_t Offset = Index * EntrySize; if (!FileEntryData.isValidOffsetForDataOfSize(Offset, EntrySize)) return std::nullopt; FileEntry FE; FE.Dir = FileEntryData.getStringOffset(&Offset); FE.Base = FileEntryData.getStringOffset(&Offset); return FE; } /// Dump the entire Gsym data contained in this object. /// /// \param OS The output stream to dump to. virtual void dump(raw_ostream &OS) = 0; /// Dump a FunctionInfo object. /// /// This function will convert any string table indexes and file indexes /// into human readable format. /// /// \param OS The output stream to dump to. /// /// \param FI The object to dump. /// /// \param Indent The indentation as number of spaces. Used when dumping as an /// item within MergedFunctionsInfo. LLVM_ABI void dump(raw_ostream &OS, const FunctionInfo &FI, uint32_t Indent = 0); /// Dump a MergedFunctionsInfo object. /// /// This function will dump a MergedFunctionsInfo object - basically by /// dumping the contained FunctionInfo objects with indentation. /// /// \param OS The output stream to dump to. /// /// \param MFI The object to dump. LLVM_ABI void dump(raw_ostream &OS, const MergedFunctionsInfo &MFI); /// Dump a CallSiteInfo object. /// /// This function will output the details of a CallSiteInfo object in a /// human-readable format. /// /// \param OS The output stream to dump to. /// /// \param CSI The CallSiteInfo object to dump. LLVM_ABI void dump(raw_ostream &OS, const CallSiteInfo &CSI); /// Dump a CallSiteInfoCollection object. /// /// This function will iterate over a collection of CallSiteInfo objects and /// dump each one. /// /// \param OS The output stream to dump to. /// /// \param CSIC The CallSiteInfoCollection object to dump. /// /// \param Indent The indentation as number of spaces. Used when dumping as an /// item from within MergedFunctionsInfo. LLVM_ABI void dump(raw_ostream &OS, const CallSiteInfoCollection &CSIC, uint32_t Indent = 0); /// Dump a LineTable object. /// /// This function will convert any string table indexes and file indexes /// into human readable format. /// /// /// \param OS The output stream to dump to. /// /// \param LT The object to dump. /// /// \param Indent The indentation as number of spaces. Used when dumping as an /// item from within MergedFunctionsInfo. LLVM_ABI void dump(raw_ostream &OS, const LineTable <, uint32_t Indent = 0); /// Dump a InlineInfo object. /// /// This function will convert any string table indexes and file indexes /// into human readable format. /// /// \param OS The output stream to dump to. /// /// \param II The object to dump. /// /// \param Indent The indentation as number of spaces. Used for recurive /// dumping. LLVM_ABI void dump(raw_ostream &OS, const InlineInfo &II, uint32_t Indent = 0); /// Dump a FileEntry object. /// /// This function will convert any string table indexes into human readable /// format. /// /// \param OS The output stream to dump to. /// /// \param FE The object to dump. LLVM_ABI void dump(raw_ostream &OS, std::optional FE); /// Gets an address from the address table. /// /// Addresses are stored as offsets frrom the gsym::Header::BaseAddress. /// /// \param Index A index into the address table. /// \returns A resolved virtual address for adddress in the address table /// or std::nullopt if Index is out of bounds. LLVM_ABI std::optional getAddress(size_t Index) const; protected: /// Get the GlobalData entry for a section type. /// /// \param Type The section type to retrieve. /// \returns The GlobalData entry, or std::nullopt if the section is not /// present. LLVM_ABI std::optional getGlobalData(GlobalInfoType Type) const; /// Get the raw bytes for a required GlobalData section as a StringRef. /// /// \param Type The section type to retrieve. /// \returns The section data, or an error if the section is not present or /// any bytes are not present in the file. LLVM_ABI llvm::Expected getRequiredGlobalDataBytes(GlobalInfoType Type) const; /// Get the raw bytes for an optional GlobalData section as a StringRef. /// /// \param Type The section type to retrieve. /// \returns The section data, or std::nullopt if the section is not present /// or any bytes are not present in the file. LLVM_ABI std::optional getOptionalGlobalDataBytes(GlobalInfoType Type) const; /// Parse the GSYM data from the memory buffer. /// /// \returns Error on failure. LLVM_ABI llvm::Error parse(); /// Parse the version-specific header and populate GlobalDataSections. /// /// \returns Error on failure. virtual llvm::Error parseHeaderAndGlobalDataEntries() = 0; /// Parse and validate the header from the beginning of the memory buffer. /// /// \param OutHdr Output pointer to the parsed header. /// \param OutSwappedHdr Storage for byte-swapped header if needed. /// \returns Error on failure. template llvm::Error parseHeader(const HeaderT *&OutHdr, std::unique_ptr &OutSwappedHdr) { const StringRef Buf = MemBuffer->getBuffer(); if (Buf.size() < HeaderT::getEncodedSize()) return createStringError(std::errc::invalid_argument, "not enough data for a GSYM header"); if (Endian == llvm::endianness::native) { // Non-swap case. Mmap the header. OutHdr = reinterpret_cast(Buf.data()); } else { // Swap case. Decode with a GsymDataExtractor with the correct endianness. GsymDataExtractor Data(Buf, isLittleEndian()); OutSwappedHdr = std::make_unique(); auto ExpectedHdr = HeaderT::decode(Data); if (!ExpectedHdr) return ExpectedHdr.takeError(); *OutSwappedHdr = *ExpectedHdr; OutHdr = OutSwappedHdr.get(); } if (Error Err = OutHdr->checkForError()) return Err; return Error::success(); } /// Parse GlobalData entries starting at \p Offset into GlobalDataSections. /// /// This should only be called by any GSYM version >= 2. If called by V1, an /// error will be returned. /// /// \param Offset The byte offset where GlobalData entries begin. /// \returns Error on failure. LLVM_ABI llvm::Error parseGlobalDataEntries(uint64_t Offset); /// Parse address offsets section bytes into AddrOffsets. /// /// \param Bytes The raw section bytes. /// \returns Error on failure. LLVM_ABI llvm::Error parseAddrOffsets(StringRef Bytes); /// Set address info offsets section bytes into AddrInfoOffsetsData. /// /// \param Bytes The raw section bytes. /// \returns Error on failure. LLVM_ABI llvm::Error setAddrInfoOffsetsData(StringRef Bytes); /// Set string table section bytes into StrTab. /// /// \param Bytes The raw section bytes. /// \returns Error on failure. LLVM_ABI llvm::Error setStringTableData(StringRef Bytes); /// Set file table section bytes into FileEntryData. /// /// \param Bytes The raw section bytes. /// \returns Error on failure. LLVM_ABI llvm::Error setFileTableData(StringRef Bytes); /// Get an appropriate address info offsets array. /// /// The address table in the GSYM file is stored as array of 1, 2, 4 or 8 /// byte offsets from the The gsym::Header::BaseAddress. The table is stored /// internally as a array of bytes that are in the correct endianness. When /// we access this table we must get an array that matches those sizes. This /// templatized helper function is used when accessing address offsets in the /// AddrOffsets member variable. /// /// \returns An ArrayRef of an appropriate address offset size. template ArrayRef getAddrOffsets() const { return ArrayRef(reinterpret_cast(AddrOffsets.data()), AddrOffsets.size()/sizeof(T)); } /// Get an appropriate address from the address table. /// /// The address table in the GSYM file is stored as array of 1, 2, 4 or 8 /// byte address offsets from the The gsym::Header::BaseAddress. The table is /// stored internally as a array of bytes that are in the correct endianness. /// In order to extract an address from the address table we must access the /// address offset using the correct size and then add it to the BaseAddress /// in the header. /// /// \param Index An index into the AddrOffsets array. /// \returns An virtual address that matches the original object file for the /// address as the specified index, or std::nullopt if Index is out of bounds. template std::optional addressForIndex(size_t Index) const { ArrayRef AIO = getAddrOffsets(); if (Index < AIO.size()) return AIO[Index] + getBaseAddress(); return std::nullopt; } /// Lookup an address offset in the AddrOffsets table. /// /// Given an address offset, look it up using a binary search of the /// AddrOffsets table. /// /// \param AddrOffset An address offset, that has already been computed by /// subtracting the gsym::Header::BaseAddress. /// \returns The matching address offset index. This index will be used to /// extract the FunctionInfo data's offset from the AddrInfoOffsets array. template std::optional getAddressOffsetIndex(const uint64_t AddrOffset) const { ArrayRef AIO = getAddrOffsets(); const auto Begin = AIO.begin(); const auto End = AIO.end(); auto Iter = std::lower_bound(Begin, End, AddrOffset); // Watch for addresses that fall between the gsym::Header::BaseAddress and // the first address offset. if (Iter == Begin && AddrOffset < *Begin) return std::nullopt; if (Iter == End || AddrOffset < *Iter) --Iter; // GSYM files have sorted function infos with the most information (line // table and/or inline info) first in the array of function infos, so // always backup as much as possible as long as the address offset is the // same as the previous entry. while (Iter != Begin) { auto Prev = Iter - 1; if (*Prev == *Iter) Iter = Prev; else break; } return std::distance(Begin, Iter); } /// Create a GSYM from a memory buffer. /// /// Called by both openFile() and copyBuffer(), this function does all of the /// work of parsing the GSYM file and returning an error. /// /// \param MemBuffer A memory buffer that will transfer ownership into the /// GsymReader. /// \returns An expected GsymReader that contains the object or an error /// object that indicates reason for failing to read the GSYM. LLVM_ABI static llvm::Expected> create(std::unique_ptr &MemBuffer); /// Given an address, find the address index. /// /// Binary search the address table and find the matching address index. /// /// \param Addr A virtual address that matches the original object file /// to lookup. /// \returns An index into the address table. This index can be used to /// extract the FunctionInfo data's offset from the AddrInfoOffsets array. /// Returns an error if the address isn't in the GSYM with details of why. LLVM_ABI Expected getAddressIndex(const uint64_t Addr) const; /// Given an address index, get the offset for the FunctionInfo. /// /// Looking up an address is done by finding the corresponding address /// index for the address. This index is then used to get the offset of the /// FunctionInfo data that we will decode using this function. /// /// \param Index An index into the address table. /// \returns An optional GSYM data offset for the offset of the FunctionInfo /// that needs to be decoded. LLVM_ABI std::optional getAddressInfoOffset(size_t Index) const; /// Given an address, find the correct function info data and function /// address. /// /// Binary search the address table and find the matching address info /// and make sure that the function info contains the address. GSYM allows /// functions to overlap, and the most debug info is contained in the first /// entries due to the sorting when GSYM files are created. We can have /// multiple function info that start at the same address only if their /// address range doesn't match. So find the first entry that matches \a Addr /// and iterate forward until we find one that contains the address. /// /// \param[in] Addr A virtual address that matches the original object file /// to lookup. /// /// \param[out] FuncStartAddr A virtual address that is the base address of /// the function that is used for decoding the FunctionInfo. /// /// \returns An valid data extractor on success, or an error if we fail to /// find the address in a function info or corrrectly decode the data LLVM_ABI llvm::Expected getFunctionInfoDataForAddress(uint64_t Addr, uint64_t &FuncStartAddr) const; /// Get the function data and address given an address index. /// /// \param AddrIdx A address index from the address table. /// /// \returns An expected FunctionInfo that contains the function info object /// or an error object that indicates reason for failing to lookup the /// address. LLVM_ABI llvm::Expected getFunctionInfoDataAtIndex(uint64_t AddrIdx, uint64_t &FuncStartAddr) const; }; } // namespace gsym } // namespace llvm #endif // LLVM_DEBUGINFO_GSYM_GSYMREADER_H