From 8b978b3e619e961c875c37e99d0fd9a501763674 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 00:50:31 +0000 Subject: [PATCH 1/8] Implement IsDiagnosticsHiddenOrLCGMethod in cDAC Add IsDiagnosticsHidden to IRuntimeTypeSystem interface and implement it in RuntimeTypeSystem_1 using IsILStub and IsWrapperStub checks. Replace the legacy-delegation stub in DacDbiImpl.cs with a full cDAC implementation including #if DEBUG validation against the legacy path. Add unit tests covering all three DynamicMethodType return values: kNone (normal IL), kDiagnosticHidden (IL stub, unboxing stub, instantiating stub), and kLCGMethod (LCG/DynamicMethod). Update RuntimeTypeSystem.md with IsDiagnosticsHidden API documentation and implementation pseudocode. Note: IsAsyncThunkMethod is not yet implemented because the AsyncMethodData.flags field is not exposed in the data descriptor. Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/ce6cb8ca-467a-4278-bf83-9285186e297d Co-authored-by: barosiak <76071368+barosiak@users.noreply.github.com> --- .../design/datacontracts/RuntimeTypeSystem.md | 19 +++ .../Contracts/IRuntimeTypeSystem.cs | 6 + .../Contracts/RuntimeTypeSystem_1.cs | 28 +++-- .../Dbi/DacDbiImpl.cs | 34 +++++- .../managed/cdac/tests/MethodDescTests.cs | 115 ++++++++++++++++++ 5 files changed, 194 insertions(+), 8 deletions(-) diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index ff1d0cd2d08af7..5a7a7d9fa851d5 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -216,6 +216,11 @@ partial interface IRuntimeTypeSystem : IContract // Returns true if the method is eligible for tiered compilation public virtual bool IsEligibleForTieredCompilation(MethodDescHandle methodDesc); + // [cDAC] Return true if the method should be hidden from diagnostic stack traces. + // In the native VM: IsILStub() || IsAsyncThunkMethod() || IsWrapperStub(). + // Note: IsAsyncThunkMethod is not yet implemented in the cDAC. + public virtual bool IsDiagnosticsHidden(MethodDescHandle methodDesc); + } ``` @@ -1497,6 +1502,20 @@ Determining if a method supports multiple code versions: } ``` +Determining if a method should be hidden from diagnostic stack traces: + +```csharp + // [cDAC] Corresponds to MethodDesc::IsDiagnosticsHidden in the native VM. + // In the native VM: IsILStub() || IsAsyncThunkMethod() || IsWrapperStub(). + // Note: IsAsyncThunkMethod is not yet implemented because the AsyncMethodData.flags + // field is not exposed in the data descriptor. + public bool IsDiagnosticsHidden(MethodDescHandle methodDescHandle) + { + MethodDesc md = _methodDescs[methodDescHandle.Address]; + return IsILStub(md) || IsWrapperStub(md); + } +``` + Extracting a pointer to the `MethodDescVersioningState` data for a given method ```csharp diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs index 624edbe5eebaac..49b000b50dcce5 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs @@ -223,6 +223,12 @@ public interface IRuntimeTypeSystem : IContract OptimizationTier GetMethodDescOptimizationTier(MethodDescHandle methodDescHandle) => throw new NotImplementedException(); bool IsEligibleForTieredCompilation(MethodDescHandle methodDescHandle) => throw new NotImplementedException(); + + // [cDAC] Return true if the method should be hidden from diagnostic stack traces. + // In the native VM, this is: IsILStub() || IsAsyncThunkMethod() || IsWrapperStub(). + // Note: IsAsyncThunkMethod is not yet implemented in the cDAC because the AsyncMethodData.flags + // field is not exposed in the data descriptor. This will be addressed when the data descriptor is updated. + bool IsDiagnosticsHidden(MethodDescHandle methodDesc) => throw new NotImplementedException(); #endregion MethodDesc inspection APIs #region FieldDesc inspection APIs TargetPointer GetMTOfEnclosingClass(TargetPointer fieldDescPointer) => throw new NotImplementedException(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index a0eb76b9f0d484..d66fa06d581aa8 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -1320,13 +1320,7 @@ public bool IsIL(MethodDescHandle methodDescHandle) public bool IsILStub(MethodDescHandle methodDescHandle) { MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; - - if (methodDesc.Classification != MethodClassification.Dynamic) - { - return false; - } - - return AsDynamicMethodDesc(methodDesc).IsILStub; + return IsILStub(methodDesc); } public bool HasMDContextArg(MethodDescHandle methodDescHandle) @@ -1727,6 +1721,26 @@ bool IRuntimeTypeSystem.IsEligibleForTieredCompilation(MethodDescHandle methodDe return methodDesc.IsEligibleForTieredCompilation; } + // [cDAC] Corresponds to MethodDesc::IsDiagnosticsHidden in the native VM (method.inl). + // In the native VM: IsILStub() || IsAsyncThunkMethod() || IsWrapperStub(). + // Note: IsAsyncThunkMethod is not yet implemented because the AsyncMethodData.flags field + // is not exposed in the data descriptor. This will be addressed when the data descriptor is updated. + bool IRuntimeTypeSystem.IsDiagnosticsHidden(MethodDescHandle methodDescHandle) + { + MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; + return IsILStub(methodDesc) || IsWrapperStub(methodDesc); + } + + private bool IsILStub(MethodDesc md) + { + if (md.Classification != MethodClassification.Dynamic) + { + return false; + } + + return AsDynamicMethodDesc(md).IsILStub; + } + private sealed class NonValidatedMethodTableQueries : MethodValidation.IMethodTableQueries { private readonly RuntimeTypeSystem_1 _rts; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs index 95fdef60493597..7fb9284bbedbff 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs @@ -631,8 +631,40 @@ public int GetContext(ulong vmThread, nint pContextBuffer) public int ConvertContextToDebuggerRegDisplay(nint pInContext, nint pOutDRD, Interop.BOOL fActive) => _legacy is not null ? _legacy.ConvertContextToDebuggerRegDisplay(pInContext, pOutDRD, fActive) : HResults.E_NOTIMPL; + // [cDAC] Implements IsDiagnosticsHiddenOrLCGMethod using IRuntimeTypeSystem.IsDiagnosticsHidden and IsDynamicMethod. public int IsDiagnosticsHiddenOrLCGMethod(ulong vmMethodDesc, int* pRetVal) - => _legacy is not null ? _legacy.IsDiagnosticsHiddenOrLCGMethod(vmMethodDesc, pRetVal) : HResults.E_NOTIMPL; + { + *pRetVal = 0; // kNone + int hr = HResults.S_OK; + try + { + Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; + Contracts.MethodDescHandle md = rts.GetMethodDescHandle(new TargetPointer(vmMethodDesc)); + if (rts.IsDiagnosticsHidden(md)) + { + *pRetVal = 1; // kDiagnosticHidden + } + else if (rts.IsDynamicMethod(md)) + { + *pRetVal = 2; // kLCGMethod + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + int resultLocal; + int hrLocal = _legacy.IsDiagnosticsHiddenOrLCGMethod(vmMethodDesc, &resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pRetVal == resultLocal, $"cDAC: {*pRetVal}, DAC: {resultLocal}"); + } +#endif + return hr; + } public int GetVarArgSig(ulong VASigCookieAddr, ulong* pArgBase, DacDbiTargetBuffer* pRetVal) => _legacy is not null ? _legacy.GetVarArgSig(VASigCookieAddr, pArgBase, pRetVal) : HResults.E_NOTIMPL; diff --git a/src/native/managed/cdac/tests/MethodDescTests.cs b/src/native/managed/cdac/tests/MethodDescTests.cs index 8cf05c2c2346b8..18a0ceb2f1a063 100644 --- a/src/native/managed/cdac/tests/MethodDescTests.cs +++ b/src/native/managed/cdac/tests/MethodDescTests.cs @@ -439,6 +439,121 @@ public void GetNativeCode_StableEntryPoint_NonVtableSlot(MockTarget.Architecture Assert.Equal(nativeCode, actualNativeCode); } + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void IsDiagnosticsHidden_ReturnsCorrectValues(MockTarget.Architecture arch) + { + TargetPointer normalMethod = TargetPointer.Null; + TargetPointer ilStubMethod = TargetPointer.Null; + TargetPointer lcgMethod = TargetPointer.Null; + TargetPointer unboxingStubMethod = TargetPointer.Null; + TargetPointer instantiatingStubMethod = TargetPointer.Null; + + IRuntimeTypeSystem rts = CreateRuntimeTypeSystemContract(arch, methodDescBuilder => + { + TargetPointer methodTable = AddMethodTable(methodDescBuilder.RTSBuilder); + + // Normal IL method (not diagnostics hidden, not LCG) + { + byte methodDescSize = (byte)(methodDescBuilder.MethodDescLayout.Size / methodDescBuilder.MethodDescAlignment); + MockMethodDescChunk chunk = methodDescBuilder.AddMethodDescChunk("normal", methodDescSize); + chunk.MethodTable = methodTable.Value; + chunk.Size = methodDescSize; + chunk.Count = 1; + MockMethodDesc md = chunk.GetMethodDescAtChunkIndex(0, methodDescBuilder.MethodDescLayout); + md.Flags = (ushort)MethodClassification.IL; + normalMethod = new TargetPointer(md.Address); + } + + // IL stub (diagnostics hidden via IsILStub) + { + uint methodDescSize = (uint)methodDescBuilder.DynamicMethodDescLayout.Size; + uint methodDescSizeByAlignment = methodDescSize / methodDescBuilder.MethodDescAlignment; + byte chunkSize = (byte)(2 * methodDescSizeByAlignment); + MockMethodDescChunk chunk = methodDescBuilder.AddMethodDescChunk("dynamic", chunkSize); + chunk.MethodTable = methodTable.Value; + chunk.Size = chunkSize; + chunk.Count = 2; + + MockDynamicMethodDesc ilStub = chunk.GetMethodDescAtChunkIndex(0, methodDescBuilder.DynamicMethodDescLayout); + ilStub.Flags = (ushort)MethodClassification.Dynamic; + ilStub.ExtendedFlags = (uint)RuntimeTypeSystem_1.DynamicMethodDescExtendedFlags.IsILStub; + ilStubMethod = new TargetPointer(ilStub.Address); + + // LCG method (not diagnostics hidden, is LCG) + MockDynamicMethodDesc lcg = chunk.GetMethodDescAtChunkIndex((int)methodDescSizeByAlignment, methodDescBuilder.DynamicMethodDescLayout); + lcg.ChunkIndex = (byte)methodDescSizeByAlignment; + lcg.Flags = (ushort)MethodClassification.Dynamic; + lcg.Slot = 1; + lcg.ExtendedFlags = (uint)RuntimeTypeSystem_1.DynamicMethodDescExtendedFlags.IsLCGMethod; + lcgMethod = new TargetPointer(lcg.Address); + } + + // Unboxing stub (diagnostics hidden via IsWrapperStub -> IsUnboxingStub) + { + byte methodDescSize = (byte)(methodDescBuilder.MethodDescLayout.Size / methodDescBuilder.MethodDescAlignment); + MockMethodDescChunk chunk = methodDescBuilder.AddMethodDescChunk("unboxing", methodDescSize); + chunk.MethodTable = methodTable.Value; + chunk.Size = methodDescSize; + chunk.Count = 1; + MockMethodDesc md = chunk.GetMethodDescAtChunkIndex(0, methodDescBuilder.MethodDescLayout); + md.Flags = (ushort)MethodClassification.IL; + md.Flags3AndTokenRemainder = (ushort)MethodDescFlags_1.MethodDescFlags3.IsUnboxingStub; + unboxingStubMethod = new TargetPointer(md.Address); + } + + // Instantiating stub (diagnostics hidden via IsWrapperStub -> IsInstantiatingStub) + { + uint methodDescSize = (uint)methodDescBuilder.InstantiatedMethodDescLayout.Size; + uint methodDescSizeByAlignment = methodDescSize / methodDescBuilder.MethodDescAlignment; + byte chunkSize = (byte)methodDescSizeByAlignment; + MockMethodDescChunk chunk = methodDescBuilder.AddMethodDescChunk("instantiating", chunkSize); + chunk.MethodTable = methodTable.Value; + chunk.Size = chunkSize; + chunk.Count = 1; + MockInstantiatedMethodDesc md = chunk.GetMethodDescAtChunkIndex(0, methodDescBuilder.InstantiatedMethodDescLayout); + md.Flags = (ushort)MethodClassification.Instantiated; + md.Flags2 = (ushort)RuntimeTypeSystem_1.InstantiatedMethodDescFlags2.WrapperStubWithInstantiations; + instantiatingStubMethod = new TargetPointer(md.Address); + } + }); + + // Normal IL method: not diagnostics hidden, not LCG + { + MethodDescHandle handle = rts.GetMethodDescHandle(normalMethod); + Assert.False(rts.IsDiagnosticsHidden(handle)); + Assert.False(rts.IsDynamicMethod(handle)); + } + + // IL stub: diagnostics hidden + { + MethodDescHandle handle = rts.GetMethodDescHandle(ilStubMethod); + Assert.True(rts.IsDiagnosticsHidden(handle)); + Assert.False(rts.IsDynamicMethod(handle)); + } + + // LCG method: not diagnostics hidden, is LCG + { + MethodDescHandle handle = rts.GetMethodDescHandle(lcgMethod); + Assert.False(rts.IsDiagnosticsHidden(handle)); + Assert.True(rts.IsDynamicMethod(handle)); + } + + // Unboxing stub: diagnostics hidden (wrapper stub) + { + MethodDescHandle handle = rts.GetMethodDescHandle(unboxingStubMethod); + Assert.True(rts.IsDiagnosticsHidden(handle)); + Assert.False(rts.IsDynamicMethod(handle)); + } + + // Instantiating stub: diagnostics hidden (wrapper stub) + { + MethodDescHandle handle = rts.GetMethodDescHandle(instantiatingStubMethod); + Assert.True(rts.IsDiagnosticsHidden(handle)); + Assert.False(rts.IsDynamicMethod(handle)); + } + } + private static TargetPointer AddMethodTable(MockDescriptors.RuntimeTypeSystem rtsBuilder, ushort numVirtuals = 5) { MockEEClass eeClass = rtsBuilder.AddEEClass(string.Empty); From 3b6b576827c33050be9fd42a9e56fb5ff5ff2498 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 00:53:43 +0000 Subject: [PATCH 2/8] Move private IsILStub(MethodDesc) helper next to sibling helpers Co-locate IsILStub with IsWrapperStub and IsInstantiatingStub for consistency and discoverability. Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/ce6cb8ca-467a-4278-bf83-9285186e297d Co-authored-by: barosiak <76071368+barosiak@users.noreply.github.com> --- .../Contracts/RuntimeTypeSystem_1.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index d66fa06d581aa8..1ec168021102da 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -1395,6 +1395,16 @@ private TargetPointer GetAddressOfSlot(TypeHandle typeHandle, uint slotNum) } } + private bool IsILStub(MethodDesc md) + { + if (md.Classification != MethodClassification.Dynamic) + { + return false; + } + + return AsDynamicMethodDesc(md).IsILStub; + } + private bool IsWrapperStub(MethodDesc md) { return md.IsUnboxingStub || IsInstantiatingStub(md); @@ -1731,16 +1741,6 @@ bool IRuntimeTypeSystem.IsDiagnosticsHidden(MethodDescHandle methodDescHandle) return IsILStub(methodDesc) || IsWrapperStub(methodDesc); } - private bool IsILStub(MethodDesc md) - { - if (md.Classification != MethodClassification.Dynamic) - { - return false; - } - - return AsDynamicMethodDesc(md).IsILStub; - } - private sealed class NonValidatedMethodTableQueries : MethodValidation.IMethodTableQueries { private readonly RuntimeTypeSystem_1 _rts; From 7b904dd738ef3ce01d4217b340a05238f21345d2 Mon Sep 17 00:00:00 2001 From: Barbara Rosiak Date: Thu, 16 Apr 2026 15:16:05 -0700 Subject: [PATCH 3/8] Implement IsAsyncThunkMethod missing from IsDiagnosticsHidden, apply style changes --- .../design/datacontracts/RuntimeTypeSystem.md | 8 +- src/coreclr/debug/inc/dacdbiinterface.h | 5 +- .../vm/datadescriptor/datadescriptor.inc | 1 + .../Contracts/IRuntimeTypeSystem.cs | 4 - .../Contracts/RuntimeTypeSystem_1.cs | 19 +++-- .../Data/AsyncMethodData.cs | 17 ++++ .../MethodDescFlags_1.cs | 7 ++ .../MethodDescOptionalSlots.cs | 32 +++++++- .../Dbi/DacDbiImpl.cs | 14 +++- .../managed/cdac/tests/MethodDescTests.cs | 82 ++++++++++++++++++- 10 files changed, 164 insertions(+), 25 deletions(-) create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/AsyncMethodData.cs diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index 5a7a7d9fa851d5..e85cfd9b555d9c 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -217,8 +217,6 @@ partial interface IRuntimeTypeSystem : IContract public virtual bool IsEligibleForTieredCompilation(MethodDescHandle methodDesc); // [cDAC] Return true if the method should be hidden from diagnostic stack traces. - // In the native VM: IsILStub() || IsAsyncThunkMethod() || IsWrapperStub(). - // Note: IsAsyncThunkMethod is not yet implemented in the cDAC. public virtual bool IsDiagnosticsHidden(MethodDescHandle methodDesc); } @@ -1505,14 +1503,10 @@ Determining if a method supports multiple code versions: Determining if a method should be hidden from diagnostic stack traces: ```csharp - // [cDAC] Corresponds to MethodDesc::IsDiagnosticsHidden in the native VM. - // In the native VM: IsILStub() || IsAsyncThunkMethod() || IsWrapperStub(). - // Note: IsAsyncThunkMethod is not yet implemented because the AsyncMethodData.flags - // field is not exposed in the data descriptor. public bool IsDiagnosticsHidden(MethodDescHandle methodDescHandle) { MethodDesc md = _methodDescs[methodDescHandle.Address]; - return IsILStub(md) || IsWrapperStub(md); + return IsILStub(md) || IsAsyncThunkMethod(md) || IsWrapperStub(md); } ``` diff --git a/src/coreclr/debug/inc/dacdbiinterface.h b/src/coreclr/debug/inc/dacdbiinterface.h index 5660b92b11e05e..5a954a5ad4e7c6 100644 --- a/src/coreclr/debug/inc/dacdbiinterface.h +++ b/src/coreclr/debug/inc/dacdbiinterface.h @@ -1472,6 +1472,7 @@ IDacDbiInterface : public IUnknown virtual HRESULT STDMETHODCALLTYPE ConvertContextToDebuggerRegDisplay(const DT_CONTEXT * pInContext, DebuggerREGDISPLAY * pOutDRD, BOOL fActive) = 0; + // [cDAC] [DacDbiImpl]: Contract depends on these values. typedef enum { kNone, @@ -2114,8 +2115,8 @@ IDacDbiInterface : public IUnknown // // Arguments: // vmObject - The object to check for ownership - // pRetVal - [out] Inside the structure we have: - // pVmThread - the owning thread or VMPTR_Thread::NullPtr() if unowned, + // pRetVal - [out] Inside the structure we have: + // pVmThread - the owning thread or VMPTR_Thread::NullPtr() if unowned, // pAcquisitionCount - the number of times the lock would need to be released in order for it to be unowned. // // Return Value: diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 0ee65734c32da2..efde49274d0c0e 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -560,6 +560,7 @@ CDAC_TYPE_END(NativeCodeSlot) CDAC_TYPE_BEGIN(AsyncMethodData) CDAC_TYPE_SIZE(sizeof(AsyncMethodData)) +CDAC_TYPE_FIELD(AsyncMethodData, /*uint32*/, Flags, offsetof(AsyncMethodData, flags)) CDAC_TYPE_END(AsyncMethodData) CDAC_TYPE_BEGIN(InstantiatedMethodDesc) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs index 49b000b50dcce5..182f52b62d0694 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs @@ -224,10 +224,6 @@ public interface IRuntimeTypeSystem : IContract OptimizationTier GetMethodDescOptimizationTier(MethodDescHandle methodDescHandle) => throw new NotImplementedException(); bool IsEligibleForTieredCompilation(MethodDescHandle methodDescHandle) => throw new NotImplementedException(); - // [cDAC] Return true if the method should be hidden from diagnostic stack traces. - // In the native VM, this is: IsILStub() || IsAsyncThunkMethod() || IsWrapperStub(). - // Note: IsAsyncThunkMethod is not yet implemented in the cDAC because the AsyncMethodData.flags - // field is not exposed in the data descriptor. This will be addressed when the data descriptor is updated. bool IsDiagnosticsHidden(MethodDescHandle methodDesc) => throw new NotImplementedException(); #endregion MethodDesc inspection APIs #region FieldDesc inspection APIs diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index 1ec168021102da..afbfe19fd9238b 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -303,12 +303,14 @@ private static uint ComputeSize(Target target, Data.MethodDesc desc) internal bool HasNonVtableSlot => MethodDescOptionalSlots.HasNonVtableSlot(_desc.Flags); internal bool HasNativeCodeSlot => MethodDescOptionalSlots.HasNativeCodeSlot(_desc.Flags); + internal bool HasAsyncMethodData => MethodDescOptionalSlots.HasAsyncMethodData(_desc.Flags); internal bool HasStableEntryPoint => HasFlags(MethodDescFlags_1.MethodDescFlags3.HasStableEntryPoint); internal bool HasPrecode => HasFlags(MethodDescFlags_1.MethodDescFlags3.HasPrecode); internal TargetPointer GetAddressOfNonVtableSlot() => MethodDescOptionalSlots.GetAddressOfNonVtableSlot(Address, Classification, _desc.Flags, _target); internal TargetPointer GetAddressOfNativeCodeSlot() => MethodDescOptionalSlots.GetAddressOfNativeCodeSlot(Address, Classification, _desc.Flags, _target); + internal TargetPointer GetAddressOfAsyncMethodData() => MethodDescOptionalSlots.GetAddressOfAsyncMethodData(Address, Classification, _desc.Flags, _target); internal bool IsLoaderModuleAttachedToChunk => HasFlags(MethodDescChunkFlags.LoaderModuleAttachedToChunk); @@ -1405,6 +1407,17 @@ private bool IsILStub(MethodDesc md) return AsDynamicMethodDesc(md).IsILStub; } + private bool IsAsyncThunkMethod(MethodDesc md) + { + if (!md.HasAsyncMethodData) + { + return false; + } + + Data.AsyncMethodData asyncData = _target.ProcessedData.GetOrAdd(md.GetAddressOfAsyncMethodData()); + return ((MethodDescFlags_1.AsyncMethodFlags)asyncData.Flags).HasFlag(MethodDescFlags_1.AsyncMethodFlags.Thunk); + } + private bool IsWrapperStub(MethodDesc md) { return md.IsUnboxingStub || IsInstantiatingStub(md); @@ -1731,14 +1744,10 @@ bool IRuntimeTypeSystem.IsEligibleForTieredCompilation(MethodDescHandle methodDe return methodDesc.IsEligibleForTieredCompilation; } - // [cDAC] Corresponds to MethodDesc::IsDiagnosticsHidden in the native VM (method.inl). - // In the native VM: IsILStub() || IsAsyncThunkMethod() || IsWrapperStub(). - // Note: IsAsyncThunkMethod is not yet implemented because the AsyncMethodData.flags field - // is not exposed in the data descriptor. This will be addressed when the data descriptor is updated. bool IRuntimeTypeSystem.IsDiagnosticsHidden(MethodDescHandle methodDescHandle) { MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; - return IsILStub(methodDesc) || IsWrapperStub(methodDesc); + return IsILStub(methodDesc) || IsAsyncThunkMethod(methodDesc) || IsWrapperStub(methodDesc); } private sealed class NonValidatedMethodTableQueries : MethodValidation.IMethodTableQueries diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/AsyncMethodData.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/AsyncMethodData.cs new file mode 100644 index 00000000000000..c28efb170d9500 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/AsyncMethodData.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class AsyncMethodData : IData +{ + static AsyncMethodData IData.Create(Target target, TargetPointer address) => new AsyncMethodData(target, address); + public AsyncMethodData(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.AsyncMethodData); + + Flags = target.ReadField(address, type, nameof(Flags)); + } + + public uint Flags { get; init; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescFlags_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescFlags_1.cs index 901d9331c8938f..643ebb6582923a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescFlags_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescFlags_1.cs @@ -34,4 +34,11 @@ internal enum MethodDescEntryPointFlags : byte { TemporaryEntryPointAssigned = 0x04, } + + [Flags] + internal enum AsyncMethodFlags : uint + { + None = 0, + Thunk = 16, + } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescOptionalSlots.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescOptionalSlots.cs index 028b58f27d1c0c..16ba95cc85a7a0 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescOptionalSlots.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescOptionalSlots.cs @@ -4,8 +4,8 @@ namespace Microsoft.Diagnostics.DataContractReader.RuntimeTypeSystemHelpers; -// Non-vtable slot, native code slot, and MethodImpl slots are stored after the MethodDesc itself, packed tightly -// in the order: [non-vtable; method impl; native code]. +// Non-vtable slot, native code slot, MethodImpl, and async method data slots are stored after the MethodDesc itself, packed tightly +// in the order: [non-vtable; method impl; native code; async method data]. internal static class MethodDescOptionalSlots { internal static bool HasNonVtableSlot(ushort flags) @@ -17,6 +17,9 @@ internal static bool HasMethodImpl(ushort flags) internal static bool HasNativeCodeSlot(ushort flags) => (flags & (ushort)MethodDescFlags_1.MethodDescFlags.HasNativeCodeSlot) != 0; + internal static bool HasAsyncMethodData(ushort flags) + => (flags & (ushort)MethodDescFlags_1.MethodDescFlags.HasAsyncMethodData) != 0; + internal static TargetPointer GetAddressOfNonVtableSlot(TargetPointer methodDesc, MethodClassification classification, ushort flags, Target target) { uint offset = StartOffset(classification, target); @@ -31,6 +34,13 @@ internal static TargetPointer GetAddressOfNativeCodeSlot(TargetPointer methodDes return methodDesc + offset; } + internal static TargetPointer GetAddressOfAsyncMethodData(TargetPointer methodDesc, MethodClassification classification, ushort flags, Target target) + { + uint offset = StartOffset(classification, target); + offset += AsyncMethodDataOffset(flags, target); + return methodDesc + offset; + } + // Offset from the MethodDesc address to the start of its optional slots private static uint StartOffset(MethodClassification classification, Target target) { @@ -89,4 +99,22 @@ private static uint NativeCodeSlotOffset(ushort flags, Target target) return offset; } + + private static uint AsyncMethodDataOffset(ushort flags, Target target) + { + if (!HasAsyncMethodData(flags)) + throw new InvalidOperationException("no async method data"); + + uint offset = 0; + if (HasNonVtableSlot(flags)) + offset += target.GetTypeInfo(DataType.NonVtableSlot).Size!.Value; + + if (HasMethodImpl(flags)) + offset += target.GetTypeInfo(DataType.MethodImpl).Size!.Value; + + if (HasNativeCodeSlot(flags)) + offset += target.GetTypeInfo(DataType.NativeCodeSlot).Size!.Value; + + return offset; + } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs index 7fb9284bbedbff..afa475dd85f29f 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs @@ -17,6 +17,13 @@ public sealed unsafe partial class DacDbiImpl : IDacDbiInterface private readonly Target _target; private readonly IDacDbiInterface? _legacy; + private enum DynamicMethodType + { + kNone, + kDiagnosticHidden, + kLCGMethod, + } + // IStringHolder is a native C++ abstract class (not COM) with a single virtual method: // virtual HRESULT AssignCopy(const WCHAR* psz) = 0; // The nint we receive is a pointer to the object, whose first field is the vtable pointer. @@ -631,10 +638,9 @@ public int GetContext(ulong vmThread, nint pContextBuffer) public int ConvertContextToDebuggerRegDisplay(nint pInContext, nint pOutDRD, Interop.BOOL fActive) => _legacy is not null ? _legacy.ConvertContextToDebuggerRegDisplay(pInContext, pOutDRD, fActive) : HResults.E_NOTIMPL; - // [cDAC] Implements IsDiagnosticsHiddenOrLCGMethod using IRuntimeTypeSystem.IsDiagnosticsHidden and IsDynamicMethod. public int IsDiagnosticsHiddenOrLCGMethod(ulong vmMethodDesc, int* pRetVal) { - *pRetVal = 0; // kNone + *pRetVal = (int)DynamicMethodType.kNone; int hr = HResults.S_OK; try { @@ -642,11 +648,11 @@ public int IsDiagnosticsHiddenOrLCGMethod(ulong vmMethodDesc, int* pRetVal) Contracts.MethodDescHandle md = rts.GetMethodDescHandle(new TargetPointer(vmMethodDesc)); if (rts.IsDiagnosticsHidden(md)) { - *pRetVal = 1; // kDiagnosticHidden + *pRetVal = (int)DynamicMethodType.kDiagnosticHidden; } else if (rts.IsDynamicMethod(md)) { - *pRetVal = 2; // kLCGMethod + *pRetVal = (int)DynamicMethodType.kLCGMethod; } } catch (System.Exception ex) diff --git a/src/native/managed/cdac/tests/MethodDescTests.cs b/src/native/managed/cdac/tests/MethodDescTests.cs index 18a0ceb2f1a063..04e6672bafeda4 100644 --- a/src/native/managed/cdac/tests/MethodDescTests.cs +++ b/src/native/managed/cdac/tests/MethodDescTests.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.Data; using Microsoft.Diagnostics.DataContractReader.RuntimeTypeSystemHelpers; using Moq; using Xunit; @@ -24,7 +25,14 @@ public class MethodDescTests [DataType.NonVtableSlot] = new Target.TypeInfo { Size = methodDescBuilder.NonVtableSlotSize }, [DataType.MethodImpl] = new Target.TypeInfo { Size = methodDescBuilder.MethodImplSize }, [DataType.NativeCodeSlot] = new Target.TypeInfo { Size = methodDescBuilder.NativeCodeSlotSize }, - [DataType.AsyncMethodData] = new Target.TypeInfo { Size = methodDescBuilder.AsyncMethodDataSize }, + [DataType.AsyncMethodData] = new Target.TypeInfo + { + Size = methodDescBuilder.AsyncMethodDataSize, + Fields = new Dictionary + { + [nameof(Data.AsyncMethodData.Flags)] = new Target.FieldInfo { Offset = 0, Type = DataType.Unknown }, + }, + }, [DataType.ArrayMethodDesc] = new Target.TypeInfo { Size = methodDescBuilder.ArrayMethodDescSize }, [DataType.FCallMethodDesc] = new Target.TypeInfo { Size = methodDescBuilder.FCallMethodDescSize }, [DataType.PInvokeMethodDesc] = new Target.TypeInfo { Size = methodDescBuilder.PInvokeMethodDescSize }, @@ -448,10 +456,14 @@ public void IsDiagnosticsHidden_ReturnsCorrectValues(MockTarget.Architecture arc TargetPointer lcgMethod = TargetPointer.Null; TargetPointer unboxingStubMethod = TargetPointer.Null; TargetPointer instantiatingStubMethod = TargetPointer.Null; + TargetPointer asyncThunkMethod = TargetPointer.Null; + TargetPointer asyncNonThunkMethod = TargetPointer.Null; + TargetPointer asyncThunkWithNativeCodeSlotMethod = TargetPointer.Null; IRuntimeTypeSystem rts = CreateRuntimeTypeSystemContract(arch, methodDescBuilder => { TargetPointer methodTable = AddMethodTable(methodDescBuilder.RTSBuilder); + TargetTestHelpers helpers = methodDescBuilder.TargetTestHelpers; // Normal IL method (not diagnostics hidden, not LCG) { @@ -516,6 +528,56 @@ public void IsDiagnosticsHidden_ReturnsCorrectValues(MockTarget.Architecture arc md.Flags2 = (ushort)RuntimeTypeSystem_1.InstantiatedMethodDescFlags2.WrapperStubWithInstantiations; instantiatingStubMethod = new TargetPointer(md.Address); } + + // Async thunk method (diagnostics hidden via IsAsyncThunkMethod, no NativeCodeSlot) + { + uint mdBaseSize = (uint)methodDescBuilder.MethodDescLayout.Size; + uint totalSize = mdBaseSize + methodDescBuilder.AsyncMethodDataSize; + byte chunkSize = (byte)(totalSize / methodDescBuilder.MethodDescAlignment); + MockMethodDescChunk chunk = methodDescBuilder.AddMethodDescChunk("asyncThunk", chunkSize); + chunk.MethodTable = methodTable.Value; + chunk.Size = chunkSize; + chunk.Count = 1; + MockMethodDesc md = chunk.GetMethodDescAtChunkIndex(0, methodDescBuilder.MethodDescLayout); + md.Flags = (ushort)((ushort)MethodClassification.IL | (ushort)MethodDescFlags_1.MethodDescFlags.HasAsyncMethodData); + asyncThunkMethod = new TargetPointer(md.Address); + int asyncDataOffset = (int)(md.Address - chunk.Address) + (int)mdBaseSize; + helpers.Write(chunk.Memory.Span.Slice(asyncDataOffset, sizeof(uint)), (uint)MethodDescFlags_1.AsyncMethodFlags.Thunk); + } + + // Async non-thunk method (not diagnostics hidden, has async data but not Thunk flag) + { + uint mdBaseSize = (uint)methodDescBuilder.MethodDescLayout.Size; + uint totalSize = mdBaseSize + methodDescBuilder.AsyncMethodDataSize; + byte chunkSize = (byte)(totalSize / methodDescBuilder.MethodDescAlignment); + MockMethodDescChunk chunk = methodDescBuilder.AddMethodDescChunk("asyncNonThunk", chunkSize); + chunk.MethodTable = methodTable.Value; + chunk.Size = chunkSize; + chunk.Count = 1; + MockMethodDesc md = chunk.GetMethodDescAtChunkIndex(0, methodDescBuilder.MethodDescLayout); + md.Flags = (ushort)((ushort)MethodClassification.IL | (ushort)MethodDescFlags_1.MethodDescFlags.HasAsyncMethodData); + md.Slot = 1; + asyncNonThunkMethod = new TargetPointer(md.Address); + int asyncDataOffset = (int)(md.Address - chunk.Address) + (int)mdBaseSize; + helpers.Write(chunk.Memory.Span.Slice(asyncDataOffset, sizeof(uint)), (uint)MethodDescFlags_1.AsyncMethodFlags.None); + } + + // Async thunk method with NativeCodeSlot (diagnostics hidden, verifies offset calculation with preceding slots) + { + uint mdBaseSize = (uint)methodDescBuilder.MethodDescLayout.Size; + uint totalSize = mdBaseSize + methodDescBuilder.NativeCodeSlotSize + methodDescBuilder.AsyncMethodDataSize; + byte chunkSize = (byte)(totalSize / methodDescBuilder.MethodDescAlignment); + MockMethodDescChunk chunk = methodDescBuilder.AddMethodDescChunk("asyncThunkWithNCS", chunkSize); + chunk.MethodTable = methodTable.Value; + chunk.Size = chunkSize; + chunk.Count = 1; + MockMethodDesc md = chunk.GetMethodDescAtChunkIndex(0, methodDescBuilder.MethodDescLayout); + md.Flags = (ushort)((ushort)MethodClassification.IL | (ushort)MethodDescFlags_1.MethodDescFlags.HasNativeCodeSlot | (ushort)MethodDescFlags_1.MethodDescFlags.HasAsyncMethodData); + md.Slot = 2; + asyncThunkWithNativeCodeSlotMethod = new TargetPointer(md.Address); + int asyncDataOffset = (int)(md.Address - chunk.Address) + (int)mdBaseSize + (int)methodDescBuilder.NativeCodeSlotSize; + helpers.Write(chunk.Memory.Span.Slice(asyncDataOffset, sizeof(uint)), (uint)MethodDescFlags_1.AsyncMethodFlags.Thunk); + } }); // Normal IL method: not diagnostics hidden, not LCG @@ -552,6 +614,24 @@ public void IsDiagnosticsHidden_ReturnsCorrectValues(MockTarget.Architecture arc Assert.True(rts.IsDiagnosticsHidden(handle)); Assert.False(rts.IsDynamicMethod(handle)); } + + // Async thunk: diagnostics hidden + { + MethodDescHandle handle = rts.GetMethodDescHandle(asyncThunkMethod); + Assert.True(rts.IsDiagnosticsHidden(handle)); + } + + // Async non-thunk: not diagnostics hidden + { + MethodDescHandle handle = rts.GetMethodDescHandle(asyncNonThunkMethod); + Assert.False(rts.IsDiagnosticsHidden(handle)); + } + + // Async thunk with NativeCodeSlot: diagnostics hidden (verifies offset calculation) + { + MethodDescHandle handle = rts.GetMethodDescHandle(asyncThunkWithNativeCodeSlotMethod); + Assert.True(rts.IsDiagnosticsHidden(handle)); + } } private static TargetPointer AddMethodTable(MockDescriptors.RuntimeTypeSystem rtsBuilder, ushort numVirtuals = 5) From b1639c281e698b8a7cbd8ddf148f1c7fdfe50157 Mon Sep 17 00:00:00 2001 From: Barbara Rosiak Date: Thu, 16 Apr 2026 15:38:30 -0700 Subject: [PATCH 4/8] Add cdac use annotation --- src/coreclr/vm/method.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index 9188d081cd5ee4..e187aa39bbaf0b 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -64,6 +64,7 @@ EXTERN_C VOID STDCALL PInvokeImportThunk(); #define FEATURE_DYNAMIC_METHOD_HAS_NATIVE_STACK_ARG_SIZE #endif +// [cDAC] [RuntimeTypeSystem]: Contract depends on the values of Thunk, None. enum class AsyncMethodFlags { // Method uses CORINFO_CALLCONV_ASYNCCALL call convention. From 07b4e4a18b33774c3480e9a2723f23359bd39075 Mon Sep 17 00:00:00 2001 From: Barbara Rosiak Date: Thu, 16 Apr 2026 15:48:18 -0700 Subject: [PATCH 5/8] Address copilot pr comments --- src/coreclr/vm/datadescriptor/datadescriptor.inc | 2 +- .../RuntimeTypeSystemHelpers/MethodDescOptionalSlots.cs | 2 +- .../Dbi/DacDbiImpl.cs | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index aea4c8533124ec..e9994627507d2e 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -559,7 +559,7 @@ CDAC_TYPE_END(NativeCodeSlot) CDAC_TYPE_BEGIN(AsyncMethodData) CDAC_TYPE_SIZE(sizeof(AsyncMethodData)) -CDAC_TYPE_FIELD(AsyncMethodData, /*uint32*/, Flags, offsetof(AsyncMethodData, flags)) +CDAC_TYPE_FIELD(AsyncMethodData, T_UINT32, Flags, offsetof(AsyncMethodData, flags)) CDAC_TYPE_END(AsyncMethodData) CDAC_TYPE_BEGIN(InstantiatedMethodDesc) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescOptionalSlots.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescOptionalSlots.cs index 16ba95cc85a7a0..afd5075105dd50 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescOptionalSlots.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescOptionalSlots.cs @@ -4,7 +4,7 @@ namespace Microsoft.Diagnostics.DataContractReader.RuntimeTypeSystemHelpers; -// Non-vtable slot, native code slot, MethodImpl, and async method data slots are stored after the MethodDesc itself, packed tightly +// Optional slots are stored after the MethodDesc itself, packed tightly // in the order: [non-vtable; method impl; native code; async method data]. internal static class MethodDescOptionalSlots { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs index d6e8911f3525ac..9c76a7c5ff7713 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs @@ -19,9 +19,9 @@ public sealed unsafe partial class DacDbiImpl : IDacDbiInterface private enum DynamicMethodType { - kNone, - kDiagnosticHidden, - kLCGMethod, + kNone = 0, + kDiagnosticHidden = 1, + kLCGMethod = 2, } // IStringHolder is a native C++ abstract class (not COM) with a single virtual method: From 20920fd4ae2ebe5e7a34c0860abaf02193f4f397 Mon Sep 17 00:00:00 2001 From: Barbara Rosiak <76071368+barosiak@users.noreply.github.com> Date: Fri, 17 Apr 2026 15:49:48 -0700 Subject: [PATCH 6/8] Apply suggestions from code review Co-authored-by: Noah Falk Co-authored-by: Rachel --- docs/design/datacontracts/RuntimeTypeSystem.md | 2 +- src/coreclr/debug/inc/dacdbiinterface.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index 27a9b0a7c184d8..dc3d5758ad53d6 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -218,7 +218,7 @@ partial interface IRuntimeTypeSystem : IContract // Returns true if the method is eligible for tiered compilation public virtual bool IsEligibleForTieredCompilation(MethodDescHandle methodDesc); - // [cDAC] Return true if the method should be hidden from diagnostic stack traces. + // Return true if the method should be hidden from diagnostic stack traces. public virtual bool IsDiagnosticsHidden(MethodDescHandle methodDesc); } diff --git a/src/coreclr/debug/inc/dacdbiinterface.h b/src/coreclr/debug/inc/dacdbiinterface.h index 225466f93a8954..b5cccc1b0a335b 100644 --- a/src/coreclr/debug/inc/dacdbiinterface.h +++ b/src/coreclr/debug/inc/dacdbiinterface.h @@ -1415,7 +1415,6 @@ IDacDbiInterface : public IUnknown virtual HRESULT STDMETHODCALLTYPE ConvertContextToDebuggerRegDisplay(const DT_CONTEXT * pInContext, DebuggerREGDISPLAY * pOutDRD, BOOL fActive) = 0; - // [cDAC] [DacDbiImpl]: Contract depends on these values. typedef enum { kNone, From 5aed49bb4c0375a7428ed6d6e399394f5bc9f7fb Mon Sep 17 00:00:00 2001 From: Barbara Rosiak Date: Fri, 17 Apr 2026 16:06:09 -0700 Subject: [PATCH 7/8] Address pr comments --- .../design/datacontracts/RuntimeTypeSystem.md | 36 +++++++++++-- .../Contracts/IRuntimeTypeSystem.cs | 4 +- .../Contracts/RuntimeTypeSystem_1.cs | 53 ++++++++++--------- .../MethodDescFlags_1.cs | 7 --- .../Dbi/DacDbiImpl.cs | 9 +--- .../Dbi/IDacDbiInterface.cs | 7 +++ .../managed/cdac/tests/MethodDescTests.cs | 42 ++++++++------- 7 files changed, 93 insertions(+), 65 deletions(-) diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index dc3d5758ad53d6..120fc3aa3cbea6 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -218,8 +218,11 @@ partial interface IRuntimeTypeSystem : IContract // Returns true if the method is eligible for tiered compilation public virtual bool IsEligibleForTieredCompilation(MethodDescHandle methodDesc); - // Return true if the method should be hidden from diagnostic stack traces. - public virtual bool IsDiagnosticsHidden(MethodDescHandle methodDesc); + // Return true if the method is an async thunk method. + public virtual bool IsAsyncThunkMethod(MethodDescHandle methodDesc); + + // Return true if the method is a wrapper stub (unboxing or instantiating). + public virtual bool IsWrapperStub(MethodDescHandle methodDesc); } ``` @@ -1101,6 +1104,13 @@ And the following enumeration definitions ILStubTypeMask = 0x000007FF, } + [Flags] + internal enum AsyncMethodFlags : uint + { + None = 0, + Thunk = 16, + } + [Flags] internal enum ILStubType : uint { @@ -1506,13 +1516,29 @@ Determining if a method supports multiple code versions: } ``` -Determining if a method should be hidden from diagnostic stack traces: +Determining if a method is an async thunk method: ```csharp - public bool IsDiagnosticsHidden(MethodDescHandle methodDescHandle) + public bool IsAsyncThunkMethod(MethodDescHandle methodDescHandle) { MethodDesc md = _methodDescs[methodDescHandle.Address]; - return IsILStub(md) || IsAsyncThunkMethod(md) || IsWrapperStub(md); + if (!md.HasAsyncMethodData) + { + return false; + } + + Data.AsyncMethodData asyncData = // Read AsyncMethodData from the address of the async method data optional slot + return ((AsyncMethodFlags)asyncData.Flags).HasFlag(AsyncMethodFlags.Thunk); + } +``` + +Determining if a method is a wrapper stub (unboxing or instantiating): + +```csharp + public bool IsWrapperStub(MethodDescHandle methodDescHandle) + { + MethodDesc md = _methodDescs[methodDescHandle.Address]; + return md.IsUnboxingStub || IsInstantiatingStub(md); } ``` diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs index b3c5ec9e09e7fc..501443d5a80362 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs @@ -226,7 +226,9 @@ public interface IRuntimeTypeSystem : IContract OptimizationTier GetMethodDescOptimizationTier(MethodDescHandle methodDescHandle) => throw new NotImplementedException(); bool IsEligibleForTieredCompilation(MethodDescHandle methodDescHandle) => throw new NotImplementedException(); - bool IsDiagnosticsHidden(MethodDescHandle methodDesc) => throw new NotImplementedException(); + bool IsAsyncThunkMethod(MethodDescHandle methodDesc) => throw new NotImplementedException(); + + bool IsWrapperStub(MethodDescHandle methodDesc) => throw new NotImplementedException(); #endregion MethodDesc inspection APIs #region FieldDesc inspection APIs TargetPointer GetMTOfEnclosingClass(TargetPointer fieldDescPointer) => throw new NotImplementedException(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index 019afcb0ed1508..2dfd837614aba7 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -151,6 +151,13 @@ internal enum DynamicMethodDescExtendedFlags : uint ILStubTypeMask = 0x000007FF, } + [Flags] + internal enum AsyncMethodFlags : uint + { + None = 0, + Thunk = 16, + } + [Flags] internal enum ILStubType : uint { @@ -1322,8 +1329,13 @@ public bool IsIL(MethodDescHandle methodDescHandle) public bool IsILStub(MethodDescHandle methodDescHandle) { - MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; - return IsILStub(methodDesc); + MethodDesc md = _methodDescs[methodDescHandle.Address]; + if (md.Classification != MethodClassification.Dynamic) + { + return false; + } + + return AsDynamicMethodDesc(md).IsILStub; } public bool HasMDContextArg(MethodDescHandle methodDescHandle) @@ -1398,27 +1410,6 @@ private TargetPointer GetAddressOfSlot(TypeHandle typeHandle, uint slotNum) } } - private bool IsILStub(MethodDesc md) - { - if (md.Classification != MethodClassification.Dynamic) - { - return false; - } - - return AsDynamicMethodDesc(md).IsILStub; - } - - private bool IsAsyncThunkMethod(MethodDesc md) - { - if (!md.HasAsyncMethodData) - { - return false; - } - - Data.AsyncMethodData asyncData = _target.ProcessedData.GetOrAdd(md.GetAddressOfAsyncMethodData()); - return ((MethodDescFlags_1.AsyncMethodFlags)asyncData.Flags).HasFlag(MethodDescFlags_1.AsyncMethodFlags.Thunk); - } - private bool IsWrapperStub(MethodDesc md) { return md.IsUnboxingStub || IsInstantiatingStub(md); @@ -1745,10 +1736,22 @@ bool IRuntimeTypeSystem.IsEligibleForTieredCompilation(MethodDescHandle methodDe return methodDesc.IsEligibleForTieredCompilation; } - bool IRuntimeTypeSystem.IsDiagnosticsHidden(MethodDescHandle methodDescHandle) + public bool IsAsyncThunkMethod(MethodDescHandle methodDescHandle) + { + MethodDesc md = _methodDescs[methodDescHandle.Address]; + if (!md.HasAsyncMethodData) + { + return false; + } + + Data.AsyncMethodData asyncData = _target.ProcessedData.GetOrAdd(md.GetAddressOfAsyncMethodData()); + return ((AsyncMethodFlags)asyncData.Flags).HasFlag(AsyncMethodFlags.Thunk); + } + + public bool IsWrapperStub(MethodDescHandle methodDescHandle) { MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; - return IsILStub(methodDesc) || IsAsyncThunkMethod(methodDesc) || IsWrapperStub(methodDesc); + return IsWrapperStub(methodDesc); } private sealed class NonValidatedMethodTableQueries : MethodValidation.IMethodTableQueries diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescFlags_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescFlags_1.cs index 643ebb6582923a..901d9331c8938f 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescFlags_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodDescFlags_1.cs @@ -34,11 +34,4 @@ internal enum MethodDescEntryPointFlags : byte { TemporaryEntryPointAssigned = 0x04, } - - [Flags] - internal enum AsyncMethodFlags : uint - { - None = 0, - Thunk = 16, - } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs index 846678898c41ae..dc86a868c98370 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs @@ -17,13 +17,6 @@ public sealed unsafe partial class DacDbiImpl : IDacDbiInterface private readonly Target _target; private readonly IDacDbiInterface? _legacy; - private enum DynamicMethodType - { - kNone = 0, - kDiagnosticHidden = 1, - kLCGMethod = 2, - } - // IStringHolder is a native C++ abstract class (not COM) with a single virtual method: // virtual HRESULT AssignCopy(const WCHAR* psz) = 0; // The nint we receive is a pointer to the object, whose first field is the vtable pointer. @@ -581,7 +574,7 @@ public int IsDiagnosticsHiddenOrLCGMethod(ulong vmMethodDesc, int* pRetVal) { Contracts.IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem; Contracts.MethodDescHandle md = rts.GetMethodDescHandle(new TargetPointer(vmMethodDesc)); - if (rts.IsDiagnosticsHidden(md)) + if (rts.IsILStub(md) || rts.IsAsyncThunkMethod(md) || rts.IsWrapperStub(md)) { *pRetVal = (int)DynamicMethodType.kDiagnosticHidden; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs index d26605e3b86f5a..8ea39f9d13b07a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs @@ -138,6 +138,13 @@ public struct COR_FIELD #pragma warning restore CS0649 +public enum DynamicMethodType +{ + kNone = 0, + kDiagnosticHidden = 1, + kLCGMethod = 2, +} + // Name-surface projection of IDacDbiInterface in native method order for COM binding validation. // Parameter shapes are intentionally coarse placeholders and will be refined with method implementation work. [GeneratedComInterface] diff --git a/src/native/managed/cdac/tests/MethodDescTests.cs b/src/native/managed/cdac/tests/MethodDescTests.cs index 04e6672bafeda4..d28673f51cef4a 100644 --- a/src/native/managed/cdac/tests/MethodDescTests.cs +++ b/src/native/managed/cdac/tests/MethodDescTests.cs @@ -542,7 +542,7 @@ public void IsDiagnosticsHidden_ReturnsCorrectValues(MockTarget.Architecture arc md.Flags = (ushort)((ushort)MethodClassification.IL | (ushort)MethodDescFlags_1.MethodDescFlags.HasAsyncMethodData); asyncThunkMethod = new TargetPointer(md.Address); int asyncDataOffset = (int)(md.Address - chunk.Address) + (int)mdBaseSize; - helpers.Write(chunk.Memory.Span.Slice(asyncDataOffset, sizeof(uint)), (uint)MethodDescFlags_1.AsyncMethodFlags.Thunk); + helpers.Write(chunk.Memory.Span.Slice(asyncDataOffset, sizeof(uint)), (uint)RuntimeTypeSystem_1.AsyncMethodFlags.Thunk); } // Async non-thunk method (not diagnostics hidden, has async data but not Thunk flag) @@ -559,7 +559,7 @@ public void IsDiagnosticsHidden_ReturnsCorrectValues(MockTarget.Architecture arc md.Slot = 1; asyncNonThunkMethod = new TargetPointer(md.Address); int asyncDataOffset = (int)(md.Address - chunk.Address) + (int)mdBaseSize; - helpers.Write(chunk.Memory.Span.Slice(asyncDataOffset, sizeof(uint)), (uint)MethodDescFlags_1.AsyncMethodFlags.None); + helpers.Write(chunk.Memory.Span.Slice(asyncDataOffset, sizeof(uint)), (uint)RuntimeTypeSystem_1.AsyncMethodFlags.None); } // Async thunk method with NativeCodeSlot (diagnostics hidden, verifies offset calculation with preceding slots) @@ -576,61 +576,65 @@ public void IsDiagnosticsHidden_ReturnsCorrectValues(MockTarget.Architecture arc md.Slot = 2; asyncThunkWithNativeCodeSlotMethod = new TargetPointer(md.Address); int asyncDataOffset = (int)(md.Address - chunk.Address) + (int)mdBaseSize + (int)methodDescBuilder.NativeCodeSlotSize; - helpers.Write(chunk.Memory.Span.Slice(asyncDataOffset, sizeof(uint)), (uint)MethodDescFlags_1.AsyncMethodFlags.Thunk); + helpers.Write(chunk.Memory.Span.Slice(asyncDataOffset, sizeof(uint)), (uint)RuntimeTypeSystem_1.AsyncMethodFlags.Thunk); } }); - // Normal IL method: not diagnostics hidden, not LCG + // Normal IL method: not hidden by any primitive { MethodDescHandle handle = rts.GetMethodDescHandle(normalMethod); - Assert.False(rts.IsDiagnosticsHidden(handle)); + Assert.False(rts.IsILStub(handle)); + Assert.False(rts.IsAsyncThunkMethod(handle)); + Assert.False(rts.IsWrapperStub(handle)); Assert.False(rts.IsDynamicMethod(handle)); } - // IL stub: diagnostics hidden + // IL stub: hidden via IsILStub { MethodDescHandle handle = rts.GetMethodDescHandle(ilStubMethod); - Assert.True(rts.IsDiagnosticsHidden(handle)); + Assert.True(rts.IsILStub(handle)); Assert.False(rts.IsDynamicMethod(handle)); } - // LCG method: not diagnostics hidden, is LCG + // LCG method: not hidden, is LCG { MethodDescHandle handle = rts.GetMethodDescHandle(lcgMethod); - Assert.False(rts.IsDiagnosticsHidden(handle)); + Assert.False(rts.IsILStub(handle)); + Assert.False(rts.IsAsyncThunkMethod(handle)); + Assert.False(rts.IsWrapperStub(handle)); Assert.True(rts.IsDynamicMethod(handle)); } - // Unboxing stub: diagnostics hidden (wrapper stub) + // Unboxing stub: hidden via IsWrapperStub { MethodDescHandle handle = rts.GetMethodDescHandle(unboxingStubMethod); - Assert.True(rts.IsDiagnosticsHidden(handle)); + Assert.True(rts.IsWrapperStub(handle)); Assert.False(rts.IsDynamicMethod(handle)); } - // Instantiating stub: diagnostics hidden (wrapper stub) + // Instantiating stub: hidden via IsWrapperStub { MethodDescHandle handle = rts.GetMethodDescHandle(instantiatingStubMethod); - Assert.True(rts.IsDiagnosticsHidden(handle)); + Assert.True(rts.IsWrapperStub(handle)); Assert.False(rts.IsDynamicMethod(handle)); } - // Async thunk: diagnostics hidden + // Async thunk: hidden via IsAsyncThunkMethod { MethodDescHandle handle = rts.GetMethodDescHandle(asyncThunkMethod); - Assert.True(rts.IsDiagnosticsHidden(handle)); + Assert.True(rts.IsAsyncThunkMethod(handle)); } - // Async non-thunk: not diagnostics hidden + // Async non-thunk: not hidden { MethodDescHandle handle = rts.GetMethodDescHandle(asyncNonThunkMethod); - Assert.False(rts.IsDiagnosticsHidden(handle)); + Assert.False(rts.IsAsyncThunkMethod(handle)); } - // Async thunk with NativeCodeSlot: diagnostics hidden (verifies offset calculation) + // Async thunk with NativeCodeSlot: hidden via IsAsyncThunkMethod (verifies offset calculation) { MethodDescHandle handle = rts.GetMethodDescHandle(asyncThunkWithNativeCodeSlotMethod); - Assert.True(rts.IsDiagnosticsHidden(handle)); + Assert.True(rts.IsAsyncThunkMethod(handle)); } } From 38b3673d2b76fdc3d41a595c6085b8051bea3123 Mon Sep 17 00:00:00 2001 From: Barbara Rosiak Date: Fri, 17 Apr 2026 17:25:34 -0700 Subject: [PATCH 8/8] Rename local variable --- .../Contracts/RuntimeTypeSystem_1.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index 2dfd837614aba7..128fba5aa83017 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -1329,13 +1329,13 @@ public bool IsIL(MethodDescHandle methodDescHandle) public bool IsILStub(MethodDescHandle methodDescHandle) { - MethodDesc md = _methodDescs[methodDescHandle.Address]; - if (md.Classification != MethodClassification.Dynamic) + MethodDesc methodDesc = _methodDescs[methodDescHandle.Address]; + if (methodDesc.Classification != MethodClassification.Dynamic) { return false; } - return AsDynamicMethodDesc(md).IsILStub; + return AsDynamicMethodDesc(methodDesc).IsILStub; } public bool HasMDContextArg(MethodDescHandle methodDescHandle)