Skip to content

Commit

Permalink
Implementation of SOSDacApi GetMethodDescName for cDAC (dotnet#106169)
Browse files Browse the repository at this point in the history
Add a number of new `MethodDesc` contract definitions

| Contract algorithm on RuntimeTypeSystem |
| --- |
| `IsGenericMethodDefinition`|
|`GetGenericMethodInstantiation`|
|`GetMethodToken`|
|`IsArrayMethod`|
|`IsDynamicMethod`|
|`IsStoredSigMethodDesc`|
|`IsNoMetadataMethod`|
|`IsILStub`|

Update cDAC compat asserts in cDAC to always be enabled by using a tls variable in `mscordaccore`

Implement `GetMethodDescName` on `ISOSDacInterface` in the `cdacreader`

Stub out an implementation of `GetPath` in the `Loader` contract used in a fallback after a fallback. This will need further work, but is included to make sure the code path isn't lost.

Fix the `EcmaMetadataReader` to be able to find blobs in the metadata

Add ability to read target data from a buffer held on the cdac side using the `Target` class. This was needed to handle signature containing a `CorElementType.Internal`.

And finally actually implement the name generation algorithm via a line for line port from the CoreCLR codebase.

Contributes to dotnet#99302
  • Loading branch information
davidwrighton committed Aug 13, 2024
1 parent 34b41f8 commit 272a83e
Show file tree
Hide file tree
Showing 23 changed files with 1,521 additions and 27 deletions.
255 changes: 247 additions & 8 deletions docs/design/datacontracts/RuntimeTypeSystem.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,51 @@ struct MethodDescHandle
}
```

```csharp
public enum ArrayFunctionType
{
Get = 0,
Set = 1,
Address = 2,
Constructor = 3
}
```

```csharp
partial interface IRuntimeTypeSystem : IContract
{
public virtual MethodDescHandle GetMethodDescHandle(TargetPointer methodDescPointer);

public virtual TargetPointer GetMethodTable(MethodDescHandle methodDesc);

// Return true for an uninstantiated generic method
public virtual bool IsGenericMethodDefinition(MethodDescHandle methodDesc);

public virtual ReadOnlySpan<TypeHandle> GetGenericMethodInstantiation(MethodDescHandle methodDesc);

// Return mdTokenNil (0x06000000) if the method doesn't have a token, otherwise return the token of the method
public virtual uint GetMethodToken(MethodDescHandle methodDesc);

// Return true if a MethodDesc represents an array method
// An array method is also a StoredSigMethodDesc
public virtual bool IsArrayMethod(MethodDescHandle methodDesc, out ArrayFunctionType functionType);

// Return true if a MethodDesc represents a method without metadata method, either an IL Stub dynamically
// generated by the runtime, or a MethodDesc that describes a method represented by the System.Reflection.Emit.DynamicMethod class
// Or something else similar.
// A no metadata method is also a StoredSigMethodDesc
public virtual bool IsNoMetadataMethod(MethodDescHandle methodDesc, out ReadOnlySpan<byte> methodName);

// A StoredSigMethodDesc is a MethodDesc for which the signature isn't found in metadata.
public virtual bool IsStoredSigMethodDesc(MethodDescHandle methodDesc, out ReadOnlySpan<byte> signature);

// Return true for a MethodDesc that describes a method represented by the System.Reflection.Emit.DynamicMethod class
// A DynamicMethod is also a StoredSigMethodDesc, and a NoMetadataMethod
public virtual bool IsDynamicMethod(MethodDescHandle methodDesc);

// Return true if a MethodDesc represents an IL Stub dynamically generated by the runtime
// A IL Stub method is also a StoredSigMethodDesc, and a NoMetadataMethod
public virtual bool IsILStub(MethodDescHandle methodDesc);
}
```

Expand Down Expand Up @@ -563,7 +602,8 @@ The version 1 `MethodDesc` APIs depend on the `MethodDescAlignment` global and t

| Global name | Meaning |
| --- | --- |
| `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.
| `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. |
| `MethodDescTokenRemainderBitCount` | Number of bits in the token remainder in `MethodDesc` |


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
Expand All @@ -572,12 +612,211 @@ will typically have multiple chunks. There are subkinds of MethodDescs at runti
We depend on the following data descriptors:
| Data Descriptor Name | Field | Meaning |
| --- | --- | --- |
| `MethodDesc` | `ChunkIndex` | Offset of this `MethodDesc` relative to the end of its containing `MethodDescChunk` - in multiples of `MethodDescAlignment`
| `MethodDesc` | `Slot` | The method's slot
| `MethodDesc` | `Flags` | The method's flags
| `MethodDescChunk` | `MethodTable` | The method table set of methods belongs to
| `MethodDescChunk` | `Next` | The next chunk of methods
| `MethodDescChunk` | `Size` | The size of this `MethodDescChunk` following this `MethodDescChunk` header, minus 1. In multiples of `MethodDescAlignment`
| `MethodDescChunk` | `Count` | The number of `MethodDesc` entries in this chunk, minus 1.
| `MethodDesc` | `ChunkIndex` | Offset of this `MethodDesc` relative to the end of its containing `MethodDescChunk` - in multiples of `MethodDescAlignment` |
| `MethodDesc` | `Slot` | The method's slot |
| `MethodDesc` | `Flags` | The method's flags |
| `MethodDesc` | `Flags3AndTokenRemainder` | More flags for the method, and the low bits of the method's token's RID |
| `MethodDescChunk` | `MethodTable` | The method table set of methods belongs to |
| `MethodDescChunk` | `Next` | The next chunk of methods |
| `MethodDescChunk` | `Size` | The size of this `MethodDescChunk` following this `MethodDescChunk` header, minus 1. In multiples of `MethodDescAlignment` |
| `MethodDescChunk` | `Count` | The number of `MethodDesc` entries in this chunk, minus 1. |
| `MethodDescChunk` | `FlagsAndTokenRange` | `MethodDescChunk` flags, and the upper bits of the method token's RID |
| `InstantiatedMethodDesc` | `PerInstInfo` | The pointer to the method's type arguments |
| `InstantiatedMethodDesc` | `Flags2` | Flags for the `InstantiatedMethodDesc` |
| `InstantiatedMethodDesc` | `NumGenericArgs` | How many generic args the method has |
| `StoredSigMethodDesc` | `Sig` | Pointer to a metadata signature |
| `StoredSigMethodDesc` | `cSig` | Count of bytes in the metadata signature |
| `StoredSigMethodDesc` | `ExtendedFlags` | Flags field for the `StoredSigMethodDesc` |
| `DynamicMethodDesc` | `MethodName` | Pointer to Null-terminated UTF8 string describing the Method desc |


And the following enumeration definitions

```csharp
internal enum MethodDescClassification
{
IL = 0, // IL
FCall = 1, // FCall (also includes tlbimped ctor, Delegate ctor)
PInvoke = 2, // PInvoke method
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)
ComInterop = 6,
Dynamic = 7, // for method desc with no metadata behind
}

[Flags]
internal enum MethodDescFlags : ushort
{
ClassificationMask = 0x7,
HasNonVtableSlot = 0x0008,
}

internal enum InstantiatedMethodDescFlags2 : ushort
{
KindMask = 0x07,
GenericMethodDefinition = 0x01,
UnsharedMethodInstantiation = 0x02,
SharedMethodInstantiation = 0x03,
WrapperStubWithInstantiations = 0x04,
}

[Flags]
internal enum DynamicMethodDescExtendedFlags : uint
{
IsLCGMethod = 0x00004000,
IsILStub = 0x00008000,
}
```


And the various apis are implemented with the following algorithms

```csharp
public bool IsGenericMethodDefinition(MethodDescHandle methodDescHandle)
{
MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];

if (methodDesc.Classification != MethodDescClassification.Instantiated)
return false;

ushort Flags2 = // Read Flags2 field from InstantiatedMethodDesc contract using address methodDescHandle.Address
return ((int)Flags2 & (int)InstantiatedMethodDescFlags2.KindMask) == (int)InstantiatedMethodDescFlags2.GenericMethodDefinition;
}

public ReadOnlySpan<TypeHandle> GetGenericMethodInstantiation(MethodDescHandle methodDescHandle)
{
MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];

if (methodDesc.Classification != MethodDescClassification.Instantiated)
return default;

TargetPointer dictionaryPointer = // Read PerInstInfo field from InstantiatedMethodDesc contract using address methodDescHandle.Address
if (dictionaryPointer == 0)
return default;

int NumTypeArgs = // Read NumGenericArgs from methodDescHandle.Address using InstantiatedMethodDesc contract
TypeHandle[] instantiation = new TypeHandle[NumTypeArgs];
for (int i = 0; i < NumTypeArgs; i++)
instantiation[i] = GetTypeHandle(_target.ReadPointer(dictionaryPointer + _target.PointerSize * i));

return instantiation;
}

public uint GetMethodToken(MethodDescHandle methodDescHandle)
{
MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];

TargetPointer methodDescChunk = // Using ChunkIndex from methodDesc, compute the wrapping MethodDescChunk
ushort Flags3AndTokenRemainder = // Read Flags3AndTokenRemainder field from MethodDesc contract using address methodDescHandle.Address
ushort FlagsAndTokenRange = // Read FlagsAndTokenRange field from MethodDescChunk contract using address methodDescChunk
int tokenRemainderBitCount = _target.ReadGlobal<byte>(Constants.Globals.MethodDescTokenRemainderBitCount);
int tokenRangeBitCount = 24 - tokenRemainderBitCount;
uint allRidBitsSet = 0xFFFFFF;
uint tokenRemainderMask = allRidBitsSet >> tokenRangeBitCount;
uint tokenRangeMask = allRidBitsSet >> tokenRemainderBitCount;

uint tokenRemainder = (uint)(_desc.Flags3AndTokenRemainder & tokenRemainderMask);
uint tokenRange = ((uint)(_chunk.FlagsAndTokenRange & tokenRangeMask)) << tokenRemainderBitCount;

return 0x06000000 | tokenRange | tokenRemainder;
}

public bool IsArrayMethod(MethodDescHandle methodDescHandle, out ArrayFunctionType functionType)
{
MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];

if (methodDesc.Classification != MethodDescClassification.Array)
{
functionType = default;
return false;
}

int arrayMethodIndex = methodDesc.Slot - GetNumVtableSlots(GetTypeHandle(methodDesc.MethodTable));

functionType = arrayMethodIndex switch
{
0 => ArrayFunctionType.Get,
1 => ArrayFunctionType.Set,
2 => ArrayFunctionType.Address,
> 3 => ArrayFunctionType.Constructor,
_ => throw new InvalidOperationException()
};

return true;
}

public bool IsNoMetadataMethod(MethodDescHandle methodDescHandle, out ReadOnlySpan<byte> methodName)
{
MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];

if (methodDesc.Classification != MethodDescClassification.Dynamic)
{
methodName = default;
return false;
}

TargetPointer methodNamePointer = // Read MethodName field from DynamicMethodDesc contract using address methodDescHandle.Address
methodName = // ReadBuffer from target of a utf8 null terminated string, starting at address methodNamePointer
return true;
}

public bool IsStoredSigMethodDesc(MethodDescHandle methodDescHandle, out ReadOnlySpan<byte> signature)
{
MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];

switch (methodDesc.Classification)
{
case MethodDescClassification.Dynamic:
case MethodDescClassification.EEImpl:
case MethodDescClassification.Array:
break; // These have stored sigs
default:
signature = default;
return false;
}

TargetPointer Sig = // Read Sig field from StoredSigMethodDesc contract using address methodDescHandle.Address
uint cSig = // Read cSig field from StoredSigMethodDesc contract using address methodDescHandle.Address
TargetPointer methodNamePointer = // Read S field from DynamicMethodDesc contract using address methodDescHandle.Address
signature = // Read buffer from target memory starting at address Sig, with cSig bytes in it.
return true;
}

public bool IsDynamicMethod(MethodDescHandle methodDescHandle)
{
MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];

if (methodDesc.Classification != MethodDescClassification.Dynamic)
{
return false;
}

uint ExtendedFlags = // Read ExtendedFlags field from StoredSigMethodDesc contract using address methodDescHandle.Address
return ((DynamicMethodDescExtendedFlags)ExtendedFlags).HasFlag(DynamicMethodDescExtendedFlags.IsLCGMethod);
}

public bool IsILStub(MethodDescHandle methodDescHandle)
{
MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];

if (methodDesc.Classification != MethodDescClassification.Dynamic)
{
return false;
}

uint ExtendedFlags = // Read ExtendedFlags field from StoredSigMethodDesc contract using address methodDescHandle.Address
return ((DynamicMethodDescExtendedFlags)ExtendedFlags).HasFlag(DynamicMethodDescExtendedFlags.IsILStub);
}
```
**TODO(cdac)**
13 changes: 13 additions & 0 deletions src/coreclr/debug/daccess/dacfn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1386,6 +1386,15 @@ DacAllocHostOnlyInstance(ULONG32 size, bool throwEx)
return inst + 1;
}

thread_local bool t_DacAssertsUnconditionally = false;

bool DacSetEnableDacAssertsUnconditionally(bool enable)
{
bool oldValue = t_DacAssertsUnconditionally;
t_DacAssertsUnconditionally = enable;
return oldValue;
}

//
// Queries whether ASSERTs should be raised when inconsistencies in the target are detected
//
Expand All @@ -1404,6 +1413,10 @@ bool DacTargetConsistencyAssertsEnabled()
return true;
}

// If asserts are unconditionally enabled via the thread local, simply return true.
if (t_DacAssertsUnconditionally)
return true;

return g_dacImpl->TargetConsistencyAssertsEnabled();
}

Expand Down
1 change: 1 addition & 0 deletions src/coreclr/debug/daccess/dacimpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1245,6 +1245,7 @@ class ClrDataAccess
HRESULT GetObjectStringDataImpl(CLRDATA_ADDRESS obj, unsigned int count, _Inout_updates_z_(count) WCHAR *stringData, unsigned int *pNeeded);
HRESULT GetUsefulGlobalsImpl(struct DacpUsefulGlobalsData *globalsData);
HRESULT GetMethodDescDataImpl(CLRDATA_ADDRESS methodDesc, CLRDATA_ADDRESS ip, struct DacpMethodDescData *data, ULONG cRevertedRejitVersions, DacpReJitData * rgRevertedRejitData, ULONG * pcNeededRevertedRejitData);
HRESULT GetMethodDescNameImpl(CLRDATA_ADDRESS methodDesc, unsigned int count, _Inout_updates_z_(count) WCHAR *name, unsigned int *pNeeded);

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

0 comments on commit 272a83e

Please sign in to comment.