Skip to content

[cdac] start of RuntimeTypeSystem contract; implement GetMethodTableData SOS method #103444

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

Merged
merged 72 commits into from
Jul 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
b98afa8
Implement GetThreadStoreData in cDAC
elinor-fung May 16, 2024
890f9c6
Add placeholder for getting thread data
elinor-fung May 17, 2024
41ba95c
Apply suggestions from code review
elinor-fung May 23, 2024
29214f0
WIP: Metadata contract
lambdageek May 14, 2024
8beced0
Merge remote-tracking branch 'elinor-fung/cdac-threadstore' into cdac…
lambdageek May 29, 2024
5213286
fix build
lambdageek May 29, 2024
622e01a
WIP: ValidateMethodTable
lambdageek May 29, 2024
e112416
DataCache.GetOrAdd
elinor-fung May 29, 2024
ae1eac9
wip
lambdageek May 30, 2024
79ea0d4
Merge remote-tracking branch 'elinor-fung/cdac-threadstore' into cdac…
lambdageek May 30, 2024
5c696da
checkpoint: ValidateWithPossibleAV
lambdageek May 30, 2024
6eaf80f
Merge remote-tracking branch 'origin/main' into cdac-wip
lambdageek May 30, 2024
3a7808d
checkpoint EEClass from MethodTable
lambdageek May 31, 2024
66e5476
checkpoint: ValidateMethodTablePointer
lambdageek May 31, 2024
95914b8
cp: delegate from legacy dac
lambdageek May 31, 2024
2b8fda3
add Metadata to runtime contract descriptor
lambdageek Jun 11, 2024
5c7d2ac
checkpoint: more MethodTable fields
lambdageek Jun 12, 2024
7be5c60
checkpoint GetMethodTableData implemented
lambdageek Jun 13, 2024
30b7b26
checkpoint: same answer as legacy dac
lambdageek Jun 13, 2024
187bcbe
Merge remote-tracking branch 'origin/main' into cdac-wip
lambdageek Jun 14, 2024
f0d1fcb
new flags for statics
lambdageek Jun 14, 2024
c53db36
fix GCC build
lambdageek Jun 14, 2024
b70bb1d
WIP: opaque MethodTableHandle
lambdageek Jun 17, 2024
a65fd50
Add contract accessors for MethodTableHandle
lambdageek Jun 18, 2024
6f844bf
fixup
lambdageek Jun 18, 2024
76e0384
simplify FreeObjectMethodTable handling
lambdageek Jun 18, 2024
cdb7543
cleanup
lambdageek Jun 18, 2024
78830ea
fixup
lambdageek Jun 18, 2024
fbbd45b
Merge remote-tracking branch 'origin/main' into cdac-wip
lambdageek Jun 18, 2024
32665b2
[dac] Return canonical MethodTable instead of EEClass
lambdageek Jun 18, 2024
acf8436
Delete unreferenced MethodTable flags
lambdageek Jun 18, 2024
f246c86
add Metadata contract doc; fixups
lambdageek Jun 19, 2024
8409f3e
Merge remote-tracking branch 'origin/main' into cdac-wip
lambdageek Jun 20, 2024
08d069a
rename DacpMethodTableData:klass field
lambdageek Jun 20, 2024
674655f
document GetMethodTableData string baseSize adjustment
lambdageek Jun 20, 2024
2ae4625
Apply suggestions from code review
lambdageek Jun 20, 2024
e18a2be
fix typo
lambdageek Jun 21, 2024
846e779
rename flag to ContainsGCPointers
lambdageek Jun 21, 2024
383af83
[vm] rename ContainsPointers flag to ContainsGCPointers
lambdageek Jun 21, 2024
0f8c7f1
code style suggestions from code review
lambdageek Jun 21, 2024
7a337c1
BUGFIX: read DwFlags2 from the correct offset
lambdageek Jun 21, 2024
a7c8158
hide utility methods
lambdageek Jun 21, 2024
f4a3493
remove EEClass_1 struct
lambdageek Jun 21, 2024
10624c7
Merge remote-tracking branch 'origin/main' into cdac-wip
lambdageek Jun 21, 2024
65cc531
rename data descriptor members to remove prefixes
lambdageek Jun 21, 2024
d526087
cleanup the contract docs
lambdageek Jun 21, 2024
0a4112e
remove hungariant notation prefixes from data descriptors
lambdageek Jun 24, 2024
1071ca4
DAC: always set wNumVirtuals and wNumVtableSlots to 0
lambdageek Jun 25, 2024
6c5235c
Remove NumVirtuals and NumVtableSlots from Metadata.md
lambdageek Jun 27, 2024
95b728a
Merge remote-tracking branch 'origin/main' into cdac-wip
lambdageek Jun 28, 2024
6573e14
"untrusted" -> "non-validated"
lambdageek Jun 28, 2024
8596892
merge fixup
lambdageek Jun 28, 2024
6eabf42
remove #if 0
lambdageek Jun 28, 2024
4d3200d
cleanup
lambdageek Jun 28, 2024
2e66740
pull test target helpers out
lambdageek Jun 28, 2024
8533148
Add one FreeObjectMethodTable unit test
lambdageek Jul 1, 2024
77cf405
clean up the test helpers a bit
lambdageek Jul 2, 2024
f9bce4c
validate that a mock system object is a valid method table
lambdageek Jul 2, 2024
3721992
code review feedback and more tests:
lambdageek Jul 2, 2024
1af7c80
Update src/coreclr/gc/env/gcenv.object.h
lambdageek Jul 2, 2024
993ae1d
Update src/native/managed/cdacreader/src/Contracts/Metadata_1.MethodT…
lambdageek Jul 2, 2024
f04d880
Address code review feedback
lambdageek Jul 2, 2024
76859d1
move non-validated MethodTable handling to a separate class
lambdageek Jul 2, 2024
a12a407
clear up ComponentSize contract spec and impl
lambdageek Jul 2, 2024
9cf4c5a
rename Metadata -> RuntimeTypeSystem
lambdageek Jul 3, 2024
1814848
add validation failure test; change validation to throw InvalidOperat…
lambdageek Jul 3, 2024
89f98a3
Merge remote-tracking branch 'origin/main' into cdac-wip
lambdageek Jul 3, 2024
a0989fa
Update src/native/managed/cdacreader/src/Contracts/RuntimeTypeSystem_…
lambdageek Jul 3, 2024
617bf62
spellcheck
lambdageek Jul 3, 2024
815ff0d
Merge branch 'cdac-wip' of github.com:lambdageek/runtime into cdac-wip
lambdageek Jul 3, 2024
1ab4f08
Add a generic instance test
lambdageek Jul 3, 2024
ee3a362
add array instance test
lambdageek Jul 3, 2024
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
224 changes: 224 additions & 0 deletions docs/design/datacontracts/RuntimeTypeSystem.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
# Contract RuntimeTypeSystem

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.

## APIs of contract

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`.

``` csharp
struct MethodTableHandle
{
// no public properties or constructors

internal TargetPointer Address { get; }
}
```

``` csharp
#region MethodTable inspection APIs
public virtual MethodTableHandle GetMethodTableHandle(TargetPointer targetPointer);

public virtual TargetPointer GetModule(MethodTableHandle methodTable);
// 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(MethodTableHandle methodTable);
public virtual TargetPointer GetParentMethodTable(MethodTableHandle methodTable);

public virtual uint GetBaseSize(MethodTableHandle methodTable);
// 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)
public virtual uint GetComponentSize(MethodTableHandle methodTable);

// True if the MethodTable is the sentinel value associated with unallocated space in the managed heap
public virtual bool IsFreeObjectMethodTable(MethodTableHandle methodTable);
public virtual bool IsString(MethodTableHandle methodTable);
// True if the MethodTable represents a type that contains managed references
public virtual bool ContainsGCPointers(MethodTableHandle methodTable);
public virtual bool IsDynamicStatics(MethodTableHandle methodTable);
public virtual ushort GetNumMethods(MethodTableHandle methodTable);
public virtual ushort GetNumInterfaces(MethodTableHandle methodTable);

// Returns an ECMA-335 TypeDef table token for this type, or for its generic type definition if it is a generic instantiation
public virtual uint GetTypeDefToken(MethodTableHandle methodTable);
// Returns the ECMA 335 TypeDef table Flags value (a bitmask of TypeAttributes) for this type,
// or for its generic type definition if it is a generic instantiation
public virtual uint GetTypeDefTypeAttributes(MethodTableHandle methodTable);
#endregion MethodTable inspection APIs
```

## Version 1

The `MethodTable` inspection APIs are implemented in terms of the following flags on the runtime `MethodTable` structure:

``` csharp
internal partial struct RuntimeTypeSystem_1
{
// The lower 16-bits of the MTFlags field are used for these flags,
// if WFLAGS_HIGH.HasComponentSize is unset
[Flags]
internal enum WFLAGS_LOW : uint
{
GenericsMask = 0x00000030,
GenericsMask_NonGeneric = 0x00000000, // no instantiation

StringArrayValues = GenericsMask_NonGeneric,
}

// Upper bits of MTFlags
[Flags]
internal enum WFLAGS_HIGH : uint
{
Category_Mask = 0x000F0000,
Category_Array = 0x00080000,
Category_Array_Mask = 0x000C0000,
Category_Interface = 0x000C0000,
ContainsGCPointers = 0x01000000,
HasComponentSize = 0x80000000, // This is set if lower 16 bits is used for the component size,
// otherwise the lower bits are used for WFLAGS_LOW
}

[Flags]
internal enum WFLAGS2_ENUM : uint
{
DynamicStatics = 0x0002,
}

// Encapsulates the MethodTable flags v1 uses
internal struct MethodTableFlags
{
public uint MTFlags { get; }
public uint MTFlags2 { get; }
public uint BaseSize { get; }

public WFLAGS_LOW GetFlag(WFLAGS_LOW mask) { ... /* mask & lower 16 bits of MTFlags */ }
public WFLAGS_HIGH GetFlag(WFLAGS_HIGH mask) { ... /* mask & upper 16 bits of MTFlags */ }

public WFLAGS2_ENUM GetFlag(WFLAGS2_ENUM mask) { ... /* mask & MTFlags2*/ }

private bool TestFlagWithMask(WFLAGS_LOW mask, WFLAGS_LOW flag)
{
if (IsStringOrArray)
{
return (WFLAGS_LOW.StringArrayValues & mask) == flag;
}
else
{
return (FlagsLow & mask) == flag;
}
}

public ushort ComponentSizeBits => (ushort)(MTFlags & 0x0000ffff); // only meaningful if HasComponentSize is set

public bool HasComponentSize => GetFlag(WFLAGS_HIGH.HasComponentSize) != 0;
public bool IsInterface => GetFlag(WFLAGS_HIGH.Category_Mask) == WFLAGS_HIGH.Category_Interface;
public bool IsString => HasComponentSize && !IsArray && ComponentSizeBits == 2;
public bool IsArray => GetFlag(WFLAGS_HIGH.Category_Array_Mask) == WFLAGS_HIGH.Category_Array;
public bool IsStringOrArray => HasComponentSize;
public ushort ComponentSize => HasComponentSize ? ComponentSizeBits : (ushort)0;
public bool HasInstantiation => !TestFlagWithMask(WFLAGS_LOW.GenericsMask, WFLAGS_LOW.GenericsMask_NonGeneric);
public bool ContainsGCPointers => GetFlag(WFLAGS_HIGH.ContainsGCPointers) != 0;
public bool IsDynamicStatics => GetFlag(WFLAGS2_ENUM.DynamicStatics) != 0;
}

[Flags]
internal enum EEClassOrCanonMTBits
{
EEClass = 0,
CanonMT = 1,
Mask = 1,
}
}
```

Internally the contract has a `MethodTable_1` struct that depends on the `MethodTable` data descriptor

```csharp
internal struct MethodTable_1
{
internal RuntimeTypeSystem_1.MethodTableFlags Flags { get; }
internal ushort NumInterfaces { get; }
internal ushort NumVirtuals { get; }
internal TargetPointer ParentMethodTable { get; }
internal TargetPointer Module { get; }
internal TargetPointer EEClassOrCanonMT { get; }
internal MethodTable_1(Data.MethodTable data)
{
Flags = new RuntimeTypeSystem_1.MethodTableFlags
{
MTFlags = data.MTFlags,
MTFlags2 = data.MTFlags2,
BaseSize = data.BaseSize,
};
NumInterfaces = data.NumInterfaces;
NumVirtuals = data.NumVirtuals;
EEClassOrCanonMT = data.EEClassOrCanonMT;
Module = data.Module;
ParentMethodTable = data.ParentMethodTable;
}
}
```

The contract depends on the global pointer value `FreeObjectMethodTablePointer`.
The contract additionally depends on the `EEClass` data descriptor.

```csharp
private readonly Dictionary<TargetPointer, MethodTable_1> _methodTables;

internal TargetPointer FreeObjectMethodTablePointer {get; }

public MethodTableHandle GetMethodTableHandle(TargetPointer methodTablePointer)
{
... // validate that methodTablePointer points to something that looks like a MethodTable.
... // read Data.MethodTable from methodTablePointer.
... // create a MethodTable_1 and add it to _methodTables.
return MethodTableHandle { Address = methodTablePointer }
}

internal static EEClassOrCanonMTBits GetEEClassOrCanonMTBits(TargetPointer eeClassOrCanonMTPtr)
{
return (EEClassOrCanonMTBits)(eeClassOrCanonMTPtr & (ulong)EEClassOrCanonMTBits.Mask);
}

public uint GetBaseSize(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.BaseSize;

public uint GetComponentSize(MethodTableHandle methodTableHandle) => GetComponentSize(_methodTables[methodTableHandle.Address]);

private TargetPointer GetClassPointer(MethodTableHandle methodTableHandle)
{
... // if the MethodTable stores a pointer to the EEClass, return it
// otherwise the MethodTable stores a pointer to the canonical MethodTable
// in that case, return the canonical MethodTable's EEClass.
// Canonical MethodTables always store an EEClass pointer.
}

private Data.EEClass GetClassData(MethodTableHandle methodTableHandle)
{
TargetPointer eeClassPtr = GetClassPointer(methodTableHandle);
... // read Data.EEClass data from eeClassPtr
}


public TargetPointer GetCanonicalMethodTable(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).MethodTable;

public TargetPointer GetModule(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Module;
public TargetPointer GetParentMethodTable(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].ParentMethodTable;

public bool IsFreeObjectMethodTable(MethodTableHandle methodTableHandle) => FreeObjectMethodTablePointer == methodTableHandle.Address;

public bool IsString(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.IsString;
public bool ContainsGCPointers(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.ContainsGCPointers;

public uint GetTypeDefToken(MethodTableHandle methodTableHandle)
{
MethodTable_1 methodTable = _methodTables[methodTableHandle.Address];
return (uint)(methodTable.Flags.GetTypeDefRid() | ((int)TableIndex.TypeDef << 24));
}

public ushort GetNumMethods(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).NumMethods;

public ushort GetNumInterfaces(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].NumInterfaces;

public uint GetTypeDefTypeAttributes(MethodTableHandle methodTableHandle) => GetClassData(methodTableHandle).CorTypeAttr;

public bool IsDynamicStatics(MethodTableHandle methodTableHandle) => _methodTables[methodTableHandle.Address].Flags.IsDynamicStatics;
```
Original file line number Diff line number Diff line change
Expand Up @@ -654,7 +654,7 @@ internal unsafe struct MethodTable
private const uint enum_flag_IsByRefLike = 0x00001000;

// WFLAGS_HIGH_ENUM
private const uint enum_flag_ContainsPointers = 0x01000000;
private const uint enum_flag_ContainsGCPointers = 0x01000000;
private const uint enum_flag_ContainsGenericVariables = 0x20000000;
private const uint enum_flag_HasComponentSize = 0x80000000;
private const uint enum_flag_HasTypeEquivalence = 0x02000000;
Expand Down Expand Up @@ -707,7 +707,7 @@ internal unsafe struct MethodTable

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

public bool ContainsGCPointers => (Flags & enum_flag_ContainsPointers) != 0;
public bool ContainsGCPointers => (Flags & enum_flag_ContainsGCPointers) != 0;

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

Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/debug/daccess/dacimpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1232,6 +1232,8 @@ class ClrDataAccess
HRESULT GetThreadDataImpl(CLRDATA_ADDRESS threadAddr, struct DacpThreadData *threadData);
HRESULT GetThreadStoreDataImpl(struct DacpThreadStoreData *data);
HRESULT GetNestedExceptionDataImpl(CLRDATA_ADDRESS exception, CLRDATA_ADDRESS *exceptionObject, CLRDATA_ADDRESS *nextNestedException);
HRESULT GetMethodTableDataImpl(CLRDATA_ADDRESS mt, struct DacpMethodTableData *data);
HRESULT GetMethodTableForEEClassImpl (CLRDATA_ADDRESS eeClassReallyMT, CLRDATA_ADDRESS *value);

BOOL IsExceptionFromManagedCode(EXCEPTION_RECORD * pExceptionRecord);
#ifndef TARGET_UNIX
Expand Down
Loading
Loading