//===- DbiStreamBuilder.cpp - PDB Dbi Stream Creation -----------*- C++ -*-===// // // 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/DbiStreamBuilder.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/BinaryFormat/COFF.h" #include "llvm/DebugInfo/CodeView/DebugFrameDataSubsection.h" #include "llvm/DebugInfo/MSF/MSFBuilder.h" #include "llvm/DebugInfo/MSF/MappedBlockStream.h" #include "llvm/DebugInfo/PDB/Native/DbiModuleDescriptorBuilder.h" #include "llvm/DebugInfo/PDB/Native/RawError.h" #include "llvm/Object/COFF.h" #include "llvm/Support/BinaryStreamWriter.h" #include "llvm/Support/Parallel.h" using namespace llvm; using namespace llvm::codeview; using namespace llvm::msf; using namespace llvm::pdb; DbiStreamBuilder::DbiStreamBuilder(msf::MSFBuilder &Msf) : Msf(Msf), Allocator(Msf.getAllocator()), Age(1), BuildNumber(0), PdbDllVersion(0), PdbDllRbld(0), Flags(0), MachineType(PDB_Machine::x86), Header(nullptr) {} DbiStreamBuilder::~DbiStreamBuilder() = default; void DbiStreamBuilder::setVersionHeader(PdbRaw_DbiVer V) { VerHeader = V; } void DbiStreamBuilder::setAge(uint32_t A) { Age = A; } void DbiStreamBuilder::setBuildNumber(uint16_t B) { BuildNumber = B; } void DbiStreamBuilder::setBuildNumber(uint8_t Major, uint8_t Minor) { BuildNumber = (uint16_t(Major) << DbiBuildNo::BuildMajorShift) & DbiBuildNo::BuildMajorMask; BuildNumber |= (uint16_t(Minor) << DbiBuildNo::BuildMinorShift) & DbiBuildNo::BuildMinorMask; BuildNumber |= DbiBuildNo::NewVersionFormatMask; } void DbiStreamBuilder::setPdbDllVersion(uint16_t V) { PdbDllVersion = V; } void DbiStreamBuilder::setPdbDllRbld(uint16_t R) { PdbDllRbld = R; } void DbiStreamBuilder::setFlags(uint16_t F) { Flags = F; } void DbiStreamBuilder::setMachineType(PDB_Machine M) { MachineType = M; } void DbiStreamBuilder::setMachineType(COFF::MachineTypes M) { // These enums are mirrors of each other, so we can just cast the value. MachineType = static_cast(static_cast(M)); } void DbiStreamBuilder::setGlobalsStreamIndex(uint32_t Index) { GlobalsStreamIndex = Index; } void DbiStreamBuilder::setSymbolRecordStreamIndex(uint32_t Index) { SymRecordStreamIndex = Index; } void DbiStreamBuilder::setPublicsStreamIndex(uint32_t Index) { PublicsStreamIndex = Index; } void DbiStreamBuilder::addNewFpoData(const codeview::FrameData &FD) { if (!NewFpoData) NewFpoData.emplace(false); NewFpoData->addFrameData(FD); } void DbiStreamBuilder::addOldFpoData(const object::FpoData &FD) { OldFpoData.push_back(FD); } Error DbiStreamBuilder::addDbgStream(pdb::DbgHeaderType Type, ArrayRef Data) { assert(Type != DbgHeaderType::NewFPO && "NewFPO data should be written via addFrameData()!"); DbgStreams[(int)Type] = DebugStream{}; DbgStreams[(int)Type]->Size = Data.size(); DbgStreams[(int)Type]->WriteFn = [Data](BinaryStreamWriter &Writer) { return Writer.writeArray(Data); }; return Error::success(); } uint32_t DbiStreamBuilder::addECName(StringRef Name) { return ECNamesBuilder.insert(Name); } uint32_t DbiStreamBuilder::calculateSerializedLength() const { // For now we only support serializing the header. return sizeof(DbiStreamHeader) + calculateFileInfoSubstreamSize() + calculateModiSubstreamSize() + calculateSectionContribsStreamSize() + calculateSectionMapStreamSize() + calculateDbgStreamsSize() + ECNamesBuilder.calculateSerializedSize(); } Expected DbiStreamBuilder::addModuleInfo(StringRef ModuleName) { uint32_t Index = ModiList.size(); ModiList.push_back( std::make_unique(ModuleName, Index, Msf)); return *ModiList.back(); } Error DbiStreamBuilder::addModuleSourceFile(DbiModuleDescriptorBuilder &Module, StringRef File) { uint32_t Index = SourceFileNames.size(); SourceFileNames.insert(std::make_pair(File, Index)); Module.addSourceFile(File); return Error::success(); } Expected DbiStreamBuilder::getSourceFileNameIndex(StringRef File) { auto NameIter = SourceFileNames.find(File); if (NameIter == SourceFileNames.end()) return make_error(raw_error_code::no_entry, "The specified source file was not found"); return NameIter->getValue(); } uint32_t DbiStreamBuilder::calculateModiSubstreamSize() const { uint32_t Size = 0; for (const auto &M : ModiList) Size += M->calculateSerializedLength(); return Size; } uint32_t DbiStreamBuilder::calculateSectionContribsStreamSize() const { if (SectionContribs.empty()) return 0; return sizeof(enum PdbRaw_DbiSecContribVer) + sizeof(SectionContribs[0]) * SectionContribs.size(); } uint32_t DbiStreamBuilder::calculateSectionMapStreamSize() const { if (SectionMap.empty()) return 0; return sizeof(SecMapHeader) + sizeof(SecMapEntry) * SectionMap.size(); } uint32_t DbiStreamBuilder::calculateNamesOffset() const { uint32_t Offset = 0; Offset += sizeof(ulittle16_t); // NumModules Offset += sizeof(ulittle16_t); // NumSourceFiles Offset += ModiList.size() * sizeof(ulittle16_t); // ModIndices Offset += ModiList.size() * sizeof(ulittle16_t); // ModFileCounts uint32_t NumFileInfos = 0; for (const auto &M : ModiList) NumFileInfos += M->source_files().size(); Offset += NumFileInfos * sizeof(ulittle32_t); // FileNameOffsets return Offset; } uint32_t DbiStreamBuilder::calculateFileInfoSubstreamSize() const { uint32_t Size = calculateNamesOffset(); Size += calculateNamesBufferSize(); return alignTo(Size, sizeof(uint32_t)); } uint32_t DbiStreamBuilder::calculateNamesBufferSize() const { uint32_t Size = 0; for (const auto &F : SourceFileNames) { Size += F.getKeyLength() + 1; // Names[I]; } return Size; } uint32_t DbiStreamBuilder::calculateDbgStreamsSize() const { return DbgStreams.size() * sizeof(uint16_t); } Error DbiStreamBuilder::generateFileInfoSubstream() { uint32_t Size = calculateFileInfoSubstreamSize(); auto Data = Allocator.Allocate(Size); uint32_t NamesOffset = calculateNamesOffset(); FileInfoBuffer = MutableBinaryByteStream(MutableArrayRef(Data, Size), llvm::support::little); WritableBinaryStreamRef MetadataBuffer = WritableBinaryStreamRef(FileInfoBuffer).keep_front(NamesOffset); BinaryStreamWriter MetadataWriter(MetadataBuffer); uint16_t ModiCount = std::min(UINT16_MAX, ModiList.size()); uint16_t FileCount = std::min(UINT16_MAX, SourceFileNames.size()); if (auto EC = MetadataWriter.writeInteger(ModiCount)) // NumModules return EC; if (auto EC = MetadataWriter.writeInteger(FileCount)) // NumSourceFiles return EC; for (uint16_t I = 0; I < ModiCount; ++I) { if (auto EC = MetadataWriter.writeInteger(I)) // Mod Indices return EC; } for (const auto &MI : ModiList) { FileCount = static_cast(MI->source_files().size()); if (auto EC = MetadataWriter.writeInteger(FileCount)) // Mod File Counts return EC; } // Before writing the FileNameOffsets array, write the NamesBuffer array. // A side effect of this is that this will actually compute the various // file name offsets, so we can then go back and write the FileNameOffsets // array to the other substream. NamesBuffer = WritableBinaryStreamRef(FileInfoBuffer).drop_front(NamesOffset); BinaryStreamWriter NameBufferWriter(NamesBuffer); for (auto &Name : SourceFileNames) { Name.second = NameBufferWriter.getOffset(); if (auto EC = NameBufferWriter.writeCString(Name.getKey())) return EC; } for (const auto &MI : ModiList) { for (StringRef Name : MI->source_files()) { auto Result = SourceFileNames.find(Name); if (Result == SourceFileNames.end()) return make_error(raw_error_code::no_entry, "The source file was not found."); if (auto EC = MetadataWriter.writeInteger(Result->second)) return EC; } } if (auto EC = NameBufferWriter.padToAlignment(sizeof(uint32_t))) return EC; if (NameBufferWriter.bytesRemaining() > 0) return make_error(raw_error_code::invalid_format, "The names buffer contained unexpected data."); if (MetadataWriter.bytesRemaining() > sizeof(uint32_t)) return make_error( raw_error_code::invalid_format, "The metadata buffer contained unexpected data."); return Error::success(); } Error DbiStreamBuilder::finalize() { if (Header) return Error::success(); for (auto &MI : ModiList) MI->finalize(); if (auto EC = generateFileInfoSubstream()) return EC; DbiStreamHeader *H = Allocator.Allocate(); ::memset(H, 0, sizeof(DbiStreamHeader)); H->VersionHeader = *VerHeader; H->VersionSignature = -1; H->Age = Age; H->BuildNumber = BuildNumber; H->Flags = Flags; H->PdbDllRbld = PdbDllRbld; H->PdbDllVersion = PdbDllVersion; H->MachineType = static_cast(MachineType); H->ECSubstreamSize = ECNamesBuilder.calculateSerializedSize(); H->FileInfoSize = FileInfoBuffer.getLength(); H->ModiSubstreamSize = calculateModiSubstreamSize(); H->OptionalDbgHdrSize = DbgStreams.size() * sizeof(uint16_t); H->SecContrSubstreamSize = calculateSectionContribsStreamSize(); H->SectionMapSize = calculateSectionMapStreamSize(); H->TypeServerSize = 0; H->SymRecordStreamIndex = SymRecordStreamIndex; H->PublicSymbolStreamIndex = PublicsStreamIndex; H->MFCTypeServerIndex = 0; // Not sure what this is, but link.exe writes 0. H->GlobalSymbolStreamIndex = GlobalsStreamIndex; Header = H; return Error::success(); } Error DbiStreamBuilder::finalizeMsfLayout() { if (NewFpoData) { DbgStreams[(int)DbgHeaderType::NewFPO] = DebugStream{}; DbgStreams[(int)DbgHeaderType::NewFPO]->Size = NewFpoData->calculateSerializedSize(); DbgStreams[(int)DbgHeaderType::NewFPO]->WriteFn = [this](BinaryStreamWriter &Writer) { return NewFpoData->commit(Writer); }; } if (!OldFpoData.empty()) { DbgStreams[(int)DbgHeaderType::FPO] = DebugStream{}; DbgStreams[(int)DbgHeaderType::FPO]->Size = sizeof(object::FpoData) * OldFpoData.size(); DbgStreams[(int)DbgHeaderType::FPO]->WriteFn = [this](BinaryStreamWriter &Writer) { return Writer.writeArray(ArrayRef(OldFpoData)); }; } for (auto &S : DbgStreams) { if (!S) continue; auto ExpectedIndex = Msf.addStream(S->Size); if (!ExpectedIndex) return ExpectedIndex.takeError(); S->StreamNumber = *ExpectedIndex; } for (auto &MI : ModiList) { if (auto EC = MI->finalizeMsfLayout()) return EC; } uint32_t Length = calculateSerializedLength(); if (auto EC = Msf.setStreamSize(StreamDBI, Length)) return EC; return Error::success(); } static uint16_t toSecMapFlags(uint32_t Flags) { uint16_t Ret = 0; if (Flags & COFF::IMAGE_SCN_MEM_READ) Ret |= static_cast(OMFSegDescFlags::Read); if (Flags & COFF::IMAGE_SCN_MEM_WRITE) Ret |= static_cast(OMFSegDescFlags::Write); if (Flags & COFF::IMAGE_SCN_MEM_EXECUTE) Ret |= static_cast(OMFSegDescFlags::Execute); if (!(Flags & COFF::IMAGE_SCN_MEM_16BIT)) Ret |= static_cast(OMFSegDescFlags::AddressIs32Bit); // This seems always 1. Ret |= static_cast(OMFSegDescFlags::IsSelector); return Ret; } // Populate the Section Map from COFF section headers. // // A Section Map seem to be a copy of a COFF section list in other format. // I don't know why a PDB file contains both a COFF section header and // a Section Map, but it seems it must be present in a PDB. void DbiStreamBuilder::createSectionMap( ArrayRef SecHdrs) { int Idx = 0; auto Add = [&]() -> SecMapEntry & { SectionMap.emplace_back(); auto &Entry = SectionMap.back(); memset(&Entry, 0, sizeof(Entry)); Entry.Frame = Idx + 1; // We don't know the meaning of these fields yet. Entry.SecName = UINT16_MAX; Entry.ClassName = UINT16_MAX; return Entry; }; for (auto &Hdr : SecHdrs) { auto &Entry = Add(); Entry.Flags = toSecMapFlags(Hdr.Characteristics); Entry.SecByteLength = Hdr.VirtualSize; ++Idx; } // The last entry is for absolute symbols. auto &Entry = Add(); Entry.Flags = static_cast(OMFSegDescFlags::AddressIs32Bit) | static_cast(OMFSegDescFlags::IsAbsoluteAddress); Entry.SecByteLength = UINT32_MAX; } Error DbiStreamBuilder::commit(const msf::MSFLayout &Layout, WritableBinaryStreamRef MsfBuffer) { if (auto EC = finalize()) return EC; auto DbiS = WritableMappedBlockStream::createIndexedStream( Layout, MsfBuffer, StreamDBI, Allocator); BinaryStreamWriter Writer(*DbiS); if (auto EC = Writer.writeObject(*Header)) return EC; for (auto &M : ModiList) { if (auto EC = M->commit(Writer)) return EC; } // Commit symbol streams. This is a lot of data, so do it in parallel. if (auto EC = parallelForEachError( ModiList, [&](std::unique_ptr &M) { return M->commitSymbolStream(Layout, MsfBuffer); })) return EC; if (!SectionContribs.empty()) { if (auto EC = Writer.writeEnum(DbiSecContribVer60)) return EC; if (auto EC = Writer.writeArray(ArrayRef(SectionContribs))) return EC; } if (!SectionMap.empty()) { ulittle16_t Size = static_cast(SectionMap.size()); SecMapHeader SMHeader = {Size, Size}; if (auto EC = Writer.writeObject(SMHeader)) return EC; if (auto EC = Writer.writeArray(ArrayRef(SectionMap))) return EC; } if (auto EC = Writer.writeStreamRef(FileInfoBuffer)) return EC; if (auto EC = ECNamesBuilder.commit(Writer)) return EC; for (auto &Stream : DbgStreams) { uint16_t StreamNumber = kInvalidStreamIndex; if (Stream) StreamNumber = Stream->StreamNumber; if (auto EC = Writer.writeInteger(StreamNumber)) return EC; } for (auto &Stream : DbgStreams) { if (!Stream) continue; assert(Stream->StreamNumber != kInvalidStreamIndex); auto WritableStream = WritableMappedBlockStream::createIndexedStream( Layout, MsfBuffer, Stream->StreamNumber, Allocator); BinaryStreamWriter DbgStreamWriter(*WritableStream); if (auto EC = Stream->WriteFn(DbgStreamWriter)) return EC; } if (Writer.bytesRemaining() > 0) return make_error(raw_error_code::invalid_format, "Unexpected bytes found in DBI Stream"); return Error::success(); }