diff --git a/docs/design/datacontracts/CodeVersions.md b/docs/design/datacontracts/CodeVersions.md index d2e73b7541153..46df04d060347 100644 --- a/docs/design/datacontracts/CodeVersions.md +++ b/docs/design/datacontracts/CodeVersions.md @@ -113,6 +113,14 @@ NativeCodeVersionHandle ICodeVersions.GetNativeCodeVersionForIP(TargetCodePointe NativeCodeVersionHandle GetSpecificNativeCodeVersion(MethodDescHandle md, TargetCodePointer startAddress) { + // "Initial" stage of NativeCodeVersionIterator::Next() with a null m_ilCodeFilter + TargetCodePointer firstNativeCode = rts.GetNativeCode(md); + if (firstNativeCode == startAddress) + { + NativeCodeVersionHandle first = new NativeCodeVersionHandle(md.Address, TargetPointer.Null); + return first; + } + // ImplicitCodeVersion stage of NativeCodeVersionIterator::Next() TargetPointer methodDescVersioningStateAddress = target.Contracts.RuntimeTypeSystem.GetMethodDescVersioningState(md); if (methodDescVersioningStateAddress == TargetPointer.Null) { diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index fce06060e280e..e4649ccc0ffc5 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -40,6 +40,7 @@ TargetPointer GetThunkHeap(ModuleHandle handle); TargetPointer GetILBase(ModuleHandle handle); ModuleLookupTables GetLookupTables(ModuleHandle handle); TargetPointer GetModuleLookupMapElement(TargetPointer table, uint token, out TargetNUInt flags); +bool IsCollectible(ModuleHandle handle); ``` ## Version 1 @@ -64,6 +65,7 @@ Data descriptors used: | `ModuleLookupMap` | `SupportedFlagsMask` | Mask for flag bits on lookup map entries | | `ModuleLookupMap` | `Count` | Number of TargetPointer sized entries in this section of the map | | `ModuleLookupMap` | `Next` | Pointer to next ModuleLookupMap segment for this map +| `Assembly` | `IsCollectible` | Flag indicating if this is module may be collected ``` csharp ModuleHandle GetModuleHandle(TargetPointer modulePointer) @@ -151,3 +153,12 @@ TargetPointer GetModuleLookupMapElement(TargetPointer table, uint token, out Tar return TargetPointer.Null; } ``` + +```csharp +bool ILoader.IsCollectible(ModuleHandle handle) +{ + TargetPointer assembly = _target.ReadPointer(handle.Address + /*Module::Assembly*/); + byte isCollectible = _target.Read(assembly + /* Assembly::IsCollectible*/); + return isCollectible != 0; +} +``` diff --git a/docs/design/datacontracts/ReJIT.md b/docs/design/datacontracts/ReJIT.md new file mode 100644 index 0000000000000..53ed0767a6a9d --- /dev/null +++ b/docs/design/datacontracts/ReJIT.md @@ -0,0 +1,43 @@ +# Contract ReJIT + +This contract encapsulates support for [ReJIT](../features/code-versioning.md) in the runtime. + +## APIs of contract + +```csharp +bool IsEnabled(); +``` + +## Version 1 + +Data descriptors used: +| Data Descriptor Name | Field | Meaning | +| --- | --- | --- | +| ProfControlBlock | GlobalEventMask | an `ICorProfiler` `COR_PRF_MONITOR` value | + +Global variables used: +| Global Name | Type | Purpose | +| --- | --- | --- | +|ProfilerControlBlock | TargetPointer | pointer to the `ProfControlBlock` | + +Contracts used: +| Contract Name | +| --- | + +```csharp +// see src/coreclr/inc/corprof.idl +[Flags] +private enum COR_PRF_MONITOR +{ + COR_PRF_ENABLE_REJIT = 0x00040000, +} + +bool IsEnabled() +{ + TargetPointer address = target.ReadGlobalPointer("ProfilerControlBlock"); + ulong globalEventMask = target.Read(address + /* ProfControlBlock::GlobalEventMask offset*/); + bool profEnabledReJIT = (GlobalEventMask & (ulong)COR_PRF_MONITOR.COR_PRF_ENABLE_REJIT) != 0; + bool clrConfigEnabledReJit = /* host process does not have environment variable DOTNET_ProfAPI_ReJitOnAttach set to 0 */; + return profEnabledReJIT || clrConfigEnabledReJIT; +} +``` diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index 4badc1291c3c1..7930404e937f2 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -62,7 +62,6 @@ partial interface IRuntimeTypeSystem : IContract public virtual ReadOnlySpan GetInstantiation(TypeHandle typeHandle); public virtual bool IsGenericTypeDefinition(TypeHandle typeHandle); - public virtual TypeHandle TypeHandleFromAddress(TargetPointer address); public virtual bool HasTypeParam(TypeHandle typeHandle); // Element type of the type. NOTE: this drops the CorElementType.GenericInst, and CorElementType.String is returned as CorElementType.Class. @@ -148,6 +147,15 @@ partial interface IRuntimeTypeSystem : IContract // Return a pointer to the IL versioning state of the MethodDesc public virtual TargetPointer GetMethodDescVersioningState(MethodDescHandle methodDesc); + // Return the MethodTable slot number of the MethodDesc + public virtual ushort GetSlotNumber(MethodDescHandle methodDesc); + + // Return true if the MethodDesc has space associated with it for storing a pointer to a code block + public virtual bool HasNativeCodeSlot(MethodDescHandle methodDesc); + + // Return the address of the space that stores a pointer to a code block associated with the MethodDesc + public virtual TargetPointer GetAddressOfNativeCodeSlot(MethodDescHandle methodDesc); + // Get an instruction pointer that can be called to cause the MethodDesc to be executed public virtual TargetCodePointer GetNativeCode(MethodDescHandle methodDesc); @@ -543,7 +551,7 @@ The contract additionally depends on these data descriptors if (!typeHandle.Flags.IsArray) throw new ArgumentException(nameof(typeHandle)); - return TypeHandleFromAddress(typeHandle.PerInstInfo); + return GetTypeHandle(typeHandle.PerInstInfo); } else if (typeHandle.IsTypeDesc()) { @@ -556,7 +564,7 @@ The contract additionally depends on these data descriptors case CorElementType.Byref: case CorElementType.Ptr: TargetPointer typeArgPointer = // Read TypeArg field from ParamTypeDesc contract using address typeHandle.TypeDescAddress() - return TypeHandleFromAddress(typeArgPointer); + return GetTypeHandle(typeArgPointer); } } throw new ArgumentException(nameof(typeHandle)); @@ -620,8 +628,6 @@ The version 1 `MethodDesc` APIs depend on the `MethodDescAlignment` global and t | `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` | -**TODO** MethodDesc code pointers additions - 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. @@ -632,11 +638,13 @@ We depend on the following data descriptors: | `MethodDesc` | `Slot` | The method's slot | | `MethodDesc` | `Flags` | The method's flags | | `MethodDesc` | `Flags3AndTokenRemainder` | More flags for the method, and the low bits of the method's token's RID | +| `MethodDescCodeData` | `VersioningState` | The IL versioning state associated with a method descriptor | `MethodDescChunk` | `MethodTable` | The method table set of methods belongs to | | `MethodDescChunk` | `Next` | The next chunk of methods | | `MethodDescChunk` | `Size` | The size of this `MethodDescChunk` following this `MethodDescChunk` header, minus 1. In multiples of `MethodDescAlignment` | | `MethodDescChunk` | `Count` | The number of `MethodDesc` entries in this chunk, minus 1. | | `MethodDescChunk` | `FlagsAndTokenRange` | `MethodDescChunk` flags, and the upper bits of the method token's RID | +| `MethodTableAuxiliaryData` | `LoaderModule` | The loader module associated with a method table | `InstantiatedMethodDesc` | `PerInstInfo` | The pointer to the method's type arguments | | `InstantiatedMethodDesc` | `Flags2` | Flags for the `InstantiatedMethodDesc` | | `InstantiatedMethodDesc` | `NumGenericArgs` | How many generic args the method has | @@ -645,15 +653,15 @@ We depend on the following data descriptors: | `StoredSigMethodDesc` | `ExtendedFlags` | Flags field for the `StoredSigMethodDesc` | | `DynamicMethodDesc` | `MethodName` | Pointer to Null-terminated UTF8 string describing the Method desc | -**TODO** MethodDesc code pointers additions The contract depends on the following other contracts | Contract | | --- | +| CodeVersions | | Loader | +| PlatformMetadata | | ReJIT | -| CodeVersions | And the following enumeration definitions @@ -675,7 +683,14 @@ And the following enumeration definitions internal enum MethodDescFlags : ushort { ClassificationMask = 0x7, + #region Additonal pointers + // The below flags each imply that there's an extra pointer-sized piece of data after the MethodDesc in the MethodDescChunk HasNonVtableSlot = 0x0008, + HasMethodImpl = 0x0010, + HasNativeCodeSlot = 0x0020, + // Mask for the above flags + MethodDescAdditionalPointersMask = 0x0038, + #endredion Additional pointers } internal enum InstantiatedMethodDescFlags2 : ushort @@ -693,12 +708,110 @@ And the following enumeration definitions IsLCGMethod = 0x00004000, IsILStub = 0x00008000, } + + [Flags] + internal enum MethodDescFlags3 : ushort + { + // HasPrecode implies that HasStableEntryPoint is set. + HasStableEntryPoint = 0x1000, // The method entrypoint is stable (either precode or actual code) + HasPrecode = 0x2000, // Precode has been allocated for this method + IsUnboxingStub = 0x4000, + IsEligibleForTieredCompilation = 0x8000, + } + + [Flags] + internal enum MethodDescEntryPointFlags : byte + { + TemporaryEntryPointAssigned = 0x04, + } + +``` + +Internal to the contract in order to answer queries about method descriptors, +we collect the information in a `MethodDesc` struct: + +```csharp +internal struct MethodDesc +{ + private readonly Data.MethodDesc _desc; + private readonly Data.MethodDescChunk _chunk; + private readonly Target _target; + internal TargetPointer Address { get; init; } + internal TargetPointer ChunkAddress { get; init; } + + internal MethodDesc(Target target, TargetPointer methodDescPointer, Data.MethodDesc desc, TargetPointer methodDescChunkAddress, Data.MethodDescChunk chunk) + { + _target = target; + _desc = desc; + _chunk = chunk; + ChunkAddress = methodDescChunkAddress; + Address = methodDescPointer; + } + + public MethodClassification Classification => (MethodClassification)(_desc.Flags & MethodDescFlags.ClassificationMask); + public bool IsIL => Classification == MethodClassification.IL || Classification == MethodClassification.Instantiated; + + public TargetPointer MethodTable => _chunk.MethodTable; + + public ushort Slot => _desc.Slot; + + + internal bool HasFlags(MethodDescChunkFlags flags) => (_chunk.FlagsAndTokenRange & (ushort)flags) != 0; + internal bool HasFlags(MethodDescFlags flags) => (_desc.Flags & (ushort)flags) != 0; + internal bool HasFlags(MethodDescFlags3 flags) => (_desc.Flags3AndTokenRemainder & (ushort)flags) != 0; + + public bool IsLoaderModuleAttachedToChunk => HasFlags(MethodDescChunkFlags.LoaderModuleAttachedToChunk); + + public ulong SizeOfChunk + { + get + { + ulong typeSize = _target.GetTypeInfo(DataType.MethodDescChunk).Size; + ulong chunkSize = (ulong)(_chunk.Size + 1) * _target.ReadGlobal("MethodDescAlignment"); + ulong extra = IsLoaderModuleAttachedToChunk ? (ulong)_target.PointerSize : 0; + return typeSize + chunkSize + extra; + } + } + + public bool IsEligibleForTieredCompilation => HasFlags(MethodDescFlags3.IsEligibleForTieredCompilation); + + // non-vtable slot, native code slot and MethodImpl slots are stored after the MethodDesc itself, packed tightly + // in the order: [non-vtable; methhod impl; native code]. + internal int NonVtableSlotIndex => HasNonVtableSlot ? 0 : throw new InvalidOperationException(); + internal int MethodImplIndex => HasMethodImpl ? /* 0 or 1 */ : throw new InvalidOperationException(); + internal int NativeCodeSlotIndex => HasNativeCodeSlot ? /* 0, 1 or 2 */ : throw new InvalidOperationException(); + + internal bool HasNativeCodeSlot => HasFlags(MethodDescFlags.HasNativeCodeSlot); + internal bool HasNonVtableSlot => HasFlags(MethodDescFlags.HasNonVtableSlot); + internal bool HasMethodImpl => HasFlags(MethodDescFlags.HasMethodImpl); + + internal bool HasStableEntryPoint => HasFlags(MethodDescFlags3.HasStableEntryPoint); + internal bool HasPrecode => HasFlags(MethodDescFlags3.HasPrecode); + +} ``` +Method descriptor handles are instantiated by caching the relevant data in a `_methodDescs` dictionary: + +```csharp + public MethodDescHandle GetMethodDescHandle(TargetPointer methodDescPointer) + { + // Validate that methodDescPointer points at a MethodDesc + // Get the corresponding MethodDescChunk pointer + // Load the relevant Data.MethodDesc and Data.MethodDescChunk structures + // and caching the results in _methodDescs + return new MethodDescHandle() { Address = methodDescPointer }; + } +``` And the various apis are implemented with the following algorithms ```csharp + public TargetPointer GetMethodTable(MethodDescHandle methodDescHandle) + { + return _methodDescs[methodDescHandle.Address].MethodTable; + } + public bool IsGenericMethodDefinition(MethodDescHandle methodDescHandle) { MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; @@ -843,26 +956,199 @@ And the various apis are implemented with the following algorithms return ((DynamicMethodDescExtendedFlags)ExtendedFlags).HasFlag(DynamicMethodDescExtendedFlags.IsILStub); } + + public ushort GetSlotNumber(MethodDescHandle methodDesc) => _methodDescs[methodDesc.Addres]._desc.Slot; ``` Determining if a method is in a collectible module: ```csharp -bool IRuntimeTypeSystem.IsCollectibleMethod(MethodDescHandle methodDesc) => // TODO[cdac]: finish this + private TargetPointer GetLoaderModule(MethodDesc md) + { + if (md.IsLoaderModuleAttachedToChunk) + { + TargetPointer methodDescChunkPointer = md.ChunkAddress; + TargetPointer endOfChunk = methodDescChunkPointer + md.SizeOfChunk; + TargetPointer ppLoaderModule = endOfChunk - (ulong)_target.PointerSize; + return _target.ReadPointer(ppLoaderModule); + } + else + { + TargetPointer mtAddr = GetMethodTable(new MethodDescHandle(md.Address)); + TypeHandle mt = GetTypeHandle(mtAddr); + return GetLoaderModule(mt); + } + } + + private TargetPointer GetLoaderModule(TypeHandle typeHandle) + { + if (!typeHandle.IsMethodTable()) + { + // FIXME[cdac]: TypeDesc::GetLoaderModule() + } + else + { + MethodTable mt = _methodTables[typeHandle.Address]; + Data.MethodTableAuxiliaryData mtAuxData = /* get the AuxiliaryData from the Method Table*/; + return mtAuxData.LoaderModule; + } + } + + public bool IsCollectibleMethod(MethodDescHandle methodDesc) + { + MethodDesc md = _methodDescs[methodDesc.Address]; + TargetPointer loaderModuleAddr = GetLoaderModule(md); + ModuleHandle mod = _target.Contracts.Loader.GetModuleHandle(loaderModuleAddr); + return _target.Contracts.Loader.IsCollectible(mod); + + } ``` Determining if a method supports multiple code versions: ```csharp -bool IRuntimeTypeSystem.IsVersionable(MethodDescHandle methodDesc) => // TODO[cdac]: finish this + private bool IsWrapperStub(MethodDesc md) + { + return md.IsUnboxingStub || IsInstantiatingStub(md); + } + + private bool IsInstantiatingStub(MethodDesc md) + { + return md.Classification == MethodClassification.Instantiated && !md.IsUnboxingStub && IsWrapperStubWithInstantiations(md); + } + + private bool IsWrapperStubWithInstantiations(MethodDesc methodDesc) + { + return /*Flags2 of InstantiatedMethodDesc at methodDesc.Address has WrapperStubWithInstantiations set*/; + } + + public bool IsVersionable(MethodDescHandle methodDesc) + { + MethodDesc md = _methodDescs[methodDesc.Address]; + if (md.IsEligibleForTieredCompilation) + return true; + // MethodDesc::IsEligibleForReJIT + if (_target.Contracts.ReJIT.IsEnabled()) + { + if (!md.IsIL) + return false; + if (IsWrapperStub(md)) + return false; + return _target.Contracts.CodeVersions.CodeVersionManagerSupportsMethod(methodDesc.Address); + } + return false; + } ``` Extracting a pointer to the `MethodDescVersioningState` data for a given method + +```csharp + public TargetPointer GetMethodDescVersioningState(MethodDescHandle methodDesc) + { + MethodDesc md = _methodDescs[methodDesc.Address]; + TargetPointer codeDataAddress = md._desc.CodeData; + return /* VersioningState field of MethodDescCodeData at codeDataAddress */; + } +``` + +Checking if a method has a native code slot and getting its address + ```csharp -TargetPointer IRuntimeTypeSystem.GetMethodDescVersioningState(MethodDescHandle methodDesc) => // TODO[cdac]: finish this + public bool HasNativeCodeSlot(MethodDescHandle methodDesc) =>_methodDescs[methodDesc.Address].HasNativeCodeSlot; + + uint GetMethodClassificationBaseSize (MethodClassification classification) + => classification switch + { + MethodClassification.IL => /*size of MethodDesc*/, + MethodClassification.FCall => /* size of FCallMethodDesc */ + MethodClassification.PInvoke => /* size of NDirectMethodDesc */ + MethodClassification.EEImpl => /* size of EEImplMethodDesc */ + MethodClassification.Array => /* size of ArrayMethodDesc */ + MethodClassification.Instantiated => /* size of InstantiatedMethodDesc */ + MethodClassification.ComInterop => /* size of CLRToCOMCallMethodDesc */ + MethodClassification.Dynamic => /* size of DynamicMethodDesc */ + }; + + private uint MethodDescAdditionalPointersOffset(MethodDesc md) => GetMethodClassificationBaseSize(md.Classification); + + public TargetPointer GetAddressOfNativeCodeSlot(MethodDescHandle methodDesc) + { + MethodDesc md = _methodDescs[methodDesc.Address]; + uint offset = MethodDescAdditionalPointersOffset(md); + offset += (uint)(_target.PointerSize * md.NativeCodeSlotIndex); + return methodDesc.Address + offset; + } ``` Getting the native code pointer for methods with a NativeCodeSlot or a stable entry point + ```csharp -public virtual TargetCodePointer GetNativeCode(MethodDescHandle methodDesc) => // TODO[cdac]: finish this + private TargetCodePointer GetStableEntryPoint(TargetPointer methodDescAddress, MethodDesc md) + { + return GetMethodEntryPointIfExists(methodDescAddress, md); + } + + private TargetCodePointer GetMethodEntryPointIfExists(TargetPointer methodDescAddress, MethodDesc md) + { + if (md.HasNonVtableSlot) + { + TargetPointer pSlot = GetAddressOfNonVtableSlot(methodDescAddress, md); + + return _target.ReadCodePointer(pSlot); + } + + TargetPointer methodTablePointer = md.MethodTable; + TypeHandle typeHandle = GetTypeHandle(methodTablePointer); + TargetPointer addrOfSlot = GetAddressOfSlot(typeHandle, md.Slot); + return _target.ReadCodePointer(addrOfSlot); + } + + private TargetPointer GetAddressOfNonVtableSlot(TargetPointer methodDescPointer, MethodDesc md) + { + uint offset = MethodDescAdditionalPointersOffset(md); + offset += (uint)(_target.PointerSize * md.NonVtableSlotIndex); + return methodDescPointer.Value + offset; + } + + private TargetPointer GetAddressOfSlot(TypeHandle typeHandle, uint slotNum) + { + if (!typeHandle.IsMethodTable()) + throw new InvalidOperationException("typeHandle is not a MethodTable"); + MethodTable mt = _methodTables[typeHandle.Address]; + + if (slotNum < mt.NumVirtuals) + { + // Virtual slots live in chunks pointed to by vtable indirections + return GetVTableIndirectionsAddressOfSlot(typeHandle.Address, slotNum); + } + else + { + // TODO[cdac]: GetNonVirtualSlotsArray from MethodTableAuxiliaryData + } + + } + + private TargetPointer GetVTableIndirectionsAddressOfSlot (TargetPointer methodTablePointer, uint slot) + { + private const int NumPointersPerIndirection = 8; + private const int NumPointersPerIndirectionLog2 = 3; + TargetPointer indirectionPointer = methodTablePointer + /*size of MethodTable*/ + (ulong)(slotNum >> NumPointersPerIndirectionLog2) * (ulong)_target.PointerSize; + TargetPointer slotsStart = _target.ReadPointer(indirectionPointer); + return slotsStart + (ulong)(slotNum & (NumPointersPerIndirection - 1)) * (ulong)_target.PointerSize; + } + + public TargetCodePointer GetNativeCode(MethodDescHandle methodDescHandle) + { + MethodDesc md = _methodDescs[methodDescHandle.Address]; + if (md.HasNativeCodeSlot) + { + TargetPointer ppCode = GetAddressOfNativeCodeSlot(methodDescHandle); + return _target.ReadCodePointer(ppCode); + } + + if (!md.HasStableEntryPoint || md.HasPrecode) + return TargetCodePointer.Null; + + return GetStableEntryPoint(methodDescHandle.Address, md); + } ``` diff --git a/src/coreclr/debug/runtimeinfo/contracts.jsonc b/src/coreclr/debug/runtimeinfo/contracts.jsonc index 1c520c22005d7..ec2d8eb4e7b22 100644 --- a/src/coreclr/debug/runtimeinfo/contracts.jsonc +++ b/src/coreclr/debug/runtimeinfo/contracts.jsonc @@ -18,6 +18,7 @@ "Object": 1, "PlatformMetadata": 1, "PrecodeStubs": 1, + "ReJIT": 1, "RuntimeTypeSystem": 1, "Thread": 1 } diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 3ec4e896ca3dc..8fb8871f6a243 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -244,10 +244,17 @@ CDAC_TYPE_FIELD(ModuleLookupMap, /*uint32*/, Count, offsetof(LookupMapBase, dwCo CDAC_TYPE_FIELD(ModuleLookupMap, /*nuint*/, SupportedFlagsMask, offsetof(LookupMapBase, supportedFlags)) CDAC_TYPE_END(ModuleLookupMap) +CDAC_TYPE_BEGIN(Assembly) +CDAC_TYPE_INDETERMINATE(Assembly) +#ifdef FEATURE_COLLECTIBLE_TYPES +CDAC_TYPE_FIELD(Assembly, /*uint8*/, IsCollectible, cdac_data::IsCollectible) +#endif +CDAC_TYPE_END(Assembly) + // RuntimeTypeSystem CDAC_TYPE_BEGIN(MethodTable) -CDAC_TYPE_INDETERMINATE(MethodTable) +CDAC_TYPE_SIZE(sizeof(MethodTable)) CDAC_TYPE_FIELD(MethodTable, /*uint32*/, MTFlags, cdac_data::MTFlags) CDAC_TYPE_FIELD(MethodTable, /*uint32*/, BaseSize, cdac_data::BaseSize) CDAC_TYPE_FIELD(MethodTable, /*uint32*/, MTFlags2, cdac_data::MTFlags2) @@ -257,8 +264,14 @@ CDAC_TYPE_FIELD(MethodTable, /*pointer*/, ParentMethodTable, cdac_data::NumInterfaces) CDAC_TYPE_FIELD(MethodTable, /*uint16*/, NumVirtuals, cdac_data::NumVirtuals) CDAC_TYPE_FIELD(MethodTable, /*pointer*/, PerInstInfo, cdac_data::PerInstInfo) +CDAC_TYPE_FIELD(MethodTable, /*pointer*/, AuxiliaryData, cdac_data::AuxiliaryData) CDAC_TYPE_END(MethodTable) +CDAC_TYPE_BEGIN(MethodTableAuxiliaryData) +CDAC_TYPE_INDETERMINATE(MethodTableAuxiliaryData) +CDAC_TYPE_FIELD(MethodTableAuxiliaryData, /*pointer*/, LoaderModule, offsetof(MethodTableAuxiliaryData, m_pLoaderModule)) +CDAC_TYPE_END(MethodTableAuxiliaryData) + CDAC_TYPE_BEGIN(EEClass) CDAC_TYPE_INDETERMINATE(EEClass) CDAC_TYPE_FIELD(EEClass, /*pointer*/, MethodTable, cdac_data::MethodTable) @@ -309,11 +322,13 @@ CDAC_TYPE_FIELD(DynamicMetadata, /*inline byte array*/, Data, cdac_data::ChunkIndex) CDAC_TYPE_FIELD(MethodDesc, /*uint16*/, Slot, cdac_data::Slot) CDAC_TYPE_FIELD(MethodDesc, /*uint16*/, Flags, cdac_data::Flags) CDAC_TYPE_FIELD(MethodDesc, /*uint16*/, Flags3AndTokenRemainder, cdac_data::Flags3AndTokenRemainder) +CDAC_TYPE_FIELD(MethodDesc, /*uint8*/, EntryPointFlags, cdac_data::EntryPointFlags) +CDAC_TYPE_FIELD(MethodDesc, /*pointer*/, CodeData, cdac_data::CodeData) CDAC_TYPE_END(MethodDesc) CDAC_TYPE_BEGIN(MethodDescChunk) @@ -326,7 +341,7 @@ CDAC_TYPE_FIELD(MethodDescChunk, /*uint16*/, FlagsAndTokenRange, cdac_data::PerInstInfo) CDAC_TYPE_FIELD(InstantiatedMethodDesc, /*uint16*/, Flags2, cdac_data::Flags2) CDAC_TYPE_FIELD(InstantiatedMethodDesc, /*uint16*/, NumGenericArgs, cdac_data::NumGenericArgs) @@ -340,7 +355,7 @@ CDAC_TYPE_FIELD(StoredSigMethodDesc, /*uint32*/, ExtendedFlags, cdac_data::MethodName) CDAC_TYPE_END(DynamicMethodDesc) @@ -348,6 +363,12 @@ CDAC_TYPE_BEGIN(CodePointer) CDAC_TYPE_SIZE(sizeof(PCODE)) CDAC_TYPE_END(CodePointer) +CDAC_TYPE_BEGIN(MethodDescCodeData) +CDAC_TYPE_INDETERMINATE(MethodDescCodeData) +CDAC_TYPE_FIELD(MethodDescCodeData, /*CodePointer*/, TemporaryEntryPoint, offsetof(MethodDescCodeData,TemporaryEntryPoint)) +CDAC_TYPE_FIELD(MethodDescCodeData, /*pointer*/, VersioningState, offsetof(MethodDescCodeData,VersioningState)) +CDAC_TYPE_END(MethodDescCodeData) + CDAC_TYPE_BEGIN(MethodDescVersioningState) CDAC_TYPE_INDETERMINATE(MethodDescVersioningState) CDAC_TYPE_FIELD(MethodDescVersioningState, /*pointer*/, NativeCodeVersionNode, cdac_data::NativeCodeVersionNode) @@ -441,6 +462,11 @@ CDAC_TYPE_FIELD(NativeCodeVersionNode, /*pointer*/, Next, cdac_data::MethodDesc) CDAC_TYPE_FIELD(NativeCodeVersionNode, /*pointer*/, NativeCode, cdac_data::NativeCode) CDAC_TYPE_END(NativeCodeVersionNode) + +CDAC_TYPE_BEGIN(ProfControlBlock) +CDAC_TYPE_FIELD(ProfControlBlock, /*uint64*/, GlobalEventMask, offsetof(ProfControlBlock, globalEventMask)) +CDAC_TYPE_END(ProfControlBlock) + CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() @@ -482,6 +508,7 @@ CDAC_GLOBAL_POINTER(MiniMetaDataBuffAddress, &::g_MiniMetaDataBuffAddress) CDAC_GLOBAL_POINTER(MiniMetaDataBuffMaxSize, &::g_MiniMetaDataBuffMaxSize) CDAC_GLOBAL_POINTER(ExecutionManagerCodeRangeMapAddress, cdac_data::CodeRangeMapAddress) CDAC_GLOBAL_POINTER(PlatformMetadata, &::g_cdacPlatformMetadata) +CDAC_GLOBAL_POINTER(ProfilerControlBlock, &::g_profControlBlock) CDAC_GLOBALS_END() #undef CDAC_BASELINE diff --git a/src/coreclr/vm/assembly.cpp b/src/coreclr/vm/assembly.cpp index 8cff98c6d3f8e..e6be6728778d5 100644 --- a/src/coreclr/vm/assembly.cpp +++ b/src/coreclr/vm/assembly.cpp @@ -129,7 +129,7 @@ Assembly::Assembly(PEAssembly* pPEAssembly, LoaderAllocator *pLoaderAllocator) #endif , m_pLoaderAllocator{pLoaderAllocator} #ifdef FEATURE_COLLECTIBLE_TYPES - , m_isCollectible{pLoaderAllocator->IsCollectible() != FALSE} + , m_isCollectible{static_cast(pLoaderAllocator->IsCollectible() != FALSE ? 1 : 0)} #endif , m_isDynamic(false) , m_isLoading{true} diff --git a/src/coreclr/vm/assembly.hpp b/src/coreclr/vm/assembly.hpp index 8e533370b043e..3b2708744e235 100644 --- a/src/coreclr/vm/assembly.hpp +++ b/src/coreclr/vm/assembly.hpp @@ -516,7 +516,7 @@ class Assembly PTR_LoaderAllocator m_pLoaderAllocator; #ifdef FEATURE_COLLECTIBLE_TYPES - bool m_isCollectible; + BYTE m_isCollectible; #endif // FEATURE_COLLECTIBLE_TYPES bool m_isDynamic; @@ -536,6 +536,16 @@ class Assembly LOADERHANDLE m_hExposedObject; DomainAssembly* m_NextAssemblyInSameALC; + + friend struct ::cdac_data; +}; + +template<> +struct cdac_data +{ +#ifdef FEATURE_COLLECTIBLE_TYPES + static constexpr size_t IsCollectible = offsetof(Assembly, m_isCollectible); +#endif }; #ifndef DACCESS_COMPILE diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index 0559e0a7945ac..51fe52db4e434 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -1914,6 +1914,8 @@ template<> struct cdac_data static constexpr size_t Slot = offsetof(MethodDesc, m_wSlotNumber); static constexpr size_t Flags = offsetof(MethodDesc, m_wFlags); static constexpr size_t Flags3AndTokenRemainder = offsetof(MethodDesc, m_wFlags3AndTokenRemainder); + static constexpr size_t EntryPointFlags = offsetof(MethodDesc, m_bFlags4); + static constexpr size_t CodeData = offsetof(MethodDesc, m_codeData); }; #ifndef DACCESS_COMPILE diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs index be0935515b941..a81bdfe92fce9 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs @@ -55,4 +55,8 @@ internal abstract class ContractRegistry /// Gets an instance of the PrecodeStubs contract for the target. /// public abstract IPrecodeStubs PrecodeStubs { get; } + /// + /// Gets an instance of the ReJIT contract for the target. + /// + public abstract IReJIT ReJIT { get; } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs index 3cdf3b20627f7..6cb5eadbaf0ad 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs @@ -49,6 +49,7 @@ internal interface ILoader : IContract public virtual ModuleLookupTables GetLookupTables(ModuleHandle handle) => throw new NotImplementedException(); public virtual TargetPointer GetModuleLookupMapElement(TargetPointer table, uint token, out TargetNUInt flags) => throw new NotImplementedException(); + public virtual bool IsCollectible(ModuleHandle handle) => throw new NotImplementedException(); } internal readonly struct Loader : ILoader diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IReJIT.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IReJIT.cs new file mode 100644 index 0000000000000..6bb9f4e0f0f17 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IReJIT.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal interface IReJIT : IContract +{ + static string IContract.Name { get; } = nameof(ReJIT); + bool IsEnabled() => throw new NotImplementedException(); +} + +internal readonly struct ReJIT : IReJIT +{ + +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs index c93aa252abeb7..d0ad8db342b0d 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs @@ -160,6 +160,11 @@ internal interface IRuntimeTypeSystem : IContract public virtual TargetCodePointer GetNativeCode(MethodDescHandle methodDesc) => throw new NotImplementedException(); + public virtual ushort GetSlotNumber(MethodDescHandle methodDesc) => throw new NotImplementedException(); + + public virtual bool HasNativeCodeSlot(MethodDescHandle methodDesc) => throw new NotImplementedException(); + + public virtual TargetPointer GetAddressOfNativeCodeSlot(MethodDescHandle methodDesc) => throw new NotImplementedException(); #endregion MethodDesc inspection APIs } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs index 2f1176152c21e..8dc9a6cf5bbdc 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -30,6 +30,7 @@ public enum DataType RuntimeThreadLocals, Module, ModuleLookupMap, + Assembly, MethodTable, EEClass, ArrayClass, @@ -44,6 +45,7 @@ public enum DataType String, MethodDesc, MethodDescChunk, + MethodDescCodeData, PlatformMetadata, PrecodeMachineDescriptor, StubPrecodeData, @@ -63,4 +65,5 @@ public enum DataType MethodDescVersioningState, ILCodeVersioningState, NativeCodeVersionNode, + ProfControlBlock, } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs index 6632082b222d4..85d29e0eef271 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs @@ -42,5 +42,6 @@ internal static class Globals internal const string ExecutionManagerCodeRangeMapAddress = nameof(ExecutionManagerCodeRangeMapAddress); internal const string StubCodeBlockLast = nameof(StubCodeBlockLast); internal const string PlatformMetadata = nameof(PlatformMetadata); + internal const string ProfilerControlBlock = nameof(ProfilerControlBlock); } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersions_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersions_1.cs index b9f16764392fe..bd0b173640a12 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersions_1.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersions_1.cs @@ -132,6 +132,14 @@ internal enum MethodDescVersioningStateFlags : byte private NativeCodeVersionHandle GetSpecificNativeCodeVersion(IRuntimeTypeSystem rts, MethodDescHandle md, TargetCodePointer startAddress) { + // initial stage of NativeCodeVersionIterator::Next() with a null m_ilCodeFilter + TargetCodePointer firstNativeCode = rts.GetNativeCode(md); + if (firstNativeCode == startAddress) + { + NativeCodeVersionHandle first = new NativeCodeVersionHandle(md.Address, TargetPointer.Null); + return first; + } + // ImplicitCodeVersion stage of NativeCodeVersionIterator::Next() TargetPointer methodDescVersioningStateAddress = rts.GetMethodDescVersioningState(md); if (methodDescVersioningStateAddress == TargetPointer.Null) { diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager_1.EEJitManager.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager_1.EEJitManager.cs index 3033a128b2ee4..a3a828192e3fb 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager_1.EEJitManager.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager_1.EEJitManager.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -42,7 +41,7 @@ public override bool GetMethodInfo(RangeSection rangeSection, TargetCodePointer } TargetPointer codeHeaderAddress = Target.ReadPointer(codeHeaderIndirect); Data.RealCodeHeader realCodeHeader = Target.ProcessedData.GetOrAdd(codeHeaderAddress); - info = new CodeBlock(jittedCodeAddress, codeHeaderOffset, relativeOffset, realCodeHeader, rangeSection.Data!.JitManager); + info = new CodeBlock(start.Value, codeHeaderOffset, relativeOffset, realCodeHeader, rangeSection.Data!.JitManager); return true; } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs index b0867feb05c6a..7c04f8ee682b5 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs @@ -111,4 +111,12 @@ TargetPointer ILoader.GetModuleLookupMapElement(TargetPointer table, uint token, } while (table != TargetPointer.Null); return TargetPointer.Null; } + + bool ILoader.IsCollectible(ModuleHandle handle) + { + Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); + TargetPointer assembly = module.Assembly; + Data.Assembly la = _target.ProcessedData.GetOrAdd(assembly); + return la.IsCollectible != 0; + } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PrecodeStubs_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PrecodeStubs_1.cs index 1357dcac66d29..c3249aa5caf02 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PrecodeStubs_1.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PrecodeStubs_1.cs @@ -64,7 +64,7 @@ internal override TargetPointer GetMethodDesc(Target target, Data.PrecodeMachine } } - internal sealed class ThisPtrRetBufPrecode : ValidPrecode // FIXME: is this a StubPrecode? + internal sealed class ThisPtrRetBufPrecode : ValidPrecode { internal ThisPtrRetBufPrecode(TargetPointer instrPointer) : base(instrPointer, KnownPrecodeType.ThisPtrRetBuf) { } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ReJITFactory.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ReJITFactory.cs new file mode 100644 index 0000000000000..8530eec204ddf --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ReJITFactory.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal sealed class ReJITFactory : IContractFactory +{ + IReJIT IContractFactory.CreateContract(Target target, int version) + { + TargetPointer profControlBlockAddress = target.ReadGlobalPointer(Constants.Globals.ProfilerControlBlock); + Data.ProfControlBlock profControlBlock = target.ProcessedData.GetOrAdd(profControlBlockAddress); + return version switch + { + 1 => new ReJIT_1(target, profControlBlock), + _ => default(ReJIT), + }; + } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ReJIT_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ReJIT_1.cs new file mode 100644 index 0000000000000..d5bcd98bd4b43 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ReJIT_1.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal readonly partial struct ReJIT_1 : IReJIT +{ + internal readonly Target _target; + private readonly Data.ProfControlBlock _profControlBlock; + + // see src/coreclr/inc/corprof.idl + [Flags] + private enum COR_PRF_MONITOR + { + COR_PRF_ENABLE_REJIT = 0x00040000, + } + + public ReJIT_1(Target target, Data.ProfControlBlock profControlBlock) + { + _target = target; + _profControlBlock = profControlBlock; + } + + bool IReJIT.IsEnabled() + { + bool profEnabledReJIT = (_profControlBlock.GlobalEventMask & (ulong)COR_PRF_MONITOR.COR_PRF_ENABLE_REJIT) != 0; + // FIXME: it is very likely this is always true in the DAC + // Most people don't set DOTNET_ProfAPI_RejitOnAttach = 0 + // See https://github.com/dotnet/runtime/issues/106148 + bool clrConfigEnabledReJIT = true; + return profEnabledReJIT || clrConfigEnabledReJIT; + } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystemFactory.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystemFactory.cs index 1f1ab288793a5..30c9e8b4254ab 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystemFactory.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystemFactory.cs @@ -14,7 +14,7 @@ IRuntimeTypeSystem IContractFactory.CreateContract(Target ta ulong methodDescAlignment = target.ReadGlobal(Constants.Globals.MethodDescAlignment); return version switch { - 1 => new RuntimeTypeSystem_1(target, new RuntimeTypeSystemHelpers.TypeValidation(target), freeObjectMethodTable, methodDescAlignment), + 1 => new RuntimeTypeSystem_1(target, new RuntimeTypeSystemHelpers.TypeValidation(target), new RuntimeTypeSystemHelpers.MethodValidation(target, methodDescAlignment), freeObjectMethodTable, methodDescAlignment), _ => default(RuntimeTypeSystem), }; } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.NonValidated.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.NonValidated.cs deleted file mode 100644 index 958767b7a1730..0000000000000 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.NonValidated.cs +++ /dev/null @@ -1,135 +0,0 @@ -// 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.CodeAnalysis; - -namespace Microsoft.Diagnostics.DataContractReader.Contracts; - -internal partial struct RuntimeTypeSystem_1 : IRuntimeTypeSystem -{ - // GC Heap corruption may create situations where a pointer value may point to garbage or even - // to an unmapped memory region. - // All types here have not been validated as actually representing a MethodTable, EEClass, etc. - // All checks are unsafe and may throw if we access an invalid address in target memory. - internal static class NonValidated - { - - internal struct MethodDesc - { - private readonly Target _target; - private readonly Data.MethodDesc _desc; - private readonly Data.MethodDescChunk _chunk; - internal MethodDesc(Target target, Data.MethodDesc desc, Data.MethodDescChunk chunk) - { - _target = target; - _desc = desc; - _chunk = chunk; - } - - private bool HasFlag(MethodDescFlags flag) => (_desc.Flags & (ushort)flag) != 0; - - internal byte ChunkIndex => _desc.ChunkIndex; - internal TargetPointer MethodTable => _chunk.MethodTable; - internal ushort Slot => _desc.Slot; - internal bool HasNonVtableSlot => HasFlag(MethodDescFlags.HasNonVtableSlot); - } - - } - - private TargetPointer GetMethodDescChunkPointerThrowing(TargetPointer methodDescPointer, Data.MethodDesc umd) - { - ulong? methodDescChunkSize = _target.GetTypeInfo(DataType.MethodDescChunk).Size; - if (!methodDescChunkSize.HasValue) - { - throw new InvalidOperationException("Target has no definite MethodDescChunk size"); - } - // The runtime allocates a contiguous block of memory for a MethodDescChunk followed by MethodDescAlignment * Size bytes of space - // that is filled with MethodDesc (or its subclasses) instances. Each MethodDesc has a ChunkIndex that indicates its - // offset from the end of the MethodDescChunk. - ulong chunkAddress = (ulong)methodDescPointer - methodDescChunkSize.Value - umd.ChunkIndex * MethodDescAlignment; - return new TargetPointer(chunkAddress); - } - - private Data.MethodDescChunk GetMethodDescChunkThrowing(TargetPointer methodDescPointer, Data.MethodDesc md, out TargetPointer methodDescChunkPointer) - { - methodDescChunkPointer = GetMethodDescChunkPointerThrowing(methodDescPointer, md); - return new Data.MethodDescChunk(_target, methodDescChunkPointer); - } - - private NonValidated.MethodDesc GetMethodDescThrowing(TargetPointer methodDescPointer, out TargetPointer methodDescChunkPointer) - { - // may throw if the method desc at methodDescPointer is corrupted - // we bypass the target data cache here because we don't want to cache non-validated data - Data.MethodDesc desc = new Data.MethodDesc(_target, methodDescPointer); - Data.MethodDescChunk chunk = GetMethodDescChunkThrowing(methodDescPointer, desc, out methodDescChunkPointer); - return new NonValidated.MethodDesc(_target, desc, chunk); - } - - private bool ValidateMethodDescPointer(TargetPointer methodDescPointer, [NotNullWhen(true)] out TargetPointer methodDescChunkPointer) - { - methodDescChunkPointer = TargetPointer.Null; - try - { - NonValidated.MethodDesc umd = GetMethodDescThrowing(methodDescPointer, out methodDescChunkPointer); - TargetPointer methodTablePointer = umd.MethodTable; - if (methodTablePointer == TargetPointer.Null - || methodTablePointer == TargetPointer.Max64Bit - || methodTablePointer == TargetPointer.Max32Bit) - { - return false; - } - TypeHandle typeHandle = GetTypeHandle(methodTablePointer); - - if (umd.Slot >= GetNumVtableSlots(typeHandle) && !umd.HasNonVtableSlot) - { - return false; - } - // TODO: request.cpp - // TODO[cdac]: this needs a Precode lookup - // see MethodDescChunk::GetTemporaryEntryPoint -#if false - MethodDesc *pMDCheck = MethodDesc::GetMethodDescFromStubAddr(pMD->GetTemporaryEntryPoint(), TRUE); - - if (PTR_HOST_TO_TADDR(pMD) != PTR_HOST_TO_TADDR(pMDCheck)) - { - retval = FALSE; - } -#endif - - // TODO: request.cpp - // TODO[cdac]: needs MethodDesc::GetNativeCode and MethodDesc::GetMethodEntryPoint() -#if false - if (retval && pMD->HasNativeCode() && !pMD->IsFCall()) - { - PCODE jitCodeAddr = pMD->GetNativeCode(); - - MethodDesc *pMDCheck = ExecutionManager::GetCodeMethodDesc(jitCodeAddr); - if (pMDCheck) - { - // Check that the given MethodDesc matches the MethodDesc from - // the CodeHeader - if (PTR_HOST_TO_TADDR(pMD) != PTR_HOST_TO_TADDR(pMDCheck)) - { - retval = FALSE; - } - } - else - { - retval = FALSE; - } - } -#endif - - } - catch (System.Exception) - { - // TODO(cdac): maybe don't swallow all exceptions? We could consider a richer contract that - // helps to track down what sort of memory corruption caused the validation to fail. - // TODO(cdac): we could also consider a more fine-grained exception type so we don't mask - // programmer mistakes in cdacreader. - return false; - } - return true; - } -} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index 32b08e80ac9a4..5f19bd7d97fa4 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -16,6 +16,7 @@ internal partial struct RuntimeTypeSystem_1 : IRuntimeTypeSystem private readonly TargetPointer _freeObjectMethodTablePointer; private readonly ulong _methodDescAlignment; private readonly TypeValidation _typeValidation; + private readonly MethodValidation _methodValidation; // TODO(cdac): we mutate this dictionary - copies of the RuntimeTypeSystem_1 struct share this instance. // If we need to invalidate our view of memory, we should clear this dictionary. @@ -32,6 +33,7 @@ internal struct MethodTable internal TargetPointer Module { get; } internal TargetPointer EEClassOrCanonMT { get; } internal TargetPointer PerInstInfo { get; } + internal TargetPointer AuxiliaryData { get; } internal MethodTable(Data.MethodTable data) { Flags = new MethodTableFlags_1 @@ -46,11 +48,11 @@ internal MethodTable(Data.MethodTable data) Module = data.Module; ParentMethodTable = data.ParentMethodTable; PerInstInfo = data.PerInstInfo; + AuxiliaryData = data.AuxiliaryData; } // this MethodTable is a canonical MethodTable if its EEClassOrCanonMT is an EEClass internal bool IsCanonMT => MethodTableFlags_1.GetEEClassOrCanonMTBits(EEClassOrCanonMT) == MethodTableFlags_1.EEClassOrCanonMTBits.EEClass; - } // Low order bits of TypeHandle address. @@ -63,26 +65,6 @@ internal enum TypeHandleBits ValidMask = 2, } - internal enum MethodClassification - { - IL = 0, // IL - FCall = 1, // FCall (also includes tlbimped ctor, Delegate ctor) - PInvoke = 2, // PInvoke Method - EEImpl = 3, // special method; implementation provided by EE (like Delegate Invoke) - Array = 4, // Array ECall - Instantiated = 5, // Instantiated generic methods, including descriptors - // for both shared and unshared code (see InstantiatedMethodDesc) - ComInterop = 6, - Dynamic = 7, // for method desc with no metadata behind - } - - [Flags] - internal enum MethodDescFlags : ushort - { - ClassificationMask = 0x7, - HasNonVtableSlot = 0x0008, - } - internal enum InstantiatedMethodDescFlags2 : ushort { KindMask = 0x07, @@ -99,6 +81,16 @@ internal enum DynamicMethodDescExtendedFlags : uint IsILStub = 0x00008000, } + // on MethodDescChunk.FlagsAndTokenRange + [Flags] + 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, + } + internal struct MethodDesc { private readonly Data.MethodDesc _desc; @@ -106,11 +98,15 @@ internal struct MethodDesc private readonly Target _target; internal TargetPointer Address { get; init; } - internal MethodDesc(Target target, TargetPointer methodDescPointer, Data.MethodDesc desc, Data.MethodDescChunk chunk) + + internal TargetPointer ChunkAddress { get; init; } + + internal MethodDesc(Target target, TargetPointer methodDescPointer, Data.MethodDesc desc, TargetPointer methodDescChunkAddress, Data.MethodDescChunk chunk) { _target = target; _desc = desc; _chunk = chunk; + ChunkAddress = methodDescChunkAddress; Address = methodDescPointer; Token = ComputeToken(target, desc, chunk); @@ -123,8 +119,8 @@ internal MethodDesc(Target target, TargetPointer methodDescPointer, Data.MethodD private static uint ComputeToken(Target target, Data.MethodDesc desc, Data.MethodDescChunk chunk) { int tokenRemainderBitCount = target.ReadGlobal(Constants.Globals.MethodDescTokenRemainderBitCount); - int tokenRangeBitCount = 24 - tokenRemainderBitCount; - uint allRidBitsSet = 0xFFFFFF; + int tokenRangeBitCount = EcmaMetadataUtils.RowIdBitCount - tokenRemainderBitCount; + uint allRidBitsSet = EcmaMetadataUtils.RIDMask; uint tokenRemainderMask = allRidBitsSet >> tokenRangeBitCount; uint tokenRangeMask = allRidBitsSet >> tokenRemainderBitCount; @@ -134,7 +130,74 @@ private static uint ComputeToken(Target target, Data.MethodDesc desc, Data.Metho return 0x06000000 | tokenRange | tokenRemainder; } - public MethodClassification Classification => (MethodClassification)((int)_desc.Flags & (int)MethodDescFlags.ClassificationMask); + public MethodClassification Classification => (MethodClassification)((int)_desc.Flags & (int)MethodDescFlags_1.MethodDescFlags.ClassificationMask); + + private bool HasFlags(MethodDescFlags_1.MethodDescFlags flags) => (_desc.Flags & (ushort)flags) != 0; + internal bool HasFlags(MethodDescFlags_1.MethodDescFlags3 flags) => (_desc.Flags3AndTokenRemainder & (ushort)flags) != 0; + + internal bool HasFlags(MethodDescChunkFlags flags) => (_chunk.FlagsAndTokenRange & (ushort)flags) != 0; + + public bool IsEligibleForTieredCompilation => HasFlags(MethodDescFlags_1.MethodDescFlags3.IsEligibleForTieredCompilation); + + + public bool IsUnboxingStub => HasFlags(MethodDescFlags_1.MethodDescFlags3.IsUnboxingStub); + + public TargetPointer CodeData => _desc.CodeData; + public bool IsIL => Classification == MethodClassification.IL || Classification == MethodClassification.Instantiated; + + public bool HasNativeCodeSlot => HasFlags(MethodDescFlags_1.MethodDescFlags.HasNativeCodeSlot); + internal bool HasNonVtableSlot => HasFlags(MethodDescFlags_1.MethodDescFlags.HasNonVtableSlot); + internal bool HasMethodImpl => HasFlags(MethodDescFlags_1.MethodDescFlags.HasMethodImpl); + + internal bool HasStableEntryPoint => HasFlags(MethodDescFlags_1.MethodDescFlags3.HasStableEntryPoint); + internal bool HasPrecode => HasFlags(MethodDescFlags_1.MethodDescFlags3.HasPrecode); + + #region Additional Pointers + private int AdditionalPointersHelper(MethodDescFlags_1.MethodDescFlags extraFlags) + => int.PopCount(_desc.Flags & (ushort)extraFlags); + + // non-vtable slot, native code slot and MethodImpl slots are stored after the MethodDesc itself, packed tightly + // in the order: [non-vtable; methhod impl; native code]. + internal int NonVtableSlotIndex => HasNonVtableSlot ? 0 : throw new InvalidOperationException("no non-vtable slot"); + internal int MethodImplIndex + { + get + { + if (!HasMethodImpl) + { + throw new InvalidOperationException("no method impl slot"); + } + return AdditionalPointersHelper(MethodDescFlags_1.MethodDescFlags.HasNonVtableSlot); + } + } + internal int NativeCodeSlotIndex + { + get + { + if (!HasNativeCodeSlot) + { + throw new InvalidOperationException("no native code slot"); + } + return AdditionalPointersHelper(MethodDescFlags_1.MethodDescFlags.HasNonVtableSlot | MethodDescFlags_1.MethodDescFlags.HasMethodImpl); + } + } + + internal int AdditionalPointersCount => AdditionalPointersHelper(MethodDescFlags_1.MethodDescFlags.MethodDescAdditionalPointersMask); + #endregion Additional Pointers + + internal bool IsLoaderModuleAttachedToChunk => HasFlags(MethodDescChunkFlags.LoaderModuleAttachedToChunk); + + public ulong SizeOfChunk + { + get + { + ulong typeSize = _target.GetTypeInfo(DataType.MethodDescChunk).Size!.Value; + ulong chunkSize = (ulong)(_chunk.Size + 1) * _target.ReadGlobal(Constants.Globals.MethodDescAlignment); + ulong extra = IsLoaderModuleAttachedToChunk ? (ulong)_target.PointerSize : 0; + return typeSize + chunkSize + extra; + } + } + } private class InstantiatedMethodDesc : IData @@ -214,12 +277,14 @@ private StoredSigMethodDesc(Target target, TargetPointer methodDescPointer) } } - internal RuntimeTypeSystem_1(Target target, TypeValidation typeValidation, TargetPointer freeObjectMethodTablePointer, ulong methodDescAlignment) + internal RuntimeTypeSystem_1(Target target, TypeValidation typeValidation, MethodValidation methodValidation, TargetPointer freeObjectMethodTablePointer, ulong methodDescAlignment) { _target = target; _freeObjectMethodTablePointer = freeObjectMethodTablePointer; _methodDescAlignment = methodDescAlignment; _typeValidation = typeValidation; + _methodValidation = methodValidation; + _methodValidation.SetMethodTableQueries(new NonValidatedMethodTableQueries(this)); } internal TargetPointer FreeObjectMethodTablePointer => _freeObjectMethodTablePointer; @@ -588,18 +653,18 @@ public MethodDescHandle GetMethodDescHandle(TargetPointer methodDescPointer) if (_target.ProcessedData.TryGet(methodDescPointer, out Data.MethodDesc? methodDescData)) { // we already cached the data, we must have validated the address, create the representation struct for our use - TargetPointer mdescChunkPtr = GetMethodDescChunkPointerThrowing(methodDescPointer, methodDescData); + TargetPointer mdescChunkPtr = _methodValidation.GetMethodDescChunkPointerThrowing(methodDescPointer, methodDescData); // FIXME[cdac]: this isn't threadsafe if (!_target.ProcessedData.TryGet(mdescChunkPtr, out Data.MethodDescChunk? methodDescChunkData)) { throw new InvalidOperationException("cached MethodDesc data but not its containing MethodDescChunk"); } - MethodDesc validatedMethodDesc = new MethodDesc(_target, methodDescPointer, methodDescData, methodDescChunkData); + MethodDesc validatedMethodDesc = new MethodDesc(_target, methodDescPointer, methodDescData, mdescChunkPtr, methodDescChunkData); _ = _methodDescs.TryAdd(methodDescPointer, validatedMethodDesc); return new MethodDescHandle(methodDescPointer); } - if (!ValidateMethodDescPointer(methodDescPointer, out TargetPointer methodDescChunkPointer)) + if (!_methodValidation.ValidateMethodDescPointer(methodDescPointer, out TargetPointer methodDescChunkPointer)) { throw new InvalidOperationException("Invalid method desc pointer"); } @@ -608,7 +673,7 @@ public MethodDescHandle GetMethodDescHandle(TargetPointer methodDescPointer) Data.MethodDescChunk validatedMethodDescChunkData = _target.ProcessedData.GetOrAdd(methodDescChunkPointer); Data.MethodDesc validatedMethodDescData = _target.ProcessedData.GetOrAdd(methodDescPointer); - MethodDesc trustedMethodDescF = new MethodDesc(_target, methodDescPointer, validatedMethodDescData, validatedMethodDescChunkData); + MethodDesc trustedMethodDescF = new MethodDesc(_target, methodDescPointer, validatedMethodDescData, methodDescChunkPointer, validatedMethodDescChunkData); _ = _methodDescs.TryAdd(methodDescPointer, trustedMethodDescF); return new MethodDescHandle(methodDescPointer); } @@ -758,4 +823,293 @@ private MethodTable GetOrCreateMethodTable(MethodDesc methodDesc) _ = GetTypeHandle(methodDesc.MethodTable); return _methodTables[methodDesc.MethodTable]; } + + private struct VtableIndirections + { + // See methodtable.inl VTABLE_SLOTS_PER_CHUNK and the comment on it + private const int NumPointersPerIndirection = 8; + private const int NumPointersPerIndirectionLog2 = 3; + private readonly Target _target; + public readonly TargetPointer Address; + public VtableIndirections(Target target, TargetPointer address) + { + _target = target; + Address = address; + } + + public TargetPointer GetAddressOfSlot(uint slotNum) + { + TargetPointer indirectionPointer = Address + (ulong)(slotNum >> NumPointersPerIndirectionLog2) * (ulong)_target.PointerSize; + TargetPointer slotsStart = _target.ReadPointer(indirectionPointer); + return slotsStart + (ulong)(slotNum & (NumPointersPerIndirection - 1)) * (ulong)_target.PointerSize; + } + } + + private VtableIndirections GetVTableIndirections(TargetPointer methodTableAddress) + { + var typeInfo = _target.GetTypeInfo(DataType.MethodTable); + return new VtableIndirections(_target, methodTableAddress + typeInfo.Size!.Value); + } + + private TargetPointer GetAddressOfSlot(TypeHandle typeHandle, uint slotNum) + { + if (!typeHandle.IsMethodTable()) + throw new InvalidOperationException("typeHandle is not a MethodTable"); + MethodTable mt = _methodTables[typeHandle.Address]; + // MethodTable::GetSlotPtrRaw + // TODO(cdac): CONSISTENCY_CHECK(slotNum < GetNumVtableSlots()); + + if (slotNum < mt.NumVirtuals) + { + // Virtual slots live in chunks pointed to by vtable indirections + return GetVTableIndirections(typeHandle.Address).GetAddressOfSlot(slotNum); + } + else + { + // Non-virtual slots < GetNumVtableSlots live before the MethodTableAuxiliaryData. The array grows backwards + // TODO(cdac): _ASSERTE(HasNonVirtualSlots()); +#if false + return MethodTableAuxiliaryData::GetNonVirtualSlotsArray(GetAuxiliaryDataForWrite()) - (1 + (slotNum - GetNumVirtuals())); +#endif + throw new NotImplementedException(); // TODO(cdac): + } + + } + + private bool IsWrapperStub(MethodDesc md) + { + return md.IsUnboxingStub || IsInstantiatingStub(md); + } + + private bool IsInstantiatingStub(MethodDesc md) + { + return md.Classification == MethodClassification.Instantiated && !md.IsUnboxingStub && AsInstantiatedMethodDesc(md).IsWrapperStubWithInstantiations; + } + + private bool HasMethodInstantiation(MethodDesc md) + { + return md.Classification == MethodClassification.Instantiated && AsInstantiatedMethodDesc(md).HasMethodInstantiation; + } + + private TargetPointer GetLoaderModule(TypeHandle typeHandle) + { + if (!typeHandle.IsMethodTable()) + throw new NotImplementedException(); // TODO[cdac]: TypeDesc::GetLoaderModule() + MethodTable mt = _methodTables[typeHandle.Address]; + Data.MethodTableAuxiliaryData mtAuxData = _target.ProcessedData.GetOrAdd(mt.AuxiliaryData); + return mtAuxData.LoaderModule; + } + + private bool IsGenericMethodDefinition(MethodDesc md) + { + return md.Classification == MethodClassification.Instantiated && AsInstantiatedMethodDesc(md).IsGenericMethodDefinition; + } + + private TargetPointer GetLoaderModule(MethodDesc md) + { + // MethodDesc::GetLoaderModule: + // return GetMethodDescChunk()->GetLoaderModule(); + // MethodDescChunk::GetLoaderModule: + if (md.IsLoaderModuleAttachedToChunk) + { + TargetPointer methodDescChunkPointer = md.ChunkAddress; + TargetPointer endOfChunk = methodDescChunkPointer + md.SizeOfChunk; + TargetPointer ppLoaderModule = endOfChunk - (ulong)_target.PointerSize; + return _target.ReadPointer(ppLoaderModule); + } + else + { + TargetPointer mtAddr = GetMethodTable(new MethodDescHandle(md.Address)); + TypeHandle mt = GetTypeHandle(mtAddr); + return GetLoaderModule(mt); + } + } + + bool IRuntimeTypeSystem.IsCollectibleMethod(MethodDescHandle methodDesc) + { + MethodDesc md = _methodDescs[methodDesc.Address]; + TargetPointer loaderModuleAddr = GetLoaderModule(md); + ModuleHandle mod = _target.Contracts.Loader.GetModuleHandle(loaderModuleAddr); + return _target.Contracts.Loader.IsCollectible(mod); + } + + bool IRuntimeTypeSystem.IsVersionable(MethodDescHandle methodDesc) + { + MethodDesc md = _methodDescs[methodDesc.Address]; + if (md.IsEligibleForTieredCompilation) + return true; + // MethodDesc::IsEligibleForReJIT + if (_target.Contracts.ReJIT.IsEnabled()) + { + if (!md.IsIL) + return false; + if (IsWrapperStub(md)) + return false; + return _target.Contracts.CodeVersions.CodeVersionManagerSupportsMethod(methodDesc.Address); + } + return false; + } + + TargetPointer IRuntimeTypeSystem.GetMethodDescVersioningState(MethodDescHandle methodDesc) + { + MethodDesc md = _methodDescs[methodDesc.Address]; + TargetPointer codeDataAddress = md.CodeData; + Data.MethodDescCodeData codeData = _target.ProcessedData.GetOrAdd(codeDataAddress); + return codeData.VersioningState; + } + + uint IRuntimeTypeSystem.GetMethodToken(MethodDescHandle methodDescHandle) + { + MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; + return methodDesc.Token; + } + + ushort IRuntimeTypeSystem.GetSlotNumber(MethodDescHandle methodDesc) + { + MethodDesc md = _methodDescs[methodDesc.Address]; + return md.Slot; + } + bool IRuntimeTypeSystem.HasNativeCodeSlot(MethodDescHandle methodDesc) + { + MethodDesc md = _methodDescs[methodDesc.Address]; + return md.HasNativeCodeSlot; + } + + internal static DataType GetMethodClassificationDataType(MethodClassification classification) + => classification switch + { + MethodClassification.IL => DataType.MethodDesc, + MethodClassification.FCall => throw new NotImplementedException(), //TODO[cdac]: + MethodClassification.PInvoke => throw new NotImplementedException(), //TODO[cdac]: + MethodClassification.EEImpl => throw new NotImplementedException(), //TODO[cdac]: + MethodClassification.Array => throw new NotImplementedException(), //TODO[cdac]: + MethodClassification.Instantiated => DataType.InstantiatedMethodDesc, + MethodClassification.ComInterop => throw new NotImplementedException(), //TODO[cdac]: + MethodClassification.Dynamic => DataType.DynamicMethodDesc, + _ => throw new InvalidOperationException($"Unexpected method classification 0x{classification:x2} for MethodDesc") + }; + + private uint MethodDescAdditionalPointersOffset(MethodDesc md) + { + // See MethodDesc::GetBaseSize and s_ClassificationSizeTable + // sizeof(MethodDesc), mcIL + // sizeof(FCallMethodDesc), mcFCall + // sizeof(NDirectMethodDesc), mcPInvoke + // sizeof(EEImplMethodDesc), mcEEImpl + // sizeof(ArrayMethodDesc), mcArray + // sizeof(InstantiatedMethodDesc), mcInstantiated + // sizeof(CLRToCOMCallMethodDesc), mcComInterOp + // sizeof(DynamicMethodDesc) mcDynamic + MethodClassification cls = md.Classification; + DataType type = GetMethodClassificationDataType(cls); + return _target.GetTypeInfo(type).Size ?? throw new InvalidOperationException($"size of MethodDesc not known"); + } + + TargetPointer IRuntimeTypeSystem.GetAddressOfNativeCodeSlot(MethodDescHandle methodDesc) + { + MethodDesc md = _methodDescs[methodDesc.Address]; + uint offset = MethodDescAdditionalPointersOffset(md); + offset += (uint)(_target.PointerSize * md.NativeCodeSlotIndex); + return methodDesc.Address + offset; + } + private TargetPointer GetAddressOfNonVtableSlot(TargetPointer methodDescPointer, MethodDesc md) + { + uint offset = MethodDescAdditionalPointersOffset(md); + offset += (uint)(_target.PointerSize * md.NonVtableSlotIndex); + return methodDescPointer.Value + offset; + } + + private TargetCodePointer CodePointerFromAddress(TargetPointer address) + { + IPlatformMetadata metadata = _target.Contracts.PlatformMetadata; + CodePointerFlags flags = metadata.GetCodePointerFlags(); + if (flags.HasFlag(CodePointerFlags.HasArm32ThumbBit)) + { + return new TargetCodePointer(address.Value | 1); + } else if (flags.HasFlag(CodePointerFlags.HasArm64PtrAuth)) + { + throw new NotImplementedException("CodePointerFromAddress: ARM64 with pointer authentication"); + } + Debug.Assert(flags == default); + return new TargetCodePointer(address.Value); + } + + TargetCodePointer IRuntimeTypeSystem.GetNativeCode(MethodDescHandle methodDescHandle) + { + MethodDesc md = _methodDescs[methodDescHandle.Address]; + // TODO(cdac): _ASSERTE(!IsDefaultInterfaceMethod() || HasNativeCodeSlot()); + if (md.HasNativeCodeSlot) + { + // When profiler is enabled, profiler may ask to rejit a code even though we + // we have ngen code for this MethodDesc. (See MethodDesc::DoPrestub). + // This means that *ppCode is not stable. It can turn from non-zero to zero. + TargetPointer ppCode = ((IRuntimeTypeSystem)this).GetAddressOfNativeCodeSlot(methodDescHandle); + TargetCodePointer pCode = _target.ReadCodePointer(ppCode); + return CodePointerFromAddress(pCode.AsTargetPointer);; + } + + if (!md.HasStableEntryPoint || md.HasPrecode) + return TargetCodePointer.Null; + + return GetStableEntryPoint(methodDescHandle.Address, md); + } + + private TargetCodePointer GetStableEntryPoint(TargetPointer methodDescAddress, MethodDesc md) + { + // TODO(cdac): _ASSERTE(HasStableEntryPoint()); + // TODO(cdac): _ASSERTE(!IsVersionableWithVtableSlotBackpatch()); + + return GetMethodEntryPointIfExists(methodDescAddress, md); + } + + private TargetCodePointer GetMethodEntryPointIfExists(TargetPointer methodDescAddress, MethodDesc md) + { + if (md.HasNonVtableSlot) + { + TargetPointer pSlot = GetAddressOfNonVtableSlot(methodDescAddress, md); + + return _target.ReadCodePointer(pSlot); + } + + TargetPointer methodTablePointer = md.MethodTable; + TypeHandle typeHandle = GetTypeHandle(methodTablePointer); + // TODO: cdac: _ASSERTE(GetMethodTable()->IsCanonicalMethodTable()); + TargetPointer addrOfSlot = GetAddressOfSlot(typeHandle, md.Slot); + return _target.ReadCodePointer(addrOfSlot); + } + + private class NonValidatedMethodTableQueries : MethodValidation.IMethodTableQueries + { + private readonly RuntimeTypeSystem_1 _rts; + public NonValidatedMethodTableQueries(RuntimeTypeSystem_1 rts) + { + _rts = rts; + } + + public bool SlotIsVtableSlot(TargetPointer methodTablePointer, uint slot) + { + return _rts.SlotIsVtableSlot(methodTablePointer, slot); + } + + public TargetPointer GetAddressOfMethodTableSlot(TargetPointer methodTablePointer, uint slot) + { + return _rts.GetAddressOfMethodTableSlot(methodTablePointer, slot); + } + } + + // for the benefit of MethodValidation + private TargetPointer GetAddressOfMethodTableSlot(TargetPointer methodTablePointer, uint slot) + { + TypeHandle typeHandle = GetTypeHandle(methodTablePointer); + Debug.Assert(_methodTables[typeHandle.Address].IsCanonMT); + TargetPointer addrOfSlot = GetAddressOfSlot(typeHandle, slot); + return addrOfSlot; + } + + private bool SlotIsVtableSlot(TargetPointer methodTablePointer, uint slot) + { + TypeHandle typeHandle = GetTypeHandle(methodTablePointer); + return slot < GetNumVtableSlots(typeHandle); + } + } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Assembly.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Assembly.cs new file mode 100644 index 0000000000000..20a9a9ed5a4c9 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Assembly.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class Assembly : IData +{ + static Assembly IData.Create(Target target, TargetPointer address) => new Assembly(target, address); + public Assembly(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.Assembly); + + IsCollectible = target.Read(address + (ulong)type.Fields[nameof(IsCollectible)].Offset); + } + + public byte IsCollectible { get; init; } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDesc.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDesc.cs index d51153ab53e59..eea2a61619421 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDesc.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDesc.cs @@ -16,12 +16,17 @@ public MethodDesc(Target target, TargetPointer address) Slot = target.Read(address + (ulong)type.Fields[nameof(Slot)].Offset); Flags = target.Read(address + (ulong)type.Fields[nameof(Flags)].Offset); Flags3AndTokenRemainder = target.Read(address + (ulong)type.Fields[nameof(Flags3AndTokenRemainder)].Offset); + EntryPointFlags = target.Read(address + (ulong)type.Fields[nameof(EntryPointFlags)].Offset); + CodeData = target.ReadPointer(address + (ulong)type.Fields[nameof(CodeData)].Offset); } public byte ChunkIndex { get; init; } public ushort Slot { get; init; } public ushort Flags { get; init; } public ushort Flags3AndTokenRemainder { get; init; } + public byte EntryPointFlags { get; init; } + + public TargetPointer CodeData { get; set; } } internal sealed class InstantiatedMethodDesc : IData diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDescCodeData.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDescCodeData.cs new file mode 100644 index 0000000000000..dc76b8981b610 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDescCodeData.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class MethodDescCodeData : IData +{ + static MethodDescCodeData IData.Create(Target target, TargetPointer address) => new MethodDescCodeData(target, address); + public MethodDescCodeData(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.MethodDescCodeData); + + TemporaryEntryPoint = target.ReadCodePointer(address + (ulong)type.Fields[nameof(TemporaryEntryPoint)].Offset); + VersioningState = target.ReadPointer(address + (ulong)type.Fields[nameof(VersioningState)].Offset); + } + + public TargetCodePointer TemporaryEntryPoint { get; set; } + public TargetPointer VersioningState { get; set; } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodTable.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodTable.cs index 6461a9521f472..522927b02af44 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodTable.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodTable.cs @@ -19,6 +19,7 @@ public MethodTable(Target target, TargetPointer address) NumInterfaces = target.Read(address + (ulong)type.Fields[nameof(NumInterfaces)].Offset); NumVirtuals = target.Read(address + (ulong)type.Fields[nameof(NumVirtuals)].Offset); PerInstInfo = target.ReadPointer(address + (ulong)type.Fields[nameof(PerInstInfo)].Offset); + AuxiliaryData = target.ReadPointer(address + (ulong)type.Fields[nameof(AuxiliaryData)].Offset); } public uint MTFlags { get; init; } @@ -30,4 +31,5 @@ public MethodTable(Target target, TargetPointer address) public TargetPointer PerInstInfo { get; init; } public ushort NumInterfaces { get; init; } public ushort NumVirtuals { get; init; } + public TargetPointer AuxiliaryData { get; init; } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodTableAuxiliaryData.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodTableAuxiliaryData.cs index e04e5b56b0c54..496b4bdd65d96 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodTableAuxiliaryData.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodTableAuxiliaryData.cs @@ -11,9 +11,9 @@ private MethodTableAuxiliaryData(Target target, TargetPointer address) { Target.TypeInfo type = target.GetTypeInfo(DataType.MethodTableAuxiliaryData); - AuxFlags = target.Read(address + (ulong)type.Fields[nameof(AuxFlags)].Offset); + LoaderModule = target.ReadPointer(address + (ulong)type.Fields[nameof(LoaderModule)].Offset); } - public uint AuxFlags { get; init; } + public TargetPointer LoaderModule { get; init; } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ProfControlBlock.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ProfControlBlock.cs new file mode 100644 index 0000000000000..e598647b7dee6 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ProfControlBlock.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class ProfControlBlock : IData +{ + static ProfControlBlock IData.Create(Target target, TargetPointer address) + => new ProfControlBlock(target, address); + + public ProfControlBlock(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.ProfControlBlock); + GlobalEventMask = target.Read(address + (ulong)type.Fields[nameof(GlobalEventMask)].Offset); + } + + public ulong GlobalEventMask { get; init; } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodClassification.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodClassification.cs new file mode 100644 index 0000000000000..4291b13e53cc9 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodClassification.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +using System; + +namespace Microsoft.Diagnostics.DataContractReader.RuntimeTypeSystemHelpers; + +internal enum MethodClassification +{ + IL = 0, // IL + FCall = 1, // FCall (also includes tlbimped ctor, Delegate ctor) + PInvoke = 2, // PInvoke Method + EEImpl = 3, // special method; implementation provided by EE (like Delegate Invoke) + Array = 4, // Array ECall + Instantiated = 5, // Instantiated generic methods, including descriptors + // for both shared and unshared code (see InstantiatedMethodDesc) + ComInterop = 6, + Dynamic = 7, // for method desc with no metadata behind +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescFlags_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescFlags_1.cs new file mode 100644 index 0000000000000..b4e33e29f38fb --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescFlags_1.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +using System; + +namespace Microsoft.Diagnostics.DataContractReader.RuntimeTypeSystemHelpers; + +internal static class MethodDescFlags_1 +{ + [Flags] + internal enum MethodDescFlags : ushort + { + ClassificationMask = 0x7, + #region Additional pointers + // The below flags each imply that there's an extra pointer-sized piece of data after the MethodDesc in the MethodDescChunk + HasNonVtableSlot = 0x0008, + HasMethodImpl = 0x0010, + HasNativeCodeSlot = 0x0020, + // Mask for the above flags + MethodDescAdditionalPointersMask = 0x0038, + #endregion Additional pointers + } + + [Flags] + internal enum MethodDescFlags3 : ushort + { + // HasPrecode implies that HasStableEntryPoint is set. + HasStableEntryPoint = 0x1000, // The method entrypoint is stable (either precode or actual code) + HasPrecode = 0x2000, // Precode has been allocated for this method + IsUnboxingStub = 0x4000, + IsEligibleForTieredCompilation = 0x8000, + } + + [Flags] + internal enum MethodDescEntryPointFlags : byte + { + TemporaryEntryPointAssigned = 0x04, + } + +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodValidation.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodValidation.cs new file mode 100644 index 0000000000000..3471b7ffba8d5 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodValidation.cs @@ -0,0 +1,306 @@ +// 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.Diagnostics.CodeAnalysis; +using Microsoft.Diagnostics.DataContractReader.Contracts; + +namespace Microsoft.Diagnostics.DataContractReader.RuntimeTypeSystemHelpers; + +// GC Heap corruption may create situations where a pointer value may point to garbage or even +// to an unmapped memory region. +// All types here have not been validated as actually representing a MethodTable, EEClass, etc. +// All checks are unsafe and may throw if we access an invalid address in target memory. +internal class MethodValidation +{ + internal interface IMethodTableQueries + { + TargetPointer GetAddressOfMethodTableSlot(TargetPointer methodTablePointer, uint slot); + bool SlotIsVtableSlot(TargetPointer methodTablePointer, uint slot); + } + + private class NIEMethodTableQueries : IMethodTableQueries + { + public TargetPointer GetAddressOfMethodTableSlot(TargetPointer methodTablePointer, uint slot) => throw new NotImplementedException(); + + public bool SlotIsVtableSlot(TargetPointer methodTablePointer, uint slot) => throw new NotImplementedException(); + + internal static NIEMethodTableQueries s_Instance = new NIEMethodTableQueries(); + } + + private readonly Target _target; + + private readonly ulong _methodDescAlignment; + + private IMethodTableQueries _methodTableQueries; + + internal MethodValidation(Target target, ulong methodDescAlignment) + { + _target = target; + _methodDescAlignment = methodDescAlignment; + _methodTableQueries = NIEMethodTableQueries.s_Instance; + } + + internal void SetMethodTableQueries(IMethodTableQueries methodTableQueries) + { + _methodTableQueries = methodTableQueries; + } + + internal struct NonValidatedMethodDesc + { + private readonly Target _target; + private readonly Data.MethodDesc _desc; + private readonly Data.MethodDescChunk _chunk; + internal NonValidatedMethodDesc(Target target, Data.MethodDesc desc, Data.MethodDescChunk chunk) + { + _target = target; + _desc = desc; + _chunk = chunk; + } + + private bool HasFlags(MethodDescFlags_1.MethodDescFlags flag) => (_desc.Flags & (ushort)flag) != 0; + private bool HasFlags(MethodDescFlags_1.MethodDescEntryPointFlags flag) => (_desc.EntryPointFlags & (byte)flag) != 0; + + private bool HasFlags(MethodDescFlags_1.MethodDescFlags3 flag) => (_desc.Flags3AndTokenRemainder & (ushort)flag) != 0; + + internal byte ChunkIndex => _desc.ChunkIndex; + internal TargetPointer MethodTable => _chunk.MethodTable; + internal ushort Slot => _desc.Slot; + internal bool HasNonVtableSlot => HasFlags(MethodDescFlags_1.MethodDescFlags.HasNonVtableSlot); + internal bool HasMethodImpl => HasFlags(MethodDescFlags_1.MethodDescFlags.HasMethodImpl); + internal bool HasNativeCodeSlot => HasFlags(MethodDescFlags_1.MethodDescFlags.HasNativeCodeSlot); + + internal bool TemporaryEntryPointAssigned => HasFlags(MethodDescFlags_1.MethodDescEntryPointFlags.TemporaryEntryPointAssigned); + + internal TargetPointer CodeData => _desc.CodeData; + + internal MethodClassification Classification => (MethodClassification)(_desc.Flags & (ushort)MethodDescFlags_1.MethodDescFlags.ClassificationMask); + internal bool IsFCall => Classification == MethodClassification.FCall; + + #region Additional Pointers + private int AdditionalPointersHelper(MethodDescFlags_1.MethodDescFlags extraFlags) + => int.PopCount(_desc.Flags & (ushort)extraFlags); + + // non-vtable slot, native code slot and MethodImpl slots are stored after the MethodDesc itself, packed tightly + // in the order: [non-vtable; methhod impl; native code]. + internal int NonVtableSlotIndex => HasNonVtableSlot ? 0 : throw new InvalidOperationException("no non-vtable slot"); + internal int MethodImplIndex + { + get + { + if (!HasMethodImpl) + { + throw new InvalidOperationException("no method impl slot"); + } + return AdditionalPointersHelper(MethodDescFlags_1.MethodDescFlags.HasNonVtableSlot); + } + } + internal int NativeCodeSlotIndex + { + get + { + if (!HasNativeCodeSlot) + { + throw new InvalidOperationException("no native code slot"); + } + return AdditionalPointersHelper(MethodDescFlags_1.MethodDescFlags.HasNonVtableSlot | MethodDescFlags_1.MethodDescFlags.HasMethodImpl); + } + } + + internal int AdditionalPointersCount => AdditionalPointersHelper(MethodDescFlags_1.MethodDescFlags.MethodDescAdditionalPointersMask); + #endregion Additional Pointers + + internal bool HasStableEntryPoint => HasFlags(MethodDescFlags_1.MethodDescFlags3.HasStableEntryPoint); + internal bool HasPrecode => HasFlags(MethodDescFlags_1.MethodDescFlags3.HasPrecode); + } + + internal TargetPointer GetMethodDescChunkPointerThrowing(TargetPointer methodDescPointer, Data.MethodDesc umd) + { + ulong? methodDescChunkSize = _target.GetTypeInfo(DataType.MethodDescChunk).Size; + if (!methodDescChunkSize.HasValue) + { + throw new InvalidOperationException("Target has no definite MethodDescChunk size"); + } + // The runtime allocates a contiguous block of memory for a MethodDescChunk followed by MethodDescAlignment * Size bytes of space + // that is filled with MethodDesc (or its subclasses) instances. Each MethodDesc has a ChunkIndex that indicates its + // offset from the end of the MethodDescChunk. + ulong chunkAddress = (ulong)methodDescPointer - methodDescChunkSize.Value - umd.ChunkIndex * _methodDescAlignment; + return new TargetPointer(chunkAddress); + } + + private Data.MethodDescChunk GetMethodDescChunkThrowing(TargetPointer methodDescPointer, Data.MethodDesc md, out TargetPointer methodDescChunkPointer) + { + methodDescChunkPointer = GetMethodDescChunkPointerThrowing(methodDescPointer, md); + return new Data.MethodDescChunk(_target, methodDescChunkPointer); + } + + private NonValidatedMethodDesc GetMethodDescThrowing(TargetPointer methodDescPointer, out TargetPointer methodDescChunkPointer) + { + // may throw if the method desc at methodDescPointer is corrupted + // we bypass the target data cache here because we don't want to cache non-validated data + Data.MethodDesc desc = new Data.MethodDesc(_target, methodDescPointer); + Data.MethodDescChunk chunk = GetMethodDescChunkThrowing(methodDescPointer, desc, out methodDescChunkPointer); + return new NonValidatedMethodDesc(_target, desc, chunk); + } + + private TargetCodePointer GetTemporaryEntryPointIfExists(NonValidatedMethodDesc umd) + { + if (!umd.TemporaryEntryPointAssigned || umd.CodeData == TargetPointer.Null) + { + return TargetCodePointer.Null; + } + Data.MethodDescCodeData codeData = _target.ProcessedData.GetOrAdd(umd.CodeData); + return codeData.TemporaryEntryPoint; + } + + private TargetPointer GetAddrOfNativeCodeSlot(TargetPointer methodDescPointer, NonValidatedMethodDesc umd) + { + uint offset = MethodDescAdditionalPointersOffset(umd); + offset += (uint)(_target.PointerSize * umd.NativeCodeSlotIndex); + return methodDescPointer.Value + offset; + } + + private TargetPointer GetAddressOfNonVtableSlot(TargetPointer methodDescPointer, NonValidatedMethodDesc umd) + { + uint offset = MethodDescAdditionalPointersOffset(umd); + offset += (uint)(_target.PointerSize * umd.NonVtableSlotIndex); + return methodDescPointer.Value + offset; + } + + // FIXME[cdac]: this is copypasted from RuntimeTypeSystem_1 - put it in a a shared class instead + private TargetCodePointer CodePointerFromAddress(TargetPointer address) + { + IPlatformMetadata metadata = _target.Contracts.PlatformMetadata; + CodePointerFlags flags = metadata.GetCodePointerFlags(); + if (flags.HasFlag(CodePointerFlags.HasArm32ThumbBit)) + { + return new TargetCodePointer(address.Value | 1); + } else if (flags.HasFlag(CodePointerFlags.HasArm64PtrAuth)) + { + throw new NotImplementedException("CodePointerFromAddress: ARM64 with pointer authentication"); + } + Debug.Assert(flags == default); + return new TargetCodePointer(address.Value); + } + + private TargetCodePointer GetCodePointer(TargetPointer methodDescPointer, NonValidatedMethodDesc umd) + { + // TODO(cdac): _ASSERTE(!IsDefaultInterfaceMethod() || HasNativeCodeSlot()); + if (umd.HasNativeCodeSlot) + { + // When profiler is enabled, profiler may ask to rejit a code even though we + // we have ngen code for this MethodDesc. (See MethodDesc::DoPrestub). + // This means that *ppCode is not stable. It can turn from non-zero to zero. + TargetPointer ppCode = GetAddrOfNativeCodeSlot(methodDescPointer, umd); + TargetCodePointer pCode = _target.ReadCodePointer(ppCode); + + return CodePointerFromAddress(pCode.AsTargetPointer); + } + + if (!umd.HasStableEntryPoint || umd.HasPrecode) + return TargetCodePointer.Null; + + return GetStableEntryPoint(methodDescPointer, umd); + } + + private TargetCodePointer GetStableEntryPoint(TargetPointer methodDescPointer, NonValidatedMethodDesc umd) + { + Debug.Assert(umd.HasStableEntryPoint); + // TODO(cdac): _ASSERTE(!IsVersionableWithVtableSlotBackpatch()); + + return GetMethodEntryPointIfExists(methodDescPointer, umd); + } + + + private TargetCodePointer GetMethodEntryPointIfExists(TargetPointer methodDescAddress, NonValidatedMethodDesc umd) + { + if (umd.HasNonVtableSlot) + { + TargetPointer pSlot = GetAddressOfNonVtableSlot(methodDescAddress, umd); + + return _target.ReadCodePointer(pSlot); + } + + TargetPointer methodTablePointer = umd.MethodTable; + Debug.Assert(methodTablePointer != TargetPointer.Null); + TargetPointer addrOfSlot = _methodTableQueries.GetAddressOfMethodTableSlot(methodTablePointer, umd.Slot); + return _target.ReadCodePointer(addrOfSlot); + } + + private uint MethodDescAdditionalPointersOffset(NonValidatedMethodDesc umd) + { + MethodClassification cls = umd.Classification; + DataType type = RuntimeTypeSystem_1.GetMethodClassificationDataType(cls); + return _target.GetTypeInfo(type).Size ?? throw new InvalidOperationException("size of MethodDesc not known"); + } + + internal uint GetMethodDescBaseSize(NonValidatedMethodDesc umd) + { + uint baseSize = MethodDescAdditionalPointersOffset(umd); + baseSize += (uint)(_target.PointerSize * umd.AdditionalPointersCount); + return baseSize; + } + + private bool HasNativeCode(TargetPointer methodDescPointer, NonValidatedMethodDesc umd) => GetCodePointer(methodDescPointer, umd) != TargetCodePointer.Null; + + internal bool ValidateMethodDescPointer(TargetPointer methodDescPointer, [NotNullWhen(true)] out TargetPointer methodDescChunkPointer) + { + methodDescChunkPointer = TargetPointer.Null; + try + { + NonValidatedMethodDesc umd = GetMethodDescThrowing(methodDescPointer, out methodDescChunkPointer); + TargetPointer methodTablePointer = umd.MethodTable; + if (methodTablePointer == TargetPointer.Null + || methodTablePointer == TargetPointer.Max64Bit + || methodTablePointer == TargetPointer.Max32Bit) + { + return false; + } + + if (!umd.HasNonVtableSlot && !_methodTableQueries.SlotIsVtableSlot(methodTablePointer, umd.Slot)) + { + return false; + } + + TargetCodePointer temporaryEntryPoint = GetTemporaryEntryPointIfExists(umd); + if (temporaryEntryPoint != TargetCodePointer.Null) + { + Contracts.IPrecodeStubs precode = _target.Contracts.PrecodeStubs; + TargetPointer methodDesc = precode.GetMethodDescFromStubAddress(temporaryEntryPoint); + if (methodDesc != methodDescPointer) + { + return false; + } + } + + if (HasNativeCode(methodDescPointer, umd) && !umd.IsFCall) + { + TargetCodePointer jitCodeAddr = GetCodePointer(methodDescPointer, umd); + Contracts.IExecutionManager executionManager = _target.Contracts.ExecutionManager; + CodeBlockHandle? codeInfo = executionManager.GetCodeBlockHandle(jitCodeAddr); + if (!codeInfo.HasValue) + { + return false; + } + TargetPointer methodDesc = executionManager.GetMethodDesc(codeInfo.Value); + if (methodDesc == TargetPointer.Null) + { + return false; + } + if (methodDesc != methodDescPointer) + { + return false; + } + } + } + catch (System.Exception) + { + // TODO(cdac): maybe don't swallow all exceptions? We could consider a richer contract that + // helps to track down what sort of memory corruption caused the validation to fail. + // TODO(cdac): we could also consider a more fine-grained exception type so we don't mask + // programmer mistakes in cdacreader. + return false; + } + return true; + } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs index 1f38f8e9d20c5..f2602ce5e1e6c 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs @@ -36,6 +36,7 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG [typeof(ICodeVersions)] = new CodeVersionsFactory(), [typeof(IPlatformMetadata)] = new PlatformMetadataFactory(), [typeof(IPrecodeStubs)] = new PrecodeStubsFactory(), + [typeof(IReJIT)] = new ReJITFactory(), }; configureFactories?.Invoke(_factories); } @@ -51,6 +52,7 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG public override ICodeVersions CodeVersions => GetContract(); public override IPlatformMetadata PlatformMetadata => GetContract(); public override IPrecodeStubs PrecodeStubs => GetContract(); + public override IReJIT ReJIT => GetContract(); private TContract GetContract() where TContract : IContract { diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index 4e7c70f563fd2..dce6b1bf9d0b2 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -190,21 +190,157 @@ int ISOSDacInterface.GetMethodDescData(ulong methodDesc, ulong ip, DacpMethodDes // elements we return return HResults.E_INVALIDARG; } + + int hr = HResults.E_NOTIMPL; try { + if (cRevertedRejitVersions != 0) + { + throw new NotImplementedException(); // TODO[cdac]: rejit + } Contracts.IRuntimeTypeSystem rtsContract = _target.Contracts.RuntimeTypeSystem; Contracts.MethodDescHandle methodDescHandle = rtsContract.GetMethodDescHandle(methodDesc); + Contracts.ICodeVersions nativeCodeContract = _target.Contracts.CodeVersions; + + if (rgRevertedRejitData != null) + { + NativeMemory.Clear(rgRevertedRejitData, (nuint)(sizeof(DacpReJitData) * cRevertedRejitVersions)); + } + if (pcNeededRevertedRejitData != null) + { + *pcNeededRevertedRejitData = 0; + } - data->MethodTablePtr = rtsContract.GetMethodTable(methodDescHandle); + NativeCodeVersionHandle requestedNativeCodeVersion; + NativeCodeVersionHandle? activeNativeCodeVersion = null; + if (ip != 0) + { + requestedNativeCodeVersion = nativeCodeContract.GetNativeCodeVersionForIP(new TargetCodePointer(ip)); + } + else + { + requestedNativeCodeVersion = nativeCodeContract.GetActiveNativeCodeVersion(new TargetPointer(methodDesc)); + activeNativeCodeVersion = requestedNativeCodeVersion; + } + + data->requestedIP = ip; + data->bIsDynamic = rtsContract.IsDynamicMethod(methodDescHandle) ? 1 : 0; + data->wSlotNumber = rtsContract.GetSlotNumber(methodDescHandle); + TargetCodePointer nativeCodeAddr = TargetCodePointer.Null; + if (requestedNativeCodeVersion.Valid) + { + nativeCodeAddr = nativeCodeContract.GetNativeCode(requestedNativeCodeVersion); + } + if (nativeCodeAddr != TargetCodePointer.Null) + { + data->bHasNativeCode = 1; + data->NativeCodeAddr = nativeCodeAddr; + } + else + { + data->bHasNativeCode = 0; + data->NativeCodeAddr = 0xffffffff_fffffffful; + } + if (rtsContract.HasNativeCodeSlot(methodDescHandle)) + { + data->AddressOfNativeCodeSlot = rtsContract.GetAddressOfNativeCodeSlot(methodDescHandle); + } + else + { + data->AddressOfNativeCodeSlot = 0; + } + data->MDToken = rtsContract.GetMethodToken(methodDescHandle); + data->MethodDescPtr = methodDesc; + TargetPointer methodTableAddr = rtsContract.GetMethodTable(methodDescHandle); + data->MethodTablePtr = methodTableAddr; + TypeHandle typeHandle = rtsContract.GetTypeHandle(methodTableAddr); + data->ModulePtr = rtsContract.GetModule(typeHandle); + + // TODO[cdac]: everything in the ReJIT TRY/CATCH in GetMethodDescDataImpl in request.cpp + if (pcNeededRevertedRejitData != null) + { + + throw new NotImplementedException(); // TODO[cdac]: rejit stuff + } + +#if false // TODO[cdac]: HAVE_GCCOVER + if (requestedNativeCodeVersion.Valid) + { + TargetPointer gcCoverAddr = nativeCodeContract.GetGCCoverageInfo(requestedNativeCodeVersion); + if (gcCoverAddr != TargetPointer.Null) + { + throw new NotImplementedException(); // TODO[cdac]: gc stress code copy + } + } +#endif + + if (data->bIsDynamic != 0) + { + throw new NotImplementedException(); // TODO[cdac]: get the dynamic method managed object + } + + hr = HResults.S_OK; } catch (global::System.Exception ex) { - return ex.HResult; + hr = ex.HResult; } - return _legacyImpl is not null - ? _legacyImpl.GetMethodDescData(methodDesc, ip, data, cRevertedRejitVersions, rgRevertedRejitData, pcNeededRevertedRejitData) - : HResults.E_NOTIMPL; +#if DEBUG + if (_legacyImpl is not null) + { + if (hr == HResults.S_OK) { + DacpMethodDescData dataLocal = default; + DacpReJitData[]? rgRevertedRejitDataLocal = null; + if (rgRevertedRejitData != null) + { + rgRevertedRejitDataLocal = new DacpReJitData[cRevertedRejitVersions]; + } + uint cNeededRevertedRejitDataLocal = 0; + uint *pcNeededRevertedRejitDataLocal = null; + if (pcNeededRevertedRejitData != null) + { + pcNeededRevertedRejitDataLocal = &cNeededRevertedRejitDataLocal; + } + int hrLocal; + fixed (DacpReJitData* rgRevertedRejitDataLocalPtr = rgRevertedRejitDataLocal) + { + hrLocal = _legacyImpl.GetMethodDescData(methodDesc, ip, &dataLocal, cRevertedRejitVersions, rgRevertedRejitDataLocalPtr, pcNeededRevertedRejitDataLocal); + } + Debug.Assert(hrLocal == hr); + Debug.Assert(data->bHasNativeCode == dataLocal.bHasNativeCode); + Debug.Assert(data->bIsDynamic == dataLocal.bIsDynamic); + Debug.Assert(data->wSlotNumber == dataLocal.wSlotNumber); + Debug.Assert(data->NativeCodeAddr == dataLocal.NativeCodeAddr); + Debug.Assert(data->AddressOfNativeCodeSlot == dataLocal.AddressOfNativeCodeSlot); + Debug.Assert(data->MethodDescPtr == dataLocal.MethodDescPtr); + Debug.Assert(data->MethodTablePtr == dataLocal.MethodTablePtr); + Debug.Assert(data->ModulePtr == dataLocal.ModulePtr); + Debug.Assert(data->MDToken == dataLocal.MDToken); + Debug.Assert(data->GCInfo == dataLocal.GCInfo); + Debug.Assert(data->GCStressCodeCopy == dataLocal.GCStressCodeCopy); + Debug.Assert(data->managedDynamicMethodObject == dataLocal.managedDynamicMethodObject); + Debug.Assert(data->requestedIP == dataLocal.requestedIP); + // TODO[cdac]: cdacreader always returns 0 currently + Debug.Assert(data->cJittedRejitVersions == 0 || data->cJittedRejitVersions == dataLocal.cJittedRejitVersions); + // TODO[cdac]: compare rejitDataCurrent and rejitDataRequested, too + if (rgRevertedRejitData != null && rgRevertedRejitDataLocal != null) + { + Debug.Assert (cNeededRevertedRejitDataLocal == *pcNeededRevertedRejitData); + for (ulong i = 0; i < cNeededRevertedRejitDataLocal; i++) + { + Debug.Assert(rgRevertedRejitData[i].rejitID == rgRevertedRejitDataLocal[i].rejitID); + Debug.Assert(rgRevertedRejitData[i].NativeCodeAddr == rgRevertedRejitDataLocal[i].NativeCodeAddr); + Debug.Assert(rgRevertedRejitData[i].flags == rgRevertedRejitDataLocal[i].flags); + } + } + } else { + // TODO[cdac]: stop delegating to the legacy DAC + hr = _legacyImpl.GetMethodDescData(methodDesc, ip, data, cRevertedRejitVersions, rgRevertedRejitData, pcNeededRevertedRejitData); + } + } +#endif + return hr; } int ISOSDacInterface.GetMethodDescFromToken(ulong moduleAddr, uint token, ulong* methodDesc) diff --git a/src/native/managed/cdacreader/tests/MethodDescTests.cs b/src/native/managed/cdacreader/tests/MethodDescTests.cs new file mode 100644 index 0000000000000..3189c2fac6591 --- /dev/null +++ b/src/native/managed/cdacreader/tests/MethodDescTests.cs @@ -0,0 +1,101 @@ +// 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; +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.Data; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.UnitTests; + +public class MethodDescTests +{ + private static void MethodDescHelper(MockTarget.Architecture arch, Action configure, Action testCase) + { + TargetTestHelpers targetTestHelpers = new(arch); + + MockMemorySpace.Builder builder = new(targetTestHelpers); + MockDescriptors.RuntimeTypeSystem rtsBuilder = new(builder) { + // arbtrary address range + TypeSystemAllocator = builder.CreateAllocator(start: 0x00000000_4a000000, end: 0x00000000_4b000000), + }; + + var loaderBuilder = new MockDescriptors.Loader(builder); + + MockDescriptors.Object objectBuilder = new(rtsBuilder) { + // arbtrary adress range + ManagedObjectAllocator = builder.CreateAllocator(start: 0x00000000_10000000, end: 0x00000000_20000000), + }; + var methodDescChunkAllocator = builder.CreateAllocator(start: 0x00000000_20002000, end: 0x00000000_20003000); + var methodDescBuilder = new MockDescriptors.MethodDescriptors(rtsBuilder, loaderBuilder) + { + MethodDescChunkAllocator = methodDescChunkAllocator, + }; + + builder = builder + .SetContracts([ nameof (Contracts.Object), nameof (Contracts.RuntimeTypeSystem), nameof (Contracts.Loader) ]) + .SetGlobals(MockDescriptors.MethodDescriptors.Globals(targetTestHelpers)) + .SetTypes(methodDescBuilder.Types); + + methodDescBuilder.AddGlobalPointers(); + + configure?.Invoke(methodDescBuilder); + + bool success = builder.TryCreateTarget(out ContractDescriptorTarget? target); + Assert.True(success); + testCase(target); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void MethodDescGetMethodDescTokenOk(MockTarget.Architecture arch) + { + TargetPointer testMethodDescAddress = default; + TargetPointer objectMethodTable = default; + const int MethodDefToken = 0x06 << 24; + const ushort expectedRidRangeStart = 0x2000; // arbitrary (larger than 1<< TokenRemainderBitCount) + const ushort expectedRidRemainder = 0x10; // arbitrary + const uint expectedRid = expectedRidRangeStart | expectedRidRemainder; // arbitrary + uint expectedToken = MethodDefToken | expectedRid; + ushort expectedSlotNum = 0x0002; // arbitrary, but must be less than number of vtable slots in the method table + MethodDescHelper(arch, + (builder) => + { + objectMethodTable = MethodTableTests.AddSystemObjectMethodTable(builder.RTSBuilder).MethodTable; + // add a loader module so that we can do the "IsCollectible" check + TargetPointer module = builder.LoaderBuilder.AddModule("testModule"); + builder.RTSBuilder.SetMethodTableAuxData(objectMethodTable, loaderModule: module); + + byte count = 10; // arbitrary + byte methodDescSize = (byte)(builder.Types[DataType.MethodDesc].Size.Value / builder.MethodDescAlignment); + byte chunkSize = (byte)(count * methodDescSize); + var chunk = builder.AddMethodDescChunk(objectMethodTable, "testMethod", count, chunkSize, tokenRange: expectedRidRangeStart); + + byte methodDescNum = 3; // abitrary, less than "count" + byte methodDescIndex = (byte)(methodDescNum * methodDescSize); + Span dest = builder.BorrowMethodDesc(chunk, methodDescIndex); + builder.SetMethodDesc(dest, methodDescIndex, slotNum: expectedSlotNum, tokenRemainder: expectedRidRemainder); + + testMethodDescAddress = builder.GetMethodDescAddress(chunk, methodDescIndex); + + }, + (target) => + { + IRuntimeTypeSystem rts = target.Contracts.RuntimeTypeSystem; + + var handle = rts.GetMethodDescHandle(testMethodDescAddress); + Assert.NotEqual(TargetPointer.Null, handle.Address); + + uint token = rts.GetMethodToken(handle); + Assert.Equal(expectedToken, token); + ushort slotNum = rts.GetSlotNumber(handle); + Assert.Equal(expectedSlotNum, slotNum); + TargetPointer mt = rts.GetMethodTable(handle); + Assert.Equal(objectMethodTable, mt); + bool isCollectible = rts.IsCollectibleMethod(handle); + Assert.False(isCollectible); + + }); + } +} diff --git a/src/native/managed/cdacreader/tests/MethodTableTests.cs b/src/native/managed/cdacreader/tests/MethodTableTests.cs index a2c7ec4c7ba31..4c8aa82293240 100644 --- a/src/native/managed/cdacreader/tests/MethodTableTests.cs +++ b/src/native/managed/cdacreader/tests/MethodTableTests.cs @@ -25,7 +25,7 @@ private static void RTSContractHelper(MockTarget.Architecture arch, Action GetTypes() @@ -136,13 +147,17 @@ public class RuntimeTypeSystem types[DataType.EEClass] = new Target.TypeInfo() { Fields = layout.Fields, Size = layout.Stride }; layout = targetTestHelpers.ExtendLayout(ArrayClassFields, eeClassLayout); types[DataType.ArrayClass] = new Target.TypeInfo() { Fields = layout.Fields, Size = layout.Stride }; + layout = targetTestHelpers.LayoutFields(MethodTableAuxiliaryDataFields); + types[DataType.MethodTableAuxiliaryData] = new Target.TypeInfo() { Fields = layout.Fields, Size = layout.Stride }; return types; } - internal static readonly (string Name, ulong Value, string? Type)[] Globals = + internal static uint GetMethodDescAlignment(TargetTestHelpers helpers) => helpers.Arch.Is64Bit ? 8u : 4u; + + internal static (string Name, ulong Value, string? Type)[] GetGlobals(TargetTestHelpers helpers) => [ (nameof(Constants.Globals.FreeObjectMethodTable), TestFreeObjectMethodTableGlobalAddress, null), - (nameof(Constants.Globals.MethodDescAlignment), 8, nameof(DataType.uint64)), + (nameof(Constants.Globals.MethodDescAlignment), GetMethodDescAlignment(helpers), nameof(DataType.uint64)), ]; internal readonly MockMemorySpace.Builder Builder; @@ -254,6 +269,19 @@ internal TargetPointer AddMethodTable(string name, uint mtflags, uint mtflags2, builder.AddHeapFragment(methodTableFragment); return methodTableFragment.Address; } + + internal void SetMethodTableAuxData(TargetPointer methodTablePointer, TargetPointer loaderModule) + { + Target.TypeInfo methodTableTypeInfo = Types[DataType.MethodTable]; + Target.TypeInfo auxDataTypeInfo = Types[DataType.MethodTableAuxiliaryData]; + MockMemorySpace.HeapFragment auxDataFragment = TypeSystemAllocator.Allocate(auxDataTypeInfo.Size.Value, "MethodTableAuxiliaryData"); + Span dest = auxDataFragment.Data; + Builder.TargetTestHelpers.WritePointer(dest.Slice(auxDataTypeInfo.Fields[nameof(Data.MethodTableAuxiliaryData.LoaderModule)].Offset), loaderModule); + Builder.AddHeapFragment(auxDataFragment); + + Span methodTable = Builder.BorrowAddressRange(methodTablePointer, (int)methodTableTypeInfo.Size.Value); + Builder.TargetTestHelpers.WritePointer(methodTable.Slice(methodTableTypeInfo.Fields[nameof(Data.MethodTable.AuxiliaryData)].Offset), auxDataFragment.Address); + } } public class Object @@ -310,7 +338,7 @@ internal Object(RuntimeTypeSystem rtsBuilder) return types; } - internal static (string Name, ulong Value, string? Type)[] Globals(TargetTestHelpers helpers) => RuntimeTypeSystem.Globals.Concat( + internal static (string Name, ulong Value, string? Type)[] Globals(TargetTestHelpers helpers) => RuntimeTypeSystem.GetGlobals(helpers).Concat( [ (nameof(Constants.Globals.ObjectToMethodTableUnmask), TestObjectToMethodTableUnmask, "uint8"), (nameof(Constants.Globals.StringMethodTable), TestStringMethodTableGlobalAddress, null), @@ -494,12 +522,18 @@ public Loader(MockMemorySpace.Builder builder, (ulong Start, ulong End) allocati } internal static Dictionary Types(TargetTestHelpers helpers) + { + Dictionary types = new(); + AddTypes(helpers, types); + return types; + } + + internal static void AddTypes(TargetTestHelpers helpers, Dictionary types) { TargetTestHelpers.LayoutResult layout = helpers.LayoutFields(ModuleFields); - return new() - { - [DataType.Module] = new Target.TypeInfo() { Fields = layout.Fields, Size = layout.Stride }, - }; + types[DataType.Module] = new Target.TypeInfo() { Fields = layout.Fields, Size = layout.Stride }; + layout= helpers.LayoutFields(AssemblyFields); + types[DataType.Assembly] = new Target.TypeInfo() { Fields = layout.Fields, Size = layout.Stride }; } internal TargetPointer AddModule(string? path = null, string? fileName = null) @@ -540,6 +574,11 @@ internal TargetPointer AddModule(string? path = null, string? fileName = null) fileNameFragment.Address); } + // add an assembly without any fields set (ie: not collectible) + MockMemorySpace.HeapFragment assembly = _allocator.Allocate((ulong)helpers.SizeOfTypeInfo(Types(helpers)[DataType.Assembly]), "Assembly"); + _builder.AddHeapFragment(assembly); + helpers.WritePointer(module.Data.AsSpan().Slice(typeInfo.Fields[nameof(Data.Module.Assembly)].Offset, helpers.PointerSize), assembly.Address); + return module.Address; } } @@ -688,4 +727,103 @@ internal TargetPointer AddThread(uint id, TargetNUInt osId) return thread.Address; } } + + internal class MethodDescriptors + { + internal const uint TokenRemainderBitCount = 12u; /* see METHOD_TOKEN_REMAINDER_BIT_COUNT*/ + + private static readonly (string Name, DataType Type)[] MethodDescFields = new[] + { + (nameof(Data.MethodDesc.ChunkIndex), DataType.uint8), + (nameof(Data.MethodDesc.Slot), DataType.uint16), + (nameof(Data.MethodDesc.Flags), DataType.uint16), + (nameof(Data.MethodDesc.Flags3AndTokenRemainder), DataType.uint16), + (nameof(Data.MethodDesc.EntryPointFlags), DataType.uint8), + (nameof(Data.MethodDesc.CodeData), DataType.pointer), + }; + + private static readonly (string Name, DataType Type)[] MethodDescChunkFields = new[] + { + (nameof(Data.MethodDescChunk.MethodTable), DataType.pointer), + (nameof(Data.MethodDescChunk.Next), DataType.pointer), + (nameof(Data.MethodDescChunk.Size), DataType.uint8), + (nameof(Data.MethodDescChunk.Count), DataType.uint8), + (nameof(Data.MethodDescChunk.FlagsAndTokenRange), DataType.uint16) + }; + + internal readonly RuntimeTypeSystem RTSBuilder; + internal readonly Loader LoaderBuilder; + + internal MockMemorySpace.BumpAllocator MethodDescChunkAllocator { get; set; } + + internal TargetTestHelpers TargetTestHelpers => RTSBuilder.Builder.TargetTestHelpers; + internal Dictionary Types => RTSBuilder.Types; + internal MockMemorySpace.Builder Builder => RTSBuilder.Builder; + internal uint MethodDescAlignment => RuntimeTypeSystem.GetMethodDescAlignment(TargetTestHelpers); + + internal MethodDescriptors(RuntimeTypeSystem rtsBuilder, Loader loaderBuilder) + { + RTSBuilder = rtsBuilder; + LoaderBuilder = loaderBuilder; + AddTypes(); + } + + private void AddTypes() + { + Dictionary types = RTSBuilder.Types; + TargetTestHelpers targetTestHelpers = Builder.TargetTestHelpers; + Loader.AddTypes(targetTestHelpers, types); + var layout = targetTestHelpers.LayoutFields(MethodDescFields); + types[DataType.MethodDesc] = new Target.TypeInfo() { Fields = layout.Fields, Size = layout.Stride }; + layout = targetTestHelpers.LayoutFields(MethodDescChunkFields); + types[DataType.MethodDescChunk] = new Target.TypeInfo() { Fields = layout.Fields, Size = layout.Stride }; + } + + internal static (string Name, ulong Value, string? Type)[] Globals(TargetTestHelpers targetTestHelpers) => RuntimeTypeSystem.GetGlobals(targetTestHelpers).Concat([ + (nameof(Constants.Globals.MethodDescTokenRemainderBitCount), TokenRemainderBitCount, "uint8"), + ]).ToArray(); + + internal void AddGlobalPointers() + { + RTSBuilder.AddGlobalPointers(); + + } + + internal TargetPointer AddMethodDescChunk(TargetPointer methodTable, string name, byte count, byte size, uint tokenRange) + { + uint totalAllocSize = Types[DataType.MethodDescChunk].Size.Value; + totalAllocSize += (uint)(size * MethodDescAlignment); + + MockMemorySpace.HeapFragment methodDescChunk = MethodDescChunkAllocator.Allocate(totalAllocSize, $"MethodDescChunk {name}"); + Span dest = methodDescChunk.Data; + TargetTestHelpers.WritePointer(dest.Slice(Types[DataType.MethodDescChunk].Fields[nameof(Data.MethodDescChunk.MethodTable)].Offset), methodTable); + TargetTestHelpers.Write(dest.Slice(Types[DataType.MethodDescChunk].Fields[nameof(Data.MethodDescChunk.Size)].Offset), size); + TargetTestHelpers.Write(dest.Slice(Types[DataType.MethodDescChunk].Fields[nameof(Data.MethodDescChunk.Count)].Offset), count); + TargetTestHelpers.Write(dest.Slice(Types[DataType.MethodDescChunk].Fields[nameof(Data.MethodDescChunk.FlagsAndTokenRange)].Offset), (ushort)(tokenRange >> (int)TokenRemainderBitCount)); + Builder.AddHeapFragment(methodDescChunk); + return methodDescChunk.Address; + } + + internal TargetPointer GetMethodDescAddress(TargetPointer chunkAddress, byte index) + { + Target.TypeInfo methodDescChunkTypeInfo = Types[DataType.MethodDescChunk]; + return chunkAddress + methodDescChunkTypeInfo.Size.Value + index * MethodDescAlignment; + } + internal Span BorrowMethodDesc(TargetPointer methodDescChunk, byte index) + { + TargetPointer methodDescAddress = GetMethodDescAddress(methodDescChunk, index); + Target.TypeInfo methodDescTypeInfo = Types[DataType.MethodDesc]; + return Builder.BorrowAddressRange(methodDescAddress, (int)methodDescTypeInfo.Size.Value); + } + + internal void SetMethodDesc(scoped Span dest, byte index, ushort slotNum, ushort tokenRemainder) + { + Target.TypeInfo methodDescTypeInfo = Types[DataType.MethodDesc]; + TargetTestHelpers.Write(dest.Slice(methodDescTypeInfo.Fields[nameof(Data.MethodDesc.ChunkIndex)].Offset), (byte)index); + TargetTestHelpers.Write(dest.Slice(methodDescTypeInfo.Fields[nameof(Data.MethodDesc.Flags3AndTokenRemainder)].Offset), tokenRemainder); + TargetTestHelpers.Write(dest.Slice(methodDescTypeInfo.Fields[nameof(Data.MethodDesc.Slot)].Offset), slotNum); + // TODO: write more fields + } + + } } diff --git a/src/native/managed/cdacreader/tests/PrecodeStubsTests.cs b/src/native/managed/cdacreader/tests/PrecodeStubsTests.cs index 296a2c0aae790..4f2dbb3a32974 100644 --- a/src/native/managed/cdacreader/tests/PrecodeStubsTests.cs +++ b/src/native/managed/cdacreader/tests/PrecodeStubsTests.cs @@ -230,7 +230,7 @@ private void SetCodePointerFlags(PrecodeTestDescriptor test) } } - public void AddCDacMetadata(PrecodeTestDescriptor descriptor) { + public void AddPlatformMetadata(PrecodeTestDescriptor descriptor) { SetCodePointerFlags(descriptor); var typeInfo = TypeInfoCache[DataType.PrecodeMachineDescriptor]; var fragment = PrecodeAllocator.Allocate((ulong)typeInfo.Size, $"{descriptor.Name} Precode Machine Descriptor"); @@ -305,7 +305,7 @@ public PrecodeTestTarget(MockTarget.Architecture arch, ReadFromTargetDelegate re SetDataReader(reader); IContractFactory precodeFactory = new PrecodeStubsFactory(); SetContracts(new TestRegistry() { - CDacMetadataContract = new (() => new TestPlatformMetadata(codePointerFlags, PrecodeMachineDescriptorAddress)), + PlatformMetadataContract = new (() => new TestPlatformMetadata(codePointerFlags, PrecodeMachineDescriptorAddress)), PrecodeStubsContract = new (() => precodeFactory.CreateContract(this, 1)), }); @@ -325,7 +325,7 @@ public override TargetPointer ReadGlobalPointer (string name) public void TestPrecodeStubPrecodeExpectedMethodDesc(PrecodeTestDescriptor test) { var builder = new PrecodeBuilder(test.Arch); - builder.AddCDacMetadata(test); + builder.AddPlatformMetadata(test); TargetPointer expectedMethodDesc = new TargetPointer(0xeeee_eee0u); // arbitrary TargetCodePointer stub1 = builder.AddStubPrecodeEntry("Stub 1", test, expectedMethodDesc); diff --git a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs index 743882ad04229..0762f0c95bbf0 100644 --- a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs +++ b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs @@ -211,8 +211,9 @@ public TestRegistry() { } internal Lazy? DacStreamsContract { get; set; } internal Lazy ExecutionManagerContract { get; set; } internal Lazy? CodeVersionsContract { get; set; } - internal Lazy? CDacMetadataContract { get; set; } + internal Lazy? PlatformMetadataContract { get; set; } internal Lazy? PrecodeStubsContract { get; set; } + internal Lazy? ReJITContract { get; set; } public override Contracts.IException Exception => ExceptionContract.Value ?? throw new NotImplementedException(); public override Contracts.ILoader Loader => LoaderContract.Value ?? throw new NotImplementedException(); @@ -223,8 +224,9 @@ public TestRegistry() { } public override Contracts.IDacStreams DacStreams => DacStreamsContract.Value ?? throw new NotImplementedException(); public override Contracts.IExecutionManager ExecutionManager => ExecutionManagerContract.Value ?? throw new NotImplementedException(); public override Contracts.ICodeVersions CodeVersions => CodeVersionsContract.Value ?? throw new NotImplementedException(); - public override Contracts.IPlatformMetadata PlatformMetadata => CDacMetadataContract.Value ?? throw new NotImplementedException(); + public override Contracts.IPlatformMetadata PlatformMetadata => PlatformMetadataContract.Value ?? throw new NotImplementedException(); public override Contracts.IPrecodeStubs PrecodeStubs => PrecodeStubsContract.Value ?? throw new NotImplementedException(); + public override Contracts.IReJIT ReJIT => ReJITContract.Value ?? throw new NotImplementedException(); } // a data cache that throws NotImplementedException for all methods,