From ac1bb9f1d854a023227a88ed639b258508a1f192 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Wed, 16 Oct 2024 15:10:40 -0400 Subject: [PATCH 01/15] [cdac] Add a CodeVersions contract The CodeVersions contract implements the IL and native code versioning as described in [code-versioning.md](docs/design/features/code-versioning.md) Contributes to https://github.com/dotnet/runtime/issues/108553 Contributes to https://github.com/dotnet/runtime/issues/99302 * rename contract NativeCodePointers => CodeVersions * FindActiveILCodeVersion * implement GetModuleLookupMapElement * FindActiveILCodeVersion/FindActiveNativeCodeVersion * il code version lookup table * remove AppDomain.CodeVersionManager from cdac * CodeVersionManager is basically a static class in the C++ side * NativeCodeVersionContract.GetSpecificNativeCodeVersion * checkpoint: start adding NativeCodeVersion operations * WIP: native code version --- docs/design/datacontracts/CodeVersions.md | 149 +++++++++++ docs/design/datacontracts/Loader.md | 6 + .../design/datacontracts/RuntimeTypeSystem.md | 25 +- src/coreclr/debug/runtimeinfo/contracts.jsonc | 1 + .../debug/runtimeinfo/datadescriptor.h | 24 ++ src/coreclr/vm/codeversion.h | 35 ++- .../ContractRegistry.cs | 4 + .../Contracts/ICodeVersions.cs | 44 ++++ .../Contracts/ILoader.cs | 2 + .../Contracts/IRuntimeTypeSystem.cs | 7 + .../DataType.cs | 8 +- .../Constants.cs | 10 + .../Contracts/CodeVersionsFactory.cs | 18 ++ .../Contracts/CodeVersions_1.cs | 243 ++++++++++++++++++ .../Contracts/Loader_1.cs | 31 +++ .../Data/ILCodeVersioningState.cs | 27 ++ .../Data/MethodDescVersioningState.cs | 21 ++ .../Data/ModuleLookupMap.cs | 6 + .../Data/NativeCodeVersionNode.cs | 24 ++ .../CachingContractRegistry.cs | 2 + .../cdacreader/tests/TestPlaceholderTarget.cs | 2 + 21 files changed, 686 insertions(+), 3 deletions(-) create mode 100644 docs/design/datacontracts/CodeVersions.md create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ICodeVersions.cs create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersionsFactory.cs create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersions_1.cs create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ILCodeVersioningState.cs create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDescVersioningState.cs create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/NativeCodeVersionNode.cs diff --git a/docs/design/datacontracts/CodeVersions.md b/docs/design/datacontracts/CodeVersions.md new file mode 100644 index 00000000000000..efd838181cbbba --- /dev/null +++ b/docs/design/datacontracts/CodeVersions.md @@ -0,0 +1,149 @@ +# Contract CodeVersions + +This contract encapsulates support for [code versioning](../features/code-versioning.md) in the runtime. + +## APIs of contract + +```csharp +internal struct NativeCodeVersionHandle +{ + // no public constructors + internal readonly TargetPointer MethodDescAddress; + internal readonly TargetPointer CodeVersionNodeAddress; + internal NativeCodeVersionHandle(TargetPointer methodDescAddress, TargetPointer codeVersionNodeAddress) + { + if (methodDescAddress != TargetPointer.Null && codeVersionNodeAddress != TargetPointer.Null) + { + throw new ArgumentException("Only one of methodDescAddress and codeVersionNodeAddress can be non-null"); + } + MethodDescAddress = methodDescAddress; + CodeVersionNodeAddress = codeVersionNodeAddress; + } + + internal static NativeCodeVersionHandle Invalid => new(TargetPointer.Null, TargetPointer.Null); + public bool Valid => MethodDescAddress != TargetPointer.Null || CodeVersionNodeAddress != TargetPointer.Null; +} +``` + +```csharp + // Return a handle to the version of the native code that includes the given instruction pointer + public virtual NativeCodeVersionHandle GetNativeCodeVersionForIP(TargetCodePointer ip); + // Return a handle to the active version of the native code for a given method descriptor + public virtual NativeCodeVersionHandle GetActiveNativeCodeVersion(TargetPointer methodDesc); + + // returns true if the given method descriptor supports multiple code versions + public virtual bool CodeVersionManagerSupportsMethod(TargetPointer methodDesc); + + // Return the instruction pointer corresponding to the start of the given native code version + public virtual TargetCodePointer GetNativeCode(NativeCodeVersionHandle codeVersionHandle); +``` + +## Version 1 + +Data descriptors used: +| Data Descriptor Name | Field | Meaning | +| --- | --- | --- | +| MethodDescVersioningState | ? | ? | +| NativeCodeVersionNode | ? | ? | +| ILCodeVersioningState | ? | ? | + + +Global variables used: +| Global Name | Type | Purpose | +| --- | --- | --- | + +Contracts used: +| Contract Name | +| --- | +| ExecutionManager | +| Loader | +| RuntimeTypeSystem | + +### Finding the start of a specific native code version + +```csharp + NativeCodeVersionHandle GetNativeCodeVersionForIP(TargetCodePointer ip) + { + Contracts.IExecutionManager executionManager = _target.Contracts.ExecutionManager; + EECodeInfoHandle? info = executionManager.GetEECodeInfoHandle(ip); + if (!info.HasValue) + { + return NativeCodeVersionHandle.Invalid; + } + TargetPointer methodDescAddress = executionManager.GetMethodDesc(info.Value); + if (methodDescAddress == TargetPointer.Null) + { + return NativeCodeVersionHandle.Invalid; + } + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + MethodDescHandle md = rts.GetMethodDescHandle(methodDescAddress); + if (!rts.IsVersionable(md)) + { + return new NativeCodeVersionHandle(methodDescAddress, codeVersionNodeAddress: TargetPointer.Null); + } + else + { + TargetCodePointer startAddress = executionManager.GetStartAddress(info.Value); + return GetSpecificNativeCodeVersion(md, startAddress); + } + } + + private NativeCodeVersionHandle GetSpecificNativeCodeVersion(MethodDescHandle md, TargetCodePointer startAddress) + { + TargetPointer methodDescVersioningStateAddress = target.Contracts.RuntimeTypeSystem.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 codeVersion.MethodDesc == md.Address && codeVersion.NativeCode == startAddress; + }); + } + + private NativeCodeVersionHandle FindFirstCodeVersion(Data.MethodDescVersioningState versioningState, Func predicate) + { + // NativeCodeVersion::Next, heavily inlined + TargetPointer currentAddress = versioningState.NativeCodeVersionNode; + while (currentAddress != TargetPointer.Null) + { + Data.NativeCodeVersionNode current = _target.ProcessedData.GetOrAdd(currentAddress); + if (predicate(current)) + { + return new NativeCodeVersionHandle(methodDescAddress: TargetPointer.Null, currentAddress); + } + currentAddress = current.Next; + } + return NativeCodeVersionHandle.Invalid; + } +``` + +### Finding the active native code version of a method descriptor + +```csharp + NativeCodeVersionHandle ICodeVersions.GetActiveNativeCodeVersion(TargetPointer methodDesc) + { + // CodeVersionManager::GetActiveILCodeVersion + // then ILCodeVersion::GetActiveNativeCodeVersion + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + MethodDescHandle md = rts.GetMethodDescHandle(methodDesc); + TargetPointer mtAddr = rts.GetMethodTable(md); + TypeHandle typeHandle = rts.GetTypeHandle(mtAddr); + TargetPointer module = rts.GetModule(typeHandle); + uint methodDefToken = rts.GetMethodToken(md); + ILCodeVersionHandle methodDefActiveVersion = FindActiveILCodeVersion(module, methodDefToken); + if (!methodDefActiveVersion.IsValid) + { + return NativeCodeVersionHandle.Invalid; + } + return FindActiveNativeCodeVersion(methodDefActiveVersion, methodDesc); + } +``` + +**FIXME** + +### Determining whether a method descriptor supports code versioning + +**TODO** diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index d0fe390b5ad39f..e0f6018435b14b 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -38,6 +38,7 @@ TargetPointer GetLoaderAllocator(ModuleHandle handle); TargetPointer GetThunkHeap(ModuleHandle handle); TargetPointer GetILBase(ModuleHandle handle); ModuleLookupTables GetLookupTables(ModuleHandle handle); +TargetPointer GetModuleLookupMapElement(TargetPointer table, uint rid, out TargetNUInt flags); ``` ## Version 1 @@ -58,6 +59,9 @@ Data descriptors used: | `Module` | `TypeDefToMethodTableMap` | Mapping table | | `Module` | `TypeRefToMethodTableMap` | Mapping table | | `ModuleLookupMap` | `TableData` | Start of the mapping table's data | +| `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 ``` csharp ModuleHandle GetModuleHandle(TargetPointer modulePointer) @@ -110,3 +114,5 @@ ModuleLookupTables GetLookupTables(ModuleHandle handle) Module::MethodDefToILCodeVersioningState */)); } ``` + +**TODO* pseudocode for IsCollectibleLoaderAllocator and LookupTableMap element lookup diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index 8accd2e6dc68fc..98c7644d8771b1 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -138,6 +138,19 @@ partial interface IRuntimeTypeSystem : IContract // Return true if a MethodDesc represents an IL Stub dynamically generated by the runtime // A IL Stub method is also a StoredSigMethodDesc, and a NoMetadataMethod public virtual bool IsILStub(MethodDescHandle methodDesc); + + // Return true if a MethodDesc is in a collectible module + public virtual bool IsCollectibleMethod(MethodDescHandle methodDesc); + + // Return true if a MethodDesc supports mulitiple code versions + public virtual bool IsVersionable(MethodDescHandle methodDesc); + + // Return a pointer to the IL versioning state of the MethodDesc + public virtual TargetPointer GetMethodDescVersioningState(MethodDescHandle methodDesc); + + // Get an instruction pointer that can be called to cause the MethodDesc to be executed + public virtual TargetCodePointer GetNativeCode(MethodDescHandle methodDesc); + } ``` @@ -607,6 +620,7 @@ 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. @@ -631,6 +645,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 | +| --- | +| Loader | +| ReJIT | +| CodeVersions | And the following enumeration definitions @@ -821,4 +844,4 @@ And the various apis are implemented with the following algorithms return ((DynamicMethodDescExtendedFlags)ExtendedFlags).HasFlag(DynamicMethodDescExtendedFlags.IsILStub); } ``` -**TODO(cdac)** +**TODO(cdac)** additional code pointers methods on MethodDesc diff --git a/src/coreclr/debug/runtimeinfo/contracts.jsonc b/src/coreclr/debug/runtimeinfo/contracts.jsonc index 15a1aece96cee4..5f3194ed3f9cf2 100644 --- a/src/coreclr/debug/runtimeinfo/contracts.jsonc +++ b/src/coreclr/debug/runtimeinfo/contracts.jsonc @@ -9,6 +9,7 @@ // cdac-build-tool can take multiple "-c contract_file" arguments // so to conditionally include contracts, put additional contracts in a separate file { + "CodeVersions": 1, "DacStreams": 1, "EcmaMetadata" : 1, "Exception": 1, diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 5165f148263af0..673f8fed7bd2cf 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -238,6 +238,9 @@ CDAC_TYPE_END(Module) CDAC_TYPE_BEGIN(ModuleLookupMap) CDAC_TYPE_FIELD(ModuleLookupMap, /*pointer*/, TableData, offsetof(LookupMapBase, pTable)) +CDAC_TYPE_FIELD(ModuleLookupMap, /*pointer*/, Next, offsetof(LookupMapBase, pNext)) +CDAC_TYPE_FIELD(ModuleLookupMap, /*uint32*/, Count, offsetof(LookupMapBase, dwCount)) +CDAC_TYPE_FIELD(ModuleLookupMap, /*nuint*/, SupportedFlagsMask, offsetof(LookupMapBase, supportedFlags)) CDAC_TYPE_END(ModuleLookupMap) // RuntimeTypeSystem @@ -344,6 +347,12 @@ CDAC_TYPE_BEGIN(CodePointer) CDAC_TYPE_SIZE(sizeof(PCODE)) CDAC_TYPE_END(CodePointer) +CDAC_TYPE_BEGIN(MethodDescVersioningState) +CDAC_TYPE_INDETERMINATE(MethodDescVersioningState) +CDAC_TYPE_FIELD(MethodDescVersioningState, /*pointer*/, NativeCodeVersionNode, cdac_data::NativeCodeVersionNode) +CDAC_TYPE_FIELD(MethodDescVersioningState, /*uint8*/, Flags, cdac_data::Flags) +CDAC_TYPE_END(MethodDescVersioningState) + CDAC_TYPE_BEGIN(RangeSectionMap) CDAC_TYPE_INDETERMINATE(RangeSectionMap) CDAC_TYPE_FIELD(RangeSectionMap, /*pointer*/, TopLevelData, cdac_data::TopLevelData) @@ -381,6 +390,21 @@ CDAC_TYPE_FIELD(CodeHeapListNode, /*pointer*/, MapBase, offsetof(HeapList, mapBa CDAC_TYPE_FIELD(CodeHeapListNode, /*pointer*/, HeaderMap, offsetof(HeapList, pHdrMap)) CDAC_TYPE_END(CodeHeapListNode) +CDAC_TYPE_BEGIN(ILCodeVersioningState) +CDAC_TYPE_INDETERMINATE(ILCodeVersioningState) +CDAC_TYPE_FIELD(ILCodeVersioningState, /*pointer*/, Node, cdac_data::Node) +CDAC_TYPE_FIELD(ILCodeVersioningState, /*uint32*/, ActiveVersionKind, cdac_data::ActiveVersionKind) +CDAC_TYPE_FIELD(ILCodeVersioningState, /*pointer*/, ActiveVersionNode, cdac_data::ActiveVersionNode) +CDAC_TYPE_FIELD(ILCodeVersioningState, /*pointer*/, ActiveVersionModule, cdac_data::ActiveVersionModule) +CDAC_TYPE_FIELD(ILCodeVersioningState, /*uint32*/, ActiveVersionMethodDef, cdac_data::ActiveVersionMethodDef) +CDAC_TYPE_END(ILCodeVersioningState) + +CDAC_TYPE_BEGIN(NativeCodeVersionNode) +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_END(NativeCodeVersionNode) CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() diff --git a/src/coreclr/vm/codeversion.h b/src/coreclr/vm/codeversion.h index faf01578836864..1eec36c7d68fe2 100644 --- a/src/coreclr/vm/codeversion.h +++ b/src/coreclr/vm/codeversion.h @@ -248,8 +248,10 @@ class ILCodeVersion mdMethodDef m_methodDef; } m_synthetic; }; -}; + // cDAC accesses fields via ILCodeVersioningState.m_activeVersion + template friend struct ::cdac_data; +}; class NativeCodeVersionNode { @@ -316,6 +318,16 @@ class NativeCodeVersionNode IsActiveChildFlag = 1 }; DWORD m_flags; + + template friend struct ::cdac_data; +}; + +template<> +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); }; class NativeCodeVersionCollection @@ -473,6 +485,15 @@ class MethodDescVersioningState BYTE m_flags; NativeCodeVersionId m_nextId; PTR_NativeCodeVersionNode m_pFirstVersionNode; + + template friend struct ::cdac_data; +}; + +template<> +struct cdac_data +{ + static constexpr size_t NativeCodeVersionNode = offsetof(MethodDescVersioningState, m_pFirstVersionNode); + static constexpr size_t Flags = offsetof(MethodDescVersioningState, m_flags); }; class ILCodeVersioningState @@ -505,6 +526,18 @@ class ILCodeVersioningState PTR_ILCodeVersionNode m_pFirstVersionNode; PTR_Module m_pModule; mdMethodDef m_methodDef; + + template friend struct ::cdac_data; +}; + +template<> +struct cdac_data +{ + static constexpr size_t Node = offsetof(ILCodeVersioningState, m_pFirstVersionNode); + static constexpr size_t ActiveVersionKind = offsetof(ILCodeVersioningState, m_activeVersion.m_storageKind); + static constexpr size_t ActiveVersionNode = offsetof(ILCodeVersioningState, m_activeVersion.m_pVersionNode); + static constexpr size_t ActiveVersionModule = offsetof(ILCodeVersioningState, m_activeVersion.m_synthetic.m_pModule); + static constexpr size_t ActiveVersionMethodDef = offsetof(ILCodeVersioningState, m_activeVersion.m_synthetic.m_methodDef); }; class CodeVersionManager 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 3d641f9ab0ef79..1075c99c5c7e79 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs @@ -43,4 +43,8 @@ internal abstract class ContractRegistry /// Gets an instance of the ExecutionManager contract for the target. /// public abstract IExecutionManager ExecutionManager { get; } + /// + /// Gets an instance of the CodeVersions contract for the target. + /// + public abstract ICodeVersions CodeVersions { get; } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ICodeVersions.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ICodeVersions.cs new file mode 100644 index 00000000000000..bddba2cdc27875 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ICodeVersions.cs @@ -0,0 +1,44 @@ +// 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 ICodeVersions : IContract +{ + static string IContract.Name { get; } = nameof(CodeVersions); + + public virtual NativeCodeVersionHandle GetNativeCodeVersionForIP(TargetCodePointer ip) => throw new NotImplementedException(); + public virtual NativeCodeVersionHandle GetActiveNativeCodeVersion(TargetPointer methodDesc) => throw new NotImplementedException(); + + public virtual bool CodeVersionManagerSupportsMethod(TargetPointer methodDesc) => throw new NotImplementedException(); + + public virtual TargetCodePointer GetNativeCode(NativeCodeVersionHandle codeVersionHandle) => throw new NotImplementedException(); + +} + +internal struct NativeCodeVersionHandle +{ + // no public constructors + internal readonly TargetPointer MethodDescAddress; + internal readonly TargetPointer CodeVersionNodeAddress; + internal NativeCodeVersionHandle(TargetPointer methodDescAddress, TargetPointer codeVersionNodeAddress) + { + if (methodDescAddress != TargetPointer.Null && codeVersionNodeAddress != TargetPointer.Null) + { + throw new ArgumentException("Only one of methodDescAddress and codeVersionNodeAddress can be non-null"); + } + MethodDescAddress = methodDescAddress; + CodeVersionNodeAddress = codeVersionNodeAddress; + } + + internal static NativeCodeVersionHandle Invalid => new(TargetPointer.Null, TargetPointer.Null); + public bool Valid => MethodDescAddress != TargetPointer.Null || CodeVersionNodeAddress != TargetPointer.Null; + +} + +internal readonly struct CodeVersions : ICodeVersions +{ + // throws NotImplementedException for all methods +} 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 4c3be1e2f0a8a5..f0113dc3a1b1c9 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 @@ -46,6 +46,8 @@ internal interface ILoader : IContract public virtual TargetPointer GetThunkHeap(ModuleHandle handle) => throw new NotImplementedException(); public virtual TargetPointer GetILBase(ModuleHandle handle) => throw new NotImplementedException(); public virtual ModuleLookupTables GetLookupTables(ModuleHandle handle) => throw new NotImplementedException(); + + public virtual TargetPointer GetModuleLookupMapElement(TargetPointer table, uint rid, out TargetNUInt flags) => throw new NotImplementedException(); } internal readonly struct Loader : ILoader 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 ddc2744d1d307b..c93aa252abeb7a 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 @@ -153,6 +153,13 @@ internal interface IRuntimeTypeSystem : IContract // A IL Stub method is also a StoredSigMethodDesc, and a NoMetadataMethod public virtual bool IsILStub(MethodDescHandle methodDesc) => throw new NotImplementedException(); + public virtual bool IsCollectibleMethod(MethodDescHandle methodDesc) => throw new NotImplementedException(); + public virtual bool IsVersionable(MethodDescHandle methodDesc) => throw new NotImplementedException(); + + public virtual TargetPointer GetMethodDescVersioningState(MethodDescHandle methodDesc) => throw new NotImplementedException(); + + public virtual TargetCodePointer GetNativeCode(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 37dae1a357bef8..c449a4dab4e665 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -44,6 +44,10 @@ public enum DataType String, MethodDesc, MethodDescChunk, + MethodDescCodeData, + PrecodeMachineDescriptor, + StubPrecodeData, + FixupPrecodeData, Array, SyncBlock, SyncTableEntry, @@ -56,5 +60,7 @@ public enum DataType RangeSection, RealCodeHeader, CodeHeapListNode, - + MethodDescVersioningState, + ILCodeVersioningState, + NativeCodeVersionNode, } 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 b5862cf785b9c6..712cac02a24c8f 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs @@ -42,4 +42,14 @@ internal static class Globals internal const string ExecutionManagerCodeRangeMapAddress = nameof(ExecutionManagerCodeRangeMapAddress); internal const string StubCodeBlockLast = nameof(StubCodeBlockLast); } + + internal static class EcmaMetadata + { + internal const int RowIdBitCount = 24; + internal const uint RIDMask = (1 << RowIdBitCount) - 1; + + internal static uint GetRowId(uint token) => token & RIDMask; + + internal static uint MakeToken(uint rid, uint table) => rid | (table << RowIdBitCount); + } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersionsFactory.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersionsFactory.cs new file mode 100644 index 00000000000000..8d0c384c7361e1 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersionsFactory.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.Contracts; + +internal sealed class CodeVersionsFactory : IContractFactory +{ + ICodeVersions IContractFactory.CreateContract(Target target, int version) + { + return version switch + { + 1 => new CodeVersions_1(target), + _ => default(CodeVersions), + }; + } +} 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 new file mode 100644 index 00000000000000..82ec9f3c658edb --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersions_1.cs @@ -0,0 +1,243 @@ +// 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 CodeVersions_1 : ICodeVersions +{ + private readonly Target _target; + + + public CodeVersions_1(Target target) + { + _target = target; + } + + NativeCodeVersionHandle ICodeVersions.GetNativeCodeVersionForIP(TargetCodePointer ip) + { + // ExecutionManager::GetNativeCodeVersion(PCODE ip)) + // and EECodeInfo::GetNativeCodeVersion + Contracts.IExecutionManager executionManager = _target.Contracts.ExecutionManager; + CodeBlockHandle? info = executionManager.GetCodeBlockHandle(ip); + if (!info.HasValue) + { + return NativeCodeVersionHandle.Invalid; + } + TargetPointer methodDescAddress = executionManager.GetMethodDesc(info.Value); + if (methodDescAddress == TargetPointer.Null) + { + return NativeCodeVersionHandle.Invalid; + } + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + MethodDescHandle md = rts.GetMethodDescHandle(methodDescAddress); + if (!rts.IsVersionable(md)) + { + return new NativeCodeVersionHandle(methodDescAddress, codeVersionNodeAddress: TargetPointer.Null); + } + else + { + TargetCodePointer startAddress = executionManager.GetStartAddress(info.Value); + return GetSpecificNativeCodeVersion(rts, md, startAddress); + } + } + + NativeCodeVersionHandle ICodeVersions.GetActiveNativeCodeVersion(TargetPointer methodDesc) + { + // CodeVersionManager::GetActiveILCodeVersion + // then ILCodeVersion::GetActiveNativeCodeVersion + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + MethodDescHandle md = rts.GetMethodDescHandle(methodDesc); + TargetPointer mtAddr = rts.GetMethodTable(md); + TypeHandle typeHandle = rts.GetTypeHandle(mtAddr); + TargetPointer module = rts.GetModule(typeHandle); + uint methodDefToken = rts.GetMethodToken(md); + ILCodeVersionHandle methodDefActiveVersion = FindActiveILCodeVersion(module, methodDefToken); + if (!methodDefActiveVersion.IsValid) + { + return NativeCodeVersionHandle.Invalid; + } + return FindActiveNativeCodeVersion(methodDefActiveVersion, methodDesc); + } + bool ICodeVersions.CodeVersionManagerSupportsMethod(TargetPointer methodDescAddress) + { + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + MethodDescHandle md = rts.GetMethodDescHandle(methodDescAddress); + if (rts.IsDynamicMethod(md)) + return false; + if (rts.IsCollectibleMethod(md)) + return false; + TargetPointer mtAddr = rts.GetMethodTable(md); + TypeHandle mt = rts.GetTypeHandle(mtAddr); + TargetPointer modAddr = rts.GetModule(mt); + ILoader loader = _target.Contracts.Loader; + ModuleHandle mod = loader.GetModuleHandle(modAddr); + ModuleFlags modFlags = loader.GetFlags(mod); + if (modFlags.HasFlag(ModuleFlags.EditAndContinue)) + return false; + return true; + } + + TargetCodePointer ICodeVersions.GetNativeCode(NativeCodeVersionHandle codeVersionHandle) + { + if (codeVersionHandle.MethodDescAddress != TargetPointer.Null) + { + MethodDescHandle md = _target.Contracts.RuntimeTypeSystem.GetMethodDescHandle(codeVersionHandle.MethodDescAddress); + return _target.Contracts.RuntimeTypeSystem.GetNativeCode(md); + } + else if (codeVersionHandle.CodeVersionNodeAddress != TargetPointer.Null) + { + throw new NotImplementedException(); // TODO[cdac]: get native code from NativeCodeVersionNode + } + else + { + throw new ArgumentException("Invalid NativeCodeVersionHandle"); + } + } + + internal struct ILCodeVersionHandle + { + internal readonly TargetPointer Module; + internal uint MethodDefinition; + internal readonly TargetPointer ILCodeVersionNode; + internal readonly uint RejitId; + + internal ILCodeVersionHandle(TargetPointer module, uint methodDef, TargetPointer ilCodeVersionNodeAddress) + { + Module = module; + MethodDefinition = methodDef; + ILCodeVersionNode = ilCodeVersionNodeAddress; + if (Module != TargetPointer.Null && ILCodeVersionNode != TargetPointer.Null) + { + throw new ArgumentException("Both MethodDesc and ILCodeVersionNode cannot be non-null"); + + } + if (Module != TargetPointer.Null && MethodDefinition == 0) + { + throw new ArgumentException("MethodDefinition must be non-zero if Module is non-null"); + } + } + public static ILCodeVersionHandle Invalid => new ILCodeVersionHandle(TargetPointer.Null, 0, TargetPointer.Null); + public bool IsValid => Module != TargetPointer.Null || ILCodeVersionNode != TargetPointer.Null; + } + + [Flags] + internal enum MethodDescVersioningStateFlags : byte + { + IsDefaultVersionActiveChildFlag = 0x4 + }; + + + private NativeCodeVersionHandle GetSpecificNativeCodeVersion(IRuntimeTypeSystem rts, MethodDescHandle md, TargetCodePointer startAddress) + { + 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 codeVersion.MethodDesc == md.Address && codeVersion.NativeCode == startAddress; + }); + } + + private NativeCodeVersionHandle FindFirstCodeVersion(Data.MethodDescVersioningState versioningState, Func predicate) + { + // NativeCodeVersion::Next, heavily inlined + TargetPointer currentAddress = versioningState.NativeCodeVersionNode; + while (currentAddress != TargetPointer.Null) + { + Data.NativeCodeVersionNode current = _target.ProcessedData.GetOrAdd(currentAddress); + if (predicate(current)) + { + return new NativeCodeVersionHandle(methodDescAddress: TargetPointer.Null, currentAddress); + } + currentAddress = current.Next; + } + return NativeCodeVersionHandle.Invalid; + } + + + private enum ILCodeVersionKind + { + Unknown = 0, + Explicit = 1, // means Node is set + Synthetic = 2, // means Module and Token are set + } + private static ILCodeVersionHandle ILCodeVersionHandleFromState(Data.ILCodeVersioningState ilState) + { + switch ((ILCodeVersionKind)ilState.ActiveVersionKind) + { + case ILCodeVersionKind.Explicit: + return new ILCodeVersionHandle(module: TargetPointer.Null, methodDef: 0, ilState.ActiveVersionNode); + case ILCodeVersionKind.Synthetic: + case ILCodeVersionKind.Unknown: + return new ILCodeVersionHandle(ilState.ActiveVersionModule, ilState.ActiveVersionMethodDef, TargetPointer.Null); + default: + throw new InvalidOperationException($"Unknown ILCodeVersionKind {ilState.ActiveVersionKind}"); + } + } + + private ILCodeVersionHandle FindActiveILCodeVersion(TargetPointer module, uint methodDefinition) + { + 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 _); + if (ilVersionStateAddress == TargetPointer.Null) + { + return new ILCodeVersionHandle(module, methodDefinition, TargetPointer.Null); + } + Data.ILCodeVersioningState ilState = _target.ProcessedData.GetOrAdd(ilVersionStateAddress); + return ILCodeVersionHandleFromState(ilState); + } + + private bool IsActiveNativeCodeVersion(NativeCodeVersionHandle nativeCodeVersion) + { + if (nativeCodeVersion.MethodDescAddress != TargetPointer.Null) + { + MethodDescHandle md = _target.Contracts.RuntimeTypeSystem.GetMethodDescHandle(nativeCodeVersion.MethodDescAddress); + TargetPointer versioningStateAddress = _target.Contracts.RuntimeTypeSystem.GetMethodDescVersioningState(md); + if (versioningStateAddress == TargetPointer.Null) + { + return true; + } + Data.MethodDescVersioningState versioningState = _target.ProcessedData.GetOrAdd(versioningStateAddress); + return (((MethodDescVersioningStateFlags)versioningState.Flags) & MethodDescVersioningStateFlags.IsDefaultVersionActiveChildFlag) != 0; + } + 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 + } + else + { + throw new ArgumentException("Invalid NativeCodeVersionHandle"); + } + } + + private NativeCodeVersionHandle FindActiveNativeCodeVersion(ILCodeVersionHandle methodDefActiveVersion, TargetPointer methodDescAddress) + { + if (methodDefActiveVersion.Module != TargetPointer.Null) + { + NativeCodeVersionHandle provisionalHandle = new NativeCodeVersionHandle(methodDescAddress: methodDescAddress, codeVersionNodeAddress: TargetPointer.Null); + if (IsActiveNativeCodeVersion(provisionalHandle)) + { + return provisionalHandle; + } + else + { + throw new NotImplementedException(); // TODO[cdac]: iterate through versioning state nodes + } + } + else + { + throw new NotImplementedException(); // TODO: [cdac] find explicit il code version + } + } + +} 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 ca61e0174b0649..62f9d5c1925e55 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 @@ -70,4 +70,35 @@ ModuleLookupTables ILoader.GetLookupTables(ModuleHandle handle) module.TypeRefToMethodTableMap, module.MethodDefToILCodeVersioningStateMap); } + + TargetPointer ILoader.GetModuleLookupMapElement(TargetPointer table, uint token, out TargetNUInt flags) + { + uint rid = Constants.EcmaMetadata.GetRowId(token); + ArgumentOutOfRangeException.ThrowIfZero(rid); + flags = new TargetNUInt(0); + if (table == TargetPointer.Null) + return TargetPointer.Null; + uint index = rid; + Data.ModuleLookupMap lookupMap = _target.ProcessedData.GetOrAdd(table); + // have to read lookupMap an extra time upfront because only the first map + // has valid supportedFlagsMask + TargetNUInt supportedFlagsMask = lookupMap.SupportedFlagsMask; + do + { + lookupMap = _target.ProcessedData.GetOrAdd(table); + if (index < lookupMap.Count) + { + TargetPointer entryAddress = lookupMap.TableData + (ulong)(index * _target.PointerSize); + TargetPointer rawValue = _target.ReadPointer(entryAddress); + flags = new TargetNUInt(rawValue & supportedFlagsMask.Value); + return rawValue & ~(supportedFlagsMask.Value); + } + else + { + table = lookupMap.Next; + index -= lookupMap.Count; + } + } while (table != TargetPointer.Null); + return TargetPointer.Null; + } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ILCodeVersioningState.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ILCodeVersioningState.cs new file mode 100644 index 00000000000000..34256abe54569b --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ILCodeVersioningState.cs @@ -0,0 +1,27 @@ +// 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 ILCodeVersioningState : IData +{ + static ILCodeVersioningState IData.Create(Target target, TargetPointer address) + => new ILCodeVersioningState(target, address); + + public ILCodeVersioningState(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.ILCodeVersioningState); + + Node = target.ReadPointer(address + (ulong)type.Fields[nameof(Node)].Offset); + ActiveVersionKind = target.Read(address + (ulong)type.Fields[nameof(ActiveVersionKind)].Offset); + ActiveVersionNode = target.ReadPointer(address + (ulong)type.Fields[nameof(ActiveVersionNode)].Offset); + ActiveVersionModule = target.ReadPointer(address + (ulong)type.Fields[nameof(ActiveVersionModule)].Offset); + ActiveVersionMethodDef = target.Read(address + (ulong)type.Fields[nameof(ActiveVersionMethodDef)].Offset); + } + + public TargetPointer Node { get; init; } + public uint ActiveVersionKind { get; set; } + public TargetPointer ActiveVersionNode { get; set; } + public TargetPointer ActiveVersionModule { get; set; } + public uint ActiveVersionMethodDef { get; set; } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDescVersioningState.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDescVersioningState.cs new file mode 100644 index 00000000000000..95b404afa29508 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDescVersioningState.cs @@ -0,0 +1,21 @@ +// 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 MethodDescVersioningState : IData +{ + static MethodDescVersioningState IData.Create(Target target, TargetPointer address) => new MethodDescVersioningState(target, address); + public MethodDescVersioningState(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.MethodDescVersioningState); + + NativeCodeVersionNode = target.ReadPointer(address + (ulong)type.Fields[nameof(NativeCodeVersionNode)].Offset); + Flags = target.Read(address + (ulong)type.Fields[nameof(Flags)].Offset); + } + + public TargetPointer NativeCodeVersionNode { get; init; } + public byte Flags { get; init; } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ModuleLookupMap.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ModuleLookupMap.cs index db69c68fe2c133..449dfb0cb04523 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ModuleLookupMap.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ModuleLookupMap.cs @@ -12,7 +12,13 @@ private ModuleLookupMap(Target target, TargetPointer address) Target.TypeInfo type = target.GetTypeInfo(DataType.ModuleLookupMap); TableData = target.ReadPointer(address + (ulong)type.Fields[nameof(TableData)].Offset); + Next = target.ReadPointer(address + (ulong)type.Fields[nameof(Next)].Offset); + Count = target.Read(address + (ulong)type.Fields[nameof(Count)].Offset); + SupportedFlagsMask = target.ReadNUInt(address + (ulong)type.Fields[nameof(SupportedFlagsMask)].Offset); } public TargetPointer TableData { get; init; } + public TargetPointer Next { get; init; } + public uint Count { get; init; } + public TargetNUInt SupportedFlagsMask { 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 new file mode 100644 index 00000000000000..87b89201a97fc6 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/NativeCodeVersionNode.cs @@ -0,0 +1,24 @@ +// 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 NativeCodeVersionNode : IData +{ + static NativeCodeVersionNode IData.Create(Target target, TargetPointer address) => new NativeCodeVersionNode(target, address); + public NativeCodeVersionNode(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.NativeCodeVersionNode); + + 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); + } + + public TargetPointer Next { get; init; } + public TargetPointer MethodDesc { get; init; } + + public TargetCodePointer NativeCode { get; init; } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs index 61ba6a28502217..9540b31b435908 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs @@ -33,6 +33,7 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG [typeof(IRuntimeTypeSystem)] = new RuntimeTypeSystemFactory(), [typeof(IDacStreams)] = new DacStreamsFactory(), [typeof(IExecutionManager)] = new ExecutionManagerFactory(), + [typeof(ICodeVersions)] = new CodeVersionsFactory(), }; configureFactories?.Invoke(_factories); } @@ -45,6 +46,7 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG public override IRuntimeTypeSystem RuntimeTypeSystem => GetContract(); public override IDacStreams DacStreams => GetContract(); public override IExecutionManager ExecutionManager => GetContract(); + public override ICodeVersions CodeVersions => GetContract(); private TContract GetContract() where TContract : IContract { diff --git a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs index afabf8a914b594..4432dbe848c3bf 100644 --- a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs +++ b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs @@ -205,6 +205,7 @@ public TestRegistry() { } internal Lazy? RuntimeTypeSystemContract { get; set; } internal Lazy? DacStreamsContract { get; set; } internal Lazy ExecutionManagerContract { get; set; } + internal Lazy? CodeVersionsContract { get; set; } public override Contracts.IException Exception => ExceptionContract.Value ?? throw new NotImplementedException(); public override Contracts.ILoader Loader => LoaderContract.Value ?? throw new NotImplementedException(); @@ -214,6 +215,7 @@ public TestRegistry() { } public override Contracts.IRuntimeTypeSystem RuntimeTypeSystem => RuntimeTypeSystemContract.Value ?? throw new NotImplementedException(); 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(); } // a data cache that throws NotImplementedException for all methods, From 28d9aa17a4919dbcecee76f6898467917a0afe0e Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Thu, 17 Oct 2024 12:26:51 -0400 Subject: [PATCH 02/15] tighter cdac_data friends --- src/coreclr/vm/codeversion.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/coreclr/vm/codeversion.h b/src/coreclr/vm/codeversion.h index 1eec36c7d68fe2..f476063b4142c4 100644 --- a/src/coreclr/vm/codeversion.h +++ b/src/coreclr/vm/codeversion.h @@ -250,7 +250,7 @@ class ILCodeVersion }; // cDAC accesses fields via ILCodeVersioningState.m_activeVersion - template friend struct ::cdac_data; + friend struct ::cdac_data; }; class NativeCodeVersionNode @@ -319,7 +319,7 @@ class NativeCodeVersionNode }; DWORD m_flags; - template friend struct ::cdac_data; + friend struct ::cdac_data; }; template<> @@ -486,7 +486,7 @@ class MethodDescVersioningState NativeCodeVersionId m_nextId; PTR_NativeCodeVersionNode m_pFirstVersionNode; - template friend struct ::cdac_data; + friend struct ::cdac_data; }; template<> @@ -527,7 +527,7 @@ class ILCodeVersioningState PTR_Module m_pModule; mdMethodDef m_methodDef; - template friend struct ::cdac_data; + friend struct ::cdac_data; }; template<> From 70f47467f7f86b319d193768c80ec69d38e8aca9 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Thu, 17 Oct 2024 12:29:49 -0400 Subject: [PATCH 03/15] remove unused DataType values they'll come in from a future PR --- .../DataType.cs | 4 ---- 1 file changed, 4 deletions(-) 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 c449a4dab4e665..5b184a44ad0230 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -44,10 +44,6 @@ public enum DataType String, MethodDesc, MethodDescChunk, - MethodDescCodeData, - PrecodeMachineDescriptor, - StubPrecodeData, - FixupPrecodeData, Array, SyncBlock, SyncTableEntry, From 0dbf69d310daf980f46e374e44e618dd00dd54d0 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Thu, 17 Oct 2024 12:38:38 -0400 Subject: [PATCH 04/15] move ecma metadata helpers to their own class --- .../Constants.cs | 10 ---------- .../Contracts/Loader_1.cs | 2 +- .../EcmaMetadataUtils.cs | 15 +++++++++++++++ 3 files changed, 16 insertions(+), 11 deletions(-) create mode 100644 src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/EcmaMetadataUtils.cs 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 712cac02a24c8f..b5862cf785b9c6 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs @@ -42,14 +42,4 @@ internal static class Globals internal const string ExecutionManagerCodeRangeMapAddress = nameof(ExecutionManagerCodeRangeMapAddress); internal const string StubCodeBlockLast = nameof(StubCodeBlockLast); } - - internal static class EcmaMetadata - { - internal const int RowIdBitCount = 24; - internal const uint RIDMask = (1 << RowIdBitCount) - 1; - - internal static uint GetRowId(uint token) => token & RIDMask; - - internal static uint MakeToken(uint rid, uint table) => rid | (table << RowIdBitCount); - } } 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 62f9d5c1925e55..6eae025454f2b8 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 @@ -73,7 +73,7 @@ ModuleLookupTables ILoader.GetLookupTables(ModuleHandle handle) TargetPointer ILoader.GetModuleLookupMapElement(TargetPointer table, uint token, out TargetNUInt flags) { - uint rid = Constants.EcmaMetadata.GetRowId(token); + uint rid = EcmaMetadataUtils.GetRowId(token); ArgumentOutOfRangeException.ThrowIfZero(rid); flags = new TargetNUInt(0); if (table == TargetPointer.Null) diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/EcmaMetadataUtils.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/EcmaMetadataUtils.cs new file mode 100644 index 00000000000000..e0fbee71811581 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/EcmaMetadataUtils.cs @@ -0,0 +1,15 @@ +// 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; + +internal static class EcmaMetadataUtils +{ + internal const int RowIdBitCount = 24; + internal const uint RIDMask = (1 << RowIdBitCount) - 1; + + internal static uint GetRowId(uint token) => token & RIDMask; + + internal static uint MakeToken(uint rid, uint table) => rid | (table << RowIdBitCount); + +} From ed6b3450109076bc3c244e682e3a2503de384654 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Thu, 17 Oct 2024 13:10:32 -0400 Subject: [PATCH 05/15] WIP: CodeVersionsTests --- .../cdacreader/tests/CodeVersionsTests.cs | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/native/managed/cdacreader/tests/CodeVersionsTests.cs diff --git a/src/native/managed/cdacreader/tests/CodeVersionsTests.cs b/src/native/managed/cdacreader/tests/CodeVersionsTests.cs new file mode 100644 index 00000000000000..73e2bcec501dc5 --- /dev/null +++ b/src/native/managed/cdacreader/tests/CodeVersionsTests.cs @@ -0,0 +1,52 @@ +// 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 Microsoft.Diagnostics.DataContractReader.Contracts; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.UnitTests; + +public class CodeVersionsTests +{ + internal class MockExecutionManager : IExecutionManager + { + CodeBlockHandle? IExecutionManager.GetCodeBlockHandle(TargetCodePointer ip) + { + if (ip == TargetCodePointer.Null) + { + return null; + } + throw new NotImplementedException(); + } + } + internal class CVTestTarget : TestPlaceholderTarget + { + public CVTestTarget(MockTarget.Architecture arch) : base(arch) { + IContractFactory cvfactory = new CodeVersionsFactory(); + IExecutionManager mockExecutionManager = new MockExecutionManager(); + SetContracts(new TestRegistry() { + CodeVersionsContract = new (() => cvfactory.CreateContract(this, 1)), + ExecutionManagerContract = new (() => mockExecutionManager), + }); + + } + + + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void TestCodeVersionNull(MockTarget.Architecture arch) + { + var target = new CVTestTarget(arch); + var codeVersions = target.Contracts.CodeVersions; + + Assert.NotNull(codeVersions); + + TargetCodePointer nullPointer = TargetCodePointer.Null; + + var handle = codeVersions.GetNativeCodeVersionForIP(nullPointer); + Assert.False(handle.Valid); + } +} From 66a86c05fb7c21aea9cef551c1187627a2910048 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Thu, 17 Oct 2024 16:45:22 -0400 Subject: [PATCH 06/15] WIP: TestGetNativeCodeVersionOneVersionVersionable --- .../cdacreader/tests/CodeVersionsTests.cs | 261 +++++++++++++++++- 1 file changed, 254 insertions(+), 7 deletions(-) diff --git a/src/native/managed/cdacreader/tests/CodeVersionsTests.cs b/src/native/managed/cdacreader/tests/CodeVersionsTests.cs index 73e2bcec501dc5..c9e6ba33f13cde 100644 --- a/src/native/managed/cdacreader/tests/CodeVersionsTests.cs +++ b/src/native/managed/cdacreader/tests/CodeVersionsTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using Microsoft.Diagnostics.DataContractReader.Contracts; using Xunit; @@ -9,35 +10,214 @@ namespace Microsoft.Diagnostics.DataContractReader.UnitTests; public class CodeVersionsTests { + + internal class MockMethodDesc + { + public TargetPointer Address { get; private set; } + public bool IsVersionable { get; private set; } + + // only non-null if IsVersionable is false + public TargetCodePointer NativeCode { get; private set; } + + // only non-null if IsVersionable is true + public TargetPointer MethodDescVersioningState { get; private set; } + + public static MockMethodDesc CreateNonVersionable (TargetPointer selfAddress, TargetCodePointer nativeCode) + { + return new MockMethodDesc() { + Address = selfAddress, + IsVersionable = false, + NativeCode = nativeCode, + MethodDescVersioningState = TargetPointer.Null, + }; + } + + public static MockMethodDesc CreateVersionable (TargetPointer selfAddress, TargetPointer methodDescVersioningState) + { + return new MockMethodDesc() { + Address = selfAddress, + IsVersionable = true, + NativeCode = TargetCodePointer.Null, + MethodDescVersioningState = methodDescVersioningState, + }; + } + } + internal class MockCodeBlockStart + { + public TargetCodePointer StartAddress { get; set;} + public uint Length { get; set; } + + public bool Contains(TargetPointer ip) => ip >= StartAddress && ip < StartAddress + Length; + public bool Contains(TargetCodePointer ip) => Contains(ip.AsTargetPointer); + public MockMethodDesc MethodDesc {get; set;} + public TargetPointer MethodDescAddress => MethodDesc.Address; + } + internal class MockExecutionManager : IExecutionManager { + private IReadOnlyCollection _codeBlocks; + + public MockExecutionManager(IReadOnlyCollection codeBlocks) + { + _codeBlocks = codeBlocks; + } + CodeBlockHandle? IExecutionManager.GetCodeBlockHandle(TargetCodePointer ip) { if (ip == TargetCodePointer.Null) { return null; } - throw new NotImplementedException(); + foreach (var block in _codeBlocks) + { + if (block.Contains(ip)) + { + return new CodeBlockHandle(ip.AsTargetPointer); + } + } + return null; + } + + TargetCodePointer IExecutionManager.GetStartAddress(CodeBlockHandle codeInfoHandle) + { + foreach (var block in _codeBlocks) + { + if (block.Contains(codeInfoHandle.Address)) + { + return block.StartAddress; + } + } + return TargetCodePointer.Null; + } + + TargetPointer IExecutionManager.GetMethodDesc(CodeBlockHandle codeInfoHandle) + { + foreach (var block in _codeBlocks) + { + if (block.Contains(codeInfoHandle.Address)) + { + return block.MethodDescAddress; + } + } + return TargetPointer.Null; + } + } + + internal class MockRuntimeTypeSystem : IRuntimeTypeSystem + { + IReadOnlyCollection _methodDescs; + public MockRuntimeTypeSystem(IReadOnlyCollection methodDescs) + { + _methodDescs = methodDescs; + } + + private MockMethodDesc? TryFindMethodDesc(TargetPointer targetPointer) + { + foreach (var methodDesc in _methodDescs) + { + if (methodDesc.Address == targetPointer) + { + return methodDesc; + } + } + return null; + } + + private MockMethodDesc FindMethodDesc(TargetPointer targetPointer) => TryFindMethodDesc(targetPointer) ?? throw new InvalidOperationException($"MethodDesc not found for {targetPointer}"); + + MethodDescHandle IRuntimeTypeSystem.GetMethodDescHandle(TargetPointer targetPointer) => new MethodDescHandle(FindMethodDesc(targetPointer).Address); + + bool IRuntimeTypeSystem.IsVersionable(MethodDescHandle methodDesc) => FindMethodDesc(methodDesc.Address).IsVersionable; + TargetCodePointer IRuntimeTypeSystem.GetNativeCode(MethodDescHandle methodDesc) => FindMethodDesc(methodDesc.Address).NativeCode; + TargetPointer IRuntimeTypeSystem.GetMethodDescVersioningState(MethodDescHandle methodDesc) => FindMethodDesc(methodDesc.Address).MethodDescVersioningState; + + } + + internal class CodeVersionsBuilder + { + internal readonly MockMemorySpace.Builder Builder; + internal readonly Dictionary TypeInfoCache = new(); + + internal struct AllocationRange + { + public ulong CodeVersionsRangeStart; + public ulong CodeVersionsRangeEnd; } + + public static readonly AllocationRange DefaultAllocationRange = new AllocationRange() { + CodeVersionsRangeStart = 0x000f_c000, + CodeVersionsRangeEnd = 0x00010_0000, + }; + + private readonly MockMemorySpace.BumpAllocator _codeVersionsAllocator; + + public CodeVersionsBuilder(MockTarget.Architecture arch, AllocationRange allocationRange) : this(new MockMemorySpace.Builder(new TargetTestHelpers(arch)), allocationRange) + {} + public CodeVersionsBuilder(MockMemorySpace.Builder builder, AllocationRange allocationRange, Dictionary? typeInfoCache = null) + { + Builder = builder; + _codeVersionsAllocator = Builder.CreateAllocator(allocationRange.CodeVersionsRangeStart, allocationRange.CodeVersionsRangeEnd); + TypeInfoCache = typeInfoCache ?? CreateTypeInfoCache(Builder.TargetTestHelpers); + } + + internal static Dictionary CreateTypeInfoCache(TargetTestHelpers targetTestHelpers) + { + Dictionary typeInfoCache = new(); + AddToTypeInfoCache(targetTestHelpers, typeInfoCache); + return typeInfoCache; + } + + internal static void AddToTypeInfoCache(TargetTestHelpers targetTestHelpers, Dictionary typeInfoCache) + { + var layout = targetTestHelpers.LayoutFields([ + (nameof(Data.MethodDescVersioningState.NativeCodeVersionNode), DataType.pointer), + (nameof(Data.MethodDescVersioningState.Flags), DataType.uint8), + ]); + typeInfoCache[DataType.MethodDescVersioningState] = new Target.TypeInfo() { + Fields = layout.Fields, + Size = layout.Stride, + }; + } + + public void MarkCreated() => Builder.MarkCreated(); + + public TargetPointer AddMethodDescVersioningState(TargetPointer nativeCodeVersionNode) + { + 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); + return fragment.Address; + } + } + internal class CVTestTarget : TestPlaceholderTarget { - public CVTestTarget(MockTarget.Architecture arch) : base(arch) { + public CVTestTarget(MockTarget.Architecture arch, IReadOnlyCollection? methodDescs = null, + IReadOnlyCollection? codeBlocks = null, + ReadFromTargetDelegate reader = null, + Dictionary? typeInfoCache = null) : base(arch) { IContractFactory cvfactory = new CodeVersionsFactory(); - IExecutionManager mockExecutionManager = new MockExecutionManager(); + IExecutionManager mockExecutionManager = new MockExecutionManager(codeBlocks ?? []); + IRuntimeTypeSystem mockRuntimeTypeSystem = new MockRuntimeTypeSystem(methodDescs ?? []); + if (reader != null) + SetDataReader(reader); + if (typeInfoCache != null) + SetTypeInfoCache(typeInfoCache); + SetDataCache(new DefaultDataCache(this)); SetContracts(new TestRegistry() { CodeVersionsContract = new (() => cvfactory.CreateContract(this, 1)), ExecutionManagerContract = new (() => mockExecutionManager), + RuntimeTypeSystemContract = new (() => mockRuntimeTypeSystem), }); - } - - } [Theory] [ClassData(typeof(MockTarget.StdArch))] - public void TestCodeVersionNull(MockTarget.Architecture arch) + public void TestGetNativeCodeVersionNull(MockTarget.Architecture arch) { var target = new CVTestTarget(arch); var codeVersions = target.Contracts.CodeVersions; @@ -49,4 +229,71 @@ public void TestCodeVersionNull(MockTarget.Architecture arch) var handle = codeVersions.GetNativeCodeVersionForIP(nullPointer); Assert.False(handle.Valid); } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void TestGetNativeCodeVersionOneVersionNonVersionable(MockTarget.Architecture arch) + { + TargetCodePointer codeBlockStart = new TargetCodePointer(0x0a0a_0000); + MockMethodDesc oneMethod = MockMethodDesc.CreateNonVersionable(selfAddress: new TargetPointer(0x1a0a_0000), nativeCode: codeBlockStart); + MockCodeBlockStart oneBlock = new MockCodeBlockStart() + { + StartAddress = codeBlockStart, + Length = 0x100, + MethodDesc = oneMethod, + }; + + var target = new CVTestTarget(arch, [oneMethod], [oneBlock]); + var codeVersions = target.Contracts.CodeVersions; + + Assert.NotNull(codeVersions); + + TargetCodePointer codeBlockEnd = codeBlockStart + oneBlock.Length; + for (TargetCodePointer ip = codeBlockStart; ip < codeBlockEnd; ip++) + { + var handle = codeVersions.GetNativeCodeVersionForIP(ip); + Assert.True(handle.Valid); + // FIXME: do we want to lock this down? it's part of the algorithm details, but maybe not part of the contract + //Assert.Equal(oneBlock.MethodDescAddress, handle.MethodDescAddress); + TargetCodePointer actualCodeStart = codeVersions.GetNativeCode(handle); + Assert.Equal(codeBlockStart, actualCodeStart); + } + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void TestGetNativeCodeVersionOneVersionVersionable(MockTarget.Architecture arch) + { + var builder = new CodeVersionsBuilder(arch, CodeVersionsBuilder.DefaultAllocationRange); + TargetPointer methodDescVersioningStateAddress = builder.AddMethodDescVersioningState(TargetPointer.Null/*FIXME*/); + TargetCodePointer codeBlockStart = new TargetCodePointer(0x0a0a_0000); + MockMethodDesc oneMethod = MockMethodDesc.CreateVersionable(selfAddress: new TargetPointer(0x1a0a_0000), methodDescVersioningState: methodDescVersioningStateAddress); + MockCodeBlockStart oneBlock = new MockCodeBlockStart() + { + StartAddress = codeBlockStart, + Length = 0x100, + MethodDesc = oneMethod, + }; + + builder.MarkCreated(); + var target = new CVTestTarget(arch, [oneMethod], [oneBlock], builder.Builder.GetReadContext().ReadFromTarget, builder.TypeInfoCache); + + // TEST + + var codeVersions = target.Contracts.CodeVersions; + + Assert.NotNull(codeVersions); + + TargetCodePointer codeBlockEnd = codeBlockStart + oneBlock.Length; + for (TargetCodePointer ip = codeBlockStart; ip < codeBlockEnd; ip++) + { + var handle = codeVersions.GetNativeCodeVersionForIP(ip); + Assert.True(handle.Valid); + // FIXME: do we want to lock this down? it's part of the algorithm details, but maybe not part of the contract + //Assert.Equal(oneBlock.MethodDescAddress, handle.MethodDescAddress); + TargetCodePointer actualCodeStart = codeVersions.GetNativeCode(handle); + Assert.Equal(codeBlockStart, actualCodeStart); + } + } + } From 76a349e6384805d12a8ac58f1e6fd7e4d97587b8 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 18 Oct 2024 10:21:43 -0400 Subject: [PATCH 07/15] implement GetNativeCode for a NativeCodeVersionNode handle --- .../Contracts/CodeVersions_1.cs | 3 ++- 1 file changed, 2 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 82ec9f3c658edb..3c1f35cfcc53b4 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 @@ -88,7 +88,8 @@ TargetCodePointer ICodeVersions.GetNativeCode(NativeCodeVersionHandle codeVersio } else if (codeVersionHandle.CodeVersionNodeAddress != TargetPointer.Null) { - throw new NotImplementedException(); // TODO[cdac]: get native code from NativeCodeVersionNode + Data.NativeCodeVersionNode nativeCodeVersionNode = _target.ProcessedData.GetOrAdd(codeVersionHandle.CodeVersionNodeAddress); + return nativeCodeVersionNode.NativeCode; } else { From 2e4d142565f279e3ccfc25e8cecfc284b1ad53a7 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 18 Oct 2024 10:22:07 -0400 Subject: [PATCH 08/15] checkpoint: TestGetNativeCodeVersionOneVersionVersionable passes --- .../cdacreader/tests/CodeVersionsTests.cs | 62 ++++++++++++------- .../cdacreader/tests/TestPlaceholderTarget.cs | 7 ++- 2 files changed, 44 insertions(+), 25 deletions(-) diff --git a/src/native/managed/cdacreader/tests/CodeVersionsTests.cs b/src/native/managed/cdacreader/tests/CodeVersionsTests.cs index c9e6ba33f13cde..3b827c1029b3fe 100644 --- a/src/native/managed/cdacreader/tests/CodeVersionsTests.cs +++ b/src/native/managed/cdacreader/tests/CodeVersionsTests.cs @@ -62,9 +62,9 @@ public MockExecutionManager(IReadOnlyCollection codeBlocks) _codeBlocks = codeBlocks; } - CodeBlockHandle? IExecutionManager.GetCodeBlockHandle(TargetCodePointer ip) + private MockCodeBlockStart? FindCodeBlock(TargetPointer ip) { - if (ip == TargetCodePointer.Null) + if (ip == TargetPointer.Null) { return null; } @@ -72,35 +72,22 @@ public MockExecutionManager(IReadOnlyCollection codeBlocks) { if (block.Contains(ip)) { - return new CodeBlockHandle(ip.AsTargetPointer); + return block; } } return null; } - TargetCodePointer IExecutionManager.GetStartAddress(CodeBlockHandle codeInfoHandle) + CodeBlockHandle? IExecutionManager.GetCodeBlockHandle(TargetCodePointer ip) { - foreach (var block in _codeBlocks) - { - if (block.Contains(codeInfoHandle.Address)) - { - return block.StartAddress; - } - } - return TargetCodePointer.Null; + var block = FindCodeBlock(ip.AsTargetPointer); + if (block == null) + return null; + return new CodeBlockHandle(ip.AsTargetPointer); } - TargetPointer IExecutionManager.GetMethodDesc(CodeBlockHandle codeInfoHandle) - { - foreach (var block in _codeBlocks) - { - if (block.Contains(codeInfoHandle.Address)) - { - return block.MethodDescAddress; - } - } - return TargetPointer.Null; - } + TargetCodePointer IExecutionManager.GetStartAddress(CodeBlockHandle codeInfoHandle) => FindCodeBlock(codeInfoHandle.Address)?.StartAddress ?? TargetCodePointer.Null; + TargetPointer IExecutionManager.GetMethodDesc(CodeBlockHandle codeInfoHandle) => FindCodeBlock(codeInfoHandle.Address)?.MethodDescAddress ?? TargetPointer.Null; } internal class MockRuntimeTypeSystem : IRuntimeTypeSystem @@ -177,6 +164,15 @@ internal static void AddToTypeInfoCache(TargetTestHelpers targetTestHelpers, Dic 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), + ]); + typeInfoCache[DataType.NativeCodeVersionNode] = new Target.TypeInfo() { + Fields = layout.Fields, + Size = layout.Stride, + }; } public void MarkCreated() => Builder.MarkCreated(); @@ -191,6 +187,22 @@ public TargetPointer AddMethodDescVersioningState(TargetPointer nativeCodeVersio return fragment.Address; } + public TargetPointer AddNativeCodeVersionNode() + { + Target.TypeInfo info = TypeInfoCache[DataType.NativeCodeVersionNode]; + MockMemorySpace.HeapFragment fragment = _codeVersionsAllocator.Allocate((ulong)TypeInfoCache[DataType.NativeCodeVersionNode].Size, "NativeCodeVersionNode"); + Builder.AddHeapFragment(fragment); + return fragment.Address; + } + public void FillNativeCodeVersionNode(TargetPointer dest, TargetPointer methodDesc, TargetCodePointer nativeCode, TargetPointer next) + { + 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); + } + } internal class CVTestTarget : TestPlaceholderTarget @@ -265,7 +277,8 @@ public void TestGetNativeCodeVersionOneVersionNonVersionable(MockTarget.Architec public void TestGetNativeCodeVersionOneVersionVersionable(MockTarget.Architecture arch) { var builder = new CodeVersionsBuilder(arch, CodeVersionsBuilder.DefaultAllocationRange); - TargetPointer methodDescVersioningStateAddress = builder.AddMethodDescVersioningState(TargetPointer.Null/*FIXME*/); + TargetPointer nativeCodeVersionNode = builder.AddNativeCodeVersionNode(); + TargetPointer methodDescVersioningStateAddress = builder.AddMethodDescVersioningState(nativeCodeVersionNode); TargetCodePointer codeBlockStart = new TargetCodePointer(0x0a0a_0000); MockMethodDesc oneMethod = MockMethodDesc.CreateVersionable(selfAddress: new TargetPointer(0x1a0a_0000), methodDescVersioningState: methodDescVersioningStateAddress); MockCodeBlockStart oneBlock = new MockCodeBlockStart() @@ -274,6 +287,7 @@ public void TestGetNativeCodeVersionOneVersionVersionable(MockTarget.Architectur Length = 0x100, MethodDesc = oneMethod, }; + builder.FillNativeCodeVersionNode(nativeCodeVersionNode, methodDesc: oneMethod.Address, nativeCode: codeBlockStart, next: TargetPointer.Null); builder.MarkCreated(); var target = new CVTestTarget(arch, [oneMethod], [oneBlock], builder.Builder.GetReadContext().ReadFromTarget, builder.TypeInfoCache); diff --git a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs index 4432dbe848c3bf..0bc04c7da53b2e 100644 --- a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs +++ b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs @@ -63,7 +63,7 @@ public override bool IsAlignedToPointerSize(TargetPointer pointer) public override TargetPointer ReadGlobalPointer(string global) => throw new NotImplementedException(); public override TargetPointer ReadPointer(ulong address) => DefaultReadPointer(address); - public override TargetCodePointer ReadCodePointer(ulong address) => throw new NotImplementedException(); + public override TargetCodePointer ReadCodePointer(ulong address) => DefaultReadCodePointer(address); public override void ReadBuffer(ulong address, Span buffer) => throw new NotImplementedException(); public override string ReadUtf8String(ulong address) => throw new NotImplementedException(); public override string ReadUtf16String(ulong address) => throw new NotImplementedException(); @@ -176,6 +176,11 @@ protected TargetNUInt DefaultReadNUInt(ulong address) return new TargetNUInt(value); } + + protected TargetCodePointer DefaultReadCodePointer(ulong address) + { + return new TargetCodePointer(DefaultReadPointer(address)); + } #endregion subclass reader helpers public override TargetPointer ReadPointerFromSpan(ReadOnlySpan bytes) => throw new NotImplementedException(); From 814173f6b8283f9b0eada472a068f70dfd3641fb Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 18 Oct 2024 13:06:13 -0400 Subject: [PATCH 09/15] Add TestGetActiveNativeCodeVersionDefaultCase --- .../Contracts/ILoader.cs | 2 +- .../Data/ILCodeVersioningState.cs | 2 +- .../cdacreader/tests/CodeVersionsTests.cs | 200 +++++++++++++++++- 3 files changed, 193 insertions(+), 11 deletions(-) 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 f0113dc3a1b1c9..009981ba3938c9 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 @@ -47,7 +47,7 @@ internal interface ILoader : IContract public virtual TargetPointer GetILBase(ModuleHandle handle) => throw new NotImplementedException(); public virtual ModuleLookupTables GetLookupTables(ModuleHandle handle) => throw new NotImplementedException(); - public virtual TargetPointer GetModuleLookupMapElement(TargetPointer table, uint rid, out TargetNUInt flags) => throw new NotImplementedException(); + public virtual TargetPointer GetModuleLookupMapElement(TargetPointer table, uint token, out TargetNUInt flags) => throw new NotImplementedException(); } internal readonly struct Loader : ILoader diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ILCodeVersioningState.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ILCodeVersioningState.cs index 34256abe54569b..57fcb91165316f 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ILCodeVersioningState.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ILCodeVersioningState.cs @@ -19,7 +19,7 @@ public ILCodeVersioningState(Target target, TargetPointer address) ActiveVersionMethodDef = target.Read(address + (ulong)type.Fields[nameof(ActiveVersionMethodDef)].Offset); } - public TargetPointer Node { get; init; } + public TargetPointer Node { get; init; } // FIXME: rename to something like FirstVersionNode public uint ActiveVersionKind { get; set; } public TargetPointer ActiveVersionNode { get; set; } public TargetPointer ActiveVersionModule { get; set; } diff --git a/src/native/managed/cdacreader/tests/CodeVersionsTests.cs b/src/native/managed/cdacreader/tests/CodeVersionsTests.cs index 3b827c1029b3fe..c1caa254e1d812 100644 --- a/src/native/managed/cdacreader/tests/CodeVersionsTests.cs +++ b/src/native/managed/cdacreader/tests/CodeVersionsTests.cs @@ -11,17 +11,34 @@ namespace Microsoft.Diagnostics.DataContractReader.UnitTests; public class CodeVersionsTests { + internal class MockModule + { + public TargetPointer Address { get; set; } + public TargetPointer MethodDefToILCodeVersioningStateAddress { get; set; } + public Dictionary MethodDefToILCodeVersioningStateTable {get; set;} + } + internal class MockMethodTable + { + public TargetPointer Address { get; set; } + public MockModule? Module {get; set; } + } + internal class MockMethodDesc { public TargetPointer Address { get; private set; } public bool IsVersionable { get; private set; } - // only non-null if IsVersionable is false + public uint RowId { get; set; } + public uint MethodToken => 0x06000000 | RowId; + + // n.b. in the real RuntimeTypeSystem_1 this is more complex public TargetCodePointer NativeCode { get; private set; } // only non-null if IsVersionable is true public TargetPointer MethodDescVersioningState { get; private set; } + public MockMethodTable? MethodTable { get; set; } + public static MockMethodDesc CreateNonVersionable (TargetPointer selfAddress, TargetCodePointer nativeCode) { return new MockMethodDesc() { @@ -32,12 +49,12 @@ public static MockMethodDesc CreateNonVersionable (TargetPointer selfAddress, Ta }; } - public static MockMethodDesc CreateVersionable (TargetPointer selfAddress, TargetPointer methodDescVersioningState) + public static MockMethodDesc CreateVersionable (TargetPointer selfAddress, TargetPointer methodDescVersioningState, TargetCodePointer nativeCode = default) { return new MockMethodDesc() { Address = selfAddress, IsVersionable = true, - NativeCode = TargetCodePointer.Null, + NativeCode = nativeCode, MethodDescVersioningState = methodDescVersioningState, }; } @@ -92,10 +109,14 @@ public MockExecutionManager(IReadOnlyCollection codeBlocks) internal class MockRuntimeTypeSystem : IRuntimeTypeSystem { + private readonly Target _target; IReadOnlyCollection _methodDescs; - public MockRuntimeTypeSystem(IReadOnlyCollection methodDescs) + IReadOnlyCollection _methodTables; + public MockRuntimeTypeSystem(Target target, IReadOnlyCollection methodDescs, IReadOnlyCollection methodTables) { + _target = target; _methodDescs = methodDescs; + _methodTables = methodTables; } private MockMethodDesc? TryFindMethodDesc(TargetPointer targetPointer) @@ -110,7 +131,20 @@ public MockRuntimeTypeSystem(IReadOnlyCollection methodDescs) return null; } + private MockMethodTable? TryFindMethodTable(TargetPointer targetPointer) + { + foreach (var methodTable in _methodTables) + { + if (methodTable.Address == targetPointer) + { + return methodTable; + } + } + return null; + } + private MockMethodDesc FindMethodDesc(TargetPointer targetPointer) => TryFindMethodDesc(targetPointer) ?? throw new InvalidOperationException($"MethodDesc not found for {targetPointer}"); + private MockMethodTable FindMethodTable(TargetPointer targetPointer) => TryFindMethodTable(targetPointer) ?? throw new InvalidOperationException($"MethodTable not found for {targetPointer}"); MethodDescHandle IRuntimeTypeSystem.GetMethodDescHandle(TargetPointer targetPointer) => new MethodDescHandle(FindMethodDesc(targetPointer).Address); @@ -118,6 +152,77 @@ public MockRuntimeTypeSystem(IReadOnlyCollection methodDescs) TargetCodePointer IRuntimeTypeSystem.GetNativeCode(MethodDescHandle methodDesc) => FindMethodDesc(methodDesc.Address).NativeCode; TargetPointer IRuntimeTypeSystem.GetMethodDescVersioningState(MethodDescHandle methodDesc) => FindMethodDesc(methodDesc.Address).MethodDescVersioningState; + TargetPointer IRuntimeTypeSystem.GetMethodTable(MethodDescHandle methodDesc) => FindMethodDesc(methodDesc.Address).MethodTable?.Address ?? throw new InvalidOperationException($"MethodTable not found for {methodDesc.Address}"); + uint IRuntimeTypeSystem.GetMethodToken(MethodDescHandle methodDesc) => FindMethodDesc(methodDesc.Address).MethodToken; + + TypeHandle IRuntimeTypeSystem.GetTypeHandle(TargetPointer address) + { + ulong addressLowBits = (ulong)address & ((ulong)_target.PointerSize - 1); + + // no typedescs for now, just method tables with 0 in the low bits + if (addressLowBits != 0) + { + throw new InvalidOperationException("Invalid type handle pointer"); + } + MockMethodTable methodTable = FindMethodTable(address); + return new TypeHandle(methodTable.Address); + } + + TargetPointer IRuntimeTypeSystem.GetModule(TypeHandle typeHandle) => FindMethodTable(typeHandle.Address).Module?.Address ?? throw new InvalidOperationException($"Module not found for {typeHandle.Address}"); + } + + internal class MockLoader : ILoader + { + private readonly IReadOnlyCollection _modules; + public MockLoader(IReadOnlyCollection modules) + { + _modules = modules; + } + private MockModule? TryFindModule(TargetPointer targetPointer) + { + foreach (var module in _modules) + { + if (module.Address == targetPointer) + { + return module; + } + } + return null; + } + + private MockModule FindModule(TargetPointer targetPointer) => TryFindModule(targetPointer) ?? throw new InvalidOperationException($"Module not found for {targetPointer}"); + + Contracts.ModuleHandle ILoader.GetModuleHandle(TargetPointer modulePointer) => new Contracts.ModuleHandle(FindModule(modulePointer).Address); + + ModuleLookupTables ILoader.GetLookupTables(Contracts.ModuleHandle handle) + { + MockModule module = FindModule(handle.Address); + return new ModuleLookupTables() { + MethodDefToILCodeVersioningState = module.MethodDefToILCodeVersioningStateAddress, + }; + } + + TargetPointer ILoader.GetModuleLookupMapElement(TargetPointer tableAddress, uint token, out TargetNUInt flags) + { + flags = new TargetNUInt(0); + Dictionary? table = null; + foreach (var module in _modules) + { + if (module.MethodDefToILCodeVersioningStateTable != null && module.MethodDefToILCodeVersioningStateAddress == tableAddress) + { + table = module.MethodDefToILCodeVersioningStateTable; + } + } + if (table == null) { + throw new InvalidOperationException($"No table found with address {tableAddress} for token 0x{token:x}, {flags}"); + } + uint rowId = EcmaMetadataUtils.GetRowId(token); + if (table.TryGetValue(rowId, out TargetPointer value)) + { + return value; + } + throw new InvalidOperationException($"No token found for 0x{token:x} in table {tableAddress}"); + } } internal class CodeVersionsBuilder @@ -173,6 +278,17 @@ internal static void AddToTypeInfoCache(TargetTestHelpers targetTestHelpers, Dic Fields = layout.Fields, Size = layout.Stride, }; + layout = targetTestHelpers.LayoutFields([ + (nameof(Data.ILCodeVersioningState.ActiveVersionMethodDef), DataType.uint32), + (nameof(Data.ILCodeVersioningState.ActiveVersionModule), DataType.pointer), + (nameof(Data.ILCodeVersioningState.ActiveVersionKind), DataType.uint32), + (nameof(Data.ILCodeVersioningState.ActiveVersionNode), DataType.pointer), + (nameof(Data.ILCodeVersioningState.Node), DataType.pointer), + ]); + typeInfoCache[DataType.ILCodeVersioningState] = new Target.TypeInfo() { + Fields = layout.Fields, + Size = layout.Stride, + }; } public void MarkCreated() => Builder.MarkCreated(); @@ -203,26 +319,51 @@ public void FillNativeCodeVersionNode(TargetPointer dest, TargetPointer methodDe Builder.TargetTestHelpers.WritePointer(ncvn.Slice(info.Fields[nameof(Data.NativeCodeVersionNode.NativeCode)].Offset, Builder.TargetTestHelpers.PointerSize), nativeCode); } + public TargetPointer AddILCodeVersioningState(TargetPointer firstVersionNode, uint activeVersionKind, TargetPointer activeVersionNode, TargetPointer activeVersionModule, uint activeVersionMethodDef) + { + Target.TypeInfo info = TypeInfoCache[DataType.ILCodeVersioningState]; + MockMemorySpace.HeapFragment fragment = _codeVersionsAllocator.Allocate((ulong)TypeInfoCache[DataType.ILCodeVersioningState].Size, "ILCodeVersioningState"); + Builder.AddHeapFragment(fragment); + Span ilcvs = Builder.BorrowAddressRange(fragment.Address, fragment.Data.Length); + Builder.TargetTestHelpers.WritePointer(ilcvs.Slice(info.Fields[nameof(Data.ILCodeVersioningState.Node)].Offset, Builder.TargetTestHelpers.PointerSize), firstVersionNode); + Builder.TargetTestHelpers.WritePointer(ilcvs.Slice(info.Fields[nameof(Data.ILCodeVersioningState.ActiveVersionModule)].Offset, Builder.TargetTestHelpers.PointerSize), activeVersionModule); + Builder.TargetTestHelpers.WritePointer(ilcvs.Slice(info.Fields[nameof(Data.ILCodeVersioningState.ActiveVersionNode)].Offset, Builder.TargetTestHelpers.PointerSize), activeVersionNode); + Builder.TargetTestHelpers.Write(ilcvs.Slice(info.Fields[nameof(Data.ILCodeVersioningState.ActiveVersionMethodDef)].Offset, sizeof(uint)), activeVersionMethodDef); + Builder.TargetTestHelpers.Write(ilcvs.Slice(info.Fields[nameof(Data.ILCodeVersioningState.ActiveVersionKind)].Offset, sizeof(uint)), activeVersionKind); + return fragment.Address; + } + } internal class CVTestTarget : TestPlaceholderTarget { + public static CVTestTarget FromBuilder(MockTarget.Architecture arch, IReadOnlyCollection methodDescs, IReadOnlyCollection methodTables, IReadOnlyCollection codeBlocks, IReadOnlyCollection modules, CodeVersionsBuilder builder) + { + builder.MarkCreated(); + return new CVTestTarget(arch, reader: builder.Builder.GetReadContext().ReadFromTarget, typeInfoCache: builder.TypeInfoCache, + methodDescs: methodDescs, methodTables: methodTables, codeBlocks: codeBlocks, modules: modules); + } + public CVTestTarget(MockTarget.Architecture arch, IReadOnlyCollection? methodDescs = null, + IReadOnlyCollection? methodTables = null, IReadOnlyCollection? codeBlocks = null, + IReadOnlyCollection? modules = null, ReadFromTargetDelegate reader = null, Dictionary? typeInfoCache = null) : base(arch) { - IContractFactory cvfactory = new CodeVersionsFactory(); IExecutionManager mockExecutionManager = new MockExecutionManager(codeBlocks ?? []); - IRuntimeTypeSystem mockRuntimeTypeSystem = new MockRuntimeTypeSystem(methodDescs ?? []); + IRuntimeTypeSystem mockRuntimeTypeSystem = new MockRuntimeTypeSystem(this, methodDescs ?? [], methodTables ?? []); + ILoader loader = new MockLoader(modules ?? []); if (reader != null) SetDataReader(reader); if (typeInfoCache != null) SetTypeInfoCache(typeInfoCache); SetDataCache(new DefaultDataCache(this)); + IContractFactory cvfactory = new CodeVersionsFactory(); SetContracts(new TestRegistry() { CodeVersionsContract = new (() => cvfactory.CreateContract(this, 1)), ExecutionManagerContract = new (() => mockExecutionManager), RuntimeTypeSystemContract = new (() => mockRuntimeTypeSystem), + LoaderContract = new (() => loader), }); } } @@ -255,7 +396,7 @@ public void TestGetNativeCodeVersionOneVersionNonVersionable(MockTarget.Architec MethodDesc = oneMethod, }; - var target = new CVTestTarget(arch, [oneMethod], [oneBlock]); + var target = new CVTestTarget(arch, methodDescs: [oneMethod], codeBlocks: [oneBlock]); var codeVersions = target.Contracts.CodeVersions; Assert.NotNull(codeVersions); @@ -289,8 +430,7 @@ public void TestGetNativeCodeVersionOneVersionVersionable(MockTarget.Architectur }; builder.FillNativeCodeVersionNode(nativeCodeVersionNode, methodDesc: oneMethod.Address, nativeCode: codeBlockStart, next: TargetPointer.Null); - builder.MarkCreated(); - var target = new CVTestTarget(arch, [oneMethod], [oneBlock], builder.Builder.GetReadContext().ReadFromTarget, builder.TypeInfoCache); + var target = CVTestTarget.FromBuilder(arch, [oneMethod], [], [oneBlock], [], builder); // TEST @@ -310,4 +450,46 @@ public void TestGetNativeCodeVersionOneVersionVersionable(MockTarget.Architectur } } + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void TestGetActiveNativeCodeVersionDefaultCase(MockTarget.Architecture arch) + { + uint methodRowId = 0x25; // arbitrary + TargetCodePointer expectedNativeCodePointer = new TargetCodePointer(0x0700_abc0); + uint methodDefToken = 0x06000000 | methodRowId; + var builder = new CodeVersionsBuilder(arch, CodeVersionsBuilder.DefaultAllocationRange); + var methodDescAddress = new TargetPointer(0x00aa_aa00); + var moduleAddress = new TargetPointer(0x00ca_ca00); + + + TargetPointer versioningState = builder.AddILCodeVersioningState(firstVersionNode: TargetPointer.Null, activeVersionKind: 0, activeVersionNode: TargetPointer.Null, activeVersionModule: moduleAddress, activeVersionMethodDef: methodDefToken); + var oneModule = new MockModule() { + Address = moduleAddress, + MethodDefToILCodeVersioningStateAddress = new TargetPointer(0x00da_da00), + MethodDefToILCodeVersioningStateTable = new Dictionary() { + { methodRowId, versioningState} + }, + }; + var oneMethodTable = new MockMethodTable() { + Address = new TargetPointer(0x00ba_ba00), + Module = oneModule, + }; + var oneMethod = MockMethodDesc.CreateVersionable(selfAddress: methodDescAddress, methodDescVersioningState: TargetPointer.Null, nativeCode: expectedNativeCodePointer); + oneMethod.MethodTable = oneMethodTable; + oneMethod.RowId = methodRowId; + + var target = CVTestTarget.FromBuilder(arch, [oneMethod], [oneMethodTable], [], [oneModule], builder); + + // TEST + + var codeVersions = target.Contracts.CodeVersions; + + Assert.NotNull(codeVersions); + + var handle = codeVersions.GetActiveNativeCodeVersion(methodDescAddress); + Assert.True(handle.Valid); + var actualCodeAddress = codeVersions.GetNativeCode(handle); + Assert.Equal(expectedNativeCodePointer, actualCodeAddress); + } + } From 5dfba683b3d75bd5940dd545fdfeb8eba9659569 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 18 Oct 2024 13:30:43 -0400 Subject: [PATCH 10/15] don't add ILCodeVersioningState::Next field yet --- src/coreclr/debug/runtimeinfo/datadescriptor.h | 1 - src/coreclr/vm/codeversion.h | 1 - .../Data/ILCodeVersioningState.cs | 2 -- 3 files changed, 4 deletions(-) diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 673f8fed7bd2cf..3cc3b5efe6bc47 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -392,7 +392,6 @@ CDAC_TYPE_END(CodeHeapListNode) CDAC_TYPE_BEGIN(ILCodeVersioningState) CDAC_TYPE_INDETERMINATE(ILCodeVersioningState) -CDAC_TYPE_FIELD(ILCodeVersioningState, /*pointer*/, Node, cdac_data::Node) CDAC_TYPE_FIELD(ILCodeVersioningState, /*uint32*/, ActiveVersionKind, cdac_data::ActiveVersionKind) CDAC_TYPE_FIELD(ILCodeVersioningState, /*pointer*/, ActiveVersionNode, cdac_data::ActiveVersionNode) CDAC_TYPE_FIELD(ILCodeVersioningState, /*pointer*/, ActiveVersionModule, cdac_data::ActiveVersionModule) diff --git a/src/coreclr/vm/codeversion.h b/src/coreclr/vm/codeversion.h index f476063b4142c4..2cdb5b5982fd59 100644 --- a/src/coreclr/vm/codeversion.h +++ b/src/coreclr/vm/codeversion.h @@ -533,7 +533,6 @@ class ILCodeVersioningState template<> struct cdac_data { - static constexpr size_t Node = offsetof(ILCodeVersioningState, m_pFirstVersionNode); static constexpr size_t ActiveVersionKind = offsetof(ILCodeVersioningState, m_activeVersion.m_storageKind); static constexpr size_t ActiveVersionNode = offsetof(ILCodeVersioningState, m_activeVersion.m_pVersionNode); static constexpr size_t ActiveVersionModule = offsetof(ILCodeVersioningState, m_activeVersion.m_synthetic.m_pModule); diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ILCodeVersioningState.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ILCodeVersioningState.cs index 57fcb91165316f..92a214d910f6d2 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ILCodeVersioningState.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ILCodeVersioningState.cs @@ -12,14 +12,12 @@ public ILCodeVersioningState(Target target, TargetPointer address) { Target.TypeInfo type = target.GetTypeInfo(DataType.ILCodeVersioningState); - Node = target.ReadPointer(address + (ulong)type.Fields[nameof(Node)].Offset); ActiveVersionKind = target.Read(address + (ulong)type.Fields[nameof(ActiveVersionKind)].Offset); ActiveVersionNode = target.ReadPointer(address + (ulong)type.Fields[nameof(ActiveVersionNode)].Offset); ActiveVersionModule = target.ReadPointer(address + (ulong)type.Fields[nameof(ActiveVersionModule)].Offset); ActiveVersionMethodDef = target.Read(address + (ulong)type.Fields[nameof(ActiveVersionMethodDef)].Offset); } - public TargetPointer Node { get; init; } // FIXME: rename to something like FirstVersionNode public uint ActiveVersionKind { get; set; } public TargetPointer ActiveVersionNode { get; set; } public TargetPointer ActiveVersionModule { get; set; } From e01fd5d3c56ea87921f5653f1060e93ca6a4b62e Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 18 Oct 2024 13:31:18 -0400 Subject: [PATCH 11/15] describe Loader::GetModuleLookupMapElement contract --- docs/design/datacontracts/Loader.md | 32 ++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index e0f6018435b14b..8b96ed6c30cafa 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -38,7 +38,7 @@ TargetPointer GetLoaderAllocator(ModuleHandle handle); TargetPointer GetThunkHeap(ModuleHandle handle); TargetPointer GetILBase(ModuleHandle handle); ModuleLookupTables GetLookupTables(ModuleHandle handle); -TargetPointer GetModuleLookupMapElement(TargetPointer table, uint rid, out TargetNUInt flags); +TargetPointer GetModuleLookupMapElement(TargetPointer table, uint token, out TargetNUInt flags); ``` ## Version 1 @@ -113,6 +113,32 @@ ModuleLookupTables GetLookupTables(ModuleHandle handle) MethodDefToILCodeVersioningState: target.ReadPointer(handle.Address + /* Module::MethodDefToILCodeVersioningState */)); } -``` -**TODO* pseudocode for IsCollectibleLoaderAllocator and LookupTableMap element lookup +TargetPointer GetModuleLookupMapElement(TargetPointer table, uint token, out TargetNUInt flags); +{ + uint rid = /* get row id from token*/ (token); + flags = new TargetNUInt(0); + if (table == TargetPointer.Null) + return TargetPointer.Null; + uint index = rid; + // have to read lookupMap an extra time upfront because only the first map + // has valid supportedFlagsMask + TargetNUInt supportedFlagsMask = _target.ReadNUInt(table + /* ModuleLookupMap::SupportedFlagsMask */); + do + { + if (index < _target.Read(table + /*ModuleLookupMap::Count*/)) + { + TargetPointer entryAddress = _target.ReadPointer(lookupMap + /*ModuleLookupMap::TableData*/) + (ulong)(index * _target.PointerSize); + TargetPointer rawValue = _target.ReadPointer(entryAddress); + flags = rawValue & supportedFlagsMask; + return rawValue & ~(supportedFlagsMask.Value); + } + else + { + table = _target.ReadPointer(lookupMap + /*ModuleLookupMap::Next*/); + index -= _target.Read(lookupMap + /*ModuleLookupMap::Count*/); + } + } while (table != TargetPointer.Null); + return TargetPointer.Null; +} +``` From a95b3ed9c5b7ee37da453decf9b174b99b44d4f8 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 18 Oct 2024 13:45:55 -0400 Subject: [PATCH 12/15] remove unused member --- src/native/managed/cdacreader/tests/CodeVersionsTests.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/native/managed/cdacreader/tests/CodeVersionsTests.cs b/src/native/managed/cdacreader/tests/CodeVersionsTests.cs index c1caa254e1d812..59bd68f1d9fdce 100644 --- a/src/native/managed/cdacreader/tests/CodeVersionsTests.cs +++ b/src/native/managed/cdacreader/tests/CodeVersionsTests.cs @@ -283,7 +283,6 @@ internal static void AddToTypeInfoCache(TargetTestHelpers targetTestHelpers, Dic (nameof(Data.ILCodeVersioningState.ActiveVersionModule), DataType.pointer), (nameof(Data.ILCodeVersioningState.ActiveVersionKind), DataType.uint32), (nameof(Data.ILCodeVersioningState.ActiveVersionNode), DataType.pointer), - (nameof(Data.ILCodeVersioningState.Node), DataType.pointer), ]); typeInfoCache[DataType.ILCodeVersioningState] = new Target.TypeInfo() { Fields = layout.Fields, @@ -319,13 +318,12 @@ public void FillNativeCodeVersionNode(TargetPointer dest, TargetPointer methodDe Builder.TargetTestHelpers.WritePointer(ncvn.Slice(info.Fields[nameof(Data.NativeCodeVersionNode.NativeCode)].Offset, Builder.TargetTestHelpers.PointerSize), nativeCode); } - public TargetPointer AddILCodeVersioningState(TargetPointer firstVersionNode, uint activeVersionKind, TargetPointer activeVersionNode, TargetPointer activeVersionModule, uint activeVersionMethodDef) + public TargetPointer AddILCodeVersioningState(uint activeVersionKind, TargetPointer activeVersionNode, TargetPointer activeVersionModule, uint activeVersionMethodDef) { Target.TypeInfo info = TypeInfoCache[DataType.ILCodeVersioningState]; MockMemorySpace.HeapFragment fragment = _codeVersionsAllocator.Allocate((ulong)TypeInfoCache[DataType.ILCodeVersioningState].Size, "ILCodeVersioningState"); Builder.AddHeapFragment(fragment); Span ilcvs = Builder.BorrowAddressRange(fragment.Address, fragment.Data.Length); - Builder.TargetTestHelpers.WritePointer(ilcvs.Slice(info.Fields[nameof(Data.ILCodeVersioningState.Node)].Offset, Builder.TargetTestHelpers.PointerSize), firstVersionNode); Builder.TargetTestHelpers.WritePointer(ilcvs.Slice(info.Fields[nameof(Data.ILCodeVersioningState.ActiveVersionModule)].Offset, Builder.TargetTestHelpers.PointerSize), activeVersionModule); Builder.TargetTestHelpers.WritePointer(ilcvs.Slice(info.Fields[nameof(Data.ILCodeVersioningState.ActiveVersionNode)].Offset, Builder.TargetTestHelpers.PointerSize), activeVersionNode); Builder.TargetTestHelpers.Write(ilcvs.Slice(info.Fields[nameof(Data.ILCodeVersioningState.ActiveVersionMethodDef)].Offset, sizeof(uint)), activeVersionMethodDef); @@ -462,7 +460,7 @@ public void TestGetActiveNativeCodeVersionDefaultCase(MockTarget.Architecture ar var moduleAddress = new TargetPointer(0x00ca_ca00); - TargetPointer versioningState = builder.AddILCodeVersioningState(firstVersionNode: TargetPointer.Null, activeVersionKind: 0, activeVersionNode: TargetPointer.Null, activeVersionModule: moduleAddress, activeVersionMethodDef: methodDefToken); + TargetPointer versioningState = builder.AddILCodeVersioningState(activeVersionKind: 0/*==unknown*/, activeVersionNode: TargetPointer.Null, activeVersionModule: moduleAddress, activeVersionMethodDef: methodDefToken); var oneModule = new MockModule() { Address = moduleAddress, MethodDefToILCodeVersioningStateAddress = new TargetPointer(0x00da_da00), From b9ad92cb39593a1cdce293cfe8abf963352c943e Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 18 Oct 2024 13:59:25 -0400 Subject: [PATCH 13/15] update contract markdown --- docs/design/datacontracts/CodeVersions.md | 260 +++++++++++++++------- 1 file changed, 183 insertions(+), 77 deletions(-) diff --git a/docs/design/datacontracts/CodeVersions.md b/docs/design/datacontracts/CodeVersions.md index efd838181cbbba..e9d06500ff6ee4 100644 --- a/docs/design/datacontracts/CodeVersions.md +++ b/docs/design/datacontracts/CodeVersions.md @@ -26,31 +26,54 @@ internal struct NativeCodeVersionHandle ``` ```csharp - // Return a handle to the version of the native code that includes the given instruction pointer - public virtual NativeCodeVersionHandle GetNativeCodeVersionForIP(TargetCodePointer ip); - // Return a handle to the active version of the native code for a given method descriptor - public virtual NativeCodeVersionHandle GetActiveNativeCodeVersion(TargetPointer methodDesc); +// Return a handle to the version of the native code that includes the given instruction pointer +public virtual NativeCodeVersionHandle GetNativeCodeVersionForIP(TargetCodePointer ip); +// Return a handle to the active version of the native code for a given method descriptor +public virtual NativeCodeVersionHandle GetActiveNativeCodeVersion(TargetPointer methodDesc); - // returns true if the given method descriptor supports multiple code versions - public virtual bool CodeVersionManagerSupportsMethod(TargetPointer methodDesc); +// returns true if the given method descriptor supports multiple code versions +public virtual bool CodeVersionManagerSupportsMethod(TargetPointer methodDesc); - // Return the instruction pointer corresponding to the start of the given native code version - public virtual TargetCodePointer GetNativeCode(NativeCodeVersionHandle codeVersionHandle); +// Return the instruction pointer corresponding to the start of the given native code version +public virtual TargetCodePointer GetNativeCode(NativeCodeVersionHandle codeVersionHandle); ``` ## Version 1 +See [code versioning](../features/code-versioning.md) for a general overview and the definitions of *synthetic* and *explicit* nodes. + Data descriptors used: | Data Descriptor Name | Field | Meaning | | --- | --- | --- | -| MethodDescVersioningState | ? | ? | -| NativeCodeVersionNode | ? | ? | -| ILCodeVersioningState | ? | ? | +| MethodDescVersioningState | Flags | `MethodDescVersioningStateFlags` flags, see below | +| MethodDescVersioningState | NativeCodeVersionNode | code version node of this method desc, if active | +| 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 | +| 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 | + +The flag indicates that the default version of the code for a method desc is active: +```csharp +internal enum MethodDescVersioningStateFlags : byte +{ + IsDefaultVersionActiveChildFlag = 0x4 +}; +``` +The value of the `ILCodeVersioningState::ActiveVersionKind` field is one of: +```csharp +private enum ILCodeVersionKind +{ + Unknown = 0, + Explicit = 1, // means Node is set + Synthetic = 2, // means Module and Token are set +} +``` -Global variables used: -| Global Name | Type | Purpose | -| --- | --- | --- | +Global variables used: *none* Contracts used: | Contract Name | @@ -62,88 +85,171 @@ Contracts used: ### Finding the start of a specific native code version ```csharp - NativeCodeVersionHandle GetNativeCodeVersionForIP(TargetCodePointer ip) +NativeCodeVersionHandle ICodeVersions.GetNativeCodeVersionForIP(TargetCodePointer ip) +{ + Contracts.IExecutionManager executionManager = _target.Contracts.ExecutionManager; + EECodeInfoHandle? info = executionManager.GetEECodeInfoHandle(ip); + if (!info.HasValue) { - Contracts.IExecutionManager executionManager = _target.Contracts.ExecutionManager; - EECodeInfoHandle? info = executionManager.GetEECodeInfoHandle(ip); - if (!info.HasValue) - { - return NativeCodeVersionHandle.Invalid; - } - TargetPointer methodDescAddress = executionManager.GetMethodDesc(info.Value); - if (methodDescAddress == TargetPointer.Null) - { - return NativeCodeVersionHandle.Invalid; - } - IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; - MethodDescHandle md = rts.GetMethodDescHandle(methodDescAddress); - if (!rts.IsVersionable(md)) - { - return new NativeCodeVersionHandle(methodDescAddress, codeVersionNodeAddress: TargetPointer.Null); - } - else - { - TargetCodePointer startAddress = executionManager.GetStartAddress(info.Value); - return GetSpecificNativeCodeVersion(md, startAddress); - } + return NativeCodeVersionHandle.Invalid; } + TargetPointer methodDescAddress = executionManager.GetMethodDesc(info.Value); + if (methodDescAddress == TargetPointer.Null) + { + return NativeCodeVersionHandle.Invalid; + } + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + MethodDescHandle md = rts.GetMethodDescHandle(methodDescAddress); + if (!rts.IsVersionable(md)) + { + return new NativeCodeVersionHandle(methodDescAddress, codeVersionNodeAddress: TargetPointer.Null); + } + else + { + TargetCodePointer startAddress = executionManager.GetStartAddress(info.Value); + return GetSpecificNativeCodeVersion(md, startAddress); + } +} - private NativeCodeVersionHandle GetSpecificNativeCodeVersion(MethodDescHandle md, TargetCodePointer startAddress) +NativeCodeVersionHandle GetSpecificNativeCodeVersion(MethodDescHandle md, TargetCodePointer startAddress) +{ + TargetPointer methodDescVersioningStateAddress = target.Contracts.RuntimeTypeSystem.GetMethodDescVersioningState(md); + if (methodDescVersioningStateAddress == TargetPointer.Null) { - TargetPointer methodDescVersioningStateAddress = target.Contracts.RuntimeTypeSystem.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 codeVersion.MethodDesc == md.Address && codeVersion.NativeCode == startAddress; - }); + return NativeCodeVersionHandle.Invalid; } + Data.MethodDescVersioningState methodDescVersioningStateData = _target.ProcessedData.GetOrAdd(methodDescVersioningStateAddress); + return FindFirstCodeVersion(methodDescVersioningStateData, (codeVersion) => + { + return codeVersion.MethodDesc == md.Address && codeVersion.NativeCode == startAddress; + }); +} - private NativeCodeVersionHandle FindFirstCodeVersion(Data.MethodDescVersioningState versioningState, Func predicate) +NativeCodeVersionHandle FindFirstCodeVersion(Data.MethodDescVersioningState versioningState, Func predicate) +{ + TargetPointer currentAddress = versioningState.NativeCodeVersionNode; + while (currentAddress != TargetPointer.Null) { - // NativeCodeVersion::Next, heavily inlined - TargetPointer currentAddress = versioningState.NativeCodeVersionNode; - while (currentAddress != TargetPointer.Null) + Data.NativeCodeVersionNode current = _target.ProcessedData.GetOrAdd(currentAddress); + if (predicate(current)) { - Data.NativeCodeVersionNode current = _target.ProcessedData.GetOrAdd(currentAddress); - if (predicate(current)) - { - return new NativeCodeVersionHandle(methodDescAddress: TargetPointer.Null, currentAddress); - } - currentAddress = current.Next; + return new NativeCodeVersionHandle(methodDescAddress: TargetPointer.Null, currentAddress); } - return NativeCodeVersionHandle.Invalid; + currentAddress = current.Next; } + return NativeCodeVersionHandle.Invalid; +} ``` ### Finding the active native code version of a method descriptor ```csharp - NativeCodeVersionHandle ICodeVersions.GetActiveNativeCodeVersion(TargetPointer methodDesc) - { - // CodeVersionManager::GetActiveILCodeVersion - // then ILCodeVersion::GetActiveNativeCodeVersion - IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; - MethodDescHandle md = rts.GetMethodDescHandle(methodDesc); - TargetPointer mtAddr = rts.GetMethodTable(md); - TypeHandle typeHandle = rts.GetTypeHandle(mtAddr); - TargetPointer module = rts.GetModule(typeHandle); - uint methodDefToken = rts.GetMethodToken(md); - ILCodeVersionHandle methodDefActiveVersion = FindActiveILCodeVersion(module, methodDefToken); - if (!methodDefActiveVersion.IsValid) +NativeCodeVersionHandle ICodeVersions.GetActiveNativeCodeVersion(TargetPointer methodDesc) +{ + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + MethodDescHandle md = rts.GetMethodDescHandle(methodDesc); + TargetPointer mtAddr = rts.GetMethodTable(md); + TypeHandle typeHandle = rts.GetTypeHandle(mtAddr); + TargetPointer module = rts.GetModule(typeHandle); + uint methodDefToken = rts.GetMethodToken(md); + ILCodeVersionHandle methodDefActiveVersion = FindActiveILCodeVersion(module, methodDefToken); + if (!methodDefActiveVersion.IsValid) + { + return NativeCodeVersionHandle.Invalid; + } + return FindActiveNativeCodeVersion(methodDefActiveVersion, methodDesc); +} + +ILCodeVersionHandle ILCodeVersionHandleFromState(Data.ILCodeVersioningState ilState) +{ + switch ((ILCodeVersionKind)ilState.ActiveVersionKind) + { + case ILCodeVersionKind.Explicit: + return new ILCodeVersionHandle(module: TargetPointer.Null, methodDef: 0, ilState.ActiveVersionNode); + case ILCodeVersionKind.Synthetic: + case ILCodeVersionKind.Unknown: + return new ILCodeVersionHandle(ilState.ActiveVersionModule, ilState.ActiveVersionMethodDef, TargetPointer.Null); + default: + throw new InvalidOperationException($"Unknown ILCodeVersionKind {ilState.ActiveVersionKind}"); + } +} + +ILCodeVersionHandle FindActiveILCodeVersion(TargetPointer module, uint methodDefinition) +{ + 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 _); + if (ilVersionStateAddress == TargetPointer.Null) + { + return new ILCodeVersionHandle(module, methodDefinition, TargetPointer.Null); + } + Data.ILCodeVersioningState ilState = _target.ProcessedData.GetOrAdd(ilVersionStateAddress); + return ILCodeVersionHandleFromState(ilState); +} + +bool IsActiveNativeCodeVersion(NativeCodeVersionHandle nativeCodeVersion) +{ + if (nativeCodeVersion.MethodDescAddress != TargetPointer.Null) + { + MethodDescHandle md = _target.Contracts.RuntimeTypeSystem.GetMethodDescHandle(nativeCodeVersion.MethodDescAddress); + TargetPointer versioningStateAddress = _target.Contracts.RuntimeTypeSystem.GetMethodDescVersioningState(md); + if (versioningStateAddress == TargetPointer.Null) { - return NativeCodeVersionHandle.Invalid; + return true; } - return FindActiveNativeCodeVersion(methodDefActiveVersion, methodDesc); + Data.MethodDescVersioningState versioningState = _target.ProcessedData.GetOrAdd(versioningStateAddress); + return (((MethodDescVersioningStateFlags)versioningState.Flags) & MethodDescVersioningStateFlags.IsDefaultVersionActiveChildFlag) != 0; } -``` + else if (nativeCodeVersion.CodeVersionNodeAddress != TargetPointer.Null) + { + throw new NotImplementedException(); // TODO[cdac]: IsActiveNativeCodeVersion - explicit + } + else + { + throw new ArgumentException("Invalid NativeCodeVersionHandle"); + } +} -**FIXME** +NativeCodeVersionHandle FindActiveNativeCodeVersion(ILCodeVersionHandle methodDefActiveVersion, TargetPointer methodDescAddress) +{ + if (methodDefActiveVersion.Module != TargetPointer.Null) + { + NativeCodeVersionHandle provisionalHandle = new NativeCodeVersionHandle(methodDescAddress: methodDescAddress, codeVersionNodeAddress: TargetPointer.Null); + if (IsActiveNativeCodeVersion(provisionalHandle)) + { + return provisionalHandle; + } + else + { + throw new NotImplementedException(); // TODO[cdac]: iterate through versioning state nodes + } + } + else + { + throw new NotImplementedException(); // TODO: [cdac] find explicit il code version + } +} +``` ### Determining whether a method descriptor supports code versioning -**TODO** +```csharp +bool ICodeVersions.CodeVersionManagerSupportsMethod(TargetPointer methodDescAddress) +{ + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + MethodDescHandle md = rts.GetMethodDescHandle(methodDescAddress); + if (rts.IsDynamicMethod(md)) + return false; + if (rts.IsCollectibleMethod(md)) + return false; + TargetPointer mtAddr = rts.GetMethodTable(md); + TypeHandle mt = rts.GetTypeHandle(mtAddr); + TargetPointer modAddr = rts.GetModule(mt); + ILoader loader = _target.Contracts.Loader; + ModuleHandle mod = loader.GetModuleHandle(modAddr); + ModuleFlags modFlags = loader.GetFlags(mod); + if (modFlags.HasFlag(ModuleFlags.EditAndContinue)) + return false; + return true; +} +``` From ab383ea0fd6960d9fa294dc7de53d12a2de9875b Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 18 Oct 2024 14:07:39 -0400 Subject: [PATCH 14/15] simplify flags handling a bit --- docs/design/datacontracts/CodeVersions.md | 3 ++- .../Contracts/CodeVersions_1.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/design/datacontracts/CodeVersions.md b/docs/design/datacontracts/CodeVersions.md index e9d06500ff6ee4..d2e73b7541153b 100644 --- a/docs/design/datacontracts/CodeVersions.md +++ b/docs/design/datacontracts/CodeVersions.md @@ -198,7 +198,8 @@ bool IsActiveNativeCodeVersion(NativeCodeVersionHandle nativeCodeVersion) return true; } Data.MethodDescVersioningState versioningState = _target.ProcessedData.GetOrAdd(versioningStateAddress); - return (((MethodDescVersioningStateFlags)versioningState.Flags) & MethodDescVersioningStateFlags.IsDefaultVersionActiveChildFlag) != 0; + MethodDescVersioningStateFlags flags = (MethodDescVersioningStateFlags)versioningState.Flags; + return flags.HasFlag(MethodDescVersioningStateFlags.IsDefaultVersionActiveChildFlag); } else if (nativeCodeVersion.CodeVersionNodeAddress != TargetPointer.Null) { 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 3c1f35cfcc53b4..b9f16764392fe9 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 @@ -206,7 +206,8 @@ private bool IsActiveNativeCodeVersion(NativeCodeVersionHandle nativeCodeVersion return true; } Data.MethodDescVersioningState versioningState = _target.ProcessedData.GetOrAdd(versioningStateAddress); - return (((MethodDescVersioningStateFlags)versioningState.Flags) & MethodDescVersioningStateFlags.IsDefaultVersionActiveChildFlag) != 0; + MethodDescVersioningStateFlags flags = (MethodDescVersioningStateFlags)versioningState.Flags; + return flags.HasFlag(MethodDescVersioningStateFlags.IsDefaultVersionActiveChildFlag); } else if (nativeCodeVersion.CodeVersionNodeAddress != TargetPointer.Null) { From 7d299128dcd89b901101414e809cb6e44603f24a Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 18 Oct 2024 14:29:46 -0400 Subject: [PATCH 15/15] add TODOs for RuntimeTypeSystem additions --- .../design/datacontracts/RuntimeTypeSystem.md | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index 98c7644d8771b1..4badc1291c3c1a 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -844,4 +844,25 @@ And the various apis are implemented with the following algorithms return ((DynamicMethodDescExtendedFlags)ExtendedFlags).HasFlag(DynamicMethodDescExtendedFlags.IsILStub); } ``` -**TODO(cdac)** additional code pointers methods on MethodDesc + +Determining if a method is in a collectible module: + +```csharp +bool IRuntimeTypeSystem.IsCollectibleMethod(MethodDescHandle methodDesc) => // TODO[cdac]: finish this +``` + +Determining if a method supports multiple code versions: + +```csharp +bool IRuntimeTypeSystem.IsVersionable(MethodDescHandle methodDesc) => // TODO[cdac]: finish this +``` + +Extracting a pointer to the `MethodDescVersioningState` data for a given method +```csharp +TargetPointer IRuntimeTypeSystem.GetMethodDescVersioningState(MethodDescHandle methodDesc) => // TODO[cdac]: finish this +``` + +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 +```