diff --git a/src/System.Reflection.Metadata/System.Reflection.Metadata.sln b/src/System.Reflection.Metadata/System.Reflection.Metadata.sln index 969a83e7caec..b7620abbd9c8 100644 --- a/src/System.Reflection.Metadata/System.Reflection.Metadata.sln +++ b/src/System.Reflection.Metadata/System.Reflection.Metadata.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.24720.0 +VisualStudioVersion = 14.0.25420.1 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Reflection.Metadata", "src\System.Reflection.Metadata.csproj", "{F3E433C8-352F-4944-BF7F-765CE435370D}" EndProject diff --git a/src/System.Reflection.Metadata/specs/PE-COFF.md b/src/System.Reflection.Metadata/specs/PE-COFF.md index e3a99d3d4ac6..305a1ac25716 100644 --- a/src/System.Reflection.Metadata/specs/PE-COFF.md +++ b/src/System.Reflection.Metadata/specs/PE-COFF.md @@ -29,10 +29,30 @@ The associated .pdb file may not exist at the path indicated by Path field. If i If the containing PE/COFF file is deterministic the Guid field above and DateTimeStamp field of the directory entry are calculated deterministically based solely on the content of the associated .pdb file. Otherwise the value of Guid is random and the value of DateTimeStamp indicates the time and date that the debug data was created. -*Version Major=0x0100, Minor=0x504d* of the data format has the same structure as above. The Age shall be 1. The format of the associated .pdb file is Portable PDB. Together 16B of the Guid concatenated with 4B of the TimeDateStamp field of the entry form a PDB ID that should be used to match the PE/COFF image with the associated PDB (instead of Guid and Age). Matching PDB ID is stored in the #Pdb stream of the .pdb file. +*Version Major=any, Minor=0x504d* of the data format has the same structure as above. The Age shall be 1. The format of the associated .pdb file is Portable PDB. The Major version specified in the entry indicates the version of the Portable PDB format. Together 16B of the Guid concatenated with 4B of the TimeDateStamp field of the entry form a PDB ID that should be used to match the PE/COFF image with the associated PDB (instead of Guid and Age). Matching PDB ID is stored in the #Pdb stream of the .pdb file. ### Deterministic Debug Directory Entry (type 16) The entry doesn't have any data associated with it. All fields of the entry, but Type shall be zero. Presence of this entry indicates that the containing PE/COFF file is deterministic. + +### Embedded Portable PDB Debug Directory Entry (type 17) + +Declares that debugging information is embedded in the PE file at location specified by PointerToRawData. + +*Version Major=any, Minor=0x0100* of the data format: + +| Offset | Size | Field | Description | +|:-------|:---------------|:-----------------|-------------------------------------------------------| +| 0 | 4 | Signature | 0x4D 0x50 0x44 0x42 | +| 4 | 4 | UncompressedSize | The size of decompressed Portable PDB image | +| 8 | SizeOfData - 8 | PortablePdbImage | Portable PDB image compressed using Deflate algorithm | + +If this entry is present and the reader recognizes this entry the debugging information for the PE file shall be read from the embedded data. If a CodeView entry is also present it shall be ignored. + +> Note: Including both entries enables a tool that does not recognize Embedded Portable PDB entry to locate debug infomration as long as it is also available in a file specified in CodeView entry. Such file can be created by extracting the embedded Portable PDB image to a separate file. + +The Major version specified in the entry indicates the version of the Portable PDB format. The Minor version indicates the version of the Embedded Portable PDB data format. + +The value of Stamp field in the entry shall be 0. \ No newline at end of file diff --git a/src/System.Reflection.Metadata/src/Resources/Strings.resx b/src/System.Reflection.Metadata/src/Resources/Strings.resx index 2f1809625c2f..639af3e5bbcd 100644 --- a/src/System.Reflection.Metadata/src/Resources/Strings.resx +++ b/src/System.Reflection.Metadata/src/Resources/Strings.resx @@ -348,6 +348,9 @@ Unexpected CodeView data signature value. + + Unexpected Embedded Portable PDB data signature value. + The path must be padded with NUL characters. @@ -426,6 +429,9 @@ Hash must be at least {0}B long. + + Expected array of length {0}. + Value must be multiple of {0}. @@ -441,4 +447,13 @@ Row count specified for table index {0} is out of allowed range. + + Declared size doesn't correspond to the actual size. + + + Data too big to fit in memory. + + + Unsupported format version: {0} + diff --git a/src/System.Reflection.Metadata/src/System.Reflection.Metadata.csproj b/src/System.Reflection.Metadata/src/System.Reflection.Metadata.csproj index b4e4aacd96b5..01654e878537 100644 --- a/src/System.Reflection.Metadata/src/System.Reflection.Metadata.csproj +++ b/src/System.Reflection.Metadata/src/System.Reflection.Metadata.csproj @@ -55,6 +55,7 @@ + diff --git a/src/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobContentId.cs b/src/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobContentId.cs index 1d6d9ab4a918..5ae3e82be900 100644 --- a/src/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobContentId.cs +++ b/src/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobContentId.cs @@ -10,6 +10,8 @@ namespace System.Reflection.Metadata { public struct BlobContentId { + private const int Size = BlobUtilities.SizeOfGuid + sizeof(uint); + public Guid Guid { get; } public uint Stamp { get; } @@ -19,6 +21,31 @@ public BlobContentId(Guid guid, uint stamp) Stamp = stamp; } + public BlobContentId(ImmutableArray id) + : this(ImmutableByteArrayInterop.DangerousGetUnderlyingArray(id)) + { + } + + public unsafe BlobContentId(byte[] id) + { + if (id == null) + { + throw new ArgumentNullException(nameof(id)); + } + + if (id.Length != Size) + { + throw new ArgumentException(SR.Format(SR.UnexpectedArrayLength, Size), nameof(id)); + } + + fixed (byte* ptr = id) + { + var reader = new BlobReader(ptr, id.Length); + Guid = reader.ReadGuid(); + Stamp = reader.ReadUInt32(); + } + } + public bool IsDefault => Guid == default(Guid) && Stamp == 0; public static BlobContentId FromHash(ImmutableArray hashCode) diff --git a/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/PortablePdbBuilder.cs b/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/PortablePdbBuilder.cs index a329731d0ea4..ee841d0b164e 100644 --- a/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/PortablePdbBuilder.cs +++ b/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/PortablePdbBuilder.cs @@ -14,8 +14,8 @@ namespace System.Reflection.Metadata.Ecma335 /// public sealed class PortablePdbBuilder { - public string MetadataVersion => "PDB v1.0"; - public ushort FormatVersion => 0x0100; + public string MetadataVersion => PortablePdbVersions.DefaultMetadataVersion; + public ushort FormatVersion => PortablePdbVersions.DefaultFormatVersion; private Blob _pdbIdBlob; private readonly MethodDefinitionHandle _entryPoint; diff --git a/src/System.Reflection.Metadata/src/System/Reflection/Metadata/MetadataReader.cs b/src/System.Reflection.Metadata/src/System/Reflection/Metadata/MetadataReader.cs index b97f95b19557..1c0d7a5cf9eb 100644 --- a/src/System.Reflection.Metadata/src/System/Reflection/Metadata/MetadataReader.cs +++ b/src/System.Reflection.Metadata/src/System/Reflection/Metadata/MetadataReader.cs @@ -1191,7 +1191,6 @@ public ExportedType GetExportedType(ExportedTypeHandle handle) public CustomAttributeHandleCollection GetCustomAttributes(EntityHandle handle) { - Debug.Assert(!handle.IsNil); return new CustomAttributeHandleCollection(this, handle); } @@ -1418,7 +1417,6 @@ public CustomDebugInformation GetCustomDebugInformation(CustomDebugInformationHa public CustomDebugInformationHandleCollection GetCustomDebugInformation(EntityHandle handle) { - Debug.Assert(!handle.IsNil); return new CustomDebugInformationHandleCollection(this, handle); } diff --git a/src/System.Reflection.Metadata/src/System/Reflection/Metadata/PortablePdb/HandleCollections.Debug.cs b/src/System.Reflection.Metadata/src/System/Reflection/Metadata/PortablePdb/HandleCollections.Debug.cs index 7bd1469a2c7f..184f3eef2e72 100644 --- a/src/System.Reflection.Metadata/src/System/Reflection/Metadata/PortablePdb/HandleCollections.Debug.cs +++ b/src/System.Reflection.Metadata/src/System/Reflection/Metadata/PortablePdb/HandleCollections.Debug.cs @@ -745,7 +745,6 @@ internal CustomDebugInformationHandleCollection(MetadataReader reader) internal CustomDebugInformationHandleCollection(MetadataReader reader, EntityHandle handle) { Debug.Assert(reader != null); - Debug.Assert(!handle.IsNil); _reader = reader; reader.CustomDebugInformationTable.GetRange(handle, out _firstRowId, out _lastRowId); diff --git a/src/System.Reflection.Metadata/src/System/Reflection/Metadata/PortablePdb/PortablePdbVersions.cs b/src/System.Reflection.Metadata/src/System/Reflection/Metadata/PortablePdb/PortablePdbVersions.cs new file mode 100644 index 000000000000..55139c5e369d --- /dev/null +++ b/src/System.Reflection.Metadata/src/System/Reflection/Metadata/PortablePdb/PortablePdbVersions.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Reflection.Metadata +{ + internal static class PortablePdbVersions + { + /// + /// Version of Portable PDB format emitted by the writer by default. Metadata version string. + /// + internal const string DefaultMetadataVersion = "PDB v1.0"; + + /// + /// Version of Portable PDB format emitted by the writer by default. + /// + internal const ushort DefaultFormatVersion = 0x0100; + + /// + /// Minimal supported version of Portable PDB format. + /// + internal const ushort MinFormatVersion = 0x0100; + + /// + /// Minimal supported version of Embedded Portable PDB blob. + /// + internal const ushort MinEmbeddedVersion = 0x0100; + + /// + /// Version of Embedded Portable PDB blob format emitted by the writer by default. + /// + internal const ushort DefaultEmbeddedVersion = 0x0100; + + /// + /// Minimal version of the Embedded Portable PDB blob that the current reader can't interpret. + /// + internal const ushort MinUnsupportedEmbeddedVersion = 0x0200; + + internal const uint DebugDirectoryEmbeddedSignature = 0x4244504d; + + internal static uint DebugDirectoryEntryVersion(ushort portablePdbVersion) => 'P' << 24 | 'M' << 16 | (uint)portablePdbVersion; + internal static uint DebugDirectoryEmbeddedVersion(ushort portablePdbVersion) => (uint)DefaultEmbeddedVersion << 16 | portablePdbVersion; + internal static string Format(ushort version) => (version >> 8) + "." + (version & 0xff); + } +} diff --git a/src/System.Reflection.Metadata/src/System/Reflection/Metadata/TypeSystem/HandleCollections.TypeSystem.cs b/src/System.Reflection.Metadata/src/System/Reflection/Metadata/TypeSystem/HandleCollections.TypeSystem.cs index d5435efc7528..2331ce81fcf9 100644 --- a/src/System.Reflection.Metadata/src/System/Reflection/Metadata/TypeSystem/HandleCollections.TypeSystem.cs +++ b/src/System.Reflection.Metadata/src/System/Reflection/Metadata/TypeSystem/HandleCollections.TypeSystem.cs @@ -243,7 +243,6 @@ internal CustomAttributeHandleCollection(MetadataReader reader) internal CustomAttributeHandleCollection(MetadataReader reader, EntityHandle handle) { Debug.Assert(reader != null); - Debug.Assert(!handle.IsNil); _reader = reader; reader.CustomAttributeTable.GetAttributeRange(handle, out _firstRowId, out _lastRowId); diff --git a/src/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/DebugDirectory/DebugDirectoryBuilder.cs b/src/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/DebugDirectory/DebugDirectoryBuilder.cs index fb1ff8c6be06..64f284a288e7 100644 --- a/src/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/DebugDirectory/DebugDirectoryBuilder.cs +++ b/src/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/DebugDirectory/DebugDirectoryBuilder.cs @@ -3,6 +3,8 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.IO; +using System.IO.Compression; using System.Reflection.Metadata; namespace System.Reflection.PortableExecutable @@ -51,14 +53,55 @@ public void AddCodeViewEntry( AddEntry( type: DebugDirectoryEntryType.CodeView, + version: (portablePdbVersion == 0) ? 0 : PortablePdbVersions.DebugDirectoryEntryVersion(portablePdbVersion), stamp: pdbContentId.Stamp, - version: (portablePdbVersion == 0) ? 0 : ('P' << 24 | 'M' << 16 | (uint)portablePdbVersion), dataSize: dataSize); } public void AddReproducibleEntry() { - AddEntry(type: DebugDirectoryEntryType.Reproducible, stamp: 0, version: 0); + AddEntry(type: DebugDirectoryEntryType.Reproducible, version: 0, stamp: 0); + } + + public void AddEmbeddedPortablePdbEntry(BlobBuilder debugMetadata, ushort portablePdbVersion) + { + if (debugMetadata == null) + { + Throw.ArgumentNull(nameof(debugMetadata)); + } + + int dataSize = WriteEmbeddedPortablePdbData(_dataBuilder, debugMetadata); + + AddEntry( + type: DebugDirectoryEntryType.EmbeddedPortablePdb, + version: PortablePdbVersions.DebugDirectoryEmbeddedVersion(portablePdbVersion), + stamp: 0, + dataSize: dataSize); + } + + private static int WriteEmbeddedPortablePdbData(BlobBuilder builder, BlobBuilder debugMetadata) + { + int start = builder.Count; + + // header (signature, decompressed size): + builder.WriteUInt32(PortablePdbVersions.DebugDirectoryEmbeddedSignature); + builder.WriteInt32(debugMetadata.Count); + + // compressed data: + var compressed = new MemoryStream(); + using (var deflate = new DeflateStream(compressed, CompressionLevel.Optimal, leaveOpen: true)) + { + foreach (var blob in debugMetadata.GetBlobs()) + { + var segment = blob.GetBytes(); + deflate.Write(segment.Array, segment.Offset, segment.Count); + } + } + + // TODO: avoid multiple copies: + builder.WriteBytes(compressed.ToArray()); + + return builder.Count - start; } private static int WriteCodeViewData(BlobBuilder builder, string pdbPath, Guid pdbGuid) diff --git a/src/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/DebugDirectory/DebugDirectoryEntryType.cs b/src/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/DebugDirectory/DebugDirectoryEntryType.cs index 23b2c0ed5ff3..c9788447127c 100644 --- a/src/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/DebugDirectory/DebugDirectoryEntryType.cs +++ b/src/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/DebugDirectory/DebugDirectoryEntryType.cs @@ -43,5 +43,17 @@ public enum DebugDirectoryEntryType /// /// Reproducible = 16, + + /// + /// The entry points to a blob containing Embedded Portable PDB. + /// + /// + /// The Embedded Portable PDB blob has the following format: + /// + /// blob ::= uncompressed-size data + /// + /// Data spans the remainder of the blob and contains a Deflate-compressed Portable PDB. + /// + EmbeddedPortablePdb = 17, } } diff --git a/src/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/PEReader.cs b/src/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/PEReader.cs index f42147197e10..c9d76e9f0c73 100644 --- a/src/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/PEReader.cs +++ b/src/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/PEReader.cs @@ -5,6 +5,7 @@ using System.Collections.Immutable; using System.Diagnostics; using System.IO; +using System.IO.Compression; using System.Reflection.Internal; using System.Reflection.Metadata; using System.Threading; @@ -534,5 +535,108 @@ public unsafe CodeViewDebugDirectoryData ReadCodeViewDebugDirectoryData(DebugDir return new CodeViewDebugDirectoryData(guid, age, path); } } + + /// + /// Reads the data pointed to by the specified Debug Directory entry and interprets them as Embedded Portable PDB blob. + /// + /// + /// Provider of a metadata reader reading Portable PDB image. + /// + /// is not a entry. + /// Bad format of the data. + public unsafe MetadataReaderProvider ReadEmbeddedPortablePdbDebugDirectoryData(DebugDirectoryEntry entry) + { + if (entry.Type != DebugDirectoryEntryType.EmbeddedPortablePdb) + { + throw new ArgumentException(SR.NotCodeViewEntry, nameof(entry)); + } + + ValidateEmbeddedPortablePdbVersion(entry); + + using (AbstractMemoryBlock block = _peImage.GetMemoryBlock(entry.DataPointer, entry.DataSize)) + { + var pdbImage = DecodeEmbeddedPortablePdbDebugDirectoryData(block); + return MetadataReaderProvider.FromPortablePdbImage(pdbImage); + } + } + + // internal for testing + internal static void ValidateEmbeddedPortablePdbVersion(DebugDirectoryEntry entry) + { + // Major version encodes the version of Portable PDB format itself. + // Minor version encodes the version of Embedded Portable PDB blob. + // Accept any version of Portable PDB >= 1.0, + // but only accept version 1.* of the Embedded Portable PDB blob. + // Any breaking change in the format should rev major version of the embedded blob. + ushort formatVersion = entry.MajorVersion; + if (formatVersion < PortablePdbVersions.MinFormatVersion) + { + throw new BadImageFormatException(SR.Format(SR.UnsupportedFormatVersion, PortablePdbVersions.Format(formatVersion))); + } + + ushort embeddedBlobVersion = entry.MinorVersion; + if (embeddedBlobVersion != PortablePdbVersions.DefaultEmbeddedVersion) + { + throw new BadImageFormatException(SR.Format(SR.UnsupportedFormatVersion, PortablePdbVersions.Format(embeddedBlobVersion))); + } + } + + // internal for testing + internal static unsafe ImmutableArray DecodeEmbeddedPortablePdbDebugDirectoryData(AbstractMemoryBlock block) + { + byte[] decompressed; + + const int headerSize = 2 * sizeof(int); + + var headerReader = new BlobReader(block.Pointer, headerSize); + + if (headerReader.ReadUInt32() != PortablePdbVersions.DebugDirectoryEmbeddedSignature) + { + throw new BadImageFormatException(SR.UnexpectedEmbeddedPortablePdbDataSignature); + } + + int decompressedSize = headerReader.ReadInt32(); + + try + { + decompressed = new byte[decompressedSize]; + } + catch + { + throw new BadImageFormatException(SR.DataTooBig); + } + + var compressed = new ReadOnlyUnmanagedMemoryStream(block.Pointer + headerSize, block.Size - headerSize); + var deflate = new DeflateStream(compressed, CompressionMode.Decompress, leaveOpen: true); + + if (decompressedSize > 0) + { + int actualLength; + + try + { + actualLength = deflate.TryReadAll(decompressed, 0, decompressed.Length); + } + catch (InvalidDataException e) + { + throw new BadImageFormatException(e.Message, e.InnerException); + } + + if (actualLength != decompressed.Length) + { + throw new BadImageFormatException(SR.SizeMismatch); + } + } + + // Check that there is no more compressed data left, + // in case the decompressed size specified in the header is smaller + // than the actual decompressed size of the data. + if (deflate.ReadByte() != -1) + { + throw new BadImageFormatException(SR.SizeMismatch); + } + + return ImmutableByteArrayInterop.DangerousCreateFromUnderlyingArray(ref decompressed); + } } } diff --git a/src/System.Reflection.Metadata/src/project.json b/src/System.Reflection.Metadata/src/project.json index d681b7e0f154..828cb9794064 100644 --- a/src/System.Reflection.Metadata/src/project.json +++ b/src/System.Reflection.Metadata/src/project.json @@ -14,7 +14,8 @@ "System.Runtime.InteropServices": "4.0.0", "System.Text.Encoding": "4.0.0", "System.Text.Encoding.Extensions": "4.0.0", - "System.Threading": "4.0.0" + "System.Threading": "4.0.0", + "System.IO.Compression": "4.0.0" }, "frameworks": { "netstandard1.0": { diff --git a/src/System.Reflection.Metadata/tests/Metadata/BlobContentIdTests.cs b/src/System.Reflection.Metadata/tests/Metadata/BlobContentIdTests.cs index c8dcba134325..15107a5d10a7 100644 --- a/src/System.Reflection.Metadata/tests/Metadata/BlobContentIdTests.cs +++ b/src/System.Reflection.Metadata/tests/Metadata/BlobContentIdTests.cs @@ -22,9 +22,25 @@ public void Ctor() Assert.Equal(0x12345678u, id2.Stamp); Assert.False(id2.IsDefault); + var id3 = new BlobContentId(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14 }); + Assert.Equal(new Guid("04030201-0605-0807-090a-0b0c0d0e0f10"), id3.Guid); + Assert.Equal(0x14131211u, id3.Stamp); + Assert.False(id3.IsDefault); + Assert.True(default(BlobContentId).IsDefault); } + [Fact] + public void Ctor_Errors() + { + Assert.Throws("id", () => new BlobContentId(null)); + Assert.Throws("id", () => new BlobContentId(default(ImmutableArray))); + Assert.Throws("id", () => new BlobContentId(ImmutableArray.Create())); + Assert.Throws("id", () => new BlobContentId(ImmutableArray.Create(0))); + Assert.Throws("id", () => new BlobContentId(ImmutableArray.Create(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13))); + Assert.Throws("id", () => new BlobContentId(ImmutableArray.Create(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15))); + } + [Fact] public void FromHash() { diff --git a/src/System.Reflection.Metadata/tests/Metadata/MetadataReaderTests.cs b/src/System.Reflection.Metadata/tests/Metadata/MetadataReaderTests.cs index f237876d6a67..2eddf138d394 100644 --- a/src/System.Reflection.Metadata/tests/Metadata/MetadataReaderTests.cs +++ b/src/System.Reflection.Metadata/tests/Metadata/MetadataReaderTests.cs @@ -91,17 +91,6 @@ internal static unsafe GCHandle GetPinnedPEImage(byte[] peImage) return pinned; } - private List GetCustomAttributes(MetadataReader reader, int token) - { - var attributes = new List(); - foreach (var caHandle in reader.GetCustomAttributes(new EntityHandle((uint)token))) - { - attributes.Add(caHandle); - } - - return attributes; - } - #endregion [Fact] @@ -2047,6 +2036,20 @@ public void ValidateCustomAttribute() } } + [Fact] + public void GetCustomAttributes() + { + var reader = GetMetadataReader(Interop.Interop_Mock01); + + var attributes1 = reader.GetCustomAttributes(MetadataTokens.EntityHandle(0x02000006)); + AssertEx.Equal(new[] { 0x16, 0x17, 0x18, 0x19 }, attributes1.Select(a => a.RowId)); + Assert.Equal(4, attributes1.Count); + + var attributes2 = reader.GetCustomAttributes(MetadataTokens.EntityHandle(0x02000000)); + AssertEx.Equal(new int[0], attributes2.Select(a => a.RowId)); + Assert.Equal(0, attributes2.Count); + } + /// /// MethodSemantics Table /// Semantic (2-byte unsigned) @@ -2319,6 +2322,22 @@ public void Bug17109() Assert.Equal(0, genericParams.Count); } + [Fact] + public void GetCustomDebugInformation() + { + using (var provider = MetadataReaderProvider.FromPortablePdbStream(new MemoryStream(PortablePdbs.DocumentsPdb))) + { + var reader = provider.GetMetadataReader(); + var cdi1 = reader.GetCustomAttributes(MetadataTokens.EntityHandle(0x30000001)); + AssertEx.Equal(new int[0], cdi1.Select(a => a.RowId)); + Assert.Equal(0, cdi1.Count); + + var cdi2 = reader.GetCustomAttributes(MetadataTokens.EntityHandle(0x03000000)); + AssertEx.Equal(new int[0], cdi2.Select(a => a.RowId)); + Assert.Equal(0, cdi2.Count); + } + } + [Fact] public void MemberCollections_AllMembers() { diff --git a/src/System.Reflection.Metadata/tests/PortableExecutable/DebugDirectoryBuilderTests.cs b/src/System.Reflection.Metadata/tests/PortableExecutable/DebugDirectoryBuilderTests.cs index 0e1d3baefbfd..88bda6e015bf 100644 --- a/src/System.Reflection.Metadata/tests/PortableExecutable/DebugDirectoryBuilderTests.cs +++ b/src/System.Reflection.Metadata/tests/PortableExecutable/DebugDirectoryBuilderTests.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Immutable; +using System.Reflection.Internal; using System.Reflection.Metadata; using System.Reflection.Metadata.Tests; using Xunit; @@ -222,5 +224,171 @@ public void MultipleEntries() (byte)'y', 0x00, // path }, blob.ToArray()); } + + [Fact] + public void EmbeddedPortablePdb() + { + var b = new DebugDirectoryBuilder(); + + var pdb = new BlobBuilder(); + pdb.WriteInt64(0x1122334455667788); + + b.AddEmbeddedPortablePdbEntry(pdb, portablePdbVersion: 0x0100); + + var blob = new BlobBuilder(); + b.Serialize(blob, new SectionLocation(0, 0), sectionOffset: 0); + var bytes = blob.ToImmutableArray(); + + AssertEx.Equal(new byte[] + { + 0x00, 0x00, 0x00, 0x00, // Characteristics + 0x00, 0x00, 0x00, 0x00, // Stamp + 0x00, 0x01, 0x00, 0x01, // Version + 0x11, 0x00, 0x00, 0x00, // Type + 0x12, 0x00, 0x00, 0x00, // SizeOfData + 0x1C, 0x00, 0x00, 0x00, // AddressOfRawData + 0x1C, 0x00, 0x00, 0x00, // PointerToRawData + + 0x4D, 0x50, 0x44, 0x42, // signature + 0x08, 0x00, 0x00, 0x00, // uncompressed size + 0xEB, 0x28, 0x4F, 0x0B, 0x75, 0x31, 0x56, 0x12, 0x04, 0x00 // compressed data + }, bytes); + + using (var pinned = new PinnedBlob(bytes)) + { + var actual = PEReader.ReadDebugDirectoryEntries(pinned.CreateReader(0, DebugDirectoryEntry.Size)); + Assert.Equal(1, actual.Length); + Assert.Equal(0u, actual[0].Stamp); + Assert.Equal(0x0100, actual[0].MajorVersion); + Assert.Equal(0x0100, actual[0].MinorVersion); + Assert.Equal(DebugDirectoryEntryType.EmbeddedPortablePdb, actual[0].Type); + Assert.Equal(0x00000012, actual[0].DataSize); + Assert.Equal(0x0000001c, actual[0].DataRelativeVirtualAddress); + Assert.Equal(0x0000001c, actual[0].DataPointer); + + var provider = new ByteArrayMemoryProvider(bytes); + using (var block = provider.GetMemoryBlock(actual[0].DataPointer, actual[0].DataSize)) + { + var decoded = PEReader.DecodeEmbeddedPortablePdbDebugDirectoryData(block); + AssertEx.Equal(new byte[] { 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11 }, decoded); + } + } + } + + [Fact] + public void EmbeddedPortablePdb_Errors() + { + var bytes1 = ImmutableArray.Create(new byte[] + { + 0x4D, 0x50, 0x44, 0x42, // signature + 0xFF, 0xFF, 0xFF, 0xFF, // uncompressed size + 0xEB, 0x28, 0x4F, 0x0B, 0x75, 0x31, 0x56, 0x12, 0x04, 0x00 // compressed data + }); + + using (var block = new ByteArrayMemoryProvider(bytes1).GetMemoryBlock(0, bytes1.Length)) + { + Assert.Throws(() => PEReader.DecodeEmbeddedPortablePdbDebugDirectoryData(block)); + } + + var bytes2 = ImmutableArray.Create(new byte[] + { + 0x4D, 0x50, 0x44, 0x42, // signature + 0x09, 0x00, 0x00, 0x00, // uncompressed size + 0xEB, 0x28, 0x4F, 0x0B, 0x75, 0x31, 0x56, 0x12, 0x04, 0x00 // compressed data + }); + + using (var block = new ByteArrayMemoryProvider(bytes2).GetMemoryBlock(0, bytes2.Length)) + { + Assert.Throws(() => PEReader.DecodeEmbeddedPortablePdbDebugDirectoryData(block)); + } + + var bytes3 = ImmutableArray.Create(new byte[] + { + 0x4D, 0x50, 0x44, 0x42, // signature + 0x00, 0x00, 0x00, 0x00, // uncompressed size + 0xEB, 0x28, 0x4F, 0x0B, 0x75, 0x31, 0x56, 0x12, 0x04, 0x00 // compressed data + }); + + using (var block = new ByteArrayMemoryProvider(bytes3).GetMemoryBlock(0, bytes3.Length)) + { + Assert.Throws(() => PEReader.DecodeEmbeddedPortablePdbDebugDirectoryData(block)); + } + + var bytes4 = ImmutableArray.Create(new byte[] + { + 0x4D, 0x50, 0x44, 0x42, // signature + 0xff, 0xff, 0xff, 0x7f, // uncompressed size + 0xEB, 0x28, 0x4F, 0x0B, 0x75, 0x31, 0x56, 0x12, 0x04, 0x00 // compressed data + }); + + using (var block = new ByteArrayMemoryProvider(bytes4).GetMemoryBlock(0, bytes4.Length)) + { + Assert.Throws(() => PEReader.DecodeEmbeddedPortablePdbDebugDirectoryData(block)); + } + + var bytes5 = ImmutableArray.Create(new byte[] + { + 0x4D, 0x50, 0x44, 0x42, // signature + 0x08, 0x00, 0x00, 0x00, // uncompressed size + 0xEF, 0xFF, 0x4F, 0xFF, 0x75, 0x31, 0x56, 0x12, 0x04, 0x00 // compressed data + }); + + using (var block = new ByteArrayMemoryProvider(bytes4).GetMemoryBlock(0, bytes4.Length)) + { + Assert.Throws(() => PEReader.DecodeEmbeddedPortablePdbDebugDirectoryData(block)); + } + + var bytes6 = ImmutableArray.Create(new byte[] + { + 0x4D, 0x50, 0x44, 0x43, // signature + 0x08, 0x00, 0x00, 0x00, // uncompressed size + 0xEB, 0x28, 0x4F, 0x0B, 0x75, 0x31, 0x56, 0x12, 0x04, 0x00 // compressed data + }); + + using (var block = new ByteArrayMemoryProvider(bytes6).GetMemoryBlock(0, bytes6.Length)) + { + Assert.Throws(() => PEReader.DecodeEmbeddedPortablePdbDebugDirectoryData(block)); + } + + var bytes7 = ImmutableArray.Create(new byte[] + { + 0x4D, 0x50, 0x44, 0x43, // signature + 0x08, 0x00, 0x00, + }); + + using (var block = new ByteArrayMemoryProvider(bytes7).GetMemoryBlock(0, bytes7.Length)) + { + Assert.Throws(() => PEReader.DecodeEmbeddedPortablePdbDebugDirectoryData(block)); + } + + var bytes8 = ImmutableArray.Create(new byte[] + { + 0x4D, 0x50, 0x44, 0x43, // signature + 0x08, 0x00, 0x00, + }); + + using (var block = new ByteArrayMemoryProvider(bytes8).GetMemoryBlock(0, bytes8.Length)) + { + Assert.Throws(() => PEReader.DecodeEmbeddedPortablePdbDebugDirectoryData(block)); + } + } + + [Fact] + public void ValidateEmbeddedPortablePdbVersion() + { + // major version (Portable PDB format): + PEReader.ValidateEmbeddedPortablePdbVersion(new DebugDirectoryEntry(0, 0x0100, 0x0100, DebugDirectoryEntryType.EmbeddedPortablePdb, 0, 0, 0)); + PEReader.ValidateEmbeddedPortablePdbVersion(new DebugDirectoryEntry(0, 0x0101, 0x0100, DebugDirectoryEntryType.EmbeddedPortablePdb, 0, 0, 0)); + PEReader.ValidateEmbeddedPortablePdbVersion(new DebugDirectoryEntry(0, 0xffff, 0x0100, DebugDirectoryEntryType.EmbeddedPortablePdb, 0, 0, 0)); + + Assert.Throws(() => PEReader.ValidateEmbeddedPortablePdbVersion(new DebugDirectoryEntry(0, 0x0000, 0x0100, DebugDirectoryEntryType.EmbeddedPortablePdb, 0, 0, 0))); + Assert.Throws(() => PEReader.ValidateEmbeddedPortablePdbVersion(new DebugDirectoryEntry(0, 0x00ff, 0x0100, DebugDirectoryEntryType.EmbeddedPortablePdb, 0, 0, 0))); + + // minor version (Embedded blob format): + Assert.Throws(() => PEReader.ValidateEmbeddedPortablePdbVersion(new DebugDirectoryEntry(0, 0x0100, 0x0101, DebugDirectoryEntryType.EmbeddedPortablePdb, 0, 0, 0))); + Assert.Throws(() => PEReader.ValidateEmbeddedPortablePdbVersion(new DebugDirectoryEntry(0, 0x0100, 0x0000, DebugDirectoryEntryType.EmbeddedPortablePdb, 0, 0, 0))); + Assert.Throws(() => PEReader.ValidateEmbeddedPortablePdbVersion(new DebugDirectoryEntry(0, 0x0100, 0x00ff, DebugDirectoryEntryType.EmbeddedPortablePdb, 0, 0, 0))); + Assert.Throws(() => PEReader.ValidateEmbeddedPortablePdbVersion(new DebugDirectoryEntry(0, 0x0100, 0x0200, DebugDirectoryEntryType.EmbeddedPortablePdb, 0, 0, 0))); + } } } diff --git a/src/System.Reflection.Metadata/tests/TestUtilities/ByteArrayUtilities.cs b/src/System.Reflection.Metadata/tests/TestUtilities/ByteArrayUtilities.cs index d3ae6ab0e94c..6055f0f97ef0 100644 --- a/src/System.Reflection.Metadata/tests/TestUtilities/ByteArrayUtilities.cs +++ b/src/System.Reflection.Metadata/tests/TestUtilities/ByteArrayUtilities.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Immutable; + namespace System.Reflection.Metadata.Tests { public static class ByteArrayUtilities @@ -11,6 +13,11 @@ public static byte[] Slice(this BlobBuilder bytes, int start, int end) return Slice(bytes.ToArray(), start, end); } + public static byte[] Slice(this byte[] bytes, int start) + { + return Slice(bytes, start, bytes.Length); + } + public static byte[] Slice(this byte[] bytes, int start, int end) { if (end < 0) @@ -22,5 +29,20 @@ public static byte[] Slice(this byte[] bytes, int start, int end) Array.Copy(bytes, start, result, 0, result.Length); return result; } + + public static ImmutableArray Slice(this ImmutableArray bytes, int start) + { + return Slice(bytes, start, bytes.Length); + } + + public static ImmutableArray Slice(this ImmutableArray bytes, int start, int end) + { + if (end < 0) + { + end = bytes.Length + end; + } + + return ImmutableArray.Create(bytes, start, end - start); + } } }