|  | //===- GsymReader.cpp -----------------------------------------------------===// | 
|  | // | 
|  | // 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/DebugInfo/GSYM/GsymReader.h" | 
|  |  | 
|  | #include <assert.h> | 
|  | #include <inttypes.h> | 
|  | #include <stdio.h> | 
|  | #include <stdlib.h> | 
|  |  | 
|  | #include "llvm/DebugInfo/GSYM/GsymCreator.h" | 
|  | #include "llvm/DebugInfo/GSYM/InlineInfo.h" | 
|  | #include "llvm/DebugInfo/GSYM/LineTable.h" | 
|  | #include "llvm/Support/BinaryStreamReader.h" | 
|  | #include "llvm/Support/DataExtractor.h" | 
|  | #include "llvm/Support/MemoryBuffer.h" | 
|  |  | 
|  | using namespace llvm; | 
|  | using namespace gsym; | 
|  |  | 
|  | GsymReader::GsymReader(std::unique_ptr<MemoryBuffer> Buffer) : | 
|  | MemBuffer(std::move(Buffer)), | 
|  | Endian(support::endian::system_endianness()) {} | 
|  |  | 
|  | GsymReader::GsymReader(GsymReader &&RHS) = default; | 
|  |  | 
|  | GsymReader::~GsymReader() = default; | 
|  |  | 
|  | llvm::Expected<GsymReader> GsymReader::openFile(StringRef Filename) { | 
|  | // Open the input file and return an appropriate error if needed. | 
|  | ErrorOr<std::unique_ptr<MemoryBuffer>> BuffOrErr = | 
|  | MemoryBuffer::getFileOrSTDIN(Filename); | 
|  | auto Err = BuffOrErr.getError(); | 
|  | if (Err) | 
|  | return llvm::errorCodeToError(Err); | 
|  | return create(BuffOrErr.get()); | 
|  | } | 
|  |  | 
|  | llvm::Expected<GsymReader> GsymReader::copyBuffer(StringRef Bytes) { | 
|  | auto MemBuffer = MemoryBuffer::getMemBufferCopy(Bytes, "GSYM bytes"); | 
|  | return create(MemBuffer); | 
|  | } | 
|  |  | 
|  | llvm::Expected<llvm::gsym::GsymReader> | 
|  | GsymReader::create(std::unique_ptr<MemoryBuffer> &MemBuffer) { | 
|  | if (!MemBuffer.get()) | 
|  | return createStringError(std::errc::invalid_argument, | 
|  | "invalid memory buffer"); | 
|  | GsymReader GR(std::move(MemBuffer)); | 
|  | llvm::Error Err = GR.parse(); | 
|  | if (Err) | 
|  | return std::move(Err); | 
|  | return std::move(GR); | 
|  | } | 
|  |  | 
|  | llvm::Error | 
|  | GsymReader::parse() { | 
|  | BinaryStreamReader FileData(MemBuffer->getBuffer(), | 
|  | support::endian::system_endianness()); | 
|  | // Check for the magic bytes. This file format is designed to be mmap'ed | 
|  | // into a process and accessed as read only. This is done for performance | 
|  | // and efficiency for symbolicating and parsing GSYM data. | 
|  | if (FileData.readObject(Hdr)) | 
|  | return createStringError(std::errc::invalid_argument, | 
|  | "not enough data for a GSYM header"); | 
|  |  | 
|  | const auto HostByteOrder = support::endian::system_endianness(); | 
|  | switch (Hdr->Magic) { | 
|  | case GSYM_MAGIC: | 
|  | Endian = HostByteOrder; | 
|  | break; | 
|  | case GSYM_CIGAM: | 
|  | // This is a GSYM file, but not native endianness. | 
|  | Endian = sys::IsBigEndianHost ? support::little : support::big; | 
|  | Swap.reset(new SwappedData); | 
|  | break; | 
|  | default: | 
|  | return createStringError(std::errc::invalid_argument, | 
|  | "not a GSYM file"); | 
|  | } | 
|  |  | 
|  | bool DataIsLittleEndian = HostByteOrder != support::little; | 
|  | // Read a correctly byte swapped header if we need to. | 
|  | if (Swap) { | 
|  | DataExtractor Data(MemBuffer->getBuffer(), DataIsLittleEndian, 4); | 
|  | if (auto ExpectedHdr = Header::decode(Data)) | 
|  | Swap->Hdr = ExpectedHdr.get(); | 
|  | else | 
|  | return ExpectedHdr.takeError(); | 
|  | Hdr = &Swap->Hdr; | 
|  | } | 
|  |  | 
|  | // Detect errors in the header and report any that are found. If we make it | 
|  | // past this without errors, we know we have a good magic value, a supported | 
|  | // version number, verified address offset size and a valid UUID size. | 
|  | if (Error Err = Hdr->checkForError()) | 
|  | return Err; | 
|  |  | 
|  | if (!Swap) { | 
|  | // This is the native endianness case that is most common and optimized for | 
|  | // efficient lookups. Here we just grab pointers to the native data and | 
|  | // use ArrayRef objects to allow efficient read only access. | 
|  |  | 
|  | // Read the address offsets. | 
|  | if (FileData.padToAlignment(Hdr->AddrOffSize) || | 
|  | FileData.readArray(AddrOffsets, | 
|  | Hdr->NumAddresses * Hdr->AddrOffSize)) | 
|  | return createStringError(std::errc::invalid_argument, | 
|  | "failed to read address table"); | 
|  |  | 
|  | // Read the address info offsets. | 
|  | if (FileData.padToAlignment(4) || | 
|  | FileData.readArray(AddrInfoOffsets, Hdr->NumAddresses)) | 
|  | return createStringError(std::errc::invalid_argument, | 
|  | "failed to read address info offsets table"); | 
|  |  | 
|  | // Read the file table. | 
|  | uint32_t NumFiles = 0; | 
|  | if (FileData.readInteger(NumFiles) || FileData.readArray(Files, NumFiles)) | 
|  | return createStringError(std::errc::invalid_argument, | 
|  | "failed to read file table"); | 
|  |  | 
|  | // Get the string table. | 
|  | FileData.setOffset(Hdr->StrtabOffset); | 
|  | if (FileData.readFixedString(StrTab.Data, Hdr->StrtabSize)) | 
|  | return createStringError(std::errc::invalid_argument, | 
|  | "failed to read string table"); | 
|  | } else { | 
|  | // This is the non native endianness case that is not common and not | 
|  | // optimized for lookups. Here we decode the important tables into local | 
|  | // storage and then set the ArrayRef objects to point to these swapped | 
|  | // copies of the read only data so lookups can be as efficient as possible. | 
|  | DataExtractor Data(MemBuffer->getBuffer(), DataIsLittleEndian, 4); | 
|  |  | 
|  | // Read the address offsets. | 
|  | uint64_t Offset = alignTo(sizeof(Header), Hdr->AddrOffSize); | 
|  | Swap->AddrOffsets.resize(Hdr->NumAddresses * Hdr->AddrOffSize); | 
|  | switch (Hdr->AddrOffSize) { | 
|  | case 1: | 
|  | if (!Data.getU8(&Offset, Swap->AddrOffsets.data(), Hdr->NumAddresses)) | 
|  | return createStringError(std::errc::invalid_argument, | 
|  | "failed to read address table"); | 
|  | break; | 
|  | case 2: | 
|  | if (!Data.getU16(&Offset, | 
|  | reinterpret_cast<uint16_t *>(Swap->AddrOffsets.data()), | 
|  | Hdr->NumAddresses)) | 
|  | return createStringError(std::errc::invalid_argument, | 
|  | "failed to read address table"); | 
|  | break; | 
|  | case 4: | 
|  | if (!Data.getU32(&Offset, | 
|  | reinterpret_cast<uint32_t *>(Swap->AddrOffsets.data()), | 
|  | Hdr->NumAddresses)) | 
|  | return createStringError(std::errc::invalid_argument, | 
|  | "failed to read address table"); | 
|  | break; | 
|  | case 8: | 
|  | if (!Data.getU64(&Offset, | 
|  | reinterpret_cast<uint64_t *>(Swap->AddrOffsets.data()), | 
|  | Hdr->NumAddresses)) | 
|  | return createStringError(std::errc::invalid_argument, | 
|  | "failed to read address table"); | 
|  | } | 
|  | AddrOffsets = ArrayRef<uint8_t>(Swap->AddrOffsets); | 
|  |  | 
|  | // Read the address info offsets. | 
|  | Offset = alignTo(Offset, 4); | 
|  | Swap->AddrInfoOffsets.resize(Hdr->NumAddresses); | 
|  | if (Data.getU32(&Offset, Swap->AddrInfoOffsets.data(), Hdr->NumAddresses)) | 
|  | AddrInfoOffsets = ArrayRef<uint32_t>(Swap->AddrInfoOffsets); | 
|  | else | 
|  | return createStringError(std::errc::invalid_argument, | 
|  | "failed to read address table"); | 
|  | // Read the file table. | 
|  | const uint32_t NumFiles = Data.getU32(&Offset); | 
|  | if (NumFiles > 0) { | 
|  | Swap->Files.resize(NumFiles); | 
|  | if (Data.getU32(&Offset, &Swap->Files[0].Dir, NumFiles*2)) | 
|  | Files = ArrayRef<FileEntry>(Swap->Files); | 
|  | else | 
|  | return createStringError(std::errc::invalid_argument, | 
|  | "failed to read file table"); | 
|  | } | 
|  | // Get the string table. | 
|  | StrTab.Data = MemBuffer->getBuffer().substr(Hdr->StrtabOffset, | 
|  | Hdr->StrtabSize); | 
|  | if (StrTab.Data.empty()) | 
|  | return createStringError(std::errc::invalid_argument, | 
|  | "failed to read string table"); | 
|  | } | 
|  | return Error::success(); | 
|  |  | 
|  | } | 
|  |  | 
|  | const Header &GsymReader::getHeader() const { | 
|  | // The only way to get a GsymReader is from GsymReader::openFile(...) or | 
|  | // GsymReader::copyBuffer() and the header must be valid and initialized to | 
|  | // a valid pointer value, so the assert below should not trigger. | 
|  | assert(Hdr); | 
|  | return *Hdr; | 
|  | } | 
|  |  | 
|  | Optional<uint64_t> GsymReader::getAddress(size_t Index) const { | 
|  | switch (Hdr->AddrOffSize) { | 
|  | case 1: return addressForIndex<uint8_t>(Index); | 
|  | case 2: return addressForIndex<uint16_t>(Index); | 
|  | case 4: return addressForIndex<uint32_t>(Index); | 
|  | case 8: return addressForIndex<uint64_t>(Index); | 
|  | } | 
|  | return llvm::None; | 
|  | } | 
|  |  | 
|  | Optional<uint64_t> GsymReader::getAddressInfoOffset(size_t Index) const { | 
|  | const auto NumAddrInfoOffsets = AddrInfoOffsets.size(); | 
|  | if (Index < NumAddrInfoOffsets) | 
|  | return AddrInfoOffsets[Index]; | 
|  | return llvm::None; | 
|  | } | 
|  |  | 
|  | Expected<uint64_t> | 
|  | GsymReader::getAddressIndex(const uint64_t Addr) const { | 
|  | if (Addr < Hdr->BaseAddress) | 
|  | return createStringError(std::errc::invalid_argument, | 
|  | "address 0x%" PRIx64 " not in GSYM", Addr); | 
|  | const uint64_t AddrOffset = Addr - Hdr->BaseAddress; | 
|  | switch (Hdr->AddrOffSize) { | 
|  | case 1: return getAddressOffsetIndex<uint8_t>(AddrOffset); | 
|  | case 2: return getAddressOffsetIndex<uint16_t>(AddrOffset); | 
|  | case 4: return getAddressOffsetIndex<uint32_t>(AddrOffset); | 
|  | case 8: return getAddressOffsetIndex<uint64_t>(AddrOffset); | 
|  | default: break; | 
|  | } | 
|  | return createStringError(std::errc::invalid_argument, | 
|  | "unsupported address offset size %u", | 
|  | Hdr->AddrOffSize); | 
|  | } | 
|  |  | 
|  | llvm::Expected<FunctionInfo> GsymReader::getFunctionInfo(uint64_t Addr) const { | 
|  | Expected<uint64_t> AddressIndex = getAddressIndex(Addr); | 
|  | if (!AddressIndex) | 
|  | return AddressIndex.takeError(); | 
|  | // Address info offsets size should have been checked in parse(). | 
|  | assert(*AddressIndex < AddrInfoOffsets.size()); | 
|  | auto AddrInfoOffset = AddrInfoOffsets[*AddressIndex]; | 
|  | DataExtractor Data(MemBuffer->getBuffer().substr(AddrInfoOffset), Endian, 4); | 
|  | if (Optional<uint64_t> OptAddr = getAddress(*AddressIndex)) { | 
|  | auto ExpectedFI = FunctionInfo::decode(Data, *OptAddr); | 
|  | if (ExpectedFI) { | 
|  | if (ExpectedFI->Range.contains(Addr) || ExpectedFI->Range.size() == 0) | 
|  | return ExpectedFI; | 
|  | return createStringError(std::errc::invalid_argument, | 
|  | "address 0x%" PRIx64 " not in GSYM", Addr); | 
|  | } | 
|  | } | 
|  | return createStringError(std::errc::invalid_argument, | 
|  | "failed to extract address[%" PRIu64 "]", | 
|  | *AddressIndex); | 
|  | } | 
|  |  | 
|  | llvm::Expected<LookupResult> GsymReader::lookup(uint64_t Addr) const { | 
|  | Expected<uint64_t> AddressIndex = getAddressIndex(Addr); | 
|  | if (!AddressIndex) | 
|  | return AddressIndex.takeError(); | 
|  | // Address info offsets size should have been checked in parse(). | 
|  | assert(*AddressIndex < AddrInfoOffsets.size()); | 
|  | auto AddrInfoOffset = AddrInfoOffsets[*AddressIndex]; | 
|  | DataExtractor Data(MemBuffer->getBuffer().substr(AddrInfoOffset), Endian, 4); | 
|  | if (Optional<uint64_t> OptAddr = getAddress(*AddressIndex)) | 
|  | return FunctionInfo::lookup(Data, *this, *OptAddr, Addr); | 
|  | return createStringError(std::errc::invalid_argument, | 
|  | "failed to extract address[%" PRIu64 "]", | 
|  | *AddressIndex); | 
|  | } |