//===- TpiStreamBuilder.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/PDB/Native/TpiStreamBuilder.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/STLExtras.h" #include "llvm/DebugInfo/CodeView/TypeIndex.h" #include "llvm/DebugInfo/CodeView/TypeRecord.h" #include "llvm/DebugInfo/MSF/MSFBuilder.h" #include "llvm/DebugInfo/MSF/MappedBlockStream.h" #include "llvm/DebugInfo/PDB/Native/PDBFile.h" #include "llvm/DebugInfo/PDB/Native/RawError.h" #include "llvm/DebugInfo/PDB/Native/RawTypes.h" #include "llvm/Support/Allocator.h" #include "llvm/Support/BinaryByteStream.h" #include "llvm/Support/BinaryStreamArray.h" #include "llvm/Support/BinaryStreamReader.h" #include "llvm/Support/BinaryStreamWriter.h" #include "llvm/Support/Endian.h" #include "llvm/Support/Error.h" #include #include #include using namespace llvm; using namespace llvm::msf; using namespace llvm::pdb; using namespace llvm::support; TpiStreamBuilder::TpiStreamBuilder(MSFBuilder &Msf, uint32_t StreamIdx) : Msf(Msf), Allocator(Msf.getAllocator()), Header(nullptr), Idx(StreamIdx) { } TpiStreamBuilder::~TpiStreamBuilder() = default; void TpiStreamBuilder::setVersionHeader(PdbRaw_TpiVer Version) { VerHeader = Version; } void TpiStreamBuilder::updateTypeIndexOffsets(ArrayRef Sizes) { // If we just crossed an 8KB threshold, add a type index offset. for (uint16_t Size : Sizes) { size_t NewSize = TypeRecordBytes + Size; constexpr size_t EightKB = 8 * 1024; if (NewSize / EightKB > TypeRecordBytes / EightKB || TypeRecordCount == 0) { TypeIndexOffsets.push_back( {codeview::TypeIndex(codeview::TypeIndex::FirstNonSimpleIndex + TypeRecordCount), ulittle32_t(TypeRecordBytes)}); } ++TypeRecordCount; TypeRecordBytes = NewSize; } } void TpiStreamBuilder::addTypeRecord(ArrayRef Record, Optional Hash) { assert(((Record.size() & 3) == 0) && "The type record's size is not a multiple of 4 bytes which will " "cause misalignment in the output TPI stream!"); assert(Record.size() <= codeview::MaxRecordLength); uint16_t OneSize = (uint16_t)Record.size(); updateTypeIndexOffsets(makeArrayRef(&OneSize, 1)); TypeRecBuffers.push_back(Record); // FIXME: Require it. if (Hash) TypeHashes.push_back(*Hash); } void TpiStreamBuilder::addTypeRecords(ArrayRef Types, ArrayRef Sizes, ArrayRef Hashes) { // Ignore empty type buffers. There should be no hashes or sizes in this case. if (Types.empty()) { assert(Sizes.empty() && Hashes.empty()); return; } assert(((Types.size() & 3) == 0) && "The type record's size is not a multiple of 4 bytes which will " "cause misalignment in the output TPI stream!"); assert(Sizes.size() == Hashes.size() && "sizes and hashes should be in sync"); assert(std::accumulate(Sizes.begin(), Sizes.end(), 0U) == Types.size() && "sizes of type records should sum to the size of the types"); updateTypeIndexOffsets(Sizes); TypeRecBuffers.push_back(Types); TypeHashes.insert(TypeHashes.end(), Hashes.begin(), Hashes.end()); } Error TpiStreamBuilder::finalize() { if (Header) return Error::success(); TpiStreamHeader *H = Allocator.Allocate(); H->Version = VerHeader; H->HeaderSize = sizeof(TpiStreamHeader); H->TypeIndexBegin = codeview::TypeIndex::FirstNonSimpleIndex; H->TypeIndexEnd = H->TypeIndexBegin + TypeRecordCount; H->TypeRecordBytes = TypeRecordBytes; H->HashStreamIndex = HashStreamIndex; H->HashAuxStreamIndex = kInvalidStreamIndex; H->HashKeySize = sizeof(ulittle32_t); H->NumHashBuckets = MaxTpiHashBuckets - 1; // Recall that hash values go into a completely different stream identified by // the `HashStreamIndex` field of the `TpiStreamHeader`. Therefore, the data // begins at offset 0 of this independent stream. H->HashValueBuffer.Off = 0; H->HashValueBuffer.Length = calculateHashBufferSize(); // We never write any adjustments into our PDBs, so this is usually some // offset with zero length. H->HashAdjBuffer.Off = H->HashValueBuffer.Off + H->HashValueBuffer.Length; H->HashAdjBuffer.Length = 0; H->IndexOffsetBuffer.Off = H->HashAdjBuffer.Off + H->HashAdjBuffer.Length; H->IndexOffsetBuffer.Length = calculateIndexOffsetSize(); Header = H; return Error::success(); } uint32_t TpiStreamBuilder::calculateSerializedLength() { return sizeof(TpiStreamHeader) + TypeRecordBytes; } uint32_t TpiStreamBuilder::calculateHashBufferSize() const { assert((TypeRecordCount == TypeHashes.size() || TypeHashes.empty()) && "either all or no type records should have hashes"); return TypeHashes.size() * sizeof(ulittle32_t); } uint32_t TpiStreamBuilder::calculateIndexOffsetSize() const { return TypeIndexOffsets.size() * sizeof(codeview::TypeIndexOffset); } Error TpiStreamBuilder::finalizeMsfLayout() { uint32_t Length = calculateSerializedLength(); if (auto EC = Msf.setStreamSize(Idx, Length)) return EC; uint32_t HashStreamSize = calculateHashBufferSize() + calculateIndexOffsetSize(); if (HashStreamSize == 0) return Error::success(); auto ExpectedIndex = Msf.addStream(HashStreamSize); if (!ExpectedIndex) return ExpectedIndex.takeError(); HashStreamIndex = *ExpectedIndex; if (!TypeHashes.empty()) { ulittle32_t *H = Allocator.Allocate(TypeHashes.size()); MutableArrayRef HashBuffer(H, TypeHashes.size()); for (uint32_t I = 0; I < TypeHashes.size(); ++I) { HashBuffer[I] = TypeHashes[I] % (MaxTpiHashBuckets - 1); } ArrayRef Bytes( reinterpret_cast(HashBuffer.data()), calculateHashBufferSize()); HashValueStream = std::make_unique(Bytes, llvm::support::little); } return Error::success(); } Error TpiStreamBuilder::commit(const msf::MSFLayout &Layout, WritableBinaryStreamRef Buffer) { if (auto EC = finalize()) return EC; auto InfoS = WritableMappedBlockStream::createIndexedStream(Layout, Buffer, Idx, Allocator); BinaryStreamWriter Writer(*InfoS); if (auto EC = Writer.writeObject(*Header)) return EC; for (auto Rec : TypeRecBuffers) { assert(!Rec.empty() && "Attempting to write an empty type record shifts " "all offsets in the TPI stream!"); assert(((Rec.size() & 3) == 0) && "The type record's size is not a multiple of 4 bytes which will " "cause misalignment in the output TPI stream!"); if (auto EC = Writer.writeBytes(Rec)) return EC; } if (HashStreamIndex != kInvalidStreamIndex) { auto HVS = WritableMappedBlockStream::createIndexedStream( Layout, Buffer, HashStreamIndex, Allocator); BinaryStreamWriter HW(*HVS); if (HashValueStream) { if (auto EC = HW.writeStreamRef(*HashValueStream)) return EC; } for (auto &IndexOffset : TypeIndexOffsets) { if (auto EC = HW.writeObject(IndexOffset)) return EC; } } return Error::success(); }