diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs index d0ad8db342b0d..31131c4475422 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs @@ -80,6 +80,7 @@ internal interface IRuntimeTypeSystem : IContract #region TypeHandle inspection APIs public virtual TypeHandle GetTypeHandle(TargetPointer address) => throw new NotImplementedException(); public virtual TargetPointer GetModule(TypeHandle typeHandle) => throw new NotImplementedException(); + // A canonical method table is either the MethodTable itself, or in the case of a generic instantiation, it is the // MethodTable of the prototypical instance. public virtual TargetPointer GetCanonicalMethodTable(TypeHandle typeHandle) => throw new NotImplementedException(); @@ -130,7 +131,7 @@ internal interface IRuntimeTypeSystem : IContract public virtual bool IsGenericMethodDefinition(MethodDescHandle methodDesc) => throw new NotImplementedException(); public virtual ReadOnlySpan GetGenericMethodInstantiation(MethodDescHandle methodDesc) => throw new NotImplementedException(); - // Return mdTokenNil (0x06000000) if the method doesn't have a token, otherwise return the token of the method + // Return mdtMethodDef (0x06000000) if the method doesn't have a token, otherwise return the token of the method public virtual uint GetMethodToken(MethodDescHandle methodDesc) => throw new NotImplementedException(); // Return true if a MethodDesc represents an array method diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersions_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersions_1.cs index a0d06e79349c3..e6b1755004c71 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersions_1.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersions_1.cs @@ -12,7 +12,6 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; { private readonly Target _target; - public CodeVersions_1(Target target) { _target = target; @@ -23,9 +22,7 @@ ILCodeVersionHandle ICodeVersions.GetActiveILCodeVersion(TargetPointer methodDes // CodeVersionManager::GetActiveILCodeVersion GetModuleAndMethodDesc(methodDesc, out TargetPointer module, out uint methodDefToken); - ModuleHandle moduleHandle = _target.Contracts.Loader.GetModuleHandle(module); - TargetPointer ilCodeVersionTable = _target.Contracts.Loader.GetLookupTables(moduleHandle).MethodDefToILCodeVersioningState; - TargetPointer ilVersionStateAddress = _target.Contracts.Loader.GetModuleLookupMapElement(ilCodeVersionTable, methodDefToken, out var _); + TargetPointer ilVersionStateAddress = GetILVersionStateAddress(module, methodDefToken); if (ilVersionStateAddress == TargetPointer.Null) { return ILCodeVersionHandle.CreateSynthetic(module, methodDefToken); @@ -73,14 +70,11 @@ IEnumerable ICodeVersions.GetILCodeVersions(TargetPointer m // CodeVersionManager::GetILCodeVersions GetModuleAndMethodDesc(methodDesc, out TargetPointer module, out uint methodDefToken); - ModuleHandle moduleHandle = _target.Contracts.Loader.GetModuleHandle(module); - TargetPointer ilCodeVersionTable = _target.Contracts.Loader.GetLookupTables(moduleHandle).MethodDefToILCodeVersioningState; - TargetPointer ilVersionStateAddress = _target.Contracts.Loader.GetModuleLookupMapElement(ilCodeVersionTable, methodDefToken, out var _); - // always add the synthetic version yield return ILCodeVersionHandle.CreateSynthetic(module, methodDefToken); // if explicit versions exist, iterate linked list and return them + TargetPointer ilVersionStateAddress = GetILVersionStateAddress(module, methodDefToken); if (ilVersionStateAddress != TargetPointer.Null) { Data.ILCodeVersioningState ilState = _target.ProcessedData.GetOrAdd(ilVersionStateAddress); @@ -187,7 +181,6 @@ NativeCodeVersionHandle ICodeVersions.GetActiveNativeCodeVersionForILCodeVersion return (ilVersionId == codeVersion.ILVersionId) && ((NativeCodeVersionNodeFlags)codeVersion.Flags).HasFlag(NativeCodeVersionNodeFlags.IsActiveChild); }); - } [Flags] @@ -301,6 +294,18 @@ private void GetModuleAndMethodDesc(TargetPointer methodDesc, out TargetPointer methodDefToken = rts.GetMethodToken(md); } + private TargetPointer GetILVersionStateAddress(TargetPointer module, uint methodDefToken) + { + // No token - for example, special runtime methods like array methods + if (methodDefToken == (uint)EcmaMetadataUtils.TokenType.mdtMethodDef) + return TargetPointer.Null; + + ModuleHandle moduleHandle = _target.Contracts.Loader.GetModuleHandle(module); + TargetPointer ilCodeVersionTable = _target.Contracts.Loader.GetLookupTables(moduleHandle).MethodDefToILCodeVersioningState; + TargetPointer ilVersionStateAddress = _target.Contracts.Loader.GetModuleLookupMapElement(ilCodeVersionTable, methodDefToken, out var _); + return ilVersionStateAddress; + } + private ILCodeVersionNode AsNode(ILCodeVersionHandle handle) { if (handle.ILCodeVersionNode == TargetPointer.Null) diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index 65f161804948e..423a2ef2ed9d0 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -23,7 +23,6 @@ internal partial struct RuntimeTypeSystem_1 : IRuntimeTypeSystem private readonly Dictionary _methodTables = new(); private readonly Dictionary _methodDescs = new(); - internal struct MethodTable { internal MethodTableFlags_1 Flags { get; } @@ -126,8 +125,7 @@ private static uint ComputeToken(Target target, Data.MethodDesc desc, Data.Metho uint tokenRemainder = (uint)(desc.Flags3AndTokenRemainder & tokenRemainderMask); uint tokenRange = ((uint)(chunk.FlagsAndTokenRange & tokenRangeMask)) << tokenRemainderBitCount; - - return 0x06000000 | tokenRange | tokenRemainder; + return EcmaMetadataUtils.CreateMethodDef(tokenRange | tokenRemainder); } public MethodClassification Classification => (MethodClassification)((int)_desc.Flags & (int)MethodDescFlags_1.MethodDescFlags.ClassificationMask); diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/EcmaMetadataUtils.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/EcmaMetadataUtils.cs index e0fbee7181158..f542da0e956ec 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/EcmaMetadataUtils.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/EcmaMetadataUtils.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; + namespace Microsoft.Diagnostics.DataContractReader; internal static class EcmaMetadataUtils @@ -12,4 +14,16 @@ internal static class EcmaMetadataUtils internal static uint MakeToken(uint rid, uint table) => rid | (table << RowIdBitCount); + // ECMA-335 II.22 + // Metadata table index is the most significant byte of the 4-byte token + public enum TokenType : uint + { + mdtMethodDef = 0x06 << 24 + } + + public static uint CreateMethodDef(uint tokenParts) + { + Debug.Assert((tokenParts & 0xff000000) == 0, $"Token type should not be set in {nameof(tokenParts)}"); + return (uint)TokenType.mdtMethodDef | tokenParts; + } } diff --git a/src/native/managed/cdacreader/tests/CodeVersionsTests.cs b/src/native/managed/cdacreader/tests/CodeVersionsTests.cs index 26e8859352787..bdd6cacdd3a10 100644 --- a/src/native/managed/cdacreader/tests/CodeVersionsTests.cs +++ b/src/native/managed/cdacreader/tests/CodeVersionsTests.cs @@ -33,7 +33,7 @@ internal class MockMethodDesc public bool IsVersionable { get; private set; } public uint RowId { get; set; } - public uint MethodToken => 0x06000000 | RowId; + public uint MethodToken => EcmaMetadataUtils.CreateMethodDef(RowId); // n.b. in the real RuntimeTypeSystem_1 this is more complex public TargetCodePointer NativeCode { get; private set; } @@ -342,9 +342,10 @@ public void GetActiveNativeCodeVersion_DefaultCase(MockTarget.Architecture arch) { uint methodRowId = 0x25; // arbitrary TargetCodePointer expectedNativeCodePointer = new TargetCodePointer(0x0700_abc0); - uint methodDefToken = 0x06000000 | methodRowId; + uint methodDefToken = EcmaMetadataUtils.CreateMethodDef(methodRowId); var builder = new MockCodeVersions(arch); var methodDescAddress = new TargetPointer(0x00aa_aa00); + var methodDescNilTokenAddress = new TargetPointer(0x00aa_bb00); var moduleAddress = new TargetPointer(0x00ca_ca00); TargetPointer versioningState = builder.AddILCodeVersioningState( @@ -353,34 +354,45 @@ public void GetActiveNativeCodeVersion_DefaultCase(MockTarget.Architecture arch) activeVersionModule: moduleAddress, activeVersionMethodDef: methodDefToken, firstVersionNode: TargetPointer.Null); - var oneModule = new MockModule() { + var module = new MockModule() { Address = moduleAddress, MethodDefToILCodeVersioningStateAddress = new TargetPointer(0x00da_da00), MethodDefToILCodeVersioningStateTable = new Dictionary() { { methodRowId, versioningState} }, }; - var oneMethodTable = new MockMethodTable() { + var methodTable = new MockMethodTable() { Address = new TargetPointer(0x00ba_ba00), - Module = oneModule, + Module = module, }; - var oneMethod = MockMethodDesc.CreateVersionable(selfAddress: methodDescAddress, methodDescVersioningState: TargetPointer.Null, nativeCode: expectedNativeCodePointer); - oneMethod.MethodTable = oneMethodTable; - oneMethod.RowId = methodRowId; + var method = MockMethodDesc.CreateVersionable(selfAddress: methodDescAddress, methodDescVersioningState: TargetPointer.Null, nativeCode: expectedNativeCodePointer); + method.MethodTable = methodTable; + method.RowId = methodRowId; + + var methodNilToken = MockMethodDesc.CreateVersionable(selfAddress: methodDescNilTokenAddress, methodDescVersioningState: TargetPointer.Null, nativeCode: expectedNativeCodePointer); + methodNilToken.MethodTable = methodTable; - var target = CreateTarget(arch, [oneMethod], [oneMethodTable], [], [oneModule], builder); + var target = CreateTarget(arch, [method, methodNilToken], [methodTable], [], [module], builder); // TEST var codeVersions = target.Contracts.CodeVersions; - Assert.NotNull(codeVersions); - NativeCodeVersionHandle handle = codeVersions.GetActiveNativeCodeVersion(methodDescAddress); - Assert.True(handle.Valid); - Assert.Equal(methodDescAddress, handle.MethodDescAddress); - var actualCodeAddress = codeVersions.GetNativeCode(handle); - Assert.Equal(expectedNativeCodePointer, actualCodeAddress); + { + NativeCodeVersionHandle handle = codeVersions.GetActiveNativeCodeVersion(methodDescAddress); + Assert.True(handle.Valid); + Assert.Equal(methodDescAddress, handle.MethodDescAddress); + var actualCodeAddress = codeVersions.GetNativeCode(handle); + Assert.Equal(expectedNativeCodePointer, actualCodeAddress); + } + { + NativeCodeVersionHandle handle = codeVersions.GetActiveNativeCodeVersion(methodDescNilTokenAddress); + Assert.True(handle.Valid); + Assert.Equal(methodDescNilTokenAddress, handle.MethodDescAddress); + var actualCodeAddress = codeVersions.GetNativeCode(handle); + Assert.Equal(expectedNativeCodePointer, actualCodeAddress); + } } [Theory]