Skip to content

Commit ae967fe

Browse files
authored
[cDAC] Compute MethodDesc size directly to support more heap dump scenarios (#124772)
Modifies how MethodDesc sizes are computed. Instead of using the runtime `s_ClassificationSizeTable` which is not present in heap dumps, RuntimeTypeSystem now computes the MethodDesc size from datadescriptors. Modified DumpTests to run on heap dumps for verification.
1 parent 58f1455 commit ae967fe

9 files changed

Lines changed: 97 additions & 34 deletions

File tree

docs/design/datacontracts/RuntimeTypeSystem.md

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -910,7 +910,6 @@ The version 1 `MethodDesc` APIs depend on the following globals:
910910
| --- | --- |
911911
| `MethodDescAlignment` | `MethodDescChunk` trailing data is allocated in multiples of this constant. The size (in bytes) of each `MethodDesc` (or subclass) instance is a multiple of this constant. |
912912
| `MethodDescTokenRemainderBitCount` | Number of bits in the token remainder in `MethodDesc` |
913-
| `MethodDescSizeTable` | A pointer to the MethodDesc size table. The MethodDesc flags are used as an offset into this table to lookup the MethodDesc size. |
914913

915914

916915
In the runtime a `MethodDesc` implicitly belongs to a single `MethodDescChunk` and some common data is shared between method descriptors that belong to the same chunk. A single method table
@@ -942,6 +941,24 @@ We depend on the following data descriptors:
942941
| `DynamicMethodDesc` | `MethodName` | Pointer to Null-terminated UTF8 string describing the Method desc |
943942
| `GCCoverageInfo` | `SavedCode` | Pointer to the GCCover saved code copy, if supported |
944943

944+
The following data descriptor types are used only for their sizes when computing the total size of a `MethodDesc` instance.
945+
The size of a `MethodDesc` is the base size of its classification subtype plus the sizes of any optional trailing slots indicated by its flags:
946+
947+
| Data Descriptor Name | Meaning |
948+
| --- | --- |
949+
| `MethodDesc` | Base size for `mcIL` classification |
950+
| `FCallMethodDesc` | Base size for `mcFCall` classification |
951+
| `PInvokeMethodDesc` | Base size for `mcPInvoke` classification |
952+
| `EEImplMethodDesc` | Base size for `mcEEImpl` classification |
953+
| `ArrayMethodDesc` | Base size for `mcArray` classification |
954+
| `InstantiatedMethodDesc` | Base size for `mcInstantiated` classification |
955+
| `CLRToCOMCallMethodDesc` | Base size for `mcComInterOp` classification |
956+
| `DynamicMethodDesc` | Base size for `mcDynamic` classification |
957+
| `NonVtableSlot` | Size of the non-vtable slot, added when `HasNonVtableSlot` flag is set |
958+
| `MethodImpl` | Size of the MethodImpl data, added when `HasMethodImpl` flag is set |
959+
| `NativeCodeSlot` | Size of the native code slot, added when `HasNativeCodeSlot` flag is set |
960+
| `AsyncMethodData` | Size of the async method data, added when `HasAsyncMethodData` flag is set |
961+
945962

946963
The contract depends on the following other contracts
947964

@@ -1181,17 +1198,32 @@ And the various apis are implemented with the following algorithms
11811198
{
11821199
MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];
11831200

1184-
// the runtime generates a table to lookup the size of a MethodDesc based on the flags
1185-
// read the location of the table and index into it using certain bits of MethodDesc.Flags
1186-
TargetPointer methodDescSizeTable = target.ReadGlobalPointer(Constants.Globals.MethodDescSizeTable);
1187-
1188-
ushort arrayOffset = (ushort)(methodDesc.Flags & (ushort)(
1189-
MethodDescFlags.ClassificationMask |
1190-
MethodDescFlags.HasNonVtableSlot |
1191-
MethodDescFlags.HasMethodImpl |
1192-
MethodDescFlags.HasNativeCodeSlot |
1193-
MethodDescFlags.HasAsyncMethodData));
1194-
return target.Read<byte>(methodDescSizeTable + arrayOffset);
1201+
// Compute the size from data descriptor type sizes.
1202+
// The base size comes from the classification subtype, and optional slot
1203+
// sizes are added based on the MethodDesc flags.
1204+
MethodClassification classification = (MethodClassification)(methodDesc.Flags & MethodDescFlags.ClassificationMask);
1205+
uint size = classification switch
1206+
{
1207+
MethodClassification.IL => target.GetTypeInfo(DataType.MethodDesc).Size,
1208+
MethodClassification.FCall => target.GetTypeInfo(DataType.FCallMethodDesc).Size,
1209+
MethodClassification.PInvoke => target.GetTypeInfo(DataType.PInvokeMethodDesc).Size,
1210+
MethodClassification.EEImpl => target.GetTypeInfo(DataType.EEImplMethodDesc).Size,
1211+
MethodClassification.Array => target.GetTypeInfo(DataType.ArrayMethodDesc).Size,
1212+
MethodClassification.Instantiated => target.GetTypeInfo(DataType.InstantiatedMethodDesc).Size,
1213+
MethodClassification.ComInterop => target.GetTypeInfo(DataType.CLRToCOMCallMethodDesc).Size,
1214+
MethodClassification.Dynamic => target.GetTypeInfo(DataType.DynamicMethodDesc).Size,
1215+
};
1216+
1217+
if (HasFlag(methodDesc, MethodDescFlags.HasNonVtableSlot))
1218+
size += target.GetTypeInfo(DataType.NonVtableSlot).Size;
1219+
if (HasFlag(methodDesc, MethodDescFlags.HasMethodImpl))
1220+
size += target.GetTypeInfo(DataType.MethodImpl).Size;
1221+
if (HasFlag(methodDesc, MethodDescFlags.HasNativeCodeSlot))
1222+
size += target.GetTypeInfo(DataType.NativeCodeSlot).Size;
1223+
if (HasFlag(methodDesc, MethodDescFlags.HasAsyncMethodData))
1224+
size += target.GetTypeInfo(DataType.AsyncMethodData).Size;
1225+
1226+
return size;
11951227
}
11961228

11971229
public bool IsArrayMethod(MethodDescHandle methodDescHandle, out ArrayFunctionType functionType)

src/coreclr/vm/datadescriptor/datadescriptor.inc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,10 @@ CDAC_TYPE_BEGIN(NativeCodeSlot)
464464
CDAC_TYPE_SIZE(sizeof(MethodDesc::NativeCodeSlot))
465465
CDAC_TYPE_END(NativeCodeSlot)
466466

467+
CDAC_TYPE_BEGIN(AsyncMethodData)
468+
CDAC_TYPE_SIZE(sizeof(AsyncMethodData))
469+
CDAC_TYPE_END(AsyncMethodData)
470+
467471
CDAC_TYPE_BEGIN(InstantiatedMethodDesc)
468472
CDAC_TYPE_SIZE(sizeof(InstantiatedMethodDesc))
469473
CDAC_TYPE_FIELD(InstantiatedMethodDesc, /*pointer*/, PerInstInfo, cdac_data<InstantiatedMethodDesc>::PerInstInfo)
@@ -1151,7 +1155,6 @@ CDAC_GLOBAL(StressLogEnabled, uint8, 0)
11511155
CDAC_GLOBAL_POINTER(ExecutionManagerCodeRangeMapAddress, cdac_data<ExecutionManager>::CodeRangeMapAddress)
11521156
CDAC_GLOBAL_POINTER(PlatformMetadata, &::g_cdacPlatformMetadata)
11531157
CDAC_GLOBAL_POINTER(ProfilerControlBlock, &::g_profControlBlock)
1154-
CDAC_GLOBAL_POINTER(MethodDescSizeTable, &MethodDesc::s_ClassificationSizeTable)
11551158

11561159
CDAC_GLOBAL_POINTER(GCLowestAddress, &g_lowest_address)
11571160
CDAC_GLOBAL_POINTER(GCHighestAddress, &g_highest_address)

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ public enum DataType
113113
NonVtableSlot,
114114
MethodImpl,
115115
NativeCodeSlot,
116+
AsyncMethodData,
116117
GCCoverageInfo,
117118
ArrayListBase,
118119
ArrayListBlock,

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,6 @@ public static class Globals
7171
public const string PlatformMetadata = nameof(PlatformMetadata);
7272
public const string ProfilerControlBlock = nameof(ProfilerControlBlock);
7373

74-
public const string MethodDescSizeTable = nameof(MethodDescSizeTable);
75-
7674
public const string HashMapSlotsPerBucket = nameof(HashMapSlotsPerBucket);
7775
public const string HashMapValueMask = nameof(HashMapValueMask);
7876

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -226,17 +226,52 @@ private static uint ComputeToken(Target target, Data.MethodDesc desc, Data.Metho
226226

227227
private static uint ComputeSize(Target target, Data.MethodDesc desc)
228228
{
229-
// Size of the MethodDesc is variable, read it from the targets lookup table
230-
// See MethodDesc::SizeOf in method.cpp for details
231-
TargetPointer methodDescSizeTable = target.ReadGlobalPointer(Constants.Globals.MethodDescSizeTable);
232-
233-
ushort arrayOffset = (ushort)(desc.Flags & (ushort)(
234-
MethodDescFlags_1.MethodDescFlags.ClassificationMask |
235-
MethodDescFlags_1.MethodDescFlags.HasNonVtableSlot |
236-
MethodDescFlags_1.MethodDescFlags.HasMethodImpl |
237-
MethodDescFlags_1.MethodDescFlags.HasNativeCodeSlot |
238-
MethodDescFlags_1.MethodDescFlags.HasAsyncMethodData));
239-
return target.Read<byte>(methodDescSizeTable + arrayOffset);
229+
// See s_ClassificationSizeTable in method.cpp in the runtime for how the size is determined based on the method classification and flags.
230+
uint baseSize;
231+
switch ((MethodClassification)(desc.Flags & (ushort)MethodDescFlags_1.MethodDescFlags.ClassificationMask))
232+
{
233+
case MethodClassification.IL:
234+
baseSize = target.GetTypeInfo(DataType.MethodDesc).Size ?? throw new InvalidOperationException("MethodDesc type size must be known");
235+
break;
236+
case MethodClassification.FCall:
237+
baseSize = target.GetTypeInfo(DataType.FCallMethodDesc).Size ?? throw new InvalidOperationException("FCallMethodDesc type size must be known");
238+
break;
239+
case MethodClassification.PInvoke:
240+
baseSize = target.GetTypeInfo(DataType.PInvokeMethodDesc).Size ?? throw new InvalidOperationException("PInvokeMethodDesc type size must be known");
241+
break;
242+
case MethodClassification.EEImpl:
243+
baseSize = target.GetTypeInfo(DataType.EEImplMethodDesc).Size ?? throw new InvalidOperationException("EEImplMethodDesc type size must be known");
244+
break;
245+
case MethodClassification.Array:
246+
baseSize = target.GetTypeInfo(DataType.ArrayMethodDesc).Size ?? throw new InvalidOperationException("ArrayMethodDesc type size must be known");
247+
break;
248+
case MethodClassification.Instantiated:
249+
baseSize = target.GetTypeInfo(DataType.InstantiatedMethodDesc).Size ?? throw new InvalidOperationException("InstantiatedMethodDesc type size must be known");
250+
break;
251+
case MethodClassification.ComInterop:
252+
baseSize = target.GetTypeInfo(DataType.CLRToCOMCallMethodDesc).Size ?? throw new InvalidOperationException("CLRToCOMCallMethodDesc type size must be known");
253+
break;
254+
case MethodClassification.Dynamic:
255+
baseSize = target.GetTypeInfo(DataType.DynamicMethodDesc).Size ?? throw new InvalidOperationException("DynamicMethodDesc type size must be known");
256+
break;
257+
default:
258+
throw new InvalidOperationException("Invalid method classification");
259+
}
260+
261+
MethodDescFlags_1.MethodDescFlags flags = (MethodDescFlags_1.MethodDescFlags)desc.Flags;
262+
if (flags.HasFlag(MethodDescFlags_1.MethodDescFlags.HasNonVtableSlot))
263+
baseSize += target.GetTypeInfo(DataType.NonVtableSlot).Size ?? throw new InvalidOperationException("NonVtableSlot type size must be known");
264+
265+
if (flags.HasFlag(MethodDescFlags_1.MethodDescFlags.HasMethodImpl))
266+
baseSize += target.GetTypeInfo(DataType.MethodImpl).Size ?? throw new InvalidOperationException("MethodImpl type size must be known");
267+
268+
if (flags.HasFlag(MethodDescFlags_1.MethodDescFlags.HasNativeCodeSlot))
269+
baseSize += target.GetTypeInfo(DataType.NativeCodeSlot).Size ?? throw new InvalidOperationException("NativeCodeSlot type size must be known");
270+
271+
if (flags.HasFlag(MethodDescFlags_1.MethodDescFlags.HasAsyncMethodData))
272+
baseSize += target.GetTypeInfo(DataType.AsyncMethodData).Size ?? throw new InvalidOperationException("AsyncMethodData type size must be known");
273+
274+
return baseSize;
240275
}
241276

242277
public MethodClassification Classification => (MethodClassification)((int)_desc.Flags & (int)MethodDescFlags_1.MethodDescFlags.ClassificationMask);
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3-
<DumpTypes>Full</DumpTypes>
43
</PropertyGroup>
54
</Project>

src/native/managed/cdac/tests/DumpTests/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ features and then calls `Environment.FailFast()` to produce a crash dump.
3131
| ServerGC | Server GC mode heap structures | Heap |
3232
| StackWalk | Deterministic call stack (Main→A→B→C→FailFast) | Full |
3333
| MultiModule | Multi-assembly metadata resolution | Full |
34-
| TypeHierarchy | Type inheritance, method tables | Full |
34+
| TypeHierarchy | Type inheritance, method tables | Heap |
3535

3636
The dump type is configured per-debuggee via the `DumpTypes` property in each debuggee's
3737
`.csproj` (default: `Heap`, set in `Debuggees/Directory.Build.props`). Debuggees that

src/native/managed/cdac/tests/DumpTests/RuntimeTypeSystemDumpTests.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ namespace Microsoft.Diagnostics.DataContractReader.DumpTests;
1717
public class RuntimeTypeSystemDumpTests : DumpTestBase
1818
{
1919
protected override string DebuggeeName => "TypeHierarchy";
20-
protected override string DumpType => "full";
2120

2221
[ConditionalTheory]
2322
[MemberData(nameof(TestConfigurations))]

src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.MethodDescriptors.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,14 +101,9 @@ internal MethodDescriptors(RuntimeTypeSystem rtsBuilder, Loader loaderBuilder, (
101101
_allocator = Builder.CreateAllocator(allocationRange.Start, allocationRange.End);
102102
Types = GetTypes();
103103

104-
// Add dummy MethodDescSizeTable. Sizes will be incorrect, but we don't use it in tests.
105-
MockMemorySpace.HeapFragment methodDescSizeTable = _allocator.Allocate(0x100, "MethodDescSizeTable");
106-
Builder.AddHeapFragment(methodDescSizeTable);
107-
108104
Globals = rtsBuilder.Globals.Concat(
109105
[
110106
new(nameof(Constants.Globals.MethodDescTokenRemainderBitCount), TokenRemainderBitCount),
111-
new(nameof(Constants.Globals.MethodDescSizeTable), methodDescSizeTable.Address),
112107
]).ToArray();
113108
}
114109

@@ -126,6 +121,7 @@ internal MethodDescriptors(RuntimeTypeSystem rtsBuilder, Loader loaderBuilder, (
126121
types[DataType.NonVtableSlot] = new Target.TypeInfo() { Size = (uint)TargetTestHelpers.PointerSize };
127122
types[DataType.MethodImpl] = new Target.TypeInfo() { Size = (uint)TargetTestHelpers.PointerSize * 2 };
128123
types[DataType.NativeCodeSlot] = new Target.TypeInfo() { Size = (uint)TargetTestHelpers.PointerSize };
124+
types[DataType.AsyncMethodData] = new Target.TypeInfo() { Size = (uint)TargetTestHelpers.PointerSize * 2 };
129125
types[DataType.ArrayMethodDesc] = new Target.TypeInfo() { Size = types[DataType.StoredSigMethodDesc].Size.Value };
130126
types[DataType.FCallMethodDesc] = new Target.TypeInfo() { Size = types[DataType.MethodDesc].Size.Value + (uint)TargetTestHelpers.PointerSize };
131127
types[DataType.PInvokeMethodDesc] = new Target.TypeInfo() { Size = types[DataType.MethodDesc].Size.Value + (uint)TargetTestHelpers.PointerSize };

0 commit comments

Comments
 (0)