From 186dfec4a839c2c5b2865e8ddbb3d96bc411beb1 Mon Sep 17 00:00:00 2001 From: Barbara Rosiak Date: Fri, 20 Feb 2026 16:17:03 -0800 Subject: [PATCH 1/3] Implement GetStackLimits for cDAC --- .../vm/datadescriptor/datadescriptor.inc | 2 + src/coreclr/vm/threads.h | 4 +- .../Contracts/IThread.cs | 6 +++ .../Contracts/Thread_1.cs | 12 ++++++ .../Data/Thread.cs | 4 ++ .../SOSDacImpl.cs | 42 ++++++++++++++++++- .../MockDescriptors/MockDescriptors.Thread.cs | 9 ++++ .../tests/MockDescriptors/MockDescriptors.cs | 2 + src/native/managed/cdac/tests/ThreadTests.cs | 31 ++++++++++++++ 9 files changed, 110 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index cc50f5dde51f01..eb534c48c4a5b4 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -18,6 +18,8 @@ CDAC_TYPE_FIELD(Thread, /*uint32*/, State, cdac_data::State) CDAC_TYPE_FIELD(Thread, /*uint32*/, PreemptiveGCDisabled, cdac_data::PreemptiveGCDisabled) CDAC_TYPE_FIELD(Thread, /*pointer*/, RuntimeThreadLocals, cdac_data::RuntimeThreadLocals) CDAC_TYPE_FIELD(Thread, /*pointer*/, Frame, cdac_data::Frame) +CDAC_TYPE_FIELD(Thread, /*pointer*/, CachedStackBase, cdac_data::CachedStackBase) +CDAC_TYPE_FIELD(Thread, /*pointer*/, CachedStackLimit, cdac_data::CachedStackLimit) CDAC_TYPE_FIELD(Thread, /*pointer*/, ExceptionTracker, cdac_data::ExceptionTracker) CDAC_TYPE_FIELD(Thread, GCHandle, GCHandle, cdac_data::ExposedObject) CDAC_TYPE_FIELD(Thread, GCHandle, LastThrownObject, cdac_data::LastThrownObject) diff --git a/src/coreclr/vm/threads.h b/src/coreclr/vm/threads.h index 7ec59c500b21e2..83ee113fa4b40e 100644 --- a/src/coreclr/vm/threads.h +++ b/src/coreclr/vm/threads.h @@ -2085,7 +2085,7 @@ class Thread #define SKIPFUNCLETS 0x0002 // UNUSED 0x0004 - + #define QUICKUNWIND 0x0008 // do not restore all registers during unwind #define HANDLESKIPPEDFRAMES 0x0010 // temporary to handle skipped frames for appdomain unload @@ -3761,6 +3761,8 @@ struct cdac_data static constexpr size_t PreemptiveGCDisabled = offsetof(Thread, m_fPreemptiveGCDisabled); static constexpr size_t RuntimeThreadLocals = offsetof(Thread, m_pRuntimeThreadLocals); static constexpr size_t Frame = offsetof(Thread, m_pFrame); + static constexpr size_t CachedStackBase = offsetof(Thread, m_CacheStackBase); + static constexpr size_t CachedStackLimit = offsetof(Thread, m_CacheStackLimit); static constexpr size_t ExposedObject = offsetof(Thread, m_ExposedObject); static constexpr size_t LastThrownObject = offsetof(Thread, m_LastThrownObjectHandle); static constexpr size_t Link = offsetof(Thread, m_Link); 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..d06c6a0330a212 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,11 @@ public record struct ThreadData( TargetPointer LastThrownObjectHandle, TargetPointer NextThread); +public record struct StackLimitData( + TargetPointer StackBase, + TargetPointer StackLimit, + TargetPointer FrameAddress); + public interface IThread : IContract { static string IContract.Name { get; } = nameof(Thread); @@ -48,6 +53,7 @@ public interface IThread : IContract ThreadStoreData GetThreadStoreData() => throw new NotImplementedException(); ThreadStoreCounts GetThreadCounts() => throw new NotImplementedException(); ThreadData GetThreadData(TargetPointer thread) => throw new NotImplementedException(); + StackLimitData GetStackLimitData(TargetPointer threadPointer) => 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..80b26b28eb4c07 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,18 @@ ThreadData IThread.GetThreadData(TargetPointer threadPointer) GetThreadFromLink(thread.LinkNext)); } + StackLimitData IThread.GetStackLimitData(TargetPointer threadPointer) + { + Data.Thread thread = _target.ProcessedData.GetOrAdd(threadPointer); + Target.TypeInfo type = _target.GetTypeInfo(DataType.Thread); + TargetPointer frameFieldAddr = threadPointer + (ulong)type.Fields[nameof(Data.Thread.Frame)].Offset; + + return new StackLimitData( + thread.CachedStackBase, + thread.CachedStackLimit, + frameFieldAddr); + } + // happens inside critical section TargetPointer IThread.IdToThread(uint id) { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Thread.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Thread.cs index ccdc5017b5d5b8..a49d65cf0fbd27 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Thread.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Thread.cs @@ -22,6 +22,8 @@ public Thread(Target target, TargetPointer address) RuntimeThreadLocals = target.ProcessedData.GetOrAdd(runtimeThreadLocalsPointer); Frame = target.ReadPointer(address + (ulong)type.Fields[nameof(Frame)].Offset); + CachedStackBase = target.ReadPointer(address + (ulong)type.Fields[nameof(CachedStackBase)].Offset); + CachedStackLimit = target.ReadPointer(address + (ulong)type.Fields[nameof(CachedStackLimit)].Offset); // TEB does not exist on certain platforms TEB = type.Fields.TryGetValue(nameof(TEB), out Target.FieldInfo fieldInfo) @@ -46,6 +48,8 @@ public Thread(Target target, TargetPointer address) public uint PreemptiveGCDisabled { get; init; } public RuntimeThreadLocals? RuntimeThreadLocals { get; init; } public TargetPointer Frame { get; init; } + public TargetPointer CachedStackBase { get; init; } + public TargetPointer CachedStackLimit { get; init; } public TargetPointer TEB { get; init; } public ObjectHandle LastThrownObject { get; init; } public TargetPointer LinkNext { get; init; } 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..e1482e55656ae0 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -2973,7 +2973,47 @@ int ISOSDacInterface.GetRCWInterfaces(ClrDataAddress rcw, uint count, void* inte int ISOSDacInterface.GetRegisterName(int regName, uint count, char* buffer, uint* pNeeded) => _legacyImpl is not null ? _legacyImpl.GetRegisterName(regName, count, buffer, pNeeded) : HResults.E_NOTIMPL; int ISOSDacInterface.GetStackLimits(ClrDataAddress threadPtr, ClrDataAddress* lower, ClrDataAddress* upper, ClrDataAddress* fp) - => _legacyImpl is not null ? _legacyImpl.GetStackLimits(threadPtr, lower, upper, fp) : HResults.E_NOTIMPL; + { + if (threadPtr == 0 || (lower == null && upper == null && fp == null)) + return HResults.E_INVALIDARG; + + int hr = HResults.S_OK; + try + { + Contracts.IThread contract = _target.Contracts.Thread; + Contracts.StackLimitData stackLimitData = contract.GetStackLimitData(threadPtr.ToTargetPointer(_target)); + + if (lower != null) + *lower = stackLimitData.StackBase.ToClrDataAddress(_target); + + if (upper != null) + *upper = stackLimitData.StackLimit.ToClrDataAddress(_target); + + if (fp != null) + *fp = stackLimitData.FrameAddress.ToClrDataAddress(_target); + } + catch (global::System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacyImpl is not null) + { + ClrDataAddress lowerLocal, upperLocal, fpLocal; + int hrLocal = _legacyImpl.GetStackLimits(threadPtr, &lowerLocal, &upperLocal, &fpLocal); + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + if (hr == HResults.S_OK) + { + Debug.Assert(lower == null || *lower == lowerLocal, $"cDAC: {*lower:x}, DAC: {lowerLocal:x}"); + Debug.Assert(upper == null || *upper == upperLocal, $"cDAC: {*upper:x}, DAC: {upperLocal:x}"); + Debug.Assert(fp == null || *fp == fpLocal, $"cDAC: {*fp:x}, DAC: {fpLocal:x}"); + } + } +#endif + return hr; + } + int ISOSDacInterface.GetStackReferences(int osThreadID, void** ppEnum) => _legacyImpl is not null ? _legacyImpl.GetStackReferences(osThreadID, ppEnum) : HResults.E_NOTIMPL; diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Thread.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Thread.cs index 12dc6299503998..2d778d2d2778b5 100644 --- a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Thread.cs +++ b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Thread.cs @@ -143,5 +143,14 @@ internal TargetPointer AddThread(uint id, TargetNUInt osId) _previousThread = thread.Address; return thread.Address; } + + internal void SetStackLimits(TargetPointer threadAddress, TargetPointer stackBase, TargetPointer stackLimit) + { + TargetTestHelpers helpers = Builder.TargetTestHelpers; + Target.TypeInfo threadType = Types[DataType.Thread]; + Span data = Builder.BorrowAddressRange(threadAddress, (int)threadType.Size.Value); + helpers.WritePointer(data.Slice(threadType.Fields[nameof(Data.Thread.CachedStackBase)].Offset), stackBase); + helpers.WritePointer(data.Slice(threadType.Fields[nameof(Data.Thread.CachedStackLimit)].Offset), stackLimit); + } } } diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs index 442e74eb2ff975..6c111cee24ef8b 100644 --- a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs +++ b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs @@ -193,6 +193,8 @@ internal record TypeFields new(nameof(Data.Thread.PreemptiveGCDisabled), DataType.uint32), new(nameof(Data.Thread.RuntimeThreadLocals), DataType.pointer), new(nameof(Data.Thread.Frame), DataType.pointer), + new(nameof(Data.Thread.CachedStackBase), DataType.pointer), + new(nameof(Data.Thread.CachedStackLimit), DataType.pointer), new(nameof(Data.Thread.TEB), DataType.pointer), new(nameof(Data.Thread.LastThrownObject), DataType.pointer), new(nameof(Data.Thread.LinkNext), DataType.pointer), diff --git a/src/native/managed/cdac/tests/ThreadTests.cs b/src/native/managed/cdac/tests/ThreadTests.cs index f8998c7766ab7b..f9ece5c94bd412 100644 --- a/src/native/managed/cdac/tests/ThreadTests.cs +++ b/src/native/managed/cdac/tests/ThreadTests.cs @@ -122,4 +122,35 @@ public void IterateThreads(MockTarget.Architecture arch) Assert.Equal(expectedCount, count); } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetStackLimits(MockTarget.Architecture arch) + { + // Set up the target + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockDescriptors.Thread thread = new(builder); + + uint id = 1; + TargetNUInt osId = new TargetNUInt(1234); + TargetPointer stackBase = new TargetPointer(0xAA00); + TargetPointer stackLimit = new TargetPointer(0xA000); + + // Add thread and set stack limits + TargetPointer addr = thread.AddThread(id, osId); + thread.SetStackLimits(addr, stackBase, stackLimit); + Target target = CreateTarget(thread); + + // Validate the expected stack limit values + IThread contract = target.Contracts.Thread; + Assert.NotNull(contract); + Target.TypeInfo threadType = target.GetTypeInfo(DataType.Thread); + TargetPointer expectedFrameAddr = addr + (ulong)threadType.Fields[nameof(Data.Thread.Frame)].Offset; + + StackLimitData data = contract.GetStackLimitData(addr); + Assert.Equal(stackBase, data.StackBase); + Assert.Equal(stackLimit, data.StackLimit); + Assert.Equal(expectedFrameAddr, data.FrameAddress); + } } From ed7f179e679cc6fc7cf511ae4c22be165c120ac5 Mon Sep 17 00:00:00 2001 From: Barbara Rosiak Date: Mon, 23 Feb 2026 12:25:10 -0800 Subject: [PATCH 2/3] Apply fixes to pr comments --- docs/design/datacontracts/Thread.md | 14 ++++++++++++-- .../Contracts/IThread.cs | 8 ++------ .../Contracts/Thread_1.cs | 10 ++++------ .../SOSDacImpl.cs | 15 ++++++++------- src/native/managed/cdac/tests/ThreadTests.cs | 9 +++++---- 5 files changed, 31 insertions(+), 25 deletions(-) diff --git a/docs/design/datacontracts/Thread.md b/docs/design/datacontracts/Thread.md index c8e17a88ebf152..15c294b1394e5f 100644 --- a/docs/design/datacontracts/Thread.md +++ b/docs/design/datacontracts/Thread.md @@ -46,6 +46,7 @@ record struct ThreadData ( ThreadStoreData GetThreadStoreData(); ThreadStoreCounts GetThreadCounts(); ThreadData GetThreadData(TargetPointer threadPointer); +void GetStackLimitData(TargetPointer threadPointer, out TargetPointer stackBase, out TargetPointer stackLimit, out TargetPointer frameAddress); TargetPointer IdToThread(uint id); TargetPointer GetThreadLocalStaticBase(TargetPointer threadPointer, int indexOffset, int indexType); ``` @@ -55,7 +56,7 @@ TargetPointer GetThreadLocalStaticBase(TargetPointer threadPointer, int indexOff The contract depends on the following globals | Global name | Type | Meaning | -| --- | --- | +| --- | --- | --- | | `AppDomain` | TargetPointer | A pointer to the address of the one AppDomain | `ThreadStore` | TargetPointer | A pointer to the address of the ThreadStore | `FeatureEHFunclets` | TargetPointer | 1 if EH funclets are enabled, 0 otherwise @@ -92,6 +93,8 @@ The contract additionally depends on these data descriptors | `Thread` | `State` | Thread state flags | | `Thread` | `PreemptiveGCDisabled` | Flag indicating if preemptive GC is disabled | | `Thread` | `Frame` | Pointer to current frame | +| `Thread` | `CachedStackBase` | Pointer to the base of the stack | +| `Thread` | `CachedStackLimit` | Pointer to the limit of the stack | | `Thread` | `TEB` | Thread Environment Block pointer | | `Thread` | `LastThrownObject` | Handle to last thrown exception object | | `Thread` | `LinkNext` | Pointer to get next thread | @@ -186,6 +189,13 @@ ThreadData GetThreadData(TargetPointer address) ); } +void IThread.GetStackLimitData(TargetPointer threadPointer, out TargetPointer stackBase, out TargetPointer stackLimit, out TargetPointer frameAddress) +{ + stackBase = target.ReadPointer(threadPointer + /* Thread::CachedStackBase offset */); + stackLimit = target.ReadPointer(threadPointer + /* Thread::CachedStackLimit offset */); + frameAddress = threadPointer + /* Thread::Frame offset */; +} + TargetPointer IThread.IdToThread(uint id) { TargetPointer idDispenserPointer = target.ReadGlobalPointer(Constants.Globals.ThinlockThreadIdDispenser); @@ -290,7 +300,7 @@ byte[] IThread.GetWatsonBuckets(TargetPointer threadPointer) Span span = new byte[_target.ReadGlobal("SizeOfGenericModeBlock")]; if (readFrom == TargetPointer.Null) return Array.Empty(); - + _target.ReadBuffer(readFrom, span); return span.ToArray(); } 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 d06c6a0330a212..6dabae55c2d6ad 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,11 +41,6 @@ public record struct ThreadData( TargetPointer LastThrownObjectHandle, TargetPointer NextThread); -public record struct StackLimitData( - TargetPointer StackBase, - TargetPointer StackLimit, - TargetPointer FrameAddress); - public interface IThread : IContract { static string IContract.Name { get; } = nameof(Thread); @@ -53,7 +48,8 @@ public interface IThread : IContract ThreadStoreData GetThreadStoreData() => throw new NotImplementedException(); ThreadStoreCounts GetThreadCounts() => throw new NotImplementedException(); ThreadData GetThreadData(TargetPointer thread) => throw new NotImplementedException(); - StackLimitData GetStackLimitData(TargetPointer threadPointer) => 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(); 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 80b26b28eb4c07..c1e31f70c9cd9e 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,16 +76,14 @@ ThreadData IThread.GetThreadData(TargetPointer threadPointer) GetThreadFromLink(thread.LinkNext)); } - StackLimitData IThread.GetStackLimitData(TargetPointer threadPointer) + void IThread.GetStackLimitData(TargetPointer threadPointer, out TargetPointer stackBase, out TargetPointer stackLimit, out TargetPointer frameAddress) { Data.Thread thread = _target.ProcessedData.GetOrAdd(threadPointer); Target.TypeInfo type = _target.GetTypeInfo(DataType.Thread); - TargetPointer frameFieldAddr = threadPointer + (ulong)type.Fields[nameof(Data.Thread.Frame)].Offset; - return new StackLimitData( - thread.CachedStackBase, - thread.CachedStackLimit, - frameFieldAddr); + stackBase = thread.CachedStackBase; + stackLimit = thread.CachedStackLimit; + frameAddress = threadPointer + (ulong)type.Fields[nameof(Data.Thread.Frame)].Offset; } // happens inside critical section 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 e1482e55656ae0..54d24e0f26737c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -2974,23 +2974,24 @@ int ISOSDacInterface.GetRegisterName(int regName, uint count, char* buffer, uint => _legacyImpl is not null ? _legacyImpl.GetRegisterName(regName, count, buffer, pNeeded) : HResults.E_NOTIMPL; int ISOSDacInterface.GetStackLimits(ClrDataAddress threadPtr, ClrDataAddress* lower, ClrDataAddress* upper, ClrDataAddress* fp) { - if (threadPtr == 0 || (lower == null && upper == null && fp == null)) - return HResults.E_INVALIDARG; - int hr = HResults.S_OK; try { + if (threadPtr == 0 || (lower == null && upper == null && fp == null)) + throw new ArgumentException(); + Contracts.IThread contract = _target.Contracts.Thread; - Contracts.StackLimitData stackLimitData = contract.GetStackLimitData(threadPtr.ToTargetPointer(_target)); + TargetPointer stackBase, stackLimit, frameAddress; + contract.GetStackLimitData(threadPtr.ToTargetPointer(_target), out stackBase, out stackLimit, out frameAddress); if (lower != null) - *lower = stackLimitData.StackBase.ToClrDataAddress(_target); + *lower = stackBase.ToClrDataAddress(_target); if (upper != null) - *upper = stackLimitData.StackLimit.ToClrDataAddress(_target); + *upper = stackLimit.ToClrDataAddress(_target); if (fp != null) - *fp = stackLimitData.FrameAddress.ToClrDataAddress(_target); + *fp = frameAddress.ToClrDataAddress(_target); } catch (global::System.Exception ex) { diff --git a/src/native/managed/cdac/tests/ThreadTests.cs b/src/native/managed/cdac/tests/ThreadTests.cs index f9ece5c94bd412..64a2a103421ccd 100644 --- a/src/native/managed/cdac/tests/ThreadTests.cs +++ b/src/native/managed/cdac/tests/ThreadTests.cs @@ -147,10 +147,11 @@ public void GetStackLimits(MockTarget.Architecture arch) Assert.NotNull(contract); Target.TypeInfo threadType = target.GetTypeInfo(DataType.Thread); TargetPointer expectedFrameAddr = addr + (ulong)threadType.Fields[nameof(Data.Thread.Frame)].Offset; + TargetPointer outStackBase, outStackLimit, outFrameAddress; - StackLimitData data = contract.GetStackLimitData(addr); - Assert.Equal(stackBase, data.StackBase); - Assert.Equal(stackLimit, data.StackLimit); - Assert.Equal(expectedFrameAddr, data.FrameAddress); + contract.GetStackLimitData(addr, out outStackBase, out outStackLimit, out outFrameAddress); + Assert.Equal(stackBase, outStackBase); + Assert.Equal(stackLimit, outStackLimit); + Assert.Equal(expectedFrameAddr, outFrameAddress); } } From 5d962b740e480e676283905983e025ffa643533d Mon Sep 17 00:00:00 2001 From: Barbara Rosiak Date: Mon, 23 Feb 2026 14:53:10 -0800 Subject: [PATCH 3/3] Add trailing | to a markdown table --- docs/design/datacontracts/Thread.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/design/datacontracts/Thread.md b/docs/design/datacontracts/Thread.md index 15c294b1394e5f..606b3d924ad6ad 100644 --- a/docs/design/datacontracts/Thread.md +++ b/docs/design/datacontracts/Thread.md @@ -57,15 +57,15 @@ The contract depends on the following globals | Global name | Type | Meaning | | --- | --- | --- | -| `AppDomain` | TargetPointer | A pointer to the address of the one AppDomain -| `ThreadStore` | TargetPointer | A pointer to the address of the ThreadStore -| `FeatureEHFunclets` | TargetPointer | 1 if EH funclets are enabled, 0 otherwise -| `FinalizerThread` | TargetPointer | A pointer to the finalizer thread -| `GCThread` | TargetPointer | A pointer to the GC thread -| `ThinLockThreadIdDispenser` | TargetPointer | Dispenser of thinlock IDs for locking objects -| `NumberOfTlsOffsetsNotUsedInNoncollectibleArray` | byte | Number of unused slots in noncollectible TLS array -| `PtrArrayOffsetToDataArray` | TargetPointer | Offset from PtrArray class address to start of enclosed data array -| `SizeOfGenericModeBlock` | uint32 | Size of GenericModeBlock struct +| `AppDomain` | TargetPointer | A pointer to the address of the one AppDomain | +| `ThreadStore` | TargetPointer | A pointer to the address of the ThreadStore | +| `FeatureEHFunclets` | TargetPointer | 1 if EH funclets are enabled, 0 otherwise | +| `FinalizerThread` | TargetPointer | A pointer to the finalizer thread | +| `GCThread` | TargetPointer | A pointer to the GC thread | +| `ThinLockThreadIdDispenser` | TargetPointer | Dispenser of thinlock IDs for locking objects | +| `NumberOfTlsOffsetsNotUsedInNoncollectibleArray` | byte | Number of unused slots in noncollectible TLS array | +| `PtrArrayOffsetToDataArray` | TargetPointer | Offset from PtrArray class address to start of enclosed data array | +| `SizeOfGenericModeBlock` | uint32 | Size of GenericModeBlock struct | The contract additionally depends on these data descriptors