diff --git a/docs/design/datacontracts/Thread.md b/docs/design/datacontracts/Thread.md index c8e17a88ebf152..606b3d924ad6ad 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,16 +56,16 @@ 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 -| `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 @@ -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/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..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 @@ -48,6 +48,8 @@ public interface IThread : IContract ThreadStoreData GetThreadStoreData() => throw new NotImplementedException(); ThreadStoreCounts GetThreadCounts() => throw new NotImplementedException(); ThreadData GetThreadData(TargetPointer thread) => 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 631c809f9b458e..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,6 +76,16 @@ ThreadData IThread.GetThreadData(TargetPointer threadPointer) GetThreadFromLink(thread.LinkNext)); } + 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); + + stackBase = thread.CachedStackBase; + stackLimit = thread.CachedStackLimit; + frameAddress = threadPointer + (ulong)type.Fields[nameof(Data.Thread.Frame)].Offset; + } + // 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..54d24e0f26737c 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,48 @@ 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; + { + int hr = HResults.S_OK; + try + { + if (threadPtr == 0 || (lower == null && upper == null && fp == null)) + throw new ArgumentException(); + + Contracts.IThread contract = _target.Contracts.Thread; + TargetPointer stackBase, stackLimit, frameAddress; + contract.GetStackLimitData(threadPtr.ToTargetPointer(_target), out stackBase, out stackLimit, out frameAddress); + + if (lower != null) + *lower = stackBase.ToClrDataAddress(_target); + + if (upper != null) + *upper = stackLimit.ToClrDataAddress(_target); + + if (fp != null) + *fp = 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..64a2a103421ccd 100644 --- a/src/native/managed/cdac/tests/ThreadTests.cs +++ b/src/native/managed/cdac/tests/ThreadTests.cs @@ -122,4 +122,36 @@ 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; + TargetPointer outStackBase, outStackLimit, outFrameAddress; + + contract.GetStackLimitData(addr, out outStackBase, out outStackLimit, out outFrameAddress); + Assert.Equal(stackBase, outStackBase); + Assert.Equal(stackLimit, outStackLimit); + Assert.Equal(expectedFrameAddr, outFrameAddress); + } }