Skip to content

Commit 8cd8921

Browse files
authored
[cDAC] Stack walk support more Frame types (#112997)
Adds cDAC support for runtime Frame types: * `TransitionFrame` (and subclasses) * `FuncEvalFrame` * `ResumableFrame` (and subclasses) * `FaultingExceptionFrame` * `HijackFrame`
1 parent 8381f2a commit 8cd8921

31 files changed

+922
-123
lines changed

docs/design/datacontracts/StackWalk.md

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,25 @@ This contract depends on the following descriptors:
4141
| `InlinedCallFrame` | `CalleeSavedFP` | FP saved in Frame |
4242
| `SoftwareExceptionFrame` | `TargetContext` | Context object saved in Frame |
4343
| `SoftwareExceptionFrame` | `ReturnAddress` | Return address saved in Frame |
44+
| `FramedMethodFrame` | `TransitionBlockPtr` | Pointer to Frame's TransitionBlock |
45+
| `TransitionBlock` | `ReturnAddress` | Return address associated with the TransitionBlock |
46+
| `TransitionBlock` | `CalleeSavedRegisters` | Platform specific CalleeSavedRegisters struct associated with the TransitionBlock |
47+
| `FuncEvalFrame` | `DebuggerEvalPtr` | Pointer to the Frame's DebuggerEval object |
48+
| `DebuggerEval` | `TargetContext` | Context saved inside DebuggerEval |
49+
| `DebuggerEval` | `EvalDuringException` | Flag used in processing FuncEvalFrame |
50+
| `ResumableFrame` | `TargetContextPtr` | Pointer to the Frame's Target Context |
51+
| `FaultingExceptionFrame` | `TargetContext` | Frame's Target Context |
52+
| `HijackFrame` | `ReturnAddress` | Frame's stored instruction pointer |
53+
| `HijackFrame` | `HijackArgsPtr` | Pointer to the Frame's stored HijackArgs |
54+
| `HijackArgs` (amd64) | `CalleeSavedRegisters` | CalleeSavedRegisters data structure |
55+
| `HijackArgs` (amd64 Windows) | `Rsp` | Saved stack pointer |
56+
| `HijackArgs` (arm64) | For each register `r` saved in HijackArgs, `r` | Register names associated with stored register values |
57+
| `CalleeSavedRegisters` | For each callee saved register `r`, `r` | Register names associated with stored register values |
4458

4559
Global variables used:
4660
| Global Name | Type | Purpose |
4761
| --- | --- | --- |
48-
| For each FrameType `<frameType>`, `<frameType>##Identifier` | FrameIdentifier enum value | Identifier used to determine concrete type of Frames |
62+
| For each FrameType `<frameType>`, `<frameType>##Identifier` | `FrameIdentifier` enum value | Identifier used to determine concrete type of Frames |
4963

5064
Contracts used:
5165
| Contract Name |
@@ -215,6 +229,71 @@ private static void bar()
215229
}
216230
```
217231

232+
### Capital 'F' Frame Handling
233+
234+
Capital 'F' Frame's store context data in a number of different ways. Of the couple dozen Frame types defined in `src/coreclr/vm/frames.h` several do not store any context data or update the context, signified by `NeedsUpdateRegDisplay_Impl() == false`. Of that Frames that do update the context, several share implementations of `UpdateRegDisplay_Impl` through inheritance. This leaves us with 9 distinct mechanisms to update the context that will be detailed below. Each mechanism is referred to using the Frame class that implements the mechanism and may be used by subclasses.
235+
236+
Most of the handlers are implemented in `BaseFrameHandler`. Platform specific components are implemented/overridden in `<arch>FrameHandler`.
237+
238+
#### InlinedCallFrame
239+
240+
InlinedCallFrames store and update only the IP, SP, and FP of a given context. If the stored IP (CallerReturnAddress) is 0 then the InlinedCallFrame does not have an active call and should not update the context.
241+
242+
#### SoftwareExceptionFrame
243+
244+
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.
245+
246+
#### TransitionFrame
247+
248+
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).
249+
250+
When updating the context from a TransitionFrame, the IP, SP, and all ABI specified callee-saved registers are copied over.
251+
252+
The following Frame types also use this mechanism:
253+
* FramedMethodFrame
254+
* CLRToCOMMethodFrame
255+
* PInvokeCallIFrame
256+
* PrestubMethodFrame
257+
* StubDispatchFrame
258+
* CallCountingHelperFrame
259+
* ExternalMethodFrame
260+
* DynamicHelperFrame
261+
262+
#### FuncEvalFrame
263+
264+
FuncEvalFrames hold a pointer to a `DebuggerEval`. The DebuggerEval holds a full context which is completely copied over to the working context when updating.
265+
266+
#### ResumableFrame
267+
268+
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.
269+
270+
RedirectedThreadFrames also use this mechanism.
271+
272+
#### FaultingExceptionFrame
273+
274+
FaultingExceptionFrames have two different implementations. One for Windows x86 and another for all other builds (with funclets).
275+
276+
Given the cDAC does not yet support Windows x86, this version is not supported.
277+
278+
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.
279+
280+
#### HijackFrame
281+
282+
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.
283+
284+
* x64 - On x64, HijackArgs contains a CalleeSavedRegister struct. The saved registers values contained in the struct are copied over to the working context.
285+
* Windows - On Windows, HijackArgs also contains the SP value directly which is copied over to the working context.
286+
* 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.
287+
* arm64 - Unlike on x64, on arm64 HijackArgs contains a list of register values instead of the CalleeSavedRegister struct. These values are copied over to the working context. The SP is fetched using the same technique as on x64 non-Windows where `SP = &hijackArgs + sizeof(HijackArgs)` and is copied over to the working context.
288+
289+
#### TailCallFrame
290+
291+
TailCallFrames are only used on Windows x86 which is not yet supported in the cDAC and therefore not implemented.
292+
293+
#### HelperMethodFrame
294+
295+
HelperMethodFrames are on the way to being removed. They are not currently supported in the cDAC.
296+
218297
### APIs
219298

220299
The majority of the contract's complexity is the stack walking algorithm (detailed above) implemented as part of `CreateStackWalk`.

src/coreclr/debug/runtimeinfo/datadescriptor.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
#include "methodtable.h"
1414
#include "threads.h"
1515

16+
#include "../debug/ee/debugger.h"
17+
1618
#ifdef HAVE_GCCOVER
1719
#include "gccover.h"
1820
#endif // HAVE_GCCOVER

src/coreclr/debug/runtimeinfo/datadescriptor.h

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,108 @@ CDAC_TYPE_FIELD(SoftwareExceptionFrame, /*pointer*/, ReturnAddress, cdac_data<So
652652
CDAC_TYPE_END(SoftwareExceptionFrame)
653653
#endif // FEATURE_EH_FUNCLETS
654654

655+
CDAC_TYPE_BEGIN(FramedMethodFrame)
656+
CDAC_TYPE_SIZE(sizeof(FramedMethodFrame))
657+
CDAC_TYPE_FIELD(FramedMethodFrame, /*pointer*/, TransitionBlockPtr, cdac_data<FramedMethodFrame>::TransitionBlockPtr)
658+
CDAC_TYPE_END(FramedMethodFrame)
659+
660+
CDAC_TYPE_BEGIN(TransitionBlock)
661+
CDAC_TYPE_SIZE(sizeof(TransitionBlock))
662+
CDAC_TYPE_FIELD(TransitionBlock, /*pointer*/, ReturnAddress, offsetof(TransitionBlock, m_ReturnAddress))
663+
CDAC_TYPE_FIELD(TransitionBlock, /*CalleeSavedRegisters*/, CalleeSavedRegisters, offsetof(TransitionBlock, m_calleeSavedRegisters))
664+
CDAC_TYPE_END(TransitionBlock)
665+
666+
#ifdef DEBUGGING_SUPPORTED
667+
CDAC_TYPE_BEGIN(FuncEvalFrame)
668+
CDAC_TYPE_SIZE(sizeof(FuncEvalFrame))
669+
CDAC_TYPE_FIELD(FuncEvalFrame, /*pointer*/, DebuggerEvalPtr, cdac_data<FuncEvalFrame>::DebuggerEvalPtr)
670+
CDAC_TYPE_END(FuncEvalFrame)
671+
672+
CDAC_TYPE_BEGIN(DebuggerEval)
673+
CDAC_TYPE_SIZE(sizeof(DebuggerEval))
674+
CDAC_TYPE_FIELD(DebuggerEval, /*T_CONTEXT*/, TargetContext, offsetof(DebuggerEval, m_context))
675+
CDAC_TYPE_FIELD(DebuggerEval, /*bool*/, EvalDuringException, offsetof(DebuggerEval, m_evalDuringException))
676+
CDAC_TYPE_END(DebuggerEval)
677+
#endif // DEBUGGING_SUPPORTED
678+
679+
#ifdef FEATURE_HIJACK
680+
CDAC_TYPE_BEGIN(ResumableFrame)
681+
CDAC_TYPE_SIZE(sizeof(ResumableFrame))
682+
CDAC_TYPE_FIELD(ResumableFrame, /*pointer*/, TargetContextPtr, cdac_data<ResumableFrame>::TargetContextPtr)
683+
CDAC_TYPE_END(ResumableFrame)
684+
685+
CDAC_TYPE_BEGIN(HijackFrame)
686+
CDAC_TYPE_SIZE(sizeof(HijackFrame))
687+
CDAC_TYPE_FIELD(HijackFrame, /*pointer*/, ReturnAddress, cdac_data<HijackFrame>::ReturnAddress)
688+
CDAC_TYPE_FIELD(HijackFrame, /*pointer*/, HijackArgsPtr, cdac_data<HijackFrame>::HijackArgsPtr)
689+
CDAC_TYPE_END(HijackFrame)
690+
691+
// HijackArgs struct is different on each platform
692+
CDAC_TYPE_BEGIN(HijackArgs)
693+
CDAC_TYPE_SIZE(sizeof(HijackArgs))
694+
#if defined(TARGET_AMD64)
695+
696+
CDAC_TYPE_FIELD(HijackArgs, /*CalleeSavedRegisters*/, CalleeSavedRegisters, offsetof(HijackArgs, Regs))
697+
#ifdef TARGET_WINDOWS
698+
CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, Rsp, offsetof(HijackArgs, Rsp))
699+
#endif // TARGET_WINDOWS
700+
701+
#elif defined(TARGET_ARM64)
702+
703+
CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, X0, offsetof(HijackArgs, X0))
704+
CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, X1, offsetof(HijackArgs, X1))
705+
CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, X19, offsetof(HijackArgs, X19))
706+
CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, X20, offsetof(HijackArgs, X20))
707+
CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, X21, offsetof(HijackArgs, X21))
708+
CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, X22, offsetof(HijackArgs, X22))
709+
CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, X23, offsetof(HijackArgs, X23))
710+
CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, X24, offsetof(HijackArgs, X24))
711+
CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, X25, offsetof(HijackArgs, X25))
712+
CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, X26, offsetof(HijackArgs, X26))
713+
CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, X27, offsetof(HijackArgs, X27))
714+
CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, X28, offsetof(HijackArgs, X28))
715+
CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, Fp, offsetof(HijackArgs, X29))
716+
CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, Lr, offsetof(HijackArgs, Lr))
717+
718+
#endif // Platform switch
719+
CDAC_TYPE_END(HijackArgs)
720+
#endif // FEATURE_HIJACK
721+
722+
CDAC_TYPE_BEGIN(FaultingExceptionFrame)
723+
CDAC_TYPE_SIZE(sizeof(FaultingExceptionFrame))
724+
#ifdef FEATURE_EH_FUNCLETS
725+
CDAC_TYPE_FIELD(FaultingExceptionFrame, /*T_CONTEXT*/, TargetContext, cdac_data<FaultingExceptionFrame>::TargetContext)
726+
#endif // FEATURE_EH_FUNCLETS
727+
CDAC_TYPE_END(FaultingExceptionFrame)
728+
729+
// CalleeSavedRegisters struct is different on each platform
730+
CDAC_TYPE_BEGIN(CalleeSavedRegisters)
731+
CDAC_TYPE_SIZE(sizeof(CalleeSavedRegisters))
732+
#if defined(TARGET_AMD64)
733+
734+
#define CALLEE_SAVED_REGISTER(regname) \
735+
CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, regname, offsetof(CalleeSavedRegisters, regname))
736+
ENUM_CALLEE_SAVED_REGISTERS()
737+
#undef CALLEE_SAVED_REGISTER
738+
739+
#elif defined(TARGET_ARM64)
740+
741+
CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, X19, offsetof(CalleeSavedRegisters, x19))
742+
CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, X20, offsetof(CalleeSavedRegisters, x20))
743+
CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, X21, offsetof(CalleeSavedRegisters, x21))
744+
CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, X22, offsetof(CalleeSavedRegisters, x22))
745+
CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, X23, offsetof(CalleeSavedRegisters, x23))
746+
CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, X24, offsetof(CalleeSavedRegisters, x24))
747+
CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, X25, offsetof(CalleeSavedRegisters, x25))
748+
CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, X26, offsetof(CalleeSavedRegisters, x26))
749+
CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, X27, offsetof(CalleeSavedRegisters, x27))
750+
CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, X28, offsetof(CalleeSavedRegisters, x28))
751+
CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, Fp, offsetof(CalleeSavedRegisters, x29))
752+
CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, Lr, offsetof(CalleeSavedRegisters, x30))
753+
754+
#endif // Platform switch
755+
CDAC_TYPE_END(CalleeSavedRegisters)
756+
655757
CDAC_TYPES_END()
656758

657759
CDAC_GLOBALS_BEGIN()

src/coreclr/vm/frames.h

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,14 @@ class ResumableFrame : public Frame
745745

746746
protected:
747747
PTR_CONTEXT m_Regs;
748+
749+
friend struct cdac_data<ResumableFrame>;
750+
};
751+
752+
template<>
753+
struct cdac_data<ResumableFrame>
754+
{
755+
static constexpr size_t TargetContextPtr = offsetof(ResumableFrame, m_Regs);
748756
};
749757

750758

@@ -1014,6 +1022,16 @@ class FaultingExceptionFrame : public Frame
10141022
}
10151023

10161024
void UpdateRegDisplay_Impl(const PREGDISPLAY, bool updateFloats = false);
1025+
1026+
friend struct ::cdac_data<FaultingExceptionFrame>;
1027+
};
1028+
1029+
template<>
1030+
struct cdac_data<FaultingExceptionFrame>
1031+
{
1032+
#ifdef FEATURE_EH_FUNCLETS
1033+
static constexpr size_t TargetContext = offsetof(FaultingExceptionFrame, m_ctx);
1034+
#endif // FEATURE_EH_FUNCLETS
10171035
};
10181036

10191037
#ifdef FEATURE_EH_FUNCLETS
@@ -1155,6 +1173,14 @@ class FuncEvalFrame : public Frame
11551173

11561174
return m_showFrame;
11571175
}
1176+
1177+
friend struct cdac_data<FuncEvalFrame>;
1178+
};
1179+
1180+
template<>
1181+
struct cdac_data<FuncEvalFrame>
1182+
{
1183+
static constexpr size_t DebuggerEvalPtr = offsetof(FuncEvalFrame, m_pDebuggerEval);
11581184
};
11591185

11601186
typedef DPTR(FuncEvalFrame) PTR_FuncEvalFrame;
@@ -1672,8 +1698,15 @@ class FramedMethodFrame : public TransitionFrame
16721698
#endif
16731699
return dac_cast<PTR_VOID>(p);
16741700
}
1701+
1702+
friend struct cdac_data<FramedMethodFrame>;
16751703
};
16761704

1705+
template<>
1706+
struct cdac_data<FramedMethodFrame>
1707+
{
1708+
static constexpr size_t TransitionBlockPtr = offsetof(FramedMethodFrame, m_pTransitionBlock);
1709+
};
16771710

16781711
#ifdef FEATURE_COMINTEROP
16791712

@@ -1966,6 +1999,15 @@ class HijackFrame : public Frame
19661999
TADDR m_ReturnAddress;
19672000
PTR_Thread m_Thread;
19682001
DPTR(HijackArgs) m_Args;
2002+
2003+
friend struct ::cdac_data<HijackFrame>;
2004+
};
2005+
2006+
template<>
2007+
struct cdac_data<HijackFrame>
2008+
{
2009+
static constexpr size_t ReturnAddress = offsetof(HijackFrame, m_ReturnAddress);
2010+
static constexpr size_t HijackArgsPtr = offsetof(HijackFrame, m_Args);
19692011
};
19702012

19712013
#endif // FEATURE_HIJACK

src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,17 @@ public enum DataType
9090
MethodImpl,
9191
NativeCodeSlot,
9292
GCCoverageInfo,
93+
TransitionBlock,
94+
DebuggerEval,
95+
CalleeSavedRegisters,
96+
HijackArgs,
9397

9498
Frame,
9599
InlinedCallFrame,
96100
SoftwareExceptionFrame,
101+
FramedMethodFrame,
102+
FuncEvalFrame,
103+
ResumableFrame,
104+
FaultingExceptionFrame,
105+
HijackFrame,
97106
}

src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/AMD64Context.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ public enum ContextFlagsValues : uint
2525
CONTEXT_ALL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS,
2626
CONTEXT_XSTATE = CONTEXT_AMD | 0x40,
2727
CONTEXT_KERNEL_CET = CONTEXT_AMD | 0x80,
28+
29+
CONTEXT_AREA_MASK = 0xFFFF,
2830
}
2931

3032
public readonly uint Size => 0x4d0;

src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64Context.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@ public enum ContextFlagsValues : uint
2121
CONTEXT_FLOATING_POINT = CONTEXT_ARM64 | 0x4,
2222
CONTEXT_DEBUG_REGISTERS = CONTEXT_ARM64 | 0x8,
2323
CONTEXT_X18 = CONTEXT_ARM64 | 0x10,
24+
CONTEXT_XSTATE = CONTEXT_ARM64 | 0x20,
2425
CONTEXT_FULL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT,
2526
CONTEXT_ALL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS | CONTEXT_X18,
27+
28+
CONTEXT_AREA_MASK = 0xFFFF,
2629
}
2730

2831
public readonly uint Size => 0x390;

0 commit comments

Comments
 (0)