diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index db9423528f089f..54d31a3cdeb424 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -6052,7 +6052,8 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetThreadOwningMonitorLock(VMPTR_ DWORD threadId = 0; DWORD recursionCount = 0; - BOOL isLockHeld = pObj->GetHeader()->PassiveGetSyncBlock()->TryGetLockInfo(&threadId, &recursionCount); + SyncBlock* psb = pObj->GetHeader()->PassiveGetSyncBlock(); + BOOL isLockHeld = psb != NULL && psb->TryGetLockInfo(&threadId, &recursionCount); if (!isLockHeld) { 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 0bcbc3fededf64..5748401c6b3b70 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 @@ -3131,7 +3131,53 @@ public int GetHandleAddressFromVmHandle(ulong vmHandle, ulong* pRetVal) } public int GetThreadOwningMonitorLock(ulong vmObject, DacDbiMonitorLockInfo* pRetVal) - => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetThreadOwningMonitorLock(vmObject, pRetVal) : HResults.E_NOTIMPL; + { + int hr = HResults.S_OK; + *pRetVal = default; + try + { + DacDbiMonitorLockInfo info = default; + uint threadId = 0; + uint recursionCount = 0; + TargetPointer syncBlock = _target.Contracts.Object.GetSyncBlockAddress(vmObject); + + if (syncBlock == TargetPointer.Null || !_target.Contracts.SyncBlock.TryGetLockInfo(syncBlock, out threadId, out recursionCount)) + { + *pRetVal = info; + } + else + { + TargetPointer threadPtr = _target.Contracts.Thread.IdToThread(threadId); + Debug.Assert(threadPtr != TargetPointer.Null, "A thread should have been found"); + if (threadPtr != TargetPointer.Null) + { + info.lockOwner = threadPtr; + info.acquisitionCount = recursionCount + 1; // The runtime tracks recursion count starting at 0, but diagnostics users expect it to start at 1. + } + *pRetVal = info; + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + DacDbiMonitorLockInfo pRetValLocal; + int hrLocal = _legacy.GetThreadOwningMonitorLock(vmObject, &pRetValLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + { + Debug.Assert(pRetVal->lockOwner == pRetValLocal.lockOwner, + $"lockOwner mismatch: cDAC={pRetVal->lockOwner}, DAC={pRetValLocal.lockOwner}"); + Debug.Assert(pRetVal->acquisitionCount == pRetValLocal.acquisitionCount, + $"acquisitionCount mismatch: cDAC={pRetVal->acquisitionCount}, DAC={pRetValLocal.acquisitionCount}"); + } + } +#endif + return hr; + } public int EnumerateMonitorEventWaitList(ulong vmObject, nint fpCallback, nint pUserData) => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.EnumerateMonitorEventWaitList(vmObject, fpCallback, pUserData) : HResults.E_NOTIMPL; diff --git a/src/native/managed/cdac/tests/UnitTests/DacDbiImplTests.cs b/src/native/managed/cdac/tests/UnitTests/DacDbiImplTests.cs index ea5a0c6f70a7af..291e498a3e6c97 100644 --- a/src/native/managed/cdac/tests/UnitTests/DacDbiImplTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/DacDbiImplTests.cs @@ -720,6 +720,74 @@ public void AreOptimizationsDisabled_WithMethodDesc(MockTarget.Architecture arch Assert.Equal(deoptimized ? Interop.BOOL.TRUE : Interop.BOOL.FALSE, result); } + private delegate void TryGetLockInfoCallback(TargetPointer syncBlock, out uint owningThreadId, out uint recursion); + + public static IEnumerable GetThreadOwningMonitorLockData() + { + foreach (var arch in new MockTarget.StdArch()) + { + yield return new object[] { arch[0], false, false, 0u, 0ul, 0u }; + yield return new object[] { arch[0], true, false, 0u, 0ul, 0u }; + yield return new object[] { arch[0], true, true, 3u, 0x7000ul, 4u }; + } + } + + [Theory] + [MemberData(nameof(GetThreadOwningMonitorLockData))] + public void GetThreadOwningMonitorLock(MockTarget.Architecture arch, bool hasSyncBlock, bool isLockHeld, uint recursionCount, ulong expectedOwner, uint expectedAcquisitionCount) + { + const ulong ObjectAddr = 0x5000; + const uint OwnerThreadId = 42; + TargetPointer syncBlockAddr = new(0x6000); + TargetPointer ownerThreadPtr = new(0x7000); + + var mockObject = new Mock(); + mockObject.Setup(o => o.GetSyncBlockAddress(new TargetPointer(ObjectAddr))) + .Returns(hasSyncBlock ? syncBlockAddr : TargetPointer.Null); + + var builder = new TestPlaceholderTarget.Builder(arch) + .UseReader((_, _) => -1) + .AddMockContract(mockObject); + + if (hasSyncBlock) + { + var mockSyncBlock = new Mock(); + var lockSetup = mockSyncBlock + .Setup(s => s.TryGetLockInfo(syncBlockAddr, out It.Ref.IsAny, out It.Ref.IsAny)); + if (isLockHeld) + { + lockSetup + .Callback(new TryGetLockInfoCallback((TargetPointer _, out uint threadId, out uint recursion) => + { + threadId = OwnerThreadId; + recursion = recursionCount; + })) + .Returns(true); + } + else + { + lockSetup.Returns(false); + } + builder.AddMockContract(mockSyncBlock); + } + + if (isLockHeld) + { + var mockThread = new Mock(); + mockThread.Setup(t => t.IdToThread(OwnerThreadId)) + .Returns(ownerThreadPtr); + builder.AddMockContract(mockThread); + } + + var dacDbi = new DacDbiImpl(builder.Build(), legacyObj: null); + + DacDbiMonitorLockInfo result; + int hr = dacDbi.GetThreadOwningMonitorLock(ObjectAddr, &result); + Assert.Equal(System.HResults.S_OK, hr); + Assert.Equal(expectedOwner, result.lockOwner); + Assert.Equal(expectedAcquisitionCount, result.acquisitionCount); + } + [Theory] [ClassData(typeof(MockTarget.StdArch))] public void AreOptimizationsDisabled_NullMethodDesc_ReturnsFalse(MockTarget.Architecture arch)