Skip to content

Commit

Permalink
[cdac] Handle non-IL method descs in `RuntimeTypeSystem_1.GetMethodCl…
Browse files Browse the repository at this point in the history
…assificationDataType` (dotnet#110602)

- Add the different method desc types to the data descriptor
  - We only need their size right now
- Add tests for different method desc classifications
  - Mostly fill-in for things I found we didn't cover - `GetNativeCode_StableEntryPoint_NonVtableSlot` is the one that actually hits the updated code in this change
  • Loading branch information
elinor-fung authored and hez2010 committed Dec 14, 2024
1 parent 07e85b6 commit 05d687e
Show file tree
Hide file tree
Showing 7 changed files with 355 additions and 18 deletions.
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

0 comments on commit 05d687e

Please sign in to comment.