Back to Dotnet

Contract Thread

docs/design/datacontracts/Thread.md

11.0.10015.5 KB
Original Source

Contract Thread

This contract is for reading and iterating the threads of the process.

APIs of contract

csharp
record struct ThreadStoreData (
    int ThreadCount,
    TargetPointer FirstThread,
    TargetPointer FinalizerThread,
    TargetPointer GcThread);

record struct ThreadStoreCounts (
    int UnstartedThreadCount,
    int BackgroundThreadCount,
    int PendingThreadCount,
    int DeadThreadCount);

enum ThreadState
{
    Unknown             = 0x00000000,    // threads are initialized this way
    Hijacked            = 0x00000080,    // Return address has been hijacked
    Background          = 0x00000200,    // Thread is a background thread
    Unstarted           = 0x00000400,    // Thread has never been started
    Dead                = 0x00000800,    // Thread is dead
    ThreadPoolWorker    = 0x01000000,    // is this a threadpool worker thread?
}

record struct ThreadData (
    uint Id;
    TargetNUInt OSId;
    ThreadState State;
    bool PreemptiveGCDisabled
    TargetPointer AllocContextPointer;
    TargetPointer AllocContextLimit;
    TargetPointer Frame;
    TargetPointer FirstNestedException;
    TargetPointer TEB;
    TargetPointer LastThrownObjectHandle;
    TargetPointer NextThread;
);
csharp
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);

Version 1

The contract depends on the following globals

Global nameTypeMeaning
AppDomainTargetPointerA pointer to the address of the one AppDomain
ThreadStoreTargetPointerA pointer to the address of the ThreadStore
FeatureEHFuncletsTargetPointer1 if EH funclets are enabled, 0 otherwise
FinalizerThreadTargetPointerA pointer to the finalizer thread
GCThreadTargetPointerA pointer to the GC thread
ThinLockThreadIdDispenserTargetPointerDispenser of thinlock IDs for locking objects
NumberOfTlsOffsetsNotUsedInNoncollectibleArraybyteNumber of unused slots in noncollectible TLS array
PtrArrayOffsetToDataArrayTargetPointerOffset from PtrArray class address to start of enclosed data array
SizeOfGenericModeBlockuint32Size of GenericModeBlock struct

The contract additionally depends on these data descriptors

Data Descriptor NameFieldMeaning
ExceptionWatsonBucketsPointer to exception Watson buckets
ExceptionInfoPreviousNestedInfoPointer to previous nested exception info
ExceptionInfoThrownObjectHandlePointer to exception object handle
ExceptionInfoExceptionWatsonBucketTrackerBucketsPointer to Watson unhandled buckets on non-Unix
GCAllocContextPointerGC allocation pointer
GCAllocContextLimitAllocation limit pointer
GCAllocContextAllocBytesNumber of bytes allocated on SOH by this context
GCAllocContextAllocBytesLohNumber of bytes allocated not on SOH by this context
IdDispenserHighestIdHighest possible small thread ID
IdDispenserIdToThreadArray mapping small thread IDs to thread pointers
InflightTLSDataNextPointer to next in-flight TLS data entry
InflightTLSDataTlsIndexTLS index for the in-flight static field
InflightTLSDataTLSDataObject handle to the TLS data for the static field
ObjectHandleObjectPointer to the managed object
RuntimeThreadLocalsAllocContextGC allocation context for the thread
TLSIndexIndexOffsetOffset index for thread local storage
TLSIndexIndexTypeType of thread local storage index
TLSIndexIsAllocatedWhether TLS storage has been allocated
TLSIndexTLSIndexRawIndexRaw index value containing type and offset
ThreadIdThread identifier
ThreadOSIdOperating system thread identifier
ThreadStateThread state flags
ThreadPreemptiveGCDisabledFlag indicating if preemptive GC is disabled
ThreadFramePointer to current frame
ThreadCachedStackBasePointer to the base of the stack
ThreadCachedStackLimitPointer to the limit of the stack
ThreadTEBThread Environment Block pointer
ThreadLastThrownObjectHandle to last thrown exception object
ThreadLinkNextPointer to get next thread
ThreadExceptionTrackerPointer to exception tracking information
ThreadRuntimeThreadLocalsPointer to some thread-local storage
ThreadThreadLocalDataPtrPointer to thread local data structure
ThreadUEWatsonBucketTrackerBucketsPointer to thread Watson buckets data (optional, Windows only)
ThreadLocalDataNonCollectibleTlsDataCount of non-collectible TLS data entries
ThreadLocalDataNonCollectibleTlsArrayDataPointer to non-collectible TLS array data
ThreadLocalDataCollectibleTlsDataCount of collectible TLS data entries
ThreadLocalDataCollectibleTlsArrayDataPointer to collectible TLS array data
ThreadLocalDataInFlightDataPointer to in-flight TLS data for fields being initialized
ThreadStoreThreadCountNumber of threads
ThreadStoreFirstThreadLinkPointer to first thread in the linked list
ThreadStoreUnstartedCountNumber of unstarted threads
ThreadStoreBackgroundCountNumber of background threads
ThreadStorePendingCountNumber of pending threads
ThreadStoreDeadCountNumber of dead threads

The contract depends on the following other contracts

Contract
Object
csharp
enum TLSIndexType
{
    NonCollectible = 0,
    Collectible = 1,
    DirectOnThreadLocalData = 2,
};


ThreadStoreData GetThreadStoreData()
{
    TargetPointer threadStore = target.ReadGlobalPointer("ThreadStore");

    ulong threadLinkoffset = ... // offset from Thread data descriptor
    return new ThreadStoreData(
        ThreadCount: target.Read<int>(threadStore + /* ThreadStore::ThreadCount offset */),
        FirstThread: target.ReadPointer(threadStore + /* ThreadStore::FirstThreadLink offset */ - threadLinkoffset),
        FinalizerThread: target.ReadGlobalPointer("FinalizerThread"),
        GCThread: target.ReadGlobalPointer("GCThread"));
}

DacThreadStoreCounts GetThreadCounts()
{
    TargetPointer threadStore = target.ReadGlobalPointer("ThreadStore");

    return new ThreadStoreCounts(
        UnstartedThreadCount: target.Read<int>(threadStore + /* ThreadStore::UnstartedCount offset */),
        BackgroundThreadCount: target.Read<int>(threadStore + /* ThreadStore::BackgroundCount offset */),,
        PendingThreadCount: target.Read<int>(threadStore + /* ThreadStore::PendingCount offset */),,
        DeadThreadCount: target.Read<int>(threadStore + /* ThreadStore::DeadCount offset */),,
}

ThreadData GetThreadData(TargetPointer address)
{
    var runtimeThread = new Thread(target, threadPointer);

    // Exception tracker is a pointer when EH funclets are enabled
    TargetPointer exceptionTrackerAddr = target.ReadGlobal<byte>("FeatureEHFunclets") != 0
        ? target.ReadPointer(address + /* Thread::ExceptionTracker offset */)
        : address + /* Thread::ExceptionTracker offset */;
    TargetPointer firstNestedException = exceptionTrackerAddr != TargetPointer.Null
        ? target.ReadPointer(exceptionTrackerAddr + /* ExceptionInfo::PreviousNestedInfo offset*/)
        : TargetPointer.Null;

    TargetPointer allocContextPointer = TargetPointer.Null;
    TargetPointer allocContextLimit = TargetPointer.Null;
    TargetPointer threadLocals = target.ReadPointer(address + /* Thread::RuntimeThreadLocals offset */);
    if (threadLocals != TargetPointer.Null)
    {
        allocContextPointer = target.ReadPointer(threadLocals + /* RuntimeThreadLocals::AllocContext offset */ + /* GCAllocContext::Pointer offset */);
        allocContextLimit = target.ReadPointer(threadLocals + /* RuntimeThreadLocals::AllocContext offset */ + /* GCAllocContext::Limit offset */);
    }

    ulong threadLinkoffset = ... // offset from Thread data descriptor
    return new ThreadData(
        Id: target.Read<uint>(address + /* Thread::Id offset */),
        OSId: target.ReadNUInt(address + /* Thread::OSId offset */),
        State: target.Read<uint>(address + /* Thread::State offset */),
        PreemptiveGCDisabled: (target.Read<uint>(address + /* Thread::PreemptiveGCDisabled offset */) & 0x1) != 0,
        AllocContextPointer: allocContextPointer,
        AllocContextLimit: allocContextLimit,
        Frame: target.ReadPointer(address + /* Thread::Frame offset */),
        TEB : /* Has Thread::TEB offset */ ? target.ReadPointer(address + /* Thread::TEB offset */) : TargetPointer.Null,
        LastThrownObjectHandle : target.ReadPointer(address + /* Thread::LastThrownObject offset */),
        FirstNestedException : firstNestedException,
        NextThread: target.ReadPointer(address + /* Thread::LinkNext offset */) - threadLinkOffset;
    );
}

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);
    TargetPointer idDispenser = target.ReadPointer(idDispenserPointer);
    uint HighestId = target.ReadPointer(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)) */);
    return threadPtr;
}

TargetPointer IThread.GetThreadLocalStaticBase(TargetPointer threadPointer, TargetPointer tlsIndexPtr)
{
    // Get the thread's TLS base address
    TargetPointer threadLocalDataPtr = target.ReadPointer(threadPointer + /* Thread::ThreadLocalDataPtr offset */);
    if (threadLocalDataPtr == TargetPointer.Null)
        return TargetPointer.Null;

    Data.TLSIndex tlsIndex = new Data.TLSIndex(tlsIndexPtr);
    if (!tlsIndex.IsAllocated)
        return TargetPointer.Null;

    TargetPointer threadLocalStaticBase = default;
    int indexType = tlsIndex.IndexType;
    int indexOffset = tlsIndex.IndexOffset;
    switch ((TLSIndexType)indexType)
    {
        case TLSIndexType.NonCollectible:
            int nonCollectibleCount = target.ReadPointer(threadLocalDataPtr + /* ThreadLocalData::NonCollectibleTlsDataCount offset */);
            // bounds check
            if (nonCollectibleCount > indexOffset)
            {
                TargetPointer nonCollectibleArray = target.ReadPointer(threadLocalDataPtr + /* ThreadLocalData::NonCollectibleTlsArrayData offset */);
                int arrayIndex = indexOffset - target.ReadGlobal<byte>("NumberOfTlsOffsetsNotUsedInNoncollectibleArray");
                TargetPointer arrayStartAddress = nonCollectibleArray + target.ReadGlobalPointer("PtrArrayOffsetToDataArray");
                threadLocalStaticBase = target.ReadPointer(arrayStartAddress + (ulong)(arrayIndex * target.PointerSize));
            }
            break;
        case TLSIndexType.Collectible:
            int collectibleCount = target.ReadPointer(threadLocalDataPtr + /* ThreadLocalData::CollectibleTlsDataCount offset */);
            if (collectibleCount > indexOffset)
            {
                TargetPointer collectibleArray = target.ReadPointer(threadLocalDataPtr + /* ThreadLocalData::CollectibleTlsArrayData offset */);
                threadLocalStaticBase = target.ReadPointer(collectibleArray + (ulong)(indexOffset * target.PointerSize));
            }
            break;
        case TLSIndexType.DirectOnThreadLocalData:
            threadLocalStaticBase = threadLocalDataPtr + indexOffset;
            break;
    }
    if (threadLocalStaticBase == TargetPointer.Null)
    {
        TargetPointer inFlightData = target.ReadPointer(threadLocalDataPtr + /* ThreadLocalData::inFlightData offset */);
        while (inFlightData != TargetPointer.Null)
        {
            TargetPointer tlsIndexInFlightPtr = target.ReadPointer(inFlightData + /* InflightTLSData::TlsIndex offset */);
            Data.TLSIndex tlsIndexInFlight = new Data.TLSIndex(tlsIndexInFlightPtr);
            if (tlsIndexInFlight.TLSIndexRawIndex == tlsIndex.TLSIndexRawIndex)
            {
                threadLocalStaticBase = target.ReadPointer(tlsIndexInFlightPtr + /* InflightTLSData::TLSData offset */);
                break;
            }
            inFlightData = target.ReadPointer(inFlightData + /* InflightTLSData::Next offset */);
        }
    }
    return threadLocalStaticBase;
}

TargetPointer IThread.GetCurrentExceptionHandle(TargetPointer threadPointer)
{
    TargetPointer exceptionTrackerPtr = target.ReadPointer(threadPointer + /*Thread::ExceptionTracker offset */);
    if (exceptionTrackerPtr == TargetPointer.Null)
        return TargetPointer.Null;
    TargetPointer thrownObjectHandle = target.ReadPointer(exceptionTrackerPtr + /* ExceptionInfo::ThrownObjectHandle offset */);

    if (thrownObjectHandle == TargetPointer.Null || target.ReadPointer(thrownObjectHandle) == TargetPointer.Null)
        return TargetPointer.Null;

    return thrownObjectHandle;
}

byte[] IThread.GetWatsonBuckets(TargetPointer threadPointer)
{
    TargetPointer readFrom;
    TargetPointer exceptionTrackerPtr = _target.ReadPointer(threadPointer + /*Thread::ExceptionTracker offset */);
    if (exceptionTrackerPtr == TargetPointer.Null)
        return Array.Empty<byte>();
    TargetPointer thrownObjectHandle = target.ReadPointer(exceptionTrackerPtr + /* ExceptionInfo::ThrownObjectHandle offset */);
    TargetPointer throwableObjectPtr = target.ReadPointer(thrownObjectHandle);
    if (throwableObjectPtr != TargetPointer.Null)
    {
        TargetPointer watsonBuckets = target.ReadPointer(throwableObjectPtr + /* Exception::WatsonBuckets offset */);
        if (watsonBuckets != TargetPointer.Null)
        {
            readFrom = _target.Contracts.Object.GetArrayData(watsonBuckets, out _, out _, out _);
        }
        else
        {
            readFrom = /* Has Thread::UEWatsonBucketTrackerBuckets offset */
                ? target.ReadPointer(threadPointer + /* Thread::UEWatsonBucketTrackerBuckets offset */)
                : TargetPointer.Null;
            if (readFrom == TargetPointer.Null)
            {
                readFrom = target.ReadPointer(exceptionTrackerPtr + /* ExceptionInfo::ExceptionWatsonBucketTrackerBuckets offset */);
            }
            else
            {
                return Array.Empty<byte>();
            }
        }
    }
    else
    {
        readFrom = /* Has Thread::UEWatsonBucketTrackerBuckets offset */
            ? target.ReadPointer(threadPointer + /* Thread::UEWatsonBucketTrackerBuckets offset */)
            : TargetPointer.Null;
    }

    Span<byte> span = new byte[_target.ReadGlobal<uint>("SizeOfGenericModeBlock")];
    if (readFrom == TargetPointer.Null)
        return Array.Empty<byte>();

    _target.ReadBuffer(readFrom, span);
    return span.ToArray();
}