From: sivadeilra Date: Tue, 20 Aug 2024 16:48:59 +0000 (-0700) Subject: Add support for MSFZ/PDZ files (#4868) X-Git-Tag: accepted/tizen/unified/20241231.014852~39^2^2~72 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=c318910cd58495f3d2a22eeebad74e71e5d64bac;p=platform%2Fcore%2Fdotnet%2Fdiagnostics.git Add support for MSFZ/PDZ files (#4868) This adds support for reading MSFZ/PDZ files. MSFZ files are a new container format for PDB files, which allows for efficient compression and decompression of small fragments of a file, without decompressing the entire file. Co-authored-by: Arlie Davis --- diff --git a/src/Microsoft.FileFormats/PDB/IMSFFile.cs b/src/Microsoft.FileFormats/PDB/IMSFFile.cs new file mode 100644 index 000000000..2c6d4f445 --- /dev/null +++ b/src/Microsoft.FileFormats/PDB/IMSFFile.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.FileFormats.PDB +{ + /// + /// An abstraction for reading both MSF (normal PDB files) and MSFZ files (compressed PDB files). + /// + internal interface IMSFFile + { + /// + /// The number of streams stored in the file. This will always be at least 1. + /// + uint NumStreams { get; } + + /// + /// Gets an object which can read the given stream. + /// + /// The index of the stream. This must be less than NumStreams. + /// A Reader which can read the stream. + Reader GetStream(uint stream); + } +} diff --git a/src/Microsoft.FileFormats/PDB/MSFFile.cs b/src/Microsoft.FileFormats/PDB/MSFFile.cs new file mode 100644 index 000000000..877380e5c --- /dev/null +++ b/src/Microsoft.FileFormats/PDB/MSFFile.cs @@ -0,0 +1,71 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; + +namespace Microsoft.FileFormats.PDB +{ + /// + /// This class can read from PDB files that use the MSF container format. + /// + internal sealed class MSFFile : IMSFFile + { + private Reader[] _streams; + + private MSFFile(Reader[] streams) + { + _streams = streams; + } + + internal static MSFFile OpenInternal(Reader fileReader, PDBFileHeader msfFileHeader) + { + // Read the Stream Directory and build the list of streams. + + uint pageSize = msfFileHeader.PageSize; + + uint secondLevelPageCount = ToPageCount(pageSize, msfFileHeader.DirectorySize); + ulong pageIndicesOffset = fileReader.SizeOf(); + PDBPagedAddressSpace secondLevelPageList = CreatePagedAddressSpace(fileReader.DataSource, fileReader.DataSource, msfFileHeader.PageSize, pageIndicesOffset, secondLevelPageCount * sizeof(uint)); + PDBPagedAddressSpace directoryContent = CreatePagedAddressSpace(fileReader.DataSource, secondLevelPageList, msfFileHeader.PageSize, 0, msfFileHeader.DirectorySize); + + Reader directoryReader = new(directoryContent); + ulong position = 0; + uint countStreams = directoryReader.Read(ref position); + uint[] streamSizes = directoryReader.ReadArray(ref position, countStreams); + Reader[] streams = new Reader[countStreams]; + for (uint i = 0; i < streamSizes.Length; i++) + { + uint streamSize = streamSizes[i]; + streams[i] = new Reader(CreatePagedAddressSpace(fileReader.DataSource, directoryContent, pageSize, position, streamSize)); + position += ToPageCount(pageSize, streamSizes[i]) * sizeof(uint); + } + + return new MSFFile(streams); + } + + private static PDBPagedAddressSpace CreatePagedAddressSpace(IAddressSpace fileData, IAddressSpace indicesData, uint pageSize, ulong offset, uint length) + { + uint[] indices = new Reader(indicesData).ReadArray(offset, ToPageCount(pageSize, length)); + return new PDBPagedAddressSpace(fileData, indices, pageSize, length); + } + + private static uint ToPageCount(uint pageSize, uint size) + { + return unchecked((pageSize + size - 1) / pageSize); + } + + public uint NumStreams + { + get + { + return (uint)_streams.Length; + } + } + + public Reader GetStream(uint index) + { + return _streams[index]; + } + } +} diff --git a/src/Microsoft.FileFormats/PDB/MSFZFile.cs b/src/Microsoft.FileFormats/PDB/MSFZFile.cs new file mode 100644 index 000000000..870899230 --- /dev/null +++ b/src/Microsoft.FileFormats/PDB/MSFZFile.cs @@ -0,0 +1,412 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Linq; + +namespace Microsoft.FileFormats.PDB +{ + /// + /// This class can read data from PDB files that use the MSFZ container format. + /// + internal sealed class MSFZFile : IMSFFile, IDisposable + { + /// + /// Provides access to the underlying MSFZ file. + /// + private readonly Reader _reader; + + /// + /// The number of streams. + /// + private readonly uint _numStreams; + + /// + /// The encoded Stream Directory. See the MSFZ specification for details on the encoding. + /// This is stored as an array of uint rather than an array of byte because the underlying + /// encoding stores uint32 values; there are no values larger than uint32 and no values + /// smaller. + /// + private readonly uint[] _streamDir; + + /// + /// _streamDirStarts[s] gives the index into _streamDir where the fragments for stream s begin. + /// _streamDirStarts.Length == _numStreams. + /// + private readonly uint[] _streamDirStarts; + + private MSFZFile(Reader reader, uint numStreams, uint[] streamDir, uint[] streamDirStarts) + { + Debug.Assert(numStreams == streamDirStarts.Length); + this._numStreams = numStreams; + this._reader = reader; + this._streamDir = streamDir; + this._streamDirStarts = streamDirStarts; + } + + public uint NumStreams + { + get { return _numStreams; } + } + + public Reader GetStream(uint stream) + { + if (stream == 0 || stream >= _numStreams) + { + throw new ArgumentException("Invalid stream index"); + } + + uint streamSize = GetStreamSize(stream); + return new Reader(new MsfzStream(this, stream, streamSize)); + } + + /// + /// Returns the size of the stream. The size is computed by iterating the fragments that + /// make up the stream and computing the sum of their sizes. + /// + /// The stream index. The caller must validate this value against + /// NumStreams. + /// The total size in bytes of the stream. + internal uint GetStreamSize(uint stream) + { + uint streamSize = 0; + + uint pos = _streamDirStarts[stream]; + while (pos < _streamDir.Length) + { + uint fragmentSize = _streamDir[pos]; + + // Nil streams do not have any fragments and are zero-length. + if (fragmentSize == MsfzConstants.NilFragmentSize) + { + return 0; + } + + // The fragment list (for a given stream) is terminated by a 0 value. + if (fragmentSize == 0) + { + break; + } + + streamSize += fragmentSize; + + // Step over this fragment record. + pos += MsfzConstants.UInt32PerFragmentRecord; + } + + return streamSize; + } + + // Can return null, on failure. + internal static MSFZFile Open(IAddressSpace dataSource) + { + Reader reader = new(dataSource); + + ulong pos = 0; + + MSFZFileHeader fileHeader = reader.Read(ref pos); + + if (!fileHeader.Signature.SequenceEqual(MSFZFileHeader.ExpectedSignature)) + { + // Wrong signature + return null; + } + + ulong headerVersion = fileHeader.Version; + uint numStreams = fileHeader.NumStreams; + ulong streamDirOffset = fileHeader.StreamDirOffset; + uint streamDirCompression = fileHeader.StreamDirCompression; + uint streamDirSizeCompressed = fileHeader.StreamDirSizeCompressed; + uint streamDirSizeUncompressed = fileHeader.StreamDirSizeUncompressed; + + if (headerVersion != MSFZFileHeader.VersionV0) + { + // Wrong version + return null; + } + + if (streamDirCompression != MSFZConstants.COMPRESSION_NONE + || streamDirSizeCompressed != streamDirSizeUncompressed) + { + // Stream directory compression is not supported + return null; + } + + if (streamDirSizeUncompressed % 4 != 0) + { + // Stream directory length should be a multiple of uint32 size. + return null; + } + + // Read the contents of the stream dir, as a sequence of bytes. + byte[] streamDirBytes = reader.Read(streamDirOffset, streamDirSizeUncompressed); + + uint streamDirEncodedSize = streamDirSizeUncompressed; + uint[] streamDirEncoded = new uint[streamDirEncodedSize / 4]; + for (int i = 0; i < streamDirEncoded.Length; ++i) + { + streamDirEncoded[i] = BitConverter.ToUInt32(streamDirBytes, i * 4); + } + + uint[] streamStarts = FindStreamDirStarts(numStreams, streamDirEncoded); + + // We do not read the Chunk Table because this implementation does not support + // compression. Since the Chunk Table describes compressed chunks, we will never use it. + + return new MSFZFile(reader, numStreams, streamDirEncoded, streamStarts); + } + + /// + /// Scans through the encoded form of the Stream Directory (in uint32 form, not byte form) + /// and builds a table of the starting locations of the fragments of each stream. + /// + private static uint[] FindStreamDirStarts(uint numStreams, uint[] streamDir) + { + uint pos = 0; // index within streamDir where the fragments for the current stream begin + + uint[] starts = new uint[numStreams]; + + for (uint stream = 0; stream < numStreams; ++stream) + { + starts[stream] = pos; + + if (pos >= streamDir.Length) + { + throw new Exception("stream directory is too short to be valid"); + } + + uint fragmentSize = streamDir[pos]; + if (fragmentSize == MsfzConstants.NilFragmentSize) + { + // It's a nil stream. + ++pos; + continue; + } + + // Read all the fragments of this stream. There may be no fragments at all. + while (fragmentSize != 0) + { + // There should be at least 3 more words. The next 2 words form the fragment location + // of the current fragment. The next word is either the length of the next fragment + // or is 0, indicating the end of the fragment list. + if (pos + MsfzConstants.UInt32PerFragmentRecord >= streamDir.Length) + { + throw new Exception("MSFZ stream directory is too short to be valid"); + } + + // Advance our pointer and read the size of the next fragment. + pos += MsfzConstants.UInt32PerFragmentRecord; + fragmentSize = streamDir[pos]; + } + + // This steps over the 0 at the end of the fragment list. + ++pos; + } + + return starts; + } + + /// + /// Reads data from a stream. + /// + /// The stream index + /// The byte offset within the stream of the data. + /// The output buffer. + /// The location within the output buffer to write the data. + /// The number of bytes to transfer. + /// The number of bytes actually transferred. + /// Thrown if the data covers a compressed fragment. + /// + internal uint ReadStream(uint stream, ulong position, byte[] buffer, uint bufferOffset, uint count) + { + // Find the fragments for this stream. + uint fragmentIndex = _streamDirStarts[stream]; + uint totalBytesTransferred = 0; + + while (count != 0) + { + uint fragmentSize = _streamDir[fragmentIndex]; + if (fragmentSize == MsfzConstants.NilFragmentSize || fragmentSize == 0) + { + break; + } + + // We can safely index into _streamDir because we have already validated its contents, + // when we first opened the MSFZ file. + + MsfzFragmentLocation fragmentLocation; + fragmentLocation.Low = _streamDir[fragmentIndex + 1]; + fragmentLocation.High = _streamDir[fragmentIndex + 2]; + fragmentIndex += MsfzConstants.UInt32PerFragmentRecord; + + // If the position is beyond the range of bytes covered by this fragment, then skip + // this fragment and adjust our read position. + if (position >= fragmentSize) + { + position -= fragmentSize; + continue; + } + + // We found the fragment that we are going to read from. + uint transferSize = Math.Min(count, (uint)(fragmentSize - position)); + + // Is this fragment compressed or uncompressed? + if (fragmentLocation.IsCompressedChunk) + { + // Compressed fragments are not supported by this implementation. + throw new NotSupportedException($"This implementation does not support decompression of PDZ (compressed PDB) files. " + + $"It can only read data from uncompressed streams within PDZ files. (stream {stream}, position {position})"); + } + + ulong fileOffset = fragmentLocation.UncompressedFileOffset + position; + + uint fileBytesTransferred = _reader.Read(fileOffset, buffer, bufferOffset, transferSize); + if (fileBytesTransferred != transferSize) + { + // We expect the entire read to be satisfied. + throw new Exception("Internal error in MSFZ reader. The underlying file reader did not read enough data from the file."); + } + + count -= transferSize; + bufferOffset += transferSize; + position += transferSize; + totalBytesTransferred += transferSize; + } + + return totalBytesTransferred; + } + + + public void Dispose() + { + if (_reader.DataSource is IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + /// + /// Describes the MSFZ File Header on-disk data structure. + /// + internal sealed class MSFZFileHeader : TStruct + { + // 00000000 : 4d 69 63 72 6f 73 6f 66 74 20 4d 53 46 5a 20 43 : Microsoft MSFZ C + // 00000010 : 6f 6e 74 61 69 6e 65 72 0d 0a 1a 41 4c 44 00 00 : ontainer...ALD.. + + internal static readonly byte[] ExpectedSignature = + { + 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, 0x4d, 0x53, 0x46, 0x5a, 0x20, 0x43, // : Microsoft MSFZ C + 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x0d, 0x0a, 0x1a, 0x41, 0x4c, 0x44, 0x00, 0x00, // : ontainer...ALD.. + }; + + internal const ulong VersionV0 = 0; + + internal const int SizeOf = 80; + +#pragma warning disable 0649 // These fields are assigned via Reflection, so the C# compiler thinks they are never assigned. + + /// Identifies this as an MSFZ file. + [ArraySize(32)] + internal byte[] Signature; + /// specifies the version number of the MSFZ file format + internal ulong Version; + /// file offset of the stream directory + internal ulong StreamDirOffset; + /// file offset of the chunk table + internal ulong ChunkTableOffset; + /// the number of streams stored within this MSFZ file + internal uint NumStreams; + /// compression algorithm used for the stream directory + internal uint StreamDirCompression; + /// size in bytes of the stream directory when compressed (on disk) + internal uint StreamDirSizeCompressed; + /// size in bytes of the stream directory when uncompressed (in memory) + internal uint StreamDirSizeUncompressed; + /// number of compressed chunks + internal uint NumChunks; + /// size in bytes of the chunk table + internal uint ChunkTableSize; + +#pragma warning restore 0649 + + internal bool IsMagicValid + { + get { return Signature.SequenceEqual(ExpectedSignature); } + } + } + + internal static class MSFZConstants + { + internal const uint COMPRESSION_NONE = 0; + internal const uint COMPRESSION_ZSTD = 1; + } + + /// Allows reading data from an MSFZ stream. + internal sealed class MsfzStream : IAddressSpace + { + /// Provides access to the MSFZ file. + private readonly MSFZFile _msfzFile; + + /// The stream index. + private readonly uint _stream; + + /// The size of the stream, in bytes. + private readonly uint _size; + + internal MsfzStream(MSFZFile msfzFile, uint stream, uint size) + { + _msfzFile = msfzFile; + _stream = stream; + _size = size; + } + + public uint Read(ulong position, byte[] buffer, uint bufferOffset, uint count) + { + return _msfzFile.ReadStream(_stream, position, buffer, bufferOffset, count); + } + + public ulong Length { get { return _size; } } + } + + internal static class MsfzConstants + { + /// + /// The special value for the size of a fragment record that indicates the stream is a nil stream, + /// not an ordinary stream. + /// + public const uint NilFragmentSize = 0xffffffffu; + + /// + /// The bitmask that is applied to the FragmentLocation.High value, which specifies that + /// the fragment is compressed. 1 means compressed, 0 means not compressed. + /// + public const uint FragmentLocationChunkMaskInUInt32 = 1u << 31; + + /// + /// The number of uint32 values per fragment record. + /// + public const uint UInt32PerFragmentRecord = 3; + } + + internal struct MsfzFragmentLocation + { + public uint Low; + public uint High; + + public bool IsCompressedChunk + { + get { return (High & MsfzConstants.FragmentLocationChunkMaskInUInt32) != 0u; } + } + + public ulong UncompressedFileOffset + { + get + { + Debug.Assert(!IsCompressedChunk); + return (((ulong)High) << 32) | ((ulong)Low); + } + } + } +} diff --git a/src/Microsoft.FileFormats/PDB/PDBFile.cs b/src/Microsoft.FileFormats/PDB/PDBFile.cs index 0ccdda087..cafccb108 100644 --- a/src/Microsoft.FileFormats/PDB/PDBFile.cs +++ b/src/Microsoft.FileFormats/PDB/PDBFile.cs @@ -8,23 +8,110 @@ namespace Microsoft.FileFormats.PDB { public class PDBFile : IDisposable { - private readonly Reader _reader; - private readonly Lazy _header; + /// + /// This provides access to the container file, which may be either MSF (uncompressed PDB) + /// or MSFZ (compressed PDB). If this field is null, then this PDBFile is invalid. + /// + private readonly IMSFFile _msfFile; + private readonly Lazy _streams; private readonly Lazy _nameStream; private readonly Lazy _dbiStream; public PDBFile(IAddressSpace dataSource) { - _reader = new Reader(dataSource); - _header = new Lazy(() => _reader.Read(0)); + Reader reader = new(dataSource); _streams = new Lazy(ReadDirectory); - _nameStream = new Lazy(() => new PDBNameStream(Streams[1])); - _dbiStream = new Lazy(() => new DbiStream(Streams[3])); + _nameStream = new Lazy(() => { + CheckValid(); + return new PDBNameStream(_msfFile.GetStream(1)); + }); + _dbiStream = new Lazy(() => { + CheckValid(); + return new DbiStream(_msfFile.GetStream(3)); + }); + + if (reader.Length > reader.SizeOf()) + { + PDBFileHeader msfFileHeader = reader.Read(0); + if (msfFileHeader.IsMagicValid) + { + MSFFile msfFile = MSFFile.OpenInternal(reader, msfFileHeader); + if (msfFile != null) + { + _msfFile = msfFile; + return; + } + } + } + + if (reader.Length > reader.SizeOf()) + { + MSFZFileHeader msfzFileHeader = reader.Read(0); + if (msfzFileHeader.IsMagicValid) + { + MSFZFile msfzFile = MSFZFile.Open(dataSource); + if (msfzFile != null) + { + _msfFile = msfzFile; + return; + } + } + } } - public PDBFileHeader Header { get { return _header.Value; } } + /// + /// Opens a PDB file. If the operation is not successful, this method will throw an exception, rather + /// than return an invalid PDBFile object. + /// + public static PDBFile Open(IAddressSpace dataSource) + { + PDBFile pdbFile = new(dataSource); + if (pdbFile.IsValid()) + { + return pdbFile; + } + else + { + throw new BadInputFormatException("The specified file is not a PDB (uses neither MSF nor MSFZ container format)."); + } + } + + [Obsolete("Switch to using NumStreams and GetStream. The 'Streams' collection is inefficient.")] public IList Streams { get { return _streams.Value; } } + + private void CheckValid() + { + if (_msfFile == null) + { + throw new Exception("Object is not valid"); + } + } + + /// + /// The number of streams stored in the file. This will always be at least 1. + /// + public uint NumStreams + { + get + { + CheckValid(); + return _msfFile.NumStreams; + } + } + + /// + /// Gets an object which can read the given stream. + /// + /// The index of the stream. This must be less than NumStreams. + /// A Reader which can read the stream. + public Reader GetStream(uint stream) + { + CheckValid(); + return _msfFile.GetStream(stream); + } + + public PDBNameStream NameStream { get { return _nameStream.Value; } } public DbiStream DbiStream { get { return _dbiStream.Value; } } public uint Age { get { return NameStream.Header.Age; } } @@ -33,50 +120,31 @@ namespace Microsoft.FileFormats.PDB public void Dispose() { - if (_reader.DataSource is IDisposable disposable) - { - disposable.Dispose(); - } + IDisposable msfFile = _msfFile as IDisposable; + msfFile?.Dispose(); } public bool IsValid() { - if (_reader.Length > _reader.SizeOf()) { - return Header.IsMagicValid.Check(); - } - return false; + return _msfFile != null; } private Reader[] ReadDirectory() { - Header.IsMagicValid.CheckThrowing(); - uint secondLevelPageCount = ToPageCount(Header.DirectorySize); - ulong pageIndicesOffset = _reader.SizeOf(); - PDBPagedAddressSpace secondLevelPageList = CreatePagedAddressSpace(_reader.DataSource, pageIndicesOffset, secondLevelPageCount * sizeof(uint)); - PDBPagedAddressSpace directoryContent = CreatePagedAddressSpace(secondLevelPageList, 0, Header.DirectorySize); - - Reader directoryReader = new(directoryContent); - ulong position = 0; - uint countStreams = directoryReader.Read(ref position); - uint[] streamSizes = directoryReader.ReadArray(ref position, countStreams); - Reader[] streams = new Reader[countStreams]; - for (uint i = 0; i < streamSizes.Length; i++) - { - streams[i] = new Reader(CreatePagedAddressSpace(directoryContent, position, streamSizes[i])); - position += ToPageCount(streamSizes[i]) * sizeof(uint); - } - return streams; - } + // We have already read the Stream Directory, in the constructor of PDBFile. + // The purpose of this function is to provide backward compatibility with the old + // 'Streams' property. New code should not use `Streams`; instead, new code should + // directly call `ReadStream`. - private PDBPagedAddressSpace CreatePagedAddressSpace(IAddressSpace indicesData, ulong offset, uint length) - { - uint[] indices = new Reader(indicesData).ReadArray(offset, ToPageCount(length)); - return new PDBPagedAddressSpace(_reader.DataSource, indices, Header.PageSize, length); - } + int numStreams = (int)this.NumStreams; - private uint ToPageCount(uint size) - { - return unchecked((Header.PageSize + size - 1) / Header.PageSize); + Reader[] streamReaders = new Reader[numStreams]; + + for (int i = 1; i < numStreams; ++i) + { + streamReaders[i] = GetStream((uint)i); + } + return streamReaders; } } diff --git a/src/Microsoft.FileFormats/PDB/PDBStructures.cs b/src/Microsoft.FileFormats/PDB/PDBStructures.cs index 7c082a6be..2a6886f57 100644 --- a/src/Microsoft.FileFormats/PDB/PDBStructures.cs +++ b/src/Microsoft.FileFormats/PDB/PDBStructures.cs @@ -33,9 +33,9 @@ namespace Microsoft.FileFormats.PDB public uint Reserved; #region Validation Rules - public ValidationRule IsMagicValid + public bool IsMagicValid { - get { return new ValidationRule("PDB header magic is invalid", () => Magic.SequenceEqual(ExpectedMagic)); } + get { return Magic.SequenceEqual(ExpectedMagic); } } #endregion } @@ -52,8 +52,8 @@ namespace Microsoft.FileFormats.PDB public class DbiStreamHeader : TStruct { - private const uint CurrentSignature = uint.MaxValue; - private const uint CurrentVersion = 19990903; // DBIImpvV70 + public const uint CurrentSignature = uint.MaxValue; + public const uint CurrentVersion = 19990903; // DBIImpvV70 public uint Signature; public uint Version; diff --git a/src/tests/Microsoft.FileFormats.UnitTests/Microsoft.FileFormats.UnitTests.csproj b/src/tests/Microsoft.FileFormats.UnitTests/Microsoft.FileFormats.UnitTests.csproj index 22139cfa2..f46b5d931 100644 --- a/src/tests/Microsoft.FileFormats.UnitTests/Microsoft.FileFormats.UnitTests.csproj +++ b/src/tests/Microsoft.FileFormats.UnitTests/Microsoft.FileFormats.UnitTests.csproj @@ -59,4 +59,10 @@ + + + + Always + + diff --git a/src/tests/Microsoft.FileFormats.UnitTests/PDB/Tests.cs b/src/tests/Microsoft.FileFormats.UnitTests/PDB/Tests.cs index b82c4db4b..5a467d2b7 100644 --- a/src/tests/Microsoft.FileFormats.UnitTests/PDB/Tests.cs +++ b/src/tests/Microsoft.FileFormats.UnitTests/PDB/Tests.cs @@ -13,16 +13,42 @@ namespace Microsoft.FileFormats.PDB.Tests public class Tests { [Fact] - public void CheckIndexingInfo() + public void CheckIndexingInfoPdb() { using (Stream s = File.OpenRead("TestBinaries/HelloWorld.pdb")) { - StreamAddressSpace fileContent = new(s); - PDBFile pdb = new(fileContent); - Assert.True(pdb.Header.IsMagicValid.Check()); - Assert.True(pdb.IsValid()); + StreamAddressSpace fileContent = new StreamAddressSpace(s); + PDBFile pdb = PDBFile.Open(fileContent); Assert.Equal((uint)1, pdb.Age); Assert.Equal(Guid.Parse("99891B3E-D7AE-4C3B-ABFF-8A2B4A9B0C43"), pdb.Signature); + + // Also read the PDBI stream directly, using the downlevel API. +#pragma warning disable CS0618 // Type or member is obsolete + var stream1 = pdb.Streams[1]; + Assert.Equal(226ul, stream1.Length); + byte[] buffer = new byte[226]; + Assert.Equal(226u, stream1.Read(0, buffer, 0, 226)); +#pragma warning restore CS0618 // Type or member is obsolete + } + } + + [Fact] + public void CheckIndexingInfoPdz() + { + using (Stream s = File.OpenRead("TestBinaries/HelloWorld.pdz")) + { + StreamAddressSpace fileContent = new StreamAddressSpace(s); + PDBFile pdb = PDBFile.Open(fileContent); + Assert.Equal((uint)1, pdb.Age); + Assert.Equal(Guid.Parse("99891B3E-D7AE-4C3B-ABFF-8A2B4A9B0C43"), pdb.Signature); + + // Also read the PDBI stream directly, using the downlevel API. +#pragma warning disable CS0618 // Type or member is obsolete + var stream1 = pdb.Streams[1]; + Assert.Equal(226ul, stream1.Length); + byte[] buffer = new byte[226]; + Assert.Equal(226u, stream1.Read(0, buffer, 0, 226)); +#pragma warning restore CS0618 // Type or member is obsolete } } } diff --git a/src/tests/Microsoft.FileFormats.UnitTests/TestBinaries/HelloWorld.pdz b/src/tests/Microsoft.FileFormats.UnitTests/TestBinaries/HelloWorld.pdz new file mode 100644 index 000000000..261e5ffdf Binary files /dev/null and b/src/tests/Microsoft.FileFormats.UnitTests/TestBinaries/HelloWorld.pdz differ