diff --git a/docs/design/datacontracts/CodeVersions.md b/docs/design/datacontracts/CodeVersions.md index 27f0814c37dd34..c903bd84007cbc 100644 --- a/docs/design/datacontracts/CodeVersions.md +++ b/docs/design/datacontracts/CodeVersions.md @@ -40,6 +40,9 @@ 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); + +// Gets the GCStressCodeCopy pointer if available, otherwise returns TargetPointer.Null +public virtual TargetPointer GetGCStressCodeCopy(NativeCodeVersionHandle codeVersionHandle); ``` ### Extension Methods ```csharp @@ -61,6 +64,7 @@ Data descriptors used: | NativeCodeVersionNode | NativeCode | indicates an explicit native code version node | | NativeCodeVersionNode | Flags | `NativeCodeVersionNodeFlags` flags, see below | | NativeCodeVersionNode | VersionId | Version ID corresponding to the parent IL code version | +| NativeCodeVersionNode | GCCoverageInfo | GCStress debug info, if supported | | ILCodeVersioningState | FirstVersionNode | pointer to the first `ILCodeVersionNode` | | 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 | @@ -68,6 +72,7 @@ Data descriptors used: | ILCodeVersioningState | ActiveVersionMethodDef | if the active version is synthetic or unknown, the MethodDef token for the method | | ILCodeVersionNode | VersionId | Version ID of the node | | ILCodeVersionNode | Next | Pointer to the next `ILCodeVersionNode`| +| GCCoverageInfo | SavedCode | Pointer to the GCCover saved code copy, if supported | The flag indicates that the default version of the code for a method desc is active: ```csharp @@ -249,3 +254,11 @@ bool ICodeVersions.CodeVersionManagerSupportsMethod(TargetPointer methodDescAddr return true; } ``` + +### Finding GCStress Code Copy +```csharp +public virtual TargetPointer GetGCStressCodeCopy(NativeCodeVersionHandle codeVersionHandle); +``` + +1. If `codeVersionHandle` is synthetic, use the `IRuntimeTypeSystem` to find the GCStressCodeCopy. +2. If `codeVersionHandle` is explicit, read the `NativeCodeVersionNode` for the `GCCoverageInfo` pointer. This value only exists in some builds. If the value doesn't exist or is a nullptr, return `TargetPointer.Null`. Otherwise return the `SavedCode` pointer from the `GCCoverageInfo` struct. diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index 84726124483c73..b1d65b7c825273 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -159,6 +159,8 @@ partial interface IRuntimeTypeSystem : IContract // Get an instruction pointer that can be called to cause the MethodDesc to be executed public virtual TargetCodePointer GetNativeCode(MethodDescHandle methodDesc); + // Gets the GCStressCodeCopy pointer if available, otherwise returns TargetPointer.Null + public virtual TargetPointer GetGCStressCodeCopy(MethodDescHandle methodDesc); } ``` @@ -638,6 +640,7 @@ We depend on the following data descriptors: | `MethodDesc` | `Slot` | The method's slot | | `MethodDesc` | `Flags` | The method's flags | | `MethodDesc` | `Flags3AndTokenRemainder` | More flags for the method, and the low bits of the method's token's RID | +| `MethodDesc` | `GCCoverageInfo` | The method's GCCover debug info, if supported | | `MethodDescCodeData` | `VersioningState` | The IL versioning state associated with a method descriptor | `MethodDescChunk` | `MethodTable` | The method table set of methods belongs to | | `MethodDescChunk` | `Next` | The next chunk of methods | @@ -654,6 +657,7 @@ We depend on the following data descriptors: | `StoredSigMethodDesc` | `cSig` | Count of bytes in the metadata signature | | `StoredSigMethodDesc` | `ExtendedFlags` | Flags field for the `StoredSigMethodDesc` | | `DynamicMethodDesc` | `MethodName` | Pointer to Null-terminated UTF8 string describing the Method desc | +| `GCCoverageInfo` | `SavedCode` | Pointer to the GCCover saved code copy, if supported | The contract depends on the following other contracts diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.cpp b/src/coreclr/debug/runtimeinfo/datadescriptor.cpp index a6443a730985e0..72a1ee18304578 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.cpp +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.cpp @@ -13,6 +13,10 @@ #include "methodtable.h" #include "threads.h" +#ifdef HAVE_GCCOVER +#include "gccover.h" +#endif // HAVE_GCCOVER + // begin blob definition extern "C" diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index 424a1a764039f6..e58d91bd6cab3a 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -331,6 +331,9 @@ CDAC_TYPE_FIELD(MethodDesc, /*uint16*/, Flags, cdac_data::Flags) CDAC_TYPE_FIELD(MethodDesc, /*uint16*/, Flags3AndTokenRemainder, cdac_data::Flags3AndTokenRemainder) CDAC_TYPE_FIELD(MethodDesc, /*uint8*/, EntryPointFlags, cdac_data::EntryPointFlags) CDAC_TYPE_FIELD(MethodDesc, /*pointer*/, CodeData, cdac_data::CodeData) +#ifdef HAVE_GCCOVER +CDAC_TYPE_FIELD(MethodDesc, /*pointer*/, GCCoverageInfo, offsetof(MethodDesc, m_GcCover)) +#endif // HAVE_GCCOVER CDAC_TYPE_END(MethodDesc) CDAC_TYPE_BEGIN(MethodDescChunk) @@ -544,6 +547,9 @@ CDAC_TYPE_FIELD(NativeCodeVersionNode, /*pointer*/, MethodDesc, cdac_data::NativeCode) CDAC_TYPE_FIELD(NativeCodeVersionNode, /*uint32*/, Flags, cdac_data::Flags) CDAC_TYPE_FIELD(NativeCodeVersionNode, /*nuint*/, ILVersionId, cdac_data::ILVersionId) +#ifdef HAVE_GCCOVER +CDAC_TYPE_FIELD(NativeCodeVersionNode, /*pointer*/, GCCoverageInfo, cdac_data::GCCoverageInfo) +#endif // HAVE_GCCOVER CDAC_TYPE_END(NativeCodeVersionNode) CDAC_TYPE_BEGIN(ILCodeVersionNode) @@ -557,6 +563,13 @@ CDAC_TYPE_BEGIN(ProfControlBlock) CDAC_TYPE_FIELD(ProfControlBlock, /*uint64*/, GlobalEventMask, offsetof(ProfControlBlock, globalEventMask)) CDAC_TYPE_END(ProfControlBlock) +#ifdef HAVE_GCCOVER +CDAC_TYPE_BEGIN(GCCoverageInfo) +CDAC_TYPE_INDETERMINATE(GCCoverageInfo) +CDAC_TYPE_FIELD(GCCoverageInfo, /*pointer*/, SavedCode, offsetof(GCCoverageInfo, savedCode)) +CDAC_TYPE_END(GCCoverageInfo) +#endif // HAVE_GCCOVER + CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() diff --git a/src/coreclr/vm/codeversion.h b/src/coreclr/vm/codeversion.h index 442a2846543757..b9dc76b90e3066 100644 --- a/src/coreclr/vm/codeversion.h +++ b/src/coreclr/vm/codeversion.h @@ -330,6 +330,9 @@ struct cdac_data static constexpr size_t NativeCode = offsetof(NativeCodeVersionNode, m_pNativeCode); static constexpr size_t Flags = offsetof(NativeCodeVersionNode, m_flags); static constexpr size_t ILVersionId = offsetof(NativeCodeVersionNode, m_parentId); +#ifdef HAVE_GCCOVER + static constexpr size_t GCCoverageInfo = offsetof(NativeCodeVersionNode, m_gcCover); +#endif // HAVE_GCCOVER }; class NativeCodeVersionCollection 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 a81bdfe92fce95..901084cd2c7d5e 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs @@ -58,5 +58,5 @@ internal abstract class ContractRegistry /// /// Gets an instance of the ReJIT contract for the target. /// - public abstract IReJIT ReJIT { get; } + public abstract IReJIT ReJIT { 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 index a6db692f7dede5..a422ccabfbd3e0 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ICodeVersions.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ICodeVersions.cs @@ -22,6 +22,8 @@ internal interface ICodeVersions : IContract public virtual TargetCodePointer GetNativeCode(NativeCodeVersionHandle codeVersionHandle) => throw new NotImplementedException(); + public virtual TargetPointer GetGCStressCodeCopy(NativeCodeVersionHandle codeVersionHandle) => throw new NotImplementedException(); + public virtual bool CodeVersionManagerSupportsMethod(TargetPointer methodDesc) => throw new NotImplementedException(); } 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 31131c4475422d..ecf243f9b6ce8a 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 @@ -166,6 +166,8 @@ internal interface IRuntimeTypeSystem : IContract public virtual bool HasNativeCodeSlot(MethodDescHandle methodDesc) => throw new NotImplementedException(); public virtual TargetPointer GetAddressOfNativeCodeSlot(MethodDescHandle methodDesc) => throw new NotImplementedException(); + + public virtual TargetPointer GetGCStressCodeCopy(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 ef93b469dcfe50..8ea30a324bfaee 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -81,4 +81,5 @@ public enum DataType NonVtableSlot, MethodImpl, NativeCodeSlot, + GCCoverageInfo, } 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 e6b1755004c710..70fec695684029 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 @@ -183,6 +183,30 @@ NativeCodeVersionHandle ICodeVersions.GetActiveNativeCodeVersionForILCodeVersion }); } + TargetPointer ICodeVersions.GetGCStressCodeCopy(NativeCodeVersionHandle codeVersionHandle) + { + Debug.Assert(codeVersionHandle.Valid); + + if (!codeVersionHandle.IsExplicit) + { + // NativeCodeVersion::GetGCCoverageInfo + IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + MethodDescHandle md = rts.GetMethodDescHandle(codeVersionHandle.MethodDescAddress); + return rts.GetGCStressCodeCopy(md); + } + else + { + // NativeCodeVersionNode::GetGCCoverageInfo + NativeCodeVersionNode codeVersionNode = AsNode(codeVersionHandle); + if (codeVersionNode.GCCoverageInfo is TargetPointer gcCoverageInfoAddr && gcCoverageInfoAddr != TargetPointer.Null) + { + Target.TypeInfo gcCoverageInfoType = _target.GetTypeInfo(DataType.GCCoverageInfo); + return gcCoverageInfoAddr + (ulong)gcCoverageInfoType.Fields["SavedCode"].Offset; + } + return TargetPointer.Null; + } + } + [Flags] internal enum MethodDescVersioningStateFlags : byte { diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index c29930d49fced7..d32fae6d4b07c7 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -141,6 +141,9 @@ private static uint ComputeToken(Target target, Data.MethodDesc desc, Data.Metho public bool IsUnboxingStub => HasFlags(MethodDescFlags_1.MethodDescFlags3.IsUnboxingStub); public TargetPointer CodeData => _desc.CodeData; + + public TargetPointer? GCCoverageInfo => _desc.GCCoverageInfo; + public bool IsIL => Classification == MethodClassification.IL || Classification == MethodClassification.Instantiated; internal bool HasNonVtableSlot => MethodDescOptionalSlots.HasNonVtableSlot(_desc.Flags); @@ -1012,6 +1015,17 @@ private TargetCodePointer GetMethodEntryPointIfExists(MethodDesc md) return _target.ReadCodePointer(addrOfSlot); } + TargetPointer IRuntimeTypeSystem.GetGCStressCodeCopy(MethodDescHandle methodDesc) + { + MethodDesc md = _methodDescs[methodDesc.Address]; + if (md.GCCoverageInfo is TargetPointer gcCoverageInfoAddr && gcCoverageInfoAddr != TargetPointer.Null) + { + Target.TypeInfo gcCoverageInfoType = _target.GetTypeInfo(DataType.GCCoverageInfo); + return gcCoverageInfoAddr + (ulong)gcCoverageInfoType.Fields["SavedCode"].Offset; + } + return TargetPointer.Null; + } + private class NonValidatedMethodTableQueries : MethodValidation.IMethodTableQueries { private readonly RuntimeTypeSystem_1 _rts; diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDesc.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDesc.cs index eea2a616194218..0181d329a8413a 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDesc.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/MethodDesc.cs @@ -18,6 +18,10 @@ public MethodDesc(Target target, TargetPointer address) Flags3AndTokenRemainder = target.Read(address + (ulong)type.Fields[nameof(Flags3AndTokenRemainder)].Offset); EntryPointFlags = target.Read(address + (ulong)type.Fields[nameof(EntryPointFlags)].Offset); CodeData = target.ReadPointer(address + (ulong)type.Fields[nameof(CodeData)].Offset); + if (type.Fields.ContainsKey(nameof(GCCoverageInfo))) + { + GCCoverageInfo = target.ReadPointer(address + (ulong)type.Fields[nameof(GCCoverageInfo)].Offset); + } } public byte ChunkIndex { get; init; } @@ -26,7 +30,9 @@ public MethodDesc(Target target, TargetPointer address) public ushort Flags3AndTokenRemainder { get; init; } public byte EntryPointFlags { get; init; } - public TargetPointer CodeData { get; set; } + public TargetPointer CodeData { get; init; } + + public TargetPointer? GCCoverageInfo { get; init; } } internal sealed class InstantiatedMethodDesc : IData diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/NativeCodeVersionNode.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/NativeCodeVersionNode.cs index 02a7d37eb1e068..e68345e4499d23 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/NativeCodeVersionNode.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/NativeCodeVersionNode.cs @@ -17,12 +17,18 @@ public NativeCodeVersionNode(Target target, TargetPointer address) NativeCode = target.ReadCodePointer(address + (ulong)type.Fields[nameof(NativeCode)].Offset); Flags = target.Read(address + (ulong)type.Fields[nameof(Flags)].Offset); ILVersionId = target.ReadNUInt(address + (ulong)type.Fields[nameof(ILVersionId)].Offset); + if (type.Fields.ContainsKey(nameof(GCCoverageInfo))) + { + GCCoverageInfo = target.ReadPointer(address + (ulong)type.Fields[nameof(GCCoverageInfo)].Offset); + } } public TargetPointer Next { get; init; } public TargetPointer MethodDesc { get; init; } public TargetCodePointer NativeCode { get; init; } - public uint Flags{ get; init; } + public uint Flags { get; init; } public TargetNUInt ILVersionId { get; init; } + + public TargetPointer? GCCoverageInfo { get; init; } } diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs index 75a60ba1b46095..b99fbdee26cac1 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.cs @@ -350,16 +350,14 @@ int ISOSDacInterface.GetMethodDescData(ulong methodDesc, ulong ip, DacpMethodDes } } -#if false // TODO[cdac]: HAVE_GCCOVER + // HAVE_GCCOVER if (requestedNativeCodeVersion.Valid) { - TargetPointer gcCoverAddr = nativeCodeContract.GetGCCoverageInfo(requestedNativeCodeVersion); - if (gcCoverAddr != TargetPointer.Null) - { - throw new NotImplementedException(); // TODO[cdac]: gc stress code copy - } + // TargetPointer.Null if GCCover information is not available. + // In certain minidumps, we won't save the GCCover information. + // (it would be unwise to do so, it is heavy and not a customer scenario). + data->GCStressCodeCopy = nativeCodeContract.GetGCStressCodeCopy(requestedNativeCodeVersion); } -#endif // Unlike the legacy implementation, the cDAC does not currently populate // data->managedDynamicMethodObject. This field is unused in both SOS and CLRMD diff --git a/src/native/managed/cdacreader/tests/CodeVersionsTests.cs b/src/native/managed/cdacreader/tests/CodeVersionsTests.cs index bdd6cacdd3a106..7a30e81ad99384 100644 --- a/src/native/managed/cdacreader/tests/CodeVersionsTests.cs +++ b/src/native/managed/cdacreader/tests/CodeVersionsTests.cs @@ -254,6 +254,24 @@ internal static Target CreateTarget( return target; } + internal static Target CreateTarget( + MockTarget.Architecture arch, + MockCodeVersions builder, + Mock mockRuntimeTypeSystem) + { + TestPlaceholderTarget target = new TestPlaceholderTarget( + arch, + builder.Builder.GetReadContext().ReadFromTarget, + builder.Types); + + IContractFactory cvfactory = new CodeVersionsFactory(); + ContractRegistry reg = Mock.Of( + c => c.CodeVersions == cvfactory.CreateContract(target, 1) + && c.RuntimeTypeSystem == mockRuntimeTypeSystem.Object); + target.SetContracts(reg); + return target; + } + [Theory] [ClassData(typeof(MockTarget.StdArch))] public void GetNativeCodeVersion_Null(MockTarget.Architecture arch) @@ -678,4 +696,65 @@ public void IlToNativeToIlCodeVersion_SyntheticAndExplicit(MockTarget.Architectu NativeCodeVersionHandle syntheticNativeCodeVersion = codeVersions.GetActiveNativeCodeVersionForILCodeVersion(methodDescAddress, syntheticILcodeVersion); Assert.True(syntheticILcodeVersion.Equals(codeVersions.GetILCodeVersion(syntheticNativeCodeVersion))); } + + private void GetGCStressCodeCopy_Impl(MockTarget.Architecture arch, bool returnsNull) + { + MockCodeVersions builder = new(arch); + Mock mockRTS = new(); + + // Setup synthetic NativeCodeVersion + TargetPointer expectedSyntheticCodeCopyAddr = returnsNull ? TargetPointer.Null : new(0x2345_6789); + TargetPointer syntheticMethodDescAddr = new(0x2345_8000); + NativeCodeVersionHandle syntheticHandle = NativeCodeVersionHandle.CreateSynthetic(syntheticMethodDescAddr); + MethodDescHandle methodDescHandle = new MethodDescHandle(syntheticMethodDescAddr); + mockRTS.Setup(rts => rts.GetMethodDescHandle(syntheticMethodDescAddr)).Returns(methodDescHandle); + mockRTS.Setup(rts => rts.GetGCStressCodeCopy(methodDescHandle)).Returns(expectedSyntheticCodeCopyAddr); + + // Setup explicit NativeCodeVersion + TargetPointer? explicitGCCoverageInfoAddr = returnsNull ? TargetPointer.Null : new(0x1234_5678); + TargetPointer nativeCodeVersionNode = builder.AddNativeCodeVersionNode(); + builder.FillNativeCodeVersionNode( + nativeCodeVersionNode, + methodDesc: TargetPointer.Null, + nativeCode: TargetCodePointer.Null, + next: TargetPointer.Null, + isActive: true, + ilVersionId: new(1), + gcCoverageInfo: explicitGCCoverageInfoAddr); + NativeCodeVersionHandle explicitHandle = NativeCodeVersionHandle.CreateExplicit(nativeCodeVersionNode); + + var target = CreateTarget(arch, builder, mockRTS); + var codeVersions = target.Contracts.CodeVersions; + + // TEST + TargetPointer actualSyntheticCodeCopyAddr = codeVersions.GetGCStressCodeCopy(syntheticHandle); + Assert.Equal(expectedSyntheticCodeCopyAddr, actualSyntheticCodeCopyAddr); + + if(returnsNull) + { + TargetPointer actualExplicitCodeCopyAddr = codeVersions.GetGCStressCodeCopy(explicitHandle); + Assert.Equal(TargetPointer.Null, actualExplicitCodeCopyAddr); + } + else + { + Target.TypeInfo gcCoverageInfoType = target.GetTypeInfo(DataType.GCCoverageInfo); + TargetPointer expectedExplicitCodeCopyAddr = explicitGCCoverageInfoAddr.Value + (ulong)gcCoverageInfoType.Fields["SavedCode"].Offset; + TargetPointer actualExplicitCodeCopyAddr = codeVersions.GetGCStressCodeCopy(explicitHandle); + Assert.Equal(expectedExplicitCodeCopyAddr, actualExplicitCodeCopyAddr); + } + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetGCStressCodeCopy_Null(MockTarget.Architecture arch) + { + GetGCStressCodeCopy_Impl(arch, returnsNull: true); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetGCStressCodeCopy_NotNull(MockTarget.Architecture arch) + { + GetGCStressCodeCopy_Impl(arch, returnsNull: false); + } } diff --git a/src/native/managed/cdacreader/tests/MethodDescTests.cs b/src/native/managed/cdacreader/tests/MethodDescTests.cs index c9b675e8ec75e2..a53d201acc2351 100644 --- a/src/native/managed/cdacreader/tests/MethodDescTests.cs +++ b/src/native/managed/cdacreader/tests/MethodDescTests.cs @@ -74,6 +74,8 @@ public void GetMethodDescHandle_ILMethod_GetBasicData(MockTarget.Architecture ar Assert.False(isCollectible); TargetPointer versioning = rts.GetMethodDescVersioningState(handle); Assert.Equal(TargetPointer.Null, versioning); + TargetPointer gcStressCodeCopy = rts.GetGCStressCodeCopy(handle); + Assert.Equal(TargetPointer.Null, gcStressCodeCopy); // Method classification - IL method Assert.False(rts.IsStoredSigMethodDesc(handle, out _)); diff --git a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.CodeVersions.cs b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.CodeVersions.cs index a4b2575eb2ac4c..bdfa03acefb2e6 100644 --- a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.CodeVersions.cs +++ b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.CodeVersions.cs @@ -34,6 +34,7 @@ public class CodeVersions new(nameof(Data.NativeCodeVersionNode.NativeCode), DataType.pointer), new(nameof(Data.NativeCodeVersionNode.Flags), DataType.uint32), new(nameof(Data.NativeCodeVersionNode.ILVersionId), DataType.nuint), + new(nameof(Data.NativeCodeVersionNode.GCCoverageInfo), DataType.pointer), ] }; @@ -94,6 +95,7 @@ public CodeVersions(MockMemorySpace.Builder builder, (ulong Start, ulong End) al NativeCodeVersionNodeFields, ILCodeVersioningStateFields, ILCodeVersionNodeFields, + GCCoverageInfoFields, ]); } @@ -116,7 +118,7 @@ public TargetPointer AddNativeCodeVersionNode() return fragment.Address; } - public void FillNativeCodeVersionNode(TargetPointer dest, TargetPointer methodDesc, TargetCodePointer nativeCode, TargetPointer next, bool isActive, TargetNUInt ilVersionId) + public void FillNativeCodeVersionNode(TargetPointer dest, TargetPointer methodDesc, TargetCodePointer nativeCode, TargetPointer next, bool isActive, TargetNUInt ilVersionId, TargetPointer? gcCoverageInfo = null) { Target.TypeInfo info = Types[DataType.NativeCodeVersionNode]; Span ncvn = Builder.BorrowAddressRange(dest, (int)info.Size!); @@ -125,6 +127,7 @@ public void FillNativeCodeVersionNode(TargetPointer dest, TargetPointer methodDe Builder.TargetTestHelpers.WritePointer(ncvn.Slice(info.Fields[nameof(Data.NativeCodeVersionNode.NativeCode)].Offset, Builder.TargetTestHelpers.PointerSize), nativeCode); Builder.TargetTestHelpers.Write(ncvn.Slice(info.Fields[nameof(Data.NativeCodeVersionNode.Flags)].Offset, sizeof(uint)), isActive ? (uint)CodeVersions_1.NativeCodeVersionNodeFlags.IsActiveChild : 0u); Builder.TargetTestHelpers.WriteNUInt(ncvn.Slice(info.Fields[nameof(Data.NativeCodeVersionNode.ILVersionId)].Offset, Builder.TargetTestHelpers.PointerSize), ilVersionId); + Builder.TargetTestHelpers.WritePointer(ncvn.Slice(info.Fields[nameof(Data.NativeCodeVersionNode.GCCoverageInfo)].Offset, Builder.TargetTestHelpers.PointerSize), gcCoverageInfo ?? TargetPointer.Null); } public (TargetPointer First, TargetPointer Active) AddNativeCodeVersionNodesForMethod(TargetPointer methodDesc, int count, int activeIndex, TargetCodePointer activeNativeCode, TargetNUInt ilVersion, TargetPointer? firstNode = null) diff --git a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.MethodDescriptors.cs b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.MethodDescriptors.cs index 4ce2bb647fa011..4a0787ac65c27f 100644 --- a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.MethodDescriptors.cs +++ b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.MethodDescriptors.cs @@ -24,6 +24,7 @@ public class MethodDescriptors new(nameof(Data.MethodDesc.Flags3AndTokenRemainder), DataType.uint16), new(nameof(Data.MethodDesc.EntryPointFlags), DataType.uint8), new(nameof(Data.MethodDesc.CodeData), DataType.pointer), + new(nameof(Data.MethodDesc.GCCoverageInfo), DataType.pointer), ] }; diff --git a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.RuntimeTypeSystem.cs b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.RuntimeTypeSystem.cs index bade23b8cb390a..8034cf22f70437 100644 --- a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.RuntimeTypeSystem.cs +++ b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.RuntimeTypeSystem.cs @@ -72,6 +72,7 @@ public class RuntimeTypeSystem FnPtrTypeDescFields, ParamTypeDescFields, TypeVarTypeDescFields, + GCCoverageInfoFields, ]); } diff --git a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.cs b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.cs index 50d3c76135c768..23444bb58d55ba 100644 --- a/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.cs +++ b/src/native/managed/cdacreader/tests/MockDescriptors/MockDescriptors.cs @@ -198,6 +198,17 @@ internal record TypeFields ] }; + private static readonly TypeFields GCCoverageInfoFields = new TypeFields() + { + DataType = DataType.GCCoverageInfo, + Fields = + [ + // Add DummyField to ensure the offset of SavedCode is not added to the TargetPointer.Null + new("DummyField", DataType.pointer), + new("SavedCode", DataType.pointer), + ] + }; + internal static Dictionary GetTypesForTypeFields(TargetTestHelpers helpers, TypeFields[] typeFields) { Dictionary types = new();