Skip to content
Draft
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
33 changes: 33 additions & 0 deletions docs/design/datacontracts/GCInfo.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ IReadOnlyList<LiveSlot> EnumerateLiveSlots(IGCInfoHandle handle, uint instructio

// Returns true if the instruction offset is a GC-safe point.
bool IsGcSafe(IGCInfoHandle handle, uint instructionOffset);
bool TryGetGenericInstantiationContextStackSlot(IGCInfoHandle handle, out int spOffset, out bool isStackBaseRelative);
TargetPointer GetAmbientSP(IGCInfoHandle handle, uint codeOffset, TargetPointer fp, TargetPointer sp);
```

```csharp
Expand Down Expand Up @@ -98,6 +100,8 @@ Constants:
| `NO_GENERICS_INST_CONTEXT` | Indicates no generics instantiation context | -1 |
| `NO_REVERSE_PINVOKE_FRAME` | Indicates no reverse P/Invoke frame | -1 |
| `NO_PSP_SYM` | Indicates no PSP symbol | -1 |
| `INVALID_SYNC_OFFSET` | Sync start offset value indicating the method is not synchronized (x86) | 0 |
| `SHADOW_SP_BITS` | Flag bits stored in the low bits of an x86 shadow-SP slot; masked off to recover the SP | 0x3 |


### GCInfo Format
Expand Down Expand Up @@ -612,6 +616,35 @@ bool IsGcSafe(IGCInfoHandle handle, uint instructionOffset)
// if the offset is fully interruptible or (for the general decoder) matches an entry
// in the safe point table.
}

bool TryGetGenericInstantiationContextStackSlot(IGCInfoHandle handle,
out int spOffset, out bool isStackBaseRelative)
{
// Non-x86 (GcInfoDecoder) decoders: ensure the header is decoded through the generic
// instantiation context and stack base register fields. If the method reports no context
// slot (NO_GENERICS_INST_CONTEXT), return false. Otherwise return the denormalized signed
// slot offset and whether it is relative to the stack base register (present) or SP (absent).
//
// x86 (InfoHdr) decoder:
// - return false unless the method reports a generics context and uses an EBP frame;
// - otherwise the slot is EBP-relative at
// -(savedRegsCountExclFP + (synchronized ? 1 : 0) + localloc + 1) * sizeof(TADDR),
// so spOffset is that negative value and isStackBaseRelative is true.
}

TargetPointer GetAmbientSP(IGCInfoHandle handle, uint codeOffset, TargetPointer fp, TargetPointer sp)
{
// Non-x86 decoders: return TargetPointer.Null (there is no ambient SP).
//
// x86 (InfoHdr) decoder, mirroring native EECodeManager::GetAmbientSP:
// - return Null if codeOffset is in the prolog or an epilog;
// - if the method has handlers, return GetOutermostBaseFP(fp) with the low
// SHADOW_SP_BITS masked off;
// - else if it is an EBP frame, return GetOutermostBaseFP(fp);
// - else (ESP frame) return sp plus the pushed-argument size at codeOffset.
// GetOutermostBaseFP reads the localloc slot when the method uses localloc,
// otherwise returns fp - stackSize + sizeof(int).
}
```

### IsGcSafe
Expand Down
59 changes: 49 additions & 10 deletions docs/design/datacontracts/RuntimeTypeSystem.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ partial interface IRuntimeTypeSystem : IContract
// Returns the address of one of the runtime's well-known singleton MethodTables, or
// TargetPointer.Null if the runtime has not yet initialized that global.
public virtual TargetPointer GetWellKnownMethodTable(WellKnownMethodTable kind);
// Returns the address of one of the runtime's well-known singleton MethodDescs, or
// TargetPointer.Null if the runtime has not yet initialized that global.
public virtual TargetPointer GetWellKnownMethodDesc(WellKnownMethodDesc kind);
// True if the MethodTable represents a type that contains managed references
public virtual bool ContainsGCPointers(TypeHandle typeHandle);
// True if the MethodTable represents a byref-like value type (Span<T>, ReadOnlySpan<T>, any ref struct).
Expand Down Expand Up @@ -204,6 +207,16 @@ public enum WellKnownMethodTable
Exception,
Free,
Canon,
EH,
ExceptionServicesInternalCalls,
StackFrameIterator,
}

// Identifies one of the runtime's well-known singleton MethodDescs, each addressable
// via a dedicated global pointer.
public enum WellKnownMethodDesc
{
EnvironmentCallEntryPoint,
}
```

Expand Down Expand Up @@ -291,8 +304,9 @@ partial interface IRuntimeTypeSystem : IContract
// Returns true if the method is eligible for tiered compilation
public virtual bool IsEligibleForTieredCompilation(MethodDescHandle methodDesc);

// Return true if the method is an async thunk method.
public virtual bool IsAsyncThunkMethod(MethodDescHandle methodDesc);
// Returns the Runtime Async flags recorded for the method (native AsyncMethodFlags),
// or AsyncMethodFlags.None if the method has no async method data.
public virtual AsyncMethodFlags GetAsyncMethodFlags(MethodDescHandle methodDesc);

// Return true if the method is a wrapper stub (unboxing or instantiating).
public virtual bool IsWrapperStub(MethodDescHandle methodDesc);
Expand Down Expand Up @@ -514,6 +528,10 @@ The contract depends on the following globals
| `ObjectArrayMethodTable` | A pointer to the address of the `object[]` `MethodTable` (`g_pPredefinedArrayTypes[ELEMENT_TYPE_OBJECT]`)
| `ExceptionMethodTable` | A pointer to the address of the `System.Exception` `MethodTable` (`g_pExceptionClass`)
| `CanonMethodTable` | A pointer to the address of the canonical `MethodTable` used for shared generics (`System.__Canon`)
| `EHMethodTable` | A pointer to the address of the `MethodTable` for the runtime exception-handling helper class
| `ExceptionServicesInternalCallsMethodTable` | A pointer to the address of the `MethodTable` for the exception-services internal-calls class
| `StackFrameIteratorMethodTable` | A pointer to the address of the `MethodTable` for `System.Runtime.StackFrameIterator`
| `EnvironmentCallEntryPointMethodDesc` | A pointer to the address of the `MethodDesc` for the runtime entry-point method
| `StaticsPointerMask` | For masking out a bit of DynamicStaticsInfo pointer fields
| `ArrayBaseSize` | The base size of an array object; used to compute multidimensional array rank from `MethodTable::BaseSize`

Expand Down Expand Up @@ -679,7 +697,25 @@ Contracts used:
WellKnownMethodTable.Exception => "ExceptionMethodTable",
WellKnownMethodTable.Free => "FreeObjectMethodTable",
WellKnownMethodTable.Canon => "CanonMethodTable",
WellKnownMethodTable.EH => "EHMethodTable",
WellKnownMethodTable.ExceptionServicesInternalCalls => "ExceptionServicesInternalCallsMethodTable",
WellKnownMethodTable.StackFrameIterator => "StackFrameIteratorMethodTable",
};
return ReadWellKnownGlobalPointer(globalName);
}

public TargetPointer GetWellKnownMethodDesc(WellKnownMethodDesc kind)
{
// As GetWellKnownMethodTable, but for well-known MethodDesc globals.
string globalName = kind switch
{
WellKnownMethodDesc.EnvironmentCallEntryPoint => "EnvironmentCallEntryPointMethodDesc",
};
return ReadWellKnownGlobalPointer(globalName);
}

private TargetPointer ReadWellKnownGlobalPointer(string globalName)
{
if (!target.TryReadGlobalPointer(globalName, out TargetPointer? ptrPtr))
return TargetPointer.Null;
if (!target.TryReadPointer(ptrPtr.Value, out TargetPointer value))
Expand Down Expand Up @@ -1421,7 +1457,10 @@ And the following enumeration definitions
internal enum AsyncMethodFlags : uint
{
None = 0,
Thunk = 16,
AsyncCall = 0x1,
IsAsyncVariant = 0x4,
Thunk = 0x10,
ReturnDroppingThunk = 0x20,
}

[Flags]
Expand Down Expand Up @@ -1849,19 +1888,19 @@ Determining if a method supports multiple code versions:
}
```

Determining if a method is an async thunk method:
Reading a method's Runtime Async flags:

```csharp
public bool IsAsyncThunkMethod(MethodDescHandle methodDescHandle)
public AsyncMethodFlags GetAsyncMethodFlags(MethodDescHandle methodDescHandle)
{
MethodDesc md = _methodDescs[methodDescHandle.Address];
if (!md.HasAsyncMethodData)
{
return false;
}
return AsyncMethodFlags.None;

Data.AsyncMethodData asyncData = // Read AsyncMethodData from the address of the async method data optional slot
return ((AsyncMethodFlags)asyncData.Flags).HasFlag(AsyncMethodFlags.Thunk);
// Read and return the raw AsyncMethodFlags from the async method data optional slot.
// Callers test individual bits (e.g. Thunk, ReturnDroppingThunk, IsAsyncVariant), since a
// method may carry several simultaneously.
return (AsyncMethodFlags)/* AsyncMethodData.Flags */;
}
```

Expand Down
36 changes: 36 additions & 0 deletions docs/design/datacontracts/StackWalk.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,25 @@ byte[] GetContext(ThreadData threadData, ThreadContextSource contextSource, uint

// Returns the saved TargetContext pointer carried by the head Frame, if applicable.
TargetPointer GetRedirectedContextPointer(ThreadData threadData);

// Returns funclet / interrupt state and the frame pointer for the current stack dataframe.
StackWalkFrameInfo GetCurrentFrameInfo(IStackDataFrameHandle stackDataFrameHandle);

// Returns the exact generic instantiation context token for the current frameless managed frame,
// or TargetPointer.Null if the method is not shared generic code or the context can't be recovered.
TargetPointer GetExactGenericArgsToken(IStackDataFrameHandle stackDataFrameHandle);
```

```csharp
public record struct StackWalkFrameInfo(
TargetPointer FramePointer,
bool IsFunclet,
bool IsFilterFunclet,
TargetPointer ParentOrSelfFrameMarker,
bool IsInterrupted,
bool HasFaulted,
uint ParentNativeOffset = 0,
TargetPointer AmbientSP = default);
Comment thread
Copilot marked this conversation as resolved.
```

## Version 1
Expand Down Expand Up @@ -619,6 +638,23 @@ If no Frame in the chain produces a usable context (thread is not running manage

`GetRedirectedContextPointer` returns the saved `TargetContext` pointer carried by the head Frame when that Frame is a `RedirectedThreadFrame` (a `ResumableFrame`). Otherwise it returns `TargetPointer.Null`.

`GetCurrentFrameInfo` returns a `StackWalkFrameInfo` describing the current stack dataframe. It is the data a debugger needs to shape a managed stack frame (funclet handling and interrupt/fault state):

* `FramePointer` is the frame pointer for the current frame. On x64 it is the current context's stack pointer; on ARM, ARM64, RISCV64 and LoongArch64 it is the caller's stack pointer (the stack pointer after unwinding the current context one iteration). On x86 it mirrors native `GetFramePointerWorker`: for a frameless managed method it is the unwound stack pointer less the callee-popped argument size and one pointer (native `ComputeX86FramePointer`), and for a runtime-unwindable native marker it is the return-address slot of the recovered hijacked context. Other architectures are unsupported.
* `IsFunclet` / `IsFilterFunclet` report whether the current frame is a funclet, and whether it is a filter funclet.
* `ParentOrSelfFrameMarker` is the caller's stack pointer for a non-funclet frame. For a funclet it is the caller's stack pointer of the funclet's parent method frame, located by a self-contained secondary stackwalk that skips intervening (possibly nested) funclets. If the parent cannot be located (the funclet and its parent have already been unwound) it falls back to the caller's stack pointer.
* `IsInterrupted` is true when the current managed frame was interrupted by an exception frame (`FaultingExceptionFrame`/`SoftwareExceptionFrame`).
* `HasFaulted` is true when the interrupting frame was a `FaultingExceptionFrame` (a hardware fault such as an access violation), which distinguishes a synchronous throw from a fault when reporting the frame.
* `ParentNativeOffset` is meaningful only for funclets: it is the relative native offset of the parent method frame located by the secondary walk above (0 for non-funclets).
* `AmbientSP` is the "ambient stack pointer" (native `taAmbientESP`), and is `TargetPointer.Null` (0) on every architecture except x86 and ARM (32-bit). On ARM32 it is the current context's stack pointer. On x86 it is computed from the GC info by `IGCInfo.GetAmbientSP` (native `EECodeManager::GetAmbientSP`): `Null` in the prolog/epilog; the masked outermost base frame pointer for methods with handlers; the outermost base frame pointer for EBP frames; and the stack pointer plus the pushed-argument size for ESP frames.

`GetExactGenericArgsToken` recovers the exact generic instantiation context for the current frameless managed frame, mirroring native `CrawlFrame::GetExactGenericArgsToken`. It returns `TargetPointer.Null` unless the frame is `Frameless`, has a `MethodDesc`, and that method is shared by generic instantiations (`GetGenericContextLoc != None`). When applicable it:

1. Decodes the method's GC info and reads the generic instantiation context stack slot via `IGCInfo.TryGetGenericInstantiationContextStackSlot`. If the method does not report a context slot (e.g. the JIT did not keep it alive, or the frame is a prolog/epilog), it returns `TargetPointer.Null`.
2. Computes the slot address from the reported offset, relative to the stack base (frame) register when the method has one, otherwise relative to SP, and reads the context pointer from that slot.
3. For a `ThisPtr` context the slot holds the `this` object reference and the token is its `MethodTable`; for an explicit context argument (`InstArgMethodDesc`/`InstArgMethodTable`) the slot value is the token itself.


#### CreateStackWalk with a caller-provided CONTEXT

`CreateStackWalk(ThreadData, byte[], bool isFirst)` seeds the walker from `contextBuffer` rather than from the thread's saved CONTEXT. `isFirst` (default `true`) is used to determine whether the walker starts with internal state `isFirst` set to true.
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/inc/eetwain.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ enum
{
SHADOW_SP_IN_FILTER = 0x1,
SHADOW_SP_FILTER_DONE = 0x2,
SHADOW_SP_BITS = 0x3
SHADOW_SP_BITS = 0x3 // [cDAC] [GCInfo]: Contract depends on this value.
};

#ifdef TARGET_X86
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/inc/gcinfotypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ enum infoHdrAdjust2 {
#define HAS_GS_COOKIE_OFFSET ((unsigned int) -1)

// 0 is not a valid sync offset
#define INVALID_SYNC_OFFSET 0
#define INVALID_SYNC_OFFSET 0 // [cDAC] [GCInfo]: Contract depends on this value.
// Temporary value to indicate that the offset needs to be read after the header
#define HAS_SYNC_OFFSET ((unsigned int) -1)

Expand Down
4 changes: 4 additions & 0 deletions src/coreclr/vm/datadescriptor/datadescriptor.inc
Original file line number Diff line number Diff line change
Expand Up @@ -1679,6 +1679,10 @@ CDAC_GLOBAL_POINTER(CanonMethodTable, &::g_pCanonMethodTableClass)
CDAC_GLOBAL_POINTER(ContinuationMethodTable, &::g_pContinuationClassIfSubTypeCreated)
CDAC_GLOBAL_POINTER(ContinuationSingletonEEClass, &::g_singletonContinuationEEClass)
CDAC_GLOBAL_POINTER(ExceptionMethodTable, &::g_pExceptionClass)
CDAC_GLOBAL_POINTER(EHMethodTable, &::g_pEHClass)
CDAC_GLOBAL_POINTER(ExceptionServicesInternalCallsMethodTable, &::g_pExceptionServicesInternalCallsClass)
CDAC_GLOBAL_POINTER(StackFrameIteratorMethodTable, &::g_pStackFrameIteratorClass)
CDAC_GLOBAL_POINTER(EnvironmentCallEntryPointMethodDesc, &::g_pEnvironmentCallEntryPointMethodDesc)
CDAC_GLOBAL_POINTER(FreeObjectMethodTable, &::g_pFreeObjectMethodTable)
CDAC_GLOBAL_POINTER(MulticastDelegateMethodTable, &::g_pMulticastDelegateClass)
CDAC_GLOBAL_POINTER(ObjectMethodTable, &::g_pObjectClass)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ public interface IGCInfo : IContract
IReadOnlyList<InterruptibleRange> GetInterruptibleRanges(IGCInfoHandle handle) => throw new NotImplementedException();
IReadOnlyList<LiveSlot> EnumerateLiveSlots(IGCInfoHandle handle, uint instructionOffset, GcSlotEnumerationOptions options) => throw new NotImplementedException();
bool IsGcSafe(IGCInfoHandle handle, uint instructionOffset) => throw new NotImplementedException();
bool TryGetGenericInstantiationContextStackSlot(IGCInfoHandle handle, out int spOffset, out bool isStackBaseRelative) => throw new NotImplementedException();
TargetPointer GetAmbientSP(IGCInfoHandle handle, uint codeOffset, TargetPointer fp, TargetPointer sp) => throw new NotImplementedException();
}

public readonly struct GCInfo : IGCInfo
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,16 @@ public enum GenericContextLoc
ThisPtr,
}

[Flags]
public enum AsyncMethodFlags : uint
{
None = 0,
AsyncCall = 0x1,
IsAsyncVariant = 0x2,
Thunk = 0x4,
ReturnDroppingThunk = 0x8,
Comment thread
rcj1 marked this conversation as resolved.
}
Comment thread
rcj1 marked this conversation as resolved.
Comment thread
rcj1 marked this conversation as resolved.

public enum WellKnownMethodTable
{
Object,
Expand All @@ -107,6 +117,14 @@ public enum WellKnownMethodTable
Exception,
Free,
Canon,
EH,
ExceptionServicesInternalCalls,
StackFrameIterator,
}

public enum WellKnownMethodDesc
{
EnvironmentCallEntryPoint,
}


Expand Down Expand Up @@ -148,6 +166,9 @@ public interface IRuntimeTypeSystem : IContract
// Returns the address of one of the runtime's well-known singleton MethodTables,
// or TargetPointer.Null if the runtime has not yet initialized that global.
TargetPointer GetWellKnownMethodTable(WellKnownMethodTable kind) => throw new NotImplementedException();
// Returns the address of one of the runtime's well-known singleton MethodDescs,
// or TargetPointer.Null if the runtime has not yet initialized that global.
TargetPointer GetWellKnownMethodDesc(WellKnownMethodDesc kind) => throw new NotImplementedException();
// True if the MethodTable represents a type that contains managed references
bool ContainsGCPointers(TypeHandle typeHandle) => throw new NotImplementedException();
// True if MethodTable represents a byreflike value (Span<T>, ReadOnlySpan<T>, etc.).
Expand Down Expand Up @@ -295,11 +316,12 @@ bool TryGetHFAElementSize(TypeHandle typeHandle, out int elementSize)

OptimizationTier GetMethodDescOptimizationTier(MethodDescHandle methodDescHandle) => throw new NotImplementedException();
bool IsEligibleForTieredCompilation(MethodDescHandle methodDescHandle) => throw new NotImplementedException();

bool IsAsyncThunkMethod(MethodDescHandle methodDesc) => throw new NotImplementedException();
AsyncMethodFlags GetAsyncMethodFlags(MethodDescHandle methodDesc) => throw new NotImplementedException();

bool IsWrapperStub(MethodDescHandle methodDesc) => throw new NotImplementedException();
bool IsUnboxingStub(MethodDescHandle methodDesc) => throw new NotImplementedException();

bool IsVarArg(MethodDescHandle methodDesc) => throw new NotImplementedException();
#endregion MethodDesc inspection APIs
#region FieldDesc inspection APIs
TargetPointer GetMTOfEnclosingClass(TargetPointer fieldDescPointer) => throw new NotImplementedException();
Expand Down
Loading
Loading