//===- 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 #include #include #include #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/MemoryBuffer.h" using namespace llvm; using namespace gsym; GsymReader::GsymReader(std::unique_ptr 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> 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> GsymReader::openFile(StringRef Filename) { // Open the input file and return an appropriate error if needed. ErrorOr> 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> GsymReader::copyBuffer(StringRef Bytes) { auto MemBuffer = MemoryBuffer::getMemBufferCopy(Bytes, "GSYM bytes"); return create(MemBuffer); } llvm::Expected> GsymReader::create(std::unique_ptr &MemBuffer) { if (!MemBuffer) return createStringError(std::errc::invalid_argument, "invalid memory buffer"); Expected> VersionEndianOrErr = checkMagicAndDetectVersionEndian(MemBuffer->getBuffer()); if (!VersionEndianOrErr) return VersionEndianOrErr.takeError(); uint16_t Version; llvm::endianness Endian; std::tie(Version, Endian) = *VersionEndianOrErr; std::unique_ptr 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(Type)); if (GlobalDataSections[GlobalInfoType::AddrOffsets].FileSize != static_cast(getNumAddresses()) * getAddressOffsetSize()) return createStringError(std::errc::invalid_argument, "AddrOffsets section size mismatch"); if (GlobalDataSections[GlobalInfoType::AddrInfoOffsets].FileSize != static_cast(getNumAddresses()) * getAddressInfoOffsetSize()) return createStringError(std::errc::invalid_argument, "AddrInfoOffsets section size mismatch"); // Step 3: Parse each global data section. llvm::Expected 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(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(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( reinterpret_cast(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(SwappedAddrOffsets.data()), NumAddrs)) return createStringError(std::errc::invalid_argument, "failed to read address table"); break; case 4: if (!Data.getU32(&Offset, reinterpret_cast(SwappedAddrOffsets.data()), NumAddrs)) return createStringError(std::errc::invalid_argument, "failed to read address table"); break; case 8: if (!Data.getU64(&Offset, reinterpret_cast(SwappedAddrOffsets.data()), NumAddrs)) return createStringError(std::errc::invalid_argument, "failed to read address table"); break; } AddrOffsets = ArrayRef(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(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 GsymReader::getGlobalData(GlobalInfoType Type) const { auto It = GlobalDataSections.find(Type); if (It == GlobalDataSections.end()) return std::nullopt; return It->second; } llvm::Expected GsymReader::getRequiredGlobalDataBytes(GlobalInfoType Type) const { if (auto Data = getOptionalGlobalDataBytes(Type)) return *Data; const char *TypeName = getNameForGlobalInfoType(Type).data(); std::optional 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 GsymReader::getOptionalGlobalDataBytes(GlobalInfoType Type) const { std::optional 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 GsymReader::getAddress(size_t Index) const { switch (getAddressOffsetSize()) { case 1: return addressForIndex(Index); case 2: return addressForIndex(Index); case 4: return addressForIndex(Index); case 8: return addressForIndex(Index); default: llvm_unreachable("unsupported address offset size"); } return std::nullopt; } std::optional 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 GsymReader::getAddressIndex(const uint64_t Addr) const { const uint64_t BaseAddr = getBaseAddress(); if (Addr >= BaseAddr) { const uint64_t AddrOffset = Addr - BaseAddr; std::optional AddrOffsetIndex; switch (getAddressOffsetSize()) { case 1: AddrOffsetIndex = getAddressOffsetIndex(AddrOffset); break; case 2: AddrOffsetIndex = getAddressOffsetIndex(AddrOffset); break; case 4: AddrOffsetIndex = getAddressOffsetIndex(AddrOffset); break; case 8: AddrOffsetIndex = getAddressOffsetIndex(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 GsymReader::getFunctionInfoDataForAddress(uint64_t Addr, uint64_t &FuncStartAddr) const { Expected 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 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 GsymReader::getFunctionInfoDataAtIndex(uint64_t AddrIdx, uint64_t &FuncStartAddr) const { const std::optional 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 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 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 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 GsymReader::lookup( uint64_t Addr, std::optional *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> GsymReader::lookupAll(uint64_t Addr) const { std::vector Results; std::optional 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; } 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 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 << ""; }