diff --git a/docs/design/datacontracts/EcmaMetadata.md b/docs/design/datacontracts/EcmaMetadata.md index b0e08f971b1592..b4a032d8a2d07e 100644 --- a/docs/design/datacontracts/EcmaMetadata.md +++ b/docs/design/datacontracts/EcmaMetadata.md @@ -5,10 +5,14 @@ This contract provides methods to get a view of the ECMA-335 metadata for a give ## APIs of contract ```csharp -TargetSpan GetReadOnlyMetadataAddress(ModuleHandle handle); -TargetSpan GetReadWriteSavedMetadataAddress(ModuleHandle handle); -System.Reflection.Metadata.MetadataReader? GetMetadata(ModuleHandle handle); -byte[] GetReadWriteMetadata(ModuleHandle handle); +enum MetadataAddressKind +{ + ReadOnly, + ReadWriteSavedCopy, +} + +TargetSpan GetMetadataAddress(ModuleHandle handle, MetadataAddressKind kind); +System.Reflection.Metadata.MetadataReader? GetMetadata(ModuleHandle handle, bool requireReadWriteMetadata = false); ``` Types from other contracts: @@ -71,8 +75,16 @@ using System.IO; using System.Reflection.Metadata; using System.Runtime.InteropServices; -TargetSpan GetReadOnlyMetadataAddress(ModuleHandle handle) +TargetSpan GetMetadataAddress(ModuleHandle handle, MetadataAddressKind kind) { + if (kind == MetadataAddressKind.ReadWriteSavedCopy) + { + TargetPointer dynamicMetadata = Target.ReadPointer(handle.Address + /* Module::DynamicMetadata offset */); + ulong size = Target.Read(dynamicMetadata + /* DynamicMetadata::Size offset */); + TargetPointer result = dynamicMetadata + /* DynamicMetadata::Data offset */; + return new(result, size); + } + TargetPointer baseAddress = Target.ReadPointer(handle.Address + /* Module::Base offset */); if (baseAddress == TargetPointer.Null) { @@ -95,9 +107,13 @@ TargetSpan GetReadOnlyMetadataAddress(ModuleHandle handle) return new(baseAddress + rva, size); } -MetadataReader? GetMetadata(ModuleHandle handle) +MetadataReader? GetMetadata(ModuleHandle handle, bool requireReadWriteMetadata = false) { AvailableMetadataType type = GetAvailableMetadataType(handle); + if (requireReadWriteMetadata && type != AvailableMetadataType.ReadWrite) + { + throw new ArgumentException(); + } switch (type) { @@ -105,23 +121,21 @@ MetadataReader? GetMetadata(ModuleHandle handle) return null; case AvailableMetadataType.ReadOnly: { - TargetSpan address = GetReadOnlyMetadataAddress(handle); + TargetSpan address = GetMetadataAddress(handle, MetadataAddressKind.ReadOnly); byte[] data = new byte[address.Size]; _target.ReadBuffer(address.Address, data); return MetadataReaderProvider.FromMetadataImage(ImmutableCollectionsMarshal.AsImmutableArray(data)).GetMetadataReader(); } case AvailableMetadataType.ReadWriteSavedCopy: { - TargetSpan address = GetReadWriteSavedMetadataAddress(handle); + TargetSpan address = GetMetadataAddress(handle, MetadataAddressKind.ReadWriteSavedCopy); byte[] data = new byte[address.Size]; _target.ReadBuffer(address.Address, data); return MetadataReaderProvider.FromMetadataImage(ImmutableCollectionsMarshal.AsImmutableArray(data)).GetMetadataReader(); } case AvailableMetadataType.ReadWrite: { - // Reconstruct a contiguous ECMA-335 image from the module's writable - // (MDInternalRW) metadata and return a reader over it. - byte[] data = GetReadWriteMetadata(handle); + byte[] data = GetReadWriteMetadataBlob(handle); return MetadataReaderProvider.FromMetadataImage(ImmutableCollectionsMarshal.AsImmutableArray(data)).GetMetadataReader(); } } @@ -130,7 +144,7 @@ MetadataReader? GetMetadata(ModuleHandle handle) // Reconstructs the module's writable (MDInternalRW) metadata as a single contiguous // ECMA-335 metadata image. The result is cached per module and reused until the // module's metadata generation counter (Module::MetadataGeneration) changes. -byte[] GetReadWriteMetadata(ModuleHandle handle) +byte[] GetReadWriteMetadataBlob(ModuleHandle handle) { // If a blob was previously built for this handle and the module's metadata // generation counter is unchanged, return the cached blob. @@ -223,11 +237,4 @@ AvailableMetadataType GetAvailableMetadataType(ModuleHandle handle) return flags; } -TargetSpan GetReadWriteSavedMetadataAddress(ModuleHandle handle) -{ - TargetPointer dynamicMetadata = Target.ReadPointer(handle.Address + /* Module::DynamicMetadata offset */); - ulong size = Target.Read(handle.Address + /* DynamicMetadata::Size offset */); - TargetPointer result = handle.Address + /* DynamicMetadata::Data offset */; - return new(result, size); -} ``` diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IEcmaMetadata.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IEcmaMetadata.cs index 2ac6b53e7c7e91..c38f1c9918a443 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IEcmaMetadata.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IEcmaMetadata.cs @@ -6,14 +6,17 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; +public enum MetadataAddressKind +{ + ReadOnly, + ReadWriteSavedCopy, +} + public interface IEcmaMetadata : IContract { static string IContract.Name { get; } = nameof(EcmaMetadata); - TargetSpan GetReadOnlyMetadataAddress(ModuleHandle handle) => throw new NotImplementedException(); - TargetSpan GetReadWriteSavedMetadataAddress(ModuleHandle handle) => throw new NotImplementedException(); - - MetadataReader? GetMetadata(ModuleHandle module) => throw new NotImplementedException(); - byte[] GetReadWriteMetadata(ModuleHandle handle) => throw new NotImplementedException(); + TargetSpan GetMetadataAddress(ModuleHandle handle, MetadataAddressKind kind) => throw new NotImplementedException(); + MetadataReader? GetMetadata(ModuleHandle handle, bool requireReadWriteMetadata = false) => throw new NotImplementedException(); } public readonly struct EcmaMetadata : IEcmaMetadata diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/EcmaMetadata_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/EcmaMetadata_1.cs index 0feb5008dbff88..17819d930987a8 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/EcmaMetadata_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/EcmaMetadata_1.cs @@ -31,8 +31,17 @@ public void Flush(FlushScope scope) } } - public TargetSpan GetReadOnlyMetadataAddress(ModuleHandle handle) + public TargetSpan GetMetadataAddress(ModuleHandle handle, MetadataAddressKind kind) { + if (kind == MetadataAddressKind.ReadWriteSavedCopy) + { + Data.Module module = target.ProcessedData.GetOrAdd(handle.Address); + Data.DynamicMetadata dynamicMetadata = target.ProcessedData.GetOrAdd(module.DynamicMetadata); + return new TargetSpan(dynamicMetadata.Data, dynamicMetadata.Size); + } + if (kind != MetadataAddressKind.ReadOnly) + throw new ArgumentOutOfRangeException(nameof(kind)); + if (_readOnlyMetadataAddress.TryGetValue(handle, out TargetSpan cached)) return cached; @@ -94,8 +103,13 @@ private TargetSpan GetWebcilReadOnlyMetadataAddress(ModuleHandle handle, TargetP return new TargetSpan(metadataAddress, metadataDirectory.Size); } - public MetadataReader? GetMetadata(ModuleHandle handle) + public MetadataReader? GetMetadata(ModuleHandle handle, bool requireReadWriteMetadata = false) { + if (requireReadWriteMetadata && GetAvailableMetadataType(handle) != AvailableMetadataType.ReadWrite) + { + throw new ArgumentException("Module does not have read/write metadata.", nameof(handle)); + } + uint generation = GetMetadataGeneration(handle); if (_metadata.TryGetValue(handle, out (uint Generation, MetadataReaderProvider? Provider) cached)) @@ -121,22 +135,12 @@ private TargetSpan GetWebcilReadOnlyMetadataAddress(ModuleHandle handle, TargetP case AvailableMetadataType.None: return null; case AvailableMetadataType.ReadOnly: - { - TargetSpan address = GetReadOnlyMetadataAddress(handle); - byte[] data = new byte[address.Size]; - target.ReadBuffer(address.Address, data); - return MetadataReaderProvider.FromMetadataImage(ImmutableCollectionsMarshal.AsImmutableArray(data)); - } + return GetMetadataProviderFromAddress(handle, MetadataAddressKind.ReadOnly); case AvailableMetadataType.ReadWriteSavedCopy: - { - TargetSpan address = GetReadWriteSavedMetadataAddress(handle); - byte[] data = new byte[address.Size]; - target.ReadBuffer(address.Address, data); - return MetadataReaderProvider.FromMetadataImage(ImmutableCollectionsMarshal.AsImmutableArray(data)); - } + return GetMetadataProviderFromAddress(handle, MetadataAddressKind.ReadWriteSavedCopy); case AvailableMetadataType.ReadWrite: { - byte[] data = GetReadWriteMetadata(handle); + byte[] data = GetReadWriteMetadataBlob(handle); return MetadataReaderProvider.FromMetadataImage(ImmutableCollectionsMarshal.AsImmutableArray(data)); } default: @@ -144,7 +148,15 @@ private TargetSpan GetWebcilReadOnlyMetadataAddress(ModuleHandle handle, TargetP } } - public byte[] GetReadWriteMetadata(ModuleHandle handle) + private MetadataReaderProvider GetMetadataProviderFromAddress(ModuleHandle handle, MetadataAddressKind kind) + { + TargetSpan address = GetMetadataAddress(handle, kind); + byte[] data = new byte[address.Size]; + target.ReadBuffer(address.Address, data); + return MetadataReaderProvider.FromMetadataImage(ImmutableCollectionsMarshal.AsImmutableArray(data)); + } + + private byte[] GetReadWriteMetadataBlob(ModuleHandle handle) { if (GetAvailableMetadataType(handle) != AvailableMetadataType.ReadWrite) { @@ -413,14 +425,6 @@ private uint GetMetadataGeneration(ModuleHandle handle) return module.MetadataGeneration; } - public TargetSpan GetReadWriteSavedMetadataAddress(ModuleHandle handle) - { - Data.Module module = target.ProcessedData.GetOrAdd(handle.Address); - Data.DynamicMetadata dynamicMetadata = target.ProcessedData.GetOrAdd(module.DynamicMetadata); - - return new TargetSpan(dynamicMetadata.Data, dynamicMetadata.Size); - } - private TargetEcmaMetadata GetTargetEcmaMetadata(ModuleHandle handle) { TargetPointer peAssemblyPtr = target.Contracts.Loader.GetPEAssembly(handle); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs index 07775791e9d49e..b2c2b7b9a941e1 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs @@ -360,10 +360,10 @@ public int GetMetadata(ulong vmModule, DacDbiTargetBuffer* pTargetBuffer) Contracts.ModuleFlags flags = loader.GetFlags(handle); Contracts.IEcmaMetadata ecmaMetadata = _target.Contracts.EcmaMetadata; - // Dynamic modules keep an eagerly-serialized metadata buffer, while non-dynamic modules read metadata from the loaded PE image. - TargetSpan targetSpan = flags.HasFlag(Contracts.ModuleFlags.ReflectionEmit) - ? ecmaMetadata.GetReadWriteSavedMetadataAddress(handle) - : ecmaMetadata.GetReadOnlyMetadataAddress(handle); + MetadataAddressKind kind = flags.HasFlag(Contracts.ModuleFlags.ReflectionEmit) + ? MetadataAddressKind.ReadWriteSavedCopy + : MetadataAddressKind.ReadOnly; + TargetSpan targetSpan = ecmaMetadata.GetMetadataAddress(handle, kind); pTargetBuffer->pAddress = targetSpan.Address.Value; pTargetBuffer->cbSize = checked((uint)targetSpan.Size); @@ -407,8 +407,8 @@ public int GetReadWriteMetadataSize(ulong vmModule, uint* pSize) Contracts.ILoader loader = _target.Contracts.Loader; Contracts.ModuleHandle handle = loader.GetModuleHandleFromModulePtr(new TargetPointer(vmModule)); - byte[] blob = _target.Contracts.EcmaMetadata.GetReadWriteMetadata(handle); - *pSize = (uint)blob.Length; + MetadataReader reader = _target.Contracts.EcmaMetadata.GetMetadata(handle, requireReadWriteMetadata: true)!; + *pSize = checked((uint)reader.MetadataLength); } catch (System.Exception ex) { @@ -443,12 +443,12 @@ public int FillReadWriteMetadata(ulong vmModule, byte* pBuffer, uint cbBuffer) Contracts.ILoader loader = _target.Contracts.Loader; Contracts.ModuleHandle handle = loader.GetModuleHandleFromModulePtr(new TargetPointer(vmModule)); - byte[] blob = _target.Contracts.EcmaMetadata.GetReadWriteMetadata(handle); - blobLength = blob.Length; - if (cbBuffer < (uint)blob.Length) + MetadataReader reader = _target.Contracts.EcmaMetadata.GetMetadata(handle, requireReadWriteMetadata: true)!; + blobLength = reader.MetadataLength; + if (cbBuffer < (uint)blobLength) throw Marshal.GetExceptionForHR(CorDbgHResults.ERROR_INSUFFICIENT_BUFFER)!; - blob.AsSpan().CopyTo(new Span(pBuffer, blobLength)); + new ReadOnlySpan(reader.MetadataPointer, blobLength).CopyTo(new Span(pBuffer, blobLength)); } catch (System.Exception ex) { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs index 18255e48911da2..9b353a0c55e9e1 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -3136,7 +3136,7 @@ int ISOSDacInterface.GetModuleData(ClrDataAddress moduleAddr, DacpModuleData* da try { - TargetSpan readOnlyMetadata = _target.Contracts.EcmaMetadata.GetReadOnlyMetadataAddress(handle); + TargetSpan readOnlyMetadata = _target.Contracts.EcmaMetadata.GetMetadataAddress(handle, MetadataAddressKind.ReadOnly); data->metadataStart = readOnlyMetadata.Address.Value; data->metadataSize = readOnlyMetadata.Size; } diff --git a/src/native/managed/cdac/tests/DumpTests/EcmaMetadataDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/EcmaMetadataDumpTests.cs index 278c58aeb53da4..c8507c99b71c8c 100644 --- a/src/native/managed/cdac/tests/DumpTests/EcmaMetadataDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/EcmaMetadataDumpTests.cs @@ -28,7 +28,7 @@ public void EcmaMetadata_RootModuleHasMetadataAddress(TestConfiguration config) TargetPointer rootAssembly = loader.GetRootAssembly(); ModuleHandle moduleHandle = loader.GetModuleHandleFromAssemblyPtr(rootAssembly); - TargetSpan metadataSpan = ecmaMetadata.GetReadOnlyMetadataAddress(moduleHandle); + TargetSpan metadataSpan = ecmaMetadata.GetMetadataAddress(moduleHandle, MetadataAddressKind.ReadOnly); Assert.NotEqual(TargetPointer.Null, metadataSpan.Address); Assert.True(metadataSpan.Size > 0, "Expected metadata size > 0"); } diff --git a/src/native/managed/cdac/tests/UnitTests/DacDbiImplTests.cs b/src/native/managed/cdac/tests/UnitTests/DacDbiImplTests.cs index 5289618bef2dec..69f55885f667a6 100644 --- a/src/native/managed/cdac/tests/UnitTests/DacDbiImplTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/DacDbiImplTests.cs @@ -631,7 +631,7 @@ public void ResolveTypeReference_TypeRef_NotCached_ReturnsClassNotLoaded(MockTar SetupTypeRefCacheMiss(loader); Mock ecma = new(MockBehavior.Strict); - ecma.Setup(e => e.GetMetadata(refHandle)).Returns((MetadataReader?)null); + ecma.Setup(e => e.GetMetadata(refHandle, false)).Returns((MetadataReader?)null); DacDbiImpl dacDbi = CreateDacDbiWithMockContracts(arch, loader, ecma); @@ -735,8 +735,8 @@ public void ResolveTypeReference_TypeRef_AssemblyRefScope_ResolvesToTypeDef(Mock loader.Setup(l => l.GetAssembly(targetHandle)).Returns(Ptr(targetAsmPtr)); Mock ecma = new(MockBehavior.Strict); - ecma.Setup(e => e.GetMetadata(refHandle)).Returns(refReader); - ecma.Setup(e => e.GetMetadata(targetHandle)).Returns(targetReader); + ecma.Setup(e => e.GetMetadata(refHandle, false)).Returns(refReader); + ecma.Setup(e => e.GetMetadata(targetHandle, false)).Returns(targetReader); DacDbiImpl dacDbi = CreateDacDbiWithMockContracts(arch, loader, ecma); @@ -789,9 +789,9 @@ public void ResolveTypeReference_TypeRef_TypeForwarder_FollowsExportedType(MockT loader.Setup(l => l.GetAssembly(handleB)).Returns(Ptr(asmBPtr)); Mock ecma = new(MockBehavior.Strict); - ecma.Setup(e => e.GetMetadata(refHandle)).Returns(refReader); - ecma.Setup(e => e.GetMetadata(handleA)).Returns(readerA); - ecma.Setup(e => e.GetMetadata(handleB)).Returns(readerB); + ecma.Setup(e => e.GetMetadata(refHandle, false)).Returns(refReader); + ecma.Setup(e => e.GetMetadata(handleA, false)).Returns(readerA); + ecma.Setup(e => e.GetMetadata(handleB, false)).Returns(readerB); DacDbiImpl dacDbi = CreateDacDbiWithMockContracts(arch, loader, ecma); @@ -845,8 +845,8 @@ public void ResolveTypeReference_TypeRef_NestedType_ResolvesNestedTypeDef(MockTa loader.Setup(l => l.GetAssembly(targetHandle)).Returns(Ptr(targetAsmPtr)); Mock ecma = new(MockBehavior.Strict); - ecma.Setup(e => e.GetMetadata(refHandle)).Returns(refReader); - ecma.Setup(e => e.GetMetadata(targetHandle)).Returns(targetReader); + ecma.Setup(e => e.GetMetadata(refHandle, false)).Returns(refReader); + ecma.Setup(e => e.GetMetadata(targetHandle, false)).Returns(targetReader); DacDbiImpl dacDbi = CreateDacDbiWithMockContracts(arch, loader, ecma); @@ -1574,7 +1574,7 @@ public void GetMetadata_NonDynamicModule(MockTarget.Architecture arch) Contracts.ModuleHandle handle = new(moduleAddr); var mockEcmaMetadata = new Mock(); - mockEcmaMetadata.Setup(e => e.GetReadOnlyMetadataAddress(handle)).Returns(new TargetSpan(metadataAddr, metadataSize)); + mockEcmaMetadata.Setup(e => e.GetMetadataAddress(handle, MetadataAddressKind.ReadOnly)).Returns(new TargetSpan(metadataAddr, metadataSize)); DacDbiImpl dacDbi = CreateDacDbiForModule(arch, vmModule, handle, (ModuleFlags)0, mockEcmaMetadata); @@ -1584,7 +1584,7 @@ public void GetMetadata_NonDynamicModule(MockTarget.Architecture arch) Assert.Equal(System.HResults.S_OK, hr); Assert.Equal(metadataAddr.Value, buffer.pAddress); Assert.Equal(metadataSize, buffer.cbSize); - mockEcmaMetadata.Verify(e => e.GetReadWriteSavedMetadataAddress(It.IsAny()), Times.Never); + mockEcmaMetadata.Verify(e => e.GetMetadataAddress(handle, MetadataAddressKind.ReadOnly), Times.Once); } [Theory] @@ -1598,7 +1598,7 @@ public void GetMetadata_DynamicModule(MockTarget.Architecture arch) Contracts.ModuleHandle handle = new(moduleAddr); var mockEcmaMetadata = new Mock(); - mockEcmaMetadata.Setup(e => e.GetReadWriteSavedMetadataAddress(handle)).Returns(new TargetSpan(metadataAddr, metadataSize)); + mockEcmaMetadata.Setup(e => e.GetMetadataAddress(handle, MetadataAddressKind.ReadWriteSavedCopy)).Returns(new TargetSpan(metadataAddr, metadataSize)); DacDbiImpl dacDbi = CreateDacDbiForModule(arch, vmModule, handle, ModuleFlags.ReflectionEmit, mockEcmaMetadata); @@ -1608,7 +1608,7 @@ public void GetMetadata_DynamicModule(MockTarget.Architecture arch) Assert.Equal(System.HResults.S_OK, hr); Assert.Equal(metadataAddr.Value, buffer.pAddress); Assert.Equal(metadataSize, buffer.cbSize); - mockEcmaMetadata.Verify(e => e.GetReadOnlyMetadataAddress(It.IsAny()), Times.Never); + mockEcmaMetadata.Verify(e => e.GetMetadataAddress(handle, MetadataAddressKind.ReadWriteSavedCopy), Times.Once); } [Theory] @@ -1620,7 +1620,7 @@ public void GetMetadata_EmptyMetadata(MockTarget.Architecture arch) Contracts.ModuleHandle handle = new(moduleAddr); var mockEcmaMetadata = new Mock(); - mockEcmaMetadata.Setup(e => e.GetReadOnlyMetadataAddress(handle)).Returns(new TargetSpan(TargetPointer.Null, 0)); + mockEcmaMetadata.Setup(e => e.GetMetadataAddress(handle, MetadataAddressKind.ReadOnly)).Returns(new TargetSpan(TargetPointer.Null, 0)); DacDbiImpl dacDbi = CreateDacDbiForModule(arch, vmModule, handle, (ModuleFlags)0, mockEcmaMetadata); @@ -1642,4 +1642,36 @@ public void GetMetadata_NullBuffer(MockTarget.Architecture arch) int hr = dacDbi.GetMetadata(vmModule, null); Assert.Equal(System.HResults.E_POINTER, hr); } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void ReadWriteMetadata_UsesMetadataReaderImage(MockTarget.Architecture arch) + { + ulong vmModule = 0x1000; + Contracts.ModuleHandle handle = new(new TargetPointer(vmModule)); + var (reader, provider) = BuildMetadata(_ => { }); + using (provider) + { + byte[] expected = new ReadOnlySpan(reader.MetadataPointer, reader.MetadataLength).ToArray(); + var mockEcmaMetadata = new Mock(); + mockEcmaMetadata.Setup(e => e.GetMetadata(handle, true)).Returns(reader); + DacDbiImpl dacDbi = CreateDacDbiForModule(arch, vmModule, handle, (ModuleFlags)0, mockEcmaMetadata); + + uint size; + int hr = dacDbi.GetReadWriteMetadataSize(vmModule, &size); + + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal((uint)expected.Length, size); + + byte[] actual = new byte[size]; + fixed (byte* buffer = actual) + { + hr = dacDbi.FillReadWriteMetadata(vmModule, buffer, size); + } + + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(expected, actual); + mockEcmaMetadata.Verify(e => e.GetMetadata(handle, true), Times.Exactly(2)); + } + } } diff --git a/src/native/managed/cdac/tests/UnitTests/EcmaMetadataTests.cs b/src/native/managed/cdac/tests/UnitTests/EcmaMetadataTests.cs new file mode 100644 index 00000000000000..2524c7b5b17254 --- /dev/null +++ b/src/native/managed/cdac/tests/UnitTests/EcmaMetadataTests.cs @@ -0,0 +1,82 @@ +// 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; +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.TestInfrastructure; +using Xunit; +using ModuleHandle = Microsoft.Diagnostics.DataContractReader.Contracts.ModuleHandle; + +namespace Microsoft.Diagnostics.DataContractReader.Tests; + +public class EcmaMetadataTests +{ + private const uint MetadataSize = 0x20; + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetMetadataAddress_ReadWriteSavedCopy_ReturnsDynamicMetadata(MockTarget.Architecture arch) + { + IEcmaMetadata contract = CreateContractWithSavedMetadata(arch, out ModuleHandle handle, out TargetSpan expected); + + TargetSpan result = contract.GetMetadataAddress(handle, MetadataAddressKind.ReadWriteSavedCopy); + + Assert.Equal(expected, result); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetMetadata_RequireReadWriteMetadata_RejectsSavedCopy(MockTarget.Architecture arch) + { + IEcmaMetadata contract = CreateContractWithSavedMetadata(arch, out ModuleHandle handle, out _); + + Assert.Throws(() => contract.GetMetadata(handle, requireReadWriteMetadata: true)); + } + + private static IEcmaMetadata CreateContractWithSavedMetadata( + MockTarget.Architecture arch, + out ModuleHandle handle, + out TargetSpan metadata) + { + TargetTestHelpers helpers = new(arch); + var targetBuilder = new TestPlaceholderTarget.Builder(arch); + MockMemorySpace.BumpAllocator allocator = targetBuilder.MemoryBuilder.CreateAllocator(0x1000, 0x3000); + TargetTestHelpers.LayoutResult moduleLayout = helpers.LayoutFields([ + new(nameof(Data.Module.DynamicMetadata), DataType.pointer), + new(nameof(Data.Module.MetadataGeneration), DataType.uint32), + ]); + TargetTestHelpers.LayoutResult dynamicMetadataLayout = helpers.LayoutFields([ + new(nameof(Data.DynamicMetadata.Size), DataType.uint32), + new(nameof(Data.DynamicMetadata.Data), DataType.uint8), + ]); + MockMemorySpace.HeapFragment module = allocator.Allocate(moduleLayout.Stride, "Module"); + MockMemorySpace.HeapFragment dynamicMetadata = + allocator.Allocate(dynamicMetadataLayout.Stride + MetadataSize, "DynamicMetadata"); + + helpers.WritePointer( + module.Data.AsSpan().Slice(moduleLayout.Fields[nameof(Data.Module.DynamicMetadata)].Offset, helpers.PointerSize), + dynamicMetadata.Address); + helpers.Write( + dynamicMetadata.Data.AsSpan().Slice( + dynamicMetadataLayout.Fields[nameof(Data.DynamicMetadata.Size)].Offset, + sizeof(uint)), + MetadataSize); + + var types = new Dictionary + { + [DataType.Module] = new() { Fields = moduleLayout.Fields, Size = moduleLayout.Stride }, + [DataType.DynamicMetadata] = new() { Fields = dynamicMetadataLayout.Fields, Size = dynamicMetadataLayout.Stride }, + }; + TestPlaceholderTarget target = targetBuilder + .AddTypes(types) + .AddContract("c1") + .Build(); + + handle = new ModuleHandle(new TargetPointer(module.Address)); + metadata = new TargetSpan( + new TargetPointer(dynamicMetadata.Address + (uint)dynamicMetadataLayout.Fields[nameof(Data.DynamicMetadata.Data)].Offset), + MetadataSize); + return target.Contracts.EcmaMetadata; + } +} diff --git a/src/native/managed/cdac/tests/UnitTests/IXCLRDataProcessTests.cs b/src/native/managed/cdac/tests/UnitTests/IXCLRDataProcessTests.cs index 03f203e6a1743b..7c30f870778548 100644 --- a/src/native/managed/cdac/tests/UnitTests/IXCLRDataProcessTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/IXCLRDataProcessTests.cs @@ -243,7 +243,7 @@ public void MethodDefinitionsByAddress(MockTarget.Architecture arch) loader.Setup(l => l.GetILHeader(module, SecondToken)).Returns(new TargetPointer(SecondHeaderAddress)); Mock ecmaMetadata = new(MockBehavior.Strict); - ecmaMetadata.Setup(e => e.GetMetadata(module)).Returns(reader); + ecmaMetadata.Setup(e => e.GetMetadata(module, false)).Returns(reader); byte[] secondHeader = new byte[14]; secondHeader[0] = FatFormat; diff --git a/src/native/managed/cdac/tests/UnitTests/TypeHandleTests.cs b/src/native/managed/cdac/tests/UnitTests/TypeHandleTests.cs index 3ed4037d007bbb..5019d8485e634a 100644 --- a/src/native/managed/cdac/tests/UnitTests/TypeHandleTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/TypeHandleTests.cs @@ -335,7 +335,7 @@ private static TestPlaceholderTarget CreateTarget( Mock loader = new(); loader.Setup(l => l.GetModuleHandleFromModulePtr(new TargetPointer(ModuleAddress))).Returns(module); Mock ecmaMetadata = new(); - ecmaMetadata.Setup(e => e.GetMetadata(module)).Returns(reader); + ecmaMetadata.Setup(e => e.GetMetadata(module, false)).Returns(reader); builder.AddMockContract(loader); builder.AddMockContract(ecmaMetadata); }