diff --git a/docs/design/datacontracts/CodeVersions.md b/docs/design/datacontracts/CodeVersions.md index 241b17bbc60c21..dec5833b3f4636 100644 --- a/docs/design/datacontracts/CodeVersions.md +++ b/docs/design/datacontracts/CodeVersions.md @@ -30,6 +30,8 @@ public virtual ILCodeVersionHandle GetILCodeVersion(NativeCodeVersionHandle code // Return all of the IL code versions for a given method descriptor public virtual IEnumerable GetILCodeVersions(TargetPointer methodDesc); +// Return all of the Native code versions for a given ILCodeVersion +public virtual IEnumerable GetNativeCodeVersions(TargetPointer methodDesc, ILCodeVersionHandle ilCodeVersionHandle); // Return a handle to the version of the native code that includes the given instruction pointer public virtual NativeCodeVersionHandle GetNativeCodeVersionForIP(TargetCodePointer ip); // 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 return first; } - return FindFirstCodeVersion(rts, md, (codeVersion) => + return FindNativeCodeVersionNodes(rts, md, (codeVersion) => { return codeVersion.MethodDesc == md.Address && codeVersion.NativeCode == startAddress; - }); + }).FirstOrDefault(NativeCodeVersionHandle.Invalid); } -NativeCodeVersionHandle FindFirstCodeVersion(IRuntimeTypeSystem rts, MethodDescHandle md, Func predicate) +IEnumerable FindNativeCodeVersionNodes(IRuntimeTypeSystem rts, MethodDescHandle md, Func predicate) { // ImplicitCodeVersion stage of NativeCodeVersionIterator::Next() TargetPointer versioningStateAddr = rts.GetMethodDescVersioningState(md); if (versioningStateAddr == TargetPointer.Null) - return NativeCodeVersionHandle.Invalid; + yield break; Data.MethodDescVersioningState versioningState = _target.ProcessedData.GetOrAdd(versioningStateAddr); @@ -215,14 +217,45 @@ NativeCodeVersionHandle FindFirstCodeVersion(IRuntimeTypeSystem rts, MethodDescH Data.NativeCodeVersionNode current = _target.ProcessedData.GetOrAdd(currentAddress); if (predicate(current)) { - return NativeCodeVersionHandle.OfExplicit(currentAddress); + yield return NativeCodeVersionHandle.OfExplicit(currentAddress); } currentAddress = current.Next; } - return NativeCodeVersionHandle.Invalid; + yield break; +} +``` + +### Finding all of the native code versions of an ILCodeVersion for a method descriptor + +```csharp +IEnumerable ICodeVersions.GetNativeCodeVersions(TargetPointer methodDesc, ILCodeVersionHandle ilCodeVersionHandle) +{ + if (!ilCodeVersionHandle.IsValid) + yield break; + + if (!ilCodeVersionHandle.IsExplicit) + { + // if the ILCodeVersion is synthetic, then yield the synthetic NativeCodeVersion + NativeCodeVersionHandle provisionalHandle = NativeCodeVersionHandle.CreateSynthetic(methodDesc); + yield return provisionalHandle; + } + + // Iterate through versioning state nodes and return the active one, matching any IL code version + Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + MethodDescHandle md = rts.GetMethodDescHandle(methodDesc); + TargetNUInt ilVersionId = GetId(ilCodeVersionHandle); + IEnumerable nativeCodeVersions = FindNativeCodeVersionNodes( + rts, + md, + (codeVersion) => ilVersionId == codeVersion.ILVersionId); + foreach (NativeCodeVersionHandle nativeCodeVersion in nativeCodeVersions) + { + yield return nativeCodeVersion; + } } ``` + ### Finding the active native code version of an ILCodeVersion for a method descriptor ```csharp public virtual NativeCodeVersionHandle GetActiveNativeCodeVersionForILCodeVersion(TargetPointer methodDesc, ILCodeVersionHandle ilCodeVersionHandle); diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index e8a8461e710e26..be25e4cba9787d 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -62,6 +62,9 @@ TargetPointer GetAssembly(ModuleHandle handle); TargetPointer GetPEAssembly(ModuleHandle handle); bool TryGetLoadedImageContents(ModuleHandle handle, out TargetPointer baseAddress, out uint size, out uint imageFlags); bool TryGetSymbolStream(ModuleHandle handle, out TargetPointer buffer, out uint size); +IEnumerable GetAvailableTypeParams(ModuleHandle handle); +IEnumerable GetInstantiatedMethods(ModuleHandle handle); + bool IsProbeExtensionResultValid(ModuleHandle handle); ModuleFlags GetFlags(ModuleHandle handle); string GetPath(ModuleHandle handle); @@ -92,6 +95,8 @@ TargetPointer GetStubHeap(TargetPointer loaderAllocatorPointer); | `Module` | `Path` | Path of the Module (UTF-16, null-terminated) | | `Module` | `FileName` | File name of the Module (UTF-16, null-terminated) | | `Module` | `GrowableSymbolStream` | Pointer to the in memory symbol stream | +| `Module` | `AvailableTypeParams` | Pointer to an EETypeHashTable | +| `Module` | `InstMethodHashTable` | Pointer to an InstMethodHashTable | | `Module` | `FieldDefToDescMap` | Mapping table | | `Module` | `ManifestModuleReferencesMap` | Mapping table | | `Module` | `MemberRefToDescMap` | Mapping table | @@ -121,6 +126,7 @@ TargetPointer GetStubHeap(TargetPointer loaderAllocatorPointer); | `AppDomain` | `RootAssembly` | Pointer to the root assembly | | `AppDomain` | `DomainAssemblyList` | ArrayListBase of assemblies in the AppDomain | | `AppDomain` | `FriendlyName` | Friendly name of the AppDomain | +| `SystemDomain` | `GlobalLoaderAllocator` | global LoaderAllocator | | `LoaderAllocator` | `ReferenceCount` | Reference count of LoaderAllocator | | `LoaderAllocator` | `HighFrequencyHeap` | High-frequency heap of LoaderAllocator | | `LoaderAllocator` | `LowFrequencyHeap` | Low-frequency heap of LoaderAllocator | @@ -130,7 +136,15 @@ TargetPointer GetStubHeap(TargetPointer loaderAllocatorPointer); | `ArrayListBlock` | `Next` | Next ArrayListBlock in chain | | `ArrayListBlock` | `Size` | Size of data section in block | | `ArrayListBlock` | `ArrayStart` | Start of data section in block | -| `SystemDomain` | `GlobalLoaderAllocator` | global LoaderAllocator | +| `EETypeHashTable` | `Buckets` | Pointer to hash table buckets | +| `EETypeHashTable` | `Count` | Count of elements in the hash table | +| `EETypeHashTable` | `VolatileEntryValue` | The data stored in the hash table entry | +| `EETypeHashTable` | `VolatileEntryNextEntry` | Next pointer in the hash table entry | +| `InstMethodHashTable` | `Buckets` | Pointer to hash table buckets | +| `InstMethodHashTable` | `Count` | Count of elements in the hash table | +| `InstMethodHashTable` | `VolatileEntryValue` | The data stored in the hash table entry | +| `InstMethodHashTable` | `VolatileEntryNextEntry` | Next pointer in the hash table entry | + ### Global variables used: @@ -332,6 +346,30 @@ bool TryGetSymbolStream(ModuleHandle handle, out TargetPointer buffer, out uint return true; } +IEnumerable GetAvailableTypeParams(ModuleHandle handle) +{ + TargetPointer availableTypeParams = target.ReadPointer(handle.Address + /* Module::AvailableTypeParams offset */); + + if (availableTypeParams == TargetPointer.Null) return []; + + // EETypeHashTable is read as a DacEnumerableHash table. + // For more information on how this is read, see section below. + EETypeHashTable typeHashTable = // read EETypeHashTable at availableTypeParams + return typeHashTable.Entries.Select(entry => entry.TypeHandle); +} + +IEnumerable GetInstantiatedMethods(ModuleHandle handle) +{ + TargetPointer instMethodHashTable = target.ReadPointer(handle.Address + /* Module::InstMethodHashTable offset */); + + if (instMethodHashTable == TargetPointer.Null) return []; + + // InstMethodHashTable is read as a DacEnumerableHash table. + // For more information on how this is read, see section below. + InstMethodHashTable methodHashTable = // read InstMethodHashTable at instMethodHashTable + return methodHashTable.Entries.Select(entry => entry.MethodDesc); +} + bool IsProbeExtensionResultValid(ModuleHandle handle) { TargetPointer peAssembly = target.ReadPointer(handle.Address + /* Module::PEAssembly offset */); @@ -473,3 +511,73 @@ TargetPointer GetStubHeap(TargetPointer loaderAllocatorPointer) } ``` + +### DacEnumerableHash (EETypeHashTable and InstMethodHashTable) + +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. + +The base implementation of `DacEnumerableHash` uses four datadescriptors: +| Datadescriptor | Purpose | +| --- | --- | +| `Buckets` | Pointer to the bucket array | +| `Count` | Number of elements in the hash table | +| `VolatileEntryValue` | The data held by an entry, defined by the derived class | +| `VolatileEntryNextEntry` | The next pointer on an hash table entry | + +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. + +| Reserved Bucket offset | Purpose | +| --- | --- | +| `0` | Length of the Bucket array, this value does not include the first 3 slots which are special | +| `1` | Pointer to the next bucket array, not currently used in the cDAC | +| `2` | End sentinel for the current bucket array, not currently used in the cDAC | + +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. + +To read all entries in the hash table: +1. Read the length bucket to find the number of chains `n`. +2. Initialize a list of elements `entries = []`. +3. For each chain, (buckets with offsets `3..n + 3`): + 1. Read the pointer in the bucket as `volatileEntryPtr`. + 2. If `volatileEntryPtr & 0x1 == 0x1`, this is an end sentinel and we stop reading this chain. + 3. Otherwise, add `volatileEntryPtr + /* VolatileEntryValue offset */` to entries. This points to the derived class defined data type. + 4. Set `volatileEntryPtr` to the value of the pointer located at `volatileEntryPtr + /* VolatileEntryNextEntry offset */` and go to step 3.2. +4. Return `entries` to be further parsed by derived classes. + +While both EETypeHashTable and InstMethodHashTable store pointer sized data types, they both use the LSBs as special flags. + +#### EETypeHashTable +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. + +```csharp +class EETypeHashTable +{ + private const ulong FLAG_MASK = 0x1ul; + + public IReadOnlyList Entires { get; } + + public readonly struct Entry(TargetPointer value) + { + public TargetPointer TypeHandle { get; } = value & ~FLAG_MASK; + public uint Flags { get; } = (uint)(value.Value & FLAG_MASK); + } +} +``` + +#### InstMethodHashTable +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. + +```csharp +class InstMethodHashTable +{ + private const ulong FLAG_MASK = 0x3ul; + + public IReadOnlyList Entires { get; } + + public readonly struct Entry(TargetPointer value) + { + public TargetPointer MethodDesc { get; } = value & ~FLAG_MASK; + public uint Flags { get; } = (uint)(value.Value & FLAG_MASK); + } +} +``` diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index 54a542056108a4..45d1f2107eb105 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -41,6 +41,8 @@ partial interface IRuntimeTypeSystem : IContract public virtual TargetPointer GetCanonicalMethodTable(TypeHandle typeHandle); public virtual TargetPointer GetParentMethodTable(TypeHandle typeHandle); + public virtual TargetPointer GetMethodDescForSlot(TypeHandle typeHandle, ushort slot); + public virtual uint GetBaseSize(TypeHandle typeHandle); // 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(TypeHandle typeHandle); @@ -349,6 +351,7 @@ The contract additionally depends on these data descriptors | `MethodTable` | `PerInstInfo` | Either the array element type, or pointer to generic information for `MethodTable` | | `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. | | `EEClass` | `MethodTable` | Pointer to the canonical MethodTable of this type | +| `EEClass` | `MethodDescChunk` | Pointer to the first MethodDescChunk of the EEClass | | `EEClass` | `NumMethods` | Count of methods attached to the EEClass | | `EEClass` | `NumNonVirtualSlots` | Count of non-virtual slots for the EEClass | | `EEClass` | `CorTypeAttr` | Various flags | @@ -645,6 +648,8 @@ The version 1 `MethodDesc` APIs depend on the following globals: | --- | --- | | `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` | +| `MethodDescSizeTable` | A pointer to the MethodDesc size table. The MethodDesc flags are used as an offset into this table to lookup the MethodDesc size. | + 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 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 | Loader | | PlatformMetadata | | ReJIT | +| ExecutionManager | +| PrecodeStubs | And the following enumeration definitions @@ -710,6 +717,7 @@ And the following enumeration definitions HasNonVtableSlot = 0x0008, HasMethodImpl = 0x0010, HasNativeCodeSlot = 0x0020, + HasAsyncMethodData = 0x0040, // Mask for the above flags MethodDescAdditionalPointersMask = 0x0038, #endredion Additional pointers @@ -887,6 +895,23 @@ And the various apis are implemented with the following algorithms return 0x06000000 | tokenRange | tokenRemainder; } + public uint GetMethodDescSize(MethodDescHandle methodDescHandle) + { + MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; + + // the runtime generates a table to lookup the size of a MethodDesc based on the flags + // read the location of the table and index into it using certain bits of MethodDesc.Flags + TargetPointer methodDescSizeTable = target.ReadGlobalPointer(Constants.Globals.MethodDescSizeTable); + + ushort arrayOffset = (ushort)(methodDesc.Flags & (ushort)( + MethodDescFlags.ClassificationMask | + MethodDescFlags.HasNonVtableSlot | + MethodDescFlags.HasMethodImpl | + MethodDescFlags.HasNativeCodeSlot | + MethodDescFlags.HasAsyncMethodData)); + return target.Read(methodDescSizeTable + arrayOffset); + } + public bool IsArrayMethod(MethodDescHandle methodDescHandle, out ArrayFunctionType functionType) { MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; @@ -1177,3 +1202,85 @@ Getting the native code pointer for methods with a NativeCodeSlot or a stable en return GetStableEntryPoint(methodDescHandle.Address, md); } ``` + +Getting a MethodDesc for a certain slot in a MethodTable +```csharp + // Based on MethodTable::IntroducedMethodIterator + private IEnumerable GetIntroducedMethods(TypeHandle typeHandle) + { + // typeHandle must represent a MethodTable + + EEClass eeClass = GetClassData(typeHandle); + + // pointer to the first MethodDescChunk + TargetPointer chunkAddr = eeClass.MethodDescChunk; + while (chunkAddr != TargetPointer.Null) + { + MethodDescChunk chunk = // read Data.MethodDescChunk data from chunkAddr + TargetPointer methodDescPtr = chunk.FirstMethodDesc; + + // chunk.Count is the number of MethodDescs in the chunk - 1 + // add 1 to get the actual number of MethodDescs within the chunk + for (int i = 0; i < chunk.Count + 1; i++) + { + MethodDescHandle methodDescHandle = GetMethodDescHandle(methodDescPtr); + + // increment pointer to the beginning of the next MethodDesc + methodDescPtr += GetMethodDescSize(methodDescHandle); + yield return methodDescHandle; + } + + // go to the next chunk + chunkAddr = chunk.Next; + } + } + + private readonly TargetPointer GetMethodDescForEntrypoint(TargetCodePointer pCode) + { + // Standard path, ask ExecutionManager for the MethodDesc + IExecutionManager executionManager = _target.Contracts.ExecutionManager; + if (executionManager.GetCodeBlockHandle(pCode) is CodeBlockHandle cbh) + { + TargetPointer methodDescPtr = executionManager.GetMethodDesc(cbh); + return methodDescPtr; + } + + // Stub path, read address as a Precode and get the MethodDesc from it + { + TargetPointer methodDescPtr = _target.Contracts.PrecodeStubs.GetMethodDescFromStubAddress(pCode); + return methodDescPtr; + } + } + + public TargetPointer GetMethodDescForSlot(TypeHandle methodTable, ushort slot) + { + if (!typeHandle.IsMethodTable()) + throw new ArgumentException($"{nameof(typeHandle)} is not a MethodTable"); + + TargetPointer cannonMTPTr = GetCanonicalMethodTable(typeHandle); + TypeHandle canonMT = GetTypeHandle(cannonMTPTr); + TargetPointer slotPtr = GetAddressOfSlot(canonMT, slot); + TargetCodePointer pCode = _target.ReadCodePointer(slotPtr); + + if (pCode == TargetCodePointer.Null) + { + // if pCode is null, we iterate through the method descs in the MT + while (true) // arbitrary limit to avoid infinite loop + { + foreach (MethodDescHandle mdh in GetIntroducedMethods(canonMT)) + { + MethodDesc md = _methodDescs[mdh.Address]; + + // if a MethodDesc matches the slot, return that MethodDesc + if (md.Slot == slot) + { + return mdh.Address; + } + } + canonMT = GetTypeHandle(GetCanonicalMethodTable(GetTypeHandle(GetParentMethodTable(canonMT)))); + } + } + + return GetMethodDescForEntrypoint(pCode); + } +``` diff --git a/src/coreclr/debug/daccess/daccess.cpp b/src/coreclr/debug/daccess/daccess.cpp index bbc26dc6d13235..1282ed4e1ea4a0 100644 --- a/src/coreclr/debug/daccess/daccess.cpp +++ b/src/coreclr/debug/daccess/daccess.cpp @@ -4175,7 +4175,7 @@ ClrDataAccess::StartEnumMethodInstancesByAddress( goto Exit; } - if (IsPossibleCodeAddress(taddr) != S_OK) + if ( (status = IsPossibleCodeAddress(taddr)) != S_OK) { goto Exit; } diff --git a/src/coreclr/vm/class.h b/src/coreclr/vm/class.h index 64c5b59746eb25..d815934d0ea390 100644 --- a/src/coreclr/vm/class.h +++ b/src/coreclr/vm/class.h @@ -1800,6 +1800,7 @@ template<> struct cdac_data static constexpr size_t InternalCorElementType = offsetof(EEClass, m_NormType); static constexpr size_t MethodTable = offsetof(EEClass, m_pMethodTable); static constexpr size_t FieldDescList = offsetof(EEClass, m_pFieldDescList); + static constexpr size_t MethodDescChunk = offsetof(EEClass, m_pChunks); static constexpr size_t NumMethods = offsetof(EEClass, m_NumMethods); static constexpr size_t CorTypeAttr = offsetof(EEClass, m_dwAttrClass); static constexpr size_t NumInstanceFields = offsetof(EEClass, m_NumInstanceFields); diff --git a/src/coreclr/vm/dacenumerablehash.h b/src/coreclr/vm/dacenumerablehash.h index dda541ed951bf4..a1295cccc14b29 100644 --- a/src/coreclr/vm/dacenumerablehash.h +++ b/src/coreclr/vm/dacenumerablehash.h @@ -55,7 +55,7 @@ // // Synchronization: It is permissable to read data from the hash without taking a lock as long as: // 1) Any hash modifications are performed under a lock or otherwise serialized. -// 2) Any miss on a lookup is handled by taking a lock are retry-ing the lookup. +// 2) Any miss on a lookup is handled by taking a lock and retry-ing the lookup. // // OVERALL DESIGN // @@ -314,7 +314,7 @@ class DacEnumerableHashTable static const int SLOT_ENDSENTINEL = 2; // normal slots start at slot #3 static const int SKIP_SPECIAL_SLOTS = 3; - + static DWORD GetLength(DPTR(PTR_VolatileEntry) buckets) { return (DWORD)dac_cast(buckets[SLOT_LENGTH]); @@ -335,6 +335,8 @@ class DacEnumerableHashTable DPTR(PTR_VolatileEntry) m_pBuckets; // Pointer to a simple bucket list (array of VolatileEntry pointers) DWORD m_cEntries; // Count of elements + + friend struct ::cdac_data; }; #endif // __DAC_ENUMERABLE_HASH_INCLUDED diff --git a/src/coreclr/vm/datadescriptor/contracts.jsonc b/src/coreclr/vm/datadescriptor/contracts.jsonc index 81b28cd2e1fe92..de820476c734dd 100644 --- a/src/coreclr/vm/datadescriptor/contracts.jsonc +++ b/src/coreclr/vm/datadescriptor/contracts.jsonc @@ -11,7 +11,7 @@ { "CodeVersions": 1, "DacStreams": 1, - "EcmaMetadata" : 1, + "EcmaMetadata": 1, "Exception": 1, "ExecutionManager": 2, "Loader": 1, @@ -24,4 +24,4 @@ "StackWalk": 1, "StressLog": 2, "Thread": 1 -} +} \ No newline at end of file diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 3d8559f5f3e92d..934bee1662b4d6 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -135,6 +135,8 @@ CDAC_TYPE_FIELD(Module, /*pointer*/, Path, cdac_data::Path) CDAC_TYPE_FIELD(Module, /*pointer*/, FileName, cdac_data::FileName) CDAC_TYPE_FIELD(Module, /*pointer*/, ReadyToRunInfo, cdac_data::ReadyToRunInfo) CDAC_TYPE_FIELD(Module, /*pointer*/, GrowableSymbolStream, cdac_data::GrowableSymbolStream) +CDAC_TYPE_FIELD(Module, /*pointer*/, AvailableTypeParams, offsetof(Module, m_pAvailableParamTypes)) +CDAC_TYPE_FIELD(Module, /*pointer*/, InstMethodHashTable, offsetof(Module, m_pInstMethodHashTable)) CDAC_TYPE_FIELD(Module, /*pointer*/, FieldDefToDescMap, cdac_data::FieldDefToDescMap) CDAC_TYPE_FIELD(Module, /*pointer*/, ManifestModuleReferencesMap, cdac_data::ManifestModuleReferencesMap) @@ -255,6 +257,7 @@ CDAC_TYPE_END(MethodTableAuxiliaryData) CDAC_TYPE_BEGIN(EEClass) CDAC_TYPE_INDETERMINATE(EEClass) CDAC_TYPE_FIELD(EEClass, /*pointer*/, MethodTable, cdac_data::MethodTable) +CDAC_TYPE_FIELD(EEClass, /*pointer*/, MethodDescChunk, cdac_data::MethodDescChunk) CDAC_TYPE_FIELD(EEClass, /*uint16*/, NumMethods, cdac_data::NumMethods) CDAC_TYPE_FIELD(EEClass, /*pointer*/, FieldDescList, cdac_data::FieldDescList) CDAC_TYPE_FIELD(EEClass, /*uint32*/, CorTypeAttr, cdac_data::CorTypeAttr) @@ -822,6 +825,22 @@ CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, Lr, offsetof(CalleeSavedRegiste #endif // Platform switch CDAC_TYPE_END(CalleeSavedRegisters) +CDAC_TYPE_BEGIN(EETypeHashTable) +CDAC_TYPE_INDETERMINATE(EETypeHashTable) +CDAC_TYPE_FIELD(EETypeHashTable, /*pointer*/, Buckets, cdac_data::Buckets) +CDAC_TYPE_FIELD(EETypeHashTable, /*uint32*/, Count, cdac_data::Count) +CDAC_TYPE_FIELD(EETypeHashTable, /*pointer*/, VolatileEntryValue, cdac_data::VolatileEntryValue) +CDAC_TYPE_FIELD(EETypeHashTable, /*pointer*/, VolatileEntryNextEntry, cdac_data::VolatileEntryNextEntry) +CDAC_TYPE_END(EETypeHashTable) + +CDAC_TYPE_BEGIN(InstMethodHashTable) +CDAC_TYPE_INDETERMINATE(InstMethodHashTable) +CDAC_TYPE_FIELD(InstMethodHashTable, /*pointer*/, Buckets, cdac_data::Buckets) +CDAC_TYPE_FIELD(InstMethodHashTable, /*uint32*/, Count, cdac_data::Count) +CDAC_TYPE_FIELD(InstMethodHashTable, /*pointer*/, VolatileEntryValue, cdac_data::VolatileEntryValue) +CDAC_TYPE_FIELD(InstMethodHashTable, /*pointer*/, VolatileEntryNextEntry, cdac_data::VolatileEntryNextEntry) +CDAC_TYPE_END(InstMethodHashTable) + CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() @@ -919,4 +938,6 @@ CDAC_GLOBAL(StressLogEnabled, uint8, 0) CDAC_GLOBAL_POINTER(ExecutionManagerCodeRangeMapAddress, cdac_data::CodeRangeMapAddress) CDAC_GLOBAL_POINTER(PlatformMetadata, &::g_cdacPlatformMetadata) CDAC_GLOBAL_POINTER(ProfilerControlBlock, &::g_profControlBlock) +CDAC_GLOBAL_POINTER(MethodDescSizeTable, &MethodDesc::s_ClassificationSizeTable) + CDAC_GLOBALS_END() diff --git a/src/coreclr/vm/instmethhash.h b/src/coreclr/vm/instmethhash.h index dc77d892ee63fc..ef59081fc2a947 100644 --- a/src/coreclr/vm/instmethhash.h +++ b/src/coreclr/vm/instmethhash.h @@ -14,6 +14,7 @@ #define _INSTMETHHASH_H #include "dacenumerablehash.h" +#include "cdacdata.h" class AllocMemTracker; @@ -145,6 +146,18 @@ class InstMethodHashTable : public DacEnumerableHashTable; +}; + +template<> +struct cdac_data +{ + static constexpr size_t Buckets = offsetof(InstMethodHashTable, m_pBuckets); + static constexpr size_t Count = offsetof(InstMethodHashTable, m_cEntries); + + static constexpr size_t VolatileEntryValue = offsetof(InstMethodHashTable::VolatileEntry, m_sValue); + static constexpr size_t VolatileEntryNextEntry = offsetof(InstMethodHashTable::VolatileEntry, m_pNextEntry); }; #endif /* _INSTMETHHASH_H */ diff --git a/src/coreclr/vm/typehash.h b/src/coreclr/vm/typehash.h index 86a97b888dd2c8..23dc5d9e364823 100644 --- a/src/coreclr/vm/typehash.h +++ b/src/coreclr/vm/typehash.h @@ -10,6 +10,7 @@ #define _TYPE_HASH_H #include "dacenumerablehash.h" +#include "cdacdata.h" //======================================================================================== // This hash table is used by class loaders to look up constructed types: @@ -136,6 +137,18 @@ class EETypeHashTable : public DacEnumerableHashTable; +}; + +template<> +struct cdac_data +{ + static constexpr size_t Buckets = offsetof(EETypeHashTable, m_pBuckets); + static constexpr size_t Count = offsetof(EETypeHashTable, m_cEntries); + + static constexpr size_t VolatileEntryValue = offsetof(EETypeHashTable::VolatileEntry, m_sValue); + static constexpr size_t VolatileEntryNextEntry = offsetof(EETypeHashTable::VolatileEntry, m_pNextEntry); }; #endif /* _TYPE_HASH_H */ diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs index 53faad2d53f99b..9056a9002a90e5 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs @@ -14,7 +14,7 @@ public abstract class ContractRegistry /// /// Gets an instance of the Exception contract for the target. /// - public abstract IException Exception { get;} + public abstract IException Exception { get; } /// /// Gets an instance of the Loader contract for the target. /// diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/Extensions/ICodeVersionsExtensions.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/Extensions/ICodeVersionsExtensions.cs index e207e101606146..61a4f8a81f4c06 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/Extensions/ICodeVersionsExtensions.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/Extensions/ICodeVersionsExtensions.cs @@ -10,4 +10,22 @@ public static NativeCodeVersionHandle GetActiveNativeCodeVersion(this ICodeVersi ILCodeVersionHandle ilCodeVersionHandle = cv.GetActiveILCodeVersion(methodDesc); return cv.GetActiveNativeCodeVersionForILCodeVersion(methodDesc, ilCodeVersionHandle); } + + public static TargetCodePointer GetNativeCodeAnyVersion(this ICodeVersions cv, TargetPointer methodDesc) + { + foreach (ILCodeVersionHandle ilCodeVersionHandle in cv.GetILCodeVersions(methodDesc)) + { + foreach (NativeCodeVersionHandle nativeCodeVersionHandle in cv.GetNativeCodeVersions(methodDesc, ilCodeVersionHandle)) + { + if (cv.GetNativeCode(nativeCodeVersionHandle) != TargetCodePointer.Null) + { + return cv.GetNativeCode(nativeCodeVersionHandle); + } + } + } + return TargetCodePointer.Null; + } + + public static bool HasNativeCodeAnyVersion(this ICodeVersions cv, TargetPointer methodDesc) + => cv.GetNativeCodeAnyVersion(methodDesc) != TargetCodePointer.Null; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ICodeVersions.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ICodeVersions.cs index 921d37583d8607..3817368120e7fc 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ICodeVersions.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ICodeVersions.cs @@ -16,6 +16,8 @@ public interface ICodeVersions : IContract public virtual IEnumerable GetILCodeVersions(TargetPointer methodDesc) => throw new NotImplementedException(); + public virtual IEnumerable GetNativeCodeVersions(TargetPointer methodDesc, ILCodeVersionHandle ilCodeVersionHandle) => throw new NotImplementedException(); + public virtual NativeCodeVersionHandle GetNativeCodeVersionForIP(TargetCodePointer ip) => throw new NotImplementedException(); public virtual NativeCodeVersionHandle GetActiveNativeCodeVersionForILCodeVersion(TargetPointer methodDesc, ILCodeVersionHandle ilCodeVersionHandle) => throw new NotImplementedException(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs index 1c81dadcbe496c..8822dfee7ba384 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs @@ -85,6 +85,9 @@ public interface ILoader : IContract TargetPointer GetPEAssembly(ModuleHandle handle) => throw new NotImplementedException(); bool TryGetLoadedImageContents(ModuleHandle handle, out TargetPointer baseAddress, out uint size, out uint imageFlags) => throw new NotImplementedException(); bool TryGetSymbolStream(ModuleHandle handle, out TargetPointer buffer, out uint size) => throw new NotImplementedException(); + IEnumerable GetAvailableTypeParams(ModuleHandle handle) => throw new NotImplementedException(); + IEnumerable GetInstantiatedMethods(ModuleHandle handle) => throw new NotImplementedException(); + bool IsProbeExtensionResultValid(ModuleHandle handle) => throw new NotImplementedException(); ModuleFlags GetFlags(ModuleHandle handle) => throw new NotImplementedException(); string GetPath(ModuleHandle handle) => throw new NotImplementedException(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs index 0fb29e318c1d42..0712f3f929f918 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs @@ -88,6 +88,8 @@ public interface IRuntimeTypeSystem : IContract TargetPointer GetCanonicalMethodTable(TypeHandle typeHandle) => throw new NotImplementedException(); TargetPointer GetParentMethodTable(TypeHandle typeHandle) => throw new NotImplementedException(); + TargetPointer GetMethodDescForSlot(TypeHandle methodTable, ushort slot) => throw new NotImplementedException(); + uint GetBaseSize(TypeHandle typeHandle) => throw new NotImplementedException(); // 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) uint GetComponentSize(TypeHandle typeHandle) => throw new NotImplementedException(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs index 98ead3efdd2fdf..0a8ddc1b5cf24a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -103,6 +103,8 @@ public enum DataType GCCoverageInfo, ArrayListBase, ArrayListBlock, + EETypeHashTable, + InstMethodHashTable, TransitionBlock, DebuggerEval, diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs index c069b1fb1c8ccf..39b338570245d8 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs @@ -140,6 +140,14 @@ public abstract class Target /// Value read from the target public abstract T Read(ulong address) where T : unmanaged, IBinaryInteger, IMinMaxValue; + /// + /// Read a value from the target in target endianness + /// + /// Type of value to read + /// Address to start reading from + /// True if read succeeds, false otherwise. + public abstract bool TryRead(ulong address, out T value) where T : unmanaged, IBinaryInteger, IMinMaxValue; + /// /// Write a value to the target in target endianness /// @@ -235,16 +243,16 @@ public readonly record struct FieldInfo /// /// The byte offset of the field in an instance of the type in the target process /// - public int Offset {get; init; } + public int Offset { get; init; } /// /// The well known data type of the field in the target process /// - public readonly DataType Type {get; init;} + public readonly DataType Type { get; init; } /// /// The name of the well known data type of the field in the target process, or null /// if the target data descriptor did not record a name /// - public readonly string? TypeName {get; init; } + public readonly string? TypeName { get; init; } } /// diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs index c5a2dbacecb8e0..3ea5c5fa948371 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs @@ -61,6 +61,8 @@ public static class Globals public const string PlatformMetadata = nameof(PlatformMetadata); public const string ProfilerControlBlock = nameof(ProfilerControlBlock); + public const string MethodDescSizeTable = nameof(MethodDescSizeTable); + public const string HashMapSlotsPerBucket = nameof(HashMapSlotsPerBucket); public const string HashMapValueMask = nameof(HashMapValueMask); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersions_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersions_1.cs index 9f0211efc7bcad..29be26f871c9f7 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersions_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersions_1.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using Microsoft.Diagnostics.DataContractReader.Data; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -88,6 +89,32 @@ IEnumerable ICodeVersions.GetILCodeVersions(TargetPointer m } } + IEnumerable ICodeVersions.GetNativeCodeVersions(TargetPointer methodDesc, ILCodeVersionHandle ilCodeVersionHandle) + { + if (!ilCodeVersionHandle.IsValid) + yield break; + + if (!ilCodeVersionHandle.IsExplicit) + { + // if the ILCodeVersion is synthetic, then yield the synthetic NativeCodeVersion + NativeCodeVersionHandle provisionalHandle = NativeCodeVersionHandle.CreateSynthetic(methodDesc); + yield return provisionalHandle; + } + + // Iterate through versioning state nodes and return the active one, matching any IL code version + Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + MethodDescHandle md = rts.GetMethodDescHandle(methodDesc); + TargetNUInt ilVersionId = GetId(ilCodeVersionHandle); + IEnumerable nativeCodeVersions = FindNativeCodeVersionNodes( + rts, + md, + (codeVersion) => ilVersionId == codeVersion.ILVersionId); + foreach (NativeCodeVersionHandle nativeCodeVersion in nativeCodeVersions) + { + yield return nativeCodeVersion; + } + } + NativeCodeVersionHandle ICodeVersions.GetNativeCodeVersionForIP(TargetCodePointer ip) { // ExecutionManager::GetNativeCodeVersion(PCODE ip)) @@ -176,11 +203,11 @@ NativeCodeVersionHandle ICodeVersions.GetActiveNativeCodeVersionForILCodeVersion Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; MethodDescHandle md = rts.GetMethodDescHandle(methodDesc); TargetNUInt ilVersionId = GetId(ilCodeVersionHandle); - return FindFirstCodeVersion(rts, md, (codeVersion) => + return FindNativeCodeVersionNodes(rts, md, (codeVersion) => { return (ilVersionId == codeVersion.ILVersionId) && ((NativeCodeVersionNodeFlags)codeVersion.Flags).HasFlag(NativeCodeVersionNodeFlags.IsActiveChild); - }); + }).FirstOrDefault(NativeCodeVersionHandle.Invalid); } TargetPointer ICodeVersions.GetGCStressCodeCopy(NativeCodeVersionHandle codeVersionHandle) @@ -224,18 +251,18 @@ private NativeCodeVersionHandle GetSpecificNativeCodeVersion(IRuntimeTypeSystem } // CodeVersionManager::GetNativeCodeVersion(PTR_MethodDesc, PCODE startAddress) - return FindFirstCodeVersion(rts, md, (codeVersion) => + return FindNativeCodeVersionNodes(rts, md, (codeVersion) => { return codeVersion.MethodDesc == md.Address && codeVersion.NativeCode == startAddress; - }); + }).FirstOrDefault(NativeCodeVersionHandle.Invalid); } - private NativeCodeVersionHandle FindFirstCodeVersion(IRuntimeTypeSystem rts, MethodDescHandle md, Func predicate) + private IEnumerable FindNativeCodeVersionNodes(IRuntimeTypeSystem rts, MethodDescHandle md, Func predicate) { // ImplicitCodeVersion stage of NativeCodeVersionIterator::Next() TargetPointer versioningStateAddr = rts.GetMethodDescVersioningState(md); if (versioningStateAddr == TargetPointer.Null) - return NativeCodeVersionHandle.Invalid; + yield break; Data.MethodDescVersioningState versioningState = _target.ProcessedData.GetOrAdd(versioningStateAddr); @@ -246,11 +273,11 @@ private NativeCodeVersionHandle FindFirstCodeVersion(IRuntimeTypeSystem rts, Met Data.NativeCodeVersionNode current = _target.ProcessedData.GetOrAdd(currentAddress); if (predicate(current)) { - return NativeCodeVersionHandle.CreateExplicit(currentAddress); + yield return NativeCodeVersionHandle.CreateExplicit(currentAddress); } currentAddress = current.Next; } - return NativeCodeVersionHandle.Invalid; + yield break; } private enum ILCodeVersionKind diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs index 7ceed03f5ac6e1..2d2562625e4688 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Microsoft.Diagnostics.DataContractReader.Data; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -213,6 +214,29 @@ bool ILoader.TryGetSymbolStream(ModuleHandle handle, out TargetPointer buffer, o return true; } + IEnumerable ILoader.GetAvailableTypeParams(ModuleHandle handle) + { + Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); + + if (module.AvailableTypeParams == TargetPointer.Null) + return []; + + EETypeHashTable typeHashTable = _target.ProcessedData.GetOrAdd(module.AvailableTypeParams); + return typeHashTable.Entries.Select(entry => entry.TypeHandle); + } + + IEnumerable ILoader.GetInstantiatedMethods(ModuleHandle handle) + { + Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); + + if (module.InstMethodHashTable == TargetPointer.Null) + return []; + + InstMethodHashTable methodHashTable = _target.ProcessedData.GetOrAdd(module.InstMethodHashTable); + + return methodHashTable.Entries.Select(entry => entry.MethodDesc); + } + bool ILoader.IsProbeExtensionResultValid(ModuleHandle handle) { Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index 7386d59628fcbd..3dc92c47021640 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -87,7 +87,7 @@ internal enum MethodDescChunkFlags : ushort // Has this chunk had its methods been determined eligible for tiered compilation or not DeterminedIsEligibleForTieredCompilation = 0x4000, // Is this chunk associated with a LoaderModule directly? If this flag is set, then the LoaderModule pointer is placed at the end of the chunk. - LoaderModuleAttachedToChunk = 0x8000, + LoaderModuleAttachedToChunk = 0x8000, } internal struct MethodDesc @@ -109,11 +109,13 @@ internal MethodDesc(Target target, TargetPointer methodDescPointer, Data.MethodD Address = methodDescPointer; Token = ComputeToken(target, desc, chunk); + Size = ComputeSize(target, desc); } public TargetPointer MethodTable => _chunk.MethodTable; public ushort Slot => _desc.Slot; public uint Token { get; } + public uint Size { get; } private static uint ComputeToken(Target target, Data.MethodDesc desc, Data.MethodDescChunk chunk) { @@ -128,6 +130,21 @@ private static uint ComputeToken(Target target, Data.MethodDesc desc, Data.Metho return EcmaMetadataUtils.CreateMethodDef(tokenRange | tokenRemainder); } + private static uint ComputeSize(Target target, Data.MethodDesc desc) + { + // Size of the MethodDesc is variable, read it from the targets lookup table + // See MethodDesc::SizeOf in method.cpp for details + TargetPointer methodDescSizeTable = target.ReadGlobalPointer(Constants.Globals.MethodDescSizeTable); + + ushort arrayOffset = (ushort)(desc.Flags & (ushort)( + MethodDescFlags_1.MethodDescFlags.ClassificationMask | + MethodDescFlags_1.MethodDescFlags.HasNonVtableSlot | + MethodDescFlags_1.MethodDescFlags.HasMethodImpl | + MethodDescFlags_1.MethodDescFlags.HasNativeCodeSlot | + MethodDescFlags_1.MethodDescFlags.HasAsyncMethodData)); + return target.Read(methodDescSizeTable + arrayOffset); + } + public MethodClassification Classification => (MethodClassification)((int)_desc.Flags & (int)MethodDescFlags_1.MethodDescFlags.ClassificationMask); private bool HasFlags(MethodDescFlags_1.MethodDescFlags flags) => (_desc.Flags & (ushort)flags) != 0; @@ -356,7 +373,7 @@ private TargetPointer GetClassPointer(TypeHandle typeHandle) case MethodTableFlags_1.EEClassOrCanonMTBits.EEClass: return methodTable.EEClassOrCanonMT; case MethodTableFlags_1.EEClassOrCanonMTBits.CanonMT: - TargetPointer canonMTPtr =MethodTableFlags_1.UntagEEClassOrCanonMT(methodTable.EEClassOrCanonMT); + TargetPointer canonMTPtr = MethodTableFlags_1.UntagEEClassOrCanonMT(methodTable.EEClassOrCanonMT); TypeHandle canonMTHandle = GetTypeHandle(canonMTPtr); MethodTable canonMT = _methodTables[canonMTHandle.Address]; return canonMT.EEClassOrCanonMT; // canonical method table EEClassOrCanonMT is always EEClass @@ -612,30 +629,27 @@ private ushort GetNumVtableSlots(TypeHandle typeHandle) } public MethodDescHandle GetMethodDescHandle(TargetPointer methodDescPointer) + => GetMethodDescHandle(methodDescPointer, validate: true); + + private MethodDescHandle GetMethodDescHandle(TargetPointer methodDescPointer, bool validate) { - // if we already validated this address, return a handle + // if we already have a method desc at this address, return a handle if (_methodDescs.ContainsKey(methodDescPointer)) { return new MethodDescHandle(methodDescPointer); } - // Check if we cached the underlying data already - if (_target.ProcessedData.TryGet(methodDescPointer, out Data.MethodDesc? methodDescData)) + + TargetPointer methodDescChunkPointer; + if (validate) { - // we already cached the data, we must have validated the address, create the representation struct for our use - TargetPointer mdescChunkPtr = _methodValidation.GetMethodDescChunkPointerThrowing(methodDescPointer, methodDescData); - // FIXME[cdac]: this isn't threadsafe - if (!_target.ProcessedData.TryGet(mdescChunkPtr, out Data.MethodDescChunk? methodDescChunkData)) + if (!_methodValidation.ValidateMethodDescPointer(methodDescPointer, out methodDescChunkPointer)) { - throw new InvalidOperationException("cached MethodDesc data but not its containing MethodDescChunk"); + throw new ArgumentException("Invalid method desc pointer", nameof(methodDescPointer)); } - MethodDesc validatedMethodDesc = new MethodDesc(_target, methodDescPointer, methodDescData, mdescChunkPtr, methodDescChunkData); - _ = _methodDescs.TryAdd(methodDescPointer, validatedMethodDesc); - return new MethodDescHandle(methodDescPointer); } - - if (!_methodValidation.ValidateMethodDescPointer(methodDescPointer, out TargetPointer methodDescChunkPointer)) + else { - throw new ArgumentException("Invalid method desc pointer", nameof(methodDescPointer)); + methodDescChunkPointer = _methodValidation.GetMethodDescChunkPointerThrowing(methodDescPointer, _target.ProcessedData.GetOrAdd(methodDescPointer)); } // ok, we validated it, cache the data and add the MethodDesc struct to the dictionary @@ -965,6 +979,82 @@ bool IRuntimeTypeSystem.HasNativeCodeSlot(MethodDescHandle methodDesc) return md.HasNativeCodeSlot; } + // Based on MethodTable::IntroducedMethodIterator + private IEnumerable GetIntroducedMethods(TypeHandle typeHandle) + { + Debug.Assert(typeHandle.IsMethodTable()); + + EEClass eeClass = GetClassData(typeHandle); + + TargetPointer chunkAddr = eeClass.MethodDescChunk; + while (chunkAddr != TargetPointer.Null) + { + MethodDescChunk chunk = _target.ProcessedData.GetOrAdd(chunkAddr); + TargetPointer methodDescPtr = chunk.FirstMethodDesc; + // chunk.Count is the number of MethodDescs in the chunk - 1 + for (int i = 0; i < chunk.Count + 1; i++) + { + // Validation of some MethodDescs fails in heap dumps due to missing memory. + // Skipping validation should be okay as the pointers come from the target. + MethodDescHandle methodDescHandle = GetMethodDescHandle(methodDescPtr, validate: false); + MethodDesc md = _methodDescs[methodDescHandle.Address]; + methodDescPtr += md.Size; + yield return methodDescHandle; + } + + chunkAddr = chunk.Next; + } + } + + TargetPointer IRuntimeTypeSystem.GetMethodDescForSlot(TypeHandle typeHandle, ushort slot) + { + // based on MethodTable::GetMethodDescForSlot_NoThrow + if (!typeHandle.IsMethodTable()) + throw new ArgumentException($"{nameof(typeHandle)} is not a MethodTable"); + + TargetPointer cannonMTPTr = GetCanonicalMethodTable(typeHandle); + TypeHandle canonMT = GetTypeHandle(cannonMTPTr); + TargetPointer slotPtr = GetAddressOfSlot(canonMT, slot); + TargetCodePointer pCode = _target.ReadCodePointer(slotPtr); + + if (pCode == TargetCodePointer.Null) + { + while (canonMT.Address != TargetPointer.Null) + { + // if pCode is null, we iterate through the method descs in the MT. + foreach (MethodDescHandle mdh in GetIntroducedMethods(canonMT)) + { + MethodDesc md = _methodDescs[mdh.Address]; + if (md.Slot == slot) + { + return mdh.Address; + } + } + canonMT = GetTypeHandle(GetCanonicalMethodTable(GetTypeHandle(GetParentMethodTable(canonMT)))); + } + Debug.Fail("We should never reach here, as there should always be a MethodDesc for a slot"); + } + + return GetMethodDescForEntrypoint(pCode); + } + + private readonly TargetPointer GetMethodDescForEntrypoint(TargetCodePointer pCode) + { + // standard path, ask ExecutionManager for the MethodDesc + IExecutionManager executionManager = _target.Contracts.ExecutionManager; + if (executionManager.GetCodeBlockHandle(pCode) is CodeBlockHandle cbh) + { + TargetPointer methodDescPtr = executionManager.GetMethodDesc(cbh); + return methodDescPtr; + } + + // stub path, read address as a Precode and read MethodDesc from it + { + TargetPointer methodDescPtr = _target.Contracts.PrecodeStubs.GetMethodDescFromStubAddress(pCode); + return methodDescPtr; + } + } + TargetPointer IRuntimeTypeSystem.GetAddressOfNativeCodeSlot(MethodDescHandle methodDesc) { MethodDesc md = _methodDescs[methodDesc.Address]; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/DacEnumerableHash.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/DacEnumerableHash.cs new file mode 100644 index 00000000000000..248eb1fcc74ad5 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/DacEnumerableHash.cs @@ -0,0 +1,98 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Diagnostics; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +/// +/// Parses hash tables that are implemented by DacEnumerableHash defined in dacenumerablehash.h +/// Requires the following datadescriptor fields on the passed type: +/// Buckets - Pointer to array of VolatileEntry pointers +/// Count - Count of elements +/// VolatileEntryValue - Offset of the value in the VolatileEntry struct +/// VolatileEntryNextEntry - Offset of the next entry pointer in the VolatileEntry struct +/// +internal sealed class DacEnumerableHash +{ + private const int SLOT_LENGTH = 0; + private const int SKIP_SPECIAL_SLOTS = 3; + private const int END_SENTINEL = 0x1; + + private readonly Target _target; + private readonly Target.TypeInfo _type; + + public DacEnumerableHash(Target target, TargetPointer address, Target.TypeInfo type) + { + // init fields + _target = target; + _type = type; + + Buckets = _target.ReadPointer(address + (ulong)_type.Fields[nameof(Buckets)].Offset); + Count = _target.Read(address + (ulong)_type.Fields[nameof(Count)].Offset); + + // read items in the hash table + uint length = GetLength(); + + List entries = []; + for (int i = 0; i < length; i++) + { + // indexes 0, 1, 2 have special purposes. buckets start at SKIP_SPECIAL_SLOTS + int bucketOffset = i + SKIP_SPECIAL_SLOTS; + TargetPointer chainElement = _target.ReadPointer(Buckets + (ulong)(bucketOffset * _target.PointerSize)); + List elements = ReadChain(chainElement); + entries.AddRange(elements); + } + + Debug.Assert(Count == entries.Count); + + Entries = entries; + } + + public TargetPointer Buckets { get; init; } + public uint Count { get; init; } + + public IReadOnlyList Entries { get; init; } + + internal sealed class VolatileEntry + { + public VolatileEntry(Target target, TargetPointer address, Target.TypeInfo type) + { + // offsets are stored on the parent type + VolatileEntryValue = address + (ulong)type.Fields[nameof(VolatileEntryValue)].Offset; + VolatileEntryNextEntry = target.ReadPointer(address + (ulong)type.Fields[nameof(VolatileEntryNextEntry)].Offset); + } + + public TargetPointer VolatileEntryValue { get; init; } + public TargetPointer VolatileEntryNextEntry { get; init; } + } + + private uint GetLength() + { + // First pointer is a size_t length + TargetPointer length = _target.ReadPointer(Buckets + (ulong)(SLOT_LENGTH * _target.PointerSize)); + return (uint)length; + } + + + private static bool IsEndSentinel(TargetPointer value) + { + return ((ulong)value & END_SENTINEL) == END_SENTINEL; + } + + private List ReadChain(TargetPointer chainElement) + { + List elements = []; + + while (!IsEndSentinel(chainElement)) + { + VolatileEntry volatileEntry = new(_target, chainElement, _type); + elements.Add(volatileEntry.VolatileEntryValue); + + chainElement = volatileEntry.VolatileEntryNextEntry; + } + + return elements; + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEClass.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEClass.cs index 6936bf50f8ba2e..b203760f644cae 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEClass.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEClass.cs @@ -11,6 +11,7 @@ public EEClass(Target target, TargetPointer address) Target.TypeInfo type = target.GetTypeInfo(DataType.EEClass); MethodTable = target.ReadPointer(address + (ulong)type.Fields[nameof(MethodTable)].Offset); + MethodDescChunk = target.ReadPointer(address + (ulong)type.Fields[nameof(MethodDescChunk)].Offset); NumMethods = target.Read(address + (ulong)type.Fields[nameof(NumMethods)].Offset); CorTypeAttr = target.Read(address + (ulong)type.Fields[nameof(CorTypeAttr)].Offset); InternalCorElementType = target.Read(address + (ulong)type.Fields[nameof(InternalCorElementType)].Offset); @@ -22,6 +23,7 @@ public EEClass(Target target, TargetPointer address) } public TargetPointer MethodTable { get; init; } + public TargetPointer MethodDescChunk { get; init; } public ushort NumMethods { get; init; } public uint CorTypeAttr { get; init; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EETypeHashTable.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EETypeHashTable.cs new file mode 100644 index 00000000000000..f96645d2d4409a --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EETypeHashTable.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class EETypeHashTable : IData +{ + private const ulong FLAG_MASK = 0x1ul; + + static EETypeHashTable IData.Create(Target target, TargetPointer address) => new EETypeHashTable(target, address); + public EETypeHashTable(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.EETypeHashTable); + + DacEnumerableHash baseHashTable = new(target, address, type); + + List entries = []; + foreach (TargetPointer entry in baseHashTable.Entries) + { + TargetPointer typeHandle = target.ReadPointer(entry); + entries.Add(new(typeHandle)); + } + Entries = entries; + } + + public IReadOnlyList Entries { get; init; } + + public readonly struct Entry(TargetPointer value) + { + public TargetPointer TypeHandle { get; } = value & ~FLAG_MASK; + public uint Flags { get; } = (uint)(value.Value & FLAG_MASK); + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/InstMethodHashTable.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/InstMethodHashTable.cs new file mode 100644 index 00000000000000..a1b36a5617cde4 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/InstMethodHashTable.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class InstMethodHashTable : IData +{ + private const ulong FLAG_MASK = 0x3ul; + + static InstMethodHashTable IData.Create(Target target, TargetPointer address) => new InstMethodHashTable(target, address); + public InstMethodHashTable(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.InstMethodHashTable); + + DacEnumerableHash baseHashTable = new(target, address, type); + + List entries = []; + foreach (TargetPointer entry in baseHashTable.Entries) + { + TargetPointer methodDescPtr = target.ReadPointer(entry); + entries.Add(new(methodDescPtr)); + } + Entries = entries; + } + + public IReadOnlyList Entries { get; init; } + + public readonly struct Entry(TargetPointer value) + { + public TargetPointer MethodDesc { get; } = value & ~FLAG_MASK; + public uint Flags { get; } = (uint)(value.Value & FLAG_MASK); + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDescChunk.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDescChunk.cs index 028050c40d9d42..a157f76e2c31fc 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDescChunk.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDescChunk.cs @@ -17,6 +17,9 @@ public MethodDescChunk(Target target, TargetPointer address) Size = target.Read(address + (ulong)type.Fields[nameof(Size)].Offset); Count = target.Read(address + (ulong)type.Fields[nameof(Count)].Offset); FlagsAndTokenRange = target.Read(address + (ulong)type.Fields[nameof(FlagsAndTokenRange)].Offset); + + // The first MethodDesc is at the end of the MethodDescChunk + FirstMethodDesc = address + type.Size!.Value; } public TargetPointer MethodTable { get; init; } @@ -24,4 +27,6 @@ public MethodDescChunk(Target target, TargetPointer address) public byte Size { get; init; } public byte Count { get; init; } public ushort FlagsAndTokenRange { get; init; } + + public TargetPointer FirstMethodDesc { get; init; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs index c9b986a20c135d..3fe2a7fbb0274e 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs @@ -24,6 +24,8 @@ public Module(Target target, TargetPointer address) FileName = target.ReadPointer(address + (ulong)type.Fields[nameof(FileName)].Offset); ReadyToRunInfo = target.ReadPointer(address + (ulong)type.Fields[nameof(ReadyToRunInfo)].Offset); GrowableSymbolStream = target.ReadPointer(address + (ulong)type.Fields[nameof(GrowableSymbolStream)].Offset); + AvailableTypeParams = target.ReadPointer(address + (ulong)type.Fields[nameof(AvailableTypeParams)].Offset); + InstMethodHashTable = target.ReadPointer(address + (ulong)type.Fields[nameof(InstMethodHashTable)].Offset); FieldDefToDescMap = address + (ulong)type.Fields[nameof(FieldDefToDescMap)].Offset; ManifestModuleReferencesMap = address + (ulong)type.Fields[nameof(ManifestModuleReferencesMap)].Offset; @@ -44,6 +46,8 @@ public Module(Target target, TargetPointer address) public TargetPointer FileName { get; init; } public TargetPointer ReadyToRunInfo { get; init; } public TargetPointer GrowableSymbolStream { get; init; } + public TargetPointer AvailableTypeParams { get; init; } + public TargetPointer InstMethodHashTable { get; init; } public TargetPointer FieldDefToDescMap { get; init; } public TargetPointer ManifestModuleReferencesMap { get; init; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescFlags_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescFlags_1.cs index 89988f355a39ad..901d9331c8938f 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescFlags_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescFlags_1.cs @@ -15,6 +15,7 @@ internal enum MethodDescFlags : ushort HasNonVtableSlot = 0x0008, HasMethodImpl = 0x0010, HasNativeCodeSlot = 0x0020, + HasAsyncMethodData = 0x040, #endregion Optional slots } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs index 7b339281951cbe..91448e65c86d61 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs @@ -24,7 +24,8 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG { _target = target; _tryGetContractVersion = tryGetContractVersion; - _factories = new() { + _factories = new() + { [typeof(IException)] = new ExceptionFactory(), [typeof(ILoader)] = new LoaderFactory(), [typeof(IEcmaMetadata)] = new EcmaMetadataFactory(), diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs index f73f955a128e46..87c1325fe0db11 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs @@ -301,6 +301,22 @@ public override T Read(ulong address) return value; } + /// + /// Read a value from the target in target endianness + /// + /// Type of value to read + /// Address to start reading from + /// True if read succeeds, false otherwise. + public override bool TryRead(ulong address, out T value) + { + value = default; + if (!TryRead(address, _config.IsLittleEndian, _dataTargetDelegates, out T readValue)) + return false; + + value = readValue; + return true; + } + private static bool TryRead(ulong address, bool isLittleEndian, DataTargetDelegates dataTargetDelegates, out T value) where T : unmanaged, IBinaryInteger, IMinMaxValue { value = default; @@ -636,7 +652,7 @@ public override TypeInfo GetTypeInfo(DataType type) public Target.TypeInfo GetTypeInfo(string type) { if (_types.TryGetValue(type, out Target.TypeInfo typeInfo)) - return typeInfo; + return typeInfo; DataType dataType = GetDataType(type); if (dataType is not DataType.Unknown) diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs new file mode 100644 index 00000000000000..27cabfe18834b9 --- /dev/null +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataMethodInstance.cs @@ -0,0 +1,185 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; +using Microsoft.Diagnostics.DataContractReader.Contracts; + +namespace Microsoft.Diagnostics.DataContractReader.Legacy; + +[GeneratedComClass] +internal sealed unsafe partial class ClrDataMethodInstance : IXCLRDataMethodInstance +{ + private readonly Target _target; + private readonly MethodDescHandle _methodDesc; + private readonly TargetPointer _appDomain; + private readonly IXCLRDataMethodInstance? _legacyImpl; + public ClrDataMethodInstance( + Target target, + MethodDescHandle methodDesc, + TargetPointer appDomain, + IXCLRDataMethodInstance? legacyImpl) + { + _target = target; + _methodDesc = methodDesc; + _appDomain = appDomain; + _legacyImpl = legacyImpl; + } + + int IXCLRDataMethodInstance.GetTypeInstance(void** typeInstance) + => _legacyImpl is not null ? _legacyImpl.GetTypeInstance(typeInstance) : HResults.E_NOTIMPL; + + int IXCLRDataMethodInstance.GetDefinition(void** methodDefinition) + => _legacyImpl is not null ? _legacyImpl.GetDefinition(methodDefinition) : HResults.E_NOTIMPL; + + int IXCLRDataMethodInstance.GetTokenAndScope(uint* token, void** /*IXCLRDataModule*/ mod) + { + int hr = HResults.S_OK; + StrategyBasedComWrappers cw = new(); + + try + { + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + if (token is not null) + { + *token = rts.GetMethodToken(_methodDesc); + } + if (mod is not null) + { + IXCLRDataModule? legacyMod = null; + if (_legacyImpl is not null) + { + void* legacyModPtr = null; + int hrLegacy = _legacyImpl.GetTokenAndScope(token, &legacyModPtr); + if (hrLegacy < 0) + return hrLegacy; + object obj = cw.GetOrCreateObjectForComInstance((nint)legacyModPtr, CreateObjectFlags.None); + if (obj is not IXCLRDataModule) + { + throw new ArgumentException("Invalid module object", nameof(mod)); + } + legacyMod = obj as IXCLRDataModule; + } + + TargetPointer mtAddr = rts.GetMethodTable(_methodDesc); + TypeHandle mainMT = rts.GetTypeHandle(mtAddr); + TargetPointer module = rts.GetModule(mainMT); + IXCLRDataModule modImpl = new ClrDataModule(module, _target, legacyMod); + nint modImplPtr = cw.GetOrCreateComInterfaceForObject(modImpl, CreateComInterfaceFlags.None); + Marshal.QueryInterface(modImplPtr, typeof(IXCLRDataModule).GUID, out nint ptrToMod); + Marshal.Release(modImplPtr); + *mod = (void*)ptrToMod; + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacyImpl is not null) + { + bool validateToken = token is not null; + bool validateMod = mod is not null; + + uint tokenLocal = 0; + void* legacyModPtr = null; + int hrLocal = _legacyImpl.GetTokenAndScope(validateToken ? &tokenLocal : null, validateMod ? &legacyModPtr : null); + + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + + if (validateToken) + { + Debug.Assert(tokenLocal == *token, $"cDAC: {*token:x}, DAC: {tokenLocal:x}"); + } + + if (validateMod && hr == HResults.S_OK) + { + Marshal.Release((nint)legacyModPtr); // release the legacy module + } + } +#endif + + return hr; + } + + int IXCLRDataMethodInstance.GetName(uint flags, uint bufLen, uint* nameLen, char* nameBuf) + => _legacyImpl is not null ? _legacyImpl.GetName(flags, bufLen, nameLen, nameBuf) : HResults.E_NOTIMPL; + + int IXCLRDataMethodInstance.GetFlags(uint* flags) + => _legacyImpl is not null ? _legacyImpl.GetFlags(flags) : HResults.E_NOTIMPL; + + int IXCLRDataMethodInstance.IsSameObject(IXCLRDataMethodInstance* method) + => _legacyImpl is not null ? _legacyImpl.IsSameObject(method) : HResults.E_NOTIMPL; + + int IXCLRDataMethodInstance.GetEnCVersion(uint* version) + => _legacyImpl is not null ? _legacyImpl.GetEnCVersion(version) : HResults.E_NOTIMPL; + + int IXCLRDataMethodInstance.GetNumTypeArguments(uint* numTypeArgs) + => _legacyImpl is not null ? _legacyImpl.GetNumTypeArguments(numTypeArgs) : HResults.E_NOTIMPL; + + int IXCLRDataMethodInstance.GetTypeArgumentByIndex(uint index, void** typeArg) + => _legacyImpl is not null ? _legacyImpl.GetTypeArgumentByIndex(index, typeArg) : HResults.E_NOTIMPL; + + int IXCLRDataMethodInstance.GetILOffsetsByAddress(ClrDataAddress address, uint offsetsLen, uint* offsetsNeeded, uint* ilOffsets) + => _legacyImpl is not null ? _legacyImpl.GetILOffsetsByAddress(address, offsetsLen, offsetsNeeded, ilOffsets) : HResults.E_NOTIMPL; + + int IXCLRDataMethodInstance.GetAddressRangesByILOffset(uint ilOffset, uint rangesLen, uint* rangesNeeded, void* addressRanges) + => _legacyImpl is not null ? _legacyImpl.GetAddressRangesByILOffset(ilOffset, rangesLen, rangesNeeded, addressRanges) : HResults.E_NOTIMPL; + + int IXCLRDataMethodInstance.GetILAddressMap(uint mapLen, uint* mapNeeded, void* maps) + => _legacyImpl is not null ? _legacyImpl.GetILAddressMap(mapLen, mapNeeded, maps) : HResults.E_NOTIMPL; + + int IXCLRDataMethodInstance.StartEnumExtents(ulong* handle) + => _legacyImpl is not null ? _legacyImpl.StartEnumExtents(handle) : HResults.E_NOTIMPL; + + int IXCLRDataMethodInstance.EnumExtent(ulong* handle, void* extent) + => _legacyImpl is not null ? _legacyImpl.EnumExtent(handle, extent) : HResults.E_NOTIMPL; + + int IXCLRDataMethodInstance.EndEnumExtents(ulong handle) + => _legacyImpl is not null ? _legacyImpl.EndEnumExtents(handle) : HResults.E_NOTIMPL; + + int IXCLRDataMethodInstance.Request(uint reqCode, uint inBufferSize, byte* inBuffer, uint outBufferSize, byte* outBuffer) + => _legacyImpl is not null ? _legacyImpl.Request(reqCode, inBufferSize, inBuffer, outBufferSize, outBuffer) : HResults.E_NOTIMPL; + + int IXCLRDataMethodInstance.GetRepresentativeEntryAddress(ClrDataAddress* addr) + { + int hr = HResults.S_OK; + + try + { + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + + TargetCodePointer addrCode = rts.GetNativeCode(_methodDesc); + + if (addrCode.Value != 0) + { + *addr = addrCode.Value; + } + else + { + hr = unchecked((int)0x8000FFFF); // E_UNEXPECTED + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacyImpl is not null) + { + ClrDataAddress addrLocal; + int hrLocal = _legacyImpl.GetRepresentativeEntryAddress(&addrLocal); + + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + Debug.Assert(addrLocal == *addr, $"cDAC: {*addr:x}, DAC: {addrLocal:x}"); + } +#endif + + return hr; + } +} diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataModule.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataModule.cs index 56eca6885f0170..5b7a1492041961 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataModule.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/ClrDataModule.cs @@ -107,8 +107,11 @@ int IXCLRDataModule.EndEnumMethodDefinitionsByName(ulong handle) int IXCLRDataModule.StartEnumMethodInstancesByName(char* name, uint flags, /*IXCLRDataAppDomain*/ void* appDomain, ulong* handle) => _legacyModule is not null ? _legacyModule.StartEnumMethodInstancesByName(name, flags, appDomain, handle) : HResults.E_NOTIMPL; - int IXCLRDataModule.EnumMethodInstanceByName(ulong* handle, /*IXCLRDataMethodInstance*/ void** method) - => _legacyModule is not null ? _legacyModule.EnumMethodInstanceByName(handle, method) : HResults.E_NOTIMPL; + int IXCLRDataModule.EnumMethodInstanceByName(ulong* handle, out IXCLRDataMethodInstance? method) + { + method = default; + return _legacyModule is not null ? _legacyModule.EnumMethodInstanceByName(handle, out method) : HResults.E_NOTIMPL; + } int IXCLRDataModule.EndEnumMethodInstancesByName(ulong handle) => _legacyModule is not null ? _legacyModule.EndEnumMethodInstancesByName(handle) : HResults.E_NOTIMPL; diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/IXCLRData.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/IXCLRData.cs index 231c0977a20686..ae043ab996a5a4 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/IXCLRData.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/IXCLRData.cs @@ -81,7 +81,7 @@ internal unsafe partial interface IXCLRDataModule [PreserveSig] int StartEnumMethodInstancesByName(char* name, uint flags, /*IXCLRDataAppDomain*/ void* appDomain, ulong* handle); [PreserveSig] - int EnumMethodInstanceByName(ulong* handle, /*IXCLRDataMethodInstance*/ void** method); + int EnumMethodInstanceByName(ulong* handle, out IXCLRDataMethodInstance? method); [PreserveSig] int EndEnumMethodInstancesByName(ulong handle); @@ -208,7 +208,7 @@ int GetRuntimeNameByAddress( [PreserveSig] int StartEnumMethodInstancesByAddress(ulong address, /*IXCLRDataAppDomain*/ void* appDomain, ulong* handle); [PreserveSig] - int EnumMethodInstanceByAddress(ulong* handle, /*IXCLRDataMethodInstance*/ void** method); + int EnumMethodInstanceByAddress(ulong* handle, out IXCLRDataMethodInstance? method); [PreserveSig] int EndEnumMethodInstancesByAddress(ulong handle); @@ -412,3 +412,79 @@ internal unsafe partial interface IXCLRDataTask [PreserveSig] int GetLastExceptionState(/*IXCLRDataExceptionState*/ void** exception); } + +[GeneratedComInterface] +[Guid("ECD73800-22CA-4b0d-AB55-E9BA7E6318A5")] +internal unsafe partial interface IXCLRDataMethodInstance +{ + [PreserveSig] + int GetTypeInstance(/*IXCLRDataTypeInstance*/ void** typeInstance); + + [PreserveSig] + int GetDefinition(/*IXCLRDataMethodDefinition*/ void** methodDefinition); + + [PreserveSig] + int GetTokenAndScope(uint* token, void** /*IXCLRDataModule*/ mod); + + [PreserveSig] + int GetName( + uint flags, + uint bufLen, + uint* nameLen, + char* nameBuf); + + [PreserveSig] + int GetFlags(uint* flags); + + [PreserveSig] + int IsSameObject(IXCLRDataMethodInstance* method); + + [PreserveSig] + int GetEnCVersion(uint* version); + + [PreserveSig] + int GetNumTypeArguments(uint* numTypeArgs); + + [PreserveSig] + int GetTypeArgumentByIndex(uint index, /*IXCLRDataTypeInstance*/ void** typeArg); + + [PreserveSig] + int GetILOffsetsByAddress( + ClrDataAddress address, + uint offsetsLen, + uint* offsetsNeeded, + uint* ilOffsets); + + [PreserveSig] + int GetAddressRangesByILOffset( + uint ilOffset, + uint rangesLen, + uint* rangesNeeded, + /*CLRDATA_ADDRESS_RANGE* */ void* addressRanges); + + [PreserveSig] + int GetILAddressMap( + uint mapLen, + uint* mapNeeded, + /*CLRDATA_IL_ADDRESS_MAP* */ void* maps); + + [PreserveSig] + int StartEnumExtents(ulong* handle); + + [PreserveSig] + int EnumExtent(ulong* handle, /*CLRDATA_ADDRESS_RANGE*/ void* extent); + + [PreserveSig] + int EndEnumExtents(ulong handle); + + [PreserveSig] + int Request( + uint reqCode, + uint inBufferSize, + byte* inBuffer, + uint outBufferSize, + byte* outBuffer); + + [PreserveSig] + int GetRepresentativeEntryAddress(ClrDataAddress* addr); +} diff --git a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs index eb74a0a35780ee..4ff34b79501d9d 100644 --- a/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs +++ b/src/native/managed/cdac/mscordaccore_universal/Legacy/SOSDacImpl.IXCLRDataProcess.cs @@ -2,9 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; -using System.Diagnostics; +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.Contracts.Extensions; namespace Microsoft.Diagnostics.DataContractReader.Legacy; @@ -132,14 +136,315 @@ int IXCLRDataProcess.EndEnumModules(ulong handle) int IXCLRDataProcess.GetModuleByAddress(ClrDataAddress address, /*IXCLRDataModule*/ void** mod) => _legacyProcess is not null ? _legacyProcess.GetModuleByAddress(address, mod) : HResults.E_NOTIMPL; + internal class EnumMethodInstances + { + private readonly Target _target; + private readonly TargetPointer _mainMethodDesc; + public readonly TargetPointer _appDomain; + private readonly ILoader _loader; + private readonly IRuntimeTypeSystem _rts; + private readonly ICodeVersions _cv; + public IEnumerator methodEnumerator = Enumerable.Empty().GetEnumerator(); + public TargetPointer LegacyHandle { get; set; } = TargetPointer.Null; + + public EnumMethodInstances(Target target, TargetPointer methodDesc, TargetPointer appDomain) + { + _target = target; + _mainMethodDesc = methodDesc; + if (appDomain == TargetPointer.Null) + { + TargetPointer appDomainPointer = _target.ReadGlobalPointer(Constants.Globals.AppDomain); + _appDomain = _target.ReadPointer(appDomainPointer); + } + else + { + _appDomain = appDomain; + } + + _loader = _target.Contracts.Loader; + _rts = _target.Contracts.RuntimeTypeSystem; + _cv = _target.Contracts.CodeVersions; + } + + public int Start() + { + MethodDescHandle mainMD = _rts.GetMethodDescHandle(_mainMethodDesc); + if (!HasClassOrMethodInstantiation(mainMD) && !_cv.HasNativeCodeAnyVersion(_mainMethodDesc)) + { + return HResults.S_FALSE; + } + + methodEnumerator = IterateMethodInstances().GetEnumerator(); + + return HResults.S_OK; + } + + private IEnumerable IterateMethodInstantiations(Contracts.ModuleHandle moduleHandle) + { + IEnumerable methodInstantiations = _loader.GetInstantiatedMethods(moduleHandle); + + foreach (TargetPointer methodPtr in methodInstantiations) + { + yield return _rts.GetMethodDescHandle(methodPtr); + } + } + + private IEnumerable IterateTypeParams(Contracts.ModuleHandle moduleHandle) + { + IEnumerable typeParams = _loader.GetAvailableTypeParams(moduleHandle); + + foreach (TargetPointer type in typeParams) + { + yield return _rts.GetTypeHandle(type); + } + } + + private IEnumerable IterateModules() + { + ILoader loader = _target.Contracts.Loader; + IEnumerable modules = loader.GetModuleHandles( + _appDomain, + AssemblyIterationFlags.IncludeLoaded | AssemblyIterationFlags.IncludeExecution); + + foreach (Contracts.ModuleHandle moduleHandle in modules) + { + yield return moduleHandle; + } + } + + private IEnumerable IterateMethodInstances() + { + /* + There are 4 cases for method instances: + 1. Non-generic method on non-generic type (There is 1 MethodDesc for the method (excluding unboxing stubs, and such) + 2. Generic method on non-generic type (There is a generic defining method + a instantiated method for each particular instantiation) + 3. Non-generic method on generic type (There is 1 method for each generic instance created + 1 for the method on the uninstantiated generic type) + 4. Generic method on Generic type (There are N generic defining methods where N is the number of generic instantiations of the generic type + 1 on the uninstantiated generic types + M different generic instances of the method) + */ + + MethodDescHandle mainMD = _rts.GetMethodDescHandle(_mainMethodDesc); + + if (!HasClassOrMethodInstantiation(mainMD)) + { + // case 1 + // no method or class instantiation, then it's not generic. + if (_cv.HasNativeCodeAnyVersion(_mainMethodDesc)) + { + yield return mainMD; + } + yield break; + } + + TargetPointer mtAddr = _rts.GetMethodTable(mainMD); + TypeHandle mainMT = _rts.GetTypeHandle(mtAddr); + TargetPointer mainModule = _rts.GetModule(mainMT); + uint mainMTToken = _rts.GetTypeDefToken(mainMT); + uint mainMDToken = _rts.GetMethodToken(mainMD); + ushort slotNum = _rts.GetSlotNumber(mainMD); + + if (HasMethodInstantiation(mainMD)) + { + // case 2/4 + // 2 is trivial, 4 is covered because the defining method on a generic type is not instantiated + foreach (Contracts.ModuleHandle moduleHandle in IterateModules()) + { + foreach (MethodDescHandle methodDesc in IterateMethodInstantiations(moduleHandle)) + { + TypeHandle methodTypeHandle = _rts.GetTypeHandle(_rts.GetMethodTable(methodDesc)); + + if (mainModule != _rts.GetModule(methodTypeHandle)) continue; + if (mainMDToken != _rts.GetMethodToken(methodDesc)) continue; + + if (_cv.HasNativeCodeAnyVersion(methodDesc.Address)) + { + yield return methodDesc; + } + } + } + + yield break; + } + + if (HasClassInstantiation(mainMD)) + { + // case 3 + // class instantiations are only interesting if the method is not generic + foreach (Contracts.ModuleHandle moduleHandle in IterateModules()) + { + if (HasClassInstantiation(mainMD)) + { + foreach (Contracts.TypeHandle typeParam in IterateTypeParams(moduleHandle)) + { + uint typeParamToken = _rts.GetTypeDefToken(typeParam); + + // not a MethodTable + if (typeParamToken == 0) continue; + + // Check the class token + if (mainMTToken != typeParamToken) continue; + + // Check the module is correct + if (mainModule != _rts.GetModule(typeParam)) continue; + + TargetPointer cmt = _rts.GetCanonicalMethodTable(typeParam); + TypeHandle cmtHandle = _rts.GetTypeHandle(cmt); + + TargetPointer methodDescAddr = _rts.GetMethodDescForSlot(cmtHandle, slotNum); + if (methodDescAddr == TargetPointer.Null) continue; + MethodDescHandle methodDesc = _rts.GetMethodDescHandle(methodDescAddr); + + if (_cv.HasNativeCodeAnyVersion(methodDescAddr)) + { + yield return methodDesc; + } + } + } + } + + yield break; + } + + } + + private bool HasClassOrMethodInstantiation(MethodDescHandle md) + { + return HasClassInstantiation(md) || HasMethodInstantiation(md); + } + + private bool HasClassInstantiation(MethodDescHandle md) + { + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + + TargetPointer mtAddr = rts.GetMethodTable(md); + TypeHandle mt = rts.GetTypeHandle(mtAddr); + return !rts.GetInstantiation(mt).IsEmpty; + } + + private bool HasMethodInstantiation(MethodDescHandle md) + { + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + + if (rts.IsGenericMethodDefinition(md)) return true; + return !rts.GetGenericMethodInstantiation(md).IsEmpty; + } + } + int IXCLRDataProcess.StartEnumMethodInstancesByAddress(ulong address, /*IXCLRDataAppDomain*/ void* appDomain, ulong* handle) - => _legacyProcess is not null ? _legacyProcess.StartEnumMethodInstancesByAddress(address, appDomain, handle) : HResults.E_NOTIMPL; + { + int hr = HResults.S_OK; - int IXCLRDataProcess.EnumMethodInstanceByAddress(ulong* handle, /*IXCLRDataMethodInstance*/ void** method) - => _legacyProcess is not null ? _legacyProcess.EnumMethodInstanceByAddress(handle, method) : HResults.E_NOTIMPL; + *handle = 0; + hr = HResults.S_FALSE; + + ulong handleLocal = default; +#if DEBUG + int hrLocal = default; + if (_legacyProcess is not null) + { + hrLocal = _legacyProcess.StartEnumMethodInstancesByAddress(address, appDomain, &handleLocal); + } +#endif + + try + { + // ClrDataAccess::IsPossibleCodeAddress + // Does a trivial check on the readability of the address + bool isTriviallyReadable = _target.TryRead(address, out byte _); + if (!isTriviallyReadable) + throw new ArgumentException(); + + IExecutionManager eman = _target.Contracts.ExecutionManager; + if (eman.GetCodeBlockHandle(address) is CodeBlockHandle cbh && + eman.GetMethodDesc(cbh) is TargetPointer methodDesc) + { + EnumMethodInstances emi = new(_target, methodDesc, TargetPointer.Null); + emi.LegacyHandle = handleLocal; + + GCHandle gcHandle = GCHandle.Alloc(emi); + *handle = (ulong)GCHandle.ToIntPtr(gcHandle).ToInt64(); + hr = emi.Start(); + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacyProcess is not null) + { + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + } +#endif + return hr; + } + + int IXCLRDataProcess.EnumMethodInstanceByAddress(ulong* handle, out IXCLRDataMethodInstance? method) + { + method = default; + int hr = HResults.S_OK; + + GCHandle gcHandle = GCHandle.FromIntPtr((IntPtr)(*handle)); + if (gcHandle.Target is not EnumMethodInstances emi) return HResults.E_INVALIDARG; + + IXCLRDataMethodInstance? legacyMethod = null; + +#if DEBUG + int hrLocal = default; + if (_legacyProcess is not null) + { + ulong legacyHandle = emi.LegacyHandle; + hrLocal = _legacyProcess.EnumMethodInstanceByAddress(&legacyHandle, out legacyMethod); + emi.LegacyHandle = legacyHandle; + } +#endif + + try + { + if (emi.methodEnumerator.MoveNext()) + { + MethodDescHandle methodDesc = emi.methodEnumerator.Current; + method = new ClrDataMethodInstance(_target, methodDesc, emi._appDomain, legacyMethod); + } + else + { + hr = HResults.S_FALSE; + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacyProcess is not null) + { + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + } +#endif + + return hr; + } int IXCLRDataProcess.EndEnumMethodInstancesByAddress(ulong handle) - => _legacyProcess is not null ? _legacyProcess.EndEnumMethodInstancesByAddress(handle) : HResults.E_NOTIMPL; + { + int hr = HResults.S_OK; + + GCHandle gcHandle = GCHandle.FromIntPtr((IntPtr)handle); + if (gcHandle.Target is not EnumMethodInstances emi) return HResults.E_INVALIDARG; + gcHandle.Free(); + +#if DEBUG + if (_legacyProcess != null && emi.LegacyHandle != TargetPointer.Null) + { + int hrLocal = _legacyProcess.EndEnumMethodInstancesByAddress(emi.LegacyHandle); + if (hrLocal < 0) + return hrLocal; + } +#endif + + return hr; + } int IXCLRDataProcess.GetDataByAddress( ClrDataAddress address, diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.MethodDescriptors.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.MethodDescriptors.cs index 4a0787ac65c27f..ea4e7ebd2e96b9 100644 --- a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.MethodDescriptors.cs +++ b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.MethodDescriptors.cs @@ -100,9 +100,15 @@ internal MethodDescriptors(RuntimeTypeSystem rtsBuilder, Loader loaderBuilder, ( LoaderBuilder = loaderBuilder; _allocator = Builder.CreateAllocator(allocationRange.Start, allocationRange.End); Types = GetTypes(); + + // Add dummy MethodDescSizeTable. Sizes will be incorrect, but we don't use it in tests. + MockMemorySpace.HeapFragment methodDescSizeTable = _allocator.Allocate(0x100, "MethodDescSizeTable"); + Builder.AddHeapFragment(methodDescSizeTable); + Globals = rtsBuilder.Globals.Concat( [ new(nameof(Constants.Globals.MethodDescTokenRemainderBitCount), TokenRemainderBitCount), + new(nameof(Constants.Globals.MethodDescSizeTable), methodDescSizeTable.Address), ]).ToArray(); } diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs index 45a6b7d5cde038..13ec9c56d6d992 100644 --- a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs +++ b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs @@ -38,6 +38,7 @@ internal record TypeFields Fields = [ new(nameof(Data.EEClass.MethodTable), DataType.pointer), + new(nameof(Data.EEClass.MethodDescChunk), DataType.pointer), new(nameof(Data.EEClass.CorTypeAttr), DataType.uint32), new(nameof(Data.EEClass.NumMethods), DataType.uint16), new(nameof(Data.EEClass.InternalCorElementType), DataType.uint8), @@ -142,6 +143,8 @@ internal record TypeFields new(nameof(Data.Module.FileName), DataType.pointer), new(nameof(Data.Module.ReadyToRunInfo), DataType.pointer), new(nameof(Data.Module.GrowableSymbolStream), DataType.pointer), + new(nameof(Data.Module.AvailableTypeParams), DataType.pointer), + new(nameof(Data.Module.InstMethodHashTable), DataType.pointer), new(nameof(Data.Module.FieldDefToDescMap), DataType.pointer), new(nameof(Data.Module.ManifestModuleReferencesMap), DataType.pointer), new(nameof(Data.Module.MemberRefToDescMap), DataType.pointer), diff --git a/src/native/managed/cdac/tests/TestPlaceholderTarget.cs b/src/native/managed/cdac/tests/TestPlaceholderTarget.cs index 0ab08df7c81e73..ba344986d26abc 100644 --- a/src/native/managed/cdac/tests/TestPlaceholderTarget.cs +++ b/src/native/managed/cdac/tests/TestPlaceholderTarget.cs @@ -161,9 +161,20 @@ public override bool TryReadGlobalString(string name, [NotNullWhen(true)] out st } public override T Read(ulong address) => DefaultRead(address); + + public override bool TryRead(ulong address, out T value) + { + value = default; + if (!DefaultTryRead(address, out T readValue)) + return false; + + value = readValue; + return true; + } + public override bool Write(ulong address, T value) => throw new NotImplementedException(); -#region subclass reader helpers + #region subclass reader helpers /// /// Basic utility to read a value from memory, all the DefaultReadXXX methods call this. @@ -224,7 +235,7 @@ protected T DefaultRead(ulong address) where T : unmanaged, IBinaryInteger return value; } - protected TargetPointer DefaultReadPointer (ulong address) + protected TargetPointer DefaultReadPointer(ulong address) { if (!DefaultTryReadPointer(address, out TargetPointer pointer)) throw new InvalidOperationException($"Failed to read pointer at 0x{address:x8}."); @@ -273,7 +284,7 @@ protected TargetCodePointer DefaultReadCodePointer(ulong address) { return new TargetCodePointer(DefaultReadPointer(address)); } -#endregion subclass reader helpers + #endregion subclass reader helpers public override TargetPointer ReadPointerFromSpan(ReadOnlySpan bytes) => throw new NotImplementedException(); @@ -311,7 +322,8 @@ public T GetOrAdd(TargetPointer address) where T : Data.IData return constructed; bool found = TryGet(address, out result); - if (!found) { + if (!found) + { throw new InvalidOperationException($"Failed to add {typeof(T)} at 0x{address:x8}."); } return result!;