Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 21 additions & 11 deletions docs/design/datacontracts/Thread.md
Original file line number Diff line number Diff line change
Expand Up @@ -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);
```
Expand All @@ -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

Expand Down Expand Up @@ -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 |
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -290,7 +300,7 @@ byte[] IThread.GetWatsonBuckets(TargetPointer threadPointer)
Span<byte> span = new byte[_target.ReadGlobal<uint>("SizeOfGenericModeBlock")];
if (readFrom == TargetPointer.Null)
return Array.Empty<byte>();

_target.ReadBuffer(readFrom, span);
return span.ToArray();
}
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/vm/datadescriptor/datadescriptor.inc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ CDAC_TYPE_FIELD(Thread, /*uint32*/, State, cdac_data<Thread>::State)
CDAC_TYPE_FIELD(Thread, /*uint32*/, PreemptiveGCDisabled, cdac_data<Thread>::PreemptiveGCDisabled)
CDAC_TYPE_FIELD(Thread, /*pointer*/, RuntimeThreadLocals, cdac_data<Thread>::RuntimeThreadLocals)
CDAC_TYPE_FIELD(Thread, /*pointer*/, Frame, cdac_data<Thread>::Frame)
CDAC_TYPE_FIELD(Thread, /*pointer*/, CachedStackBase, cdac_data<Thread>::CachedStackBase)
CDAC_TYPE_FIELD(Thread, /*pointer*/, CachedStackLimit, cdac_data<Thread>::CachedStackLimit)
CDAC_TYPE_FIELD(Thread, /*pointer*/, ExceptionTracker, cdac_data<Thread>::ExceptionTracker)
CDAC_TYPE_FIELD(Thread, GCHandle, GCHandle, cdac_data<Thread>::ExposedObject)
CDAC_TYPE_FIELD(Thread, GCHandle, LastThrownObject, cdac_data<Thread>::LastThrownObject)
Expand Down
4 changes: 3 additions & 1 deletion src/coreclr/vm/threads.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -3761,6 +3761,8 @@ struct cdac_data<Thread>
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Data.Thread>(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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public Thread(Target target, TargetPointer address)
RuntimeThreadLocals = target.ProcessedData.GetOrAdd<RuntimeThreadLocals>(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)
Expand All @@ -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; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<byte> 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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
32 changes: 32 additions & 0 deletions src/native/managed/cdac/tests/ThreadTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Loading