diff --git a/docs/design/datacontracts/CodeVersions.md b/docs/design/datacontracts/CodeVersions.md new file mode 100644 index 00000000000000..d2e73b7541153b --- /dev/null +++ b/docs/design/datacontracts/CodeVersions.md @@ -0,0 +1,256 @@ +# 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 + +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 | 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: *none* + +Contracts used: +| Contract Name | +| --- | +| ExecutionManager | +| Loader | +| RuntimeTypeSystem | + +### Finding the start of a specific native code version + +```csharp +NativeCodeVersionHandle ICodeVersions.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); + } +} + +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); + return FindFirstCodeVersion(methodDescVersioningStateData, (codeVersion) => + { + return codeVersion.MethodDesc == md.Address && codeVersion.NativeCode == startAddress; + }); +} + +NativeCodeVersionHandle FindFirstCodeVersion(Data.MethodDescVersioningState versioningState, Func predicate) +{ + 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) +{ + 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 true; + } + Data.MethodDescVersioningState versioningState = _target.ProcessedData.GetOrAdd(versioningStateAddress); + MethodDescVersioningStateFlags flags = (MethodDescVersioningStateFlags)versioningState.Flags; + return flags.HasFlag(MethodDescVersioningStateFlags.IsDefaultVersionActiveChildFlag); + } + else if (nativeCodeVersion.CodeVersionNodeAddress != TargetPointer.Null) + { + throw new NotImplementedException(); // TODO[cdac]: IsActiveNativeCodeVersion - explicit + } + else + { + throw new ArgumentException("Invalid NativeCodeVersionHandle"); + } +} + +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 + +```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; +} +``` diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index d0fe390b5ad39f..8b96ed6c30cafa 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 token, 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) @@ -109,4 +113,32 @@ ModuleLookupTables GetLookupTables(ModuleHandle handle) MethodDefToILCodeVersioningState: target.ReadPointer(handle.Address + /* Module::MethodDefToILCodeVersioningState */)); } + +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; +} ``` diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index 8accd2e6dc68fc..4badc1291c3c1a 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,25 @@ And the various apis are implemented with the following algorithms return ((DynamicMethodDescExtendedFlags)ExtendedFlags).HasFlag(DynamicMethodDescExtendedFlags.IsILStub); } ``` -**TODO(cdac)** + +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 +``` 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..3cc3b5efe6bc47 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,20 @@ 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, /*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..2cdb5b5982fd59 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 + friend struct ::cdac_data; +}; class NativeCodeVersionNode { @@ -316,6 +318,16 @@ class NativeCodeVersionNode IsActiveChildFlag = 1 }; DWORD m_flags; + + 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; + + 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,17 @@ class ILCodeVersioningState PTR_ILCodeVersionNode m_pFirstVersionNode; PTR_Module m_pModule; mdMethodDef m_methodDef; + + friend struct ::cdac_data; +}; + +template<> +struct cdac_data +{ + 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..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 @@ -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 token, 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..5b184a44ad0230 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -56,5 +56,7 @@ public enum DataType RangeSection, RealCodeHeader, CodeHeapListNode, - + MethodDescVersioningState, + ILCodeVersioningState, + NativeCodeVersionNode, } 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..b9f16764392fe9 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CodeVersions_1.cs @@ -0,0 +1,245 @@ +// 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) + { + Data.NativeCodeVersionNode nativeCodeVersionNode = _target.ProcessedData.GetOrAdd(codeVersionHandle.CodeVersionNodeAddress); + return nativeCodeVersionNode.NativeCode; + } + 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); + MethodDescVersioningStateFlags flags = (MethodDescVersioningStateFlags)versioningState.Flags; + return flags.HasFlag(MethodDescVersioningStateFlags.IsDefaultVersionActiveChildFlag); + } + 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..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 @@ -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 = EcmaMetadataUtils.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..92a214d910f6d2 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ILCodeVersioningState.cs @@ -0,0 +1,25 @@ +// 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); + + 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 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.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); + +} 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/CodeVersionsTests.cs b/src/native/managed/cdacreader/tests/CodeVersionsTests.cs new file mode 100644 index 00000000000000..59bd68f1d9fdce --- /dev/null +++ b/src/native/managed/cdacreader/tests/CodeVersionsTests.cs @@ -0,0 +1,493 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Xunit; + +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; } + + 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() { + Address = selfAddress, + IsVersionable = false, + NativeCode = nativeCode, + MethodDescVersioningState = TargetPointer.Null, + }; + } + + public static MockMethodDesc CreateVersionable (TargetPointer selfAddress, TargetPointer methodDescVersioningState, TargetCodePointer nativeCode = default) + { + return new MockMethodDesc() { + Address = selfAddress, + IsVersionable = true, + NativeCode = nativeCode, + 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; + } + + private MockCodeBlockStart? FindCodeBlock(TargetPointer ip) + { + if (ip == TargetPointer.Null) + { + return null; + } + foreach (var block in _codeBlocks) + { + if (block.Contains(ip)) + { + return block; + } + } + return null; + } + + CodeBlockHandle? IExecutionManager.GetCodeBlockHandle(TargetCodePointer ip) + { + var block = FindCodeBlock(ip.AsTargetPointer); + if (block == null) + return null; + return new CodeBlockHandle(ip.AsTargetPointer); + } + + 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 + { + private readonly Target _target; + IReadOnlyCollection _methodDescs; + IReadOnlyCollection _methodTables; + public MockRuntimeTypeSystem(Target target, IReadOnlyCollection methodDescs, IReadOnlyCollection methodTables) + { + _target = target; + _methodDescs = methodDescs; + _methodTables = methodTables; + } + + private MockMethodDesc? TryFindMethodDesc(TargetPointer targetPointer) + { + foreach (var methodDesc in _methodDescs) + { + if (methodDesc.Address == targetPointer) + { + return methodDesc; + } + } + 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); + + 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; + + 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 + { + 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, + }; + 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, + }; + 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), + ]); + typeInfoCache[DataType.ILCodeVersioningState] = 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; + } + + 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); + } + + 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.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) { + IExecutionManager mockExecutionManager = new MockExecutionManager(codeBlocks ?? []); + 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), + }); + } + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void TestGetNativeCodeVersionNull(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); + } + + [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, methodDescs: [oneMethod], codeBlocks: [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 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() + { + StartAddress = codeBlockStart, + Length = 0x100, + MethodDesc = oneMethod, + }; + builder.FillNativeCodeVersionNode(nativeCodeVersionNode, methodDesc: oneMethod.Address, nativeCode: codeBlockStart, next: TargetPointer.Null); + + var target = CVTestTarget.FromBuilder(arch, [oneMethod], [], [oneBlock], [], builder); + + // 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); + } + } + + [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(activeVersionKind: 0/*==unknown*/, 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); + } + +} diff --git a/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs b/src/native/managed/cdacreader/tests/TestPlaceholderTarget.cs index afabf8a914b594..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(); @@ -205,6 +210,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 +220,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,