Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[cdac] Handle non-IL method descs in RuntimeTypeSystem_1.GetMethodClassificationDataType #110602

Merged
merged 5 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/coreclr/debug/runtimeinfo/datadescriptor.h
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,28 @@ CDAC_TYPE_SIZE(sizeof(DynamicMethodDesc))
CDAC_TYPE_FIELD(DynamicMethodDesc, /*pointer*/, MethodName, cdac_data<DynamicMethodDesc>::MethodName)
CDAC_TYPE_END(DynamicMethodDesc)

CDAC_TYPE_BEGIN(ArrayMethodDesc)
CDAC_TYPE_SIZE(sizeof(ArrayMethodDesc))
CDAC_TYPE_END(ArrayMethodDesc)

CDAC_TYPE_BEGIN(FCallMethodDesc)
CDAC_TYPE_SIZE(sizeof(FCallMethodDesc))
CDAC_TYPE_END(FCallMethodDesc)

CDAC_TYPE_BEGIN(PInvokeMethodDesc)
CDAC_TYPE_SIZE(sizeof(NDirectMethodDesc))
CDAC_TYPE_END(PInvokeMethodDesc)

CDAC_TYPE_BEGIN(EEImplMethodDesc)
CDAC_TYPE_SIZE(sizeof(EEImplMethodDesc))
CDAC_TYPE_END(EEImplMethodDesc)

#ifdef FEATURE_COMINTEROP
CDAC_TYPE_BEGIN(CLRToCOMCallMethodDesc)
CDAC_TYPE_SIZE(sizeof(CLRToCOMCallMethodDesc))
CDAC_TYPE_END(CLRToCOMCallMethodDesc)
#endif // FEATURE_COMINTEROP

CDAC_TYPE_BEGIN(CodePointer)
CDAC_TYPE_SIZE(sizeof(PCODE))
CDAC_TYPE_END(CodePointer)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ public enum DataType
InstantiatedMethodDesc,
DynamicMethodDesc,
StoredSigMethodDesc,
ArrayMethodDesc,
FCallMethodDesc,
PInvokeMethodDesc,
EEImplMethodDesc,
CLRToCOMCallMethodDesc,
RangeSectionMap,
RangeSectionFragment,
RangeSection,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Microsoft.Diagnostics.DataContractReader.RuntimeTypeSystemHelpers;

// See MethodClassification in src/coreclr/vm/method.hpp
internal enum MethodClassification
{
IL = 0, // IL
Expand All @@ -12,7 +13,7 @@ internal enum MethodClassification
EEImpl = 3, // special method; implementation provided by EE (like Delegate Invoke)
Array = 4, // Array ECall
Instantiated = 5, // Instantiated generic methods, including descriptors
// for both shared and unshared code (see InstantiatedMethodDesc)
// for both shared and unshared code (see InstantiatedMethodDesc)
ComInterop = 6,
Dynamic = 7, // for method desc with no metadata behind
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,12 @@ private static uint StartOffset(MethodClassification classification, Target targ
DataType type = classification switch
{
MethodClassification.IL => DataType.MethodDesc,
MethodClassification.FCall => throw new NotImplementedException(), //TODO[cdac]:
MethodClassification.PInvoke => throw new NotImplementedException(), //TODO[cdac]:
MethodClassification.EEImpl => throw new NotImplementedException(), //TODO[cdac]:
MethodClassification.Array => throw new NotImplementedException(), //TODO[cdac]:
MethodClassification.FCall => DataType.FCallMethodDesc,
MethodClassification.PInvoke => DataType.PInvokeMethodDesc,
MethodClassification.EEImpl => DataType.EEImplMethodDesc,
MethodClassification.Array => DataType.ArrayMethodDesc,
MethodClassification.Instantiated => DataType.InstantiatedMethodDesc,
MethodClassification.ComInterop => throw new NotImplementedException(), //TODO[cdac]:
MethodClassification.ComInterop => DataType.CLRToCOMCallMethodDesc,
MethodClassification.Dynamic => DataType.DynamicMethodDesc,
_ => throw new InvalidOperationException($"Unexpected method classification 0x{classification:x2} for MethodDesc")
};
Expand Down
255 changes: 246 additions & 9 deletions src/native/managed/cdacreader/tests/MethodDescTests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// 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 System.Linq;
using Microsoft.Diagnostics.DataContractReader.Contracts;
using Microsoft.Diagnostics.DataContractReader.RuntimeTypeSystemHelpers;
using Moq;
Expand All @@ -11,20 +13,23 @@ namespace Microsoft.Diagnostics.DataContractReader.Tests;

public class MethodDescTests
{
private static Target CreateTarget(MockDescriptors.MethodDescriptors methodDescBuilder)
private static Target CreateTarget(MockDescriptors.MethodDescriptors methodDescBuilder, Mock<IExecutionManager> mockExecutionManager = null)
{
MockMemorySpace.Builder builder = methodDescBuilder.Builder;
var target = new TestPlaceholderTarget(builder.TargetTestHelpers.Arch, builder.GetReadContext().ReadFromTarget, methodDescBuilder.Types, methodDescBuilder.Globals);

mockExecutionManager ??= new Mock<IExecutionManager>();
target.SetContracts(Mock.Of<ContractRegistry>(
c => c.RuntimeTypeSystem == ((IContractFactory<IRuntimeTypeSystem>)new RuntimeTypeSystemFactory()).CreateContract(target, 1)
&& c.Loader == ((IContractFactory<ILoader>)new LoaderFactory()).CreateContract(target, 1)
&& c.PlatformMetadata == new Mock<Contracts.IPlatformMetadata>().Object));
&& c.PlatformMetadata == new Mock<IPlatformMetadata>().Object
&& c.ExecutionManager == mockExecutionManager.Object));
return target;
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void MethodDescGetMethodDescTokenOk(MockTarget.Architecture arch)
public void GetMethodDescHandle_ILMethod_GetBasicData(MockTarget.Architecture arch)
{
TargetTestHelpers helpers = new(arch);
MockMemorySpace.Builder builder = new(helpers);
Expand Down Expand Up @@ -69,6 +74,163 @@ public void MethodDescGetMethodDescTokenOk(MockTarget.Architecture arch)
Assert.False(isCollectible);
TargetPointer versioning = rts.GetMethodDescVersioningState(handle);
Assert.Equal(TargetPointer.Null, versioning);

// Method classification - IL method
Assert.False(rts.IsStoredSigMethodDesc(handle, out _));
Assert.False(rts.IsNoMetadataMethod(handle, out _));
Assert.False(rts.IsDynamicMethod(handle));
Assert.False(rts.IsILStub(handle));
Assert.False(rts.IsArrayMethod(handle, out _));
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void IsArrayMethod(MockTarget.Architecture arch)
{
TargetTestHelpers helpers = new(arch);
MockMemorySpace.Builder builder = new(helpers);
MockDescriptors.RuntimeTypeSystem rtsBuilder = new(builder);
MockDescriptors.Loader loaderBuilder = new(builder);
MockDescriptors.MethodDescriptors methodDescBuilder = new(rtsBuilder, loaderBuilder);

ushort numVirtuals = 1;
TargetPointer methodTable = AddMethodTable(rtsBuilder, numVirtuals);

byte count = 5;
uint methodDescSize = methodDescBuilder.Types[DataType.ArrayMethodDesc].Size.Value;
uint methodDescSizeByAlignment = methodDescSize / methodDescBuilder.MethodDescAlignment;
byte chunkSize = (byte)(count * methodDescSizeByAlignment);
TargetPointer chunk = methodDescBuilder.AddMethodDescChunk(methodTable, string.Empty, count, chunkSize, tokenRange: 0);

TargetPointer[] arrayMethods = new TargetPointer[count];
for (byte i = 0; i < count; i++)
{
// Add the array methods by setting the appropriate slot number
// Array vtable is:
// <base class vtables>
// Get
// Set
// Address
// .ctor
// [optionally other constructors]
byte index = (byte)(i * methodDescSizeByAlignment);
ushort slotNum = (ushort)(numVirtuals + i);
ushort flags = (ushort)MethodClassification.Array | (ushort)MethodDescFlags_1.MethodDescFlags.HasNonVtableSlot;
arrayMethods[i] = methodDescBuilder.SetMethodDesc(chunk, index, slotNum, flags, tokenRemainder: 0);
}

Target target = CreateTarget(methodDescBuilder);
IRuntimeTypeSystem rts = target.Contracts.RuntimeTypeSystem;

for (byte i = 0; i < count; i++)
{
MethodDescHandle handle = rts.GetMethodDescHandle(arrayMethods[i]);
Assert.NotEqual(TargetPointer.Null, handle.Address);
Assert.True(rts.IsStoredSigMethodDesc(handle, out _));
Assert.True(rts.IsArrayMethod(handle, out ArrayFunctionType functionType));

ArrayFunctionType expectedFunctionType = i <= (byte)ArrayFunctionType.Constructor
? (ArrayFunctionType)i
: ArrayFunctionType.Constructor;
Assert.Equal(expectedFunctionType, functionType);
}
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void IsDynamicMethod(MockTarget.Architecture arch)
{
TargetTestHelpers helpers = new(arch);
MockMemorySpace.Builder builder = new(helpers);
MockDescriptors.RuntimeTypeSystem rtsBuilder = new(builder);
MockDescriptors.Loader loaderBuilder = new(builder);
MockDescriptors.MethodDescriptors methodDescBuilder = new(rtsBuilder, loaderBuilder);

TargetPointer methodTable = AddMethodTable(rtsBuilder);

byte count = 2;
uint methodDescSize = methodDescBuilder.Types[DataType.DynamicMethodDesc].Size.Value;
uint methodDescSizeByAlignment = methodDescSize / methodDescBuilder.MethodDescAlignment;
byte chunkSize = (byte)(count * methodDescSizeByAlignment);
TargetPointer chunk = methodDescBuilder.AddMethodDescChunk(methodTable, string.Empty, count, chunkSize, tokenRange: 0);

ushort flags = (ushort)MethodClassification.Dynamic;
TargetPointer dynamicMethod = methodDescBuilder.SetMethodDesc(chunk, index: 0, slotNum: 0, flags, tokenRemainder: 0);
methodDescBuilder.SetDynamicMethodDesc(dynamicMethod, (uint)RuntimeTypeSystem_1.DynamicMethodDescExtendedFlags.IsLCGMethod);
TargetPointer ilStubMethod = methodDescBuilder.SetMethodDesc(chunk, index: (byte)methodDescSizeByAlignment, slotNum: 1, flags, tokenRemainder: 0);
methodDescBuilder.SetDynamicMethodDesc(ilStubMethod, (uint)RuntimeTypeSystem_1.DynamicMethodDescExtendedFlags.IsILStub);

Target target = CreateTarget(methodDescBuilder);
IRuntimeTypeSystem rts = target.Contracts.RuntimeTypeSystem;

{
MethodDescHandle handle = rts.GetMethodDescHandle(dynamicMethod);
Assert.NotEqual(TargetPointer.Null, handle.Address);
Assert.True(rts.IsStoredSigMethodDesc(handle, out _));
Assert.True(rts.IsNoMetadataMethod(handle, out _));
Assert.True(rts.IsDynamicMethod(handle));
Assert.False(rts.IsILStub(handle));
}
{
MethodDescHandle handle = rts.GetMethodDescHandle(ilStubMethod);
Assert.NotEqual(TargetPointer.Null, handle.Address);
Assert.True(rts.IsStoredSigMethodDesc(handle, out _));
Assert.True(rts.IsNoMetadataMethod(handle, out _));
Assert.False(rts.IsDynamicMethod(handle));
Assert.True(rts.IsILStub(handle));
}
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void IsGenericMethodDefinition(MockTarget.Architecture arch)
{
TargetTestHelpers helpers = new(arch);
MockMemorySpace.Builder builder = new(helpers);
MockDescriptors.RuntimeTypeSystem rtsBuilder = new(builder);
MockDescriptors.Loader loaderBuilder = new(builder);
MockDescriptors.MethodDescriptors methodDescBuilder = new(rtsBuilder, loaderBuilder);

TargetPointer methodTable = AddMethodTable(rtsBuilder);

byte count = 2;
uint methodDescSize = methodDescBuilder.Types[DataType.InstantiatedMethodDesc].Size.Value;
uint methodDescSizeByAlignment = methodDescSize / methodDescBuilder.MethodDescAlignment;
byte chunkSize = (byte)(count * methodDescSizeByAlignment);
TargetPointer chunk = methodDescBuilder.AddMethodDescChunk(methodTable, string.Empty, count, chunkSize, tokenRange: 0);

ushort flags = (ushort)MethodClassification.Instantiated;
TargetPointer genericMethodDef = methodDescBuilder.SetMethodDesc(chunk, index: 0, slotNum: 0, flags, tokenRemainder: 0);
methodDescBuilder.SetInstantiatedMethodDesc(genericMethodDef, (ushort)RuntimeTypeSystem_1.InstantiatedMethodDescFlags2.GenericMethodDefinition, []);
TargetPointer[] typeArgsRawAddrs = [0x1000, 0x2000, 0x3000];
TargetPointer[] typeArgsHandles = typeArgsRawAddrs.Select(a => GetTypeDescHandlePointer(a)).ToArray();

TargetPointer genericWithInst = methodDescBuilder.SetMethodDesc(chunk, index: (byte)methodDescSizeByAlignment, slotNum: 1, flags, tokenRemainder: 0);
methodDescBuilder.SetInstantiatedMethodDesc(genericWithInst, (ushort)RuntimeTypeSystem_1.InstantiatedMethodDescFlags2.GenericMethodDefinition, typeArgsHandles);

Target target = CreateTarget(methodDescBuilder);
IRuntimeTypeSystem rts = target.Contracts.RuntimeTypeSystem;

{
MethodDescHandle handle = rts.GetMethodDescHandle(genericMethodDef);
Assert.NotEqual(TargetPointer.Null, handle.Address);
Assert.True(rts.IsGenericMethodDefinition(handle));
ReadOnlySpan<TypeHandle> instantiation = rts.GetGenericMethodInstantiation(handle);
Assert.Equal(0, instantiation.Length);
}

{
MethodDescHandle handle = rts.GetMethodDescHandle(genericWithInst);
Assert.NotEqual(TargetPointer.Null, handle.Address);
Assert.True(rts.IsGenericMethodDefinition(handle));
ReadOnlySpan<TypeHandle> instantiation = rts.GetGenericMethodInstantiation(handle);
Assert.Equal(typeArgsRawAddrs.Length, instantiation.Length);
for (int i = 0; i < typeArgsRawAddrs.Length; i++)
{
Assert.Equal(typeArgsHandles[i], instantiation[i].Address);
Assert.Equal(typeArgsRawAddrs[i], instantiation[i].TypeDescAddress());
}
}
}

public static IEnumerable<object[]> StdArchOptionalSlotsData()
Expand Down Expand Up @@ -98,12 +260,7 @@ public void GetAddressOfNativeCodeSlot_OptionalSlots(MockTarget.Architecture arc
MockDescriptors.MethodDescriptors methodDescBuilder = new(rtsBuilder, loaderBuilder);

MethodDescFlags_1.MethodDescFlags flags = (MethodDescFlags_1.MethodDescFlags)flagsValue;
ushort numVirtuals = 1;
TargetPointer eeClass = rtsBuilder.AddEEClass(string.Empty, 0, 2, 1);
TargetPointer methodTable = rtsBuilder.AddMethodTable(string.Empty,
mtflags: default, mtflags2: default, baseSize: helpers.ObjectBaseSize,
module: TargetPointer.Null, parentMethodTable: TargetPointer.Null, numInterfaces: 0, numVirtuals: numVirtuals);
rtsBuilder.SetEEClassAndCanonMTRefs(eeClass, methodTable);
TargetPointer methodTable = AddMethodTable(rtsBuilder);

uint methodDescSize = methodDescBuilder.Types[DataType.MethodDesc].Size.Value;
if (flags.HasFlag(MethodDescFlags_1.MethodDescFlags.HasNonVtableSlot))
Expand Down Expand Up @@ -135,4 +292,84 @@ public void GetAddressOfNativeCodeSlot_OptionalSlots(MockTarget.Architecture arc
Assert.Equal(expectedCodeSlotAddr, actualNativeCodeSlotAddr);
}
}

public static IEnumerable<object[]> StdArchMethodDescTypeData()
{
foreach (object[] arr in new MockTarget.StdArch())
{
MockTarget.Architecture arch = (MockTarget.Architecture)arr[0];
yield return new object[] { arch, DataType.MethodDesc };
yield return new object[] { arch, DataType.FCallMethodDesc };
yield return new object[] { arch, DataType.PInvokeMethodDesc };
yield return new object[] { arch, DataType.EEImplMethodDesc };
yield return new object[] { arch, DataType.ArrayMethodDesc };
yield return new object[] { arch, DataType.InstantiatedMethodDesc };
yield return new object[] { arch, DataType.CLRToCOMCallMethodDesc };
yield return new object[] { arch, DataType.DynamicMethodDesc };
}
}

[Theory]
[MemberData(nameof(StdArchMethodDescTypeData))]
public void GetNativeCode_StableEntryPoint_NonVtableSlot(MockTarget.Architecture arch, DataType methodDescType)
{
TargetTestHelpers helpers = new(arch);
MockMemorySpace.Builder builder = new(helpers);
MockDescriptors.RuntimeTypeSystem rtsBuilder = new(builder);
MockDescriptors.Loader loaderBuilder = new(builder);
MockDescriptors.MethodDescriptors methodDescBuilder = new(rtsBuilder, loaderBuilder);

TargetPointer methodTable = AddMethodTable(rtsBuilder);
MethodClassification classification = methodDescType switch
{
DataType.MethodDesc => MethodClassification.IL,
DataType.FCallMethodDesc => MethodClassification.FCall,
DataType.PInvokeMethodDesc => MethodClassification.PInvoke,
DataType.EEImplMethodDesc => MethodClassification.EEImpl,
DataType.ArrayMethodDesc => MethodClassification.Array,
DataType.InstantiatedMethodDesc => MethodClassification.Instantiated,
DataType.CLRToCOMCallMethodDesc => MethodClassification.ComInterop,
DataType.DynamicMethodDesc => MethodClassification.Dynamic,
_ => throw new ArgumentOutOfRangeException(nameof(methodDescType))
};
uint methodDescBaseSize = methodDescBuilder.Types[methodDescType].Size.Value;
uint methodDescSize = methodDescBaseSize + methodDescBuilder.Types[DataType.NonVtableSlot].Size!.Value;
byte chunkSize = (byte)(methodDescSize / methodDescBuilder.MethodDescAlignment);
TargetPointer chunk = methodDescBuilder.AddMethodDescChunk(methodTable, string.Empty, count: 1, chunkSize, tokenRange: 0);

ushort flags = (ushort)((ushort)classification | (ushort)MethodDescFlags_1.MethodDescFlags.HasNonVtableSlot);
TargetPointer methodDescAddress = methodDescBuilder.SetMethodDesc(chunk, index: 0, slotNum: 0, flags, tokenRemainder: 0, flags3: (ushort)MethodDescFlags_1.MethodDescFlags3.HasStableEntryPoint);
TargetCodePointer nativeCode = new TargetCodePointer(0x0789_abc0);
helpers.WritePointer(
methodDescBuilder.Builder.BorrowAddressRange(methodDescAddress + methodDescBaseSize, helpers.PointerSize),
nativeCode);

Mock<IExecutionManager> mockExecutionManager = new();
CodeBlockHandle codeBlock = new CodeBlockHandle(methodDescAddress);
mockExecutionManager.Setup(em => em.GetCodeBlockHandle(nativeCode))
.Returns(codeBlock);
mockExecutionManager.Setup(em => em.GetMethodDesc(codeBlock))
.Returns(methodDescAddress);
Target target = CreateTarget(methodDescBuilder, mockExecutionManager);
IRuntimeTypeSystem rts = target.Contracts.RuntimeTypeSystem;

var handle = rts.GetMethodDescHandle(methodDescAddress);
Assert.NotEqual(TargetPointer.Null, handle.Address);

TargetCodePointer actualNativeCode = rts.GetNativeCode(handle);
Assert.Equal(nativeCode, actualNativeCode);
}

private TargetPointer AddMethodTable(MockDescriptors.RuntimeTypeSystem rtsBuilder, ushort numVirtuals = 5)
{
TargetPointer eeClass = rtsBuilder.AddEEClass(string.Empty, attr: 0, numMethods: 2, numNonVirtualSlots: 1);
TargetPointer methodTable = rtsBuilder.AddMethodTable(string.Empty,
mtflags: default, mtflags2: default, baseSize: rtsBuilder.Builder.TargetTestHelpers.ObjectBaseSize,
module: TargetPointer.Null, parentMethodTable: TargetPointer.Null, numInterfaces: 0, numVirtuals);
rtsBuilder.SetEEClassAndCanonMTRefs(eeClass, methodTable);
return methodTable;
}

private static TargetPointer GetTypeDescHandlePointer(TargetPointer addr)
=> addr | (ulong)RuntimeTypeSystem_1.TypeHandleBits.TypeDesc;
}
Loading
Loading