From 9402642255a9302d2fae5f49c0bff15d50dcdc96 Mon Sep 17 00:00:00 2001 From: Barbara Rosiak Date: Tue, 21 Jul 2026 16:36:54 -0700 Subject: [PATCH 1/2] Implement IsThreadSuspendedOrHijacked for cDAC --- .../Dbi/DacDbiImpl.cs | 25 ++++++++++++++++++- .../DumpTests/DacDbi/DacDbiThreadDumpTests.cs | 25 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) 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 214f0365a77136..4cdeabfbddd785 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 @@ -4175,7 +4175,30 @@ public int GetModuleMetaDataFileInfo(ulong vmModule, uint* dwTimeStamp, uint* dw } public int IsThreadSuspendedOrHijacked(ulong vmThread, Interop.BOOL* pResult) - => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.IsThreadSuspendedOrHijacked(vmThread, pResult) : HResults.E_NOTIMPL; + { + int hr = HResults.S_OK; + try + { + Contracts.ThreadState threadState = _target.Contracts.Thread.GetThreadData(vmThread).State; + *pResult = (threadState & (Contracts.ThreadState.DebugSyncSuspended | Contracts.ThreadState.Hijacked)) != 0 + ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + Interop.BOOL pResultLocal; + int hrLocal = _legacy.IsThreadSuspendedOrHijacked(vmThread, &pResultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pResult == pResultLocal); + } +#endif + return hr; + } public int CreateHeapWalk(nuint* pHandle) { diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiThreadDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiThreadDumpTests.cs index 033e23eb026842..f641c93755cb57 100644 --- a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiThreadDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiThreadDumpTests.cs @@ -81,6 +81,31 @@ public unsafe void IsThreadMarkedDead_CrossValidateWithContract(TestConfiguratio } } + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public unsafe void IsThreadSuspendedOrHijacked_CrossValidateWithContract(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + IThread threadContract = Target.Contracts.Thread; + ThreadStoreData storeData = threadContract.GetThreadStoreData(); + + TargetPointer current = storeData.FirstThread; + while (current != TargetPointer.Null) + { + Interop.BOOL isSuspendedOrHijacked; + int hr = dbi.IsThreadSuspendedOrHijacked(current, &isSuspendedOrHijacked); + Assert.Equal(System.HResults.S_OK, hr); + + ThreadData data = threadContract.GetThreadData(current); + bool contractSaysSuspendedOrHijacked = (data.State & (Contracts.ThreadState.DebugSyncSuspended | Contracts.ThreadState.Hijacked)) != 0; + Assert.Equal(contractSaysSuspendedOrHijacked, isSuspendedOrHijacked == Interop.BOOL.TRUE); + + current = data.NextThread; + } + } + [ConditionalTheory] [MemberData(nameof(TestConfigurations))] public unsafe void TryGetVolatileOSThreadID_MatchesContract(TestConfiguration config) From 3d58bb42d2fb0f9af547a35aba61742a91dbc541 Mon Sep 17 00:00:00 2001 From: Noah Falk Date: Thu, 23 Jul 2026 16:37:42 -0700 Subject: [PATCH 2/2] Code review feedback --- .../Dbi/DacDbiImpl.cs | 11 ++++-- .../cdac/tests/UnitTests/DacDbiImplTests.cs | 37 +++++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) 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 4cdeabfbddd785..85c512c7b6c4a2 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 @@ -4179,6 +4179,10 @@ public int IsThreadSuspendedOrHijacked(ulong vmThread, Interop.BOOL* pResult) int hr = HResults.S_OK; try { + if (pResult is null) + throw new NullReferenceException(); + + *pResult = Interop.BOOL.FALSE; Contracts.ThreadState threadState = _target.Contracts.Thread.GetThreadData(vmThread).State; *pResult = (threadState & (Contracts.ThreadState.DebugSyncSuspended | Contracts.ThreadState.Hijacked)) != 0 ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; @@ -4190,11 +4194,12 @@ public int IsThreadSuspendedOrHijacked(ulong vmThread, Interop.BOOL* pResult) #if DEBUG if (_legacy is not null) { - Interop.BOOL pResultLocal; - int hrLocal = _legacy.IsThreadSuspendedOrHijacked(vmThread, &pResultLocal); + Interop.BOOL resultLocal = Interop.BOOL.FALSE; + Interop.BOOL* resultLocalPtr = pResult is null ? null : &resultLocal; + int hrLocal = _legacy.IsThreadSuspendedOrHijacked(vmThread, resultLocalPtr); Debug.ValidateHResult(hr, hrLocal); if (hr == HResults.S_OK) - Debug.Assert(*pResult == pResultLocal); + Debug.Assert(*pResult == resultLocal); } #endif return hr; diff --git a/src/native/managed/cdac/tests/UnitTests/DacDbiImplTests.cs b/src/native/managed/cdac/tests/UnitTests/DacDbiImplTests.cs index ab85a54c569667..61ea498a93fc7c 100644 --- a/src/native/managed/cdac/tests/UnitTests/DacDbiImplTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/DacDbiImplTests.cs @@ -37,6 +37,43 @@ private static (DacDbiImpl DacDbi, TestPlaceholderTarget Target) CreateDacDbiWit return (dacDbi, target); } + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void IsThreadSuspendedOrHijacked_NullOutput_ReturnsEPointer(MockTarget.Architecture arch) + { + var mockThread = new Mock(); + TestPlaceholderTarget target = new TestPlaceholderTarget.Builder(arch) + .AddMockContract(mockThread) + .Build(); + DacDbiImpl dacDbi = new(target, legacyObj: null); + + int hr = dacDbi.IsThreadSuspendedOrHijacked(0, null); + + Assert.Equal(System.HResults.E_POINTER, hr); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void IsThreadSuspendedOrHijacked_ContractFailure_InitializesOutput(MockTarget.Architecture arch) + { + const ulong ThreadAddress = 0x1000; + InvalidOperationException exception = new(); + var mockThread = new Mock(); + mockThread + .Setup(t => t.GetThreadData(new TargetPointer(ThreadAddress))) + .Throws(exception); + TestPlaceholderTarget target = new TestPlaceholderTarget.Builder(arch) + .AddMockContract(mockThread) + .Build(); + DacDbiImpl dacDbi = new(target, legacyObj: null); + Interop.BOOL result = Interop.BOOL.TRUE; + + int hr = dacDbi.IsThreadSuspendedOrHijacked(ThreadAddress, &result); + + Assert.Equal(exception.HResult, hr); + Assert.Equal(Interop.BOOL.FALSE, result); + } + [Fact] public void DacSetTargetConsistencyChecks_Standalone_ReturnsSuccess() {