You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -501,6 +504,24 @@ After obtaining the clause array bounds, the common iteration logic classifies e
501
504
502
505
`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.
503
506
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.
@@ -85,6 +96,8 @@ This contract depends on the following descriptors:
85
96
|`ExceptionInfo`|`CallerOfActualHandlerFrame`| Stack frame of the caller of the catch handler |
86
97
|`ExceptionInfo`|`PreviousNestedInfo`| Pointer to previous nested ExInfo |
87
98
|`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 |
88
101
89
102
Global variables used:
90
103
| Global Name | Type | Purpose |
@@ -102,6 +115,7 @@ Contracts used:
102
115
|`ExecutionManager`|
103
116
|`Thread`|
104
117
|`RuntimeTypeSystem`|
118
+
|`GCInfo`|
105
119
106
120
107
121
### Stackwalk Algorithm
@@ -277,10 +291,14 @@ InlinedCallFrames store and update only the IP, SP, and FP of a given context. I
277
291
278
292
* 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.
279
293
294
+
**Return Address**: `CallerReturnAddress`, but only when the frame has an active call (i.e., `CallerReturnAddress != 0`). Returns null otherwise.
295
+
280
296
#### SoftwareExceptionFrame
281
297
282
298
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.
283
299
300
+
**Return Address**: Read from the `ReturnAddress` field on the frame.
301
+
284
302
#### TransitionFrame
285
303
286
304
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
289
307
290
308
* On ARM, the additional register values stored in `ArgumentRegisters` are copied over. The `TransitionBlock` holds a pointer to the `ArgumentRegister` struct containing these values.
291
309
310
+
**Return Address**: Read from `TransitionBlock.ReturnAddress`. This applies to all frame types that use the TransitionFrame mechanism.
311
+
292
312
The following Frame types also use this mechanism:
293
313
* FramedMethodFrame
294
314
* PInvokeCallIFrame
@@ -302,12 +322,16 @@ The following Frame types also use this mechanism:
302
322
303
323
FuncEvalFrames hold a pointer to a `DebuggerEval`. The DebuggerEval holds a full context which is completely copied over to the working context when updating.
304
324
325
+
**Return Address**: Returns null when using hijack evaluation (`EvalUsesHijack`). Otherwise, read from `TransitionBlock.ReturnAddress` like other TransitionFrame types.
326
+
305
327
#### ResumableFrame
306
328
307
329
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.
308
330
309
331
RedirectedThreadFrames also use this mechanism.
310
332
333
+
**Return Address**: Extracted from the saved context's instruction pointer (`TargetContextPtr` -> context IP).
334
+
311
335
#### FaultingExceptionFrame
312
336
313
337
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.
316
340
317
341
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.
318
342
343
+
**Return Address**: Extracted from the saved context's instruction pointer (`TargetContext` -> context IP).
344
+
319
345
#### HijackFrame
320
346
321
347
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.
322
348
349
+
**Return Address**: Read from the `ReturnAddress` field directly.
350
+
323
351
* x64 - On x64, HijackArgs contains a CalleeSavedRegister struct. The saved registers values contained in the struct are copied over to the working context.
324
352
* Windows - On Windows, HijackArgs also contains the SP value directly which is copied over to the working context.
325
353
* 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
331
359
332
360
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.
333
361
362
+
**Return Address**: Read from the `ReturnAddress` field directly.
363
+
334
364
### APIs
335
365
336
366
The majority of the contract's complexity is the stack walking algorithm (detailed above) implemented as part of `CreateStackWalk`.
`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`.
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:
0 commit comments