| //===- 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/ADT/StringExtras.h" | |
| #include "llvm/DebugInfo/GSYM/GsymReaderV1.h" | |
| #include "llvm/DebugInfo/GSYM/GsymReaderV2.h" | |
| #include "llvm/DebugInfo/GSYM/Header.h" | |
| #include "llvm/DebugInfo/GSYM/HeaderV2.h" | |
| #include "llvm/DebugInfo/GSYM/InlineInfo.h" | |
| #include "llvm/DebugInfo/GSYM/LineTable.h" | |
| #include "llvm/Support/JSON.h" | |
| #include "llvm/Support/MemoryBuffer.h" | |
| using namespace llvm; | |
| using namespace gsym; | |
| GsymReader::GsymReader(std::unique_ptr<MemoryBuffer> Buffer, | |
| llvm::endianness Endian) | |
| : MemBuffer(std::move(Buffer)), Endian(Endian), | |
| AddrInfoOffsetsData(StringRef(), true), FileEntryData(StringRef(), true) { | |
| } | |
| /// Check magic bytes, determine endianness, and return the GSYM version and | |
| /// endianness. If magic bytes are invalid, return error. | |
| static Expected<std::pair<uint16_t, llvm::endianness>> | |
| checkMagicAndDetectVersionEndian(StringRef Bytes) { | |
| if (Bytes.size() < 6) | |
| return createStringError(std::errc::invalid_argument, | |
| "data too small to be a GSYM file"); | |
| // Detect host endian | |
| const auto HostEndian = llvm::endianness::native; | |
| const bool IsHostLittleEndian = (HostEndian == llvm::endianness::little); | |
| // Read magic bytes using host endian | |
| GsymDataExtractor Data(Bytes, IsHostLittleEndian); | |
| uint64_t Offset = 0; | |
| uint32_t Magic = Data.getU32(&Offset); | |
| llvm::endianness FileEndian; | |
| // If magic bytes looks alright, the host and the file have the same | |
| // endianness, vice versa. | |
| if (Magic == GSYM_MAGIC) { | |
| FileEndian = HostEndian; | |
| } else if (Magic == GSYM_CIGAM) { | |
| FileEndian = | |
| IsHostLittleEndian ? llvm::endianness::big : llvm::endianness::little; | |
| // Re-create GsymDataExtractor with correct endianness to read version. | |
| Data = GsymDataExtractor(Bytes, !IsHostLittleEndian); | |
| } else { | |
| return createStringError(std::errc::invalid_argument, | |
| "not a GSYM file (bad magic)"); | |
| } | |
| // Read version using the correct endian | |
| uint16_t Version = Data.getU16(&Offset); | |
| return std::make_pair(Version, FileEndian); | |
| } | |
| llvm::Expected<std::unique_ptr<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); | |
| auto &Buf = BuffOrErr.get(); | |
| Buf->randomAccessIfMmap(); | |
| return create(Buf); | |
| } | |
| llvm::Expected<std::unique_ptr<GsymReader>> | |
| GsymReader::copyBuffer(StringRef Bytes) { | |
| auto MemBuffer = MemoryBuffer::getMemBufferCopy(Bytes, "GSYM bytes"); | |
| return create(MemBuffer); | |
| } | |
| llvm::Expected<std::unique_ptr<GsymReader>> | |
| GsymReader::create(std::unique_ptr<MemoryBuffer> &MemBuffer) { | |
| if (!MemBuffer) | |
| return createStringError(std::errc::invalid_argument, | |
| "invalid memory buffer"); | |
| Expected<std::pair<uint16_t, llvm::endianness>> VersionEndianOrErr = | |
| checkMagicAndDetectVersionEndian(MemBuffer->getBuffer()); | |
| if (!VersionEndianOrErr) | |
| return VersionEndianOrErr.takeError(); | |
| uint16_t Version; | |
| llvm::endianness Endian; | |
| std::tie(Version, Endian) = *VersionEndianOrErr; | |
| std::unique_ptr<GsymReader> GR; | |
| switch (Version) { | |
| case Header::getVersion(): | |
| GR.reset(new GsymReaderV1(std::move(MemBuffer), Endian)); | |
| break; | |
| case HeaderV2::getVersion(): | |
| GR.reset(new GsymReaderV2(std::move(MemBuffer), Endian)); | |
| break; | |
| default: | |
| return createStringError(std::errc::invalid_argument, | |
| "unsupported GSYM version %u", Version); | |
| } | |
| if (auto Err = GR->parse()) | |
| return std::move(Err); | |
| return std::move(GR); | |
| } | |
| llvm::Error GsymReader::parse() { | |
| // Step 1: Parse the version-specific header and populate GlobalDataSections. | |
| if (auto Err = parseHeaderAndGlobalDataEntries()) | |
| return Err; | |
| // Step 2: Validate that all required sections are present and consistent. | |
| for (auto Type : | |
| {GlobalInfoType::AddrOffsets, GlobalInfoType::AddrInfoOffsets, | |
| GlobalInfoType::StringTable, GlobalInfoType::FileTable, | |
| GlobalInfoType::FunctionInfo}) | |
| if (!GlobalDataSections.count(Type)) | |
| return createStringError( | |
| std::errc::invalid_argument, "missing required section type %s (%u)", | |
| getNameForGlobalInfoType(Type).data(), static_cast<uint32_t>(Type)); | |
| if (GlobalDataSections[GlobalInfoType::AddrOffsets].FileSize != | |
| static_cast<uint64_t>(getNumAddresses()) * getAddressOffsetSize()) | |
| return createStringError(std::errc::invalid_argument, | |
| "AddrOffsets section size mismatch"); | |
| if (GlobalDataSections[GlobalInfoType::AddrInfoOffsets].FileSize != | |
| static_cast<uint64_t>(getNumAddresses()) * getAddressInfoOffsetSize()) | |
| return createStringError(std::errc::invalid_argument, | |
| "AddrInfoOffsets section size mismatch"); | |
| // Step 3: Parse each global data section. | |
| llvm::Expected<StringRef> Bytes = | |
| getRequiredGlobalDataBytes(GlobalInfoType::AddrOffsets); | |
| if (!Bytes) | |
| return Bytes.takeError(); | |
| if (auto Err = parseAddrOffsets(*Bytes)) | |
| return Err; | |
| Bytes = getRequiredGlobalDataBytes(GlobalInfoType::AddrInfoOffsets); | |
| if (!Bytes) | |
| return Bytes.takeError(); | |
| if (auto Err = setAddrInfoOffsetsData(*Bytes)) | |
| return Err; | |
| Bytes = getRequiredGlobalDataBytes(GlobalInfoType::StringTable); | |
| if (!Bytes) | |
| return Bytes.takeError(); | |
| if (auto Err = setStringTableData(*Bytes)) | |
| return Err; | |
| Bytes = getRequiredGlobalDataBytes(GlobalInfoType::FileTable); | |
| if (!Bytes) | |
| return Bytes.takeError(); | |
| if (auto Err = setFileTableData(*Bytes)) | |
| return Err; | |
| return Error::success(); | |
| } | |
| llvm::Error GsymReader::parseGlobalDataEntries(uint64_t Offset) { | |
| if (getVersion() < HeaderV2::getVersion()) | |
| return createStringError(std::errc::invalid_argument, | |
| "GlobalData section not supported in GSYM V1"); | |
| const StringRef Buf = MemBuffer->getBuffer(); | |
| const uint64_t BufSize = Buf.size(); | |
| GsymDataExtractor Data(Buf, isLittleEndian()); | |
| while (Offset + sizeof(GlobalData) <= BufSize) { | |
| auto GDOrErr = GlobalData::decode(Data, Offset); | |
| if (!GDOrErr) | |
| return GDOrErr.takeError(); | |
| const GlobalData &GD = *GDOrErr; | |
| if (GD.Type == GlobalInfoType::EndOfList) | |
| return Error::success(); | |
| if (GD.FileSize == 0) | |
| return createStringError(std::errc::invalid_argument, | |
| "GlobalData section type %u has zero size", | |
| static_cast<uint32_t>(GD.Type)); | |
| if (GD.FileOffset + GD.FileSize > BufSize) | |
| return createStringError( | |
| std::errc::invalid_argument, | |
| "GlobalData section type %u extends beyond " | |
| "buffer (offset=%" PRIu64 ", size=%" PRIu64 ", bufsize=%" PRIu64 ")", | |
| static_cast<uint32_t>(GD.Type), GD.FileOffset, GD.FileSize, BufSize); | |
| GlobalDataSections[GD.Type] = GD; | |
| } | |
| return createStringError(std::errc::invalid_argument, | |
| "GlobalData array not terminated by EndOfList"); | |
| } | |
| llvm::Error GsymReader::parseAddrOffsets(StringRef Bytes) { | |
| const uint8_t AddrOffSize = getAddressOffsetSize(); | |
| const uint32_t NumAddrs = getNumAddresses(); | |
| const size_t TotalBytes = NumAddrs * AddrOffSize; | |
| if (Bytes.size() < TotalBytes) | |
| return createStringError(std::errc::invalid_argument, | |
| "failed to read address table"); | |
| // Parse the non-swap case | |
| if (Endian == llvm::endianness::native) { | |
| AddrOffsets = ArrayRef<uint8_t>( | |
| reinterpret_cast<const uint8_t *>(Bytes.data()), TotalBytes); | |
| return Error::success(); | |
| } | |
| // Parse the swap case | |
| GsymDataExtractor Data(Bytes, isLittleEndian()); | |
| uint64_t Offset = 0; | |
| SwappedAddrOffsets.resize(TotalBytes); | |
| switch (AddrOffSize) { | |
| case 1: | |
| if (!Data.getU8(&Offset, SwappedAddrOffsets.data(), NumAddrs)) | |
| return createStringError(std::errc::invalid_argument, | |
| "failed to read address table"); | |
| break; | |
| case 2: | |
| if (!Data.getU16(&Offset, | |
| reinterpret_cast<uint16_t *>(SwappedAddrOffsets.data()), | |
| NumAddrs)) | |
| return createStringError(std::errc::invalid_argument, | |
| "failed to read address table"); | |
| break; | |
| case 4: | |
| if (!Data.getU32(&Offset, | |
| reinterpret_cast<uint32_t *>(SwappedAddrOffsets.data()), | |
| NumAddrs)) | |
| return createStringError(std::errc::invalid_argument, | |
| "failed to read address table"); | |
| break; | |
| case 8: | |
| if (!Data.getU64(&Offset, | |
| reinterpret_cast<uint64_t *>(SwappedAddrOffsets.data()), | |
| NumAddrs)) | |
| return createStringError(std::errc::invalid_argument, | |
| "failed to read address table"); | |
| break; | |
| } | |
| AddrOffsets = ArrayRef<uint8_t>(SwappedAddrOffsets); | |
| return Error::success(); | |
| } | |
| llvm::Error GsymReader::setAddrInfoOffsetsData(StringRef Bytes) { | |
| AddrInfoOffsetsData = GsymDataExtractor(Bytes, isLittleEndian()); | |
| return Error::success(); | |
| } | |
| llvm::Error GsymReader::setStringTableData(StringRef Bytes) { | |
| StrTab.Data = Bytes; | |
| return Error::success(); | |
| } | |
| llvm::Error GsymReader::setFileTableData(StringRef Bytes) { | |
| const uint8_t StrpSize = getStringOffsetSize(); | |
| GsymDataExtractor Data(Bytes, isLittleEndian(), StrpSize); | |
| uint64_t Offset = 0; | |
| uint32_t NumFiles = Data.getU32(&Offset); | |
| uint64_t EntriesSize = | |
| static_cast<uint64_t>(NumFiles) * FileEntry::getEncodedSize(StrpSize); | |
| if (Bytes.size() < Offset + EntriesSize) | |
| return createStringError(std::errc::invalid_argument, | |
| "FileTable section too small for %u files", | |
| NumFiles); | |
| FileEntryData = GsymDataExtractor(Data, Offset, EntriesSize); | |
| return Error::success(); | |
| } | |
| std::optional<GlobalData> GsymReader::getGlobalData(GlobalInfoType Type) const { | |
| auto It = GlobalDataSections.find(Type); | |
| if (It == GlobalDataSections.end()) | |
| return std::nullopt; | |
| return It->second; | |
| } | |
| llvm::Expected<StringRef> | |
| GsymReader::getRequiredGlobalDataBytes(GlobalInfoType Type) const { | |
| if (auto Data = getOptionalGlobalDataBytes(Type)) | |
| return *Data; | |
| const char *TypeName = getNameForGlobalInfoType(Type).data(); | |
| std::optional<GlobalData> GD = getGlobalData(Type); | |
| // We have a GlobalData entry but didn't get any bytes — the file may be | |
| // truncated. | |
| if (GD) | |
| return createStringError( | |
| std::errc::invalid_argument, | |
| "missing bytes for %s, GSYM file might be truncated", TypeName); | |
| return createStringError(std::errc::invalid_argument, | |
| "missing required section type %s", TypeName); | |
| } | |
| std::optional<StringRef> | |
| GsymReader::getOptionalGlobalDataBytes(GlobalInfoType Type) const { | |
| std::optional<GlobalData> GD = getGlobalData(Type); | |
| if (!GD) | |
| return std::nullopt; | |
| StringRef Buf = MemBuffer->getBuffer(); | |
| if (GD->FileSize == 0 || GD->FileOffset + GD->FileSize > Buf.size()) | |
| return std::nullopt; | |
| return Buf.substr(GD->FileOffset, GD->FileSize); | |
| } | |
| std::optional<uint64_t> GsymReader::getAddress(size_t Index) const { | |
| switch (getAddressOffsetSize()) { | |
| 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); | |
| default: | |
| llvm_unreachable("unsupported address offset size"); | |
| } | |
| return std::nullopt; | |
| } | |
| std::optional<uint64_t> GsymReader::getAddressInfoOffset(size_t Index) const { | |
| if (Index >= getNumAddresses()) | |
| return std::nullopt; | |
| const uint8_t AddrInfoOffsetSize = getAddressInfoOffsetSize(); | |
| uint64_t Offset = Index * AddrInfoOffsetSize; | |
| uint64_t AddrInfoOffset = | |
| AddrInfoOffsetsData.getUnsigned(&Offset, AddrInfoOffsetSize); | |
| // V1 stores absolute file offsets in AddrInfoOffsets, so no base offset is | |
| // needed. V2+ stores offsets relative to the FunctionInfo section start. | |
| if (getVersion() != Header::getVersion()) | |
| AddrInfoOffset += | |
| GlobalDataSections.at(GlobalInfoType::FunctionInfo).FileOffset; | |
| return AddrInfoOffset; | |
| } | |
| Expected<uint64_t> GsymReader::getAddressIndex(const uint64_t Addr) const { | |
| const uint64_t BaseAddr = getBaseAddress(); | |
| if (Addr >= BaseAddr) { | |
| const uint64_t AddrOffset = Addr - BaseAddr; | |
| std::optional<uint64_t> AddrOffsetIndex; | |
| switch (getAddressOffsetSize()) { | |
| case 1: | |
| AddrOffsetIndex = getAddressOffsetIndex<uint8_t>(AddrOffset); | |
| break; | |
| case 2: | |
| AddrOffsetIndex = getAddressOffsetIndex<uint16_t>(AddrOffset); | |
| break; | |
| case 4: | |
| AddrOffsetIndex = getAddressOffsetIndex<uint32_t>(AddrOffset); | |
| break; | |
| case 8: | |
| AddrOffsetIndex = getAddressOffsetIndex<uint64_t>(AddrOffset); | |
| break; | |
| default: | |
| return createStringError(std::errc::invalid_argument, | |
| "unsupported address offset size %u", | |
| getAddressOffsetSize()); | |
| } | |
| if (AddrOffsetIndex) | |
| return *AddrOffsetIndex; | |
| } | |
| return createStringError(std::errc::invalid_argument, | |
| "address 0x%" PRIx64 " is not in GSYM", Addr); | |
| } | |
| llvm::Expected<GsymDataExtractor> | |
| GsymReader::getFunctionInfoDataForAddress(uint64_t Addr, | |
| uint64_t &FuncStartAddr) const { | |
| Expected<uint64_t> ExpectedAddrIdx = getAddressIndex(Addr); | |
| if (!ExpectedAddrIdx) | |
| return ExpectedAddrIdx.takeError(); | |
| const uint64_t FirstAddrIdx = *ExpectedAddrIdx; | |
| // The AddrIdx is the first index of the function info entries that match | |
| // \a Addr. We need to iterate over all function info objects that start with | |
| // the same address until we find a range that contains \a Addr. | |
| std::optional<uint64_t> FirstFuncStartAddr; | |
| const size_t NumAddresses = getNumAddresses(); | |
| for (uint64_t AddrIdx = FirstAddrIdx; AddrIdx < NumAddresses; ++AddrIdx) { | |
| auto ExpextedData = getFunctionInfoDataAtIndex(AddrIdx, FuncStartAddr); | |
| // If there was an error, return the error. | |
| if (!ExpextedData) | |
| return ExpextedData; | |
| // Remember the first function start address if it hasn't already been set. | |
| // If it is already valid, check to see if it matches the first function | |
| // start address and only continue if it matches. | |
| if (FirstFuncStartAddr.has_value()) { | |
| if (*FirstFuncStartAddr != FuncStartAddr) | |
| break; // Done with consecutive function entries with same address. | |
| } else { | |
| FirstFuncStartAddr = FuncStartAddr; | |
| } | |
| // Make sure the current function address ranges contains \a Addr. | |
| // Some symbols on Darwin don't have valid sizes, so if we run into a | |
| // symbol with zero size, then we have found a match for our address. | |
| // The first thing the encoding of a FunctionInfo object is the function | |
| // size. | |
| uint64_t Offset = 0; | |
| uint32_t FuncSize = ExpextedData->getU32(&Offset); | |
| if (FuncSize == 0 || | |
| AddressRange(FuncStartAddr, FuncStartAddr + FuncSize).contains(Addr)) | |
| return ExpextedData; | |
| } | |
| return createStringError(std::errc::invalid_argument, | |
| "address 0x%" PRIx64 " is not in GSYM", Addr); | |
| } | |
| llvm::Expected<GsymDataExtractor> | |
| GsymReader::getFunctionInfoDataAtIndex(uint64_t AddrIdx, | |
| uint64_t &FuncStartAddr) const { | |
| const std::optional<uint64_t> AddrInfoOffset = getAddressInfoOffset(AddrIdx); | |
| if (AddrInfoOffset == std::nullopt) | |
| return createStringError(std::errc::invalid_argument, | |
| "invalid address index %" PRIu64, AddrIdx); | |
| assert((Endian == endianness::big || Endian == endianness::little) && | |
| "Endian must be either big or little"); | |
| StringRef Bytes = MemBuffer->getBuffer().substr(*AddrInfoOffset); | |
| if (Bytes.empty()) | |
| return createStringError(std::errc::invalid_argument, | |
| "invalid address info offset 0x%" PRIx64, | |
| *AddrInfoOffset); | |
| std::optional<uint64_t> OptFuncStartAddr = getAddress(AddrIdx); | |
| if (!OptFuncStartAddr) | |
| return createStringError(std::errc::invalid_argument, | |
| "failed to extract address[%" PRIu64 "]", AddrIdx); | |
| FuncStartAddr = *OptFuncStartAddr; | |
| GsymDataExtractor Data(Bytes, isLittleEndian(), getStringOffsetSize()); | |
| return Data; | |
| } | |
| llvm::Expected<FunctionInfo> GsymReader::getFunctionInfo(uint64_t Addr) const { | |
| uint64_t FuncStartAddr = 0; | |
| if (auto ExpectedData = getFunctionInfoDataForAddress(Addr, FuncStartAddr)) | |
| return FunctionInfo::decode(*ExpectedData, FuncStartAddr); | |
| else | |
| return ExpectedData.takeError(); | |
| } | |
| llvm::Expected<FunctionInfo> | |
| GsymReader::getFunctionInfoAtIndex(uint64_t Idx) const { | |
| uint64_t FuncStartAddr = 0; | |
| if (auto ExpectedData = getFunctionInfoDataAtIndex(Idx, FuncStartAddr)) | |
| return FunctionInfo::decode(*ExpectedData, FuncStartAddr); | |
| else | |
| return ExpectedData.takeError(); | |
| } | |
| llvm::Expected<LookupResult> GsymReader::lookup( | |
| uint64_t Addr, | |
| std::optional<GsymDataExtractor> *MergedFunctionsData) const { | |
| uint64_t FuncStartAddr = 0; | |
| if (auto ExpectedData = getFunctionInfoDataForAddress(Addr, FuncStartAddr)) | |
| return FunctionInfo::lookup(*ExpectedData, *this, FuncStartAddr, Addr, | |
| MergedFunctionsData); | |
| else | |
| return ExpectedData.takeError(); | |
| } | |
| llvm::Expected<std::vector<LookupResult>> | |
| GsymReader::lookupAll(uint64_t Addr) const { | |
| std::vector<LookupResult> Results; | |
| std::optional<GsymDataExtractor> MergedFunctionsData; | |
| // First perform a lookup to get the primary function info result. | |
| auto MainResult = lookup(Addr, &MergedFunctionsData); | |
| if (!MainResult) | |
| return MainResult.takeError(); | |
| // Add the main result as the first entry. | |
| Results.push_back(std::move(*MainResult)); | |
| // Now process any merged functions data that was found during the lookup. | |
| if (MergedFunctionsData) { | |
| // Get data extractors for each merged function. | |
| auto ExpectedMergedFuncExtractors = | |
| MergedFunctionsInfo::getFuncsDataExtractors(*MergedFunctionsData); | |
| if (!ExpectedMergedFuncExtractors) | |
| return ExpectedMergedFuncExtractors.takeError(); | |
| // Process each merged function data. | |
| for (GsymDataExtractor &MergedData : *ExpectedMergedFuncExtractors) { | |
| if (auto FI = FunctionInfo::lookup(MergedData, *this, | |
| MainResult->FuncRange.start(), Addr)) { | |
| Results.push_back(std::move(*FI)); | |
| } else { | |
| return FI.takeError(); | |
| } | |
| } | |
| } | |
| return Results; | |
| } | |
| /// Format raw UUID bytes as a hex string, using the canonical 8-4-4-4-12 | |
| /// dashed layout for the common 16-byte UUID and plain hex otherwise. | |
| static std::string formatGsymUUID(StringRef Bytes) { | |
| std::string Hex = toHex(Bytes, /*LowerCase=*/false); | |
| if (Bytes.size() == 16) { | |
| Hex.insert(20, "-"); | |
| Hex.insert(16, "-"); | |
| Hex.insert(12, "-"); | |
| Hex.insert(8, "-"); | |
| } | |
| return Hex; | |
| } | |
| void GsymReader::dumpStatistics(raw_ostream &OS, StatisticsFormat Format, | |
| StringRef GSYMPath) { | |
| // The total file size is the size of the in-memory buffer this reader was | |
| // created from, so no filesystem access is required and in-memory GSYM data | |
| // can be analyzed too. | |
| const uint64_t FileSize = MemBuffer->getBufferSize(); | |
| // Section sizes come from the GlobalData directory, which is populated for | |
| // both GSYM v1 and v2 readers, so the same logic works for both versions. | |
| auto SectionSize = [&](GlobalInfoType Type) -> uint64_t { | |
| if (std::optional<GlobalData> GD = getGlobalData(Type)) | |
| return GD->FileSize; | |
| return 0; | |
| }; | |
| const uint64_t AddrTableSize = SectionSize(GlobalInfoType::AddrOffsets); | |
| const uint64_t AddrInfoOffsetsSize = | |
| SectionSize(GlobalInfoType::AddrInfoOffsets); | |
| const uint64_t FileTableSize = SectionSize(GlobalInfoType::FileTable); | |
| const uint64_t StrtabSize = SectionSize(GlobalInfoType::StringTable); | |
| const uint64_t FuncInfoSize = SectionSize(GlobalInfoType::FunctionInfo); | |
| // The V2 GlobalData directory is an on-disk array of 20-byte entries (Type | |
| // u32 | |
| // + FileOffset u64 + FileSize u64) terminated by an EndOfList entry. V1 | |
| // synthesizes its GlobalData entries and has no on-disk directory. | |
| const uint64_t GlobalDataDirSize = | |
| getVersion() >= 2 ? (GlobalDataSections.size() + 1) * 20 : 0; | |
| // In V2 the UUID is its own data section; report its payload separately. In | |
| // V1 the UUID lives inline in the fixed header, so it is already counted | |
| // there. | |
| const uint64_t UUIDSize = | |
| getVersion() >= 2 ? SectionSize(GlobalInfoType::UUID) : 0; | |
| // The fixed file header precedes the GlobalData directory (V2) and the data | |
| // sections. Its V2 size is a constant; in V1 (no on-disk directory) the | |
| // header ends where the earliest data section begins. | |
| uint64_t HeaderSize = HeaderV2::getEncodedSize(); | |
| if (getVersion() < 2) { | |
| uint64_t MinSectionOffset = FileSize; | |
| for (const auto &KV : GlobalDataSections) | |
| MinSectionOffset = std::min(MinSectionOffset, KV.second.FileOffset); | |
| HeaderSize = MinSectionOffset; | |
| } | |
| // Anything left over (alignment padding between sections) is reported as | |
| // padding so that the byte-sizes sum exactly to the file size. | |
| const uint64_t KnownSize = HeaderSize + GlobalDataDirSize + UUIDSize + | |
| AddrTableSize + AddrInfoOffsetsSize + | |
| FileTableSize + StrtabSize + FuncInfoSize; | |
| const uint64_t PaddingSize = FileSize > KnownSize ? FileSize - KnownSize : 0; | |
| const uint64_t NumAddresses = getNumAddresses(); | |
| // Walk every FunctionInfo to accumulate the per-field byte sizes. | |
| FunctionInfoStats FI; | |
| FunctionInfoStats Merged; | |
| for (uint64_t I = 0; I < NumAddresses; ++I) { | |
| uint64_t FuncStartAddr = 0; | |
| if (auto ExpData = getFunctionInfoDataAtIndex(I, FuncStartAddr)) { | |
| GsymDataExtractor Data = std::move(*ExpData); | |
| FunctionInfo::parseStatistics(Data, FI, &Merged); | |
| } else { | |
| consumeError(ExpData.takeError()); | |
| } | |
| } | |
| // Alignment padding between top-level FunctionInfos (each is 4-byte aligned) | |
| // is not attributed to any per-function field; report it as the remainder so | |
| // that the sum of the type sizes equals function_info_data. | |
| const uint64_t FIAttributed = FI.SizeAndName + FI.LineTableInfo + | |
| FI.InlineInfo + FI.CallSiteInfo + | |
| FI.MergedFuncInfo + FI.EndOfList; | |
| const uint64_t Padding = | |
| FuncInfoSize > FIAttributed ? FuncInfoSize - FIAttributed : 0; | |
| const std::string UUIDStr = formatGsymUUID(getUUID()); | |
| if (Format == StatisticsFormat::JSON || | |
| Format == StatisticsFormat::PrettyJSON) { | |
| json::Object MergedTypes{ | |
| {"infotype_infolength_count_and_fnsize", | |
| static_cast<int64_t>(Merged.InfoTypeInfoLengthCountAndFnSize)}, | |
| {"size_and_name", static_cast<int64_t>(Merged.SizeAndName)}, | |
| {"line_table_info", static_cast<int64_t>(Merged.LineTableInfo)}, | |
| {"inline_info", static_cast<int64_t>(Merged.InlineInfo)}, | |
| {"call_site_info", static_cast<int64_t>(Merged.CallSiteInfo)}, | |
| {"merged_func_info", static_cast<int64_t>(Merged.MergedFuncInfo)}, | |
| {"end_of_list", static_cast<int64_t>(Merged.EndOfList)}}; | |
| json::Object FuncTypes{ | |
| {"size_and_name", static_cast<int64_t>(FI.SizeAndName)}, | |
| {"line_table_info", static_cast<int64_t>(FI.LineTableInfo)}, | |
| {"inline_info", static_cast<int64_t>(FI.InlineInfo)}, | |
| {"call_site_info", static_cast<int64_t>(FI.CallSiteInfo)}, | |
| {"merged_func_info", static_cast<int64_t>(FI.MergedFuncInfo)}, | |
| {"end_of_list", static_cast<int64_t>(FI.EndOfList)}, | |
| {"padding", static_cast<int64_t>(Padding)}, | |
| {"merged_func_info_type_sizes", std::move(MergedTypes)}}; | |
| json::Object ByteSizes{ | |
| {"file_size", static_cast<int64_t>(FileSize)}, | |
| {"header", static_cast<int64_t>(HeaderSize)}, | |
| {"global_data_directory", static_cast<int64_t>(GlobalDataDirSize)}, | |
| {"uuid_section", static_cast<int64_t>(UUIDSize)}, | |
| {"padding", static_cast<int64_t>(PaddingSize)}, | |
| {"address_table", static_cast<int64_t>(AddrTableSize)}, | |
| {"addr_info_offsets", static_cast<int64_t>(AddrInfoOffsetsSize)}, | |
| {"file_table", static_cast<int64_t>(FileTableSize)}, | |
| {"string_table", static_cast<int64_t>(StrtabSize)}, | |
| {"function_info_data", static_cast<int64_t>(FuncInfoSize)}, | |
| {"function_info_type_sizes", std::move(FuncTypes)}}; | |
| json::Object Root{{"path", GSYMPath.str()}, | |
| {"uuid", UUIDStr}, | |
| {"num_addresses", static_cast<int64_t>(NumAddresses)}, | |
| {"byte-sizes", std::move(ByteSizes)}}; | |
| json::Value V(std::move(Root)); | |
| if (Format == StatisticsFormat::PrettyJSON) | |
| OS << formatv("{0:2}", V) << "\n"; | |
| else | |
| OS << V << "\n"; | |
| return; | |
| } | |
| // Text format output. | |
| auto Fmt = [](uint64_t Value) { | |
| std::string Num = std::to_string(Value); | |
| int InsertPosition = Num.length() - 3; | |
| while (InsertPosition > 0) { | |
| Num.insert(InsertPosition, ","); | |
| InsertPosition -= 3; | |
| } | |
| return std::string(std::max((size_t)0, 14 - Num.length()), ' ') + Num; | |
| }; | |
| auto Pct = [&](uint64_t Value) -> std::string { | |
| char Buf[16]; | |
| snprintf(Buf, sizeof(Buf), "(%5.2f%%)", 100.0 * Value / FileSize); | |
| return Buf; | |
| }; | |
| OS << "GSYM statistics for \"" << GSYMPath << "\":\n"; | |
| OS << " UUID: " << UUIDStr << "\n"; | |
| OS << " Number of addresses: " << Fmt(NumAddresses) << "\n"; | |
| OS << " File size: " << Fmt(FileSize) << " bytes\n"; | |
| OS << " Header: " << Fmt(HeaderSize) << " bytes " | |
| << Pct(HeaderSize) << "\n"; | |
| OS << " Global data dir: " << Fmt(GlobalDataDirSize) << " bytes " | |
| << Pct(GlobalDataDirSize) << "\n"; | |
| OS << " UUID section: " << Fmt(UUIDSize) << " bytes " << Pct(UUIDSize) | |
| << "\n"; | |
| OS << " Address table: " << Fmt(AddrTableSize) << " bytes " | |
| << Pct(AddrTableSize) << "\n"; | |
| OS << " Addr info offsets: " << Fmt(AddrInfoOffsetsSize) << " bytes " | |
| << Pct(AddrInfoOffsetsSize) << "\n"; | |
| OS << " File table: " << Fmt(FileTableSize) << " bytes " | |
| << Pct(FileTableSize) << "\n"; | |
| OS << " String table: " << Fmt(StrtabSize) << " bytes " | |
| << Pct(StrtabSize) << "\n"; | |
| OS << " Function info data: " << Fmt(FuncInfoSize) << " bytes " | |
| << Pct(FuncInfoSize) << "\n"; | |
| OS << " Size and name: " << Fmt(FI.SizeAndName) << " bytes " | |
| << Pct(FI.SizeAndName) << "\n"; | |
| OS << " Line table info: " << Fmt(FI.LineTableInfo) << " bytes " | |
| << Pct(FI.LineTableInfo) << "\n"; | |
| OS << " Inline info: " << Fmt(FI.InlineInfo) << " bytes " | |
| << Pct(FI.InlineInfo) << "\n"; | |
| OS << " Call site info: " << Fmt(FI.CallSiteInfo) << " bytes " | |
| << Pct(FI.CallSiteInfo) << "\n"; | |
| OS << " End of list: " << Fmt(FI.EndOfList) << " bytes " | |
| << Pct(FI.EndOfList) << "\n"; | |
| OS << " Padding: " << Fmt(Padding) << " bytes " << Pct(Padding) | |
| << "\n"; | |
| OS << " Merged func info: " << Fmt(FI.MergedFuncInfo) << " bytes " | |
| << Pct(FI.MergedFuncInfo) << "\n"; | |
| OS << " InfoType/InfoLength/Count/FnSize: " | |
| << Fmt(Merged.InfoTypeInfoLengthCountAndFnSize) << " bytes " | |
| << Pct(Merged.InfoTypeInfoLengthCountAndFnSize) << "\n"; | |
| OS << " Size and name: " << Fmt(Merged.SizeAndName) << " bytes " | |
| << Pct(Merged.SizeAndName) << "\n"; | |
| OS << " Line table info: " << Fmt(Merged.LineTableInfo) << " bytes " | |
| << Pct(Merged.LineTableInfo) << "\n"; | |
| OS << " Inline info: " << Fmt(Merged.InlineInfo) << " bytes " | |
| << Pct(Merged.InlineInfo) << "\n"; | |
| OS << " Call site info: " << Fmt(Merged.CallSiteInfo) << " bytes " | |
| << Pct(Merged.CallSiteInfo) << "\n"; | |
| OS << " Merged func info:" << Fmt(Merged.MergedFuncInfo) << " bytes " | |
| << Pct(Merged.MergedFuncInfo) << "\n"; | |
| OS << " End of list: " << Fmt(Merged.EndOfList) << " bytes " | |
| << Pct(Merged.EndOfList) << "\n"; | |
| OS << " Padding: " << Fmt(PaddingSize) << " bytes " | |
| << Pct(PaddingSize) << "\n"; | |
| } | |
| void GsymReader::dump(raw_ostream &OS, const FunctionInfo &FI, | |
| uint32_t Indent) { | |
| OS.indent(Indent); | |
| OS << FI.Range << " \"" << getString(FI.Name) << "\"\n"; | |
| if (FI.OptLineTable) | |
| dump(OS, *FI.OptLineTable, Indent); | |
| if (FI.Inline) | |
| dump(OS, *FI.Inline, Indent); | |
| if (FI.CallSites) | |
| dump(OS, *FI.CallSites, Indent); | |
| if (FI.MergedFunctions) { | |
| assert(Indent == 0 && "MergedFunctionsInfo should only exist at top level"); | |
| dump(OS, *FI.MergedFunctions); | |
| } | |
| } | |
| void GsymReader::dump(raw_ostream &OS, const MergedFunctionsInfo &MFI) { | |
| for (uint32_t inx = 0; inx < MFI.MergedFunctions.size(); inx++) { | |
| OS << "++ Merged FunctionInfos[" << inx << "]:\n"; | |
| dump(OS, MFI.MergedFunctions[inx], 4); | |
| } | |
| } | |
| void GsymReader::dump(raw_ostream &OS, const CallSiteInfo &CSI) { | |
| OS << HEX16(CSI.ReturnOffset); | |
| std::string Flags; | |
| auto addFlag = [&](const char *Flag) { | |
| if (!Flags.empty()) | |
| Flags += " | "; | |
| Flags += Flag; | |
| }; | |
| if (CSI.Flags == CallSiteInfo::Flags::None) | |
| Flags = "None"; | |
| else { | |
| if (CSI.Flags & CallSiteInfo::Flags::InternalCall) | |
| addFlag("InternalCall"); | |
| if (CSI.Flags & CallSiteInfo::Flags::ExternalCall) | |
| addFlag("ExternalCall"); | |
| } | |
| OS << " Flags[" << Flags << "]"; | |
| if (!CSI.MatchRegex.empty()) { | |
| OS << " MatchRegex["; | |
| for (uint32_t i = 0; i < CSI.MatchRegex.size(); ++i) { | |
| if (i > 0) | |
| OS << ";"; | |
| OS << getString(CSI.MatchRegex[i]); | |
| } | |
| OS << "]"; | |
| } | |
| } | |
| void GsymReader::dump(raw_ostream &OS, const CallSiteInfoCollection &CSIC, | |
| uint32_t Indent) { | |
| OS.indent(Indent); | |
| OS << "CallSites (by relative return offset):\n"; | |
| for (const auto &CS : CSIC.CallSites) { | |
| OS.indent(Indent); | |
| OS << " "; | |
| dump(OS, CS); | |
| OS << "\n"; | |
| } | |
| } | |
| void GsymReader::dump(raw_ostream &OS, const LineTable <, uint32_t Indent) { | |
| OS.indent(Indent); | |
| OS << "LineTable:\n"; | |
| for (auto &LE : LT) { | |
| OS.indent(Indent); | |
| OS << " " << HEX64(LE.Addr) << ' '; | |
| if (LE.File) | |
| dump(OS, getFile(LE.File)); | |
| OS << ':' << LE.Line << '\n'; | |
| } | |
| } | |
| void GsymReader::dump(raw_ostream &OS, const InlineInfo &II, uint32_t Indent) { | |
| if (Indent == 0) | |
| OS << "InlineInfo:\n"; | |
| else | |
| OS.indent(Indent); | |
| OS << II.Ranges << ' ' << getString(II.Name); | |
| if (II.CallFile != 0) { | |
| if (auto File = getFile(II.CallFile)) { | |
| OS << " called from "; | |
| dump(OS, File); | |
| OS << ':' << II.CallLine; | |
| } | |
| } | |
| OS << '\n'; | |
| for (const auto &ChildII : II.Children) | |
| dump(OS, ChildII, Indent + 2); | |
| } | |
| void GsymReader::dump(raw_ostream &OS, std::optional<FileEntry> FE) { | |
| if (FE) { | |
| // IF we have the file from index 0, then don't print anything | |
| if (FE->Dir == 0 && FE->Base == 0) | |
| return; | |
| StringRef Dir = getString(FE->Dir); | |
| StringRef Base = getString(FE->Base); | |
| if (!Dir.empty()) { | |
| OS << Dir; | |
| if (Dir.contains('\\') && !Dir.contains('/')) | |
| OS << '\\'; | |
| else | |
| OS << '/'; | |
| } | |
| if (!Base.empty()) { | |
| OS << Base; | |
| } | |
| if (!Dir.empty() || !Base.empty()) | |
| return; | |
| } | |
| OS << "<invalid-file>"; | |
| } |