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#include <algorithm>
31
32using namespace llvm;
33using namespace llvm::msf;
34using namespace llvm::pdb;
35
36GlobalsStream::GlobalsStream(std::unique_ptr<MappedBlockStream> Stream)
37 : Stream(std::move(Stream)) {}
38
39GlobalsStream::~GlobalsStream() = default;
40
41Error GlobalsStream::reload() {
42 BinaryStreamReader Reader(*Stream);
43 if (auto E = GlobalsTable.read(Reader))
44 return E;
45 return Error::success();
46}
47
48std::vector<std::pair<uint32_t, codeview::CVSymbol>>
49GlobalsStream::findRecordsByName(StringRef Name,
50 const SymbolStream &Symbols) const {
51 std::vector<std::pair<uint32_t, codeview::CVSymbol>> Result;
52
53 // Hash the name to figure out which bucket this goes into.
54 size_t ExpandedBucketIndex = hashStringV1(Str: Name) % IPHR_HASH;
55 int32_t CompressedBucketIndex = GlobalsTable.BucketMap[ExpandedBucketIndex];
56 if (CompressedBucketIndex == -1)
57 return Result;
58
59 uint32_t LastBucketIndex = GlobalsTable.HashBuckets.size() - 1;
60 uint32_t StartRecordIndex =
61 GlobalsTable.HashBuckets[CompressedBucketIndex] / 12;
62 uint32_t EndRecordIndex = 0;
63 if (LLVM_LIKELY(uint32_t(CompressedBucketIndex) < LastBucketIndex)) {
64 EndRecordIndex = GlobalsTable.HashBuckets[CompressedBucketIndex + 1];
65 } else {
66 // If this is the last bucket, it consists of all hash records until the end
67 // of the HashRecords array.
68 EndRecordIndex = GlobalsTable.HashRecords.size() * 12;
69 }
70
71 EndRecordIndex /= 12;
72
73 assert(EndRecordIndex <= GlobalsTable.HashRecords.size());
74 while (StartRecordIndex < EndRecordIndex) {
75 PSHashRecord PSH = GlobalsTable.HashRecords[StartRecordIndex];
76 uint32_t Off = PSH.Off - 1;
77 codeview::CVSymbol Record = Symbols.readRecord(Offset: Off);
78 if (codeview::getSymbolName(Sym: Record) == Name)
79 Result.push_back(x: std::make_pair(x&: Off, y: std::move(Record)));
80 ++StartRecordIndex;
81 }
82 return Result;
83}
84
85static Error checkHashHdrVersion(const GSIHashHeader *HashHdr) {
86 if (HashHdr->VerHdr != GSIHashHeader::HdrVersion)
87 return make_error<RawError>(
88 Args: raw_error_code::feature_unsupported,
89 Args: "Encountered unsupported globals stream version.");
90
91 return Error::success();
92}
93
94static Error readGSIHashHeader(const GSIHashHeader *&HashHdr,
95 BinaryStreamReader &Reader) {
96 if (Reader.readObject(Dest&: HashHdr))
97 return make_error<RawError>(Args: raw_error_code::corrupt_file,
98 Args: "Stream does not contain a GSIHashHeader.");
99
100 if (HashHdr->VerSignature != GSIHashHeader::HdrSignature)
101 return make_error<RawError>(
102 Args: raw_error_code::feature_unsupported,
103 Args: "GSIHashHeader signature (0xffffffff) not found.");
104
105 return Error::success();
106}
107
108static Error readGSIHashRecords(FixedStreamArray<PSHashRecord> &HashRecords,
109 const GSIHashHeader *HashHdr,
110 BinaryStreamReader &Reader) {
111 if (auto EC = checkHashHdrVersion(HashHdr))
112 return EC;
113
114 // HashHdr->HrSize specifies the number of bytes of PSHashRecords we have.
115 // Verify that we can read them all.
116 if (HashHdr->HrSize % sizeof(PSHashRecord))
117 return make_error<RawError>(Args: raw_error_code::corrupt_file,
118 Args: "Invalid HR array size.");
119 uint32_t NumHashRecords = HashHdr->HrSize / sizeof(PSHashRecord);
120 if (auto EC = Reader.readArray(Array&: HashRecords, NumItems: NumHashRecords))
121 return joinErrors(E1: std::move(EC),
122 E2: make_error<RawError>(Args: raw_error_code::corrupt_file,
123 Args: "Error reading hash records."));
124
125 return Error::success();
126}
127
128static Error
129readGSIHashBuckets(FixedStreamArray<support::ulittle32_t> &HashBuckets,
130 FixedStreamArray<support::ulittle32_t> &HashBitmap,
131 const GSIHashHeader *HashHdr,
132 MutableArrayRef<int32_t> BucketMap,
133 BinaryStreamReader &Reader) {
134 if (auto EC = checkHashHdrVersion(HashHdr))
135 return EC;
136
137 // Before the actual hash buckets, there is a bitmap of length determined by
138 // IPHR_HASH.
139 size_t BitmapSizeInBits = alignTo(Value: IPHR_HASH + 1, Align: 32);
140 uint32_t NumBitmapEntries = BitmapSizeInBits / 32;
141 if (auto EC = Reader.readArray(Array&: HashBitmap, NumItems: NumBitmapEntries))
142 return joinErrors(E1: std::move(EC),
143 E2: make_error<RawError>(Args: raw_error_code::corrupt_file,
144 Args: "Could not read a bitmap."));
145 uint32_t CompressedBucketIdx = 0;
146 for (uint32_t I = 0; I <= IPHR_HASH; ++I) {
147 uint8_t WordIdx = I / 32;
148 uint8_t BitIdx = I % 32;
149 bool IsSet = HashBitmap[WordIdx] & (1U << BitIdx);
150 if (IsSet) {
151 BucketMap[I] = CompressedBucketIdx++;
152 } else {
153 BucketMap[I] = -1;
154 }
155 }
156
157 uint32_t NumBuckets = 0;
158 for (uint32_t B : HashBitmap)
159 NumBuckets += llvm::popcount(Value: B);
160
161 // Hash buckets follow.
162 if (auto EC = Reader.readArray(Array&: HashBuckets, NumItems: NumBuckets))
163 return joinErrors(E1: std::move(EC),
164 E2: make_error<RawError>(Args: raw_error_code::corrupt_file,
165 Args: "Hash buckets corrupted."));
166
167 return Error::success();
168}
169
170Error GSIHashTable::read(BinaryStreamReader &Reader) {
171 if (auto EC = readGSIHashHeader(HashHdr, Reader))
172 return EC;
173 if (auto EC = readGSIHashRecords(HashRecords, HashHdr, Reader))
174 return EC;
175 if (HashHdr->HrSize > 0)
176 if (auto EC = readGSIHashBuckets(HashBuckets, HashBitmap, HashHdr,
177 BucketMap, Reader))
178 return EC;
179 return Error::success();
180}
181