diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataMethodDefinition.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataMethodDefinition.cs index 737dbeb2c36290..30318aeb4dd775 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataMethodDefinition.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataMethodDefinition.cs @@ -2,7 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using System.Runtime.InteropServices; @@ -15,6 +17,17 @@ namespace Microsoft.Diagnostics.DataContractReader.Legacy; [GeneratedComClass] public sealed unsafe partial class ClrDataMethodDefinition : IXCLRDataMethodDefinition { + private sealed class EnumMethodDefinitionExtents : IEnum + { + public IEnumerator Enumerator { get; } + public nuint LegacyHandle { get; set; } + + public EnumMethodDefinitionExtents(ClrDataMethodDefinitionExtent extent) + { + Enumerator = Enumerable.Repeat(extent, 1).GetEnumerator(); + } + } + private readonly Target _target; private readonly TargetPointer _module; private readonly uint _token; @@ -41,6 +54,22 @@ private TargetPointer TryResolveMethodDesc() return methodDescAddr; } + private TargetPointer GetILExtentStart(out uint codeSize) + { + ILoader loader = _target.Contracts.Loader; + Contracts.ModuleHandle moduleHandle = loader.GetModuleHandleFromModulePtr(_module); + TargetPointer ilHeader = loader.GetILHeader(moduleHandle, _token); + if (ilHeader == TargetPointer.Null) + { + codeSize = 0; + return TargetPointer.Null; + } + + int headerSize = HeaderReaderHelpers.GetHeaderSize(_target, ilHeader); + codeSize = (uint)HeaderReaderHelpers.GetCodeSize(_target, ilHeader); + return headerSize == sizeof(byte) ? ilHeader : ilHeader + (uint)headerSize; + } + private static bool HasClassInstantiation(Target target, MethodDescHandle md) { IRuntimeTypeSystem rts = target.Contracts.RuntimeTypeSystem; @@ -384,13 +413,155 @@ int IXCLRDataMethodDefinition.GetLatestEnCVersion(uint* version) => HResults.E_NOTIMPL; int IXCLRDataMethodDefinition.StartEnumExtents(ulong* handle) - => LegacyFallbackHelper.CanFallback() && _legacyImpl is not null ? _legacyImpl.StartEnumExtents(handle) : HResults.E_NOTIMPL; + { + int hr = HResults.S_OK; + try + { + if (handle is null) + throw new ArgumentNullException(nameof(handle)); + + *handle = 0; + TargetPointer code = GetILExtentStart(out uint codeSize); + if (code == TargetPointer.Null) + { + hr = HResults.S_FALSE; + } + else + { + ClrDataAddress startAddress = code.ToClrDataAddress(_target); + ClrDataMethodDefinitionExtent extent = new() + { + startAddress = startAddress, + endAddress = startAddress + codeSize - 1, + enCVersion = 0, + type = 0, + }; + EnumMethodDefinitionExtents extents = new(extent); + *handle = (ulong)((IEnum)extents).GetHandle(); + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (LegacyFallbackHelper.CanFallback() && _legacyImpl is not null) + { + ulong legacyHandle = 0; + int hrLocal = _legacyImpl.StartEnumExtents(handle is null ? null : &legacyHandle); + Debug.ValidateHResult(hr, hrLocal); + + if (hr == HResults.S_OK && hrLocal == HResults.S_OK) + { + GCHandle gcHandle = GCHandle.FromIntPtr((IntPtr)(*handle)); + ((EnumMethodDefinitionExtents)gcHandle.Target!).LegacyHandle = (nuint)legacyHandle; + } + else if (hrLocal == HResults.S_OK) + { + _legacyImpl.EndEnumExtents(legacyHandle); + } + } +#endif + + return hr; + } int IXCLRDataMethodDefinition.EnumExtent(ulong* handle, ClrDataMethodDefinitionExtent* extent) - => LegacyFallbackHelper.CanFallback() && _legacyImpl is not null ? _legacyImpl.EnumExtent(handle, extent) : HResults.E_NOTIMPL; + { + int hr = HResults.S_OK; + EnumMethodDefinitionExtents? extents = null; + bool completed = false; + try + { + if (handle is null) + throw new ArgumentNullException(nameof(handle)); + if (extent is null) + throw new ArgumentNullException(nameof(extent)); + if (*handle == 0) + throw new ArgumentException("Extent enumeration has not been started or has already ended.", nameof(handle)); + + GCHandle gcHandle = GCHandle.FromIntPtr((IntPtr)(*handle)); + if (gcHandle.Target is not EnumMethodDefinitionExtents methodExtents) + throw new ArgumentException("Handle does not reference a method definition extent enumeration.", nameof(handle)); + + extents = methodExtents; + if (extents.Enumerator.MoveNext()) + { + *extent = extents.Enumerator.Current; + completed = true; + } + else + { + hr = HResults.S_FALSE; + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (LegacyFallbackHelper.CanFallback() && _legacyImpl is not null && extents is { LegacyHandle: not 0 }) + { + ulong legacyHandle = (ulong)extents.LegacyHandle; + ClrDataMethodDefinitionExtent extentLocal = default; + int hrLocal = _legacyImpl.EnumExtent(&legacyHandle, &extentLocal); + extents.LegacyHandle = (nuint)legacyHandle; + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + { + Debug.Assert(extent->startAddress == extentLocal.startAddress, $"StartAddress - cDAC: {extent->startAddress:x}, DAC: {extentLocal.startAddress:x}"); + Debug.Assert(extent->endAddress == extentLocal.endAddress, $"EndAddress - cDAC: {extent->endAddress:x}, DAC: {extentLocal.endAddress:x}"); + Debug.Assert(extent->enCVersion == extentLocal.enCVersion, $"EnCVersion - cDAC: {extent->enCVersion:x}, DAC: {extentLocal.enCVersion:x}"); + Debug.Assert(extent->type == extentLocal.type, $"Type - cDAC: {extent->type:x}, DAC: {extentLocal.type:x}"); + } + } +#endif + + if (completed && extents is not null) + { + ((IEnum)extents).Dispose(); + GCHandle gcHandle = GCHandle.FromIntPtr((IntPtr)(*handle)); + gcHandle.Free(); + *handle = 0; + } + + return hr; + } int IXCLRDataMethodDefinition.EndEnumExtents(ulong handle) - => LegacyFallbackHelper.CanFallback() && _legacyImpl is not null ? _legacyImpl.EndEnumExtents(handle) : HResults.E_NOTIMPL; + { + int hr = HResults.S_OK; + nuint legacyHandle = 0; + try + { + if (handle != 0) + { + GCHandle gcHandle = GCHandle.FromIntPtr((IntPtr)handle); + if (gcHandle.Target is not EnumMethodDefinitionExtents extents) + throw new ArgumentException("Handle does not reference a method definition extent enumeration.", nameof(handle)); + + legacyHandle = extents.LegacyHandle; + ((IEnum)extents).Dispose(); + gcHandle.Free(); + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (LegacyFallbackHelper.CanFallback() && _legacyImpl is not null && legacyHandle != 0) + { + int hrLocal = _legacyImpl.EndEnumExtents((ulong)legacyHandle); + Debug.ValidateHResult(hr, hrLocal); + } +#endif + + return hr; + } int IXCLRDataMethodDefinition.GetCodeNotification(uint* flags) { @@ -484,7 +655,40 @@ int IXCLRDataMethodDefinition.Request(uint reqCode, uint inBufferSize, byte* inB } int IXCLRDataMethodDefinition.GetRepresentativeEntryAddress(ClrDataAddress* addr) - => LegacyFallbackHelper.CanFallback() && _legacyImpl is not null ? _legacyImpl.GetRepresentativeEntryAddress(addr) : HResults.E_NOTIMPL; + { + int hr = HResults.S_OK; + + try + { + if (addr is null) + throw new ArgumentNullException(nameof(addr)); + + TargetPointer code = GetILExtentStart(out _); + if (code == TargetPointer.Null) + throw new COMException("Method definition does not have an IL header.", unchecked((int)0x8000FFFF)); + + *addr = code.ToClrDataAddress(_target); + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (LegacyFallbackHelper.CanFallback() && _legacyImpl is not null) + { + ClrDataAddress addrLocal = 0; + int hrLocal = _legacyImpl.GetRepresentativeEntryAddress(addr is null ? null : &addrLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + { + Debug.Assert(*addr == addrLocal, $"cDAC: {*addr:x}, DAC: {addrLocal:x}"); + } + } +#endif + + return hr; + } int IXCLRDataMethodDefinition.HasClassOrMethodInstantiation(int* bGeneric) { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/HeaderReaderHelpers.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/HeaderReaderHelpers.cs index 950eef6f46577b..2130c8f2c41e2f 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/HeaderReaderHelpers.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/HeaderReaderHelpers.cs @@ -29,7 +29,7 @@ public static int GetHeaderSize(Target target, TargetPointer ilHeader) if (IsTiny(firstByte)) return 1; if (IsFat(firstByte)) - return 12; + return (target.Read(ilHeader + 1) >> 4) * sizeof(uint); throw new BadImageFormatException("Invalid IL method header."); } @@ -40,7 +40,7 @@ public static int GetCodeSize(Target target, TargetPointer ilHeader) if (IsTiny(firstByte)) return firstByte >> 2; if (IsFat(firstByte)) - return (int)target.Read(ilHeader + 4); + return (int)target.ReadLittleEndian(ilHeader + 4); throw new BadImageFormatException("Invalid IL method header."); } @@ -52,7 +52,7 @@ public static bool TryGetLocalVarSigToken(Target target, TargetPointer ilHeader, if (!IsFat(firstByte)) return false; - localVarSigToken = target.Read(ilHeader + 8); + localVarSigToken = target.ReadLittleEndian(ilHeader + 8); return true; } } diff --git a/src/native/managed/cdac/tests/UnitTests/IXCLRDataProcessTests.cs b/src/native/managed/cdac/tests/UnitTests/IXCLRDataProcessTests.cs index c5e702d8809dc1..674c2008152bb9 100644 --- a/src/native/managed/cdac/tests/UnitTests/IXCLRDataProcessTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/IXCLRDataProcessTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Buffers.Binary; using System.Collections.Generic; using System.Reflection; using System.Reflection.Metadata; @@ -214,9 +215,10 @@ public void MethodDefinitionsByAddress(MockTarget.Architecture arch) const uint SecondToken = 0x06000003; const byte TinyFormat = 0x2; const byte FatFormat = 0x3; - const byte FatHeaderDwords = 3; + const byte FatHeaderDwords = 4; const int TinyHeaderSize = sizeof(byte); const int TinyCodeSize = 3; + const int FatHeaderSize = FatHeaderDwords * sizeof(uint); const int FatCodeSize = 2; const int FatCodeSizeOffset = 4; @@ -231,6 +233,7 @@ public void MethodDefinitionsByAddress(MockTarget.Architecture arch) Mock loader = new(MockBehavior.Strict); loader.Setup(l => l.GetAppDomain()).Returns(new TargetPointer(AppDomainAddress)); + loader.Setup(l => l.GetModuleHandleFromModulePtr(new TargetPointer(ModuleAddress))).Returns(module); loader.Setup(l => l.GetModuleHandles( new TargetPointer(AppDomainAddress), AssemblyIterationFlags.IncludeLoaded | AssemblyIterationFlags.IncludeExecution)).Returns([module]); @@ -238,15 +241,16 @@ public void MethodDefinitionsByAddress(MockTarget.Architecture arch) loader.Setup(l => l.GetPEAssembly(module)).Returns(new TargetPointer(PeAssemblyAddress)); loader.Setup(l => l.GetILAddr(new TargetPointer(PeAssemblyAddress), 0x10)).Returns(new TargetPointer(FirstHeaderAddress)); loader.Setup(l => l.GetILAddr(new TargetPointer(PeAssemblyAddress), 0x20)).Returns(new TargetPointer(SecondHeaderAddress)); + loader.Setup(l => l.GetILHeader(module, FirstToken)).Returns(new TargetPointer(FirstHeaderAddress)); + loader.Setup(l => l.GetILHeader(module, SecondToken)).Returns(new TargetPointer(SecondHeaderAddress)); Mock ecmaMetadata = new(MockBehavior.Strict); ecmaMetadata.Setup(e => e.GetMetadata(module)).Returns(reader); - byte[] secondHeader = new byte[14]; + byte[] secondHeader = new byte[FatHeaderSize + FatCodeSize]; secondHeader[0] = FatFormat; secondHeader[1] = FatHeaderDwords << 4; - int codeSizeByteOffset = FatCodeSizeOffset + (arch.IsLittleEndian ? 0 : sizeof(uint) - 1); - secondHeader[codeSizeByteOffset] = FatCodeSize; + BinaryPrimitives.WriteUInt32LittleEndian(secondHeader.AsSpan(FatCodeSizeOffset), FatCodeSize); MockMemorySpace.HeapFragment[] memory = [ new() @@ -292,6 +296,10 @@ public void MethodDefinitionsByAddress(MockTarget.Architecture arch) DacComNullableByRef nullModule = new(isNullRef: true); Assert.Equal(HResults.S_OK, method.GetTokenAndScope(&token, nullModule)); Assert.Equal(FirstToken, token); + AssertMethodDefinitionExtent( + method, + FirstHeaderAddress, + FirstHeaderAddress + TinyCodeSize - 1); DacComNullableByRef endOut = new(isNullRef: false); Assert.Equal(HResults.S_FALSE, process.EnumMethodDefinitionByAddress(&handle, endOut)); @@ -301,7 +309,7 @@ public void MethodDefinitionsByAddress(MockTarget.Architecture arch) Assert.Equal(HResults.S_OK, process.EndEnumMethodDefinitionsByAddress(handle)); } - hr = process.StartEnumMethodDefinitionsByAddress(SecondHeaderAddress + 12, &handle); + hr = process.StartEnumMethodDefinitionsByAddress(SecondHeaderAddress + FatHeaderSize, &handle); Assert.Equal(HResults.S_OK, hr); try { @@ -314,6 +322,10 @@ public void MethodDefinitionsByAddress(MockTarget.Architecture arch) DacComNullableByRef nullModule = new(isNullRef: true); Assert.Equal(HResults.S_OK, method.GetTokenAndScope(&token, nullModule)); Assert.Equal(SecondToken, token); + AssertMethodDefinitionExtent( + method, + SecondHeaderAddress + FatHeaderSize, + SecondHeaderAddress + FatHeaderSize + FatCodeSize - 1); } finally { @@ -339,6 +351,72 @@ public void MethodDefinitionsByAddress(MockTarget.Architecture arch) } } + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void MethodDefinitionWithoutIL(MockTarget.Architecture arch) + { + const ulong ModuleAddress = 0x2000; + const uint Token = 0x06000001; + const int EUnexpected = unchecked((int)0x8000FFFF); + ModuleHandle module = new(new TargetPointer(ModuleAddress)); + Mock loader = new(MockBehavior.Strict); + loader.Setup(l => l.GetModuleHandleFromModulePtr(new TargetPointer(ModuleAddress))).Returns(module); + loader.Setup(l => l.GetILHeader(module, Token)).Returns(TargetPointer.Null); + TestPlaceholderTarget.Builder builder = new(arch); + builder.AddMockContract(loader.Object); + IXCLRDataMethodDefinition method = new ClrDataMethodDefinition( + builder.Build(), + new TargetPointer(ModuleAddress), + Token, + legacyImpl: null); + + ulong handle; + Assert.Equal(HResults.S_FALSE, method.StartEnumExtents(&handle)); + Assert.Equal(0ul, handle); + + ClrDataAddress address; + Assert.Equal(EUnexpected, method.GetRepresentativeEntryAddress(&address)); + } + + private static void AssertMethodDefinitionExtent( + IXCLRDataMethodDefinition method, + ClrDataAddress expectedStart, + ClrDataAddress expectedEnd) + { + Assert.Equal(HResults.E_POINTER, method.StartEnumExtents(null)); + Assert.Equal(HResults.E_POINTER, method.GetRepresentativeEntryAddress(null)); + + ulong invalidHandle = 0; + ClrDataMethodDefinitionExtent extent; + Assert.Equal(HResults.E_INVALIDARG, method.EnumExtent(&invalidHandle, &extent)); + + ulong handle; + Assert.Equal(HResults.S_OK, method.StartEnumExtents(&handle)); + Assert.NotEqual(0ul, handle); + try + { + Assert.Equal(HResults.E_POINTER, method.EnumExtent(null, &extent)); + Assert.Equal(HResults.E_POINTER, method.EnumExtent(&handle, null)); + Assert.Equal(HResults.S_OK, method.EnumExtent(&handle, &extent)); + Assert.Equal(expectedStart, extent.startAddress); + Assert.Equal(expectedEnd, extent.endAddress); + Assert.Equal(0u, extent.enCVersion); + Assert.Equal(0u, extent.type); + Assert.Equal(0ul, handle); + Assert.Equal(HResults.E_INVALIDARG, method.EnumExtent(&handle, &extent)); + } + finally + { + Assert.Equal(HResults.S_OK, method.EndEnumExtents(handle)); + } + + Assert.Equal(HResults.S_OK, method.EndEnumExtents(0)); + + ClrDataAddress address; + Assert.Equal(HResults.S_OK, method.GetRepresentativeEntryAddress(&address)); + Assert.Equal(expectedStart, address); + } + private static byte[] BuildMethodDefinitionMetadata() { MetadataBuilder builder = new();