From 3815971d1c04a13df95a0c66ef42da990bc0d613 Mon Sep 17 00:00:00 2001 From: Max Charlamb Date: Thu, 16 Jul 2026 13:18:36 -0400 Subject: [PATCH 1/2] [cDAC] Include the highest assigned ID in IdToThread lookup The runtime's IdDispenser::IdToThread resolves a thread by ID using an inclusive bound (`if (id <= m_highestId)`). The cDAC IThread.IdToThread implementation used an exclusive `<` comparison, so it failed to resolve the thread occupying the highest assigned ID slot -- an off-by-one that made the highest valid ID always return a null thread pointer. Match the native inclusive bound and add a boundary unit test that verifies the highest ID resolves to the expected thread while the next ID beyond it still returns null. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: f2aa5dc4-0c99-43f0-af14-734143849c4a --- docs/design/datacontracts/Thread.md | 2 +- .../Contracts/Thread_1.cs | 2 +- .../cdac/tests/UnitTests/ThreadTests.cs | 51 +++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/docs/design/datacontracts/Thread.md b/docs/design/datacontracts/Thread.md index 8be90599fd9bd8..df6b58ecc08bf9 100644 --- a/docs/design/datacontracts/Thread.md +++ b/docs/design/datacontracts/Thread.md @@ -286,7 +286,7 @@ TargetPointer IThread.IdToThread(uint id) TargetPointer idDispenser = target.ReadPointer(idDispenserPointer); uint HighestId = target.ReadPointer(idDispenser + /* IdDispenser::HighestId offset */); TargetPointer threadPtr = TargetPointer.Null; - if (id < HighestId) + if (id <= HighestId) threadPtr = target.ReadPointer(idDispenser + /* IdDispenser::IdToThread offset + (index into IdToThread array * size of array elements (== size of target pointer)) */); return threadPtr; } 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 d5ce23c8f1cc88..f7933e18239b97 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 @@ -205,7 +205,7 @@ TargetPointer IThread.IdToThread(uint id) TargetPointer idDispenser = _target.ReadPointer(idDispenserPtr); Data.IdDispenser idDispenserObj = _target.ProcessedData.GetOrAdd(idDispenser); TargetPointer threadPtr = TargetPointer.Null; - if (id < idDispenserObj.HighestId) + if (id <= idDispenserObj.HighestId) threadPtr = _target.ReadPointer(idDispenserObj.IdToThread + (ulong)(id * _target.PointerSize)); return threadPtr; } diff --git a/src/native/managed/cdac/tests/UnitTests/ThreadTests.cs b/src/native/managed/cdac/tests/UnitTests/ThreadTests.cs index bd286ee8afddfc..84c41bdac860d2 100644 --- a/src/native/managed/cdac/tests/UnitTests/ThreadTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/ThreadTests.cs @@ -42,6 +42,57 @@ private static TestPlaceholderTarget CreateTarget( [DataType.RuntimeThreadLocals] = TargetTestHelpers.CreateTypeInfo(threadBuilder.RuntimeThreadLocalsLayout), }; + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void IdToThread_IncludesHighestId(MockTarget.Architecture arch) + { + const uint HighestId = 3; + const ulong ExpectedThread = 0x7000; + TargetTestHelpers helpers = new(arch); + var builder = new TestPlaceholderTarget.Builder(arch); + MockMemorySpace.BumpAllocator allocator = builder.MemoryBuilder.CreateAllocator(0x1000, 0x9000); + TargetTestHelpers.LayoutResult layout = helpers.LayoutFields( + [ + new(nameof(Data.IdDispenser.IdToThread), DataType.pointer), + new(nameof(Data.IdDispenser.HighestId), DataType.uint32), + ]); + + MockMemorySpace.HeapFragment idToThread = allocator.Allocate( + (ulong)((HighestId + 1) * helpers.PointerSize), + "IdToThread"); + helpers.WritePointer( + idToThread.Data.AsSpan((int)HighestId * helpers.PointerSize, helpers.PointerSize), + ExpectedThread); + + MockMemorySpace.HeapFragment dispenser = allocator.Allocate(layout.Stride, "IdDispenser"); + helpers.WritePointer( + dispenser.Data.AsSpan(layout.Fields[nameof(Data.IdDispenser.IdToThread)].Offset, helpers.PointerSize), + idToThread.Address); + helpers.Write( + dispenser.Data.AsSpan(layout.Fields[nameof(Data.IdDispenser.HighestId)].Offset, sizeof(uint)), + HighestId); + + MockMemorySpace.HeapFragment global = allocator.Allocate((ulong)helpers.PointerSize, "ThinlockThreadIdDispenser"); + helpers.WritePointer(global.Data, dispenser.Address); + MockMemorySpace.HeapFragment threadStoreGlobal = allocator.Allocate((ulong)helpers.PointerSize, "ThreadStore"); + helpers.WritePointer(threadStoreGlobal.Data, 0); + + Target target = builder + .AddTypes(new Dictionary + { + [DataType.IdDispenser] = new Target.TypeInfo { Fields = layout.Fields, Size = layout.Stride }, + [DataType.Thread] = new Target.TypeInfo { Fields = new Dictionary(), Size = 0 }, + }) + .AddGlobals( + (Constants.Globals.ThinlockThreadIdDispenser, global.Address), + (Constants.Globals.ThreadStore, threadStoreGlobal.Address)) + .AddContract("c1") + .Build(); + + Assert.Equal(new TargetPointer(ExpectedThread), target.Contracts.Thread.IdToThread(HighestId)); + Assert.Equal(TargetPointer.Null, target.Contracts.Thread.IdToThread(HighestId + 1)); + } + [Theory] [ClassData(typeof(MockTarget.StdArch))] public void GetThreadStoreData(MockTarget.Architecture arch) From f121f8c9d698be9aac65e02ff80916e01aaf5317 Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Thu, 16 Jul 2026 13:28:18 -0400 Subject: [PATCH 2/2] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- docs/design/datacontracts/Thread.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design/datacontracts/Thread.md b/docs/design/datacontracts/Thread.md index df6b58ecc08bf9..a3a8241e05431d 100644 --- a/docs/design/datacontracts/Thread.md +++ b/docs/design/datacontracts/Thread.md @@ -284,7 +284,7 @@ TargetPointer IThread.IdToThread(uint id) { TargetPointer idDispenserPointer = target.ReadGlobalPointer(Constants.Globals.ThinlockThreadIdDispenser); TargetPointer idDispenser = target.ReadPointer(idDispenserPointer); - uint HighestId = target.ReadPointer(idDispenser + /* IdDispenser::HighestId offset */); + uint HighestId = target.Read(idDispenser + /* IdDispenser::HighestId offset */); TargetPointer threadPtr = TargetPointer.Null; if (id <= HighestId) threadPtr = target.ReadPointer(idDispenser + /* IdDispenser::IdToThread offset + (index into IdToThread array * size of array elements (== size of target pointer)) */);