Skip to content

Commit 6e51115

Browse files
max-charlambMax CharlambCopilot
authored
[cDAC] Stack walk GC reference scanning and bug fixes (1/5) (dotnet#127395)
## Summary Part 1 of 5 stacked PRs splitting [dotnet#126408](dotnet#126408) into reviewable pieces. ### What this PR contains **Stack Walk GC Reference Scanning:** - `PromoteCallerStack` / `PromoteCallerStackUsingGCRefMap` for transition frames - `GCRefMapDecoder` + `FindGCRefMap` with ReadyToRun import section resolution - `GcSignatureTypeProvider` for GC type classification - `SOSDacImpl.GetStackReferences` fully implemented using cDAC contracts - `GCInfoDecoder.EnumerateLiveSlots` promoted to `IGCInfo` contract (returns `IReadOnlyList<LiveSlot>`) - `GcSlotEnumerationOptions` replaces native `CodeManagerFlags` with descriptive boolean properties **Stack Walker Fixes:** - `IsFirst` preserved for skipped frames (matches native SFITER_SKIPPED_FRAME_FUNCTION) - `IsInterrupted` state tracking for exception frames (FaultingExceptionFrame, SoftwareExceptionFrame) - `GetReturnAddress` gating in SW_FRAME (only UpdateRegDisplay if return address non-null) - Catch handler offset override via `GetInterruptibleRanges` for EH resumption **Contract API Additions:** - `IGCInfo`: `EnumerateLiveSlots`, `GetStackBaseRegister`, `GetInterruptibleRanges` - `IExecutionManager`: `FindReadyToRunModule` - `IRuntimeTypeSystem`: `RequiresInstArg`, `IsAsyncMethod` - `IStackWalk`: `WalkStackReferences` **Data Descriptor Changes:** - Removed `ZapModule` and `GCRefMap` cached pointers (always resolve via `FindReadyToRunModule`) - Added `Indirection` for StubDispatchFrame, ExternalMethodFrame - Added `DynamicHelperFrame.DynamicHelperFrameFlags` - Added TransitionBlock fields (`OffsetOfArgs`, `ArgumentRegistersOffset`, `FirstGCRefMapSlot`) - Added ReadyToRunInfo fields (`ImportSections`, `NumImportSections`) - Added ExceptionInfo catch clause fields (`ClauseForCatchHandlerStartPC`, `ClauseForCatchHandlerEndPC`) **Documentation:** - GCInfo.md: Comprehensive implementation docs (header/body decoding, slot table, EnumerateLiveSlots algorithm, type definitions for `LiveSlot`, `InterruptibleRange`, `GcSlotEnumerationOptions`) - StackWalk.md: GC scanning algorithm, GCRefMap resolution flow, return address per frame type, `WalkStackReferences` API - ExecutionManager.md: `FindReadyToRunModule` API and implementation - RuntimeTypeSystem.md: `RequiresInstArg`, `IsAsyncMethod` APIs ### Stack overview | PR | Content | Status | |----|---------|--------| | **This PR** | Stack walk fixes + GC scanning | Open | | PR 2 | RuntimeSignatureDecoder (ELEMENT_TYPE_INTERNAL) | Pending | | PR 3 | ArgIterator port from crossgen2 | Pending | | PR 4 | Native stress framework (cdacstress.cpp) | Pending | | PR 5 | Managed stress tests + CI pipeline | Pending | ### Testing - 1727/1751 unit tests pass (24 pre-existing ThreadTests failures on main) - Dump tests (StackWalkDumpTests, StackReferenceDumpTests) validate end-to-end > [!NOTE] > This PR description was created with AI assistance from Copilot. --------- Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 0e1b3d9 commit 6e51115

35 files changed

Lines changed: 1705 additions & 272 deletions

docs/design/datacontracts/ExecutionManager.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ struct CodeBlockHandle
5757
bool IsFunclet(CodeBlockHandle codeInfoHandle);
5858
// Returns true if the code block is specifically a filter funclet
5959
bool IsFilterFunclet(CodeBlockHandle codeInfoHandle);
60+
61+
// Finds the ReadyToRun module that contains the given address.
62+
TargetPointer FindReadyToRunModule(TargetPointer address);
6063
```
6164

6265
```csharp
@@ -501,6 +504,24 @@ After obtaining the clause array bounds, the common iteration logic classifies e
501504

502505
`IsFilterFunclet` first checks `IsFunclet`. If the code block is a funclet, it retrieves the EH clauses for the method and checks whether any filter clause's handler offset matches the funclet's relative offset. If a match is found, the funclet is a filter funclet.
503506

507+
### FindReadyToRunModule
508+
509+
`FindReadyToRunModule` locates the ReadyToRun module whose PE image contains the given address. Unlike `GetCodeBlockHandle` (which only matches code regions), this API matches against the full PE image range - including data sections such as import tables. This is used in GCRefMap resolution as it requires finding the module that owns an import section indirection address, which is in the data section rather than the code section.
510+
511+
```csharp
512+
TargetPointer IExecutionManager.FindReadyToRunModule(TargetPointer address)
513+
{
514+
// Use the RangeSectionMap to find the RangeSection containing the address.
515+
// ReadyToRun range sections cover the entire PE image (code + data),
516+
// so this works for import section addresses used by GCRefMap lookup.
517+
RangeSection range = RangeSection.Find(target, topRangeSectionMap, address);
518+
if (range.Data is null)
519+
return TargetPointer.Null;
520+
521+
return range.Data.R2RModule;
522+
}
523+
```
524+
504525
### EE JIT Manager and Code Heap Info
505526

506527
```csharp

docs/design/datacontracts/GCInfo.md

Lines changed: 260 additions & 10 deletions
Large diffs are not rendered by default.

docs/design/datacontracts/RuntimeTypeSystem.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,14 @@ partial interface IRuntimeTypeSystem : IContract
190190
// Return true if a MethodDesc represents an IL stub with a special MethodDesc context arg
191191
public virtual bool HasMDContextArg(MethodDescHandle);
192192

193+
// Return true if the method requires a hidden instantiation argument (generic context parameter).
194+
// Corresponds to native MethodDesc::RequiresInstArg().
195+
public virtual bool RequiresInstArg(MethodDescHandle methodDesc);
196+
197+
// Return true if the method uses the async calling convention.
198+
// Corresponds to native MethodDesc::IsAsyncMethod().
199+
public virtual bool IsAsyncMethod(MethodDescHandle methodDesc);
200+
193201
// Return true if a MethodDesc is in a collectible module
194202
public virtual bool IsCollectibleMethod(MethodDescHandle methodDesc);
195203

@@ -1150,6 +1158,7 @@ And the following enumeration definitions
11501158
HasMethodImpl = 0x0010,
11511159
HasNativeCodeSlot = 0x0020,
11521160
HasAsyncMethodData = 0x0040,
1161+
Static = 0x0080,
11531162
// Mask for the above flags
11541163
MethodDescAdditionalPointersMask = 0x0038,
11551164
#endredion Additional pointers
@@ -1600,6 +1609,57 @@ Determining if a method is an async thunk method:
16001609
}
16011610
```
16021611

1612+
Determining if a method requires a hidden instantiation argument (generic context parameter):
1613+
1614+
```csharp
1615+
public bool RequiresInstArg(MethodDescHandle methodDescHandle)
1616+
{
1617+
MethodDesc md = _methodDescs[methodDescHandle.Address];
1618+
1619+
// RequiresInstArg = IsSharedByGenericInstantiations && (HasMethodInstantiation || IsStatic || IsValueType || IsInterface)
1620+
if (!IsSharedByGenericInstantiations(md))
1621+
return false;
1622+
1623+
if (HasMethodInstantiation(md))
1624+
return true;
1625+
1626+
// md.IsStatic reads from MethodDescFlags.Static (0x0080)
1627+
if (md.IsStatic)
1628+
return true;
1629+
1630+
MethodTable mt = _methodTables[md.MethodTable];
1631+
return mt.Flags.IsInterface || mt.Flags.IsValueType;
1632+
}
1633+
1634+
private bool IsSharedByGenericInstantiations(MethodDesc md)
1635+
{
1636+
if (md.Classification == MethodClassification.Instantiated)
1637+
{
1638+
InstantiatedMethodDesc imd = AsInstantiatedMethodDesc(md);
1639+
if (imd.IsWrapperStubWithInstantiations)
1640+
return false;
1641+
if (/* Flags2 of InstantiatedMethodDesc has SharedMethodInstantiation set */)
1642+
return true;
1643+
}
1644+
MethodTable mt = _methodTables[md.MethodTable];
1645+
return mt.IsCanonMT && mt.Flags.HasInstantiation;
1646+
}
1647+
```
1648+
1649+
Determining if a method uses the async calling convention:
1650+
1651+
```csharp
1652+
public bool IsAsyncMethod(MethodDescHandle methodDescHandle)
1653+
{
1654+
MethodDesc md = _methodDescs[methodDescHandle.Address];
1655+
if (!md.HasAsyncMethodData)
1656+
return false;
1657+
1658+
Data.AsyncMethodData asyncData = // Read AsyncMethodData from the async method data optional slot
1659+
return ((AsyncMethodFlags)asyncData.Flags).HasFlag(AsyncMethodFlags.AsyncCall);
1660+
}
1661+
```
1662+
16031663
Determining if a method is a wrapper stub (unboxing or instantiating):
16041664

16051665
```csharp

docs/design/datacontracts/StackWalk.md

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ TargetPointer GetMethodDescPtr(IStackDataFrameHandle stackDataFrameHandle);
2828

2929
// Gets the instruction pointer from the current frame's context.
3030
TargetPointer GetInstructionPointer(IStackDataFrameHandle stackDataFrameHandle);
31+
32+
// Walks the stack and returns all GC references found on each frame.
33+
// This is the primary API for GC reference enumeration, used by SOSDacImpl.GetStackReferences.
34+
IReadOnlyList<StackReferenceData> WalkStackReferences(ThreadData threadData);
3135
```
3236

3337
## Version 1
@@ -60,12 +64,19 @@ This contract depends on the following descriptors:
6064
| `StubDispatchFrame` | `MethodDescPtr` | Pointer to Frame's method desc |
6165
| `StubDispatchFrame` | `RepresentativeMTPtr` | Pointer to Frame's method table pointer |
6266
| `StubDispatchFrame` | `RepresentativeSlot` | Frame's method table slot |
67+
| `StubDispatchFrame` | `Indirection` | Import slot pointer for GCRefMap resolution via `FindReadyToRunModule` |
68+
| `ExternalMethodFrame` | `Indirection` | Import slot pointer for GCRefMap resolution via `FindReadyToRunModule` |
69+
| `DynamicHelperFrame` | `DynamicHelperFrameFlags` | Flags indicating which argument registers contain GC references |
6370
| `TransitionBlock` | `ReturnAddress` | Return address associated with the TransitionBlock |
6471
| `TransitionBlock` | `CalleeSavedRegisters` | Platform specific CalleeSavedRegisters struct associated with the TransitionBlock |
65-
| `TransitionBlock` (arm) | `ArgumentRegisters` | ARM specific `ArgumentRegisters` struct |
72+
| `TransitionBlock` | `OffsetOfArgs` | Byte offset of stack arguments (first arg after registers) = `sizeof(TransitionBlock)` |
73+
| `TransitionBlock` | `ArgumentRegisters` | Byte offset of the argument registers area within the TransitionBlock |
74+
| `TransitionBlock` | `FirstGCRefMapSlot` | Byte offset where GCRefMap slot enumeration begins. ARM64: RetBuffArgReg offset; others: ArgumentRegisters offset |
75+
| `ReadyToRunInfo` | `ImportSections` | Pointer to array of `READYTORUN_IMPORT_SECTION` structs for GCRefMap resolution |
76+
| `ReadyToRunInfo` | `NumImportSections` | Count of import sections in the array |
6677
| `FuncEvalFrame` | `DebuggerEvalPtr` | Pointer to the Frame's DebuggerEval object |
6778
| `DebuggerEval` | `TargetContext` | Context saved inside DebuggerEval |
68-
| `DebuggerEval` | `EvalDuringException` | Flag used in processing FuncEvalFrame |
79+
| `DebuggerEval` | `EvalUsesHijack` | Flag used in processing FuncEvalFrame |
6980
| `ResumableFrame` | `TargetContextPtr` | Pointer to the Frame's Target Context |
7081
| `FaultingExceptionFrame` | `TargetContext` | Frame's Target Context |
7182
| `HijackFrame` | `ReturnAddress` | Frame's stored instruction pointer |
@@ -85,6 +96,8 @@ This contract depends on the following descriptors:
8596
| `ExceptionInfo` | `CallerOfActualHandlerFrame` | Stack frame of the caller of the catch handler |
8697
| `ExceptionInfo` | `PreviousNestedInfo` | Pointer to previous nested ExInfo |
8798
| `ExceptionInfo` | `PassNumber` | Exception handling pass (1 or 2) |
99+
| `ExceptionInfo` | `ClauseForCatchHandlerStartPC` | Start PC offset of the catch handler clause, used for interruptible offset override |
100+
| `ExceptionInfo` | `ClauseForCatchHandlerEndPC` | End PC offset of the catch handler clause, used for interruptible offset override |
88101

89102
Global variables used:
90103
| Global Name | Type | Purpose |
@@ -102,6 +115,7 @@ Contracts used:
102115
| `ExecutionManager` |
103116
| `Thread` |
104117
| `RuntimeTypeSystem` |
118+
| `GCInfo` |
105119

106120

107121
### Stackwalk Algorithm
@@ -277,10 +291,14 @@ InlinedCallFrames store and update only the IP, SP, and FP of a given context. I
277291

278292
* On ARM, the InlinedCallFrame stores the value of the SP after the prolog (`SPAfterProlog`) to allow unwinding for functions with stackalloc. When a function uses stackalloc, the CallSiteSP can already have been adjusted. This value should be placed in R9.
279293

294+
**Return Address**: `CallerReturnAddress`, but only when the frame has an active call (i.e., `CallerReturnAddress != 0`). Returns null otherwise.
295+
280296
#### SoftwareExceptionFrame
281297

282298
SoftwareExceptionFrames store a copy of the context struct. The IP, SP, and all ABI specified (platform specific) callee-saved registers are copied from the stored context to the working context.
283299

300+
**Return Address**: Read from the `ReturnAddress` field on the frame.
301+
284302
#### TransitionFrame
285303

286304
TransitionFrames hold a pointer to a `TransitionBlock`. The TransitionBlock holds a return address along with a `CalleeSavedRegisters` struct which has values for all ABI specified callee-saved registers. The SP can be found using the address of the TransitionBlock. Since the TransitionBlock will be the lowest element on the stack, the SP is the address of the TransitionBlock + sizeof(TransitionBlock).
@@ -289,6 +307,8 @@ When updating the context from a TransitionFrame, the IP, SP, and all ABI specif
289307

290308
* On ARM, the additional register values stored in `ArgumentRegisters` are copied over. The `TransitionBlock` holds a pointer to the `ArgumentRegister` struct containing these values.
291309

310+
**Return Address**: Read from `TransitionBlock.ReturnAddress`. This applies to all frame types that use the TransitionFrame mechanism.
311+
292312
The following Frame types also use this mechanism:
293313
* FramedMethodFrame
294314
* PInvokeCallIFrame
@@ -302,12 +322,16 @@ The following Frame types also use this mechanism:
302322

303323
FuncEvalFrames hold a pointer to a `DebuggerEval`. The DebuggerEval holds a full context which is completely copied over to the working context when updating.
304324

325+
**Return Address**: Returns null when using hijack evaluation (`EvalUsesHijack`). Otherwise, read from `TransitionBlock.ReturnAddress` like other TransitionFrame types.
326+
305327
#### ResumableFrame
306328

307329
ResumableFrames hold a pointer to a context object (Note this is different from SoftwareExceptionFrames which hold the context directly). The entire context object is copied over to the working context when updating.
308330

309331
RedirectedThreadFrames also use this mechanism.
310332

333+
**Return Address**: Extracted from the saved context's instruction pointer (`TargetContextPtr` -> context IP).
334+
311335
#### FaultingExceptionFrame
312336

313337
FaultingExceptionFrames have two different implementations. One for Windows x86 and another for all other builds (with funclets).
@@ -316,10 +340,14 @@ Given the cDAC does not yet support Windows x86, this version is not supported.
316340

317341
The other version stores a context struct. To update the working context, the entire stored context is copied over. In addition the `ContextFlags` are updated to ensure the `CONTEXT_XSTATE` bit is not set given the debug version of the contexts can not store extended state. This bit is architecture specific.
318342

343+
**Return Address**: Extracted from the saved context's instruction pointer (`TargetContext` -> context IP).
344+
319345
#### HijackFrame
320346

321347
HijackFrames carry a IP (ReturnAddress) and a pointer to `HijackArgs`. All platforms update the IP and use the platform specific HijackArgs to update further registers. The following details currently implemented platforms.
322348

349+
**Return Address**: Read from the `ReturnAddress` field directly.
350+
323351
* x64 - On x64, HijackArgs contains a CalleeSavedRegister struct. The saved registers values contained in the struct are copied over to the working context.
324352
* Windows - On Windows, HijackArgs also contains the SP value directly which is copied over to the working context.
325353
* Non-Windows - On OS's other than Windows, HijackArgs does not contain an SP value. Instead since the HijackArgs struct lives on the stack, the SP is `&hijackArgs + sizeof(HijackArgs)`. This value is also copied over.
@@ -331,6 +359,8 @@ HijackFrames carry a IP (ReturnAddress) and a pointer to `HijackArgs`. All platf
331359

332360
TailCallFrames only appear on x86 Windows. They hold a `CalleeSavedRegisters` struct as well as a `ReturnAddress`. While the stack pointer is not directly contained in the TailCallFrame structure, it will be on the stack immediately following the Frame (found at the address of the Frame + size of the Frame). To process these Frames, update all of the registers in `CalleeSavedRegisters`, the instruction pointer from the stored return address, and the stack pointer from the address saved on the stack.
333361

362+
**Return Address**: Read from the `ReturnAddress` field directly.
363+
334364
### APIs
335365

336366
The majority of the contract's complexity is the stack walking algorithm (detailed above) implemented as part of `CreateStackWalk`.
@@ -399,6 +429,78 @@ TargetPointer GetMethodDescPtr(IStackDataFrameHandle stackDataFrameHandle)
399429
TargetPointer GetInstructionPointer(IStackDataFrameHandle stackDataFrameHandle)
400430
```
401431

432+
`WalkStackReferences` walks the entire managed stack and enumerates all live GC references at each frame. It returns a list of `StackReferenceData` describing each GC-tracked slot (its address, whether it's an interior pointer, and the register/stack location). This API is the primary consumer for `SOSDacImpl.GetStackReferences`.
433+
434+
```csharp
435+
IReadOnlyList<StackReferenceData> WalkStackReferences(ThreadData threadData)
436+
```
437+
438+
The implementation uses the same stack walk algorithm as `CreateStackWalk`, but integrates the GC-aware `Filter` directly (rather than consuming pre-generated frames) and performs GC reference enumeration at each frame. See [GC Stack Reference Scanning](#gc-stack-reference-scanning) for details.
439+
440+
### GC Stack Reference Scanning
441+
442+
`WalkStackReferences` scans the stack for GC references by walking through each frame and reporting live object references and interior pointers. The native equivalent is `DacStackReferenceWalker` which calls `GcStackCrawlCallBack` at each frame.
443+
444+
#### Stack Walk Integration
445+
446+
The GC reference walk uses the `Filter` function to drive the stack walk. `Filter` is a port of native `StackFrameIterator::Filter` (with `GC_FUNCLET_REFERENCE_REPORTING` mode) that handles funclet-to-parent frame transitions, exception tracker correlation, and determines whether each frame should report GC references. Unlike `CreateStackWalk` which yields all frames, `Filter` calls `Next()` directly and may skip frames that don't contribute GC roots.
447+
448+
Key state tracked during the walk:
449+
450+
- **IsInterrupted**: Set when transitioning to a managed frame from a `FaultingExceptionFrame` or `SoftwareExceptionFrame` (frames with `FRAME_ATTR_EXCEPTION`). When true, the managed frame's GC enumeration uses `ExecutionAborted` mode, which causes the GcInfoDecoder to skip live slot reporting at non-interruptible offsets.
451+
- **LastProcessedFrameType**: Records the frame type when processing `SW_FRAME` state, so `UpdateState` can detect exception frames during the transition to `SW_FRAMELESS`.
452+
- **IsFirst**: Preserved during skipped frame processing (native `SFITER_SKIPPED_FRAME_FUNCTION` does not modify `IsFirst`), ensuring the subsequent managed frame is still treated as the leaf/active frame.
453+
- **GetReturnAddress gating**: In `SW_FRAME` state, `UpdateContextFromFrame` is only called when `GetReturnAddress()` returns a non-null value. This matches native behavior where the context is only updated when the frame has a valid return address.
454+
455+
#### Per-Frame GC Enumeration
456+
457+
At each frame yielded by `Filter`, the walk determines whether to scan for GC references:
458+
459+
**Managed (frameless) frames** use `EnumGcRefsForManagedFrame`:
460+
461+
1. Get the code block handle and relative offset from the `ExecutionManager` contract.
462+
2. Decode the GCInfo for the code block via the `GCInfo` contract.
463+
3. Determine `GcSlotEnumerationOptions`: set `IsActiveFrame` if this is the leaf frame (`IsFirst`), `IsExecutionAborted` if the frame was interrupted, `IsParentOfFuncletStackFrame` if funclet GC reporting was delegated to the parent, `SuppressUntrackedSlots` if the code block is a filter funclet.
464+
4. **Catch handler offset override**: When `ShouldParentFrameUseUnwindTargetPCforGCReporting` is set (parent frame resuming from a catch handler), the GC liveness offset is overridden to the first interruptible point within the catch handler clause range. This uses `GetInterruptibleRanges` from the `GCInfo` contract. See [How EH affects GC info/reporting](../coreclr/botr/clr-abi.md#how-eh-affects-gc-inforeporting) for background on why this override is needed.
465+
5. Call `GcInfoDecoder.EnumerateLiveSlots` with the computed offset and flags to report all live register and stack slots. See the [GCInfo contract — EnumerateLiveSlots](./GCInfo.md#enumerateliveslots) for details on the algorithm.
466+
467+
**Capital "F" Frames** use `GcScanRoots`, which dispatches based on frame type:
468+
469+
- **StubDispatchFrame / ExternalMethodFrame**: Resolve GCRefMap via `FindGCRefMap` using the frame's `Indirection` pointer, otherwise fall back to signature-based scanning.
470+
- **DynamicHelperFrame**: Use flag-based scanning (`DynamicHelperFrameFlags`).
471+
- **PrestubMethodFrame / CallCountingHelperFrame**: Use signature-based scanning.
472+
- Other frame types: No GC roots to report.
473+
474+
See [GCRefMap Format and Resolution](#gcrefmap-format-and-resolution) for the GCRefMap scanning path details.
475+
476+
### GCRefMap Format and Resolution
477+
478+
A **GCRefMap** is a compact per-callsite encoding that describes which stack slots in a `TransitionBlock` contain GC references. GCRefMaps are pre-computed by the ReadyToRun compiler and stored in the PE image's import section auxiliary data.
479+
480+
The GCRefMap encoding format — including token values, bit encoding, lookup table structure, and per-architecture position semantics — is documented in the [ReadyToRun format specification](../coreclr/botr/readytorun-format.md#readytorun_import_sectionsauxiliarydata).
481+
482+
#### Resolution Flow
483+
484+
GCRefMap resolution from a frame's `Indirection` pointer proceeds as follows:
485+
486+
1. Call `FindReadyToRunModule(indirection)` (see [ExecutionManager contract](./ExecutionManager.md)) to find the ReadyToRun module containing the import slot.
487+
2. Load the module's `ReadyToRunInfo` to access the import section array.
488+
3. Compute the RVA of the indirection address: `rva = indirection - imageBase`.
489+
4. Search through `READYTORUN_IMPORT_SECTION` entries to find the section containing the RVA.
490+
5. Compute the slot index within the section: `index = (rva - sectionVA) / entrySize`.
491+
6. Use the section's `AuxiliaryData` RVA to locate the GCRefMap lookup table.
492+
7. Use stride-based lookup (stride = 1024) plus linear scan to find the specific GCRefMap entry.
493+
494+
#### Slot Mapping
495+
496+
GCRefMap positions map to `TransitionBlock` offsets using the formula:
497+
498+
```csharp
499+
slotAddress = transitionBlockPtr + FirstGCRefMapSlot + (position * pointerSize)
500+
```
501+
502+
Where `FirstGCRefMapSlot` is the byte offset in the `TransitionBlock` where GCRefMap slot enumeration begins (platform-dependent: on ARM64 it is the return buffer argument register offset; on other platforms it is the argument registers offset).
503+
402504
### x86 Specifics
403505

404506
The x86 platform has some major differences to other platforms. In general this stems from the platform being older and not having a defined unwinding codes. Instead, to unwind managed frames, we rely on GCInfo associated with JITted code. For the unwind, we do not defer to a 'Windows like' native unwinder, instead the custom unwinder implementation was ported to managed code.

0 commit comments

Comments
 (0)