Skip to content

Commit 60b5538

Browse files
authored
[cDAC] Implement IXCLRDataProcess.EnumMethodInstancesByAddress (#115131)
* Implement `IXCLRDataProcess.EnumMethodInstancesByAddress` * Implement `IXCLRDataMethodInstance.GetTokenAndScope` * Implement `IXCLRDataMethodInstance.GetRepresentativeEntryAddress` * Adds to cDAC ILoader and IRuntimeTypeSystem API
1 parent b4408eb commit 60b5538

File tree

37 files changed

+1320
-55
lines changed

37 files changed

+1320
-55
lines changed

docs/design/datacontracts/CodeVersions.md

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ public virtual ILCodeVersionHandle GetILCodeVersion(NativeCodeVersionHandle code
3030
// Return all of the IL code versions for a given method descriptor
3131
public virtual IEnumerable<ILCodeVersionHandle> GetILCodeVersions(TargetPointer methodDesc);
3232

33+
// Return all of the Native code versions for a given ILCodeVersion
34+
public virtual IEnumerable<NativeCodeVersionHandle> GetNativeCodeVersions(TargetPointer methodDesc, ILCodeVersionHandle ilCodeVersionHandle);
3335
// Return a handle to the version of the native code that includes the given instruction pointer
3436
public virtual NativeCodeVersionHandle GetNativeCodeVersionForIP(TargetCodePointer ip);
3537
// Return a handle to the active version of the native code for a given method descriptor and IL code version. The IL code version and method descriptor must represent the same method
@@ -193,18 +195,18 @@ NativeCodeVersionHandle GetSpecificNativeCodeVersion(MethodDescHandle md, Target
193195
return first;
194196
}
195197

196-
return FindFirstCodeVersion(rts, md, (codeVersion) =>
198+
return FindNativeCodeVersionNodes(rts, md, (codeVersion) =>
197199
{
198200
return codeVersion.MethodDesc == md.Address && codeVersion.NativeCode == startAddress;
199-
});
201+
}).FirstOrDefault(NativeCodeVersionHandle.Invalid);
200202
}
201203

202-
NativeCodeVersionHandle FindFirstCodeVersion(IRuntimeTypeSystem rts, MethodDescHandle md, Func<Data.NativeCodeVersionNode, bool> predicate)
204+
IEnumerable<NativeCodeVersionHandle> FindNativeCodeVersionNodes(IRuntimeTypeSystem rts, MethodDescHandle md, Func<Data.NativeCodeVersionNode, bool> predicate)
203205
{
204206
// ImplicitCodeVersion stage of NativeCodeVersionIterator::Next()
205207
TargetPointer versioningStateAddr = rts.GetMethodDescVersioningState(md);
206208
if (versioningStateAddr == TargetPointer.Null)
207-
return NativeCodeVersionHandle.Invalid;
209+
yield break;
208210

209211
Data.MethodDescVersioningState versioningState = _target.ProcessedData.GetOrAdd<Data.MethodDescVersioningState>(versioningStateAddr);
210212

@@ -215,14 +217,45 @@ NativeCodeVersionHandle FindFirstCodeVersion(IRuntimeTypeSystem rts, MethodDescH
215217
Data.NativeCodeVersionNode current = _target.ProcessedData.GetOrAdd<Data.NativeCodeVersionNode>(currentAddress);
216218
if (predicate(current))
217219
{
218-
return NativeCodeVersionHandle.OfExplicit(currentAddress);
220+
yield return NativeCodeVersionHandle.OfExplicit(currentAddress);
219221
}
220222
currentAddress = current.Next;
221223
}
222-
return NativeCodeVersionHandle.Invalid;
224+
yield break;
225+
}
226+
```
227+
228+
### Finding all of the native code versions of an ILCodeVersion for a method descriptor
229+
230+
```csharp
231+
IEnumerable<NativeCodeVersionHandle> ICodeVersions.GetNativeCodeVersions(TargetPointer methodDesc, ILCodeVersionHandle ilCodeVersionHandle)
232+
{
233+
if (!ilCodeVersionHandle.IsValid)
234+
yield break;
235+
236+
if (!ilCodeVersionHandle.IsExplicit)
237+
{
238+
// if the ILCodeVersion is synthetic, then yield the synthetic NativeCodeVersion
239+
NativeCodeVersionHandle provisionalHandle = NativeCodeVersionHandle.CreateSynthetic(methodDesc);
240+
yield return provisionalHandle;
241+
}
242+
243+
// Iterate through versioning state nodes and return the active one, matching any IL code version
244+
Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
245+
MethodDescHandle md = rts.GetMethodDescHandle(methodDesc);
246+
TargetNUInt ilVersionId = GetId(ilCodeVersionHandle);
247+
IEnumerable<NativeCodeVersionHandle> nativeCodeVersions = FindNativeCodeVersionNodes(
248+
rts,
249+
md,
250+
(codeVersion) => ilVersionId == codeVersion.ILVersionId);
251+
foreach (NativeCodeVersionHandle nativeCodeVersion in nativeCodeVersions)
252+
{
253+
yield return nativeCodeVersion;
254+
}
223255
}
224256
```
225257

258+
226259
### Finding the active native code version of an ILCodeVersion for a method descriptor
227260
```csharp
228261
public virtual NativeCodeVersionHandle GetActiveNativeCodeVersionForILCodeVersion(TargetPointer methodDesc, ILCodeVersionHandle ilCodeVersionHandle);

docs/design/datacontracts/Loader.md

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ TargetPointer GetAssembly(ModuleHandle handle);
6262
TargetPointer GetPEAssembly(ModuleHandle handle);
6363
bool TryGetLoadedImageContents(ModuleHandle handle, out TargetPointer baseAddress, out uint size, out uint imageFlags);
6464
bool TryGetSymbolStream(ModuleHandle handle, out TargetPointer buffer, out uint size);
65+
IEnumerable<TargetPointer> GetAvailableTypeParams(ModuleHandle handle);
66+
IEnumerable<TargetPointer> GetInstantiatedMethods(ModuleHandle handle);
67+
6568
bool IsProbeExtensionResultValid(ModuleHandle handle);
6669
ModuleFlags GetFlags(ModuleHandle handle);
6770
string GetPath(ModuleHandle handle);
@@ -92,6 +95,8 @@ TargetPointer GetStubHeap(TargetPointer loaderAllocatorPointer);
9295
| `Module` | `Path` | Path of the Module (UTF-16, null-terminated) |
9396
| `Module` | `FileName` | File name of the Module (UTF-16, null-terminated) |
9497
| `Module` | `GrowableSymbolStream` | Pointer to the in memory symbol stream |
98+
| `Module` | `AvailableTypeParams` | Pointer to an EETypeHashTable |
99+
| `Module` | `InstMethodHashTable` | Pointer to an InstMethodHashTable |
95100
| `Module` | `FieldDefToDescMap` | Mapping table |
96101
| `Module` | `ManifestModuleReferencesMap` | Mapping table |
97102
| `Module` | `MemberRefToDescMap` | Mapping table |
@@ -121,6 +126,7 @@ TargetPointer GetStubHeap(TargetPointer loaderAllocatorPointer);
121126
| `AppDomain` | `RootAssembly` | Pointer to the root assembly |
122127
| `AppDomain` | `DomainAssemblyList` | ArrayListBase of assemblies in the AppDomain |
123128
| `AppDomain` | `FriendlyName` | Friendly name of the AppDomain |
129+
| `SystemDomain` | `GlobalLoaderAllocator` | global LoaderAllocator |
124130
| `LoaderAllocator` | `ReferenceCount` | Reference count of LoaderAllocator |
125131
| `LoaderAllocator` | `HighFrequencyHeap` | High-frequency heap of LoaderAllocator |
126132
| `LoaderAllocator` | `LowFrequencyHeap` | Low-frequency heap of LoaderAllocator |
@@ -130,7 +136,15 @@ TargetPointer GetStubHeap(TargetPointer loaderAllocatorPointer);
130136
| `ArrayListBlock` | `Next` | Next ArrayListBlock in chain |
131137
| `ArrayListBlock` | `Size` | Size of data section in block |
132138
| `ArrayListBlock` | `ArrayStart` | Start of data section in block |
133-
| `SystemDomain` | `GlobalLoaderAllocator` | global LoaderAllocator |
139+
| `EETypeHashTable` | `Buckets` | Pointer to hash table buckets |
140+
| `EETypeHashTable` | `Count` | Count of elements in the hash table |
141+
| `EETypeHashTable` | `VolatileEntryValue` | The data stored in the hash table entry |
142+
| `EETypeHashTable` | `VolatileEntryNextEntry` | Next pointer in the hash table entry |
143+
| `InstMethodHashTable` | `Buckets` | Pointer to hash table buckets |
144+
| `InstMethodHashTable` | `Count` | Count of elements in the hash table |
145+
| `InstMethodHashTable` | `VolatileEntryValue` | The data stored in the hash table entry |
146+
| `InstMethodHashTable` | `VolatileEntryNextEntry` | Next pointer in the hash table entry |
147+
134148

135149

136150
### Global variables used:
@@ -332,6 +346,30 @@ bool TryGetSymbolStream(ModuleHandle handle, out TargetPointer buffer, out uint
332346
return true;
333347
}
334348

349+
IEnumerable<TargetPointer> GetAvailableTypeParams(ModuleHandle handle)
350+
{
351+
TargetPointer availableTypeParams = target.ReadPointer(handle.Address + /* Module::AvailableTypeParams offset */);
352+
353+
if (availableTypeParams == TargetPointer.Null) return [];
354+
355+
// EETypeHashTable is read as a DacEnumerableHash table.
356+
// For more information on how this is read, see section below.
357+
EETypeHashTable typeHashTable = // read EETypeHashTable at availableTypeParams
358+
return typeHashTable.Entries.Select(entry => entry.TypeHandle);
359+
}
360+
361+
IEnumerable<TargetPointer> GetInstantiatedMethods(ModuleHandle handle)
362+
{
363+
TargetPointer instMethodHashTable = target.ReadPointer(handle.Address + /* Module::InstMethodHashTable offset */);
364+
365+
if (instMethodHashTable == TargetPointer.Null) return [];
366+
367+
// InstMethodHashTable is read as a DacEnumerableHash table.
368+
// For more information on how this is read, see section below.
369+
InstMethodHashTable methodHashTable = // read InstMethodHashTable at instMethodHashTable
370+
return methodHashTable.Entries.Select(entry => entry.MethodDesc);
371+
}
372+
335373
bool IsProbeExtensionResultValid(ModuleHandle handle)
336374
{
337375
TargetPointer peAssembly = target.ReadPointer(handle.Address + /* Module::PEAssembly offset */);
@@ -473,3 +511,73 @@ TargetPointer GetStubHeap(TargetPointer loaderAllocatorPointer)
473511
}
474512

475513
```
514+
515+
### DacEnumerableHash (EETypeHashTable and InstMethodHashTable)
516+
517+
Both `EETypeHashTable` and `InstMethodHashTable` are based on the templated `DacEnumerableHash`. Because the base class is templated on the derived type, offsets may be different in derived types.
518+
519+
The base implementation of `DacEnumerableHash` uses four datadescriptors:
520+
| Datadescriptor | Purpose |
521+
| --- | --- |
522+
| `Buckets` | Pointer to the bucket array |
523+
| `Count` | Number of elements in the hash table |
524+
| `VolatileEntryValue` | The data held by an entry, defined by the derived class |
525+
| `VolatileEntryNextEntry` | The next pointer on an hash table entry |
526+
527+
The hash table is laid out as an array of `VolatileEntry` pointers's (buckets), each possibly forming a chain for values that hash into that bucket. The first three buckets are special and reserved for metadata. Instead of containing a `VolatileEntry`, these pointers are read as values with the following meanings.
528+
529+
| Reserved Bucket offset | Purpose |
530+
| --- | --- |
531+
| `0` | Length of the Bucket array, this value does not include the first 3 slots which are special |
532+
| `1` | Pointer to the next bucket array, not currently used in the cDAC |
533+
| `2` | End sentinel for the current bucket array, not currently used in the cDAC |
534+
535+
The current cDAC implementation does not use the 'hash' part of the table at all. Instead it iterates all elements in the table. Following the existing iteration logic in the runtime (and DAC), resizing the table while iterating is not supported. Given this constraint, the pointer to the next bucket array (resized data table) and the current end sentinel are not required to iterate all entries.
536+
537+
To read all entries in the hash table:
538+
1. Read the length bucket to find the number of chains `n`.
539+
2. Initialize a list of elements `entries = []`.
540+
3. For each chain, (buckets with offsets `3..n + 3`):
541+
1. Read the pointer in the bucket as `volatileEntryPtr`.
542+
2. If `volatileEntryPtr & 0x1 == 0x1`, this is an end sentinel and we stop reading this chain.
543+
3. Otherwise, add `volatileEntryPtr + /* VolatileEntryValue offset */` to entries. This points to the derived class defined data type.
544+
4. Set `volatileEntryPtr` to the value of the pointer located at `volatileEntryPtr + /* VolatileEntryNextEntry offset */` and go to step 3.2.
545+
4. Return `entries` to be further parsed by derived classes.
546+
547+
While both EETypeHashTable and InstMethodHashTable store pointer sized data types, they both use the LSBs as special flags.
548+
549+
#### EETypeHashTable
550+
EETypeHashTable uses the LSB to indicate if the TypeHandle is a hot entry. The cDAC implementation separates each value `value` in the table into two parts. The actual TypeHandle pointer and the associated flags.
551+
552+
```csharp
553+
class EETypeHashTable
554+
{
555+
private const ulong FLAG_MASK = 0x1ul;
556+
557+
public IReadOnlyList<Entry> Entires { get; }
558+
559+
public readonly struct Entry(TargetPointer value)
560+
{
561+
public TargetPointer TypeHandle { get; } = value & ~FLAG_MASK;
562+
public uint Flags { get; } = (uint)(value.Value & FLAG_MASK);
563+
}
564+
}
565+
```
566+
567+
#### InstMethodHashTable
568+
InstMethodHashTable uses the 2 LSBs as flags for the MethodDesc. The cDAC implementation separates each value `value` in the table into two parts. The actual MethodDesc pointer and the associated flags.
569+
570+
```csharp
571+
class InstMethodHashTable
572+
{
573+
private const ulong FLAG_MASK = 0x3ul;
574+
575+
public IReadOnlyList<Entry> Entires { get; }
576+
577+
public readonly struct Entry(TargetPointer value)
578+
{
579+
public TargetPointer MethodDesc { get; } = value & ~FLAG_MASK;
580+
public uint Flags { get; } = (uint)(value.Value & FLAG_MASK);
581+
}
582+
}
583+
```

docs/design/datacontracts/RuntimeTypeSystem.md

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ partial interface IRuntimeTypeSystem : IContract
4141
public virtual TargetPointer GetCanonicalMethodTable(TypeHandle typeHandle);
4242
public virtual TargetPointer GetParentMethodTable(TypeHandle typeHandle);
4343

44+
public virtual TargetPointer GetMethodDescForSlot(TypeHandle typeHandle, ushort slot);
45+
4446
public virtual uint GetBaseSize(TypeHandle typeHandle);
4547
// 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)
4648
public virtual uint GetComponentSize(TypeHandle typeHandle);
@@ -349,6 +351,7 @@ The contract additionally depends on these data descriptors
349351
| `MethodTable` | `PerInstInfo` | Either the array element type, or pointer to generic information for `MethodTable` |
350352
| `EEClass` | `InternalCorElementType` | An InternalCorElementType uses the enum values of a CorElementType to indicate some of the information about the type of the type which uses the EEClass In particular, all reference types are CorElementType.Class, Enums are the element type of their underlying type and ValueTypes which can exactly be represented as an element type are represented as such, all other values types are represented as CorElementType.ValueType. |
351353
| `EEClass` | `MethodTable` | Pointer to the canonical MethodTable of this type |
354+
| `EEClass` | `MethodDescChunk` | Pointer to the first MethodDescChunk of the EEClass |
352355
| `EEClass` | `NumMethods` | Count of methods attached to the EEClass |
353356
| `EEClass` | `NumNonVirtualSlots` | Count of non-virtual slots for the EEClass |
354357
| `EEClass` | `CorTypeAttr` | Various flags |
@@ -645,6 +648,8 @@ The version 1 `MethodDesc` APIs depend on the following globals:
645648
| --- | --- |
646649
| `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. |
647650
| `MethodDescTokenRemainderBitCount` | Number of bits in the token remainder in `MethodDesc` |
651+
| `MethodDescSizeTable` | A pointer to the MethodDesc size table. The MethodDesc flags are used as an offset into this table to lookup the MethodDesc size. |
652+
648653

649654
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
650655
will typically have multiple chunks. There are subkinds of MethodDescs at runtime of varying sizes (but the sizes must be mutliples of `MethodDescAlignment`) and each chunk contains method descriptors of the same size.
@@ -684,6 +689,8 @@ The contract depends on the following other contracts
684689
| Loader |
685690
| PlatformMetadata |
686691
| ReJIT |
692+
| ExecutionManager |
693+
| PrecodeStubs |
687694

688695
And the following enumeration definitions
689696

@@ -710,6 +717,7 @@ And the following enumeration definitions
710717
HasNonVtableSlot = 0x0008,
711718
HasMethodImpl = 0x0010,
712719
HasNativeCodeSlot = 0x0020,
720+
HasAsyncMethodData = 0x0040,
713721
// Mask for the above flags
714722
MethodDescAdditionalPointersMask = 0x0038,
715723
#endredion Additional pointers
@@ -887,6 +895,23 @@ And the various apis are implemented with the following algorithms
887895
return 0x06000000 | tokenRange | tokenRemainder;
888896
}
889897

898+
public uint GetMethodDescSize(MethodDescHandle methodDescHandle)
899+
{
900+
MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];
901+
902+
// the runtime generates a table to lookup the size of a MethodDesc based on the flags
903+
// read the location of the table and index into it using certain bits of MethodDesc.Flags
904+
TargetPointer methodDescSizeTable = target.ReadGlobalPointer(Constants.Globals.MethodDescSizeTable);
905+
906+
ushort arrayOffset = (ushort)(methodDesc.Flags & (ushort)(
907+
MethodDescFlags.ClassificationMask |
908+
MethodDescFlags.HasNonVtableSlot |
909+
MethodDescFlags.HasMethodImpl |
910+
MethodDescFlags.HasNativeCodeSlot |
911+
MethodDescFlags.HasAsyncMethodData));
912+
return target.Read<byte>(methodDescSizeTable + arrayOffset);
913+
}
914+
890915
public bool IsArrayMethod(MethodDescHandle methodDescHandle, out ArrayFunctionType functionType)
891916
{
892917
MethodDesc methodDesc = _methodDescs[methodDescHandle.Address];
@@ -1177,3 +1202,85 @@ Getting the native code pointer for methods with a NativeCodeSlot or a stable en
11771202
return GetStableEntryPoint(methodDescHandle.Address, md);
11781203
}
11791204
```
1205+
1206+
Getting a MethodDesc for a certain slot in a MethodTable
1207+
```csharp
1208+
// Based on MethodTable::IntroducedMethodIterator
1209+
private IEnumerable<MethodDescHandle> GetIntroducedMethods(TypeHandle typeHandle)
1210+
{
1211+
// typeHandle must represent a MethodTable
1212+
1213+
EEClass eeClass = GetClassData(typeHandle);
1214+
1215+
// pointer to the first MethodDescChunk
1216+
TargetPointer chunkAddr = eeClass.MethodDescChunk;
1217+
while (chunkAddr != TargetPointer.Null)
1218+
{
1219+
MethodDescChunk chunk = // read Data.MethodDescChunk data from chunkAddr
1220+
TargetPointer methodDescPtr = chunk.FirstMethodDesc;
1221+
1222+
// chunk.Count is the number of MethodDescs in the chunk - 1
1223+
// add 1 to get the actual number of MethodDescs within the chunk
1224+
for (int i = 0; i < chunk.Count + 1; i++)
1225+
{
1226+
MethodDescHandle methodDescHandle = GetMethodDescHandle(methodDescPtr);
1227+
1228+
// increment pointer to the beginning of the next MethodDesc
1229+
methodDescPtr += GetMethodDescSize(methodDescHandle);
1230+
yield return methodDescHandle;
1231+
}
1232+
1233+
// go to the next chunk
1234+
chunkAddr = chunk.Next;
1235+
}
1236+
}
1237+
1238+
private readonly TargetPointer GetMethodDescForEntrypoint(TargetCodePointer pCode)
1239+
{
1240+
// Standard path, ask ExecutionManager for the MethodDesc
1241+
IExecutionManager executionManager = _target.Contracts.ExecutionManager;
1242+
if (executionManager.GetCodeBlockHandle(pCode) is CodeBlockHandle cbh)
1243+
{
1244+
TargetPointer methodDescPtr = executionManager.GetMethodDesc(cbh);
1245+
return methodDescPtr;
1246+
}
1247+
1248+
// Stub path, read address as a Precode and get the MethodDesc from it
1249+
{
1250+
TargetPointer methodDescPtr = _target.Contracts.PrecodeStubs.GetMethodDescFromStubAddress(pCode);
1251+
return methodDescPtr;
1252+
}
1253+
}
1254+
1255+
public TargetPointer GetMethodDescForSlot(TypeHandle methodTable, ushort slot)
1256+
{
1257+
if (!typeHandle.IsMethodTable())
1258+
throw new ArgumentException($"{nameof(typeHandle)} is not a MethodTable");
1259+
1260+
TargetPointer cannonMTPTr = GetCanonicalMethodTable(typeHandle);
1261+
TypeHandle canonMT = GetTypeHandle(cannonMTPTr);
1262+
TargetPointer slotPtr = GetAddressOfSlot(canonMT, slot);
1263+
TargetCodePointer pCode = _target.ReadCodePointer(slotPtr);
1264+
1265+
if (pCode == TargetCodePointer.Null)
1266+
{
1267+
// if pCode is null, we iterate through the method descs in the MT
1268+
while (true) // arbitrary limit to avoid infinite loop
1269+
{
1270+
foreach (MethodDescHandle mdh in GetIntroducedMethods(canonMT))
1271+
{
1272+
MethodDesc md = _methodDescs[mdh.Address];
1273+
1274+
// if a MethodDesc matches the slot, return that MethodDesc
1275+
if (md.Slot == slot)
1276+
{
1277+
return mdh.Address;
1278+
}
1279+
}
1280+
canonMT = GetTypeHandle(GetCanonicalMethodTable(GetTypeHandle(GetParentMethodTable(canonMT))));
1281+
}
1282+
}
1283+
1284+
return GetMethodDescForEntrypoint(pCode);
1285+
}
1286+
```

src/coreclr/debug/daccess/daccess.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4175,7 +4175,7 @@ ClrDataAccess::StartEnumMethodInstancesByAddress(
41754175
goto Exit;
41764176
}
41774177

4178-
if (IsPossibleCodeAddress(taddr) != S_OK)
4178+
if ( (status = IsPossibleCodeAddress(taddr)) != S_OK)
41794179
{
41804180
goto Exit;
41814181
}

0 commit comments

Comments
 (0)