diff --git a/docs/design/datacontracts/StackWalk.md b/docs/design/datacontracts/StackWalk.md index d76aece42731f9..774be93cdf7ea6 100644 --- a/docs/design/datacontracts/StackWalk.md +++ b/docs/design/datacontracts/StackWalk.md @@ -41,11 +41,25 @@ This contract depends on the following descriptors: | `InlinedCallFrame` | `CalleeSavedFP` | FP saved in Frame | | `SoftwareExceptionFrame` | `TargetContext` | Context object saved in Frame | | `SoftwareExceptionFrame` | `ReturnAddress` | Return address saved in Frame | +| `FramedMethodFrame` | `TransitionBlockPtr` | Pointer to Frame's TransitionBlock | +| `TransitionBlock` | `ReturnAddress` | Return address associated with the TransitionBlock | +| `TransitionBlock` | `CalleeSavedRegisters` | Platform specific CalleeSavedRegisters struct associated with the TransitionBlock | +| `FuncEvalFrame` | `DebuggerEvalPtr` | Pointer to the Frame's DebuggerEval object | +| `DebuggerEval` | `TargetContext` | Context saved inside DebuggerEval | +| `DebuggerEval` | `EvalDuringException` | Flag used in processing FuncEvalFrame | +| `ResumableFrame` | `TargetContextPtr` | Pointer to the Frame's Target Context | +| `FaultingExceptionFrame` | `TargetContext` | Frame's Target Context | +| `HijackFrame` | `ReturnAddress` | Frame's stored instruction pointer | +| `HijackFrame` | `HijackArgsPtr` | Pointer to the Frame's stored HijackArgs | +| `HijackArgs` (amd64) | `CalleeSavedRegisters` | CalleeSavedRegisters data structure | +| `HijackArgs` (amd64 Windows) | `Rsp` | Saved stack pointer | +| `HijackArgs` (arm64) | For each register `r` saved in HijackArgs, `r` | Register names associated with stored register values | +| `CalleeSavedRegisters` | For each callee saved register `r`, `r` | Register names associated with stored register values | Global variables used: | Global Name | Type | Purpose | | --- | --- | --- | -| For each FrameType ``, `##Identifier` | FrameIdentifier enum value | Identifier used to determine concrete type of Frames | +| For each FrameType ``, `##Identifier` | `FrameIdentifier` enum value | Identifier used to determine concrete type of Frames | Contracts used: | Contract Name | @@ -215,6 +229,71 @@ private static void bar() } ``` +### Capital 'F' Frame Handling + +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. + +Most of the handlers are implemented in `BaseFrameHandler`. Platform specific components are implemented/overridden in `FrameHandler`. + +#### InlinedCallFrame + +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. + +#### SoftwareExceptionFrame + +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. + +#### TransitionFrame + +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). + +When updating the context from a TransitionFrame, the IP, SP, and all ABI specified callee-saved registers are copied over. + +The following Frame types also use this mechanism: +* FramedMethodFrame +* CLRToCOMMethodFrame +* PInvokeCallIFrame +* PrestubMethodFrame +* StubDispatchFrame +* CallCountingHelperFrame +* ExternalMethodFrame +* DynamicHelperFrame + +#### FuncEvalFrame + +FuncEvalFrames hold a pointer to a `DebuggerEval`. The DebuggerEval holds a full context which is completely copied over to the working context when updating. + +#### ResumableFrame + +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. + +RedirectedThreadFrames also use this mechanism. + +#### FaultingExceptionFrame + +FaultingExceptionFrames have two different implementations. One for Windows x86 and another for all other builds (with funclets). + +Given the cDAC does not yet support Windows x86, this version is not supported. + +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. + +#### HijackFrame + +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. + +* x64 - On x64, HijackArgs contains a CalleeSavedRegister struct. The saved registers values contained in the struct are copied over to the working context. + * Windows - On Windows, HijackArgs also contains the SP value directly which is copied over to the working context. + * 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. +* 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. + +#### TailCallFrame + +TailCallFrames are only used on Windows x86 which is not yet supported in the cDAC and therefore not implemented. + +#### HelperMethodFrame + +HelperMethodFrames are on the way to being removed. They are not currently supported in the cDAC. + ### APIs The majority of the contract's complexity is the stack walking algorithm (detailed above) implemented as part of `CreateStackWalk`. diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.cpp b/src/coreclr/debug/runtimeinfo/datadescriptor.cpp index 72a1ee18304578..c4d0aa3d42b645 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.cpp +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.cpp @@ -13,6 +13,8 @@ #include "methodtable.h" #include "threads.h" +#include "../debug/ee/debugger.h" + #ifdef HAVE_GCCOVER #include "gccover.h" #endif // HAVE_GCCOVER diff --git a/src/coreclr/debug/runtimeinfo/datadescriptor.h b/src/coreclr/debug/runtimeinfo/datadescriptor.h index be0889c9f48385..4f7b6bef62d982 100644 --- a/src/coreclr/debug/runtimeinfo/datadescriptor.h +++ b/src/coreclr/debug/runtimeinfo/datadescriptor.h @@ -646,6 +646,108 @@ CDAC_TYPE_FIELD(SoftwareExceptionFrame, /*pointer*/, ReturnAddress, cdac_data::TransitionBlockPtr) +CDAC_TYPE_END(FramedMethodFrame) + +CDAC_TYPE_BEGIN(TransitionBlock) +CDAC_TYPE_SIZE(sizeof(TransitionBlock)) +CDAC_TYPE_FIELD(TransitionBlock, /*pointer*/, ReturnAddress, offsetof(TransitionBlock, m_ReturnAddress)) +CDAC_TYPE_FIELD(TransitionBlock, /*CalleeSavedRegisters*/, CalleeSavedRegisters, offsetof(TransitionBlock, m_calleeSavedRegisters)) +CDAC_TYPE_END(TransitionBlock) + +#ifdef DEBUGGING_SUPPORTED +CDAC_TYPE_BEGIN(FuncEvalFrame) +CDAC_TYPE_SIZE(sizeof(FuncEvalFrame)) +CDAC_TYPE_FIELD(FuncEvalFrame, /*pointer*/, DebuggerEvalPtr, cdac_data::DebuggerEvalPtr) +CDAC_TYPE_END(FuncEvalFrame) + +CDAC_TYPE_BEGIN(DebuggerEval) +CDAC_TYPE_SIZE(sizeof(DebuggerEval)) +CDAC_TYPE_FIELD(DebuggerEval, /*T_CONTEXT*/, TargetContext, offsetof(DebuggerEval, m_context)) +CDAC_TYPE_FIELD(DebuggerEval, /*bool*/, EvalDuringException, offsetof(DebuggerEval, m_evalDuringException)) +CDAC_TYPE_END(DebuggerEval) +#endif // DEBUGGING_SUPPORTED + +#ifdef FEATURE_HIJACK +CDAC_TYPE_BEGIN(ResumableFrame) +CDAC_TYPE_SIZE(sizeof(ResumableFrame)) +CDAC_TYPE_FIELD(ResumableFrame, /*pointer*/, TargetContextPtr, cdac_data::TargetContextPtr) +CDAC_TYPE_END(ResumableFrame) + +CDAC_TYPE_BEGIN(HijackFrame) +CDAC_TYPE_SIZE(sizeof(HijackFrame)) +CDAC_TYPE_FIELD(HijackFrame, /*pointer*/, ReturnAddress, cdac_data::ReturnAddress) +CDAC_TYPE_FIELD(HijackFrame, /*pointer*/, HijackArgsPtr, cdac_data::HijackArgsPtr) +CDAC_TYPE_END(HijackFrame) + +// HijackArgs struct is different on each platform +CDAC_TYPE_BEGIN(HijackArgs) +CDAC_TYPE_SIZE(sizeof(HijackArgs)) +#if defined(TARGET_AMD64) + +CDAC_TYPE_FIELD(HijackArgs, /*CalleeSavedRegisters*/, CalleeSavedRegisters, offsetof(HijackArgs, Regs)) +#ifdef TARGET_WINDOWS +CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, Rsp, offsetof(HijackArgs, Rsp)) +#endif // TARGET_WINDOWS + +#elif defined(TARGET_ARM64) + +CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, X0, offsetof(HijackArgs, X0)) +CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, X1, offsetof(HijackArgs, X1)) +CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, X19, offsetof(HijackArgs, X19)) +CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, X20, offsetof(HijackArgs, X20)) +CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, X21, offsetof(HijackArgs, X21)) +CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, X22, offsetof(HijackArgs, X22)) +CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, X23, offsetof(HijackArgs, X23)) +CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, X24, offsetof(HijackArgs, X24)) +CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, X25, offsetof(HijackArgs, X25)) +CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, X26, offsetof(HijackArgs, X26)) +CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, X27, offsetof(HijackArgs, X27)) +CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, X28, offsetof(HijackArgs, X28)) +CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, Fp, offsetof(HijackArgs, X29)) +CDAC_TYPE_FIELD(HijackArgs, /*pointer*/, Lr, offsetof(HijackArgs, Lr)) + +#endif // Platform switch +CDAC_TYPE_END(HijackArgs) +#endif // FEATURE_HIJACK + +CDAC_TYPE_BEGIN(FaultingExceptionFrame) +CDAC_TYPE_SIZE(sizeof(FaultingExceptionFrame)) +#ifdef FEATURE_EH_FUNCLETS +CDAC_TYPE_FIELD(FaultingExceptionFrame, /*T_CONTEXT*/, TargetContext, cdac_data::TargetContext) +#endif // FEATURE_EH_FUNCLETS +CDAC_TYPE_END(FaultingExceptionFrame) + +// CalleeSavedRegisters struct is different on each platform +CDAC_TYPE_BEGIN(CalleeSavedRegisters) +CDAC_TYPE_SIZE(sizeof(CalleeSavedRegisters)) +#if defined(TARGET_AMD64) + +#define CALLEE_SAVED_REGISTER(regname) \ + CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, regname, offsetof(CalleeSavedRegisters, regname)) +ENUM_CALLEE_SAVED_REGISTERS() +#undef CALLEE_SAVED_REGISTER + +#elif defined(TARGET_ARM64) + +CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, X19, offsetof(CalleeSavedRegisters, x19)) +CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, X20, offsetof(CalleeSavedRegisters, x20)) +CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, X21, offsetof(CalleeSavedRegisters, x21)) +CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, X22, offsetof(CalleeSavedRegisters, x22)) +CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, X23, offsetof(CalleeSavedRegisters, x23)) +CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, X24, offsetof(CalleeSavedRegisters, x24)) +CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, X25, offsetof(CalleeSavedRegisters, x25)) +CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, X26, offsetof(CalleeSavedRegisters, x26)) +CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, X27, offsetof(CalleeSavedRegisters, x27)) +CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, X28, offsetof(CalleeSavedRegisters, x28)) +CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, Fp, offsetof(CalleeSavedRegisters, x29)) +CDAC_TYPE_FIELD(CalleeSavedRegisters, /*nuint*/, Lr, offsetof(CalleeSavedRegisters, x30)) + +#endif // Platform switch +CDAC_TYPE_END(CalleeSavedRegisters) + CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() diff --git a/src/coreclr/vm/frames.h b/src/coreclr/vm/frames.h index be519af3b9b239..90e203cb1e0234 100644 --- a/src/coreclr/vm/frames.h +++ b/src/coreclr/vm/frames.h @@ -742,6 +742,14 @@ class ResumableFrame : public Frame protected: PTR_CONTEXT m_Regs; + + friend struct cdac_data; +}; + +template<> +struct cdac_data +{ + static constexpr size_t TargetContextPtr = offsetof(ResumableFrame, m_Regs); }; @@ -1011,6 +1019,16 @@ class FaultingExceptionFrame : public Frame } void UpdateRegDisplay_Impl(const PREGDISPLAY, bool updateFloats = false); + + friend struct ::cdac_data; +}; + +template<> +struct cdac_data +{ +#ifdef FEATURE_EH_FUNCLETS + static constexpr size_t TargetContext = offsetof(FaultingExceptionFrame, m_ctx); +#endif // FEATURE_EH_FUNCLETS }; #ifdef FEATURE_EH_FUNCLETS @@ -1152,6 +1170,14 @@ class FuncEvalFrame : public Frame return m_showFrame; } + + friend struct cdac_data; +}; + +template<> +struct cdac_data +{ + static constexpr size_t DebuggerEvalPtr = offsetof(FuncEvalFrame, m_pDebuggerEval); }; typedef DPTR(FuncEvalFrame) PTR_FuncEvalFrame; @@ -1669,8 +1695,15 @@ class FramedMethodFrame : public TransitionFrame #endif return dac_cast(p); } + + friend struct cdac_data; }; +template<> +struct cdac_data +{ + static constexpr size_t TransitionBlockPtr = offsetof(FramedMethodFrame, m_pTransitionBlock); +}; #ifdef FEATURE_COMINTEROP @@ -1963,6 +1996,15 @@ class HijackFrame : public Frame TADDR m_ReturnAddress; PTR_Thread m_Thread; DPTR(HijackArgs) m_Args; + + friend struct ::cdac_data; +}; + +template<> +struct cdac_data +{ + static constexpr size_t ReturnAddress = offsetof(HijackFrame, m_ReturnAddress); + static constexpr size_t HijackArgsPtr = offsetof(HijackFrame, m_Args); }; #endif // FEATURE_HIJACK diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs index b1cb058483ca01..2941b7ca5cf7fe 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -89,8 +89,17 @@ public enum DataType MethodImpl, NativeCodeSlot, GCCoverageInfo, + TransitionBlock, + DebuggerEval, + CalleeSavedRegisters, + HijackArgs, Frame, InlinedCallFrame, SoftwareExceptionFrame, + FramedMethodFrame, + FuncEvalFrame, + ResumableFrame, + FaultingExceptionFrame, + HijackFrame, } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/AMD64Context.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/AMD64Context.cs index a56fc168d9b73b..23c54aa48b2d72 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/AMD64Context.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/AMD64Context.cs @@ -25,6 +25,8 @@ public enum ContextFlagsValues : uint CONTEXT_ALL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS, CONTEXT_XSTATE = CONTEXT_AMD | 0x40, CONTEXT_KERNEL_CET = CONTEXT_AMD | 0x80, + + CONTEXT_AREA_MASK = 0xFFFF, } public readonly uint Size => 0x4d0; diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64Context.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64Context.cs index 6be29853aa6f47..5ab9258755d00d 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64Context.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64Context.cs @@ -21,8 +21,11 @@ public enum ContextFlagsValues : uint CONTEXT_FLOATING_POINT = CONTEXT_ARM64 | 0x4, CONTEXT_DEBUG_REGISTERS = CONTEXT_ARM64 | 0x8, CONTEXT_X18 = CONTEXT_ARM64 | 0x10, + CONTEXT_XSTATE = CONTEXT_ARM64 | 0x20, CONTEXT_FULL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT, CONTEXT_ALL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS | CONTEXT_X18, + + CONTEXT_AREA_MASK = 0xFFFF, } public readonly uint Size => 0x390; diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ContextHolder.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ContextHolder.cs index b5d194d0824fc2..3e9091533e6da7 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ContextHolder.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ContextHolder.cs @@ -2,11 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; using System.Runtime.InteropServices; namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; -public class CotnextHolder : IPlatformAgnosticContext where T : unmanaged, IPlatformContext +public class ContextHolder<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T> : IPlatformAgnosticContext, IEquatable> + where T : unmanaged, IPlatformContext { public T Context; @@ -42,7 +45,58 @@ public unsafe byte[] GetBytes() Span byteSpan = MemoryMarshal.AsBytes(structSpan); return byteSpan.ToArray(); } - public IPlatformAgnosticContext Clone() => new CotnextHolder() { Context = Context }; + public IPlatformAgnosticContext Clone() => new ContextHolder() { Context = Context }; public void Clear() => Context = default; public void Unwind(Target target) => Context.Unwind(target); + + public bool TrySetRegister(Target target, string fieldName, TargetNUInt value) + { + if (typeof(T).GetField(fieldName) is not FieldInfo field) return false; + switch (field.FieldType) + { + case Type t when t == typeof(ulong) && target.PointerSize == sizeof(ulong): + field.SetValueDirect(__makeref(Context), value.Value); + return true; + case Type t when t == typeof(uint) && target.PointerSize == sizeof(uint): + field.SetValueDirect(__makeref(Context), (uint)value.Value); + return true; + default: + return false; + } + } + + public bool TryReadRegister(Target target, string fieldName, out TargetNUInt value) + { + value = default; + if (typeof(T).GetField(fieldName) is not FieldInfo field) return false; + object? fieldValue = field.GetValue(Context); + if (fieldValue is ulong ul && target.PointerSize == sizeof(ulong)) + { + value = new(ul); + return true; + } + if (fieldValue is uint ui && target.PointerSize == sizeof(uint)) + { + value = new(ui); + return true; + } + return false; + } + + public bool Equals(ContextHolder? other) + { + if (GetType() != other?.GetType()) + { + return false; + } + + return Context.Equals(other.Context); + } + + public override bool Equals(object? obj) + { + return Equals(obj as ContextHolder); + } + + public override int GetHashCode() => Context.GetHashCode(); } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs index 7cb0db6ea79a54..8a82caf4cb2abd 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs @@ -19,6 +19,8 @@ public interface IPlatformAgnosticContext public abstract void FillFromBuffer(Span buffer); public abstract byte[] GetBytes(); public abstract IPlatformAgnosticContext Clone(); + public abstract bool TrySetRegister(Target target, string fieldName, TargetNUInt value); + public abstract bool TryReadRegister(Target target, string fieldName, out TargetNUInt value); public abstract void Unwind(Target target); public static IPlatformAgnosticContext GetContextForPlatform(Target target) @@ -28,10 +30,10 @@ public static IPlatformAgnosticContext GetContextForPlatform(Target target) case Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_AMD64: case Target.CorDebugPlatform.CORDB_PLATFORM_POSIX_AMD64: case Target.CorDebugPlatform.CORDB_PLATFORM_MAC_AMD64: - return new CotnextHolder(); + return new ContextHolder(); case Target.CorDebugPlatform.CORDB_PLATFORM_POSIX_ARM64: case Target.CorDebugPlatform.CORDB_PLATFORM_WINDOWS_ARM64: - return new CotnextHolder(); + return new ContextHolder(); default: throw new InvalidOperationException($"Unsupported platform {target.Platform}"); } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IContext.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformContext.cs similarity index 99% rename from src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IContext.cs rename to src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformContext.cs index 7a8c3bf1ebacb3..cee2c4fe9fce42 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IContext.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformContext.cs @@ -11,5 +11,6 @@ public interface IPlatformContext public TargetPointer StackPointer { get; set; } public TargetPointer InstructionPointer { get; set; } public TargetPointer FramePointer { get; set; } + public abstract void Unwind(Target target); } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/Unwinder.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/Unwinder.cs index b977d1d263a731..925d8d75d8ba0c 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/Unwinder.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/Unwinder.cs @@ -104,12 +104,17 @@ private void Dispose(bool disposing) [UnmanagedCallersOnly] private static unsafe int ReadFromTarget(ulong address, void* pBuffer, int bufferSize, void* context) { - if (GCHandle.FromIntPtr((IntPtr)context).Target is not CallbackContext callbackContext) + CallbackContext callbackContext = (CallbackContext)GCHandle.FromIntPtr((IntPtr)context).Target!; + Span span = new Span(pBuffer, bufferSize); + try { + callbackContext.Target.ReadBuffer(address, span); + } + catch (InvalidOperationException) + { + // if the read fails, the unwinder behavior changes. Return failing HR instead of throwing return -1; } - Span span = new Span(pBuffer, bufferSize); - callbackContext.Target.ReadBuffer(address, span); return 0; } @@ -119,10 +124,7 @@ private static unsafe int ReadFromTarget(ulong address, void* pBuffer, int buffe [UnmanagedCallersOnly] private static unsafe int GetAllocatedBuffer(int bufferSize, void** ppBuffer, void* context) { - if (GCHandle.FromIntPtr((IntPtr)context).Target is not CallbackContext callbackContext) - { - return -1; - } + CallbackContext callbackContext = (CallbackContext)GCHandle.FromIntPtr((IntPtr)context).Target!; *ppBuffer = NativeMemory.Alloc((nuint)bufferSize); callbackContext.AllocatedRegions.Add((IntPtr)(*ppBuffer)); return 0; @@ -137,10 +139,7 @@ private static unsafe void GetStackWalkInfo(ulong controlPC, void* pUnwindInfoBa if ((nuint)pUnwindInfoBase != 0) *(nuint*)pUnwindInfoBase = 0; if ((nuint)pFuncEntry != 0) *(nuint*)pFuncEntry = 0; - if (GCHandle.FromIntPtr((IntPtr)context).Target is not CallbackContext callbackContext) - { - return; - } + CallbackContext callbackContext = (CallbackContext)GCHandle.FromIntPtr((IntPtr)context).Target!; IExecutionManager eman = callbackContext.Target.Contracts.ExecutionManager; try diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/AMD64FrameHandler.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/AMD64FrameHandler.cs new file mode 100644 index 00000000000000..d5649e2a861994 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/AMD64FrameHandler.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using Microsoft.Diagnostics.DataContractReader.Data; +using static Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.AMD64Context; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; + +internal class AMD64FrameHandler(Target target, ContextHolder contextHolder) : BaseFrameHandler(target, contextHolder), IPlatformFrameHandler +{ + private readonly ContextHolder _holder = contextHolder; + + void IPlatformFrameHandler.HandleFaultingExceptionFrame(FaultingExceptionFrame frame) + { + if (frame.TargetContext is not TargetPointer targetContext) + { + throw new InvalidOperationException("Unexpected null context pointer on FaultingExceptionFrame"); + } + _holder.ReadFromAddress(_target, targetContext); + + // Clear the CONTEXT_XSTATE, since the AMD64Context contains just plain CONTEXT structure + // that does not support holding any extended state. + _holder.Context.ContextFlags &= ~(uint)(ContextFlagsValues.CONTEXT_XSTATE & ContextFlagsValues.CONTEXT_AREA_MASK); + } + + void IPlatformFrameHandler.HandleHijackFrame(HijackFrame frame) + { + HijackArgsAMD64 args = _target.ProcessedData.GetOrAdd(frame.HijackArgsPtr); + + _holder.InstructionPointer = frame.ReturnAddress; + if (args.Rsp is TargetPointer rsp) + { + // Windows case, Rsp is passed directly + _holder.StackPointer = rsp; + } + else + { + // Non-Windows case, the stack pointer is the address immediately following HijacksArgs + uint hijackArgsSize = _target.GetTypeInfo(DataType.HijackArgs).Size ?? throw new InvalidOperationException("HijackArgs size is not set"); + _holder.StackPointer = frame.HijackArgsPtr + hijackArgsSize; + } + + Data.CalleeSavedRegisters calleeSavedRegisters = _target.ProcessedData.GetOrAdd(args.CalleeSavedRegisters); + UpdateFromRegisterDict(calleeSavedRegisters.Registers); + } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARM64FrameHandler.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARM64FrameHandler.cs new file mode 100644 index 00000000000000..59d8b055ba5078 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/ARM64FrameHandler.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using Microsoft.Diagnostics.DataContractReader.Data; +using static Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers.ARM64Context; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; + +internal class ARM64FrameHandler(Target target, ContextHolder contextHolder) : BaseFrameHandler(target, contextHolder), IPlatformFrameHandler +{ + private readonly ContextHolder _holder = contextHolder; + + void IPlatformFrameHandler.HandleFaultingExceptionFrame(FaultingExceptionFrame frame) + { + if (frame.TargetContext is not TargetPointer targetContext) + { + throw new InvalidOperationException("Unexpected null context pointer on FaultingExceptionFrame"); + } + _holder.ReadFromAddress(_target, targetContext); + + // Clear the CONTEXT_XSTATE, since the ARM64Context contains just plain CONTEXT structure + // that does not support holding any extended state. + _holder.Context.ContextFlags &= ~(uint)(ContextFlagsValues.CONTEXT_XSTATE & ContextFlagsValues.CONTEXT_AREA_MASK); + } + + void IPlatformFrameHandler.HandleHijackFrame(HijackFrame frame) + { + HijackArgsARM64 args = _target.ProcessedData.GetOrAdd(frame.HijackArgsPtr); + + _holder.InstructionPointer = frame.ReturnAddress; + + // The stack pointer is the address immediately following HijacksArgs + uint hijackArgsSize = _target.GetTypeInfo(DataType.HijackArgs).Size ?? throw new InvalidOperationException("HijackArgs size is not set"); + Debug.Assert(hijackArgsSize % 8 == 0, "HijackArgs contains register values and should be a multiple of 8"); + // The stack must be multiple of 16. So if hijackArgsSize is not multiple of 16 then there must be padding of 8 bytes + hijackArgsSize += hijackArgsSize % 16; + _holder.StackPointer = frame.HijackArgsPtr + hijackArgsSize; + + UpdateFromRegisterDict(args.Registers); + } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/BaseFrameHandler.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/BaseFrameHandler.cs new file mode 100644 index 00000000000000..31e912bdc5fa09 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/BaseFrameHandler.cs @@ -0,0 +1,100 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using Microsoft.Diagnostics.DataContractReader.Data; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; + +/// +/// Abstract base class providing platform agnostic functionality for handling frames. +/// +internal abstract class BaseFrameHandler(Target target, IPlatformAgnosticContext context) +{ + protected readonly Target _target = target; + private readonly IPlatformAgnosticContext _context = context; + + public virtual void HandleInlinedCallFrame(InlinedCallFrame inlinedCallFrame) + { + // if the caller return address is 0, then the call is not active + // and we should not update the context + if (inlinedCallFrame.CallerReturnAddress == 0) + { + return; + } + + _context.InstructionPointer = inlinedCallFrame.CallerReturnAddress; + _context.StackPointer = inlinedCallFrame.CallSiteSP; + _context.FramePointer = inlinedCallFrame.CalleeSavedFP; + } + + public virtual void HandleSoftwareExceptionFrame(SoftwareExceptionFrame softwareExceptionFrame) + { + IPlatformAgnosticContext otherContextHolder = IPlatformAgnosticContext.GetContextForPlatform(_target); + otherContextHolder.ReadFromAddress(_target, softwareExceptionFrame.TargetContext); + + UpdateCalleeSavedRegistersFromOtherContext(otherContextHolder); + + _context.InstructionPointer = otherContextHolder.InstructionPointer; + _context.StackPointer = otherContextHolder.StackPointer; + } + + public virtual void HandleTransitionFrame(FramedMethodFrame framedMethodFrame) + { + Data.TransitionBlock transitionBlock = _target.ProcessedData.GetOrAdd(framedMethodFrame.TransitionBlockPtr); + if (_target.GetTypeInfo(DataType.TransitionBlock).Size is not uint transitionBlockSize) + { + throw new InvalidOperationException("TransitionBlock size is not set"); + } + + _context.InstructionPointer = transitionBlock.ReturnAddress; + _context.StackPointer = framedMethodFrame.TransitionBlockPtr + transitionBlockSize; + + Data.CalleeSavedRegisters calleeSavedRegisters = _target.ProcessedData.GetOrAdd(transitionBlock.CalleeSavedRegisters); + UpdateFromRegisterDict(calleeSavedRegisters.Registers); + } + + public virtual void HandleFuncEvalFrame(FuncEvalFrame funcEvalFrame) + { + Data.DebuggerEval debuggerEval = _target.ProcessedData.GetOrAdd(funcEvalFrame.DebuggerEvalPtr); + + // No context to update if we're doing a func eval from within exception processing. + if (debuggerEval.EvalDuringException) + { + return; + } + _context.ReadFromAddress(_target, debuggerEval.TargetContext); + } + + public virtual void HandleResumableFrame(ResumableFrame frame) + { + _context.ReadFromAddress(_target, frame.TargetContextPtr); + } + + protected void UpdateFromRegisterDict(IReadOnlyDictionary registers) + { + foreach ((string name, TargetNUInt value) in registers) + { + if (!_context.TrySetRegister(_target, name, value)) + { + throw new InvalidOperationException($"Unexpected register {name} found when trying to update context"); + } + } + } + + protected void UpdateCalleeSavedRegistersFromOtherContext(IPlatformAgnosticContext otherContext) + { + foreach (string name in _target.GetTypeInfo(DataType.CalleeSavedRegisters).Fields.Keys) + { + if (!otherContext.TryReadRegister(_target, name, out TargetNUInt value)) + { + throw new InvalidOperationException($"Unexpected register {name} in callee saved registers"); + } + if (!_context.TrySetRegister(_target, name, value)) + { + throw new InvalidOperationException($"Unexpected register {name} in callee saved registers"); + } + } + } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameIterator.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameIterator.cs new file mode 100644 index 00000000000000..b48a9e9b5433a9 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/FrameIterator.cs @@ -0,0 +1,164 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; + +internal sealed class FrameIterator +{ + private enum FrameType + { + Unknown, + + InlinedCallFrame, + SoftwareExceptionFrame, + + /* TransitionFrame Types */ + FramedMethodFrame, + CLRToCOMMethodFrame, + PInvokeCalliFrame, + PrestubMethodFrame, + StubDispatchFrame, + CallCountingHelperFrame, + ExternalMethodFrame, + DynamicHelperFrame, + + FuncEvalFrame, + + /* ResumableFrame Types */ + ResumableFrame, + RedirectedThreadFrame, + + FaultingExceptionFrame, + + HijackFrame, + } + + private readonly Target target; + private readonly TargetPointer terminator; + private TargetPointer currentFramePointer; + + internal Data.Frame CurrentFrame => target.ProcessedData.GetOrAdd(currentFramePointer); + + public TargetPointer CurrentFrameAddress => currentFramePointer; + + public FrameIterator(Target target, ThreadData threadData) + { + this.target = target; + terminator = new TargetPointer(target.PointerSize == 8 ? ulong.MaxValue : uint.MaxValue); + currentFramePointer = threadData.Frame; + } + + public bool IsValid() + { + return currentFramePointer != terminator; + } + + public bool Next() + { + if (currentFramePointer == terminator) + return false; + + currentFramePointer = CurrentFrame.Next; + return currentFramePointer != terminator; + } + + public void UpdateContextFromFrame(IPlatformAgnosticContext context) + { + switch (GetFrameType(CurrentFrame)) + { + case FrameType.InlinedCallFrame: + Data.InlinedCallFrame inlinedCallFrame = target.ProcessedData.GetOrAdd(CurrentFrame.Address); + GetFrameHandler(context).HandleInlinedCallFrame(inlinedCallFrame); + return; + + case FrameType.SoftwareExceptionFrame: + Data.SoftwareExceptionFrame softwareExceptionFrame = target.ProcessedData.GetOrAdd(CurrentFrame.Address); + GetFrameHandler(context).HandleSoftwareExceptionFrame(softwareExceptionFrame); + return; + + // TransitionFrame type frames + case FrameType.FramedMethodFrame: + case FrameType.CLRToCOMMethodFrame: + case FrameType.PInvokeCalliFrame: + case FrameType.PrestubMethodFrame: + case FrameType.StubDispatchFrame: + case FrameType.CallCountingHelperFrame: + case FrameType.ExternalMethodFrame: + case FrameType.DynamicHelperFrame: + // FrameMethodFrame is the base type for all transition Frames + Data.FramedMethodFrame framedMethodFrame = target.ProcessedData.GetOrAdd(CurrentFrame.Address); + GetFrameHandler(context).HandleTransitionFrame(framedMethodFrame); + return; + + case FrameType.FuncEvalFrame: + Data.FuncEvalFrame funcEvalFrame = target.ProcessedData.GetOrAdd(CurrentFrame.Address); + GetFrameHandler(context).HandleFuncEvalFrame(funcEvalFrame); + return; + + // ResumableFrame type frames + case FrameType.ResumableFrame: + case FrameType.RedirectedThreadFrame: + Data.ResumableFrame resumableFrame = target.ProcessedData.GetOrAdd(CurrentFrame.Address); + GetFrameHandler(context).HandleResumableFrame(resumableFrame); + return; + + case FrameType.FaultingExceptionFrame: + Data.FaultingExceptionFrame faultingExceptionFrame = target.ProcessedData.GetOrAdd(CurrentFrame.Address); + GetFrameHandler(context).HandleFaultingExceptionFrame(faultingExceptionFrame); + return; + + case FrameType.HijackFrame: + Data.HijackFrame hijackFrame = target.ProcessedData.GetOrAdd(CurrentFrame.Address); + GetFrameHandler(context).HandleHijackFrame(hijackFrame); + return; + default: + // Unknown Frame type. This could either be a Frame that we don't know how to handle, + // or a Frame that does not update the context. + return; + } + } + + public bool IsInlineCallFrameWithActiveCall() + { + if (GetFrameType(CurrentFrame) != FrameType.InlinedCallFrame) + { + return false; + } + Data.InlinedCallFrame inlinedCallFrame = target.ProcessedData.GetOrAdd(currentFramePointer); + return inlinedCallFrame.CallerReturnAddress != 0; + } + + private FrameType GetFrameType(Data.Frame frame) + { + foreach (FrameType frameType in Enum.GetValues()) + { + TargetPointer typeVptr; + try + { + // not all Frames are in all builds, so we need to catch the exception + typeVptr = target.ReadGlobalPointer(frameType.ToString() + "Identifier"); + if (frame.Identifier == typeVptr) + { + return frameType; + } + } + catch (InvalidOperationException) + { + } + } + + return FrameType.Unknown; + } + + private IPlatformFrameHandler GetFrameHandler(IPlatformAgnosticContext context) + { + return context switch + { + ContextHolder contextHolder => new AMD64FrameHandler(target, contextHolder), + ContextHolder contextHolder => new ARM64FrameHandler(target, contextHolder), + _ => throw new InvalidOperationException("Unsupported context type"), + }; + } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/IPlatformFrameHandler.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/IPlatformFrameHandler.cs new file mode 100644 index 00000000000000..28e40d281763d2 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/IPlatformFrameHandler.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; + +/// +/// Interface for handling platform-specific frames. +/// +internal interface IPlatformFrameHandler +{ + void HandleInlinedCallFrame(Data.InlinedCallFrame frame); + void HandleSoftwareExceptionFrame(Data.SoftwareExceptionFrame frame); + void HandleTransitionFrame(Data.FramedMethodFrame frame); + void HandleFuncEvalFrame(Data.FuncEvalFrame frame); + void HandleResumableFrame(Data.ResumableFrame frame); + void HandleFaultingExceptionFrame(Data.FaultingExceptionFrame frame); + void HandleHijackFrame(Data.HijackFrame frame); +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameIterator.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameIterator.cs deleted file mode 100644 index 3ef20699a973c5..00000000000000 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameIterator.cs +++ /dev/null @@ -1,97 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; - -namespace Microsoft.Diagnostics.DataContractReader.Contracts; - -internal sealed class FrameIterator -{ - private static readonly DataType[] SupportedFrameTypes = - [ - DataType.InlinedCallFrame, - DataType.SoftwareExceptionFrame - ]; - - private readonly Target target; - private readonly TargetPointer terminator; - private TargetPointer currentFramePointer; - - internal Data.Frame CurrentFrame => target.ProcessedData.GetOrAdd(currentFramePointer); - - public TargetPointer CurrentFrameAddress => currentFramePointer; - - public FrameIterator(Target target, ThreadData threadData) - { - this.target = target; - terminator = new TargetPointer(target.PointerSize == 8 ? ulong.MaxValue : uint.MaxValue); - currentFramePointer = threadData.Frame; - } - - public bool IsValid() - { - return currentFramePointer != terminator; - } - - public bool Next() - { - if (currentFramePointer == terminator) - return false; - - currentFramePointer = CurrentFrame.Next; - return true; - } - - public bool TryUpdateContext(IPlatformAgnosticContext context) - { - switch (GetFrameType(CurrentFrame)) - { - case DataType.InlinedCallFrame: - Data.InlinedCallFrame inlinedCallFrame = target.ProcessedData.GetOrAdd(CurrentFrame.Address); - context.Clear(); - context.InstructionPointer = inlinedCallFrame.CallerReturnAddress; - context.StackPointer = inlinedCallFrame.CallSiteSP; - context.FramePointer = inlinedCallFrame.CalleeSavedFP; - return true; - case DataType.SoftwareExceptionFrame: - Data.SoftwareExceptionFrame softwareExceptionFrame = target.ProcessedData.GetOrAdd(CurrentFrame.Address); - context.ReadFromAddress(target, softwareExceptionFrame.TargetContext); - return true; - default: - return false; - } - } - - public bool IsInlineCallFrameWithActiveCall() - { - if (GetFrameType(CurrentFrame) != DataType.InlinedCallFrame) - { - return false; - } - Data.InlinedCallFrame inlinedCallFrame = target.ProcessedData.GetOrAdd(currentFramePointer); - return inlinedCallFrame.CallerReturnAddress != 0; - } - - private DataType GetFrameType(Data.Frame frame) - { - foreach (DataType frameType in SupportedFrameTypes) - { - TargetPointer typeVptr; - try - { - // not all Frames are in all builds, so we need to catch the exception - typeVptr = target.ReadGlobalPointer(frameType.ToString() + "Identifier"); - if (frame.VPtr == typeVptr) - { - return frameType; - } - } - catch (InvalidOperationException) - { - } - } - - return DataType.Unknown; - } -} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs index 24f0c9c19e5908..b5939c9a6b78c9 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs @@ -82,7 +82,7 @@ private bool Next(StackWalkData handle) handle.FrameIter.Next(); break; case StackWalkState.SW_FRAME: - handle.FrameIter.TryUpdateContext(handle.Context); + handle.FrameIter.UpdateContextFromFrame(handle.Context); if (!handle.FrameIter.IsInlineCallFrameWithActiveCall()) { handle.FrameIter.Next(); diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/CalleeSavedRegisters.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/CalleeSavedRegisters.cs new file mode 100644 index 00000000000000..d826f505c00c38 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/CalleeSavedRegisters.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal class CalleeSavedRegisters : IData +{ + static CalleeSavedRegisters IData.Create(Target target, TargetPointer address) + => new CalleeSavedRegisters(target, address); + + public CalleeSavedRegisters(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.CalleeSavedRegisters); + Dictionary registers = new Dictionary(type.Fields.Count); + foreach ((string name, Target.FieldInfo field) in type.Fields) + { + TargetNUInt value = target.ReadNUInt(address + (ulong)field.Offset); + registers.Add(name, value); + } + Registers = registers; + } + + public IReadOnlyDictionary Registers { get; } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/DebuggerEval.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/DebuggerEval.cs new file mode 100644 index 00000000000000..31352b4cc6880a --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/DebuggerEval.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal class DebuggerEval : IData +{ + static DebuggerEval IData.Create(Target target, TargetPointer address) + => new DebuggerEval(target, address); + + public DebuggerEval(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.DebuggerEval); + TargetContext = address + (ulong)type.Fields[nameof(TargetContext)].Offset; + EvalDuringException = target.Read(address + (ulong)type.Fields[nameof(EvalDuringException)].Offset) != 0; + Address = address; + } + + public TargetPointer Address { get; } + public TargetPointer TargetContext { get; } + public bool EvalDuringException { get; } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/FaultingExceptionFrame.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/FaultingExceptionFrame.cs new file mode 100644 index 00000000000000..a205b59f78bb41 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/FaultingExceptionFrame.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal class FaultingExceptionFrame : IData +{ + static FaultingExceptionFrame IData.Create(Target target, TargetPointer address) + => new FaultingExceptionFrame(target, address); + + public FaultingExceptionFrame(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.FaultingExceptionFrame); + + // TargetContextPtr only exists when FEATURE_EH_FUNCLETS is defined + if (type.Fields.ContainsKey(nameof(TargetContext))) + { + TargetContext = address + (ulong)type.Fields[nameof(TargetContext)].Offset; + } + Address = address; + } + + public TargetPointer Address { get; } + public TargetPointer? TargetContext { get; } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/Frame.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/Frame.cs index b6939f9d70b3bc..cee27f37f723ad 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/Frame.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/Frame.cs @@ -1,9 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; - namespace Microsoft.Diagnostics.DataContractReader.Data; internal sealed class Frame : IData @@ -16,10 +13,10 @@ public Frame(Target target, TargetPointer address) Address = address; Target.TypeInfo type = target.GetTypeInfo(DataType.Frame); Next = target.ReadPointer(address + (ulong)type.Fields[nameof(Next)].Offset); - VPtr = target.ReadPointer(address); + Identifier = target.ReadPointer(address); } public TargetPointer Address { get; init; } - public TargetPointer VPtr { get; init; } + public TargetPointer Identifier { get; init; } public TargetPointer Next { get; init; } } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/FramedMethodFrame.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/FramedMethodFrame.cs new file mode 100644 index 00000000000000..66d7ec86274bcb --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/FramedMethodFrame.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal class FramedMethodFrame : IData +{ + static FramedMethodFrame IData.Create(Target target, TargetPointer address) + => new FramedMethodFrame(target, address); + + public FramedMethodFrame(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.FramedMethodFrame); + TransitionBlockPtr = target.ReadPointer(address + (ulong)type.Fields[nameof(TransitionBlockPtr)].Offset); + Address = address; + } + + public TargetPointer Address { get; } + public TargetPointer TransitionBlockPtr { get; } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/FuncEvalFrame.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/FuncEvalFrame.cs new file mode 100644 index 00000000000000..65379d77d35f49 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/FuncEvalFrame.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +/// +/// Only exists if DEBUGGING_SUPPORTED defined in the target runtime. +/// +internal class FuncEvalFrame : IData +{ + static FuncEvalFrame IData.Create(Target target, TargetPointer address) + => new FuncEvalFrame(target, address); + + public FuncEvalFrame(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.FuncEvalFrame); + DebuggerEvalPtr = target.ReadPointer(address + (ulong)type.Fields[nameof(DebuggerEvalPtr)].Offset); + Address = address; + } + + public TargetPointer Address { get; } + public TargetPointer DebuggerEvalPtr { get; } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HijackArgsAMD64.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HijackArgsAMD64.cs new file mode 100644 index 00000000000000..e6428549a154bd --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HijackArgsAMD64.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal class HijackArgsAMD64 : IData +{ + static HijackArgsAMD64 IData.Create(Target target, TargetPointer address) + => new HijackArgsAMD64(target, address); + + public HijackArgsAMD64(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.HijackArgs); + CalleeSavedRegisters = address + (ulong)type.Fields[nameof(CalleeSavedRegisters)].Offset; + + // On Windows, Rsp is present + if (type.Fields.ContainsKey(nameof(Rsp))) + { + Rsp = target.ReadPointer(address + (ulong)type.Fields[nameof(Rsp)].Offset); + } + } + + public TargetPointer CalleeSavedRegisters { get; } + public TargetPointer? Rsp { get; } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HijackArgsARM64.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HijackArgsARM64.cs new file mode 100644 index 00000000000000..73168fd286c7c7 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HijackArgsARM64.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal class HijackArgsARM64 : IData +{ + static HijackArgsARM64 IData.Create(Target target, TargetPointer address) + => new HijackArgsARM64(target, address); + + public HijackArgsARM64(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.HijackArgs); + + Dictionary registers = new Dictionary(type.Fields.Count); + foreach ((string name, Target.FieldInfo field) in type.Fields) + { + TargetNUInt value = target.ReadNUInt(address + (ulong)field.Offset); + registers.Add(name, value); + } + Registers = registers; + } + + public IReadOnlyDictionary Registers { get; } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HijackFrame.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HijackFrame.cs new file mode 100644 index 00000000000000..db5061ed271bb0 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/HijackFrame.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal class HijackFrame : IData +{ + static HijackFrame IData.Create(Target target, TargetPointer address) + => new HijackFrame(target, address); + + public HijackFrame(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.HijackFrame); + ReturnAddress = target.ReadPointer(address + (ulong)type.Fields[nameof(ReturnAddress)].Offset); + HijackArgsPtr = target.ReadPointer(address + (ulong)type.Fields[nameof(HijackArgsPtr)].Offset); + Address = address; + } + + public TargetPointer Address { get; } + public TargetPointer ReturnAddress { get; } + public TargetPointer HijackArgsPtr { get; } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/InlinedCallFrame.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/InlinedCallFrame.cs index 78af563e607985..4cdd72e1ab8a09 100644 --- a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/InlinedCallFrame.cs +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/InlinedCallFrame.cs @@ -17,7 +17,7 @@ public InlinedCallFrame(Target target, TargetPointer address) Address = address; } - public TargetPointer Address { get;} + public TargetPointer Address { get; } public TargetPointer CallSiteSP { get; } public TargetPointer CallerReturnAddress { get; } public TargetPointer CalleeSavedFP { get; } diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/ResumableFrame.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/ResumableFrame.cs new file mode 100644 index 00000000000000..3faa26701b2102 --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/ResumableFrame.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal class ResumableFrame : IData +{ + static ResumableFrame IData.Create(Target target, TargetPointer address) + => new ResumableFrame(target, address); + + public ResumableFrame(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.ResumableFrame); + TargetContextPtr = target.ReadPointer(address + (ulong)type.Fields[nameof(TargetContextPtr)].Offset); + Address = address; + } + + public TargetPointer Address { get; } + public TargetPointer TargetContextPtr { get; } +} diff --git a/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/TransitionBlock.cs b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/TransitionBlock.cs new file mode 100644 index 00000000000000..b6a0d9c666b67e --- /dev/null +++ b/src/native/managed/cdacreader/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/TransitionBlock.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal class TransitionBlock : IData +{ + static TransitionBlock IData.Create(Target target, TargetPointer address) + => new TransitionBlock(target, address); + + public TransitionBlock(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.TransitionBlock); + ReturnAddress = target.ReadPointer(address + (ulong)type.Fields[nameof(ReturnAddress)].Offset); + CalleeSavedRegisters = address + (ulong)type.Fields[nameof(CalleeSavedRegisters)].Offset; + } + + public TargetPointer ReturnAddress { get; } + public TargetPointer CalleeSavedRegisters { get; } +} diff --git a/src/native/managed/cdacreader/src/Legacy/ClrDataStackWalk.cs b/src/native/managed/cdacreader/src/Legacy/ClrDataStackWalk.cs index c351796c33c8c3..615e7123029a82 100644 --- a/src/native/managed/cdacreader/src/Legacy/ClrDataStackWalk.cs +++ b/src/native/managed/cdacreader/src/Legacy/ClrDataStackWalk.cs @@ -65,9 +65,7 @@ int IXCLRDataStackWalk.GetContext(uint contextFlags, uint contextBufSize, uint* contextStruct.FillFromBuffer(contextBuf); localContextStruct.FillFromBuffer(localContextBuf); - Debug.Assert(contextStruct.InstructionPointer == localContextStruct.InstructionPointer, $"cDAC IP: {contextStruct.InstructionPointer:x}, DAC IP: {localContextStruct.InstructionPointer:x}"); - Debug.Assert(contextStruct.StackPointer == localContextStruct.StackPointer, $"cDAC SP: {contextStruct.StackPointer:x}, DAC SP: {localContextStruct.StackPointer:x}"); - Debug.Assert(contextStruct.FramePointer == localContextStruct.FramePointer, $"cDAC FP: {contextStruct.FramePointer:x}, DAC FP: {localContextStruct.FramePointer:x}"); + Debug.Assert(contextStruct.Equals(localContextStruct)); } #endif