1 //===- TpiStreamBuilder.cpp - -------------------------------------------===// 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 #include "llvm/DebugInfo/PDB/Native/TpiStreamBuilder.h" 10 #include "llvm/ADT/ArrayRef.h" 11 #include "llvm/ADT/STLExtras.h" 12 #include "llvm/DebugInfo/CodeView/TypeIndex.h" 13 #include "llvm/DebugInfo/CodeView/TypeRecord.h" 14 #include "llvm/DebugInfo/MSF/MSFBuilder.h" 15 #include "llvm/DebugInfo/MSF/MappedBlockStream.h" 16 #include "llvm/DebugInfo/PDB/Native/PDBFile.h" 17 #include "llvm/DebugInfo/PDB/Native/RawError.h" 18 #include "llvm/DebugInfo/PDB/Native/RawTypes.h" 19 #include "llvm/Support/Allocator.h" 20 #include "llvm/Support/BinaryByteStream.h" 21 #include "llvm/Support/BinaryStreamArray.h" 22 #include "llvm/Support/BinaryStreamReader.h" 23 #include "llvm/Support/BinaryStreamWriter.h" 24 #include "llvm/Support/Endian.h" 25 #include "llvm/Support/Error.h" 26 #include <algorithm> 27 #include <cstdint> 28 #include <numeric> 29 30 using namespace llvm; 31 using namespace llvm::msf; 32 using namespace llvm::pdb; 33 using namespace llvm::support; 34 35 TpiStreamBuilder::TpiStreamBuilder(MSFBuilder &Msf, uint32_t StreamIdx) 36 : Msf(Msf), Allocator(Msf.getAllocator()), Header(nullptr), Idx(StreamIdx) { 37 } 38 39 TpiStreamBuilder::~TpiStreamBuilder() = default; 40 41 void TpiStreamBuilder::setVersionHeader(PdbRaw_TpiVer Version) { 42 VerHeader = Version; 43 } 44 45 void TpiStreamBuilder::updateTypeIndexOffsets(ArrayRef<uint16_t> Sizes) { 46 // If we just crossed an 8KB threshold, add a type index offset. 47 for (uint16_t Size : Sizes) { 48 size_t NewSize = TypeRecordBytes + Size; 49 constexpr size_t EightKB = 8 * 1024; 50 if (NewSize / EightKB > TypeRecordBytes / EightKB || TypeRecordCount == 0) { 51 TypeIndexOffsets.push_back( 52 {codeview::TypeIndex(codeview::TypeIndex::FirstNonSimpleIndex + 53 TypeRecordCount), 54 ulittle32_t(TypeRecordBytes)}); 55 } 56 ++TypeRecordCount; 57 TypeRecordBytes = NewSize; 58 } 59 } 60 61 void TpiStreamBuilder::addTypeRecord(ArrayRef<uint8_t> Record, 62 Optional<uint32_t> Hash) { 63 assert(((Record.size() & 3) == 0) && 64 "The type record's size is not a multiple of 4 bytes which will " 65 "cause misalignment in the output TPI stream!"); 66 assert(Record.size() <= codeview::MaxRecordLength); 67 uint16_t OneSize = (uint16_t)Record.size(); 68 updateTypeIndexOffsets(makeArrayRef(&OneSize, 1)); 69 70 TypeRecBuffers.push_back(Record); 71 // FIXME: Require it. 72 if (Hash) 73 TypeHashes.push_back(*Hash); 74 } 75 76 void TpiStreamBuilder::addTypeRecords(ArrayRef<uint8_t> Types, 77 ArrayRef<uint16_t> Sizes, 78 ArrayRef<uint32_t> Hashes) { 79 // Ignore empty type buffers. There should be no hashes or sizes in this case. 80 if (Types.empty()) { 81 assert(Sizes.empty() && Hashes.empty()); 82 return; 83 } 84 85 assert(((Types.size() & 3) == 0) && 86 "The type record's size is not a multiple of 4 bytes which will " 87 "cause misalignment in the output TPI stream!"); 88 assert(Sizes.size() == Hashes.size() && "sizes and hashes should be in sync"); 89 assert(std::accumulate(Sizes.begin(), Sizes.end(), 0U) == Types.size() && 90 "sizes of type records should sum to the size of the types"); 91 updateTypeIndexOffsets(Sizes); 92 93 TypeRecBuffers.push_back(Types); 94 llvm::append_range(TypeHashes, Hashes); 95 } 96 97 Error TpiStreamBuilder::finalize() { 98 if (Header) 99 return Error::success(); 100 101 TpiStreamHeader *H = Allocator.Allocate<TpiStreamHeader>(); 102 103 H->Version = VerHeader; 104 H->HeaderSize = sizeof(TpiStreamHeader); 105 H->TypeIndexBegin = codeview::TypeIndex::FirstNonSimpleIndex; 106 H->TypeIndexEnd = H->TypeIndexBegin + TypeRecordCount; 107 H->TypeRecordBytes = TypeRecordBytes; 108 109 H->HashStreamIndex = HashStreamIndex; 110 H->HashAuxStreamIndex = kInvalidStreamIndex; 111 H->HashKeySize = sizeof(ulittle32_t); 112 H->NumHashBuckets = MaxTpiHashBuckets - 1; 113 114 // Recall that hash values go into a completely different stream identified by 115 // the `HashStreamIndex` field of the `TpiStreamHeader`. Therefore, the data 116 // begins at offset 0 of this independent stream. 117 H->HashValueBuffer.Off = 0; 118 H->HashValueBuffer.Length = calculateHashBufferSize(); 119 120 // We never write any adjustments into our PDBs, so this is usually some 121 // offset with zero length. 122 H->HashAdjBuffer.Off = H->HashValueBuffer.Off + H->HashValueBuffer.Length; 123 H->HashAdjBuffer.Length = 0; 124 125 H->IndexOffsetBuffer.Off = H->HashAdjBuffer.Off + H->HashAdjBuffer.Length; 126 H->IndexOffsetBuffer.Length = calculateIndexOffsetSize(); 127 128 Header = H; 129 return Error::success(); 130 } 131 132 uint32_t TpiStreamBuilder::calculateSerializedLength() { 133 return sizeof(TpiStreamHeader) + TypeRecordBytes; 134 } 135 136 uint32_t TpiStreamBuilder::calculateHashBufferSize() const { 137 assert((TypeRecordCount == TypeHashes.size() || TypeHashes.empty()) && 138 "either all or no type records should have hashes"); 139 return TypeHashes.size() * sizeof(ulittle32_t); 140 } 141 142 uint32_t TpiStreamBuilder::calculateIndexOffsetSize() const { 143 return TypeIndexOffsets.size() * sizeof(codeview::TypeIndexOffset); 144 } 145 146 Error TpiStreamBuilder::finalizeMsfLayout() { 147 uint32_t Length = calculateSerializedLength(); 148 if (auto EC = Msf.setStreamSize(Idx, Length)) 149 return EC; 150 151 uint32_t HashStreamSize = 152 calculateHashBufferSize() + calculateIndexOffsetSize(); 153 154 if (HashStreamSize == 0) 155 return Error::success(); 156 157 auto ExpectedIndex = Msf.addStream(HashStreamSize); 158 if (!ExpectedIndex) 159 return ExpectedIndex.takeError(); 160 HashStreamIndex = *ExpectedIndex; 161 if (!TypeHashes.empty()) { 162 ulittle32_t *H = Allocator.Allocate<ulittle32_t>(TypeHashes.size()); 163 MutableArrayRef<ulittle32_t> HashBuffer(H, TypeHashes.size()); 164 for (uint32_t I = 0; I < TypeHashes.size(); ++I) { 165 HashBuffer[I] = TypeHashes[I] % (MaxTpiHashBuckets - 1); 166 } 167 ArrayRef<uint8_t> Bytes( 168 reinterpret_cast<const uint8_t *>(HashBuffer.data()), 169 calculateHashBufferSize()); 170 HashValueStream = 171 std::make_unique<BinaryByteStream>(Bytes, llvm::support::little); 172 } 173 return Error::success(); 174 } 175 176 Error TpiStreamBuilder::commit(const msf::MSFLayout &Layout, 177 WritableBinaryStreamRef Buffer) { 178 if (auto EC = finalize()) 179 return EC; 180 181 auto InfoS = WritableMappedBlockStream::createIndexedStream(Layout, Buffer, 182 Idx, Allocator); 183 184 BinaryStreamWriter Writer(*InfoS); 185 if (auto EC = Writer.writeObject(*Header)) 186 return EC; 187 188 for (auto Rec : TypeRecBuffers) { 189 assert(!Rec.empty() && "Attempting to write an empty type record shifts " 190 "all offsets in the TPI stream!"); 191 assert(((Rec.size() & 3) == 0) && 192 "The type record's size is not a multiple of 4 bytes which will " 193 "cause misalignment in the output TPI stream!"); 194 if (auto EC = Writer.writeBytes(Rec)) 195 return EC; 196 } 197 198 if (HashStreamIndex != kInvalidStreamIndex) { 199 auto HVS = WritableMappedBlockStream::createIndexedStream( 200 Layout, Buffer, HashStreamIndex, Allocator); 201 BinaryStreamWriter HW(*HVS); 202 if (HashValueStream) { 203 if (auto EC = HW.writeStreamRef(*HashValueStream)) 204 return EC; 205 } 206 207 for (auto &IndexOffset : TypeIndexOffsets) { 208 if (auto EC = HW.writeObject(IndexOffset)) 209 return EC; 210 } 211 } 212 213 return Error::success(); 214 } 215