diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ThreadFactory.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ThreadFactory.cs new file mode 100644 index 0000000000000..22532289d2093 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ThreadFactory.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal sealed class ThreadFactory : IContractFactory +{ + IThread IContractFactory.CreateContract(Target target, int version) + { + TargetPointer threadStorePointer = target.ReadGlobalPointer(Constants.Globals.ThreadStore); + TargetPointer threadStore = target.ReadPointer(threadStorePointer); + return version switch + { + 1 => new Thread_1(target, threadStore), + _ => default(Thread), + }; + } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs similarity index 87% rename from src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread.cs rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs index f09a2997a1c1a..796eb89dffce7 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs @@ -5,20 +5,6 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; -internal sealed class ThreadFactory : IContractFactory -{ - IThread IContractFactory.CreateContract(Target target, int version) - { - TargetPointer threadStorePointer = target.ReadGlobalPointer(Constants.Globals.ThreadStore); - TargetPointer threadStore = target.ReadPointer(threadStorePointer); - return version switch - { - 1 => new Thread_1(target, threadStore), - _ => default(Thread), - }; - } -} - internal readonly struct Thread_1 : IThread { private readonly Target _target; diff --git a/src/native/managed/cdacreader/src/Legacy/ClrDataTask.cs b/src/native/managed/cdacreader/src/Legacy/ClrDataTask.cs new file mode 100644 index 0000000000000..b2a05958acb5e --- /dev/null +++ b/src/native/managed/cdacreader/src/Legacy/ClrDataTask.cs @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices.Marshalling; + +namespace Microsoft.Diagnostics.DataContractReader.Legacy; + +[GeneratedComClass] +internal sealed unsafe partial class ClrDataTask : IXCLRDataTask +{ + private readonly TargetPointer _address; + private readonly Target _target; + private readonly IXCLRDataTask? _legacyImpl; + + public ClrDataTask(TargetPointer address, Target target, IXCLRDataTask? legacyImpl) + { + _address = address; + _target = target; + _legacyImpl = legacyImpl; + } + + public int GetProcess(/*IXCLRDataProcess*/ void** process) + => _legacyImpl is not null ? _legacyImpl.GetProcess(process) : HResults.E_NOTIMPL; + public int GetCurrentAppDomain(/*IXCLRDataAppDomain*/ void** appDomain) + => _legacyImpl is not null ? _legacyImpl.GetCurrentAppDomain(appDomain) : HResults.E_NOTIMPL; + public int GetUniqueID(ulong* id) + => _legacyImpl is not null ? _legacyImpl.GetUniqueID(id) : HResults.E_NOTIMPL; + public int GetFlags(uint* flags) + => _legacyImpl is not null ? _legacyImpl.GetFlags(flags) : HResults.E_NOTIMPL; + public int IsSameObject(IXCLRDataTask* task) + => _legacyImpl is not null ? _legacyImpl.IsSameObject(task) : HResults.E_NOTIMPL; + public int GetManagedObject(/*IXCLRDataValue*/ void** value) + => _legacyImpl is not null ? _legacyImpl.GetManagedObject(value) : HResults.E_NOTIMPL; + public int GetDesiredExecutionState(uint* state) + => _legacyImpl is not null ? _legacyImpl.GetDesiredExecutionState(state) : HResults.E_NOTIMPL; + public int SetDesiredExecutionState(uint state) + => _legacyImpl is not null ? _legacyImpl.SetDesiredExecutionState(state) : HResults.E_NOTIMPL; + public int CreateStackWalk(uint flags, /*IXCLRDataStackWalk*/ void** stackWalk) + => _legacyImpl is not null ? _legacyImpl.CreateStackWalk(flags, stackWalk) : HResults.E_NOTIMPL; + public int GetOSThreadID(uint* id) + => _legacyImpl is not null ? _legacyImpl.GetOSThreadID(id) : HResults.E_NOTIMPL; + public int GetContext(uint contextFlags, uint contextBufSize, uint* contextSize, byte* contextBuffer) + => _legacyImpl is not null ? _legacyImpl.GetContext(contextFlags, contextBufSize, contextSize, contextBuffer) : HResults.E_NOTIMPL; + public int SetContext(uint contextSize, byte* context) + => _legacyImpl is not null ? _legacyImpl.SetContext(contextSize, context) : HResults.E_NOTIMPL; + public int GetCurrentExceptionState(/*IXCLRDataExceptionState*/ void** exception) + => _legacyImpl is not null ? _legacyImpl.GetCurrentExceptionState(exception) : HResults.E_NOTIMPL; + public int Request(uint reqCode, uint inBufferSize, byte* inBuffer, uint outBufferSize, byte* outBuffer) + => _legacyImpl is not null ? _legacyImpl.Request(reqCode, inBufferSize, inBuffer, outBufferSize, outBuffer) : HResults.E_NOTIMPL; + public int GetName(uint bufLen, uint* nameLen, char* nameBuffer) + => _legacyImpl is not null ? _legacyImpl.GetName(bufLen, nameLen, nameBuffer) : HResults.E_NOTIMPL; + public int GetLastExceptionState(/*IXCLRDataExceptionState*/ void** exception) + => _legacyImpl is not null ? _legacyImpl.GetLastExceptionState(exception) : HResults.E_NOTIMPL; +} diff --git a/src/native/managed/cdacreader/src/Legacy/IXCLRData.cs b/src/native/managed/cdacreader/src/Legacy/IXCLRData.cs index 677e1683d6961..c3f8a50be7bee 100644 --- a/src/native/managed/cdacreader/src/Legacy/IXCLRData.cs +++ b/src/native/managed/cdacreader/src/Legacy/IXCLRData.cs @@ -131,7 +131,7 @@ internal unsafe partial interface IXCLRDataProcess int EndEnumTasks(ulong handle); [PreserveSig] - int GetTaskByOSThreadID(uint osThreadID, /*IXCLRDataTask*/ void** task); + int GetTaskByOSThreadID(uint osThreadID, out IXCLRDataTask? task); [PreserveSig] int GetTaskByUniqueID(ulong taskID, /*IXCLRDataTask*/ void** task); @@ -309,3 +309,56 @@ internal unsafe partial interface IXCLRDataProcess2 : IXCLRDataProcess [PreserveSig] int SetGcNotification(GcEvtArgs gcEvtArgs); } + +[GeneratedComInterface] +[Guid("A5B0BEEA-EC62-4618-8012-A24FFC23934C")] +internal unsafe partial interface IXCLRDataTask +{ + [PreserveSig] + int GetProcess(/*IXCLRDataProcess*/ void** process); + + [PreserveSig] + int GetCurrentAppDomain(/*IXCLRDataAppDomain*/ void** appDomain); + + [PreserveSig] + int GetUniqueID(ulong* id); + + [PreserveSig] + int GetFlags(uint* flags); + + [PreserveSig] + int IsSameObject(IXCLRDataTask* task); + + [PreserveSig] + int GetManagedObject(/*IXCLRDataValue*/ void** value); + + [PreserveSig] + int GetDesiredExecutionState(uint* state); + + [PreserveSig] + int SetDesiredExecutionState(uint state); + + [PreserveSig] + int CreateStackWalk(uint flags, /*IXCLRDataStackWalk*/ void** stackWalk); + + [PreserveSig] + int GetOSThreadID(uint* id); + + [PreserveSig] + int GetContext(uint contextFlags, uint contextBufSize, uint* contextSize, byte* contextBuffer); + + [PreserveSig] + int SetContext(uint contextSize, byte* context); + + [PreserveSig] + int GetCurrentExceptionState(/*IXCLRDataExceptionState*/ void** exception); + + [PreserveSig] + int Request(uint reqCode, uint inBufferSize, byte* inBuffer, uint outBufferSize, byte* outBuffer); + + [PreserveSig] + int GetName(uint bufLen, uint* nameLen, char* nameBuffer); + + [PreserveSig] + int GetLastExceptionState(/*IXCLRDataExceptionState*/ void** exception); +} diff --git a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.IXCLRDataProcess.cs b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.IXCLRDataProcess.cs index f2529c109dff3..58799a26cf645 100644 --- a/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.IXCLRDataProcess.cs +++ b/src/native/managed/cdacreader/src/Legacy/SOSDacImpl.IXCLRDataProcess.cs @@ -25,8 +25,40 @@ int IXCLRDataProcess.EnumTask(ulong* handle, /*IXCLRDataTask*/ void** task) int IXCLRDataProcess.EndEnumTasks(ulong handle) => _legacyProcess is not null ? _legacyProcess.EndEnumTasks(handle) : HResults.E_NOTIMPL; - int IXCLRDataProcess.GetTaskByOSThreadID(uint osThreadID, /*IXCLRDataTask*/ void** task) - => _legacyProcess is not null ? _legacyProcess.GetTaskByOSThreadID(osThreadID, task) : HResults.E_NOTIMPL; + int IXCLRDataProcess.GetTaskByOSThreadID(uint osThreadID, out IXCLRDataTask? task) + { + task = default; + + // Find the thread correspending to the OS thread ID + Contracts.IThread contract = _target.Contracts.Thread; + TargetPointer thread = contract.GetThreadStoreData().FirstThread; + TargetPointer matchingThread = TargetPointer.Null; + while (thread != TargetPointer.Null) + { + Contracts.ThreadData threadData = contract.GetThreadData(thread); + if (threadData.OSId.Value == osThreadID) + { + matchingThread = thread; + break; + } + + thread = threadData.NextThread; + } + + if (matchingThread == TargetPointer.Null) + return HResults.E_INVALIDARG; + + IXCLRDataTask? legacyTask = null; + if (_legacyProcess is not null) + { + int hr = _legacyProcess.GetTaskByOSThreadID(osThreadID, out legacyTask); + if (hr < 0) + return hr; + } + + task = new ClrDataTask(matchingThread, _target, legacyTask); + return HResults.S_OK; + } int IXCLRDataProcess.GetTaskByUniqueID(ulong taskID, /*IXCLRDataTask*/ void** task) => _legacyProcess is not null ? _legacyProcess.GetTaskByUniqueID(taskID, task) : HResults.E_NOTIMPL; diff --git a/src/native/managed/cdacreader/tests/LoaderTests.cs b/src/native/managed/cdacreader/tests/LoaderTests.cs index 2a5913863818c..6cf7650d49212 100644 --- a/src/native/managed/cdacreader/tests/LoaderTests.cs +++ b/src/native/managed/cdacreader/tests/LoaderTests.cs @@ -26,8 +26,8 @@ public void GetPath(MockTarget.Architecture arch) // Add the modules MockLoader loader = new(builder); - TargetPointer moduleAddr = loader.AddModule(helpers, path: expected); - TargetPointer moduleAddrEmptyPath = loader.AddModule(helpers); + TargetPointer moduleAddr = loader.AddModule(path: expected); + TargetPointer moduleAddrEmptyPath = loader.AddModule(); bool success = builder.TryCreateTarget(out ContractDescriptorTarget? target); Assert.True(success); @@ -62,8 +62,8 @@ public void GetFileName(MockTarget.Architecture arch) // Add the modules MockLoader loader = new(builder); - TargetPointer moduleAddr = loader.AddModule(helpers, fileName: expected); - TargetPointer moduleAddrEmptyName = loader.AddModule(helpers); + TargetPointer moduleAddr = loader.AddModule(fileName: expected); + TargetPointer moduleAddrEmptyName = loader.AddModule(); bool success = builder.TryCreateTarget(out ContractDescriptorTarget? target); Assert.True(success); diff --git a/src/native/managed/cdacreader/tests/MockDescriptors.cs b/src/native/managed/cdacreader/tests/MockDescriptors.cs index 2aa14dea521c7..f9d393b552f7b 100644 --- a/src/native/managed/cdacreader/tests/MockDescriptors.cs +++ b/src/native/managed/cdacreader/tests/MockDescriptors.cs @@ -108,6 +108,36 @@ private static readonly (string, DataType)[] ModuleFields = (nameof(Data.Module.MethodDefToILCodeVersioningStateMap), DataType.pointer), ]; + private static readonly (string, DataType)[] ExceptionInfoFields = + [ + (nameof(Data.ExceptionInfo.PreviousNestedInfo), DataType.pointer), + (nameof(Data.ExceptionInfo.ThrownObject), DataType.pointer), + ]; + + private static readonly (string, DataType)[] ThreadFields = + [ + (nameof(Data.Thread.Id), DataType.uint32), + (nameof(Data.Thread.OSId), DataType.nuint), + (nameof(Data.Thread.State), DataType.uint32), + (nameof(Data.Thread.PreemptiveGCDisabled), DataType.uint32), + (nameof(Data.Thread.RuntimeThreadLocals), DataType.pointer), + (nameof(Data.Thread.Frame), DataType.pointer), + (nameof(Data.Thread.TEB), DataType.pointer), + (nameof(Data.Thread.LastThrownObject), DataType.pointer), + (nameof(Data.Thread.LinkNext), DataType.pointer), + (nameof(Data.Thread.ExceptionTracker), DataType.pointer), + ]; + + private static readonly (string, DataType)[] ThreadStoreFields = + [ + (nameof(Data.ThreadStore.ThreadCount), DataType.uint32), + (nameof(Data.ThreadStore.FirstThreadLink), DataType.pointer), + (nameof(Data.ThreadStore.UnstartedCount), DataType.uint32), + (nameof(Data.ThreadStore.BackgroundCount), DataType.uint32), + (nameof(Data.ThreadStore.PendingCount), DataType.uint32), + (nameof(Data.ThreadStore.DeadCount), DataType.uint32), + ]; + public static class RuntimeTypeSystem { internal const ulong TestFreeObjectMethodTableGlobalAddress = 0x00000000_7a0000a0; @@ -373,8 +403,9 @@ public Loader(MockMemorySpace.Builder builder, (ulong Start, ulong End) allocati }; } - internal TargetPointer AddModule(TargetTestHelpers helpers, string? path = null, string? fileName = null) + internal TargetPointer AddModule(string? path = null, string? fileName = null) { + TargetTestHelpers helpers = _builder.TargetTestHelpers; Target.TypeInfo typeInfo = Types(helpers)[DataType.Module]; uint size = typeInfo.Size.Value; MockMemorySpace.HeapFragment module = _allocator.Allocate(size, "Module"); @@ -413,4 +444,144 @@ internal TargetPointer AddModule(TargetTestHelpers helpers, string? path = null, return module.Address; } } + + public class Thread + { + private const ulong DefaultAllocationRangeStart = 0x0003_0000; + private const ulong DefaultAllocationRangeEnd = 0x0004_0000; + + internal Dictionary Types { get; init; } + internal (string Name, ulong Value, string? Type)[] Globals { get; init; } + + internal TargetPointer FinalizerThreadAddress { get; init; } + internal TargetPointer GCThreadAddress { get; init; } + + private readonly MockMemorySpace.Builder _builder; + private readonly MockMemorySpace.BumpAllocator _allocator; + + private readonly TargetPointer _threadStoreAddress; + + // Most recently added thread. We update its link to the next thread if another thread is added. + private TargetPointer _previousThread = TargetPointer.Null; + + public Thread(MockMemorySpace.Builder builder) + : this(builder, (DefaultAllocationRangeStart, DefaultAllocationRangeEnd)) + { } + + public Thread(MockMemorySpace.Builder builder, (ulong Start, ulong End) allocationRange) + { + _builder = builder; + _allocator = _builder.CreateAllocator(allocationRange.Start, allocationRange.End); + + TargetTestHelpers helpers = builder.TargetTestHelpers; + + Types = GetTypes(helpers); + + // Add thread store and set global to point at it + MockMemorySpace.HeapFragment threadStoreGlobal = _allocator.Allocate((ulong)helpers.PointerSize, "[global pointer] ThreadStore"); + MockMemorySpace.HeapFragment threadStore = _allocator.Allocate(Types[DataType.ThreadStore].Size.Value, "ThreadStore"); + helpers.WritePointer(threadStoreGlobal.Data, threadStore.Address); + _builder.AddHeapFragments([threadStoreGlobal, threadStore]); + _threadStoreAddress = threadStore.Address; + + // Add finalizer thread and set global to point at it + MockMemorySpace.HeapFragment finalizerThreadGlobal = _allocator.Allocate((ulong)helpers.PointerSize, "[global pointer] Finalizer thread"); + MockMemorySpace.HeapFragment finalizerThread = _allocator.Allocate(Types[DataType.Thread].Size.Value, "Finalizer thread"); + helpers.WritePointer(finalizerThreadGlobal.Data, finalizerThread.Address); + _builder.AddHeapFragments([finalizerThreadGlobal, finalizerThread]); + FinalizerThreadAddress = finalizerThread.Address; + + // Add GC thread and set global to point at it + MockMemorySpace.HeapFragment gcThreadGlobal = _allocator.Allocate((ulong)helpers.PointerSize, "[global pointer] GC thread"); + MockMemorySpace.HeapFragment gcThread = _allocator.Allocate(Types[DataType.Thread].Size.Value, "GC thread"); + helpers.WritePointer(gcThreadGlobal.Data, gcThread.Address); + _builder.AddHeapFragments([gcThreadGlobal, gcThread]); + GCThreadAddress = gcThread.Address; + + Globals = + [ + (nameof(Constants.Globals.ThreadStore), threadStoreGlobal.Address, null), + (nameof(Constants.Globals.FinalizerThread), finalizerThreadGlobal.Address, null), + (nameof(Constants.Globals.GCThread), gcThreadGlobal.Address, null), + (nameof(Constants.Globals.FeatureEHFunclets), 0, null), + ]; + } + + private static Dictionary GetTypes(TargetTestHelpers helpers) + { + TargetTestHelpers.LayoutResult exceptionInfoLayout = helpers.LayoutFields(ExceptionInfoFields); + TargetTestHelpers.LayoutResult threadLayout = helpers.LayoutFields(ThreadFields); + TargetTestHelpers.LayoutResult threadStoreLayout = helpers.LayoutFields(ThreadStoreFields); + return new() + { + [DataType.ExceptionInfo] = new Target.TypeInfo() { Fields = exceptionInfoLayout.Fields, Size = exceptionInfoLayout.Stride }, + [DataType.Thread] = new Target.TypeInfo() { Fields = threadLayout.Fields, Size = threadLayout.Stride }, + [DataType.ThreadStore] = new Target.TypeInfo() { Fields = threadStoreLayout.Fields, Size = threadStoreLayout.Stride }, + }; + } + + internal void SetThreadCounts(int threadCount, int unstartedCount, int backgroundCount, int pendingCount, int deadCount) + { + TargetTestHelpers helpers = _builder.TargetTestHelpers; + Target.TypeInfo typeInfo = Types[DataType.ThreadStore]; + Span data = _builder.BorrowAddressRange(_threadStoreAddress, (int)typeInfo.Size.Value); + helpers.Write( + data.Slice(typeInfo.Fields[nameof(Data.ThreadStore.ThreadCount)].Offset), + threadCount); + helpers.Write( + data.Slice(typeInfo.Fields[nameof(Data.ThreadStore.UnstartedCount)].Offset), + unstartedCount); + helpers.Write( + data.Slice(typeInfo.Fields[nameof(Data.ThreadStore.BackgroundCount)].Offset), + backgroundCount); + helpers.Write( + data.Slice(typeInfo.Fields[nameof(Data.ThreadStore.PendingCount)].Offset), + pendingCount); + helpers.Write( + data.Slice(typeInfo.Fields[nameof(Data.ThreadStore.DeadCount)].Offset), + deadCount); + } + + internal TargetPointer AddThread(uint id, TargetNUInt osId) + { + TargetTestHelpers helpers = _builder.TargetTestHelpers; + Target.TypeInfo typeInfo = Types[DataType.Thread]; + MockMemorySpace.HeapFragment thread = _allocator.Allocate(typeInfo.Size.Value, "Thread"); + Span data = thread.Data.AsSpan(); + helpers.Write( + data.Slice(typeInfo.Fields[nameof(Data.Thread.Id)].Offset), + id); + helpers.WriteNUInt( + data.Slice(typeInfo.Fields[nameof(Data.Thread.OSId)].Offset), + osId); + _builder.AddHeapFragment(thread); + + // Add exception info for the thread + MockMemorySpace.HeapFragment exceptionInfo = _allocator.Allocate(Types[DataType.ExceptionInfo].Size.Value, "ExceptionInfo"); + _builder.AddHeapFragment(exceptionInfo); + helpers.WritePointer( + data.Slice(typeInfo.Fields[nameof(Data.Thread.ExceptionTracker)].Offset), + exceptionInfo.Address); + + ulong threadLinkOffset = (ulong)typeInfo.Fields[nameof(Data.Thread.LinkNext)].Offset; + if (_previousThread != TargetPointer.Null) + { + // Set the next link for the previously added thread to the newly added one + helpers.WritePointer( + _builder.BorrowAddressRange(_previousThread + threadLinkOffset, helpers.PointerSize), + thread.Address + threadLinkOffset); + } + else + { + // Set the first thread link in the thread store + ulong firstThreadLinkAddr = _threadStoreAddress + (ulong)Types[DataType.ThreadStore].Fields[nameof(Data.ThreadStore.FirstThreadLink)].Offset; + helpers.WritePointer( + _builder.BorrowAddressRange(firstThreadLinkAddr, helpers.PointerSize), + thread.Address + threadLinkOffset); + } + + _previousThread = thread.Address; + return thread.Address; + } + } } diff --git a/src/native/managed/cdacreader/tests/TargetTestHelpers.cs b/src/native/managed/cdacreader/tests/TargetTestHelpers.cs index 60feb7e7c1b62..0c96b19a49f40 100644 --- a/src/native/managed/cdacreader/tests/TargetTestHelpers.cs +++ b/src/native/managed/cdacreader/tests/TargetTestHelpers.cs @@ -198,6 +198,18 @@ internal void Write(Span dest, ushort u) } } + internal void Write(Span dest, int i) + { + if (Arch.IsLittleEndian) + { + BinaryPrimitives.WriteInt32LittleEndian(dest, i); + } + else + { + BinaryPrimitives.WriteInt32BigEndian(dest, i); + } + } + internal void Write(Span dest, uint u) { if (Arch.IsLittleEndian) diff --git a/src/native/managed/cdacreader/tests/ThreadTests.cs b/src/native/managed/cdacreader/tests/ThreadTests.cs new file mode 100644 index 0000000000000..83b40bee67d4a --- /dev/null +++ b/src/native/managed/cdacreader/tests/ThreadTests.cs @@ -0,0 +1,130 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.UnitTests; + +using MockThread = MockDescriptors.Thread; + +public unsafe class ThreadTests +{ + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetThreadStoreData(MockTarget.Architecture arch) + { + // Set up the target + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockThread thread = new(builder); + builder = builder + .SetContracts([nameof(Contracts.Thread)]) + .SetTypes(thread.Types) + .SetGlobals(thread.Globals); + + int threadCount = 15; + int unstartedCount = 1; + int backgroundCount = 2; + int pendingCount = 3; + int deadCount = 4; + + // Set thread store data + thread.SetThreadCounts( + threadCount, + unstartedCount, + backgroundCount, + pendingCount, + deadCount); + + bool success = builder.TryCreateTarget(out ContractDescriptorTarget? target); + Assert.True(success); + + // Validate the expected thread counts + Contracts.IThread contract = target.Contracts.Thread; + Assert.NotNull(contract); + + Contracts.ThreadStoreCounts counts = contract.GetThreadCounts(); + Assert.Equal(unstartedCount, counts.UnstartedThreadCount); + Assert.Equal(backgroundCount, counts.BackgroundThreadCount); + Assert.Equal(pendingCount, counts.PendingThreadCount); + Assert.Equal(deadCount, counts.DeadThreadCount); + + Contracts.ThreadStoreData data = contract.GetThreadStoreData(); + Assert.Equal(threadCount, data.ThreadCount); + Assert.Equal(thread.FinalizerThreadAddress, data.FinalizerThread); + Assert.Equal(thread.GCThreadAddress, data.GCThread); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetThreadData(MockTarget.Architecture arch) + { + // Set up the target + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockThread thread = new(builder); + builder = builder + .SetContracts([nameof(Contracts.Thread)]) + .SetTypes(thread.Types) + .SetGlobals(thread.Globals); + + uint id = 1; + TargetNUInt osId = new TargetNUInt(1234); + + // Add thread + TargetPointer addr = thread.AddThread(id, osId); + + bool success = builder.TryCreateTarget(out ContractDescriptorTarget? target); + Assert.True(success); + + // Validate the expected thread counts + Contracts.IThread contract = target.Contracts.Thread; + Assert.NotNull(contract); + + Contracts.ThreadData data= contract.GetThreadData(addr); + Assert.Equal(id, data.Id); + Assert.Equal(osId, data.OSId); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void IterateThreads(MockTarget.Architecture arch) + { + // Set up the target + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockThread thread = new(builder); + builder = builder + .SetContracts([nameof(Contracts.Thread)]) + .SetTypes(thread.Types) + .SetGlobals(thread.Globals); + + // Add threads + uint expectedCount = 10; + uint osIdStart = 1000; + for (uint i = 1; i <= expectedCount; i++) + { + thread.AddThread(i, new TargetNUInt(i + osIdStart)); + } + + bool success = builder.TryCreateTarget(out ContractDescriptorTarget? target); + Assert.True(success); + + // Validate the expected thread counts + Contracts.IThread contract = target.Contracts.Thread; + Assert.NotNull(contract); + + TargetPointer currentThread = contract.GetThreadStoreData().FirstThread; + uint count = 0; + while (currentThread != TargetPointer.Null) + { + count++; + Contracts.ThreadData threadData = contract.GetThreadData(currentThread); + Assert.Equal(count, threadData.Id); + Assert.Equal(count + osIdStart, threadData.OSId.Value); + currentThread = threadData.NextThread; + } + + Assert.Equal(expectedCount, count); + } +}