From 0a9dc60a8312b8a7241bd490a87f7daea11c5490 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Mon, 4 Nov 2024 18:13:12 -0800 Subject: [PATCH 1/4] Handle non-virtual slots in RuntimeTypeSystem_1.GetAddressOfSlot --- .../design/datacontracts/RuntimeTypeSystem.md | 9 +++++-- .../debug/runtimeinfo/datadescriptor.h | 1 + .../Contracts/RuntimeTypeSystem_1.cs | 24 +++++++++---------- .../Data/MethodTableAuxiliaryData.cs | 2 ++ .../cdacreader/tests/MockDescriptors.cs | 1 + 5 files changed, 23 insertions(+), 14 deletions(-) diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index 7930404e937f2..84726124483c7 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -621,7 +621,7 @@ The contract additionally depends on these data descriptors ### MethodDesc -The version 1 `MethodDesc` APIs depend on the `MethodDescAlignment` global and the `MethodDesc` and `MethodDescChunk` data descriptors. +The version 1 `MethodDesc` APIs depend on the following globals: | Global name | Meaning | | --- | --- | @@ -644,7 +644,9 @@ We depend on the following data descriptors: | `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 | +| `MethodTable` | `AuxiliaryData` | Auxiliary data associated with the method table. See `MethodTableAuxiliaryData` | | `MethodTableAuxiliaryData` | `LoaderModule` | The loader module associated with a method table +| `MethodTableAuxiliaryData` | `OffsetToNonVirtualSlots` | Offset from the auxiliary data address to the array of non-virtual slots | | `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 | @@ -1123,7 +1125,10 @@ Getting the native code pointer for methods with a NativeCodeSlot or a stable en } else { - // TODO[cdac]: GetNonVirtualSlotsArray from MethodTableAuxiliaryData + // Non-virtual slots < GetNumVtableSlots live before the MethodTableAuxiliaryData. The array grows backwards + TargetPointer auxDataPtr = _target.ReadPointer(typeHandle.Address + /* MethodTable::AuxiliaryData offset */); + TargetPointer nonVirtualSlotsArray = auxDataPtr + _target.Read(/* MethodTableAuxiliaryData::OffsetToNonVirtualSlots offset */); + return nonVirtualSlotsArray - (1 + (slotNum - mt.NumVirtuals)); } } diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 8fb8871f6a243..ea74d6e5fa234 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -270,6 +270,7 @@ CDAC_TYPE_END(MethodTable) CDAC_TYPE_BEGIN(MethodTableAuxiliaryData) CDAC_TYPE_INDETERMINATE(MethodTableAuxiliaryData) CDAC_TYPE_FIELD(MethodTableAuxiliaryData, /*pointer*/, LoaderModule, offsetof(MethodTableAuxiliaryData, m_pLoaderModule)) +CDAC_TYPE_FIELD(MethodTableAuxiliaryData, /*int16*/, OffsetToNonVirtualSlots, offsetof(MethodTableAuxiliaryData, m_offsetToNonVirtualSlots)) CDAC_TYPE_END(MethodTableAuxiliaryData) CDAC_TYPE_BEGIN(EEClass) 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 5f19bd7d97fa4..97d71e24823d4 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 @@ -854,11 +854,12 @@ private VtableIndirections GetVTableIndirections(TargetPointer methodTableAddres 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()); + throw new InvalidOperationException($"nameof{typeHandle} is not a MethodTable"); + + Debug.Assert(slotNum < GetNumVtableSlots(typeHandle), "Slot number is greater than the number of slots"); + // MethodTable::GetSlotPtrRaw + MethodTable mt = _methodTables[typeHandle.Address]; if (slotNum < mt.NumVirtuals) { // Virtual slots live in chunks pointed to by vtable indirections @@ -866,14 +867,14 @@ private TargetPointer GetAddressOfSlot(TypeHandle typeHandle, uint slotNum) } else { + Debug.Assert(mt.NumVirtuals < GetNumVtableSlots(typeHandle), "Method table does not have non-virtual slots"); + // 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): + TargetPointer auxDataPtr = mt.AuxiliaryData; + Data.MethodTableAuxiliaryData auxData = _target.ProcessedData.GetOrAdd(auxDataPtr); + TargetPointer nonVirtualSlotsArray = auxDataPtr + (ulong)auxData.OffsetToNonVirtualSlots; + return nonVirtualSlotsArray - (1 + (slotNum - mt.NumVirtuals)); } - } private bool IsWrapperStub(MethodDesc md) @@ -1067,13 +1068,12 @@ private TargetCodePointer GetMethodEntryPointIfExists(TargetPointer methodDescAd 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()); + Debug.Assert(_methodTables[typeHandle.Address].IsCanonMT); TargetPointer addrOfSlot = GetAddressOfSlot(typeHandle, md.Slot); return _target.ReadCodePointer(addrOfSlot); } 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 496b4bdd65d96..c181335f410c6 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 @@ -12,8 +12,10 @@ private MethodTableAuxiliaryData(Target target, TargetPointer address) Target.TypeInfo type = target.GetTypeInfo(DataType.MethodTableAuxiliaryData); LoaderModule = target.ReadPointer(address + (ulong)type.Fields[nameof(LoaderModule)].Offset); + OffsetToNonVirtualSlots = target.Read(address + (ulong)type.Fields[nameof(OffsetToNonVirtualSlots)].Offset); } public TargetPointer LoaderModule { get; init; } + public short OffsetToNonVirtualSlots { get; init; } } diff --git a/src/native/managed/cdacreader/tests/MockDescriptors.cs b/src/native/managed/cdacreader/tests/MockDescriptors.cs index a115452b2e567..d57effb30ad36 100644 --- a/src/native/managed/cdacreader/tests/MockDescriptors.cs +++ b/src/native/managed/cdacreader/tests/MockDescriptors.cs @@ -39,6 +39,7 @@ private static readonly (string Name, DataType Type)[] EEClassFields = new[] private static readonly (string Name, DataType Type)[] MethodTableAuxiliaryDataFields = new[] { (nameof(Data.MethodTableAuxiliaryData.LoaderModule), DataType.pointer), + (nameof(Data.MethodTableAuxiliaryData.OffsetToNonVirtualSlots), DataType.int16), }; private static readonly (string Name, DataType Type)[] ArrayClassFields = new[] From 83fdcd76cff9c84795f813b00f6b462291ade32a Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Mon, 4 Nov 2024 18:16:39 -0800 Subject: [PATCH 2/4] Handle non-default active version and explicit IL code version cases for CodeVersions.GetActiveNativeCodeVersion --- docs/design/datacontracts/CodeVersions.md | 52 ++++++++++---- .../debug/runtimeinfo/datadescriptor.h | 7 ++ src/coreclr/vm/codeversion.h | 10 +++ .../DataType.cs | 1 + .../Contracts/CodeVersions_1.cs | 71 +++++++++++-------- .../Data/ILCodeVersionNode.cs | 19 +++++ .../Data/NativeCodeVersionNode.cs | 4 ++ 7 files changed, 120 insertions(+), 44 deletions(-) create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ILCodeVersionNode.cs diff --git a/docs/design/datacontracts/CodeVersions.md b/docs/design/datacontracts/CodeVersions.md index 46df04d060347..19cee2b20730c 100644 --- a/docs/design/datacontracts/CodeVersions.md +++ b/docs/design/datacontracts/CodeVersions.md @@ -50,10 +50,13 @@ Data descriptors used: | NativeCodeVersionNode | Next | pointer to the next native code version | | NativeCodeVersionNode | MethodDesc | indicates a synthetic native code version node | | NativeCodeVersionNode | NativeCode | indicates an explicit native code version node | +| NativeCodeVersionNode | Flags | `NativeCodeVersionNodeFlags` flags, see below | +| NativeCodeVersionNode | VersionId | Version ID corresponding to the parent IL code version | | ILCodeVersioningState | ActiveVersionKind | an `ILCodeVersionKind` value indicating which fields of the active version are value | | ILCodeVersioningState | ActiveVersionNode | if the active version is explicit, the NativeCodeVersionNode for the active version | | ILCodeVersioningState | ActiveVersionModule | if the active version is synthetic or unknown, the pointer to the Module that defines the method | | ILCodeVersioningState | ActiveVersionMethodDef | if the active version is synthetic or unknown, the MethodDef token for the method | +| ILCodeVersionNode | VersionId | Version ID of the node | The flag indicates that the default version of the code for a method desc is active: ```csharp @@ -63,6 +66,14 @@ internal enum MethodDescVersioningStateFlags : byte }; ``` +The flag indicates the native code version is active: +```csharp +internal enum NativeCodeVersionNodeFlags : uint +{ + IsActiveChild = 1 +}; +``` + The value of the `ILCodeVersioningState::ActiveVersionKind` field is one of: ```csharp private enum ILCodeVersionKind @@ -120,21 +131,23 @@ NativeCodeVersionHandle GetSpecificNativeCodeVersion(MethodDescHandle md, Target 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) - { - return NativeCodeVersionHandle.Invalid; - } - Data.MethodDescVersioningState methodDescVersioningStateData = _target.ProcessedData.GetOrAdd(methodDescVersioningStateAddress); - return FindFirstCodeVersion(methodDescVersioningStateData, (codeVersion) => + + return FindFirstCodeVersion(rts, md, (codeVersion) => { return codeVersion.MethodDesc == md.Address && codeVersion.NativeCode == startAddress; }); } -NativeCodeVersionHandle FindFirstCodeVersion(Data.MethodDescVersioningState versioningState, Func predicate) +NativeCodeVersionHandle FindFirstCodeVersion(IRuntimeTypeSystem rts, MethodDescHandle md, Func predicate) { + // ImplicitCodeVersion stage of NativeCodeVersionIterator::Next() + TargetPointer versioningStateAddr = rts.GetMethodDescVersioningState(md); + if (versioningStateAddr == TargetPointer.Null) + return NativeCodeVersionHandle.Invalid; + + Data.MethodDescVersioningState versioningState = _target.ProcessedData.GetOrAdd(versioningStateAddr); + + // LinkedList stage of NativeCodeVersion::Next, heavily inlined TargetPointer currentAddress = versioningState.NativeCodeVersionNode; while (currentAddress != TargetPointer.Null) { @@ -211,7 +224,8 @@ bool IsActiveNativeCodeVersion(NativeCodeVersionHandle nativeCodeVersion) } else if (nativeCodeVersion.CodeVersionNodeAddress != TargetPointer.Null) { - throw new NotImplementedException(); // TODO[cdac]: IsActiveNativeCodeVersion - explicit + uint flags = _target.Read(nativeCodeVersion.CodeVersionNodeAddress + /* NativeCodVersionNode::Flags offset*/) + return ((NativeCodeVersionNodeFlags)flags).HasFlag(NativeCodeVersionNodeFlags.IsActiveChild); } else { @@ -221,6 +235,7 @@ bool IsActiveNativeCodeVersion(NativeCodeVersionHandle nativeCodeVersion) NativeCodeVersionHandle FindActiveNativeCodeVersion(ILCodeVersionHandle methodDefActiveVersion, TargetPointer methodDescAddress) { + TargetNUInt? ilVersionId = default; if (methodDefActiveVersion.Module != TargetPointer.Null) { NativeCodeVersionHandle provisionalHandle = new NativeCodeVersionHandle(methodDescAddress: methodDescAddress, codeVersionNodeAddress: TargetPointer.Null); @@ -228,15 +243,22 @@ NativeCodeVersionHandle FindActiveNativeCodeVersion(ILCodeVersionHandle methodDe { return provisionalHandle; } - else - { - throw new NotImplementedException(); // TODO[cdac]: iterate through versioning state nodes - } } else { - throw new NotImplementedException(); // TODO: [cdac] find explicit il code version + // Get the explicit IL code version + Debug.Assert(methodDefActiveVersion.ILCodeVersionNode != TargetPointer.Null); + ilVersionId = _target.ReadNUint(methodDefActiveVersion.ILCodeVersionNode + /* ILCodeVersionNode::VersionId offset */); } + + // Iterate through versioning state nodes and return the active one, matching any IL code version + Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + MethodDescHandle md = rts.GetMethodDescHandle(methodDescAddress); + return FindFirstCodeVersion(rts, md, (codeVersion) => + { + return (!ilVersionId.HasValue || ilVersionId.Value.Value == codeVersion.ILVersionId.Value) + && ((NativeCodeVersionNodeFlags)codeVersion.Flags).HasFlag(NativeCodeVersionNodeFlags.IsActiveChild); + }); } ``` diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index ea74d6e5fa234..1bc05369a3871 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -462,8 +462,15 @@ CDAC_TYPE_INDETERMINATE(NativeCodeVersionNode) CDAC_TYPE_FIELD(NativeCodeVersionNode, /*pointer*/, Next, cdac_data::Next) CDAC_TYPE_FIELD(NativeCodeVersionNode, /*pointer*/, MethodDesc, cdac_data::MethodDesc) CDAC_TYPE_FIELD(NativeCodeVersionNode, /*pointer*/, NativeCode, cdac_data::NativeCode) +CDAC_TYPE_FIELD(NativeCodeVersionNode, /*uint32*/, Flags, cdac_data::Flags) +CDAC_TYPE_FIELD(NativeCodeVersionNode, /*nuint*/, ILVersionId, cdac_data::ILVersionId) CDAC_TYPE_END(NativeCodeVersionNode) +CDAC_TYPE_BEGIN(ILCodeVersionNode) +CDAC_TYPE_INDETERMINATE(ILCodeVersionNode) +CDAC_TYPE_FIELD(ILCodeVersionNode, /*nuint*/, VersionId, cdac_data::VersionId) +CDAC_TYPE_END(ILCodeVersionNode) + CDAC_TYPE_BEGIN(ProfControlBlock) CDAC_TYPE_FIELD(ProfControlBlock, /*uint64*/, GlobalEventMask, offsetof(ProfControlBlock, globalEventMask)) CDAC_TYPE_END(ProfControlBlock) diff --git a/src/coreclr/vm/codeversion.h b/src/coreclr/vm/codeversion.h index 2cdb5b5982fd5..55f3407ca0a55 100644 --- a/src/coreclr/vm/codeversion.h +++ b/src/coreclr/vm/codeversion.h @@ -328,6 +328,8 @@ struct cdac_data static constexpr size_t Next = offsetof(NativeCodeVersionNode, m_pNextMethodDescSibling); static constexpr size_t MethodDesc = offsetof(NativeCodeVersionNode, m_pMethodDesc); static constexpr size_t NativeCode = offsetof(NativeCodeVersionNode, m_pNativeCode); + static constexpr size_t Flags = offsetof(NativeCodeVersionNode, m_flags); + static constexpr size_t ILVersionId = offsetof(NativeCodeVersionNode, m_parentId); }; class NativeCodeVersionCollection @@ -409,6 +411,14 @@ class ILCodeVersionNode Volatile m_jitFlags; InstrumentedILOffsetMapping m_instrumentedILMap; BOOL m_deoptimized; + + friend struct ::cdac_data; +}; + +template<> +struct cdac_data +{ + static constexpr size_t VersionId = offsetof(ILCodeVersionNode, m_rejitId); }; class ILCodeVersionCollection 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 8dc9a6cf5bbdc..60ee363c81d25 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -66,4 +66,5 @@ public enum DataType ILCodeVersioningState, NativeCodeVersionNode, ProfControlBlock, + ILCodeVersionNode } 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 bd0b173640a12..1672c6fa7a849 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 @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -106,18 +107,15 @@ internal struct ILCodeVersionHandle internal ILCodeVersionHandle(TargetPointer module, uint methodDef, TargetPointer ilCodeVersionNodeAddress) { - Module = module; - MethodDefinition = methodDef; - ILCodeVersionNode = ilCodeVersionNodeAddress; - if (Module != TargetPointer.Null && ILCodeVersionNode != TargetPointer.Null) - { + if (module != TargetPointer.Null && ilCodeVersionNodeAddress != TargetPointer.Null) throw new ArgumentException("Both MethodDesc and ILCodeVersionNode cannot be non-null"); - } - if (Module != TargetPointer.Null && MethodDefinition == 0) - { + if (module != TargetPointer.Null && methodDef == 0) throw new ArgumentException("MethodDefinition must be non-zero if Module is non-null"); - } + + Module = module; + MethodDefinition = methodDef; + ILCodeVersionNode = ilCodeVersionNodeAddress; } public static ILCodeVersionHandle Invalid => new ILCodeVersionHandle(TargetPointer.Null, 0, TargetPointer.Null); public bool IsValid => Module != TargetPointer.Null || ILCodeVersionNode != TargetPointer.Null; @@ -129,7 +127,6 @@ internal enum MethodDescVersioningStateFlags : byte IsDefaultVersionActiveChildFlag = 0x4 }; - private NativeCodeVersionHandle GetSpecificNativeCodeVersion(IRuntimeTypeSystem rts, MethodDescHandle md, TargetCodePointer startAddress) { // initial stage of NativeCodeVersionIterator::Next() with a null m_ilCodeFilter @@ -139,23 +136,24 @@ private NativeCodeVersionHandle GetSpecificNativeCodeVersion(IRuntimeTypeSystem NativeCodeVersionHandle first = new NativeCodeVersionHandle(md.Address, TargetPointer.Null); return first; } - // ImplicitCodeVersion stage of NativeCodeVersionIterator::Next() - TargetPointer methodDescVersioningStateAddress = rts.GetMethodDescVersioningState(md); - if (methodDescVersioningStateAddress == TargetPointer.Null) - { - return NativeCodeVersionHandle.Invalid; - } - Data.MethodDescVersioningState methodDescVersioningStateData = _target.ProcessedData.GetOrAdd(methodDescVersioningStateAddress); + // CodeVersionManager::GetNativeCodeVersion(PTR_MethodDesc, PCODE startAddress) - return FindFirstCodeVersion(methodDescVersioningStateData, (codeVersion) => + return FindFirstCodeVersion(rts, md, (codeVersion) => { return codeVersion.MethodDesc == md.Address && codeVersion.NativeCode == startAddress; }); } - private NativeCodeVersionHandle FindFirstCodeVersion(Data.MethodDescVersioningState versioningState, Func predicate) + private NativeCodeVersionHandle FindFirstCodeVersion(IRuntimeTypeSystem rts, MethodDescHandle md, Func predicate) { - // NativeCodeVersion::Next, heavily inlined + // ImplicitCodeVersion stage of NativeCodeVersionIterator::Next() + TargetPointer versioningStateAddr = rts.GetMethodDescVersioningState(md); + if (versioningStateAddr == TargetPointer.Null) + return NativeCodeVersionHandle.Invalid; + + Data.MethodDescVersioningState versioningState = _target.ProcessedData.GetOrAdd(versioningStateAddr); + + // LinkedList stage of NativeCodeVersion::Next, heavily inlined TargetPointer currentAddress = versioningState.NativeCodeVersionNode; while (currentAddress != TargetPointer.Null) { @@ -169,7 +167,6 @@ private NativeCodeVersionHandle FindFirstCodeVersion(Data.MethodDescVersioningSt return NativeCodeVersionHandle.Invalid; } - private enum ILCodeVersionKind { Unknown = 0, @@ -192,6 +189,7 @@ private static ILCodeVersionHandle ILCodeVersionHandleFromState(Data.ILCodeVersi private ILCodeVersionHandle FindActiveILCodeVersion(TargetPointer module, uint methodDefinition) { + // CodeVersionManager::GetActiveILCodeVersion ModuleHandle moduleHandle = _target.Contracts.Loader.GetModuleHandle(module); TargetPointer ilCodeVersionTable = _target.Contracts.Loader.GetLookupTables(moduleHandle).MethodDefToILCodeVersioningState; TargetPointer ilVersionStateAddress = _target.Contracts.Loader.GetModuleLookupMapElement(ilCodeVersionTable, methodDefinition, out var _); @@ -203,8 +201,15 @@ private ILCodeVersionHandle FindActiveILCodeVersion(TargetPointer module, uint m return ILCodeVersionHandleFromState(ilState); } + [Flags] + internal enum NativeCodeVersionNodeFlags : uint + { + IsActiveChild = 1 + }; + private bool IsActiveNativeCodeVersion(NativeCodeVersionHandle nativeCodeVersion) { + // NativeCodeVersion::IsActiveChildVersion if (nativeCodeVersion.MethodDescAddress != TargetPointer.Null) { MethodDescHandle md = _target.Contracts.RuntimeTypeSystem.GetMethodDescHandle(nativeCodeVersion.MethodDescAddress); @@ -220,9 +225,8 @@ private bool IsActiveNativeCodeVersion(NativeCodeVersionHandle nativeCodeVersion else if (nativeCodeVersion.CodeVersionNodeAddress != TargetPointer.Null) { // NativeCodeVersionNode::IsActiveChildVersion - // Data.NativeCodeVersionNode codeVersion = _target.ProcessedData.GetOrAdd(nativeCodeVersion.CodeVersionNodeAddress); - // return codeVersion has flag IsActive - throw new NotImplementedException(); // TODO[cdac]: IsActiveNativeCodeVersion - explicit + Data.NativeCodeVersionNode codeVersion = _target.ProcessedData.GetOrAdd(nativeCodeVersion.CodeVersionNodeAddress); + return ((NativeCodeVersionNodeFlags)codeVersion.Flags).HasFlag(NativeCodeVersionNodeFlags.IsActiveChild); } else { @@ -232,6 +236,7 @@ private bool IsActiveNativeCodeVersion(NativeCodeVersionHandle nativeCodeVersion private NativeCodeVersionHandle FindActiveNativeCodeVersion(ILCodeVersionHandle methodDefActiveVersion, TargetPointer methodDescAddress) { + TargetNUInt? ilVersionId = default; if (methodDefActiveVersion.Module != TargetPointer.Null) { NativeCodeVersionHandle provisionalHandle = new NativeCodeVersionHandle(methodDescAddress: methodDescAddress, codeVersionNodeAddress: TargetPointer.Null); @@ -239,15 +244,23 @@ private NativeCodeVersionHandle FindActiveNativeCodeVersion(ILCodeVersionHandle { return provisionalHandle; } - else - { - throw new NotImplementedException(); // TODO[cdac]: iterate through versioning state nodes - } } else { - throw new NotImplementedException(); // TODO: [cdac] find explicit il code version + // Get the explicit IL code version + Debug.Assert(methodDefActiveVersion.ILCodeVersionNode != TargetPointer.Null); + Data.ILCodeVersionNode ilCodeVersion = _target.ProcessedData.GetOrAdd(methodDefActiveVersion.ILCodeVersionNode); + ilVersionId = ilCodeVersion.VersionId; } + + // Iterate through versioning state nodes and return the active one, matching any IL code version + Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + MethodDescHandle md = rts.GetMethodDescHandle(methodDescAddress); + return FindFirstCodeVersion(rts, md, (codeVersion) => + { + return (!ilVersionId.HasValue || ilVersionId.Value.Value == codeVersion.ILVersionId.Value) + && ((NativeCodeVersionNodeFlags)codeVersion.Flags).HasFlag(NativeCodeVersionNodeFlags.IsActiveChild); + }); } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ILCodeVersionNode.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ILCodeVersionNode.cs new file mode 100644 index 0000000000000..fbc7183c6688e --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ILCodeVersionNode.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 ILCodeVersionNode : IData +{ + static ILCodeVersionNode IData.Create(Target target, TargetPointer address) => new ILCodeVersionNode(target, address); + public ILCodeVersionNode(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.ILCodeVersionNode); + + VersionId = target.ReadNUInt(address + (ulong)type.Fields[nameof(VersionId)].Offset); + } + + public TargetNUInt VersionId { get; init; } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/NativeCodeVersionNode.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/NativeCodeVersionNode.cs index 87b89201a97fc..02a7d37eb1e06 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/NativeCodeVersionNode.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/NativeCodeVersionNode.cs @@ -15,10 +15,14 @@ public NativeCodeVersionNode(Target target, TargetPointer address) Next = target.ReadPointer(address + (ulong)type.Fields[nameof(Next)].Offset); MethodDesc = target.ReadPointer(address + (ulong)type.Fields[nameof(MethodDesc)].Offset); NativeCode = target.ReadCodePointer(address + (ulong)type.Fields[nameof(NativeCode)].Offset); + Flags = target.Read(address + (ulong)type.Fields[nameof(Flags)].Offset); + ILVersionId = target.ReadNUInt(address + (ulong)type.Fields[nameof(ILVersionId)].Offset); } public TargetPointer Next { get; init; } public TargetPointer MethodDesc { get; init; } public TargetCodePointer NativeCode { get; init; } + public uint Flags{ get; init; } + public TargetNUInt ILVersionId { get; init; } } From b3cff917dd01ece57a6195f1db24018c3afdcc79 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Mon, 4 Nov 2024 19:39:27 -0800 Subject: [PATCH 3/4] Add tests --- .../cdacreader/tests/CodeVersionsTests.cs | 213 ++++++++++++++++-- 1 file changed, 196 insertions(+), 17 deletions(-) diff --git a/src/native/managed/cdacreader/tests/CodeVersionsTests.cs b/src/native/managed/cdacreader/tests/CodeVersionsTests.cs index 59bd68f1d9fdc..51cab9f29bae6 100644 --- a/src/native/managed/cdacreader/tests/CodeVersionsTests.cs +++ b/src/native/managed/cdacreader/tests/CodeVersionsTests.cs @@ -10,7 +10,6 @@ namespace Microsoft.Diagnostics.DataContractReader.UnitTests; public class CodeVersionsTests { - internal class MockModule { public TargetPointer Address { get; set; } @@ -266,17 +265,19 @@ internal static void AddToTypeInfoCache(TargetTestHelpers targetTestHelpers, Dic (nameof(Data.MethodDescVersioningState.Flags), DataType.uint8), ]); typeInfoCache[DataType.MethodDescVersioningState] = new Target.TypeInfo() { - Fields = layout.Fields, - Size = layout.Stride, + Fields = layout.Fields, + Size = layout.Stride, }; layout = targetTestHelpers.LayoutFields([ (nameof(Data.NativeCodeVersionNode.Next), DataType.pointer), (nameof(Data.NativeCodeVersionNode.MethodDesc), DataType.pointer), (nameof(Data.NativeCodeVersionNode.NativeCode), DataType.pointer), + (nameof(Data.NativeCodeVersionNode.Flags), DataType.uint32), + (nameof(Data.NativeCodeVersionNode.ILVersionId), DataType.nuint), ]); typeInfoCache[DataType.NativeCodeVersionNode] = new Target.TypeInfo() { - Fields = layout.Fields, - Size = layout.Stride, + Fields = layout.Fields, + Size = layout.Stride, }; layout = targetTestHelpers.LayoutFields([ (nameof(Data.ILCodeVersioningState.ActiveVersionMethodDef), DataType.uint32), @@ -285,20 +286,29 @@ internal static void AddToTypeInfoCache(TargetTestHelpers targetTestHelpers, Dic (nameof(Data.ILCodeVersioningState.ActiveVersionNode), DataType.pointer), ]); typeInfoCache[DataType.ILCodeVersioningState] = new Target.TypeInfo() { - Fields = layout.Fields, - Size = layout.Stride, + Fields = layout.Fields, + Size = layout.Stride, + }; + layout = targetTestHelpers.LayoutFields([ + (nameof(Data.ILCodeVersionNode.VersionId), DataType.nuint), + ]); + typeInfoCache[DataType.ILCodeVersionNode] = new Target.TypeInfo() + { + Fields = layout.Fields, + Size = layout.Stride, }; } public void MarkCreated() => Builder.MarkCreated(); - public TargetPointer AddMethodDescVersioningState(TargetPointer nativeCodeVersionNode) + public TargetPointer AddMethodDescVersioningState(TargetPointer nativeCodeVersionNode, bool isDefaultVersionActive) { Target.TypeInfo info = TypeInfoCache[DataType.MethodDescVersioningState]; MockMemorySpace.HeapFragment fragment = _codeVersionsAllocator.Allocate((ulong)TypeInfoCache[DataType.MethodDescVersioningState].Size, "MethodDescVersioningState"); Builder.AddHeapFragment(fragment); Span mdvs = Builder.BorrowAddressRange(fragment.Address, fragment.Data.Length); Builder.TargetTestHelpers.WritePointer(mdvs.Slice(info.Fields[nameof(Data.MethodDescVersioningState.NativeCodeVersionNode)].Offset, Builder.TargetTestHelpers.PointerSize), nativeCodeVersionNode); + Builder.TargetTestHelpers.Write(mdvs.Slice(info.Fields[nameof(Data.MethodDescVersioningState.Flags)].Offset, sizeof(byte)), (byte)(isDefaultVersionActive ? CodeVersions_1.MethodDescVersioningStateFlags.IsDefaultVersionActiveChildFlag : 0)); return fragment.Address; } @@ -309,13 +319,35 @@ public TargetPointer AddNativeCodeVersionNode() Builder.AddHeapFragment(fragment); return fragment.Address; } - public void FillNativeCodeVersionNode(TargetPointer dest, TargetPointer methodDesc, TargetCodePointer nativeCode, TargetPointer next) + + public void FillNativeCodeVersionNode(TargetPointer dest, TargetPointer methodDesc, TargetCodePointer nativeCode, TargetPointer next, bool isActive, TargetNUInt ilVersionId) { Target.TypeInfo info = TypeInfoCache[DataType.NativeCodeVersionNode]; Span ncvn = Builder.BorrowAddressRange(dest, (int)info.Size!); Builder.TargetTestHelpers.WritePointer(ncvn.Slice(info.Fields[nameof(Data.NativeCodeVersionNode.Next)].Offset, Builder.TargetTestHelpers.PointerSize), next); Builder.TargetTestHelpers.WritePointer(ncvn.Slice(info.Fields[nameof(Data.NativeCodeVersionNode.MethodDesc)].Offset, Builder.TargetTestHelpers.PointerSize), methodDesc); Builder.TargetTestHelpers.WritePointer(ncvn.Slice(info.Fields[nameof(Data.NativeCodeVersionNode.NativeCode)].Offset, Builder.TargetTestHelpers.PointerSize), nativeCode); + Builder.TargetTestHelpers.Write(ncvn.Slice(info.Fields[nameof(Data.NativeCodeVersionNode.Flags)].Offset, sizeof(uint)), isActive ? (uint)CodeVersions_1.NativeCodeVersionNodeFlags.IsActiveChild : 0u); + Builder.TargetTestHelpers.WriteNUInt(ncvn.Slice(info.Fields[nameof(Data.NativeCodeVersionNode.ILVersionId)].Offset, Builder.TargetTestHelpers.PointerSize), ilVersionId); + } + + public (TargetPointer First, TargetPointer Active) AddNativeCodeVersionNodesForMethod(TargetPointer methodDesc, int count, int activeIndex, TargetCodePointer activeNativeCode, TargetNUInt explicitILVersion) + { + TargetPointer activeVersionNode = TargetPointer.Null; + TargetPointer next = TargetPointer.Null; + for (int i = count - 1; i >= 0; i--) + { + TargetPointer node = AddNativeCodeVersionNode(); + bool isActive = i == activeIndex; + TargetCodePointer nativeCode = isActive ? activeNativeCode : 0; + TargetNUInt ilVersionId = isActive ? explicitILVersion : default; + FillNativeCodeVersionNode(node, methodDesc, nativeCode, next, isActive, ilVersionId); + next = node; + if (isActive) + activeVersionNode = node; + } + + return (next, activeVersionNode); } public TargetPointer AddILCodeVersioningState(uint activeVersionKind, TargetPointer activeVersionNode, TargetPointer activeVersionModule, uint activeVersionMethodDef) @@ -331,6 +363,14 @@ public TargetPointer AddILCodeVersioningState(uint activeVersionKind, TargetPoin return fragment.Address; } + public TargetPointer AddILCodeVersionNode(TargetNUInt versionId) + { + Target.TypeInfo info = TypeInfoCache[DataType.ILCodeVersionNode]; + MockMemorySpace.HeapFragment fragment = _codeVersionsAllocator.Allocate((ulong)TypeInfoCache[DataType.ILCodeVersionNode].Size, "NativeCodeVersionNode"); + Builder.AddHeapFragment(fragment); + Builder.TargetTestHelpers.WriteNUInt(fragment.Data.AsSpan().Slice(info.Fields[nameof(Data.ILCodeVersionNode.VersionId)].Offset), versionId); + return fragment.Address; + } } internal class CVTestTarget : TestPlaceholderTarget @@ -368,7 +408,7 @@ public CVTestTarget(MockTarget.Architecture arch, IReadOnlyCollection() { + { methodRowId, versioningState} + }, + }; + var methodTable = new MockMethodTable() + { + Address = new TargetPointer(0x00ba_ba00), + Module = module, + }; + + // Add the linked list of native code version nodes + int count = 3; + int activeIndex = shouldFindActiveCodeVersion ? count - 1 : -1; + (TargetPointer firstNode, TargetPointer activeVersionNode) = builder.AddNativeCodeVersionNodesForMethod(methodDescAddress, count, activeIndex, expectedNativeCodePointer, default); + TargetPointer methodDescVersioningStateAddress = builder.AddMethodDescVersioningState(nativeCodeVersionNode: firstNode, isDefaultVersionActive: false); + + var methodDesc = MockMethodDesc.CreateVersionable(selfAddress: methodDescAddress, methodDescVersioningState: methodDescVersioningStateAddress, nativeCode: expectedNativeCodePointer); + methodDesc.MethodTable = methodTable; + methodDesc.RowId = methodRowId; + + var target = CVTestTarget.FromBuilder(arch, [methodDesc], [methodTable], [], [module], builder); + + // TEST + + var codeVersions = target.Contracts.CodeVersions; + Assert.NotNull(codeVersions); + + NativeCodeVersionHandle handle = codeVersions.GetActiveNativeCodeVersion(methodDescAddress); + if (shouldFindActiveCodeVersion) + { + Assert.True(handle.Valid); + Assert.Equal(activeVersionNode, handle.CodeVersionNodeAddress); + var actualCodeAddress = codeVersions.GetNativeCode(handle); + Assert.Equal(expectedNativeCodePointer, actualCodeAddress); + } + else + { + Assert.False(handle.Valid); + } + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetActiveNativeCodeVersion_ExplicitILCodeVersion(MockTarget.Architecture arch) + { + GetActiveNativeCodeVersion_ExplicitILCodeVersion_Impl(arch, shouldFindActiveCodeVersion: true); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetActiveNativeCodeVersion_ExplicitILCodeVersion_NoMatch(MockTarget.Architecture arch) + { + GetActiveNativeCodeVersion_ExplicitILCodeVersion_Impl(arch, shouldFindActiveCodeVersion: false); + } + + private void GetActiveNativeCodeVersion_ExplicitILCodeVersion_Impl(MockTarget.Architecture arch, bool shouldFindActiveCodeVersion) + { + uint methodRowId = 0x25; // arbitrary + TargetCodePointer expectedNativeCodePointer = new TargetCodePointer(0x0700_abc0); + var builder = new CodeVersionsBuilder(arch, CodeVersionsBuilder.DefaultAllocationRange); + var methodDescAddress = new TargetPointer(0x00aa_aa00); + var moduleAddress = new TargetPointer(0x00ca_ca00); + + TargetNUInt ilVersionId = new TargetNUInt(5); + TargetPointer ilVersionNode = builder.AddILCodeVersionNode(ilVersionId); + TargetPointer versioningState = builder.AddILCodeVersioningState(activeVersionKind: 1 /* Explicit */, activeVersionNode: ilVersionNode, activeVersionModule: TargetPointer.Null, activeVersionMethodDef: 0); + var module = new MockModule() + { + Address = moduleAddress, + MethodDefToILCodeVersioningStateAddress = new TargetPointer(0x00da_da00), + MethodDefToILCodeVersioningStateTable = new Dictionary() { + { methodRowId, versioningState} + }, + }; + var methodTable = new MockMethodTable() + { + Address = new TargetPointer(0x00ba_ba00), + Module = module, + }; + + // Add the linked list of native code version nodes + int count = 3; + int activeIndex = shouldFindActiveCodeVersion ? count - 1 : -1; + TargetNUInt activeIlVersionId = shouldFindActiveCodeVersion ? ilVersionId : default; + (TargetPointer firstNode, TargetPointer activeVersionNode) = builder.AddNativeCodeVersionNodesForMethod(methodDescAddress, count, activeIndex, expectedNativeCodePointer, activeIlVersionId); + TargetPointer methodDescVersioningStateAddress = builder.AddMethodDescVersioningState(nativeCodeVersionNode: firstNode, isDefaultVersionActive: false); + + var oneMethod = MockMethodDesc.CreateVersionable(selfAddress: methodDescAddress, methodDescVersioningState: methodDescVersioningStateAddress, nativeCode: expectedNativeCodePointer); + oneMethod.MethodTable = methodTable; + oneMethod.RowId = methodRowId; + + var target = CVTestTarget.FromBuilder(arch, [oneMethod], [methodTable], [], [module], builder); + + // TEST + + var codeVersions = target.Contracts.CodeVersions; + Assert.NotNull(codeVersions); + + var handle = codeVersions.GetActiveNativeCodeVersion(methodDescAddress); + if (shouldFindActiveCodeVersion) + { + Assert.True(handle.Valid); + Assert.Equal(activeVersionNode, handle.CodeVersionNodeAddress); + var actualCodeAddress = codeVersions.GetNativeCode(handle); + Assert.Equal(expectedNativeCodePointer, actualCodeAddress); + } + else + { + Assert.False(handle.Valid); + } + } } From 0c3eaaf9d410694d06f4403f4b9d6ccd463865be Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Tue, 5 Nov 2024 10:10:56 -0800 Subject: [PATCH 4/4] Check for null MethodDescCodeData --- .../Contracts/CodeVersions_1.cs | 1 + .../Contracts/RuntimeTypeSystem_1.cs | 3 +++ src/native/managed/cdacreader/tests/MethodDescTests.cs | 3 ++- 3 files changed, 6 insertions(+), 1 deletion(-) 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 1672c6fa7a849..37086698d95e0 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 @@ -218,6 +218,7 @@ private bool IsActiveNativeCodeVersion(NativeCodeVersionHandle nativeCodeVersion { return true; } + Data.MethodDescVersioningState versioningState = _target.ProcessedData.GetOrAdd(versioningStateAddress); MethodDescVersioningStateFlags flags = (MethodDescVersioningStateFlags)versioningState.Flags; return flags.HasFlag(MethodDescVersioningStateFlags.IsDefaultVersionActiveChildFlag); 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 97d71e24823d4..d72c1b51a5af1 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 @@ -955,6 +955,9 @@ TargetPointer IRuntimeTypeSystem.GetMethodDescVersioningState(MethodDescHandle m { MethodDesc md = _methodDescs[methodDesc.Address]; TargetPointer codeDataAddress = md.CodeData; + if (codeDataAddress == TargetPointer.Null) + return TargetPointer.Null; + Data.MethodDescCodeData codeData = _target.ProcessedData.GetOrAdd(codeDataAddress); return codeData.VersioningState; } diff --git a/src/native/managed/cdacreader/tests/MethodDescTests.cs b/src/native/managed/cdacreader/tests/MethodDescTests.cs index 3189c2fac6591..66871cc967541 100644 --- a/src/native/managed/cdacreader/tests/MethodDescTests.cs +++ b/src/native/managed/cdacreader/tests/MethodDescTests.cs @@ -95,7 +95,8 @@ public void MethodDescGetMethodDescTokenOk(MockTarget.Architecture arch) Assert.Equal(objectMethodTable, mt); bool isCollectible = rts.IsCollectibleMethod(handle); Assert.False(isCollectible); - + TargetPointer versioning = rts.GetMethodDescVersioningState(handle); + Assert.Equal(TargetPointer.Null, versioning); }); } }