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