Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3180,7 +3180,90 @@ public int GetThreadOwningMonitorLock(ulong vmObject, DacDbiMonitorLockInfo* pRe
}

public int EnumerateMonitorEventWaitList(ulong vmObject, nint fpCallback, nint pUserData)
=> LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.EnumerateMonitorEventWaitList(vmObject, fpCallback, pUserData) : HResults.E_NOTIMPL;
{
int hr = HResults.S_OK;
#if DEBUG
List<ulong>? cdacThreads = _legacy is not null ? new() : null;
#endif
try
{
var callback = (delegate* unmanaged<ulong, nint, void>)fpCallback;
if (callback == null)
throw new ArgumentNullException(nameof(fpCallback));

TargetPointer syncBlock = _target.Contracts.Object.GetSyncBlockAddress(vmObject);

// no sync block means no wait list
if (syncBlock != TargetPointer.Null
&& _target.Contracts.ManagedTypeSource.TryGetStaticFieldAddress(
"System.Threading.Monitor", "s_conditionTable", out TargetPointer conditionTableSlot))
{
TargetPointer conditionTable = _target.ReadPointer(conditionTableSlot);

if (conditionTable != TargetPointer.Null
&& _target.Contracts.ConditionalWeakTable.TryGetValue(conditionTable, new TargetPointer(vmObject), out TargetPointer condition))
{
// Build a map of Waiter objects to their owning Threads.
var waiterToThreadMap = new Dictionary<ulong, TargetPointer>();
Contracts.IThread threadContract = _target.Contracts.Thread;
Contracts.ThreadStoreData threadStore = threadContract.GetThreadStoreData();
TargetPointer currentThread = threadStore.FirstThread;
while (currentThread != TargetPointer.Null)
{
Contracts.ThreadData threadData = threadContract.GetThreadData(currentThread);

if (_target.Contracts.ManagedTypeSource.TryGetThreadStaticFieldAddress(
"System.Threading.Condition", "t_waiterForCurrentThread", currentThread, out TargetPointer waiterSlot))
{
TargetPointer waiterObj = _target.ReadPointer(waiterSlot);
if (waiterObj != TargetPointer.Null)
Comment on lines +3206 to +3219
waiterToThreadMap[waiterObj.Value] = currentThread;
}

currentThread = threadData.NextThread;
}

// Walk the waiters linked list and invoke callback for each matching thread
Target.TypeInfo conditionType = _target.Contracts.ManagedTypeSource.GetTypeInfo("System.Threading.Condition");
TargetPointer waiter = _target.ReadPointer(condition + (ulong)conditionType.Fields["_waitersHead"].Offset);

Target.TypeInfo waiterType = _target.Contracts.ManagedTypeSource.GetTypeInfo("System.Threading.Condition+Waiter");
while (waiter != TargetPointer.Null)
{
if (waiterToThreadMap.TryGetValue(waiter.Value, out TargetPointer thread))
{
callback(thread.Value, pUserData);
#if DEBUG
cdacThreads?.Add(thread.Value);
#endif
}

waiter = _target.ReadPointer(waiter + (ulong)waiterType.Fields["next"].Offset);
}
}
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}
#if DEBUG
if (_legacy is not null && fpCallback != 0)
List<ulong> dacThreads = new();
GCHandle dacHandle = GCHandle.Alloc(dacThreads);
int hrLocal = _legacy.EnumerateMonitorEventWaitList(vmObject, (nint)(delegate* unmanaged<ulong, nint, void>)&CollectEnumerationCallback, GCHandle.ToIntPtr(dacHandle));
dacHandle.Free();
Debug.ValidateHResult(hr, hrLocal);
if (hr == HResults.S_OK)
{
Debug.Assert(
cdacThreads!.SequenceEqual(dacThreads),
$"MonitorEventWaitList mismatch - cDAC: [{string.Join(",", cdacThreads!.Select(t => $"0x{t:x}"))}], DAC: [{string.Join(",", dacThreads.Select(t => $"0x{t:x}"))}]");
}
}
#endif
return hr;
}

public int GetAttachStateFlags(int* pRetVal)
{
Expand Down
224 changes: 224 additions & 0 deletions src/native/managed/cdac/tests/UnitTests/DacDbiImplTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.Diagnostics.DataContractReader.Contracts;
using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers;
Expand Down Expand Up @@ -901,4 +902,227 @@ public void GetManagedStoppedContext_NoContextAvailable(MockTarget.Architecture
Assert.Equal(System.HResults.S_OK, hr);
Assert.Equal(0UL, retVal);
}

private const int ConditionWaitersHeadOffset = 8;
private const int WaiterNextOffset = 8;

private static (DacDbiImpl DacDbi, TestPlaceholderTarget Target) CreateMonitorWaitListDacDbi(
MockTarget.Architecture arch,
TargetPointer objectAddr,
TargetPointer syncBlockAddr,
TargetPointer conditionTableAddr,
bool cwtFindsCondition,
(TargetPointer Thread, bool IsWaiting)[]? threads = null)
{
var helpers = new TargetTestHelpers(arch);
int ptrSize = arch.Is64Bit ? 8 : 4;
var builder = new TestPlaceholderTarget.Builder(arch);
var allocator = builder.MemoryBuilder.CreateAllocator(0x0040_0000, 0x0040_F000);
var mockMts = new Mock<IManagedTypeSource>();

var globalFragment = allocator.Allocate((ulong)ptrSize, "ConditionTableGlobal");
helpers.WritePointer(globalFragment.Data, conditionTableAddr.Value);
TargetPointer conditionTableSlotAddr = new(globalFragment.Address);
mockMts.Setup(m => m.TryGetStaticFieldAddress(
"System.Threading.Monitor", "s_conditionTable", out conditionTableSlotAddr))
.Returns(true);

mockMts.Setup(m => m.GetTypeInfo("System.Threading.Condition")).Returns(new Target.TypeInfo
{
Size = (uint)(ConditionWaitersHeadOffset + ptrSize),
Fields = new Dictionary<string, Target.FieldInfo>
{
["_waitersHead"] = new Target.FieldInfo { Offset = ConditionWaitersHeadOffset, TypeName = null },
}
});
mockMts.Setup(m => m.GetTypeInfo("System.Threading.Condition+Waiter")).Returns(new Target.TypeInfo
{
Size = (uint)(WaiterNextOffset + ptrSize),
Fields = new Dictionary<string, Target.FieldInfo>
{
["next"] = new Target.FieldInfo { Offset = WaiterNextOffset, TypeName = null },
}
});

var waitingThreads = threads?.Where(t => t.IsWaiting).ToArray() ?? [];
var waiterFragments = new MockMemorySpace.HeapFragment[waitingThreads.Length];
for (int i = 0; i < waitingThreads.Length; i++)
{
waiterFragments[i] = allocator.Allocate((ulong)(WaiterNextOffset + ptrSize), $"Waiter_{i}");
}

for (int i = 0; i < waiterFragments.Length; i++)
{
ulong nextAddr = i + 1 < waiterFragments.Length ? waiterFragments[i + 1].Address : 0;
helpers.WritePointer(waiterFragments[i].Data.AsSpan(WaiterNextOffset), nextAddr);
}

var conditionFragment = allocator.Allocate((ulong)(ConditionWaitersHeadOffset + ptrSize), "Condition");
ulong waitersHeadValue = waiterFragments.Length > 0 ? waiterFragments[0].Address : 0;
helpers.WritePointer(conditionFragment.Data.AsSpan(ConditionWaitersHeadOffset), waitersHeadValue);

var mockObject = new Mock<IObject>();
mockObject.Setup(o => o.GetSyncBlockAddress(objectAddr)).Returns(syncBlockAddr);

var mockCwt = new Mock<IConditionalWeakTable>();
TargetPointer cwtOutCondition = new(conditionFragment.Address);
mockCwt.Setup(c => c.TryGetValue(conditionTableAddr, objectAddr, out cwtOutCondition))
.Returns(cwtFindsCondition);

var mockThread = new Mock<IThread>();
if (threads is not null && threads.Length > 0)
{
var threadStore = new ThreadStoreData(threads.Length, threads[0].Thread, TargetPointer.Null, TargetPointer.Null);
mockThread.Setup(t => t.GetThreadStoreData()).Returns(threadStore);
for (int i = 0; i < threads.Length; i++)
{
TargetPointer nextThread = i + 1 < threads.Length ? threads[i + 1].Thread : TargetPointer.Null;
var threadData = new ThreadData(
threads[i].Thread, 0, default, default, false,
TargetPointer.Null, TargetPointer.Null, TargetPointer.Null,
TargetPointer.Null, TargetPointer.Null, TargetPointer.Null,
TargetPointer.Null, false, false, nextThread,
TargetPointer.Null, false, TargetPointer.Null);
mockThread.Setup(t => t.GetThreadData(threads[i].Thread)).Returns(threadData);
}
}
else
{
var threadStore = new ThreadStoreData(0, TargetPointer.Null, TargetPointer.Null, TargetPointer.Null);
mockThread.Setup(t => t.GetThreadStoreData()).Returns(threadStore);
}

int waiterIdx = 0;
if (threads is not null)
{
foreach (var (thread, isWaiting) in threads)
{
if (isWaiting)
{
var slotFragment = allocator.Allocate((ulong)ptrSize, $"WaiterSlot_{thread.Value:x}");
helpers.WritePointer(slotFragment.Data, waiterFragments[waiterIdx].Address);

TargetPointer outAddr = new(slotFragment.Address);
mockMts.Setup(m => m.TryGetThreadStaticFieldAddress(
"System.Threading.Condition", "t_waiterForCurrentThread", thread, out outAddr))
.Returns(true);
waiterIdx++;
Comment on lines +1002 to +1009
}
else
{
TargetPointer nullAddr = TargetPointer.Null;
mockMts.Setup(m => m.TryGetThreadStaticFieldAddress(
"System.Threading.Condition", "t_waiterForCurrentThread", thread, out nullAddr))
.Returns(false);
}
}
}

builder.AddMockContract(mockObject);
builder.AddMockContract(mockCwt);
builder.AddMockContract(mockThread);
builder.AddMockContract(mockMts);

var target = builder.Build();
var dacDbi = new DacDbiImpl(target, legacyObj: null);
return (dacDbi, target);
}

[UnmanagedCallersOnly]
private static void CollectThreadCallback(ulong value, nint pUserData)
{
GCHandle handle = GCHandle.FromIntPtr(pUserData);
((List<ulong>)handle.Target!).Add(value);
}

private static (int Hr, List<ulong> Threads) RunEnumerateMonitorEventWaitList(DacDbiImpl dacDbi, ulong vmObject)
{
List<ulong> threads = new();
GCHandle gcHandle = GCHandle.Alloc(threads);
int hr = dacDbi.EnumerateMonitorEventWaitList(
vmObject,
(nint)(delegate* unmanaged<ulong, nint, void>)&CollectThreadCallback,
GCHandle.ToIntPtr(gcHandle));
gcHandle.Free();
return (hr, threads);
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void EnumerateMonitorEventWaitList_NoSyncBlock(MockTarget.Architecture arch)
{
var mockObject = new Mock<IObject>();
mockObject.Setup(o => o.GetSyncBlockAddress(new TargetPointer(0x1000))).Returns(TargetPointer.Null);

var target = new TestPlaceholderTarget.Builder(arch)
.UseReader((_, _) => -1)
.AddMockContract(mockObject)
.Build();
var dacDbi = new DacDbiImpl(target, legacyObj: null);

var (hr, threads) = RunEnumerateMonitorEventWaitList(dacDbi, 0x1000);
Assert.Equal(System.HResults.S_OK, hr);
Assert.Empty(threads);
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void EnumerateMonitorEventWaitList_NoConditionOrEmptyList(MockTarget.Architecture arch)
{
// Object not found in ConditionalWeakTable, so no waiters are returned.
var (dacDbi1, _) = CreateMonitorWaitListDacDbi(arch, new(0x1000), new(0x2000), new(0x3000), cwtFindsCondition: false);
var (hr1, threads1) = RunEnumerateMonitorEventWaitList(dacDbi1, 0x1000);
Assert.Equal(System.HResults.S_OK, hr1);
Assert.Empty(threads1);

// Condition exists but waiter list is empty, so no waiters are returned.
var (dacDbi2, _) = CreateMonitorWaitListDacDbi(arch, new(0x1000), new(0x2000), new(0x3000), cwtFindsCondition: true);
var (hr2, threads2) = RunEnumerateMonitorEventWaitList(dacDbi2, 0x1000);
Assert.Equal(System.HResults.S_OK, hr2);
Assert.Empty(threads2);
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void EnumerateMonitorEventWaitList_NullCallback(MockTarget.Architecture arch)
{
var mockObject = new Mock<IObject>();
mockObject.Setup(o => o.GetSyncBlockAddress(new TargetPointer(0x1000))).Returns(new TargetPointer(0x2000));
var target = new TestPlaceholderTarget.Builder(arch)
.UseReader((_, _) => -1)
.AddMockContract(mockObject)
.Build();
var dacDbi = new DacDbiImpl(target, legacyObj: null);
int hr = dacDbi.EnumerateMonitorEventWaitList(0x1000, 0, 0);
Assert.NotEqual(System.HResults.S_OK, hr);
}

[Theory]
[ClassData(typeof(MockTarget.StdArch))]
public void EnumerateMonitorEventWaitList_WaitersReturnMatchingThreads(MockTarget.Architecture arch)
{
TargetPointer thread1 = new(0x6000);
TargetPointer thread2 = new(0x6100);

// Single waiter
var (dacDbi1, _) = CreateMonitorWaitListDacDbi(arch, new(0x1000), new(0x2000), new(0x3000),
cwtFindsCondition: true, threads: [(thread1, true)]);
var (hr1, result1) = RunEnumerateMonitorEventWaitList(dacDbi1, 0x1000);
Assert.Equal(System.HResults.S_OK, hr1);
Assert.Equal(new[] { thread1.Value }, result1);

// Two waiters
var (dacDbi2, _) = CreateMonitorWaitListDacDbi(arch, new(0x1000), new(0x2000), new(0x3000),
cwtFindsCondition: true, threads: [(thread1, true), (thread2, true)]);
var (hr2, result2) = RunEnumerateMonitorEventWaitList(dacDbi2, 0x1000);
Assert.Equal(System.HResults.S_OK, hr2);
Assert.Equal(new[] { thread1.Value, thread2.Value }, result2);

// Non-waiting thread skipped
var (dacDbi3, _) = CreateMonitorWaitListDacDbi(arch, new(0x1000), new(0x2000), new(0x3000),
cwtFindsCondition: true, threads: [(thread1, true), (thread2, false)]);
var (hr3, result3) = RunEnumerateMonitorEventWaitList(dacDbi3, 0x1000);
Assert.Equal(System.HResults.S_OK, hr3);
Assert.Equal(new[] { thread1.Value }, result3);
}
}
Loading