Skip to content

Commit e336326

Browse files
lambdageekelinor-fungAaronRobinsonMSFTjkotas
authored
[cdac] RuntimeTypeSystem contract; rename ContainsPointers -> ContainsGCPointers (#103444)
* Implement GetThreadStoreData in cDAC * [dac] Return canonical MethodTable instead of EEClass Instead of storing the EEClass pointer in DacpMethodTableData, store the canonical method table instead. Correspondingly, update GetMethodTableForEEClass to expect a canonical method table pointer instead of an EEClass Also update cDAC to do likewise * document GetMethodTableData string baseSize adjustment * Apply suggestions from code review Co-Authored-By: Aaron Robinson <[email protected]> * [vm] rename ContainsPointers flag to ContainsGCPointers also rename getter/setter methods in MethodTable * code style suggestions from code review * DAC: always set wNumVirtuals and wNumVtableSlots to 0 This information can be retreived from the MethodTable using normal lldb/windbg primitives and doesn't need to be part of the DAC API contract * Remove NumVirtuals and NumVtableSlots from RuntimeTypeSystem.md Co-authored-by: Jan Kotas <[email protected]> * "untrusted" -> "non-validated" * pull test target helpers out goal is to be able to use this for testing contracts that depend on some data in the heap * Add one FreeObjectMethodTable unit test * validate that a mock system object is a valid method table * code review feedback and more tests: 1. rename AttrClass data descriptor field to CorTypeAttr 2. fixup HasComponentSize / RawGetComponentSize comments and code 3. update "system.object" mock methodtable with more field values 4. update "system.string" mock methodtable with more field values * Update src/coreclr/gc/env/gcenv.object.h Co-authored-by: Elinor Fung <[email protected]> * Update src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodTableFlags.cs Co-authored-by: Elinor Fung <[email protected]> * move non-validated MethodTable handling to a separate class * clear up ComponentSize contract spec and impl * rename Metadata -> RuntimeTypeSystem * add validation failure test; change validation to throw InvalidOperationException * Update src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_1.cs Co-authored-by: Jan Kotas <[email protected]> * Add a generic instance test * add array instance test --------- Co-authored-by: Elinor Fung <[email protected]> Co-authored-by: Aaron Robinson <[email protected]> Co-authored-by: Jan Kotas <[email protected]>
1 parent e0d8b0d commit e336326

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+2155
-394
lines changed
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
# Contract RuntimeTypeSystem
2+
3+
This contract is for exploring the properties of the runtime types of values on the managed heap or on the stack in a .NET process.
4+
5+
## APIs of contract
6+
7+
A `MethodTable` is the runtime representation of the type information about a value. Given a `TargetPointer` address, the `RuntimeTypeSystem` contract provides a `MethodTableHandle` for querying the `MethodTable`.
8+
9+
``` csharp
10+
struct MethodTableHandle
11+
{
12+
// no public properties or constructors
13+
14+
internal TargetPointer Address { get; }
15+
}
16+
```
17+
18+
``` csharp
19+
#region MethodTable inspection APIs
20+
public virtual MethodTableHandle GetMethodTableHandle(TargetPointer targetPointer);
21+
22+
public virtual TargetPointer GetModule(MethodTableHandle methodTable);
23+
// A canonical method table is either the MethodTable itself, or in the case of a generic instantiation, it is the
24+
// MethodTable of the prototypical instance.
25+
public virtual TargetPointer GetCanonicalMethodTable(MethodTableHandle methodTable);
26+
public virtual TargetPointer GetParentMethodTable(MethodTableHandle methodTable);
27+
28+
public virtual uint GetBaseSize(MethodTableHandle methodTable);
29+
// The component size is only available for strings and arrays. It is the size of the element type of the array, or the size of an ECMA 335 character (2 bytes)
30+
public virtual uint GetComponentSize(MethodTableHandle methodTable);
31+
32+
// True if the MethodTable is the sentinel value associated with unallocated space in the managed heap
33+
public virtual bool IsFreeObjectMethodTable(MethodTableHandle methodTable);
34+
public virtual bool IsString(MethodTableHandle methodTable);
35+
// True if the MethodTable represents a type that contains managed references
36+
public virtual bool ContainsGCPointers(MethodTableHandle methodTable);
37+
public virtual bool IsDynamicStatics(MethodTableHandle methodTable);
38+
public virtual ushort GetNumMethods(MethodTableHandle methodTable);
39+
public virtual ushort GetNumInterfaces(MethodTableHandle methodTable);
40+
41+
// Returns an ECMA-335 TypeDef table token for this type, or for its generic type definition if it is a generic instantiation
42+
public virtual uint GetTypeDefToken(MethodTableHandle methodTable);
43+
// Returns the ECMA 335 TypeDef table Flags value (a bitmask of TypeAttributes) for this type,
44+
// or for its generic type definition if it is a generic instantiation
45+
public virtual uint GetTypeDefTypeAttributes(MethodTableHandle methodTable);
46+
#endregion MethodTable inspection APIs
47+
```
48+
49+
## Version 1
50+
51+
The `MethodTable` inspection APIs are implemented in terms of the following flags on the runtime `MethodTable` structure:
52+
53+
``` csharp
54+
internal partial struct RuntimeTypeSystem_1
55+
{
56+
// The lower 16-bits of the MTFlags field are used for these flags,
57+
// if WFLAGS_HIGH.HasComponentSize is unset
58+
[Flags]
59+
internal enum WFLAGS_LOW : uint
60+
{
61+
GenericsMask = 0x00000030,
62+
GenericsMask_NonGeneric = 0x00000000, // no instantiation
63+
64+
StringArrayValues = GenericsMask_NonGeneric,
65+
}
66+
67+
// Upper bits of MTFlags
68+
[Flags]
69+
internal enum WFLAGS_HIGH : uint
70+
{
71+
Category_Mask = 0x000F0000,
72+
Category_Array = 0x00080000,
73+
Category_Array_Mask = 0x000C0000,
74+
Category_Interface = 0x000C0000,
75+
ContainsGCPointers = 0x01000000,
76+
HasComponentSize = 0x80000000, // This is set if lower 16 bits is used for the component size,
77+
// otherwise the lower bits are used for WFLAGS_LOW
78+
}
79+
80+
[Flags]
81+
internal enum WFLAGS2_ENUM : uint
82+
{
83+
DynamicStatics = 0x0002,
84+
}
85+
86+
// Encapsulates the MethodTable flags v1 uses
87+
internal struct MethodTableFlags
88+
{
89+
public uint MTFlags { get; }
90+
public uint MTFlags2 { get; }
91+
public uint BaseSize { get; }
92+
93+
public WFLAGS_LOW GetFlag(WFLAGS_LOW mask) { ... /* mask & lower 16 bits of MTFlags */ }
94+
public WFLAGS_HIGH GetFlag(WFLAGS_HIGH mask) { ... /* mask & upper 16 bits of MTFlags */ }
95+
96+
public WFLAGS2_ENUM GetFlag(WFLAGS2_ENUM mask) { ... /* mask & MTFlags2*/ }
97+
98+
private bool TestFlagWithMask(WFLAGS_LOW mask, WFLAGS_LOW flag)
99+
{
100+
if (IsStringOrArray)
101+
{
102+
return (WFLAGS_LOW.StringArrayValues & mask) == flag;
103+
}
104+
else
105+
{
106+
return (FlagsLow & mask) == flag;
107+
}
108+
}
109+
110+
public ushort ComponentSizeBits => (ushort)(MTFlags & 0x0000ffff); // only meaningful if HasComponentSize is set
111+
112+
public bool HasComponentSize => GetFlag(WFLAGS_HIGH.HasComponentSize) != 0;
113+
public bool IsInterface => GetFlag(WFLAGS_HIGH.Category_Mask) == WFLAGS_HIGH.Category_Interface;
114+
public bool IsString => HasComponentSize && !IsArray && ComponentSizeBits == 2;
115+
public bool IsArray => GetFlag(WFLAGS_HIGH.Category_Array_Mask) == WFLAGS_HIGH.Category_Array;
116+
public bool IsStringOrArray => HasComponentSize;
117+
public ushort ComponentSize => HasComponentSize ? ComponentSizeBits : (ushort)0;
118+
public bool HasInstantiation => !TestFlagWithMask(WFLAGS_LOW.GenericsMask, WFLAGS_LOW.GenericsMask_NonGeneric);
119+
public bool ContainsGCPointers => GetFlag(WFLAGS_HIGH.ContainsGCPointers) != 0;
120+
public bool IsDynamicStatics => GetFlag(WFLAGS2_ENUM.DynamicStatics) != 0;
121+
}
122+
123+
[Flags]
124+
internal enum EEClassOrCanonMTBits
125+
{
126+
EEClass = 0,
127+
CanonMT = 1,
128+
Mask = 1,
129+
}
130+
}
131+
```
132+
133+
Internally the contract has a `MethodTable_1` struct that depends on the `MethodTable` data descriptor
134+
135+
```csharp
136+
internal struct MethodTable_1
137+
{
138+
internal RuntimeTypeSystem_1.MethodTableFlags Flags { get; }
139+
internal ushort NumInterfaces { get; }
140+
internal ushort NumVirtuals { get; }
141+
internal TargetPointer ParentMethodTable { get; }
142+
internal TargetPointer Module { get; }
143+
internal TargetPointer EEClassOrCanonMT { get; }
144+
internal MethodTable_1(Data.MethodTable data)
145+
{
146+
Flags = new RuntimeTypeSystem_1.MethodTableFlags
147+
{
148+
MTFlags = data.MTFlags,
149+
MTFlags2 = data.MTFlags2,
150+
BaseSize = data.BaseSize,
151+
};
152+
NumInterfaces = data.NumInterfaces;
153+
NumVirtuals = data.NumVirtuals;
154+
EEClassOrCanonMT = data.EEClassOrCanonMT;
155+
Module = data.Module;
156+
ParentMethodTable = data.ParentMethodTable;
157+
}
158+
}
159+
```
160+
161+
The contract depends on the global pointer value `FreeObjectMethodTablePointer`.
162+
The contract additionally depends on the `EEClass` data descriptor.
163+
164+
```csharp
165+
private readonly Dictionary<TargetPointer, MethodTable_1> _methodTables;
166+
167+
internal TargetPointer FreeObjectMethodTablePointer {get; }
168+
169+
public MethodTableHandle GetMethodTableHandle(TargetPointer methodTablePointer)
170+
{
171+
... // validate that methodTablePointer points to something that looks like a MethodTable.
172+
... // read Data.MethodTable from methodTablePointer.
173+
... // create a MethodTable_1 and add it to _methodTables.
174+
return MethodTableHandle { Address = methodTablePointer }
175+
}
176+
177+
internal static EEClassOrCanonMTBits GetEEClassOrCanonMTBits(TargetPointer eeClassOrCanonMTPtr)
178+
{
179+
return (EEClassOrCanonMTBits)(eeClassOrCanonMTPtr & (ulong)EEClassOrCanonMTBits.Mask);
180+
}
181+
182+
public uint GetBaseSize(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.BaseSize;
183+
184+
public uint GetComponentSize(MethodTableHandle methodTableHandle) => GetComponentSize(_methodTables[methodTableHandle.Address]);
185+
186+
private TargetPointer GetClassPointer(MethodTableHandle methodTableHandle)
187+
{
188+
... // if the MethodTable stores a pointer to the EEClass, return it
189+
// otherwise the MethodTable stores a pointer to the canonical MethodTable
190+
// in that case, return the canonical MethodTable's EEClass.
191+
// Canonical MethodTables always store an EEClass pointer.
192+
}
193+
194+
private Data.EEClass GetClassData(MethodTableHandle methodTableHandle)
195+
{
196+
TargetPointer eeClassPtr = GetClassPointer(methodTableHandle);
197+
... // read Data.EEClass data from eeClassPtr
198+
}
199+
200+
201+
public TargetPointer GetCanonicalMethodTable(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).MethodTable;
202+
203+
public TargetPointer GetModule(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Module;
204+
public TargetPointer GetParentMethodTable(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].ParentMethodTable;
205+
206+
public bool IsFreeObjectMethodTable(MethodTableHandle methodTableHandle) => FreeObjectMethodTablePointer == methodTableHandle.Address;
207+
208+
public bool IsString(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.IsString;
209+
public bool ContainsGCPointers(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.ContainsGCPointers;
210+
211+
public uint GetTypeDefToken(MethodTableHandle methodTableHandle)
212+
{
213+
MethodTable_1 methodTable = _methodTables[methodTableHandle.Address];
214+
return (uint)(methodTable.Flags.GetTypeDefRid() | ((int)TableIndex.TypeDef << 24));
215+
}
216+
217+
public ushort GetNumMethods(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).NumMethods;
218+
219+
public ushort GetNumInterfaces(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].NumInterfaces;
220+
221+
public uint GetTypeDefTypeAttributes(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).CorTypeAttr;
222+
223+
public bool IsDynamicStatics(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.IsDynamicStatics;
224+
```

src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -654,7 +654,7 @@ internal unsafe struct MethodTable
654654
private const uint enum_flag_IsByRefLike = 0x00001000;
655655

656656
// WFLAGS_HIGH_ENUM
657-
private const uint enum_flag_ContainsPointers = 0x01000000;
657+
private const uint enum_flag_ContainsGCPointers = 0x01000000;
658658
private const uint enum_flag_ContainsGenericVariables = 0x20000000;
659659
private const uint enum_flag_HasComponentSize = 0x80000000;
660660
private const uint enum_flag_HasTypeEquivalence = 0x02000000;
@@ -707,7 +707,7 @@ internal unsafe struct MethodTable
707707

708708
public bool HasComponentSize => (Flags & enum_flag_HasComponentSize) != 0;
709709

710-
public bool ContainsGCPointers => (Flags & enum_flag_ContainsPointers) != 0;
710+
public bool ContainsGCPointers => (Flags & enum_flag_ContainsGCPointers) != 0;
711711

712712
public bool NonTrivialInterfaceCast => (Flags & enum_flag_NonTrivialInterfaceCast) != 0;
713713

src/coreclr/debug/daccess/dacimpl.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1232,6 +1232,8 @@ class ClrDataAccess
12321232
HRESULT GetThreadDataImpl(CLRDATA_ADDRESS threadAddr, struct DacpThreadData *threadData);
12331233
HRESULT GetThreadStoreDataImpl(struct DacpThreadStoreData *data);
12341234
HRESULT GetNestedExceptionDataImpl(CLRDATA_ADDRESS exception, CLRDATA_ADDRESS *exceptionObject, CLRDATA_ADDRESS *nextNestedException);
1235+
HRESULT GetMethodTableDataImpl(CLRDATA_ADDRESS mt, struct DacpMethodTableData *data);
1236+
HRESULT GetMethodTableForEEClassImpl (CLRDATA_ADDRESS eeClassReallyMT, CLRDATA_ADDRESS *value);
12351237

12361238
BOOL IsExceptionFromManagedCode(EXCEPTION_RECORD * pExceptionRecord);
12371239
#ifndef TARGET_UNIX

0 commit comments

Comments
 (0)