| 1 | //===- GlobalsStream.cpp - PDB Index of Symbols by Name ---------*- C++ -*-===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | // |
| 9 | // The on-disk structores used in this file are based on the reference |
| 10 | // implementation which is available at |
| 11 | // https://github.com/Microsoft/microsoft-pdb/blob/master/PDB/dbi/gsi.h |
| 12 | // |
| 13 | // When you are reading the reference source code, you'd find the |
| 14 | // information below useful. |
| 15 | // |
| 16 | // - ppdb1->m_fMinimalDbgInfo seems to be always true. |
| 17 | // - SMALLBUCKETS macro is defined. |
| 18 | // |
| 19 | //===----------------------------------------------------------------------===// |
| 20 | |
| 21 | #include "llvm/DebugInfo/PDB/Native/GlobalsStream.h" |
| 22 | |
| 23 | #include "llvm/DebugInfo/CodeView/RecordName.h" |
| 24 | #include "llvm/DebugInfo/MSF/MappedBlockStream.h" |
| 25 | #include "llvm/DebugInfo/PDB/Native/Hash.h" |
| 26 | #include "llvm/DebugInfo/PDB/Native/RawError.h" |
| 27 | #include "llvm/DebugInfo/PDB/Native/SymbolStream.h" |
| 28 | #include "llvm/Support/BinaryStreamReader.h" |
| 29 | #include "llvm/Support/Error.h" |
| 30 | |
| 31 | using namespace llvm; |
| 32 | using namespace llvm::msf; |
| 33 | using namespace llvm::pdb; |
| 34 | |
| 35 | GlobalsStream::GlobalsStream(std::unique_ptr<MappedBlockStream> Stream) |
| 36 | : Stream(std::move(Stream)) {} |
| 37 | |
| 38 | GlobalsStream::~GlobalsStream() = default; |
| 39 | |
| 40 | Error GlobalsStream::reload() { |
| 41 | BinaryStreamReader Reader(*Stream); |
| 42 | if (auto E = GlobalsTable.read(Reader)) |
| 43 | return E; |
| 44 | return Error::success(); |
| 45 | } |
| 46 | |
| 47 | std::vector<std::pair<uint32_t, codeview::CVSymbol>> |
| 48 | GlobalsStream::findRecordsByName(StringRef Name, |
| 49 | const SymbolStream &Symbols) const { |
| 50 | std::vector<std::pair<uint32_t, codeview::CVSymbol>> Result; |
| 51 | |
| 52 | // Hash the name to figure out which bucket this goes into. |
| 53 | size_t ExpandedBucketIndex = hashStringV1(Str: Name) % IPHR_HASH; |
| 54 | int32_t CompressedBucketIndex = GlobalsTable.BucketMap[ExpandedBucketIndex]; |
| 55 | if (CompressedBucketIndex == -1) |
| 56 | return Result; |
| 57 | |
| 58 | uint32_t LastBucketIndex = GlobalsTable.HashBuckets.size() - 1; |
| 59 | uint32_t StartRecordIndex = |
| 60 | GlobalsTable.HashBuckets[CompressedBucketIndex] / 12; |
| 61 | uint32_t EndRecordIndex = 0; |
| 62 | if (LLVM_LIKELY(uint32_t(CompressedBucketIndex) < LastBucketIndex)) { |
| 63 | EndRecordIndex = GlobalsTable.HashBuckets[CompressedBucketIndex + 1]; |
| 64 | } else { |
| 65 | // If this is the last bucket, it consists of all hash records until the end |
| 66 | // of the HashRecords array. |
| 67 | EndRecordIndex = GlobalsTable.HashRecords.size() * 12; |
| 68 | } |
| 69 | |
| 70 | EndRecordIndex /= 12; |
| 71 | |
| 72 | assert(EndRecordIndex <= GlobalsTable.HashRecords.size()); |
| 73 | while (StartRecordIndex < EndRecordIndex) { |
| 74 | PSHashRecord PSH = GlobalsTable.HashRecords[StartRecordIndex]; |
| 75 | uint32_t Off = PSH.Off - 1; |
| 76 | codeview::CVSymbol Record = Symbols.readRecord(Offset: Off); |
| 77 | if (codeview::getSymbolName(Sym: Record) == Name) |
| 78 | Result.push_back(x: std::make_pair(x&: Off, y: std::move(Record))); |
| 79 | ++StartRecordIndex; |
| 80 | } |
| 81 | return Result; |
| 82 | } |
| 83 | |
| 84 | static Error (const GSIHashHeader *HashHdr) { |
| 85 | if (HashHdr->VerHdr != GSIHashHeader::HdrVersion) |
| 86 | return make_error<RawError>( |
| 87 | Args: raw_error_code::feature_unsupported, |
| 88 | Args: "Encountered unsupported globals stream version." ); |
| 89 | |
| 90 | return Error::success(); |
| 91 | } |
| 92 | |
| 93 | static Error (const GSIHashHeader *&HashHdr, |
| 94 | BinaryStreamReader &Reader) { |
| 95 | if (Reader.readObject(Dest&: HashHdr)) |
| 96 | return make_error<RawError>(Args: raw_error_code::corrupt_file, |
| 97 | Args: "Stream does not contain a GSIHashHeader." ); |
| 98 | |
| 99 | if (HashHdr->VerSignature != GSIHashHeader::HdrSignature) |
| 100 | return make_error<RawError>( |
| 101 | Args: raw_error_code::feature_unsupported, |
| 102 | Args: "GSIHashHeader signature (0xffffffff) not found." ); |
| 103 | |
| 104 | return Error::success(); |
| 105 | } |
| 106 | |
| 107 | static Error (FixedStreamArray<PSHashRecord> &HashRecords, |
| 108 | const GSIHashHeader *HashHdr, |
| 109 | BinaryStreamReader &Reader) { |
| 110 | if (auto EC = checkHashHdrVersion(HashHdr)) |
| 111 | return EC; |
| 112 | |
| 113 | // HashHdr->HrSize specifies the number of bytes of PSHashRecords we have. |
| 114 | // Verify that we can read them all. |
| 115 | if (HashHdr->HrSize % sizeof(PSHashRecord)) |
| 116 | return make_error<RawError>(Args: raw_error_code::corrupt_file, |
| 117 | Args: "Invalid HR array size." ); |
| 118 | uint32_t NumHashRecords = HashHdr->HrSize / sizeof(PSHashRecord); |
| 119 | if (auto EC = Reader.readArray(Array&: HashRecords, NumItems: NumHashRecords)) |
| 120 | return joinErrors(E1: std::move(EC), |
| 121 | E2: make_error<RawError>(Args: raw_error_code::corrupt_file, |
| 122 | Args: "Error reading hash records." )); |
| 123 | |
| 124 | return Error::success(); |
| 125 | } |
| 126 | |
| 127 | static Error |
| 128 | readGSIHashBuckets(FixedStreamArray<support::ulittle32_t> &HashBuckets, |
| 129 | FixedStreamArray<support::ulittle32_t> &HashBitmap, |
| 130 | const GSIHashHeader *HashHdr, |
| 131 | MutableArrayRef<int32_t> BucketMap, |
| 132 | BinaryStreamReader &Reader) { |
| 133 | if (auto EC = checkHashHdrVersion(HashHdr)) |
| 134 | return EC; |
| 135 | |
| 136 | // Before the actual hash buckets, there is a bitmap of length determined by |
| 137 | // IPHR_HASH. |
| 138 | size_t BitmapSizeInBits = alignTo(Value: IPHR_HASH + 1, Align: 32); |
| 139 | uint32_t NumBitmapEntries = BitmapSizeInBits / 32; |
| 140 | if (auto EC = Reader.readArray(Array&: HashBitmap, NumItems: NumBitmapEntries)) |
| 141 | return joinErrors(E1: std::move(EC), |
| 142 | E2: make_error<RawError>(Args: raw_error_code::corrupt_file, |
| 143 | Args: "Could not read a bitmap." )); |
| 144 | uint32_t CompressedBucketIdx = 0; |
| 145 | for (uint32_t I = 0; I <= IPHR_HASH; ++I) { |
| 146 | uint8_t WordIdx = I / 32; |
| 147 | uint8_t BitIdx = I % 32; |
| 148 | bool IsSet = HashBitmap[WordIdx] & (1U << BitIdx); |
| 149 | if (IsSet) { |
| 150 | BucketMap[I] = CompressedBucketIdx++; |
| 151 | } else { |
| 152 | BucketMap[I] = -1; |
| 153 | } |
| 154 | } |
| 155 | |
| 156 | uint32_t NumBuckets = 0; |
| 157 | for (uint32_t B : HashBitmap) |
| 158 | NumBuckets += llvm::popcount(Value: B); |
| 159 | |
| 160 | // Hash buckets follow. |
| 161 | if (auto EC = Reader.readArray(Array&: HashBuckets, NumItems: NumBuckets)) |
| 162 | return joinErrors(E1: std::move(EC), |
| 163 | E2: make_error<RawError>(Args: raw_error_code::corrupt_file, |
| 164 | Args: "Hash buckets corrupted." )); |
| 165 | |
| 166 | return Error::success(); |
| 167 | } |
| 168 | |
| 169 | Error GSIHashTable::read(BinaryStreamReader &Reader) { |
| 170 | if (auto EC = readGSIHashHeader(HashHdr, Reader)) |
| 171 | return EC; |
| 172 | if (auto EC = readGSIHashRecords(HashRecords, HashHdr, Reader)) |
| 173 | return EC; |
| 174 | if (HashHdr->HrSize > 0) |
| 175 | if (auto EC = readGSIHashBuckets(HashBuckets, HashBitmap, HashHdr, |
| 176 | BucketMap, Reader)) |
| 177 | return EC; |
| 178 | return Error::success(); |
| 179 | } |
| 180 | |