From 31fae06f0f661bcc96b1ca9e501497560b45ba73 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 20:23:26 +0000 Subject: [PATCH 1/7] Initial plan From 95f03eb96c39e2e0811645d2ddf0e37090bd386f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 21:02:51 +0000 Subject: [PATCH 2/7] Implement GetThreadAllocData in cDAC SOSDacImpl - Add AllocBytes and AllocBytesLoh fields to GCAllocContext data descriptor - Add ThreadAllocData record struct and GetThreadAllocData to IThread contract - Implement GetThreadAllocData in Thread_1.cs contract - Define DacpAllocData managed struct in ISOSDacInterface.cs - Implement GetThreadAllocData in SOSDacImpl.cs with DEBUG comparison - Add unit tests for GetThreadAllocData with non-zero and zero values - Add dump test for GetThreadAllocData - Update Thread.md and GC.md documentation Co-authored-by: max-charlamb <44248479+max-charlamb@users.noreply.github.com> --- docs/design/datacontracts/GC.md | 2 + docs/design/datacontracts/Thread.md | 2 + .../vm/datadescriptor/datadescriptor.inc | 2 + .../Contracts/IThread.cs | 5 ++ .../Contracts/Thread_1.cs | 9 +++ .../Data/GCAllocContext.cs | 4 ++ .../ISOSDacInterface.cs | 8 ++- .../SOSDacImpl.cs | 39 ++++++++++- .../cdac/tests/DumpTests/ThreadDumpTests.cs | 20 ++++++ .../MockDescriptors/MockDescriptors.Thread.cs | 66 ++++++++++++++++++- src/native/managed/cdac/tests/ThreadTests.cs | 41 ++++++++++++ 11 files changed, 194 insertions(+), 4 deletions(-) diff --git a/docs/design/datacontracts/GC.md b/docs/design/datacontracts/GC.md index bbb6d3ebafb121..49deb609f8aa07 100644 --- a/docs/design/datacontracts/GC.md +++ b/docs/design/datacontracts/GC.md @@ -157,6 +157,8 @@ Data descriptors used: | `OomHistory` | LohP | GC | Large object heap flag indicating if OOM was related to LOH | | `GCAllocContext` | Pointer | VM | Current GCAllocContext pointer | | `GCAllocContext` | Limit | VM | Pointer to the GCAllocContext limit | +| `GCAllocContext` | AllocBytes | VM | Number of bytes allocated on SOH by this context | +| `GCAllocContext` | AllocBytesLoh | VM | Number of bytes allocated on UOH by this context | Global variables used: | Global Name | Type | Source | Purpose | diff --git a/docs/design/datacontracts/Thread.md b/docs/design/datacontracts/Thread.md index c8e17a88ebf152..6bca42d4b53b4f 100644 --- a/docs/design/datacontracts/Thread.md +++ b/docs/design/datacontracts/Thread.md @@ -76,6 +76,8 @@ The contract additionally depends on these data descriptors | `ExceptionInfo` | `ExceptionWatsonBucketTrackerBuckets` | Pointer to Watson unhandled buckets on non-Unix | | `GCAllocContext` | `Pointer` | GC allocation pointer | | `GCAllocContext` | `Limit` | Allocation limit pointer | +| `GCAllocContext` | `AllocBytes` | Number of bytes allocated on SOH by this context | +| `GCAllocContext` | `AllocBytesLoh` | Number of bytes allocated on UOH by this context | | `IdDispenser` | `HighestId` | Highest possible small thread ID | | `IdDispenser` | `IdToThread` | Array mapping small thread IDs to thread pointers | | `InflightTLSData` | `Next` | Pointer to next in-flight TLS data entry | diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 4799a56f287703..8eea2fb3acdf9a 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -86,6 +86,8 @@ CDAC_TYPE_BEGIN(GCAllocContext) CDAC_TYPE_INDETERMINATE(GCAllocContext) CDAC_TYPE_FIELD(GCAllocContext, /*pointer*/, Pointer, offsetof(gc_alloc_context, alloc_ptr)) CDAC_TYPE_FIELD(GCAllocContext, /*pointer*/, Limit, offsetof(gc_alloc_context, alloc_limit)) +CDAC_TYPE_FIELD(GCAllocContext, /*int64*/, AllocBytes, offsetof(gc_alloc_context, alloc_bytes)) +CDAC_TYPE_FIELD(GCAllocContext, /*int64*/, AllocBytesLoh, offsetof(gc_alloc_context, alloc_bytes_uoh)) CDAC_TYPE_END(GCAllocContext) // Exception diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs index 6ee3f2c36a8f73..7d8cd8681aea9f 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs @@ -41,6 +41,10 @@ public record struct ThreadData( TargetPointer LastThrownObjectHandle, TargetPointer NextThread); +public record struct ThreadAllocData( + long AllocBytes, + long AllocBytesLoh); + public interface IThread : IContract { static string IContract.Name { get; } = nameof(Thread); @@ -48,6 +52,7 @@ public interface IThread : IContract ThreadStoreData GetThreadStoreData() => throw new NotImplementedException(); ThreadStoreCounts GetThreadCounts() => throw new NotImplementedException(); ThreadData GetThreadData(TargetPointer thread) => throw new NotImplementedException(); + ThreadAllocData GetThreadAllocData(TargetPointer thread) => throw new NotImplementedException(); TargetPointer IdToThread(uint id) => throw new NotImplementedException(); TargetPointer GetThreadLocalStaticBase(TargetPointer threadPointer, TargetPointer tlsIndexPtr) => throw new NotImplementedException(); TargetPointer GetThrowableObject(TargetPointer threadPointer) => throw new NotImplementedException(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs index 631c809f9b458e..3a1ccfd5a29107 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs @@ -76,6 +76,15 @@ ThreadData IThread.GetThreadData(TargetPointer threadPointer) GetThreadFromLink(thread.LinkNext)); } + ThreadAllocData IThread.GetThreadAllocData(TargetPointer threadPointer) + { + Data.Thread thread = _target.ProcessedData.GetOrAdd(threadPointer); + + return new ThreadAllocData( + thread.RuntimeThreadLocals?.AllocContext.GCAllocationContext.AllocBytes ?? 0, + thread.RuntimeThreadLocals?.AllocContext.GCAllocationContext.AllocBytesLoh ?? 0); + } + // happens inside critical section TargetPointer IThread.IdToThread(uint id) { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/GCAllocContext.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/GCAllocContext.cs index 02b22b0e335dad..bc6b6e1a98a8a1 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/GCAllocContext.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/GCAllocContext.cs @@ -13,8 +13,12 @@ public GCAllocContext(Target target, TargetPointer address) Target.TypeInfo type = target.GetTypeInfo(DataType.GCAllocContext); Pointer = target.ReadPointer(address + (ulong)type.Fields[nameof(Pointer)].Offset); Limit = target.ReadPointer(address + (ulong)type.Fields[nameof(Limit)].Offset); + AllocBytes = target.Read(address + (ulong)type.Fields[nameof(AllocBytes)].Offset); + AllocBytesLoh = target.Read(address + (ulong)type.Fields[nameof(AllocBytesLoh)].Offset); } public TargetPointer Pointer { get; init; } public TargetPointer Limit { get; init; } + public long AllocBytes { get; init; } + public long AllocBytesLoh { get; init; } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs index 6851ade98cdffa..e048d68daa7a75 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs @@ -108,6 +108,12 @@ public struct DacpThreadData public ClrDataAddress nextThread; } +public struct DacpAllocData +{ + public ClrDataAddress allocBytes; + public ClrDataAddress allocBytesLoh; +} + public struct DacpModuleData { public enum TransientFlags : uint @@ -629,7 +635,7 @@ public unsafe partial interface ISOSDacInterface int GetRegisterName(int regName, uint count, char* buffer, uint* pNeeded); [PreserveSig] - int GetThreadAllocData(ClrDataAddress thread, /*struct DacpAllocData */ void* data); + int GetThreadAllocData(ClrDataAddress thread, DacpAllocData* data); [PreserveSig] int GetHeapAllocData(uint count, /*struct DacpGenerationAllocData */ void* data, uint* pNeeded); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs index f1ee4780f5968e..240daaff82881f 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -2997,8 +2997,43 @@ int ISOSDacInterface.GetSyncBlockCleanupData(ClrDataAddress addr, void* data) => _legacyImpl is not null ? _legacyImpl.GetSyncBlockCleanupData(addr, data) : HResults.E_NOTIMPL; int ISOSDacInterface.GetSyncBlockData(uint number, void* data) => _legacyImpl is not null ? _legacyImpl.GetSyncBlockData(number, data) : HResults.E_NOTIMPL; - int ISOSDacInterface.GetThreadAllocData(ClrDataAddress thread, void* data) - => _legacyImpl is not null ? _legacyImpl.GetThreadAllocData(thread, data) : HResults.E_NOTIMPL; + + int ISOSDacInterface.GetThreadAllocData(ClrDataAddress thread, DacpAllocData* data) + { + if (thread == 0) + return HResults.E_INVALIDARG; + + int hr = HResults.S_OK; + try + { + if (data is null) + throw new ArgumentException(); + + Contracts.IThread contract = _target.Contracts.Thread; + Contracts.ThreadAllocData allocData = contract.GetThreadAllocData(thread.ToTargetPointer(_target)); + data->allocBytes = (ClrDataAddress)(ulong)allocData.AllocBytes; + data->allocBytesLoh = (ClrDataAddress)(ulong)allocData.AllocBytesLoh; + } + catch (global::System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacyImpl is not null) + { + DacpAllocData dataLocal = default; + int hrLocal = _legacyImpl.GetThreadAllocData(thread, &dataLocal); + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + if (hr == HResults.S_OK) + { + Debug.Assert(data->allocBytes == dataLocal.allocBytes, $"cDAC: {data->allocBytes:x}, DAC: {dataLocal.allocBytes:x}"); + Debug.Assert(data->allocBytesLoh == dataLocal.allocBytesLoh, $"cDAC: {data->allocBytesLoh:x}, DAC: {dataLocal.allocBytesLoh:x}"); + } + } +#endif + return hr; + } int ISOSDacInterface.GetThreadData(ClrDataAddress thread, DacpThreadData* data) { diff --git a/src/native/managed/cdac/tests/DumpTests/ThreadDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/ThreadDumpTests.cs index d46a4dbc47fc8c..b71e78f631e735 100644 --- a/src/native/managed/cdac/tests/DumpTests/ThreadDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/ThreadDumpTests.cs @@ -111,4 +111,24 @@ public void ThreadCounts_AreNonNegative(TestConfiguration config) Assert.True(counts.PendingThreadCount >= 0, $"PendingThreadCount should be non-negative, got {counts.PendingThreadCount}"); Assert.True(counts.DeadThreadCount >= 0, $"DeadThreadCount should be non-negative, got {counts.DeadThreadCount}"); } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + public void GetThreadAllocData_CanReadForAllThreads(TestConfiguration config) + { + InitializeDumpTest(config); + IThread threadContract = Target.Contracts.Thread; + ThreadStoreData storeData = threadContract.GetThreadStoreData(); + + TargetPointer currentThread = storeData.FirstThread; + while (currentThread != TargetPointer.Null) + { + ThreadAllocData allocData = threadContract.GetThreadAllocData(currentThread); + Assert.True(allocData.AllocBytes >= 0, $"AllocBytes should be non-negative, got {allocData.AllocBytes}"); + Assert.True(allocData.AllocBytesLoh >= 0, $"AllocBytesLoh should be non-negative, got {allocData.AllocBytesLoh}"); + + ThreadData threadData = threadContract.GetThreadData(currentThread); + currentThread = threadData.NextThread; + } + } } diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Thread.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Thread.cs index 12dc6299503998..17796e51a76b19 100644 --- a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Thread.cs +++ b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Thread.cs @@ -72,13 +72,49 @@ public Thread(MockMemorySpace.Builder builder, (ulong Start, ulong End) allocati private static Dictionary GetTypes(TargetTestHelpers helpers) { - return GetTypesForTypeFields( + var types = GetTypesForTypeFields( helpers, [ ExceptionInfoFields, ThreadFields, ThreadStoreFields, ]); + + // Compute layouts for embedded struct types (GCAllocContext -> EEAllocContext -> RuntimeThreadLocals) + var gcAllocContextLayout = helpers.LayoutFields( + [ + new(nameof(Data.GCAllocContext.Pointer), DataType.pointer), + new(nameof(Data.GCAllocContext.Limit), DataType.pointer), + new(nameof(Data.GCAllocContext.AllocBytes), DataType.int64), + new(nameof(Data.GCAllocContext.AllocBytesLoh), DataType.int64), + ]); + types[DataType.GCAllocContext] = new Target.TypeInfo() + { + Fields = gcAllocContextLayout.Fields, + Size = gcAllocContextLayout.Stride, + }; + + var eeAllocContextLayout = helpers.LayoutFields( + [ + new(nameof(Data.EEAllocContext.GCAllocationContext), DataType.GCAllocContext, gcAllocContextLayout.Stride), + ]); + types[DataType.EEAllocContext] = new Target.TypeInfo() + { + Fields = eeAllocContextLayout.Fields, + Size = eeAllocContextLayout.Stride, + }; + + var runtimeThreadLocalsLayout = helpers.LayoutFields( + [ + new(nameof(Data.RuntimeThreadLocals.AllocContext), DataType.EEAllocContext, eeAllocContextLayout.Stride), + ]); + types[DataType.RuntimeThreadLocals] = new Target.TypeInfo() + { + Fields = runtimeThreadLocalsLayout.Fields, + Size = runtimeThreadLocalsLayout.Stride, + }; + + return types; } internal void SetThreadCounts(int threadCount, int unstartedCount, int backgroundCount, int pendingCount, int deadCount) @@ -104,11 +140,19 @@ internal void SetThreadCounts(int threadCount, int unstartedCount, int backgroun } internal TargetPointer AddThread(uint id, TargetNUInt osId) + => AddThread(id, osId, allocBytes: 0, allocBytesLoh: 0); + + internal TargetPointer AddThread(uint id, TargetNUInt osId, long allocBytes, long allocBytesLoh) { TargetTestHelpers helpers = Builder.TargetTestHelpers; Target.TypeInfo threadType = Types[DataType.Thread]; Target.TypeInfo exceptionInfoType = Types[DataType.ExceptionInfo]; + Target.TypeInfo runtimeThreadLocalsType = Types[DataType.RuntimeThreadLocals]; + Target.TypeInfo eeAllocContextType = Types[DataType.EEAllocContext]; + Target.TypeInfo gcAllocContextType = Types[DataType.GCAllocContext]; + MockMemorySpace.HeapFragment exceptionInfo = _allocator.Allocate(exceptionInfoType.Size.Value, "ExceptionInfo"); + MockMemorySpace.HeapFragment runtimeThreadLocals = _allocator.Allocate(runtimeThreadLocalsType.Size.Value, "RuntimeThreadLocals"); MockMemorySpace.HeapFragment thread = _allocator.Allocate(threadType.Size.Value, "Thread"); Span data = thread.Data.AsSpan(); helpers.Write( @@ -120,8 +164,28 @@ internal TargetPointer AddThread(uint id, TargetNUInt osId) helpers.WritePointer( data.Slice(threadType.Fields[nameof(Data.Thread.ExceptionTracker)].Offset), exceptionInfo.Address); + helpers.WritePointer( + data.Slice(threadType.Fields[nameof(Data.Thread.RuntimeThreadLocals)].Offset), + runtimeThreadLocals.Address); + + // Write alloc bytes into the GCAllocContext embedded within RuntimeThreadLocals + int allocContextOffset = runtimeThreadLocalsType.Fields[nameof(Data.RuntimeThreadLocals.AllocContext)].Offset; + int gcAllocationContextOffset = eeAllocContextType.Fields[nameof(Data.EEAllocContext.GCAllocationContext)].Offset; + int allocBytesOffset = gcAllocContextType.Fields[nameof(Data.GCAllocContext.AllocBytes)].Offset; + int allocBytesLohOffset = gcAllocContextType.Fields[nameof(Data.GCAllocContext.AllocBytesLoh)].Offset; + + Span runtimeThreadLocalsData = runtimeThreadLocals.Data.AsSpan(); + int baseOffset = allocContextOffset + gcAllocationContextOffset; + helpers.Write( + runtimeThreadLocalsData.Slice(baseOffset + allocBytesOffset), + (ulong)allocBytes); + helpers.Write( + runtimeThreadLocalsData.Slice(baseOffset + allocBytesLohOffset), + (ulong)allocBytesLoh); + Builder.AddHeapFragment(thread); Builder.AddHeapFragment(exceptionInfo); + Builder.AddHeapFragment(runtimeThreadLocals); ulong threadLinkOffset = (ulong)threadType.Fields[nameof(Data.Thread.LinkNext)].Offset; if (_previousThread != TargetPointer.Null) diff --git a/src/native/managed/cdac/tests/ThreadTests.cs b/src/native/managed/cdac/tests/ThreadTests.cs index f8998c7766ab7b..ac3f5476a2c102 100644 --- a/src/native/managed/cdac/tests/ThreadTests.cs +++ b/src/native/managed/cdac/tests/ThreadTests.cs @@ -122,4 +122,45 @@ public void IterateThreads(MockTarget.Architecture arch) Assert.Equal(expectedCount, count); } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetThreadAllocData(MockTarget.Architecture arch) + { + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockDescriptors.Thread thread = new(builder); + + long allocBytes = 1024; + long allocBytesLoh = 4096; + + TargetPointer addr = thread.AddThread(1, new TargetNUInt(1234), allocBytes, allocBytesLoh); + + Target target = CreateTarget(thread); + IThread contract = target.Contracts.Thread; + Assert.NotNull(contract); + + ThreadAllocData data = contract.GetThreadAllocData(addr); + Assert.Equal(allocBytes, data.AllocBytes); + Assert.Equal(allocBytesLoh, data.AllocBytesLoh); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetThreadAllocData_ZeroValues(MockTarget.Architecture arch) + { + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockDescriptors.Thread thread = new(builder); + + TargetPointer addr = thread.AddThread(1, new TargetNUInt(1234)); + + Target target = CreateTarget(thread); + IThread contract = target.Contracts.Thread; + Assert.NotNull(contract); + + ThreadAllocData data = contract.GetThreadAllocData(addr); + Assert.Equal(0, data.AllocBytes); + Assert.Equal(0, data.AllocBytesLoh); + } } From 764358c9e950282cddd8bba9a5c7bd243766248a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 21:05:35 +0000 Subject: [PATCH 3/7] Fix documentation: align field descriptions with native comments Co-authored-by: max-charlamb <44248479+max-charlamb@users.noreply.github.com> --- docs/design/datacontracts/GC.md | 2 +- docs/design/datacontracts/Thread.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/design/datacontracts/GC.md b/docs/design/datacontracts/GC.md index 49deb609f8aa07..ec9061680b69c4 100644 --- a/docs/design/datacontracts/GC.md +++ b/docs/design/datacontracts/GC.md @@ -158,7 +158,7 @@ Data descriptors used: | `GCAllocContext` | Pointer | VM | Current GCAllocContext pointer | | `GCAllocContext` | Limit | VM | Pointer to the GCAllocContext limit | | `GCAllocContext` | AllocBytes | VM | Number of bytes allocated on SOH by this context | -| `GCAllocContext` | AllocBytesLoh | VM | Number of bytes allocated on UOH by this context | +| `GCAllocContext` | AllocBytesLoh | VM | Number of bytes allocated not on SOH by this context | Global variables used: | Global Name | Type | Source | Purpose | diff --git a/docs/design/datacontracts/Thread.md b/docs/design/datacontracts/Thread.md index 6bca42d4b53b4f..49c451de674b9c 100644 --- a/docs/design/datacontracts/Thread.md +++ b/docs/design/datacontracts/Thread.md @@ -77,7 +77,7 @@ The contract additionally depends on these data descriptors | `GCAllocContext` | `Pointer` | GC allocation pointer | | `GCAllocContext` | `Limit` | Allocation limit pointer | | `GCAllocContext` | `AllocBytes` | Number of bytes allocated on SOH by this context | -| `GCAllocContext` | `AllocBytesLoh` | Number of bytes allocated on UOH by this context | +| `GCAllocContext` | `AllocBytesLoh` | Number of bytes allocated not on SOH by this context | | `IdDispenser` | `HighestId` | Highest possible small thread ID | | `IdDispenser` | `IdToThread` | Array mapping small thread IDs to thread pointers | | `InflightTLSData` | `Next` | Pointer to next in-flight TLS data entry | From 9fbc6c9a6de834a76219eec758c1003a2afc57a4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 21:45:13 +0000 Subject: [PATCH 4/7] Address review feedback: move checks inside try, use NullReferenceException for E_POINTER, rename to GetThreadAllocContext Co-authored-by: max-charlamb <44248479+max-charlamb@users.noreply.github.com> --- .../Contracts/IThread.cs | 2 +- .../Contracts/Thread_1.cs | 2 +- .../SOSDacImpl.cs | 9 ++++----- .../managed/cdac/tests/DumpTests/ThreadDumpTests.cs | 4 ++-- src/native/managed/cdac/tests/ThreadTests.cs | 8 ++++---- 5 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs index 7d8cd8681aea9f..5400365da2d8a1 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs @@ -52,7 +52,7 @@ public interface IThread : IContract ThreadStoreData GetThreadStoreData() => throw new NotImplementedException(); ThreadStoreCounts GetThreadCounts() => throw new NotImplementedException(); ThreadData GetThreadData(TargetPointer thread) => throw new NotImplementedException(); - ThreadAllocData GetThreadAllocData(TargetPointer thread) => throw new NotImplementedException(); + ThreadAllocData GetThreadAllocContext(TargetPointer thread) => throw new NotImplementedException(); TargetPointer IdToThread(uint id) => throw new NotImplementedException(); TargetPointer GetThreadLocalStaticBase(TargetPointer threadPointer, TargetPointer tlsIndexPtr) => throw new NotImplementedException(); TargetPointer GetThrowableObject(TargetPointer threadPointer) => throw new NotImplementedException(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs index 3a1ccfd5a29107..0f9885052a049d 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs @@ -76,7 +76,7 @@ ThreadData IThread.GetThreadData(TargetPointer threadPointer) GetThreadFromLink(thread.LinkNext)); } - ThreadAllocData IThread.GetThreadAllocData(TargetPointer threadPointer) + ThreadAllocData IThread.GetThreadAllocContext(TargetPointer threadPointer) { Data.Thread thread = _target.ProcessedData.GetOrAdd(threadPointer); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs index 240daaff82881f..b31c94fc6e7edd 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -3000,17 +3000,16 @@ int ISOSDacInterface.GetSyncBlockData(uint number, void* data) int ISOSDacInterface.GetThreadAllocData(ClrDataAddress thread, DacpAllocData* data) { - if (thread == 0) - return HResults.E_INVALIDARG; - int hr = HResults.S_OK; try { - if (data is null) + if (thread == 0) throw new ArgumentException(); + if (data is null) + throw new NullReferenceException(); Contracts.IThread contract = _target.Contracts.Thread; - Contracts.ThreadAllocData allocData = contract.GetThreadAllocData(thread.ToTargetPointer(_target)); + Contracts.ThreadAllocData allocData = contract.GetThreadAllocContext(thread.ToTargetPointer(_target)); data->allocBytes = (ClrDataAddress)(ulong)allocData.AllocBytes; data->allocBytesLoh = (ClrDataAddress)(ulong)allocData.AllocBytesLoh; } diff --git a/src/native/managed/cdac/tests/DumpTests/ThreadDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/ThreadDumpTests.cs index b71e78f631e735..00a837b0605547 100644 --- a/src/native/managed/cdac/tests/DumpTests/ThreadDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/ThreadDumpTests.cs @@ -114,7 +114,7 @@ public void ThreadCounts_AreNonNegative(TestConfiguration config) [ConditionalTheory] [MemberData(nameof(TestConfigurations))] - public void GetThreadAllocData_CanReadForAllThreads(TestConfiguration config) + public void GetThreadAllocContext_CanReadForAllThreads(TestConfiguration config) { InitializeDumpTest(config); IThread threadContract = Target.Contracts.Thread; @@ -123,7 +123,7 @@ public void GetThreadAllocData_CanReadForAllThreads(TestConfiguration config) TargetPointer currentThread = storeData.FirstThread; while (currentThread != TargetPointer.Null) { - ThreadAllocData allocData = threadContract.GetThreadAllocData(currentThread); + ThreadAllocData allocData = threadContract.GetThreadAllocContext(currentThread); Assert.True(allocData.AllocBytes >= 0, $"AllocBytes should be non-negative, got {allocData.AllocBytes}"); Assert.True(allocData.AllocBytesLoh >= 0, $"AllocBytesLoh should be non-negative, got {allocData.AllocBytesLoh}"); diff --git a/src/native/managed/cdac/tests/ThreadTests.cs b/src/native/managed/cdac/tests/ThreadTests.cs index ac3f5476a2c102..2addabe5b6d15b 100644 --- a/src/native/managed/cdac/tests/ThreadTests.cs +++ b/src/native/managed/cdac/tests/ThreadTests.cs @@ -125,7 +125,7 @@ public void IterateThreads(MockTarget.Architecture arch) [Theory] [ClassData(typeof(MockTarget.StdArch))] - public void GetThreadAllocData(MockTarget.Architecture arch) + public void GetThreadAllocContext(MockTarget.Architecture arch) { TargetTestHelpers helpers = new(arch); MockMemorySpace.Builder builder = new(helpers); @@ -140,14 +140,14 @@ public void GetThreadAllocData(MockTarget.Architecture arch) IThread contract = target.Contracts.Thread; Assert.NotNull(contract); - ThreadAllocData data = contract.GetThreadAllocData(addr); + ThreadAllocData data = contract.GetThreadAllocContext(addr); Assert.Equal(allocBytes, data.AllocBytes); Assert.Equal(allocBytesLoh, data.AllocBytesLoh); } [Theory] [ClassData(typeof(MockTarget.StdArch))] - public void GetThreadAllocData_ZeroValues(MockTarget.Architecture arch) + public void GetThreadAllocContext_ZeroValues(MockTarget.Architecture arch) { TargetTestHelpers helpers = new(arch); MockMemorySpace.Builder builder = new(helpers); @@ -159,7 +159,7 @@ public void GetThreadAllocData_ZeroValues(MockTarget.Architecture arch) IThread contract = target.Contracts.Thread; Assert.NotNull(contract); - ThreadAllocData data = contract.GetThreadAllocData(addr); + ThreadAllocData data = contract.GetThreadAllocContext(addr); Assert.Equal(0, data.AllocBytes); Assert.Equal(0, data.AllocBytesLoh); } From 0abc0666d4311419158d1582af33f6d1c3102463 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 21:56:08 +0000 Subject: [PATCH 5/7] Remove extra trailing whitespace in Thread_1.cs Co-authored-by: max-charlamb <44248479+max-charlamb@users.noreply.github.com> --- .../Contracts/Thread_1.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs index 55ffdf0ad62848..ecd78544d4a11a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs @@ -94,7 +94,7 @@ void IThread.GetStackLimitData(TargetPointer threadPointer, out TargetPointer st stackLimit = thread.CachedStackLimit; frameAddress = threadPointer + (ulong)type.Fields[nameof(Data.Thread.Frame)].Offset; } - + // happens inside critical section TargetPointer IThread.IdToThread(uint id) { From 0e5922e01a1879625a375165ae15c2d147c1e9bb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 22:41:29 +0000 Subject: [PATCH 6/7] Fix build break: add missing MockDescriptors.Thread setup in GetThreadAllocContext test and fix trailing whitespace Co-authored-by: max-charlamb <44248479+max-charlamb@users.noreply.github.com> --- .../Contracts/Thread_1.cs | 2 +- src/native/managed/cdac/tests/ThreadTests.cs | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs index ecd78544d4a11a..9259891b601c19 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs @@ -84,7 +84,7 @@ ThreadAllocData IThread.GetThreadAllocContext(TargetPointer threadPointer) thread.RuntimeThreadLocals?.AllocContext.GCAllocationContext.AllocBytes ?? 0, thread.RuntimeThreadLocals?.AllocContext.GCAllocationContext.AllocBytesLoh ?? 0); } - + void IThread.GetStackLimitData(TargetPointer threadPointer, out TargetPointer stackBase, out TargetPointer stackLimit, out TargetPointer frameAddress) { Data.Thread thread = _target.ProcessedData.GetOrAdd(threadPointer); diff --git a/src/native/managed/cdac/tests/ThreadTests.cs b/src/native/managed/cdac/tests/ThreadTests.cs index 50dc5582bc680c..8a9e668ea87c75 100644 --- a/src/native/managed/cdac/tests/ThreadTests.cs +++ b/src/native/managed/cdac/tests/ThreadTests.cs @@ -122,11 +122,15 @@ public void IterateThreads(MockTarget.Architecture arch) Assert.Equal(expectedCount, count); } - + [Theory] - [ClassData(typeof(MockTarget.StdArch))] + [ClassData(typeof(MockTarget.StdArch))] public void GetThreadAllocContext(MockTarget.Architecture arch) { + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockDescriptors.Thread thread = new(builder); + long allocBytes = 1024; long allocBytesLoh = 4096; @@ -159,7 +163,7 @@ public void GetThreadAllocContext_ZeroValues(MockTarget.Architecture arch) Assert.Equal(0, data.AllocBytes); Assert.Equal(0, data.AllocBytesLoh); } - + [Theory] [ClassData(typeof(MockTarget.StdArch))] public void GetStackLimits(MockTarget.Architecture arch) From fa4b86721e659e438dc5ec46c6b529b9d18bcb69 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 22:52:56 +0000 Subject: [PATCH 7/7] Replace ThreadAllocData struct with out parameters on GetThreadAllocContext Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com> --- .../Contracts/IThread.cs | 6 +----- .../Contracts/Thread_1.cs | 7 +++---- .../SOSDacImpl.cs | 6 +++--- .../managed/cdac/tests/DumpTests/ThreadDumpTests.cs | 6 +++--- src/native/managed/cdac/tests/ThreadTests.cs | 12 ++++++------ 5 files changed, 16 insertions(+), 21 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs index ab504b5cd3155f..296552dfa73836 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs @@ -41,10 +41,6 @@ public record struct ThreadData( TargetPointer LastThrownObjectHandle, TargetPointer NextThread); -public record struct ThreadAllocData( - long AllocBytes, - long AllocBytesLoh); - public interface IThread : IContract { static string IContract.Name { get; } = nameof(Thread); @@ -52,7 +48,7 @@ public interface IThread : IContract ThreadStoreData GetThreadStoreData() => throw new NotImplementedException(); ThreadStoreCounts GetThreadCounts() => throw new NotImplementedException(); ThreadData GetThreadData(TargetPointer thread) => throw new NotImplementedException(); - ThreadAllocData GetThreadAllocContext(TargetPointer thread) => throw new NotImplementedException(); + void GetThreadAllocContext(TargetPointer thread, out long allocBytes, out long allocBytesLoh) => throw new NotImplementedException(); void GetStackLimitData(TargetPointer threadPointer, out TargetPointer stackBase, out TargetPointer stackLimit, out TargetPointer frameAddress) => throw new NotImplementedException(); TargetPointer IdToThread(uint id) => throw new NotImplementedException(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs index 9259891b601c19..3b91d1d2bb138e 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs @@ -76,13 +76,12 @@ ThreadData IThread.GetThreadData(TargetPointer threadPointer) GetThreadFromLink(thread.LinkNext)); } - ThreadAllocData IThread.GetThreadAllocContext(TargetPointer threadPointer) + void IThread.GetThreadAllocContext(TargetPointer threadPointer, out long allocBytes, out long allocBytesLoh) { Data.Thread thread = _target.ProcessedData.GetOrAdd(threadPointer); - return new ThreadAllocData( - thread.RuntimeThreadLocals?.AllocContext.GCAllocationContext.AllocBytes ?? 0, - thread.RuntimeThreadLocals?.AllocContext.GCAllocationContext.AllocBytesLoh ?? 0); + allocBytes = thread.RuntimeThreadLocals?.AllocContext.GCAllocationContext.AllocBytes ?? 0; + allocBytesLoh = thread.RuntimeThreadLocals?.AllocContext.GCAllocationContext.AllocBytesLoh ?? 0; } void IThread.GetStackLimitData(TargetPointer threadPointer, out TargetPointer stackBase, out TargetPointer stackLimit, out TargetPointer frameAddress) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs index d189ce13aed6da..473967ef96006f 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -3050,9 +3050,9 @@ int ISOSDacInterface.GetThreadAllocData(ClrDataAddress thread, DacpAllocData* da throw new NullReferenceException(); Contracts.IThread contract = _target.Contracts.Thread; - Contracts.ThreadAllocData allocData = contract.GetThreadAllocContext(thread.ToTargetPointer(_target)); - data->allocBytes = (ClrDataAddress)(ulong)allocData.AllocBytes; - data->allocBytesLoh = (ClrDataAddress)(ulong)allocData.AllocBytesLoh; + contract.GetThreadAllocContext(thread.ToTargetPointer(_target), out long allocBytes, out long allocBytesLoh); + data->allocBytes = (ClrDataAddress)(ulong)allocBytes; + data->allocBytesLoh = (ClrDataAddress)(ulong)allocBytesLoh; } catch (global::System.Exception ex) { diff --git a/src/native/managed/cdac/tests/DumpTests/ThreadDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/ThreadDumpTests.cs index 00a837b0605547..8e24fafc4d12f6 100644 --- a/src/native/managed/cdac/tests/DumpTests/ThreadDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/ThreadDumpTests.cs @@ -123,9 +123,9 @@ public void GetThreadAllocContext_CanReadForAllThreads(TestConfiguration config) TargetPointer currentThread = storeData.FirstThread; while (currentThread != TargetPointer.Null) { - ThreadAllocData allocData = threadContract.GetThreadAllocContext(currentThread); - Assert.True(allocData.AllocBytes >= 0, $"AllocBytes should be non-negative, got {allocData.AllocBytes}"); - Assert.True(allocData.AllocBytesLoh >= 0, $"AllocBytesLoh should be non-negative, got {allocData.AllocBytesLoh}"); + threadContract.GetThreadAllocContext(currentThread, out long allocBytes, out long allocBytesLoh); + Assert.True(allocBytes >= 0, $"AllocBytes should be non-negative, got {allocBytes}"); + Assert.True(allocBytesLoh >= 0, $"AllocBytesLoh should be non-negative, got {allocBytesLoh}"); ThreadData threadData = threadContract.GetThreadData(currentThread); currentThread = threadData.NextThread; diff --git a/src/native/managed/cdac/tests/ThreadTests.cs b/src/native/managed/cdac/tests/ThreadTests.cs index 8a9e668ea87c75..ef768016a2a9f9 100644 --- a/src/native/managed/cdac/tests/ThreadTests.cs +++ b/src/native/managed/cdac/tests/ThreadTests.cs @@ -140,9 +140,9 @@ public void GetThreadAllocContext(MockTarget.Architecture arch) IThread contract = target.Contracts.Thread; Assert.NotNull(contract); - ThreadAllocData data = contract.GetThreadAllocContext(addr); - Assert.Equal(allocBytes, data.AllocBytes); - Assert.Equal(allocBytesLoh, data.AllocBytesLoh); + contract.GetThreadAllocContext(addr, out long resultAllocBytes, out long resultAllocBytesLoh); + Assert.Equal(allocBytes, resultAllocBytes); + Assert.Equal(allocBytesLoh, resultAllocBytesLoh); } [Theory] @@ -159,9 +159,9 @@ public void GetThreadAllocContext_ZeroValues(MockTarget.Architecture arch) IThread contract = target.Contracts.Thread; Assert.NotNull(contract); - ThreadAllocData data = contract.GetThreadAllocContext(addr); - Assert.Equal(0, data.AllocBytes); - Assert.Equal(0, data.AllocBytesLoh); + contract.GetThreadAllocContext(addr, out long allocBytes, out long allocBytesLoh); + Assert.Equal(0, allocBytes); + Assert.Equal(0, allocBytesLoh); } [Theory]