From 016d356e43659b7789d23defe570628c8864ee73 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Mon, 9 Dec 2024 10:56:41 -0800 Subject: [PATCH] [cdac] Handle no method def token when trying to get the IL version state (#110449) Some methods have a nil token - for example, special runtime methods like array functions. When we tried to look up their IL version state, we were throwing an exception. Methods like this will have no versioning state, so check for a nil token and skip the lookup. --- .../Contracts/IRuntimeTypeSystem.cs | 3 +- .../Contracts/CodeVersions_1.cs | 23 ++++++---- .../Contracts/RuntimeTypeSystem_1.cs | 4 +- .../EcmaMetadataUtils.cs | 14 +++++++ .../cdacreader/tests/CodeVersionsTests.cs | 42 ++++++++++++------- 5 files changed, 58 insertions(+), 28 deletions(-) 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]