Index: llvm/include/llvm/DebugInfo/MSF/MSFBuilder.h =================================================================== --- llvm/include/llvm/DebugInfo/MSF/MSFBuilder.h +++ llvm/include/llvm/DebugInfo/MSF/MSFBuilder.h @@ -59,6 +59,10 @@ uint32_t MinBlockCount = 0, bool CanGrow = true); + /// Create a new `MSFBuilder` from an existing layout. + static MSFBuilder create(BumpPtrAllocator &Allocator, + const MSFLayout &Layout); + /// Request the block map to be at a specific block address. This is useful /// when editing a MSF and you want the layout to be as stable as possible. Error setBlockMapAddr(uint32_t Addr); @@ -116,12 +120,21 @@ /// Write the MSF layout to the underlying file. Expected commit(StringRef Path, MSFLayout &Layout); + /// Write the MSF layout to the underlying file, leaving any contents of the + /// existing file untouched. + Expected commitPartial(StringRef Path, + MSFLayout &Layout); + BumpPtrAllocator &getAllocator() { return Allocator; } private: + MSFBuilder(BumpPtrAllocator &Allocator); MSFBuilder(uint32_t BlockSize, uint32_t MinBlockCount, bool CanGrow, BumpPtrAllocator &Allocator); + Expected + commitInternal(StringRef Path, MSFLayout &Layout, unsigned Flags); + Error allocateBlocks(uint32_t NumBlocks, MutableArrayRef Blocks); uint32_t computeDirectoryByteSize() const; Index: llvm/lib/DebugInfo/MSF/MSFBuilder.cpp =================================================================== --- llvm/lib/DebugInfo/MSF/MSFBuilder.cpp +++ llvm/lib/DebugInfo/MSF/MSFBuilder.cpp @@ -47,6 +47,9 @@ FreeBlocks[BlockMapAddr] = false; } +MSFBuilder::MSFBuilder(BumpPtrAllocator &Allocator) + : MSFBuilder(4096, msf::getMinimumBlockCount() + 2, true, Allocator) {} + Expected MSFBuilder::create(BumpPtrAllocator &Allocator, uint32_t BlockSize, uint32_t MinBlockCount, bool CanGrow) { @@ -59,6 +62,29 @@ CanGrow, Allocator); } +MSFBuilder MSFBuilder::create(BumpPtrAllocator &Allocator, + const MSFLayout &Layout) { + MSFBuilder Result(Allocator); + Result.IsGrowable = true; + Result.setFreePageMap(Layout.SB->FreeBlockMapBlock); + Result.setUnknown1(Layout.SB->Unknown1); + Result.BlockSize = Layout.SB->BlockSize; + Result.BlockMapAddr = Layout.SB->BlockMapAddr; + Result.FreeBlocks = Layout.FreePageMap; + Result.DirectoryBlocks.assign(Layout.DirectoryBlocks.begin(), + Layout.DirectoryBlocks.end()); + + assert(Layout.StreamMap.size() == Layout.StreamSizes.size()); + Result.StreamData.reserve(Layout.StreamMap.size()); + for (size_t I = 0; I < Layout.StreamMap.size(); ++I) { + uint32_t Size = Layout.StreamSizes[I]; + ArrayRef Blocks = Layout.StreamMap[I]; + std::vector BlockVector{Blocks.begin(), Blocks.end()}; + Result.StreamData.emplace_back(Size, std::move(BlockVector)); + } + return std::move(Result); +} + Error MSFBuilder::setBlockMapAddr(uint32_t Addr) { if (Addr == BlockMapAddr) return Error::success(); @@ -336,8 +362,8 @@ assert(FpmWriter.bytesRemaining() == 0); } -Expected MSFBuilder::commit(StringRef Path, - MSFLayout &Layout) { +Expected +MSFBuilder::commitInternal(StringRef Path, MSFLayout &Layout, unsigned Flags) { Expected L = generateLayout(); if (!L) return L.takeError(); @@ -345,7 +371,7 @@ Layout = std::move(*L); uint64_t FileSize = Layout.SB->BlockSize * Layout.SB->NumBlocks; - auto OutFileOrError = FileOutputBuffer::create(Path, FileSize); + auto OutFileOrError = FileOutputBuffer::create(Path, FileSize, Flags); if (auto EC = OutFileOrError.takeError()) return std::move(EC); @@ -380,3 +406,15 @@ return std::move(Buffer); } + +/// Write the MSF layout to the underlying file, leaving any contents of the +/// existing file untouched. +Expected MSFBuilder::commitPartial(StringRef Path, + MSFLayout &Layout) { + return commitInternal(Path, Layout, FileOutputBuffer::F_modify); +} + +Expected MSFBuilder::commit(StringRef Path, + MSFLayout &Layout) { + return commitInternal(Path, Layout, 0); +} Index: llvm/tools/llvm-pdbutil/llvm-pdbutil.cpp =================================================================== --- llvm/tools/llvm-pdbutil/llvm-pdbutil.cpp +++ llvm/tools/llvm-pdbutil/llvm-pdbutil.cpp @@ -119,6 +119,9 @@ cl::SubCommand ExportSubcommand("export", "Write binary data from a stream to a file"); +cl::SubCommand EditStreamSubcommand("edit-stream", + "Edit the contents of a PDB stream"); + cl::OptionCategory TypeCategory("Symbol Type Options"); cl::OptionCategory FilterCategory("Filtering and Sorting Options"); cl::OptionCategory OtherOptions("Other Options"); @@ -654,10 +657,38 @@ cl::sub(ExportSubcommand), cl::Optional, cl::init(false)); } // namespace exportstream + +namespace writestream { +cl::list OutputPdbFilename(cl::Positional, + cl::desc(""), + cl::Required, + cl::sub(EditStreamSubcommand)); +cl::opt + StreamFile("stream-file", + cl::desc("A file from which to set the stream contents"), + cl::Optional, cl::sub(EditStreamSubcommand)); +cl::opt + StreamSize("set-size", + cl::desc("Set the size of the stream to the specified value"), + cl::Optional, cl::sub(EditStreamSubcommand)); +cl::opt + Stream("stream", cl::Required, + cl::desc("The index or name of the stream whose contents to import"), + cl::sub(EditStreamSubcommand)); +cl::opt ForceName("name", + cl::desc("Force the interpretation of -stream as a " + "string, even if it is a valid integer"), + cl::sub(EditStreamSubcommand), cl::Optional, + cl::init(false)); +} // namespace writestream } static ExitOnError ExitOnErr; +template static bool optWasPassed(const OptType &Opt) { + return Opt.getNumOccurrences() > 0; +} + static void yamlToPdb(StringRef Path) { BumpPtrAllocator Allocator; ErrorOr> ErrorOrBuffer = @@ -1134,6 +1165,27 @@ } } +static Expected getStreamIndex(PDBFile &File, StringRef Stream, + bool ForceName) { + uint32_t Index = 0; + // Make sure that the stream the user is requesting actually exists. + if (!ForceName) { + // First try to parse it as an integer, if it fails fall back to treating it + // as a named stream. + if (to_integer(Stream, Index)) { + if (Index >= File.getNumStreams()) { + errs() << "Error: " << Index << " is not a valid stream index.\n"; + exit(1); + } + return Index; + } + } + + InfoStream &IS = cantFail(File.getPDBInfoStream()); + Index = ExitOnErr(IS.getNamedStreamIndex(opts::exportstream::Stream)); + return Index; +} + static void exportStream() { std::unique_ptr Session; PDBFile &File = loadPDB(opts::exportstream::InputFilename.front(), Session); @@ -1174,6 +1226,57 @@ ExitOnErr(DestStream.commit()); } +static void editStream() { + uint32_t StreamIndex = 0; + std::error_code EC; + std::string FileName = opts::writestream::OutputPdbFilename.front(); + + BumpPtrAllocator Allocator; + Optional Builder; + // Load the file first to get the current MSF layout. + { + std::unique_ptr Session; + PDBFile &File = loadPDB(FileName, Session); + StreamIndex = ExitOnErr(getStreamIndex(File, opts::writestream::Stream, + opts::writestream::ForceName)); + // We need to do this part before the session goes out of scope, since + // MSFLayout uses memory owned by its session's allocator, and we need + // that to live long enough to "clone" the layout in a new MSFBuilder. + Builder.emplace(MSFBuilder::create(Allocator, File.getMsfLayout())); + } + + MSFLayout NewLayout; + if (optWasPassed(opts::writestream::StreamSize)) { + assert(optWasPassed(opts::writestream::StreamFile)); + cantFail( + Builder->setStreamSize(StreamIndex, opts::writestream::StreamSize)); + FileBufferByteStream OutputBuffer = + ExitOnErr(Builder->commitPartial(FileName, NewLayout)); + + cantFail(OutputBuffer.commit()); + return; + } + + auto ErrorOrFile = MemoryBuffer::getFile(opts::writestream::StreamFile); + if (ErrorOrFile.getError()) + ExitOnErr(make_error(generic_error_code::invalid_path, + opts::writestream::StreamFile)); + + auto StreamContents = std::move(*ErrorOrFile); + cantFail( + Builder->setStreamSize(StreamIndex, StreamContents->getBufferSize())); + + FileBufferByteStream OutputBuffer = + ExitOnErr(Builder->commitPartial(FileName, NewLayout)); + + auto Stream = WritableMappedBlockStream::createIndexedStream( + NewLayout, OutputBuffer, StreamIndex, Allocator); + BinaryStreamWriter Writer(*Stream); + cantFail( + Writer.writeBytes(arrayRefFromStringRef(StreamContents->getBuffer()))); + cantFail(OutputBuffer.commit()); +} + static bool parseRange(StringRef Str, Optional &Parsed) { if (Str.empty()) @@ -1342,6 +1445,15 @@ explain(); } else if (opts::ExportSubcommand) { exportStream(); + } else if (opts::EditStreamSubcommand) { + if (optWasPassed(opts::writestream::StreamSize) == + optWasPassed(opts::writestream::StreamFile)) { + errs() << "Error: Exactly one of `-stream-size` or `-stream-file` must " + "be specified.\n"; + exit(1); + } + + editStream(); } outs().flush();