Skip to content

Commit

Permalink
Add example algorithmic contracts
Browse files Browse the repository at this point in the history
- To start, I've added the contracts for iterating the thread list and gathering information about individual threads
- Note that this algorithmic description is paired with a simplification of the SList data structure
  - See PR dotnet#100107
  • Loading branch information
davidwrighton committed Mar 21, 2024
1 parent 3ffef6c commit 3b6b83c
Show file tree
Hide file tree
Showing 4 changed files with 276 additions and 0 deletions.
23 changes: 23 additions & 0 deletions docs/design/datacontracts/GCHandle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Contract GCHandle

This contract allows decoding and reading of GCHandles. This will also include handle enumeration in the future

## Data structures defined by contract
``` csharp
```

## Apis of contract
``` csharp
TargetPointer GetObject(TargetPointer gcHandle);
```

## Version 1

``` csharp
TargetPointer GetObject(TargetPointer gcHandle)
{
if (gcHandle == TargetPointer.Null)
return TargetPointer.Null;
return Target.ReadTargetPointer(gcHandle);
}
```
78 changes: 78 additions & 0 deletions docs/design/datacontracts/SList.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Contract SList

This contract allows reading and iterating over an SList data structure.

## Data structures defined by contract
``` csharp
class SListReader
{
public abstract TargetPointer GetHead(TargetPointer slistPointer);
public abstract TargetPointer GetNext(TargetPointer entryInSList);
public IEnumerator<TargetPointer> EnumerateList(TargetPointer slistPointer)
{
TargetPointer current = GetHead(slistPointer);

while (current != TargetPointer.Null)
{
yield return current;
current = GetNext(current);
}
}
public IEnumerator<TargetPointer> EnumerateListFromEntry(TargetPointer entryInSList)
{
TargetPointer current = entryInSList;

while (current != TargetPointer.Null)
{
yield return current;
current = GetNext(current);
}
}
}
```

## Apis of contract
``` csharp
SListReader GetReader(string typeOfDataStructure);
```

## Version 1

``` csharp
private class SListReaderV1 : SListReader
{
uint _offsetToSLinkField;
Target Target;

SListReaderV1(Target target, string typeToEnumerate)
{
Target = target;
_offsetToSLinkField = Target.Contracts.GetFieldLayout(typeToEnumerate, "m_Link").Offset;
}
public override TargetPointer GetHead(TargetPointer slistPointer)
{
TargetPointer headPointer = new SListBase(Target, slistPointer).m_pHead;
TargetPointer slinkInHeadObject = new SLink(Target, headPointer).m_pNext;
if (slinkInHeadObject == TargetPointer.Null)
return TargetPointer.Null;
return slinkInHeadObject - _offsetToSLinkField;
}

public override TargetPointer GetNext(TargetPointer entryInSList)
{
if (entryInSList == TargetPointer.Null)
throw new ArgumentException();

TargetPointer slinkPointer = entryInSList + _offsetToSLinkField;
TargetPointer slinkInObject = new SLink(Target, slinkPointer).m_pNext;
if (slinkInObject == TargetPointer.Null)
return TargetPointer.Null;
return slinkInHeadObject - _offsetToSLinkField;
}
}

SListReader GetReader(string typeOfDataStructure)
{
return new SListReaderV1(typeOfDataStructure);
}
```
174 changes: 174 additions & 0 deletions docs/design/datacontracts/Thread.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# Contract Thread

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

## Data structures defined by contract
``` csharp
record struct DacThreadStoreData (
int ThreadCount,
int UnstartedThreadCount,
int BackgroundThreadCount,
int PendingThreadCount,
int DeadThreadCount,
TargetPointer FirstThread,
TargetPointer FinalizerThread,
TargetPointer GcThread);

enum ThreadState
{
TS_Unknown = 0x00000000, // threads are initialized this way
TS_AbortRequested = 0x00000001, // Abort the thread
TS_GCSuspendPending = 0x00000002, // ThreadSuspend::SuspendRuntime watches this thread to leave coop mode.
TS_GCSuspendRedirected = 0x00000004, // ThreadSuspend::SuspendRuntime has redirected the thread to suspention routine.
TS_GCSuspendFlags = TS_GCSuspendPending | TS_GCSuspendRedirected, // used to track suspension progress. Only SuspendRuntime writes/resets these.
TS_DebugSuspendPending = 0x00000008, // Is the debugger suspending threads?
TS_GCOnTransitions = 0x00000010, // Force a GC on stub transitions (GCStress only)
TS_LegalToJoin = 0x00000020, // Is it now legal to attempt a Join()
TS_ExecutingOnAltStack = 0x00000040, // Runtime is executing on an alternate stack located anywhere in the memory
TS_Hijacked = 0x00000080, // Return address has been hijacked
// unused = 0x00000100,
TS_Background = 0x00000200, // Thread is a background thread
TS_Unstarted = 0x00000400, // Thread has never been started
TS_Dead = 0x00000800, // Thread is dead
TS_WeOwn = 0x00001000, // Exposed object initiated this thread
TS_CoInitialized = 0x00002000, // CoInitialize has been called for this thread
TS_InSTA = 0x00004000, // Thread hosts an STA
TS_InMTA = 0x00008000, // Thread is part of the MTA
// Some bits that only have meaning for reporting the state to clients.
TS_ReportDead = 0x00010000, // in WaitForOtherThreads()
TS_FullyInitialized = 0x00020000, // Thread is fully initialized and we are ready to broadcast its existence to external clients
TS_TaskReset = 0x00040000, // The task is reset
TS_SyncSuspended = 0x00080000, // Suspended via WaitSuspendEvent
TS_DebugWillSync = 0x00100000, // Debugger will wait for this thread to sync
TS_StackCrawlNeeded = 0x00200000, // A stackcrawl is needed on this thread, such as for thread abort
// See comment for s_pWaitForStackCrawlEvent for reason.
// unused = 0x00400000,
// unused = 0x00800000,
TS_TPWorkerThread = 0x01000000, // is this a threadpool worker thread?
TS_Interruptible = 0x02000000, // sitting in a Sleep(), Wait(), Join()
TS_Interrupted = 0x04000000, // was awakened by an interrupt APC. !!! This can be moved to TSNC
TS_CompletionPortThread = 0x08000000, // Completion port thread
TS_AbortInitiated = 0x10000000, // set when abort is begun
TS_Finalized = 0x20000000, // The associated managed Thread object has been finalized.
// We can clean up the unmanaged part now.
TS_FailStarted = 0x40000000, // The thread fails during startup.
TS_Detached = 0x80000000, // Thread was detached by DllMain
}

record struct DacThreadData (
uint ThreadId;
uint OsThreadId;
ThreadState State;
bool PreemptiveGCDisabled
TargetPointer AllocContextPointer;
TargetPointer AllocContextLimit;
TargetPointer Frame;
TargetPointer FirstNestedException;
TargetPointer TEB;
TargetPointer LastThrownObjectHandle;
TargetPointer NextThread;
);
```

## Apis of contract
``` csharp
DacThreadStoreData GetThreadStoreData();
```

## Version 1



``` csharp
SListReader ThreadListReader = Contracts.SList.GetReader("Thread");

DacThreadStoreData GetThreadStoreData()
{
TargetPointer threadStore = Target.ReadGlobalTargetPointer("s_pThreadStore");
var runtimeThreadStore = new ThreadStore(Target, threadStore);

TargetPointer firstThread = ThreadListReader.GetHead(runtimeThreadStore.SList.Pointer);

return new DacThreadStoreData(
ThreadCount : runtimeThreadStore.m_ThreadCount,
UnstartedThreadCount : runtimeThreadStore.m_UnstartedThreadCount,
BackgroundThreadCount : runtimeThreadStore.m_BackgroundThreadCount,
PendingThreadCount : runtimeThreadStore.m_PendingThreadCount,
DeadThreadCount: runtimeThreadStore.m_DeadThreadCount,
FirstThread: firstThread,
FinalizerThread: Target.ReadGlobalTargetPointer("g_pFinalizerThread"),
GcThread: Target.ReadGlobalTargetPointer("g_pSuspensionThread"));
}

DacThreadData GetThreadData(TargetPointer threadPointer)
{
var runtimeThread = new Thread(Target, threadPointer);

TargetPointer firstNestedException = TargetPointer.Null;
if (Target.ReadGlobalInt32("FEATURE_EH_FUNCLETS"))
{
if (runtimeThread.m_ExceptionState.m_pCurrentTracker != TargetPointer.Null)
{
firstNestedException = new ExceptionTrackerBase(Target, runtimeThread.m_ExceptionState.m_pCurrentTracker).m_pPrevNestedInfo;
}
}
else
{
firstNestedException = runtimeThread.m_ExceptionState.m_currentExInfo.m_pPrevNestedInfo;
}

return new DacThread(
ThreadId : runtimeThread.m_ThreadId,
OsThreadId : runtimeThread.m_OSThreadId,
State : (ThreadState)runtimeThread.m_State,
PreemptiveGCDisabled : thread.m_fPreemptiveGCDisabled != 0,
AllocContextPointer : thread.m_alloc_context.alloc_ptr,
AllocContextLimit : thread.m_alloc_context.alloc_limit,
Frame : thread.m_pFrame,
TEB : thread.Has_m_pTEB ? thread.m_pTEB : TargetPointer.Null,
LastThreadObjectHandle : thread.m_LastThrownObjectHandle,
FirstNestedException : firstNestedException,
NextThread : ThreadListReader.GetHead.GetNext(threadPointer)
);
}

TargetPointer GetNestedExceptionInfo(TargetPointer nestedExceptionPointer, out TargetPointer nextNestedException)
{
if (nestedExceptionPointer == TargetPointer.Null)
{
throw new InvalidArgumentException();
}
if (Target.ReadGlobalInt32("FEATURE_EH_FUNCLETS"))
{
var exData = new ExceptionTrackerBase(Target, nestedExceptionPointer);
nextNestedException = exData.m_pPrevNestedInfo;
return Contracts.GCHandle.GetObject(exData.m_hThrowable);
}
else
{
var exData = new ExInfo(Target, nestedExceptionPointer);
nextNestedException = exData.m_pPrevNestedInfo;
return Contracts.GCHandle.GetObject(exData.m_hThrowable);
}
}
```
1 change: 1 addition & 0 deletions docs/design/datacontracts/contract_csharp_api_design.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class DataContractAlgorithmAttribute : System.Attribute
struct TargetPointer
{
public ulong Value;
public static TargetPointer Null = new TargetPointer(0);
// Add a full set of operators to support pointer arithmetic
}

Expand Down

0 comments on commit 3b6b83c

Please sign in to comment.