| //===- 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); | |
| } |